Repository: biomejs/biome (language: rust)

Issue:
📎  Next.js rules missing domain config
### Description

Some rules coming from `eslint-plugin-next` are not in the Next domain (`RuleDomain::Next`).

- `no-head-element`
- `google-font-display`
- `google-font-preconnect`


Discussion / hints:

Fix patch:
diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs
index 9bcf2c01a92c..d405eb4e5459 100644
--- a/crates/biome_configuration/src/analyzer/linter/rules.rs
+++ b/crates/biome_configuration/src/analyzer/linter/rules.rs
@@ -1293,7 +1293,6 @@ impl Complexity {
     const RECOMMENDED_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]),
-        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6]),
@@ -2167,10 +2166,7 @@ impl Correctness {
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]),
-        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]),
-        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]),
-        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52]),
     ];
@@ -5943,14 +5939,11 @@ impl Suspicious {
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]),
-        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]),
-        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]),
-        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]),
diff --git a/crates/biome_fs/src/fs.rs b/crates/biome_fs/src/fs.rs
index 42a6d0fcd4a6..308bc84932e1 100644
--- a/crates/biome_fs/src/fs.rs
+++ b/crates/biome_fs/src/fs.rs
@@ -3,7 +3,7 @@ use biome_diagnostics::{console, Advices, Diagnostic, IoError, LogCategory, Visi
 use biome_diagnostics::{Error, Severity};
 use camino::{Utf8Path, Utf8PathBuf};
 pub use memory::{ErrorEntry, MemoryFileSystem};
-pub use os::OsFileSystem;
+pub use os::{OsFileSystem, TemporaryFs};
 use oxc_resolver::{FsResolution, ResolveError};
 use serde::{Deserialize, Serialize};
 use std::collections::BTreeSet;
diff --git a/crates/biome_fs/src/fs/os.rs b/crates/biome_fs/src/fs/os.rs
index 8992ed703bbe..0df2147b67e4 100644
--- a/crates/biome_fs/src/fs/os.rs
+++ b/crates/biome_fs/src/fs/os.rs
@@ -3,12 +3,13 @@ use super::{BoxedTraversal, File, FileSystemDiagnostic, FsErrorKind, PathKind};
 use crate::fs::OpenOptions;
 use crate::{
     fs::{TraversalContext, TraversalScope},
-    BiomePath, FileSystem,
+    BiomePath, FileSystem, MemoryFileSystem,
 };
 use biome_diagnostics::{DiagnosticExt, Error, IoError, Severity};
 use camino::{Utf8DirEntry, Utf8Path, Utf8PathBuf};
 use oxc_resolver::{FsResolution, ResolveError, ResolveOptions, Resolver};
 use rayon::{scope, Scope};
+use std::env::temp_dir;
 use std::fs::FileType;
 use std::panic::AssertUnwindSafe;
 use std::process::Command;
@@ -483,3 +484,62 @@ impl From<FileType> for FsErrorKind {
         Self::UnknownFileType
     }
 }
