swc_css_lints/rules/
no_invalid_position_at_import_rule.rs1use serde::{Deserialize, Serialize};
2use swc_css_ast::*;
3use swc_css_visit::{Visit, VisitWith};
4
5use crate::{
6 pattern::NamePattern,
7 rule::{visitor_rule, LintRule, LintRuleContext},
8 ConfigError,
9};
10
11const MESSAGE: &str = "Unexpected invalid position '@import' rule.";
12
13#[derive(Debug, Clone, Default, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct NoInvalidPositionAtImportRuleConfig {
16 ignore_at_rules: Option<Vec<String>>,
17}
18
19pub fn no_invalid_position_at_import_rule(
20 ctx: LintRuleContext<NoInvalidPositionAtImportRuleConfig>,
21) -> Result<Box<dyn LintRule>, ConfigError> {
22 let ignored = ctx
23 .config()
24 .ignore_at_rules
25 .clone()
26 .unwrap_or_default()
27 .into_iter()
28 .map(NamePattern::try_from)
29 .collect::<Result<_, _>>()?;
30 Ok(visitor_rule(
31 ctx.reaction(),
32 NoInvalidPositionAtImportRule { ctx, ignored },
33 ))
34}
35
36#[derive(Debug, Default)]
37struct NoInvalidPositionAtImportRule {
38 ctx: LintRuleContext<NoInvalidPositionAtImportRuleConfig>,
39 ignored: Vec<NamePattern>,
40}
41
42impl Visit for NoInvalidPositionAtImportRule {
43 fn visit_stylesheet(&mut self, stylesheet: &Stylesheet) {
44 stylesheet.rules.iter().fold(false, |seen, rule| {
45 if seen
46 && rule
47 .as_at_rule()
48 .and_then(|at_rule| at_rule.prelude.as_ref())
49 .map(|prelude| prelude.is_import_prelude())
50 .unwrap_or_default()
51 {
52 self.ctx.report(rule, MESSAGE);
53 }
54
55 let Rule::AtRule(at_rule) = rule else {
57 return true;
58 };
59
60 let AtRule {
61 name,
62 prelude,
63 block,
64 ..
65 } = &**at_rule;
66
67 if let Some(prelude) = prelude {
68 match &**prelude {
69 AtRulePrelude::CharsetPrelude(_) | AtRulePrelude::ImportPrelude(_) => {
70 return seen
71 }
72 AtRulePrelude::LayerPrelude(_) => match block {
73 Some(block) if block.value.is_empty() => return seen,
74 None => return seen,
75 _ => return true,
76 },
77 _ => {}
78 }
79 };
80
81 let name = match name {
82 AtRuleName::DashedIdent(dashed_ident) => &dashed_ident.value,
83 AtRuleName::Ident(ident) => &ident.value,
84 };
85
86 if self.ignored.iter().any(|item| item.is_match(name)) {
87 seen
88 } else {
89 true
90 }
91 });
92
93 stylesheet.visit_children_with(self);
94 }
95}