Skip to main content

plumb_core/rules/shadow/
scale_conformance.rs

1//! `shadow/scale-conformance` — flag `box-shadow` values that aren't
2//! in `shadow.scale`.
3
4use crate::config::Config;
5use crate::report::{Confidence, Fix, FixKind, Severity, Violation, ViolationSink};
6use crate::rules::Rule;
7use crate::snapshot::SnapshotCtx;
8
9/// The single property this rule inspects.
10const BOX_SHADOW: &str = "box-shadow";
11
12/// Flags `box-shadow` values that aren't in `shadow.scale`.
13#[derive(Debug, Clone, Copy)]
14pub struct ScaleConformance;
15
16impl Rule for ScaleConformance {
17    fn id(&self) -> &'static str {
18        "shadow/scale-conformance"
19    }
20
21    fn default_severity(&self) -> Severity {
22        Severity::Warning
23    }
24
25    fn summary(&self) -> &'static str {
26        "Flags `box-shadow` values that aren't in `shadow.scale`."
27    }
28
29    fn check(&self, ctx: &SnapshotCtx<'_>, config: &Config, sink: &mut ViolationSink<'_>) {
30        let scale = &config.shadow.scale;
31        if scale.is_empty() {
32            return;
33        }
34
35        for node in ctx.nodes() {
36            let Some(raw) = node.computed_styles.get(BOX_SHADOW) else {
37                continue;
38            };
39            let trimmed = raw.trim();
40            if trimmed.eq_ignore_ascii_case("none") {
41                continue;
42            }
43
44            let matches = scale.iter().any(|s| s.trim() == trimmed);
45            if matches {
46                continue;
47            }
48
49            sink.push(Violation {
50                rule_id: self.id().to_owned(),
51                severity: self.default_severity(),
52                message: format!(
53                    "`{selector}` has off-scale box-shadow `{trimmed}`; expected a value from shadow.scale.",
54                    selector = node.selector,
55                ),
56                selector: node.selector.clone(),
57                viewport: ctx.snapshot().viewport.clone(),
58                rect: ctx.rect_for(node.dom_order),
59                dom_order: node.dom_order,
60                fix: Some(Fix {
61                    kind: FixKind::Description {
62                        text: format!(
63                            "The box-shadow value `{trimmed}` is not in the allowed shadow scale.",
64                        ),
65                    },
66                    description: "Replace `box-shadow` with one of the allowed shadow tokens."
67                        .to_owned(),
68                    confidence: Confidence::Medium,
69                }),
70                doc_url: "https://plumb.aramhammoudeh.com/rules/shadow-scale-conformance"
71                    .to_owned(),
72                metadata: indexmap::IndexMap::new(),
73            });
74        }
75    }
76}