rigsql_rules/aliasing/
al05.rs1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
10pub struct RuleAL05;
11
12impl Rule for RuleAL05 {
13 fn code(&self) -> &'static str {
14 "AL05"
15 }
16 fn name(&self) -> &'static str {
17 "aliasing.unused"
18 }
19 fn description(&self) -> &'static str {
20 "Tables/CTEs should not be unused."
21 }
22 fn explanation(&self) -> &'static str {
23 "Every CTE (Common Table Expression) defined in a WITH clause should be \
24 referenced in the main query or in another CTE. Unused CTEs add complexity \
25 without benefit and should be removed."
26 }
27 fn groups(&self) -> &[RuleGroup] {
28 &[RuleGroup::Aliasing]
29 }
30 fn is_fixable(&self) -> bool {
31 false
32 }
33
34 fn crawl_type(&self) -> CrawlType {
35 CrawlType::Segment(vec![SegmentType::WithClause])
36 }
37
38 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
39 let children = ctx.segment.children();
40
41 let mut cte_names: Vec<(String, rigsql_core::Span)> = Vec::new();
43 for child in children {
44 if child.segment_type() == SegmentType::CteDefinition {
45 if let Some(name) = extract_cte_name(child) {
46 cte_names.push((name.to_lowercase(), child.span()));
47 }
48 }
49 }
50
51 if cte_names.is_empty() {
52 return vec![];
53 }
54
55 let statement = ctx.parent.unwrap_or(ctx.root);
57 let raw = statement.raw().to_lowercase();
58
59 let mut violations = Vec::new();
60 for (name, span) in &cte_names {
61 let count = raw.matches(name.as_str()).count();
64 if count <= 1 {
66 violations.push(LintViolation::new(
67 self.code(),
68 format!("CTE '{}' is defined but not used.", name),
69 *span,
70 ));
71 }
72 }
73
74 violations
75 }
76}
77
78fn extract_cte_name(cte_def: &Segment) -> Option<String> {
79 for child in cte_def.children() {
81 let st = child.segment_type();
82 if st == SegmentType::Identifier || st == SegmentType::QuotedIdentifier {
83 if let Segment::Token(t) = child {
84 return Some(t.token.text.to_string());
85 }
86 }
87 if st == SegmentType::Keyword {
88 break;
90 }
91 }
92 None
93}