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 <kirill@zed.dev>
This commit is contained in:
John Gibb
2025-12-04 12:07:40 +00:00
committed by GitHub
co-authored by Kirill Bulatov
parent 4ec2d04ad9
commit bad6bde03a
2 changed files with 165 additions and 16 deletions
+103
View File
@@ -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, |_| {});
+62 -16
View File
@@ -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<Option<String>> {
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<bool> {
let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME);
if let Some(node_modules_location_metadata) = fs