From 0acca585818e85ca41bde41014724ddd512f41b5 Mon Sep 17 00:00:00 2001 From: mysteryven <33973865+mysteryven@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:56:23 +0000 Subject: [PATCH] feat(linter): support `--print-config all` to print config file for project (#6579) Continue work on #4742. Only `oxlint --print-config all` is supported. It's useful to migrate from command-line interface to config file. The `--print-config PATH` looks not really useful for us now, I will add it after config file overrides supported. --- .../print_config/ban_rules/eslintrc.json | 9 ++++ .../print_config/ban_rules/expect.json | 44 +++++++++++++++++ .../fixtures/print_config/normal/expect.json | 37 +++++++++++++++ apps/oxlint/src/command/mod.rs | 5 ++ apps/oxlint/src/lint.rs | 45 ++++++++++++++++++ apps/oxlint/src/result.rs | 5 ++ crates/oxc_linter/src/builder.rs | 47 +++++++++++++++++-- crates/oxc_linter/src/config/globals.rs | 2 +- crates/oxc_linter/src/config/mod.rs | 2 + crates/oxc_linter/src/config/oxlintrc.rs | 2 +- crates/oxc_linter/src/config/rules.rs | 6 +++ .../oxc_linter/src/config/settings/jsdoc.rs | 2 +- .../src/config/settings/jsx_a11y.rs | 2 +- crates/oxc_linter/src/config/settings/mod.rs | 2 +- crates/oxc_linter/src/config/settings/next.rs | 2 +- .../oxc_linter/src/config/settings/react.rs | 2 +- tasks/website/src/linter/snapshots/cli.snap | 2 + .../src/linter/snapshots/cli_terminal.snap | 2 + 18 files changed, 208 insertions(+), 10 deletions(-) create mode 100644 apps/oxlint/fixtures/print_config/ban_rules/eslintrc.json create mode 100644 apps/oxlint/fixtures/print_config/ban_rules/expect.json create mode 100644 apps/oxlint/fixtures/print_config/normal/expect.json diff --git a/apps/oxlint/fixtures/print_config/ban_rules/eslintrc.json b/apps/oxlint/fixtures/print_config/ban_rules/eslintrc.json new file mode 100644 index 0000000000000..ceb957385dd28 --- /dev/null +++ b/apps/oxlint/fixtures/print_config/ban_rules/eslintrc.json @@ -0,0 +1,9 @@ +{ + "rules": { + "no-debugger": "warn", + "eqeqeq": [ + "warn", + "always" + ] + } +} diff --git a/apps/oxlint/fixtures/print_config/ban_rules/expect.json b/apps/oxlint/fixtures/print_config/ban_rules/expect.json new file mode 100644 index 0000000000000..bed2b95024fff --- /dev/null +++ b/apps/oxlint/fixtures/print_config/ban_rules/expect.json @@ -0,0 +1,44 @@ +{ + "plugins": [ + "react", + "unicorn", + "typescript", + "oxc" + ], + "categories": {}, + "rules": { + "eqeqeq": [ + "deny", + [ + "always" + ] + ] + }, + "settings": { + "jsx-a11y": { + "polymorphicPropName": null, + "components": {} + }, + "next": { + "rootDir": [] + }, + "react": { + "formComponents": [], + "linkComponents": [] + }, + "jsdoc": { + "ignorePrivate": false, + "ignoreInternal": false, + "ignoreReplacesDocs": true, + "overrideReplacesDocs": true, + "augmentsExtendsReplacesDocs": false, + "implementsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "tagNamePreference": {} + } + }, + "env": { + "builtin": true + }, + "globals": {} +} diff --git a/apps/oxlint/fixtures/print_config/normal/expect.json b/apps/oxlint/fixtures/print_config/normal/expect.json new file mode 100644 index 0000000000000..cda666fbb9ede --- /dev/null +++ b/apps/oxlint/fixtures/print_config/normal/expect.json @@ -0,0 +1,37 @@ +{ + "plugins": [ + "react", + "unicorn", + "typescript", + "oxc" + ], + "categories": {}, + "rules": {}, + "settings": { + "jsx-a11y": { + "polymorphicPropName": null, + "components": {} + }, + "next": { + "rootDir": [] + }, + "react": { + "formComponents": [], + "linkComponents": [] + }, + "jsdoc": { + "ignorePrivate": false, + "ignoreInternal": false, + "ignoreReplacesDocs": true, + "overrideReplacesDocs": true, + "augmentsExtendsReplacesDocs": false, + "implementsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "tagNamePreference": {} + } + }, + "env": { + "builtin": true + }, + "globals": {} +} diff --git a/apps/oxlint/src/command/mod.rs b/apps/oxlint/src/command/mod.rs index 73871c7d64e83..1252a2e187578 100644 --- a/apps/oxlint/src/command/mod.rs +++ b/apps/oxlint/src/command/mod.rs @@ -25,6 +25,11 @@ pub struct MiscOptions { /// Number of threads to use. Set to 1 for using only 1 CPU core #[bpaf(argument("INT"), hide_usage)] pub threads: Option, + + /// This option outputs the configuration to be used. + /// When present, no linting is performed and only config-related options are valid. + #[bpaf(switch, hide_usage)] + pub print_config: bool, } #[allow(clippy::ptr_arg)] diff --git a/apps/oxlint/src/lint.rs b/apps/oxlint/src/lint.rs index e2cd45cb6322c..9710cc1f2e0f7 100644 --- a/apps/oxlint/src/lint.rs +++ b/apps/oxlint/src/lint.rs @@ -116,10 +116,19 @@ impl Runner for LintRunner { }; enable_plugins.apply_overrides(&mut oxlintrc.plugins); + + let oxlintrc_for_print = + if misc_options.print_config { Some(oxlintrc.clone()) } else { None }; let builder = LinterBuilder::from_oxlintrc(false, oxlintrc) .with_filters(filter) .with_fix(fix_options.fix_kind()); + if let Some(basic_config_file) = oxlintrc_for_print { + return CliRunResult::PrintConfigResult { + config_file: builder.resolve_final_config_file(basic_config_file), + }; + } + let mut options = LintServiceOptions::new(cwd, paths).with_cross_module(builder.plugins().has_import()); let linter = builder.build(); @@ -586,4 +595,40 @@ mod test { // Write the file back. fs::write(file, content).unwrap(); } + + #[test] + fn test_print_config_ban_all_rules() { + let args = &["-A", "all", "--print-config"]; + let options = lint_command().run_inner(args).unwrap(); + let ret = LintRunner::new(options).run(); + let CliRunResult::PrintConfigResult { config_file: config } = ret else { + panic!("Expected PrintConfigResult, got {ret:?}") + }; + + let expect_json = + std::fs::read_to_string("fixtures/print_config/normal/expect.json").unwrap(); + assert_eq!(config, expect_json.trim()); + } + + #[test] + fn test_print_config_ban_rules() { + let args = &[ + "-c", + "fixtures/print_config/ban_rules/eslintrc.json", + "-A", + "all", + "-D", + "eqeqeq", + "--print-config", + ]; + let options = lint_command().run_inner(args).unwrap(); + let ret = LintRunner::new(options).run(); + let CliRunResult::PrintConfigResult { config_file: config } = ret else { + panic!("Expected PrintConfigResult, got {ret:?}") + }; + + let expect_json = + std::fs::read_to_string("fixtures/print_config/ban_rules/expect.json").unwrap(); + assert_eq!(config, expect_json.trim()); + } } diff --git a/apps/oxlint/src/result.rs b/apps/oxlint/src/result.rs index d2dbad34dbb42..5ef69e19c3ee6 100644 --- a/apps/oxlint/src/result.rs +++ b/apps/oxlint/src/result.rs @@ -12,6 +12,7 @@ pub enum CliRunResult { LintResult(LintResult), FormatResult(FormatResult), TypeCheckResult { duration: Duration, number_of_diagnostics: usize }, + PrintConfigResult { config_file: String }, } #[derive(Debug, Default)] @@ -107,6 +108,10 @@ impl Termination for CliRunResult { ExitCode::from(0) } + Self::PrintConfigResult { config_file } => { + println!("{config_file}"); + ExitCode::from(0) + } } } } diff --git a/crates/oxc_linter/src/builder.rs b/crates/oxc_linter/src/builder.rs index 5ef238745c678..89b2669c17ca5 100644 --- a/crates/oxc_linter/src/builder.rs +++ b/crates/oxc_linter/src/builder.rs @@ -3,12 +3,15 @@ use std::{ fmt, }; +use oxc_span::CompactStr; use rustc_hash::FxHashSet; use crate::{ - options::LintPlugins, rules::RULES, AllowWarnDeny, FixKind, FrameworkFlags, LintConfig, - LintFilter, LintFilterKind, LintOptions, Linter, Oxlintrc, RuleCategory, RuleEnum, - RuleWithSeverity, + config::{ESLintRule, OxlintRules}, + options::LintPlugins, + rules::RULES, + AllowWarnDeny, FixKind, FrameworkFlags, LintConfig, LintFilter, LintFilterKind, LintOptions, + Linter, Oxlintrc, RuleCategory, RuleEnum, RuleWithSeverity, }; #[must_use = "You dropped your builder without building a Linter! Did you mean to call .build()?"] @@ -244,6 +247,44 @@ impl LinterBuilder { .map(|rule| RuleWithSeverity { rule: rule.clone(), severity: AllowWarnDeny::Warn }) .collect() } + + /// # Panics + /// This function will panic if the `oxlintrc` is not valid JSON. + pub fn resolve_final_config_file(&self, oxlintrc: Oxlintrc) -> String { + let mut oxlintrc = oxlintrc; + let previous_rules = std::mem::take(&mut oxlintrc.rules); + + let rule_name_to_rule = previous_rules + .into_iter() + .map(|r| (get_name(&r.plugin_name, &r.rule_name), r)) + .collect::>(); + + let new_rules = self + .rules + .iter() + .map(|r: &RuleWithSeverity| { + return ESLintRule { + plugin_name: r.plugin_name().to_string(), + rule_name: r.rule.name().to_string(), + severity: r.severity, + config: rule_name_to_rule + .get(&get_name(r.plugin_name(), r.rule.name())) + .and_then(|r| r.config.clone()), + }; + }) + .collect(); + + oxlintrc.rules = OxlintRules::new(new_rules); + serde_json::to_string_pretty(&oxlintrc).unwrap() + } +} + +fn get_name(plugin_name: &str, rule_name: &str) -> CompactStr { + if plugin_name == "eslint" { + CompactStr::from(rule_name) + } else { + CompactStr::from(format!("{plugin_name}/{rule_name}")) + } } impl From for LinterBuilder { diff --git a/crates/oxc_linter/src/config/globals.rs b/crates/oxc_linter/src/config/globals.rs index 6f9c53ea69ee5..82069d28917cf 100644 --- a/crates/oxc_linter/src/config/globals.rs +++ b/crates/oxc_linter/src/config/globals.rs @@ -29,7 +29,7 @@ use serde::{de::Visitor, Deserialize, Serialize}; /// You may also use `"readable"` or `false` to represent `"readonly"`, and /// `"writeable"` or `true` to represent `"writable"`. // -#[derive(Debug, Default, Deserialize, Serialize, JsonSchema)] +#[derive(Debug, Default, Deserialize, Serialize, JsonSchema, Clone)] pub struct OxlintGlobals(FxHashMap); impl OxlintGlobals { pub fn is_enabled(&self, name: &Q) -> bool diff --git a/crates/oxc_linter/src/config/mod.rs b/crates/oxc_linter/src/config/mod.rs index 7980952d79116..67f86fd1966b6 100644 --- a/crates/oxc_linter/src/config/mod.rs +++ b/crates/oxc_linter/src/config/mod.rs @@ -9,6 +9,8 @@ pub use self::{ env::OxlintEnv, globals::OxlintGlobals, oxlintrc::Oxlintrc, + rules::ESLintRule, + rules::OxlintRules, settings::{jsdoc::JSDocPluginSettings, OxlintSettings}, }; diff --git a/crates/oxc_linter/src/config/oxlintrc.rs b/crates/oxc_linter/src/config/oxlintrc.rs index e9754cebcd7d3..7e161fbcb7603 100644 --- a/crates/oxc_linter/src/config/oxlintrc.rs +++ b/crates/oxc_linter/src/config/oxlintrc.rs @@ -43,7 +43,7 @@ use crate::{options::LintPlugins, utils::read_to_string}; /// } /// } /// ``` -#[derive(Debug, Default, Deserialize, Serialize, JsonSchema)] +#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema)] #[serde(default)] #[non_exhaustive] pub struct Oxlintrc { diff --git a/crates/oxc_linter/src/config/rules.rs b/crates/oxc_linter/src/config/rules.rs index 3c17880247879..ae51d9f885cdc 100644 --- a/crates/oxc_linter/src/config/rules.rs +++ b/crates/oxc_linter/src/config/rules.rs @@ -26,6 +26,12 @@ type RuleSet = FxHashSet; #[cfg_attr(test, derive(PartialEq))] pub struct OxlintRules(Vec); +impl OxlintRules { + pub fn new(rules: Vec) -> Self { + Self(rules) + } +} + #[derive(Debug, Clone)] #[cfg_attr(test, derive(PartialEq))] pub struct ESLintRule { diff --git a/crates/oxc_linter/src/config/settings/jsdoc.rs b/crates/oxc_linter/src/config/settings/jsdoc.rs index e7bf9d284f742..3f8fe24d6e62a 100644 --- a/crates/oxc_linter/src/config/settings/jsdoc.rs +++ b/crates/oxc_linter/src/config/settings/jsdoc.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::utils::default_true; // -#[derive(Debug, Deserialize, Serialize, JsonSchema)] +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] #[cfg_attr(test, derive(PartialEq))] pub struct JSDocPluginSettings { /// For all rules but NOT apply to `check-access` and `empty-tags` rule diff --git a/crates/oxc_linter/src/config/settings/jsx_a11y.rs b/crates/oxc_linter/src/config/settings/jsx_a11y.rs index 11b823ee38957..be81c6d17896d 100644 --- a/crates/oxc_linter/src/config/settings/jsx_a11y.rs +++ b/crates/oxc_linter/src/config/settings/jsx_a11y.rs @@ -4,7 +4,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; // -#[derive(Debug, Deserialize, Default, Serialize, JsonSchema)] +#[derive(Debug, Clone, Deserialize, Default, Serialize, JsonSchema)] #[cfg_attr(test, derive(PartialEq))] pub struct JSXA11yPluginSettings { #[serde(rename = "polymorphicPropName")] diff --git a/crates/oxc_linter/src/config/settings/mod.rs b/crates/oxc_linter/src/config/settings/mod.rs index 4a496bc9b554c..6cc38c0a83ed4 100644 --- a/crates/oxc_linter/src/config/settings/mod.rs +++ b/crates/oxc_linter/src/config/settings/mod.rs @@ -12,7 +12,7 @@ use self::{ }; /// Shared settings for plugins -#[derive(Debug, Deserialize, Serialize, Default, JsonSchema)] +#[derive(Debug, Clone, Deserialize, Serialize, Default, JsonSchema)] #[cfg_attr(test, derive(PartialEq))] pub struct OxlintSettings { #[serde(default)] diff --git a/crates/oxc_linter/src/config/settings/next.rs b/crates/oxc_linter/src/config/settings/next.rs index 4f289641531af..e9bde3d9dcf9b 100644 --- a/crates/oxc_linter/src/config/settings/next.rs +++ b/crates/oxc_linter/src/config/settings/next.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use schemars::JsonSchema; use serde::{Deserialize, Serialize, Serializer}; -#[derive(Debug, Deserialize, Default, Serialize, JsonSchema)] +#[derive(Debug, Clone, Deserialize, Default, Serialize, JsonSchema)] #[cfg_attr(test, derive(PartialEq))] pub struct NextPluginSettings { #[serde(default)] diff --git a/crates/oxc_linter/src/config/settings/react.rs b/crates/oxc_linter/src/config/settings/react.rs index 9cd913dea0fcf..770b71a15add9 100644 --- a/crates/oxc_linter/src/config/settings/react.rs +++ b/crates/oxc_linter/src/config/settings/react.rs @@ -5,7 +5,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; // -#[derive(Debug, Deserialize, Default, Serialize, JsonSchema)] +#[derive(Debug, Clone, Deserialize, Default, Serialize, JsonSchema)] #[cfg_attr(test, derive(PartialEq))] pub struct ReactPluginSettings { #[serde(default)] diff --git a/tasks/website/src/linter/snapshots/cli.snap b/tasks/website/src/linter/snapshots/cli.snap index a039d20ff77ad..3bddf2116085d 100644 --- a/tasks/website/src/linter/snapshots/cli.snap +++ b/tasks/website/src/linter/snapshots/cli.snap @@ -117,6 +117,8 @@ Arguments: Do not display any diagnostics - **` --threads`**=_`INT`_ — Number of threads to use. Set to 1 for using only 1 CPU core +- **` --print-config`** — + This option outputs the configuration to be used. When present, no linting is performed and only config-related options are valid. diff --git a/tasks/website/src/linter/snapshots/cli_terminal.snap b/tasks/website/src/linter/snapshots/cli_terminal.snap index 3700e67c642df..b490bd1f009aa 100644 --- a/tasks/website/src/linter/snapshots/cli_terminal.snap +++ b/tasks/website/src/linter/snapshots/cli_terminal.snap @@ -72,6 +72,8 @@ Output Miscellaneous --silent Do not display any diagnostics --threads=INT Number of threads to use. Set to 1 for using only 1 CPU core + --print-config This option outputs the configuration to be used. When present, no + linting is performed and only config-related options are valid. Available positional items: PATH Single file, single path or list of paths