mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 20:28:58 +00:00
feat(editor): Create a command to apply all auto-fixes for the current active text editor (#7672)
Ref #7456 - Creates a custom action in the language server that applies all auto-fixes for a given file. - Updates VS Code to use the custom action with a command to apply fixes for the currently active text editor.
This commit is contained in:
parent
b24a636b0a
commit
38b1c2ec65
5 changed files with 127 additions and 11 deletions
|
|
@ -10,6 +10,8 @@ This crate provides an [LSP](https://microsoft.github.io/language-server-protoco
|
|||
- File Operations: `false`
|
||||
- [Code Actions Provider](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionKind):
|
||||
- `quickfix`
|
||||
- `source.fixAll.oxc`, behaves the same as `quickfix` only used when the `CodeActionContext#only` contains
|
||||
`source.fixAll.oxc`.
|
||||
|
||||
## Supported LSP Specifications from Server
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,9 @@ enum SyntheticRunLevel {
|
|||
OnType,
|
||||
}
|
||||
|
||||
const CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC: CodeActionKind =
|
||||
CodeActionKind::new("source.fixAll.oxc");
|
||||
|
||||
#[tower_lsp::async_trait]
|
||||
impl LanguageServer for Backend {
|
||||
async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
|
||||
|
|
@ -111,7 +114,10 @@ impl LanguageServer for Backend {
|
|||
})
|
||||
}) {
|
||||
Some(CodeActionProviderCapability::Options(CodeActionOptions {
|
||||
code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
|
||||
code_action_kinds: Some(vec![
|
||||
CodeActionKind::QUICKFIX,
|
||||
CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC,
|
||||
]),
|
||||
work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
|
||||
resolve_provider: None,
|
||||
}))
|
||||
|
|
@ -278,12 +284,24 @@ impl LanguageServer for Backend {
|
|||
|
||||
async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
|
||||
let uri = params.text_document.uri;
|
||||
let is_source_fix_all_oxc = params
|
||||
.context
|
||||
.only
|
||||
.is_some_and(|only| only.contains(&CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC));
|
||||
|
||||
let mut code_actions_vec: Vec<CodeActionOrCommand> = vec![];
|
||||
if let Some(value) = self.diagnostics_report_map.get(&uri.to_string()) {
|
||||
if let Some(report) = value.iter().find(|r| r.diagnostic.range == params.range) {
|
||||
let reports = value
|
||||
.iter()
|
||||
.filter(|r| {
|
||||
r.diagnostic.range == params.range
|
||||
|| range_includes(params.range, r.diagnostic.range)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for report in reports {
|
||||
// TODO: Would be better if we had exact rule name from the diagnostic instead of having to parse it.
|
||||
let mut rule_name: Option<String> = None;
|
||||
if let Some(NumberOrString::String(code)) = report.clone().diagnostic.code {
|
||||
if let Some(NumberOrString::String(code)) = &report.diagnostic.code {
|
||||
let open_paren = code.chars().position(|c| c == '(');
|
||||
let close_paren = code.chars().position(|c| c == ')');
|
||||
if open_paren.is_some() && close_paren.is_some() {
|
||||
|
|
@ -292,14 +310,17 @@ impl LanguageServer for Backend {
|
|||
}
|
||||
}
|
||||
|
||||
let mut code_actions_vec: Vec<CodeActionOrCommand> = vec![];
|
||||
if let Some(fixed_content) = &report.fixed_content {
|
||||
code_actions_vec.push(CodeActionOrCommand::CodeAction(CodeAction {
|
||||
title: report.diagnostic.message.split(':').next().map_or_else(
|
||||
|| "Fix this problem".into(),
|
||||
|s| format!("Fix this {s} problem"),
|
||||
),
|
||||
kind: Some(CodeActionKind::QUICKFIX),
|
||||
kind: Some(if is_source_fix_all_oxc {
|
||||
CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC
|
||||
} else {
|
||||
CodeActionKind::QUICKFIX
|
||||
}),
|
||||
is_preferred: Some(true),
|
||||
edit: Some(WorkspaceEdit {
|
||||
#[expect(clippy::disallowed_types)]
|
||||
|
|
@ -391,12 +412,14 @@ impl LanguageServer for Backend {
|
|||
diagnostics: None,
|
||||
command: None,
|
||||
}));
|
||||
|
||||
return Ok(Some(code_actions_vec));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
if code_actions_vec.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(code_actions_vec))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -578,3 +601,13 @@ async fn main() {
|
|||
|
||||
Server::new(stdin, stdout, socket).serve(service).await;
|
||||
}
|
||||
|
||||
fn range_includes(range: Range, to_include: Range) -> bool {
|
||||
if range.start >= to_include.start {
|
||||
return false;
|
||||
}
|
||||
if range.end <= to_include.end {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,3 +16,6 @@ This is the linter for Oxc. The currently supported features are listed below.
|
|||
- Highlighting for warnings or errors identified by Oxlint
|
||||
- Quick fixes to fix a warning or error when possible
|
||||
- JSON schema validation for supported Oxlint configuration files (does not include ESLint configuration files)
|
||||
- Command to fix all auto-fixable content within the current text editor.
|
||||
- Support for `source.fixAll.oxc` as a code action provider. Configure this in your settings `editor.codeActionsOnSave`
|
||||
to automatically apply fixes when saving the file.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,25 @@
|
|||
import { promises as fsPromises } from 'node:fs';
|
||||
|
||||
import { commands, ExtensionContext, StatusBarAlignment, StatusBarItem, ThemeColor, window, workspace } from 'vscode';
|
||||
import {
|
||||
CodeAction,
|
||||
Command,
|
||||
commands,
|
||||
ExtensionContext,
|
||||
StatusBarAlignment,
|
||||
StatusBarItem,
|
||||
ThemeColor,
|
||||
window,
|
||||
workspace,
|
||||
} from 'vscode';
|
||||
|
||||
import { MessageType, ShowMessageNotification } from 'vscode-languageclient';
|
||||
import {
|
||||
CodeActionRequest,
|
||||
CodeActionTriggerKind,
|
||||
MessageType,
|
||||
Position,
|
||||
Range,
|
||||
ShowMessageNotification,
|
||||
} from 'vscode-languageclient';
|
||||
|
||||
import { Executable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node';
|
||||
|
||||
|
|
@ -15,7 +32,7 @@ const commandPrefix = 'oxc';
|
|||
|
||||
const enum OxcCommands {
|
||||
RestartServer = `${commandPrefix}.restartServer`,
|
||||
ApplyAllFixes = `${commandPrefix}.applyAllFixes`,
|
||||
ApplyAllFixesFile = `${commandPrefix}.applyAllFixesFile`,
|
||||
ShowOutputChannel = `${commandPrefix}.showOutputChannel`,
|
||||
ToggleEnable = `${commandPrefix}.toggleEnable`,
|
||||
}
|
||||
|
|
@ -62,7 +79,63 @@ export async function activate(context: ExtensionContext) {
|
|||
},
|
||||
);
|
||||
|
||||
const applyAllFixesFile = commands.registerCommand(
|
||||
OxcCommands.ApplyAllFixesFile,
|
||||
async () => {
|
||||
if (!client) {
|
||||
window.showErrorMessage('oxc client not found');
|
||||
return;
|
||||
}
|
||||
const textEditor = window.activeTextEditor;
|
||||
if (!textEditor) {
|
||||
window.showErrorMessage('active text editor not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const lastLine = textEditor.document.lineAt(textEditor.document.lineCount - 1);
|
||||
const codeActionResult = await client.sendRequest(CodeActionRequest.type, {
|
||||
textDocument: {
|
||||
uri: textEditor.document.uri.toString(),
|
||||
},
|
||||
range: Range.create(Position.create(0, 0), lastLine.range.end),
|
||||
context: {
|
||||
diagnostics: [],
|
||||
only: [],
|
||||
triggerKind: CodeActionTriggerKind.Invoked,
|
||||
},
|
||||
});
|
||||
const commandsOrCodeActions = await client.protocol2CodeConverter.asCodeActionResult(codeActionResult || []);
|
||||
|
||||
await Promise.all(
|
||||
commandsOrCodeActions
|
||||
.map(async (codeActionOrCommand) => {
|
||||
// Commands are always applied. Regardless of whether it's a Command or CodeAction#command.
|
||||
if (isCommand(codeActionOrCommand)) {
|
||||
await commands.executeCommand(codeActionOrCommand.command, codeActionOrCommand.arguments);
|
||||
} else {
|
||||
// Only preferred edits are applied
|
||||
// LSP states edits must be run first, then commands
|
||||
if (codeActionOrCommand.edit && codeActionOrCommand.isPreferred) {
|
||||
await workspace.applyEdit(codeActionOrCommand.edit);
|
||||
}
|
||||
if (codeActionOrCommand.command) {
|
||||
await commands.executeCommand(
|
||||
codeActionOrCommand.command.command,
|
||||
codeActionOrCommand.command.arguments,
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
function isCommand(codeActionOrCommand: CodeAction | Command): codeActionOrCommand is Command {
|
||||
return typeof codeActionOrCommand.command === 'string';
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
applyAllFixesFile,
|
||||
restartCommand,
|
||||
showOutputCommand,
|
||||
toggleEnable,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,11 @@
|
|||
"command": "oxc.showOutputChannel",
|
||||
"title": "Show Output Channel",
|
||||
"category": "Oxc"
|
||||
},
|
||||
{
|
||||
"command": "oxc.applyAllFixesFile",
|
||||
"title": "Fix all auto-fixable problems (file)",
|
||||
"category": "Oxc"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue