feat(linter): start adding json schema for configuration file (#3375)

This commit is contained in:
Boshen 2024-05-22 00:35:29 +08:00 committed by GitHub
parent 1e84644220
commit fe208ddef6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 146 additions and 63 deletions

45
Cargo.lock generated
View file

@ -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",
]

View file

@ -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"]

View file

@ -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 }

View file

@ -1,12 +1,9 @@
use rustc_hash::FxHashMap;
use schemars::JsonSchema;
use serde::Deserialize;
/// Environment
/// <https://eslint.org/docs/latest/use/configure/language-options#using-configuration-files>
///
/// TS type is `Record<string, boolean>`
/// <https://github.com/eslint/eslint/blob/ce838adc3b673e52a151f36da0eedf5876977514/lib/shared/types.js#L40>
#[derive(Debug, Clone, Deserialize)]
// TODO: list the keys we support
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ESLintEnv(FxHashMap<String, bool>);
impl ESLintEnv {

View file

@ -1,12 +1,14 @@
use schemars::JsonSchema;
use serde::Deserialize;
use rustc_hash::FxHashMap;
/// <https://eslint.org/docs/v8.x/use/configure/language-options#using-configuration-files-1>
#[derive(Debug, Default, Deserialize)]
#[derive(Debug, Default, Deserialize, JsonSchema)]
pub struct ESLintGlobals(FxHashMap<String, GlobalValue>);
#[derive(Debug, Eq, PartialEq, Deserialize)]
// TODO: support deprecated `false`
#[derive(Debug, Eq, PartialEq, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum GlobalValue {
Readonly,

View file

@ -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
/// <https://eslint.org/docs/v8.x/use/configure/configuration-files>
#[derive(Debug, Default, Deserialize)]
// <https://eslint.org/docs/v8.x/use/configure/configuration-files>
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[serde(default)]
pub struct ESLintConfig {
pub(crate) rules: ESLintRules,

View file

@ -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<string, RuleConf>`
/// - type SeverityConf = 0 | 1 | 2 | "off" | "warn" | "error";
/// - type RuleConf = SeverityConf | [SeverityConf, ...any[]];
/// <https://github.com/eslint/eslint/blob/ce838adc3b673e52a151f36da0eedf5876977514/lib/shared/types.js#L12>
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<string, RuleConf>`
// - type SeverityConf = 0 | 1 | 2 | "off" | "warn" | "error";
// - type RuleConf = SeverityConf | [SeverityConf, ...any[]];
// <https://github.com/eslint/eslint/blob/ce838adc3b673e52a151f36da0eedf5876977514/lib/shared/types.js#L12>
#[derive(Debug, Clone, Default)]
pub struct ESLintRules(Vec<ESLintRule>);
@ -22,6 +26,29 @@ pub struct ESLintRule {
pub config: Option<serde_json::Value>,
}
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<serde_json::Value>),
}
DummyRule::json_schema(gen)
}
}
// Manually implement Deserialize because the type is a bit complex...
// - Handle single value form and array form
// - SeverityConf into AllowWarnDeny

View file

@ -1,12 +1,14 @@
use rustc_hash::FxHashMap;
use schemars::JsonSchema;
use serde::Deserialize;
/// <https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/settings.md>
#[derive(Debug, Deserialize)]
// <https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/settings.md>
#[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),

View file

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

View file

@ -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`
/// <https://github.com/eslint/eslint/blob/ce838adc3b673e52a151f36da0eedf5876977514/lib/shared/types.js#L53>
/// 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`
// <https://github.com/eslint/eslint/blob/ce838adc3b673e52a151f36da0eedf5876977514/lib/shared/types.js#L53>
// 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,
}

View file

@ -1,7 +1,7 @@
use schemars::JsonSchema;
use serde::Deserialize;
/// <https://nextjs.org/docs/pages/building-your-application/configuring/eslint#eslint-plugin>
#[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<T> {
One(T),

View file

@ -1,11 +1,13 @@
use schemars::JsonSchema;
use serde::Deserialize;
/// <https://github.com/jsx-eslint/eslint-plugin-react#configuration-legacy-eslintrc->
#[derive(Debug, Deserialize, Default)]
// <https://github.com/jsx-eslint/eslint-plugin-react#configuration-legacy-eslintrc->
#[derive(Debug, Deserialize, Default, JsonSchema)]
pub struct ReactPluginSettings {
#[serde(default)]
#[serde(rename = "formComponents")]
form_components: Vec<CustomComponent>,
#[serde(default)]
#[serde(rename = "linkComponents")]
link_components: Vec<CustomComponent>,
@ -24,7 +26,7 @@ impl ReactPluginSettings {
// Deserialize helper types
#[derive(Clone, Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize, JsonSchema)]
#[serde(untagged)]
enum CustomComponent {
NameOnly(String),