mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(oxfmt/lsp): return error on config resolving error (#26003)
Instead of silently ignoring config-resolve errors, and not formatting the document, `oxfmt --lsp` now returns an `Err` instead, allowing users to see on their LSP client a meaningful message. Hitting Format on a file, could produce a `toast` like in this following example: <img width="463" height="173" alt="grafik" src="https://github.com/user-attachments/assets/7d549ab1-b65b-41bc-9fa0-f5f9d10989dc" />
This commit is contained in:
1 parent
961f318571
commit
5dae56f577
7 files changed
+77
-31
No files matched your search
@@ -226,7 +226,7 @@ impl Tool for ServerFormatter {
|
||||
&file_content
|
||||
};
|
||||
|
||||
let Some(result) = self.format_file(&path, source_text) else {
|
||||
let Some(result) = self.format_file(&path, source_text)? else {
|
||||
return Ok(vec![]); // No formatting for this file (unsupported or ignored)
|
||||
};
|
||||
|
||||
@@ -238,7 +238,7 @@ impl Tool for ServerFormatter {
|
||||
.ok_or_else(|| "In-memory formatting requires content".to_string())?;
|
||||
|
||||
let Some(result) =
|
||||
self.format_in_memory(document.uri, source_text, &document.language_id)
|
||||
self.format_in_memory(document.uri, source_text, &document.language_id)?
|
||||
else {
|
||||
return Ok(vec![]); // currently not supported
|
||||
};
|
||||
@@ -346,7 +346,7 @@ impl ServerFormatter {
|
||||
scopes: &ConfigScopes,
|
||||
path: &Path,
|
||||
source_text: &str,
|
||||
) -> Option<FormatResult> {
|
||||
) -> Result<Option<FormatResult>, String> {
|
||||
let resolver = match scopes.resolve(path) {
|
||||
Ok(r) => r,
|
||||
Err(err) => {
|
||||
@@ -360,37 +360,35 @@ impl ServerFormatter {
|
||||
|
||||
if resolver.is_path_ignored(path, false) {
|
||||
debug!("File is ignored by config ignorePatterns: {}", path.display());
|
||||
return None;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let Some(strategy) = classify_file(path) else {
|
||||
debug!("Unsupported file type for formatting: {}", path.display());
|
||||
return None;
|
||||
return Ok(None);
|
||||
};
|
||||
let plan = match resolver.resolve(path, strategy) {
|
||||
Ok(ResolveOutcome::Format(plan)) => plan,
|
||||
Ok(ResolveOutcome::MissingPlugin(plugin)) => {
|
||||
warn!(
|
||||
"Skipping `.{plugin}`: `{plugin}` plugin is not enabled in resolved config: {}",
|
||||
return Err(format!(
|
||||
"`{plugin}` plugin is not enabled in resolved config: {}",
|
||||
path.display()
|
||||
);
|
||||
return None;
|
||||
));
|
||||
}
|
||||
Err(err) => {
|
||||
debug!("Config resolve error for {}: {err}", path.display());
|
||||
return None;
|
||||
return Err(format!("Config resolve error for {}: {err}", path.display()));
|
||||
}
|
||||
};
|
||||
debug!("plan = {plan:?}");
|
||||
|
||||
Some(tokio::task::block_in_place(|| self.source_formatter.format(source_text, plan)))
|
||||
Ok(Some(tokio::task::block_in_place(|| self.source_formatter.format(source_text, plan))))
|
||||
}
|
||||
|
||||
fn format_file(&self, path: &Path, source_text: &str) -> Option<FormatResult> {
|
||||
fn format_file(&self, path: &Path, source_text: &str) -> Result<Option<FormatResult>, String> {
|
||||
let state = self.snapshot();
|
||||
if is_ignored(&state.ignore_matchers, path, false, true) {
|
||||
debug!("File is ignored by .prettierignore: {}", path.display());
|
||||
return None;
|
||||
return Ok(None);
|
||||
}
|
||||
self.resolve_and_format(&state.scopes, path, source_text)
|
||||
}
|
||||
@@ -400,11 +398,11 @@ impl ServerFormatter {
|
||||
uri: &Uri,
|
||||
source_text: &str,
|
||||
language_id: &LanguageId,
|
||||
) -> Option<FormatResult> {
|
||||
) -> Result<Option<FormatResult>, String> {
|
||||
let Some(path) = create_fake_file_path_from_language_id(language_id, &self.root_path, uri)
|
||||
else {
|
||||
debug!("Unsupported language id for in-memory formatting: {language_id:?}");
|
||||
return None;
|
||||
return Ok(None);
|
||||
};
|
||||
self.resolve_and_format(&self.snapshot().scopes, &path, source_text)
|
||||
}
|
||||
|
||||
@@ -21,12 +21,8 @@ const count=1
|
||||
---
|
||||
<button onclick={()=>count}>{count}</button>
|
||||
|
||||
--- AFTER ----------
|
||||
---
|
||||
const count=1
|
||||
---
|
||||
<button onclick={()=>count}>{count}</button>
|
||||
|
||||
--- ERROR ----------
|
||||
\`astro\` plugin is not enabled in resolved config: <cwd>/test/lsp/format/fixtures/format/test.astro
|
||||
--------------------"
|
||||
`;
|
||||
|
||||
@@ -49,10 +45,8 @@ file://<fixture>/format/test.svelte
|
||||
<script>let count=$state(0);</script>
|
||||
<button onclick={()=>count++}>{count}</button>
|
||||
|
||||
--- AFTER ----------
|
||||
<script>let count=$state(0);</script>
|
||||
<button onclick={()=>count++}>{count}</button>
|
||||
|
||||
--- ERROR ----------
|
||||
\`svelte\` plugin is not enabled in resolved config: <cwd>/test/lsp/format/fixtures/format/test.svelte
|
||||
--------------------"
|
||||
`;
|
||||
|
||||
@@ -381,6 +375,18 @@ const x = "1"
|
||||
--------------------"
|
||||
`;
|
||||
|
||||
exports[`LSP formatting > error handling on format > should handle config-invalid-override/test.ts 1`] = `
|
||||
"--- URI -----------
|
||||
file://<fixture>/config-invalid-override/test.ts
|
||||
--- BEFORE ---------
|
||||
const value = 1;
|
||||
|
||||
--- ERROR ----------
|
||||
Config resolve error for <cwd>/test/lsp/format/fixtures/config-invalid-override/test.ts: Invalid tabWidth: The indent width should be between 0 and 24
|
||||
|
||||
--------------------"
|
||||
`;
|
||||
|
||||
exports[`LSP formatting > ignore patterns > should handle ignore-config/file.generated.ts 1`] = `
|
||||
"--- URI -----------
|
||||
file://<fixture>/ignore-config/file.generated.ts
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"options": {
|
||||
"tabWidth": 250
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
const value = 1;
|
||||
@@ -166,6 +166,15 @@ describe("LSP formatting", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("error handling on format", () => {
|
||||
it.each([["config-invalid-override/test.ts", "typescript"]])(
|
||||
"should handle %s",
|
||||
async (path, languageId) => {
|
||||
expect(await formatFixture(FIXTURES_DIR, path, languageId)).toMatchSnapshot();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("initializationOptions", () => {
|
||||
it("should use custom config path from fmt.configPath", async () => {
|
||||
expect(
|
||||
|
||||
@@ -173,19 +173,36 @@ export async function formatFixtureContent(
|
||||
}
|
||||
await clientOrConfig.didOpen(fileUri, languageId, content);
|
||||
|
||||
const edits = await clientOrConfig.format(fileUri);
|
||||
try {
|
||||
const edits = await clientOrConfig.format(fileUri);
|
||||
|
||||
if (innerClient) {
|
||||
await innerClient[Symbol.asyncDispose]();
|
||||
}
|
||||
if (innerClient) {
|
||||
await innerClient[Symbol.asyncDispose]();
|
||||
}
|
||||
|
||||
return `${uriSnapshotHeader(fileUri, fixturesDir)}
|
||||
return `${uriSnapshotHeader(fileUri, fixturesDir)}
|
||||
--- BEFORE ---------
|
||||
${content}
|
||||
--- AFTER ----------
|
||||
${applyEdits(content, edits, languageId)}
|
||||
--------------------
|
||||
`.trim();
|
||||
} catch (error) {
|
||||
const msg =
|
||||
error instanceof Error ? sanitizeMessage(error.message) : sanitizeMessage(error as string);
|
||||
|
||||
if (innerClient) {
|
||||
await innerClient[Symbol.asyncDispose]();
|
||||
}
|
||||
|
||||
return `${uriSnapshotHeader(fileUri, fixturesDir)}
|
||||
--- BEFORE ---------
|
||||
${content}
|
||||
--- ERROR ----------
|
||||
${msg}
|
||||
--------------------
|
||||
`.trim();
|
||||
}
|
||||
}
|
||||
|
||||
export async function formatMultipleFixtures(
|
||||
@@ -302,3 +319,7 @@ function uriSnapshotHeader(fileUri: string, fixtureDir: string): string {
|
||||
--- URI -----------
|
||||
${safeUri}`;
|
||||
}
|
||||
|
||||
function sanitizeMessage(message: string): string {
|
||||
return message.replaceAll(process.cwd(), "<cwd>");
|
||||
}
|
||||
@@ -961,6 +961,7 @@ impl LanguageServer for Backend {
|
||||
Ok(Some(edits))
|
||||
}
|
||||
Err(err) => {
|
||||
error!(err);
|
||||
Err(Error { code: ErrorCode::ServerError(1), message: Cow::Owned(err), data: None })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user