feat(tasks/website): start generating linter config markdown from json schema (#3386)

This commit is contained in:
Boshen 2024-05-22 16:09:33 +00:00
parent 82c21f38f6
commit 57d2bcacea
9 changed files with 160 additions and 24 deletions

2
Cargo.lock generated
View file

@ -2973,10 +2973,12 @@ name = "website"
version = "0.0.0"
dependencies = [
"bpaf",
"handlebars",
"oxc_cli",
"oxc_linter",
"pico-args",
"schemars",
"serde",
"serde_json",
]

View file

@ -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>);

View file

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

View file

@ -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());
}

View 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}");
}

View 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),
}
}
}

View 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,
};

View 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());
}

View file

@ -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."),