1use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::collections::{BTreeMap, HashSet};
6
7#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
11#[serde(rename_all = "camelCase")]
12pub struct LintConfig {
13 #[serde(default = "default_enabled")]
15 pub enabled: bool,
16
17 #[serde(default, skip_serializing_if = "Vec::is_empty")]
19 pub disabled_rules: Vec<String>,
20
21 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
24 pub rule_configs: BTreeMap<String, serde_json::Value>,
25}
26
27impl Default for LintConfig {
28 fn default() -> Self {
29 Self {
30 enabled: true,
31 disabled_rules: Vec::new(),
32 rule_configs: BTreeMap::new(),
33 }
34 }
35}
36
37fn default_enabled() -> bool {
38 true
39}
40
41impl LintConfig {
42 pub fn is_rule_enabled(&self, code: &str) -> bool {
44 let requested =
45 canonicalize_rule_code(code).unwrap_or_else(|| code.trim().to_ascii_uppercase());
46 let disabled: HashSet<String> = self
47 .disabled_rules
48 .iter()
49 .filter_map(|rule| canonicalize_rule_code(rule))
50 .collect();
51
52 self.enabled && !disabled.contains(&requested)
53 }
54
55 pub fn rule_config_object(
57 &self,
58 code: &str,
59 ) -> Option<&serde_json::Map<String, serde_json::Value>> {
60 self.matching_rule_config_value(code)?.as_object()
61 }
62
63 pub fn rule_option_str(&self, code: &str, key: &str) -> Option<&str> {
65 self.rule_config_object(code)?.get(key)?.as_str()
66 }
67
68 pub fn rule_option_bool(&self, code: &str, key: &str) -> Option<bool> {
70 self.rule_config_object(code)?.get(key)?.as_bool()
71 }
72
73 pub fn rule_option_usize(&self, code: &str, key: &str) -> Option<usize> {
75 let value = self.rule_config_object(code)?.get(key)?.as_u64()?;
76 usize::try_from(value).ok()
77 }
78
79 pub fn rule_option_string_list(&self, code: &str, key: &str) -> Option<Vec<String>> {
81 let values = self.rule_config_object(code)?.get(key)?.as_array()?;
82 values
83 .iter()
84 .map(|value| value.as_str().map(str::to_string))
85 .collect()
86 }
87
88 pub fn config_section_object(
90 &self,
91 section: &str,
92 ) -> Option<&serde_json::Map<String, serde_json::Value>> {
93 self.rule_configs
94 .iter()
95 .find_map(|(rule_ref, value)| rule_ref.eq_ignore_ascii_case(section).then_some(value))
96 .and_then(serde_json::Value::as_object)
97 }
98
99 pub fn section_option_str(&self, section: &str, key: &str) -> Option<&str> {
101 self.config_section_object(section)?.get(key)?.as_str()
102 }
103
104 pub fn section_option_bool(&self, section: &str, key: &str) -> Option<bool> {
106 self.config_section_object(section)?.get(key)?.as_bool()
107 }
108
109 pub fn section_option_usize(&self, section: &str, key: &str) -> Option<usize> {
111 let value = self.config_section_object(section)?.get(key)?.as_u64()?;
112 usize::try_from(value).ok()
113 }
114
115 pub fn core_option_bool(&self, key: &str) -> Option<bool> {
117 self.section_option_bool("core", key)
118 }
119
120 fn matching_rule_config_value(&self, code: &str) -> Option<&serde_json::Value> {
121 let canonical = canonicalize_rule_code(code)?;
122 self.rule_configs.iter().find_map(|(rule_ref, value)| {
123 (canonicalize_rule_code(rule_ref).as_deref() == Some(canonical.as_str()))
124 .then_some(value)
125 })
126 }
127}
128
129pub fn canonicalize_rule_code(rule: &str) -> Option<String> {
131 let raw = rule.trim();
132 if raw.is_empty() {
133 return None;
134 }
135
136 let normalized = raw.to_ascii_uppercase();
137
138 if let Some(code) = canonical_lint_code(&normalized) {
139 return Some(code.to_string());
140 }
141
142 if let Some(code) = underscore_or_short_code_to_lint(&normalized) {
143 return Some(code);
144 }
145
146 if let Some(code) = dotted_name_to_code(&normalized) {
147 return Some(code.to_string());
148 }
149
150 None
151}
152
153pub fn sqlfluff_name_for_code(code: &str) -> Option<&'static str> {
155 let canonical = canonicalize_rule_code(code)?;
156 match canonical.as_str() {
157 "LINT_AL_001" => Some("aliasing.table"),
158 "LINT_AL_002" => Some("aliasing.column"),
159 "LINT_AL_003" => Some("aliasing.expression"),
160 "LINT_AL_004" => Some("aliasing.unique.table"),
161 "LINT_AL_005" => Some("aliasing.unused"),
162 "LINT_AL_006" => Some("aliasing.length"),
163 "LINT_AL_007" => Some("aliasing.forbid"),
164 "LINT_AL_008" => Some("aliasing.unique.column"),
165 "LINT_AL_009" => Some("aliasing.self_alias.column"),
166 "LINT_AM_001" => Some("ambiguous.distinct"),
167 "LINT_AM_002" => Some("ambiguous.union"),
168 "LINT_AM_003" => Some("ambiguous.order_by"),
169 "LINT_AM_004" => Some("ambiguous.column_count"),
170 "LINT_AM_005" => Some("ambiguous.join"),
171 "LINT_AM_006" => Some("ambiguous.column_references"),
172 "LINT_AM_007" => Some("ambiguous.set_columns"),
173 "LINT_AM_008" => Some("ambiguous.join_condition"),
174 "LINT_AM_009" => Some("ambiguous.order_by_limit"),
175 "LINT_CP_001" => Some("capitalisation.keywords"),
176 "LINT_CP_002" => Some("capitalisation.identifiers"),
177 "LINT_CP_003" => Some("capitalisation.functions"),
178 "LINT_CP_004" => Some("capitalisation.literals"),
179 "LINT_CP_005" => Some("capitalisation.types"),
180 "LINT_CV_001" => Some("convention.not_equal"),
181 "LINT_CV_002" => Some("convention.coalesce"),
182 "LINT_CV_003" => Some("convention.select_trailing_comma"),
183 "LINT_CV_004" => Some("convention.count_rows"),
184 "LINT_CV_005" => Some("convention.is_null"),
185 "LINT_CV_006" => Some("convention.terminator"),
186 "LINT_CV_007" => Some("convention.statement_brackets"),
187 "LINT_CV_008" => Some("convention.left_join"),
188 "LINT_CV_009" => Some("convention.blocked_words"),
189 "LINT_CV_010" => Some("convention.quoted_literals"),
190 "LINT_CV_011" => Some("convention.casting_style"),
191 "LINT_CV_012" => Some("convention.join_condition"),
192 "LINT_JJ_001" => Some("jinja.padding"),
193 "LINT_LT_001" => Some("layout.spacing"),
194 "LINT_LT_002" => Some("layout.indent"),
195 "LINT_LT_003" => Some("layout.operators"),
196 "LINT_LT_004" => Some("layout.commas"),
197 "LINT_LT_005" => Some("layout.long_lines"),
198 "LINT_LT_006" => Some("layout.functions"),
199 "LINT_LT_007" => Some("layout.cte_bracket"),
200 "LINT_LT_008" => Some("layout.cte_newline"),
201 "LINT_LT_009" => Some("layout.select_targets"),
202 "LINT_LT_010" => Some("layout.select_modifiers"),
203 "LINT_LT_011" => Some("layout.set_operators"),
204 "LINT_LT_012" => Some("layout.end_of_file"),
205 "LINT_LT_013" => Some("layout.start_of_file"),
206 "LINT_LT_014" => Some("layout.keyword_newline"),
207 "LINT_LT_015" => Some("layout.newlines"),
208 "LINT_RF_001" => Some("references.from"),
209 "LINT_RF_002" => Some("references.qualification"),
210 "LINT_RF_003" => Some("references.consistent"),
211 "LINT_RF_004" => Some("references.keywords"),
212 "LINT_RF_005" => Some("references.special_chars"),
213 "LINT_RF_006" => Some("references.quoting"),
214 "LINT_ST_001" => Some("structure.else_null"),
215 "LINT_ST_002" => Some("structure.simple_case"),
216 "LINT_ST_003" => Some("structure.unused_cte"),
217 "LINT_ST_004" => Some("structure.nested_case"),
218 "LINT_ST_005" => Some("structure.subquery"),
219 "LINT_ST_006" => Some("structure.column_order"),
220 "LINT_ST_007" => Some("structure.using"),
221 "LINT_ST_008" => Some("structure.distinct"),
222 "LINT_ST_009" => Some("structure.join_condition_order"),
223 "LINT_ST_010" => Some("structure.constant_expression"),
224 "LINT_ST_011" => Some("structure.unused_join"),
225 "LINT_ST_012" => Some("structure.consecutive_semicolons"),
226 "LINT_TQ_001" => Some("tsql.sp_prefix"),
227 "LINT_TQ_002" => Some("tsql.procedure_begin_end"),
228 "LINT_TQ_003" => Some("tsql.empty_batch"),
229 _ => None,
230 }
231}
232
233fn canonical_lint_code(normalized: &str) -> Option<&'static str> {
234 if !normalized.starts_with("LINT_") {
235 return None;
236 }
237
238 if sqlfluff_name_for_canonical_code(normalized).is_some() {
239 Some(match normalized {
240 "LINT_AL_001" => "LINT_AL_001",
241 "LINT_AL_002" => "LINT_AL_002",
242 "LINT_AL_003" => "LINT_AL_003",
243 "LINT_AL_004" => "LINT_AL_004",
244 "LINT_AL_005" => "LINT_AL_005",
245 "LINT_AL_006" => "LINT_AL_006",
246 "LINT_AL_007" => "LINT_AL_007",
247 "LINT_AL_008" => "LINT_AL_008",
248 "LINT_AL_009" => "LINT_AL_009",
249 "LINT_AM_001" => "LINT_AM_001",
250 "LINT_AM_002" => "LINT_AM_002",
251 "LINT_AM_003" => "LINT_AM_003",
252 "LINT_AM_004" => "LINT_AM_004",
253 "LINT_AM_005" => "LINT_AM_005",
254 "LINT_AM_006" => "LINT_AM_006",
255 "LINT_AM_007" => "LINT_AM_007",
256 "LINT_AM_008" => "LINT_AM_008",
257 "LINT_AM_009" => "LINT_AM_009",
258 "LINT_CP_001" => "LINT_CP_001",
259 "LINT_CP_002" => "LINT_CP_002",
260 "LINT_CP_003" => "LINT_CP_003",
261 "LINT_CP_004" => "LINT_CP_004",
262 "LINT_CP_005" => "LINT_CP_005",
263 "LINT_CV_001" => "LINT_CV_001",
264 "LINT_CV_002" => "LINT_CV_002",
265 "LINT_CV_003" => "LINT_CV_003",
266 "LINT_CV_004" => "LINT_CV_004",
267 "LINT_CV_005" => "LINT_CV_005",
268 "LINT_CV_006" => "LINT_CV_006",
269 "LINT_CV_007" => "LINT_CV_007",
270 "LINT_CV_008" => "LINT_CV_008",
271 "LINT_CV_009" => "LINT_CV_009",
272 "LINT_CV_010" => "LINT_CV_010",
273 "LINT_CV_011" => "LINT_CV_011",
274 "LINT_CV_012" => "LINT_CV_012",
275 "LINT_JJ_001" => "LINT_JJ_001",
276 "LINT_LT_001" => "LINT_LT_001",
277 "LINT_LT_002" => "LINT_LT_002",
278 "LINT_LT_003" => "LINT_LT_003",
279 "LINT_LT_004" => "LINT_LT_004",
280 "LINT_LT_005" => "LINT_LT_005",
281 "LINT_LT_006" => "LINT_LT_006",
282 "LINT_LT_007" => "LINT_LT_007",
283 "LINT_LT_008" => "LINT_LT_008",
284 "LINT_LT_009" => "LINT_LT_009",
285 "LINT_LT_010" => "LINT_LT_010",
286 "LINT_LT_011" => "LINT_LT_011",
287 "LINT_LT_012" => "LINT_LT_012",
288 "LINT_LT_013" => "LINT_LT_013",
289 "LINT_LT_014" => "LINT_LT_014",
290 "LINT_LT_015" => "LINT_LT_015",
291 "LINT_RF_001" => "LINT_RF_001",
292 "LINT_RF_002" => "LINT_RF_002",
293 "LINT_RF_003" => "LINT_RF_003",
294 "LINT_RF_004" => "LINT_RF_004",
295 "LINT_RF_005" => "LINT_RF_005",
296 "LINT_RF_006" => "LINT_RF_006",
297 "LINT_ST_001" => "LINT_ST_001",
298 "LINT_ST_002" => "LINT_ST_002",
299 "LINT_ST_003" => "LINT_ST_003",
300 "LINT_ST_004" => "LINT_ST_004",
301 "LINT_ST_005" => "LINT_ST_005",
302 "LINT_ST_006" => "LINT_ST_006",
303 "LINT_ST_007" => "LINT_ST_007",
304 "LINT_ST_008" => "LINT_ST_008",
305 "LINT_ST_009" => "LINT_ST_009",
306 "LINT_ST_010" => "LINT_ST_010",
307 "LINT_ST_011" => "LINT_ST_011",
308 "LINT_ST_012" => "LINT_ST_012",
309 "LINT_TQ_001" => "LINT_TQ_001",
310 "LINT_TQ_002" => "LINT_TQ_002",
311 "LINT_TQ_003" => "LINT_TQ_003",
312 _ => return None,
313 })
314 } else {
315 None
316 }
317}
318
319fn sqlfluff_name_for_canonical_code(code: &str) -> Option<&'static str> {
320 match code {
321 "LINT_AL_001" => Some("aliasing.table"),
322 "LINT_AL_002" => Some("aliasing.column"),
323 "LINT_AL_003" => Some("aliasing.expression"),
324 "LINT_AL_004" => Some("aliasing.unique.table"),
325 "LINT_AL_005" => Some("aliasing.unused"),
326 "LINT_AL_006" => Some("aliasing.length"),
327 "LINT_AL_007" => Some("aliasing.forbid"),
328 "LINT_AL_008" => Some("aliasing.unique.column"),
329 "LINT_AL_009" => Some("aliasing.self_alias.column"),
330 "LINT_AM_001" => Some("ambiguous.distinct"),
331 "LINT_AM_002" => Some("ambiguous.union"),
332 "LINT_AM_003" => Some("ambiguous.order_by"),
333 "LINT_AM_004" => Some("ambiguous.column_count"),
334 "LINT_AM_005" => Some("ambiguous.join"),
335 "LINT_AM_006" => Some("ambiguous.column_references"),
336 "LINT_AM_007" => Some("ambiguous.set_columns"),
337 "LINT_AM_008" => Some("ambiguous.join_condition"),
338 "LINT_AM_009" => Some("ambiguous.order_by_limit"),
339 "LINT_CP_001" => Some("capitalisation.keywords"),
340 "LINT_CP_002" => Some("capitalisation.identifiers"),
341 "LINT_CP_003" => Some("capitalisation.functions"),
342 "LINT_CP_004" => Some("capitalisation.literals"),
343 "LINT_CP_005" => Some("capitalisation.types"),
344 "LINT_CV_001" => Some("convention.not_equal"),
345 "LINT_CV_002" => Some("convention.coalesce"),
346 "LINT_CV_003" => Some("convention.select_trailing_comma"),
347 "LINT_CV_004" => Some("convention.count_rows"),
348 "LINT_CV_005" => Some("convention.is_null"),
349 "LINT_CV_006" => Some("convention.terminator"),
350 "LINT_CV_007" => Some("convention.statement_brackets"),
351 "LINT_CV_008" => Some("convention.left_join"),
352 "LINT_CV_009" => Some("convention.blocked_words"),
353 "LINT_CV_010" => Some("convention.quoted_literals"),
354 "LINT_CV_011" => Some("convention.casting_style"),
355 "LINT_CV_012" => Some("convention.join_condition"),
356 "LINT_JJ_001" => Some("jinja.padding"),
357 "LINT_LT_001" => Some("layout.spacing"),
358 "LINT_LT_002" => Some("layout.indent"),
359 "LINT_LT_003" => Some("layout.operators"),
360 "LINT_LT_004" => Some("layout.commas"),
361 "LINT_LT_005" => Some("layout.long_lines"),
362 "LINT_LT_006" => Some("layout.functions"),
363 "LINT_LT_007" => Some("layout.cte_bracket"),
364 "LINT_LT_008" => Some("layout.cte_newline"),
365 "LINT_LT_009" => Some("layout.select_targets"),
366 "LINT_LT_010" => Some("layout.select_modifiers"),
367 "LINT_LT_011" => Some("layout.set_operators"),
368 "LINT_LT_012" => Some("layout.end_of_file"),
369 "LINT_LT_013" => Some("layout.start_of_file"),
370 "LINT_LT_014" => Some("layout.keyword_newline"),
371 "LINT_LT_015" => Some("layout.newlines"),
372 "LINT_RF_001" => Some("references.from"),
373 "LINT_RF_002" => Some("references.qualification"),
374 "LINT_RF_003" => Some("references.consistent"),
375 "LINT_RF_004" => Some("references.keywords"),
376 "LINT_RF_005" => Some("references.special_chars"),
377 "LINT_RF_006" => Some("references.quoting"),
378 "LINT_ST_001" => Some("structure.else_null"),
379 "LINT_ST_002" => Some("structure.simple_case"),
380 "LINT_ST_003" => Some("structure.unused_cte"),
381 "LINT_ST_004" => Some("structure.nested_case"),
382 "LINT_ST_005" => Some("structure.subquery"),
383 "LINT_ST_006" => Some("structure.column_order"),
384 "LINT_ST_007" => Some("structure.using"),
385 "LINT_ST_008" => Some("structure.distinct"),
386 "LINT_ST_009" => Some("structure.join_condition_order"),
387 "LINT_ST_010" => Some("structure.constant_expression"),
388 "LINT_ST_011" => Some("structure.unused_join"),
389 "LINT_ST_012" => Some("structure.consecutive_semicolons"),
390 "LINT_TQ_001" => Some("tsql.sp_prefix"),
391 "LINT_TQ_002" => Some("tsql.procedure_begin_end"),
392 "LINT_TQ_003" => Some("tsql.empty_batch"),
393 _ => None,
394 }
395}
396
397fn underscore_or_short_code_to_lint(normalized: &str) -> Option<String> {
398 if normalized.len() == 6 {
399 let mut chars = normalized.chars();
400 let p0 = chars.next()?;
401 let p1 = chars.next()?;
402 let underscore = chars.next()?;
403 let d0 = chars.next()?;
404 let d1 = chars.next()?;
405 let d2 = chars.next()?;
406
407 if p0.is_ascii_alphabetic()
408 && p1.is_ascii_alphabetic()
409 && underscore == '_'
410 && d0.is_ascii_digit()
411 && d1.is_ascii_digit()
412 && d2.is_ascii_digit()
413 {
414 let lint = format!("LINT_{}", normalized);
415 return canonical_lint_code(&lint).map(str::to_string);
416 }
417 }
418
419 if normalized.len() >= 3 {
420 let prefix = &normalized[..2];
421 let digits = &normalized[2..];
422
423 if prefix.chars().all(|c| c.is_ascii_alphabetic())
424 && digits.chars().all(|c| c.is_ascii_digit())
425 {
426 let number: usize = digits.parse().ok()?;
427 if number == 0 || number > 999 {
428 return None;
429 }
430 let lint = format!("LINT_{}_{number:03}", prefix);
431 return canonical_lint_code(&lint).map(str::to_string);
432 }
433 }
434
435 None
436}
437
438fn dotted_name_to_code(alias: &str) -> Option<&'static str> {
439 match alias {
440 "ALIASING.TABLE" => Some("LINT_AL_001"),
441 "ALIASING.COLUMN" => Some("LINT_AL_002"),
442 "ALIASING.EXPRESSION" => Some("LINT_AL_003"),
443 "ALIASING.UNIQUE.TABLE" => Some("LINT_AL_004"),
444 "ALIASING.UNUSED" => Some("LINT_AL_005"),
445 "ALIASING.LENGTH" => Some("LINT_AL_006"),
446 "ALIASING.FORBID" => Some("LINT_AL_007"),
447 "ALIASING.UNIQUE.COLUMN" => Some("LINT_AL_008"),
448 "ALIASING.SELF_ALIAS.COLUMN" => Some("LINT_AL_009"),
449 "AMBIGUOUS.DISTINCT" => Some("LINT_AM_001"),
450 "AMBIGUOUS.UNION" => Some("LINT_AM_002"),
451 "AMBIGUOUS.ORDER_BY" => Some("LINT_AM_003"),
452 "AMBIGUOUS.COLUMN_COUNT" => Some("LINT_AM_004"),
453 "AMBIGUOUS.JOIN" => Some("LINT_AM_005"),
454 "AMBIGUOUS.COLUMN_REFERENCES" => Some("LINT_AM_006"),
455 "AMBIGUOUS.SET_COLUMNS" => Some("LINT_AM_007"),
456 "AMBIGUOUS.JOIN_CONDITION" | "AMBIGUOUS.JOIN.CONDITION" => Some("LINT_AM_008"),
457 "AMBIGUOUS.ORDER_BY_LIMIT" => Some("LINT_AM_009"),
458 "CAPITALISATION.KEYWORDS" => Some("LINT_CP_001"),
459 "CAPITALISATION.IDENTIFIERS" => Some("LINT_CP_002"),
460 "CAPITALISATION.FUNCTIONS" => Some("LINT_CP_003"),
461 "CAPITALISATION.LITERALS" => Some("LINT_CP_004"),
462 "CAPITALISATION.TYPES" => Some("LINT_CP_005"),
463 "CONVENTION.NOT_EQUAL" => Some("LINT_CV_001"),
464 "CONVENTION.COALESCE" => Some("LINT_CV_002"),
465 "CONVENTION.SELECT_TRAILING_COMMA" => Some("LINT_CV_003"),
466 "CONVENTION.COUNT_ROWS" | "CONVENTION.STAR_COUNT" => Some("LINT_CV_004"),
467 "CONVENTION.IS_NULL" => Some("LINT_CV_005"),
468 "CONVENTION.TERMINATOR" => Some("LINT_CV_006"),
469 "CONVENTION.STATEMENT_BRACKETS" => Some("LINT_CV_007"),
470 "CONVENTION.LEFT_JOIN" => Some("LINT_CV_008"),
471 "CONVENTION.BLOCKED_WORDS" => Some("LINT_CV_009"),
472 "CONVENTION.QUOTED_LITERALS" => Some("LINT_CV_010"),
473 "CONVENTION.CASTING_STYLE" => Some("LINT_CV_011"),
474 "CONVENTION.JOIN_CONDITION" | "CONVENTION.JOIN" => Some("LINT_CV_012"),
475 "JJ.PADDING" | "JINJA.PADDING" | "JJ.JJ01" => Some("LINT_JJ_001"),
476 "LAYOUT.SPACING" => Some("LINT_LT_001"),
477 "LAYOUT.INDENT" => Some("LINT_LT_002"),
478 "LAYOUT.OPERATORS" => Some("LINT_LT_003"),
479 "LAYOUT.COMMAS" => Some("LINT_LT_004"),
480 "LAYOUT.LONG_LINES" => Some("LINT_LT_005"),
481 "LAYOUT.FUNCTIONS" => Some("LINT_LT_006"),
482 "LAYOUT.CTE_BRACKET" => Some("LINT_LT_007"),
483 "LAYOUT.CTE_NEWLINE" => Some("LINT_LT_008"),
484 "LAYOUT.SELECT_TARGETS" => Some("LINT_LT_009"),
485 "LAYOUT.SELECT_MODIFIERS" => Some("LINT_LT_010"),
486 "LAYOUT.SET_OPERATORS" => Some("LINT_LT_011"),
487 "LAYOUT.END_OF_FILE" => Some("LINT_LT_012"),
488 "LAYOUT.START_OF_FILE" => Some("LINT_LT_013"),
489 "LAYOUT.KEYWORD_NEWLINE" => Some("LINT_LT_014"),
490 "LAYOUT.NEWLINES" => Some("LINT_LT_015"),
491 "REFERENCES.FROM" => Some("LINT_RF_001"),
492 "REFERENCES.QUALIFICATION" => Some("LINT_RF_002"),
493 "REFERENCES.CONSISTENT" => Some("LINT_RF_003"),
494 "REFERENCES.KEYWORDS" => Some("LINT_RF_004"),
495 "REFERENCES.SPECIAL_CHARS" => Some("LINT_RF_005"),
496 "REFERENCES.QUOTING" => Some("LINT_RF_006"),
497 "STRUCTURE.ELSE_NULL" => Some("LINT_ST_001"),
498 "STRUCTURE.SIMPLE_CASE" => Some("LINT_ST_002"),
499 "STRUCTURE.UNUSED_CTE" => Some("LINT_ST_003"),
500 "STRUCTURE.NESTED_CASE" => Some("LINT_ST_004"),
501 "STRUCTURE.SUBQUERY" => Some("LINT_ST_005"),
502 "STRUCTURE.COLUMN_ORDER" => Some("LINT_ST_006"),
503 "STRUCTURE.USING" => Some("LINT_ST_007"),
504 "STRUCTURE.DISTINCT" => Some("LINT_ST_008"),
505 "STRUCTURE.JOIN_CONDITION_ORDER" => Some("LINT_ST_009"),
506 "STRUCTURE.CONSTANT_EXPRESSION" => Some("LINT_ST_010"),
507 "STRUCTURE.UNUSED_JOIN" => Some("LINT_ST_011"),
508 "STRUCTURE.CONSECUTIVE_SEMICOLONS" => Some("LINT_ST_012"),
509 "TSQL.SP_PREFIX" => Some("LINT_TQ_001"),
510 "TSQL.PROCEDURE_BEGIN_END" => Some("LINT_TQ_002"),
511 "TSQL.EMPTY_BATCH" => Some("LINT_TQ_003"),
512 _ => None,
513 }
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519
520 #[test]
521 fn default_config_enables_all() {
522 let config = LintConfig::default();
523 assert!(config.enabled);
524 assert!(config.is_rule_enabled("LINT_AM_008"));
525 }
526
527 #[test]
528 fn disabled_rules_are_case_insensitive_and_trimmed() {
529 let config = LintConfig {
530 enabled: true,
531 disabled_rules: vec![" lint_am_009 ".to_string(), " LINT_ST_006".to_string()],
532 rule_configs: BTreeMap::new(),
533 };
534 assert!(!config.is_rule_enabled("LINT_AM_009"));
535 assert!(!config.is_rule_enabled("lint_st_006"));
536 assert!(config.is_rule_enabled("LINT_CV_007"));
537 }
538
539 #[test]
540 fn disabled_rules_support_dotted_names() {
541 let config = LintConfig {
542 enabled: true,
543 disabled_rules: vec![
544 " ambiguous.join ".to_string(),
545 "AMBIGUOUS.UNION".to_string(),
546 ],
547 rule_configs: BTreeMap::new(),
548 };
549 assert!(!config.is_rule_enabled("LINT_AM_005"));
550 assert!(!config.is_rule_enabled("LINT_AM_002"));
551 assert!(config.is_rule_enabled("LINT_AM_001"));
552 }
553
554 #[test]
555 fn canonicalize_rule_supports_short_and_underscore_forms() {
556 assert_eq!(
557 canonicalize_rule_code("al01"),
558 Some("LINT_AL_001".to_string())
559 );
560 assert_eq!(
561 canonicalize_rule_code("AL_001"),
562 Some("LINT_AL_001".to_string())
563 );
564 assert_eq!(
565 canonicalize_rule_code("ambiguous.order_by"),
566 Some("LINT_AM_003".to_string())
567 );
568 assert_eq!(canonicalize_rule_code("unknown"), None);
569 }
570
571 #[test]
572 fn sqlfluff_name_lookup_works() {
573 assert_eq!(
574 sqlfluff_name_for_code("LINT_CV_008"),
575 Some("convention.left_join")
576 );
577 assert_eq!(sqlfluff_name_for_code("cv08"), Some("convention.left_join"));
578 assert_eq!(sqlfluff_name_for_code("unknown"), None);
579 }
580
581 #[test]
582 fn master_toggle_off_disables_everything() {
583 let config = LintConfig {
584 enabled: false,
585 disabled_rules: vec![],
586 rule_configs: BTreeMap::new(),
587 };
588 assert!(!config.is_rule_enabled("LINT_AM_008"));
589 }
590
591 #[test]
592 fn deserialization_defaults() {
593 let json = "{}";
594 let config: LintConfig = serde_json::from_str(json).expect("valid lint config json");
595 assert!(config.enabled);
596 assert!(config.disabled_rules.is_empty());
597 assert!(config.rule_configs.is_empty());
598 }
599
600 #[test]
601 fn rule_config_options_resolve_by_dotted_or_code() {
602 let config = LintConfig {
603 enabled: true,
604 disabled_rules: vec![],
605 rule_configs: BTreeMap::from([
606 (
607 "aliasing.table".to_string(),
608 serde_json::json!({"aliasing": "implicit"}),
609 ),
610 (
611 "LINT_AL_002".to_string(),
612 serde_json::json!({"aliasing": "explicit"}),
613 ),
614 (
615 "AM06".to_string(),
616 serde_json::json!({"group_by_and_order_by_style": "explicit"}),
617 ),
618 (
619 "references.consistent".to_string(),
620 serde_json::json!({"single_table_references": "qualified"}),
621 ),
622 ]),
623 };
624
625 assert_eq!(
626 config.rule_option_str("LINT_AL_001", "aliasing"),
627 Some("implicit")
628 );
629 assert_eq!(
630 config.rule_option_str("aliasing.column", "aliasing"),
631 Some("explicit")
632 );
633 assert_eq!(
634 config.rule_option_str("LINT_AM_006", "group_by_and_order_by_style"),
635 Some("explicit")
636 );
637 assert_eq!(
638 config.rule_option_str("RF03", "single_table_references"),
639 Some("qualified")
640 );
641 }
642
643 #[test]
644 fn core_options_resolve_case_insensitively() {
645 let config = LintConfig {
646 enabled: true,
647 disabled_rules: vec![],
648 rule_configs: BTreeMap::from([(
649 "CORE".to_string(),
650 serde_json::json!({"ignore_templated_areas": true}),
651 )]),
652 };
653
654 assert_eq!(
655 config.core_option_bool("ignore_templated_areas"),
656 Some(true)
657 );
658 }
659
660 #[test]
661 fn section_options_resolve_case_insensitively() {
662 let config = LintConfig {
663 enabled: true,
664 disabled_rules: vec![],
665 rule_configs: BTreeMap::from([(
666 "INDENTATION".to_string(),
667 serde_json::json!({"indent_unit": "tab", "tab_space_size": 2}),
668 )]),
669 };
670
671 assert_eq!(
672 config.section_option_str("indentation", "indent_unit"),
673 Some("tab")
674 );
675 assert_eq!(
676 config.section_option_usize("indentation", "tab_space_size"),
677 Some(2)
678 );
679 }
680}