mago_linter/
lib.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
use std::sync::Arc;
use std::sync::RwLock;

use mago_interner::ThreadedInterner;
use mago_reporting::IssueCollection;
use mago_semantics::Semantics;

use crate::context::Context;
use crate::plugin::Plugin;
use crate::rule::ConfiguredRule;
use crate::rule::Rule;
use crate::settings::RuleSettings;
use crate::settings::Settings;

pub mod consts;
pub mod context;
pub mod plugin;
pub mod rule;
pub mod settings;

#[derive(Debug, Clone)]
pub struct Linter {
    settings: Settings,
    interner: ThreadedInterner,
    rules: Arc<RwLock<Vec<ConfiguredRule>>>,
}

impl Linter {
    pub fn new(settings: Settings, interner: ThreadedInterner) -> Self {
        Self { settings, interner, rules: Arc::new(RwLock::new(Vec::new())) }
    }

    pub fn add_plugin(&mut self, plugin: impl Plugin) {
        let name = plugin.get_name();

        tracing::info!("Adding plugin `{name}`...");

        let enabled = self.settings.plugins.iter().any(|p| p.eq(name));
        if !enabled {
            if self.settings.default_plugins && plugin.is_enabled_by_default() {
                tracing::info!("Enabling default plugin `{name}`.");
            } else {
                tracing::debug!(
                    "Plugin `{name}` is not enabled in the configuration and is not a default plugin. Skipping."
                );

                return;
            }
        } else {
            tracing::info!("Enabling plugin `{name}`.");
        }

        for rule in plugin.get_rules() {
            self.add_rule(name, rule);
        }
    }

    pub fn add_rule(&mut self, plugin: impl Into<String>, rule: Box<dyn Rule>) {
        let plugin = plugin.into();
        let rule_name = rule.get_name();
        let full_name = format!("{}/{}", plugin, rule_name);

        tracing::info!("Adding rule `{full_name}`...");

        let settings = self.settings.get_rule_settings(full_name.as_str()).cloned().unwrap_or_else(|| {
            tracing::debug!("No configuration found for rule `{full_name}`, using default.");

            RuleSettings::from_level(rule.get_default_level())
        });

        if !settings.enabled {
            tracing::debug!("Rule `{full_name}` is configured to be off. Skipping.");

            return;
        }

        let level = match settings.level {
            Some(level) => level,
            None => match rule.get_default_level() {
                Some(level) => level,
                None => {
                    tracing::warn!("Rule `{full_name}` does not have a default level. Skipping.");

                    return;
                }
            },
        };

        tracing::info!("Enabling rule `{full_name}` with level `{level:?}`.");

        self.rules.write().expect("Unable to add rule: poisoned lock").push(ConfiguredRule {
            level,
            settings,
            plugin,
            rule,
        });
    }

    pub fn lint(&self, semantics: &Semantics) -> IssueCollection {
        let source_name = self.interner.lookup(&semantics.source.identifier.value());

        tracing::debug!("Linting source `{}`...", source_name);

        let mut context = Context::new(&self.interner, semantics);

        let configured_rules = self.rules.read().expect("Unable to read rules: poisoned lock");

        tracing::debug!("Linting source `{}` with {} rules...", source_name, configured_rules.len());

        for configured_rule in configured_rules.iter() {
            tracing::trace!("Running rule `{}`...", configured_rule.rule.get_name());

            let mut lint_context = context.for_rule(configured_rule);

            configured_rule.rule.as_ref().lint(&semantics.program, &mut lint_context);
        }

        context.take_issue_collection()
    }
}