From bad6bde03a1789b80ffc13c5d8c066746017b013 Mon Sep 17 00:00:00 2001 From: John Gibb <32365131+JPGibb@users.noreply.github.com> Date: Thu, 4 Dec 2025 12:07:40 +0000 Subject: [PATCH] Use buffer language when formatting with Prettier (#43368) Set `prettier_parser` explicitly if the file extension for the buffer does not match a known one for the current language Release Notes: - N/A --------- Co-authored-by: Kirill Bulatov --- crates/editor/src/editor_tests.rs | 103 ++++++++++++++++++++++++++++++ crates/prettier/src/prettier.rs | 78 +++++++++++++++++----- 2 files changed, 165 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 011715804665563b9588da28bad3137120f9c4c3..64c335e2e4b0dc660efe1b28bb87984fba8aafb4 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -19095,6 +19095,109 @@ async fn test_document_format_with_prettier(cx: &mut TestAppContext) { ); } +#[gpui::test] +async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) { + init_test(cx, |settings| { + settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier)) + }); + + let fs = FakeFs::new(cx.executor()); + fs.insert_file(path!("/file.settings"), Default::default()) + .await; + + let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await; + let language_registry = project.read_with(cx, |project, _| project.languages().clone()); + + let ts_lang = Arc::new(Language::new( + LanguageConfig { + name: "TypeScript".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["ts".to_string()], + ..LanguageMatcher::default() + }, + prettier_parser_name: Some("typescript".to_string()), + ..LanguageConfig::default() + }, + Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()), + )); + + language_registry.add(ts_lang.clone()); + + update_test_language_settings(cx, |settings| { + settings.defaults.prettier.get_or_insert_default().allowed = Some(true); + }); + + let test_plugin = "test_plugin"; + let _ = language_registry.register_fake_lsp( + "TypeScript", + FakeLspAdapter { + prettier_plugins: vec![test_plugin], + ..Default::default() + }, + ); + + let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX; + let buffer = project + .update(cx, |project, cx| { + project.open_local_buffer(path!("/file.settings"), cx) + }) + .await + .unwrap(); + + project.update(cx, |project, cx| { + project.set_language_for_buffer(&buffer, ts_lang, cx) + }); + + let buffer_text = "one\ntwo\nthree\n"; + let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); + let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx)); + editor.update_in(cx, |editor, window, cx| { + editor.set_text(buffer_text, window, cx) + }); + + editor + .update_in(cx, |editor, window, cx| { + editor.perform_format( + project.clone(), + FormatTrigger::Manual, + FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()), + window, + cx, + ) + }) + .unwrap() + .await; + assert_eq!( + editor.update(cx, |editor, cx| editor.text(cx)), + buffer_text.to_string() + prettier_format_suffix + "\ntypescript", + "Test prettier formatting was not applied to the original buffer text", + ); + + update_test_language_settings(cx, |settings| { + settings.defaults.formatter = Some(FormatterList::default()) + }); + let format = editor.update_in(cx, |editor, window, cx| { + editor.perform_format( + project.clone(), + FormatTrigger::Manual, + FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()), + window, + cx, + ) + }); + format.await.unwrap(); + + assert_eq!( + editor.update(cx, |editor, cx| editor.text(cx)), + buffer_text.to_string() + + prettier_format_suffix + + "\ntypescript\n" + + prettier_format_suffix + + "\ntypescript", + "Autoformatting (via test prettier) was not applied to the original buffer text", + ); +} + #[gpui::test] async fn test_addition_reverts(cx: &mut TestAppContext) { init_test(cx, |_| {}); diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 381fdc2b2b35be53a0f07878c83cadd2862d06bf..bc4ce609a1fd39e4303c5fd048a0c8605b3a3ddc 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -2,7 +2,8 @@ use anyhow::Context as _; use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncApp, Entity}; -use language::{Buffer, Diff, language_settings::language_settings}; +use language::language_settings::PrettierSettings; +use language::{Buffer, Diff, Language, language_settings::language_settings}; use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use paths::default_prettier_dir; @@ -349,7 +350,7 @@ impl Prettier { Self::Real(local) => { let params = buffer .update(cx, |buffer, cx| { - let buffer_language = buffer.language(); + let buffer_language = buffer.language().map(|language| language.as_ref()); let language_settings = language_settings(buffer_language.map(|l| l.name()), buffer.file(), cx); let prettier_settings = &language_settings.prettier; anyhow::ensure!( @@ -449,15 +450,7 @@ impl Prettier { }) .collect(); - let mut prettier_parser = prettier_settings.parser.as_deref(); - if buffer_path.is_none() { - prettier_parser = prettier_parser.or_else(|| buffer_language.and_then(|language| language.prettier_parser_name())); - if prettier_parser.is_none() { - log::error!("Formatting unsaved file with prettier failed. No prettier parser configured for language {buffer_language:?}"); - anyhow::bail!("Cannot determine prettier parser for unsaved file"); - } - - } + let parser = prettier_parser_name(buffer_path.as_deref(), buffer_language, prettier_settings).context("getting prettier parser")?; let ignore_path = ignore_dir.and_then(|dir| { let ignore_file = dir.join(".prettierignore"); @@ -475,15 +468,15 @@ impl Prettier { anyhow::Ok(FormatParams { text: buffer.text(), options: FormatOptions { - parser: prettier_parser.map(ToOwned::to_owned), - plugins, path: buffer_path, + parser, + plugins, prettier_options, ignore_path, }, }) - })? - .context("building prettier request")?; + })? + .context("building prettier request")?; let response = local .server @@ -503,7 +496,26 @@ impl Prettier { { Some("rust") => anyhow::bail!("prettier does not support Rust"), Some(_other) => { - let formatted_text = buffer.text() + FORMAT_SUFFIX; + let mut formatted_text = buffer.text() + FORMAT_SUFFIX; + + let buffer_language = + buffer.language().map(|language| language.as_ref()); + let language_settings = language_settings( + buffer_language.map(|l| l.name()), + buffer.file(), + cx, + ); + let prettier_settings = &language_settings.prettier; + let parser = prettier_parser_name( + buffer_path.as_deref(), + buffer_language, + prettier_settings, + )?; + + if let Some(parser) = parser { + formatted_text = format!("{formatted_text}\n{parser}"); + } + Ok(buffer.diff(formatted_text, cx)) } None => panic!("Should not format buffer without a language with prettier"), @@ -551,6 +563,40 @@ impl Prettier { } } +fn prettier_parser_name( + buffer_path: Option<&Path>, + buffer_language: Option<&Language>, + prettier_settings: &PrettierSettings, +) -> anyhow::Result> { + let parser = if buffer_path.is_none() { + let parser = prettier_settings + .parser + .as_deref() + .or_else(|| buffer_language.and_then(|language| language.prettier_parser_name())); + if parser.is_none() { + log::error!( + "Formatting unsaved file with prettier failed. No prettier parser configured for language {buffer_language:?}" + ); + anyhow::bail!("Cannot determine prettier parser for unsaved file"); + } + parser + } else if let (Some(buffer_language), Some(buffer_path)) = (buffer_language, buffer_path) + && buffer_path.extension().is_some_and(|extension| { + !buffer_language + .config() + .matcher + .path_suffixes + .contains(&extension.to_string_lossy().into_owned()) + }) + { + buffer_language.prettier_parser_name() + } else { + prettier_settings.parser.as_deref() + }; + + Ok(parser.map(ToOwned::to_owned)) +} + async fn has_prettier_in_node_modules(fs: &dyn Fs, path: &Path) -> anyhow::Result { let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME); if let Some(node_modules_location_metadata) = fs