Skip to main content

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_config(
81            UndefinedVariableLint::new(()).unwrap(),
82            "undefined_variable",
83            "compound_assignments",
84            TestUtilConfig::luau(),
85        );
86    }
87
88    #[test]
89    fn test_function_overriding() {
90        test_lint(
91            UndefinedVariableLint::new(()).unwrap(),
92            "undefined_variable",
93            "function_overriding",
94        );
95    }
96
97    #[test]
98    fn test_hoisting() {
99        test_lint(
100            UndefinedVariableLint::new(()).unwrap(),
101            "undefined_variable",
102            "hoisting",
103        );
104    }
105
106    #[test]
107    fn test_self() {
108        test_lint(
109            UndefinedVariableLint::new(()).unwrap(),
110            "undefined_variable",
111            "self",
112        );
113    }
114
115    #[test]
116    fn test_shadowing() {
117        test_lint(
118            UndefinedVariableLint::new(()).unwrap(),
119            "undefined_variable",
120            "shadowing",
121        );
122    }
123
124    #[test]
125    fn test_varargs() {
126        test_lint(
127            UndefinedVariableLint::new(()).unwrap(),
128            "undefined_variable",
129            "varargs",
130        );
131    }
132
133    #[cfg(feature = "roblox")]
134    #[test]
135    fn test_string_interpolation() {
136        test_lint_config(
137            UndefinedVariableLint::new(()).unwrap(),
138            "undefined_variable",
139            "string_interpolation",
140            TestUtilConfig::luau(),
141        );
142    }
143}