diff --git a/CHANGELOG.md b/CHANGELOG.md index 4640d51cf7b7..b4f930d8cf97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,39 +21,36 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b #### New features -- Add a new option `--rule` to the command `biome lint` ([#58](https://github.com/biomejs/biome/issues/58)). +- Add two new options `--only` and `--skip` to the command `biome lint` ([#58](https://github.com/biomejs/biome/issues/58)). - This new option allows you to execute a single rule or a rule group. - This option is convenient to test a rule or apply the code fixes of a single rule. - - For example, you can execute the `style/useNamingConvention` rule on the working directory: + The `--only` option allows you to run a given rule or rule group, + For example, the following command runs only the `style/useNamingConvention` and `style/noInferrableTypes` rules. + If the rule is disabled in the configuration, then its severity level is set to `error` for a recommended rule or `warn` otherwise. ```shell - biome lint --rule=style/useNamingConvention ./ + biome lint --only=style/useNamingConvention --only=style/noInferrableTypes ``` - If the rule has a code action (autofix), you can use `--apply` to apply the fix: + Passing a group does not change the severity level of the rules in the group. + All the disabled rules in the group will remain disabled. + To ensure that the group is run, the `recommended` field of the group is enabled. + The `nursery` group cannot be passed, as no rules are enabled by default in the nursery group. + + The `--skip` option allows you to skip the execution of a given group or a given rule. + For example, the following command skips the `style` group and the `suspicious/noExplicitAny` rule. ```shell - biome lint --rule=style/useNamingConvention --apply ./ + biome lint --skip=style --skip=suspicious/noExplicitAny ``` - The option takes the rule options in the Biome configuration file into account. - Only, the severity level of the rule is overridden by its default value, - i.e. `error` for a recommended rule or `warn` otherwise. - - You can also run a group of rules: + You can also use `--only` and `--skip` together. `--skip` oevrrides `--only`. + The following command executes only the rules from the `style` group, but the `style/useNamingConvention` rule. ```shell - biome lint --rule=suspicious src/main.js + biome lint --only=style --skip=style/useNamingConvention ``` - In this case, the severity level of a rule is not overridden. - Thus, the disabled rules stay disabled. - To ensure that the group is run, the `recommended` field of the group is turned on. - The `nursery` group cannot be passed because no rules are enabled in the nursery group by default. - - The option is compatible with other options such as `--apply`, `--apply-unsafe` and `--reporter`. + These options are compatible with other options such as `--write` (previously `--apply`), and `--reporter`. Contributed by @Conaclos diff --git a/crates/biome_analyze/src/lib.rs b/crates/biome_analyze/src/lib.rs index 6dfe8b032221..743b11412e50 100644 --- a/crates/biome_analyze/src/lib.rs +++ b/crates/biome_analyze/src/lib.rs @@ -548,7 +548,7 @@ where let line_index = *self.line_index + 1; // If the last suppression was on the same or previous line, extend its - // range and set of supressed rules with the content for the new suppression + // range and set of suppressed rules with the content for the new suppression if let Some(last_suppression) = self.line_suppressions.last_mut() { if last_suppression.line_index == line_index || last_suppression.line_index + 1 == line_index @@ -767,7 +767,7 @@ pub enum RuleFilter<'a> { } impl<'a> RuleFilter<'a> { - // Returns the group name of thie filter. + // Returns the group name of this filter. pub fn group(self) -> &'a str { match self { RuleFilter::Group(group) => group, @@ -822,14 +822,23 @@ pub struct AnalysisFilter<'a> { /// Only allow rules with these categories to emit signals pub categories: RuleCategories, /// Only allow rules matching these names to emit signals + /// If `enabled_rules` is set to `None`, then all rules are enabled. pub enabled_rules: Option<&'a [RuleFilter<'a>]>, /// Do not allow rules matching these names to emit signals - pub disabled_rules: Option<&'a [RuleFilter<'a>]>, + pub disabled_rules: &'a [RuleFilter<'a>], /// Only emit signals matching this text range pub range: Option, } impl<'analysis> AnalysisFilter<'analysis> { + /// It creates a new filter with the set of [enabled rules](RuleFilter) passed as argument + pub fn from_enabled_rules(enabled_rules: &'analysis [RuleFilter<'analysis>]) -> Self { + Self { + enabled_rules: Some(enabled_rules), + ..AnalysisFilter::default() + } + } + /// Return `true` if the category `C` matches this filter pub fn match_category(&self) -> bool { self.categories.contains(C::CATEGORY.into()) @@ -841,33 +850,22 @@ impl<'analysis> AnalysisFilter<'analysis> { && self.enabled_rules.map_or(true, |enabled_rules| { enabled_rules.iter().any(|filter| filter.match_group::()) }) - && self.disabled_rules.map_or(true, |disabled_rules| { - !disabled_rules - .iter() - .any(|filter| filter.match_group::()) - }) + && !self + .disabled_rules + .iter() + .any(|filter| matches!(filter, RuleFilter::Group(_)) && filter.match_group::()) } /// Return `true` if the rule `R` matches this filter - pub fn match_rule(&self) -> bool - where - R: Rule, - { - self.match_group::() + pub fn match_rule(&self) -> bool { + self.match_category::<::Category>() && self.enabled_rules.map_or(true, |enabled_rules| { enabled_rules.iter().any(|filter| filter.match_rule::()) }) - && self.disabled_rules.map_or(true, |disabled_rules| { - !disabled_rules.iter().any(|filter| filter.match_rule::()) - }) - } - - /// It creates a new filter with the set of [enabled rules](RuleFilter) passed as argument - pub fn from_enabled_rules(enabled_rules: Option<&'analysis [RuleFilter<'analysis>]>) -> Self { - Self { - enabled_rules, - ..AnalysisFilter::default() - } + && !self + .disabled_rules + .iter() + .any(|filter| filter.match_rule::()) } } diff --git a/crates/biome_cli/src/commands/lint.rs b/crates/biome_cli/src/commands/lint.rs index b0beaa001e65..8abfabf4acc9 100644 --- a/crates/biome_cli/src/commands/lint.rs +++ b/crates/biome_cli/src/commands/lint.rs @@ -33,7 +33,8 @@ pub(crate) struct LintCommandPayload { pub(crate) vcs_configuration: Option, pub(crate) files_configuration: Option, pub(crate) paths: Vec, - pub(crate) rule: Option, + pub(crate) only: Vec, + pub(crate) skip: Vec, pub(crate) stdin_file_path: Option, pub(crate) staged: bool, pub(crate) changed: bool, @@ -54,7 +55,8 @@ pub(crate) fn lint(session: CliSession, payload: LintCommandPayload) -> Result<( cli_options, mut linter_configuration, mut paths, - rule, + only, + skip, stdin_file_path, vcs_configuration, files_configuration, @@ -161,7 +163,8 @@ pub(crate) fn lint(session: CliSession, payload: LintCommandPayload) -> Result<( Execution::new(TraversalMode::Lint { fix_file_mode, stdin, - rule, + only, + skip, }) .set_report(&cli_options), session, diff --git a/crates/biome_cli/src/commands/mod.rs b/crates/biome_cli/src/commands/mod.rs index baae4ba71af8..215230e0669c 100644 --- a/crates/biome_cli/src/commands/mod.rs +++ b/crates/biome_cli/src/commands/mod.rs @@ -193,19 +193,20 @@ pub enum BiomeCommand { #[bpaf(external, hide_usage)] cli_options: CliOptions, - /// Run only the given rule or rule group. + /// Run only the given rule or group of rules. + /// If the severity level of a rule is `off`, + /// then the severity level of the rule is set to `error` if it is a recommended rule or `warn` otherwise. /// - /// The option overrides the Biome configuration file as follows: - /// - /// - When a rule is passed, its severity level is set to `error' if it is a recommended rule, or `warn' otherwise. - /// - /// - When a rule group is passed, the `recommended` flag is enabled, but if the `all` flag is enabled. - /// - /// Example: `biome lint --rule=correctness/noUnusedVariables` + /// Example: `biome lint --only=correctness/noUnusedVariables --only=suspicious` + #[bpaf(long("only"), argument("GROUP|RULE"))] + only: Vec, + + /// Skip the given rule or group of rules by setting the severity level of the rules to `off`. + /// This option takes precedence over `--only`. /// - /// Example: `biome lint --rule=suspicious` - #[bpaf(long("rule"), argument("GROUP|RULE"))] - rule: Option, + /// Example: `biome lint --skip=correctness/noUnusedVariables --skip=suspicious` + #[bpaf(long("skip"), argument("GROUP|RULE"))] + skip: Vec, /// Use this option when you want to format code piped from `stdin`, and print the output to `stdout`. /// diff --git a/crates/biome_cli/src/execute/mod.rs b/crates/biome_cli/src/execute/mod.rs index a920a145e13f..e4d2a34a2590 100644 --- a/crates/biome_cli/src/execute/mod.rs +++ b/crates/biome_cli/src/execute/mod.rs @@ -127,11 +127,13 @@ pub enum TraversalMode { /// 1. The virtual path to the file /// 2. The content of the file stdin: Option, - /// Run only the given rule or rule group. - /// The option overrides the Biome configuration file as follows: - /// - When a rule is passed, its severity level is set to `error' if it is a recommended rule, or `warn' otherwise. - /// - When a rule group is passed, the `recommended` flag is enabled, but if the `all` flag is enabled. - rule: Option, + /// Run only the given rule or group of rules. + /// If the severity level of a rule is `off`, + /// then the severity level of the rule is set to `error` if it is a recommended rule or `warn` otherwise. + only: Vec, + /// Skip the given rule or group of rules by setting the severity level of the rules to `off`. + /// This option takes precedence over `--only`. + skip: Vec, }, /// This mode is enabled when running the command `biome ci` CI { diff --git a/crates/biome_cli/src/execute/process_file/format.rs b/crates/biome_cli/src/execute/process_file/format.rs index 10b35d5d98d6..fbc2be9c40b3 100644 --- a/crates/biome_cli/src/execute/process_file/format.rs +++ b/crates/biome_cli/src/execute/process_file/format.rs @@ -26,7 +26,12 @@ pub(crate) fn format_with_guard<'ctx>( debug!("Pulling diagnostics from parsed file"); let diagnostics_result = workspace_file .guard() - .pull_diagnostics(RuleCategories::SYNTAX, max_diagnostics.into(), None) + .pull_diagnostics( + RuleCategories::SYNTAX, + max_diagnostics.into(), + Vec::new(), + Vec::new(), + ) .with_file_path_and_code( workspace_file.path.display().to_string(), category!("format"), diff --git a/crates/biome_cli/src/execute/process_file/lint.rs b/crates/biome_cli/src/execute/process_file/lint.rs index 29ba6975545f..9bbe4e45a0b3 100644 --- a/crates/biome_cli/src/execute/process_file/lint.rs +++ b/crates/biome_cli/src/execute/process_file/lint.rs @@ -57,17 +57,19 @@ pub(crate) fn lint_with_guard<'ctx>( } let max_diagnostics = ctx.remaining_diagnostics.load(Ordering::Relaxed); - let rule = if let TraversalMode::Lint { rule, .. } = ctx.execution.traversal_mode() { - *rule - } else { - None - }; + let (only, skip) = + if let TraversalMode::Lint { only, skip, .. } = ctx.execution.traversal_mode() { + (only.clone(), skip.clone()) + } else { + (Vec::new(), Vec::new()) + }; let pull_diagnostics_result = workspace_file .guard() .pull_diagnostics( RuleCategories::LINT | RuleCategories::SYNTAX, max_diagnostics.into(), - rule, + only, + skip, ) .with_file_path_and_code( workspace_file.path.display().to_string(), diff --git a/crates/biome_cli/src/execute/std_in.rs b/crates/biome_cli/src/execute/std_in.rs index 8474b54463b3..65503dce7def 100644 --- a/crates/biome_cli/src/execute/std_in.rs +++ b/crates/biome_cli/src/execute/std_in.rs @@ -156,17 +156,18 @@ pub(crate) fn run<'a>( } } - let rule = if let TraversalMode::Lint { rule, .. } = mode.traversal_mode() { - *rule + let (only, skip) = if let TraversalMode::Lint { only, skip, .. } = mode.traversal_mode() { + (only.clone(), skip.clone()) } else { - None + (Vec::new(), Vec::new()) }; if !mode.is_check_apply_unsafe() { let result = workspace.pull_diagnostics(PullDiagnosticsParams { categories: RuleCategories::LINT | RuleCategories::SYNTAX, path: biome_path.clone(), max_diagnostics: mode.max_diagnostics.into(), - rule, + only, + skip, })?; diagnostics.extend(result.diagnostics); } diff --git a/crates/biome_cli/src/lib.rs b/crates/biome_cli/src/lib.rs index 8af0b407cbce..0b66f0c179f4 100644 --- a/crates/biome_cli/src/lib.rs +++ b/crates/biome_cli/src/lib.rs @@ -124,7 +124,8 @@ impl<'app> CliSession<'app> { cli_options, linter_configuration, paths, - rule, + only, + skip, stdin_file_path, vcs_configuration, files_configuration, @@ -145,7 +146,8 @@ impl<'app> CliSession<'app> { cli_options, linter_configuration, paths, - rule, + only, + skip, stdin_file_path, vcs_configuration, files_configuration, diff --git a/crates/biome_cli/tests/commands/lint.rs b/crates/biome_cli/tests/commands/lint.rs index 505c2bb9a164..796c8de7316f 100644 --- a/crates/biome_cli/tests/commands/lint.rs +++ b/crates/biome_cli/tests/commands/lint.rs @@ -131,38 +131,6 @@ fn parse_error() { )); } -#[test] -fn lint_rule_rule_doesnt_exist() { - let mut fs = MemoryFileSystem::default(); - let mut console = BufferConsole::default(); - - let file_path = Path::new("check.js"); - fs.insert(file_path.into(), LINT_ERROR.as_bytes()); - - let result = run_cli( - DynRef::Borrowed(&mut fs), - &mut console, - Args::from( - [ - ("lint"), - "--rule=suspicious/inexistant", - file_path.as_os_str().to_str().unwrap(), - ] - .as_slice(), - ), - ); - - assert!(result.is_err(), "run_cli returned {result:?}"); - - assert_cli_snapshot(SnapshotPayload::new( - module_path!(), - "lint_rule_rule_doesnt_exist", - fs, - console, - result, - )); -} - #[test] fn maximum_diagnostics() { let mut fs = MemoryFileSystem::default(); @@ -3306,7 +3274,39 @@ fn lint_error() { } #[test] -fn lint_rule_missing_group() { +fn lint_only_rule_doesnt_exist() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Path::new("check.js"); + fs.insert(file_path.into(), LINT_ERROR.as_bytes()); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from( + [ + ("lint"), + "--only=suspicious/inexistant", + file_path.as_os_str().to_str().unwrap(), + ] + .as_slice(), + ), + ); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_only_rule_doesnt_exist", + fs, + console, + result, + )); +} + +#[test] +fn lint_only_missing_group() { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); @@ -3319,7 +3319,7 @@ fn lint_rule_missing_group() { Args::from( [ ("lint"), - "--rule=noDebugger", + "--only=noDebugger", file_path.as_os_str().to_str().unwrap(), ] .as_slice(), @@ -3330,7 +3330,7 @@ fn lint_rule_missing_group() { assert_cli_snapshot(SnapshotPayload::new( module_path!(), - "lint_rule_missing_group", + "lint_only_missing_group", fs, console, result, @@ -3338,7 +3338,7 @@ fn lint_rule_missing_group() { } #[test] -fn lint_rule_filter() { +fn lint_only_rule() { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); let content = "debugger; delete obj.prop;"; @@ -3352,7 +3352,7 @@ fn lint_rule_filter() { Args::from( [ ("lint"), - "--rule=suspicious/noDebugger", + "--only=suspicious/noDebugger", file_path.as_os_str().to_str().unwrap(), ] .as_slice(), @@ -3361,7 +3361,7 @@ fn lint_rule_filter() { assert_cli_snapshot(SnapshotPayload::new( module_path!(), - "lint_rule_filter", + "lint_only_rule", fs, console, result, @@ -3369,7 +3369,71 @@ fn lint_rule_filter() { } #[test] -fn lint_rule_filter_ignore_suppression_comments() { +fn lint_only_multiple_rules() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let content = "debugger; delete obj.prop; a === -0;"; + + let file_path = Path::new("check.js"); + fs.insert(file_path.into(), content.as_bytes()); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from( + [ + ("lint"), + "--only=suspicious/noDebugger", + "--only=performance/noDelete", + file_path.as_os_str().to_str().unwrap(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_only_multiple_rules", + fs, + console, + result, + )); +} + +#[test] +fn lint_only_rule_and_group() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let content = "debugger; delete obj.prop; a === -0;"; + + let file_path = Path::new("check.js"); + fs.insert(file_path.into(), content.as_bytes()); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from( + [ + ("lint"), + "--only=suspicious/noDebugger", + "--only=performance", + file_path.as_os_str().to_str().unwrap(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_only_rule_and_group", + fs, + console, + result, + )); +} + +#[test] +fn lint_only_rule_ignore_suppression_comments() { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); let content = r#" @@ -3387,7 +3451,7 @@ fn lint_rule_filter_ignore_suppression_comments() { Args::from( [ ("lint"), - "--rule=suspicious/noDebugger", + "--only=suspicious/noDebugger", file_path.as_os_str().to_str().unwrap(), ] .as_slice(), @@ -3396,7 +3460,7 @@ fn lint_rule_filter_ignore_suppression_comments() { assert_cli_snapshot(SnapshotPayload::new( module_path!(), - "lint_rule_filter_ignore_suppression_comments", + "lint_only_rule_ignore_suppression_comments", fs, console, result, @@ -3404,7 +3468,7 @@ fn lint_rule_filter_ignore_suppression_comments() { } #[test] -fn lint_rule_filter_with_config() { +fn lint_only_rule_with_config() { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); let config = r#"{ @@ -3437,7 +3501,7 @@ fn lint_rule_filter_with_config() { Args::from( [ ("lint"), - "--rule=style/useNamingConvention", + "--only=style/useNamingConvention", file_path.as_os_str().to_str().unwrap(), ] .as_slice(), @@ -3446,7 +3510,7 @@ fn lint_rule_filter_with_config() { assert_cli_snapshot(SnapshotPayload::new( module_path!(), - "lint_rule_filter_with_config", + "lint_only_rule_with_config", fs, console, result, @@ -3454,7 +3518,7 @@ fn lint_rule_filter_with_config() { } #[test] -fn lint_rule_filter_with_recommended_disabled() { +fn lint_only_rule_with_recommended_disabled() { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); let config = r#"{ @@ -3479,7 +3543,7 @@ fn lint_rule_filter_with_recommended_disabled() { Args::from( [ ("lint"), - "--rule=lint/style/useNamingConvention", + "--only=lint/style/useNamingConvention", file_path.as_os_str().to_str().unwrap(), ] .as_slice(), @@ -3488,7 +3552,7 @@ fn lint_rule_filter_with_recommended_disabled() { assert_cli_snapshot(SnapshotPayload::new( module_path!(), - "lint_rule_filter_with_recommended_disabled", + "lint_only_rule_with_recommended_disabled", fs, console, result, @@ -3496,7 +3560,7 @@ fn lint_rule_filter_with_recommended_disabled() { } #[test] -fn lint_rule_filter_with_linter_disabled() { +fn lint_only_rule_with_linter_disabled() { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); let config = r#"{ @@ -3519,7 +3583,7 @@ fn lint_rule_filter_with_linter_disabled() { Args::from( [ ("lint"), - "--rule=style/useNamingConvention", + "--only=style/useNamingConvention", file_path.as_os_str().to_str().unwrap(), ] .as_slice(), @@ -3528,7 +3592,7 @@ fn lint_rule_filter_with_linter_disabled() { assert_cli_snapshot(SnapshotPayload::new( module_path!(), - "lint_rule_filter_with_linter_disabled", + "lint_only_rule_with_linter_disabled", fs, console, result, @@ -3536,7 +3600,7 @@ fn lint_rule_filter_with_linter_disabled() { } #[test] -fn lint_rule_filter_group() { +fn lint_only_group() { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); let config = r#"{ @@ -3565,7 +3629,7 @@ fn lint_rule_filter_group() { Args::from( [ ("lint"), - "--rule=suspicious", + "--only=suspicious", file_path.as_os_str().to_str().unwrap(), ] .as_slice(), @@ -3574,7 +3638,7 @@ fn lint_rule_filter_group() { assert_cli_snapshot(SnapshotPayload::new( module_path!(), - "lint_rule_filter_group", + "lint_only_group", fs, console, result, @@ -3582,7 +3646,7 @@ fn lint_rule_filter_group() { } #[test] -fn lint_rule_filter_nursery_group() { +fn lint_only_nursery_group() { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); let content = ""; @@ -3596,7 +3660,7 @@ fn lint_rule_filter_nursery_group() { Args::from( [ ("lint"), - "--rule=nursery", + "--only=nursery", file_path.as_os_str().to_str().unwrap(), ] .as_slice(), @@ -3605,7 +3669,7 @@ fn lint_rule_filter_nursery_group() { assert_cli_snapshot(SnapshotPayload::new( module_path!(), - "lint_rule_filter_nursery_group", + "lint_only_nursery_group", fs, console, result, @@ -3613,7 +3677,7 @@ fn lint_rule_filter_nursery_group() { } #[test] -fn lint_rule_filter_group_with_disabled_rule() { +fn lint_only_group_with_disabled_rule() { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); let config = r#"{ @@ -3642,7 +3706,272 @@ fn lint_rule_filter_group_with_disabled_rule() { Args::from( [ ("lint"), - "--rule=lint/suspicious", + "--only=lint/suspicious", + file_path.as_os_str().to_str().unwrap(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_only_group_with_disabled_rule", + fs, + console, + result, + )); +} + +#[test] +fn lint_skip_rule() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let content = "debugger; delete obj.prop; a === -0;"; + + let file_path = Path::new("check.js"); + fs.insert(file_path.into(), content.as_bytes()); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from( + [ + ("lint"), + "--skip=suspicious/noDebugger", + file_path.as_os_str().to_str().unwrap(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_skip_rule", + fs, + console, + result, + )); +} + +#[test] +fn lint_skip_group_with_enabled_rule() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let config = r#"{ + "linter": { + "rules": { + "suspicious": { + "noDebugger": "error" + } + } + } + }"#; + let content = "debugger; delete obj.prop;"; + + let file_path = Path::new("check.js"); + fs.insert(file_path.into(), content.as_bytes()); + let config_path = Path::new("biome.json"); + fs.insert(config_path.into(), config.as_bytes()); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from( + [ + ("lint"), + "--skip=suspicious", + file_path.as_os_str().to_str().unwrap(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_skip_group_with_enabled_rule", + fs, + console, + result, + )); +} + +#[test] +fn lint_skip_multiple_rules() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let content = "debugger; delete obj.prop; a === -0;"; + + let file_path = Path::new("check.js"); + fs.insert(file_path.into(), content.as_bytes()); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from( + [ + ("lint"), + "--skip=suspicious/noDebugger", + "--skip=performance/noDelete", + file_path.as_os_str().to_str().unwrap(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_skip_multiple_rules", + fs, + console, + result, + )); +} + +#[test] +fn lint_skip_rule_and_group() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let content = "debugger; delete obj.prop; a === -0;"; + + let file_path = Path::new("check.js"); + fs.insert(file_path.into(), content.as_bytes()); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from( + [ + ("lint"), + "--skip=suspicious/noDebugger", + "--skip=performance", + file_path.as_os_str().to_str().unwrap(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_skip_rule_and_group", + fs, + console, + result, + )); +} + +#[test] +fn lint_only_group_skip_rule() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let content = "debugger; delete obj.prop; a === -0;"; + + let file_path = Path::new("check.js"); + fs.insert(file_path.into(), content.as_bytes()); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from( + [ + ("lint"), + "--only=suspicious", + "--skip=suspicious/noDebugger", + file_path.as_os_str().to_str().unwrap(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_only_group_skip_rule", + fs, + console, + result, + )); +} + +#[test] +fn lint_only_rule_skip_group() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let content = "debugger; delete obj.prop; a === -0;"; + + let file_path = Path::new("check.js"); + fs.insert(file_path.into(), content.as_bytes()); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from( + [ + ("lint"), + "--only=suspicious/noDebugger", + "--skip=suspicious", + file_path.as_os_str().to_str().unwrap(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_only_rule_skip_group", + fs, + console, + result, + )); +} + +#[test] +fn lint_only_skip_rule() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let content = ""; + + let file_path = Path::new("check.js"); + fs.insert(file_path.into(), content.as_bytes()); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from( + [ + ("lint"), + "--only=suspicious/noDebugger", + "--skip=suspicious/noDebugger", + file_path.as_os_str().to_str().unwrap(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_only_skip_rule", + fs, + console, + result, + )); +} + +#[test] +fn lint_only_skip_group() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + let content = ""; + + let file_path = Path::new("check.js"); + fs.insert(file_path.into(), content.as_bytes()); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from( + [ + ("lint"), + "--only=suspicious", + "--skip=suspicious", file_path.as_os_str().to_str().unwrap(), ] .as_slice(), @@ -3651,7 +3980,7 @@ fn lint_rule_filter_group_with_disabled_rule() { assert_cli_snapshot(SnapshotPayload::new( module_path!(), - "lint_rule_filter_group_with_disabled_rule", + "lint_only_skip_group", fs, console, result, diff --git a/crates/biome_cli/tests/snapshots/main_commands_lint/lint_help.snap b/crates/biome_cli/tests/snapshots/main_commands_lint/lint_help.snap index 0be989a08e21..316f2da0c85f 100644 --- a/crates/biome_cli/tests/snapshots/main_commands_lint/lint_help.snap +++ b/crates/biome_cli/tests/snapshots/main_commands_lint/lint_help.snap @@ -7,7 +7,8 @@ expression: content ```block Run various checks on a set of files. -Usage: lint [--write] [--unsafe] [--rule=] [--staged] [--changed] [--since=REF] [PATH]... +Usage: lint [--write] [--unsafe] [--only=]... [--skip=]... [--staged] [--changed +] [--since=REF] [PATH]... Set of properties to integrate Biome with a VCS software. --vcs-client-kind= The kind of client. @@ -71,14 +72,13 @@ Available options: --apply Alias for `--write`, writes safe fixes (deprecated, use `--write`) --apply-unsafe Alias for `--write --unsafe`, writes safe and unsafe fixes (deprecated, use `--write --unsafe`) - --rule= Run only the given rule or rule group. - The option overrides the Biome configuration file as follows: - - When a rule is passed, its severity level is set to `error' if it is - a recommended rule, or `warn' otherwise. - - When a rule group is passed, the `recommended` flag is enabled, but if - the `all` flag is enabled. - Example: `biome lint --rule=correctness/noUnusedVariables` - Example: `biome lint --rule=suspicious` + --only= Run only the given rule or group of rules. If the severity level of a rule + is `off`, then the severity level of the rule is set to `error` if it is + a recommended rule or `warn` otherwise. + Example: `biome lint --only=correctness/noUnusedVariables --only=suspicious` + --skip= Skip the given rule or group of rules by setting the severity level of + the rules to `off`. This option takes precedence over `--only`. + Example: `biome lint --skip=correctness/noUnusedVariables --skip=suspicious` --stdin-file-path=PATH Use this option when you want to format code piped from `stdin`, and print the output to `stdout`. The file doesn't need to exist on disk, what matters is the extension of diff --git a/crates/biome_cli/tests/snapshots/main_commands_lint/lint_rule_filter_group.snap b/crates/biome_cli/tests/snapshots/main_commands_lint/lint_only_group.snap similarity index 100% rename from crates/biome_cli/tests/snapshots/main_commands_lint/lint_rule_filter_group.snap rename to crates/biome_cli/tests/snapshots/main_commands_lint/lint_only_group.snap diff --git a/crates/biome_cli/tests/snapshots/main_commands_lint/lint_only_group_skip_rule.snap b/crates/biome_cli/tests/snapshots/main_commands_lint/lint_only_group_skip_rule.snap new file mode 100644 index 000000000000..193ee47d2b01 --- /dev/null +++ b/crates/biome_cli/tests/snapshots/main_commands_lint/lint_only_group_skip_rule.snap @@ -0,0 +1,42 @@ +--- +source: crates/biome_cli/tests/snap_test.rs +expression: content +--- +## `check.js` + +```js +debugger; delete obj.prop; a === -0; +``` + +# Termination Message + +```block +lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Some errors were emitted while running checks. + + + +``` + +# Emitted Messages + +```block +check.js:1:28 lint/suspicious/noCompareNegZero FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Do not use the === operator to compare against -0. + + > 1 │ debugger; delete obj.prop; a === -0; + │ ^^^^^^^^ + + i Safe fix: Replace -0 with 0 + + 1 │ debugger;·delete·obj.prop;·a·===·-0; + │ - + +``` + +```block +Checked 1 file in