Skip to content

Commit

Permalink
Code actions for removing dead code (#989)
Browse files Browse the repository at this point in the history
* code actions for removing dead code in the current file + each dead item, driven by reanalyze

* changelog and cleanup

* comment and extra match

* comment, and handle another case
  • Loading branch information
zth committed May 27, 2024
1 parent 34c4a3b commit ee2a297
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`. https://github.com/rescript-lang/rescript-vscode/pull/989

## 1.50.0

Expand Down
50 changes: 48 additions & 2 deletions client/src/commands/code_analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -91,8 +107,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
Expand All @@ -119,6 +133,38 @@ let resultsToDiagnostics = (
}
}
}

// This heuristic below helps only target dead code that can be removed
// safely by just removing its text.
if (classifyMessage(item.message) === ClassifiedMessage.Removable) {
{
let codeAction = new CodeAction("Remove unused");
codeAction.kind = CodeActionKind.RefactorRewrite;

let codeActionEdit = new WorkspaceEdit();

codeActionEdit.replace(
Uri.parse(item.file),
new Range(
new Position(item.range[0], item.range[1]),
new Position(item.range[2], item.range[3])
),
""
);

codeAction.edit = codeActionEdit;

if (diagnosticsResultCodeActions.has(item.file)) {
diagnosticsResultCodeActions
.get(item.file)
.push({ range: issueLocationRange, codeAction });
} else {
diagnosticsResultCodeActions.set(item.file, [
{ range: issueLocationRange, codeAction },
]);
}
}
}
}
});

Expand Down
33 changes: 29 additions & 4 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ import {
Uri,
Range,
Position,
CodeAction,
WorkspaceEdit,
CodeActionKind,
} from "vscode";

import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
State,
Executable,
TransportKind
TransportKind,
} from "vscode-languageclient/node";

import * as customCommands from "./commands";
Expand Down Expand Up @@ -91,7 +93,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"],
Expand Down Expand Up @@ -189,12 +195,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;
},
});

Expand Down

0 comments on commit ee2a297

Please sign in to comment.