Add .prettierignore support (#21297)

Closes #11115

**Context**:

Consider a monorepo setup like this: the root has Prettier installed,
but the individual monorepos do not. In this case, only one Prettier
instance is used, with its installation located at the root. The
monorepos also use this same instance for formatting.

However, monorepo can have its own `.prettierignore` file, which will
take precedence over the `.prettierignore` file at the root level (if
one exists) for files in that monorepo.

<img
src="https://github.com/user-attachments/assets/742f16ac-11ad-4d2f-a5a2-696e47a617b9"
alt="prettier" width="200px" />

**Implementation**:

From the context above, we should keep ignore dir decoupled from the
Prettier instance. This means that even if the project has only one
Prettier installation (and thus a single Prettier instance), there can
still be multiple `.prettierignore` in play.

This approach also allows us to respect `.prettierignore` even when the
project does not have Prettier installed locally and instead relies on
the editor’s Prettier instance.

**Tests**:

1. No Prettier in project, using editor Prettier: Ensures
`.prettierignore` is respected even without a local Prettier
installation.
2. Monorepo with root Prettier and child `.prettierignore`: Confirms
that the child project’s ignore file is correctly used.
3. Monorepo with root and child `.prettierignore` files: Verifies the
child ignore file takes precedence over the root’s.

Release Notes:

- Added `.prettierignore` support to the Prettier integration.
This commit is contained in:
tims 2024-12-13 05:15:44 +05:30 committed by GitHub
parent 8dd1c23b92
commit 6a37307302
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 344 additions and 4 deletions

View file

@ -36,6 +36,7 @@ pub struct PrettierStore {
worktree_store: Model<WorktreeStore>,
default_prettier: DefaultPrettier,
prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
prettier_ignores_per_worktree: HashMap<WorktreeId, HashSet<PathBuf>>,
prettier_instances: HashMap<PathBuf, PrettierInstance>,
}
@ -65,11 +66,13 @@ impl PrettierStore {
worktree_store,
default_prettier: DefaultPrettier::default(),
prettiers_per_worktree: HashMap::default(),
prettier_ignores_per_worktree: HashMap::default(),
prettier_instances: HashMap::default(),
}
}
pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
self.prettier_ignores_per_worktree.remove(&id_to_remove);
let mut prettier_instances_to_clean = FuturesUnordered::new();
if let Some(prettier_paths) = self.prettiers_per_worktree.remove(&id_to_remove) {
for path in prettier_paths.iter().flatten() {
@ -211,6 +214,65 @@ impl PrettierStore {
}
}
fn prettier_ignore_for_buffer(
&mut self,
buffer: &Model<Buffer>,
cx: &mut ModelContext<Self>,
) -> Task<Option<PathBuf>> {
let buffer = buffer.read(cx);
let buffer_file = buffer.file();
if buffer.language().is_none() {
return Task::ready(None);
}
match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) {
Some((worktree_id, buffer_path)) => {
let fs = Arc::clone(&self.fs);
let prettier_ignores = self
.prettier_ignores_per_worktree
.get(&worktree_id)
.cloned()
.unwrap_or_default();
cx.spawn(|lsp_store, mut cx| async move {
match cx
.background_executor()
.spawn(async move {
Prettier::locate_prettier_ignore(
fs.as_ref(),
&prettier_ignores,
&buffer_path,
)
.await
})
.await
{
Ok(ControlFlow::Break(())) => None,
Ok(ControlFlow::Continue(None)) => None,
Ok(ControlFlow::Continue(Some(ignore_dir))) => {
log::debug!("Found prettier ignore in {ignore_dir:?}");
lsp_store
.update(&mut cx, |store, _| {
store
.prettier_ignores_per_worktree
.entry(worktree_id)
.or_default()
.insert(ignore_dir.clone());
})
.ok();
Some(ignore_dir)
}
Err(e) => {
log::error!(
"Failed to determine prettier ignore path for buffer: {e:#}"
);
None
}
}
})
}
None => Task::ready(None),
}
}
fn start_prettier(
node: NodeRuntime,
prettier_dir: PathBuf,
@ -654,6 +716,13 @@ pub(super) async fn format_with_prettier(
.ok()?
.await;
let ignore_dir = prettier_store
.update(cx, |prettier_store, cx| {
prettier_store.prettier_ignore_for_buffer(buffer, cx)
})
.ok()?
.await;
let (prettier_path, prettier_task) = prettier_instance?;
let prettier_description = match prettier_path.as_ref() {
@ -671,7 +740,7 @@ pub(super) async fn format_with_prettier(
.flatten();
let format_result = prettier
.format(buffer, buffer_path, cx)
.format(buffer, buffer_path, ignore_dir, cx)
.await
.map(crate::lsp_store::FormatOperation::Prettier)
.with_context(|| format!("{} failed to format buffer", prettier_description));