rigsql_rules/convention/
cv05.rs1use rigsql_core::{Segment, SegmentType, TokenKind};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
10pub struct RuleCV05;
11
12impl Rule for RuleCV05 {
13 fn code(&self) -> &'static str {
14 "CV05"
15 }
16 fn name(&self) -> &'static str {
17 "convention.is_null"
18 }
19 fn description(&self) -> &'static str {
20 "Comparisons with NULL should use IS or IS NOT."
21 }
22 fn explanation(&self) -> &'static str {
23 "In SQL, NULL is not a value but the absence of one. Comparison operators \
24 (=, !=, <>) with NULL always return NULL (which is falsy). Use 'IS NULL' or \
25 'IS NOT NULL' instead of '= NULL' or '!= NULL'."
26 }
27 fn groups(&self) -> &[RuleGroup] {
28 &[RuleGroup::Convention]
29 }
30 fn is_fixable(&self) -> bool {
31 true
32 }
33
34 fn crawl_type(&self) -> CrawlType {
35 CrawlType::Segment(vec![SegmentType::BinaryExpression])
36 }
37
38 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
39 let children = ctx.segment.children();
40
41 let non_trivia: Vec<_> = children
44 .iter()
45 .filter(|c| !c.segment_type().is_trivia())
46 .collect();
47
48 if non_trivia.len() < 3 {
49 return vec![];
50 }
51
52 let op = non_trivia[1];
54 let is_comparison = matches!(op.segment_type(), SegmentType::ComparisonOperator);
55 if !is_comparison {
56 return vec![];
57 }
58
59 let lhs_is_null = is_null_literal(non_trivia[0]);
61 let rhs_is_null = non_trivia.get(2).is_some_and(|s| is_null_literal(s));
62
63 if lhs_is_null || rhs_is_null {
64 return vec![LintViolation::new(
65 self.code(),
66 "Use IS NULL or IS NOT NULL instead of comparison operator with NULL.",
67 ctx.segment.span(),
68 )];
69 }
70
71 vec![]
72 }
73}
74
75fn is_null_literal(seg: &Segment) -> bool {
76 match seg {
77 Segment::Token(t) => {
78 t.segment_type == SegmentType::NullLiteral
79 || (t.token.kind == TokenKind::Word && t.token.text.eq_ignore_ascii_case("NULL"))
80 }
81 Segment::Node(n) => n.segment_type == SegmentType::NullLiteral,
82 }
83}