From 023c1607b0ec5dbc067cf110700a0275e64c86d2 Mon Sep 17 00:00:00 2001 From: DonIsaac <22823424+DonIsaac@users.noreply.github.com> Date: Mon, 9 Sep 2024 05:13:19 +0000 Subject: [PATCH] feat(linter): impl `Serialize` for `OxlintConfig` (#5594) Re-creation of #5331 --- crates/oxc_linter/src/config/env.rs | 4 +- crates/oxc_linter/src/config/globals.rs | 6 +- crates/oxc_linter/src/config/mod.rs | 4 +- crates/oxc_linter/src/config/rules.rs | 41 ++++++++- .../oxc_linter/src/config/settings/jsdoc.rs | 6 +- .../src/config/settings/jsx_a11y.rs | 4 +- crates/oxc_linter/src/config/settings/mod.rs | 4 +- crates/oxc_linter/src/config/settings/next.rs | 17 +++- .../oxc_linter/src/config/settings/react.rs | 6 +- .../oxc_linter/src/options/allow_warn_deny.rs | 8 ++ .../oxc_linter/src/snapshots/schema_json.snap | 89 +++++++++++++++++-- npm/oxlint/configuration_schema.json | 89 +++++++++++++++++-- 12 files changed, 246 insertions(+), 32 deletions(-) diff --git a/crates/oxc_linter/src/config/env.rs b/crates/oxc_linter/src/config/env.rs index 02ca9aebe0df7..8649a01590fa8 100644 --- a/crates/oxc_linter/src/config/env.rs +++ b/crates/oxc_linter/src/config/env.rs @@ -2,7 +2,7 @@ use std::{borrow::Borrow, hash::Hash}; use rustc_hash::FxHashMap; use schemars::JsonSchema; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; /// Predefine global variables. /// @@ -10,7 +10,7 @@ use serde::Deserialize; /// 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. -#[derive(Debug, Clone, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] pub struct OxlintEnv(FxHashMap); impl OxlintEnv { diff --git a/crates/oxc_linter/src/config/globals.rs b/crates/oxc_linter/src/config/globals.rs index 419703bb994bb..6f9c53ea69ee5 100644 --- a/crates/oxc_linter/src/config/globals.rs +++ b/crates/oxc_linter/src/config/globals.rs @@ -2,7 +2,7 @@ use std::{borrow, fmt, hash}; use rustc_hash::FxHashMap; use schemars::JsonSchema; -use serde::{de::Visitor, Deserialize}; +use serde::{de::Visitor, Deserialize, Serialize}; /// Add or remove global variables. /// @@ -29,7 +29,7 @@ use serde::{de::Visitor, Deserialize}; /// You may also use `"readable"` or `false` to represent `"readonly"`, and /// `"writeable"` or `true` to represent `"writable"`. // -#[derive(Debug, Default, Deserialize, JsonSchema)] +#[derive(Debug, Default, Deserialize, Serialize, JsonSchema)] pub struct OxlintGlobals(FxHashMap); impl OxlintGlobals { pub fn is_enabled(&self, name: &Q) -> bool @@ -41,7 +41,7 @@ impl OxlintGlobals { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, JsonSchema)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum GlobalValue { Readonly, diff --git a/crates/oxc_linter/src/config/mod.rs b/crates/oxc_linter/src/config/mod.rs index 9e1b7d424cad7..60bbc34052602 100644 --- a/crates/oxc_linter/src/config/mod.rs +++ b/crates/oxc_linter/src/config/mod.rs @@ -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, @@ -53,7 +53,7 @@ use crate::{ /// } /// } /// ``` -#[derive(Debug, Default, Deserialize, JsonSchema)] +#[derive(Debug, Default, Deserialize, Serialize, JsonSchema)] #[serde(default)] pub struct OxlintConfig { /// See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html). diff --git a/crates/oxc_linter/src/config/rules.rs b/crates/oxc_linter/src/config/rules.rs index a46201c4345f5..fc38d238ec6fc 100644 --- a/crates/oxc_linter/src/config/rules.rs +++ b/crates/oxc_linter/src/config/rules.rs @@ -5,7 +5,8 @@ use rustc_hash::FxHashMap; use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; use serde::{ de::{self, Deserializer, Visitor}, - Deserialize, + ser::SerializeMap, + Deserialize, Serialize, }; use crate::{ @@ -58,6 +59,32 @@ impl JsonSchema for OxlintRules { } } +impl Serialize for OxlintRules { + fn serialize(&self, s: S) -> Result + where + S: serde::Serializer, + { + let mut rules = s.serialize_map(Some(self.len()))?; + + for rule in &self.0 { + let key = rule.full_name(); + match rule.config.as_ref() { + // e.g. unicorn/some-rule: ["warn", { foo: "bar" }] + Some(config) if !config.is_null() => { + let value = (rule.severity.as_str(), config); + rules.serialize_entry(&key, &value)?; + } + // e.g. unicorn/some-rule: "warn" + _ => { + rules.serialize_entry(&key, rule.severity.as_str())?; + } + } + } + + rules.end() + } +} + // Manually implement Deserialize because the type is a bit complex... // - Handle single value form and array form // - SeverityConf into AllowWarnDeny @@ -174,6 +201,18 @@ fn failed_to_parse_rule_value(value: &str, err: &str) -> OxcDiagnostic { OxcDiagnostic::error(format!("Failed to rule value {value:?} with error {err:?}")) } +impl ESLintRule { + /// Returns `/` for non-eslint rules. For eslint rules, returns + /// ``. This is effectively the inverse operation for [`parse_rule_key`]. + fn full_name(&self) -> Cow<'_, str> { + if self.plugin_name == "eslint" { + Cow::Borrowed(self.rule_name.as_str()) + } else { + Cow::Owned(format!("{}/{}", self.plugin_name, self.rule_name)) + } + } +} + #[cfg(test)] mod test { use serde::Deserialize; diff --git a/crates/oxc_linter/src/config/settings/jsdoc.rs b/crates/oxc_linter/src/config/settings/jsdoc.rs index 50b0a0314771c..2f581897cb409 100644 --- a/crates/oxc_linter/src/config/settings/jsdoc.rs +++ b/crates/oxc_linter/src/config/settings/jsdoc.rs @@ -2,12 +2,12 @@ use std::borrow::Cow; use rustc_hash::FxHashMap; use schemars::JsonSchema; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use crate::utils::default_true; // -#[derive(Debug, Deserialize, JsonSchema)] +#[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct JSDocPluginSettings { /// For all rules but NOT apply to `check-access` and `empty-tags` rule #[serde(default, rename = "ignorePrivate")] @@ -180,7 +180,7 @@ impl JSDocPluginSettings { } } -#[derive(Clone, Debug, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] #[serde(untagged)] enum TagNamePreference { TagNameOnly(String), diff --git a/crates/oxc_linter/src/config/settings/jsx_a11y.rs b/crates/oxc_linter/src/config/settings/jsx_a11y.rs index 6632311a81dbc..e6c73f7e3f686 100644 --- a/crates/oxc_linter/src/config/settings/jsx_a11y.rs +++ b/crates/oxc_linter/src/config/settings/jsx_a11y.rs @@ -1,10 +1,10 @@ use oxc_span::CompactStr; use rustc_hash::FxHashMap; use schemars::JsonSchema; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; // -#[derive(Debug, Deserialize, Default, JsonSchema)] +#[derive(Debug, Deserialize, Default, Serialize, JsonSchema)] pub struct JSXA11yPluginSettings { #[serde(rename = "polymorphicPropName")] pub polymorphic_prop_name: Option, diff --git a/crates/oxc_linter/src/config/settings/mod.rs b/crates/oxc_linter/src/config/settings/mod.rs index 9d61c49c08852..10bfc1d8fd8eb 100644 --- a/crates/oxc_linter/src/config/settings/mod.rs +++ b/crates/oxc_linter/src/config/settings/mod.rs @@ -4,7 +4,7 @@ mod next; mod react; use schemars::JsonSchema; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use self::{ jsdoc::JSDocPluginSettings, jsx_a11y::JSXA11yPluginSettings, next::NextPluginSettings, @@ -12,7 +12,7 @@ use self::{ }; /// Shared settings for plugins -#[derive(Debug, Deserialize, Default, JsonSchema)] +#[derive(Debug, Deserialize, Serialize, Default, JsonSchema)] pub struct OxlintSettings { #[serde(default)] #[serde(rename = "jsx-a11y")] diff --git a/crates/oxc_linter/src/config/settings/next.rs b/crates/oxc_linter/src/config/settings/next.rs index 1e5421152db9f..8246e3fc14503 100644 --- a/crates/oxc_linter/src/config/settings/next.rs +++ b/crates/oxc_linter/src/config/settings/next.rs @@ -1,9 +1,9 @@ use std::borrow::Cow; use schemars::JsonSchema; -use serde::Deserialize; +use serde::{Deserialize, Serialize, Serializer}; -#[derive(Debug, Deserialize, Default, JsonSchema)] +#[derive(Debug, Deserialize, Default, Serialize, JsonSchema)] pub struct NextPluginSettings { #[serde(default)] #[serde(rename = "rootDir")] @@ -27,8 +27,21 @@ enum OneOrMany { One(T), Many(Vec), } + impl Default for OneOrMany { fn default() -> Self { OneOrMany::Many(Vec::new()) } } + +impl Serialize for OneOrMany { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::One(val) => val.serialize(serializer), + Self::Many(vec) => vec.serialize(serializer), + } + } +} diff --git a/crates/oxc_linter/src/config/settings/react.rs b/crates/oxc_linter/src/config/settings/react.rs index eeed7115f0c07..40b0219bd43e3 100644 --- a/crates/oxc_linter/src/config/settings/react.rs +++ b/crates/oxc_linter/src/config/settings/react.rs @@ -2,10 +2,10 @@ use std::borrow::Cow; use oxc_span::CompactStr; use schemars::JsonSchema; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; // -#[derive(Debug, Deserialize, Default, JsonSchema)] +#[derive(Debug, Deserialize, Default, Serialize, JsonSchema)] pub struct ReactPluginSettings { #[serde(default)] #[serde(rename = "formComponents")] @@ -30,7 +30,7 @@ impl ReactPluginSettings { // Deserialize helper types -#[derive(Clone, Debug, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] #[serde(untagged)] enum CustomComponent { NameOnly(CompactStr), diff --git a/crates/oxc_linter/src/options/allow_warn_deny.rs b/crates/oxc_linter/src/options/allow_warn_deny.rs index 85cc303d366ea..1237fe05f92da 100644 --- a/crates/oxc_linter/src/options/allow_warn_deny.rs +++ b/crates/oxc_linter/src/options/allow_warn_deny.rs @@ -19,6 +19,14 @@ impl AllowWarnDeny { pub fn is_allow(self) -> bool { self == Self::Allow } + + pub fn as_str(self) -> &'static str { + match self { + Self::Allow => "allow", + Self::Warn => "warn", + Self::Deny => "deny", + } + } } impl TryFrom<&str> for AllowWarnDeny { diff --git a/crates/oxc_linter/src/snapshots/schema_json.snap b/crates/oxc_linter/src/snapshots/schema_json.snap index 65195d87165a6..95eb4f592f4ce 100644 --- a/crates/oxc_linter/src/snapshots/schema_json.snap +++ b/crates/oxc_linter/src/snapshots/schema_json.snap @@ -10,6 +10,9 @@ expression: json "properties": { "env": { "description": "Environments enable and disable collections of global variables.", + "default": { + "builtin": true + }, "allOf": [ { "$ref": "#/definitions/OxlintEnv" @@ -18,6 +21,7 @@ expression: json }, "globals": { "description": "Enabled or disabled specific global variables.", + "default": {}, "allOf": [ { "$ref": "#/definitions/OxlintGlobals" @@ -26,6 +30,7 @@ expression: json }, "rules": { "description": "See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html).", + "default": {}, "allOf": [ { "$ref": "#/definitions/OxlintRules" @@ -33,7 +38,34 @@ expression: json ] }, "settings": { - "$ref": "#/definitions/OxlintSettings" + "default": { + "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": {} + } + }, + "allOf": [ + { + "$ref": "#/definitions/OxlintSettings" + } + ] } }, "definitions": { @@ -164,6 +196,7 @@ expression: json "type": "boolean" }, "tagNamePreference": { + "default": {}, "type": "object", "additionalProperties": { "$ref": "#/definitions/TagNamePreference" @@ -193,7 +226,12 @@ expression: json "type": "object", "properties": { "rootDir": { - "$ref": "#/definitions/OneOrMany_for_String" + "default": [], + "allOf": [ + { + "$ref": "#/definitions/OneOrMany_for_String" + } + ] } } }, @@ -232,16 +270,53 @@ expression: json "type": "object", "properties": { "jsdoc": { - "$ref": "#/definitions/JSDocPluginSettings" + "default": { + "ignorePrivate": false, + "ignoreInternal": false, + "ignoreReplacesDocs": true, + "overrideReplacesDocs": true, + "augmentsExtendsReplacesDocs": false, + "implementsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "tagNamePreference": {} + }, + "allOf": [ + { + "$ref": "#/definitions/JSDocPluginSettings" + } + ] }, "jsx-a11y": { - "$ref": "#/definitions/JSXA11yPluginSettings" + "default": { + "polymorphicPropName": null, + "components": {} + }, + "allOf": [ + { + "$ref": "#/definitions/JSXA11yPluginSettings" + } + ] }, "next": { - "$ref": "#/definitions/NextPluginSettings" + "default": { + "rootDir": [] + }, + "allOf": [ + { + "$ref": "#/definitions/NextPluginSettings" + } + ] }, "react": { - "$ref": "#/definitions/ReactPluginSettings" + "default": { + "formComponents": [], + "linkComponents": [] + }, + "allOf": [ + { + "$ref": "#/definitions/ReactPluginSettings" + } + ] } } }, @@ -249,12 +324,14 @@ expression: json "type": "object", "properties": { "formComponents": { + "default": [], "type": "array", "items": { "$ref": "#/definitions/CustomComponent" } }, "linkComponents": { + "default": [], "type": "array", "items": { "$ref": "#/definitions/CustomComponent" diff --git a/npm/oxlint/configuration_schema.json b/npm/oxlint/configuration_schema.json index 0a8386fe277da..c7899de5f4923 100644 --- a/npm/oxlint/configuration_schema.json +++ b/npm/oxlint/configuration_schema.json @@ -6,6 +6,9 @@ "properties": { "env": { "description": "Environments enable and disable collections of global variables.", + "default": { + "builtin": true + }, "allOf": [ { "$ref": "#/definitions/OxlintEnv" @@ -14,6 +17,7 @@ }, "globals": { "description": "Enabled or disabled specific global variables.", + "default": {}, "allOf": [ { "$ref": "#/definitions/OxlintGlobals" @@ -22,6 +26,7 @@ }, "rules": { "description": "See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html).", + "default": {}, "allOf": [ { "$ref": "#/definitions/OxlintRules" @@ -29,7 +34,34 @@ ] }, "settings": { - "$ref": "#/definitions/OxlintSettings" + "default": { + "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": {} + } + }, + "allOf": [ + { + "$ref": "#/definitions/OxlintSettings" + } + ] } }, "definitions": { @@ -160,6 +192,7 @@ "type": "boolean" }, "tagNamePreference": { + "default": {}, "type": "object", "additionalProperties": { "$ref": "#/definitions/TagNamePreference" @@ -189,7 +222,12 @@ "type": "object", "properties": { "rootDir": { - "$ref": "#/definitions/OneOrMany_for_String" + "default": [], + "allOf": [ + { + "$ref": "#/definitions/OneOrMany_for_String" + } + ] } } }, @@ -228,16 +266,53 @@ "type": "object", "properties": { "jsdoc": { - "$ref": "#/definitions/JSDocPluginSettings" + "default": { + "ignorePrivate": false, + "ignoreInternal": false, + "ignoreReplacesDocs": true, + "overrideReplacesDocs": true, + "augmentsExtendsReplacesDocs": false, + "implementsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "tagNamePreference": {} + }, + "allOf": [ + { + "$ref": "#/definitions/JSDocPluginSettings" + } + ] }, "jsx-a11y": { - "$ref": "#/definitions/JSXA11yPluginSettings" + "default": { + "polymorphicPropName": null, + "components": {} + }, + "allOf": [ + { + "$ref": "#/definitions/JSXA11yPluginSettings" + } + ] }, "next": { - "$ref": "#/definitions/NextPluginSettings" + "default": { + "rootDir": [] + }, + "allOf": [ + { + "$ref": "#/definitions/NextPluginSettings" + } + ] }, "react": { - "$ref": "#/definitions/ReactPluginSettings" + "default": { + "formComponents": [], + "linkComponents": [] + }, + "allOf": [ + { + "$ref": "#/definitions/ReactPluginSettings" + } + ] } } }, @@ -245,12 +320,14 @@ "type": "object", "properties": { "formComponents": { + "default": [], "type": "array", "items": { "$ref": "#/definitions/CustomComponent" } }, "linkComponents": { + "default": [], "type": "array", "items": { "$ref": "#/definitions/CustomComponent"