react_auditor/rules/quality/
no_empty_blocks.rs1use oxc_ast::ast::{Program, Statement};
2use oxc_ast_visit::Visit;
3use oxc_ast_visit::walk::walk_statement;
4use oxc_semantic::Semantic;
5
6use crate::rules::{Fix, Rule, RuleFinding, RuleMeta, Severity};
7
8pub struct NoEmptyBlocks;
9
10const RULE_META: RuleMeta = RuleMeta {
11 id: "no-empty-blocks",
12 default_severity: Severity::Warning,
13 category: "quality",
14 description: "No empty if/while/for/try/catch/finally blocks",
15};
16
17impl Rule for NoEmptyBlocks {
18 fn meta(&self) -> &RuleMeta {
19 &RULE_META
20 }
21
22 fn run(&self, program: &Program, _semantic: &Semantic, source_text: &str) -> Vec<RuleFinding> {
23 let mut collector = EmptyBlockCollector {
24 findings: Vec::new(),
25 source: source_text,
26 };
27 collector.visit_program(program);
28 collector.findings
29 }
30
31 fn has_fix(&self) -> bool {
32 true
33 }
34
35 fn fix(&self, finding: &RuleFinding, source_text: &str) -> Option<Fix> {
36 let start = crate::rules::line_col_to_offset(source_text, finding.line, finding.column)?;
37 let after = &source_text[start..];
38 let close = after.find('}')?;
39 Some(Fix {
40 start,
41 end: start + close + 1,
42 replacement: String::new(),
43 })
44 }
45}
46
47struct EmptyBlockCollector<'a> {
48 findings: Vec<RuleFinding>,
49 source: &'a str,
50}
51
52impl<'a> Visit<'a> for EmptyBlockCollector<'a> {
53 fn visit_statement(&mut self, stmt: &Statement<'a>) {
54 match stmt {
55 Statement::IfStatement(if_stmt) => {
56 if let Some(alt) = &if_stmt.alternate
57 && let Statement::BlockStatement(block) = alt
58 && block.body.is_empty()
59 {
60 let start = block.span.start as usize;
61 let line = self.source[..start].lines().count().max(1);
62 let col = start - self.source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
63 self.findings.push(RuleFinding {
64 line,
65 column: col + 1,
66 message: "Empty else block".to_string(),
67 });
68 }
69 if let Statement::BlockStatement(block) = &if_stmt.consequent
70 && block.body.is_empty()
71 {
72 let start = block.span.start as usize;
73 let line = self.source[..start].lines().count().max(1);
74 let col = start - self.source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
75 self.findings.push(RuleFinding {
76 line,
77 column: col + 1,
78 message: "Empty if block".to_string(),
79 });
80 }
81 }
82 Statement::WhileStatement(while_stmt) => {
83 if let Statement::BlockStatement(block) = &while_stmt.body
84 && block.body.is_empty()
85 {
86 let start = block.span.start as usize;
87 let line = self.source[..start].lines().count().max(1);
88 let col = start - self.source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
89 self.findings.push(RuleFinding {
90 line,
91 column: col + 1,
92 message: "Empty while loop body".to_string(),
93 });
94 }
95 }
96 Statement::ForStatement(for_stmt) => {
97 if let Statement::BlockStatement(block) = &for_stmt.body
98 && block.body.is_empty()
99 {
100 let start = block.span.start as usize;
101 let line = self.source[..start].lines().count().max(1);
102 let col = start - self.source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
103 self.findings.push(RuleFinding {
104 line,
105 column: col + 1,
106 message: "Empty for loop body".to_string(),
107 });
108 }
109 }
110 Statement::TryStatement(try_stmt) => {
111 if let Some(handler) = &try_stmt.handler
112 && handler.body.body.is_empty()
113 {
114 let start = handler.body.span.start as usize;
115 let line = self.source[..start].lines().count().max(1);
116 let col = start - self.source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
117 self.findings.push(RuleFinding {
118 line,
119 column: col + 1,
120 message: "Empty catch block".to_string(),
121 });
122 }
123 if let Some(finalizer) = &try_stmt.finalizer
124 && finalizer.body.is_empty()
125 {
126 let start = finalizer.span.start as usize;
127 let line = self.source[..start].lines().count().max(1);
128 let col = start - self.source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
129 self.findings.push(RuleFinding {
130 line,
131 column: col + 1,
132 message: "Empty finally block".to_string(),
133 });
134 }
135 }
136 _ => {}
137 }
138
139 walk_statement(self, stmt);
140 }
141}