swc_css_lints/rules/
no_duplicate_at_import_rules.rs1use 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}