Skip to content

Commit

Permalink
feat(linter): support --print-config all to print config file for p…
Browse files Browse the repository at this point in the history
…roject (#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.
  • Loading branch information
mysteryven committed Oct 22, 2024
1 parent ffa3945 commit 0acca58
Show file tree
Hide file tree
Showing 18 changed files with 208 additions and 10 deletions.
9 changes: 9 additions & 0 deletions apps/oxlint/fixtures/print_config/ban_rules/eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"rules": {
"no-debugger": "warn",
"eqeqeq": [
"warn",
"always"
]
}
}
44 changes: 44 additions & 0 deletions apps/oxlint/fixtures/print_config/ban_rules/expect.json
Original file line number Diff line number Diff line change
@@ -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": {}
}
37 changes: 37 additions & 0 deletions apps/oxlint/fixtures/print_config/normal/expect.json
Original file line number Diff line number Diff line change
@@ -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": {}
}
5 changes: 5 additions & 0 deletions apps/oxlint/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize>,

/// 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)]
Expand Down
45 changes: 45 additions & 0 deletions apps/oxlint/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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());
}
}
5 changes: 5 additions & 0 deletions apps/oxlint/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -107,6 +108,10 @@ impl Termination for CliRunResult {

ExitCode::from(0)
}
Self::PrintConfigResult { config_file } => {
println!("{config_file}");
ExitCode::from(0)
}
}
}
}
Expand Down
47 changes: 44 additions & 3 deletions crates/oxc_linter/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?"]
Expand Down Expand Up @@ -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::<rustc_hash::FxHashMap<_, _>>();

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<Oxlintrc> for LinterBuilder {
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/config/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"`.
// <https://eslint.org/docs/v8.x/use/configure/language-options#using-configuration-files-1>
#[derive(Debug, Default, Deserialize, Serialize, JsonSchema)]
#[derive(Debug, Default, Deserialize, Serialize, JsonSchema, Clone)]
pub struct OxlintGlobals(FxHashMap<String, GlobalValue>);
impl OxlintGlobals {
pub fn is_enabled<Q>(&self, name: &Q) -> bool
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub use self::{
env::OxlintEnv,
globals::OxlintGlobals,
oxlintrc::Oxlintrc,
rules::ESLintRule,
rules::OxlintRules,
settings::{jsdoc::JSDocPluginSettings, OxlintSettings},
};

Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/config/oxlintrc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions crates/oxc_linter/src/config/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ type RuleSet = FxHashSet<RuleWithSeverity>;
#[cfg_attr(test, derive(PartialEq))]
pub struct OxlintRules(Vec<ESLintRule>);

impl OxlintRules {
pub fn new(rules: Vec<ESLintRule>) -> Self {
Self(rules)
}
}

#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ESLintRule {
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/config/settings/jsdoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use crate::utils::default_true;

// <https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/settings.md>
#[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
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/config/settings/jsx_a11y.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

// <https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#configurations>
#[derive(Debug, Deserialize, Default, Serialize, JsonSchema)]
#[derive(Debug, Clone, Deserialize, Default, Serialize, JsonSchema)]
#[cfg_attr(test, derive(PartialEq))]
pub struct JSXA11yPluginSettings {
#[serde(rename = "polymorphicPropName")]
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/config/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/config/settings/next.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/config/settings/react.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

// <https://github.com/jsx-eslint/eslint-plugin-react#configuration-legacy-eslintrc->
#[derive(Debug, Deserialize, Default, Serialize, JsonSchema)]
#[derive(Debug, Clone, Deserialize, Default, Serialize, JsonSchema)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ReactPluginSettings {
#[serde(default)]
Expand Down
2 changes: 2 additions & 0 deletions tasks/website/src/linter/snapshots/cli.snap
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ Arguments:
Do not display any diagnostics
- **` --threads`**=_`INT`_ &mdash;
Number of threads to use. Set to 1 for using only 1 CPU core
- **` --print-config`** &mdash;
This option outputs the configuration to be used. When present, no linting is performed and only config-related options are valid.



Expand Down
2 changes: 2 additions & 0 deletions tasks/website/src/linter/snapshots/cli_terminal.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 0acca58

Please sign in to comment.