plumb_core/rules/spacing/
scale_conformance.rs1use indexmap::IndexMap;
10
11use crate::config::Config;
12use crate::report::{Confidence, Fix, FixKind, Severity, Violation, ViolationSink};
13use crate::rules::Rule;
14use crate::rules::spacing::SPACING_PROPERTIES;
15use crate::rules::util::{nearest_in_scale, parse_px};
16use crate::snapshot::SnapshotCtx;
17
18const SCALE_TOLERANCE: f64 = 0.5;
23
24#[derive(Debug, Clone, Copy)]
26pub struct ScaleConformance;
27
28impl Rule for ScaleConformance {
29 fn id(&self) -> &'static str {
30 "spacing/scale-conformance"
31 }
32
33 fn default_severity(&self) -> Severity {
34 Severity::Warning
35 }
36
37 fn summary(&self) -> &'static str {
38 "Flags spacing values that aren't members of `spacing.scale`."
39 }
40
41 fn check(&self, ctx: &SnapshotCtx<'_>, config: &Config, sink: &mut ViolationSink<'_>) {
42 let scale = &config.spacing.scale;
43 if scale.is_empty() {
44 return;
47 }
48
49 for node in ctx.nodes() {
50 for prop in SPACING_PROPERTIES {
51 let Some(raw) = node.computed_styles.get(*prop) else {
52 continue;
53 };
54 let Some(value) = parse_px(raw) else { continue };
55 let abs = value.abs();
56 if scale
57 .iter()
58 .any(|&elem| (abs - f64::from(elem)).abs() < SCALE_TOLERANCE)
59 {
60 continue;
61 }
62 let Some(suggested) = nearest_in_scale(value, scale) else {
63 continue;
67 };
68 let to = if suggested == 0 {
69 "0".to_owned()
70 } else {
71 format!("{suggested}px")
72 };
73 sink.push(Violation {
74 rule_id: self.id().to_owned(),
75 severity: self.default_severity(),
76 message: format!(
77 "`{selector}` has off-scale {prop} {raw}; expected a value from spacing.scale.",
78 selector = node.selector,
79 ),
80 selector: node.selector.clone(),
81 viewport: ctx.snapshot().viewport.clone(),
82 rect: ctx.rect_for(node.dom_order),
83 dom_order: node.dom_order,
84 fix: Some(Fix {
85 kind: FixKind::CssPropertyReplace {
86 property: (*prop).to_owned(),
87 from: raw.clone(),
88 to: to.clone(),
89 },
90 description: format!(
91 "Snap `{prop}` to the nearest spacing-scale value ({to}).",
92 ),
93 confidence: Confidence::Medium,
94 }),
95 doc_url: "https://plumb.aramhammoudeh.com/rules/spacing-scale-conformance"
96 .to_owned(),
97 metadata: IndexMap::new(),
98 });
99 }
100 }
101 }
102}