Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(linter): support --print-config to print config file for project #6579

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading