···44 Error, STDLIB_PACKAGE_NAME, analyse,
55 ast::{
66 self, AssignName, AssignmentKind, BitArraySegmentTruncation, CallArg, CustomType,
77- FunctionLiteralKind, ImplicitCallArgOrigin, PIPE_PRECEDENCE, Pattern,
77+ FunctionLiteralKind, ImplicitCallArgOrigin, Import, PIPE_PRECEDENCE, Pattern,
88 PatternUnusedArguments, PipelineAssignmentKind, RecordConstructor, SrcSpan, TodoKind,
99 TypedArg, TypedAssignment, TypedExpr, TypedModuleConstant, TypedPattern,
1010 TypedPipelineAssignment, TypedRecordConstructor, TypedStatement, TypedUse,
···13001300}
1301130113021302pub struct QualifiedConstructor<'a> {
13031303- import: &'a ast::Import<EcoString>,
13031303+ import: &'a Import<EcoString>,
13041304 module_aliased: bool,
13051305 used_name: EcoString,
13061306 constructor: EcoString,
···13431343 module_name: &EcoString,
13441344 constructor: &EcoString,
13451345 layer: ast::Layer,
13461346- ) -> Option<&'a ast::Import<EcoString>> {
13461346+ ) -> Option<&'a Import<EcoString>> {
13471347 let mut matching_import = None;
1348134813491349 for def in &self.module.ast.definitions {
···1608160816091609 fn find_last_char_before_closing_brace(&self) -> Option<(usize, char)> {
16101610 let QualifiedConstructor {
16111611- import: ast::Import { location, .. },
16111611+ import: Import { location, .. },
16121612 ..
16131613 } = self.qualified_constructor;
16141614 let import_code = self.get_import_code();
···1632163216331633 fn get_import_code(&self) -> &str {
16341634 let QualifiedConstructor {
16351635- import: ast::Import { location, .. },
16351635+ import: Import { location, .. },
16361636 ..
16371637 } = self.qualified_constructor;
16381638 self.module
···16581658 // Handle inserting into an unbraced import
16591659 fn insert_into_unbraced_import(&self, name: String, module_aliased: bool) -> (u32, String) {
16601660 let QualifiedConstructor {
16611661- import: ast::Import { location, .. },
16611661+ import: Import { location, .. },
16621662 ..
16631663 } = self.qualified_constructor;
16641664 if !module_aliased {
···16821682 // Handle inserting into a braced import
16831683 fn insert_into_braced_import(&self, name: String) -> (u32, String) {
16841684 let QualifiedConstructor {
16851685- import: ast::Import { location, .. },
16851685+ import: Import { location, .. },
16861686 ..
16871687 } = self.qualified_constructor;
16881688 if let Some((pos, c)) = self.find_last_char_before_closing_brace() {
···70087008 ast::visit::visit_typed_expr_bit_array_segment(self, segment);
70097009 }
70107010}
70117011+70127012+/// Code action builder to remove unused imports and values.
70137013+///
70147014+pub struct RemoveUnusedImports<'a> {
70157015+ module: &'a Module,
70167016+ params: &'a CodeActionParams,
70177017+ imports: Vec<&'a Import<EcoString>>,
70187018+ edits: TextEdits<'a>,
70197019+}
70207020+70217021+#[derive(Debug)]
70227022+enum UnusedImport {
70237023+ Value(SrcSpan),
70247024+ Module(SrcSpan),
70257025+ ModuleAlias(SrcSpan),
70267026+}
70277027+70287028+impl UnusedImport {
70297029+ fn location(&self) -> SrcSpan {
70307030+ match self {
70317031+ UnusedImport::Value(location)
70327032+ | UnusedImport::Module(location)
70337033+ | UnusedImport::ModuleAlias(location) => *location,
70347034+ }
70357035+ }
70367036+}
70377037+70387038+impl<'a> RemoveUnusedImports<'a> {
70397039+ pub fn new(
70407040+ module: &'a Module,
70417041+ line_numbers: &'a LineNumbers,
70427042+ params: &'a CodeActionParams,
70437043+ ) -> Self {
70447044+ Self {
70457045+ module,
70467046+ params,
70477047+ edits: TextEdits::new(line_numbers),
70487048+ imports: vec![],
70497049+ }
70507050+ }
70517051+70527052+ pub fn code_actions(mut self) -> Vec<CodeAction> {
70537053+ // If there's no import in the module then there can't be any unused
70547054+ // import to remove.
70557055+ self.visit_typed_module(&self.module.ast);
70567056+ if self.imports.is_empty() {
70577057+ return vec![];
70587058+ }
70597059+70607060+ let unused_imports = (self.module.ast.type_info.warnings.iter())
70617061+ .filter_map(|warning| match warning {
70627062+ type_::Warning::UnusedImportedValue { location, .. } => {
70637063+ Some(UnusedImport::Value(*location))
70647064+ }
70657065+ type_::Warning::UnusedImportedModule { location, .. } => {
70667066+ Some(UnusedImport::Module(*location))
70677067+ }
70687068+ type_::Warning::UnusedImportedModuleAlias { location, .. } => {
70697069+ Some(UnusedImport::ModuleAlias(*location))
70707070+ }
70717071+ _ => None,
70727072+ })
70737073+ .collect_vec();
70747074+70757075+ println!("{:#?}", unused_imports);
70767076+ println!("{:#?}", self.params.range);
70777077+70787078+ // If the cursor is not over any of the unused imports then we don't offer
70797079+ // the code action.
70807080+ let hovering_unused_import = unused_imports.iter().any(|import| {
70817081+ let unused_range = self.edits.src_span_to_lsp_range(import.location());
70827082+ overlaps(self.params.range, unused_range)
70837083+ });
70847084+ if !hovering_unused_import {
70857085+ return vec![];
70867086+ }
70877087+70887088+ // Otherwise we start removing all unused imports:
70897089+ for import in unused_imports {
70907090+ match import {
70917091+ // When an entire module is unused we can delete its entire location
70927092+ // in the source code.
70937093+ UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => {
70947094+ if self.edits.line_numbers.spans_entire_line(&location) {
70957095+ // If the unused module spans over the entire line then
70967096+ // we also take care of removing the following newline
70977097+ // characther!
70987098+ self.edits.delete(SrcSpan {
70997099+ start: location.start,
71007100+ end: location.end + 1,
71017101+ })
71027102+ } else {
71037103+ self.edits.delete(location)
71047104+ }
71057105+ }
71067106+71077107+ // When removing unused imported values we have to be a bit more
71087108+ // careful: an unused value might be followed by a comma that we
71097109+ // also need to remove! So we have to look for this imported
71107110+ // value and also delete all characters between it and the
71117111+ // following imported value. For example:
71127112+ //
71137113+ // ```gleam
71147114+ // import wibble.{wobble, woo}
71157115+ // // ^^^^^^^^^^^ If wobble is unused we must remove
71167116+ // // all of this!
71177117+ // ```
71187118+ //
71197119+ UnusedImport::Value(_) => continue,
71207120+ }
71217121+ }
71227122+71237123+ let mut action = Vec::with_capacity(1);
71247124+ CodeActionBuilder::new("Remove unused imports")
71257125+ .kind(CodeActionKind::REFACTOR_REWRITE)
71267126+ .changes(self.params.text_document.uri.clone(), self.edits.edits)
71277127+ .preferred(true)
71287128+ .push_to(&mut action);
71297129+ action
71307130+ }
71317131+}
71327132+71337133+impl<'ast> ast::visit::Visit<'ast> for RemoveUnusedImports<'ast> {
71347134+ fn visit_typed_module(&mut self, module: &'ast ast::TypedModule) {
71357135+ self.imports = module
71367136+ .definitions
71377137+ .iter()
71387138+ .filter_map(|definition| match definition {
71397139+ ast::Definition::Import(import) => Some(import),
71407140+ _ => None,
71417141+ })
71427142+ .collect_vec();
71437143+ }
71447144+}
+4-72
compiler-core/src/language_server/engine.rs
···4141 FillInMissingLabelledArgs, FillUnusedFields, FixBinaryOperation,
4242 FixTruncatedBitArraySegment, GenerateDynamicDecoder, GenerateFunction, GenerateJsonEncoder,
4343 GenerateVariant, InlineVariable, InterpolateString, LetAssertToCase, PatternMatchOnValue,
4444- RedundantTupleInCaseSubject, RemoveEchos, UseLabelShorthandSyntax, WrapInBlock,
4545- code_action_add_missing_patterns, code_action_convert_qualified_constructor_to_unqualified,
4444+ RedundantTupleInCaseSubject, RemoveEchos, RemoveUnusedImports, UseLabelShorthandSyntax,
4545+ WrapInBlock, code_action_add_missing_patterns,
4646+ code_action_convert_qualified_constructor_to_unqualified,
4647 code_action_convert_unqualified_constructor_to_qualified, code_action_import_module,
4748 code_action_inexhaustive_let_to_case,
4849 },
···382383 let lines = LineNumbers::new(&module.code);
383384384385 code_action_unused_values(module, &lines, ¶ms, &mut actions);
385385- code_action_unused_imports(module, &lines, ¶ms, &mut actions);
386386+ actions.extend(RemoveUnusedImports::new(module, &lines, ¶ms).code_actions());
386387 code_action_convert_qualified_constructor_to_unqualified(
387388 module,
388389 &lines,
···14591460 }
14601461}
1461146214621462-/// Code action to remove unused imports.
14631463-///
14641464-fn code_action_unused_imports(
14651465- module: &Module,
14661466- line_numbers: &LineNumbers,
14671467- params: &lsp::CodeActionParams,
14681468- actions: &mut Vec<CodeAction>,
14691469-) {
14701470- let uri = ¶ms.text_document.uri;
14711471- let unused: Vec<&SrcSpan> = module
14721472- .ast
14731473- .type_info
14741474- .warnings
14751475- .iter()
14761476- .filter_map(|warning| match warning {
14771477- type_::Warning::UnusedImportedModuleAlias { location, .. }
14781478- | type_::Warning::UnusedImportedModule { location, .. } => Some(location),
14791479- _ => None,
14801480- })
14811481- .collect();
14821482-14831483- if unused.is_empty() {
14841484- return;
14851485- }
14861486-14871487- let mut hovered = false;
14881488- let mut edits = Vec::with_capacity(unused.len());
14891489-14901490- for unused in unused {
14911491- let SrcSpan { start, end } = *unused;
14921492-14931493- // If removing an unused alias or at the beginning of the file, don't backspace
14941494- // Otherwise, adjust the end position by 1 to ensure the entire line is deleted with the import.
14951495- let adjusted_end = if delete_line(unused, line_numbers) {
14961496- end + 1
14971497- } else {
14981498- end
14991499- };
15001500-15011501- let range = src_span_to_lsp_range(SrcSpan::new(start, adjusted_end), line_numbers);
15021502- // Keep track of whether any unused import has is where the cursor is
15031503- hovered = hovered || overlaps(params.range, range);
15041504-15051505- edits.push(TextEdit {
15061506- range,
15071507- new_text: "".into(),
15081508- });
15091509- }
15101510-15111511- // If none of the imports are where the cursor is we do nothing
15121512- if !hovered {
15131513- return;
15141514- }
15151515- edits.sort_by_key(|edit| edit.range.start);
15161516-15171517- CodeActionBuilder::new("Remove unused imports")
15181518- .kind(lsp_types::CodeActionKind::QUICKFIX)
15191519- .changes(uri.clone(), edits)
15201520- .preferred(true)
15211521- .push_to(actions);
15221522-}
15231523-15241463struct NameCorrection {
15251464 pub location: SrcSpan,
15261465 pub correction: EcoString,
···15761515 .push_to(actions);
15771516 }
15781517 }
15791579-}
15801580-15811581-// Check if the edit empties a whole line; if so, delete the line.
15821582-fn delete_line(span: &SrcSpan, line_numbers: &LineNumbers) -> bool {
15831583- line_numbers.line_starts.iter().any(|&line_start| {
15841584- line_start == span.start && line_numbers.line_starts.contains(&(span.end + 1))
15851585- })
15861518}
1587151915881520fn get_expr_qualified_name(expression: &TypedExpr) -> Option<(&EcoString, &EcoString)> {
+10-2
compiler-core/src/line_numbers.rs
···11-use std::collections::HashMap;
22-11+use crate::ast::SrcSpan;
32use lsp_types::Position;
33+use std::collections::HashMap;
4455/// A struct which contains information about line numbers of a source file,
66/// and can convert between byte offsets that are used in the compiler and
···135135 }
136136137137 u8_offset
138138+ }
139139+140140+ /// Checks if the given span spans an entire line (excluding the newline
141141+ /// character itself).
142142+ pub fn spans_entire_line(&self, span: &SrcSpan) -> bool {
143143+ self.line_starts.iter().any(|&line_start| {
144144+ line_start == span.start && self.line_starts.contains(&(span.end + 1))
145145+ })
138146 }
139147}
140148