Skip to content

Commit

Permalink
feat: support for nested ignore files
Browse files Browse the repository at this point in the history
  • Loading branch information
ematipico committed Feb 24, 2025
1 parent 1835578 commit 25ae4d9
Show file tree
Hide file tree
Showing 54 changed files with 692 additions and 1,631 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-trains-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@biomejs/biome": minor
---

Biome VCS integration now supports nested ignore files. Globs inside ignore files are expected to behave as per VCS spec, based in the client.
5 changes: 0 additions & 5 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,9 @@
"**/benchmark/**",
"!**/crates/**",
"!**/dist/**",
"!**/.astro/**",
"!**/assets/**",
"!**/packages/@biomejs/backend-jsonrpc/src/workspace.ts",
"!**/public/**",
"!**/__snapshots__",
"!**/undefined/**",
"!**/_fonts/**",
"!**/packages/@biomejs/wasm-*",
"!**/benchmark/target/**"
]
},
Expand Down
11 changes: 2 additions & 9 deletions crates/biome_cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ use biome_console::{markup, Console, ConsoleExt};
use biome_diagnostics::{Diagnostic, PrintDiagnostic, Severity};
use biome_fs::{BiomePath, FileSystem};
use biome_grit_patterns::GritTargetLanguage;
use biome_service::configuration::{
load_configuration, load_editorconfig, ConfigurationExt, LoadedConfiguration,
};
use biome_service::configuration::{load_configuration, load_editorconfig, LoadedConfiguration};
use biome_service::documentation::Doc;
use biome_service::projects::ProjectKey;
use biome_service::workspace::{
Expand Down Expand Up @@ -788,9 +786,6 @@ pub(crate) trait CommandRunner: Sized {
);
let configuration_path = loaded_configuration.directory_path.clone();
let configuration = self.merge_configuration(loaded_configuration, fs, console)?;
let vcs_base_path = configuration_path.clone().or(fs.working_directory());
let (vcs_base_path, gitignore_matches) =
configuration.retrieve_gitignore_matches(fs, vcs_base_path.as_deref())?;
let paths = self.get_files_to_process(fs, &configuration)?;
let project_path = fs
.working_directory()
Expand All @@ -805,8 +800,6 @@ pub(crate) trait CommandRunner: Sized {
project_key,
workspace_directory: configuration_path.map(BiomePath::from),
configuration,
vcs_base_path: vcs_base_path.map(BiomePath::from),
gitignore_matches,
})?;

