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/Cargo.lock | 3 + mingling_cli/Cargo.toml | 3 +- mingling_cli/help/help.txt | 2 +- mingling_cli/src/linter.rs | 2 + mingling_cli/src/linter/cmd_explain.rs | 123 +++++++++++++++++++++++++++++++++ mingling_cli/src/main.rs | 10 ++- 6 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 mingling_cli/src/linter/cmd_explain.rs (limited to 'mingling_cli') diff --git a/mingling_cli/Cargo.lock b/mingling_cli/Cargo.lock index 5a0dcd0..60f8792 100644 --- a/mingling_cli/Cargo.lock +++ b/mingling_cli/Cargo.lock @@ -277,6 +277,7 @@ dependencies = [ "arg-picker", "mingling_core", "mingling_macros", + "serde", ] [[package]] @@ -304,6 +305,8 @@ dependencies = [ "just_template", "might_be_async", "mingling_pathf", + "serde", + "serde_json", ] [[package]] diff --git a/mingling_cli/Cargo.toml b/mingling_cli/Cargo.toml index 4dfea20..a7a8538 100644 --- a/mingling_cli/Cargo.toml +++ b/mingling_cli/Cargo.toml @@ -27,7 +27,8 @@ features = [ "pathf", "dispatch_tree", "async", - "comp" + "comp", + "structural_renderer" ] [build-dependencies.mingling] diff --git a/mingling_cli/help/help.txt b/mingling_cli/help/help.txt index d5073e6..2089e2b 100644 --- a/mingling_cli/help/help.txt +++ b/mingling_cli/help/help.txt @@ -17,7 +17,7 @@ COMMANDS: ra-lint Run `mling lint` and output the results ra-lint-check Run `mling lint` and `cargo check`, and output the combined results ra-lint-clippy Run `mling lint` and `cargo clippy`, and output the combined results - explain [NOT_IMPL] Explain the meaning of the specified Lint + explain Explain the meaning of the specified Lint PROJECT MANAGE: proj-new [NOT_IMPL] Create a project diff --git a/mingling_cli/src/linter.rs b/mingling_cli/src/linter.rs index 45111b6..4916eba 100644 --- a/mingling_cli/src/linter.rs +++ b/mingling_cli/src/linter.rs @@ -1,4 +1,6 @@ +pub mod cmd_explain; pub mod cmd_lint; pub mod cmd_ra_lints; pub mod mlint_attr; pub mod mlint_report; + 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); + } +} diff --git a/mingling_cli/src/main.rs b/mingling_cli/src/main.rs index cb41368..da30861 100644 --- a/mingling_cli/src/main.rs +++ b/mingling_cli/src/main.rs @@ -7,9 +7,12 @@ use crate::metadata::{ }; use mingling::{ ShellContext, Suggest, - consts::HELP_FLAG, + consts::{HELP_FLAG, JSON_FLAG, JSON_PRETTY_FLAG}, macros::{completion, gen_program, help, suggest}, - setup::{ExitCodeSetup, picker::HelpFlagSetup}, + setup::{ + ExitCodeSetup, + picker::{HelpFlagSetup, StructuralRendererSetup}, + }, }; pub mod diagnostic; @@ -26,6 +29,7 @@ async fn main() { let mut program = ThisProgram::new(); // Setups + program.with_setup(StructuralRendererSetup); program.with_setup(HelpFlagSetup::default()); program.with_setup(ExitCodeSetup::default()); @@ -50,6 +54,8 @@ pub fn complete_global(_ctx: &ShellContext) -> Suggest { ARG_ALL_FEATURES: "Enable all features", ARG_NO_DEFAULT_FEATURES: "Disable default features", ARG_NO_DEPS: "Do not include dependencies in metadata", + JSON_FLAG: "Render results in JSON format", + JSON_PRETTY_FLAG: "Render results in pretty JSON format" } } -- cgit