mago_service/linter/
mod.rs

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
141
142
143
144
145
146
147
148
149
150
151
152
153
use mago_feedback::create_progress_bar;
use mago_feedback::remove_progress_bar;
use mago_feedback::ProgressBarTheme;
use mago_interner::ThreadedInterner;
use mago_linter::plugin::best_practices::BestPracticesPlugin;
use mago_linter::plugin::comment::CommentPlugin;
use mago_linter::plugin::consistency::ConsistencyPlugin;
use mago_linter::plugin::naming::NamingPlugin;
use mago_linter::plugin::redundancy::RedundancyPlugin;
use mago_linter::plugin::safety::SafetyPlugin;
use mago_linter::plugin::strictness::StrictnessPlugin;
use mago_linter::plugin::symfony::SymfonyPlugin;
use mago_linter::settings::RuleSettings;
use mago_linter::settings::Settings;
use mago_linter::Linter;
use mago_reporting::Issue;
use mago_reporting::IssueCollection;
use mago_reporting::Level;
use mago_semantics::Semantics;
use mago_source::error::SourceError;
use mago_source::SourceIdentifier;
use mago_source::SourceManager;

use crate::linter::config::LinterConfiguration;
use crate::linter::config::LinterLevel;

pub mod config;

#[derive(Debug)]
pub struct LintService {
    configuration: LinterConfiguration,
    interner: ThreadedInterner,
    source_manager: SourceManager,
}

impl LintService {
    pub fn new(configuration: LinterConfiguration, interner: ThreadedInterner, source_manager: SourceManager) -> Self {
        Self { configuration, interner, source_manager }
    }

    /// Runs the linting process and returns a stream of issues.
    pub async fn run(&self) -> Result<IssueCollection, SourceError> {
        // Initialize the linter
        let linter = self.initialize_linter();

        // Process sources concurrently
        self.process_sources(linter, self.source_manager.user_defined_source_ids().collect()).await
    }

    #[inline]
    async fn process_sources(
        &self,
        linter: Linter,
        source_ids: Vec<SourceIdentifier>,
    ) -> Result<IssueCollection, SourceError> {
        let mut handles = Vec::with_capacity(source_ids.len());

        let source_pb = create_progress_bar(source_ids.len(), "📂  Loading", ProgressBarTheme::Red);
        let semantics_pb = create_progress_bar(source_ids.len(), "🔬  Building", ProgressBarTheme::Blue);
        let lint_pb = create_progress_bar(source_ids.len(), "🧹  Linting", ProgressBarTheme::Cyan);

        for source_id in source_ids.into_iter() {
            handles.push(tokio::spawn({
                let interner = self.interner.clone();
                let manager = self.source_manager.clone();
                let linter = linter.clone();
                let source_pb = source_pb.clone();
                let semantics_pb = semantics_pb.clone();
                let lint_pb = lint_pb.clone();

                async move {
                    // Step 1: load the source
                    let source = manager.load(source_id)?;
                    source_pb.inc(1);

                    // Step 2: build semantics
                    let semantics = Semantics::build(&interner, source);
                    semantics_pb.inc(1);

                    // Step 3: Collect issues
                    let mut issues = linter.lint(&semantics);
                    issues.extend(semantics.issues);
                    if let Some(error) = &semantics.parse_error {
                        issues.push(Into::<Issue>::into(error));
                    }
                    lint_pb.inc(1);

                    Result::<_, SourceError>::Ok(issues)
                }
            }));
        }

        let mut results = Vec::with_capacity(handles.len());
        for handle in handles {
            results.push(handle.await.expect("failed to collect issues. this should never happen.")?);
        }

        remove_progress_bar(source_pb);
        remove_progress_bar(semantics_pb);
        remove_progress_bar(lint_pb);

        Ok(IssueCollection::from(results.into_iter().flatten()))
    }

    #[inline]
    fn initialize_linter(&self) -> Linter {
        let mut settings = Settings::new();

        if let Some(level) = self.configuration.level {
            settings = match level {
                LinterLevel::Off => settings.off(),
                LinterLevel::Help => settings.with_level(Level::Help),
                LinterLevel::Note => settings.with_level(Level::Note),
                LinterLevel::Warning => settings.with_level(Level::Warning),
                LinterLevel::Error => settings.with_level(Level::Error),
            };
        }

        if let Some(default_plugins) = self.configuration.default_plugins {
            settings = settings.with_default_plugins(default_plugins);
        }

        settings = settings.with_plugins(self.configuration.plugins.clone());

        for rule in &self.configuration.rules {
            let rule_settings = match rule.level {
                Some(linter_level) => match linter_level {
                    LinterLevel::Off => RuleSettings::disabled(),
                    LinterLevel::Help => RuleSettings::from_level(Some(Level::Help)),
                    LinterLevel::Note => RuleSettings::from_level(Some(Level::Note)),
                    LinterLevel::Warning => RuleSettings::from_level(Some(Level::Warning)),
                    LinterLevel::Error => RuleSettings::from_level(Some(Level::Error)),
                },
                None => RuleSettings::enabled(),
            };

            settings = settings.with_rule(rule.name.clone(), rule_settings.with_options(rule.options.clone()));
        }

        let mut linter = Linter::new(settings, self.interner.clone());

        linter.add_plugin(BestPracticesPlugin);
        linter.add_plugin(CommentPlugin);
        linter.add_plugin(ConsistencyPlugin);
        linter.add_plugin(NamingPlugin);
        linter.add_plugin(RedundancyPlugin);
        linter.add_plugin(SafetyPlugin);
        linter.add_plugin(StrictnessPlugin);
        linter.add_plugin(SymfonyPlugin);

        linter
    }
}