Skip to main content

sqruff_lib/rules/layout/
lt04.rs

1use std::ops::Deref;
2
3use hashbrown::HashMap;
4use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
5
6use super::lt03::RuleLT03;
7use crate::core::config::Value;
8use crate::core::rules::context::RuleContext;
9use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
10use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
11use crate::utils::reflow::sequence::{RebreakType, ReflowSequence, TargetSide};
12
13#[derive(Debug, Default, Clone)]
14pub struct RuleLT04 {
15    base: RuleLT03,
16}
17
18impl Rule for RuleLT04 {
19    fn load_from_config(&self, _config: &HashMap<String, Value>) -> Result<ErasedRule, String> {
20        Ok(RuleLT04::default().erased())
21    }
22    fn name(&self) -> &'static str {
23        "layout.commas"
24    }
25
26    fn description(&self) -> &'static str {
27        "Leading/Trailing comma enforcement."
28    }
29
30    fn long_description(&self) -> &'static str {
31        r#"
32**Anti-pattern**
33
34There is a mixture of leading and trailing commas.
35
36```sql
37SELECT
38    a
39    , b,
40    c
41FROM foo
42```
43
44**Best practice**
45
46By default, sqruff prefers trailing commas. However it is configurable for leading commas. The chosen style must be used consistently throughout your SQL.
47
48```sql
49SELECT
50    a,
51    b,
52    c
53FROM foo
54
55-- Alternatively, set the configuration file to 'leading'
56-- and then the following would be acceptable:
57
58SELECT
59    a
60    , b
61    , c
62FROM foo
63```
64"#
65    }
66    fn groups(&self) -> &'static [RuleGroups] {
67        &[RuleGroups::All, RuleGroups::Layout]
68    }
69
70    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
71        let comma_positioning = context
72            .config
73            .reflow()
74            .line_position_for(SyntaxKind::Comma)
75            .unwrap()
76            .position();
77
78        if self.check_trail_lead_shortcut(
79            &context.segment,
80            context.parent_stack.last().unwrap(),
81            comma_positioning,
82        ) {
83            return vec![LintResult::new(None, Vec::new(), None, None)];
84        };
85
86        ReflowSequence::from_around_target(
87            &context.segment,
88            context.parent_stack.first().unwrap(),
89            TargetSide::Both,
90            context.config,
91        )
92        .rebreak(context.tables, RebreakType::Lines)
93        .results()
94    }
95
96    fn is_fix_compatible(&self) -> bool {
97        true
98    }
99
100    fn crawl_behaviour(&self) -> Crawler {
101        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::Comma]) }).into()
102    }
103}
104
105impl Deref for RuleLT04 {
106    type Target = RuleLT03;
107
108    fn deref(&self) -> &Self::Target {
109        &self.base
110    }
111}