feat(linter): impl Serialize for OxlintConfig (#5594)

Re-creation of #5331
This commit is contained in:
DonIsaac 2024-09-09 05:13:19 +00:00
parent 28aad281b6
commit 023c1607b0
12 changed files with 246 additions and 32 deletions

View file

@ -2,7 +2,7 @@ use std::{borrow::Borrow, hash::Hash};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::{Deserialize, Serialize};
/// Predefine global variables. /// Predefine global variables.
/// ///
@ -10,7 +10,7 @@ use serde::Deserialize;
/// list of /// list of
/// environments](https://eslint.org/docs/v8.x/use/configure/language-options#specifying-environments) /// environments](https://eslint.org/docs/v8.x/use/configure/language-options#specifying-environments)
/// for what environments are available and what each one provides. /// 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<String, bool>); pub struct OxlintEnv(FxHashMap<String, bool>);
impl OxlintEnv { impl OxlintEnv {

View file

@ -2,7 +2,7 @@ use std::{borrow, fmt, hash};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{de::Visitor, Deserialize}; use serde::{de::Visitor, Deserialize, Serialize};
/// Add or remove global variables. /// 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 /// You may also use `"readable"` or `false` to represent `"readonly"`, and
/// `"writeable"` or `true` to represent `"writable"`. /// `"writeable"` or `true` to represent `"writable"`.
// <https://eslint.org/docs/v8.x/use/configure/language-options#using-configuration-files-1> // <https://eslint.org/docs/v8.x/use/configure/language-options#using-configuration-files-1>
#[derive(Debug, Default, Deserialize, JsonSchema)] #[derive(Debug, Default, Deserialize, Serialize, JsonSchema)]
pub struct OxlintGlobals(FxHashMap<String, GlobalValue>); pub struct OxlintGlobals(FxHashMap<String, GlobalValue>);
impl OxlintGlobals { impl OxlintGlobals {
pub fn is_enabled<Q>(&self, name: &Q) -> bool pub fn is_enabled<Q>(&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")] #[serde(rename_all = "lowercase")]
pub enum GlobalValue { pub enum GlobalValue {
Readonly, Readonly,

View file

@ -8,7 +8,7 @@ use std::path::Path;
use oxc_diagnostics::OxcDiagnostic; use oxc_diagnostics::OxcDiagnostic;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::{Deserialize, Serialize};
pub use self::{ pub use self::{
env::OxlintEnv, env::OxlintEnv,
@ -53,7 +53,7 @@ use crate::{
/// } /// }
/// } /// }
/// ``` /// ```
#[derive(Debug, Default, Deserialize, JsonSchema)] #[derive(Debug, Default, Deserialize, Serialize, JsonSchema)]
#[serde(default)] #[serde(default)]
pub struct OxlintConfig { pub struct OxlintConfig {
/// See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html). /// See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html).

View file

@ -5,7 +5,8 @@ use rustc_hash::FxHashMap;
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
use serde::{ use serde::{
de::{self, Deserializer, Visitor}, de::{self, Deserializer, Visitor},
Deserialize, ser::SerializeMap,
Deserialize, Serialize,
}; };
use crate::{ use crate::{
@ -58,6 +59,32 @@ impl JsonSchema for OxlintRules {
} }
} }
impl Serialize for OxlintRules {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
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... // Manually implement Deserialize because the type is a bit complex...
// - Handle single value form and array form // - Handle single value form and array form
// - SeverityConf into AllowWarnDeny // - 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:?}")) OxcDiagnostic::error(format!("Failed to rule value {value:?} with error {err:?}"))
} }
impl ESLintRule {
/// Returns `<plugin_name>/<rule_name>` for non-eslint rules. For eslint rules, returns
/// `<rule_name>`. 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)] #[cfg(test)]
mod test { mod test {
use serde::Deserialize; use serde::Deserialize;

View file

@ -2,12 +2,12 @@ use std::borrow::Cow;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use crate::utils::default_true; use crate::utils::default_true;
// <https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/settings.md> // <https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/settings.md>
#[derive(Debug, Deserialize, JsonSchema)] #[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct JSDocPluginSettings { pub struct JSDocPluginSettings {
/// For all rules but NOT apply to `check-access` and `empty-tags` rule /// For all rules but NOT apply to `check-access` and `empty-tags` rule
#[serde(default, rename = "ignorePrivate")] #[serde(default, rename = "ignorePrivate")]
@ -180,7 +180,7 @@ impl JSDocPluginSettings {
} }
} }
#[derive(Clone, Debug, Deserialize, JsonSchema)] #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
#[serde(untagged)] #[serde(untagged)]
enum TagNamePreference { enum TagNamePreference {
TagNameOnly(String), TagNameOnly(String),

View file

@ -1,10 +1,10 @@
use oxc_span::CompactStr; use oxc_span::CompactStr;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::{Deserialize, Serialize};
// <https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#configurations> // <https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#configurations>
#[derive(Debug, Deserialize, Default, JsonSchema)] #[derive(Debug, Deserialize, Default, Serialize, JsonSchema)]
pub struct JSXA11yPluginSettings { pub struct JSXA11yPluginSettings {
#[serde(rename = "polymorphicPropName")] #[serde(rename = "polymorphicPropName")]
pub polymorphic_prop_name: Option<CompactStr>, pub polymorphic_prop_name: Option<CompactStr>,

View file

@ -4,7 +4,7 @@ mod next;
mod react; mod react;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use self::{ use self::{
jsdoc::JSDocPluginSettings, jsx_a11y::JSXA11yPluginSettings, next::NextPluginSettings, jsdoc::JSDocPluginSettings, jsx_a11y::JSXA11yPluginSettings, next::NextPluginSettings,
@ -12,7 +12,7 @@ use self::{
}; };
/// Shared settings for plugins /// Shared settings for plugins
#[derive(Debug, Deserialize, Default, JsonSchema)] #[derive(Debug, Deserialize, Serialize, Default, JsonSchema)]
pub struct OxlintSettings { pub struct OxlintSettings {
#[serde(default)] #[serde(default)]
#[serde(rename = "jsx-a11y")] #[serde(rename = "jsx-a11y")]

View file

@ -1,9 +1,9 @@
use std::borrow::Cow; use std::borrow::Cow;
use schemars::JsonSchema; 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 { pub struct NextPluginSettings {
#[serde(default)] #[serde(default)]
#[serde(rename = "rootDir")] #[serde(rename = "rootDir")]
@ -27,8 +27,21 @@ enum OneOrMany<T> {
One(T), One(T),
Many(Vec<T>), Many(Vec<T>),
} }
impl<T> Default for OneOrMany<T> { impl<T> Default for OneOrMany<T> {
fn default() -> Self { fn default() -> Self {
OneOrMany::Many(Vec::new()) OneOrMany::Many(Vec::new())
} }
} }
impl<T: Serialize> Serialize for OneOrMany<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::One(val) => val.serialize(serializer),
Self::Many(vec) => vec.serialize(serializer),
}
}
}

View file

@ -2,10 +2,10 @@ use std::borrow::Cow;
use oxc_span::CompactStr; use oxc_span::CompactStr;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::{Deserialize, Serialize};
// <https://github.com/jsx-eslint/eslint-plugin-react#configuration-legacy-eslintrc-> // <https://github.com/jsx-eslint/eslint-plugin-react#configuration-legacy-eslintrc->
#[derive(Debug, Deserialize, Default, JsonSchema)] #[derive(Debug, Deserialize, Default, Serialize, JsonSchema)]
pub struct ReactPluginSettings { pub struct ReactPluginSettings {
#[serde(default)] #[serde(default)]
#[serde(rename = "formComponents")] #[serde(rename = "formComponents")]
@ -30,7 +30,7 @@ impl ReactPluginSettings {
// Deserialize helper types // Deserialize helper types
#[derive(Clone, Debug, Deserialize, JsonSchema)] #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
#[serde(untagged)] #[serde(untagged)]
enum CustomComponent { enum CustomComponent {
NameOnly(CompactStr), NameOnly(CompactStr),

View file

@ -19,6 +19,14 @@ impl AllowWarnDeny {
pub fn is_allow(self) -> bool { pub fn is_allow(self) -> bool {
self == Self::Allow 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 { impl TryFrom<&str> for AllowWarnDeny {

View file

@ -10,6 +10,9 @@ expression: json
"properties": { "properties": {
"env": { "env": {
"description": "Environments enable and disable collections of global variables.", "description": "Environments enable and disable collections of global variables.",
"default": {
"builtin": true
},
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/OxlintEnv" "$ref": "#/definitions/OxlintEnv"
@ -18,6 +21,7 @@ expression: json
}, },
"globals": { "globals": {
"description": "Enabled or disabled specific global variables.", "description": "Enabled or disabled specific global variables.",
"default": {},
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/OxlintGlobals" "$ref": "#/definitions/OxlintGlobals"
@ -26,6 +30,7 @@ expression: json
}, },
"rules": { "rules": {
"description": "See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html).", "description": "See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html).",
"default": {},
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/OxlintRules" "$ref": "#/definitions/OxlintRules"
@ -33,8 +38,35 @@ expression: json
] ]
}, },
"settings": { "settings": {
"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" "$ref": "#/definitions/OxlintSettings"
} }
]
}
}, },
"definitions": { "definitions": {
"AllowWarnDeny": { "AllowWarnDeny": {
@ -164,6 +196,7 @@ expression: json
"type": "boolean" "type": "boolean"
}, },
"tagNamePreference": { "tagNamePreference": {
"default": {},
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
"$ref": "#/definitions/TagNamePreference" "$ref": "#/definitions/TagNamePreference"
@ -193,8 +226,13 @@ expression: json
"type": "object", "type": "object",
"properties": { "properties": {
"rootDir": { "rootDir": {
"default": [],
"allOf": [
{
"$ref": "#/definitions/OneOrMany_for_String" "$ref": "#/definitions/OneOrMany_for_String"
} }
]
}
} }
}, },
"OneOrMany_for_String": { "OneOrMany_for_String": {
@ -232,29 +270,68 @@ expression: json
"type": "object", "type": "object",
"properties": { "properties": {
"jsdoc": { "jsdoc": {
"default": {
"ignorePrivate": false,
"ignoreInternal": false,
"ignoreReplacesDocs": true,
"overrideReplacesDocs": true,
"augmentsExtendsReplacesDocs": false,
"implementsReplacesDocs": false,
"exemptDestructuredRootsFromChecks": false,
"tagNamePreference": {}
},
"allOf": [
{
"$ref": "#/definitions/JSDocPluginSettings" "$ref": "#/definitions/JSDocPluginSettings"
}
]
}, },
"jsx-a11y": { "jsx-a11y": {
"default": {
"polymorphicPropName": null,
"components": {}
},
"allOf": [
{
"$ref": "#/definitions/JSXA11yPluginSettings" "$ref": "#/definitions/JSXA11yPluginSettings"
}
]
}, },
"next": { "next": {
"default": {
"rootDir": []
},
"allOf": [
{
"$ref": "#/definitions/NextPluginSettings" "$ref": "#/definitions/NextPluginSettings"
}
]
}, },
"react": { "react": {
"default": {
"formComponents": [],
"linkComponents": []
},
"allOf": [
{
"$ref": "#/definitions/ReactPluginSettings" "$ref": "#/definitions/ReactPluginSettings"
} }
]
}
} }
}, },
"ReactPluginSettings": { "ReactPluginSettings": {
"type": "object", "type": "object",
"properties": { "properties": {
"formComponents": { "formComponents": {
"default": [],
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/CustomComponent" "$ref": "#/definitions/CustomComponent"
} }
}, },
"linkComponents": { "linkComponents": {
"default": [],
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/CustomComponent" "$ref": "#/definitions/CustomComponent"

View file

@ -6,6 +6,9 @@
"properties": { "properties": {
"env": { "env": {
"description": "Environments enable and disable collections of global variables.", "description": "Environments enable and disable collections of global variables.",
"default": {
"builtin": true
},
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/OxlintEnv" "$ref": "#/definitions/OxlintEnv"
@ -14,6 +17,7 @@
}, },
"globals": { "globals": {
"description": "Enabled or disabled specific global variables.", "description": "Enabled or disabled specific global variables.",
"default": {},
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/OxlintGlobals" "$ref": "#/definitions/OxlintGlobals"
@ -22,6 +26,7 @@
}, },
"rules": { "rules": {
"description": "See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html).", "description": "See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html).",
"default": {},
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/OxlintRules" "$ref": "#/definitions/OxlintRules"
@ -29,8 +34,35 @@
] ]
}, },
"settings": { "settings": {
"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" "$ref": "#/definitions/OxlintSettings"
} }
]
}
}, },
"definitions": { "definitions": {
"AllowWarnDeny": { "AllowWarnDeny": {
@ -160,6 +192,7 @@
"type": "boolean" "type": "boolean"
}, },
"tagNamePreference": { "tagNamePreference": {
"default": {},
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
"$ref": "#/definitions/TagNamePreference" "$ref": "#/definitions/TagNamePreference"
@ -189,8 +222,13 @@
"type": "object", "type": "object",
"properties": { "properties": {
"rootDir": { "rootDir": {
"default": [],
"allOf": [
{
"$ref": "#/definitions/OneOrMany_for_String" "$ref": "#/definitions/OneOrMany_for_String"
} }
]
}
} }
}, },
"OneOrMany_for_String": { "OneOrMany_for_String": {
@ -228,29 +266,68 @@
"type": "object", "type": "object",
"properties": { "properties": {
"jsdoc": { "jsdoc": {
"default": {
"ignorePrivate": false,
"ignoreInternal": false,
"ignoreReplacesDocs": true,
"overrideReplacesDocs": true,
"augmentsExtendsReplacesDocs": false,
"implementsReplacesDocs": false,
"exemptDestructuredRootsFromChecks": false,
"tagNamePreference": {}
},
"allOf": [
{
"$ref": "#/definitions/JSDocPluginSettings" "$ref": "#/definitions/JSDocPluginSettings"
}
]
}, },
"jsx-a11y": { "jsx-a11y": {
"default": {
"polymorphicPropName": null,
"components": {}
},
"allOf": [
{
"$ref": "#/definitions/JSXA11yPluginSettings" "$ref": "#/definitions/JSXA11yPluginSettings"
}
]
}, },
"next": { "next": {
"default": {
"rootDir": []
},
"allOf": [
{
"$ref": "#/definitions/NextPluginSettings" "$ref": "#/definitions/NextPluginSettings"
}
]
}, },
"react": { "react": {
"default": {
"formComponents": [],
"linkComponents": []
},
"allOf": [
{
"$ref": "#/definitions/ReactPluginSettings" "$ref": "#/definitions/ReactPluginSettings"
} }
]
}
} }
}, },
"ReactPluginSettings": { "ReactPluginSettings": {
"type": "object", "type": "object",
"properties": { "properties": {
"formComponents": { "formComponents": {
"default": [],
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/CustomComponent" "$ref": "#/definitions/CustomComponent"
} }
}, },
"linkComponents": { "linkComponents": {
"default": [],
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/CustomComponent" "$ref": "#/definitions/CustomComponent"