1use crate::{ast_util::scopes::ScopeManager, standard_library::StandardLibrary};
2use std::convert::TryInto;
3
4use codespan_reporting::diagnostic::{
5 Diagnostic as CodespanDiagnostic, Label as CodespanLabel, Severity as CodespanSeverity,
6};
7use full_moon::{ast::Ast, node::Node};
8use serde::de::DeserializeOwned;
9
10pub mod almost_swapped;
11pub mod bad_string_escape;
12pub mod compare_nan;
13pub mod constant_table_comparison;
14pub mod deprecated;
15pub mod divide_by_zero;
16pub mod duplicate_keys;
17pub mod empty_if;
18pub mod empty_loop;
19pub mod global_usage;
20pub mod high_cyclomatic_complexity;
21pub mod if_same_then_else;
22pub mod ifs_same_cond;
23pub mod invalid_lint_filter;
24pub mod manual_table_clone;
25pub mod mismatched_arg_count;
26pub mod mixed_table;
27pub mod multiple_statements;
28pub mod must_use;
29pub mod parenthese_conditions;
30pub mod shadowing;
31pub mod standard_library;
32pub mod suspicious_reverse_loop;
33pub mod type_check_inside_call;
34pub mod unbalanced_assignments;
35pub mod undefined_variable;
36pub mod unscoped_variables;
37pub mod unused_variable;
38
39#[cfg(feature = "roblox")]
40pub mod roblox_incorrect_color3_new_bounds;
41
42#[cfg(feature = "roblox")]
43pub mod roblox_incorrect_roact_usage;
44
45#[cfg(feature = "roblox")]
46pub mod roblox_suspicious_udim2_new;
47
48#[cfg(test)]
49mod test_util;
50
51pub trait Lint {
52 type Config: DeserializeOwned;
53 type Error: std::error::Error;
54
55 const SEVERITY: Severity;
56 const LINT_TYPE: LintType;
57
58 fn new(config: Self::Config) -> Result<Self, Self::Error>
59 where
60 Self: Sized;
61
62 fn pass(
63 &self,
64 ast: &full_moon::ast::Ast,
65 context: &Context,
66 ast_context: &AstContext,
67 ) -> Vec<Diagnostic>;
68}
69
70pub enum LintType {
71 Complexity,
73
74 Correctness,
77
78 Performance,
80
81 Style,
83}
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub enum Severity {
87 Allow,
88 Error,
89 Warning,
90}
91
92#[derive(Debug)]
93pub struct Diagnostic {
94 pub code: &'static str,
95 pub message: String,
96 pub notes: Vec<String>,
97 pub primary_label: Label,
98 pub secondary_labels: Vec<Label>,
99}
100
101impl Diagnostic {
102 pub fn new(code: &'static str, message: String, primary_label: Label) -> Self {
103 Self {
104 code,
105 message,
106 primary_label,
107
108 notes: Vec::new(),
109 secondary_labels: Vec::new(),
110 }
111 }
112
113 pub fn new_complete(
114 code: &'static str,
115 message: String,
116 primary_label: Label,
117 notes: Vec<String>,
118 secondary_labels: Vec<Label>,
119 ) -> Self {
120 Self {
121 code,
122 message,
123 notes,
124 primary_label,
125 secondary_labels,
126 }
127 }
128
129 pub fn into_codespan_diagnostic(
130 self,
131 file_id: codespan::FileId,
132 severity: CodespanSeverity,
133 ) -> CodespanDiagnostic<codespan::FileId> {
134 let mut labels = Vec::with_capacity(1 + self.secondary_labels.len());
135 labels.push(self.primary_label.codespan_label(file_id));
136 labels.extend(&mut self.secondary_labels.iter().map(|label| {
137 CodespanLabel::secondary(file_id, codespan::Span::new(label.range.0, label.range.1))
138 .with_message(label.message.as_ref().unwrap_or(&"".to_owned()).to_owned())
139 }));
140
141 CodespanDiagnostic {
142 code: Some(self.code.to_owned()),
143 labels,
144 message: self.message.to_owned(),
145 notes: self.notes,
146 severity,
147 }
148 }
149
150 pub fn start_position(&self) -> u32 {
151 self.primary_label.range.0
152 }
153}
154
155#[derive(Debug, PartialEq, Eq)]
156pub struct Label {
157 pub message: Option<String>,
158 pub range: (u32, u32),
159}
160
161impl Label {
162 pub fn new<P: TryInto<u32>>(range: (P, P)) -> Label {
163 let range = (
164 range
165 .0
166 .try_into()
167 .unwrap_or_else(|_| panic!("TryInto failed for Label::new range")),
168 range
169 .1
170 .try_into()
171 .unwrap_or_else(|_| panic!("TryInto failed for Label::new range")),
172 );
173
174 Label {
175 range,
176 message: None,
177 }
178 }
179
180 pub fn from_node<N: Node>(node: N, message: Option<String>) -> Label {
181 let (start, end) = node.range().expect("node passed returned a None range");
182
183 Label {
184 message,
185 range: (start.bytes() as u32, end.bytes() as u32),
186 }
187 }
188
189 pub fn new_with_message<P: TryInto<u32>>(range: (P, P), message: String) -> Label {
190 let range = (
191 range
192 .0
193 .try_into()
194 .unwrap_or_else(|_| panic!("TryInto failed for Label::new range")),
195 range
196 .1
197 .try_into()
198 .unwrap_or_else(|_| panic!("TryInto failed for Label::new range")),
199 );
200
201 Label {
202 range,
203 message: Some(message),
204 }
205 }
206
207 pub fn codespan_label(&self, file_id: codespan::FileId) -> CodespanLabel<codespan::FileId> {
208 CodespanLabel::primary(
209 file_id.to_owned(),
210 codespan::Span::new(self.range.0, self.range.1),
211 )
212 .with_message(self.message.as_ref().unwrap_or(&"".to_owned()).to_owned())
213 }
214}
215
216#[derive(Clone, Debug)]
217pub struct Context {
218 pub standard_library: StandardLibrary,
219 pub user_set_standard_library: Option<Vec<String>>,
220}
221
222impl Context {
223 #[cfg(feature = "roblox")]
224 pub fn is_roblox(&self) -> bool {
225 self.standard_library.name.as_deref() == Some("roblox")
226 }
227
228 #[cfg(not(feature = "roblox"))]
229 pub fn is_roblox(&self) -> bool {
230 false
231 }
232}
233
234#[derive(Debug)]
235pub struct AstContext {
236 pub scope_manager: ScopeManager,
237}
238
239impl AstContext {
240 pub fn from_ast(ast: &Ast) -> Self {
241 Self {
242 scope_manager: ScopeManager::new(ast),
243 }
244 }
245}