From d03b085d40b104ec25bad195f9b6f85f5ec24604 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Thu, 6 Aug 2026 17:02:36 +0800 Subject: feat(cli): implement `explain` command and add JSON structural rendering Add a new `explain ` command that queries the embedded lint registry (generated by `build.rs`) and displays detailed information about a specifoic lint, including its name, title, summary, active-on target, default setting, and author. Enable the `structural_renderer` feature and add `serde`/`serde_json` dependencies to support JSON output rendering for command results. --- mingling_cli/src/linter/cmd_explain.rs | 123 +++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 mingling_cli/src/linter/cmd_explain.rs (limited to 'mingling_cli/src/linter/cmd_explain.rs') diff --git a/mingling_cli/src/linter/cmd_explain.rs b/mingling_cli/src/linter/cmd_explain.rs new file mode 100644 index 0000000..d76cf65 --- /dev/null +++ b/mingling_cli/src/linter/cmd_explain.rs @@ -0,0 +1,123 @@ +use mingling::{ + Grouped, Routable, StructuralData, + macros::{ + arg, buffer, chain, dispatcher, metadata, pack, pack_err_structural, r_println, renderer, + routeify, + }, + metadata::Description, + picker::EntryPicker, +}; +use serde::{Deserialize, Serialize}; +use std::sync::OnceLock; + +use crate::Next; + +dispatcher!("explain"); + +#[metadata(EntryExplain)] +pub fn desc_explain() -> Description { + "Explain the meaning of the specified Lint".into() +} + +pack!(StateExplainLint = String); +pack_err_structural!(ErrorNoExplainLintProvided); +pack_err_structural!(ErrorNoSuchLint = String); + +#[derive(Debug, Default, Grouped, StructuralData, Serialize)] +pub struct ResultExplainLint { + pub lint_name: String, + pub title: String, + pub summary: String, + pub active_on: String, + pub author: String, + pub default: String, +} + +#[chain(routeify)] +pub fn handle_explain(args: EntryExplain) -> Next { + let lint_name = args + .pick_or_route(&arg![String], || { + ErrorNoExplainLintProvided::default().to_chain() + }) + .to_result()?; + StateExplainLint::new(lint_name).into() +} + +/// Mirror of the lint registry JSON generated by `build.rs` (`registry.json`). +#[derive(Debug, Deserialize)] +struct LintRegistry { + lints: Vec, +} + +#[derive(Debug, Deserialize)] +struct LintEntry { + name: String, + title: String, + summary: String, + metadata: LintMetadata, +} + +#[derive(Debug, Deserialize)] +struct LintMetadata { + active_on: String, + author: String, + default: String, +} + +/// The lint registry, embedded at compile time via `include_str!`. +/// +/// `registry.json` is regenerated by `build.rs` on every build, so the +/// embedded copy always reflects the lints in `src/lints/`. +fn lint_registry() -> &'static LintRegistry { + static REGISTRY: OnceLock = OnceLock::new(); + REGISTRY.get_or_init(|| { + serde_json::from_str(include_str!("../../registry.json")) + .expect("failed to parse embedded registry.json") + }) +} + +#[chain] +pub fn handle_state_explain_lint(p: StateExplainLint) -> Next { + let lint_name = p.inner; + let Some(entry) = lint_registry().lints.iter().find(|l| l.name == lint_name) else { + return ErrorNoSuchLint::new(lint_name).to_chain(); + }; + ResultExplainLint { + lint_name: entry.name.clone(), + title: entry.title.clone(), + summary: entry.summary.clone(), + active_on: entry.metadata.active_on.clone(), + author: entry.metadata.author.clone(), + default: entry.metadata.default.clone(), + } + .to_chain() +} + +#[renderer(buffer)] +pub fn render_explain_lint(r: ResultExplainLint) { + r_println!("{}", r.title); + r_println!(""); + r_println!(" Name: {}", r.lint_name); + r_println!(" Active on: {}", r.active_on); + r_println!(" Default: {}", r.default); + r_println!(" Author: {}", r.author); + r_println!(""); + r_println!("{}", r.summary); +} + +#[renderer(buffer)] +pub fn render_error_no_explain_lint_provided(_: ErrorNoExplainLintProvided) { + r_println!("No lint name provided"); + r_println!(""); + r_println!("Usage: mling explain "); +} + +#[renderer(buffer)] +pub fn render_error_no_such_lint(err: ErrorNoSuchLint) { + r_println!("No such lint: \"{}\"", err.info); + r_println!(""); + r_println!("Available lints:"); + for entry in &lint_registry().lints { + r_println!(" {}", entry.name); + } +} -- cgit