Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(linter): generate schemas for rules (with documentation) #4174

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/oxc_linter/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod env;
mod globals;
mod rules;
mod schema;
mod settings;

use std::path::Path;
Expand Down
25 changes: 1 addition & 24 deletions crates/oxc_linter/src/config/rules.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use std::{borrow::Cow, fmt, ops::Deref};
use std::{fmt, ops::Deref};

use oxc_diagnostics::{Error, OxcDiagnostic};
use rustc_hash::FxHashMap;
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
use serde::{
de::{self, Deserializer, Visitor},
Deserialize,
Expand All @@ -25,27 +23,6 @@ pub struct ESLintRule {
pub config: Option<serde_json::Value>,
}

impl JsonSchema for OxlintRules {
fn schema_name() -> String {
"OxlintRules".to_owned()
}

fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("OxlintRules")
}

fn json_schema(gen: &mut SchemaGenerator) -> Schema {
#[allow(unused)]
#[derive(Debug, Clone, JsonSchema)]
#[serde(untagged)]
enum DummyRule {
Toggle(AllowWarnDeny),
ToggleAndConfig(Vec<serde_json::Value>),
}
gen.subschema_for::<FxHashMap<String, DummyRule>>()
}
}

// Manually implement Deserialize because the type is a bit complex...
// - Handle single value form and array form
// - SeverityConf into AllowWarnDeny
Expand Down
64 changes: 64 additions & 0 deletions crates/oxc_linter/src/config/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use crate::{rules::RULES, AllowWarnDeny};

use super::OxlintRules;
use schemars::{
gen::SchemaGenerator,
schema::{Schema, SchemaObject},
JsonSchema,
};
// use crate::RuleMeta;
use std::borrow::Cow;

impl JsonSchema for OxlintRules {
fn schema_name() -> String {
"OxlintRules".to_owned()
}

fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("OxlintRules")
}

fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema: SchemaObject = SchemaObject::default();
schema.object().properties.extend(RULES.iter().map(|rule| {
let rule_config_schema = rule.schema(gen);
let mut rule_schema = rule_property_schema(gen, rule_config_schema);

let docs = rule.documentation();
rule_schema.metadata().description = docs.map(Into::into);
if let Some(docs) = docs {
// markdownDescription is a non-standard property that VSCode
// uses in intellisense. It lets us show the rule's
// documentation with markdown formatting.
rule_schema
.extensions
.insert("markdownDescription".into(), docs.to_string().into());
}

// Don't scope eslint rules, only plugins.
let scoped_name = if rule.plugin_name() == "eslint" {
rule.name().into()
} else {
rule.plugin_name().to_string() + "/" + rule.name()
};

(scoped_name, Schema::Object(rule_schema))
}));

schema.into()
}
}

fn rule_property_schema(gen: &mut SchemaGenerator, config_schema: Schema) -> SchemaObject {
let any_list = gen.subschema_for::<Vec<serde_json::Value>>().into_object();
let toggle_schema = gen.subschema_for::<AllowWarnDeny>().into_object();
let mut toggle_and_config = SchemaObject::default();
toggle_and_config.array().items = Some(Schema::Object(toggle_schema.clone()).into());
toggle_and_config.array().additional_items = Some(Box::new(config_schema));

let mut schema = SchemaObject::default();
schema.subschemas().any_of =
Some(vec![toggle_schema.into(), toggle_and_config.into(), any_list.into()]);

schema
}
14 changes: 8 additions & 6 deletions crates/oxc_linter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,18 +212,20 @@ mod test {

#[test]
fn test_schema_json() {
use std::fs;

use project_root::get_project_root;
use std::fs;
let path = get_project_root().unwrap().join("npm/oxlint/configuration_schema.json");
let existing_json = fs::read_to_string(&path).unwrap_or_default();
let schema = schemars::schema_for!(OxlintConfig);
let json = serde_json::to_string_pretty(&schema).unwrap();
let existing_json = fs::read_to_string(&path).unwrap_or_default();
if existing_json != json {
std::fs::write(&path, &json).unwrap();
}
insta::with_settings!({ prepend_module_to_snapshot => false }, {
insta::assert_snapshot!(json);
});
let s = fs::read_to_string(&path).expect("file exits");
let json = serde_json::from_str::<serde_json::Value>(&s).expect("is json");
assert_eq!(
json.as_object().unwrap().get("title").unwrap().as_str().unwrap(),
"OxlintConfig"
);
}
}
25 changes: 25 additions & 0 deletions crates/oxc_linter/src/rules/eslint/default_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use regex::{Regex, RegexBuilder};
use schemars::{
schema::{Schema, SchemaObject},
JsonSchema,
};

use crate::{context::LintContext, rule::Rule, AstNode};

Expand All @@ -19,6 +23,26 @@ pub struct DefaultCase(Box<DefaultCaseConfig>);
pub struct DefaultCaseConfig {
comment_pattern: Option<Regex>,
}
impl JsonSchema for DefaultCaseConfig {
fn schema_name() -> String {
"DefaultCaseConfig".to_string()
}
fn json_schema(gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
let mut schema = SchemaObject::default();
schema.object().properties.insert("commentPattern".to_string(), {
let mut schema = <String>::json_schema(gen);
if let Schema::Object(schema) = &mut schema {
schema.metadata().title = Some("Comment pattern".to_string());
schema.metadata().description = Some(
"A regex pattern to match comments that indicate the default case is omitted."
.to_string(),
);
}
schema.into()
});
schema.into()
}
}

impl std::ops::Deref for DefaultCase {
type Target = DefaultCaseConfig;
Expand Down Expand Up @@ -47,6 +71,7 @@ declare_oxc_lint!(
/// ```
DefaultCase,
restriction,
DefaultCaseConfig
);

impl Rule for DefaultCase {
Expand Down
Loading
Loading