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