v2 / vlib / v / tests / vls / autocomplete_module_test.v
445 lines · 419 sloc · 11.97 KB · b308c1f38f9962c572499def72282f0745b15244
Raw
1import os
2import term
3import v.util.diff
4import json
5
6const vroot = os.real_path(@VMODROOT)
7const tmp_dir = os.real_path(os.temp_dir())
8const text_file = os.join_path(vroot, 'vlib', 'v', 'tests', 'vls', 'sample_text.vv')
9// note: windows path separator will cause json decode fail
10const json_errors_text_file = os.to_slash(text_file)
11const mod1_text_file = os.join_path(vroot, 'vlib', 'v', 'tests', 'vls', 'sample_mod1', 'sample.v')
12
13const autocomplete_info_for_mod_sample_mod1 = '{"details": [
14{"kind":3,"label":"public_fn1","detail":"string","declaration":"fn public_fn1(val int) string","documentation":""},
15{"kind":22,"label":"PublicStruct1","detail":"","declaration":"","documentation":""},
16{"kind":7,"label":"PublicAlias1_1","detail":"","declaration":"","documentation":""},
17{"kind":7,"label":"PublicAlias1_2","detail":"","declaration":"","documentation":""},
18{"kind":13,"label":"PublicEnum1","detail":"","declaration":"","documentation":""},
19{"kind":8,"label":"PublicInterface1","detail":"","declaration":"","documentation":""},
20{"kind":21,"label":"public_const1","detail":"","declaration":"","documentation":""}
21]}'
22
23const autocomplete_info_for_mod_sample_mod2 = '{"details": [
24{"kind":3,"label":"public_fn2","detail":"string","declaration":"fn public_fn2(val int) string","documentation":""},
25{"kind":22,"label":"PublicStruct2","detail":"","declaration":"","documentation":""},
26{"kind":13,"label":"PublicEnum2","detail":"","declaration":"","documentation":""},
27{"kind":8,"label":"PublicInterface2","detail":"","declaration":"","documentation":""},
28{"kind":7,"label":"PublicAlias2","detail":"","declaration":"","documentation":""},
29{"kind":21,"label":"public_const2","detail":"","declaration":"","documentation":""}
30]}'
31
32const autocomplete_info_for_mod_struct = '{"details": [
33{"kind":5,"label":"a","detail":"int","declaration":"","documentation":""},
34{"kind":5,"label":"b","detail":"string","declaration":"","documentation":""},
35{"kind":2,"label":"add","detail":"void","declaration":"","documentation":""}
36]}'
37
38const hover_info_for_public_fn1 = '{"contents":{"kind":"markdown","value":"```v\\nfn public_fn1(val int) string\\n```"}}'
39
40const hover_info_for_public_struct1 = '{"contents":{"kind":"markdown","value":"```v\\nstruct PublicStruct1\\n```"}}'
41
42const fn_signature_info_for_all_before_last = '{
43"signatures":[{
44 "label":"all_before_last(sub string) string",
45 "parameters":[{
46 "label":"sub string"
47 }]
48}],
49"activeSignature":0,
50"activeParameter":0
51}
52'
53
54enum Method {
55 unknown @['unknown']
56 initialize @['initialize']
57 initialized @['initialized']
58 did_open @['textDocument/didOpen']
59 did_change @['textDocument/didChange']
60 definition @['textDocument/definition']
61 completion @['textDocument/completion']
62 signature_help @['textDocument/signatureHelp']
63 hover @['textDocument/hover']
64 set_trace @['$/setTrace']
65 cancel_request @['$/cancelRequest']
66 shutdown @['shutdown']
67 exit @['exit']
68}
69
70struct TestData {
71 method Method
72 cmd string
73 output string
74}
75
76const test_data = [
77 TestData{
78 method: .completion
79 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:19:3" ${os.quoted_path(text_file)}'
80 output: autocomplete_info_for_mod_sample_mod1
81 },
82 TestData{
83 method: .completion
84 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:20:13" ${os.quoted_path(text_file)}'
85 output: autocomplete_info_for_mod_sample_mod2
86 },
87 TestData{
88 method: .completion
89 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:22:3" ${os.quoted_path(text_file)}'
90 output: autocomplete_info_for_mod_struct
91 },
92 TestData{
93 method: .completion
94 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:23:3" ${os.quoted_path(text_file)}'
95 output: ''
96 },
97 TestData{
98 method: .completion
99 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:26:28" ${os.quoted_path(text_file)}'
100 output: autocomplete_info_for_mod_sample_mod1
101 },
102 TestData{
103 method: .signature_help
104 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:25:fn^26" ${os.quoted_path(text_file)}'
105 output: fn_signature_info_for_all_before_last
106 },
107 TestData{
108 method: .completion
109 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:27:9" ${os.quoted_path(text_file)}'
110 output: ''
111 },
112 TestData{
113 method: .completion
114 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:28:9" ${os.quoted_path(text_file)}'
115 output: ''
116 },
117 TestData{
118 method: .hover
119 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:30:hv^10" ${os.quoted_path(text_file)}'
120 output: hover_info_for_public_fn1
121 },
122 TestData{
123 method: .hover
124 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:31:hv^12" ${os.quoted_path(text_file)}'
125 output: hover_info_for_public_struct1
126 },
127 TestData{
128 method: .definition
129 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:30:gd^10" ${os.quoted_path(text_file)}'
130 output: '${mod1_text_file}:50:7'
131 },
132 TestData{
133 method: .definition
134 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:31:gd^12" ${os.quoted_path(text_file)}'
135 output: '${mod1_text_file}:8:11'
136 },
137 TestData{
138 method: .definition
139 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:32:gd^11" ${os.quoted_path(text_file)}'
140 output: '${mod1_text_file}:41:9'
141 },
142 TestData{
143 method: .definition
144 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:33:gd^15" ${os.quoted_path(text_file)}'
145 output: '${mod1_text_file}:44:9'
146 },
147 TestData{
148 method: .definition
149 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:34:gd^13" ${os.quoted_path(text_file)}'
150 output: '${mod1_text_file}:19:10'
151 },
152 TestData{
153 method: .definition
154 cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:39:gd^13" ${os.quoted_path(text_file)}'
155 output: '${mod1_text_file}:50:7'
156 },
157 TestData{
158 method: .did_change
159 cmd: 'v -w -vls-mode -check -json-errors ${os.quoted_path(text_file)}'
160 output: '[
161{
162"path":"${json_errors_text_file}",
163"message":"unexpected token `:=`, expecting `)`",
164"line_nr":26,
165"col":4,
166"len":0
167}
168,
169{
170"path":"${json_errors_text_file}",
171"message":"unexpected token `:=`, expecting `)`",
172"line_nr":29,
173"col":4,
174"len":0
175}
176,
177{
178"path":"${json_errors_text_file}",
179"message":"undefined ident: ``",
180"line_nr":19,
181"col":3,
182"len":0
183}
184,
185{
186"path":"${json_errors_text_file}",
187"message":"undefined ident: ``",
188"line_nr":20,
189"col":13,
190"len":0
191}
192,
193{
194"path":"${json_errors_text_file}",
195"message":"type `main.MyS` has no field named `s`.\\n2 possibilities: `a`, `b`.",
196"line_nr":23,
197"col":2,
198"len":0
199}
200,
201{
202"path":"${json_errors_text_file}",
203"message":"undefined ident: `j`",
204"line_nr":26,
205"col":2,
206"len":0
207}
208,
209{
210"path":"${json_errors_text_file}",
211"message":"`j` (no value) used as value in argument 1 to `string.all_before_last`",
212"line_nr":26,
213"col":2,
214"len":0
215}
216,
217{
218"path":"${json_errors_text_file}",
219"message":"undefined ident: ``",
220"line_nr":26,
221"col":28,
222"len":0
223}
224,
225{
226"path":"${json_errors_text_file}",
227"message":"undefined ident: `strings`",
228"line_nr":27,
229"col":2,
230"len":0
231}
232,
233{
234"path":"${json_errors_text_file}",
235"message":"`strings` does not return a value",
236"line_nr":28,
237"col":2,
238"len":0
239}
240,
241{
242"path":"${json_errors_text_file}",
243"message":"`strings.builtin` does not return a value",
244"line_nr":29,
245"col":2,
246"len":0
247}
248,
249{
250"path":"${json_errors_text_file}",
251"message":"expected 1 argument, but got 2",
252"line_nr":27,
253"col":2,
254"len":0
255}
256,
257{
258"path":"${json_errors_text_file}",
259"message":"unknown type `main.NotExistStruct`",
260"line_nr":38,
261"col":11,
262"len":0
263}
264]
265'
266 },
267]
268
269// copy from `vls`
270struct JsonError {
271 path string
272 message string
273 line_nr int
274 col int
275 len int
276}
277
278struct Detail {
279 kind int // The type of item (e.g., Method, Function, Field)
280 label string // The name of the completion item
281 detail string // Additional info like the function signature or return type
282 documentation string // The documentation for the item
283 insert_text ?string @[json: 'insertText']
284 insert_text_format ?int @[json: 'insertTextFormat'] // 1 for PlainText, 2 for Snippet
285}
286
287struct JsonVarAC {
288 details []Detail
289}
290
291struct SignatureHelp {
292 signatures []SignatureInformation
293 active_signature int @[json: 'activeSignature']
294 active_parameter int @[json: 'activeParameter']
295}
296
297struct SignatureInformation {
298 label string
299 parameters []ParameterInformation
300}
301
302struct ParameterInformation {
303 label string
304}
305
306fn test_main() {
307 mut total_errors := 0
308
309 for t in test_data {
310 res := os.execute(t.cmd)
311 if res.exit_code < 0 {
312 println('fail execute ${t.cmd}')
313 panic(res.output)
314 }
315 res_output := $if windows {
316 res.output.replace('\r\n', '\n')
317 } $else {
318 res.output
319 }
320 if t.output.trim_space() != res_output.trim_space() {
321 println('${term.red('FAIL')} ${t.cmd}')
322 if diff_ := diff.compare_text(t.output, res_output) {
323 println(term.header('difference:', '-'))
324 println(diff_)
325 } else {
326 println(term.header('expected:', '-'))
327 println(t.output)
328 println(term.header('found:', '-'))
329 println(res_output)
330 }
331 println(term.h_divider('-'))
332 total_errors++
333 } else {
334 println('${term.green('OK ')} ${t.cmd}')
335 }
336
337 // Try to decode the response message and verify
338 if t.output.trim_space().len > 0 {
339 dump(t.output)
340 match t.method {
341 .definition {
342 check_valid_goto_definition(t.output)!
343 }
344 .completion {
345 check_valid_auto_completion(t.output)!
346 }
347 .did_change {
348 check_valid_json_errors(t.output)!
349 }
350 .signature_help {
351 check_valid_fn_signature(t.output)!
352 }
353 .hover {
354 check_valid_hover(t.output)!
355 }
356 else {}
357 }
358 }
359 }
360 assert total_errors == 0
361}
362
363fn check_valid_goto_definition(message string) ! {
364 // `/home/path/aaa.v:19:10`
365 fields := message.split(':')
366 if fields.len >= 3 {
367 path := fields[..fields.len - 2].join(':')
368 line_nr := fields[fields.len - 2].int()
369 col := fields[fields.len - 1].int()
370 if line_nr <= 0 {
371 return error('goto_definition: line_nr should > 0: ${line_nr}')
372 }
373 if col <= 0 {
374 return error('goto_definition: col should > 0: ${col}')
375 }
376 if path.len == 0 {
377 return error('goto_definition: file.len should > 0: ${path}')
378 }
379 } else {
380 return error('goto_definition: goto_definition format error')
381 }
382}
383
384fn check_valid_auto_completion(message string) ! {
385 // {"kind":5,"label":"a","detail":"int","documentation":""},
386 result := json.decode(JsonVarAC, message) or { return error('completion: fail to json decode') }
387 for detail in result.details {
388 if detail.kind <= 0 || detail.kind > 25 {
389 return error('completion: kind should in 1-25 : ${detail.kind}')
390 }
391 }
392}
393
394fn check_valid_json_errors(message string) ! {
395 results := json.decode([]JsonError, message) or {
396 return error('json_errors: fail to json decode')
397 }
398 for result in results {
399 if result.path.len == 0 {
400 return error('json_errors: path.len should > 0')
401 }
402 if result.message.len == 0 {
403 return error('json_errors: message.len should > 0')
404 }
405 if result.line_nr <= 0 {
406 return error('json_errors: line_nr should > 0')
407 }
408 if result.col <= 0 {
409 return error('json_errors: col should > 0')
410 }
411 }
412}
413
414struct HoverContents {
415 kind string
416 value string
417}
418
419struct HoverResult {
420 contents HoverContents
421}
422
423fn check_valid_hover(message string) ! {
424 result := json.decode(HoverResult, message) or {
425 return error('hover: fail to json decode: ${err}')
426 }
427 if result.contents.kind != 'markdown' {
428 return error('hover: contents.kind should be "markdown": ${result.contents.kind}')
429 }
430 if result.contents.value.len == 0 {
431 return error('hover: contents.value should not be empty')
432 }
433}
434
435fn check_valid_fn_signature(message string) ! {
436 result := json.decode(SignatureHelp, message) or {
437 return error('fn_signature: fail to json decode')
438 }
439 if result.signatures.len != 1 {
440 return error('fn_signature: signatures.len != 1')
441 }
442 if result.signatures[0].label.len == 0 {
443 return error('fn_signature: label.len == 0')
444 }
445}
446