swc_css_lints/rules/
selector_max_class.rs

1use swc_css_ast::*;
2use swc_css_visit::{Visit, VisitWith};
3
4use crate::rule::{visitor_rule, LintRule, LintRuleContext};
5
6pub(crate) type SelectorMaxClassConfig = Option<usize>;
7
8pub fn selector_max_class(ctx: LintRuleContext<SelectorMaxClassConfig>) -> Box<dyn LintRule> {
9    let max = ctx.config().unwrap_or(3);
10    visitor_rule(ctx.reaction(), SelectorMaxClass { ctx, max })
11}
12
13#[derive(Debug, Default)]
14struct SelectorMaxClass {
15    ctx: LintRuleContext<SelectorMaxClassConfig>,
16    max: usize,
17}
18
19impl SelectorMaxClass {
20    fn build_message(&self, count: usize) -> String {
21        let class = if self.max == 1 { "class" } else { "classes" };
22        format!(
23            "Expected selector to have no more than {} {}, but {} actually.",
24            self.max, class, count
25        )
26    }
27}
28
29impl Visit for SelectorMaxClass {
30    fn visit_complex_selector(&mut self, complex_selector: &ComplexSelector) {
31        let count = complex_selector
32            .children
33            .iter()
34            .filter_map(|selector| match selector {
35                ComplexSelectorChildren::CompoundSelector(compound_selector) => {
36                    Some(compound_selector)
37                }
38                _ => None,
39            })
40            .flat_map(|selector| {
41                selector
42                    .subclass_selectors
43                    .iter()
44                    .filter(|selector| matches!(selector, SubclassSelector::Class(..)))
45            })
46            .count();
47
48        if count > self.max {
49            let message = self.build_message(count);
50            self.ctx.report(complex_selector, message);
51        }
52
53        complex_selector.visit_children_with(self);
54    }
55}