From e80baea3c0743793589b78d069ed6b0b7cf0041b Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 27 May 2024 11:28:32 +0200 Subject: [PATCH 1/4] code actions for removing dead code in the current file + each dead item, driven by reanalyze --- CHANGELOG.md | 1 + client/src/commands/code_analysis.ts | 36 ++++++++++++++++++++++++++++ client/src/extension.ts | 32 ++++++++++++++++++++++--- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8533bb6d5..6b7a4e82f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - Complete `%todo`. https://github.com/rescript-lang/rescript-vscode/pull/981 - Add code action for extracting a locally defined module into its own file. https://github.com/rescript-lang/rescript-vscode/pull/983 - Add code action for expanding catch-all patterns. https://github.com/rescript-lang/rescript-vscode/pull/987 +- Add code actions for removing unused code (per item and for an entire file), driven by `reanalyze`. ## 1.50.0 diff --git a/client/src/commands/code_analysis.ts b/client/src/commands/code_analysis.ts index 557747882..21f550aa9 100644 --- a/client/src/commands/code_analysis.ts +++ b/client/src/commands/code_analysis.ts @@ -119,6 +119,42 @@ let resultsToDiagnostics = ( } } } + + if (item.message.endsWith(" is never used")) { + { + let codeAction = new CodeAction("Remove unused"); + codeAction.kind = CodeActionKind.RefactorRewrite; + + let codeActionEdit = new WorkspaceEdit(); + + // In the future, it would be cool to have an additional code action + // here for automatically removing whatever the thing that's dead is. + codeActionEdit.replace( + Uri.parse(item.file), + // Make sure the full line is replaced + + new Range( + new Position(item.range[0], item.range[1]), + new Position(item.range[2], item.range[3]) + ), + // reanalyze seems to add two extra spaces at the start of the line + // content to replace. + "" + ); + + codeAction.edit = codeActionEdit; + + if (diagnosticsResultCodeActions.has(item.file)) { + diagnosticsResultCodeActions + .get(item.file) + .push({ range: issueLocationRange, codeAction }); + } else { + diagnosticsResultCodeActions.set(item.file, [ + { range: issueLocationRange, codeAction }, + ]); + } + } + } } }); diff --git a/client/src/extension.ts b/client/src/extension.ts index 5e34dae37..92aa75ffa 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -9,6 +9,9 @@ import { Uri, Range, Position, + CodeAction, + WorkspaceEdit, + CodeActionKind, } from "vscode"; import { @@ -17,7 +20,7 @@ import { ServerOptions, State, Executable, - TransportKind + TransportKind, } from "vscode-languageclient/node"; import * as customCommands from "./commands"; @@ -91,7 +94,11 @@ export function activate(context: ExtensionContext) { // If the extension is launched in debug mode then the debug server options are used // Otherwise the run options are used let serverOptions: ServerOptions = { - run: { module: serverModule, args: ["--node-ipc"], transport: TransportKind.ipc }, + run: { + module: serverModule, + args: ["--node-ipc"], + transport: TransportKind.ipc, + }, debug: { module: serverModule, args: ["--node-ipc"], @@ -189,12 +196,31 @@ export function activate(context: ExtensionContext) { let availableActions = diagnosticsResultCodeActions.get(document.uri.fsPath) ?? []; - return availableActions + const allRemoveActionEdits = availableActions.filter( + ({ codeAction }) => codeAction.title === "Remove unused" + ); + + const actions: CodeAction[] = availableActions .filter( ({ range }) => range.contains(rangeOrSelection) || range.isEqual(rangeOrSelection) ) .map(({ codeAction }) => codeAction); + + if (allRemoveActionEdits.length > 0) { + const removeAllCodeAction = new CodeAction("Remove all unused in file"); + const edit = new WorkspaceEdit(); + allRemoveActionEdits.forEach((subEdit) => { + subEdit.codeAction.edit.entries().forEach(([uri, [textEdit]]) => { + edit.replace(uri, textEdit.range, textEdit.newText); + }); + }); + removeAllCodeAction.kind = CodeActionKind.RefactorRewrite; + removeAllCodeAction.edit = edit; + actions.push(removeAllCodeAction); + } + + return actions; }, }); From 94540700d4dfa9be6b24ff261f1b04ac0c189a2a Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 27 May 2024 11:32:25 +0200 Subject: [PATCH 2/4] changelog and cleanup --- CHANGELOG.md | 2 +- client/src/commands/code_analysis.ts | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b7a4e82f..12fa9b0bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ - Complete `%todo`. https://github.com/rescript-lang/rescript-vscode/pull/981 - Add code action for extracting a locally defined module into its own file. https://github.com/rescript-lang/rescript-vscode/pull/983 - Add code action for expanding catch-all patterns. https://github.com/rescript-lang/rescript-vscode/pull/987 -- Add code actions for removing unused code (per item and for an entire file), driven by `reanalyze`. +- Add code actions for removing unused code (per item and for an entire file), driven by `reanalyze`. https://github.com/rescript-lang/rescript-vscode/pull/989 ## 1.50.0 diff --git a/client/src/commands/code_analysis.ts b/client/src/commands/code_analysis.ts index 21f550aa9..8ba352551 100644 --- a/client/src/commands/code_analysis.ts +++ b/client/src/commands/code_analysis.ts @@ -91,8 +91,6 @@ let resultsToDiagnostics = ( let codeActionEdit = new WorkspaceEdit(); - // In the future, it would be cool to have an additional code action - // here for automatically removing whatever the thing that's dead is. codeActionEdit.replace( Uri.parse(item.file), // Make sure the full line is replaced @@ -127,18 +125,12 @@ let resultsToDiagnostics = ( let codeActionEdit = new WorkspaceEdit(); - // In the future, it would be cool to have an additional code action - // here for automatically removing whatever the thing that's dead is. codeActionEdit.replace( Uri.parse(item.file), - // Make sure the full line is replaced - new Range( new Position(item.range[0], item.range[1]), new Position(item.range[2], item.range[3]) ), - // reanalyze seems to add two extra spaces at the start of the line - // content to replace. "" ); From 28f6031736de91cf6ef91d6f5f1140ce3748e9b1 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 27 May 2024 13:04:17 +0200 Subject: [PATCH 3/4] comment and extra match --- client/src/commands/code_analysis.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/src/commands/code_analysis.ts b/client/src/commands/code_analysis.ts index 8ba352551..a26c4167f 100644 --- a/client/src/commands/code_analysis.ts +++ b/client/src/commands/code_analysis.ts @@ -118,7 +118,12 @@ let resultsToDiagnostics = ( } } - if (item.message.endsWith(" is never used")) { + // This heuristic below helps only target dead code that can be removed + // safely by just removing its text. + if ( + item.message.endsWith(" is never used") || + item.message.endsWith(" has no side effects and can be removed") + ) { { let codeAction = new CodeAction("Remove unused"); codeAction.kind = CodeActionKind.RefactorRewrite; From 1839bd31de868db5e967a819837e6b50ded119e0 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 27 May 2024 13:17:56 +0200 Subject: [PATCH 4/4] comment, and handle another case --- client/src/commands/code_analysis.ts | 21 +++++++++++++++++---- client/src/extension.ts | 1 - 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/client/src/commands/code_analysis.ts b/client/src/commands/code_analysis.ts index a26c4167f..fc0dffcca 100644 --- a/client/src/commands/code_analysis.ts +++ b/client/src/commands/code_analysis.ts @@ -34,6 +34,22 @@ export type DiagnosticsResultFormat = Array<{ }; }>; +enum ClassifiedMessage { + Removable, + Default, +} + +let classifyMessage = (msg: string) => { + if ( + msg.endsWith(" is never used") || + msg.endsWith(" has no side effects and can be removed") + ) { + return ClassifiedMessage.Removable; + } + + return ClassifiedMessage.Default; +}; + let resultsToDiagnostics = ( results: DiagnosticsResultFormat, diagnosticsResultCodeActions: DiagnosticsResultCodeActionsMap @@ -120,10 +136,7 @@ let resultsToDiagnostics = ( // This heuristic below helps only target dead code that can be removed // safely by just removing its text. - if ( - item.message.endsWith(" is never used") || - item.message.endsWith(" has no side effects and can be removed") - ) { + if (classifyMessage(item.message) === ClassifiedMessage.Removable) { { let codeAction = new CodeAction("Remove unused"); codeAction.kind = CodeActionKind.RefactorRewrite; diff --git a/client/src/extension.ts b/client/src/extension.ts index 92aa75ffa..00b60e50b 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -19,7 +19,6 @@ import { LanguageClientOptions, ServerOptions, State, - Executable, TransportKind, } from "vscode-languageclient/node";