aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli/src/linter/cmd_explain.rs
blob: ab7a08fbeb5be5c7b0df43efe2855ee8ce1616c7 (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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use mingling::{
    Grouped, Routable, ShellContext, StructuralData, Suggest, SuggestItem,
    macros::{
        arg, buffer, chain, completion, 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);
    }
}

#[completion(EntryExplain)]
pub fn complete_explain(ctx: &ShellContext) -> Suggest {
    if ctx.previous_word != "explain" {
        return Suggest::FileCompletion;
    }
    let lints: Vec<String> = lint_registry()
        .lints
        .iter()
        .map(|l| l.name.clone())
        .collect();
    let mut suggest = Suggest::new();
    for lint in lints {
        suggest.insert(SuggestItem::Simple(lint));
    }
    suggest
}