[refurb
] Implement math-constant
(FURB152
) (#8727) · astral-sh/ruff@2faac1e (original) (raw)
``
1
`+
use anyhow::Result;
`
``
2
+
``
3
`+
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
`
``
4
`+
use ruff_macros::{derive_message_formats, violation};
`
``
5
`+
use ruff_python_ast::{self as ast, Number};
`
``
6
`+
use ruff_text_size::Ranged;
`
``
7
+
``
8
`+
use crate::checkers::ast::Checker;
`
``
9
`+
use crate::importer::ImportRequest;
`
``
10
+
``
11
`+
/// ## What it does
`
``
12
`` +
/// Checks for literals that are similar to constants in math
module.
``
``
13
`+
///
`
``
14
`+
/// ## Why is this bad?
`
``
15
`+
/// Hard-coding mathematical constants like π increases code duplication,
`
``
16
`+
/// reduces readability, and may lead to a lack of precision.
`
``
17
`+
///
`
``
18
`+
/// ## Example
`
``
19
/// ```python
``
20
`+
/// A = 3.141592 * r**2
`
``
21
/// ```
``
22
`+
///
`
``
23
`+
/// Use instead:
`
``
24
/// ```python
``
25
`+
/// A = math.pi * r**2
`
``
26
/// ```
``
27
`+
///
`
``
28
`+
/// ## References
`
``
29
`` +
/// - Python documentation: math
constants
``
``
30
`+
#[violation]
`
``
31
`+
pub struct MathConstant {
`
``
32
`+
literal: String,
`
``
33
`+
constant: &'static str,
`
``
34
`+
}
`
``
35
+
``
36
`+
impl Violation for MathConstant {
`
``
37
`+
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
`
``
38
+
``
39
`+
#[derive_message_formats]
`
``
40
`+
fn message(&self) -> String {
`
``
41
`+
let MathConstant { literal, constant } = self;
`
``
42
`` +
format!("Replace {literal}
with math.{constant}
")
``
``
43
`+
}
`
``
44
+
``
45
`+
fn fix_title(&self) -> Option {
`
``
46
`+
let MathConstant { constant, .. } = self;
`
``
47
`` +
Some(format!("Use math.{constant}
"))
``
``
48
`+
}
`
``
49
`+
}
`
``
50
+
``
51
`+
/// FURB152
`
``
52
`+
pub(crate) fn math_constant(checker: &mut Checker, literal: &ast::ExprNumberLiteral) {
`
``
53
`+
let Number::Float(value) = literal.value else {
`
``
54
`+
return;
`
``
55
`+
};
`
``
56
`+
for (real_value, constant) in [
`
``
57
`+
(std::f64::consts::PI, "pi"),
`
``
58
`+
(std::f64::consts::E, "e"),
`
``
59
`+
(std::f64::consts::TAU, "tau"),
`
``
60
`+
] {
`
``
61
`+
if (value - real_value).abs() < 1e-2 {
`
``
62
`+
let mut diagnostic = Diagnostic::new(
`
``
63
`+
MathConstant {
`
``
64
`+
literal: checker.locator().slice(literal).into(),
`
``
65
`+
constant,
`
``
66
`+
},
`
``
67
`+
literal.range(),
`
``
68
`+
);
`
``
69
`+
diagnostic.try_set_fix(|| convert_to_constant(literal, constant, checker));
`
``
70
`+
checker.diagnostics.push(diagnostic);
`
``
71
`+
return;
`
``
72
`+
}
`
``
73
`+
}
`
``
74
`+
}
`
``
75
+
``
76
`+
fn convert_to_constant(
`
``
77
`+
literal: &ast::ExprNumberLiteral,
`
``
78
`+
constant: &'static str,
`
``
79
`+
checker: &Checker,
`
``
80
`+
) -> Result {
`
``
81
`+
let (edit, binding) = checker.importer().get_or_import_symbol(
`
``
82
`+
&ImportRequest::import("math", constant),
`
``
83
`+
literal.start(),
`
``
84
`+
checker.semantic(),
`
``
85
`+
)?;
`
``
86
`+
Ok(Fix::safe_edits(
`
``
87
`+
Edit::range_replacement(binding, literal.range()),
`
``
88
`+
[edit],
`
``
89
`+
))
`
``
90
`+
}
`