diff --git a/compiler-core/src/language_server/code_action.rs b/compiler-core/src/language_server/code_action.rs index b75340e2dce..cd3b7949406 100644 --- a/compiler-core/src/language_server/code_action.rs +++ b/compiler-core/src/language_server/code_action.rs @@ -20,7 +20,7 @@ use crate::{ strings::to_snake_case, type_::{ self, FieldMap, ModuleValueConstructor, Type, TypeVar, TypedCallArg, ValueConstructor, - error::{ModuleSuggestion, VariableDeclaration, VariableOrigin}, + error::{ModuleSuggestion, VariableDeclaration, VariableOrigin, VariableSyntax}, printer::Printer, }, }; @@ -8196,3 +8196,76 @@ impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableBranches<'ast> { ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); } } + +/// Code action builder to discard unused variables. +pub struct DiscardUnusedVariables<'a> { + module: &'a Module, + params: &'a CodeActionParams, + edits: TextEdits<'a>, +} + +/// Struct used to parse each UnusedVariable warning into code editions. +struct UnusedVariable<'a> { + location: &'a SrcSpan, + origin: &'a VariableOrigin, +} + +impl<'a> DiscardUnusedVariables<'a> { + pub fn new( + module: &'a Module, + line_numbers: &'a LineNumbers, + params: &'a CodeActionParams, + ) -> Self { + Self { + module, + params, + edits: TextEdits::new(line_numbers), + } + } + + // Parse UnusedVariable warnings into code editions. + fn gen_edits(&mut self, unused_variable: &UnusedVariable<'a>) { + match unused_variable.origin.syntax { + VariableSyntax::Variable(_) => { + self.edits + .insert(unused_variable.location.start, ("_").to_string()); + } + VariableSyntax::LabelShorthand(_) => { + self.edits + .insert(unused_variable.location.end, (" _").to_string()); + } + VariableSyntax::AssignmentPattern => { + self.edits.delete(SrcSpan { + start: unused_variable.location.start - 4, // Remove 'as ' before variable name + end: unused_variable.location.end, + }); + } + VariableSyntax::Generated => (), + }; + } + + pub fn code_actions(mut self) -> Vec { + self.module + .ast + .type_info + .warnings + .iter() + // get all UnusedVariable warnings and parse them as UnusedVariable struct + .filter_map(|warning| match warning { + type_::Warning::UnusedVariable { location, origin } => { + Some(UnusedVariable { location, origin }) + } + _ => None, + }) + //Insert to self.edits the edits needed to discard each unused variable + .for_each(|unused_variable| self.gen_edits(&unused_variable)); + + let mut action = Vec::with_capacity(1); + CodeActionBuilder::new("Discard unused variables") + .kind(CodeActionKind::QUICKFIX) + .changes(self.params.text_document.uri.clone(), self.edits.edits) + .preferred(true) + .push_to(&mut action); + action + } +} diff --git a/compiler-core/src/language_server/engine.rs b/compiler-core/src/language_server/engine.rs index a07835cb4b4..51811e9ada1 100644 --- a/compiler-core/src/language_server/engine.rs +++ b/compiler-core/src/language_server/engine.rs @@ -44,8 +44,8 @@ use super::{ DownloadDependencies, MakeLocker, code_action::{ AddAnnotations, CodeActionBuilder, ConvertFromUse, ConvertToFunctionCall, ConvertToPipe, - ConvertToUse, ExpandFunctionCapture, ExtractConstant, ExtractVariable, - FillInMissingLabelledArgs, FillUnusedFields, FixBinaryOperation, + ConvertToUse, DiscardUnusedVariables, ExpandFunctionCapture, ExtractConstant, + ExtractVariable, FillInMissingLabelledArgs, FillUnusedFields, FixBinaryOperation, FixTruncatedBitArraySegment, GenerateDynamicDecoder, GenerateFunction, GenerateJsonEncoder, GenerateVariant, InlineVariable, InterpolateString, LetAssertToCase, PatternMatchOnValue, RedundantTupleInCaseSubject, RemoveEchos, RemoveUnusedImports, UseLabelShorthandSyntax, @@ -447,6 +447,7 @@ where actions.extend(WrapInBlock::new(module, &lines, ¶ms).code_actions()); actions.extend(RemoveBlock::new(module, &lines, ¶ms).code_actions()); actions.extend(RemovePrivateOpaque::new(module, &lines, ¶ms).code_actions()); + actions.extend(DiscardUnusedVariables::new(module, &lines, ¶ms).code_actions()); GenerateDynamicDecoder::new(module, &lines, ¶ms, &mut actions).code_actions(); GenerateJsonEncoder::new( module, diff --git a/compiler-core/src/language_server/tests/action.rs b/compiler-core/src/language_server/tests/action.rs index a716f269406..d3a06786c49 100644 --- a/compiler-core/src/language_server/tests/action.rs +++ b/compiler-core/src/language_server/tests/action.rs @@ -134,6 +134,7 @@ const REMOVE_BLOCK: &str = "Remove block"; const REMOVE_OPAQUE_FROM_PRIVATE_TYPE: &str = "Remove opaque from private type"; const COLLAPSE_NESTED_CASE: &str = "Collapse nested case"; const REMOVE_UNREACHABLE_BRANCHES: &str = "Remove unreachable branches"; +const DISCARD_UNUSED_VARIABLE: &str = "Discard unused variables"; macro_rules! assert_code_action { ($title:expr, $code:literal, $range:expr $(,)?) => { @@ -9797,6 +9798,63 @@ fn remove_unreachable_branches_does_not_pop_up_if_all_branches_are_reachable() { ); } +#[test] +fn discard_unused_variable() { + assert_code_action!( + DISCARD_UNUSED_VARIABLE, + "pub fn main() -> Nil{ + let x = 3 +Nil +}", + find_position_of("x").to_selection() + ); +} + +#[test] +fn discard_unused_variable_shorthand() { + assert_code_action!( + DISCARD_UNUSED_VARIABLE, + "pub type MyType { + Variant1(a: Int, b: Int) + Variant2(c: Int, d: Int) +} + +fn check_my_type(my_value: MyType) -> Int { + case my_value { + Variant1(b:, a:) -> a + Variant2(d:, c:) -> c + } +} + +pub fn main() -> Nil { + let instance = Variant1(1, 2) + + let _ = check_my_type(instance) + + Nil +}", + find_position_of("b:,").to_selection() + ); +} + +#[test] +fn discard_unused_variable_pattern() { + assert_code_action!( + DISCARD_UNUSED_VARIABLE, + "fn inner(all: List(Int)) -> Nil { + case all { + [_, ..] as inner -> Nil + [] -> Nil + } +} + +pub fn main() -> Nil { + inner([1, 2, 3]) +}", + find_position_of("as inner").to_selection() + ); +} + #[test] fn add_type_annotations_public_alias_to_internal_type_aliased_module() { let src = " diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__discard_unused_variable.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__discard_unused_variable.snap new file mode 100644 index 00000000000..8b2c9071090 --- /dev/null +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__discard_unused_variable.snap @@ -0,0 +1,17 @@ +--- +source: compiler-core/src/language_server/tests/action.rs +expression: "pub fn main() -> Nil{\n let x = 3\nNil\n}" +--- +----- BEFORE ACTION +pub fn main() -> Nil{ + let x = 3 + ↑ +Nil +} + + +----- AFTER ACTION +pub fn main() -> Nil{ + let _x = 3 +Nil +} diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__discard_unused_variable_pattern.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__discard_unused_variable_pattern.snap new file mode 100644 index 00000000000..dde4575727c --- /dev/null +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__discard_unused_variable_pattern.snap @@ -0,0 +1,29 @@ +--- +source: compiler-core/src/language_server/tests/action.rs +expression: "fn inner(all: List(Int)) -> Nil {\n case all {\n [_, ..] as inner -> Nil\n [] -> Nil\n }\n}\n\npub fn main() -> Nil {\n inner([1, 2, 3])\n}" +--- +----- BEFORE ACTION +fn inner(all: List(Int)) -> Nil { + case all { + [_, ..] as inner -> Nil + ↑ + [] -> Nil + } +} + +pub fn main() -> Nil { + inner([1, 2, 3]) +} + + +----- AFTER ACTION +fn inner(all: List(Int)) -> Nil { + case all { + [_, ..] -> Nil + [] -> Nil + } +} + +pub fn main() -> Nil { + inner([1, 2, 3]) +} diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__discard_unused_variable_shorthand.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__discard_unused_variable_shorthand.snap new file mode 100644 index 00000000000..f56a8ec81fa --- /dev/null +++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__action__discard_unused_variable_shorthand.snap @@ -0,0 +1,47 @@ +--- +source: compiler-core/src/language_server/tests/action.rs +expression: "pub type MyType {\n Variant1(a: Int, b: Int)\n Variant2(c: Int, d: Int)\n}\n\nfn check_my_type(my_value: MyType) -> Int {\n case my_value {\n Variant1(b:, a:) -> a\n Variant2(d:, c:) -> c\n }\n}\n\npub fn main() -> Nil {\n let instance = Variant1(1, 2)\n\n let _ = check_my_type(instance)\n\n Nil\n}" +--- +----- BEFORE ACTION +pub type MyType { + Variant1(a: Int, b: Int) + Variant2(c: Int, d: Int) +} + +fn check_my_type(my_value: MyType) -> Int { + case my_value { + Variant1(b:, a:) -> a + ↑ + Variant2(d:, c:) -> c + } +} + +pub fn main() -> Nil { + let instance = Variant1(1, 2) + + let _ = check_my_type(instance) + + Nil +} + + +----- AFTER ACTION +pub type MyType { + Variant1(a: Int, b: Int) + Variant2(c: Int, d: Int) +} + +fn check_my_type(my_value: MyType) -> Int { + case my_value { + Variant1(b: _, a:) -> a + Variant2(d: _, c:) -> c + } +} + +pub fn main() -> Nil { + let instance = Variant1(1, 2) + + let _ = check_my_type(instance) + + Nil +}