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:
co-authored by
Kirill Bulatov
parent
4ec2d04ad9
commit
bad6bde03a
@@ -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, |_| {});
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user