aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli/src/linter/cmd_explain.rs
blob: d76cf65ee79c76f69c5ac1076e3dfca7e9eb60e6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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);
    }
}