diff --git a/Cargo.lock b/Cargo.lock index 59008fad76d3..3dc7556e0e67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -797,8 +797,8 @@ dependencies = [ "biome_js_parser", "biome_js_semantic", "biome_js_syntax", + "biome_package", "biome_plugin_loader", - "biome_project", "biome_rowan", "biome_string_case", "biome_suppression", @@ -1114,7 +1114,7 @@ dependencies = [ "biome_json_formatter", "biome_json_parser", "biome_json_syntax", - "biome_project", + "biome_package", "biome_rowan", "biome_test_utils", "camino", @@ -1123,6 +1123,26 @@ dependencies = [ "tests_macros", ] +[[package]] +name = "biome_package" +version = "0.5.7" +dependencies = [ + "biome_console", + "biome_deserialize", + "biome_deserialize_macros", + "biome_diagnostics", + "biome_json_parser", + "biome_json_syntax", + "biome_parser", + "biome_rowan", + "biome_text_size", + "insta", + "node-semver", + "rustc-hash 2.1.0", + "serde", + "tests_macros", +] + [[package]] name = "biome_parser" version = "0.5.7" @@ -1157,26 +1177,6 @@ dependencies = [ "serde", ] -[[package]] -name = "biome_project" -version = "0.5.7" -dependencies = [ - "biome_console", - "biome_deserialize", - "biome_deserialize_macros", - "biome_diagnostics", - "biome_json_parser", - "biome_json_syntax", - "biome_parser", - "biome_rowan", - "biome_text_size", - "insta", - "node-semver", - "rustc-hash 2.1.0", - "serde", - "tests_macros", -] - [[package]] name = "biome_rowan" version = "0.5.7" @@ -1232,8 +1232,8 @@ dependencies = [ "biome_json_formatter", "biome_json_parser", "biome_json_syntax", + "biome_package", "biome_parser", - "biome_project", "biome_rowan", "biome_string_case", "biome_text_edit", @@ -1290,7 +1290,7 @@ dependencies = [ "biome_diagnostics", "biome_formatter", "biome_json_parser", - "biome_project", + "biome_package", "biome_rowan", "biome_service", "camino", diff --git a/Cargo.toml b/Cargo.toml index 3ca05029c80d..668a6c094bb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -154,8 +154,8 @@ biome_yaml_parser = { version = "0.0.1", path = "./crates/biome_yaml_ biome_yaml_syntax = { version = "0.0.1", path = "./crates/biome_yaml_syntax" } biome_markup = { version = "0.5.7", path = "./crates/biome_markup" } +biome_package = { version = "0.5.7", path = "./crates/biome_package" } biome_parser = { version = "0.5.7", path = "./crates/biome_parser" } -biome_project = { version = "0.5.7", path = "./crates/biome_project" } biome_rowan = { version = "0.5.7", path = "./crates/biome_rowan" } biome_string_case = { version = "0.5.7", path = "./crates/biome_string_case" } biome_suppression = { version = "0.5.7", path = "./crates/biome_suppression" } diff --git a/crates/biome_cli/src/commands/mod.rs b/crates/biome_cli/src/commands/mod.rs index b274698543c5..71491edeaf65 100644 --- a/crates/biome_cli/src/commands/mod.rs +++ b/crates/biome_cli/src/commands/mod.rs @@ -635,20 +635,6 @@ pub(crate) fn validate_configuration_diagnostics( Ok(()) } -fn resolve_manifest(fs: &dyn FileSystem) -> Result, WorkspaceError> { - let result = fs.auto_search( - &fs.working_directory().unwrap_or_default(), - &["package.json"], - false, - )?; - - if let Some(result) = result { - return Ok(Some((BiomePath::new(result.file_path), result.content))); - } - - Ok(None) -} - fn get_files_to_process_with_cli_options( since: Option<&str>, changed: bool, @@ -774,7 +760,6 @@ pub(crate) trait CommandRunner: Sized { /// - Configure the VCS integration /// - Computes the paths to traverse/handle. This changes based on the VCS arguments that were passed. /// - Register a project folder using the working directory. - /// - Resolves the closets manifest AKA `package.json` and registers it. /// - Updates the settings that belong to the project registered fn configure_workspace( &mut self, @@ -807,11 +792,6 @@ pub(crate) trait CommandRunner: Sized { open_uninitialized: true, })?; - let manifest_data = resolve_manifest(fs)?; - - if let Some((path, content)) = manifest_data { - workspace.set_manifest_for_project((project_key, path, content).into())?; - } workspace.update_settings(UpdateSettingsParams { project_key, workspace_directory: configuration_path.map(BiomePath::from), diff --git a/crates/biome_cli/tests/commands/lint.rs b/crates/biome_cli/tests/commands/lint.rs index 6cce3c48ff7a..10d2d2fe1183 100644 --- a/crates/biome_cli/tests/commands/lint.rs +++ b/crates/biome_cli/tests/commands/lint.rs @@ -7,7 +7,8 @@ use crate::configs::{ }; use crate::snap_test::{assert_file_contents, markup_to_string, SnapshotPayload}; use crate::{ - assert_cli_snapshot, run_cli, run_cli_with_dyn_fs, FORMATTED, LINT_ERROR, PARSE_ERROR, + assert_cli_snapshot, run_cli, run_cli_with_dyn_fs, run_cli_with_server_workspace, FORMATTED, + LINT_ERROR, PARSE_ERROR, }; use biome_console::{markup, BufferConsole, LogLevel, MarkupBuf}; use biome_fs::{ErrorEntry, FileSystemExt, MemoryFileSystem, OsFileSystem}; @@ -3841,3 +3842,158 @@ fn linter_shows_the_default_severity_of_rule_on() { result, )); } + +#[test] +fn linter_finds_package_json_for_no_undeclared_dependencies() { + let mut console = BufferConsole::default(); + let mut fs = MemoryFileSystem::default(); + + fs.insert( + Utf8Path::new("biome.json").into(), + r#"{ + "linter": { + "rules": { + "correctness": { + "noUndeclaredDependencies": "on" + } + } + } +}"# + .as_bytes(), + ); + + fs.insert( + Utf8Path::new("frontend/package.json").into(), + r#"{ + "dependencies": { + "react": "19.0.0" + } +}"# + .as_bytes(), + ); + + let file = Utf8Path::new("frontend/file1.js"); + fs.insert(file.into(), r#"import 'react-dom'"#.as_bytes()); + let (fs, result) = run_cli_with_server_workspace( + fs, + &mut console, + Args::from(["lint", file.as_str()].as_slice()), + ); + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "linter_finds_package_json_for_no_undeclared_dependencies", + fs, + console, + result, + )); +} + +#[test] +fn linter_finds_nested_package_json_for_no_undeclared_dependencies() { + let mut console = BufferConsole::default(); + let mut fs = MemoryFileSystem::default(); + + fs.insert( + Utf8Path::new("biome.json").into(), + r#"{ + "linter": { + "rules": { + "correctness": { + "noUndeclaredDependencies": "on" + } + } + } +}"# + .as_bytes(), + ); + + fs.insert( + Utf8Path::new("package.json").into(), + r#"{ + "dependencies": { + "react-dom": "19.0.0" + } +}"# + .as_bytes(), + ); + + fs.insert( + Utf8Path::new("frontend/package.json").into(), + r#"{ + "dependencies": { + "react": "19.0.0" + } +}"# + .as_bytes(), + ); + + let file = Utf8Path::new("frontend/file1.js"); + fs.insert(file.into(), r#"import 'react-dom'"#.as_bytes()); + let (fs, result) = run_cli_with_server_workspace( + fs, + &mut console, + Args::from(["lint", file.as_str()].as_slice()), + ); + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "linter_finds_nested_package_json_for_no_undeclared_dependencies", + fs, + console, + result, + )); +} + +#[test] +fn linter_finds_nested_package_json_for_no_undeclared_dependencies_inversed() { + let mut console = BufferConsole::default(); + let mut fs = MemoryFileSystem::default(); + + fs.insert( + Utf8Path::new("biome.json").into(), + r#"{ + "linter": { + "rules": { + "correctness": { + "noUndeclaredDependencies": "on" + } + } + } +}"# + .as_bytes(), + ); + + fs.insert( + Utf8Path::new("package.json").into(), + r#"{ + "dependencies": { + "react": "19.0.0" + } +}"# + .as_bytes(), + ); + + fs.insert( + Utf8Path::new("frontend/package.json").into(), + r#"{ + "dependencies": { + "react-dom": "19.0.0" + } +}"# + .as_bytes(), + ); + + let file = Utf8Path::new("frontend/file1.js"); + fs.insert(file.into(), r#"import 'react-dom'"#.as_bytes()); + let (fs, result) = run_cli_with_server_workspace( + fs, + &mut console, + Args::from(["lint", file.as_str()].as_slice()), + ); + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "linter_finds_nested_package_json_for_no_undeclared_dependencies_inversed", + fs, + console, + result, + )); +} diff --git a/crates/biome_cli/tests/main.rs b/crates/biome_cli/tests/main.rs index f1e2f4fdd194..c5477e4aac27 100644 --- a/crates/biome_cli/tests/main.rs +++ b/crates/biome_cli/tests/main.rs @@ -389,3 +389,40 @@ pub(crate) fn run_cli_with_dyn_fs( } } } + +/// Create an [App] instance using the provided [FileSystem] and [Console] +/// instance, and using an in-process server instance of the workspace +pub(crate) fn run_cli_with_server_workspace( + fs: MemoryFileSystem, + console: &mut dyn Console, + args: bpaf::Args, +) -> (MemoryFileSystem, Result<(), CliDiagnostic>) { + use biome_service::{workspace, WorkspaceRef}; + + let files = fs.files.clone(); + + let workspace = workspace::server(Box::new(fs)); + let app = App::new(console, WorkspaceRef::Owned(workspace)); + + let mut session = CliSession { app }; + let command = biome_command().run_inner(args); + let result = match command { + Ok(command) => session.run(command), + Err(failure) => { + if let ParseFailure::Stdout(help, _) = &failure { + let console = &mut session.app.console; + console.log(markup! {{help.to_string()}}); + Ok(()) + } else { + Err(CliDiagnostic::parse_error_bpaf(failure)) + } + } + }; + + // This is a little bit of a workaround to allow us to easily create + // a snapshot of the files even though the original file system was + // consumed by the workspace. + let fs = MemoryFileSystem::from_files(files); + + (fs, result) +} diff --git a/crates/biome_cli/tests/snapshots/main_commands_lint/linter_finds_nested_package_json_for_no_undeclared_dependencies.snap b/crates/biome_cli/tests/snapshots/main_commands_lint/linter_finds_nested_package_json_for_no_undeclared_dependencies.snap new file mode 100644 index 000000000000..7ef880891a32 --- /dev/null +++ b/crates/biome_cli/tests/snapshots/main_commands_lint/linter_finds_nested_package_json_for_no_undeclared_dependencies.snap @@ -0,0 +1,64 @@ +--- +source: crates/biome_cli/tests/snap_test.rs +expression: content +--- +## `biome.json` + +```json +{ + "linter": { + "rules": { + "correctness": { + "noUndeclaredDependencies": "on" + } + } + } +} +``` + +## `frontend/file1.js` + +```js +import 'react-dom' +``` + +## `frontend/package.json` + +```json +{ + "dependencies": { + "react": "19.0.0" + } +} +``` + +## `package.json` + +```json +{ + "dependencies": { + "react-dom": "19.0.0" + } +} +``` + +# Emitted Messages + +```block +frontend/file1.js:1:8 lint/correctness/noUndeclaredDependencies ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i The current dependency isn't specified in your package.json. + + > 1 │ import 'react-dom' + │ ^^^^^^^^^^^ + + i This could lead to errors. + + i Add the dependency in your manifest. + + +``` + +```block +Checked 1 file in