Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

refactor remove unused imports into its own code action builder

+155 -81
+141 -7
compiler-core/src/language_server/code_action.rs
··· 4 4 Error, STDLIB_PACKAGE_NAME, analyse, 5 5 ast::{ 6 6 self, AssignName, AssignmentKind, BitArraySegmentTruncation, CallArg, CustomType, 7 - FunctionLiteralKind, ImplicitCallArgOrigin, PIPE_PRECEDENCE, Pattern, 7 + FunctionLiteralKind, ImplicitCallArgOrigin, Import, PIPE_PRECEDENCE, Pattern, 8 8 PatternUnusedArguments, PipelineAssignmentKind, RecordConstructor, SrcSpan, TodoKind, 9 9 TypedArg, TypedAssignment, TypedExpr, TypedModuleConstant, TypedPattern, 10 10 TypedPipelineAssignment, TypedRecordConstructor, TypedStatement, TypedUse, ··· 1300 1300 } 1301 1301 1302 1302 pub struct QualifiedConstructor<'a> { 1303 - import: &'a ast::Import<EcoString>, 1303 + import: &'a Import<EcoString>, 1304 1304 module_aliased: bool, 1305 1305 used_name: EcoString, 1306 1306 constructor: EcoString, ··· 1343 1343 module_name: &EcoString, 1344 1344 constructor: &EcoString, 1345 1345 layer: ast::Layer, 1346 - ) -> Option<&'a ast::Import<EcoString>> { 1346 + ) -> Option<&'a Import<EcoString>> { 1347 1347 let mut matching_import = None; 1348 1348 1349 1349 for def in &self.module.ast.definitions { ··· 1608 1608 1609 1609 fn find_last_char_before_closing_brace(&self) -> Option<(usize, char)> { 1610 1610 let QualifiedConstructor { 1611 - import: ast::Import { location, .. }, 1611 + import: Import { location, .. }, 1612 1612 .. 1613 1613 } = self.qualified_constructor; 1614 1614 let import_code = self.get_import_code(); ··· 1632 1632 1633 1633 fn get_import_code(&self) -> &str { 1634 1634 let QualifiedConstructor { 1635 - import: ast::Import { location, .. }, 1635 + import: Import { location, .. }, 1636 1636 .. 1637 1637 } = self.qualified_constructor; 1638 1638 self.module ··· 1658 1658 // Handle inserting into an unbraced import 1659 1659 fn insert_into_unbraced_import(&self, name: String, module_aliased: bool) -> (u32, String) { 1660 1660 let QualifiedConstructor { 1661 - import: ast::Import { location, .. }, 1661 + import: Import { location, .. }, 1662 1662 .. 1663 1663 } = self.qualified_constructor; 1664 1664 if !module_aliased { ··· 1682 1682 // Handle inserting into a braced import 1683 1683 fn insert_into_braced_import(&self, name: String) -> (u32, String) { 1684 1684 let QualifiedConstructor { 1685 - import: ast::Import { location, .. }, 1685 + import: Import { location, .. }, 1686 1686 .. 1687 1687 } = self.qualified_constructor; 1688 1688 if let Some((pos, c)) = self.find_last_char_before_closing_brace() { ··· 7008 7008 ast::visit::visit_typed_expr_bit_array_segment(self, segment); 7009 7009 } 7010 7010 } 7011 + 7012 + /// Code action builder to remove unused imports and values. 7013 + /// 7014 + pub struct RemoveUnusedImports<'a> { 7015 + module: &'a Module, 7016 + params: &'a CodeActionParams, 7017 + imports: Vec<&'a Import<EcoString>>, 7018 + edits: TextEdits<'a>, 7019 + } 7020 + 7021 + #[derive(Debug)] 7022 + enum UnusedImport { 7023 + Value(SrcSpan), 7024 + Module(SrcSpan), 7025 + ModuleAlias(SrcSpan), 7026 + } 7027 + 7028 + impl UnusedImport { 7029 + fn location(&self) -> SrcSpan { 7030 + match self { 7031 + UnusedImport::Value(location) 7032 + | UnusedImport::Module(location) 7033 + | UnusedImport::ModuleAlias(location) => *location, 7034 + } 7035 + } 7036 + } 7037 + 7038 + impl<'a> RemoveUnusedImports<'a> { 7039 + pub fn new( 7040 + module: &'a Module, 7041 + line_numbers: &'a LineNumbers, 7042 + params: &'a CodeActionParams, 7043 + ) -> Self { 7044 + Self { 7045 + module, 7046 + params, 7047 + edits: TextEdits::new(line_numbers), 7048 + imports: vec![], 7049 + } 7050 + } 7051 + 7052 + pub fn code_actions(mut self) -> Vec<CodeAction> { 7053 + // If there's no import in the module then there can't be any unused 7054 + // import to remove. 7055 + self.visit_typed_module(&self.module.ast); 7056 + if self.imports.is_empty() { 7057 + return vec![]; 7058 + } 7059 + 7060 + let unused_imports = (self.module.ast.type_info.warnings.iter()) 7061 + .filter_map(|warning| match warning { 7062 + type_::Warning::UnusedImportedValue { location, .. } => { 7063 + Some(UnusedImport::Value(*location)) 7064 + } 7065 + type_::Warning::UnusedImportedModule { location, .. } => { 7066 + Some(UnusedImport::Module(*location)) 7067 + } 7068 + type_::Warning::UnusedImportedModuleAlias { location, .. } => { 7069 + Some(UnusedImport::ModuleAlias(*location)) 7070 + } 7071 + _ => None, 7072 + }) 7073 + .collect_vec(); 7074 + 7075 + println!("{:#?}", unused_imports); 7076 + println!("{:#?}", self.params.range); 7077 + 7078 + // If the cursor is not over any of the unused imports then we don't offer 7079 + // the code action. 7080 + let hovering_unused_import = unused_imports.iter().any(|import| { 7081 + let unused_range = self.edits.src_span_to_lsp_range(import.location()); 7082 + overlaps(self.params.range, unused_range) 7083 + }); 7084 + if !hovering_unused_import { 7085 + return vec![]; 7086 + } 7087 + 7088 + // Otherwise we start removing all unused imports: 7089 + for import in unused_imports { 7090 + match import { 7091 + // When an entire module is unused we can delete its entire location 7092 + // in the source code. 7093 + UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => { 7094 + if self.edits.line_numbers.spans_entire_line(&location) { 7095 + // If the unused module spans over the entire line then 7096 + // we also take care of removing the following newline 7097 + // characther! 7098 + self.edits.delete(SrcSpan { 7099 + start: location.start, 7100 + end: location.end + 1, 7101 + }) 7102 + } else { 7103 + self.edits.delete(location) 7104 + } 7105 + } 7106 + 7107 + // When removing unused imported values we have to be a bit more 7108 + // careful: an unused value might be followed by a comma that we 7109 + // also need to remove! So we have to look for this imported 7110 + // value and also delete all characters between it and the 7111 + // following imported value. For example: 7112 + // 7113 + // ```gleam 7114 + // import wibble.{wobble, woo} 7115 + // // ^^^^^^^^^^^ If wobble is unused we must remove 7116 + // // all of this! 7117 + // ``` 7118 + // 7119 + UnusedImport::Value(_) => continue, 7120 + } 7121 + } 7122 + 7123 + let mut action = Vec::with_capacity(1); 7124 + CodeActionBuilder::new("Remove unused imports") 7125 + .kind(CodeActionKind::REFACTOR_REWRITE) 7126 + .changes(self.params.text_document.uri.clone(), self.edits.edits) 7127 + .preferred(true) 7128 + .push_to(&mut action); 7129 + action 7130 + } 7131 + } 7132 + 7133 + impl<'ast> ast::visit::Visit<'ast> for RemoveUnusedImports<'ast> { 7134 + fn visit_typed_module(&mut self, module: &'ast ast::TypedModule) { 7135 + self.imports = module 7136 + .definitions 7137 + .iter() 7138 + .filter_map(|definition| match definition { 7139 + ast::Definition::Import(import) => Some(import), 7140 + _ => None, 7141 + }) 7142 + .collect_vec(); 7143 + } 7144 + }
+4 -72
compiler-core/src/language_server/engine.rs
··· 41 41 FillInMissingLabelledArgs, FillUnusedFields, FixBinaryOperation, 42 42 FixTruncatedBitArraySegment, GenerateDynamicDecoder, GenerateFunction, GenerateJsonEncoder, 43 43 GenerateVariant, InlineVariable, InterpolateString, LetAssertToCase, PatternMatchOnValue, 44 - RedundantTupleInCaseSubject, RemoveEchos, UseLabelShorthandSyntax, WrapInBlock, 45 - code_action_add_missing_patterns, code_action_convert_qualified_constructor_to_unqualified, 44 + RedundantTupleInCaseSubject, RemoveEchos, RemoveUnusedImports, UseLabelShorthandSyntax, 45 + WrapInBlock, code_action_add_missing_patterns, 46 + code_action_convert_qualified_constructor_to_unqualified, 46 47 code_action_convert_unqualified_constructor_to_qualified, code_action_import_module, 47 48 code_action_inexhaustive_let_to_case, 48 49 }, ··· 382 383 let lines = LineNumbers::new(&module.code); 383 384 384 385 code_action_unused_values(module, &lines, &params, &mut actions); 385 - code_action_unused_imports(module, &lines, &params, &mut actions); 386 + actions.extend(RemoveUnusedImports::new(module, &lines, &params).code_actions()); 386 387 code_action_convert_qualified_constructor_to_unqualified( 387 388 module, 388 389 &lines, ··· 1459 1460 } 1460 1461 } 1461 1462 1462 - /// Code action to remove unused imports. 1463 - /// 1464 - fn code_action_unused_imports( 1465 - module: &Module, 1466 - line_numbers: &LineNumbers, 1467 - params: &lsp::CodeActionParams, 1468 - actions: &mut Vec<CodeAction>, 1469 - ) { 1470 - let uri = &params.text_document.uri; 1471 - let unused: Vec<&SrcSpan> = module 1472 - .ast 1473 - .type_info 1474 - .warnings 1475 - .iter() 1476 - .filter_map(|warning| match warning { 1477 - type_::Warning::UnusedImportedModuleAlias { location, .. } 1478 - | type_::Warning::UnusedImportedModule { location, .. } => Some(location), 1479 - _ => None, 1480 - }) 1481 - .collect(); 1482 - 1483 - if unused.is_empty() { 1484 - return; 1485 - } 1486 - 1487 - let mut hovered = false; 1488 - let mut edits = Vec::with_capacity(unused.len()); 1489 - 1490 - for unused in unused { 1491 - let SrcSpan { start, end } = *unused; 1492 - 1493 - // If removing an unused alias or at the beginning of the file, don't backspace 1494 - // Otherwise, adjust the end position by 1 to ensure the entire line is deleted with the import. 1495 - let adjusted_end = if delete_line(unused, line_numbers) { 1496 - end + 1 1497 - } else { 1498 - end 1499 - }; 1500 - 1501 - let range = src_span_to_lsp_range(SrcSpan::new(start, adjusted_end), line_numbers); 1502 - // Keep track of whether any unused import has is where the cursor is 1503 - hovered = hovered || overlaps(params.range, range); 1504 - 1505 - edits.push(TextEdit { 1506 - range, 1507 - new_text: "".into(), 1508 - }); 1509 - } 1510 - 1511 - // If none of the imports are where the cursor is we do nothing 1512 - if !hovered { 1513 - return; 1514 - } 1515 - edits.sort_by_key(|edit| edit.range.start); 1516 - 1517 - CodeActionBuilder::new("Remove unused imports") 1518 - .kind(lsp_types::CodeActionKind::QUICKFIX) 1519 - .changes(uri.clone(), edits) 1520 - .preferred(true) 1521 - .push_to(actions); 1522 - } 1523 - 1524 1463 struct NameCorrection { 1525 1464 pub location: SrcSpan, 1526 1465 pub correction: EcoString, ··· 1576 1515 .push_to(actions); 1577 1516 } 1578 1517 } 1579 - } 1580 - 1581 - // Check if the edit empties a whole line; if so, delete the line. 1582 - fn delete_line(span: &SrcSpan, line_numbers: &LineNumbers) -> bool { 1583 - line_numbers.line_starts.iter().any(|&line_start| { 1584 - line_start == span.start && line_numbers.line_starts.contains(&(span.end + 1)) 1585 - }) 1586 1518 } 1587 1519 1588 1520 fn get_expr_qualified_name(expression: &TypedExpr) -> Option<(&EcoString, &EcoString)> {
+10 -2
compiler-core/src/line_numbers.rs
··· 1 - use std::collections::HashMap; 2 - 1 + use crate::ast::SrcSpan; 3 2 use lsp_types::Position; 3 + use std::collections::HashMap; 4 4 5 5 /// A struct which contains information about line numbers of a source file, 6 6 /// and can convert between byte offsets that are used in the compiler and ··· 135 135 } 136 136 137 137 u8_offset 138 + } 139 + 140 + /// Checks if the given span spans an entire line (excluding the newline 141 + /// character itself). 142 + pub fn spans_entire_line(&self, span: &SrcSpan) -> bool { 143 + self.line_starts.iter().any(|&line_start| { 144 + line_start == span.start && self.line_starts.contains(&(span.end + 1)) 145 + }) 138 146 } 139 147 } 140 148