rigsql_rules/references/
rf05.rs1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
12pub struct RuleRF05;
13
14impl Rule for RuleRF05 {
15 fn code(&self) -> &'static str {
16 "RF05"
17 }
18 fn name(&self) -> &'static str {
19 "references.special_chars"
20 }
21 fn description(&self) -> &'static str {
22 "Identifiers should not contain special characters."
23 }
24 fn explanation(&self) -> &'static str {
25 "Bare identifiers should only contain alphanumeric characters and underscores. \
26 Special characters (spaces, hyphens, etc.) in identifiers require quoting and \
27 can cause portability issues. If special characters are needed, use quoted \
28 identifiers explicitly."
29 }
30 fn groups(&self) -> &[RuleGroup] {
31 &[RuleGroup::References]
32 }
33 fn is_fixable(&self) -> bool {
34 false
35 }
36
37 fn crawl_type(&self) -> CrawlType {
38 CrawlType::Segment(vec![SegmentType::Identifier])
39 }
40
41 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
42 let Segment::Token(t) = ctx.segment else {
43 return vec![];
44 };
45
46 let text = &t.token.text;
47 let has_special = text.chars().any(|c| !c.is_alphanumeric() && c != '_');
48
49 if has_special {
50 vec![LintViolation::new(
51 self.code(),
52 format!("Identifier '{}' contains special characters.", text),
53 t.token.span,
54 )]
55 } else {
56 vec![]
57 }
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use crate::test_utils::lint_sql;
65
66 #[test]
67 fn test_rf05_accepts_normal_identifiers() {
68 let violations = lint_sql("SELECT user_id, email FROM users_2024", RuleRF05);
69 assert_eq!(violations.len(), 0);
70 }
71
72 #[test]
73 fn test_rf05_accepts_quoted_identifiers() {
74 let violations = lint_sql("SELECT \"my-col\" FROM t", RuleRF05);
76 assert_eq!(violations.len(), 0);
77 }
78}