aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-06 17:02:36 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-07 16:57:24 +0800
commitd026b05624df4ab8a0be1c57adeb4947b566902d (patch)
treed39876abe221949b56c8b751c7b075734dbae585 /mingling_cli
parentf809e616b4ec51ea981f671507591e213e0f4192 (diff)
feat(cli): implement `explain` command and add JSON structural rendering
Add a new `explain <LINT>` 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.
Diffstat (limited to 'mingling_cli')
-rw-r--r--mingling_cli/Cargo.lock3
-rw-r--r--mingling_cli/Cargo.toml3
-rw-r--r--mingling_cli/help/help.txt2
-rw-r--r--mingling_cli/src/linter.rs2
-rw-r--r--mingling_cli/src/linter/cmd_explain.rs123
-rw-r--r--mingling_cli/src/main.rs10
6 files changed, 139 insertions, 4 deletions
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 <LINT> [NOT_IMPL] Explain the meaning of the specified Lint
+ explain <LINT> Explain the meaning of the specified Lint
PROJECT MANAGE:
proj-new <NAME> [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<LintEntry>,
+}
+
+#[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<LintRegistry> = 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 <LINT>");
+}
+
+#[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"
}
}