swc_css_lints/rules/
color_hex_length.rs

1use serde::{Deserialize, Serialize};
2use swc_css_ast::*;
3use swc_css_visit::{Visit, VisitWith};
4
5use crate::rule::{visitor_rule, LintRule, LintRuleContext};
6
7pub type ColorHexLengthConfig = Option<HexForm>;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub enum HexForm {
12    Long,
13    Short,
14}
15
16impl Default for HexForm {
17    fn default() -> Self {
18        Self::Long
19    }
20}
21
22pub fn color_hex_length(ctx: LintRuleContext<ColorHexLengthConfig>) -> Box<dyn LintRule> {
23    let form = ctx.config().clone().unwrap_or_default();
24    visitor_rule(ctx.reaction(), ColorHexLength { ctx, form })
25}
26
27#[derive(Debug, Default)]
28struct ColorHexLength {
29    ctx: LintRuleContext<ColorHexLengthConfig>,
30    form: HexForm,
31}
32
33impl ColorHexLength {
34    fn build_message(&self, actual: &str, expected: &str) -> String {
35        format!("Hex color value '#{actual}' should be written into: '#{expected}'.")
36    }
37}
38
39impl Visit for ColorHexLength {
40    fn visit_hex_color(&mut self, hex_color: &HexColor) {
41        match self.form {
42            HexForm::Long => {
43                if let Some(lengthened) = lengthen(&hex_color.value) {
44                    let message = self.build_message(&hex_color.value, &lengthened);
45                    self.ctx.report(hex_color, message);
46                }
47            }
48            HexForm::Short => {
49                if let Some(shortened) = shorten(&hex_color.value) {
50                    let message = self.build_message(&hex_color.value, &shortened);
51                    self.ctx.report(hex_color, message);
52                }
53            }
54        }
55
56        hex_color.visit_children_with(self);
57    }
58}
59
60fn shorten(hex: &str) -> Option<String> {
61    let chars = hex.chars().collect::<Vec<_>>();
62    match &*chars {
63        [c1, c2, c3, c4, c5, c6] if c1 == c2 && c3 == c4 && c5 == c6 => {
64            Some(format!("{c1}{c3}{c5}"))
65        }
66        [c1, c2, c3, c4, c5, c6, c7, c8] if c1 == c2 && c3 == c4 && c5 == c6 && c7 == c8 => {
67            Some(format!("{c1}{c3}{c5}{c7}"))
68        }
69        _ => None,
70    }
71}
72
73fn lengthen(hex: &str) -> Option<String> {
74    let chars = hex.chars().collect::<Vec<_>>();
75    match &*chars {
76        [c1, c2, c3] => Some(format!("{c1}{c1}{c2}{c2}{c3}{c3}")),
77        [c1, c2, c3, c4] => Some(format!("{c1}{c1}{c2}{c2}{c3}{c3}{c4}{c4}")),
78        _ => None,
79    }
80}