github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/cue/token/position_test.go (about) 1 // Copyright 2018 The CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package token 16 17 import ( 18 "fmt" 19 "testing" 20 ) 21 22 func checkPos(t *testing.T, msg string, got, want Position) { 23 if got.Filename != want.Filename { 24 t.Errorf("%s: got filename = %q; want %q", msg, got.Filename, want.Filename) 25 } 26 if got.Offset != want.Offset { 27 t.Errorf("%s: got offset = %d; want %d", msg, got.Offset, want.Offset) 28 } 29 if got.Line != want.Line { 30 t.Errorf("%s: got line = %d; want %d", msg, got.Line, want.Line) 31 } 32 if got.Column != want.Column { 33 t.Errorf("%s: got column = %d; want %d", msg, got.Column, want.Column) 34 } 35 } 36 37 func TestNoPos(t *testing.T) { 38 if NoPos.IsValid() { 39 t.Errorf("NoPos should not be valid") 40 } 41 checkPos(t, "nil NoPos", NoPos.Position(), Position{}) 42 } 43 44 var tests = []struct { 45 filename string 46 source []byte // may be nil 47 size int 48 lines []int 49 }{ 50 {"a", []byte{}, 0, []int{}}, 51 {"b", []byte("01234"), 5, []int{0}}, 52 {"c", []byte("\n\n\n\n\n\n\n\n\n"), 9, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}}, 53 {"d", nil, 100, []int{0, 5, 10, 20, 30, 70, 71, 72, 80, 85, 90, 99}}, 54 {"e", nil, 777, []int{0, 80, 100, 120, 130, 180, 267, 455, 500, 567, 620}}, 55 {"f", []byte("package p\n\nimport \"fmt\""), 23, []int{0, 10, 11}}, 56 {"g", []byte("package p\n\nimport \"fmt\"\n"), 24, []int{0, 10, 11}}, 57 {"h", []byte("package p\n\nimport \"fmt\"\n "), 25, []int{0, 10, 11, 24}}, 58 } 59 60 func linecol(lines []int, offs int) (int, int) { 61 prevLineOffs := 0 62 for line, lineOffs := range lines { 63 if offs < lineOffs { 64 return line, offs - prevLineOffs + 1 65 } 66 prevLineOffs = lineOffs 67 } 68 return len(lines), offs - prevLineOffs + 1 69 } 70 71 func verifyPositions(t *testing.T, f *File, lines []int) { 72 for offs := 0; offs < f.Size(); offs++ { 73 p := f.Pos(offs, 0) 74 offs2 := f.Offset(p) 75 if offs2 != offs { 76 t.Errorf("%s, Offset: got offset %d; want %d", f.Name(), offs2, offs) 77 } 78 line, col := linecol(lines, offs) 79 msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p.offset) 80 checkPos(t, msg, f.Pos(offs, 0).Position(), Position{f.Name(), offs, line, col}) 81 checkPos(t, msg, p.Position(), Position{f.Name(), offs, line, col}) 82 } 83 } 84 85 func makeTestSource(size int, lines []int) []byte { 86 src := make([]byte, size) 87 for _, offs := range lines { 88 if offs > 0 { 89 src[offs-1] = '\n' 90 } 91 } 92 return src 93 } 94 95 func TestPositions(t *testing.T) { 96 const delta = 7 // a non-zero base offset increment 97 for _, test := range tests { 98 // verify consistency of test case 99 if test.source != nil && len(test.source) != test.size { 100 t.Errorf("%s: inconsistent test case: got file size %d; want %d", test.filename, len(test.source), test.size) 101 } 102 103 // add file and verify name and size 104 f := NewFile(test.filename, 1+delta, test.size) 105 if f.Name() != test.filename { 106 t.Errorf("got filename %q; want %q", f.Name(), test.filename) 107 } 108 if f.Size() != test.size { 109 t.Errorf("%s: got file size %d; want %d", f.Name(), f.Size(), test.size) 110 } 111 if f.Pos(0, 0).file != f { 112 t.Errorf("%s: f.Pos(0, 0) was not found in f", f.Name()) 113 } 114 115 // add lines individually and verify all positions 116 for i, offset := range test.lines { 117 f.AddLine(offset) 118 if f.LineCount() != i+1 { 119 t.Errorf("%s, AddLine: got line count %d; want %d", f.Name(), f.LineCount(), i+1) 120 } 121 // adding the same offset again should be ignored 122 f.AddLine(offset) 123 if f.LineCount() != i+1 { 124 t.Errorf("%s, AddLine: got unchanged line count %d; want %d", f.Name(), f.LineCount(), i+1) 125 } 126 verifyPositions(t, f, test.lines[0:i+1]) 127 } 128 129 // add lines with SetLines and verify all positions 130 if ok := f.SetLines(test.lines); !ok { 131 t.Errorf("%s: SetLines failed", f.Name()) 132 } 133 if f.LineCount() != len(test.lines) { 134 t.Errorf("%s, SetLines: got line count %d; want %d", f.Name(), f.LineCount(), len(test.lines)) 135 } 136 verifyPositions(t, f, test.lines) 137 138 // add lines with SetLinesForContent and verify all positions 139 src := test.source 140 if src == nil { 141 // no test source available - create one from scratch 142 src = makeTestSource(test.size, test.lines) 143 } 144 f.SetLinesForContent(src) 145 if f.LineCount() != len(test.lines) { 146 t.Errorf("%s, SetLinesForContent: got line count %d; want %d", f.Name(), f.LineCount(), len(test.lines)) 147 } 148 verifyPositions(t, f, test.lines) 149 } 150 } 151 152 func TestLineInfo(t *testing.T) { 153 f := NewFile("foo", 1, 500) 154 lines := []int{0, 42, 77, 100, 210, 220, 277, 300, 333, 401} 155 // add lines individually and provide alternative line information 156 for _, offs := range lines { 157 f.AddLine(offs) 158 f.AddLineInfo(offs, "bar", 42) 159 } 160 // verify positions for all offsets 161 for offs := 0; offs <= f.Size(); offs++ { 162 p := f.Pos(offs, 0) 163 _, col := linecol(lines, offs) 164 msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p.offset) 165 checkPos(t, msg, f.Position(f.Pos(offs, 0)), Position{"bar", offs, 42, col}) 166 checkPos(t, msg, p.Position(), Position{"bar", offs, 42, col}) 167 } 168 } 169 170 func TestPositionFor(t *testing.T) { 171 src := []byte(` 172 foo 173 b 174 ar 175 //line :100 176 foobar 177 //line bar:3 178 done 179 `) 180 181 const filename = "foo" 182 f := NewFile(filename, 1, len(src)) 183 f.SetLinesForContent(src) 184 185 // verify position info 186 for i, offs := range f.lines { 187 got1 := f.PositionFor(f.Pos(int(offs), 0), false) 188 got2 := f.PositionFor(f.Pos(int(offs), 0), true) 189 got3 := f.Position(f.Pos(int(offs), 0)) 190 want := Position{filename, int(offs), i + 1, 1} 191 checkPos(t, "1. PositionFor unadjusted", got1, want) 192 checkPos(t, "1. PositionFor adjusted", got2, want) 193 checkPos(t, "1. Position", got3, want) 194 } 195 196 // manually add //line info on lines l1, l2 197 const l1, l2 = 5, 7 198 f.AddLineInfo(int(f.lines[l1-1]), "", 100) 199 f.AddLineInfo(int(f.lines[l2-1]), "bar", 3) 200 201 // unadjusted position info must remain unchanged 202 for i, offs := range f.lines { 203 got1 := f.PositionFor(f.Pos(int(offs), 0), false) 204 want := Position{filename, int(offs), i + 1, 1} 205 checkPos(t, "2. PositionFor unadjusted", got1, want) 206 } 207 208 // adjusted position info should have changed 209 for i, offs := range f.lines { 210 got2 := f.PositionFor(f.Pos(int(offs), 0), true) 211 got3 := f.Position(f.Pos(int(offs), 0)) 212 want := Position{filename, int(offs), i + 1, 1} 213 // manually compute wanted filename and line 214 line := want.Line 215 if i+1 >= l1 { 216 want.Filename = "" 217 want.Line = line - l1 + 100 218 } 219 if i+1 >= l2 { 220 want.Filename = "bar" 221 want.Line = line - l2 + 3 222 } 223 checkPos(t, "3. PositionFor adjusted", got2, want) 224 checkPos(t, "3. Position", got3, want) 225 } 226 }