selene_lib/lints/
unscoped_variables.rs

1use crate::ast_util::scopes::ReferenceWrite;
2
3use super::*;
4use std::collections::HashSet;
5
6use full_moon::ast::Ast;
7use regex::Regex;
8use serde::Deserialize;
9
10#[derive(Clone, Deserialize)]
11#[serde(default)]
12pub struct UnscopedVariablesConfig {
13    ignore_pattern: String,
14}
15
16impl Default for UnscopedVariablesConfig {
17    fn default() -> Self {
18        Self {
19            ignore_pattern: "^_".to_owned(),
20        }
21    }
22}
23
24pub struct UnscopedVariablesLint {
25    ignore_pattern: Regex,
26}
27
28impl Lint for UnscopedVariablesLint {
29    type Config = UnscopedVariablesConfig;
30    type Error = regex::Error;
31
32    const SEVERITY: Severity = Severity::Warning;
33    const LINT_TYPE: LintType = LintType::Complexity;
34
35    fn new(config: Self::Config) -> Result<Self, Self::Error> {
36        Ok(UnscopedVariablesLint {
37            ignore_pattern: Regex::new(&config.ignore_pattern)?,
38        })
39    }
40
41    fn pass(&self, _: &Ast, context: &Context, ast_context: &AstContext) -> Vec<Diagnostic> {
42        // ScopeManager repeats references, and I just don't want to fix it right now
43        let mut read = HashSet::new();
44
45        let mut diagnostics = Vec::new();
46
47        for (_, reference) in &ast_context.scope_manager.references {
48            if reference.resolved.is_none()
49                && reference.write == Some(ReferenceWrite::Assign)
50                && !read.contains(&reference.identifier)
51                && !self.ignore_pattern.is_match(&reference.name)
52                && !context.standard_library.global_has_fields(&reference.name)
53            {
54                read.insert(reference.identifier);
55
56                diagnostics.push(Diagnostic::new(
57                    "unscoped_variables",
58                    format!(
59                        "`{}` is not declared locally, and will be available in every scope",
60                        reference.name
61                    ),
62                    Label::new(reference.identifier),
63                ));
64            }
65        }
66
67        diagnostics
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::{super::test_util::*, *};
74
75    #[test]
76    fn test_function_overriding() {
77        test_lint(
78            UnscopedVariablesLint::new(UnscopedVariablesConfig::default()).unwrap(),
79            "unscoped_variables",
80            "function_overriding",
81        );
82    }
83
84    #[test]
85    fn test_unscoped_variables() {
86        test_lint(
87            UnscopedVariablesLint::new(UnscopedVariablesConfig::default()).unwrap(),
88            "unscoped_variables",
89            "unscoped_variables",
90        );
91    }
92}