wowlab_tidy/languages/rust/rules/tests/
manual_float_epsilon.rs1use ra_ap_syntax::{
2 AstNode,
3 ast::{self, HasName},
4};
5
6use super::{first_macro_argument, macro_name};
7use crate::{AstCtx, Example, Violation};
8
9#[rustfmt::skip]
10const EXAMPLES: &[Example] = &[
11 Example { label: "near matcher", code: "fn test() { verify_that!(actual, near(expected, 1e-9)); }", pass: true },
12 Example { label: "manual epsilon assert", code: "fn test() { assert!((actual - expected).abs() < f64::EPSILON); }", pass: false },
13 Example { label: "manual epsilon verify", code: "fn test() { verify_that!((actual - expected).abs() < tolerance, is_true()); }", pass: false },
14 Example { label: "assert near helper", code: "fn assert_near(actual: f64, expected: f64) { assert!((actual - expected).abs() < 1e-9); }", pass: false },
15];
16
17crate::ast_rule!(
18 manual_float_epsilon,
19 "Disallow manual floating-point epsilon assertions and local assert-near helpers.",
20 "The shared `near` and `near_tol` matchers provide consistent diagnostics and tolerance policy.",
21 Medium,
22);
23
24fn check_manual_float_epsilon(ctx: &AstCtx<'_>) -> Vec<Violation> {
25 let mut violations: Vec<Violation> = ctx
26 .nodes::<ast::Fn>()
27 .filter(|function| {
28 function
29 .name()
30 .is_some_and(|name| name.text() == "assert_near")
31 })
32 .map(|function| {
33 ctx.violation(
34 &function,
35 "local `assert_near` helper should use the shared matcher",
36 )
37 })
38 .collect();
39
40 for call in ctx
41 .nodes::<ast::MacroCall>()
42 .filter(|call| matches!(macro_name(call).as_deref(), Some("assert" | "verify_that")))
43 {
44 let Some(expression) = first_macro_argument(&call) else {
45 continue;
46 };
47 let text: String = expression
48 .syntax()
49 .text()
50 .to_string()
51 .chars()
52 .filter(|ch| !ch.is_whitespace())
53 .collect();
54
55 if text.contains(".abs()<") && text.contains('-') {
56 violations.push(ctx.violation(
57 &call,
58 "manual float epsilon comparison should use `near` or `near_tol`",
59 ));
60 }
61 }
62
63 violations
64}
65
66crate::tidy_ast_test!(check_manual_float_epsilon, {
67 crate::example_tests!(EXAMPLES, check_manual_float_epsilon);
68});