+
+/// Testing utility that creates a working directory inside the
+/// temporary OS folder.
+pub struct TemporaryFs {
+    /// The current working directory. It's the OS temporary folder joined with a file
+    /// name passed in the [TemporaryFs::new] function
+    working_directory: Utf8PathBuf,
+    files: Vec<(Utf8PathBuf, String)>,
+}
+
+impl TemporaryFs {
+    /// Creates a temporary directory named using `directory_name`
+    pub fn new(directory_name: &str) -> Self {
+        let path = temp_dir().join(directory_name);
+        if path.exists() {
+            fs::remove_dir_all(path.as_path()).unwrap();
+        }
+        fs::create_dir(&path).unwrap();
+        Self {
+            working_directory: Utf8PathBuf::from_path_buf(path).unwrap(),
+            files: Vec::new(),
+        }
+    }
+
+    /// Creates a file under the working directory
+    pub fn create_file(&mut self, name: &str, content: &str) {
+        let path = self.working_directory.join(name);
+        std::fs::write(path.as_std_path(), content)
+            .expect("Temporary directory to exist and being writable");
+        self.files.push((path, content.to_string()));
+    }
+
+    /// Returns the path to use when running the CLI
+    pub fn cli_path(&self) -> &str {
+        self.working_directory.as_str()
+    }
+
+    /// Returns an instance of [OsFileSystem] given the current working directory
+    pub fn create_os(&self) -> OsFileSystem {
+        OsFileSystem::new(self.working_directory.clone())
+    }
+
+    /// Returns an instance of [MemoryFileSystem]. The files saved in the file system
+    /// will be stripped of the working directory path, making snapshots predictable.
+    pub fn create_mem(&self) -> MemoryFileSystem {
+        let mut fs = MemoryFileSystem::default();
+        for (path, content) in self.files.iter() {
+            fs.insert(
+                path.clone()
+                    .strip_prefix(self.working_directory.as_str())
+                    .expect("Working directory")
+                    .to_path_buf(),
+                content.as_bytes(),
+            );
+        }
+
+        fs
+    }
+}
diff --git a/crates/biome_fs/src/lib.rs b/crates/biome_fs/src/lib.rs
index fbb9d1634247..fe0c610eb542 100644
--- a/crates/biome_fs/src/lib.rs
+++ b/crates/biome_fs/src/lib.rs
@@ -6,7 +6,7 @@ mod path;
 pub use dir::ensure_cache_dir;
 pub use fs::{
     AutoSearchResult, ConfigName, ErrorEntry, File, FileSystem, FileSystemDiagnostic,
-    FileSystemExt, FsErrorKind, MemoryFileSystem, OpenOptions, OsFileSystem, PathKind,
+    FileSystemExt, FsErrorKind, MemoryFileSystem, OpenOptions, OsFileSystem, PathKind, TemporaryFs,
     TraversalContext, TraversalScope,
 };
 pub use interner::{PathInterner, PathInternerSet};
diff --git a/crates/biome_js_analyze/src/lint/nursery/no_document_import_in_page.rs b/crates/biome_js_analyze/src/lint/nursery/no_document_import_in_page.rs
index 463fc641673b..7cf0ac1a0771 100644
--- a/crates/biome_js_analyze/src/lint/nursery/no_document_import_in_page.rs
+++ b/crates/biome_js_analyze/src/lint/nursery/no_document_import_in_page.rs
@@ -36,7 +36,7 @@ declare_lint_rule! {
         language: "jsx",
         sources: &[RuleSource::EslintNext("no-document-import-in-page")],
         source_kind: RuleSourceKind::SameLogic,
-        recommended: false,
+        recommended: true,
         domains: &[RuleDomain::Next],
     }
 }
diff --git a/crates/biome_js_analyze/src/lint/nursery/no_head_element.rs b/crates/biome_js_analyze/src/lint/nursery/no_head_element.rs
index 97bc1b1f2e58..756389de5f29 100644
--- a/crates/biome_js_analyze/src/lint/nursery/no_head_element.rs
+++ b/crates/biome_js_analyze/src/lint/nursery/no_head_element.rs
@@ -1,6 +1,6 @@
 use biome_analyze::RuleSourceKind;
 use biome_analyze::{
-    context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource,
+    context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource,
 };
 use biome_console::markup;
 use biome_js_syntax::JsxOpeningElement;
@@ -49,7 +49,8 @@ declare_lint_rule! {
         language: "jsx",
         sources: &[RuleSource::EslintNext("no-head-element")],
         source_kind: RuleSourceKind::SameLogic,
-        recommended: false,
+        recommended: true,
+        domains: &[RuleDomain::Next],
     }
 }
 
diff --git a/crates/biome_js_analyze/src/lint/nursery/no_head_import_in_document.rs b/crates/biome_js_analyze/src/lint/nursery/no_head_import_in_document.rs
index 460aa4e5f90f..86880ba7bf81 100644
--- a/crates/biome_js_analyze/src/lint/nursery/no_head_import_in_document.rs
+++ b/crates/biome_js_analyze/src/lint/nursery/no_head_import_in_document.rs
@@ -49,7 +49,7 @@ declare_lint_rule! {
         language: "jsx",
         sources: &[RuleSource::EslintNext("no-head-import-in-document")],
         source_kind: RuleSourceKind::SameLogic,
-        recommended: false,
+        recommended: true,
         domains: &[RuleDomain::Next],
     }
 }
