selene_lib/lints/
unscoped_variables.rs1use 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 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}