mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(tasks/website): start generating linter config markdown from json schema (#3386)
This commit is contained in:
parent
82c21f38f6
commit
57d2bcacea
9 changed files with 160 additions and 24 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -2973,10 +2973,12 @@ name = "website"
|
|||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bpaf",
|
||||
"handlebars",
|
||||
"oxc_cli",
|
||||
"oxc_linter",
|
||||
"pico-args",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use rustc_hash::FxHashMap;
|
|||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Env
|
||||
// TODO: list the keys we support
|
||||
#[derive(Debug, Clone, Deserialize, JsonSchema)]
|
||||
pub struct ESLintEnv(FxHashMap<String, bool>);
|
||||
|
|
|
|||
|
|
@ -20,3 +20,5 @@ bpaf = { workspace = true, features = ["docgen"] }
|
|||
pico-args = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
handlebars = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
// <https://oxc-project.github.io/docs/guide/usage/linter-cli.html>
|
||||
pub fn generate_cli() {
|
||||
use bpaf::Parser;
|
||||
use oxc_cli::lint_options;
|
||||
let markdown = lint_options().to_options().render_markdown("oxlint");
|
||||
println!("{markdown}");
|
||||
}
|
||||
|
||||
// <https://oxc-project.github.io/docs/guide/usage/linter-rules.html>
|
||||
pub fn generate_rules() {
|
||||
use oxc_linter::Linter;
|
||||
let mut v = vec![];
|
||||
Linter::print_rules(&mut v);
|
||||
println!("{}", String::from_utf8(v).unwrap());
|
||||
}
|
||||
|
||||
pub fn generate_json_schema() {
|
||||
use schemars::schema_for;
|
||||
|
||||
use oxc_linter::ESLintConfig;
|
||||
let schema = schema_for!(ESLintConfig);
|
||||
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
|
||||
}
|
||||
8
tasks/website/src/linter/cli.rs
Normal file
8
tasks/website/src/linter/cli.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
use bpaf::Parser;
|
||||
use oxc_cli::lint_options;
|
||||
|
||||
// <https://oxc-project.github.io/docs/guide/usage/linter/cli.html>
|
||||
pub fn generate_cli() {
|
||||
let markdown = lint_options().to_options().render_markdown("oxlint");
|
||||
println!("{markdown}");
|
||||
}
|
||||
129
tasks/website/src/linter/json_schema.rs
Normal file
129
tasks/website/src/linter/json_schema.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
use handlebars::Handlebars;
|
||||
use schemars::{
|
||||
schema::{RootSchema, Schema, SchemaObject},
|
||||
schema_for,
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
use oxc_linter::ESLintConfig;
|
||||
|
||||
pub fn generate_schema_json() {
|
||||
let schema = schema_for!(ESLintConfig);
|
||||
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
|
||||
}
|
||||
|
||||
pub fn generate_schema_markdown() {
|
||||
let rendered = Renderer::new().render();
|
||||
println!("{rendered}");
|
||||
}
|
||||
|
||||
const ROOT: &str = "
|
||||
# {{title}}
|
||||
|
||||
{{> section}}
|
||||
";
|
||||
|
||||
const SECTION: &str = "
|
||||
{{#each sections}}
|
||||
{{level}} `{{title}}`
|
||||
|
||||
type: `{{instance_type}}`
|
||||
|
||||
{{description}}
|
||||
|
||||
{{> section}}
|
||||
|
||||
{{/each}}
|
||||
";
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Root {
|
||||
title: String,
|
||||
sections: Vec<Section>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Section {
|
||||
level: String,
|
||||
title: String,
|
||||
instance_type: String,
|
||||
description: String,
|
||||
sections: Vec<Section>,
|
||||
}
|
||||
|
||||
struct Renderer {
|
||||
handlebars: Handlebars<'static>,
|
||||
root_schema: RootSchema,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
fn new() -> Self {
|
||||
let mut handlebars = Handlebars::new();
|
||||
handlebars.register_escape_fn(handlebars::no_escape);
|
||||
assert!(handlebars.register_template_string("root", ROOT).is_ok());
|
||||
assert!(handlebars.register_template_string("section", SECTION).is_ok());
|
||||
let root_schema = schema_for!(ESLintConfig);
|
||||
Self { handlebars, root_schema }
|
||||
}
|
||||
|
||||
fn render(self) -> String {
|
||||
let root = self.render_root_schema(&self.root_schema);
|
||||
self.handlebars.render("root", &root).unwrap()
|
||||
}
|
||||
|
||||
fn get_schema_object(schema: &Schema) -> &SchemaObject {
|
||||
match schema {
|
||||
Schema::Object(schema_object) => schema_object,
|
||||
Schema::Bool(_) => panic!("definition must be an object."),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_referenced_schema(&self, reference: &str) -> &SchemaObject {
|
||||
let definitions = &self.root_schema.definitions;
|
||||
let definition = definitions.get(reference.trim_start_matches("#/definitions/"));
|
||||
definition.map(Self::get_schema_object).unwrap()
|
||||
}
|
||||
|
||||
fn render_root_schema(&self, root_schema: &RootSchema) -> Root {
|
||||
let title = root_schema
|
||||
.schema
|
||||
.metadata
|
||||
.as_ref()
|
||||
.and_then(|m| m.description.clone())
|
||||
.unwrap_or_default();
|
||||
let sections = self.render_properties(1, None, &root_schema.schema);
|
||||
Root { title, sections }
|
||||
}
|
||||
|
||||
fn render_properties(
|
||||
&self,
|
||||
depth: usize,
|
||||
parent_key: Option<&str>,
|
||||
schema: &SchemaObject,
|
||||
) -> Vec<Section> {
|
||||
let Some(object) = &schema.object else { return vec![] };
|
||||
object
|
||||
.properties
|
||||
.iter()
|
||||
.map(|(key, schema)| {
|
||||
let key = parent_key.map_or_else(|| key.clone(), |k| format!("{k}.{key}"));
|
||||
self.render_schema(depth + 1, &key, Self::get_schema_object(schema))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn render_schema(&self, depth: usize, key: &str, schema: &SchemaObject) -> Section {
|
||||
let schema = schema.reference.as_ref().map_or(schema, |r| self.get_referenced_schema(r));
|
||||
Section {
|
||||
level: "#".repeat(depth),
|
||||
title: key.into(),
|
||||
instance_type: serde_json::to_string(&schema.instance_type).unwrap().replace('"', ""),
|
||||
description: schema
|
||||
.metadata
|
||||
.as_ref()
|
||||
.and_then(|m| m.description.clone())
|
||||
.unwrap_or_default(),
|
||||
sections: self.render_properties(depth, Some(key), schema),
|
||||
}
|
||||
}
|
||||
}
|
||||
9
tasks/website/src/linter/mod.rs
Normal file
9
tasks/website/src/linter/mod.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
mod cli;
|
||||
mod json_schema;
|
||||
mod rules;
|
||||
|
||||
pub use self::{
|
||||
cli::generate_cli,
|
||||
json_schema::{generate_schema_json, generate_schema_markdown},
|
||||
rules::generate_rules,
|
||||
};
|
||||
7
tasks/website/src/linter/rules.rs
Normal file
7
tasks/website/src/linter/rules.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// <https://oxc-project.github.io/docs/guide/usage/linter/rules.html>
|
||||
pub fn generate_rules() {
|
||||
use oxc_linter::Linter;
|
||||
let mut v = vec![];
|
||||
Linter::print_rules(&mut v);
|
||||
println!("{}", String::from_utf8(v).unwrap());
|
||||
}
|
||||
|
|
@ -9,7 +9,8 @@ fn main() {
|
|||
let task = command.as_deref().unwrap_or("default");
|
||||
|
||||
match task {
|
||||
"linter-json-schema" => linter::generate_json_schema(),
|
||||
"linter-schema-json" => linter::generate_schema_json(),
|
||||
"linter-schema-markdown" => linter::generate_schema_markdown(),
|
||||
"linter-cli" => linter::generate_cli(),
|
||||
"linter-rules" => linter::generate_rules(),
|
||||
_ => println!("Missing task command."),
|
||||
|
|
|
|||
Loading…
Reference in a new issue