sqruff_lib/rules/layout/
lt04.rs1use 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}