learnlytics-go/templ/parser/v2/sourcemap_test.go
2025-03-20 12:35:13 +01:00

175 lines
5.1 KiB
Go

package parser
import (
"sort"
"testing"
"github.com/a-h/parse"
"github.com/google/go-cmp/cmp"
)
// Test data.
//
// | - | 0 1 2 3 4 5 6 7 8 9
// | - | - - - - - - - - -
// | 0 |
// | 1 | a b c d e f g h i
// | 2 | j k l m n o
// | 3 | p q r s t u v
// | 4 |
// | 5 | w x y
// | 6 | z
// | 7 | m u l t i
// | 8 | l i n e
// | 9 | m a t c h
// | 10 | 生 日 快 乐
func pos(index, line, col int) parse.Position {
return parse.Position{
Index: index,
Line: line,
Col: col,
}
}
func TestSourceMapPosition(t *testing.T) {
sm := NewSourceMap()
// Test that out of bounds requests don't return results.
t.Run("out of bounds", func(t *testing.T) {
actualTarget, ok := sm.TargetPositionFromSource(20, 10)
if ok {
t.Errorf("searching for a source position that's not in the map should not result in a target position, but got %v", actualTarget)
}
actualSource, ok := sm.SourcePositionFromTarget(20, 10)
if ok {
t.Errorf("searching for a target position that's not in the map should not result in a source position, but got %v", actualSource)
}
})
var tests = []struct {
name string
setup func(sm *SourceMap)
source Position
target Position
expectedOK bool
}{
{
name: "searching within the map returns a result",
setup: func(sm *SourceMap) {
sm.Add(NewExpression("abc", pos(0, 1, 1), pos(2, 1, 3)),
Range{From: NewPosition(0, 5, 1), To: NewPosition(0, 5, 3)})
},
source: NewPosition(0, 1, 1), // a
target: NewPosition(0, 5, 1),
},
{
name: "offsets within the match are handled",
setup: func(sm *SourceMap) {
sm.Add(NewExpression("abc", pos(0, 1, 1), pos(2, 1, 3)),
Range{From: NewPosition(0, 5, 1), To: NewPosition(0, 5, 3)})
},
source: NewPosition(1, 1, 2), // b
target: NewPosition(1, 5, 2),
},
{
name: "the match that starts closest to the source is returned",
setup: func(sm *SourceMap) {
sm.Add(NewExpression("rst", pos(4, 3, 3), pos(7, 3, 6)),
Range{From: NewPosition(0, 8, 6), To: NewPosition(0, 8, 9)})
// s is inside rst.
sm.Add(NewExpression("s", pos(-1, 3, 4), pos(-1, 3, 4)),
Range{From: NewPosition(-1, 8, 7), To: NewPosition(-1, 8, 7)})
},
source: NewPosition(-1, 3, 4), // s
target: NewPosition(-1, 8, 7),
},
{
name: "the start line within a multiline match is detected",
setup: func(sm *SourceMap) {
// Multi-line match.
sm.Add(NewExpression("multi\nline\nmatch", pos(0, 0, 0), pos(16, 2, 5)),
Range{From: NewPosition(1, 1, 1), To: NewPosition(17, 3, 5)})
},
source: NewPosition(0, 0, 0), // m (ulti)
target: NewPosition(1, 1, 1),
},
{
name: "the middle line within a multiline match is detected",
setup: func(sm *SourceMap) {
// Multi-line match.
sm.Add(NewExpression("multi\nline\nmatch", pos(0, 0, 0), pos(16, 2, 5)),
Range{From: NewPosition(1, 1, 1), To: NewPosition(17, 3, 5)})
},
source: NewPosition(7, 1, 1), // (l) i (ne)
target: NewPosition(8, 2, 1),
},
{
name: "the final line within a multiline match is detected",
setup: func(sm *SourceMap) {
// Multi-line match.
sm.Add(NewExpression("multi\nline\nmatch", pos(0, 0, 0), pos(16, 2, 5)),
Range{From: NewPosition(1, 1, 1), To: NewPosition(17, 3, 5)})
},
source: NewPosition(11, 2, 0), // m (atch)
target: NewPosition(12, 3, 0),
},
{
name: "unicode characters are indexed correctly (sheng)",
setup: func(sm *SourceMap) {
sm.Add(NewExpression("生日快乐", pos(0, 10, 0), pos(12, 10, 4)),
Range{From: NewPosition(1, 11, 1), To: NewPosition(13, 11, 5)})
},
source: NewPosition(0, 10, 0), // 生
target: NewPosition(1, 11, 1),
},
{
name: "unicode characters are indexed correctly (ri)",
setup: func(sm *SourceMap) {
sm.Add(NewExpression("生日快乐", pos(0, 10, 0), pos(12, 10, 4)),
Range{From: NewPosition(1, 11, 1), To: NewPosition(13, 11, 5)})
},
source: NewPosition(3, 10, 3), // 日
target: NewPosition(4, 11, 4),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
sm := NewSourceMap()
tt.setup(sm)
actualTarget, ok := sm.TargetPositionFromSource(uint32(tt.source.Line), uint32(tt.source.Col))
if !ok {
t.Errorf("TargetPositionFromSource: expected result from source %v, got no results", tt.source)
}
if diff := cmp.Diff(tt.target, actualTarget); diff != "" {
lines := keys(sm.SourceLinesToTarget)
sort.Slice(lines, func(i, j int) bool {
return lines[i] < lines[j]
})
for _, lineIndex := range lines {
cols := keys(sm.SourceLinesToTarget[lineIndex])
sort.Slice(cols, func(i, j int) bool {
return cols[i] < cols[j]
})
t.Error(lineIndex, cols)
}
t.Error("TargetPositionFromSource\n\n" + diff)
}
actualSource, ok := sm.SourcePositionFromTarget(actualTarget.Line, actualTarget.Col)
if !ok {
t.Fatalf("SourcePositionFromTarget: expected result, got no results")
}
if diff := cmp.Diff(tt.source, actualSource); diff != "" {
t.Error("SourcePositionFromTarget\n\n" + diff)
}
})
}
}
func keys[K comparable, V any](m map[K]V) (keys []K) {
for k := range m {
keys = append(keys, k)
}
return
}