rigsql_rules/references/
rf04.rs1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
11pub struct RuleRF04;
12
13const RESERVED_KEYWORDS: &[&str] = &[
14 "SELECT",
15 "FROM",
16 "WHERE",
17 "INSERT",
18 "UPDATE",
19 "DELETE",
20 "CREATE",
21 "DROP",
22 "ALTER",
23 "TABLE",
24 "INDEX",
25 "VIEW",
26 "JOIN",
27 "ON",
28 "AND",
29 "OR",
30 "NOT",
31 "IN",
32 "IS",
33 "NULL",
34 "TRUE",
35 "FALSE",
36 "BETWEEN",
37 "LIKE",
38 "ORDER",
39 "BY",
40 "GROUP",
41 "HAVING",
42 "LIMIT",
43 "OFFSET",
44 "UNION",
45 "EXCEPT",
46 "INTERSECT",
47 "INTO",
48 "VALUES",
49 "SET",
50 "AS",
51 "CASE",
52 "WHEN",
53 "THEN",
54 "ELSE",
55 "END",
56 "EXISTS",
57 "ALL",
58 "ANY",
59 "DISTINCT",
60 "TOP",
61 "COUNT",
62 "SUM",
63 "AVG",
64 "MIN",
65 "MAX",
66 "CAST",
67 "COALESCE",
68 "NULLIF",
69];
70
71impl Rule for RuleRF04 {
72 fn code(&self) -> &'static str {
73 "RF04"
74 }
75 fn name(&self) -> &'static str {
76 "references.keywords"
77 }
78 fn description(&self) -> &'static str {
79 "Keywords should not be used as identifiers."
80 }
81 fn explanation(&self) -> &'static str {
82 "Using SQL reserved keywords as identifiers (column names, table names, aliases) \
83 can cause parsing ambiguity, confuse readers, and reduce portability across \
84 SQL dialects. Use descriptive, non-reserved names instead."
85 }
86 fn groups(&self) -> &[RuleGroup] {
87 &[RuleGroup::References]
88 }
89 fn is_fixable(&self) -> bool {
90 false
91 }
92
93 fn crawl_type(&self) -> CrawlType {
94 CrawlType::Segment(vec![SegmentType::Identifier])
95 }
96
97 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
98 let Segment::Token(t) = ctx.segment else {
99 return vec![];
100 };
101
102 let upper = t.token.text.to_ascii_uppercase();
103 if RESERVED_KEYWORDS.contains(&upper.as_str()) {
104 vec![LintViolation::with_msg_key(
105 self.code(),
106 format!("Identifier '{}' is a reserved keyword.", t.token.text),
107 t.token.span,
108 "rules.RF04.msg",
109 vec![("name".to_string(), t.token.text.to_string())],
110 )]
111 } else {
112 vec![]
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::test_utils::lint_sql;
121
122 #[test]
123 fn test_rf04_flags_keyword_as_identifier() {
124 let violations = lint_sql("SELECT id FROM \"table\"", RuleRF04);
126 assert_eq!(violations.len(), 0);
128 }
129
130 #[test]
131 fn test_rf04_accepts_normal_identifiers() {
132 let violations = lint_sql("SELECT user_id, email FROM users", RuleRF04);
133 assert_eq!(violations.len(), 0);
134 }
135
136 #[test]
137 fn test_rf04_flags_keyword_identifier_in_alias() {
138 let violations = lint_sql("SELECT 1 AS \"values\"", RuleRF04);
140 assert_eq!(violations.len(), 0);
142 }
143}