175 lines
5.1 KiB
Go
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
|
|
}
|