diff --git a/apps/oxfmt/src/lsp/mod.rs b/apps/oxfmt/src/lsp/mod.rs index 340dc15fe3771..6c5086fa2f032 100644 --- a/apps/oxfmt/src/lsp/mod.rs +++ b/apps/oxfmt/src/lsp/mod.rs @@ -11,7 +11,9 @@ mod server_formatter; mod tester; const FORMAT_CONFIG_FILES: &[&str; 2] = &[".oxfmtrc.json", ".oxfmtrc.jsonc"]; -fn get_file_extension_from_language_id(language_id: &LanguageId) -> Option<&'static str> { +pub(super) fn get_file_extension_from_language_id( + language_id: &LanguageId, +) -> Option<&'static str> { match language_id.as_str() { "javascript" => Some("js"), "typescript" => Some("ts"), @@ -36,14 +38,25 @@ fn get_file_extension_from_language_id(language_id: &LanguageId) -> Option<&'sta } } +/// Returns a copy of `path` with the extension replaced by the one corresponding to `language_id`. +/// Returns `None` if `language_id` is not recognized. +pub(super) fn apply_language_id_extension( + language_id: &LanguageId, + path: &Path, +) -> Option { + let ext = get_file_extension_from_language_id(language_id)?; + let mut p = path.to_path_buf(); + p.set_extension(ext); + Some(p) +} + pub fn create_fake_file_path_from_language_id( language_id: &LanguageId, root: &Path, uri: &Uri, ) -> Option { - let file_extension = get_file_extension_from_language_id(language_id)?; - let file_name = format!("{}.{}", uri.authority()?, file_extension); - Some(root.join(file_name)) + let base = root.join(uri.authority()?.as_str()); + apply_language_id_extension(language_id, &base) } /// Run the language server diff --git a/apps/oxfmt/src/lsp/server_formatter.rs b/apps/oxfmt/src/lsp/server_formatter.rs index 156a196011b86..d4aac26736ccc 100644 --- a/apps/oxfmt/src/lsp/server_formatter.rs +++ b/apps/oxfmt/src/lsp/server_formatter.rs @@ -268,7 +268,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, language_id) else { return Ok(vec![]); // No formatting for this file (unsupported or ignored) }; @@ -334,18 +334,27 @@ impl ServerFormatter { } } - fn format_file(&self, path: &Path, source_text: &str) -> Option { + fn format_file( + &self, + path: &Path, + source_text: &str, + language_id: &LanguageId, + ) -> Option { if self.is_ignored(path) { debug!("File is ignored: {}", path.display()); return None; } - // Determine format strategy from file path (supports JS/TS, JSON, YAML, CSS, etc.) - let Ok(strategy) = FormatFileStrategy::try_from(path.to_path_buf()) else { + // Prefer language_id over file extension to determine the format strategy. + // This allows e.g. a `.txt` file opened as `typescript` to be formatted. + let strategy_opt = super::apply_language_id_extension(language_id, path) + .and_then(|p| FormatFileStrategy::try_from(p).ok()) + .or_else(|| FormatFileStrategy::try_from(path.to_path_buf()).ok()); + + let Some(strategy) = strategy_opt else { debug!("Unsupported file type for formatting: {}", path.display()); return None; }; - // Resolve options for this file let resolved_options = self.config_resolver.resolve(&strategy); debug!("resolved_options = {resolved_options:?}"); diff --git a/apps/oxfmt/test/lsp/format/__snapshots__/format.test.ts.snap b/apps/oxfmt/test/lsp/format/__snapshots__/format.test.ts.snap index a7755d7c3730e..2306ff70e60b4 100644 --- a/apps/oxfmt/test/lsp/format/__snapshots__/format.test.ts.snap +++ b/apps/oxfmt/test/lsp/format/__snapshots__/format.test.ts.snap @@ -40,6 +40,18 @@ version = "1.0.0" --------------------" `; +exports[`LSP formatting > basic formatting > should handle format/test.ts.txt 1`] = ` +"--- URI ----------- +file:///format/test.ts.txt +--- BEFORE --------- +const x = 1 + +--- AFTER ---------- +const x = 1; + +--------------------" +`; + exports[`LSP formatting > basic formatting > should handle format/test.tsx 1`] = ` "--- URI ----------- file:///format/test.tsx diff --git a/apps/oxfmt/test/lsp/format/fixtures/format/test.ts.txt b/apps/oxfmt/test/lsp/format/fixtures/format/test.ts.txt new file mode 100644 index 0000000000000..aa21a4683f649 --- /dev/null +++ b/apps/oxfmt/test/lsp/format/fixtures/format/test.ts.txt @@ -0,0 +1 @@ +const x = 1 diff --git a/apps/oxfmt/test/lsp/format/format.test.ts b/apps/oxfmt/test/lsp/format/format.test.ts index bcd3eaf501cd5..26ec8a14b558a 100644 --- a/apps/oxfmt/test/lsp/format/format.test.ts +++ b/apps/oxfmt/test/lsp/format/format.test.ts @@ -14,6 +14,7 @@ describe("LSP formatting", () => { ["format/test.toml", "toml"], ["format/formatted.ts", "typescript"], ["format/test.txt", "plaintext"], + ["format/test.ts.txt", "typescript"], ])("should handle %s", async (path, languageId) => { expect(await formatFixture(FIXTURES_DIR, path, languageId)).toMatchSnapshot(); });