swc_css_lints/rules/
selector_max_class.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};

use crate::rule::{visitor_rule, LintRule, LintRuleContext};

pub(crate) type SelectorMaxClassConfig = Option<usize>;

pub fn selector_max_class(ctx: LintRuleContext<SelectorMaxClassConfig>) -> Box<dyn LintRule> {
    let max = ctx.config().unwrap_or(3);
    visitor_rule(ctx.reaction(), SelectorMaxClass { ctx, max })
}

#[derive(Debug, Default)]
struct SelectorMaxClass {
    ctx: LintRuleContext<SelectorMaxClassConfig>,
    max: usize,
}

impl SelectorMaxClass {
    fn build_message(&self, count: usize) -> String {
        let class = if self.max == 1 { "class" } else { "classes" };
        format!(
            "Expected selector to have no more than {} {}, but {} actually.",
            self.max, class, count
        )
    }
}

impl Visit for SelectorMaxClass {
    fn visit_complex_selector(&mut self, complex_selector: &ComplexSelector) {
        let count = complex_selector
            .children
            .iter()
            .filter_map(|selector| match selector {
                ComplexSelectorChildren::CompoundSelector(compound_selector) => {
                    Some(compound_selector)
                }
                _ => None,
            })
            .flat_map(|selector| {
                selector
                    .subclass_selectors
                    .iter()
                    .filter(|selector| matches!(selector, SubclassSelector::Class(..)))
            })
            .count();

        if count > self.max {
            let message = self.build_message(count);
            self.ctx.report(complex_selector, message);
        }

        complex_selector.visit_children_with(self);
    }
}