selene_lib/lints/
undefined_variable.rs

1use super::*;
2use crate::{
3    ast_util::scopes::{Reference, ScopeManager},
4    possible_std::possible_standard_library_notes,
5};
6use std::{collections::HashSet, convert::Infallible};
7
8use full_moon::ast::Ast;
9
10pub struct UndefinedVariableLint;
11
12lazy_static::lazy_static! {
13    static ref VARARG_STRING: String = "...".to_owned();
14}
15
16impl Lint for UndefinedVariableLint {
17    type Config = ();
18    type Error = Infallible;
19
20    const SEVERITY: Severity = Severity::Error;
21    const LINT_TYPE: LintType = LintType::Correctness;
22
23    fn new(_: Self::Config) -> Result<Self, Self::Error> {
24        Ok(UndefinedVariableLint)
25    }
26
27    fn pass(&self, _: &Ast, context: &Context, ast_context: &AstContext) -> Vec<Diagnostic> {
28        // ScopeManager repeats references, and I just don't want to fix it right now
29        let mut read = HashSet::new();
30
31        let mut diagnostics = Vec::new();
32
33        for (_, reference) in &ast_context.scope_manager.references {
34            if reference.resolved.is_none()
35                && reference.read
36                && !read.contains(&reference.identifier)
37                && !is_valid_vararg_reference(&ast_context.scope_manager, reference)
38                && !context.standard_library.global_has_fields(&reference.name)
39            {
40                read.insert(reference.identifier);
41
42                diagnostics.push(Diagnostic::new_complete(
43                    "undefined_variable",
44                    format!("`{}` is not defined", reference.name),
45                    Label::new(reference.identifier),
46                    possible_standard_library_notes(
47                        &[reference.name.as_str()],
48                        &context.user_set_standard_library,
49                    ),
50                    Vec::new(),
51                ));
52            }
53        }
54
55        diagnostics
56    }
57}
58
59// `...` is valid in the opening scope, but everywhere else must be explicitly defined.
60fn is_valid_vararg_reference(scope_manager: &ScopeManager, reference: &Reference) -> bool {
61    Some(reference.scope_id) == scope_manager.initial_scope && reference.name == *VARARG_STRING
62}
63
64#[cfg(test)]
65mod tests {
66    use super::{super::test_util::*, *};
67
68    #[test]
69    fn test_basic() {
70        test_lint(
71            UndefinedVariableLint::new(()).unwrap(),
72            "undefined_variable",
73            "basic",
74        );
75    }
76
77    #[test]
78    #[cfg(feature = "roblox")]
79    fn test_compound_assignments() {
80        test_lint(
81            UndefinedVariableLint::new(()).unwrap(),
82            "undefined_variable",
83            "compound_assignments",
84        );
85    }
86
87    #[test]
88    fn test_function_overriding() {
89        test_lint(
90            UndefinedVariableLint::new(()).unwrap(),
91            "undefined_variable",
92            "function_overriding",
93        );
94    }
95
96    #[test]
97    fn test_hoisting() {
98        test_lint(
99            UndefinedVariableLint::new(()).unwrap(),
100            "undefined_variable",
101            "hoisting",
102        );
103    }
104
105    #[test]
106    fn test_self() {
107        test_lint(
108            UndefinedVariableLint::new(()).unwrap(),
109            "undefined_variable",
110            "self",
111        );
112    }
113
114    #[test]
115    fn test_shadowing() {
116        test_lint(
117            UndefinedVariableLint::new(()).unwrap(),
118            "undefined_variable",
119            "shadowing",
120        );
121    }
122
123    #[test]
124    fn test_varargs() {
125        test_lint(
126            UndefinedVariableLint::new(()).unwrap(),
127            "undefined_variable",
128            "varargs",
129        );
130    }
131
132    #[cfg(feature = "roblox")]
133    #[test]
134    fn test_string_interpolation() {
135        test_lint(
136            UndefinedVariableLint::new(()).unwrap(),
137            "undefined_variable",
138            "string_interpolation",
139        );
140    }
141}