selene_lib/lints/
undefined_variable.rs1use 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 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
59fn 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}