swc_css_lints/rules/
unit_no_unknown.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
11#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct UnitNoUnknownConfig {
14 ignore_units: Option<Vec<String>>,
15}
16
17pub fn unit_no_unknown(
18 ctx: LintRuleContext<UnitNoUnknownConfig>,
19) -> Result<Box<dyn LintRule>, ConfigError> {
20 let ignored_units = ctx
21 .config()
22 .ignore_units
23 .clone()
24 .unwrap_or_default()
25 .into_iter()
26 .map(NamePattern::try_from)
27 .collect::<Result<_, _>>()?;
28 Ok(visitor_rule(
29 ctx.reaction(),
30 UnitNoUnknown { ctx, ignored_units },
31 ))
32}
33
34#[derive(Debug, Default)]
35struct UnitNoUnknown {
36 ctx: LintRuleContext<UnitNoUnknownConfig>,
37 ignored_units: Vec<NamePattern>,
38}
39
40impl Visit for UnitNoUnknown {
41 fn visit_unknown_dimension(&mut self, unknown_dimension: &UnknownDimension) {
42 let unit = &unknown_dimension.unit.value;
43
44 if self.ignored_units.iter().all(|item| !item.is_match(unit)) {
45 let message = format!("Unexpected unknown unit \"{unit}\".");
46 self.ctx.report(&unknown_dimension.unit, message);
47 }
48
49 unknown_dimension.visit_children_with(self);
50 }
51
52 fn visit_component_value(&mut self, component_value: &ComponentValue) {
53 if let Some(token_and_span) = component_value.as_preserved_token() {
54 if let Some(unit) = match &token_and_span.token {
55 Token::Dimension {
56 dimension: dimension_token,
57 } => Some(dimension_token.unit.as_ref()),
58 _ => None,
59 } {
60 if self.ignored_units.iter().all(|item| !item.is_match(unit)) {
61 let message = format!("Unexpected unknown unit \"{unit}\".");
62 self.ctx.report(token_and_span, message);
63 }
64 }
65 }
66
67 component_value.visit_children_with(self);
68 }
69}