Fork of daniellemaywood.uk/gleam — Wasm codegen work
12 kB
499 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2024 The Gleam contributors
3
4use super::*;
5use lsp_types::{
6 ActiveParameter, ParameterInformation, ParameterInformationLabel, SignatureHelp,
7 SignatureHelpParams, SignatureInformation, WorkDoneProgressParams,
8};
9
10fn signature_help(tester: TestProject<'_>, position: Position) -> Option<SignatureHelp> {
11 tester.at(position, |engine, param, _| {
12 let params = SignatureHelpParams {
13 context: None,
14 text_document_position_params: param,
15 work_done_progress_params: WorkDoneProgressParams::default(),
16 };
17 let response = engine.signature_help(params);
18
19 response.result.unwrap()
20 })
21}
22
23fn pretty_signature_help(signature_help: SignatureHelp) -> String {
24 let SignatureHelp {
25 signatures,
26 active_signature,
27 active_parameter,
28 } = signature_help;
29
30 let SignatureInformation {
31 label,
32 documentation,
33 parameters,
34 active_parameter: _,
35 } = signatures
36 .get(active_signature.expect("an active signature") as usize)
37 .expect("an active signature");
38
39 let parameters = parameters
40 .as_ref()
41 .expect("no signature help for function with no parameters");
42
43 let documentation = match documentation {
44 Some(d) => format!("Documentation:\n{d:#?}"),
45 None => "No documentation".to_string(),
46 };
47
48 let label = match active_parameter {
49 None | Some(ActiveParameter::Null) => label.to_string(),
50 Some(ActiveParameter::Int(i)) => match parameters.get(i as usize) {
51 None => label.to_string(),
52 Some(ParameterInformation {
53 label: ParameterInformationLabel::Tuple((start, end)),
54 ..
55 }) => {
56 let spaces = " ".repeat(*start as usize);
57 let underlined = "▔".repeat((end - start) as usize);
58 format!("{label}\n{spaces}{underlined}")
59 }
60 Some(_) => panic!("unexpected response"),
61 },
62 };
63
64 format!("{label}\n\n{documentation}")
65}
66
67#[macro_export]
68macro_rules! assert_signature_help {
69 ($code:literal, $position:expr $(,)?) => {
70 let project = TestProject::for_source($code);
71 assert_signature_help!(project, $position);
72 };
73
74 ($project:expr, $position:expr $(,)?) => {
75 let src = $project.src;
76 let position = $position.find_position(src);
77 let result = signature_help($project, position).expect("no signature help produced");
78 let pretty_hover = hover::show_hover(
79 src,
80 lsp_types::Range {
81 start: Position {
82 character: 1,
83 line: 1,
84 },
85 end: Position {
86 character: 0,
87 line: 0,
88 },
89 },
90 position,
91 );
92 let output = format!(
93 "{}\n\n----- Signature help -----\n{}",
94 pretty_hover,
95 pretty_signature_help(result)
96 );
97 insta::assert_snapshot!(insta::internals::AutoName, output, src);
98 };
99}
100
101#[macro_export]
102macro_rules! assert_no_signature_help {
103 ($code:literal, $position:expr $(,)?) => {
104 let project = TestProject::for_source($code);
105 assert_no_signature_help!(project, $position);
106 };
107
108 ($project:expr, $position:expr $(,)?) => {
109 let src = $project.src;
110 let position = $position.find_position(src);
111 let result = signature_help($project, position);
112 match result {
113 Some(_) => panic!("Expected no signature help"),
114 None => (),
115 }
116 };
117}
118
119#[test]
120pub fn help_for_calling_local_variable_first_arg() {
121 assert_signature_help!(
122 r#"
123pub fn main() {
124 let wibble = fn(a: Int, b: String) { 1.0 }
125 wibble()
126}
127"#,
128 find_position_of("wibble()").under_last_char()
129 );
130}
131
132#[test]
133pub fn help_for_calling_local_variable_last_arg() {
134 assert_signature_help!(
135 r#"
136pub fn main() {
137 let wibble = fn(a: Int, b: String) { 1.0 }
138 wibble(1,)
139}
140"#,
141 find_position_of("wibble(1,)").under_last_char()
142 );
143}
144
145#[test]
146pub fn help_for_calling_local_variable_with_module_function() {
147 assert_signature_help!(
148 r#"
149pub fn wibble(a: Int, b: String) { 1.0 }
150
151pub fn main() {
152 let wobble = fn(a: Int, b: String) { 1.0 }
153 wobble(1,)
154}
155"#,
156 find_position_of("wobble(1,)").under_last_char()
157 );
158}
159
160#[test]
161pub fn help_for_calling_module_function() {
162 assert_signature_help!(
163 r#"
164pub fn wibble(a: Int, b: String) { 1.0 }
165
166pub fn main() {
167 wibble()
168}
169"#,
170 find_position_of("wibble()").under_last_char()
171 );
172}
173
174#[test]
175pub fn help_for_calling_module_constant_referencing_function() {
176 assert_signature_help!(
177 r#"
178pub fn wibble(a: Int, b: String) { 1.0 }
179const wobble = wibble
180
181pub fn main() {
182 wobble()
183}
184"#,
185 find_position_of("wobble()").under_last_char()
186 );
187}
188
189#[test]
190pub fn help_for_calling_local_variable_referencing_constant_referencing_function() {
191 assert_signature_help!(
192 r#"
193pub fn wibble(a: Int, b: String) { 1.0 }
194const wobble = wibble
195
196pub fn main() {
197 let woo = wobble
198 woo()
199}
200"#,
201 find_position_of("woo()").under_last_char()
202 );
203}
204
205#[test]
206pub fn help_still_shows_up_even_if_an_argument_has_the_wrong_type() {
207 assert_signature_help!(
208 r#"
209pub fn wibble(a: Int, b: String) { 1.0 }
210
211pub fn main() {
212 wibble("wrong",)
213}
214"#,
215 find_position_of("wibble(\"wrong\",)").under_last_char()
216 );
217}
218
219#[test]
220pub fn help_shows_documentation_for_local_function() {
221 assert_signature_help!(
222 r#"
223/// Some doc!
224pub fn wibble(a: Int, b: String) { 1.0 }
225
226pub fn main() {
227 wibble()
228}
229"#,
230 find_position_of("wibble()").under_last_char()
231 );
232}
233
234#[test]
235pub fn help_shows_documentation_for_imported_function() {
236 let code = r#"
237import example
238pub fn main() {
239 example.example_fn()
240}
241"#;
242 assert_signature_help!(
243 TestProject::for_source(code).add_module(
244 "example",
245 "/// Some doc!
246pub fn example_fn(a: Int, b: String) { Nil }"
247 ),
248 find_position_of("example_fn()").under_last_char()
249 );
250}
251
252#[test]
253pub fn help_for_unqualified_call() {
254 let code = r#"
255import example.{example_fn}
256pub fn main() {
257 example_fn()
258}
259"#;
260
261 assert_signature_help!(
262 TestProject::for_source(code)
263 .add_module("example", "pub fn example_fn(a: Int, b: String) { Nil }"),
264 find_position_of("example_fn()").under_last_char()
265 );
266}
267
268#[test]
269pub fn help_for_aliased_unqualified_call() {
270 let code = r#"
271import example.{example_fn as wibble}
272pub fn main() {
273 wibble()
274}
275"#;
276
277 assert_signature_help!(
278 TestProject::for_source(code)
279 .add_module("example", "pub fn example_fn(a: Int, b: String) { Nil }"),
280 find_position_of("wibble()").under_last_char()
281 );
282}
283
284#[test]
285pub fn help_for_qualified_call() {
286 let code = r#"
287import example
288pub fn main() {
289 example.example_fn()
290}
291"#;
292
293 assert_signature_help!(
294 TestProject::for_source(code)
295 .add_module("example", "pub fn example_fn(a: Int, b: String) { Nil }"),
296 find_position_of("example_fn()").under_last_char()
297 );
298}
299
300#[test]
301pub fn help_for_aliased_qualified_call() {
302 let code = r#"
303import example as wibble
304pub fn main() {
305 wibble.example_fn()
306}
307"#;
308
309 assert_signature_help!(
310 TestProject::for_source(code)
311 .add_module("example", "pub fn example_fn(a: Int, b: String) { Nil }"),
312 find_position_of("example_fn()").under_last_char()
313 );
314}
315
316#[test]
317pub fn help_shows_labels() {
318 assert_signature_help!(
319 r#"
320pub fn wibble(a: Int, b b: Int, c c: String) { 1.0 }
321
322pub fn main() {
323 wibble()
324}
325 "#,
326 find_position_of("wibble()").under_last_char()
327 );
328}
329
330#[test]
331pub fn help_shows_labelled_argument_after_all_unlabelled() {
332 assert_signature_help!(
333 r#"
334pub fn wibble(a: Int, b b: Int, c c: String) { 1.0 }
335
336pub fn main() {
337 wibble(1,)
338}
339 "#,
340 find_position_of("wibble(1,)").under_last_char()
341 );
342}
343
344#[test]
345pub fn help_shows_first_missing_labelled_argument_if_out_of_order() {
346 assert_signature_help!(
347 r#"
348pub fn wibble(a a: Int, b b: Int, c c: String) { 1.0 }
349
350pub fn main() {
351 wibble(c: "c",)
352}
353 "#,
354 find_position_of("wibble(c: \"c\",)").under_last_char()
355 );
356}
357
358#[test]
359pub fn help_for_piped_imported_function_starts_from_second_argument() {
360 let code = r#"
361import example
362pub fn main() {
363 1 |> example.example_fn()
364}
365 "#;
366
367 assert_signature_help!(
368 TestProject::for_source(code)
369 .add_module("example", "pub fn example_fn(a: Int, b: String) { Nil }"),
370 find_position_of("example_fn()").under_last_char()
371 );
372}
373
374#[test]
375pub fn help_for_piped_function_starts_from_second_argument() {
376 assert_signature_help!(
377 r#"
378pub fn wibble(a a: Int, b b: Int, c c: String) { 1.0 }
379
380pub fn main() {
381 1 |> wibble()
382}
383 "#,
384 find_position_of("wibble()").under_last_char()
385 );
386}
387
388#[test]
389pub fn help_for_use_function_call_starts_from_first_argument() {
390 assert_signature_help!(
391 r#"
392pub fn wibble(a: Int, b: Int, c: fn() -> Int) { 1.0 }
393
394pub fn main() {
395 use <- wibble()
396}
397 "#,
398 find_position_of("wibble()").under_last_char()
399 );
400}
401
402#[test]
403pub fn help_for_use_function_call_uses_precise_types_when_missing_some_arguments() {
404 assert_signature_help!(
405 r#"
406pub fn guard(a: Bool, b: a, c: fn() -> a) { 1.0 }
407
408pub fn main() {
409 use <- guard(True,)
410}
411 "#,
412 find_position_of("guard(True,)").under_last_char()
413 );
414}
415
416#[test]
417pub fn help_for_use_function_shows_next_unlabelled_argument() {
418 assert_signature_help!(
419 r#"
420pub fn guard(a a: Bool, b b: a, c c: fn() -> a) { 1.0 }
421
422pub fn main() {
423 use <- guard(b: 1,)
424}
425 "#,
426 find_position_of("guard(b: 1,)").under_last_char()
427 );
428}
429
430#[test]
431pub fn help_does_not_come_up_for_function_that_does_not_exist() {
432 assert_no_signature_help!(
433 r#"
434pub fn main() {
435 use <- to_be_or_not_to_be()
436}
437 "#,
438 find_position_of("to_be_or_not_to_be()").under_last_char()
439 );
440}
441
442#[test]
443// Regression introduced by 4112682cdb5d5b0bb6d1defc6cde849b6a6f65ab.
444pub fn help_with_labelled_constructor() {
445 assert_signature_help!(
446 r#"
447pub type Pokemon {
448 Pokemon(name: String, types: List(String), moves: List(String))
449}
450
451pub fn main() {
452 Pokemon(name: "Jirachi",)
453}
454 "#,
455 find_position_of(r#"Pokemon(name: "Jirachi",)"#).under_last_char()
456 );
457}
458
459#[test]
460pub fn help_for_use_function_call_uses_generic_names_when_missing_all_arguments() {
461 assert_signature_help!(
462 r#"
463pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }
464pub fn main() {
465 wibble( )
466}
467"#,
468 find_position_of("wibble( ").under_last_char()
469 );
470}
471
472#[test]
473pub fn help_for_use_function_call_uses_concrete_types_when_possible_or_generic_names_when_unbound()
474{
475 assert_signature_help!(
476 r#"
477pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }
478pub fn main() {
479 wibble(1, )
480}
481"#,
482 find_position_of("wibble(1, )").under_last_char()
483 );
484}
485
486#[test]
487pub fn help_for_use_function_call_uses_concrete_types_when_late_ubound() {
488 assert_signature_help!(
489 r#"
490fn identity(x: a) -> a { x }
491
492fn main() {
493 let a = Ok(10)
494 identity( a)
495}
496"#,
497 find_position_of("identity( ").under_last_char()
498 );
499}