turbo-editors/turbo-gopublic Fork 0
v1.0.1
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-go.git
git clone ssh://git@rickub.com/turbo-editors/turbo-go.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

scan_test.go · 249 lines · 6.9 KBGo Blame HistoryRaw
📦 Turbo Go 3d7798b k33g 13h ago1package golang
2
3import (
4 "strings"
5 "testing"
6
7 "rickub.com/turbo-editors/turbo-core/syntax"
8)
9
10// The tests below came from turbo-core, where the Go scanner used to live. They
11// drive it from the outside now — through syntax.Highlight, after Register —
12// which is how the editor reaches it too.
13
14func init() { Register() }
15
16// classAt returns the class covering a rune column on a line, and whether any
17// span covers it at all.
18func classAt(spans [][]syntax.Span, line, col int) (syntax.Class, bool) {
19 if line < 0 || line >= len(spans) {
20 return 0, false
21 }
22 for _, s := range spans[line] {
23 if col >= s.Start && col < s.End {
24 return s.Class, true
25 }
26 }
27 return 0, false
28}
29
30// classOfWord returns the class of the first occurrence of word in src.
31func classOfWord(t *testing.T, src, word string) syntax.Class {
32 t.Helper()
33
34 index := strings.Index(src, word)
35 if index < 0 {
36 t.Fatalf("%q does not appear in the source", word)
37 }
38 line := strings.Count(src[:index], "\n")
39 col := index - (strings.LastIndex(src[:index], "\n") + 1)
40
41 class, ok := classAt(syntax.Highlight(Language, src), line, col)
42 if !ok {
43 t.Fatalf("no span covers %q at line %d column %d", word, line, col)
44 }
45 return class
46}
47
48func TestHighlightReturnsOneEntryPerLine(t *testing.T) {
49 tests := []struct {
50 name string
51 src string
52 want int
53 }{
54 {"empty", "", 1},
55 {"one line without a terminator", "package main", 1},
56 {"one line with a terminator", "package main\n", 2},
57 {"three lines", "a\nb\nc", 3},
58 }
59
60 for _, tc := range tests {
61 t.Run(tc.name, func(t *testing.T) {
62 if got := len(syntax.Highlight(Language, tc.src)); got != tc.want {
63 t.Errorf("syntax.Highlight(Language, %q) returned %d lines, want %d", tc.src, got, tc.want)
64 }
65 })
66 }
67}
68
69func TestEachTokenClass(t *testing.T) {
70 const src = `package main
71
72// a comment
73import "fmt"
74
75type Point struct{ X, Y int }
76
77func main() {
78 var name string = "hello"
79 const c = 'x'
80 n := 42 + 3.5
81 ok := true
82 _ = len(name)
83 fmt.Println(name, n, ok, nil)
84}
85`
86
87 tests := []struct {
88 word string
89 want syntax.Class
90 }{
91 {"package", syntax.ClassKeyword},
92 {"func", syntax.ClassKeyword},
93 {"// a comment", syntax.ClassComment},
94 {`"fmt"`, syntax.ClassString},
95 {"Point", syntax.ClassType},
96 {"string", syntax.ClassType},
97 {"int", syntax.ClassType},
98 {"'x'", syntax.ClassChar},
99 {"42", syntax.ClassNumber},
100 {"3.5", syntax.ClassNumber},
101 {"true", syntax.ClassConstant},
102 {"nil", syntax.ClassConstant},
103 {"len", syntax.ClassBuiltin},
104 {"Println", syntax.ClassFunction},
105 {"name", syntax.ClassIdentifier},
106 {":=", syntax.ClassOperator},
107 {"+", syntax.ClassOperator},
108 }
109
110 for _, tc := range tests {
111 t.Run(tc.word, func(t *testing.T) {
112 if got := classOfWord(t, src, tc.word); got != tc.want {
113 t.Errorf("%q is coloured as %v, want %v", tc.word, got, tc.want)
114 }
115 })
116 }
117}
118
119func TestADeclaredFunctionNameIsAFunction(t *testing.T) {
120 if got := classOfWord(t, "func add(a, b int) int { return a + b }", "add"); got != syntax.ClassFunction {
121 t.Errorf("the declared name is %v, want function", got)
122 }
123}
124
125func TestAPackageNameIsAPlainIdentifier(t *testing.T) {
126 // "main" here is neither a call nor a declaration, so it must not be
127 // dressed up as a function just because it follows a keyword.
128 if got := classOfWord(t, "package main\n", "main"); got != syntax.ClassIdentifier {
129 t.Errorf("the package name is %v, want identifier", got)
130 }
131}
132
133func TestPunctuationIsSeparateFromOperators(t *testing.T) {
134 const src = "f(a, b)"
135
136 for _, col := range []int{1, 3, 6} { // '(' ',' ')'
137 if got, _ := classAt(syntax.Highlight(Language, src), 0, col); got != syntax.ClassPunctuation {
138 t.Errorf("column %d is %v, want punctuation", col, got)
139 }
140 }
141}
142
143func TestSpansNeverStraddleALineBreak(t *testing.T) {
144 const src = "/* a comment\nspanning three\nlines */\nx := 1"
145
146 spans := syntax.Highlight(Language, src)
147
148 for line := range 3 {
149 if got, ok := classAt(spans, line, 0); !ok || got != syntax.ClassComment {
150 t.Errorf("line %d starts with %v (covered=%v), want comment", line, got, ok)
151 }
152 }
153 for _, s := range spans[0] {
154 if s.End > len([]rune("/* a comment")) {
155 t.Errorf("a span on line 0 ends at column %d, past the end of the line", s.End)
156 }
157 }
158 if got, _ := classAt(spans, 3, 0); got != syntax.ClassIdentifier {
159 t.Errorf("the line after the comment is %v, want identifier", got)
160 }
161}
162
163func TestRawStringsSpanningLinesAreColoured(t *testing.T) {
164 src := "s := `line one\nline two`\n"
165
166 spans := syntax.Highlight(Language, src)
167
168 if got, _ := classAt(spans, 0, 5); got != syntax.ClassString {
169 t.Errorf("the opening of the raw string is %v, want string", got)
170 }
171 if got, _ := classAt(spans, 1, 0); got != syntax.ClassString {
172 t.Errorf("the continuation of the raw string is %v, want string", got)
173 }
174}
175
176func TestBrokenSourceIsStillColoured(t *testing.T) {
177 tests := []struct {
178 name string
179 src string
180 }{
181 {"unterminated string", `x := "hello`},
182 {"unterminated comment", "/* never closed"},
183 {"unterminated rune", "c := 'a"},
184 {"stray brace", "func main() { }}}"},
185 {"half-typed declaration", "func "},
186 {"nothing but an operator", "=="},
187 {"an illegal character", "x := #"},
188 }
189
190 for _, tc := range tests {
191 t.Run(tc.name, func(t *testing.T) {
192 spans := syntax.Highlight(Language, tc.src) // must not panic
193 if len(spans) == 0 {
194 t.Error("Highlight returned no lines at all")
195 }
196 })
197 }
198}
199
200func TestUnterminatedStringIsStillAString(t *testing.T) {
201 spans := syntax.Highlight(Language, `x := "hello`)
202
203 if got, _ := classAt(spans, 0, 6); got != syntax.ClassString {
204 t.Errorf("the text after the quote is %v, want string — colours must not flicker while typing", got)
205 }
206}
207
208func TestColumnsAreCountedInRunesNotBytes(t *testing.T) {
209 // The comment holds multi-byte characters, so a span measured in bytes
210 // would run past the end of the following line.
211 const src = "// héllo wörld\nfunc main() {}"
212
213 spans := syntax.Highlight(Language, src)
214
215 if got, _ := classAt(spans, 0, 13); got != syntax.ClassComment {
216 t.Errorf("column 13 of the comment is %v, want comment", got)
217 }
218 if _, ok := classAt(spans, 0, 14); ok {
219 t.Error("a span covers column 14, past the 14 runes of the comment")
220 }
221 if got, _ := classAt(spans, 1, 0); got != syntax.ClassKeyword {
222 t.Errorf("the line after the accented comment is %v, want keyword", got)
223 }
224}
225
226func TestIdentifiersInsideStringsAreNotColouredAsCode(t *testing.T) {
227 spans := syntax.Highlight(Language, `s := "func main"`)
228
229 if got, _ := classAt(spans, 0, 6); got != syntax.ClassString {
230 t.Errorf("a keyword inside a string is %v, want string", got)
231 }
232}
233
234func TestSpansOnALineAreOrderedAndDoNotOverlap(t *testing.T) {
235 const src = "func add(a, b int) int { return a + b }"
236
237 for _, line := range syntax.Highlight(Language, src) {
238 previousEnd := 0
239 for _, s := range line {
240 if s.Start < previousEnd {
241 t.Errorf("span %+v starts before the previous one ended at %d", s, previousEnd)
242 }
243 if s.End <= s.Start {
244 t.Errorf("span %+v is empty or reversed", s)
245 }
246 previousEnd = s.End
247 }
248 }
249}