swc_css_lints/rules/
no_duplicate_at_import_rules.rs

1use std::fmt::Display;
2
3use rustc_hash::FxHashSet;
4use swc_atoms::Atom;
5use swc_css_ast::*;
6use swc_css_visit::{Visit, VisitWith};
7
8use crate::rule::{visitor_rule, LintRule, LintRuleContext};
9
10pub fn no_duplicate_at_import_rules(ctx: LintRuleContext<()>) -> Box<dyn LintRule> {
11    visitor_rule(
12        ctx.reaction(),
13        NoDuplicateAtImportRules {
14            ctx,
15            imports: Default::default(),
16            import_at_rules: None,
17        },
18    )
19}
20
21fn build_message<S>(href: S) -> String
22where
23    S: AsRef<str> + Display,
24{
25    format!("Unexpected duplicate '@import' rule '{href}'.")
26}
27
28#[derive(Debug, Default)]
29struct NoDuplicateAtImportRules {
30    ctx: LintRuleContext<()>,
31    imports: FxHashSet<(Atom, Option<Atom>)>,
32    import_at_rules: Option<AtRule>,
33}
34
35impl Visit for NoDuplicateAtImportRules {
36    fn visit_at_rule(&mut self, at_rule: &AtRule) {
37        if let Some(AtRulePrelude::ImportPrelude(_)) = at_rule.prelude.as_deref() {
38            self.import_at_rules = Some(at_rule.clone());
39
40            at_rule.visit_children_with(self);
41
42            self.import_at_rules = None;
43        }
44    }
45
46    fn visit_import_prelude(&mut self, import_prelude: &ImportPrelude) {
47        let href = match &*import_prelude.href {
48            ImportHref::Str(Str { value, .. }) => value,
49            ImportHref::Url(Url {
50                value: Some(value), ..
51            }) => match &**value {
52                UrlValue::Raw(UrlValueRaw { value, .. }) => value,
53                UrlValue::Str(Str { value, .. }) => value,
54            },
55            _ => {
56                import_prelude.visit_children_with(self);
57
58                return;
59            }
60        };
61
62        if let Some(import_conditions) = &import_prelude.import_conditions {
63            if let Some(queries) = import_conditions.media.as_ref().map(|media| &media.queries) {
64                queries.iter().fold(&mut self.imports, |imports, query| {
65                    let media = query.media_type.as_ref().map(|ident| {
66                        let MediaType::Ident(Ident { value, .. }) = ident;
67
68                        value.clone()
69                    });
70                    let pair = (href.clone(), media);
71
72                    if imports.contains(&pair) {
73                        if let Some(at_rule) = &self.import_at_rules {
74                            self.ctx.report(at_rule, build_message(href));
75                        }
76                    }
77
78                    imports.insert(pair);
79                    imports
80                });
81            }
82        } else {
83            let pair = (href.clone(), None);
84
85            if self.imports.contains(&pair) {
86                if let Some(at_rule) = &self.import_at_rules {
87                    self.ctx.report(at_rule, build_message(href));
88                }
89            }
90
91            self.imports.insert(pair);
92        }
93
94        import_prelude.visit_children_with(self);
95    }
96}