diff --git a/crates/biome_js_analyze/src/lint/nursery/no_img_element.rs b/crates/biome_js_analyze/src/lint/nursery/no_img_element.rs
index 89dde7d51488..0d623a244885 100644
--- a/crates/biome_js_analyze/src/lint/nursery/no_img_element.rs
+++ b/crates/biome_js_analyze/src/lint/nursery/no_img_element.rs
@@ -57,7 +57,7 @@ declare_lint_rule! {
         language: "jsx",
         sources: &[RuleSource::EslintNext("no-img-element")],
         source_kind: RuleSourceKind::SameLogic,
-        recommended: false,
+        recommended: true,
         domains: &[RuleDomain::Next],
     }
 }
diff --git a/crates/biome_js_analyze/src/lint/nursery/no_unwanted_polyfillio.rs b/crates/biome_js_analyze/src/lint/nursery/no_unwanted_polyfillio.rs
index edd78e75da2f..0670e1ac4742 100644
--- a/crates/biome_js_analyze/src/lint/nursery/no_unwanted_polyfillio.rs
+++ b/crates/biome_js_analyze/src/lint/nursery/no_unwanted_polyfillio.rs
@@ -54,7 +54,7 @@ declare_lint_rule! {
         language: "jsx",
         sources: &[RuleSource::EslintNext("no-unwanted-polyfillio")],
         source_kind: RuleSourceKind::SameLogic,
-        recommended: false,
+        recommended: true,
         severity: Severity::Warning,
         domains: &[RuleDomain::Next],
     }
diff --git a/crates/biome_js_analyze/src/lint/nursery/use_google_font_display.rs b/crates/biome_js_analyze/src/lint/nursery/use_google_font_display.rs
index 832724e854be..d42192ae6a72 100644
--- a/crates/biome_js_analyze/src/lint/nursery/use_google_font_display.rs
+++ b/crates/biome_js_analyze/src/lint/nursery/use_google_font_display.rs
@@ -1,5 +1,6 @@
 use biome_analyze::{
-    context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource, RuleSourceKind,
+    context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource,
+    RuleSourceKind,
 };
 use biome_console::markup;
 use biome_js_syntax::jsx_ext::AnyJsxElement;
@@ -54,7 +55,8 @@ declare_lint_rule! {
         language: "jsx",
         sources: &[RuleSource::EslintNext("google-font-display")],
         source_kind: RuleSourceKind::SameLogic,
-        recommended: false,
+        recommended: true,
+        domains: &[RuleDomain::Next],
     }
 }
 
diff --git a/crates/biome_js_analyze/src/lint/nursery/use_google_font_preconnect.rs b/crates/biome_js_analyze/src/lint/nursery/use_google_font_preconnect.rs
index 169df8e6da73..eda96de96f90 100644
--- a/crates/biome_js_analyze/src/lint/nursery/use_google_font_preconnect.rs
+++ b/crates/biome_js_analyze/src/lint/nursery/use_google_font_preconnect.rs
@@ -1,6 +1,6 @@
 use biome_analyze::{
-    context::RuleContext, declare_lint_rule, Ast, FixKind, Rule, RuleDiagnostic, RuleSource,
-    RuleSourceKind,
+    context::RuleContext, declare_lint_rule, Ast, FixKind, Rule, RuleDiagnostic, RuleDomain,
+    RuleSource, RuleSourceKind,
 };
 use biome_console::markup;
 use biome_js_factory::make;
@@ -51,8 +51,9 @@ declare_lint_rule! {
         language: "jsx",
         sources: &[RuleSource::EslintNext("google-font-preconnect")],
         source_kind: RuleSourceKind::SameLogic,
-        recommended: false,
+        recommended: true,
         fix_kind: FixKind::Safe,
+        domains: &[RuleDomain::Next],
     }
 }
 
