1
1
package fourslash
2
2
3
3
import (
4
+ "encoding/json"
4
5
"strings"
5
6
"testing"
6
7
"unicode/utf8"
@@ -19,18 +20,18 @@ import (
19
20
// [|text in range|]
20
21
//
21
22
// is a range with `text in range` "selected".
22
- type markerRange struct {
23
- core.TextRange
24
- filename string
25
- position int
26
- data string
23
+ type RangeMarker struct {
24
+ * Marker
25
+ Range core.TextRange
26
+ LSRange lsproto.Range
27
27
}
28
28
29
29
type Marker struct {
30
- Filename string
30
+ FileName string
31
31
Position int
32
32
LSPosition lsproto.Position
33
33
Name string
34
+ Data map [string ]interface {}
34
35
}
35
36
36
37
type TestData struct {
@@ -39,12 +40,13 @@ type TestData struct {
39
40
Markers []* Marker
40
41
Symlinks map [string ]string
41
42
GlobalOptions map [string ]string
42
- Ranges []* markerRange
43
+ Ranges []* RangeMarker
43
44
}
44
45
45
46
type testFileWithMarkers struct {
46
47
file * TestFileInfo
47
48
markers []* Marker
49
+ ranges []* RangeMarker
48
50
}
49
51
50
52
func ParseTestData (t * testing.T , contents string , fileName string ) TestData {
@@ -53,6 +55,7 @@ func ParseTestData(t *testing.T, contents string, fileName string) TestData {
53
55
54
56
markerPositions := make (map [string ]* Marker )
55
57
var markers []* Marker
58
+ var ranges []* RangeMarker
56
59
filesWithMarker , symlinks , _ , globalOptions := testrunner .ParseTestFilesAndSymlinks (
57
60
contents ,
58
61
fileName ,
@@ -62,14 +65,17 @@ func ParseTestData(t *testing.T, contents string, fileName string) TestData {
62
65
hasTSConfig := false
63
66
for _ , file := range filesWithMarker {
64
67
files = append (files , file .file )
65
- hasTSConfig = hasTSConfig || isConfigFile (file .file .Filename )
68
+ hasTSConfig = hasTSConfig || isConfigFile (file .file .fileName )
69
+
66
70
markers = append (markers , file .markers ... )
71
+ ranges = append (ranges , file .ranges ... )
67
72
for _ , marker := range file .markers {
68
73
if _ , ok := markerPositions [marker .Name ]; ok {
69
74
t .Fatalf ("Duplicate marker name: %s" , marker .Name )
70
75
}
71
76
markerPositions [marker .Name ] = marker
72
77
}
78
+
73
79
}
74
80
75
81
if hasTSConfig && len (globalOptions ) > 0 {
@@ -82,13 +88,13 @@ func ParseTestData(t *testing.T, contents string, fileName string) TestData {
82
88
Markers : markers ,
83
89
Symlinks : symlinks ,
84
90
GlobalOptions : globalOptions ,
85
- Ranges : nil ,
91
+ Ranges : ranges ,
86
92
}
87
93
}
88
94
89
- func isConfigFile (filename string ) bool {
90
- filename = strings .ToLower (filename )
91
- return strings .HasSuffix (filename , "tsconfig.json" ) || strings .HasSuffix (filename , "jsconfig.json" )
95
+ func isConfigFile (fileName string ) bool {
96
+ fileName = strings .ToLower (fileName )
97
+ return strings .HasSuffix (fileName , "tsconfig.json" ) || strings .HasSuffix (fileName , "jsconfig.json" )
92
98
}
93
99
94
100
type locationInformation struct {
@@ -98,16 +104,21 @@ type locationInformation struct {
98
104
sourceColumn int
99
105
}
100
106
107
+ type rangeLocationInformation struct {
108
+ locationInformation
109
+ marker * Marker
110
+ }
111
+
101
112
type TestFileInfo struct {
102
- Filename string
113
+ fileName string
103
114
// The contents of the file (with markers, etc stripped out)
104
115
Content string
105
116
emit bool
106
117
}
107
118
108
119
// FileName implements ls.Script.
109
120
func (t * TestFileInfo ) FileName () string {
110
- return t .Filename
121
+ return t .fileName
111
122
}
112
123
113
124
// Text implements ls.Script.
@@ -127,14 +138,19 @@ const (
127
138
stateInObjectMarker
128
139
)
129
140
130
- func parseFileContent (filename string , content string , fileOptions map [string ]string ) * testFileWithMarkers {
131
- filename = tspath .GetNormalizedAbsolutePath (filename , "/" )
141
+ func parseFileContent (fileName string , content string , fileOptions map [string ]string ) * testFileWithMarkers {
142
+ fileName = tspath .GetNormalizedAbsolutePath (fileName , "/" )
132
143
133
144
// The file content (minus metacharacters) so far
134
145
var output strings.Builder
135
146
136
147
var markers []* Marker
137
148
149
+ /// A stack of the open range markers that are still unclosed
150
+ openRanges := []rangeLocationInformation {}
151
+ /// A list of closed ranges we've collected so far
152
+ rangeMarkers := []* RangeMarker {}
153
+
138
154
// The total number of metacharacters removed from the file (so far)
139
155
difference := 0
140
156
@@ -164,9 +180,43 @@ func parseFileContent(filename string, content string, fileOptions map[string]st
164
180
currentCharacter , size = utf8 .DecodeRuneInString (content [i :])
165
181
switch state {
166
182
case stateNone :
167
- // !!! case '[', '|'
168
- // !!! case '|', ']'
169
- if previousCharacter == '/' && currentCharacter == '*' {
183
+ if previousCharacter == '[' && currentCharacter == '|' {
184
+ // found a range start
185
+ openRanges = append (openRanges , rangeLocationInformation {
186
+ locationInformation : locationInformation {
187
+ position : (i - 1 ) - difference ,
188
+ sourcePosition : i - 1 ,
189
+ sourceLine : line ,
190
+ sourceColumn : column ,
191
+ },
192
+ })
193
+ // copy all text up to marker position
194
+ flush (i - 1 )
195
+ lastNormalCharPosition = i + 1
196
+ difference += 2
197
+ } else if previousCharacter == '|' && currentCharacter == ']' {
198
+ // found a range end
199
+ if len (openRanges ) == 0 {
200
+ reportError (fileName , line , column , "Found range end with no matching start." )
201
+ }
202
+ rangeStart := openRanges [len (openRanges )- 1 ]
203
+ openRanges = openRanges [:len (openRanges )- 1 ]
204
+
205
+ closedRange := & RangeMarker {Range : core .NewTextRange (rangeStart .position , (i - 1 )- difference )}
206
+ if rangeStart .marker != nil {
207
+ closedRange .Marker = rangeStart .marker
208
+ } else {
209
+ // RangeMarker is not added to list of markers
210
+ closedRange .Marker = & Marker {FileName : fileName }
211
+ }
212
+
213
+ rangeMarkers = append (rangeMarkers , closedRange )
214
+
215
+ // copy all text up to range marker position
216
+ flush (i - 1 )
217
+ lastNormalCharPosition = i + 1
218
+ difference += 2
219
+ } else if previousCharacter == '/' && currentCharacter == '*' {
170
220
// found a possible marker start
171
221
state = stateInSlashStarMarker
172
222
openMarker = locationInformation {
@@ -175,20 +225,49 @@ func parseFileContent(filename string, content string, fileOptions map[string]st
175
225
sourceLine : line ,
176
226
sourceColumn : column - 1 ,
177
227
}
228
+ } else if previousCharacter == '{' && currentCharacter == '|' {
229
+ // found an object marker start
230
+ state = stateInObjectMarker
231
+ openMarker = locationInformation {
232
+ position : (i - 1 ) - difference ,
233
+ sourcePosition : i - 1 ,
234
+ sourceLine : line ,
235
+ sourceColumn : column ,
236
+ }
237
+ flush (i - 1 )
178
238
}
179
- // !!! case '{', '|'
180
239
case stateInObjectMarker :
181
- // !!! object marker
240
+ // Object markers are only ever terminated by |} and have no content restrictions
241
+ if previousCharacter == '|' && currentCharacter == '}' {
242
+ objectMarkerData := strings .TrimSpace (content [openMarker .sourcePosition + 2 : i - 1 ])
243
+ marker := getObjectMarker (fileName , openMarker , objectMarkerData )
244
+
245
+ if len (openRanges ) > 0 {
246
+ openRanges [len (openRanges )- 1 ].marker = marker
247
+ }
248
+ markers = append (markers , marker )
249
+
250
+ // Set the current start to point to the end of the current marker to ignore its text
251
+ lastNormalCharPosition = i + 1
252
+ difference += i + 1 - openMarker .sourcePosition
253
+
254
+ // Reset the state
255
+ openMarker = locationInformation {}
256
+ state = stateNone
257
+ }
182
258
case stateInSlashStarMarker :
183
259
if previousCharacter == '*' && currentCharacter == '/' {
184
260
// Record the marker
185
261
// start + 2 to ignore the */, -1 on the end to ignore the * (/ is next)
186
262
markerNameText := strings .TrimSpace (content [openMarker .sourcePosition + 2 : i - 1 ])
187
263
marker := & Marker {
188
- Filename : filename ,
264
+ FileName : fileName ,
189
265
Position : openMarker .position ,
190
266
Name : markerNameText ,
191
267
}
268
+ if len (openRanges ) > 0 {
269
+ openRanges [len (openRanges )- 1 ].marker = marker
270
+ }
192
271
markers = append (markers , marker )
193
272
194
273
// Set the current start to point to the end of the current marker to ignore its text
@@ -240,17 +319,60 @@ func parseFileContent(filename string, content string, fileOptions map[string]st
240
319
emit := fileOptions [emitThisFileOption ] == "true"
241
320
242
321
testFileInfo := & TestFileInfo {
243
- Filename : filename ,
322
+ fileName : fileName ,
244
323
Content : outputString ,
245
324
emit : emit ,
246
325
}
247
326
248
327
for _ , marker := range markers {
249
328
marker .LSPosition = converters .PositionToLineAndCharacter (testFileInfo , core .TextPos (marker .Position ))
250
329
}
330
+ for _ , rangeMarker := range rangeMarkers {
331
+ rangeMarker .LSRange = lsproto.Range {
332
+ Start : converters .PositionToLineAndCharacter (testFileInfo , core .TextPos (rangeMarker .Range .Pos ())),
333
+ End : converters .PositionToLineAndCharacter (testFileInfo , core .TextPos (rangeMarker .Range .End ())),
334
+ }
335
+ }
251
336
252
337
return & testFileWithMarkers {
253
338
file : testFileInfo ,
254
339
markers : markers ,
340
+ ranges : rangeMarkers ,
255
341
}
256
342
}
343
+
344
+ func getObjectMarker (fileName string , location locationInformation , text string ) * Marker {
345
+ // Attempt to parse the marker value as JSON
346
+ var v interface {}
347
+ e := json .Unmarshal ([]byte ("{ " + text + " }" ), & v )
348
+
349
+ if e != nil {
350
+ reportError (fileName , location .sourceLine , location .sourceColumn , "Unable to parse marker text " + text )
351
+ return nil
352
+ }
353
+ markerValue , ok := v .(map [string ]interface {})
354
+ if ! ok || len (markerValue ) == 0 {
355
+ reportError (fileName , location .sourceLine , location .sourceColumn , "Object markers can not be empty" )
356
+ return nil
357
+ }
358
+
359
+ marker := & Marker {
360
+ FileName : fileName ,
361
+ Position : location .position ,
362
+ Data : markerValue ,
363
+ }
364
+
365
+ // Object markers can be anonymous
366
+ if markerValue ["name" ] != nil {
367
+ if name , ok := markerValue ["name" ].(string ); ok && name != "" {
368
+ marker .Name = name
369
+ }
370
+ }
371
+
372
+ return marker
373
+ }
374
+
375
+ func reportError (fileName string , line int , col int , message string ) {
376
+ // !!! not implemented
377
+ // errorMessage := fileName + "(" + string(line) + "," + string(col) + "): " + message;
378
+ }
0 commit comments