let execution = self.get_execution(cli_options, console, workspace, project_key)?;
Expand All @@ -817,7 +810,7 @@ pub(crate) trait CommandRunner: Sized {
path: Some(project_path),
})?;
for diagnostic in result.diagnostics {
if diagnostic.severity() == Severity::Fatal {
if diagnostic.severity() >= Severity::Error {
console.log(markup! {{PrintDiagnostic::simple(&diagnostic)}});
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_cli/src/commands/rage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ impl Display for RageConfiguration<'_> {
let vcs_enabled = configuration.is_vcs_enabled();
let mut settings = Settings::default();
settings
.merge_with_configuration(configuration.clone(), None, None, &[])
.merge_with_configuration(configuration.clone(), None)
.unwrap();

let status = if !diagnostics.is_empty() {
Expand Down
6 changes: 4 additions & 2 deletions crates/biome_cli/src/execute/process_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use crate::execute::traverse::TraversalOptions;
use crate::execute::TraversalMode;
use biome_diagnostics::{category, DiagnosticExt, DiagnosticTags, Error};
use biome_fs::BiomePath;
use biome_service::workspace::{FeatureKind, SupportKind, SupportsFeatureParams};
use biome_service::workspace::{
DocumentFileSource, FeatureKind, SupportKind, SupportsFeatureParams,
};
use check::check_file;
use format::format;
use lint::lint;
Expand Down Expand Up @@ -143,7 +145,7 @@ pub(crate) fn process_file(ctx: &TraversalOptions, biome_path: &BiomePath) -> Fi
// first we stop if there are some files that don't have ALL features enabled, e.g. images, fonts, etc.
if file_features.is_ignored() || file_features.is_not_enabled() {
return Ok(FileStatus::Ignored);
} else if file_features.is_not_supported() {
} else if file_features.is_not_supported() || !DocumentFileSource::can_read(biome_path) {
return Err(Message::from(
UnhandledDiagnostic.with_file_path(biome_path.to_string()),
));
Expand Down
8 changes: 5 additions & 3 deletions crates/biome_cli/src/execute/traverse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use biome_fs::{BiomePath, FileSystem, PathInterner};
use biome_fs::{TraversalContext, TraversalScope};
use biome_service::dome::Dome;
use biome_service::projects::ProjectKey;
use biome_service::workspace::{DropPatternParams, IsPathIgnoredParams};
use biome_service::workspace::{DocumentFileSource, DropPatternParams, IsPathIgnoredParams};
use biome_service::{extension_error, workspace::SupportsFeatureParams, Workspace, WorkspaceError};
use camino::{Utf8Path, Utf8PathBuf};
use crossbeam::channel::{unbounded, Receiver, Sender};
Expand Down Expand Up @@ -596,7 +596,7 @@ impl TraversalContext for TraversalOptions<'_, '_> {
self.push_message(error);
}

#[instrument(level = "debug", skip(self, biome_path))]
#[instrument(level = "trace", skip(self, biome_path), fields(can_handle))]
fn can_handle(&self, biome_path: &BiomePath) -> bool {
let path = biome_path.as_path();
if self.fs.path_is_dir(path) || self.fs.path_is_symlink(path) {
Expand Down Expand Up @@ -634,6 +634,8 @@ impl TraversalContext for TraversalOptions<'_, '_> {
features: self.execution.to_feature(),
});

let can_read = DocumentFileSource::can_read(biome_path);

let file_features = match file_features {
Ok(file_features) => {
if file_features.is_protected() {
Expand All @@ -642,7 +644,7 @@ impl TraversalContext for TraversalOptions<'_, '_> {
return false;
}

if file_features.is_not_supported() && !file_features.is_ignored() {
if file_features.is_not_supported() && !file_features.is_ignored() && !can_read {
// we should throw a diagnostic if we can't handle a file that isn't ignored
self.miss_handler_err(extension_error(biome_path), biome_path);
Span::current().record("can_handle", false);
Expand Down
1 change: 1 addition & 0 deletions crates/biome_cli/tests/cases/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ mod reporter_summary;
mod rules_via_dependencies;
mod suppressions;
mod unknown_files;
mod vcs_ignored_files;
194 changes: 194 additions & 0 deletions crates/biome_cli/tests/cases/vcs_ignored_files.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
use crate::run_cli_with_dyn_fs;
use crate::snap_test::{assert_cli_snapshot, SnapshotPayload};
use biome_console::BufferConsole;
use biome_fs::TemporaryFs;
use bpaf::Args;

const UNFORMATTED: &str = " statement( ) ";

#[test]
fn include_vcs_ignore_cascade() {
let mut console = BufferConsole::default();
let mut fs = TemporaryFs::new("include_vcs_ignore_cascade");

fs.create_file(".gitignore", r#"file4.js"#);
fs.create_file(
"biome.json",
r#"{
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": ["**", "!file2.js"]
},
"formatter": {
"includes": ["file1.js", "file2.js", "file4.js", "!file3.js"]
}
}"#,
);

// Only `file1.js` will be formatted:
// - `file2.js` is ignored at top-level
// - `file3.js` is ignored at formatter-level
// - `file4.js` is ignored in `.gitignore`
let files = [
("file1.js", true),
("file2.js", false),
("file3.js", false),
("file4.js", false),
];
for (file_path, _) in files {
fs.create_file(file_path, UNFORMATTED);
}

let result = run_cli_with_dyn_fs(
Box::new(fs.create_os()),
&mut console,
Args::from(["format", fs.cli_path(), "--write"].as_slice()),
);
assert!(result.is_ok(), "run_cli returned {result:?}");

assert_cli_snapshot(SnapshotPayload::new(
module_path!(),
"include_vcs_ignore_cascade",
fs.create_mem(),
console,
result,
));
}

#[test]
fn ignore_vcs_os_independent_parse() {
let mut fs = TemporaryFs::new("ignore_vcs_os_independent_parse");
let mut console = BufferConsole::default();

fs.create_file(
"biome.json",
r#"{
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
}
}"#,
);

fs.create_file(".gitignore", "something.js\nfile2.js\r\nfile3.js");

fs.create_file("file3.js", r#"console.log('biome is cool');"#);
fs.create_file("file2.js", r#"foo.call(); bar.call();"#);
fs.create_file("file1.js", r#"blah.call();"#);

let result = run_cli_with_dyn_fs(
Box::new(fs.create_os()),
&mut console,
Args::from(["check", fs.cli_path()].as_slice()),
);

assert!(result.is_err(), "run_cli returned {result:?}");

assert_cli_snapshot(SnapshotPayload::new(
module_path!(),
"ignore_vcs_os_independent_parse",
fs.create_mem(),
console,
result,
));
}

#[test]
fn ignore_vcs_ignored_file_via_cli() {
let mut fs = TemporaryFs::new("ignore_vcs_ignored_file_via_cli");
let mut console = BufferConsole::default();

fs.create_file(
".gitignore",
r#"
file2.js
"#,
);

fs.create_file("file2.js", r#"foo.call(); bar.call();"#);
fs.create_file(
"file1.js",
r#"array.map(sentence => sentence.split(' ')).flat();"#,
);

// git folder
fs.create_folder("git");

let result = run_cli_with_dyn_fs(
Box::new(fs.create_os()),
&mut console,
Args::from(
[
"lint",
"--vcs-enabled=true",
"--vcs-client-kind=git",
"--vcs-use-ignore-file=true",
"--vcs-root=.",
fs.cli_path(),
]
.as_slice(),
),
);

assert!(result.is_err(), "run_cli returned {result:?}");

assert_cli_snapshot(SnapshotPayload::new(
module_path!(),
"ignore_vcs_ignored_file_via_cli",
fs.create_mem(),
console,
result,
));
}

#[test]
fn ignores_file_inside_directory() {
let mut fs = TemporaryFs::new("ignores_file_inside_directory");
let mut console = BufferConsole::default();

fs.create_file(
".gitignore",
r#"
**/ignored/
"#,
);

fs.create_file(
"ignored/file1.js",
r#"array.map(sentence => sentence.split(' ')).flat();"#,
);
fs.create_file("ignored/file2.js", r#"foo.call(); bar.call();"#);

let result = run_cli_with_dyn_fs(
Box::new(fs.create_os()),
&mut console,
Args::from(
[
"check",
"--vcs-enabled=true",
"--vcs-client-kind=git",
"--vcs-use-ignore-file=true",
"--vcs-root=.",
"--write",
"--unsafe",
fs.cli_path(),
]
.as_slice(),
),
);

assert!(result.is_err(), "run_cli returned {result:?}");

assert_cli_snapshot(SnapshotPayload::new(
module_path!(),
"ignores_file_inside_directory",
fs.create_mem(),
console,
result,
));
}
Loading

0 comments on commit 25ae4d9

Please sign in to comment.