1use super::*;
2use crate::ast_util::range;
3use std::convert::Infallible;
4
5use full_moon::{
6 ast::{self, Ast, TableConstructor},
7 visitors::Visitor,
8};
9
10use serde::Deserialize;
11
12#[derive(Clone, Copy, Deserialize)]
13pub struct HighCyclomaticComplexityConfig {
14 maximum_complexity: u16,
15}
16
17impl Default for HighCyclomaticComplexityConfig {
18 fn default() -> Self {
19 Self {
20 maximum_complexity: 40,
22 }
23 }
24}
25
26#[derive(Default)]
27pub struct HighCyclomaticComplexityLint {
28 config: HighCyclomaticComplexityConfig,
29}
30
31impl Lint for HighCyclomaticComplexityLint {
32 type Config = HighCyclomaticComplexityConfig;
33 type Error = Infallible;
34
35 const SEVERITY: Severity = Severity::Allow;
36 const LINT_TYPE: LintType = LintType::Style;
37
38 fn new(config: Self::Config) -> Result<Self, Self::Error> {
39 Ok(HighCyclomaticComplexityLint { config })
40 }
41
42 fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
43 let mut visitor = HighCyclomaticComplexityVisitor {
44 positions: Vec::new(),
45 config: self.config,
46 };
47
48 visitor.visit_ast(ast);
49
50 visitor
51 .positions
52 .into_iter()
53 .map(|(position, complexity)| {
54 Diagnostic::new(
55 "high_cyclomatic_complexity",
56 format!(
57 "cyclomatic complexity is too high ({complexity} > {})",
58 self.config.maximum_complexity
59 ),
60 Label::new(position),
61 )
62 })
63 .collect()
64 }
65}
66
67struct HighCyclomaticComplexityVisitor {
68 positions: Vec<((u32, u32), u16)>,
69 config: HighCyclomaticComplexityConfig,
70}
71
72fn count_table_complexity(table: &TableConstructor, starting_complexity: u16) -> u16 {
73 let mut complexity = starting_complexity;
74
75 for field in table.fields() {
76 #[cfg_attr(
77 feature = "force_exhaustive_checks",
78 deny(non_exhaustive_omitted_patterns)
79 )]
80 match field {
81 ast::Field::ExpressionKey { key, value, .. } => {
82 complexity = count_expression_complexity(key, complexity);
83 complexity = count_expression_complexity(value, complexity);
84 }
85
86 ast::Field::NameKey { value, .. } => {
87 complexity = count_expression_complexity(value, complexity);
88 }
89
90 ast::Field::NoKey(expression) => {
91 complexity = count_expression_complexity(expression, complexity);
92 }
93
94 _ => {}
95 }
96 }
97 complexity
98}
99
100fn count_arguments_complexity(function_args: &ast::FunctionArgs, starting_complexity: u16) -> u16 {
101 let mut complexity = starting_complexity;
102
103 #[cfg_attr(
104 feature = "force_exhaustive_checks",
105 deny(non_exhaustive_omitted_patterns)
106 )]
107 match function_args {
108 ast::FunctionArgs::Parentheses { arguments, .. } => {
109 for argument in arguments {
110 complexity = count_expression_complexity(argument, complexity)
111 }
112 complexity
113 }
114 ast::FunctionArgs::TableConstructor(table) => {
115 complexity = count_table_complexity(table, complexity);
116 complexity
117 }
118 ast::FunctionArgs::String(_) => complexity,
119 _ => complexity,
120 }
121}
122
123fn count_suffix_complexity(suffix: &ast::Suffix, starting_complexity: u16) -> u16 {
124 let mut complexity = starting_complexity;
125
126 #[cfg_attr(
127 feature = "force_exhaustive_checks",
128 deny(non_exhaustive_omitted_patterns)
129 )]
130 match suffix {
131 ast::Suffix::Index(ast::Index::Brackets { expression, .. }) => {
132 complexity = count_expression_complexity(expression, complexity)
133 }
134 ast::Suffix::Index(ast::Index::Dot { .. }) => {
135 }
137 ast::Suffix::Call(call) => match call {
138 ast::Call::AnonymousCall(arguments) => {
139 complexity = count_arguments_complexity(arguments, complexity)
140 }
141 ast::Call::MethodCall(method_call) => {
142 complexity = count_arguments_complexity(method_call.args(), complexity)
143 }
144 _ => {}
145 },
146 _ => {}
147 }
148
149 complexity
150}
151
152fn count_expression_complexity(expression: &ast::Expression, starting_complexity: u16) -> u16 {
153 let mut complexity = starting_complexity;
154
155 #[cfg_attr(
156 feature = "force_exhaustive_checks",
157 deny(non_exhaustive_omitted_patterns)
158 )]
159 match expression {
160 ast::Expression::BinaryOperator {
161 lhs, binop, rhs, ..
162 } => {
163 #[cfg_attr(
164 feature = "force_exhaustive_checks",
165 allow(non_exhaustive_omitted_patterns)
166 )]
167 if matches!(binop, ast::BinOp::And(_) | ast::BinOp::Or(_)) {
168 complexity += 1;
169 }
170
171 complexity = count_expression_complexity(lhs, complexity);
172 complexity = count_expression_complexity(rhs, complexity);
173
174 complexity
175 }
176
177 ast::Expression::Parentheses { expression, .. } => {
178 count_expression_complexity(expression, complexity)
179 }
180
181 ast::Expression::UnaryOperator { expression, .. } => {
182 count_expression_complexity(expression, complexity)
183 }
184
185 ast::Expression::Function(_) => complexity,
187
188 ast::Expression::FunctionCall(call) => {
189 if let ast::Prefix::Expression(prefix_expression) = call.prefix() {
190 complexity = count_expression_complexity(prefix_expression, complexity)
191 }
192 for suffix in call.suffixes() {
193 complexity = count_suffix_complexity(suffix, complexity)
194 }
195
196 complexity
197 }
198
199 ast::Expression::Number(_) => complexity,
200 ast::Expression::String(_) => complexity,
201 ast::Expression::Symbol(_) => complexity,
202
203 ast::Expression::TableConstructor(table) => count_table_complexity(table, complexity),
204
205 ast::Expression::Var(ast::Var::Expression(var_expression)) => {
206 for suffix in var_expression.suffixes() {
207 complexity = count_suffix_complexity(suffix, complexity)
208 }
209 complexity
210 }
211
212 ast::Expression::Var(ast::Var::Name(_)) => complexity,
213
214 #[cfg(feature = "roblox")]
215 ast::Expression::IfExpression(if_expression) => {
216 complexity += 1;
217 if let Some(else_if_expressions) = if_expression.else_if_expressions() {
218 for else_if_expression in else_if_expressions {
219 complexity += 1;
220 complexity =
221 count_expression_complexity(else_if_expression.expression(), complexity);
222 }
223 }
224 complexity
225 }
226
227 #[cfg(feature = "roblox")]
228 ast::Expression::InterpolatedString(interpolated_string) => {
229 for expression in interpolated_string.expressions() {
230 complexity = count_expression_complexity(expression, complexity)
231 }
232
233 complexity
234 }
235
236 #[cfg(feature = "roblox")]
237 ast::Expression::TypeAssertion { expression, .. } => {
238 count_expression_complexity(expression, complexity)
239 }
240
241 _ => complexity,
242 }
243}
244
245fn count_block_complexity(block: &ast::Block, starting_complexity: u16) -> u16 {
246 let mut complexity = starting_complexity;
247
248 for statement in block.stmts() {
250 #[cfg_attr(
251 feature = "force_exhaustive_checks",
252 deny(non_exhaustive_omitted_patterns)
253 )]
254 match statement {
255 ast::Stmt::Assignment(assignment) => {
256 for var in assignment.variables() {
257 if let ast::Var::Expression(var_expression) = var {
258 for suffix in var_expression.suffixes() {
259 complexity = count_suffix_complexity(suffix, complexity)
260 }
261 }
262 }
263 for expression in assignment.expressions() {
264 complexity = count_expression_complexity(expression, complexity);
265 }
266 }
267
268 ast::Stmt::Do(do_) => {
269 complexity = count_block_complexity(do_.block(), complexity);
270 }
271
272 ast::Stmt::FunctionCall(call) => {
273 if let ast::Prefix::Expression(prefix_expression) = call.prefix() {
274 complexity = count_expression_complexity(prefix_expression, complexity)
275 }
276 for suffix in call.suffixes() {
277 complexity = count_suffix_complexity(suffix, complexity)
278 }
279 }
280
281 ast::Stmt::FunctionDeclaration(_) => {}
283
284 ast::Stmt::GenericFor(generic_for) => {
285 complexity += 1;
286 for expression in generic_for.expressions() {
287 complexity = count_expression_complexity(expression, complexity);
288 }
289 complexity = count_block_complexity(generic_for.block(), complexity);
290 }
291
292 ast::Stmt::If(if_block) => {
293 complexity += 1;
294 complexity = count_expression_complexity(if_block.condition(), complexity);
295 complexity = count_block_complexity(if_block.block(), complexity);
296
297 if let Some(else_if_statements) = if_block.else_if() {
298 for else_if in else_if_statements {
299 complexity += 1;
300 complexity = count_expression_complexity(else_if.condition(), complexity);
301 complexity = count_block_complexity(else_if.block(), complexity);
302 }
303 }
304 }
305
306 ast::Stmt::LocalAssignment(local_assignment) => {
307 for expression in local_assignment.expressions() {
308 complexity = count_expression_complexity(expression, complexity);
309 }
310 }
311
312 ast::Stmt::LocalFunction(_) => {}
314
315 ast::Stmt::NumericFor(numeric_for) => {
316 complexity += 1;
317 complexity = count_expression_complexity(numeric_for.start(), complexity);
318 complexity = count_expression_complexity(numeric_for.end(), complexity);
319
320 if let Some(step_expression) = numeric_for.step() {
321 complexity = count_expression_complexity(step_expression, complexity);
322 }
323
324 complexity = count_block_complexity(numeric_for.block(), complexity);
325 }
326
327 ast::Stmt::Repeat(repeat_block) => {
328 complexity = count_expression_complexity(repeat_block.until(), complexity + 1);
329 complexity = count_block_complexity(repeat_block.block(), complexity);
330 }
331
332 ast::Stmt::While(while_block) => {
333 complexity = count_expression_complexity(while_block.condition(), complexity + 1);
334 complexity = count_block_complexity(while_block.block(), complexity);
335 }
336
337 #[cfg(feature = "roblox")]
338 ast::Stmt::CompoundAssignment(compound_expression) => {
339 complexity = count_expression_complexity(compound_expression.rhs(), complexity)
340 }
341
342 #[cfg(feature = "roblox")]
343 ast::Stmt::ExportedTypeDeclaration(_) => {
344 }
346
347 #[cfg(feature = "roblox")]
348 ast::Stmt::TypeDeclaration(_) => {
349 }
351
352 #[cfg(feature = "lua52")]
353 ast::Stmt::Goto(_) => {
354 }
356
357 #[cfg(feature = "lua52")]
358 ast::Stmt::Label(_) => {
359 }
361
362 _ => {}
363 }
364 }
365
366 if let Some(ast::LastStmt::Return(return_stmt)) = block.last_stmt() {
367 for return_expression in return_stmt.returns() {
368 complexity = count_expression_complexity(return_expression, complexity);
369 }
370 }
371
372 complexity
373}
374
375impl Visitor for HighCyclomaticComplexityVisitor {
376 fn visit_local_function(&mut self, local_function: &ast::LocalFunction) {
377 let complexity = count_block_complexity(local_function.body().block(), 1);
378 if complexity > self.config.maximum_complexity {
379 self.positions.push((
380 (
381 range(local_function.function_token()).0,
382 range(local_function.body().parameters_parentheses()).1,
383 ),
384 complexity,
385 ));
386 }
387 }
388
389 fn visit_function_declaration(&mut self, function_declaration: &ast::FunctionDeclaration) {
390 let complexity = count_block_complexity(function_declaration.body().block(), 1);
391 if complexity > self.config.maximum_complexity {
392 self.positions.push((
393 (
394 range(function_declaration.function_token()).0,
395 range(function_declaration.body().parameters_parentheses()).1,
396 ),
397 complexity,
398 ));
399 }
400 }
401
402 fn visit_expression(&mut self, expression: &ast::Expression) {
403 if let ast::Expression::Function(function_box) = expression {
404 let function_body = &function_box.1;
405 let complexity = count_block_complexity(function_body.block(), 1);
406 if complexity > self.config.maximum_complexity {
407 self.positions.push((
408 (
409 expression.start_position().unwrap().bytes() as u32,
410 range(function_body.parameters_parentheses()).1,
411 ),
412 complexity,
413 ));
414 }
415 }
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::{super::test_util::test_lint, *};
422
423 #[test]
424 #[cfg(feature = "roblox")]
425 #[cfg_attr(debug_assertions, ignore)] fn test_high_cyclomatic_complexity() {
427 test_lint(
428 HighCyclomaticComplexityLint::new(HighCyclomaticComplexityConfig::default()).unwrap(),
429 "high_cyclomatic_complexity",
430 "high_cyclomatic_complexity",
431 );
432 }
433
434 #[test]
435 #[cfg(feature = "roblox")]
436 #[cfg_attr(debug_assertions, ignore)]
437 fn test_complex_var_expressions() {
438 test_lint(
439 HighCyclomaticComplexityLint::new(HighCyclomaticComplexityConfig::default()).unwrap(),
440 "high_cyclomatic_complexity",
441 "complex_var_expressions",
442 );
443 }
444
445 #[test]
446 fn test_lua51_basic_complexity() {
447 test_lint(
448 HighCyclomaticComplexityLint::new(HighCyclomaticComplexityConfig {
449 maximum_complexity: 1,
450 })
451 .unwrap(),
452 "high_cyclomatic_complexity",
453 "lua51_basic_complexity",
454 );
455 }
456}