diff --git a/crates/biome_service/src/file_handlers/mod.rs b/crates/biome_service/src/file_handlers/mod.rs
index fd3295a596a8..f78679c54a40 100644
--- a/crates/biome_service/src/file_handlers/mod.rs
+++ b/crates/biome_service/src/file_handlers/mod.rs
@@ -991,25 +991,24 @@ impl<'a, 'b> LintVisitor<'a, 'b> {
         L: biome_rowan::Language,
         R: Rule<Query: Queryable<Language = L, Output: Clone>> + 'static,
     {
+        let path = self.path.expect("File path");
         let no_only = self.only.is_some_and(|only| only.is_empty());
         let no_domains = self
             .settings
-            .and_then(|settings| settings.as_linter_domains(self.path.expect("File path")))
+            .and_then(|settings| settings.as_linter_domains(path))
             .is_none_or(|d| d.is_empty());
+        if !(no_only && no_domains) {
+            return;
+        }
 
-        if no_only && no_domains {
-            if let Some((_, manifest)) = self
-                .path
-                .and_then(|path| self.project_layout.get_node_manifest_for_path(path))
-            {
-                for domain in R::METADATA.domains {
-                    self.analyzer_options
-                        .push_globals(domain.globals().iter().map(|s| Box::from(*s)).collect());
+        if let Some((_, manifest)) = self.project_layout.get_node_manifest_for_path(path) {
+            for domain in R::METADATA.domains {
+                self.analyzer_options
+                    .push_globals(domain.globals().iter().map(|s| Box::from(*s)).collect());
 
-                    for (dependency, range) in domain.manifest_dependencies() {
-                        if manifest.matches_dependency(dependency, range) {
-                            self.enabled_rules.insert(rule_filter);
-                        }
+                for (dependency, range) in domain.manifest_dependencies() {
+                    if manifest.matches_dependency(dependency, range) {
+                        self.enabled_rules.insert(rule_filter);
                     }
                 }
             }
@@ -1028,32 +1027,50 @@ impl<'a, 'b> LintVisitor<'a, 'b> {
     {
         let no_only = self.only.is_some_and(|only| only.is_empty());
 
-        if no_only {
-            let domains = self
-                .settings
-                .and_then(|settings| settings.as_linter_domains(self.path.expect("File path")));
+        if !no_only {
+            return;
+        }
 
-            // domains, no need to record the rule
-            if domains.as_ref().is_none_or(|d| d.is_empty()) {
-                return;
-            }
+        let domains = self
+            .settings
+            .and_then(|settings| settings.as_linter_domains(self.path.expect("File path")));
 
-            // If the rule is recommended, and it has some domains, it should be disabled, but only if the configuration doesn't enable some domains.
-            if R::METADATA.recommended
-                && !R::METADATA.domains.is_empty()
-                && domains.as_ref().is_none_or(|d| d.is_empty())
-            {
-                self.disabled_rules.insert(rule_filter);
-                return;
-            }
+        // no domains, no need to record the rule
+        if domains.as_ref().is_none_or(|d| d.is_empty()) {
+            return;
+        }
+
+        // If the rule is recommended, and it has some domains, it should be disabled, but only if the configuration doesn't enable some domains.
+        if R::METADATA.recommended
+            && !R::METADATA.domains.is_empty()
+            && domains.as_ref().is_none_or(|d| d.is_empty())
+        {
+            self.disabled_rules.insert(rule_filter);
+            return;
+        }
 
-            for rule_domain in R::METADATA.domains {
-                if let Some((configured_domain, configured_domain_value)) = domains
-                    .as_ref()
-                    .and_then(|domains| domains.get_key_value(rule_domain))
-                {
-                    match configured_domain_value {
-                        RuleDomainValue::All => {
+        for rule_domain in R::METADATA.domains {
+            if let Some((configured_domain, configured_domain_value)) = domains
+                .as_ref()
+                .and_then(|domains| domains.get_key_value(rule_domain))
+            {
+                match configured_domain_value {
+                    RuleDomainValue::All => {
+                        self.enabled_rules.insert(rule_filter);
+
+                        self.analyzer_options.push_globals(
+                            configured_domain
+                                .globals()
+                                .iter()
+                                .map(|s| Box::from(*s))
+                                .collect::<Vec<_>>(),
+                        );
+                    }
+                    RuleDomainValue::None => {
+                        self.disabled_rules.insert(rule_filter);
+                    }
+                    RuleDomainValue::Recommended => {
+                        if R::METADATA.recommended {
                             self.enabled_rules.insert(rule_filter);
 
                             self.analyzer_options.push_globals(
@@ -1064,22 +1081,6 @@ impl<'a, 'b> LintVisitor<'a, 'b> {
                                     .collect::<Vec<_>>(),
                             );
                         }
-                        RuleDomainValue::None => {
-                            self.disabled_rules.insert(rule_filter);
-                        }
-                        RuleDomainValue::Recommended => {
-                            if R::METADATA.recommended {
-                                self.enabled_rules.insert(rule_filter);
-
-                                self.analyzer_options.push_globals(
-                                    configured_domain
-                                        .globals()
-                                        .iter()
-                                        .map(|s| Box::from(*s))
-                                        .collect::<Vec<_>>(),
-                                );
-                            }
-                        }
                     }
                 }
             }
diff --git a/xtask/codegen/src/generate_configuration.rs b/xtask/codegen/src/generate_configuration.rs
index 8894e7a9ee49..b2d31c0830a4 100644
--- a/xtask/codegen/src/generate_configuration.rs
+++ b/xtask/codegen/src/generate_configuration.rs
@@ -699,7 +699,7 @@ fn generate_group_struct(
             }
         );
         let rule_name = Ident::new(&to_capitalized(rule), Span::call_site());
-        if metadata.recommended {
+        if metadata.recommended && metadata.domains.is_empty() {
             lines_recommended_rule_as_filter.push(quote! {
                 RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[#rule_position])
             });


Test patch:
diff --git a/crates/biome_cli/tests/cases/rules_via_dependencies.rs b/crates/biome_cli/tests/cases/rules_via_dependencies.rs
index 5bc9c3d72748..0ea4f0b02bf9 100644
--- a/crates/biome_cli/tests/cases/rules_via_dependencies.rs
+++ b/crates/biome_cli/tests/cases/rules_via_dependencies.rs
@@ -1,26 +1,23 @@
 //! Here, we put test cases where lint rules are enabled via package.json dependencies
 
-use crate::run_cli;
+use crate::run_cli_with_dyn_fs;
 use crate::snap_test::{assert_cli_snapshot, SnapshotPayload};
 use biome_console::BufferConsole;
-use biome_fs::MemoryFileSystem;
+use biome_fs::TemporaryFs;
 use bpaf::Args;
-use camino::Utf8Path;
 
 #[test]
 fn enables_react_rules_via_dependencies() {
     let mut console = BufferConsole::default();
-    let mut fs = MemoryFileSystem::default();
-    let file_path = Utf8Path::new("package.json");
-    fs.insert(
-        file_path.into(),
+    let mut fs = TemporaryFs::new("enables_react_rules_via_dependencies");
+    fs.create_file(
+        "package.json",
         r#"{
     "dependencies": {
         "react": "^16.0.0"
     }
 }
-"#
-        .as_bytes(),
+"#,
     );
 
     let content = r#"
@@ -33,13 +30,12 @@ function Component2() {
     }, [local, local]);
 }
     "#;
-    let test = Utf8Path::new("test.jsx");
-    fs.insert(test.into(), content.as_bytes());
+    fs.create_file("test.jsx", content);
 
-    let (fs, result) = run_cli(
-        fs,
+    let result = run_cli_with_dyn_fs(
+        Box::new(fs.create_os()),
         &mut console,
-        Args::from(["lint", test.as_str()].as_slice()),
+        Args::from(["lint", fs.cli_path()].as_slice()),
     );
 
     assert!(result.is_err(), "run_cli returned {result:?}");
@@ -47,7 +43,7 @@ function Component2() {
     assert_cli_snapshot(SnapshotPayload::new(
         module_path!(),
         "enables_react_rules_via_dependencies",
-        fs,
+        fs.create_mem(),
         console,
         result,
     ));
@@ -56,17 +52,15 @@ function Component2() {
 #[test]
 fn enables_test_globals_via_dependencies() {
     let mut console = BufferConsole::default();
-    let mut fs = MemoryFileSystem::default();
-    let file_path = Utf8Path::new("package.json");
-    fs.insert(
-        file_path.into(),
+    let mut fs = TemporaryFs::new("enables_test_globals_via_dependencies");
+    fs.create_file(
+        "package.json",
         r#"{
     "dependencies": {
         "mocha": "10.0.0"
     }
 }
-"#
-        .as_bytes(),
+"#,
     );
 
     let content = r#"
@@ -80,13 +74,12 @@ describe("foo", () => {
 	});
 });
     "#;
-    let test = Utf8Path::new("test.js");
-    fs.insert(test.into(), content.as_bytes());
+    fs.create_file("test.js", content);
 
-    let (fs, result) = run_cli(
-        fs,
+    let result = run_cli_with_dyn_fs(
+        Box::new(fs.create_os()),
         &mut console,
-        Args::from(["lint", test.as_str()].as_slice()),
+        Args::from(["lint", fs.cli_path()].as_slice()),
     );
 
     assert!(result.is_err(), "run_cli returned {result:?}");
@@ -94,7 +87,7 @@ describe("foo", () => {
     assert_cli_snapshot(SnapshotPayload::new(
         module_path!(),
         "enables_test_globals_via_dependencies",
-        fs,
+        fs.create_mem(),
         console,
         result,
     ));
@@ -103,17 +96,15 @@ describe("foo", () => {
 #[test]
 fn enables_rules_via_dependencies_but_disable_rule_from_config() {
     let mut console = BufferConsole::default();
-    let mut fs = MemoryFileSystem::default();
-    let file_path = Utf8Path::new("package.json");
-    fs.insert(
-        file_path.into(),
+    let mut fs = TemporaryFs::new("enables_rules_via_dependencies_but_disable_rule_from_config");
+    fs.create_file(
+        "package.json",
         r#"{
     "dependencies": {
         "react": "latest"
     }
 }
-"#
-        .as_bytes(),
+"#,
     );
 
     let content = r#"
@@ -126,12 +117,10 @@ function Component2() {
     }, [local, local]);
 }
     "#;
-    let test = Utf8Path::new("test.jsx");
-    fs.insert(test.into(), content.as_bytes());
+    fs.create_file("test.jsx", content);
 
-    let config = Utf8Path::new("biome.json");
-    fs.insert(
-        config.into(),
+    fs.create_file(
+        "biome.json",
         r#"{
     "linter": {
         "rules": {
@@ -141,14 +130,13 @@ function Component2() {
         }
     }
 }
-"#
-        .as_bytes(),
+"#,
     );
 
-    let (fs, result) = run_cli(
-        fs,
+    let result = run_cli_with_dyn_fs(
+        Box::new(fs.create_os()),
         &mut console,
-        Args::from(["lint", test.as_str()].as_slice()),
+        Args::from(["lint", fs.cli_path()].as_slice()),
     );
 
     assert!(result.is_ok(), "run_cli returned {result:?}");
@@ -156,7 +144,54 @@ function Component2() {
     assert_cli_snapshot(SnapshotPayload::new(
         module_path!(),
         "enables_rules_via_dependencies_but_disable_rule_from_config",
-        fs,
+        fs.create_mem(),
+        console,
+        result,
+    ));
+}
+
+#[test]
+fn enables_next_rules_via_dependencies() {
+    let mut console = BufferConsole::default();
+    let mut fs = TemporaryFs::new("enables_next_rules_via_dependencies");
+    fs.create_file(
+        "package.json",
+        r#"{
+    "dependencies": {
+        "next": ">=14.0.0"
+    }
+}"#,
+    );
+
+    fs.create_file(
+        "test.jsx",
+        r#"import React from 'react';
+
+function IndexPage() {
+    return (
+        <div>
+            <img alt="Foo" />
+            <p>Some content</p>
+        </div>
+    );
+}
+
+export default IndexPage;
+"#,
+    );
+
+    let result = run_cli_with_dyn_fs(
+        Box::new(fs.create_os()),
+        &mut console,
+        Args::from(["lint", fs.cli_path()].as_slice()),
+    );
+
+    assert!(result.is_ok(), "run_cli returned {result:?}");
+
+    assert_cli_snapshot(SnapshotPayload::new(
+        module_path!(),
+        "enables_next_rules_via_dependencies",
+        fs.create_mem(),
         console,
         result,
     ));
diff --git a/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_next_rules_via_dependencies.snap b/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_next_rules_via_dependencies.snap
new file mode 100644
index 000000000000..7074a5a5f27b
--- /dev/null
+++ b/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_next_rules_via_dependencies.snap
@@ -0,0 +1,55 @@
+---
+source: crates/biome_cli/tests/snap_test.rs
+expression: redactor(content)
+snapshot_kind: text
+---
+## `package.json`
+
+```json
+{
+    "dependencies": {
+        "next": ">=14.0.0"
+    }
+}
+```
+
+## `test.jsx`
+
+```jsx
+import React from 'react';
+
+function IndexPage() {
+    return (
+        <div>
+            <img alt="Foo" />
+            <p>Some content</p>
+        </div>
+    );
+}
+
+export default IndexPage;
+
+```
+
+# Emitted Messages
+
+```block
+/test.jsx:6:13 lint/nursery/noImgElement ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+  i Don't use <img> element.
+  
+    4 │     return (
+    5 │         <div>
+  > 6 │             <img alt="Foo" />
+      │             ^^^^^^^^^^^^^^^^^
+    7 │             <p>Some content</p>
+    8 │         </div>
+  
+  i Using the <img> can lead to slower LCP and higher bandwidth. Consider using <Image /> from next/image to automatically optimize images.
+  
+
+```
+
+```block
+Checked 2 files in <TIME>. No fixes applied.
+```
diff --git a/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_react_rules_via_dependencies.snap b/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_react_rules_via_dependencies.snap
index f08b00016f04..1f948eb4f674 100644
--- a/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_react_rules_via_dependencies.snap
+++ b/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_react_rules_via_dependencies.snap
@@ -1,6 +1,6 @@
 ---
 source: crates/biome_cli/tests/snap_test.rs
-expression: content
+expression: redactor(content)
 snapshot_kind: text
 ---
 ## `package.json`
@@ -43,7 +43,7 @@ lint ━━━━━━━━━━━━━━━━━━━━━━━━━
 # Emitted Messages
 
 ```block
-test.jsx:6:20 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+/test.jsx:6:20 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
   × This hook specifies more dependencies than necessary: local
   
@@ -67,6 +67,6 @@ test.jsx:6:20 lint/correctness/useExhaustiveDependencies ━━━━━━━
 ```
 
 ```block
-Checked 1 file in <TIME>. No fixes applied.
+Checked 2 files in <TIME>. No fixes applied.
 Found 1 error.
 ```
diff --git a/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_rules_via_dependencies_but_disable_rule_from_config.snap b/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_rules_via_dependencies_but_disable_rule_from_config.snap
index 1d85f805c0b4..4bd8d19320c5 100644
--- a/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_rules_via_dependencies_but_disable_rule_from_config.snap
+++ b/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_rules_via_dependencies_but_disable_rule_from_config.snap
@@ -1,6 +1,6 @@
 ---
 source: crates/biome_cli/tests/snap_test.rs
-expression: content
+expression: redactor(content)
 snapshot_kind: text
 ---
 ## `biome.json`
@@ -46,5 +46,5 @@ function Component2() {
 # Emitted Messages
 
 ```block
-Checked 1 file in <TIME>. No fixes applied.
+Checked 3 files in <TIME>. No fixes applied.
 ```
diff --git a/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_test_globals_via_dependencies.snap b/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_test_globals_via_dependencies.snap
index 6f6a45abf15e..d002423638ce 100644
--- a/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_test_globals_via_dependencies.snap
+++ b/crates/biome_cli/tests/snapshots/main_cases_rules_via_dependencies/enables_test_globals_via_dependencies.snap
@@ -1,6 +1,6 @@
 ---
 source: crates/biome_cli/tests/snap_test.rs
-expression: content
+expression: redactor(content)
 snapshot_kind: text
 ---
 ## `package.json`
@@ -44,7 +44,7 @@ lint ━━━━━━━━━━━━━━━━━━━━━━━━━
 # Emitted Messages
 
 ```block
-test.js:5:2 lint/suspicious/noDuplicateTestHooks ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+/test.js:5:2 lint/suspicious/noDuplicateTestHooks ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
   × Disallow duplicate setup and teardown hooks.
   
@@ -63,6 +63,6 @@ test.js:5:2 lint/suspicious/noDuplicateTestHooks ━━━━━━━━━━
 ```
 
 ```block
-Checked 1 file in <TIME>. No fixes applied.
+Checked 2 files in <TIME>. No fixes applied.
 Found 1 error.
 ```
diff --git a/crates/biome_cli/tests/snapshots/main_commands_rage/with_linter_configuration.snap b/crates/biome_cli/tests/snapshots/main_commands_rage/with_linter_configuration.snap
index 3416c3276ab5..0d85aa7621e4 100644
--- a/crates/biome_cli/tests/snapshots/main_commands_rage/with_linter_configuration.snap
+++ b/crates/biome_cli/tests/snapshots/main_commands_rage/with_linter_configuration.snap
@@ -68,7 +68,6 @@ Linter:
   Enabled rules:
     complexity/noBannedTypes
     complexity/noEmptyTypeParameters
-    complexity/noExcessiveNestedTestSuites
     complexity/noExtraBooleanCast
     complexity/noForEach
     complexity/noMultipleSpacesInRegularExpressionLiterals