1use std::{collections::HashSet, iter, sync::Arc};
2
3use crate::{
4 Error, STDLIB_PACKAGE_NAME, analyse,
5 ast::{
6 self, ArgNames, AssignName, AssignmentKind, BitArraySegmentTruncation, CallArg, CustomType,
7 FunctionLiteralKind, ImplicitCallArgOrigin, Import, PIPE_PRECEDENCE, Pattern,
8 PatternUnusedArguments, PipelineAssignmentKind, RecordConstructor, SrcSpan, TodoKind,
9 TypedArg, TypedAssignment, TypedExpr, TypedModuleConstant, TypedPattern,
10 TypedPipelineAssignment, TypedRecordConstructor, TypedStatement, TypedUse,
11 visit::Visit as _,
12 },
13 build::{Located, Module},
14 config::PackageConfig,
15 exhaustiveness::CompiledCase,
16 io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter},
17 line_numbers::LineNumbers,
18 parse::{extra::ModuleExtra, lexer::str_to_keyword},
19 strings::to_snake_case,
20 type_::{
21 self, FieldMap, ModuleValueConstructor, Type, TypeVar, TypedCallArg, ValueConstructor,
22 error::{ModuleSuggestion, VariableDeclaration, VariableOrigin},
23 printer::{Names, Printer},
24 },
25};
26use ecow::{EcoString, eco_format};
27use im::HashMap;
28use itertools::Itertools;
29use lsp_types::{CodeAction, CodeActionKind, CodeActionParams, Position, Range, TextEdit, Url};
30use vec1::{Vec1, vec1};
31
32use super::{
33 TextEdits,
34 compiler::LspProjectCompiler,
35 edits::{add_newlines_after_import, get_import_edit, position_of_first_definition_if_import},
36 engine::{overlaps, within},
37 files::FileSystemProxy,
38 reference::{VariableReferenceKind, find_variable_references},
39 src_span_to_lsp_range, url_from_path,
40};
41
42#[derive(Debug)]
43pub struct CodeActionBuilder {
44 action: CodeAction,
45}
46
47impl CodeActionBuilder {
48 pub fn new(title: &str) -> Self {
49 Self {
50 action: CodeAction {
51 title: title.to_string(),
52 kind: None,
53 diagnostics: None,
54 edit: None,
55 command: None,
56 is_preferred: None,
57 disabled: None,
58 data: None,
59 },
60 }
61 }
62
63 pub fn kind(mut self, kind: CodeActionKind) -> Self {
64 self.action.kind = Some(kind);
65 self
66 }
67
68 pub fn changes(mut self, uri: Url, edits: Vec<TextEdit>) -> Self {
69 let mut edit = self.action.edit.take().unwrap_or_default();
70 let mut changes = edit.changes.take().unwrap_or_default();
71 _ = changes.insert(uri, edits);
72
73 edit.changes = Some(changes);
74 self.action.edit = Some(edit);
75 self
76 }
77
78 pub fn preferred(mut self, is_preferred: bool) -> Self {
79 self.action.is_preferred = Some(is_preferred);
80 self
81 }
82
83 pub fn push_to(self, actions: &mut Vec<CodeAction>) {
84 actions.push(self.action);
85 }
86}
87
88/// A small helper function to get the indentation at a given position.
89fn count_indentation(code: &str, line_numbers: &LineNumbers, line: u32) -> usize {
90 let mut indent_size = 0;
91 let line_start = *line_numbers
92 .line_starts
93 .get(line as usize)
94 .expect("Line number should be valid");
95
96 let mut chars = code[line_start as usize..].chars();
97 while chars.next() == Some(' ') {
98 indent_size += 1;
99 }
100
101 indent_size
102}
103
104/// Code action to remove literal tuples in case subjects, essentially making
105/// the elements of the tuples into the case's subjects.
106///
107/// The code action is only available for the i'th subject if:
108/// - it is a non-empty tuple, and
109/// - the i'th pattern (including alternative patterns) is a literal tuple for all clauses.
110///
111/// # Basic example:
112///
113/// The following case expression:
114///
115/// ```gleam
116/// case #(1, 2) {
117/// #(a, b) -> 0
118/// }
119/// ```
120///
121/// Becomes:
122///
123/// ```gleam
124/// case 1, 2 {
125/// a, b -> 0
126/// }
127/// ```
128///
129/// # Another example:
130///
131/// The following case expression does not produce any code action
132///
133/// ```gleam
134/// case #(1, 2) {
135/// a -> 0 // <- the pattern is not a tuple
136/// }
137/// ```
138pub struct RedundantTupleInCaseSubject<'a> {
139 edits: TextEdits<'a>,
140 code: &'a EcoString,
141 extra: &'a ModuleExtra,
142 params: &'a CodeActionParams,
143 module: &'a ast::TypedModule,
144 hovered: bool,
145}
146
147impl<'ast> ast::visit::Visit<'ast> for RedundantTupleInCaseSubject<'_> {
148 fn visit_typed_expr_case(
149 &mut self,
150 location: &'ast SrcSpan,
151 type_: &'ast Arc<Type>,
152 subjects: &'ast [TypedExpr],
153 clauses: &'ast [ast::TypedClause],
154 compiled_case: &'ast CompiledCase,
155 ) {
156 for (subject_idx, subject) in subjects.iter().enumerate() {
157 let TypedExpr::Tuple {
158 location, elements, ..
159 } = subject
160 else {
161 continue;
162 };
163
164 // Ignore empty tuple
165 if elements.is_empty() {
166 continue;
167 }
168
169 // We cannot rewrite clauses whose i-th pattern is not a discard or
170 // tuples.
171 let all_ith_patterns_are_tuples_or_discards = clauses
172 .iter()
173 .map(|clause| clause.pattern.get(subject_idx))
174 .all(|pattern| {
175 matches!(
176 pattern,
177 Some(Pattern::Tuple { .. } | Pattern::Discard { .. })
178 )
179 });
180
181 if !all_ith_patterns_are_tuples_or_discards {
182 continue;
183 }
184
185 self.delete_tuple_tokens(*location, elements.last().map(|element| element.location()));
186
187 for clause in clauses {
188 match clause.pattern.get(subject_idx) {
189 Some(Pattern::Tuple { location, elements }) => self.delete_tuple_tokens(
190 *location,
191 elements.last().map(|element| element.location()),
192 ),
193 Some(Pattern::Discard { location, .. }) => {
194 self.discard_tuple_items(*location, elements.len())
195 }
196 _ => panic!("safe: we've just checked all patterns must be discards/tuples"),
197 }
198 }
199 let range = self.edits.src_span_to_lsp_range(*location);
200 self.hovered = self.hovered || overlaps(self.params.range, range);
201 }
202
203 ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case)
204 }
205}
206
207impl<'a> RedundantTupleInCaseSubject<'a> {
208 pub fn new(
209 module: &'a Module,
210 line_numbers: &'a LineNumbers,
211 params: &'a CodeActionParams,
212 ) -> Self {
213 Self {
214 edits: TextEdits::new(line_numbers),
215 code: &module.code,
216 extra: &module.extra,
217 params,
218 module: &module.ast,
219 hovered: false,
220 }
221 }
222
223 pub fn code_actions(mut self) -> Vec<CodeAction> {
224 self.visit_typed_module(self.module);
225 if !self.hovered {
226 return vec![];
227 }
228
229 self.edits.edits.sort_by_key(|edit| edit.range.start);
230
231 let mut actions = vec![];
232 CodeActionBuilder::new("Remove redundant tuples")
233 .kind(CodeActionKind::REFACTOR_REWRITE)
234 .changes(self.params.text_document.uri.clone(), self.edits.edits)
235 .preferred(true)
236 .push_to(&mut actions);
237
238 actions
239 }
240
241 fn delete_tuple_tokens(&mut self, location: SrcSpan, last_elem_location: Option<SrcSpan>) {
242 let tuple_code = self
243 .code
244 .get(location.start as usize..location.end as usize)
245 .expect("valid span");
246
247 // Delete `#`
248 self.edits
249 .delete(SrcSpan::new(location.start, location.start + 1));
250
251 // Delete `(`
252 let (lparen_offset, _) = tuple_code
253 .match_indices('(')
254 // Ignore in comments
255 .find(|(i, _)| !self.extra.is_within_comment(location.start + *i as u32))
256 .expect("`(` not found in tuple");
257
258 self.edits.delete(SrcSpan::new(
259 location.start + lparen_offset as u32,
260 location.start + lparen_offset as u32 + 1,
261 ));
262
263 // Delete trailing `,` (if applicable)
264 if let Some(last_elem_location) = last_elem_location {
265 // Get the code after the last element until the tuple's `)`
266 let code_after_last_elem = self
267 .code
268 .get(last_elem_location.end as usize..location.end as usize)
269 .expect("valid span");
270
271 if let Some((trailing_comma_offset, _)) = code_after_last_elem
272 .rmatch_indices(',')
273 // Ignore in comments
274 .find(|(i, _)| {
275 !self
276 .extra
277 .is_within_comment(last_elem_location.end + *i as u32)
278 })
279 {
280 self.edits.delete(SrcSpan::new(
281 last_elem_location.end + trailing_comma_offset as u32,
282 last_elem_location.end + trailing_comma_offset as u32 + 1,
283 ));
284 }
285 }
286
287 // Delete )
288 self.edits
289 .delete(SrcSpan::new(location.end - 1, location.end));
290 }
291
292 fn discard_tuple_items(&mut self, discard_location: SrcSpan, tuple_items: usize) {
293 // Replace the old discard with multiple discard, one for each of the
294 // tuple items.
295 self.edits.replace(
296 discard_location,
297 itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(),
298 )
299 }
300}
301
302/// Builder for code action to convert `let assert` into a case expression.
303///
304pub struct LetAssertToCase<'a> {
305 module: &'a Module,
306 params: &'a CodeActionParams,
307 actions: Vec<CodeAction>,
308 edits: TextEdits<'a>,
309}
310
311impl<'ast> ast::visit::Visit<'ast> for LetAssertToCase<'_> {
312 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
313 // To prevent weird behaviour when `let assert` statements are nested,
314 // we only check for the code action between the `let` and `=`.
315 let code_action_location =
316 SrcSpan::new(assignment.location.start, assignment.value.location().start);
317 let code_action_range =
318 src_span_to_lsp_range(code_action_location, self.edits.line_numbers);
319
320 self.visit_typed_expr(&assignment.value);
321
322 // Only offer the code action if the cursor is over the statement
323 if !overlaps(code_action_range, self.params.range) {
324 return;
325 }
326
327 // This pattern only applies to `let assert`
328 let AssignmentKind::Assert { message, .. } = &assignment.kind else {
329 return;
330 };
331
332 // Get the source code for the tested expression
333 let location = assignment.value.location();
334 let expr = self
335 .module
336 .code
337 .get(location.start as usize..location.end as usize)
338 .expect("Location must be valid");
339
340 // Get the source code for the pattern
341 let pattern_location = assignment.pattern.location();
342 let pattern = self
343 .module
344 .code
345 .get(pattern_location.start as usize..pattern_location.end as usize)
346 .expect("Location must be valid");
347
348 let message = message.as_ref().map(|message| {
349 let location = message.location();
350 self.module
351 .code
352 .get(location.start as usize..location.end as usize)
353 .expect("Location must be valid")
354 });
355
356 let range = src_span_to_lsp_range(assignment.location, self.edits.line_numbers);
357
358 // Figure out which variables are assigned in the pattern
359 let variables = PatternVariableFinder::find_variables_in_pattern(&assignment.pattern);
360
361 let assigned = match variables.len() {
362 0 => "_",
363 1 => variables.first().expect("Variables is length one"),
364 _ => &format!("#({})", variables.join(", ")),
365 };
366
367 let mut new_text = format!("let {assigned} = ");
368 let panic_message = if let Some(message) = message {
369 &format!("panic as {message}")
370 } else {
371 "panic"
372 };
373 let clauses = vec![
374 // The existing pattern
375 CaseClause {
376 pattern,
377 // `_` is not a valid expression, so if we are not
378 // binding any variables in the pattern, we simply return Nil.
379 expression: if assigned == "_" { "Nil" } else { assigned },
380 },
381 CaseClause {
382 pattern: "_",
383 expression: panic_message,
384 },
385 ];
386 print_case_expression(range.start.character as usize, expr, clauses, &mut new_text);
387
388 let uri = &self.params.text_document.uri;
389
390 CodeActionBuilder::new("Convert to case")
391 .kind(CodeActionKind::REFACTOR_REWRITE)
392 .changes(uri.clone(), vec![TextEdit { range, new_text }])
393 .preferred(false)
394 .push_to(&mut self.actions);
395 }
396}
397
398impl<'a> LetAssertToCase<'a> {
399 pub fn new(
400 module: &'a Module,
401 line_numbers: &'a LineNumbers,
402 params: &'a CodeActionParams,
403 ) -> Self {
404 Self {
405 module,
406 params,
407 actions: Vec::new(),
408 edits: TextEdits::new(line_numbers),
409 }
410 }
411
412 pub fn code_actions(mut self) -> Vec<CodeAction> {
413 self.visit_typed_module(&self.module.ast);
414 self.actions
415 }
416}
417
418struct PatternVariableFinder {
419 pattern_variables: Vec<EcoString>,
420}
421
422impl PatternVariableFinder {
423 fn new() -> Self {
424 Self {
425 pattern_variables: Vec::new(),
426 }
427 }
428
429 fn find_variables_in_pattern(pattern: &TypedPattern) -> Vec<EcoString> {
430 let mut finder = Self::new();
431 finder.visit_typed_pattern(pattern);
432 finder.pattern_variables
433 }
434}
435
436impl<'ast> ast::visit::Visit<'ast> for PatternVariableFinder {
437 fn visit_typed_pattern_variable(
438 &mut self,
439 _location: &'ast SrcSpan,
440 name: &'ast EcoString,
441 _type: &'ast Arc<Type>,
442 _origin: &'ast VariableOrigin,
443 ) {
444 self.pattern_variables.push(name.clone());
445 }
446
447 fn visit_typed_pattern_assign(
448 &mut self,
449 location: &'ast SrcSpan,
450 name: &'ast EcoString,
451 pattern: &'ast TypedPattern,
452 ) {
453 self.pattern_variables.push(name.clone());
454 ast::visit::visit_typed_pattern_assign(self, location, name, pattern);
455 }
456
457 fn visit_typed_pattern_string_prefix(
458 &mut self,
459 _location: &'ast SrcSpan,
460 _left_location: &'ast SrcSpan,
461 left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
462 _right_location: &'ast SrcSpan,
463 _left_side_string: &'ast EcoString,
464 right_side_assignment: &'ast AssignName,
465 ) {
466 if let Some((name, _)) = left_side_assignment {
467 self.pattern_variables.push(name.clone());
468 }
469 if let AssignName::Variable(name) = right_side_assignment {
470 self.pattern_variables.push(name.clone());
471 }
472 }
473}
474
475pub fn code_action_inexhaustive_let_to_case(
476 module: &Module,
477 line_numbers: &LineNumbers,
478 params: &CodeActionParams,
479 error: &Option<Error>,
480 actions: &mut Vec<CodeAction>,
481) {
482 let Some(Error::Type { errors, .. }) = error else {
483 return;
484 };
485 let inexhaustive_assignments = errors
486 .iter()
487 .filter_map(|error| match error {
488 type_::Error::InexhaustiveLetAssignment { location, missing } => {
489 Some((*location, missing))
490 }
491 _ => None,
492 })
493 .collect_vec();
494
495 if inexhaustive_assignments.is_empty() {
496 return;
497 }
498
499 for (location, missing) in inexhaustive_assignments {
500 let mut text_edits = TextEdits::new(line_numbers);
501
502 let range = text_edits.src_span_to_lsp_range(location);
503 if !overlaps(params.range, range) {
504 return;
505 }
506
507 let Some(Located::Statement(TypedStatement::Assignment(assignment))) =
508 module.find_node(location.start)
509 else {
510 continue;
511 };
512
513 let TypedAssignment {
514 value,
515 pattern,
516 kind: AssignmentKind::Let,
517 location,
518 compiled_case: _,
519 annotation: _,
520 } = assignment.as_ref()
521 else {
522 continue;
523 };
524
525 // Get the source code for the tested expression
526 let value_location = value.location();
527 let expr = module
528 .code
529 .get(value_location.start as usize..value_location.end as usize)
530 .expect("Location must be valid");
531
532 // Get the source code for the pattern
533 let pattern_location = pattern.location();
534 let pattern_code = module
535 .code
536 .get(pattern_location.start as usize..pattern_location.end as usize)
537 .expect("Location must be valid");
538
539 let range = text_edits.src_span_to_lsp_range(*location);
540
541 // Figure out which variables are assigned in the pattern
542 let variables = PatternVariableFinder::find_variables_in_pattern(pattern);
543
544 let assigned = match variables.len() {
545 0 => "_",
546 1 => variables.first().expect("Variables is length one"),
547 _ => &format!("#({})", variables.join(", ")),
548 };
549
550 let mut new_text = format!("let {assigned} = ");
551 print_case_expression(
552 range.start.character as usize,
553 expr,
554 iter::once(CaseClause {
555 pattern: pattern_code,
556 expression: if assigned == "_" { "Nil" } else { assigned },
557 })
558 .chain(missing.iter().map(|pattern| CaseClause {
559 pattern,
560 expression: "todo",
561 }))
562 .collect(),
563 &mut new_text,
564 );
565
566 let uri = ¶ms.text_document.uri;
567
568 text_edits.replace(*location, new_text);
569
570 CodeActionBuilder::new("Convert to case")
571 .kind(CodeActionKind::QUICKFIX)
572 .changes(uri.clone(), text_edits.edits)
573 .preferred(true)
574 .push_to(actions);
575 }
576}
577
578struct CaseClause<'a> {
579 pattern: &'a str,
580 expression: &'a str,
581}
582
583fn print_case_expression(
584 indent_size: usize,
585 subject: &str,
586 clauses: Vec<CaseClause<'_>>,
587 buffer: &mut String,
588) {
589 let indent = " ".repeat(indent_size);
590
591 // Print the beginning of the expression
592 buffer.push_str("case ");
593 buffer.push_str(subject);
594 buffer.push_str(" {");
595
596 for clause in clauses.iter() {
597 // Print the newline and indentation for this clause
598 buffer.push('\n');
599 buffer.push_str(&indent);
600 // Indent this clause one level deeper than the case expression
601 buffer.push_str(" ");
602
603 // Print the clause
604 buffer.push_str(clause.pattern);
605 buffer.push_str(" -> ");
606 buffer.push_str(clause.expression);
607 }
608
609 // If there are no clauses to print, the closing brace should be
610 // on the same line as the opening one, with no space between.
611 if !clauses.is_empty() {
612 buffer.push('\n');
613 buffer.push_str(&indent);
614 }
615 buffer.push('}');
616}
617
618/// Builder for code action to apply the label shorthand syntax on arguments
619/// where the label has the same name as the variable.
620///
621pub struct UseLabelShorthandSyntax<'a> {
622 module: &'a Module,
623 params: &'a CodeActionParams,
624 edits: TextEdits<'a>,
625}
626
627impl<'a> UseLabelShorthandSyntax<'a> {
628 pub fn new(
629 module: &'a Module,
630 line_numbers: &'a LineNumbers,
631 params: &'a CodeActionParams,
632 ) -> Self {
633 Self {
634 module,
635 params,
636 edits: TextEdits::new(line_numbers),
637 }
638 }
639
640 pub fn code_actions(mut self) -> Vec<CodeAction> {
641 self.visit_typed_module(&self.module.ast);
642 if self.edits.edits.is_empty() {
643 return vec![];
644 }
645 let mut action = Vec::with_capacity(1);
646 CodeActionBuilder::new("Use label shorthand syntax")
647 .kind(CodeActionKind::REFACTOR)
648 .changes(self.params.text_document.uri.clone(), self.edits.edits)
649 .preferred(false)
650 .push_to(&mut action);
651 action
652 }
653}
654
655impl<'ast> ast::visit::Visit<'ast> for UseLabelShorthandSyntax<'_> {
656 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
657 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
658 let is_selected = overlaps(arg_range, self.params.range);
659
660 match arg {
661 CallArg {
662 label: Some(label),
663 value: TypedExpr::Var { name, location, .. },
664 ..
665 } if is_selected && !arg.uses_label_shorthand() && label == name => {
666 self.edits.delete(*location)
667 }
668 _ => (),
669 }
670
671 ast::visit::visit_typed_call_arg(self, arg)
672 }
673
674 fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
675 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
676 let is_selected = overlaps(arg_range, self.params.range);
677
678 match arg {
679 CallArg {
680 label: Some(label),
681 value: TypedPattern::Variable { name, location, .. },
682 ..
683 } if is_selected && !arg.uses_label_shorthand() && label == name => {
684 self.edits.delete(*location)
685 }
686 _ => (),
687 }
688
689 ast::visit::visit_typed_pattern_call_arg(self, arg)
690 }
691}
692
693/// Builder for code action to apply the fill in the missing labelled arguments
694/// of the selected function call.
695///
696pub struct FillInMissingLabelledArgs<'a> {
697 module: &'a Module,
698 params: &'a CodeActionParams,
699 edits: TextEdits<'a>,
700 use_right_hand_side_location: Option<SrcSpan>,
701 selected_call: Option<SelectedCall<'a>>,
702}
703
704struct SelectedCall<'a> {
705 location: SrcSpan,
706 field_map: &'a FieldMap,
707 arguments: Vec<CallArg<()>>,
708 kind: SelectedCallKind,
709}
710
711enum SelectedCallKind {
712 Value,
713 Pattern,
714}
715
716impl<'a> FillInMissingLabelledArgs<'a> {
717 pub fn new(
718 module: &'a Module,
719 line_numbers: &'a LineNumbers,
720 params: &'a CodeActionParams,
721 ) -> Self {
722 Self {
723 module,
724 params,
725 edits: TextEdits::new(line_numbers),
726 use_right_hand_side_location: None,
727 selected_call: None,
728 }
729 }
730
731 pub fn code_actions(mut self) -> Vec<CodeAction> {
732 self.visit_typed_module(&self.module.ast);
733
734 if let Some(SelectedCall {
735 location: call_location,
736 field_map,
737 arguments,
738 kind,
739 }) = self.selected_call
740 {
741 let is_use_call = arguments.iter().any(|arg| arg.is_use_implicit_callback());
742 let missing_labels = field_map.missing_labels(&arguments);
743
744 // If we're applying the code action to a use call, then we know
745 // that the last missing argument is going to be implicitly inserted
746 // by the compiler, so in that case we don't want to also add that
747 // last label to the completions.
748 let missing_labels = missing_labels.iter().peekable();
749 let mut missing_labels = if is_use_call {
750 missing_labels.dropping_back(1)
751 } else {
752 missing_labels
753 };
754
755 // If we couldn't find any missing label to insert we just return.
756 if missing_labels.peek().is_none() {
757 return vec![];
758 }
759
760 // Now we need to figure out if there's a comma at the end of the
761 // arguments list:
762 //
763 // call(one, |)
764 // ^ Cursor here, with a comma behind
765 //
766 // call(one|)
767 // ^ Cursor here, no comma behind, we'll have to add one!
768 //
769 let label_insertion_start = call_location.end - 1;
770 let has_comma_after_last_argument = if let Some(last_arg) = arguments
771 .iter()
772 .filter(|arg| !arg.is_implicit())
773 .next_back()
774 {
775 self.module
776 .code
777 .get(last_arg.location.end as usize..=label_insertion_start as usize)
778 .is_some_and(|text| text.contains(','))
779 } else {
780 false
781 };
782
783 let format_label = match kind {
784 SelectedCallKind::Value => |label| format!("{label}: todo"),
785 SelectedCallKind::Pattern => |label| format!("{label}:"),
786 };
787
788 let labels_list = missing_labels.map(format_label).join(", ");
789
790 let has_no_explicit_arguments = arguments
791 .iter()
792 .filter(|arg| !arg.is_implicit())
793 .peekable()
794 .peek()
795 .is_none();
796
797 let labels_list = if has_no_explicit_arguments || has_comma_after_last_argument {
798 labels_list
799 } else {
800 format!(", {labels_list}")
801 };
802
803 self.edits.insert(label_insertion_start, labels_list);
804
805 let mut action = Vec::with_capacity(1);
806 CodeActionBuilder::new("Fill labels")
807 .kind(CodeActionKind::QUICKFIX)
808 .changes(self.params.text_document.uri.clone(), self.edits.edits)
809 .preferred(true)
810 .push_to(&mut action);
811 return action;
812 }
813
814 vec![]
815 }
816
817 fn empty_argument<A>(argument: &CallArg<A>) -> CallArg<()> {
818 CallArg {
819 label: argument.label.clone(),
820 location: argument.location,
821 value: (),
822 implicit: argument.implicit,
823 }
824 }
825}
826
827impl<'ast> ast::visit::Visit<'ast> for FillInMissingLabelledArgs<'ast> {
828 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
829 // If we're adding labels to a use call the correct location of the
830 // function we need to add labels to is `use_right_hand_side_location`.
831 // So we store it for when we're typing the use call.
832 let previous = self.use_right_hand_side_location;
833 self.use_right_hand_side_location = Some(use_.right_hand_side_location);
834 ast::visit::visit_typed_use(self, use_);
835 self.use_right_hand_side_location = previous;
836 }
837
838 fn visit_typed_expr_call(
839 &mut self,
840 location: &'ast SrcSpan,
841 type_: &'ast Arc<Type>,
842 fun: &'ast TypedExpr,
843 args: &'ast [TypedCallArg],
844 ) {
845 let call_range = self.edits.src_span_to_lsp_range(*location);
846 if !within(self.params.range, call_range) {
847 return;
848 }
849
850 if let Some(field_map) = fun.field_map() {
851 let location = self.use_right_hand_side_location.unwrap_or(*location);
852 self.selected_call = Some(SelectedCall {
853 location,
854 field_map,
855 arguments: args.iter().map(Self::empty_argument).collect(),
856 kind: SelectedCallKind::Value,
857 })
858 }
859
860 // We only want to take into account the innermost function call
861 // containing the current selection so we can't stop at the first call
862 // we find (the outermost one) and have to keep traversing it in case
863 // we're inside a nested call.
864 let previous = self.use_right_hand_side_location;
865 self.use_right_hand_side_location = None;
866 ast::visit::visit_typed_expr_call(self, location, type_, fun, args);
867 self.use_right_hand_side_location = previous;
868 }
869
870 fn visit_typed_pattern_constructor(
871 &mut self,
872 location: &'ast SrcSpan,
873 name_location: &'ast SrcSpan,
874 name: &'ast EcoString,
875 arguments: &'ast Vec<CallArg<TypedPattern>>,
876 module: &'ast Option<(EcoString, SrcSpan)>,
877 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
878 spread: &'ast Option<SrcSpan>,
879 type_: &'ast Arc<Type>,
880 ) {
881 let call_range = self.edits.src_span_to_lsp_range(*location);
882 if !within(self.params.range, call_range) {
883 return;
884 }
885
886 if let Some(field_map) = constructor.field_map() {
887 self.selected_call = Some(SelectedCall {
888 location: *location,
889 field_map,
890 arguments: arguments.iter().map(Self::empty_argument).collect(),
891 kind: SelectedCallKind::Pattern,
892 })
893 }
894
895 ast::visit::visit_typed_pattern_constructor(
896 self,
897 location,
898 name_location,
899 name,
900 arguments,
901 module,
902 constructor,
903 spread,
904 type_,
905 );
906 }
907}
908
909struct MissingImport {
910 location: SrcSpan,
911 suggestions: Vec<ImportSuggestion>,
912}
913
914struct ImportSuggestion {
915 // The name to replace with, if the user made a typo
916 name: EcoString,
917 // The optional module to import, if suggesting an importable module
918 import: Option<EcoString>,
919}
920
921pub fn code_action_import_module(
922 module: &Module,
923 line_numbers: &LineNumbers,
924 params: &CodeActionParams,
925 error: &Option<Error>,
926 actions: &mut Vec<CodeAction>,
927) {
928 let uri = ¶ms.text_document.uri;
929 let Some(Error::Type { errors, .. }) = error else {
930 return;
931 };
932
933 let missing_imports = errors
934 .into_iter()
935 .filter_map(|e| match e {
936 type_::Error::UnknownModule {
937 location,
938 suggestions,
939 ..
940 } => suggest_imports(*location, suggestions),
941 _ => None,
942 })
943 .collect_vec();
944
945 if missing_imports.is_empty() {
946 return;
947 }
948
949 let first_import_pos = position_of_first_definition_if_import(module, line_numbers);
950 let first_is_import = first_import_pos.is_some();
951 let import_location = first_import_pos.unwrap_or_default();
952
953 let after_import_newlines =
954 add_newlines_after_import(import_location, first_is_import, line_numbers, &module.code);
955
956 for missing_import in missing_imports {
957 let range = src_span_to_lsp_range(missing_import.location, line_numbers);
958 if !overlaps(params.range, range) {
959 continue;
960 }
961
962 for suggestion in missing_import.suggestions {
963 let mut edits = vec![TextEdit {
964 range,
965 new_text: suggestion.name.to_string(),
966 }];
967 if let Some(import) = &suggestion.import {
968 edits.push(get_import_edit(
969 import_location,
970 import,
971 &after_import_newlines,
972 ))
973 };
974
975 let title = match &suggestion.import {
976 Some(import) => &format!("Import `{import}`"),
977 _ => &format!("Did you mean `{}`", suggestion.name),
978 };
979
980 CodeActionBuilder::new(title)
981 .kind(CodeActionKind::QUICKFIX)
982 .changes(uri.clone(), edits)
983 .preferred(true)
984 .push_to(actions);
985 }
986 }
987}
988
989fn suggest_imports(
990 location: SrcSpan,
991 importable_modules: &[ModuleSuggestion],
992) -> Option<MissingImport> {
993 let suggestions = importable_modules
994 .iter()
995 .map(|suggestion| {
996 let imported_name = suggestion.last_name_component();
997 match suggestion {
998 ModuleSuggestion::Importable(name) => ImportSuggestion {
999 name: imported_name.into(),
1000 import: Some(name.clone()),
1001 },
1002 ModuleSuggestion::Imported(_) => ImportSuggestion {
1003 name: imported_name.into(),
1004 import: None,
1005 },
1006 }
1007 })
1008 .collect_vec();
1009
1010 if suggestions.is_empty() {
1011 None
1012 } else {
1013 Some(MissingImport {
1014 location,
1015 suggestions,
1016 })
1017 }
1018}
1019
1020pub fn code_action_add_missing_patterns(
1021 module: &Module,
1022 line_numbers: &LineNumbers,
1023 params: &CodeActionParams,
1024 error: &Option<Error>,
1025 actions: &mut Vec<CodeAction>,
1026) {
1027 let uri = ¶ms.text_document.uri;
1028 let Some(Error::Type { errors, .. }) = error else {
1029 return;
1030 };
1031 let missing_patterns = errors
1032 .iter()
1033 .filter_map(|error| match error {
1034 type_::Error::InexhaustiveCaseExpression { location, missing } => {
1035 Some((*location, missing))
1036 }
1037 _ => None,
1038 })
1039 .collect_vec();
1040
1041 if missing_patterns.is_empty() {
1042 return;
1043 }
1044
1045 for (location, missing) in missing_patterns {
1046 let mut edits = TextEdits::new(line_numbers);
1047 let range = edits.src_span_to_lsp_range(location);
1048 if !overlaps(params.range, range) {
1049 return;
1050 }
1051
1052 let Some(Located::Expression {
1053 expression: TypedExpr::Case {
1054 clauses, subjects, ..
1055 },
1056 ..
1057 }) = module.find_node(location.start)
1058 else {
1059 continue;
1060 };
1061
1062 let indent_size = count_indentation(&module.code, edits.line_numbers, range.start.line);
1063
1064 let indent = " ".repeat(indent_size);
1065
1066 // Insert the missing patterns just after the final clause, or just before
1067 // the closing brace if there are no clauses
1068
1069 let insert_at = clauses
1070 .last()
1071 .map(|clause| clause.location.end)
1072 .unwrap_or(location.end - 1);
1073
1074 for pattern in missing {
1075 let new_text = format!("\n{indent} {pattern} -> todo");
1076 edits.insert(insert_at, new_text);
1077 }
1078
1079 // Add a newline + indent after the last pattern if there are no clauses
1080 //
1081 // This improves the generated code for this case:
1082 // ```gleam
1083 // case True {}
1084 // ```
1085 // This produces:
1086 // ```gleam
1087 // case True {
1088 // True -> todo
1089 // False -> todo
1090 // }
1091 // ```
1092 // Instead of:
1093 // ```gleam
1094 // case True {
1095 // True -> todo
1096 // False -> todo}
1097 // ```
1098 //
1099 if clauses.is_empty() {
1100 let last_subject_location = subjects
1101 .last()
1102 .expect("Case expressions have at least one subject")
1103 .location()
1104 .end;
1105
1106 // Find the opening brace of the case expression
1107 let chars = module.code[last_subject_location as usize..].chars();
1108 let mut start_brace_location = last_subject_location;
1109 for char in chars {
1110 start_brace_location += 1;
1111 if char == '{' {
1112 break;
1113 }
1114 }
1115
1116 // Remove any blank spaces/lines between the start brace and end brace
1117 edits.delete(SrcSpan::new(start_brace_location, insert_at));
1118 edits.insert(insert_at, format!("\n{indent}"));
1119 }
1120
1121 CodeActionBuilder::new("Add missing patterns")
1122 .kind(CodeActionKind::QUICKFIX)
1123 .changes(uri.clone(), edits.edits)
1124 .preferred(true)
1125 .push_to(actions);
1126 }
1127}
1128
1129/// Builder for code action to add annotations to an assignment or function
1130///
1131pub struct AddAnnotations<'a> {
1132 module: &'a Module,
1133 params: &'a CodeActionParams,
1134 edits: TextEdits<'a>,
1135 printer: Printer<'a>,
1136}
1137
1138impl<'ast> ast::visit::Visit<'ast> for AddAnnotations<'_> {
1139 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
1140 self.visit_typed_expr(&assignment.value);
1141
1142 // We only offer this code action between `let` and `=`, because
1143 // otherwise it could lead to confusing behaviour if inside a block
1144 // which is part of a let binding.
1145 let pattern_location = assignment.pattern.location();
1146 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
1147 let code_action_range = self.edits.src_span_to_lsp_range(location);
1148
1149 // Only offer the code action if the cursor is over the statement
1150 if !overlaps(code_action_range, self.params.range) {
1151 return;
1152 }
1153
1154 // We don't need to add an annotation if there already is one
1155 if assignment.annotation.is_some() {
1156 return;
1157 }
1158
1159 // Various expressions such as pipelines and `use` expressions generate assignments
1160 // internally. However, these cannot be annotated and so we don't offer a code action here.
1161 if matches!(assignment.kind, AssignmentKind::Generated) {
1162 return;
1163 }
1164
1165 self.edits.insert(
1166 pattern_location.end,
1167 format!(": {}", self.printer.print_type(&assignment.type_())),
1168 );
1169 }
1170
1171 fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
1172 let code_action_range = self.edits.src_span_to_lsp_range(constant.location);
1173
1174 // Only offer the code action if the cursor is over the statement
1175 if !overlaps(code_action_range, self.params.range) {
1176 return;
1177 }
1178
1179 // We don't need to add an annotation if there already is one
1180 if constant.annotation.is_some() {
1181 return;
1182 }
1183
1184 self.edits.insert(
1185 constant.name_location.end,
1186 format!(": {}", self.printer.print_type(&constant.type_)),
1187 );
1188 }
1189
1190 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
1191 ast::visit::visit_typed_function(self, fun);
1192
1193 let code_action_range = self.edits.src_span_to_lsp_range(fun.location);
1194
1195 // Only offer the code action if the cursor is over the statement
1196 if !overlaps(code_action_range, self.params.range) {
1197 return;
1198 }
1199
1200 // Annotate each argument separately
1201 for argument in fun.arguments.iter() {
1202 // Don't annotate the argument if it's already annotated
1203 if argument.annotation.is_some() {
1204 continue;
1205 }
1206
1207 self.edits.insert(
1208 argument.location.end,
1209 format!(": {}", self.printer.print_type(&argument.type_)),
1210 );
1211 }
1212
1213 // Annotate the return type if it isn't already annotated
1214 if fun.return_annotation.is_none() {
1215 self.edits.insert(
1216 fun.location.end,
1217 format!(" -> {}", self.printer.print_type(&fun.return_type)),
1218 );
1219 }
1220 }
1221
1222 fn visit_typed_expr_fn(
1223 &mut self,
1224 location: &'ast SrcSpan,
1225 type_: &'ast Arc<Type>,
1226 kind: &'ast FunctionLiteralKind,
1227 args: &'ast [TypedArg],
1228 body: &'ast Vec1<TypedStatement>,
1229 return_annotation: &'ast Option<ast::TypeAst>,
1230 ) {
1231 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
1232
1233 // If the function doesn't have a head, we can't annotate it
1234 let location = match kind {
1235 // Function captures don't need any type annotations
1236 FunctionLiteralKind::Capture { .. } => return,
1237 FunctionLiteralKind::Anonymous { head } => head,
1238 FunctionLiteralKind::Use { location } => location,
1239 };
1240
1241 let code_action_range = self.edits.src_span_to_lsp_range(*location);
1242
1243 // Only offer the code action if the cursor is over the expression
1244 if !overlaps(code_action_range, self.params.range) {
1245 return;
1246 }
1247
1248 // Annotate each argument separately
1249 for argument in args.iter() {
1250 // Don't annotate the argument if it's already annotated
1251 if argument.annotation.is_some() {
1252 continue;
1253 }
1254
1255 self.edits.insert(
1256 argument.location.end,
1257 format!(": {}", self.printer.print_type(&argument.type_)),
1258 );
1259 }
1260
1261 // Annotate the return type if it isn't already annotated, and this is
1262 // an anonymous function.
1263 if return_annotation.is_none() && matches!(kind, FunctionLiteralKind::Anonymous { .. }) {
1264 let return_type = &type_.return_type().expect("Type must be a function");
1265 let pretty_type = self.printer.print_type(return_type);
1266 self.edits
1267 .insert(location.end, format!(" -> {pretty_type}"));
1268 }
1269 }
1270}
1271
1272impl<'a> AddAnnotations<'a> {
1273 pub fn new(
1274 module: &'a Module,
1275 line_numbers: &'a LineNumbers,
1276 params: &'a CodeActionParams,
1277 ) -> Self {
1278 Self {
1279 module,
1280 params,
1281 edits: TextEdits::new(line_numbers),
1282 // We need to use the same printer for all the edits because otherwise
1283 // we could get duplicate type variable names.
1284 printer: Printer::new(&module.ast.names),
1285 }
1286 }
1287
1288 pub fn code_action(mut self, actions: &mut Vec<CodeAction>) {
1289 self.visit_typed_module(&self.module.ast);
1290
1291 let uri = &self.params.text_document.uri;
1292
1293 let title = match self.edits.edits.len() {
1294 // We don't offer a code action if there is no action to perform
1295 0 => return,
1296 1 => "Add type annotation",
1297 _ => "Add type annotations",
1298 };
1299
1300 CodeActionBuilder::new(title)
1301 .kind(CodeActionKind::REFACTOR)
1302 .changes(uri.clone(), self.edits.edits)
1303 .preferred(false)
1304 .push_to(actions);
1305 }
1306}
1307
1308pub struct QualifiedConstructor<'a> {
1309 import: &'a Import<EcoString>,
1310 module_aliased: bool,
1311 used_name: EcoString,
1312 constructor: EcoString,
1313 layer: ast::Layer,
1314}
1315
1316impl QualifiedConstructor<'_> {
1317 fn constructor_import(&self) -> String {
1318 if self.layer.is_value() {
1319 self.constructor.to_string()
1320 } else {
1321 format!("type {}", self.constructor)
1322 }
1323 }
1324}
1325
1326pub struct QualifiedToUnqualifiedImportFirstPass<'a> {
1327 module: &'a Module,
1328 params: &'a CodeActionParams,
1329 line_numbers: &'a LineNumbers,
1330 qualified_constructor: Option<QualifiedConstructor<'a>>,
1331}
1332
1333impl<'a> QualifiedToUnqualifiedImportFirstPass<'a> {
1334 fn new(
1335 module: &'a Module,
1336 params: &'a CodeActionParams,
1337 line_numbers: &'a LineNumbers,
1338 ) -> Self {
1339 Self {
1340 module,
1341 params,
1342 line_numbers,
1343 qualified_constructor: None,
1344 }
1345 }
1346
1347 fn get_module_import(
1348 &self,
1349 module_name: &EcoString,
1350 constructor: &EcoString,
1351 layer: ast::Layer,
1352 ) -> Option<&'a Import<EcoString>> {
1353 let mut matching_import = None;
1354
1355 for definition in &self.module.ast.definitions {
1356 if let ast::Definition::Import(import) = definition {
1357 let imported = if layer.is_value() {
1358 &import.unqualified_values
1359 } else {
1360 &import.unqualified_types
1361 };
1362
1363 if import.module != *module_name
1364 && imported.iter().any(|imp| imp.used_name() == constructor)
1365 {
1366 return None;
1367 }
1368
1369 if import.module == *module_name {
1370 matching_import = Some(import);
1371 }
1372 }
1373 }
1374
1375 matching_import
1376 }
1377}
1378
1379impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportFirstPass<'ast> {
1380 fn visit_typed_expr_fn(
1381 &mut self,
1382 location: &'ast SrcSpan,
1383 type_: &'ast Arc<Type>,
1384 kind: &'ast FunctionLiteralKind,
1385 args: &'ast [TypedArg],
1386 body: &'ast Vec1<TypedStatement>,
1387 return_annotation: &'ast Option<ast::TypeAst>,
1388 ) {
1389 for arg in args {
1390 if let Some(annotation) = &arg.annotation {
1391 self.visit_type_ast(annotation);
1392 }
1393 }
1394 if let Some(return_) = return_annotation {
1395 self.visit_type_ast(return_);
1396 }
1397 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
1398 }
1399
1400 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
1401 for arg in &fun.arguments {
1402 if let Some(annotation) = &arg.annotation {
1403 self.visit_type_ast(annotation);
1404 }
1405 }
1406
1407 if let Some(return_annotation) = &fun.return_annotation {
1408 self.visit_type_ast(return_annotation);
1409 }
1410 ast::visit::visit_typed_function(self, fun);
1411 }
1412
1413 fn visit_type_ast_constructor(
1414 &mut self,
1415 location: &'ast SrcSpan,
1416 name_location: &'ast SrcSpan,
1417 module: &'ast Option<(EcoString, SrcSpan)>,
1418 name: &'ast EcoString,
1419 arguments: &'ast Vec<ast::TypeAst>,
1420 ) {
1421 let range = src_span_to_lsp_range(*location, self.line_numbers);
1422 if overlaps(self.params.range, range) {
1423 if let Some((module_alias, _)) = module {
1424 if let Some(import) = self.module.find_node(location.end).and_then(|node| {
1425 if let Located::Annotation { type_, .. } = node {
1426 if let Some((module, _)) = type_.named_type_name() {
1427 return self.get_module_import(&module, name, ast::Layer::Type);
1428 }
1429 }
1430 None
1431 }) {
1432 self.qualified_constructor = Some(QualifiedConstructor {
1433 import,
1434 module_aliased: import.as_name.is_some(),
1435 used_name: module_alias.clone(),
1436 constructor: name.clone(),
1437 layer: ast::Layer::Type,
1438 });
1439 }
1440 }
1441 }
1442 ast::visit::visit_type_ast_constructor(
1443 self,
1444 location,
1445 name_location,
1446 module,
1447 name,
1448 arguments,
1449 );
1450 }
1451
1452 fn visit_typed_expr_module_select(
1453 &mut self,
1454 location: &'ast SrcSpan,
1455 field_start: &'ast u32,
1456 type_: &'ast Arc<Type>,
1457 label: &'ast EcoString,
1458 module_name: &'ast EcoString,
1459 module_alias: &'ast EcoString,
1460 constructor: &'ast ModuleValueConstructor,
1461 ) {
1462 // When hovering over a Record Value Constructor, we want to expand the source span to
1463 // include the module name:
1464 // option.Some
1465 // ↑
1466 // This allows us to offer a code action when hovering over the module name.
1467 let range = src_span_to_lsp_range(*location, self.line_numbers);
1468 if overlaps(self.params.range, range) {
1469 if let ModuleValueConstructor::Record {
1470 name: constructor_name,
1471 ..
1472 } = constructor
1473 {
1474 if let Some(import) =
1475 self.get_module_import(module_name, constructor_name, ast::Layer::Value)
1476 {
1477 self.qualified_constructor = Some(QualifiedConstructor {
1478 import,
1479 module_aliased: import.as_name.is_some(),
1480 used_name: module_alias.clone(),
1481 constructor: constructor_name.clone(),
1482 layer: ast::Layer::Value,
1483 });
1484 }
1485 }
1486 }
1487 ast::visit::visit_typed_expr_module_select(
1488 self,
1489 location,
1490 field_start,
1491 type_,
1492 label,
1493 module_name,
1494 module_alias,
1495 constructor,
1496 )
1497 }
1498
1499 fn visit_typed_pattern_constructor(
1500 &mut self,
1501 location: &'ast SrcSpan,
1502 name_location: &'ast SrcSpan,
1503 name: &'ast EcoString,
1504 arguments: &'ast Vec<CallArg<TypedPattern>>,
1505 module: &'ast Option<(EcoString, SrcSpan)>,
1506 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
1507 spread: &'ast Option<SrcSpan>,
1508 type_: &'ast Arc<Type>,
1509 ) {
1510 let range = src_span_to_lsp_range(*location, self.line_numbers);
1511 if overlaps(self.params.range, range) {
1512 if let Some((module_alias, _)) = module {
1513 if let analyse::Inferred::Known(constructor) = constructor {
1514 if let Some(import) =
1515 self.get_module_import(&constructor.module, name, ast::Layer::Value)
1516 {
1517 self.qualified_constructor = Some(QualifiedConstructor {
1518 import,
1519 module_aliased: import.as_name.is_some(),
1520 used_name: module_alias.clone(),
1521 constructor: name.clone(),
1522 layer: ast::Layer::Value,
1523 });
1524 }
1525 }
1526 }
1527 }
1528 ast::visit::visit_typed_pattern_constructor(
1529 self,
1530 location,
1531 name_location,
1532 name,
1533 arguments,
1534 module,
1535 constructor,
1536 spread,
1537 type_,
1538 );
1539 }
1540}
1541
1542pub struct QualifiedToUnqualifiedImportSecondPass<'a> {
1543 module: &'a Module,
1544 params: &'a CodeActionParams,
1545 edits: TextEdits<'a>,
1546 qualified_constructor: QualifiedConstructor<'a>,
1547}
1548
1549impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> {
1550 pub fn new(
1551 module: &'a Module,
1552 params: &'a CodeActionParams,
1553 line_numbers: &'a LineNumbers,
1554 qualified_constructor: QualifiedConstructor<'a>,
1555 ) -> Self {
1556 Self {
1557 module,
1558 params,
1559 edits: TextEdits::new(line_numbers),
1560 qualified_constructor,
1561 }
1562 }
1563
1564 pub fn code_actions(mut self) -> Vec<CodeAction> {
1565 self.visit_typed_module(&self.module.ast);
1566 if self.edits.edits.is_empty() {
1567 return vec![];
1568 }
1569 self.edit_import();
1570 let mut action = Vec::with_capacity(1);
1571 CodeActionBuilder::new(&format!(
1572 "Unqualify {}.{}",
1573 self.qualified_constructor.used_name, self.qualified_constructor.constructor
1574 ))
1575 .kind(CodeActionKind::REFACTOR)
1576 .changes(self.params.text_document.uri.clone(), self.edits.edits)
1577 .preferred(false)
1578 .push_to(&mut action);
1579 action
1580 }
1581
1582 fn remove_module_qualifier(&mut self, location: SrcSpan) {
1583 self.edits.delete(SrcSpan {
1584 start: location.start,
1585 end: location.start + self.qualified_constructor.used_name.len() as u32 + 1, // plus .
1586 })
1587 }
1588
1589 fn edit_import(&mut self) {
1590 let QualifiedConstructor {
1591 constructor,
1592 layer,
1593 import,
1594 ..
1595 } = &self.qualified_constructor;
1596 let is_imported = if layer.is_value() {
1597 import
1598 .unqualified_values
1599 .iter()
1600 .any(|value| value.used_name() == constructor)
1601 } else {
1602 import
1603 .unqualified_types
1604 .iter()
1605 .any(|type_| type_.used_name() == constructor)
1606 };
1607 if is_imported {
1608 return;
1609 }
1610 let (insert_pos, new_text) = self.determine_insert_position_and_text();
1611 let span = SrcSpan::new(insert_pos, insert_pos);
1612 self.edits.replace(span, new_text);
1613 }
1614
1615 fn find_last_char_before_closing_brace(&self) -> Option<(usize, char)> {
1616 let QualifiedConstructor {
1617 import: Import { location, .. },
1618 ..
1619 } = self.qualified_constructor;
1620 let import_code = self.get_import_code();
1621 let closing_brace_pos = import_code.rfind('}')?;
1622
1623 let bytes = import_code.as_bytes();
1624 let mut pos = closing_brace_pos;
1625 while pos > 0 {
1626 pos -= 1;
1627 let c = (*bytes.get(pos)?) as char;
1628 if c.is_whitespace() {
1629 continue;
1630 }
1631 if c == '{' {
1632 break;
1633 }
1634 return Some((location.start as usize + pos, c));
1635 }
1636 None
1637 }
1638
1639 fn get_import_code(&self) -> &str {
1640 let QualifiedConstructor {
1641 import: Import { location, .. },
1642 ..
1643 } = self.qualified_constructor;
1644 self.module
1645 .code
1646 .get(location.start as usize..location.end as usize)
1647 .expect("import not found")
1648 }
1649
1650 fn determine_insert_position_and_text(&self) -> (u32, String) {
1651 let QualifiedConstructor { module_aliased, .. } = &self.qualified_constructor;
1652
1653 let name = self.qualified_constructor.constructor_import();
1654 let import_code = self.get_import_code();
1655 let has_brace = import_code.contains('}');
1656
1657 if has_brace {
1658 self.insert_into_braced_import(name)
1659 } else {
1660 self.insert_into_unbraced_import(name, *module_aliased)
1661 }
1662 }
1663
1664 // Handle inserting into an unbraced import
1665 fn insert_into_unbraced_import(&self, name: String, module_aliased: bool) -> (u32, String) {
1666 let QualifiedConstructor {
1667 import: Import { location, .. },
1668 ..
1669 } = self.qualified_constructor;
1670 if !module_aliased {
1671 // Case: import module
1672 (location.end, format!(".{{{}}}", name))
1673 } else {
1674 // Case: import module as alias
1675 let import_code = &self.get_import_code();
1676 let as_pos = import_code
1677 .find(" as ")
1678 .expect("Expected ' as ' in import statement");
1679 let before_as_pos = import_code
1680 .get(..as_pos)
1681 .and_then(|s| s.rfind(|c: char| !c.is_whitespace()))
1682 .map(|pos| location.start as usize + pos + 1)
1683 .expect("Expected non-whitespace character before ' as '");
1684 (before_as_pos as u32, format!(".{{{}}}", name))
1685 }
1686 }
1687
1688 // Handle inserting into a braced import
1689 fn insert_into_braced_import(&self, name: String) -> (u32, String) {
1690 let QualifiedConstructor {
1691 import: Import { location, .. },
1692 ..
1693 } = self.qualified_constructor;
1694 if let Some((pos, c)) = self.find_last_char_before_closing_brace() {
1695 // Case: import module.{Existing, } (as alias)
1696 if c == ',' {
1697 (pos as u32 + 1, format!(" {}", name))
1698 } else {
1699 // Case: import module.{Existing} (as alias)
1700 (pos as u32 + 1, format!(", {}", name))
1701 }
1702 } else {
1703 // Case: import module.{} (as alias)
1704 let import_code = self.get_import_code();
1705 let left_brace_pos = import_code
1706 .find('{')
1707 .map(|pos| location.start as usize + pos)
1708 .expect("Expected '{' in import statement");
1709 (left_brace_pos as u32 + 1, name)
1710 }
1711 }
1712}
1713
1714impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportSecondPass<'ast> {
1715 fn visit_typed_expr_fn(
1716 &mut self,
1717 location: &'ast SrcSpan,
1718 type_: &'ast Arc<Type>,
1719 kind: &'ast FunctionLiteralKind,
1720 args: &'ast [TypedArg],
1721 body: &'ast Vec1<TypedStatement>,
1722 return_annotation: &'ast Option<ast::TypeAst>,
1723 ) {
1724 for arg in args {
1725 if let Some(annotation) = &arg.annotation {
1726 self.visit_type_ast(annotation);
1727 }
1728 }
1729 if let Some(return_) = return_annotation {
1730 self.visit_type_ast(return_);
1731 }
1732 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
1733 }
1734
1735 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
1736 for arg in &fun.arguments {
1737 if let Some(annotation) = &arg.annotation {
1738 self.visit_type_ast(annotation);
1739 }
1740 }
1741
1742 if let Some(return_annotation) = &fun.return_annotation {
1743 self.visit_type_ast(return_annotation);
1744 }
1745 ast::visit::visit_typed_function(self, fun);
1746 }
1747
1748 fn visit_type_ast_constructor(
1749 &mut self,
1750 location: &'ast SrcSpan,
1751 name_location: &'ast SrcSpan,
1752 module: &'ast Option<(EcoString, SrcSpan)>,
1753 name: &'ast EcoString,
1754 arguments: &'ast Vec<ast::TypeAst>,
1755 ) {
1756 if let Some((module_name, _)) = module {
1757 let QualifiedConstructor {
1758 used_name,
1759 constructor,
1760 layer,
1761 ..
1762 } = &self.qualified_constructor;
1763
1764 if !layer.is_value() && used_name == module_name && name == constructor {
1765 self.remove_module_qualifier(*location);
1766 }
1767 }
1768 ast::visit::visit_type_ast_constructor(
1769 self,
1770 location,
1771 name_location,
1772 module,
1773 name,
1774 arguments,
1775 );
1776 }
1777
1778 fn visit_typed_expr_module_select(
1779 &mut self,
1780 location: &'ast SrcSpan,
1781 field_start: &'ast u32,
1782 type_: &'ast Arc<Type>,
1783 label: &'ast EcoString,
1784 module_name: &'ast EcoString,
1785 module_alias: &'ast EcoString,
1786 constructor: &'ast ModuleValueConstructor,
1787 ) {
1788 if let ModuleValueConstructor::Record { name, .. } = constructor {
1789 let QualifiedConstructor {
1790 used_name,
1791 constructor,
1792 layer,
1793 ..
1794 } = &self.qualified_constructor;
1795
1796 if layer.is_value() && used_name == module_alias && name == constructor {
1797 self.remove_module_qualifier(*location);
1798 }
1799 }
1800 ast::visit::visit_typed_expr_module_select(
1801 self,
1802 location,
1803 field_start,
1804 type_,
1805 label,
1806 module_name,
1807 module_alias,
1808 constructor,
1809 )
1810 }
1811
1812 fn visit_typed_pattern_constructor(
1813 &mut self,
1814 location: &'ast SrcSpan,
1815 name_location: &'ast SrcSpan,
1816 name: &'ast EcoString,
1817 arguments: &'ast Vec<CallArg<TypedPattern>>,
1818 module: &'ast Option<(EcoString, SrcSpan)>,
1819 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
1820 spread: &'ast Option<SrcSpan>,
1821 type_: &'ast Arc<Type>,
1822 ) {
1823 if let Some((module_alias, _)) = module {
1824 if let analyse::Inferred::Known(_) = constructor {
1825 let QualifiedConstructor {
1826 used_name,
1827 constructor,
1828 layer,
1829 ..
1830 } = &self.qualified_constructor;
1831
1832 if layer.is_value() && used_name == module_alias && name == constructor {
1833 self.remove_module_qualifier(*location);
1834 }
1835 }
1836 }
1837 ast::visit::visit_typed_pattern_constructor(
1838 self,
1839 location,
1840 name_location,
1841 name,
1842 arguments,
1843 module,
1844 constructor,
1845 spread,
1846 type_,
1847 );
1848 }
1849}
1850
1851pub fn code_action_convert_qualified_constructor_to_unqualified(
1852 module: &Module,
1853 line_numbers: &LineNumbers,
1854 params: &CodeActionParams,
1855 actions: &mut Vec<CodeAction>,
1856) {
1857 let mut first_pass = QualifiedToUnqualifiedImportFirstPass::new(module, params, line_numbers);
1858 first_pass.visit_typed_module(&module.ast);
1859 let Some(qualified_constructor) = first_pass.qualified_constructor else {
1860 return;
1861 };
1862 let second_pass = QualifiedToUnqualifiedImportSecondPass::new(
1863 module,
1864 params,
1865 line_numbers,
1866 qualified_constructor,
1867 );
1868 let new_actions = second_pass.code_actions();
1869 actions.extend(new_actions);
1870}
1871
1872struct UnqualifiedConstructor<'a> {
1873 module_name: EcoString,
1874 constructor: &'a ast::UnqualifiedImport,
1875 layer: ast::Layer,
1876}
1877
1878struct UnqualifiedToQualifiedImportFirstPass<'a> {
1879 module: &'a Module,
1880 params: &'a CodeActionParams,
1881 line_numbers: &'a LineNumbers,
1882 unqualified_constructor: Option<UnqualifiedConstructor<'a>>,
1883}
1884
1885impl<'a> UnqualifiedToQualifiedImportFirstPass<'a> {
1886 fn new(
1887 module: &'a Module,
1888 params: &'a CodeActionParams,
1889 line_numbers: &'a LineNumbers,
1890 ) -> Self {
1891 Self {
1892 module,
1893 params,
1894 line_numbers,
1895 unqualified_constructor: None,
1896 }
1897 }
1898
1899 fn get_module_import_from_value_constructor(
1900 &mut self,
1901 module_name: &EcoString,
1902 constructor_name: &EcoString,
1903 ) {
1904 self.unqualified_constructor =
1905 self.module
1906 .ast
1907 .definitions
1908 .iter()
1909 .find_map(|definition| match definition {
1910 ast::Definition::Import(import) if import.module == *module_name => import
1911 .unqualified_values
1912 .iter()
1913 .find(|value| value.used_name() == constructor_name)
1914 .and_then(|value| {
1915 Some(UnqualifiedConstructor {
1916 constructor: value,
1917 module_name: import.used_name()?,
1918 layer: ast::Layer::Value,
1919 })
1920 }),
1921 _ => None,
1922 })
1923 }
1924
1925 fn get_module_import_from_type_constructor(&mut self, constructor_name: &EcoString) {
1926 self.unqualified_constructor =
1927 self.module
1928 .ast
1929 .definitions
1930 .iter()
1931 .find_map(|definition| match definition {
1932 ast::Definition::Import(import) => {
1933 if let Some(ty) = import
1934 .unqualified_types
1935 .iter()
1936 .find(|ty| ty.used_name() == constructor_name)
1937 {
1938 return Some(UnqualifiedConstructor {
1939 constructor: ty,
1940 module_name: import.used_name()?,
1941 layer: ast::Layer::Type,
1942 });
1943 }
1944 None
1945 }
1946 _ => None,
1947 })
1948 }
1949}
1950
1951impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportFirstPass<'ast> {
1952 fn visit_typed_expr_fn(
1953 &mut self,
1954 location: &'ast SrcSpan,
1955 type_: &'ast Arc<Type>,
1956 kind: &'ast FunctionLiteralKind,
1957 args: &'ast [TypedArg],
1958 body: &'ast Vec1<TypedStatement>,
1959 return_annotation: &'ast Option<ast::TypeAst>,
1960 ) {
1961 for arg in args {
1962 if let Some(annotation) = &arg.annotation {
1963 self.visit_type_ast(annotation);
1964 }
1965 }
1966 if let Some(return_) = return_annotation {
1967 self.visit_type_ast(return_);
1968 }
1969 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
1970 }
1971
1972 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
1973 for arg in &fun.arguments {
1974 if let Some(annotation) = &arg.annotation {
1975 self.visit_type_ast(annotation);
1976 }
1977 }
1978
1979 if let Some(return_annotation) = &fun.return_annotation {
1980 self.visit_type_ast(return_annotation);
1981 }
1982 ast::visit::visit_typed_function(self, fun);
1983 }
1984 fn visit_type_ast_constructor(
1985 &mut self,
1986 location: &'ast SrcSpan,
1987 name_location: &'ast SrcSpan,
1988 module: &'ast Option<(EcoString, SrcSpan)>,
1989 name: &'ast EcoString,
1990 arguments: &'ast Vec<ast::TypeAst>,
1991 ) {
1992 if module.is_none()
1993 && overlaps(
1994 self.params.range,
1995 src_span_to_lsp_range(*location, self.line_numbers),
1996 )
1997 {
1998 self.get_module_import_from_type_constructor(name);
1999 }
2000
2001 ast::visit::visit_type_ast_constructor(
2002 self,
2003 location,
2004 name_location,
2005 module,
2006 name,
2007 arguments,
2008 );
2009 }
2010
2011 fn visit_typed_expr_var(
2012 &mut self,
2013 location: &'ast SrcSpan,
2014 constructor: &'ast ValueConstructor,
2015 name: &'ast EcoString,
2016 ) {
2017 let range = src_span_to_lsp_range(*location, self.line_numbers);
2018 if overlaps(self.params.range, range) {
2019 if let Some(module_name) = match &constructor.variant {
2020 type_::ValueConstructorVariant::ModuleConstant { module, .. }
2021 | type_::ValueConstructorVariant::ModuleFn { module, .. }
2022 | type_::ValueConstructorVariant::Record { module, .. } => Some(module),
2023
2024 type_::ValueConstructorVariant::LocalVariable { .. }
2025 | type_::ValueConstructorVariant::LocalConstant { .. } => None,
2026 } {
2027 self.get_module_import_from_value_constructor(module_name, name);
2028 }
2029 }
2030 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2031 }
2032
2033 fn visit_typed_pattern_constructor(
2034 &mut self,
2035 location: &'ast SrcSpan,
2036 name_location: &'ast SrcSpan,
2037 name: &'ast EcoString,
2038 arguments: &'ast Vec<CallArg<TypedPattern>>,
2039 module: &'ast Option<(EcoString, SrcSpan)>,
2040 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
2041 spread: &'ast Option<SrcSpan>,
2042 type_: &'ast Arc<Type>,
2043 ) {
2044 if module.is_none()
2045 && overlaps(
2046 self.params.range,
2047 src_span_to_lsp_range(*location, self.line_numbers),
2048 )
2049 {
2050 if let analyse::Inferred::Known(constructor) = constructor {
2051 self.get_module_import_from_value_constructor(&constructor.module, name);
2052 }
2053 }
2054
2055 ast::visit::visit_typed_pattern_constructor(
2056 self,
2057 location,
2058 name_location,
2059 name,
2060 arguments,
2061 module,
2062 constructor,
2063 spread,
2064 type_,
2065 );
2066 }
2067}
2068
2069struct UnqualifiedToQualifiedImportSecondPass<'a> {
2070 module: &'a Module,
2071 params: &'a CodeActionParams,
2072 edits: TextEdits<'a>,
2073 unqualified_constructor: UnqualifiedConstructor<'a>,
2074}
2075
2076impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> {
2077 pub fn new(
2078 module: &'a Module,
2079 params: &'a CodeActionParams,
2080 line_numbers: &'a LineNumbers,
2081 unqualified_constructor: UnqualifiedConstructor<'a>,
2082 ) -> Self {
2083 Self {
2084 module,
2085 params,
2086 edits: TextEdits::new(line_numbers),
2087 unqualified_constructor,
2088 }
2089 }
2090
2091 fn add_module_qualifier(&mut self, location: SrcSpan) {
2092 let src_span = SrcSpan::new(
2093 location.start,
2094 location.start + self.unqualified_constructor.constructor.used_name().len() as u32,
2095 );
2096
2097 self.edits.replace(
2098 src_span,
2099 format!(
2100 "{}.{}",
2101 self.unqualified_constructor.module_name,
2102 self.unqualified_constructor.constructor.name
2103 ),
2104 );
2105 }
2106
2107 pub fn code_actions(mut self) -> Vec<CodeAction> {
2108 self.visit_typed_module(&self.module.ast);
2109 if self.edits.edits.is_empty() {
2110 return vec![];
2111 }
2112 self.edit_import();
2113 let mut action = Vec::with_capacity(1);
2114 let UnqualifiedConstructor {
2115 module_name,
2116 constructor,
2117 ..
2118 } = self.unqualified_constructor;
2119 CodeActionBuilder::new(&format!(
2120 "Qualify {} as {}.{}",
2121 constructor.used_name(),
2122 module_name,
2123 constructor.name,
2124 ))
2125 .kind(CodeActionKind::REFACTOR)
2126 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2127 .preferred(false)
2128 .push_to(&mut action);
2129 action
2130 }
2131
2132 fn edit_import(&mut self) {
2133 let UnqualifiedConstructor {
2134 constructor:
2135 ast::UnqualifiedImport {
2136 location: constructor_import_span,
2137 ..
2138 },
2139 ..
2140 } = self.unqualified_constructor;
2141
2142 let mut last_char_pos = constructor_import_span.end as usize;
2143 while self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2144 last_char_pos += 1;
2145 }
2146 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(",") {
2147 last_char_pos += 1;
2148 }
2149 if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") {
2150 last_char_pos += 1;
2151 }
2152
2153 self.edits.delete(SrcSpan::new(
2154 constructor_import_span.start,
2155 last_char_pos as u32,
2156 ));
2157 }
2158}
2159
2160impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportSecondPass<'ast> {
2161 fn visit_typed_expr_fn(
2162 &mut self,
2163 location: &'ast SrcSpan,
2164 type_: &'ast Arc<Type>,
2165 kind: &'ast FunctionLiteralKind,
2166 args: &'ast [TypedArg],
2167 body: &'ast Vec1<TypedStatement>,
2168 return_annotation: &'ast Option<ast::TypeAst>,
2169 ) {
2170 for arg in args {
2171 if let Some(annotation) = &arg.annotation {
2172 self.visit_type_ast(annotation);
2173 }
2174 }
2175 if let Some(return_) = return_annotation {
2176 self.visit_type_ast(return_);
2177 }
2178 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
2179 }
2180
2181 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
2182 for arg in &fun.arguments {
2183 if let Some(annotation) = &arg.annotation {
2184 self.visit_type_ast(annotation);
2185 }
2186 }
2187
2188 if let Some(return_annotation) = &fun.return_annotation {
2189 self.visit_type_ast(return_annotation);
2190 }
2191 ast::visit::visit_typed_function(self, fun);
2192 }
2193
2194 fn visit_type_ast_constructor(
2195 &mut self,
2196 location: &'ast SrcSpan,
2197 name_location: &'ast SrcSpan,
2198 module: &'ast Option<(EcoString, SrcSpan)>,
2199 name: &'ast EcoString,
2200 arguments: &'ast Vec<ast::TypeAst>,
2201 ) {
2202 if module.is_none() {
2203 let UnqualifiedConstructor {
2204 constructor, layer, ..
2205 } = &self.unqualified_constructor;
2206 if !layer.is_value() && constructor.used_name() == name {
2207 self.add_module_qualifier(*location);
2208 }
2209 }
2210 ast::visit::visit_type_ast_constructor(
2211 self,
2212 location,
2213 name_location,
2214 module,
2215 name,
2216 arguments,
2217 );
2218 }
2219
2220 fn visit_typed_expr_var(
2221 &mut self,
2222 location: &'ast SrcSpan,
2223 constructor: &'ast ValueConstructor,
2224 name: &'ast EcoString,
2225 ) {
2226 let UnqualifiedConstructor {
2227 constructor: wanted_constructor,
2228 layer,
2229 ..
2230 } = &self.unqualified_constructor;
2231
2232 if layer.is_value()
2233 && wanted_constructor.used_name() == name
2234 && !constructor.is_local_variable()
2235 {
2236 self.add_module_qualifier(*location);
2237 }
2238 ast::visit::visit_typed_expr_var(self, location, constructor, name);
2239 }
2240
2241 fn visit_typed_pattern_constructor(
2242 &mut self,
2243 location: &'ast SrcSpan,
2244 name_location: &'ast SrcSpan,
2245 name: &'ast EcoString,
2246 arguments: &'ast Vec<CallArg<TypedPattern>>,
2247 module: &'ast Option<(EcoString, SrcSpan)>,
2248 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
2249 spread: &'ast Option<SrcSpan>,
2250 type_: &'ast Arc<Type>,
2251 ) {
2252 if module.is_none() {
2253 let UnqualifiedConstructor {
2254 constructor: wanted_constructor,
2255 layer,
2256 ..
2257 } = &self.unqualified_constructor;
2258 if layer.is_value() && wanted_constructor.used_name() == name {
2259 self.add_module_qualifier(*location);
2260 }
2261 }
2262 ast::visit::visit_typed_pattern_constructor(
2263 self,
2264 location,
2265 name_location,
2266 name,
2267 arguments,
2268 module,
2269 constructor,
2270 spread,
2271 type_,
2272 );
2273 }
2274}
2275
2276pub fn code_action_convert_unqualified_constructor_to_qualified(
2277 module: &Module,
2278 line_numbers: &LineNumbers,
2279 params: &CodeActionParams,
2280 actions: &mut Vec<CodeAction>,
2281) {
2282 let mut first_pass = UnqualifiedToQualifiedImportFirstPass::new(module, params, line_numbers);
2283 first_pass.visit_typed_module(&module.ast);
2284 let Some(unqualified_constructor) = first_pass.unqualified_constructor else {
2285 return;
2286 };
2287 let second_pass = UnqualifiedToQualifiedImportSecondPass::new(
2288 module,
2289 params,
2290 line_numbers,
2291 unqualified_constructor,
2292 );
2293 let new_actions = second_pass.code_actions();
2294 actions.extend(new_actions);
2295}
2296
2297/// Builder for code action to apply the convert from use action, turning a use
2298/// expression into a regular function call.
2299///
2300pub struct ConvertFromUse<'a> {
2301 module: &'a Module,
2302 params: &'a CodeActionParams,
2303 edits: TextEdits<'a>,
2304 selected_use: Option<&'a TypedUse>,
2305}
2306
2307impl<'a> ConvertFromUse<'a> {
2308 pub fn new(
2309 module: &'a Module,
2310 line_numbers: &'a LineNumbers,
2311 params: &'a CodeActionParams,
2312 ) -> Self {
2313 Self {
2314 module,
2315 params,
2316 edits: TextEdits::new(line_numbers),
2317 selected_use: None,
2318 }
2319 }
2320
2321 pub fn code_actions(mut self) -> Vec<CodeAction> {
2322 self.visit_typed_module(&self.module.ast);
2323
2324 let Some(use_) = self.selected_use else {
2325 return vec![];
2326 };
2327
2328 let TypedExpr::Call { args, fun, .. } = use_.call.as_ref() else {
2329 return vec![];
2330 };
2331
2332 // If the use callback we're desugaring is using labels, that means we
2333 // have to add the last argument's label when writing the callback;
2334 // otherwise, it would result in invalid code.
2335 //
2336 // use acc, item <- list.fold(over: list, from: 1)
2337 // todo
2338 //
2339 // Needs to be rewritten as:
2340 //
2341 // list.fold(over: list, from: 1, with: fn(acc, item) { ... })
2342 // ^^^^^ We cannot forget to add this label back!
2343 //
2344 let callback_label = if args.iter().any(|arg| arg.label.is_some()) {
2345 fun.field_map()
2346 .and_then(|field_map| field_map.missing_labels(args).last().cloned())
2347 .map(|label| eco_format!("{label}: "))
2348 .unwrap_or(EcoString::from(""))
2349 } else {
2350 EcoString::from("")
2351 };
2352
2353 // The use callback is not necessarily the last argument. If you have
2354 // the following function: `wibble(a a, b b) { todo }`
2355 // And use it like this: `use <- wibble(b: 1)`, the first argument `a`
2356 // is going to be the use callback, not the last one!
2357 let use_callback = args.iter().find(|arg| arg.is_use_implicit_callback());
2358 let Some(CallArg {
2359 implicit: Some(ImplicitCallArgOrigin::Use),
2360 value: TypedExpr::Fn { body, type_, .. },
2361 ..
2362 }) = use_callback
2363 else {
2364 return vec![];
2365 };
2366
2367 // If there's arguments on the left hand side of the function we extract
2368 // those so we can paste them back as the anonymous function arguments.
2369 let assignments = if type_.fn_arity().is_some_and(|arity| arity >= 1) {
2370 let assignments_range =
2371 use_.assignments_location.start as usize..use_.assignments_location.end as usize;
2372 self.module
2373 .code
2374 .get(assignments_range)
2375 .expect("use assignments")
2376 } else {
2377 ""
2378 };
2379
2380 // We first delete everything on the left hand side of use and the use
2381 // arrow.
2382 self.edits.delete(SrcSpan {
2383 start: use_.location.start,
2384 end: use_.right_hand_side_location.start,
2385 });
2386
2387 let use_line_end = use_.right_hand_side_location.end;
2388 let use_rhs_function_has_some_explicit_args = args
2389 .iter()
2390 .filter(|arg| !arg.is_use_implicit_callback())
2391 .peekable()
2392 .peek()
2393 .is_some();
2394
2395 let use_rhs_function_ends_with_closed_parentheses = self
2396 .module
2397 .code
2398 .get(use_line_end as usize - 1..use_line_end as usize)
2399 == Some(")");
2400
2401 let last_explicit_arg = args.iter().filter(|arg| !arg.is_implicit()).next_back();
2402 let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end);
2403
2404 // This is the piece of code between the end of the last argument and
2405 // the end of the use_expression:
2406 //
2407 // use <- wibble(a, b, )
2408 // ^^^^^ This piece right here, from `,` included
2409 // up to `)` excluded.
2410 //
2411 let text_after_last_argument = self
2412 .module
2413 .code
2414 .get(last_arg_end as usize..use_line_end as usize - 1);
2415 let use_rhs_has_comma_after_last_argument =
2416 text_after_last_argument.is_some_and(|code| code.contains(','));
2417 let needs_space_before_callback =
2418 text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' '));
2419
2420 if use_rhs_function_ends_with_closed_parentheses {
2421 // If the function on the right hand side of use ends with a closed
2422 // parentheses then we have to remove it and add it later at the end
2423 // of the anonymous function we're inserting.
2424 //
2425 // use <- wibble()
2426 // ^ To add the fn() we need to first remove this
2427 //
2428 // So here we write over the last closed parentheses to remove it.
2429 let callback_start = format!("{callback_label}fn({assignments}) {{");
2430 self.edits.replace(
2431 SrcSpan {
2432 start: use_line_end - 1,
2433 end: use_line_end,
2434 },
2435 // If the function on the rhs of use has other orguments besides
2436 // the implicit fn expression then we need to put a comma after
2437 // the last argument.
2438 if use_rhs_function_has_some_explicit_args && !use_rhs_has_comma_after_last_argument
2439 {
2440 format!(", {callback_start}")
2441 } else if needs_space_before_callback {
2442 format!(" {callback_start}")
2443 } else {
2444 callback_start.to_string()
2445 },
2446 )
2447 } else {
2448 // On the other hand, if the function on the right hand side doesn't
2449 // end with a closed parenthese then we have to manually add it.
2450 //
2451 // use <- wibble
2452 // ^ No parentheses
2453 //
2454 self.edits
2455 .insert(use_line_end, format!("(fn({}) {{", assignments))
2456 };
2457
2458 // Then we have to increase indentation for all the lines of the use
2459 // body.
2460 let first_fn_expression_range = self.edits.src_span_to_lsp_range(body.first().location());
2461 let use_body_range = self.edits.src_span_to_lsp_range(use_.call.location());
2462
2463 for line in first_fn_expression_range.start.line..=use_body_range.end.line {
2464 self.edits.edits.push(TextEdit {
2465 range: Range {
2466 start: Position { line, character: 0 },
2467 end: Position { line, character: 0 },
2468 },
2469 new_text: " ".to_string(),
2470 })
2471 }
2472
2473 let final_line_indentation = " ".repeat(use_body_range.start.character as usize);
2474 self.edits.insert(
2475 use_.call.location().end,
2476 format!("\n{final_line_indentation}}})"),
2477 );
2478
2479 let mut action = Vec::with_capacity(1);
2480 CodeActionBuilder::new("Convert from `use`")
2481 .kind(CodeActionKind::REFACTOR_REWRITE)
2482 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2483 .preferred(false)
2484 .push_to(&mut action);
2485 action
2486 }
2487}
2488
2489impl<'ast> ast::visit::Visit<'ast> for ConvertFromUse<'ast> {
2490 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
2491 let use_range = self.edits.src_span_to_lsp_range(use_.location);
2492
2493 // If the use expression is using patterns that are not just variable
2494 // assignments then we can't automatically rewrite it as it would result
2495 // in a syntax error as we can't pattern match in an anonymous function
2496 // head.
2497 // At the same time we can't safely add bindings inside the anonymous
2498 // function body by picking placeholder names as we'd risk shadowing
2499 // variables coming from the outer scope.
2500 // So we just skip those use expressions we can't safely rewrite!
2501 if within(self.params.range, use_range)
2502 && use_
2503 .assignments
2504 .iter()
2505 .all(|assignment| assignment.pattern.is_variable())
2506 {
2507 self.selected_use = Some(use_);
2508 }
2509
2510 // We still want to visit the use expression so that we always end up
2511 // picking the innermost, most relevant use under the cursor.
2512 self.visit_typed_expr(&use_.call);
2513 }
2514}
2515
2516/// Builder for code action to apply the convert to use action.
2517///
2518pub struct ConvertToUse<'a> {
2519 module: &'a Module,
2520 params: &'a CodeActionParams,
2521 edits: TextEdits<'a>,
2522 selected_call: Option<CallLocations>,
2523}
2524
2525/// All the locations we'll need to transform a function call into a use
2526/// expression.
2527///
2528struct CallLocations {
2529 call_span: SrcSpan,
2530 called_function_span: SrcSpan,
2531 callback_args_span: Option<SrcSpan>,
2532 arg_before_callback_span: Option<SrcSpan>,
2533 callback_body_span: SrcSpan,
2534}
2535
2536impl<'a> ConvertToUse<'a> {
2537 pub fn new(
2538 module: &'a Module,
2539 line_numbers: &'a LineNumbers,
2540 params: &'a CodeActionParams,
2541 ) -> Self {
2542 Self {
2543 module,
2544 params,
2545 edits: TextEdits::new(line_numbers),
2546 selected_call: None,
2547 }
2548 }
2549
2550 pub fn code_actions(mut self) -> Vec<CodeAction> {
2551 self.visit_typed_module(&self.module.ast);
2552
2553 let Some(CallLocations {
2554 call_span,
2555 called_function_span,
2556 callback_args_span,
2557 arg_before_callback_span,
2558 callback_body_span,
2559 }) = self.selected_call
2560 else {
2561 return vec![];
2562 };
2563
2564 // This is the nesting level of the `use` keyword we've inserted, we
2565 // want to move the entire body of the anonymous function to this level.
2566 let use_nesting_level = self.edits.src_span_to_lsp_range(call_span).start.character;
2567 let indentation = " ".repeat(use_nesting_level as usize);
2568
2569 // First we move the callback arguments to the left hand side of the
2570 // call and add the `use` keyword.
2571 let left_hand_side_text = if let Some(args_location) = callback_args_span {
2572 let args_start = args_location.start as usize;
2573 let args_end = args_location.end as usize;
2574 let args_text = self.module.code.get(args_start..args_end).expect("fn args");
2575 format!("use {args_text} <- ")
2576 } else {
2577 "use <- ".into()
2578 };
2579
2580 self.edits.insert(call_span.start, left_hand_side_text);
2581
2582 match arg_before_callback_span {
2583 // If the function call has no other arguments besides the callback then
2584 // we just have to remove the `fn(...) {` part.
2585 //
2586 // wibble(fn(...) { ... })
2587 // ^^^^^^^^^^ This goes from the end of the called function
2588 // To the start of the first thing in the anonymous
2589 // function's body.
2590 //
2591 None => self.edits.replace(
2592 SrcSpan::new(called_function_span.end, callback_body_span.start),
2593 format!("\n{indentation}"),
2594 ),
2595 // If it has other arguments we'll have to remove those and add a closed
2596 // parentheses too:
2597 //
2598 // wibble(1, 2, fn(...) { ... })
2599 // ^^^^^^^^^^^ We have to replace this with a `)`, it
2600 // goes from the end of the second-to-last
2601 // argument to the start of the first thing
2602 // in the anonymous function's body.
2603 //
2604 Some(arg_before_callback) => self.edits.replace(
2605 SrcSpan::new(arg_before_callback.end, callback_body_span.start),
2606 format!(")\n{indentation}"),
2607 ),
2608 };
2609
2610 // Then we have to remove two spaces of indentation from each line of
2611 // the callback function's body.
2612 let body_range = self.edits.src_span_to_lsp_range(callback_body_span);
2613 for line in body_range.start.line + 1..=body_range.end.line {
2614 self.edits.delete_range(Range::new(
2615 Position { line, character: 0 },
2616 Position { line, character: 2 },
2617 ))
2618 }
2619
2620 // Then we have to remove the anonymous fn closing `}` and the call's
2621 // closing `)`.
2622 self.edits
2623 .delete(SrcSpan::new(callback_body_span.end, call_span.end));
2624
2625 let mut action = Vec::with_capacity(1);
2626 CodeActionBuilder::new("Convert to `use`")
2627 .kind(CodeActionKind::REFACTOR_REWRITE)
2628 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2629 .preferred(false)
2630 .push_to(&mut action);
2631 action
2632 }
2633}
2634
2635impl<'ast> ast::visit::Visit<'ast> for ConvertToUse<'ast> {
2636 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
2637 // The cursor has to be inside the last statement of the function to
2638 // offer the code action.
2639 let last_statement_range = self.edits.src_span_to_lsp_range(fun.body.last().location());
2640 if within(self.params.range, last_statement_range) {
2641 if let Some(call_data) = turn_statement_into_use(fun.body.last()) {
2642 self.selected_call = Some(call_data);
2643 }
2644 }
2645
2646 ast::visit::visit_typed_function(self, fun)
2647 }
2648
2649 fn visit_typed_expr_fn(
2650 &mut self,
2651 location: &'ast SrcSpan,
2652 type_: &'ast Arc<Type>,
2653 kind: &'ast FunctionLiteralKind,
2654 args: &'ast [TypedArg],
2655 body: &'ast Vec1<TypedStatement>,
2656 return_annotation: &'ast Option<ast::TypeAst>,
2657 ) {
2658 // The cursor has to be inside the last statement of the body to
2659 // offer the code action.
2660 let last_statement_range = self.edits.src_span_to_lsp_range(body.last().location());
2661 if within(self.params.range, last_statement_range) {
2662 if let Some(call_data) = turn_statement_into_use(body.last()) {
2663 self.selected_call = Some(call_data);
2664 }
2665 }
2666
2667 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
2668 }
2669
2670 fn visit_typed_expr_block(
2671 &mut self,
2672 location: &'ast SrcSpan,
2673 statements: &'ast [TypedStatement],
2674 ) {
2675 let Some(last_statement) = statements.last() else {
2676 return;
2677 };
2678
2679 // The cursor has to be inside the last statement of the block to offer
2680 // the code action.
2681 let statement_range = self.edits.src_span_to_lsp_range(last_statement.location());
2682 if within(self.params.range, statement_range) {
2683 // Only the last statement of a block can be turned into a use!
2684 if let Some(selected_call) = turn_statement_into_use(last_statement) {
2685 self.selected_call = Some(selected_call)
2686 }
2687 }
2688
2689 ast::visit::visit_typed_expr_block(self, location, statements);
2690 }
2691}
2692
2693fn turn_statement_into_use(statement: &TypedStatement) -> Option<CallLocations> {
2694 match statement {
2695 ast::Statement::Use(_) | ast::Statement::Assignment(_) | ast::Statement::Assert(_) => None,
2696 ast::Statement::Expression(expression) => turn_expression_into_use(expression),
2697 }
2698}
2699
2700fn turn_expression_into_use(expr: &TypedExpr) -> Option<CallLocations> {
2701 let TypedExpr::Call {
2702 args,
2703 location: call_span,
2704 fun: called_function,
2705 ..
2706 } = expr
2707 else {
2708 return None;
2709 };
2710
2711 // The function arguments in the ast are reordered using function's field map.
2712 // This means that in the `args` array they might not appear in the same order
2713 // in which they are written by the user. Since the rest of the code relies
2714 // on their order in the written code we first have to sort them by their
2715 // source position.
2716 let args = args
2717 .iter()
2718 .sorted_by_key(|arg| arg.location.start)
2719 .collect_vec();
2720
2721 let CallArg {
2722 value: last_arg,
2723 implicit: None,
2724 ..
2725 } = args.last()?
2726 else {
2727 return None;
2728 };
2729
2730 let TypedExpr::Fn {
2731 args: callback_args,
2732 body,
2733 ..
2734 } = last_arg
2735 else {
2736 return None;
2737 };
2738
2739 let callback_args_span = match (callback_args.first(), callback_args.last()) {
2740 (Some(first), Some(last)) => Some(first.location.merge(&last.location)),
2741 _ => None,
2742 };
2743
2744 let arg_before_callback_span = if args.len() >= 2 {
2745 args.get(args.len() - 2).map(|call_arg| call_arg.location)
2746 } else {
2747 None
2748 };
2749
2750 let callback_body_span = body.first().location().merge(&body.last().last_location());
2751
2752 Some(CallLocations {
2753 call_span: *call_span,
2754 called_function_span: called_function.location(),
2755 callback_args_span,
2756 arg_before_callback_span,
2757 callback_body_span,
2758 })
2759}
2760
2761/// Builder for code action to extract expression into a variable.
2762/// The action will wrap the expression in a block if needed in the appropriate scope.
2763///
2764/// For using the code action on the following selection:
2765///
2766/// ```gleam
2767/// fn void() {
2768/// case result {
2769/// Ok(value) -> 2 * value + 1
2770/// // ^^^^^^^^^
2771/// Error(_) -> panic
2772/// }
2773/// }
2774/// ```
2775///
2776/// Will result:
2777///
2778/// ```gleam
2779/// fn void() {
2780/// case result {
2781/// Ok(value) -> {
2782/// let int = 2 * value
2783/// int + 1
2784/// }
2785/// Error(_) -> panic
2786/// }
2787/// }
2788/// ```
2789pub struct ExtractVariable<'a> {
2790 module: &'a Module,
2791 params: &'a CodeActionParams,
2792 edits: TextEdits<'a>,
2793 position: Option<ExtractVariablePosition>,
2794 selected_expression: Option<(SrcSpan, Arc<Type>)>,
2795 statement_before_selected_expression: Option<SrcSpan>,
2796 latest_statement: Option<SrcSpan>,
2797 to_be_wrapped: bool,
2798}
2799
2800/// The Position of the selected code
2801#[derive(PartialEq, Eq, Copy, Clone, Debug)]
2802enum ExtractVariablePosition {
2803 InsideCaptureBody,
2804 /// Full statements (i.e. assignments, `use`s, and simple expressions).
2805 TopLevelStatement,
2806 /// The call on the right hand side of a pipe `|>`.
2807 PipelineCall,
2808 /// The right hand side of the `->` in a case expression.
2809 InsideCaseClause,
2810 // A call argument. This can also be a `use` callback.
2811 CallArg,
2812}
2813
2814impl<'a> ExtractVariable<'a> {
2815 pub fn new(
2816 module: &'a Module,
2817 line_numbers: &'a LineNumbers,
2818 params: &'a CodeActionParams,
2819 ) -> Self {
2820 Self {
2821 module,
2822 params,
2823 edits: TextEdits::new(line_numbers),
2824 position: None,
2825 selected_expression: None,
2826 latest_statement: None,
2827 statement_before_selected_expression: None,
2828 to_be_wrapped: false,
2829 }
2830 }
2831
2832 pub fn code_actions(mut self) -> Vec<CodeAction> {
2833 self.visit_typed_module(&self.module.ast);
2834
2835 let (Some((expression_span, expression_type)), Some(insert_location)) = (
2836 self.selected_expression,
2837 self.statement_before_selected_expression,
2838 ) else {
2839 return vec![];
2840 };
2841
2842 let mut name_generator = NameGenerator::new();
2843 let variable_name = name_generator.generate_name_from_type(&expression_type);
2844
2845 let content = self
2846 .module
2847 .code
2848 .get(expression_span.start as usize..expression_span.end as usize)
2849 .expect("selected expression");
2850
2851 let range = self.edits.src_span_to_lsp_range(insert_location);
2852
2853 let indent_size =
2854 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
2855
2856 let mut indent = " ".repeat(indent_size);
2857
2858 // We insert the variable declaration
2859 // Wrap in a block if needed
2860 let mut insertion = format!("let {variable_name} = {content}");
2861 if self.to_be_wrapped {
2862 let line_end = self
2863 .edits
2864 .line_numbers
2865 .line_starts
2866 .get((range.end.line + 1) as usize)
2867 .expect("Line number should be valid");
2868
2869 self.edits.insert(*line_end, format!("{indent}}}\n"));
2870 indent += " ";
2871 insertion = format!("{{\n{indent}{insertion}");
2872 };
2873 self.edits
2874 .insert(insert_location.start, insertion + &format!("\n{indent}"));
2875 self.edits
2876 .replace(expression_span, String::from(variable_name));
2877
2878 let mut action = Vec::with_capacity(1);
2879 CodeActionBuilder::new("Extract variable")
2880 .kind(CodeActionKind::REFACTOR_EXTRACT)
2881 .changes(self.params.text_document.uri.clone(), self.edits.edits)
2882 .preferred(false)
2883 .push_to(&mut action);
2884 action
2885 }
2886
2887 fn at_position<F>(&mut self, position: ExtractVariablePosition, fun: F)
2888 where
2889 F: Fn(&mut Self),
2890 {
2891 self.at_optional_position(Some(position), fun);
2892 }
2893
2894 fn at_optional_position<F>(&mut self, position: Option<ExtractVariablePosition>, fun: F)
2895 where
2896 F: Fn(&mut Self),
2897 {
2898 let previous_statement = self.latest_statement;
2899 let previous_position = self.position;
2900 self.position = position;
2901 fun(self);
2902 self.position = previous_position;
2903 self.latest_statement = previous_statement;
2904 }
2905}
2906
2907impl<'ast> ast::visit::Visit<'ast> for ExtractVariable<'ast> {
2908 fn visit_typed_statement(&mut self, stmt: &'ast TypedStatement) {
2909 let range = self.edits.src_span_to_lsp_range(stmt.location());
2910 if !within(self.params.range, range) {
2911 self.latest_statement = Some(stmt.location());
2912 ast::visit::visit_typed_statement(self, stmt);
2913 return;
2914 }
2915
2916 match self.position {
2917 // A capture body is comprised of just a single expression statement
2918 // that is inserted by the compiler, we don't really want to put
2919 // anything before that; so in this case we avoid tracking it.
2920 Some(ExtractVariablePosition::InsideCaptureBody) => {}
2921 Some(ExtractVariablePosition::PipelineCall) => {
2922 // Insert above the pipeline start
2923 self.latest_statement = Some(stmt.location());
2924 }
2925 _ => {
2926 // Insert below the previous statement
2927 self.latest_statement = Some(stmt.location());
2928 self.statement_before_selected_expression = self.latest_statement;
2929 }
2930 }
2931
2932 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
2933 ast::visit::visit_typed_statement(this, stmt);
2934 });
2935 }
2936
2937 fn visit_typed_expr_pipeline(
2938 &mut self,
2939 location: &'ast SrcSpan,
2940 first_value: &'ast TypedPipelineAssignment,
2941 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
2942 finally: &'ast TypedExpr,
2943 finally_kind: &'ast PipelineAssignmentKind,
2944 ) {
2945 let expr_range = self.edits.src_span_to_lsp_range(*location);
2946 if !within(self.params.range, expr_range) {
2947 ast::visit::visit_typed_expr_pipeline(
2948 self,
2949 location,
2950 first_value,
2951 assignments,
2952 finally,
2953 finally_kind,
2954 );
2955 return;
2956 };
2957
2958 // When visiting the assignments or the final pipeline call we want to
2959 // keep track of out position so that we can avoid extracting those.
2960 let all_assignments =
2961 iter::once(first_value).chain(assignments.iter().map(|(assignment, _kind)| assignment));
2962
2963 for assignment in all_assignments {
2964 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
2965 this.visit_typed_pipeline_assignment(assignment);
2966 });
2967 }
2968
2969 self.at_position(ExtractVariablePosition::PipelineCall, |this| {
2970 this.visit_typed_expr(finally)
2971 });
2972 }
2973
2974 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
2975 let expr_location = expr.location();
2976 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
2977 if !within(self.params.range, expr_range) {
2978 ast::visit::visit_typed_expr(self, expr);
2979 return;
2980 }
2981
2982 // If the expression is a top level statement we don't want to extract
2983 // it into a variable. It would mean we would turn this:
2984 //
2985 // ```gleam
2986 // pub fn main() {
2987 // let wibble = 1
2988 // // ^ cursor here
2989 // }
2990 //
2991 // // into:
2992 //
2993 // pub fn main() {
2994 // let int = 1
2995 // let wibble = int
2996 // }
2997 // ```
2998 //
2999 // Not all that useful!
3000 //
3001 match self.position {
3002 Some(
3003 ExtractVariablePosition::TopLevelStatement | ExtractVariablePosition::PipelineCall,
3004 ) => {
3005 self.at_optional_position(None, |this| {
3006 ast::visit::visit_typed_expr(this, expr);
3007 });
3008 return;
3009 }
3010 Some(
3011 ExtractVariablePosition::InsideCaptureBody
3012 | ExtractVariablePosition::InsideCaseClause
3013 | ExtractVariablePosition::CallArg,
3014 )
3015 | None => {}
3016 }
3017
3018 match expr {
3019 // Expressions that don't make sense to extract
3020 TypedExpr::Panic { .. }
3021 | TypedExpr::Echo { .. }
3022 | TypedExpr::Block { .. }
3023 | TypedExpr::ModuleSelect { .. }
3024 | TypedExpr::Invalid { .. }
3025 | TypedExpr::Var { .. } => (),
3026
3027 TypedExpr::Int { location, .. }
3028 | TypedExpr::Float { location, .. }
3029 | TypedExpr::String { location, .. }
3030 | TypedExpr::Pipeline { location, .. }
3031 | TypedExpr::Fn { location, .. }
3032 | TypedExpr::Todo { location, .. }
3033 | TypedExpr::List { location, .. }
3034 | TypedExpr::Call { location, .. }
3035 | TypedExpr::BinOp { location, .. }
3036 | TypedExpr::Case { location, .. }
3037 | TypedExpr::RecordAccess { location, .. }
3038 | TypedExpr::Tuple { location, .. }
3039 | TypedExpr::TupleIndex { location, .. }
3040 | TypedExpr::BitArray { location, .. }
3041 | TypedExpr::RecordUpdate { location, .. }
3042 | TypedExpr::NegateBool { location, .. }
3043 | TypedExpr::NegateInt { location, .. } => {
3044 if let Some(ExtractVariablePosition::CallArg) = self.position {
3045 // Don't update latest statement, we don't want to insert the extracted
3046 // variable inside the parenthesis where the call argument is located.
3047 } else {
3048 self.statement_before_selected_expression = self.latest_statement;
3049 };
3050 self.selected_expression = Some((*location, expr.type_()));
3051 }
3052 }
3053
3054 ast::visit::visit_typed_expr(self, expr);
3055 }
3056
3057 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
3058 let range = self.edits.src_span_to_lsp_range(use_.call.location());
3059 if !within(self.params.range, range) {
3060 ast::visit::visit_typed_use(self, use_);
3061 return;
3062 }
3063
3064 // Insert code under the `use`
3065 self.statement_before_selected_expression = Some(use_.call.location());
3066 self.at_position(ExtractVariablePosition::TopLevelStatement, |this| {
3067 ast::visit::visit_typed_use(this, use_);
3068 });
3069 }
3070
3071 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
3072 let range = self.edits.src_span_to_lsp_range(clause.location());
3073 if !within(self.params.range, range) {
3074 ast::visit::visit_typed_clause(self, clause);
3075 return;
3076 }
3077
3078 // Insert code after the `->`
3079 self.latest_statement = Some(clause.then.location());
3080 self.to_be_wrapped = true;
3081 self.at_position(ExtractVariablePosition::InsideCaseClause, |this| {
3082 ast::visit::visit_typed_clause(this, clause);
3083 });
3084 }
3085
3086 fn visit_typed_expr_block(
3087 &mut self,
3088 location: &'ast SrcSpan,
3089 statements: &'ast [TypedStatement],
3090 ) {
3091 let range = self.edits.src_span_to_lsp_range(*location);
3092 if !within(self.params.range, range) {
3093 ast::visit::visit_typed_expr_block(self, location, statements);
3094 return;
3095 }
3096
3097 // Don't extract block as variable
3098 let mut position = self.position;
3099 if let Some(ExtractVariablePosition::InsideCaseClause) = position {
3100 position = None;
3101 self.to_be_wrapped = false;
3102 }
3103
3104 self.at_optional_position(position, |this| {
3105 ast::visit::visit_typed_expr_block(this, location, statements);
3106 });
3107 }
3108
3109 fn visit_typed_expr_fn(
3110 &mut self,
3111 location: &'ast SrcSpan,
3112 type_: &'ast Arc<Type>,
3113 kind: &'ast FunctionLiteralKind,
3114 args: &'ast [TypedArg],
3115 body: &'ast Vec1<TypedStatement>,
3116 return_annotation: &'ast Option<ast::TypeAst>,
3117 ) {
3118 let range = self.edits.src_span_to_lsp_range(*location);
3119 if !within(self.params.range, range) {
3120 ast::visit::visit_typed_expr_fn(
3121 self,
3122 location,
3123 type_,
3124 kind,
3125 args,
3126 body,
3127 return_annotation,
3128 );
3129 return;
3130 }
3131
3132 let position = match kind {
3133 // If a fn is a capture `int.wibble(1, _)` its body will consist of
3134 // just a single expression statement. When visiting we must record
3135 // we're inside a capture body.
3136 FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody),
3137 FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement),
3138 FunctionLiteralKind::Anonymous { .. } => self.position,
3139 };
3140
3141 self.at_optional_position(position, |this| {
3142 ast::visit::visit_typed_expr_fn(
3143 this,
3144 location,
3145 type_,
3146 kind,
3147 args,
3148 body,
3149 return_annotation,
3150 );
3151 });
3152 }
3153
3154 fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
3155 let range = self.edits.src_span_to_lsp_range(arg.location);
3156 if !within(self.params.range, range) {
3157 ast::visit::visit_typed_call_arg(self, arg);
3158 return;
3159 }
3160
3161 let position = if arg.is_use_implicit_callback() {
3162 Some(ExtractVariablePosition::TopLevelStatement)
3163 } else {
3164 Some(ExtractVariablePosition::CallArg)
3165 };
3166
3167 self.at_optional_position(position, |this| {
3168 ast::visit::visit_typed_call_arg(this, arg);
3169 });
3170 }
3171
3172 // We don't want to offer the action if the cursor is over some invalid
3173 // piece of code.
3174 fn visit_typed_expr_invalid(&mut self, location: &'ast SrcSpan, _type_: &'ast Arc<Type>) {
3175 let invalid_range = self.edits.src_span_to_lsp_range(*location);
3176 if within(self.params.range, invalid_range) {
3177 self.selected_expression = None;
3178 }
3179 }
3180}
3181
3182/// Builder for code action to convert a literal use into a const.
3183///
3184/// For using the code action on each of the following lines:
3185///
3186/// ```gleam
3187/// fn void() {
3188/// let var = [1, 2, 3]
3189/// let res = function("Statement", var)
3190/// }
3191/// ```
3192///
3193/// Both value literals will become:
3194///
3195/// ```gleam
3196/// const var = [1, 2, 3]
3197/// const string = "Statement"
3198///
3199/// fn void() {
3200/// let res = function(string, var)
3201/// }
3202/// ```
3203pub struct ExtractConstant<'a> {
3204 module: &'a Module,
3205 params: &'a CodeActionParams,
3206 edits: TextEdits<'a>,
3207 /// The whole selected expression
3208 selected_expression: Option<SrcSpan>,
3209 /// The location of the start of the function containing the expression
3210 container_function_start: Option<u32>,
3211 /// The variant of the extractable expression being extracted (if any)
3212 variant_of_extractable: Option<ExtractableToConstant>,
3213 /// The name of the newly created constant
3214 name_to_use: Option<EcoString>,
3215 /// The right hand side expression of the newly created constant
3216 value_to_use: Option<EcoString>,
3217}
3218
3219/// Used when an expression can be extracted to a constant
3220enum ExtractableToConstant {
3221 /// Used for collections and operator uses. This means that elements
3222 /// inside, are also extractable as constants.
3223 ComposedValue,
3224 /// Used for single values. Literals in Gleam can be Ints, Floats, Strings
3225 /// and type variants (not records).
3226 SingleValue,
3227 /// Used for whole variable assignments. If the right hand side of the
3228 /// expression can be extracted, the whole expression extracted and use the
3229 /// local variable as a constant.
3230 Assignment,
3231}
3232
3233fn can_be_constant(
3234 module: &Module,
3235 expr: &TypedExpr,
3236 module_constants: Option<&HashSet<&EcoString>>,
3237) -> bool {
3238 // We pass the `module_constants` on recursion to not compute them each time
3239 let mc = match module_constants {
3240 None => &module
3241 .ast
3242 .definitions
3243 .iter()
3244 .filter_map(|definition| match definition {
3245 ast::Definition::ModuleConstant(module_constant) => Some(&module_constant.name),
3246
3247 ast::Definition::Function(_)
3248 | ast::Definition::TypeAlias(_)
3249 | ast::Definition::CustomType(_)
3250 | ast::Definition::Import(_) => None,
3251 })
3252 .collect(),
3253 Some(mc) => mc,
3254 };
3255
3256 match expr {
3257 // Attempt to extract whole list as long as it's comprised of only literals
3258 TypedExpr::List { elements, tail, .. } => {
3259 elements
3260 .iter()
3261 .all(|element| can_be_constant(module, element, Some(mc)))
3262 && tail.is_none()
3263 }
3264
3265 // Attempt to extract whole bit array as long as it's made up of literals
3266 TypedExpr::BitArray { segments, .. } => {
3267 segments
3268 .iter()
3269 .all(|segment| can_be_constant(module, &segment.value, Some(mc)))
3270 && segments.iter().all(|segment| {
3271 segment.options.iter().all(|option| match option {
3272 ast::BitArrayOption::Size { value, .. } => {
3273 can_be_constant(module, value, Some(mc))
3274 }
3275
3276 ast::BitArrayOption::Bytes { .. }
3277 | ast::BitArrayOption::Int { .. }
3278 | ast::BitArrayOption::Float { .. }
3279 | ast::BitArrayOption::Bits { .. }
3280 | ast::BitArrayOption::Utf8 { .. }
3281 | ast::BitArrayOption::Utf16 { .. }
3282 | ast::BitArrayOption::Utf32 { .. }
3283 | ast::BitArrayOption::Utf8Codepoint { .. }
3284 | ast::BitArrayOption::Utf16Codepoint { .. }
3285 | ast::BitArrayOption::Utf32Codepoint { .. }
3286 | ast::BitArrayOption::Signed { .. }
3287 | ast::BitArrayOption::Unsigned { .. }
3288 | ast::BitArrayOption::Big { .. }
3289 | ast::BitArrayOption::Little { .. }
3290 | ast::BitArrayOption::Native { .. }
3291 | ast::BitArrayOption::Unit { .. } => true,
3292 })
3293 })
3294 }
3295
3296 // Attempt to extract whole tuple as long as it's comprised of only literals
3297 TypedExpr::Tuple { elements, .. } => elements
3298 .iter()
3299 .all(|element| can_be_constant(module, element, Some(mc))),
3300
3301 // Extract literals directly
3302 TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true,
3303
3304 // Extract non-record types directly
3305 TypedExpr::Var {
3306 constructor, name, ..
3307 } => {
3308 matches!(
3309 constructor.variant,
3310 type_::ValueConstructorVariant::Record { arity: 0, .. }
3311 ) || mc.contains(name)
3312 }
3313
3314 // Extract record types as long as arguments can be constant
3315 TypedExpr::Call { args, fun, .. } => {
3316 fun.is_record_builder()
3317 && args
3318 .iter()
3319 .all(|arg| can_be_constant(module, &arg.value, module_constants))
3320 }
3321
3322 // Extract concat binary operation if both sides can be constants
3323 TypedExpr::BinOp {
3324 name, left, right, ..
3325 } => {
3326 matches!(name, ast::BinOp::Concatenate)
3327 && can_be_constant(module, left, Some(mc))
3328 && can_be_constant(module, right, Some(mc))
3329 }
3330
3331 TypedExpr::Block { .. }
3332 | TypedExpr::Pipeline { .. }
3333 | TypedExpr::Fn { .. }
3334 | TypedExpr::Case { .. }
3335 | TypedExpr::RecordAccess { .. }
3336 | TypedExpr::ModuleSelect { .. }
3337 | TypedExpr::TupleIndex { .. }
3338 | TypedExpr::Todo { .. }
3339 | TypedExpr::Panic { .. }
3340 | TypedExpr::Echo { .. }
3341 | TypedExpr::RecordUpdate { .. }
3342 | TypedExpr::NegateBool { .. }
3343 | TypedExpr::NegateInt { .. }
3344 | TypedExpr::Invalid { .. } => false,
3345 }
3346}
3347
3348/// Takes the list of already existing constants and functions and creates a
3349/// name that doesn't conflict with them
3350///
3351fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString {
3352 let mut name_generator = NameGenerator::new();
3353 let already_taken_names = VariablesNames {
3354 names: module
3355 .ast
3356 .definitions
3357 .iter()
3358 .filter_map(|definition| match definition {
3359 ast::Definition::ModuleConstant(constant) => Some(constant.name.clone()),
3360 ast::Definition::Function(function) => function.name.as_ref().map(|n| n.1.clone()),
3361
3362 ast::Definition::TypeAlias(_)
3363 | ast::Definition::CustomType(_)
3364 | ast::Definition::Import(_) => None,
3365 })
3366 .collect(),
3367 };
3368 name_generator.reserve_variable_names(already_taken_names);
3369
3370 name_generator.generate_name_from_type(&expr.type_())
3371}
3372
3373impl<'a> ExtractConstant<'a> {
3374 pub fn new(
3375 module: &'a Module,
3376 line_numbers: &'a LineNumbers,
3377 params: &'a CodeActionParams,
3378 ) -> Self {
3379 Self {
3380 module,
3381 params,
3382 edits: TextEdits::new(line_numbers),
3383 selected_expression: None,
3384 container_function_start: None,
3385 variant_of_extractable: None,
3386 name_to_use: None,
3387 value_to_use: None,
3388 }
3389 }
3390
3391 pub fn code_actions(mut self) -> Vec<CodeAction> {
3392 self.visit_typed_module(&self.module.ast);
3393
3394 let (
3395 Some(expr_span),
3396 Some(function_start),
3397 Some(type_of_extractable),
3398 Some(new_const_name),
3399 Some(const_value),
3400 ) = (
3401 self.selected_expression,
3402 self.container_function_start,
3403 self.variant_of_extractable,
3404 self.name_to_use,
3405 self.value_to_use,
3406 )
3407 else {
3408 return vec![];
3409 };
3410
3411 // We insert the constant declaration
3412 self.edits.insert(
3413 function_start,
3414 format!("const {new_const_name} = {const_value}\n\n"),
3415 );
3416
3417 // We remove or replace the selected expression
3418 match type_of_extractable {
3419 // The whole expression is deleted for assignments
3420 ExtractableToConstant::Assignment => {
3421 let range = self
3422 .edits
3423 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
3424
3425 let indent_size =
3426 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
3427
3428 let expr_span_with_new_line = SrcSpan {
3429 // We remove leading indentation + 1 to remove the newline with it
3430 start: expr_span.start - (indent_size as u32 + 1),
3431 end: expr_span.end,
3432 };
3433 self.edits.delete(expr_span_with_new_line);
3434 }
3435
3436 // Only right hand side is replaced for collection or values
3437 ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => {
3438 self.edits.replace(expr_span, String::from(new_const_name));
3439 }
3440 }
3441
3442 let mut action = Vec::with_capacity(1);
3443 CodeActionBuilder::new("Extract constant")
3444 .kind(CodeActionKind::REFACTOR_EXTRACT)
3445 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3446 .preferred(false)
3447 .push_to(&mut action);
3448 action
3449 }
3450}
3451
3452impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> {
3453 /// To get the position of the function containing the value or assignment
3454 /// to extract
3455 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
3456 let fun_location = fun.location;
3457 let fun_range = self.edits.src_span_to_lsp_range(SrcSpan {
3458 start: fun_location.start,
3459 end: fun.end_position,
3460 });
3461
3462 if !within(self.params.range, fun_range) {
3463 return;
3464 }
3465 self.container_function_start = Some(fun.location.start);
3466
3467 ast::visit::visit_typed_function(self, fun);
3468 }
3469
3470 /// To extract the whole assignment
3471 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
3472 let expr_location = assignment.location;
3473
3474 // We only offer this code action for extracting the whole assignment
3475 // between `let` and `=`.
3476 let pattern_location = assignment.pattern.location();
3477 let location = SrcSpan::new(assignment.location.start, pattern_location.end);
3478 let code_action_range = self.edits.src_span_to_lsp_range(location);
3479
3480 if !within(self.params.range, code_action_range) {
3481 ast::visit::visit_typed_assignment(self, assignment);
3482 return;
3483 }
3484
3485 // Has to be variable because patterns can't be constants.
3486 if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None)
3487 {
3488 self.variant_of_extractable = Some(ExtractableToConstant::Assignment);
3489 self.selected_expression = Some(expr_location);
3490 self.name_to_use = match &assignment.pattern {
3491 Pattern::Variable { name, .. } => Some(name.clone()),
3492 _ => None,
3493 };
3494 self.value_to_use = Some(EcoString::from(
3495 self.module
3496 .code
3497 .get(
3498 (assignment.value.location().start as usize)
3499 ..(assignment.location.end as usize),
3500 )
3501 .expect("selected expression"),
3502 ));
3503 }
3504 }
3505
3506 /// To extract only the literal
3507 fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
3508 let expr_location = expr.location();
3509 let expr_range = self.edits.src_span_to_lsp_range(expr_location);
3510
3511 if !within(self.params.range, expr_range) {
3512 ast::visit::visit_typed_expr(self, expr);
3513 return;
3514 }
3515
3516 // Keep going down recursively if:
3517 // - It's no extractable has been found yet (`None`).
3518 // - It's a collection, which may or may not contain a value that can
3519 // be extracted.
3520 // - It's a binary operator, which may or may not operate on
3521 // extractable values.
3522 if matches!(
3523 self.variant_of_extractable,
3524 None | Some(ExtractableToConstant::ComposedValue)
3525 ) && can_be_constant(self.module, expr, None)
3526 {
3527 self.variant_of_extractable = match expr {
3528 TypedExpr::Var { .. }
3529 | TypedExpr::Int { .. }
3530 | TypedExpr::Float { .. }
3531 | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue),
3532
3533 TypedExpr::List { .. }
3534 | TypedExpr::Tuple { .. }
3535 | TypedExpr::BitArray { .. }
3536 | TypedExpr::BinOp { .. }
3537 | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue),
3538
3539 TypedExpr::Block { .. }
3540 | TypedExpr::Pipeline { .. }
3541 | TypedExpr::Fn { .. }
3542 | TypedExpr::Case { .. }
3543 | TypedExpr::RecordAccess { .. }
3544 | TypedExpr::ModuleSelect { .. }
3545 | TypedExpr::TupleIndex { .. }
3546 | TypedExpr::Todo { .. }
3547 | TypedExpr::Panic { .. }
3548 | TypedExpr::Echo { .. }
3549 | TypedExpr::RecordUpdate { .. }
3550 | TypedExpr::NegateBool { .. }
3551 | TypedExpr::NegateInt { .. }
3552 | TypedExpr::Invalid { .. } => None,
3553 };
3554
3555 self.selected_expression = Some(expr_location);
3556 self.name_to_use = Some(generate_new_name_for_constant(self.module, expr));
3557 self.value_to_use = Some(EcoString::from(
3558 self.module
3559 .code
3560 .get((expr_location.start as usize)..(expr_location.end as usize))
3561 .expect("selected expression"),
3562 ));
3563 }
3564
3565 ast::visit::visit_typed_expr(self, expr);
3566 }
3567}
3568
3569/// Builder for code action to apply the "expand function capture" action.
3570///
3571pub struct ExpandFunctionCapture<'a> {
3572 module: &'a Module,
3573 params: &'a CodeActionParams,
3574 edits: TextEdits<'a>,
3575 function_capture_data: Option<FunctionCaptureData>,
3576}
3577
3578pub struct FunctionCaptureData {
3579 function_span: SrcSpan,
3580 hole_span: SrcSpan,
3581 hole_type: Arc<Type>,
3582 reserved_names: VariablesNames,
3583}
3584
3585impl<'a> ExpandFunctionCapture<'a> {
3586 pub fn new(
3587 module: &'a Module,
3588 line_numbers: &'a LineNumbers,
3589 params: &'a CodeActionParams,
3590 ) -> Self {
3591 Self {
3592 module,
3593 params,
3594 edits: TextEdits::new(line_numbers),
3595 function_capture_data: None,
3596 }
3597 }
3598
3599 pub fn code_actions(mut self) -> Vec<CodeAction> {
3600 self.visit_typed_module(&self.module.ast);
3601
3602 let Some(FunctionCaptureData {
3603 function_span,
3604 hole_span,
3605 hole_type,
3606 reserved_names,
3607 }) = self.function_capture_data
3608 else {
3609 return vec![];
3610 };
3611
3612 let mut name_generator = NameGenerator::new();
3613 name_generator.reserve_variable_names(reserved_names);
3614 let name = name_generator.generate_name_from_type(&hole_type);
3615
3616 self.edits.replace(hole_span, name.clone().into());
3617 self.edits.insert(function_span.end, " }".into());
3618 self.edits
3619 .insert(function_span.start, format!("fn({name}) {{ "));
3620
3621 let mut action = Vec::with_capacity(1);
3622 CodeActionBuilder::new("Expand function capture")
3623 .kind(CodeActionKind::REFACTOR_REWRITE)
3624 .changes(self.params.text_document.uri.clone(), self.edits.edits)
3625 .preferred(false)
3626 .push_to(&mut action);
3627 action
3628 }
3629}
3630
3631impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> {
3632 fn visit_typed_expr_fn(
3633 &mut self,
3634 location: &'ast SrcSpan,
3635 type_: &'ast Arc<Type>,
3636 kind: &'ast FunctionLiteralKind,
3637 args: &'ast [TypedArg],
3638 body: &'ast Vec1<TypedStatement>,
3639 return_annotation: &'ast Option<ast::TypeAst>,
3640 ) {
3641 let fn_range = self.edits.src_span_to_lsp_range(*location);
3642 if within(self.params.range, fn_range) && kind.is_capture() {
3643 if let [arg] = args {
3644 self.function_capture_data = Some(FunctionCaptureData {
3645 function_span: *location,
3646 hole_span: arg.location,
3647 hole_type: arg.type_.clone(),
3648 reserved_names: VariablesNames::from_statements(body),
3649 });
3650 }
3651 }
3652
3653 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation)
3654 }
3655}
3656
3657struct VariablesNames {
3658 names: HashSet<EcoString>,
3659}
3660
3661impl VariablesNames {
3662 fn from_statements(statements: &[TypedStatement]) -> Self {
3663 let mut variables = Self {
3664 names: HashSet::new(),
3665 };
3666
3667 for statement in statements {
3668 variables.visit_typed_statement(statement);
3669 }
3670 variables
3671 }
3672}
3673
3674impl<'ast> ast::visit::Visit<'ast> for VariablesNames {
3675 fn visit_typed_expr_var(
3676 &mut self,
3677 _location: &'ast SrcSpan,
3678 _constructor: &'ast ValueConstructor,
3679 name: &'ast EcoString,
3680 ) {
3681 let _ = self.names.insert(name.clone());
3682 }
3683}
3684
3685/// Builder for code action to apply the "generate dynamic decoder action.
3686///
3687pub struct GenerateDynamicDecoder<'a> {
3688 module: &'a Module,
3689 params: &'a CodeActionParams,
3690 edits: TextEdits<'a>,
3691 printer: Printer<'a>,
3692 actions: &'a mut Vec<CodeAction>,
3693}
3694
3695const DECODE_MODULE: &str = "gleam/dynamic/decode";
3696
3697impl<'a> GenerateDynamicDecoder<'a> {
3698 pub fn new(
3699 module: &'a Module,
3700 line_numbers: &'a LineNumbers,
3701 params: &'a CodeActionParams,
3702 actions: &'a mut Vec<CodeAction>,
3703 ) -> Self {
3704 let printer = Printer::new(&module.ast.names);
3705 Self {
3706 module,
3707 params,
3708 edits: TextEdits::new(line_numbers),
3709 printer,
3710 actions,
3711 }
3712 }
3713
3714 pub fn code_actions(&mut self) {
3715 self.visit_typed_module(&self.module.ast);
3716 }
3717
3718 fn custom_type_decoder_body(
3719 &mut self,
3720 custom_type: &CustomType<Arc<Type>>,
3721 ) -> Option<EcoString> {
3722 // We cannot generate a decoder for an external type with no constructors!
3723 let constructors_size = custom_type.constructors.len();
3724 let (first, rest) = custom_type.constructors.split_first()?;
3725 let mode = EncodingMode::for_custom_type(custom_type);
3726
3727 // We generate a decoder for a type with a single constructor: it does not
3728 // require pattern matching on a tag as there's no variants to tell apart.
3729 if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag {
3730 return self.constructor_decoder(mode, custom_type, first, 0);
3731 }
3732
3733 // Otherwise we need to generate a decoder that has to tell apart different
3734 // variants, depending on the mode we might have to decode a type field or
3735 // plain strings!
3736 let module = self.printer.print_module(DECODE_MODULE);
3737 let discriminant = if mode == EncodingMode::PlainString {
3738 eco_format!("use variant <- {module}.then({module}.string)")
3739 } else {
3740 eco_format!("use variant <- {module}.field(\"type\", {module}.string)")
3741 };
3742
3743 let mut branches = Vec::with_capacity(constructors_size);
3744 for constructor in iter::once(first).chain(rest) {
3745 let body = self.constructor_decoder(mode, custom_type, constructor, 4)?;
3746 let name = to_snake_case(&constructor.name);
3747 branches.push(eco_format!(r#" "{name}" -> {body}"#));
3748 }
3749
3750 let cases = branches.join("\n");
3751 let type_name = &custom_type.name;
3752 Some(eco_format!(
3753 r#"{{
3754 {discriminant}
3755 case variant {{
3756{cases}
3757 _ -> {module}.failure(todo as "Zero value for {type_name}", "{type_name}")
3758 }}
3759}}"#,
3760 ))
3761 }
3762
3763 fn constructor_decoder(
3764 &mut self,
3765 mode: EncodingMode,
3766 custom_type: &ast::TypedCustomType,
3767 constructor: &TypedRecordConstructor,
3768 nesting: usize,
3769 ) -> Option<EcoString> {
3770 let decode_module = self.printer.print_module(DECODE_MODULE);
3771 let constructor_name = &constructor.name;
3772
3773 // If the constructor was encoded as a plain string with no additional
3774 // fields it means there's nothing else to decode and we can just
3775 // succeed.
3776 if mode == EncodingMode::PlainString {
3777 return Some(eco_format!("{decode_module}.success({constructor_name})"));
3778 }
3779
3780 // Otherwise we have to decode all the constructor fields to build it.
3781 let mut fields = Vec::with_capacity(constructor.arguments.len());
3782 for argument in constructor.arguments.iter() {
3783 let (_, name) = argument.label.as_ref()?;
3784 let field = RecordField {
3785 label: RecordLabel::Labeled(name),
3786 type_: &argument.type_,
3787 };
3788 fields.push(field);
3789 }
3790
3791 let mut decoder_printer = DecoderPrinter::new(
3792 &self.module.ast.names,
3793 custom_type.name.clone(),
3794 self.module.name.clone(),
3795 );
3796
3797 let decoders = fields
3798 .iter()
3799 .map(|field| decoder_printer.decode_field(field, nesting + 2))
3800 .join("\n");
3801
3802 let indent = " ".repeat(nesting);
3803
3804 Some(if decoders.is_empty() {
3805 eco_format!("{decode_module}.success({constructor_name})")
3806 } else {
3807 let field_names = fields
3808 .iter()
3809 .map(|field| format!("{}:", field.label.variable_name()))
3810 .join(", ");
3811
3812 eco_format!(
3813 "{{
3814{decoders}
3815{indent} {decode_module}.success({constructor_name}({field_names}))
3816{indent}}}",
3817 )
3818 })
3819 }
3820}
3821
3822impl<'ast> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast> {
3823 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
3824 let range = self.edits.src_span_to_lsp_range(custom_type.location);
3825 if !overlaps(self.params.range, range) {
3826 return;
3827 }
3828
3829 let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name));
3830 let Some(function_body) = self.custom_type_decoder_body(custom_type) else {
3831 return;
3832 };
3833
3834 let parameters = match custom_type.parameters.len() {
3835 0 => EcoString::new(),
3836 _ => eco_format!(
3837 "({})",
3838 custom_type
3839 .parameters
3840 .iter()
3841 .map(|(_, name)| name)
3842 .join(", ")
3843 ),
3844 };
3845
3846 let decoder_type = self.printer.print_type(&Type::Named {
3847 publicity: ast::Publicity::Public,
3848 package: STDLIB_PACKAGE_NAME.into(),
3849 module: DECODE_MODULE.into(),
3850 name: "Decoder".into(),
3851 args: vec![],
3852 inferred_variant: None,
3853 });
3854
3855 let function = format!(
3856 "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}",
3857 type_name = custom_type.name,
3858 );
3859
3860 self.edits.insert(custom_type.end_position, function);
3861 maybe_import(&mut self.edits, self.module, DECODE_MODULE);
3862
3863 CodeActionBuilder::new("Generate dynamic decoder")
3864 .kind(CodeActionKind::REFACTOR)
3865 .preferred(false)
3866 .changes(
3867 self.params.text_document.uri.clone(),
3868 std::mem::take(&mut self.edits.edits),
3869 )
3870 .push_to(self.actions);
3871 }
3872}
3873
3874/// If `module_name` is not already imported inside `module`, adds an edit to
3875/// add that import.
3876/// This function also makes sure not to import a module in itself.
3877///
3878fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) {
3879 if module.ast.names.is_imported(module_name) || module.name == module_name {
3880 return;
3881 }
3882
3883 let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers);
3884 let first_is_import = first_import_pos.is_some();
3885 let import_location = first_import_pos.unwrap_or_default();
3886 let after_import_newlines = add_newlines_after_import(
3887 import_location,
3888 first_is_import,
3889 edits.line_numbers,
3890 &module.code,
3891 );
3892
3893 edits.edits.push(get_import_edit(
3894 import_location,
3895 module_name,
3896 &after_import_newlines,
3897 ));
3898}
3899
3900struct DecoderPrinter<'a> {
3901 printer: Printer<'a>,
3902 /// The name of the root type we are printing a decoder for
3903 type_name: EcoString,
3904 /// The module name of the root type we are printing a decoder for
3905 type_module: EcoString,
3906}
3907
3908struct RecordField<'a> {
3909 label: RecordLabel<'a>,
3910 type_: &'a Type,
3911}
3912
3913enum RecordLabel<'a> {
3914 Labeled(&'a str),
3915 Unlabeled(usize),
3916}
3917
3918impl RecordLabel<'_> {
3919 fn field_key(&self) -> EcoString {
3920 match self {
3921 RecordLabel::Labeled(label) => eco_format!("\"{label}\""),
3922 RecordLabel::Unlabeled(index) => {
3923 eco_format!("{index}")
3924 }
3925 }
3926 }
3927
3928 fn variable_name(&self) -> EcoString {
3929 match self {
3930 RecordLabel::Labeled(label) => (*label).into(),
3931 &RecordLabel::Unlabeled(mut index) => {
3932 let mut characters = Vec::new();
3933 let alphabet_length = 26;
3934 let alphabet_offset = b'a';
3935 loop {
3936 let alphabet_index = (index % alphabet_length) as u8;
3937 characters.push((alphabet_offset + alphabet_index) as char);
3938 index /= alphabet_length;
3939
3940 if index == 0 {
3941 break;
3942 }
3943 index -= 1;
3944 }
3945 characters.into_iter().rev().collect()
3946 }
3947 }
3948 }
3949}
3950
3951impl<'a> DecoderPrinter<'a> {
3952 fn new(names: &'a Names, type_name: EcoString, type_module: EcoString) -> Self {
3953 Self {
3954 type_name,
3955 type_module,
3956 printer: Printer::new(names),
3957 }
3958 }
3959
3960 fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString {
3961 let module_name = self.printer.print_module(DECODE_MODULE);
3962 if type_.is_bit_array() {
3963 eco_format!("{module_name}.bit_array")
3964 } else if type_.is_bool() {
3965 eco_format!("{module_name}.bool")
3966 } else if type_.is_float() {
3967 eco_format!("{module_name}.float")
3968 } else if type_.is_int() {
3969 eco_format!("{module_name}.int")
3970 } else if type_.is_string() {
3971 eco_format!("{module_name}.string")
3972 } else {
3973 match type_.tuple_types() {
3974 Some(types) => {
3975 let fields = types
3976 .iter()
3977 .enumerate()
3978 .map(|(index, type_)| RecordField {
3979 type_,
3980 label: RecordLabel::Unlabeled(index),
3981 })
3982 .collect_vec();
3983 let decoders = fields
3984 .iter()
3985 .map(|field| self.decode_field(field, indent + 2))
3986 .join("\n");
3987 let mut field_names = fields.iter().map(|field| field.label.variable_name());
3988
3989 eco_format!(
3990 "{{
3991{decoders}
3992
3993{indent} {module_name}.success(#({fields}))
3994{indent}}}",
3995 fields = field_names.join(", "),
3996 indent = " ".repeat(indent)
3997 )
3998 }
3999 _ => {
4000 let type_information = type_.named_type_information();
4001 let type_information =
4002 type_information.as_ref().map(|(module, name, arguments)| {
4003 (module.as_str(), name.as_str(), arguments.as_slice())
4004 });
4005
4006 match type_information {
4007 Some(("gleam/dynamic", "Dynamic", _)) => {
4008 eco_format!("{module_name}.dynamic")
4009 }
4010 Some(("gleam", "List", [element])) => {
4011 eco_format!("{module_name}.list({})", self.decoder_for(element, indent))
4012 }
4013 Some(("gleam/option", "Option", [some])) => {
4014 eco_format!(
4015 "{module_name}.optional({})",
4016 self.decoder_for(some, indent)
4017 )
4018 }
4019 Some(("gleam/dict", "Dict", [key, value])) => {
4020 eco_format!(
4021 "{module_name}.dict({}, {})",
4022 self.decoder_for(key, indent),
4023 self.decoder_for(value, indent)
4024 )
4025 }
4026 Some((module, name, _))
4027 if module == self.type_module && name == self.type_name =>
4028 {
4029 eco_format!("{}_decoder()", to_snake_case(name))
4030 }
4031 _ => eco_format!(
4032 r#"todo as "Decoder for {}""#,
4033 self.printer.print_type(type_)
4034 ),
4035 }
4036 }
4037 }
4038 }
4039 }
4040
4041 fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
4042 let decoder = self.decoder_for(field.type_, indent);
4043
4044 eco_format!(
4045 r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#,
4046 indent = " ".repeat(indent),
4047 variable = field.label.variable_name(),
4048 field = field.label.field_key(),
4049 module = self.printer.print_module(DECODE_MODULE)
4050 )
4051 }
4052}
4053
4054/// Builder for code action to apply the "Generate to-JSON function" action.
4055///
4056pub struct GenerateJsonEncoder<'a> {
4057 module: &'a Module,
4058 params: &'a CodeActionParams,
4059 edits: TextEdits<'a>,
4060 printer: Printer<'a>,
4061 actions: &'a mut Vec<CodeAction>,
4062 config: &'a PackageConfig,
4063}
4064
4065const JSON_MODULE: &str = "gleam/json";
4066const JSON_PACKAGE_NAME: &str = "gleam_json";
4067
4068#[derive(Eq, PartialEq, Copy, Clone)]
4069enum EncodingMode {
4070 PlainString,
4071 ObjectWithTypeTag,
4072 ObjectWithNoTypeTag,
4073}
4074
4075impl EncodingMode {
4076 pub fn for_custom_type(type_: &CustomType<Arc<Type>>) -> Self {
4077 match type_.constructors.as_slice() {
4078 [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString,
4079 [_constructor] => EncodingMode::ObjectWithNoTypeTag,
4080 constructors if constructors.iter().all(|c| c.arguments.is_empty()) => {
4081 EncodingMode::PlainString
4082 }
4083 _constructors => EncodingMode::ObjectWithTypeTag,
4084 }
4085 }
4086}
4087
4088impl<'a> GenerateJsonEncoder<'a> {
4089 pub fn new(
4090 module: &'a Module,
4091 line_numbers: &'a LineNumbers,
4092 params: &'a CodeActionParams,
4093 actions: &'a mut Vec<CodeAction>,
4094 config: &'a PackageConfig,
4095 ) -> Self {
4096 let printer = Printer::new(&module.ast.names);
4097 Self {
4098 module,
4099 params,
4100 edits: TextEdits::new(line_numbers),
4101 printer,
4102 actions,
4103 config,
4104 }
4105 }
4106
4107 pub fn code_actions(&mut self) {
4108 if self.config.dependencies.contains_key(JSON_PACKAGE_NAME)
4109 || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME)
4110 {
4111 self.visit_typed_module(&self.module.ast);
4112 }
4113 }
4114
4115 fn custom_type_encoder_body(
4116 &mut self,
4117 record_name: EcoString,
4118 custom_type: &CustomType<Arc<Type>>,
4119 ) -> Option<EcoString> {
4120 // We cannot generate a decoder for an external type with no constructors!
4121 let constructors_size = custom_type.constructors.len();
4122 let (first, rest) = custom_type.constructors.split_first()?;
4123 let mode = EncodingMode::for_custom_type(custom_type);
4124
4125 // We generate an encoder for a type with a single constructor: it does not
4126 // require pattern matching on the argument as we can access all its fields
4127 // with the usual record access syntax.
4128 if rest.is_empty() {
4129 let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?;
4130 let unpacking = if first.arguments.is_empty() {
4131 ""
4132 } else {
4133 &eco_format!(
4134 "let {name}({fields}:) = {record_name}\n ",
4135 name = first.name,
4136 fields = first
4137 .arguments
4138 .iter()
4139 .filter_map(|argument| {
4140 argument.label.as_ref().map(|(_location, label)| label)
4141 })
4142 .join(":, ")
4143 )
4144 };
4145 return Some(eco_format!("{unpacking}{encoder}"));
4146 }
4147
4148 // Otherwise we generate an encoder for a type with multiple constructors:
4149 // it will need to pattern match on the various constructors and encode each
4150 // one separately.
4151 let mut branches = Vec::with_capacity(constructors_size);
4152 for constructor in iter::once(first).chain(rest) {
4153 let RecordConstructor { name, .. } = constructor;
4154 let encoder =
4155 self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?;
4156 let unpacking = if constructor.arguments.is_empty() {
4157 ""
4158 } else {
4159 &eco_format!(
4160 "({}:)",
4161 constructor
4162 .arguments
4163 .iter()
4164 .filter_map(|argument| {
4165 argument.label.as_ref().map(|(_location, label)| label)
4166 })
4167 .join(":, ")
4168 )
4169 };
4170 branches.push(eco_format!(" {name}{unpacking} -> {encoder}"));
4171 }
4172
4173 let branches = branches.join("\n");
4174 Some(eco_format!(
4175 "case {record_name} {{
4176{branches}
4177 }}",
4178 ))
4179 }
4180
4181 fn constructor_encoder(
4182 &mut self,
4183 mode: EncodingMode,
4184 constructor: &TypedRecordConstructor,
4185 type_name: EcoString,
4186 nesting: usize,
4187 ) -> Option<EcoString> {
4188 let json_module = self.printer.print_module(JSON_MODULE);
4189 let tag = to_snake_case(&constructor.name);
4190 let indent = " ".repeat(nesting);
4191
4192 // If the variant is encoded as a simple json string we just call the
4193 // `json.string` with the variant tag as an argument.
4194 if mode == EncodingMode::PlainString {
4195 return Some(eco_format!("{json_module}.string(\"{tag}\")"));
4196 }
4197
4198 // Otherwise we turn it into an object with a `type` tag field.
4199 let mut encoder_printer =
4200 JsonEncoderPrinter::new(&self.module.ast.names, type_name, self.module.name.clone());
4201
4202 // These are the fields of the json object to encode.
4203 let mut fields = Vec::with_capacity(constructor.arguments.len());
4204 if mode == EncodingMode::ObjectWithTypeTag {
4205 // Any needed type tag is always going to be the first field in the object
4206 fields.push(eco_format!(
4207 "{indent} #(\"type\", {json_module}.string(\"{tag}\"))"
4208 ));
4209 }
4210
4211 for argument in constructor.arguments.iter() {
4212 let (_, label) = argument.label.as_ref()?;
4213 let field = RecordField {
4214 label: RecordLabel::Labeled(label),
4215 type_: &argument.type_,
4216 };
4217 let encoder = encoder_printer.encode_field(&field, nesting + 2);
4218 fields.push(encoder);
4219 }
4220
4221 let fields = fields.join(",\n");
4222 Some(eco_format!(
4223 "{json_module}.object([
4224{fields},
4225{indent}])"
4226 ))
4227 }
4228}
4229
4230impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> {
4231 fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) {
4232 let range = self.edits.src_span_to_lsp_range(custom_type.location);
4233 if !overlaps(self.params.range, range) {
4234 return;
4235 }
4236
4237 let record_name = to_snake_case(&custom_type.name);
4238 let name = eco_format!("{record_name}_to_json");
4239 let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else {
4240 return;
4241 };
4242
4243 let json_type = self.printer.print_type(&Type::Named {
4244 publicity: ast::Publicity::Public,
4245 package: JSON_PACKAGE_NAME.into(),
4246 module: JSON_MODULE.into(),
4247 name: "Json".into(),
4248 args: vec![],
4249 inferred_variant: None,
4250 });
4251
4252 let type_ = if custom_type.parameters.is_empty() {
4253 custom_type.name.clone()
4254 } else {
4255 let parameters = custom_type
4256 .parameters
4257 .iter()
4258 .map(|(_, name)| name)
4259 .join(", ");
4260 eco_format!("{}({})", custom_type.name, parameters)
4261 };
4262
4263 let function = format!(
4264 "
4265
4266fn {name}({record_name}: {type_}) -> {json_type} {{
4267 {encoder}
4268}}",
4269 );
4270
4271 self.edits.insert(custom_type.end_position, function);
4272 maybe_import(&mut self.edits, self.module, JSON_MODULE);
4273
4274 CodeActionBuilder::new("Generate to-JSON function")
4275 .kind(CodeActionKind::REFACTOR)
4276 .preferred(false)
4277 .changes(
4278 self.params.text_document.uri.clone(),
4279 std::mem::take(&mut self.edits.edits),
4280 )
4281 .push_to(self.actions);
4282 }
4283}
4284
4285struct JsonEncoderPrinter<'a> {
4286 printer: Printer<'a>,
4287 /// The name of the root type we are printing an encoder for
4288 type_name: EcoString,
4289 /// The module name of the root type we are printing an encoder for
4290 type_module: EcoString,
4291}
4292
4293impl<'a> JsonEncoderPrinter<'a> {
4294 fn new(names: &'a Names, type_name: EcoString, type_module: EcoString) -> Self {
4295 Self {
4296 type_name,
4297 type_module,
4298 printer: Printer::new(names),
4299 }
4300 }
4301
4302 fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString {
4303 let module_name = self.printer.print_module(JSON_MODULE);
4304 let is_capture = encoded_value == "_";
4305 let maybe_capture = |mut function: EcoString| {
4306 if is_capture {
4307 function
4308 } else {
4309 function.push('(');
4310 function.push_str(encoded_value);
4311 function.push(')');
4312 function
4313 }
4314 };
4315
4316 if type_.is_bool() {
4317 maybe_capture(eco_format!("{module_name}.bool"))
4318 } else if type_.is_float() {
4319 maybe_capture(eco_format!("{module_name}.float"))
4320 } else if type_.is_int() {
4321 maybe_capture(eco_format!("{module_name}.int"))
4322 } else if type_.is_string() {
4323 maybe_capture(eco_format!("{module_name}.string"))
4324 } else {
4325 match type_.tuple_types() {
4326 Some(types) => {
4327 let (tuple, new_indent) = if is_capture {
4328 ("value", indent + 4)
4329 } else {
4330 (encoded_value, indent + 2)
4331 };
4332
4333 let encoders = types
4334 .iter()
4335 .enumerate()
4336 .map(|(index, type_)| {
4337 self.encoder_for(&format!("{tuple}.{index}"), type_, new_indent)
4338 })
4339 .collect_vec();
4340
4341 if is_capture {
4342 eco_format!(
4343 "fn(value) {{
4344{indent} {module_name}.preprocessed_array([
4345{indent} {encoders},
4346{indent} ])
4347{indent}}}",
4348 indent = " ".repeat(indent),
4349 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
4350 )
4351 } else {
4352 eco_format!(
4353 "{module_name}.preprocessed_array([
4354{indent} {encoders},
4355{indent}])",
4356 indent = " ".repeat(indent),
4357 encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))),
4358 )
4359 }
4360 }
4361 _ => {
4362 let type_information = type_.named_type_information();
4363 let type_information: Option<(&str, &str, &[Arc<Type>])> =
4364 type_information.as_ref().map(|(module, name, arguments)| {
4365 (module.as_str(), name.as_str(), arguments.as_slice())
4366 });
4367
4368 match type_information {
4369 Some(("gleam", "List", [element])) => {
4370 eco_format!(
4371 "{module_name}.array({encoded_value}, {map_function})",
4372 map_function = self.encoder_for("_", element, indent)
4373 )
4374 }
4375 Some(("gleam/option", "Option", [some])) => {
4376 eco_format!(
4377 "case {encoded_value} {{
4378{indent} {none} -> {module_name}.null()
4379{indent} {some}(value) -> {encoder}
4380{indent}}}",
4381 indent = " ".repeat(indent),
4382 none = self
4383 .printer
4384 .print_constructor(&"gleam/option".into(), &"None".into()),
4385 some = self
4386 .printer
4387 .print_constructor(&"gleam/option".into(), &"Some".into()),
4388 encoder = self.encoder_for("value", some, indent + 2)
4389 )
4390 }
4391 Some(("gleam/dict", "Dict", [key, value])) => {
4392 let stringify_function = match key
4393 .named_type_information()
4394 .as_ref()
4395 .map(|(module, name, arguments)| {
4396 (module.as_str(), name.as_str(), arguments.as_slice())
4397 }) {
4398 Some(("gleam", "String", [])) => "fn(string) { string }",
4399 _ => &format!(
4400 r#"todo as "Function to stringify {}""#,
4401 self.printer.print_type(key)
4402 ),
4403 };
4404 eco_format!(
4405 "{module_name}.dict({encoded_value}, {stringify_function}, {})",
4406 self.encoder_for("_", value, indent)
4407 )
4408 }
4409 Some((module, name, _))
4410 if module == self.type_module && name == self.type_name =>
4411 {
4412 maybe_capture(eco_format!("{}_to_json", to_snake_case(name)))
4413 }
4414 _ => eco_format!(
4415 r#"todo as "Encoder for {}""#,
4416 self.printer.print_type(type_)
4417 ),
4418 }
4419 }
4420 }
4421 }
4422 }
4423
4424 fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString {
4425 let field_name = field.label.variable_name();
4426 let encoder = self.encoder_for(&field_name, field.type_, indent);
4427
4428 eco_format!(
4429 r#"{indent}#("{field_name}", {encoder})"#,
4430 indent = " ".repeat(indent),
4431 )
4432 }
4433}
4434
4435/// Builder for code action to pattern match on things like (anonymous) function
4436/// arguments or variables.
4437/// For example:
4438///
4439/// ```gleam
4440/// pub fn wibble(arg: #(key, value)) {
4441/// // ^ [pattern match on argument]
4442/// }
4443///
4444/// // Generates
4445///
4446/// pub fn wibble(arg: #(key, value)) {
4447/// let #(value_0, value_1) = arg
4448/// }
4449/// ```
4450///
4451/// Another example with variables:
4452///
4453/// ```gleam
4454/// pub fn main() {
4455/// let pair = #(1, 3)
4456/// // ^ [pattern match on value]
4457/// }
4458///
4459/// // Generates
4460///
4461/// pub fn main() {
4462/// let pair = #(1, 3)
4463/// let #(value_0, value_1) = pair
4464/// }
4465/// ```
4466///
4467pub struct PatternMatchOnValue<'a, A> {
4468 module: &'a Module,
4469 params: &'a CodeActionParams,
4470 compiler: &'a LspProjectCompiler<A>,
4471 selected_value: Option<PatternMatchedValue<'a>>,
4472 edits: TextEdits<'a>,
4473}
4474
4475/// A value we might want to pattern match on.
4476/// Each variant will also contain all the info needed to know how to properly
4477/// print and format the corresponding pattern matching code; that's why you'll
4478/// see `Range`s and `SrcSpan` besides the type of the thing being matched.
4479///
4480pub enum PatternMatchedValue<'a> {
4481 FunctionArgument {
4482 /// The argument being pattern matched on.
4483 ///
4484 arg: &'a TypedArg,
4485 /// The first statement inside the function body. Used to correctly
4486 /// position the inserted pattern matching.
4487 ///
4488 first_statement: &'a TypedStatement,
4489 /// The range of the entire function holding the argument.
4490 ///
4491 function_range: Range,
4492 },
4493 LetVariable {
4494 variable_name: &'a EcoString,
4495 variable_type: &'a Arc<Type>,
4496 /// The location of the entire let assignment the variable is part of,
4497 /// so that we can add the pattern matching _after_ it.
4498 ///
4499 assignment_location: SrcSpan,
4500 },
4501 UseVariable {
4502 variable_name: &'a EcoString,
4503 variable_type: &'a Arc<Type>,
4504 /// The location of the entire use expression the variable is part of,
4505 /// so that we can add the pattern matching _after_ it.
4506 ///
4507 use_location: SrcSpan,
4508 },
4509}
4510
4511impl<'a, IO> PatternMatchOnValue<'a, IO>
4512where
4513 IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone,
4514{
4515 pub fn new(
4516 module: &'a Module,
4517 line_numbers: &'a LineNumbers,
4518 params: &'a CodeActionParams,
4519 compiler: &'a LspProjectCompiler<IO>,
4520 ) -> Self {
4521 Self {
4522 module,
4523 params,
4524 compiler,
4525 selected_value: None,
4526 edits: TextEdits::new(line_numbers),
4527 }
4528 }
4529
4530 pub fn code_actions(mut self) -> Vec<CodeAction> {
4531 self.visit_typed_module(&self.module.ast);
4532
4533 let action_title = match self.selected_value {
4534 Some(PatternMatchedValue::FunctionArgument {
4535 arg,
4536 first_statement: function_body,
4537 function_range,
4538 }) => {
4539 self.match_on_function_argument(arg, function_body, function_range);
4540 "Pattern match on argument"
4541 }
4542 Some(
4543 PatternMatchedValue::LetVariable {
4544 variable_name,
4545 variable_type,
4546 assignment_location: location,
4547 }
4548 | PatternMatchedValue::UseVariable {
4549 variable_name,
4550 variable_type,
4551 use_location: location,
4552 },
4553 ) => {
4554 self.match_on_let_variable(variable_name, variable_type, location);
4555 "Pattern match on variable"
4556 }
4557 None => return vec![],
4558 };
4559
4560 if self.edits.edits.is_empty() {
4561 return vec![];
4562 }
4563
4564 let mut action = Vec::with_capacity(1);
4565 CodeActionBuilder::new(action_title)
4566 .kind(CodeActionKind::REFACTOR_REWRITE)
4567 .changes(self.params.text_document.uri.clone(), self.edits.edits)
4568 .preferred(false)
4569 .push_to(&mut action);
4570 action
4571 }
4572
4573 fn match_on_function_argument(
4574 &mut self,
4575 arg: &TypedArg,
4576 first_statement: &TypedStatement,
4577 function_range: Range,
4578 ) {
4579 let Some(arg_name) = arg.get_variable_name() else {
4580 return;
4581 };
4582
4583 let Some(patterns) = self.type_to_destructure_patterns(arg.type_.as_ref()) else {
4584 return;
4585 };
4586
4587 let first_statement_location = first_statement.location();
4588 let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location);
4589
4590 // If we're trying to insert the pattern matching on the same
4591 // line as the one where the function is defined we will want to
4592 // put it on a new line instead. So in that case the nesting will
4593 // be the default 2 spaces.
4594 let needs_newline = function_range.start.line == first_statement_range.start.line;
4595 let nesting = if needs_newline {
4596 String::from(" ")
4597 } else {
4598 " ".repeat(first_statement_range.start.character as usize)
4599 };
4600
4601 let pattern_matching = if patterns.len() == 1 {
4602 let pattern = patterns.first();
4603 format!("let {pattern} = {arg_name}")
4604 } else {
4605 let patterns = patterns
4606 .iter()
4607 .map(|p| format!(" {nesting}{p} -> todo"))
4608 .join("\n");
4609 format!("case {arg_name} {{\n{patterns}\n{nesting}}}")
4610 };
4611
4612 let pattern_matching = if needs_newline {
4613 format!("\n{nesting}{pattern_matching}")
4614 } else {
4615 pattern_matching
4616 };
4617
4618 let has_empty_body = match first_statement {
4619 ast::Statement::Expression(TypedExpr::Todo {
4620 kind: TodoKind::EmptyFunction { .. },
4621 ..
4622 }) => true,
4623 _ => false,
4624 };
4625
4626 // If the pattern matching is added to a function with an empty
4627 // body then we do not add any nesting after it, or we would be
4628 // increasing the nesting of the closing `}`!
4629 let pattern_matching = if has_empty_body {
4630 format!("{pattern_matching}\n")
4631 } else {
4632 format!("{pattern_matching}\n{nesting}")
4633 };
4634
4635 self.edits
4636 .insert(first_statement_location.start, pattern_matching);
4637 }
4638
4639 fn match_on_let_variable(
4640 &mut self,
4641 variable_name: &EcoString,
4642 variable_type: &Arc<Type>,
4643 assignment_location: SrcSpan,
4644 ) {
4645 let Some(patterns) = self.type_to_destructure_patterns(variable_type.as_ref()) else {
4646 return;
4647 };
4648
4649 let assignment_range = self.edits.src_span_to_lsp_range(assignment_location);
4650 let nesting = " ".repeat(assignment_range.start.character as usize);
4651
4652 let pattern_matching = if patterns.len() == 1 {
4653 let pattern = patterns.first();
4654 format!("let {pattern} = {variable_name}")
4655 } else {
4656 let patterns = patterns
4657 .iter()
4658 .map(|p| format!(" {nesting}{p} -> todo"))
4659 .join("\n");
4660 format!("case {variable_name} {{\n{patterns}\n{nesting}}}")
4661 };
4662
4663 self.edits.insert(
4664 assignment_location.end,
4665 format!("\n{nesting}{pattern_matching}"),
4666 );
4667 }
4668
4669 /// Will produce a pattern that can be used on the left hand side of a let
4670 /// assignment to destructure a value of the given type. For example given
4671 /// this type:
4672 ///
4673 /// ```gleam
4674 /// pub type Wibble {
4675 /// Wobble(Int, label: String)
4676 /// }
4677 /// ```
4678 ///
4679 /// The produced pattern will look like this: `Wobble(value_0, label:)`.
4680 /// The pattern will use the correct qualified/unqualified name for the
4681 /// constructor if it comes from another package.
4682 ///
4683 /// The function will only produce a list of patterns that can be used from
4684 /// the current module. So if the type comes from another module it must be
4685 /// public! Otherwise this function will return an empty vec.
4686 ///
4687 fn type_to_destructure_patterns(&mut self, type_: &Type) -> Option<Vec1<EcoString>> {
4688 match type_ {
4689 Type::Fn { .. } => None,
4690 Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow()),
4691 Type::Named {
4692 module: type_module,
4693 name: type_name,
4694 ..
4695 } => {
4696 let patterns =
4697 get_type_constructors(self.compiler, &self.module.name, type_module, type_name)
4698 .iter()
4699 .filter_map(|c| self.record_constructor_to_destructure_pattern(c))
4700 .collect_vec();
4701
4702 Vec1::try_from_vec(patterns).ok()
4703 }
4704 // We don't want to suggest this action for empty tuple as it
4705 // doesn't make a lot of sense to match on those.
4706 Type::Tuple { elements } if elements.is_empty() => None,
4707 Type::Tuple { elements } => Some(vec1![eco_format!(
4708 "#({})",
4709 (0..elements.len() as u32)
4710 .map(|i| format!("value_{i}"))
4711 .join(", ")
4712 )]),
4713 }
4714 }
4715
4716 fn type_var_to_destructure_patterns(&mut self, type_var: &TypeVar) -> Option<Vec1<EcoString>> {
4717 match type_var {
4718 TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None,
4719 TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_),
4720 }
4721 }
4722
4723 /// Given the value constructor of a record, returns a string with the
4724 /// pattern used to match on that specific variant.
4725 ///
4726 /// Note how:
4727 /// - If the constructor is internal to another module or comes from another
4728 /// module, then this returns `None` since one cannot pattern match on it.
4729 /// - If the provided `ValueConstructor` is not a record constructor this
4730 /// will return `None`.
4731 ///
4732 fn record_constructor_to_destructure_pattern(
4733 &self,
4734 constructor: &ValueConstructor,
4735 ) -> Option<EcoString> {
4736 let type_::ValueConstructorVariant::Record {
4737 name: constructor_name,
4738 arity: constructor_arity,
4739 module: constructor_module,
4740 field_map,
4741 ..
4742 } = &constructor.variant
4743 else {
4744 // The constructor should always be a record, in case it's not
4745 // there's not much we can do and just fail.
4746 return None;
4747 };
4748
4749 // Since the constructor is a record constructor we know that its type
4750 // is either `Named` or a `Fn` type, in either case we have to get the
4751 // arguments types out of it.
4752 let Some(arguments_types) = constructor
4753 .type_
4754 .fn_types()
4755 .map(|(arguments_types, _return)| arguments_types)
4756 .or_else(|| constructor.type_.constructor_types())
4757 else {
4758 // This should never happen but just in case we don't want to unwrap
4759 // and panic.
4760 return None;
4761 };
4762
4763 let mut name_generator = NameGenerator::new();
4764 let index_to_label = match field_map {
4765 None => HashMap::new(),
4766 Some(field_map) => {
4767 name_generator.reserve_all_labels(field_map);
4768
4769 field_map
4770 .fields
4771 .iter()
4772 .map(|(label, index)| (index, label))
4773 .collect::<HashMap<_, _>>()
4774 }
4775 };
4776
4777 let mut pattern =
4778 pretty_constructor_name(self.module, constructor_module, constructor_name)?;
4779
4780 if *constructor_arity == 0 {
4781 return Some(pattern);
4782 }
4783
4784 pattern.push('(');
4785 let args = (0..*constructor_arity as u32)
4786 .map(|i| match index_to_label.get(&i) {
4787 Some(label) => eco_format!("{label}:"),
4788 None => match arguments_types.get(i as usize) {
4789 None => name_generator.rename_to_avoid_shadowing(EcoString::from("value")),
4790 Some(type_) => name_generator.generate_name_from_type(type_),
4791 },
4792 })
4793 .join(", ");
4794
4795 pattern.push_str(&args);
4796 pattern.push(')');
4797 Some(pattern)
4798 }
4799}
4800
4801impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO>
4802where
4803 IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone,
4804{
4805 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
4806 // If we're not inside the function there's no point in exploring its
4807 // ast further.
4808 let function_span = SrcSpan {
4809 start: fun.location.start,
4810 end: fun.end_position,
4811 };
4812 let function_range = self.edits.src_span_to_lsp_range(function_span);
4813 if !within(self.params.range, function_range) {
4814 return;
4815 }
4816
4817 for arg in &fun.arguments {
4818 // If the cursor is placed on one of the arguments, then we can try
4819 // and generate code for that one.
4820 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
4821 if within(self.params.range, arg_range) {
4822 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
4823 arg,
4824 first_statement: fun.body.first(),
4825 function_range,
4826 });
4827 return;
4828 }
4829 }
4830
4831 // If the cursor is not on any of the function arguments then we keep
4832 // exploring the function body as we might want to destructure the
4833 // argument of an expression function!
4834 ast::visit::visit_typed_function(self, fun);
4835 }
4836
4837 fn visit_typed_expr_fn(
4838 &mut self,
4839 location: &'ast SrcSpan,
4840 type_: &'ast Arc<Type>,
4841 kind: &'ast FunctionLiteralKind,
4842 args: &'ast [TypedArg],
4843 body: &'ast Vec1<TypedStatement>,
4844 return_annotation: &'ast Option<ast::TypeAst>,
4845 ) {
4846 // If we're not inside the function there's no point in exploring its
4847 // ast further.
4848 let function_range = self.edits.src_span_to_lsp_range(*location);
4849 if !within(self.params.range, function_range) {
4850 return;
4851 }
4852
4853 for arg in args {
4854 // If the cursor is placed on one of the arguments, then we can try
4855 // and generate code for that one.
4856 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
4857 if within(self.params.range, arg_range) {
4858 self.selected_value = Some(PatternMatchedValue::FunctionArgument {
4859 arg,
4860 first_statement: body.first(),
4861 function_range,
4862 });
4863 return;
4864 }
4865 }
4866
4867 // If the cursor is not on any of the function arguments then we keep
4868 // exploring the function body as we might want to destructure the
4869 // argument of an expression function!
4870 ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation);
4871 }
4872
4873 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
4874 if let Pattern::Variable {
4875 name,
4876 location,
4877 type_,
4878 ..
4879 } = &assignment.pattern
4880 {
4881 let variable_range = self.edits.src_span_to_lsp_range(*location);
4882 if within(self.params.range, variable_range) {
4883 self.selected_value = Some(PatternMatchedValue::LetVariable {
4884 variable_name: name,
4885 variable_type: type_,
4886 assignment_location: assignment.location,
4887 });
4888 // If we've found the variable to pattern match on, there's no
4889 // point in keeping traversing the AST.
4890 return;
4891 }
4892 }
4893
4894 ast::visit::visit_typed_assignment(self, assignment);
4895 }
4896
4897 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
4898 if let Some(assignments) = use_.callback_arguments() {
4899 for variable in assignments {
4900 let ast::Arg {
4901 names: ArgNames::Named { name, .. },
4902 location: variable_location,
4903 type_,
4904 ..
4905 } = variable
4906 else {
4907 continue;
4908 };
4909
4910 // If we use a pattern in a use assignment, that will end up
4911 // being called `_use` something. We don't want to offer the
4912 // action when hovering a pattern so we ignore those.
4913 if name.starts_with("_use") {
4914 continue;
4915 }
4916
4917 let variable_range = self.edits.src_span_to_lsp_range(*variable_location);
4918 if within(self.params.range, variable_range) {
4919 self.selected_value = Some(PatternMatchedValue::UseVariable {
4920 variable_name: name,
4921 variable_type: type_,
4922 use_location: use_.location,
4923 });
4924 // If we've found the variable to pattern match on, there's no
4925 // point in keeping traversing the AST.
4926 return;
4927 }
4928 }
4929 }
4930
4931 ast::visit::visit_typed_use(self, use_);
4932 }
4933}
4934
4935/// Given a type and its module, returns a list of its *importable*
4936/// constructors.
4937///
4938/// Since this focuses just on importable constructors, if either the module or
4939/// the type are internal the returned array will be empty!
4940///
4941fn get_type_constructors<'a, 'b, IO>(
4942 compiler: &'a LspProjectCompiler<IO>,
4943 current_module: &'b EcoString,
4944 type_module: &'b EcoString,
4945 type_name: &'b EcoString,
4946) -> Vec<&'a ValueConstructor>
4947where
4948 IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone,
4949{
4950 let type_is_inside_current_module = current_module == type_module;
4951 let module_interface = if !type_is_inside_current_module {
4952 // If the type is outside of the module we're in, we can only pattern
4953 // match on it if the module can be imported.
4954 // The `get_module_interface` already takes care of making this check.
4955 compiler.get_module_interface(type_module)
4956 } else {
4957 // However, if the type is defined in the module we're in, we can always
4958 // pattern match on it. So we get the current module's interface.
4959 compiler
4960 .modules
4961 .get(current_module)
4962 .map(|module| &module.ast.type_info)
4963 };
4964
4965 let Some(module_interface) = module_interface else {
4966 return vec![];
4967 };
4968
4969 // If the type is in an internal module that is not the current one, we
4970 // cannot use its constructors!
4971 if !type_is_inside_current_module && module_interface.is_internal {
4972 return vec![];
4973 }
4974
4975 let Some(constructors) = module_interface.types_value_constructors.get(type_name) else {
4976 return vec![];
4977 };
4978
4979 constructors
4980 .variants
4981 .iter()
4982 .filter_map(|variant| {
4983 let constructor = module_interface.values.get(&variant.name)?;
4984 if type_is_inside_current_module || constructor.publicity.is_public() {
4985 Some(constructor)
4986 } else {
4987 None
4988 }
4989 })
4990 .collect_vec()
4991}
4992
4993/// Returns a pretty printed record constructor name, the way it would be used
4994/// inside the given `module` (with the correct name and qualification).
4995///
4996/// If the constructor cannot be used inside the module because it's not
4997/// imported, then this function will return `None`.
4998///
4999fn pretty_constructor_name(
5000 module: &Module,
5001 constructor_module: &EcoString,
5002 constructor_name: &EcoString,
5003) -> Option<EcoString> {
5004 match module
5005 .ast
5006 .names
5007 .named_constructor(constructor_module, constructor_name)
5008 {
5009 type_::printer::NameContextInformation::Unimported(_) => None,
5010 type_::printer::NameContextInformation::Unqualified(constructor_name) => {
5011 Some(eco_format!("{constructor_name}"))
5012 }
5013 type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => {
5014 Some(eco_format!("{module_name}.{constructor_name}"))
5015 }
5016 }
5017}
5018
5019/// Builder for the "generate function" code action.
5020/// Whenever someone hovers an invalid expression that is inferred to have a
5021/// function type the language server can generate a function definition for it.
5022/// For example:
5023///
5024/// ```gleam
5025/// pub fn main() {
5026/// wibble(1, 2, "hello")
5027/// // ^ [generate function]
5028/// }
5029/// ```
5030///
5031/// Will generate the following definition:
5032///
5033/// ```gleam
5034/// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a {
5035/// todo
5036/// }
5037/// ```
5038///
5039pub struct GenerateFunction<'a> {
5040 module: &'a Module,
5041 params: &'a CodeActionParams,
5042 edits: TextEdits<'a>,
5043 last_visited_function_end: Option<u32>,
5044 function_to_generate: Option<FunctionToGenerate<'a>>,
5045}
5046
5047struct FunctionToGenerate<'a> {
5048 name: &'a str,
5049 arguments_types: Vec<Arc<Type>>,
5050
5051 /// The arguments actually supplied as input to the function, if any.
5052 /// A function to generate might as well be just a name passed as an argument
5053 /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually
5054 /// have any actual arguments!
5055 given_arguments: Option<&'a [TypedCallArg]>,
5056 return_type: Arc<Type>,
5057 previous_function_end: Option<u32>,
5058}
5059
5060impl<'a> GenerateFunction<'a> {
5061 pub fn new(
5062 module: &'a Module,
5063 line_numbers: &'a LineNumbers,
5064 params: &'a CodeActionParams,
5065 ) -> Self {
5066 Self {
5067 module,
5068 params,
5069 edits: TextEdits::new(line_numbers),
5070 last_visited_function_end: None,
5071 function_to_generate: None,
5072 }
5073 }
5074
5075 pub fn code_actions(mut self) -> Vec<CodeAction> {
5076 self.visit_typed_module(&self.module.ast);
5077
5078 let Some(FunctionToGenerate {
5079 name,
5080 arguments_types,
5081 given_arguments,
5082 previous_function_end: Some(insert_at),
5083 return_type,
5084 }) = self.function_to_generate
5085 else {
5086 return vec![];
5087 };
5088
5089 // Labels do not share the same namespace as argument so we use two separate
5090 // generators to avoid renaming a label in case it shares a name with an argument.
5091 let mut label_names = NameGenerator::new();
5092 let mut argument_names = NameGenerator::new();
5093 let mut printer = Printer::new(&self.module.ast.names);
5094 let arguments = arguments_types
5095 .iter()
5096 .enumerate()
5097 .map(|(index, argument_type)| {
5098 let call_argument = given_arguments.and_then(|arguments| arguments.get(index));
5099 let (label, name) =
5100 argument_names.generate_label_and_name(call_argument, argument_type);
5101 let pretty_type = printer.print_type(argument_type);
5102 if let Some(label) = label {
5103 let label = label_names.rename_to_avoid_shadowing(label.clone());
5104 format!("{label} {name}: {pretty_type}")
5105 } else {
5106 format!("{name}: {pretty_type}")
5107 }
5108 })
5109 .join(", ");
5110
5111 let return_type = printer.print_type(&return_type);
5112
5113 self.edits.insert(
5114 insert_at,
5115 format!("\n\nfn {name}({arguments}) -> {return_type} {{\n todo\n}}"),
5116 );
5117
5118 let mut action = Vec::with_capacity(1);
5119 CodeActionBuilder::new("Generate function")
5120 .kind(CodeActionKind::REFACTOR_REWRITE)
5121 .changes(self.params.text_document.uri.clone(), self.edits.edits)
5122 .preferred(false)
5123 .push_to(&mut action);
5124 action
5125 }
5126
5127 fn try_save_function_to_generate(
5128 &mut self,
5129 function_name_location: SrcSpan,
5130 function_type: &Arc<Type>,
5131 given_arguments: Option<&'a [TypedCallArg]>,
5132 ) {
5133 let name_range = function_name_location.start as usize..function_name_location.end as usize;
5134 let candidate_name = self.module.code.get(name_range);
5135 match (candidate_name, function_type.fn_types()) {
5136 (None, _) | (_, None) => (),
5137 (Some(name), _) if !is_valid_lowercase_name(name) => (),
5138 (Some(name), Some((arguments_types, return_type))) => {
5139 self.function_to_generate = Some(FunctionToGenerate {
5140 name,
5141 arguments_types,
5142 given_arguments,
5143 return_type,
5144 previous_function_end: self.last_visited_function_end,
5145 })
5146 }
5147 }
5148 }
5149}
5150
5151impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> {
5152 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
5153 self.last_visited_function_end = Some(fun.end_position);
5154 ast::visit::visit_typed_function(self, fun);
5155 }
5156
5157 fn visit_typed_expr_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) {
5158 let invalid_range = self.edits.src_span_to_lsp_range(*location);
5159 if within(self.params.range, invalid_range) {
5160 self.try_save_function_to_generate(*location, type_, None);
5161 }
5162
5163 ast::visit::visit_typed_expr_invalid(self, location, type_);
5164 }
5165
5166 fn visit_typed_expr_call(
5167 &mut self,
5168 location: &'ast SrcSpan,
5169 type_: &'ast Arc<Type>,
5170 fun: &'ast TypedExpr,
5171 args: &'ast [TypedCallArg],
5172 ) {
5173 // If the function being called is invalid we need to generate a
5174 // function that has the proper labels.
5175 let fun_range = self.edits.src_span_to_lsp_range(fun.location());
5176
5177 if within(self.params.range, fun_range) && fun.is_invalid() {
5178 if labels_are_correct(args) {
5179 self.try_save_function_to_generate(fun.location(), &fun.type_(), Some(args));
5180 }
5181 } else {
5182 ast::visit::visit_typed_expr_call(self, location, type_, fun, args);
5183 }
5184 }
5185}
5186
5187/// Builder for the "generate variant" code action. This will generate a variant
5188/// for a type if it can tell the type it should come from. It will work with
5189/// non-existing variants both used as expressions
5190///
5191/// ```gleam
5192/// let a = IDoNotExist(1)
5193/// // ^^^^^^^^^^^ It would generate this variant here
5194/// ```
5195///
5196/// And as patterns:
5197///
5198/// ```gleam
5199/// let assert IDoNotExist(1) = todo
5200/// ^^^^^^^^^^^ It would generate this variant here
5201/// ```
5202///
5203pub struct GenerateVariant<'a, IO> {
5204 module: &'a Module,
5205 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
5206 params: &'a CodeActionParams,
5207 line_numbers: &'a LineNumbers,
5208 variant_to_generate: Option<VariantToGenerate<'a>>,
5209}
5210
5211struct VariantToGenerate<'a> {
5212 name: &'a str,
5213 end_position: u32,
5214 arguments_types: Vec<Arc<Type>>,
5215
5216 /// Wether the type we're adding the variant to is written with braces or
5217 /// not. We need this information to add braces when missing.
5218 ///
5219 type_braces: TypeBraces,
5220
5221 /// The module this variant will be added to.
5222 ///
5223 module_name: EcoString,
5224
5225 /// The arguments actually supplied as input to the variant, if any.
5226 /// A variant to generate might as well be just a name passed as an argument
5227 /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually
5228 /// have any actual arguments!
5229 ///
5230 given_arguments: Option<Arguments<'a>>,
5231}
5232
5233#[derive(Debug, Clone, Copy)]
5234enum TypeBraces {
5235 /// If the type is written like this: `pub type Wibble`
5236 HasBraces,
5237 /// If the type is written like this: `pub type Wibble {}`
5238 NoBraces,
5239}
5240
5241/// The arguments to an invalid call or pattern we can use to generate a variant.
5242///
5243enum Arguments<'a> {
5244 /// These are the arguments provided to the invalid variant constructor
5245 /// when it's used as a function: `let a = Wibble(1, 2)`.
5246 ///
5247 Expressions(&'a [TypedCallArg]),
5248 /// These are the arguments provided to the invalid variant constructor when
5249 /// it's used in a pattern: `let assert Wibble(1, 2) = a`
5250 ///
5251 Patterns(&'a [CallArg<TypedPattern>]),
5252}
5253
5254/// An invalid variant might be used both as a pattern in a case expression or
5255/// as a regular value in an expression. We want to generate the variant in both
5256/// cases, so we use this enum to tell apart the two cases and be able to reuse
5257/// most of the code for both as they are very similar.
5258///
5259enum Argument<'a> {
5260 Expression(&'a TypedCallArg),
5261 Pattern(&'a CallArg<TypedPattern>),
5262}
5263
5264impl<'a> Arguments<'a> {
5265 fn get(&self, index: usize) -> Option<Argument<'a>> {
5266 match self {
5267 Arguments::Patterns(call_args) => call_args.get(index).map(Argument::Pattern),
5268 Arguments::Expressions(call_args) => call_args.get(index).map(Argument::Expression),
5269 }
5270 }
5271
5272 fn types(&self) -> Vec<Arc<Type>> {
5273 match self {
5274 Arguments::Expressions(call_args) => call_args
5275 .iter()
5276 .map(|argument| argument.value.type_())
5277 .collect_vec(),
5278
5279 Arguments::Patterns(call_args) => call_args
5280 .iter()
5281 .map(|argument| argument.value.type_())
5282 .collect_vec(),
5283 }
5284 }
5285}
5286
5287impl Argument<'_> {
5288 fn label(&self) -> Option<EcoString> {
5289 match self {
5290 Argument::Expression(call_arg) => call_arg.label.clone(),
5291 Argument::Pattern(call_arg) => call_arg.label.clone(),
5292 }
5293 }
5294}
5295
5296impl<'a, IO> GenerateVariant<'a, IO>
5297where
5298 IO: FileSystemReader + FileSystemWriter + BeamCompiler + CommandExecutor + Clone,
5299{
5300 pub fn new(
5301 module: &'a Module,
5302 compiler: &'a LspProjectCompiler<FileSystemProxy<IO>>,
5303 line_numbers: &'a LineNumbers,
5304 params: &'a CodeActionParams,
5305 ) -> Self {
5306 Self {
5307 module,
5308 params,
5309 compiler,
5310 line_numbers,
5311 variant_to_generate: None,
5312 }
5313 }
5314
5315 pub fn code_actions(mut self) -> Vec<CodeAction> {
5316 self.visit_typed_module(&self.module.ast);
5317
5318 let Some(VariantToGenerate {
5319 name,
5320 arguments_types,
5321 given_arguments,
5322 module_name,
5323 end_position,
5324 type_braces,
5325 }) = &self.variant_to_generate
5326 else {
5327 return vec![];
5328 };
5329
5330 let Some((variant_module, variant_edits)) = self.edits_to_create_variant(
5331 name,
5332 arguments_types,
5333 given_arguments,
5334 module_name,
5335 *end_position,
5336 *type_braces,
5337 ) else {
5338 return vec![];
5339 };
5340
5341 let mut action = Vec::with_capacity(1);
5342 CodeActionBuilder::new("Generate variant")
5343 .kind(CodeActionKind::REFACTOR_REWRITE)
5344 .changes(variant_module, variant_edits)
5345 .preferred(false)
5346 .push_to(&mut action);
5347 action
5348 }
5349
5350 /// Returns the edits needed to add this new variant to the given module.
5351 /// It also returns the uri of the module the edits should be applied to.
5352 ///
5353 fn edits_to_create_variant(
5354 &self,
5355 variant_name: &str,
5356 arguments_types: &[Arc<Type>],
5357 given_arguments: &Option<Arguments<'_>>,
5358 module_name: &EcoString,
5359 end_position: u32,
5360 type_braces: TypeBraces,
5361 ) -> Option<(Url, Vec<TextEdit>)> {
5362 let mut label_names = NameGenerator::new();
5363 let mut printer = Printer::new(&self.module.ast.names);
5364 let arguments = arguments_types
5365 .iter()
5366 .enumerate()
5367 .map(|(index, argument_type)| {
5368 let label = given_arguments
5369 .as_ref()
5370 .and_then(|arguments| arguments.get(index)?.label())
5371 .map(|label| label_names.rename_to_avoid_shadowing(label));
5372
5373 let pretty_type = printer.print_type(argument_type);
5374 if let Some(arg_label) = label {
5375 format!("{arg_label}: {pretty_type}")
5376 } else {
5377 format!("{pretty_type}")
5378 }
5379 })
5380 .join(", ");
5381
5382 let variant = if arguments.is_empty() {
5383 variant_name.to_string()
5384 } else {
5385 format!("{variant_name}({arguments})")
5386 };
5387
5388 let (new_text, insert_at) = match type_braces {
5389 TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1),
5390 TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position),
5391 };
5392
5393 if *module_name == self.module.name {
5394 // If we're editing the current module we can use the line numbers that
5395 // were already computed before-hand without wasting any time to add the
5396 // new edit.
5397 let mut edits = TextEdits::new(self.line_numbers);
5398 edits.insert(insert_at, new_text);
5399 Some((self.params.text_document.uri.clone(), edits.edits))
5400 } else {
5401 // Otherwise we're changing a different module and we need to get its
5402 // code and line numbers to properly apply the new edit.
5403 let module = self
5404 .compiler
5405 .modules
5406 .get(module_name)
5407 .expect("module to exist");
5408 let line_numbers = LineNumbers::new(&module.code);
5409 let mut edits = TextEdits::new(&line_numbers);
5410 edits.insert(insert_at, new_text);
5411 Some((url_from_path(module.input_path.as_str())?, edits.edits))
5412 }
5413 }
5414
5415 fn try_save_variant_to_generate(
5416 &mut self,
5417 function_name_location: SrcSpan,
5418 function_type: &Arc<Type>,
5419 given_arguments: Option<Arguments<'a>>,
5420 ) {
5421 let variant_to_generate =
5422 self.variant_to_generate(function_name_location, function_type, given_arguments);
5423 if variant_to_generate.is_some() {
5424 self.variant_to_generate = variant_to_generate;
5425 }
5426 }
5427
5428 fn variant_to_generate(
5429 &mut self,
5430 function_name_location: SrcSpan,
5431 type_: &Arc<Type>,
5432 given_arguments: Option<Arguments<'a>>,
5433 ) -> Option<VariantToGenerate<'a>> {
5434 let name_range = function_name_location.start as usize..function_name_location.end as usize;
5435 let name = self.module.code.get(name_range).expect("valid code range");
5436 if !is_valid_uppercase_name(name) {
5437 return None;
5438 }
5439
5440 let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) {
5441 (Some(result), _) => result,
5442 (None, Some(arguments)) => (arguments.types(), type_.clone()),
5443 (None, None) => (vec![], type_.clone()),
5444 };
5445
5446 let (module_name, type_name, _) = custom_type.named_type_information()?;
5447 let module = self.compiler.modules.get(&module_name)?;
5448 let (end_position, type_braces) =
5449 (module.ast.definitions.iter()).find_map(|definition| match definition {
5450 ast::Definition::CustomType(custom_type) if custom_type.name == type_name => {
5451 // If there's already a variant with this name then we definitely
5452 // don't want to generate a new variant with the same name!
5453 let variant_with_this_name_already_exists = custom_type
5454 .constructors
5455 .iter()
5456 .map(|constructor| &constructor.name)
5457 .any(|existing_constructor_name| existing_constructor_name == name);
5458 if variant_with_this_name_already_exists {
5459 return None;
5460 }
5461 let type_braces = if custom_type.end_position == custom_type.location.end {
5462 TypeBraces::NoBraces
5463 } else {
5464 TypeBraces::HasBraces
5465 };
5466 Some((custom_type.end_position, type_braces))
5467 }
5468 _ => None,
5469 })?;
5470
5471 Some(VariantToGenerate {
5472 name,
5473 arguments_types,
5474 given_arguments,
5475 module_name,
5476 end_position,
5477 type_braces,
5478 })
5479 }
5480}
5481
5482impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO>
5483where
5484 IO: FileSystemReader + FileSystemWriter + BeamCompiler + CommandExecutor + Clone,
5485{
5486 fn visit_typed_expr_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) {
5487 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
5488 if within(self.params.range, invalid_range) {
5489 self.try_save_variant_to_generate(*location, type_, None);
5490 }
5491 ast::visit::visit_typed_expr_invalid(self, location, type_);
5492 }
5493
5494 fn visit_typed_expr_call(
5495 &mut self,
5496 location: &'ast SrcSpan,
5497 type_: &'ast Arc<Type>,
5498 fun: &'ast TypedExpr,
5499 args: &'ast [TypedCallArg],
5500 ) {
5501 // If the function being called is invalid we need to generate a
5502 // function that has the proper labels.
5503 let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers);
5504 if within(self.params.range, fun_range) && fun.is_invalid() {
5505 if labels_are_correct(args) {
5506 self.try_save_variant_to_generate(
5507 fun.location(),
5508 &fun.type_(),
5509 Some(Arguments::Expressions(args)),
5510 );
5511 }
5512 } else {
5513 ast::visit::visit_typed_expr_call(self, location, type_, fun, args);
5514 }
5515 }
5516
5517 fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) {
5518 let invalid_range = src_span_to_lsp_range(*location, self.line_numbers);
5519 if within(self.params.range, invalid_range) {
5520 self.try_save_variant_to_generate(*location, type_, None);
5521 }
5522 ast::visit::visit_typed_pattern_invalid(self, location, type_);
5523 }
5524
5525 fn visit_typed_pattern_constructor(
5526 &mut self,
5527 location: &'ast SrcSpan,
5528 name_location: &'ast SrcSpan,
5529 name: &'ast EcoString,
5530 arguments: &'ast Vec<CallArg<TypedPattern>>,
5531 module: &'ast Option<(EcoString, SrcSpan)>,
5532 constructor: &'ast analyse::Inferred<type_::PatternConstructor>,
5533 spread: &'ast Option<SrcSpan>,
5534 type_: &'ast Arc<Type>,
5535 ) {
5536 let pattern_range = src_span_to_lsp_range(*location, self.line_numbers);
5537 // TODO)) Solo se il pattern non è valido!!!!!
5538 if within(self.params.range, pattern_range) {
5539 if labels_are_correct(arguments) {
5540 self.try_save_variant_to_generate(
5541 *name_location,
5542 type_,
5543 Some(Arguments::Patterns(arguments)),
5544 );
5545 }
5546 } else {
5547 ast::visit::visit_typed_pattern_constructor(
5548 self,
5549 location,
5550 name_location,
5551 name,
5552 arguments,
5553 module,
5554 constructor,
5555 spread,
5556 type_,
5557 );
5558 }
5559 }
5560}
5561
5562#[must_use]
5563/// Checks the labels in the given arguments are correct: that is there's no
5564/// duplicate labels and all labelled arguments come after the unlabelled ones.
5565fn labels_are_correct<A>(args: &[CallArg<A>]) -> bool {
5566 let mut labelled_arg_found = false;
5567 let mut used_labels = HashSet::new();
5568
5569 for arg in args {
5570 match &arg.label {
5571 // Labels are invalid if there's duplicate ones or if an unlabelled
5572 // argument comes after a labelled one.
5573 Some(label) if used_labels.contains(label) => return false,
5574 None if labelled_arg_found => return false,
5575 // Otherwise we just add the label to the used ones.
5576 Some(label) => {
5577 labelled_arg_found = true;
5578 let _ = used_labels.insert(label);
5579 }
5580 None => {}
5581 }
5582 }
5583
5584 true
5585}
5586
5587struct NameGenerator {
5588 used_names: HashSet<EcoString>,
5589}
5590
5591impl NameGenerator {
5592 pub fn new() -> Self {
5593 NameGenerator {
5594 used_names: HashSet::new(),
5595 }
5596 }
5597
5598 pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString {
5599 let mut i = 1;
5600 let mut candidate_name = base.clone();
5601
5602 loop {
5603 if self.used_names.contains(&candidate_name) {
5604 i += 1;
5605 candidate_name = eco_format!("{base}_{i}");
5606 } else {
5607 let _ = self.used_names.insert(candidate_name.clone());
5608 return candidate_name;
5609 }
5610 }
5611 }
5612
5613 /// Given an argument type and the actual call argument (if any), comes up
5614 /// with a label and a name to use for that argument when generating a
5615 /// function.
5616 ///
5617 pub fn generate_label_and_name(
5618 &mut self,
5619 call_argument: Option<&CallArg<TypedExpr>>,
5620 argument_type: &Arc<Type>,
5621 ) -> (Option<EcoString>, EcoString) {
5622 let label = call_argument.and_then(|argument| argument.label.clone());
5623 let argument_name = call_argument
5624 // We always favour a name derived from the expression (for example if
5625 // the argument is a variable)
5626 .and_then(|argument| self.generate_name_from_expression(&argument.value))
5627 // If we don't have such a name and there's a label we use that name.
5628 .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?)))
5629 // If all else fails we fallback to using a name derived from the
5630 // argument's type.
5631 .unwrap_or_else(|| self.generate_name_from_type(argument_type));
5632
5633 (label, argument_name)
5634 }
5635
5636 pub fn generate_name_from_type(&mut self, type_: &Arc<Type>) -> EcoString {
5637 let type_to_base_name = |type_: &Arc<Type>| {
5638 type_
5639 .named_type_name()
5640 .map(|(_type_module, type_name)| to_snake_case(&type_name))
5641 .filter(|name| is_valid_lowercase_name(name))
5642 .unwrap_or(EcoString::from("value"))
5643 };
5644
5645 let base_name = match type_.list_type() {
5646 None => type_to_base_name(type_),
5647 // If we're coming up with a name for a list we want to use the
5648 // plural form for the name of the inner type. For example:
5649 // `List(Pokemon)` should generate `pokemons`.
5650 Some(inner_type) => {
5651 let base_name = type_to_base_name(&inner_type);
5652 // If the inner type name already ends in "s" we leave it as it
5653 // is, or it would look funny.
5654 if base_name.ends_with('s') {
5655 base_name
5656 } else {
5657 eco_format!("{base_name}s")
5658 }
5659 }
5660 };
5661
5662 self.rename_to_avoid_shadowing(base_name)
5663 }
5664
5665 fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option<EcoString> {
5666 match expression {
5667 // If the argument is a record, we can't use it as an argument name.
5668 // Similarly, we don't want to base the variable name off a
5669 // compiler-generated variable like `_pipe`.
5670 TypedExpr::Var {
5671 name, constructor, ..
5672 } if !constructor.variant.is_record()
5673 && !constructor.variant.is_generated_variable() =>
5674 {
5675 Some(self.rename_to_avoid_shadowing(name.clone()))
5676 }
5677 _ => None,
5678 }
5679 }
5680
5681 pub fn add_used_name(&mut self, name: EcoString) {
5682 let _ = self.used_names.insert(name);
5683 }
5684
5685 pub fn reserve_all_labels(&mut self, field_map: &FieldMap) {
5686 field_map
5687 .fields
5688 .iter()
5689 .for_each(|(label, _)| self.add_used_name(label.clone()));
5690 }
5691
5692 pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) {
5693 variable_names
5694 .names
5695 .iter()
5696 .for_each(|name| self.add_used_name(name.clone()));
5697 }
5698}
5699
5700#[must_use]
5701fn is_valid_lowercase_name(name: &str) -> bool {
5702 if !name.starts_with(|char: char| char.is_ascii_lowercase()) {
5703 return false;
5704 }
5705
5706 for char in name.chars() {
5707 let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_';
5708 if !is_valid_char {
5709 return false;
5710 }
5711 }
5712
5713 str_to_keyword(name).is_none()
5714}
5715
5716#[must_use]
5717fn is_valid_uppercase_name(name: &str) -> bool {
5718 if !name.starts_with(|char: char| char.is_ascii_uppercase()) {
5719 return false;
5720 }
5721
5722 for char in name.chars() {
5723 if !char.is_ascii_alphanumeric() {
5724 return false;
5725 }
5726 }
5727
5728 true
5729}
5730
5731/// Code action to rewrite a single-step pipeline into a regular function call.
5732/// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`.
5733///
5734pub struct ConvertToFunctionCall<'a> {
5735 module: &'a Module,
5736 params: &'a CodeActionParams,
5737 edits: TextEdits<'a>,
5738 locations: Option<ConvertToFunctionCallLocations>,
5739}
5740
5741/// All the different locations the "Convert to function call" code action needs
5742/// to properly rewrite a pipeline into a function call.
5743///
5744struct ConvertToFunctionCallLocations {
5745 /// This is the location of the value being piped into a call.
5746 ///
5747 /// ```gleam
5748 /// [1, 2, 3] |> list.length
5749 /// // ^^^^^^^^^ This one here
5750 /// ```
5751 ///
5752 first_value: SrcSpan,
5753
5754 /// This is the location of the call the value is being piped into.
5755 ///
5756 /// ```gleam
5757 /// [1, 2, 3] |> list.length
5758 /// // ^^^^^^^^^^^ This one here
5759 /// ```
5760 ///
5761 call: SrcSpan,
5762
5763 /// This is the kind of desugaring that is taking place when piping
5764 /// `first_value` into `call`.
5765 ///
5766 call_kind: PipelineAssignmentKind,
5767}
5768
5769impl<'a> ConvertToFunctionCall<'a> {
5770 pub fn new(
5771 module: &'a Module,
5772 line_numbers: &'a LineNumbers,
5773 params: &'a CodeActionParams,
5774 ) -> Self {
5775 Self {
5776 module,
5777 params,
5778 edits: TextEdits::new(line_numbers),
5779 locations: None,
5780 }
5781 }
5782
5783 pub fn code_actions(mut self) -> Vec<CodeAction> {
5784 self.visit_typed_module(&self.module.ast);
5785
5786 // If we couldn't find a pipeline to rewrite we don't return any action.
5787 let Some(ConvertToFunctionCallLocations {
5788 first_value,
5789 call,
5790 call_kind,
5791 }) = self.locations
5792 else {
5793 return vec![];
5794 };
5795
5796 // We first delete the first value of the pipeline as it's going to be
5797 // inlined as a function call argument.
5798 self.edits.delete(SrcSpan {
5799 start: first_value.start,
5800 end: call.start,
5801 });
5802
5803 // Then we have to insert the piped value in the appropriate position.
5804 // This will change based on how the pipeline is being desugared, we
5805 // know this thanks to the `call_kind`
5806 let first_value_text = self
5807 .module
5808 .code
5809 .get(first_value.start as usize..first_value.end as usize)
5810 .expect("invalid code span")
5811 .to_string();
5812
5813 match call_kind {
5814 // When piping into a `_` we replace the hole with the piped value:
5815 // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`.
5816 PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text),
5817
5818 // When piping is desguared as a function call we need to add the
5819 // missing parentheses:
5820 // `[1, 2] |> length` becomes `length([1, 2])`
5821 PipelineAssignmentKind::FunctionCall => {
5822 self.edits.insert(call.end, format!("({first_value_text})"))
5823 }
5824
5825 // When the piped value is inserted as the first argument there's two
5826 // possible scenarios:
5827 // - there's a second argument as well: in that case we insert it
5828 // before the second arg and add a comma
5829 // - there's no other argument: `[1, 2] |> length()` becomes
5830 // `length([1, 2])`, we insert the value between the empty
5831 // parentheses
5832 PipelineAssignmentKind::FirstArgument {
5833 second_argument: Some(SrcSpan { start, .. }),
5834 } => self.edits.insert(start, format!("{first_value_text}, ")),
5835 PipelineAssignmentKind::FirstArgument {
5836 second_argument: None,
5837 } => self.edits.insert(call.end - 1, first_value_text),
5838
5839 // When the value is piped into an echo, to rewrite the pipeline we
5840 // have to insert the value after the `echo` with no parentheses:
5841 // `a |> echo` is rewritten as `echo a`.
5842 PipelineAssignmentKind::Echo => {
5843 self.edits.insert(call.end, format!(" {first_value_text}"))
5844 }
5845 }
5846
5847 let mut action = Vec::with_capacity(1);
5848 CodeActionBuilder::new("Convert to function call")
5849 .kind(CodeActionKind::REFACTOR_REWRITE)
5850 .changes(self.params.text_document.uri.clone(), self.edits.edits)
5851 .preferred(false)
5852 .push_to(&mut action);
5853 action
5854 }
5855}
5856
5857impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> {
5858 fn visit_typed_expr_pipeline(
5859 &mut self,
5860 location: &'ast SrcSpan,
5861 first_value: &'ast TypedPipelineAssignment,
5862 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
5863 finally: &'ast TypedExpr,
5864 finally_kind: &'ast PipelineAssignmentKind,
5865 ) {
5866 let pipeline_range = self.edits.src_span_to_lsp_range(*location);
5867 if within(self.params.range, pipeline_range) {
5868 // We will always desugar the pipeline's first step. If there's no
5869 // intermediate assignment it means we're dealing with a single step
5870 // pipeline and the call is `finally`.
5871 let (call, call_kind) = assignments
5872 .first()
5873 .map(|(call, kind)| (call.location, *kind))
5874 .unwrap_or_else(|| (finally.location(), *finally_kind));
5875
5876 self.locations = Some(ConvertToFunctionCallLocations {
5877 first_value: first_value.location,
5878 call,
5879 call_kind,
5880 });
5881
5882 ast::visit::visit_typed_expr_pipeline(
5883 self,
5884 location,
5885 first_value,
5886 assignments,
5887 finally,
5888 finally_kind,
5889 );
5890 }
5891 }
5892}
5893
5894/// Builder for code action to inline a variable.
5895///
5896pub struct InlineVariable<'a> {
5897 module: &'a Module,
5898 params: &'a CodeActionParams,
5899 edits: TextEdits<'a>,
5900 actions: Vec<CodeAction>,
5901}
5902
5903impl<'a> InlineVariable<'a> {
5904 pub fn new(
5905 module: &'a Module,
5906 line_numbers: &'a LineNumbers,
5907 params: &'a CodeActionParams,
5908 ) -> Self {
5909 Self {
5910 module,
5911 params,
5912 edits: TextEdits::new(line_numbers),
5913 actions: Vec::new(),
5914 }
5915 }
5916
5917 pub fn code_actions(mut self) -> Vec<CodeAction> {
5918 self.visit_typed_module(&self.module.ast);
5919
5920 self.actions
5921 }
5922
5923 fn maybe_inline(&mut self, location: SrcSpan) {
5924 let reference = match find_variable_references(&self.module.ast, location).as_slice() {
5925 [only_reference] => *only_reference,
5926 _ => return,
5927 };
5928
5929 let Some(ast::Statement::Assignment(assignment)) =
5930 self.module.ast.find_statement(location.start)
5931 else {
5932 return;
5933 };
5934
5935 // If the assignment does not simple bind a variable, for example:
5936 // ```gleam
5937 // let #(first, second, third)
5938 // io.println(first)
5939 // // ^ Inline here
5940 // ```
5941 // We can't inline it.
5942 if !matches!(assignment.pattern, Pattern::Variable { .. }) {
5943 return;
5944 }
5945
5946 // If the assignment was generated by the compiler, it doesn't have a
5947 // syntactical representation, so we can't inline it.
5948 if matches!(assignment.kind, AssignmentKind::Generated) {
5949 return;
5950 }
5951
5952 let value_location = assignment.value.location();
5953 let value = self
5954 .module
5955 .code
5956 .get(value_location.start as usize..value_location.end as usize)
5957 .expect("Span is valid");
5958
5959 match reference.kind {
5960 VariableReferenceKind::Variable => {
5961 self.edits.replace(reference.location, value.into());
5962 }
5963 VariableReferenceKind::LabelShorthand => {
5964 self.edits
5965 .insert(reference.location.end, format!(" {value}"));
5966 }
5967 }
5968
5969 let mut location = assignment.location;
5970
5971 let mut chars = self.module.code[location.end as usize..].chars();
5972 // Delete any whitespace after the removed statement
5973 while chars.next().is_some_and(char::is_whitespace) {
5974 location.end += 1;
5975 }
5976
5977 self.edits.delete(location);
5978
5979 CodeActionBuilder::new("Inline variable")
5980 .kind(CodeActionKind::REFACTOR_INLINE)
5981 .changes(
5982 self.params.text_document.uri.clone(),
5983 std::mem::take(&mut self.edits.edits),
5984 )
5985 .preferred(false)
5986 .push_to(&mut self.actions);
5987 }
5988}
5989
5990impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> {
5991 fn visit_typed_expr_var(
5992 &mut self,
5993 location: &'ast SrcSpan,
5994 constructor: &'ast ValueConstructor,
5995 _name: &'ast EcoString,
5996 ) {
5997 let range = self.edits.src_span_to_lsp_range(*location);
5998
5999 if !overlaps(self.params.range, range) {
6000 return;
6001 }
6002
6003 let type_::ValueConstructorVariant::LocalVariable { location, origin } =
6004 &constructor.variant
6005 else {
6006 return;
6007 };
6008
6009 // We can only inline variables assigned by `let` statements, as it
6010 //doesn't make sense to do so with any other kind of variable.
6011 match origin.declaration {
6012 VariableDeclaration::LetPattern => {}
6013 VariableDeclaration::UsePattern
6014 | VariableDeclaration::ClausePattern
6015 | VariableDeclaration::FunctionParameter
6016 | VariableDeclaration::Generated => return,
6017 }
6018
6019 self.maybe_inline(*location);
6020 }
6021
6022 fn visit_typed_pattern_variable(
6023 &mut self,
6024 location: &'ast SrcSpan,
6025 _name: &'ast EcoString,
6026 _type: &'ast Arc<Type>,
6027 origin: &'ast VariableOrigin,
6028 ) {
6029 // We can only inline variables assigned by `let` statements, as it
6030 //doesn't make sense to do so with any other kind of variable.
6031 match origin.declaration {
6032 VariableDeclaration::LetPattern => {}
6033 VariableDeclaration::UsePattern
6034 | VariableDeclaration::ClausePattern
6035 | VariableDeclaration::FunctionParameter
6036 | VariableDeclaration::Generated => return,
6037 }
6038
6039 let range = self.edits.src_span_to_lsp_range(*location);
6040
6041 if !overlaps(self.params.range, range) {
6042 return;
6043 }
6044
6045 self.maybe_inline(*location);
6046 }
6047}
6048
6049/// Builder for the "convert to pipe" code action.
6050///
6051/// ```gleam
6052/// pub fn main() {
6053/// wibble(wobble, woo)
6054/// // ^ [convert to pipe]
6055/// }
6056/// ```
6057///
6058/// Will turn the code into the following pipeline:
6059///
6060/// ```gleam
6061/// pub fn main() {
6062/// wobble |> wibble(woo)
6063/// }
6064/// ```
6065///
6066pub struct ConvertToPipe<'a> {
6067 module: &'a Module,
6068 params: &'a CodeActionParams,
6069 edits: TextEdits<'a>,
6070 argument_to_pipe: Option<ConvertToPipeArg<'a>>,
6071 /// this will be true if we're visiting the call on the right hand side of a
6072 /// use expression. So we can skip it and not try to turn it into a
6073 /// function.
6074 visiting_use_call: bool,
6075}
6076
6077/// Holds all the data needed by the "convert to pipe" code action to properly
6078/// rewrite a call into a pipe. Here's what each span means:
6079///
6080/// ```gleam
6081/// wibble(wobb|le, woo)
6082/// // ^^^^^^^^^^^^^^^^^^^^ call
6083/// // ^^^^^^ called
6084/// // ^^^^^^^ arg
6085/// // ^^^ next arg
6086/// ```
6087///
6088/// In this example `position` is 0, since the cursor is over the first
6089/// argument.
6090///
6091pub struct ConvertToPipeArg<'a> {
6092 /// The span of the called function.
6093 called: SrcSpan,
6094 /// The span of the entire function call.
6095 call: SrcSpan,
6096 /// The position (0-based) of the argument.
6097 position: usize,
6098 /// The argument we have to pipe.
6099 arg: &'a TypedCallArg,
6100 /// The span of the argument following the one we have to pipe, if there's
6101 /// any.
6102 next_arg: Option<SrcSpan>,
6103}
6104
6105impl<'a> ConvertToPipe<'a> {
6106 pub fn new(
6107 module: &'a Module,
6108 line_numbers: &'a LineNumbers,
6109 params: &'a CodeActionParams,
6110 ) -> Self {
6111 Self {
6112 module,
6113 params,
6114 edits: TextEdits::new(line_numbers),
6115 visiting_use_call: false,
6116 argument_to_pipe: None,
6117 }
6118 }
6119
6120 pub fn code_actions(mut self) -> Vec<CodeAction> {
6121 self.visit_typed_module(&self.module.ast);
6122
6123 let Some(ConvertToPipeArg {
6124 called,
6125 call,
6126 position,
6127 arg,
6128 next_arg,
6129 }) = self.argument_to_pipe
6130 else {
6131 return vec![];
6132 };
6133
6134 let arg_range = if arg.uses_label_shorthand() {
6135 arg.location.start as usize..arg.location.end as usize - 1
6136 } else if arg.label.is_some() {
6137 let value = arg.value.location();
6138 value.start as usize..value.end as usize
6139 } else {
6140 arg.location.start as usize..arg.location.end as usize
6141 };
6142
6143 let arg_text = self.module.code.get(arg_range).expect("invalid srcspan");
6144 let arg_text = match arg.value {
6145 // If the expression being piped is a binary operation with
6146 // precedence lower than pipes then we have to wrap it in curly
6147 // braces to not mess with the order of operations.
6148 TypedExpr::BinOp { name, .. } if name.precedence() < PIPE_PRECEDENCE => {
6149 &format!("{{ {arg_text} }}")
6150 }
6151 _ => arg_text,
6152 };
6153
6154 match next_arg {
6155 // When extracting an argument we never want to remove any explicit
6156 // label that was written down, so in case it is labelled (be it a
6157 // shorthand or not) we'll always replace the value with a `_`
6158 _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()),
6159 _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()),
6160
6161 // Now we can deal with unlabelled arguments:
6162 // If we're removing the first argument and there's other arguments
6163 // after it, we need to delete the comma that was separating the
6164 // two.
6165 Some(next_arg) if position == 0 => self.edits.delete(SrcSpan {
6166 start: arg.location.start,
6167 end: next_arg.start,
6168 }),
6169 // Otherwise, if we're deleting the first argument and there's
6170 // no other arguments following it, we remove the call's
6171 // parentheses.
6172 None if position == 0 => self.edits.delete(SrcSpan {
6173 start: called.end,
6174 end: call.end,
6175 }),
6176 // In all other cases we're piping something that is not the first
6177 // argument so we just replace it with an `_`.
6178 _ => self.edits.replace(arg.location, "_".into()),
6179 };
6180
6181 // Finally we can add the argument that was removed as the first step
6182 // of the newly defined pipeline.
6183 self.edits.insert(call.start, format!("{arg_text} |> "));
6184
6185 let mut action = Vec::with_capacity(1);
6186 CodeActionBuilder::new("Convert to pipe")
6187 .kind(CodeActionKind::REFACTOR_REWRITE)
6188 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6189 .preferred(false)
6190 .push_to(&mut action);
6191 action
6192 }
6193}
6194
6195impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> {
6196 fn visit_typed_expr_call(
6197 &mut self,
6198 location: &'ast SrcSpan,
6199 _type_: &'ast Arc<Type>,
6200 fun: &'ast TypedExpr,
6201 args: &'ast [TypedCallArg],
6202 ) {
6203 if args.iter().any(|arg| arg.is_capture_hole()) {
6204 return;
6205 }
6206
6207 // If we're visiting the typed function produced by typing a use, we
6208 // skip the thing itself and only visit its arguments and called
6209 // function, that is the body of the use.
6210 if self.visiting_use_call {
6211 self.visiting_use_call = false;
6212 ast::visit::visit_typed_expr(self, fun);
6213 args.iter()
6214 .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg));
6215 return;
6216 }
6217
6218 // We only visit a call if the cursor is somewhere within its location,
6219 // otherwise we skip it entirely.
6220 let call_range = self.edits.src_span_to_lsp_range(*location);
6221 if !within(self.params.range, call_range) {
6222 return;
6223 }
6224
6225 // If the cursor is over any of the arguments then we'll use that as
6226 // the one to extract.
6227 // Otherwise the cursor must be over the called function, in that case
6228 // we extract the first argument (if there's one):
6229 //
6230 // ```gleam
6231 // wibble(wobble, woo)
6232 // // ^^^^^^^^^^^^^ pipe the first argument if I'm here
6233 // // ^^^ pipe the second argument if I'm here
6234 // ```
6235 let argument_to_pipe = args
6236 .iter()
6237 .enumerate()
6238 .find_map(|(position, arg)| {
6239 let arg_range = self.edits.src_span_to_lsp_range(arg.location);
6240 if within(self.params.range, arg_range) {
6241 Some((position, arg))
6242 } else {
6243 None
6244 }
6245 })
6246 .or_else(|| args.first().map(|arg| (0, arg)));
6247
6248 // If we're not hovering over any of the arguments _or_ there's no
6249 // argument to extract at all we just return, there's nothing we can do
6250 // on this call or any of its arguments (since we've determined the
6251 // cursor is not over any of those).
6252 let Some((position, arg)) = argument_to_pipe else {
6253 return;
6254 };
6255
6256 self.argument_to_pipe = Some(ConvertToPipeArg {
6257 called: fun.location(),
6258 call: *location,
6259 position,
6260 arg,
6261 next_arg: args.get(position + 1).map(|arg| arg.location),
6262 })
6263 }
6264
6265 fn visit_typed_expr_pipeline(
6266 &mut self,
6267 _location: &'ast SrcSpan,
6268 first_value: &'ast TypedPipelineAssignment,
6269 _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
6270 _finally: &'ast TypedExpr,
6271 _finally_kind: &'ast PipelineAssignmentKind,
6272 ) {
6273 // We can only apply the action on the first step of a pipeline, so we
6274 // visit just that one and skip all the others.
6275 ast::visit::visit_typed_pipeline_assignment(self, first_value);
6276 }
6277
6278 fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
6279 self.visiting_use_call = true;
6280 ast::visit::visit_typed_use(self, use_);
6281 }
6282}
6283
6284/// Code action to interpolate a string. If the cursor is inside the string
6285/// (not selecting anything) the language server will offer to split it:
6286///
6287/// ```gleam
6288/// "wibble | wobble"
6289/// // ^ [Split string]
6290/// // Will produce the following
6291/// "wibble " <> todo <> " wobble"
6292/// ```
6293///
6294/// If the cursor is selecting an entire valid gleam name, then the language
6295/// server will offer to interpolate it as a variable:
6296///
6297/// ```gleam
6298/// "wibble wobble woo"
6299/// // ^^^^^^ [Interpolate variable]
6300/// // Will produce the following
6301/// "wibble " <> wobble <> " woo"
6302/// ```
6303///
6304/// > Note: the cursor won't end up right after the inserted variable/todo.
6305/// > that's a bit annoying, but in a future LSP version we will be able to
6306/// > isnert tab stops to allow one to jump to the newly added variable/todo.
6307///
6308pub struct InterpolateString<'a> {
6309 module: &'a Module,
6310 params: &'a CodeActionParams,
6311 edits: TextEdits<'a>,
6312 string_interpolation: Option<(SrcSpan, StringInterpolation)>,
6313 string_literal_position: StringLiteralPosition,
6314}
6315
6316#[derive(Debug, Clone, Copy, Eq, PartialEq)]
6317pub enum StringLiteralPosition {
6318 FirstPipelineStep,
6319 Other,
6320}
6321
6322#[derive(Clone, Copy)]
6323enum StringInterpolation {
6324 InterpolateValue { value_location: SrcSpan },
6325 SplitString { split_at: u32 },
6326}
6327
6328impl<'a> InterpolateString<'a> {
6329 pub fn new(
6330 module: &'a Module,
6331 line_numbers: &'a LineNumbers,
6332 params: &'a CodeActionParams,
6333 ) -> Self {
6334 Self {
6335 module,
6336 params,
6337 edits: TextEdits::new(line_numbers),
6338 string_interpolation: None,
6339 string_literal_position: StringLiteralPosition::Other,
6340 }
6341 }
6342
6343 pub fn code_actions(mut self) -> Vec<CodeAction> {
6344 self.visit_typed_module(&self.module.ast);
6345
6346 let Some((string_location, interpolation)) = self.string_interpolation else {
6347 return vec![];
6348 };
6349
6350 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
6351 self.edits.insert(string_location.start, "{ ".into());
6352 }
6353
6354 match interpolation {
6355 StringInterpolation::InterpolateValue { value_location } => {
6356 let name = self
6357 .module
6358 .code
6359 .get(value_location.start as usize..value_location.end as usize)
6360 .expect("invalid value range");
6361
6362 if is_valid_lowercase_name(name) {
6363 self.edits
6364 .insert(value_location.start, format!("\" <> {name} <> \""));
6365 self.edits.delete(value_location);
6366 } else if self.can_split_string_at(value_location.end) {
6367 // If the string is not a valid name we just try and split
6368 // the string at the end of the selection.
6369 self.edits
6370 .insert(value_location.end, "\" <> todo <> \"".into());
6371 } else {
6372 // Otherwise there's no meaningful action we can do.
6373 return vec![];
6374 }
6375 }
6376
6377 StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => {
6378 self.edits.insert(split_at, "\" <> todo <> \"".into());
6379 }
6380
6381 StringInterpolation::SplitString { .. } => return vec![],
6382 };
6383
6384 if self.string_literal_position == StringLiteralPosition::FirstPipelineStep {
6385 self.edits.insert(string_location.end, " }".into());
6386 }
6387
6388 let mut action = Vec::with_capacity(1);
6389 CodeActionBuilder::new("Interpolate string")
6390 .kind(CodeActionKind::REFACTOR_REWRITE)
6391 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6392 .preferred(false)
6393 .push_to(&mut action);
6394 action
6395 }
6396
6397 fn can_split_string_at(&self, at: u32) -> bool {
6398 self.string_interpolation
6399 .is_some_and(|(string_location, _)| {
6400 !(at <= string_location.start + 1 || at >= string_location.end - 1)
6401 })
6402 }
6403
6404 fn visit_literal_string(
6405 &mut self,
6406 string_location: SrcSpan,
6407 string_position: StringLiteralPosition,
6408 ) {
6409 // We can only interpolate/split a string if the cursor is somewhere
6410 // within its location, otherwise we skip it.
6411 let string_range = self.edits.src_span_to_lsp_range(string_location);
6412 if !within(self.params.range, string_range) {
6413 return;
6414 }
6415
6416 let selection @ SrcSpan { start, end } =
6417 self.edits.lsp_range_to_src_span(self.params.range);
6418
6419 let interpolation = if start == end {
6420 StringInterpolation::SplitString { split_at: start }
6421 } else {
6422 StringInterpolation::InterpolateValue {
6423 value_location: selection,
6424 }
6425 };
6426 self.string_interpolation = Some((string_location, interpolation));
6427 self.string_literal_position = string_position;
6428 }
6429}
6430
6431impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> {
6432 fn visit_typed_expr_string(
6433 &mut self,
6434 location: &'ast SrcSpan,
6435 _type_: &'ast Arc<Type>,
6436 _value: &'ast EcoString,
6437 ) {
6438 self.visit_literal_string(*location, StringLiteralPosition::Other);
6439 }
6440
6441 fn visit_typed_expr_pipeline(
6442 &mut self,
6443 _location: &'ast SrcSpan,
6444 first_value: &'ast TypedPipelineAssignment,
6445 assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
6446 finally: &'ast TypedExpr,
6447 _finally_kind: &'ast PipelineAssignmentKind,
6448 ) {
6449 if first_value.value.is_literal_string() {
6450 self.visit_literal_string(
6451 first_value.location,
6452 StringLiteralPosition::FirstPipelineStep,
6453 );
6454 } else {
6455 ast::visit::visit_typed_pipeline_assignment(self, first_value);
6456 }
6457
6458 assignments
6459 .iter()
6460 .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a));
6461 self.visit_typed_expr(finally);
6462 }
6463}
6464
6465/// Code action to replace a `..` in a pattern with all the missing fields that
6466/// have not been explicitly provided; labelled ones are introduced with the
6467/// shorthand syntax.
6468///
6469/// ```gleam
6470/// pub type Pokemon {
6471/// Pokemon(Int, name: String, moves: List(String))
6472/// }
6473///
6474/// pub fn main() {
6475/// let Pokemon(..) = todo
6476/// // ^^ Cursor over the spread
6477/// }
6478/// ```
6479/// Would become
6480/// ```gleam
6481/// pub fn main() {
6482/// let Pokemon(int, name:, moves:) = todo
6483/// }
6484///
6485pub struct FillUnusedFields<'a> {
6486 module: &'a Module,
6487 params: &'a CodeActionParams,
6488 edits: TextEdits<'a>,
6489 data: Option<FillUnusedFieldsData>,
6490}
6491
6492pub struct FillUnusedFieldsData {
6493 /// All the missing positional and labelled fields.
6494 positional: Vec<Arc<Type>>,
6495 labelled: Vec<(EcoString, Arc<Type>)>,
6496 /// We need this in order to tell where the missing positional arguments
6497 /// should be inserted.
6498 first_labelled_argument_start: Option<u32>,
6499 /// The end of the final argument before the spread, if there's any.
6500 /// We'll use this to delete everything that comes after the final argument,
6501 /// after adding all the ignored fields.
6502 last_argument_end: Option<u32>,
6503 spread_location: SrcSpan,
6504}
6505
6506impl<'a> FillUnusedFields<'a> {
6507 pub fn new(
6508 module: &'a Module,
6509 line_numbers: &'a LineNumbers,
6510 params: &'a CodeActionParams,
6511 ) -> Self {
6512 Self {
6513 module,
6514 params,
6515 edits: TextEdits::new(line_numbers),
6516 data: None,
6517 }
6518 }
6519
6520 pub fn code_actions(mut self) -> Vec<CodeAction> {
6521 self.visit_typed_module(&self.module.ast);
6522
6523 let Some(FillUnusedFieldsData {
6524 positional,
6525 labelled,
6526 first_labelled_argument_start,
6527 last_argument_end,
6528 spread_location,
6529 }) = self.data
6530 else {
6531 return vec![];
6532 };
6533
6534 // Do not suggest this code action if there's no ignored fields at all.
6535 if positional.is_empty() && labelled.is_empty() {
6536 return vec![];
6537 };
6538
6539 // We add all the missing positional arguments before the first
6540 // labelled one (and so after all the already existing positional ones).
6541 if !positional.is_empty() {
6542 // We want to make sure that all positional args will have a name
6543 // that's different from any label. So we add those as already used
6544 // names.
6545 let mut names = NameGenerator::new();
6546 for (label, _) in labelled.iter() {
6547 names.add_used_name(label.clone());
6548 }
6549
6550 let positional_args = positional
6551 .iter()
6552 .map(|type_| names.generate_name_from_type(type_))
6553 .join(", ");
6554 let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start);
6555
6556 // The positional arguments are going to be followed by some other
6557 // arguments if there's some already existing labelled args
6558 // (`last_argument_end.is_some`), of if we're adding those labelled args
6559 // ourselves (`!labelled.is_empty()`). So we need to put a comma after the
6560 // final positional argument we're adding to separate it from the ones that
6561 // are going to come after.
6562 let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty();
6563 let positional_args = if has_arguments_after {
6564 format!("{positional_args}, ")
6565 } else {
6566 positional_args
6567 };
6568
6569 self.edits.insert(insert_at, positional_args);
6570 }
6571
6572 if !labelled.is_empty() {
6573 // If there's labelled arguments to add, we replace the existing spread
6574 // with the arguments to be added. This way commas and all should already
6575 // be correct.
6576 let labelled_args = labelled
6577 .iter()
6578 .map(|(label, _)| format!("{label}:"))
6579 .join(", ");
6580 self.edits.replace(spread_location, labelled_args);
6581 } else if let Some(delete_start) = last_argument_end {
6582 // However, if there's no labelled arguments to insert we still need
6583 // to delete the entire spread: we start deleting from the end of the
6584 // final argument, if there's one.
6585 // This way we also get rid of any comma separating the last argument
6586 // and the spread to be removed.
6587 self.edits
6588 .delete(SrcSpan::new(delete_start, spread_location.end))
6589 } else {
6590 // Otherwise we just delete the spread.
6591 self.edits.delete(spread_location)
6592 }
6593
6594 let mut action = Vec::with_capacity(1);
6595 CodeActionBuilder::new("Fill unused fields")
6596 .kind(CodeActionKind::REFACTOR_REWRITE)
6597 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6598 .preferred(false)
6599 .push_to(&mut action);
6600 action
6601 }
6602}
6603
6604impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> {
6605 fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) {
6606 // We can only interpolate/split a string if the cursor is somewhere
6607 // within its location, otherwise we skip it.
6608 let pattern_range = self.edits.src_span_to_lsp_range(pattern.location());
6609 if !within(self.params.range, pattern_range) {
6610 return;
6611 }
6612
6613 if let TypedPattern::Constructor {
6614 arguments,
6615 spread: Some(spread_location),
6616 ..
6617 } = pattern
6618 {
6619 if let Some(PatternUnusedArguments {
6620 positional,
6621 labelled,
6622 }) = pattern.unused_arguments()
6623 {
6624 // If there's any unused argument that's being ignored we want to
6625 // suggest the code action.
6626 let first_labelled_argument_start = arguments
6627 .iter()
6628 .find(|arg| !arg.is_implicit() && arg.label.is_some())
6629 .map(|arg| arg.location.start);
6630
6631 let last_argument_end = arguments
6632 .iter()
6633 .filter(|arg| !arg.is_implicit())
6634 .next_back()
6635 .map(|arg| arg.location.end);
6636
6637 self.data = Some(FillUnusedFieldsData {
6638 positional,
6639 labelled,
6640 first_labelled_argument_start,
6641 last_argument_end,
6642 spread_location: *spread_location,
6643 });
6644 };
6645 }
6646
6647 ast::visit::visit_typed_pattern(self, pattern);
6648 }
6649}
6650
6651/// Code action to remove an echo.
6652///
6653pub struct RemoveEchos<'a> {
6654 module: &'a Module,
6655 params: &'a CodeActionParams,
6656 edits: TextEdits<'a>,
6657 is_hovering_echo: bool,
6658 echo_spans_to_delete: Vec<SrcSpan>,
6659 // We need to keep a reference to the two latest pipeline assignments we
6660 // run into to properly delete an echo that's inside a pipeline.
6661 latest_pipe_step: Option<SrcSpan>,
6662 second_to_latest_pipe_step: Option<SrcSpan>,
6663}
6664
6665impl<'a> RemoveEchos<'a> {
6666 pub fn new(
6667 module: &'a Module,
6668 line_numbers: &'a LineNumbers,
6669 params: &'a CodeActionParams,
6670 ) -> Self {
6671 Self {
6672 module,
6673 params,
6674 edits: TextEdits::new(line_numbers),
6675 is_hovering_echo: false,
6676 echo_spans_to_delete: vec![],
6677 latest_pipe_step: None,
6678 second_to_latest_pipe_step: None,
6679 }
6680 }
6681
6682 pub fn code_actions(mut self) -> Vec<CodeAction> {
6683 self.visit_typed_module(&self.module.ast);
6684
6685 // We only want to trigger the action if we're over one of the echos in
6686 // the module
6687 if !self.is_hovering_echo {
6688 return vec![];
6689 };
6690
6691 for span in self.echo_spans_to_delete {
6692 self.edits.delete(span);
6693 }
6694
6695 let mut action = Vec::with_capacity(1);
6696 CodeActionBuilder::new("Remove all `echo`s from this module")
6697 .kind(CodeActionKind::REFACTOR_REWRITE)
6698 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6699 .preferred(false)
6700 .push_to(&mut action);
6701 action
6702 }
6703
6704 fn visit_function_statements(&mut self, statements: &'a [TypedStatement]) {
6705 for i in 0..statements.len() {
6706 let statement = statements
6707 .get(i)
6708 .expect("Statement must exist in iteration");
6709 let next_statement = statements.get(i + 1);
6710 let is_last = i == statements.len() - 1;
6711
6712 match statement {
6713 // We remove any echo that is used as a standalone statement used
6714 // to print a literal value.
6715 //
6716 // ```gleam
6717 // pub fn main() {
6718 // echo "I'm here"
6719 // do_something()
6720 // echo "Safe!"
6721 // do_something_else()
6722 // }
6723 // ```
6724 //
6725 // Here we want to remove not just the echo but also the literal
6726 // strings they're printing.
6727 //
6728 // It's safe to do this only if echo is not the last expression
6729 // in a function's block (otherwise we might change the function's
6730 // return type by removing the entire line) and the value being
6731 // printed is a literal expression.
6732 //
6733 ast::Statement::Expression(TypedExpr::Echo {
6734 location,
6735 expression,
6736 ..
6737 }) if !is_last
6738 && expression.as_ref().is_some_and(|expression| {
6739 expression.is_literal() || expression.is_var()
6740 }) =>
6741 {
6742 let echo_range = self.edits.src_span_to_lsp_range(*location);
6743 if within(self.params.range, echo_range) {
6744 self.is_hovering_echo = true;
6745 }
6746
6747 let end = next_statement
6748 .map(|next| {
6749 let echo_end = location.end;
6750 let next_start = next.location().start;
6751 // We want to remove everything until the start of the
6752 // following statement. However, we have to be careful not to
6753 // delete any comments. So if there's any comment between the
6754 // echo to remove and the next statement, we just delete until
6755 // the comment's start.
6756 self.module
6757 .extra
6758 .first_comment_between(echo_end, next_start)
6759 // For comments we record the start of their content, not of the `//`
6760 // so we're subtracting 2 here to not delete the `//` as well
6761 .map(|comment| comment.start - 2)
6762 .unwrap_or(next_start)
6763 })
6764 .unwrap_or(location.end);
6765
6766 self.echo_spans_to_delete.push(SrcSpan {
6767 start: location.start,
6768 end,
6769 });
6770 }
6771
6772 // Otherwise we visit the statement as usual.
6773 ast::Statement::Expression(_)
6774 | ast::Statement::Assignment(_)
6775 | ast::Statement::Use(_)
6776 | ast::Statement::Assert(_) => ast::visit::visit_typed_statement(self, statement),
6777 }
6778 }
6779 }
6780}
6781
6782impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> {
6783 fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) {
6784 self.visit_function_statements(&fun.body);
6785 }
6786
6787 fn visit_typed_expr_fn(
6788 &mut self,
6789 _location: &'ast SrcSpan,
6790 _type_: &'ast Arc<Type>,
6791 _kind: &'ast FunctionLiteralKind,
6792 _args: &'ast [TypedArg],
6793 body: &'ast Vec1<TypedStatement>,
6794 _return_annotation: &'ast Option<ast::TypeAst>,
6795 ) {
6796 self.visit_function_statements(body);
6797 }
6798
6799 fn visit_typed_expr_echo(
6800 &mut self,
6801 location: &'ast SrcSpan,
6802 type_: &'ast Arc<Type>,
6803 expression: &'ast Option<Box<TypedExpr>>,
6804 ) {
6805 // We also want to trigger the action if we're hovering over the expression
6806 // being printed. So we create a unique span starting from the start of echo
6807 // end ending at the end of the expression.
6808 //
6809 // ```
6810 // echo 1 + 2
6811 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're
6812 // inside it, not just the keyword
6813 // ```
6814 //
6815 let echo_range = self.edits.src_span_to_lsp_range(*location);
6816 if within(self.params.range, echo_range) {
6817 self.is_hovering_echo = true;
6818 }
6819
6820 if let Some(expression) = expression {
6821 // If there's an expression we delete everything we find until its
6822 // start (excluded).
6823 let span_to_delete = SrcSpan::new(location.start, expression.location().start);
6824 self.echo_spans_to_delete.push(span_to_delete);
6825 } else {
6826 // Othwerise we know we're inside a pipeline, we take the closest step
6827 // that is not echo itself and delete everything from its end until the
6828 // end of the echo keyword:
6829 //
6830 // ```txt
6831 // wibble |> echo |> wobble
6832 // ^^^^^^^^ This span right here
6833 // ```
6834 let step_preceding_echo = self
6835 .latest_pipe_step
6836 .filter(|l| l != location)
6837 .or(self.second_to_latest_pipe_step);
6838 if let Some(step_preceding_echo) = step_preceding_echo {
6839 let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4);
6840 self.echo_spans_to_delete.push(span_to_delete);
6841 }
6842 }
6843
6844 ast::visit::visit_typed_expr_echo(self, location, type_, expression);
6845 }
6846
6847 fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
6848 if self.latest_pipe_step.is_some() {
6849 self.second_to_latest_pipe_step = self.latest_pipe_step;
6850 }
6851 self.latest_pipe_step = Some(assignment.location);
6852 ast::visit::visit_typed_pipeline_assignment(self, assignment);
6853 }
6854}
6855
6856/// Code action to wrap assignment and case clause values in a block.
6857///
6858/// ```gleam
6859/// pub type PokemonType {
6860/// Fire
6861/// Water
6862/// }
6863///
6864/// pub fn main() {
6865/// let pokemon_type: PokemonType = todo
6866/// case pokemon_type {
6867/// Water -> soak()
6868/// ^^^^^^ Cursor over the spread
6869/// Fire -> burn()
6870/// }
6871/// }
6872/// ```
6873/// Becomes
6874/// ```gleam
6875/// pub type PokemonType {
6876/// Fire
6877/// Water
6878/// }
6879///
6880/// pub fn main() {
6881/// let pokemon_type: PokemonType = todo
6882/// case pokemon_type {
6883/// Water -> {
6884/// soak()
6885/// }
6886/// Fire -> burn()
6887/// }
6888/// }
6889/// ```
6890///
6891pub struct WrapInBlock<'a> {
6892 module: &'a Module,
6893 params: &'a CodeActionParams,
6894 edits: TextEdits<'a>,
6895 selected_expression: Option<SrcSpan>,
6896}
6897
6898impl<'a> WrapInBlock<'a> {
6899 pub fn new(
6900 module: &'a Module,
6901 line_numbers: &'a LineNumbers,
6902 params: &'a CodeActionParams,
6903 ) -> Self {
6904 Self {
6905 module,
6906 params,
6907 edits: TextEdits::new(line_numbers),
6908 selected_expression: None,
6909 }
6910 }
6911
6912 pub fn code_actions(mut self) -> Vec<CodeAction> {
6913 self.visit_typed_module(&self.module.ast);
6914
6915 let Some(expr_span) = self.selected_expression else {
6916 return vec![];
6917 };
6918
6919 let Some(expr_string) = self
6920 .module
6921 .code
6922 .get(expr_span.start as usize..(expr_span.end as usize + 1))
6923 else {
6924 return vec![];
6925 };
6926
6927 let range = self
6928 .edits
6929 .src_span_to_lsp_range(self.selected_expression.expect("Real range value"));
6930
6931 let indent_size =
6932 count_indentation(&self.module.code, self.edits.line_numbers, range.start.line);
6933
6934 let expr_indent_size = indent_size + 2;
6935
6936 let indent = " ".repeat(indent_size);
6937 let inner_indent = " ".repeat(expr_indent_size);
6938
6939 self.edits.replace(
6940 expr_span,
6941 format!("{{\n{inner_indent}{expr_string}{indent}}}"),
6942 );
6943
6944 let mut action = Vec::with_capacity(1);
6945 CodeActionBuilder::new("Wrap in block")
6946 .kind(CodeActionKind::REFACTOR_EXTRACT)
6947 .changes(self.params.text_document.uri.clone(), self.edits.edits)
6948 .preferred(false)
6949 .push_to(&mut action);
6950 action
6951 }
6952}
6953
6954impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> {
6955 fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
6956 ast::visit::visit_typed_expr(self, &assignment.value);
6957 if !within(
6958 self.params.range,
6959 self.edits
6960 .src_span_to_lsp_range(assignment.value.location()),
6961 ) {
6962 return;
6963 }
6964 match &assignment.value {
6965 // To avoid wrapping the same expression in multiple, nested blocks.
6966 TypedExpr::Block { .. } => {}
6967 TypedExpr::RecordAccess { .. }
6968 | TypedExpr::Int { .. }
6969 | TypedExpr::Float { .. }
6970 | TypedExpr::String { .. }
6971 | TypedExpr::Pipeline { .. }
6972 | TypedExpr::Var { .. }
6973 | TypedExpr::Fn { .. }
6974 | TypedExpr::List { .. }
6975 | TypedExpr::Call { .. }
6976 | TypedExpr::BinOp { .. }
6977 | TypedExpr::Case { .. }
6978 | TypedExpr::ModuleSelect { .. }
6979 | TypedExpr::Tuple { .. }
6980 | TypedExpr::TupleIndex { .. }
6981 | TypedExpr::Todo { .. }
6982 | TypedExpr::Panic { .. }
6983 | TypedExpr::Echo { .. }
6984 | TypedExpr::BitArray { .. }
6985 | TypedExpr::RecordUpdate { .. }
6986 | TypedExpr::NegateBool { .. }
6987 | TypedExpr::NegateInt { .. }
6988 | TypedExpr::Invalid { .. } => {
6989 self.selected_expression = Some(assignment.value.location());
6990 }
6991 };
6992 ast::visit::visit_typed_assignment(self, assignment);
6993 }
6994
6995 fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) {
6996 ast::visit::visit_typed_clause(self, clause);
6997
6998 if !within(
6999 self.params.range,
7000 self.edits.src_span_to_lsp_range(clause.then.location()),
7001 ) {
7002 return;
7003 }
7004
7005 // To avoid wrapping the same expression in multiple, nested blocks.
7006 if !matches!(clause.then, TypedExpr::Block { .. }) {
7007 self.selected_expression = Some(clause.then.location());
7008 };
7009
7010 ast::visit::visit_typed_clause(self, clause);
7011 }
7012}
7013
7014/// Code action to fix wrong binary operators when the compiler can easily tell
7015/// what the correct alternative is.
7016///
7017/// ```gleam
7018///
7019/// ```
7020///
7021pub struct FixBinaryOperation<'a> {
7022 module: &'a Module,
7023 params: &'a CodeActionParams,
7024 edits: TextEdits<'a>,
7025 fix: Option<(SrcSpan, ast::BinOp)>,
7026}
7027
7028impl<'a> FixBinaryOperation<'a> {
7029 pub fn new(
7030 module: &'a Module,
7031 line_numbers: &'a LineNumbers,
7032 params: &'a CodeActionParams,
7033 ) -> Self {
7034 Self {
7035 module,
7036 params,
7037 edits: TextEdits::new(line_numbers),
7038 fix: None,
7039 }
7040 }
7041
7042 pub fn code_actions(mut self) -> Vec<CodeAction> {
7043 self.visit_typed_module(&self.module.ast);
7044
7045 let Some((location, replacement)) = self.fix else {
7046 return vec![];
7047 };
7048
7049 self.edits.replace(location, replacement.name().into());
7050
7051 let mut action = Vec::with_capacity(1);
7052 CodeActionBuilder::new(&format!("Use `{}`", replacement.name()))
7053 .kind(CodeActionKind::REFACTOR_REWRITE)
7054 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7055 .preferred(true)
7056 .push_to(&mut action);
7057 action
7058 }
7059}
7060
7061impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> {
7062 fn visit_typed_expr_bin_op(
7063 &mut self,
7064 location: &'ast SrcSpan,
7065 type_: &'ast Arc<Type>,
7066 name: &'ast ast::BinOp,
7067 name_location: &'ast SrcSpan,
7068 left: &'ast TypedExpr,
7069 right: &'ast TypedExpr,
7070 ) {
7071 let binop_range = self.edits.src_span_to_lsp_range(*location);
7072 if !within(self.params.range, binop_range) {
7073 return;
7074 }
7075
7076 if name.is_int_operator() && left.type_().is_float() && right.type_().is_float() {
7077 self.fix = name.float_equivalent().map(|fix| (*name_location, fix));
7078 } else if name.is_float_operator() && left.type_().is_int() && right.type_().is_int() {
7079 self.fix = name.int_equivalent().map(|fix| (*name_location, fix))
7080 } else if *name == ast::BinOp::AddInt
7081 && left.type_().is_string()
7082 && right.type_().is_string()
7083 {
7084 self.fix = Some((*name_location, ast::BinOp::Concatenate))
7085 }
7086
7087 ast::visit::visit_typed_expr_bin_op(
7088 self,
7089 location,
7090 type_,
7091 name,
7092 name_location,
7093 left,
7094 right,
7095 );
7096 }
7097}
7098
7099/// Code action builder to automatically fix segments that have a value that's
7100/// guaranteed to overflow.
7101///
7102pub struct FixTruncatedBitArraySegment<'a> {
7103 module: &'a Module,
7104 params: &'a CodeActionParams,
7105 edits: TextEdits<'a>,
7106 truncation: Option<BitArraySegmentTruncation>,
7107}
7108
7109impl<'a> FixTruncatedBitArraySegment<'a> {
7110 pub fn new(
7111 module: &'a Module,
7112 line_numbers: &'a LineNumbers,
7113 params: &'a CodeActionParams,
7114 ) -> Self {
7115 Self {
7116 module,
7117 params,
7118 edits: TextEdits::new(line_numbers),
7119 truncation: None,
7120 }
7121 }
7122
7123 pub fn code_actions(mut self) -> Vec<CodeAction> {
7124 self.visit_typed_module(&self.module.ast);
7125
7126 let Some(truncation) = self.truncation else {
7127 return vec![];
7128 };
7129
7130 let replacement = truncation.truncated_into.to_string();
7131 self.edits
7132 .replace(truncation.value_location, replacement.clone());
7133
7134 let mut action = Vec::with_capacity(1);
7135 CodeActionBuilder::new(&format!("Replace with `{}`", replacement))
7136 .kind(CodeActionKind::REFACTOR_REWRITE)
7137 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7138 .preferred(true)
7139 .push_to(&mut action);
7140 action
7141 }
7142}
7143
7144impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> {
7145 fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) {
7146 let segment_range = self.edits.src_span_to_lsp_range(segment.location);
7147 if !within(self.params.range, segment_range) {
7148 return;
7149 }
7150
7151 if let Some(truncation) = segment.check_for_truncated_value() {
7152 self.truncation = Some(truncation);
7153 }
7154
7155 ast::visit::visit_typed_expr_bit_array_segment(self, segment);
7156 }
7157}
7158
7159/// Code action builder to remove unused imports and values.
7160///
7161pub struct RemoveUnusedImports<'a> {
7162 module: &'a Module,
7163 params: &'a CodeActionParams,
7164 imports: Vec<&'a Import<EcoString>>,
7165 edits: TextEdits<'a>,
7166}
7167
7168#[derive(Debug)]
7169enum UnusedImport {
7170 ValueOrType(SrcSpan),
7171 Module(SrcSpan),
7172 ModuleAlias(SrcSpan),
7173}
7174
7175impl UnusedImport {
7176 fn location(&self) -> SrcSpan {
7177 match self {
7178 UnusedImport::ValueOrType(location)
7179 | UnusedImport::Module(location)
7180 | UnusedImport::ModuleAlias(location) => *location,
7181 }
7182 }
7183}
7184
7185impl<'a> RemoveUnusedImports<'a> {
7186 pub fn new(
7187 module: &'a Module,
7188 line_numbers: &'a LineNumbers,
7189 params: &'a CodeActionParams,
7190 ) -> Self {
7191 Self {
7192 module,
7193 params,
7194 edits: TextEdits::new(line_numbers),
7195 imports: vec![],
7196 }
7197 }
7198
7199 /// Given an import location, returns a list of the spans of all the
7200 /// unqualified values it's importing. Sorted by SrcSpan location.
7201 ///
7202 fn imported_values(&self, import_location: SrcSpan) -> Vec<SrcSpan> {
7203 self.imports
7204 .iter()
7205 .find(|import| import.location.contains(import_location.start))
7206 .map(|import| {
7207 let types = import.unqualified_types.iter().map(|type_| type_.location);
7208 let values = import.unqualified_values.iter().map(|value| value.location);
7209 types
7210 .chain(values)
7211 .sorted_by_key(|location| location.start)
7212 .collect_vec()
7213 })
7214 .unwrap_or_default()
7215 }
7216
7217 pub fn code_actions(mut self) -> Vec<CodeAction> {
7218 // If there's no import in the module then there can't be any unused
7219 // import to remove.
7220 self.visit_typed_module(&self.module.ast);
7221 if self.imports.is_empty() {
7222 return vec![];
7223 }
7224
7225 let unused_imports = (self.module.ast.type_info.warnings.iter())
7226 .filter_map(|warning| match warning {
7227 type_::Warning::UnusedImportedValue { location, .. } => {
7228 Some(UnusedImport::ValueOrType(*location))
7229 }
7230 type_::Warning::UnusedType {
7231 location,
7232 imported: true,
7233 ..
7234 } => Some(UnusedImport::ValueOrType(*location)),
7235 type_::Warning::UnusedImportedModule { location, .. } => {
7236 Some(UnusedImport::Module(*location))
7237 }
7238 type_::Warning::UnusedImportedModuleAlias { location, .. } => {
7239 Some(UnusedImport::ModuleAlias(*location))
7240 }
7241 _ => None,
7242 })
7243 .sorted_by_key(|import| import.location())
7244 .collect_vec();
7245
7246 // If the cursor is not over any of the unused imports then we don't offer
7247 // the code action.
7248 let hovering_unused_import = unused_imports.iter().any(|import| {
7249 let unused_range = self.edits.src_span_to_lsp_range(import.location());
7250 overlaps(self.params.range, unused_range)
7251 });
7252 if !hovering_unused_import {
7253 return vec![];
7254 }
7255
7256 // Otherwise we start removing all unused imports:
7257 for import in &unused_imports {
7258 match import {
7259 // When an entire module is unused we can delete its entire location
7260 // in the source code.
7261 UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => {
7262 if self.edits.line_numbers.spans_entire_line(location) {
7263 // If the unused module spans over the entire line then
7264 // we also take care of removing the following newline
7265 // characther!
7266 self.edits.delete(SrcSpan {
7267 start: location.start,
7268 end: location.end + 1,
7269 })
7270 } else {
7271 self.edits.delete(*location)
7272 }
7273 }
7274
7275 // When removing unused imported values we have to be a bit more
7276 // careful: an unused value might be followed or preceded by a
7277 // comma that we also need to remove!
7278 UnusedImport::ValueOrType(location) => {
7279 let imported = self.imported_values(*location);
7280 let unused_index = imported.binary_search(location);
7281 let is_last = unused_index.is_ok_and(|index| index == imported.len() - 1);
7282 let next_value = unused_index
7283 .ok()
7284 .and_then(|value_index| imported.get(value_index + 1));
7285 let previous_value = unused_index.ok().and_then(|value_index| {
7286 value_index
7287 .checked_sub(1)
7288 .and_then(|previous_index| imported.get(previous_index))
7289 });
7290 let previous_is_unused = previous_value.is_some_and(|previous| {
7291 unused_imports
7292 .as_slice()
7293 .binary_search_by_key(previous, |import| import.location())
7294 .is_ok()
7295 });
7296
7297 match (previous_value, next_value) {
7298 // If there's a value following the unused import we need
7299 // to remove all characters until its start!
7300 //
7301 // ```gleam
7302 // import wibble.{unused, used}
7303 // // ^^^^^^^^^^^ We need to remove all of this!
7304 // ```
7305 //
7306 (_, Some(next_value)) => self.edits.delete(SrcSpan {
7307 start: location.start,
7308 end: next_value.start,
7309 }),
7310
7311 // If this unused import is the last of the unuqualified
7312 // list and is preceded by another used value then we
7313 // need to do some additional cleanup and remove all
7314 // characters starting from its end.
7315 // (If the previous one is unused as well it will take
7316 // care of removing all the extra space)
7317 //
7318 // ```gleam
7319 // import wibble.{used, unused}
7320 // // ^^^^^^^^^^^^ We need to remove all of this!
7321 // ```
7322 //
7323 (Some(previous_value), _) if is_last && !previous_is_unused => {
7324 self.edits.delete(SrcSpan {
7325 start: previous_value.end,
7326 end: location.end,
7327 })
7328 }
7329
7330 // In all other cases it means that this is the only
7331 // item in the import list. We can just remove it.
7332 //
7333 // ```gleam
7334 // import wibble.{unused}
7335 // // ^^^^^^ We remove this import, the formatter will already
7336 // // take care of removing the empty curly braces
7337 // ```
7338 //
7339 (_, _) => self.edits.delete(*location),
7340 }
7341 }
7342 }
7343 }
7344
7345 let mut action = Vec::with_capacity(1);
7346 CodeActionBuilder::new("Remove unused imports")
7347 .kind(CodeActionKind::REFACTOR_REWRITE)
7348 .changes(self.params.text_document.uri.clone(), self.edits.edits)
7349 .preferred(true)
7350 .push_to(&mut action);
7351 action
7352 }
7353}
7354
7355impl<'ast> ast::visit::Visit<'ast> for RemoveUnusedImports<'ast> {
7356 fn visit_typed_module(&mut self, module: &'ast ast::TypedModule) {
7357 self.imports = module
7358 .definitions
7359 .iter()
7360 .filter_map(|definition| match definition {
7361 ast::Definition::Import(import) => Some(import),
7362 _ => None,
7363 })
7364 .collect_vec();
7365 }
7366}