From 055e4079ef5964bb0ed00bb4973ab2269bacb410 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Fri, 27 Sep 2024 13:17:28 -0400 Subject: [PATCH] feat(linter): configure by category in config files > closes #5454 Adds a `categories` property to config files, where each key is a `RuleCategory` and each value is `"allow"/"off"`, `"warn"`, or `"deny"/"error"`. Note that this change won't come into effect until after #6088 is merged. --- crates/oxc_linter/src/builder.rs | 7 +- crates/oxc_linter/src/config/categories.rs | 88 +++++++++++++++++++ crates/oxc_linter/src/config/mod.rs | 1 + crates/oxc_linter/src/config/oxlintrc.rs | 6 +- crates/oxc_linter/src/rule.rs | 12 +++ .../oxc_linter/src/snapshots/schema_json.snap | 41 +++++++++ npm/oxlint/configuration_schema.json | 41 +++++++++ .../src/linter/snapshots/schema_markdown.snap | 57 ++++++++++++ 8 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 crates/oxc_linter/src/config/categories.rs diff --git a/crates/oxc_linter/src/builder.rs b/crates/oxc_linter/src/builder.rs index c20600620ef1d..ea09f9713655f 100644 --- a/crates/oxc_linter/src/builder.rs +++ b/crates/oxc_linter/src/builder.rs @@ -70,7 +70,8 @@ impl LinterBuilder { /// ``` pub fn from_oxlintrc(start_empty: bool, oxlintrc: Oxlintrc) -> Self { // TODO: monorepo config merging, plugin-based extends, etc. - let Oxlintrc { plugins, settings, env, globals, rules: oxlintrc_rules } = oxlintrc; + let Oxlintrc { plugins, settings, env, globals, categories, rules: oxlintrc_rules } = + oxlintrc; let config = LintConfig { settings, env, globals }; let options = LintOptions { plugins, ..Default::default() }; @@ -79,6 +80,10 @@ impl LinterBuilder { let cache = RulesCache::new(options.plugins); let mut builder = Self { rules, options, config, cache }; + if !categories.is_empty() { + builder = builder.with_filters(categories.filters()); + } + { let all_rules = builder.cache.borrow(); oxlintrc_rules.override_rules(&mut builder.rules, all_rules.as_slice()); diff --git a/crates/oxc_linter/src/config/categories.rs b/crates/oxc_linter/src/config/categories.rs new file mode 100644 index 0000000000000..18c80a4cb1c0f --- /dev/null +++ b/crates/oxc_linter/src/config/categories.rs @@ -0,0 +1,88 @@ +use std::{borrow::Cow, ops::Deref}; + +use rustc_hash::FxHashMap; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::{AllowWarnDeny, LintFilter, RuleCategory}; + +/// Configure an entire category of rules all at once. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct OxlintCategories(FxHashMap); + +impl Deref for OxlintCategories { + type Target = FxHashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl OxlintCategories { + #[must_use] + pub fn filters(&self) -> impl Iterator + '_ { + self.iter().map(|(category, severity)| LintFilter::new(*severity, *category).unwrap()) + } +} + +impl JsonSchema for OxlintCategories { + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("OxlintCategories") + } + + fn schema_name() -> String { + "OxlintCategories".to_string() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + let severity = gen.subschema_for::(); + let mut schema = + gen.subschema_for::>().into_object(); + + { + let obj = &mut schema.object(); + let properties = &mut obj.properties; + + obj.additional_properties = None; + + properties.insert(RuleCategory::Correctness.as_str().to_string(), severity.clone()); + properties.insert(RuleCategory::Suspicious.as_str().to_string(), severity.clone()); + properties.insert(RuleCategory::Pedantic.as_str().to_string(), severity.clone()); + properties.insert(RuleCategory::Perf.as_str().to_string(), severity.clone()); + properties.insert(RuleCategory::Style.as_str().to_string(), severity.clone()); + properties.insert(RuleCategory::Restriction.as_str().to_string(), severity.clone()); + properties.insert(RuleCategory::Nursery.as_str().to_string(), severity.clone()); + } + + { + let metadata = &mut schema.metadata(); + metadata.title = Some("Rule Categories".to_string()); + + metadata.description = Some( + r#" +Configure an entire category of rules all at once. + +Rules enabled or disabled this way will be overwritten by individual rules in the `rules` field. + +# Example +```json +{ + "categories": { + "correctness": "warn" + }, + "rules": { + "eslint/no-unused-vars": "error" + } +} +``` +"# + .trim() + .to_string(), + ); + + metadata.examples = vec![serde_json::json!({ "correctness": "warn" })]; + } + + schema.into() + } +} diff --git a/crates/oxc_linter/src/config/mod.rs b/crates/oxc_linter/src/config/mod.rs index 229c31fa8912b..7980952d79116 100644 --- a/crates/oxc_linter/src/config/mod.rs +++ b/crates/oxc_linter/src/config/mod.rs @@ -1,3 +1,4 @@ +mod categories; mod env; mod globals; mod oxlintrc; diff --git a/crates/oxc_linter/src/config/oxlintrc.rs b/crates/oxc_linter/src/config/oxlintrc.rs index 100fb7ca6585b..437ca80fb3497 100644 --- a/crates/oxc_linter/src/config/oxlintrc.rs +++ b/crates/oxc_linter/src/config/oxlintrc.rs @@ -4,7 +4,10 @@ use oxc_diagnostics::OxcDiagnostic; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use super::{env::OxlintEnv, globals::OxlintGlobals, rules::OxlintRules, settings::OxlintSettings}; +use super::{ + categories::OxlintCategories, env::OxlintEnv, globals::OxlintGlobals, rules::OxlintRules, + settings::OxlintSettings, +}; use crate::{options::LintPlugins, utils::read_to_string}; @@ -45,6 +48,7 @@ use crate::{options::LintPlugins, utils::read_to_string}; #[non_exhaustive] pub struct Oxlintrc { pub plugins: LintPlugins, + pub categories: OxlintCategories, /// See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html). pub rules: OxlintRules, pub settings: OxlintSettings, diff --git a/crates/oxc_linter/src/rule.rs b/crates/oxc_linter/src/rule.rs index abd2b0b23a9c2..27f1db5285b38 100644 --- a/crates/oxc_linter/src/rule.rs +++ b/crates/oxc_linter/src/rule.rs @@ -100,6 +100,18 @@ impl RuleCategory { Self::Nursery => "New lints that are still under development.", } } + + pub fn as_str(self) -> &'static str { + match self { + Self::Correctness => "correctness", + Self::Suspicious => "suspicious", + Self::Pedantic => "pedantic", + Self::Perf => "perf", + Self::Style => "style", + Self::Restriction => "restriction", + Self::Nursery => "nursery", + } + } } impl TryFrom<&str> for RuleCategory { diff --git a/crates/oxc_linter/src/snapshots/schema_json.snap b/crates/oxc_linter/src/snapshots/schema_json.snap index 792e860337bad..d2cc90856ced8 100644 --- a/crates/oxc_linter/src/snapshots/schema_json.snap +++ b/crates/oxc_linter/src/snapshots/schema_json.snap @@ -8,6 +8,14 @@ expression: json "description": "Oxlint Configuration File\n\nThis configuration is aligned with ESLint v8's configuration schema (`eslintrc.json`).\n\nUsage: `oxlint -c oxlintrc.json --import-plugin`\n\n::: danger NOTE\n\nOnly the `.json` format is supported. You can use comments in configuration files.\n\n:::\n\nExample\n\n`.oxlintrc.json`\n\n```json { \"env\": { \"browser\": true }, \"globals\": { \"foo\": \"readonly\" }, \"settings\": { }, \"rules\": { \"eqeqeq\": \"warn\", \"import/no-cycle\": \"error\" } } ```", "type": "object", "properties": { + "categories": { + "default": {}, + "allOf": [ + { + "$ref": "#/definitions/OxlintCategories" + } + ] + }, "env": { "description": "Environments enable and disable collections of global variables.", "default": { @@ -267,6 +275,39 @@ expression: json } ] }, + "OxlintCategories": { + "title": "Rule Categories", + "description": "Configure an entire category of rules all at once.\n\nRules enabled or disabled this way will be overwritten by individual rules in the `rules` field.\n\n# Example\n```json\n{\n \"categories\": {\n \"correctness\": \"warn\"\n },\n \"rules\": {\n \"eslint/no-unused-vars\": \"error\"\n }\n}\n```", + "examples": [ + { + "correctness": "warn" + } + ], + "type": "object", + "properties": { + "correctness": { + "$ref": "#/definitions/AllowWarnDeny" + }, + "nursery": { + "$ref": "#/definitions/AllowWarnDeny" + }, + "pedantic": { + "$ref": "#/definitions/AllowWarnDeny" + }, + "perf": { + "$ref": "#/definitions/AllowWarnDeny" + }, + "restriction": { + "$ref": "#/definitions/AllowWarnDeny" + }, + "style": { + "$ref": "#/definitions/AllowWarnDeny" + }, + "suspicious": { + "$ref": "#/definitions/AllowWarnDeny" + } + } + }, "OxlintEnv": { "description": "Predefine global variables.\n\nEnvironments specify what global variables are predefined. See [ESLint's list of environments](https://eslint.org/docs/v8.x/use/configure/language-options#specifying-environments) for what environments are available and what each one provides.", "type": "object", diff --git a/npm/oxlint/configuration_schema.json b/npm/oxlint/configuration_schema.json index 23c32c74fa96a..c921205af3d17 100644 --- a/npm/oxlint/configuration_schema.json +++ b/npm/oxlint/configuration_schema.json @@ -4,6 +4,14 @@ "description": "Oxlint Configuration File\n\nThis configuration is aligned with ESLint v8's configuration schema (`eslintrc.json`).\n\nUsage: `oxlint -c oxlintrc.json --import-plugin`\n\n::: danger NOTE\n\nOnly the `.json` format is supported. You can use comments in configuration files.\n\n:::\n\nExample\n\n`.oxlintrc.json`\n\n```json { \"env\": { \"browser\": true }, \"globals\": { \"foo\": \"readonly\" }, \"settings\": { }, \"rules\": { \"eqeqeq\": \"warn\", \"import/no-cycle\": \"error\" } } ```", "type": "object", "properties": { + "categories": { + "default": {}, + "allOf": [ + { + "$ref": "#/definitions/OxlintCategories" + } + ] + }, "env": { "description": "Environments enable and disable collections of global variables.", "default": { @@ -263,6 +271,39 @@ } ] }, + "OxlintCategories": { + "title": "Rule Categories", + "description": "Configure an entire category of rules all at once.\n\nRules enabled or disabled this way will be overwritten by individual rules in the `rules` field.\n\n# Example\n```json\n{\n \"categories\": {\n \"correctness\": \"warn\"\n },\n \"rules\": {\n \"eslint/no-unused-vars\": \"error\"\n }\n}\n```", + "examples": [ + { + "correctness": "warn" + } + ], + "type": "object", + "properties": { + "correctness": { + "$ref": "#/definitions/AllowWarnDeny" + }, + "nursery": { + "$ref": "#/definitions/AllowWarnDeny" + }, + "pedantic": { + "$ref": "#/definitions/AllowWarnDeny" + }, + "perf": { + "$ref": "#/definitions/AllowWarnDeny" + }, + "restriction": { + "$ref": "#/definitions/AllowWarnDeny" + }, + "style": { + "$ref": "#/definitions/AllowWarnDeny" + }, + "suspicious": { + "$ref": "#/definitions/AllowWarnDeny" + } + } + }, "OxlintEnv": { "description": "Predefine global variables.\n\nEnvironments specify what global variables are predefined. See [ESLint's list of environments](https://eslint.org/docs/v8.x/use/configure/language-options#specifying-environments) for what environments are available and what each one provides.", "type": "object", diff --git a/tasks/website/src/linter/snapshots/schema_markdown.snap b/tasks/website/src/linter/snapshots/schema_markdown.snap index f6a6e801867c3..d619b3aabceb6 100644 --- a/tasks/website/src/linter/snapshots/schema_markdown.snap +++ b/tasks/website/src/linter/snapshots/schema_markdown.snap @@ -35,6 +35,63 @@ Example ``` +## categories + +type: `object` + + + + +### categories.correctness + + + + + + +### categories.nursery + + + + + + +### categories.pedantic + + + + + + +### categories.perf + + + + + + +### categories.restriction + + + + + + +### categories.style + + + + + + +### categories.suspicious + + + + + + + ## env type: `object`