swc_css_lints/rules/
no_duplicate_at_import_rules.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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use std::fmt::Display;

use swc_atoms::JsWord;
use swc_common::collections::AHashSet;
use swc_css_ast::*;
use swc_css_visit::{Visit, VisitWith};

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

pub fn no_duplicate_at_import_rules(ctx: LintRuleContext<()>) -> Box<dyn LintRule> {
    visitor_rule(
        ctx.reaction(),
        NoDuplicateAtImportRules {
            ctx,
            imports: Default::default(),
            import_at_rules: None,
        },
    )
}

fn build_message<S>(href: S) -> String
where
    S: AsRef<str> + Display,
{
    format!("Unexpected duplicate '@import' rule '{}'.", href)
}

#[derive(Debug, Default)]
struct NoDuplicateAtImportRules {
    ctx: LintRuleContext<()>,
    imports: AHashSet<(JsWord, Option<JsWord>)>,
    import_at_rules: Option<AtRule>,
}

impl Visit for NoDuplicateAtImportRules {
    fn visit_at_rule(&mut self, at_rule: &AtRule) {
        if let Some(AtRulePrelude::ImportPrelude(_)) = at_rule.prelude.as_deref() {
            self.import_at_rules = Some(at_rule.clone());

            at_rule.visit_children_with(self);

            self.import_at_rules = None;
        }
    }

    fn visit_import_prelude(&mut self, import_prelude: &ImportPrelude) {
        let href = match &*import_prelude.href {
            ImportHref::Str(Str { value, .. }) => value,
            ImportHref::Url(Url {
                value: Some(value), ..
            }) => match &**value {
                UrlValue::Raw(UrlValueRaw { value, .. }) => value,
                UrlValue::Str(Str { value, .. }) => value,
            },
            _ => {
                import_prelude.visit_children_with(self);

                return;
            }
        };

        if let Some(import_conditions) = &import_prelude.import_conditions {
            if let Some(queries) = import_conditions.media.as_ref().map(|media| &media.queries) {
                queries.iter().fold(&mut self.imports, |imports, query| {
                    let media = query.media_type.as_ref().map(|ident| {
                        let MediaType::Ident(Ident { value, .. }) = ident;

                        value.clone()
                    });
                    let pair = (href.clone(), media);

                    if imports.contains(&pair) {
                        if let Some(at_rule) = &self.import_at_rules {
                            self.ctx.report(at_rule, build_message(href));
                        }
                    }

                    imports.insert(pair);
                    imports
                });
            }
        } else {
            let pair = (href.clone(), None);

            if self.imports.contains(&pair) {
                if let Some(at_rule) = &self.import_at_rules {
                    self.ctx.report(at_rule, build_message(href));
                }
            }

            self.imports.insert(pair);
        }

        import_prelude.visit_children_with(self);
    }
}