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(oxlint): implement --print-config cli option #4742

Closed
wants to merge 11 commits into from
4 changes: 4 additions & 0 deletions apps/oxlint/src/command/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ pub struct LintCommand {
#[bpaf(long("rules"), switch, hide_usage)]
pub list_rules: bool,

/// print a generated configuration file
#[bpaf(long("print-config"), switch, hide_usage)]
pub print_config: bool,

#[bpaf(external)]
pub misc_options: MiscOptions,

Expand Down
23 changes: 23 additions & 0 deletions apps/oxlint/src/lint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ impl Runner for LintRunner {
enable_plugins,
output_options,
misc_options,
print_config,
..
} = self.options;

Expand Down Expand Up @@ -136,6 +137,11 @@ impl Runner for LintRunner {
let mut diagnostic_service =
Self::get_diagnostic_service(&warning_options, &output_options, &misc_options);

if print_config {
let mut stdout = BufWriter::new(std::io::stdout());
lint_service.linter().print_config(&mut stdout);
return CliRunResult::None;
}
// Spawn linting in another thread so diagnostics can be printed immediately from diagnostic_service.run.
rayon::spawn({
let tx_error = diagnostic_service.sender().clone();
Expand Down Expand Up @@ -512,4 +518,21 @@ mod test {
assert_eq!(result.number_of_files, 1);
assert_eq!(result.number_of_errors, 1);
}

#[test]
fn test_print_config() {
let args = &[
"-W",
"correctness",
"-A",
"no-debugger",
"fixtures/linter/debugger.js",
"--print-config",
];
let options = lint_command().run_inner(args).unwrap();
match LintRunner::new(options).run() {
CliRunResult::None => {}
other => panic!("{other:?}"),
}
}
}
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/config/env.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use rustc_hash::FxHashMap;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use std::{borrow::Borrow, hash::Hash};

/// Predefine global variables.
// TODO: list the keys we support
// <https://eslint.org/docs/v8.x/use/configure/language-options#specifying-environments>
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[derive(Debug, Clone, Deserialize, JsonSchema, Serialize)]
pub struct OxlintEnv(FxHashMap<String, bool>);

impl OxlintEnv {
Expand Down
6 changes: 3 additions & 3 deletions crates/oxc_linter/src/config/globals.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use rustc_hash::FxHashMap;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::{Deserialize, Serialize};

/// Add or remove global variables.
// <https://eslint.org/docs/v8.x/use/configure/language-options#using-configuration-files-1>
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[derive(Debug, Default, Deserialize, JsonSchema, Serialize)]
pub struct OxlintGlobals(FxHashMap<String, GlobalValue>);

// TODO: support deprecated `false`
#[derive(Debug, Eq, PartialEq, Deserialize, JsonSchema)]
#[derive(Debug, Eq, PartialEq, Deserialize, JsonSchema, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum GlobalValue {
Readonly,
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::path::Path;
use oxc_diagnostics::OxcDiagnostic;
use rustc_hash::FxHashSet;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::{Deserialize, Serialize};

pub use self::{
env::OxlintEnv,
Expand Down Expand Up @@ -51,7 +51,7 @@ use crate::{
/// }
/// }
/// ```
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[derive(Debug, Default, Deserialize, JsonSchema, Serialize)]
#[serde(default)]
pub struct OxlintConfig {
/// See [Oxlint Rules](./rules)
Expand Down
41 changes: 39 additions & 2 deletions crates/oxc_linter/src/config/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use rustc_hash::FxHashMap;
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
use serde::{
de::{self, Deserializer, Visitor},
Deserialize,
Deserialize, Serialize,
};

use crate::AllowWarnDeny;
use crate::{AllowWarnDeny, RuleWithSeverity};

// TS type is `Record<string, RuleConf>`
// - type SeverityConf = 0 | 1 | 2 | "off" | "warn" | "error";
Expand All @@ -25,6 +25,21 @@ pub struct ESLintRule {
pub config: Option<serde_json::Value>,
}

impl OxlintRules {
pub fn from_rules(rules: Vec<RuleWithSeverity>) -> Self {
let rules = rules
.into_iter()
.map(|rule| ESLintRule {
plugin_name: rule.plugin_name().to_string(),
rule_name: rule.name().to_string(),
severity: rule.severity,
config: None,
})
.collect();
Self(rules)
}
}

impl JsonSchema for OxlintRules {
fn schema_name() -> String {
"OxlintRules".to_owned()
Expand Down Expand Up @@ -83,6 +98,28 @@ impl<'de> Deserialize<'de> for OxlintRules {
}
}

impl Serialize for OxlintRules {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let rules = self.0.iter().map(|rule| {
let key = format!("{}/{}", rule.plugin_name, rule.rule_name);
let value = match &rule.config {
Some(config) => serde_json::Value::Array(vec![
serde_json::to_value(rule.severity).unwrap(),
config.clone(),
]),
None => serde_json::to_value(rule.severity).unwrap(),
};
(key, value)
});

let map = rules.collect::<FxHashMap<_, _>>();
map.serialize(serializer)
}
}

fn parse_rule_key(name: &str) -> (String, String) {
let Some((plugin_name, rule_name)) = name.split_once('/') else {
return ("eslint".to_string(), name.to_string());
Expand Down
6 changes: 3 additions & 3 deletions crates/oxc_linter/src/config/settings/jsdoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use std::borrow::Cow;

use rustc_hash::FxHashMap;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::{Deserialize, Serialize};

// <https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/settings.md>
#[derive(Debug, Deserialize, JsonSchema)]
#[derive(Debug, Deserialize, JsonSchema, Serialize)]
pub struct JSDocPluginSettings {
/// For all rules but NOT apply to `check-access` and `empty-tags` rule
#[serde(default, rename = "ignorePrivate")]
Expand Down Expand Up @@ -184,7 +184,7 @@ fn default_true() -> bool {
true
}

#[derive(Clone, Debug, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)]
#[serde(untagged)]
enum TagNamePreference {
TagNameOnly(String),
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/config/settings/jsx_a11y.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use oxc_span::CompactStr;
use rustc_hash::FxHashMap;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::{Deserialize, Serialize};

// <https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#configurations>
#[derive(Debug, Deserialize, Default, JsonSchema)]
#[derive(Debug, Deserialize, Default, JsonSchema, Serialize)]
pub struct JSXA11yPluginSettings {
#[serde(rename = "polymorphicPropName")]
pub polymorphic_prop_name: Option<CompactStr>,
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/config/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ mod next;
mod react;

use schemars::JsonSchema;
use serde::Deserialize;
use serde::{Deserialize, Serialize};

use self::{
jsdoc::JSDocPluginSettings, jsx_a11y::JSXA11yPluginSettings, next::NextPluginSettings,
react::ReactPluginSettings,
};

/// Shared settings for plugins
#[derive(Debug, Deserialize, Default, JsonSchema)]
#[derive(Debug, Deserialize, Default, JsonSchema, Serialize)]
pub struct OxlintSettings {
#[serde(default)]
#[serde(rename = "jsx-a11y")]
Expand Down
6 changes: 3 additions & 3 deletions crates/oxc_linter/src/config/settings/next.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::borrow::Cow;

use schemars::JsonSchema;
use serde::Deserialize;
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Default, JsonSchema)]
#[derive(Debug, Deserialize, Default, JsonSchema, Serialize)]
pub struct NextPluginSettings {
#[serde(default)]
#[serde(rename = "rootDir")]
Expand All @@ -21,7 +21,7 @@ impl NextPluginSettings {

// Deserialize helper types

#[derive(Clone, Debug, Deserialize, PartialEq, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, JsonSchema, Serialize)]
#[serde(untagged)]
enum OneOrMany<T> {
One(T),
Expand Down
6 changes: 3 additions & 3 deletions crates/oxc_linter/src/config/settings/react.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use std::borrow::Cow;

use oxc_span::CompactStr;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::{Deserialize, Serialize};

// <https://github.com/jsx-eslint/eslint-plugin-react#configuration-legacy-eslintrc->
#[derive(Debug, Deserialize, Default, JsonSchema)]
#[derive(Debug, Deserialize, Default, JsonSchema, Serialize)]
pub struct ReactPluginSettings {
#[serde(default)]
#[serde(rename = "formComponents")]
Expand All @@ -30,7 +30,7 @@ impl ReactPluginSettings {

// Deserialize helper types

#[derive(Clone, Debug, Deserialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)]
#[serde(untagged)]
enum CustomComponent {
NameOnly(CompactStr),
Expand Down
20 changes: 19 additions & 1 deletion crates/oxc_linter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use oxc_diagnostics::Error;
use oxc_semantic::{AstNode, Semantic};

pub use crate::{
config::OxlintConfig,
config::{OxlintConfig, OxlintRules},
context::LintContext,
fixer::FixKind,
frameworks::FrameworkFlags,
Expand Down Expand Up @@ -142,6 +142,24 @@ impl Linter {
rules.into_iter().flat_map(|(_, ctx)| ctx.into_message()).collect::<Vec<_>>()
}

/// # Panics
pub fn print_config<W: Write>(&self, writer: &mut W) {
// merge rules with eslint_config
let additional_rules = OxlintRules::from_rules(self.rules.clone());
let rules = serde_json::to_value(additional_rules).unwrap();
let mut config = serde_json::to_value(self.eslint_config.as_ref()).unwrap();
config
.get_mut("rules")
.unwrap()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use expect and provide an error message, otherwise users will have no idea what happened.

.as_object_mut()
.unwrap()
.extend(rules.as_object().unwrap().clone());

let json = serde_json::to_string_pretty(&config).unwrap();
// serialize the config to json and print it
writeln!(writer, "{json}").unwrap();
}

/// # Panics
pub fn print_rules<W: Write>(writer: &mut W) {
let table = RuleTable::new();
Expand Down
6 changes: 4 additions & 2 deletions crates/oxc_linter/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{convert::From, path::PathBuf};
use oxc_diagnostics::{Error, OxcDiagnostic, Severity};
use rustc_hash::FxHashSet;
use schemars::{schema::SchemaObject, JsonSchema};
use serde::Serialize;
use serde_json::{Number, Value};

use crate::{
Expand Down Expand Up @@ -165,7 +166,8 @@ impl LintOptions {
}
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum AllowWarnDeny {
Allow, // Off
Warn, // Warn
Expand Down Expand Up @@ -251,7 +253,7 @@ impl JsonSchema for AllowWarnDeny {
int_schema.number().maximum = Some(2.0);
int_schema.metadata().description = Some(
"Oxlint rule.

- 0: Turn off the rule.
- 1: Turn the rule on as a warning (doesn't affect exit code).
- 2: Turn the rule on as an error (will exit with a failure code)."
Expand Down
Loading