Skip to main content

wowlab_tidy/languages/rust/rules/tests/
manual_float_epsilon.rs

1use 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});