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::with_msg_key(
51 self.code(),
52 format!("Identifier '{}' contains special characters.", text),
53 t.token.span,
54 "rules.RF05.msg",
55 vec![("name".to_string(), text.to_string())],
56 )]
57 } else {
58 vec![]
59 }
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use crate::test_utils::lint_sql;
67
68 #[test]
69 fn test_rf05_accepts_normal_identifiers() {
70 let violations = lint_sql("SELECT user_id, email FROM users_2024", RuleRF05);
71 assert_eq!(violations.len(), 0);
72 }
73
74 #[test]
75 fn test_rf05_accepts_quoted_identifiers() {
76 let violations = lint_sql("SELECT \"my-col\" FROM t", RuleRF05);
78 assert_eq!(violations.len(), 0);
79 }
80}