diff --git a/Cargo.lock b/Cargo.lock index 5f5065c56..2257df578 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -435,6 +435,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "either" version = "1.11.0" @@ -1437,6 +1443,7 @@ dependencies = [ "regex", "rust-lapper", "rustc-hash", + "schemars", "serde", "serde_json", "static_assertions", @@ -2171,6 +2178,31 @@ dependencies = [ "hashlink", ] +[[package]] +name = "schemars" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0218ceea14babe24a4a5836f86ade86c1effbc198164e619194cb5069187e29" +dependencies = [ + "dyn-clone", + "indexmap", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed5a1ccce8ff962e31a165d41f6e2a2dd1245099dc4d594f5574a86cd90f4d3" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals 0.29.1", + "syn", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2231,6 +2263,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_json" version = "1.0.117" @@ -2699,7 +2742,7 @@ checksum = "7a94b0f0954b3e59bfc2c246b4c8574390d94a4ad4ad246aaf2fb07d7dfd3b47" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals", + "serde_derive_internals 0.28.0", "syn", ] diff --git a/Cargo.toml b/Cargo.toml index b6929d942..eb13f2d34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,6 +173,7 @@ unicode-width = "0.1.12" saphyr = "0.0.1" base64-simd = "0.8" cfg-if = "1.0.0" +schemars = "0.8.20" [workspace.metadata.cargo-shear] ignored = ["napi", "oxc_traverse"] diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index ba09ec296..79e9ef862 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -30,26 +30,25 @@ oxc_syntax = { workspace = true } oxc_codegen = { workspace = true } oxc_resolver = { workspace = true } -rayon = { workspace = true } -lazy_static = { workspace = true } # used in oxc_macros -serde_json = { workspace = true } -serde = { workspace = true, features = ["derive"] } -regex = { workspace = true } -rustc-hash = { workspace = true } -phf = { workspace = true, features = ["macros"] } -itertools = { workspace = true } -dashmap = { workspace = true } -convert_case = { workspace = true } -language-tags = { workspace = true } -mime_guess = { workspace = true } -url = { workspace = true } - +rayon = { workspace = true } +lazy_static = { workspace = true } +serde_json = { workspace = true } +serde = { workspace = true, features = ["derive"] } +regex = { workspace = true } +rustc-hash = { workspace = true } +phf = { workspace = true, features = ["macros"] } +itertools = { workspace = true } +dashmap = { workspace = true } +convert_case = { workspace = true } +language-tags = { workspace = true } +mime_guess = { workspace = true } +url = { workspace = true } rust-lapper = { workspace = true } once_cell = { workspace = true } memchr = { workspace = true } json-strip-comments = { workspace = true } - -static_assertions = { workspace = true } +schemars = { workspace = true, features = ["indexmap2"] } +static_assertions = { workspace = true } [dev-dependencies] insta = { workspace = true } diff --git a/crates/oxc_linter/src/config/env.rs b/crates/oxc_linter/src/config/env.rs index 8b6354348..d8d2eea68 100644 --- a/crates/oxc_linter/src/config/env.rs +++ b/crates/oxc_linter/src/config/env.rs @@ -1,12 +1,9 @@ use rustc_hash::FxHashMap; +use schemars::JsonSchema; use serde::Deserialize; -/// Environment -/// -/// -/// TS type is `Record` -/// -#[derive(Debug, Clone, Deserialize)] +// TODO: list the keys we support +#[derive(Debug, Clone, Deserialize, JsonSchema)] pub struct ESLintEnv(FxHashMap); impl ESLintEnv { diff --git a/crates/oxc_linter/src/config/globals.rs b/crates/oxc_linter/src/config/globals.rs index 31b52f18d..d958e593a 100644 --- a/crates/oxc_linter/src/config/globals.rs +++ b/crates/oxc_linter/src/config/globals.rs @@ -1,12 +1,14 @@ +use schemars::JsonSchema; use serde::Deserialize; use rustc_hash::FxHashMap; /// -#[derive(Debug, Default, Deserialize)] +#[derive(Debug, Default, Deserialize, JsonSchema)] pub struct ESLintGlobals(FxHashMap); -#[derive(Debug, Eq, PartialEq, Deserialize)] +// TODO: support deprecated `false` +#[derive(Debug, Eq, PartialEq, Deserialize, 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 0be55db08..1c838789a 100644 --- a/crates/oxc_linter/src/config/mod.rs +++ b/crates/oxc_linter/src/config/mod.rs @@ -7,6 +7,7 @@ use std::path::Path; use oxc_diagnostics::OxcDiagnostic; use rustc_hash::FxHashSet; +use schemars::JsonSchema; use serde::Deserialize; use crate::{rules::RuleEnum, AllowWarnDeny, RuleWithSeverity}; @@ -17,8 +18,8 @@ pub use self::{ }; /// ESLint Config -/// -#[derive(Debug, Default, Deserialize)] +// +#[derive(Debug, Default, Deserialize, JsonSchema)] #[serde(default)] pub struct ESLintConfig { pub(crate) rules: ESLintRules, diff --git a/crates/oxc_linter/src/config/rules.rs b/crates/oxc_linter/src/config/rules.rs index 732834ac3..66abe5b32 100644 --- a/crates/oxc_linter/src/config/rules.rs +++ b/crates/oxc_linter/src/config/rules.rs @@ -1,16 +1,20 @@ -use crate::AllowWarnDeny; -use oxc_diagnostics::{Error, OxcDiagnostic}; -use serde::de::{self, Deserializer, Visitor}; -use serde::Deserialize; -use std::fmt; -use std::ops::Deref; +use std::{borrow::Cow, fmt, ops::Deref}; -/// The `rules` field from ESLint config -/// -/// TS type is `Record` -/// - type SeverityConf = 0 | 1 | 2 | "off" | "warn" | "error"; -/// - type RuleConf = SeverityConf | [SeverityConf, ...any[]]; -/// +use oxc_diagnostics::{Error, OxcDiagnostic}; +use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; +use serde::{ + de::{self, Deserializer, Visitor}, + Deserialize, +}; + +use crate::AllowWarnDeny; + +// The `rules` field from ESLint config +// +// TS type is `Record` +// - type SeverityConf = 0 | 1 | 2 | "off" | "warn" | "error"; +// - type RuleConf = SeverityConf | [SeverityConf, ...any[]]; +// #[derive(Debug, Clone, Default)] pub struct ESLintRules(Vec); @@ -22,6 +26,29 @@ pub struct ESLintRule { pub config: Option, } +impl JsonSchema for ESLintRules { + fn schema_name() -> String { + "ESLintRules".to_owned() + } + + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("ESLintRules") + } + + fn json_schema(gen: &mut SchemaGenerator) -> Schema { + #[allow(unused)] + #[derive(Debug, Clone, JsonSchema)] + #[serde(untagged)] + enum DummyRule { + #[schemars(range(min = 0, max = 2.0))] + Number(usize), + String(String), + Array(Vec), + } + DummyRule::json_schema(gen) + } +} + // Manually implement Deserialize because the type is a bit complex... // - Handle single value form and array form // - SeverityConf into AllowWarnDeny diff --git a/crates/oxc_linter/src/config/settings/jsdoc.rs b/crates/oxc_linter/src/config/settings/jsdoc.rs index c8c59dd78..c5191e30f 100644 --- a/crates/oxc_linter/src/config/settings/jsdoc.rs +++ b/crates/oxc_linter/src/config/settings/jsdoc.rs @@ -1,12 +1,14 @@ use rustc_hash::FxHashMap; +use schemars::JsonSchema; use serde::Deserialize; -/// -#[derive(Debug, Deserialize)] +// +#[derive(Debug, Deserialize, JsonSchema)] pub struct JSDocPluginSettings { /// For all rules but NOT apply to `check-access` and `empty-tags` rule #[serde(default, rename = "ignorePrivate")] pub ignore_private: bool, + /// For all rules but NOT apply to `empty-tags` rule #[serde(default, rename = "ignoreInternal")] pub ignore_internal: bool, @@ -14,12 +16,15 @@ pub struct JSDocPluginSettings { /// Only for `require-(yields|returns|description|example|param|throws)` rule #[serde(default = "default_true", rename = "ignoreReplacesDocs")] pub ignore_replaces_docs: bool, + /// Only for `require-(yields|returns|description|example|param|throws)` rule #[serde(default = "default_true", rename = "overrideReplacesDocs")] pub override_replaces_docs: bool, + /// Only for `require-(yields|returns|description|example|param|throws)` rule #[serde(default, rename = "augmentsExtendsReplacesDocs")] pub augments_extends_replaces_docs: bool, + /// Only for `require-(yields|returns|description|example|param|throws)` rule #[serde(default, rename = "implementsReplacesDocs")] pub implements_replaces_docs: bool, @@ -173,7 +178,7 @@ fn default_true() -> bool { true } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, 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 987655592..dab21aa64 100644 --- a/crates/oxc_linter/src/config/settings/jsx_a11y.rs +++ b/crates/oxc_linter/src/config/settings/jsx_a11y.rs @@ -1,8 +1,9 @@ use rustc_hash::FxHashMap; +use schemars::JsonSchema; use serde::Deserialize; -/// -#[derive(Debug, Deserialize, Default)] +// +#[derive(Debug, Deserialize, Default, 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 eaca63662..0dc735482 100644 --- a/crates/oxc_linter/src/config/settings/mod.rs +++ b/crates/oxc_linter/src/config/settings/mod.rs @@ -1,29 +1,34 @@ -use self::{ - jsdoc::JSDocPluginSettings, jsx_a11y::JSXA11yPluginSettings, next::NextPluginSettings, - react::ReactPluginSettings, -}; -use serde::Deserialize; - pub mod jsdoc; mod jsx_a11y; mod next; mod react; -/// The `settings` field from ESLint config -/// An object containing name-value pairs of information that should be available to all rules -/// -/// TS type is `Object` -/// -/// But each plugin extends this with their own properties. -#[derive(Debug, Deserialize, Default)] +use schemars::JsonSchema; +use serde::Deserialize; + +use self::{ + jsdoc::JSDocPluginSettings, jsx_a11y::JSXA11yPluginSettings, next::NextPluginSettings, + react::ReactPluginSettings, +}; + +// The `settings` field from ESLint config +// An object containing name-value pairs of information that should be available to all rules +// +// TS type is `Object` +// +// But each plugin extends this with their own properties. +#[derive(Debug, Deserialize, Default, JsonSchema)] pub struct ESLintSettings { #[serde(default)] #[serde(rename = "jsx-a11y")] pub jsx_a11y: JSXA11yPluginSettings, + #[serde(default)] pub next: NextPluginSettings, + #[serde(default)] pub react: ReactPluginSettings, + #[serde(default)] pub jsdoc: JSDocPluginSettings, } diff --git a/crates/oxc_linter/src/config/settings/next.rs b/crates/oxc_linter/src/config/settings/next.rs index de678d22a..0f9f20234 100644 --- a/crates/oxc_linter/src/config/settings/next.rs +++ b/crates/oxc_linter/src/config/settings/next.rs @@ -1,7 +1,7 @@ +use schemars::JsonSchema; use serde::Deserialize; -/// -#[derive(Debug, Deserialize, Default)] +#[derive(Debug, Deserialize, Default, JsonSchema)] pub struct NextPluginSettings { #[serde(default)] #[serde(rename = "rootDir")] @@ -19,7 +19,7 @@ impl NextPluginSettings { // Deserialize helper types -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, PartialEq, JsonSchema)] #[serde(untagged)] enum OneOrMany { One(T), diff --git a/crates/oxc_linter/src/config/settings/react.rs b/crates/oxc_linter/src/config/settings/react.rs index 65768448e..d1efbfe5e 100644 --- a/crates/oxc_linter/src/config/settings/react.rs +++ b/crates/oxc_linter/src/config/settings/react.rs @@ -1,11 +1,13 @@ +use schemars::JsonSchema; use serde::Deserialize; -/// -#[derive(Debug, Deserialize, Default)] +// +#[derive(Debug, Deserialize, Default, JsonSchema)] pub struct ReactPluginSettings { #[serde(default)] #[serde(rename = "formComponents")] form_components: Vec, + #[serde(default)] #[serde(rename = "linkComponents")] link_components: Vec, @@ -24,7 +26,7 @@ impl ReactPluginSettings { // Deserialize helper types -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, JsonSchema)] #[serde(untagged)] enum CustomComponent { NameOnly(String),