mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +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"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bpaf",
|
"bpaf",
|
||||||
|
"handlebars",
|
||||||
"oxc_cli",
|
"oxc_cli",
|
||||||
"oxc_linter",
|
"oxc_linter",
|
||||||
"pico-args",
|
"pico-args",
|
||||||
"schemars",
|
"schemars",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use rustc_hash::FxHashMap;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Env
|
||||||
// TODO: list the keys we support
|
// TODO: list the keys we support
|
||||||
#[derive(Debug, Clone, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, JsonSchema)]
|
||||||
pub struct ESLintEnv(FxHashMap<String, bool>);
|
pub struct ESLintEnv(FxHashMap<String, bool>);
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,5 @@ bpaf = { workspace = true, features = ["docgen"] }
|
||||||
pico-args = { workspace = true }
|
pico-args = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
schemars = { 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");
|
let task = command.as_deref().unwrap_or("default");
|
||||||
|
|
||||||
match task {
|
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-cli" => linter::generate_cli(),
|
||||||
"linter-rules" => linter::generate_rules(),
|
"linter-rules" => linter::generate_rules(),
|
||||||
_ => println!("Missing task command."),
|
_ => println!("Missing task command."),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue