add rule RUF062 for PEP 515 number formatting with digit grouping

This commit is contained in:
Guillaume 2025-07-03 09:00:41 +02:00
parent 9fc04d6bf0
commit c30fbe2b91
10 changed files with 884 additions and 1 deletions

View file

@ -0,0 +1,53 @@
"""Tests for the RUF062 rule (large numeric literals without underscore separators)."""
# These should trigger the rule (large numbers without underscore separators)
i = 1000000
f = 123456789.123456789
x = 0x1234ABCD
b = 0b10101010101010101010101
o = 0o12345671234
# Scientific notation
sci = 1000000e10
sci_uppercase = 1000000E10
# These should not trigger the rule (small numbers or already have separators)
dec_small_int = 1234
dec_small_float = 123.45
dec_with_separators = 1_000_000
hex_with_separators = 0x1234_ABCD
bin_with_separators = 0b10101_01010101_01010101
oct_with_separators = 0o123_4567_1234
sci_with_separators = 1_000_000e10
# These should trigger the rule because their separators are misplaced
dec_misplaced_separators = 123_4567_89
oct_misplaced_separators = 0o12_34_56
hex_misplaced_separators = 0xABCD_EF
flt_misplaced_separators = 123.12_3456_789
# uppercase base prefix
hex_uppercase = 0XABCDEF
oct_uppercase = 0O123456
bin_uppercase = 0B01010101010101
# Negative numbers should also be checked
neg_large = -1000000
neg_with_separators = -1_000_000 # should not trigger
neg_with_spaces = - 100000
neg_oct = -0o1234567
neg_hex = -0xABCDEF
neg_bin -0b0101010100101
neg_hex_with_spaces = - 0xABCDEF
# Testing for minimun size thresholds
dec_4_digits = 1234 # Should not trigger, just below the threshold of 5 digits
dec_5_digits = 12345 # Should trigger, 5 digits
oct_4_digits = 0o1234 # Should not trigger, just below the threshold of 4 digits
oct_5_digits = 0o12345 # Should trigger, 5 digits
bin_8_digits = 0b01010101 # Should not trigger, just below the threshold of 9 digits
bin_9_digits = 0b101010101 # Should trigger, 9 digits
hex_4_digits = 0xABCD # Should not trigger, just below the threshold of 5 digits
hex_5_digits = 0xABCDE # Should trigger, 5 digits
flt_4_digits = .1234 # Should not trigger, just below the threshold of 5 digits
flt_5_digits = .12345 # Should trigger, 5 digits

View file

@ -1573,6 +1573,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
if checker.is_rule_enabled(Rule::MathConstant) {
refurb::rules::math_constant(checker, number_literal);
}
if checker.is_rule_enabled(Rule::LargeNumberWithoutUnderscoreSeparators) {
ruff::rules::large_number_without_underscore_separators(checker, expr);
}
}
Expr::StringLiteral(
string_like @ ast::ExprStringLiteral {

View file

@ -1034,6 +1034,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable),
(Ruff, "060") => (RuleGroup::Preview, rules::ruff::rules::InEmptyCollection),
(Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises),
(Ruff, "062") => (RuleGroup::Preview, rules::ruff::rules::LargeNumberWithoutUnderscoreSeparators),
(Ruff, "063") => (RuleGroup::Preview, rules::ruff::rules::AccessAnnotationsFromClassDict),
(Ruff, "064") => (RuleGroup::Preview, rules::ruff::rules::NonOctalPermissions),
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),

View file

@ -110,6 +110,7 @@ mod tests {
#[test_case(Rule::LegacyFormPytestRaises, Path::new("RUF061_raises.py"))]
#[test_case(Rule::LegacyFormPytestRaises, Path::new("RUF061_warns.py"))]
#[test_case(Rule::LegacyFormPytestRaises, Path::new("RUF061_deprecated_call.py"))]
#[test_case(Rule::LargeNumberWithoutUnderscoreSeparators, Path::new("RUF062.py"))]
#[test_case(Rule::NonOctalPermissions, Path::new("RUF064.py"))]
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_0.py"))]
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_1.py"))]
@ -131,6 +132,7 @@ mod tests {
&LinterSettings {
ruff: super::settings::Settings {
parenthesize_tuple_in_subscript: true,
..Default::default()
},
..LinterSettings::for_rule(Rule::IncorrectlyParenthesizedTupleInSubscript)
},
@ -146,6 +148,7 @@ mod tests {
&LinterSettings {
ruff: super::settings::Settings {
parenthesize_tuple_in_subscript: false,
..Default::default()
},
unresolved_target_version: PythonVersion::PY310.into(),
..LinterSettings::for_rule(Rule::IncorrectlyParenthesizedTupleInSubscript)

View file

@ -0,0 +1,205 @@
use crate::AlwaysFixableViolation;
use crate::checkers::ast::Checker;
use crate::rules::ruff::settings::Settings;
use ruff_diagnostics::{Edit, Fix};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast as ast;
use ruff_text_size::Ranged;
/// ## What it does
/// Checks for numeric literals that could be more readable with underscore separators
/// between groups of digits.
///
/// ## Why is this bad?
/// Large numeric literals can be difficult to read. Using underscore separators
/// improves readability by visually separating groups of digits.
///
/// ## Example
///
/// ```python
/// # Before
/// x = 1000000
/// y = 1234567.89
/// ```
///
/// Use instead:
/// ```python
/// # After
/// x = 1_000_000
/// y = 1_234_567.89
/// ```
///
/// ## References
/// - [PEP 515 - Underscores in Numeric Literals](https://peps.python.org/pep-0515/)
/// - [Number Localization Formatting Guide](https://randombits.dev/articles/number-localization/formatting)
#[derive(ViolationMetadata)]
pub(crate) struct LargeNumberWithoutUnderscoreSeparators;
impl AlwaysFixableViolation for LargeNumberWithoutUnderscoreSeparators {
#[derive_message_formats]
fn message(&self) -> String {
"Large numeric literal without underscore separators".to_string()
}
fn fix_title(&self) -> String {
"Add underscore separators to numeric literal".to_string()
}
}
/// RUF062: Large numeric literal without underscore separators
pub(crate) fn large_number_without_underscore_separators(checker: &Checker, expr: &ast::Expr) {
let value_text = checker.locator().slice(expr.range());
// format number to compare with the source
let formatted_value: String =
format_number_with_underscores(value_text, &checker.settings().ruff);
if formatted_value != value_text {
checker
.report_diagnostic(LargeNumberWithoutUnderscoreSeparators, expr.range())
.set_fix(Fix::safe_edit(Edit::range_replacement(
formatted_value,
expr.range(),
)));
}
}
/// Format a numeric literal with properly placed underscore separators
fn format_number_with_underscores(value: &str, settings: &Settings) -> String {
// Remove existing underscores
let value = value.replace("_", "");
if value.starts_with("0x") || value.starts_with("0X") {
// Hexadecimal
let prefix = &value[..2];
let hex_part = &value[2..];
let formatted = format_digits(
hex_part,
settings.hex_digit_group_size,
settings.hex_digit_group_size,
settings.hex_digit_grouping_threshold,
);
format!("{}{}", prefix, formatted)
} else if value.starts_with("0b") || value.starts_with("0B") {
// Binary
let prefix = &value[..2];
let bin_part = &value[2..];
let formatted = format_digits(
bin_part,
settings.bin_digit_group_size,
settings.bin_digit_group_size,
settings.bin_digit_grouping_threshold,
);
format!("{}{}", prefix, formatted)
} else if value.starts_with("0o") || value.starts_with("0O") {
// Octal
let prefix = &value[..2];
let oct_part = &value[2..];
let formatted = format_digits(
oct_part,
settings.oct_digit_group_size,
settings.oct_digit_group_size,
settings.oct_digit_grouping_threshold,
);
format!("{}{}", prefix, formatted)
} else {
if value.contains(['e', 'E']) {
// Handle scientific notation
let parts: Vec<&str> = value.split(['e', 'E']).collect();
let base = format_number_with_underscores(parts[0], settings);
let exponent = parts[1];
// Determine which separator was used (e or E)
let separator = if value.contains('e') { 'e' } else { 'E' };
return format!("{}{}{}", base, separator, exponent);
}
// Decimal (integer or float)
let parts: Vec<&str> = value.split('.').collect();
let group_size = if settings.use_indian_decimal_format {
2
} else {
3
};
let integer_part = format_digits(
&parts[0],
group_size,
3,
settings.dec_digit_grouping_threshold,
);
if parts.len() > 1 {
// It's a float, handle the fractional part
let float_part = format_float(
parts[1],
group_size,
3,
settings.dec_digit_grouping_threshold,
);
format!("{}.{}", integer_part, float_part)
} else {
// It's an integer
format!("{}", integer_part)
}
}
}
/// Helper function to format digits with underscores at specified intervals
fn format_digits(
digits: &str,
group_size: usize,
first_group_size: usize,
threshold: usize,
) -> String {
if digits.len() < threshold || group_size == 0 || first_group_size == 0 {
return digits.to_string();
}
let mut result = String::with_capacity(digits.len() * 2);
let mut count = 0;
// Process digits from right to left
for c in digits.chars().rev() {
if count == first_group_size
|| (count > first_group_size + 1 && (count - first_group_size) % group_size == 0)
{
result.push('_');
}
result.push(c);
count += 1;
}
// Reverse the result to get the correct order
result.chars().rev().collect()
}
// Helper function to format float parts with underscores at specified intervals
fn format_float(
digits: &str,
group_size: usize,
first_group_size: usize,
threshold: usize,
) -> String {
if digits.len() < threshold || group_size == 0 || first_group_size == 0 {
return digits.to_string();
}
let mut result = String::with_capacity(digits.len() * 2);
let mut count = 0;
// Process digits from right to left
for c in digits.chars() {
if count == first_group_size
|| (count > first_group_size && (count - first_group_size) % group_size == 0)
{
result.push('_');
}
result.push(c);
count += 1;
}
result
}

View file

@ -32,6 +32,7 @@ pub(crate) use needless_else::*;
pub(crate) use never_union::*;
pub(crate) use non_octal_permissions::*;
pub(crate) use none_not_at_end_of_union::*;
pub(crate) use large_number_without_underscore_separators::*;
pub(crate) use parenthesize_chained_operators::*;
pub(crate) use post_init_default::*;
pub(crate) use pytest_raises_ambiguous_pattern::*;
@ -62,6 +63,7 @@ pub(crate) use zip_instead_of_pairwise::*;
mod access_annotations_from_class_dict;
mod ambiguous_unicode_character;
mod large_number_without_underscore_separators;
mod assert_with_print_message;
mod assignment_in_assert;
mod asyncio_dangling_task;

View file

@ -4,9 +4,33 @@ use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt;
#[derive(Debug, Clone, CacheKey, Default)]
#[derive(Debug, Clone, CacheKey)]
pub struct Settings {
pub parenthesize_tuple_in_subscript: bool,
pub use_indian_decimal_format: bool,
pub hex_digit_group_size: usize,
pub oct_digit_group_size: usize,
pub bin_digit_group_size: usize,
pub hex_digit_grouping_threshold: usize,
pub dec_digit_grouping_threshold: usize,
pub oct_digit_grouping_threshold: usize,
pub bin_digit_grouping_threshold: usize,
}
impl Default for Settings {
fn default() -> Self {
Settings {
parenthesize_tuple_in_subscript: false,
use_indian_decimal_format: false,
hex_digit_group_size: 4,
oct_digit_group_size: 4,
bin_digit_group_size: 8,
hex_digit_grouping_threshold: 5,
dec_digit_grouping_threshold: 5,
oct_digit_grouping_threshold: 5,
bin_digit_grouping_threshold: 8,
}
}
}
impl fmt::Display for Settings {
@ -16,6 +40,14 @@ impl fmt::Display for Settings {
namespace = "linter.ruff",
fields = [
self.parenthesize_tuple_in_subscript,
self.use_indian_decimal_format,
self.bin_digit_grouping_threshold,
self.oct_digit_grouping_threshold,
self.hex_digit_grouping_threshold,
self.dec_digit_grouping_threshold,
self.bin_digit_group_size,
self.oct_digit_group_size,
self.hex_digit_group_size,
]
}
Ok(())

View file

@ -0,0 +1,511 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
RUF062.py:4:5: RUF062 [*] Large numeric literal without underscore separators
|
3 | # These should trigger the rule (large numbers without underscore separators)
4 | i = 1000000
| ^^^^^^^ RUF062
5 | f = 123456789.123456789
6 | x = 0x1234ABCD
|
= help: Add underscore separators to numeric literal
Safe fix
1 1 | """Tests for the RUF062 rule (large numeric literals without underscore separators)."""
2 2 |
3 3 | # These should trigger the rule (large numbers without underscore separators)
4 |-i = 1000000
4 |+i = 1_000_000
5 5 | f = 123456789.123456789
6 6 | x = 0x1234ABCD
7 7 | b = 0b10101010101010101010101
RUF062.py:5:5: RUF062 [*] Large numeric literal without underscore separators
|
3 | # These should trigger the rule (large numbers without underscore separators)
4 | i = 1000000
5 | f = 123456789.123456789
| ^^^^^^^^^^^^^^^^^^^ RUF062
6 | x = 0x1234ABCD
7 | b = 0b10101010101010101010101
|
= help: Add underscore separators to numeric literal
Safe fix
2 2 |
3 3 | # These should trigger the rule (large numbers without underscore separators)
4 4 | i = 1000000
5 |-f = 123456789.123456789
5 |+f = 123_456_789.123_456_789
6 6 | x = 0x1234ABCD
7 7 | b = 0b10101010101010101010101
8 8 | o = 0o12345671234
RUF062.py:6:5: RUF062 [*] Large numeric literal without underscore separators
|
4 | i = 1000000
5 | f = 123456789.123456789
6 | x = 0x1234ABCD
| ^^^^^^^^^^ RUF062
7 | b = 0b10101010101010101010101
8 | o = 0o12345671234
|
= help: Add underscore separators to numeric literal
Safe fix
3 3 | # These should trigger the rule (large numbers without underscore separators)
4 4 | i = 1000000
5 5 | f = 123456789.123456789
6 |-x = 0x1234ABCD
6 |+x = 0x1234_ABCD
7 7 | b = 0b10101010101010101010101
8 8 | o = 0o12345671234
9 9 |
RUF062.py:7:5: RUF062 [*] Large numeric literal without underscore separators
|
5 | f = 123456789.123456789
6 | x = 0x1234ABCD
7 | b = 0b10101010101010101010101
| ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF062
8 | o = 0o12345671234
|
= help: Add underscore separators to numeric literal
Safe fix
4 4 | i = 1000000
5 5 | f = 123456789.123456789
6 6 | x = 0x1234ABCD
7 |-b = 0b10101010101010101010101
7 |+b = 0b1010101_01010101_01010101
8 8 | o = 0o12345671234
9 9 |
10 10 | # Scientific notation
RUF062.py:8:5: RUF062 [*] Large numeric literal without underscore separators
|
6 | x = 0x1234ABCD
7 | b = 0b10101010101010101010101
8 | o = 0o12345671234
| ^^^^^^^^^^^^^ RUF062
9 |
10 | # Scientific notation
|
= help: Add underscore separators to numeric literal
Safe fix
5 5 | f = 123456789.123456789
6 6 | x = 0x1234ABCD
7 7 | b = 0b10101010101010101010101
8 |-o = 0o12345671234
8 |+o = 0o123_4567_1234
9 9 |
10 10 | # Scientific notation
11 11 | sci = 1000000e10
RUF062.py:11:7: RUF062 [*] Large numeric literal without underscore separators
|
10 | # Scientific notation
11 | sci = 1000000e10
| ^^^^^^^^^^ RUF062
12 | sci_uppercase = 1000000E10
|
= help: Add underscore separators to numeric literal
Safe fix
8 8 | o = 0o12345671234
9 9 |
10 10 | # Scientific notation
11 |-sci = 1000000e10
11 |+sci = 1_000_000e10
12 12 | sci_uppercase = 1000000E10
13 13 |
14 14 | # These should not trigger the rule (small numbers or already have separators)
RUF062.py:12:17: RUF062 [*] Large numeric literal without underscore separators
|
10 | # Scientific notation
11 | sci = 1000000e10
12 | sci_uppercase = 1000000E10
| ^^^^^^^^^^ RUF062
13 |
14 | # These should not trigger the rule (small numbers or already have separators)
|
= help: Add underscore separators to numeric literal
Safe fix
9 9 |
10 10 | # Scientific notation
11 11 | sci = 1000000e10
12 |-sci_uppercase = 1000000E10
12 |+sci_uppercase = 1_000_000E10
13 13 |
14 14 | # These should not trigger the rule (small numbers or already have separators)
15 15 | dec_small_int = 1234
RUF062.py:24:28: RUF062 [*] Large numeric literal without underscore separators
|
23 | # These should trigger the rule because their separators are misplaced
24 | dec_misplaced_separators = 123_4567_89
| ^^^^^^^^^^^ RUF062
25 | oct_misplaced_separators = 0o12_34_56
26 | hex_misplaced_separators = 0xABCD_EF
|
= help: Add underscore separators to numeric literal
Safe fix
21 21 | sci_with_separators = 1_000_000e10
22 22 |
23 23 | # These should trigger the rule because their separators are misplaced
24 |-dec_misplaced_separators = 123_4567_89
24 |+dec_misplaced_separators = 123_456_789
25 25 | oct_misplaced_separators = 0o12_34_56
26 26 | hex_misplaced_separators = 0xABCD_EF
27 27 | flt_misplaced_separators = 123.12_3456_789
RUF062.py:25:28: RUF062 [*] Large numeric literal without underscore separators
|
23 | # These should trigger the rule because their separators are misplaced
24 | dec_misplaced_separators = 123_4567_89
25 | oct_misplaced_separators = 0o12_34_56
| ^^^^^^^^^^ RUF062
26 | hex_misplaced_separators = 0xABCD_EF
27 | flt_misplaced_separators = 123.12_3456_789
|
= help: Add underscore separators to numeric literal
Safe fix
22 22 |
23 23 | # These should trigger the rule because their separators are misplaced
24 24 | dec_misplaced_separators = 123_4567_89
25 |-oct_misplaced_separators = 0o12_34_56
25 |+oct_misplaced_separators = 0o12_3456
26 26 | hex_misplaced_separators = 0xABCD_EF
27 27 | flt_misplaced_separators = 123.12_3456_789
28 28 |
RUF062.py:26:28: RUF062 [*] Large numeric literal without underscore separators
|
24 | dec_misplaced_separators = 123_4567_89
25 | oct_misplaced_separators = 0o12_34_56
26 | hex_misplaced_separators = 0xABCD_EF
| ^^^^^^^^^ RUF062
27 | flt_misplaced_separators = 123.12_3456_789
|
= help: Add underscore separators to numeric literal
Safe fix
23 23 | # These should trigger the rule because their separators are misplaced
24 24 | dec_misplaced_separators = 123_4567_89
25 25 | oct_misplaced_separators = 0o12_34_56
26 |-hex_misplaced_separators = 0xABCD_EF
26 |+hex_misplaced_separators = 0xAB_CDEF
27 27 | flt_misplaced_separators = 123.12_3456_789
28 28 |
29 29 | # uppercase base prefix
RUF062.py:27:28: RUF062 [*] Large numeric literal without underscore separators
|
25 | oct_misplaced_separators = 0o12_34_56
26 | hex_misplaced_separators = 0xABCD_EF
27 | flt_misplaced_separators = 123.12_3456_789
| ^^^^^^^^^^^^^^^ RUF062
28 |
29 | # uppercase base prefix
|
= help: Add underscore separators to numeric literal
Safe fix
24 24 | dec_misplaced_separators = 123_4567_89
25 25 | oct_misplaced_separators = 0o12_34_56
26 26 | hex_misplaced_separators = 0xABCD_EF
27 |-flt_misplaced_separators = 123.12_3456_789
27 |+flt_misplaced_separators = 123.123_456_789
28 28 |
29 29 | # uppercase base prefix
30 30 | hex_uppercase = 0XABCDEF
RUF062.py:30:17: RUF062 [*] Large numeric literal without underscore separators
|
29 | # uppercase base prefix
30 | hex_uppercase = 0XABCDEF
| ^^^^^^^^ RUF062
31 | oct_uppercase = 0O123456
32 | bin_uppercase = 0B01010101010101
|
= help: Add underscore separators to numeric literal
Safe fix
27 27 | flt_misplaced_separators = 123.12_3456_789
28 28 |
29 29 | # uppercase base prefix
30 |-hex_uppercase = 0XABCDEF
30 |+hex_uppercase = 0XAB_CDEF
31 31 | oct_uppercase = 0O123456
32 32 | bin_uppercase = 0B01010101010101
33 33 |
RUF062.py:31:17: RUF062 [*] Large numeric literal without underscore separators
|
29 | # uppercase base prefix
30 | hex_uppercase = 0XABCDEF
31 | oct_uppercase = 0O123456
| ^^^^^^^^ RUF062
32 | bin_uppercase = 0B01010101010101
|
= help: Add underscore separators to numeric literal
Safe fix
28 28 |
29 29 | # uppercase base prefix
30 30 | hex_uppercase = 0XABCDEF
31 |-oct_uppercase = 0O123456
31 |+oct_uppercase = 0O12_3456
32 32 | bin_uppercase = 0B01010101010101
33 33 |
34 34 | # Negative numbers should also be checked
RUF062.py:32:17: RUF062 [*] Large numeric literal without underscore separators
|
30 | hex_uppercase = 0XABCDEF
31 | oct_uppercase = 0O123456
32 | bin_uppercase = 0B01010101010101
| ^^^^^^^^^^^^^^^^ RUF062
33 |
34 | # Negative numbers should also be checked
|
= help: Add underscore separators to numeric literal
Safe fix
29 29 | # uppercase base prefix
30 30 | hex_uppercase = 0XABCDEF
31 31 | oct_uppercase = 0O123456
32 |-bin_uppercase = 0B01010101010101
32 |+bin_uppercase = 0B010101_01010101
33 33 |
34 34 | # Negative numbers should also be checked
35 35 | neg_large = -1000000
RUF062.py:35:14: RUF062 [*] Large numeric literal without underscore separators
|
34 | # Negative numbers should also be checked
35 | neg_large = -1000000
| ^^^^^^^ RUF062
36 | neg_with_separators = -1_000_000 # should not trigger
37 | neg_with_spaces = - 100000
|
= help: Add underscore separators to numeric literal
Safe fix
32 32 | bin_uppercase = 0B01010101010101
33 33 |
34 34 | # Negative numbers should also be checked
35 |-neg_large = -1000000
35 |+neg_large = -1_000_000
36 36 | neg_with_separators = -1_000_000 # should not trigger
37 37 | neg_with_spaces = - 100000
38 38 | neg_oct = -0o1234567
RUF062.py:37:23: RUF062 [*] Large numeric literal without underscore separators
|
35 | neg_large = -1000000
36 | neg_with_separators = -1_000_000 # should not trigger
37 | neg_with_spaces = - 100000
| ^^^^^^ RUF062
38 | neg_oct = -0o1234567
39 | neg_hex = -0xABCDEF
|
= help: Add underscore separators to numeric literal
Safe fix
34 34 | # Negative numbers should also be checked
35 35 | neg_large = -1000000
36 36 | neg_with_separators = -1_000_000 # should not trigger
37 |-neg_with_spaces = - 100000
37 |+neg_with_spaces = - 100_000
38 38 | neg_oct = -0o1234567
39 39 | neg_hex = -0xABCDEF
40 40 | neg_bin -0b0101010100101
RUF062.py:38:12: RUF062 [*] Large numeric literal without underscore separators
|
36 | neg_with_separators = -1_000_000 # should not trigger
37 | neg_with_spaces = - 100000
38 | neg_oct = -0o1234567
| ^^^^^^^^^ RUF062
39 | neg_hex = -0xABCDEF
40 | neg_bin -0b0101010100101
|
= help: Add underscore separators to numeric literal
Safe fix
35 35 | neg_large = -1000000
36 36 | neg_with_separators = -1_000_000 # should not trigger
37 37 | neg_with_spaces = - 100000
38 |-neg_oct = -0o1234567
38 |+neg_oct = -0o123_4567
39 39 | neg_hex = -0xABCDEF
40 40 | neg_bin -0b0101010100101
41 41 | neg_hex_with_spaces = - 0xABCDEF
RUF062.py:39:12: RUF062 [*] Large numeric literal without underscore separators
|
37 | neg_with_spaces = - 100000
38 | neg_oct = -0o1234567
39 | neg_hex = -0xABCDEF
| ^^^^^^^^ RUF062
40 | neg_bin -0b0101010100101
41 | neg_hex_with_spaces = - 0xABCDEF
|
= help: Add underscore separators to numeric literal
Safe fix
36 36 | neg_with_separators = -1_000_000 # should not trigger
37 37 | neg_with_spaces = - 100000
38 38 | neg_oct = -0o1234567
39 |-neg_hex = -0xABCDEF
39 |+neg_hex = -0xAB_CDEF
40 40 | neg_bin -0b0101010100101
41 41 | neg_hex_with_spaces = - 0xABCDEF
42 42 |
RUF062.py:40:10: RUF062 [*] Large numeric literal without underscore separators
|
38 | neg_oct = -0o1234567
39 | neg_hex = -0xABCDEF
40 | neg_bin -0b0101010100101
| ^^^^^^^^^^^^^^^ RUF062
41 | neg_hex_with_spaces = - 0xABCDEF
|
= help: Add underscore separators to numeric literal
Safe fix
37 37 | neg_with_spaces = - 100000
38 38 | neg_oct = -0o1234567
39 39 | neg_hex = -0xABCDEF
40 |-neg_bin -0b0101010100101
40 |+neg_bin -0b01010_10100101
41 41 | neg_hex_with_spaces = - 0xABCDEF
42 42 |
43 43 | # Testing for minimun size thresholds
RUF062.py:41:27: RUF062 [*] Large numeric literal without underscore separators
|
39 | neg_hex = -0xABCDEF
40 | neg_bin -0b0101010100101
41 | neg_hex_with_spaces = - 0xABCDEF
| ^^^^^^^^ RUF062
42 |
43 | # Testing for minimun size thresholds
|
= help: Add underscore separators to numeric literal
Safe fix
38 38 | neg_oct = -0o1234567
39 39 | neg_hex = -0xABCDEF
40 40 | neg_bin -0b0101010100101
41 |-neg_hex_with_spaces = - 0xABCDEF
41 |+neg_hex_with_spaces = - 0xAB_CDEF
42 42 |
43 43 | # Testing for minimun size thresholds
44 44 | dec_4_digits = 1234 # Should not trigger, just below the threshold of 5 digits
RUF062.py:45:16: RUF062 [*] Large numeric literal without underscore separators
|
43 | # Testing for minimun size thresholds
44 | dec_4_digits = 1234 # Should not trigger, just below the threshold of 5 digits
45 | dec_5_digits = 12345 # Should trigger, 5 digits
| ^^^^^ RUF062
46 | oct_4_digits = 0o1234 # Should not trigger, just below the threshold of 4 digits
47 | oct_5_digits = 0o12345 # Should trigger, 5 digits
|
= help: Add underscore separators to numeric literal
Safe fix
42 42 |
43 43 | # Testing for minimun size thresholds
44 44 | dec_4_digits = 1234 # Should not trigger, just below the threshold of 5 digits
45 |-dec_5_digits = 12345 # Should trigger, 5 digits
45 |+dec_5_digits = 12_345 # Should trigger, 5 digits
46 46 | oct_4_digits = 0o1234 # Should not trigger, just below the threshold of 4 digits
47 47 | oct_5_digits = 0o12345 # Should trigger, 5 digits
48 48 | bin_8_digits = 0b01010101 # Should not trigger, just below the threshold of 9 digits
RUF062.py:47:16: RUF062 [*] Large numeric literal without underscore separators
|
45 | dec_5_digits = 12345 # Should trigger, 5 digits
46 | oct_4_digits = 0o1234 # Should not trigger, just below the threshold of 4 digits
47 | oct_5_digits = 0o12345 # Should trigger, 5 digits
| ^^^^^^^ RUF062
48 | bin_8_digits = 0b01010101 # Should not trigger, just below the threshold of 9 digits
49 | bin_9_digits = 0b101010101 # Should trigger, 9 digits
|
= help: Add underscore separators to numeric literal
Safe fix
44 44 | dec_4_digits = 1234 # Should not trigger, just below the threshold of 5 digits
45 45 | dec_5_digits = 12345 # Should trigger, 5 digits
46 46 | oct_4_digits = 0o1234 # Should not trigger, just below the threshold of 4 digits
47 |-oct_5_digits = 0o12345 # Should trigger, 5 digits
47 |+oct_5_digits = 0o1_2345 # Should trigger, 5 digits
48 48 | bin_8_digits = 0b01010101 # Should not trigger, just below the threshold of 9 digits
49 49 | bin_9_digits = 0b101010101 # Should trigger, 9 digits
50 50 | hex_4_digits = 0xABCD # Should not trigger, just below the threshold of 5 digits
RUF062.py:49:16: RUF062 [*] Large numeric literal without underscore separators
|
47 | oct_5_digits = 0o12345 # Should trigger, 5 digits
48 | bin_8_digits = 0b01010101 # Should not trigger, just below the threshold of 9 digits
49 | bin_9_digits = 0b101010101 # Should trigger, 9 digits
| ^^^^^^^^^^^ RUF062
50 | hex_4_digits = 0xABCD # Should not trigger, just below the threshold of 5 digits
51 | hex_5_digits = 0xABCDE # Should trigger, 5 digits
|
= help: Add underscore separators to numeric literal
Safe fix
46 46 | oct_4_digits = 0o1234 # Should not trigger, just below the threshold of 4 digits
47 47 | oct_5_digits = 0o12345 # Should trigger, 5 digits
48 48 | bin_8_digits = 0b01010101 # Should not trigger, just below the threshold of 9 digits
49 |-bin_9_digits = 0b101010101 # Should trigger, 9 digits
49 |+bin_9_digits = 0b1_01010101 # Should trigger, 9 digits
50 50 | hex_4_digits = 0xABCD # Should not trigger, just below the threshold of 5 digits
51 51 | hex_5_digits = 0xABCDE # Should trigger, 5 digits
52 52 | flt_4_digits = .1234 # Should not trigger, just below the threshold of 5 digits
RUF062.py:51:16: RUF062 [*] Large numeric literal without underscore separators
|
49 | bin_9_digits = 0b101010101 # Should trigger, 9 digits
50 | hex_4_digits = 0xABCD # Should not trigger, just below the threshold of 5 digits
51 | hex_5_digits = 0xABCDE # Should trigger, 5 digits
| ^^^^^^^ RUF062
52 | flt_4_digits = .1234 # Should not trigger, just below the threshold of 5 digits
53 | flt_5_digits = .12345 # Should trigger, 5 digits
|
= help: Add underscore separators to numeric literal
Safe fix
48 48 | bin_8_digits = 0b01010101 # Should not trigger, just below the threshold of 9 digits
49 49 | bin_9_digits = 0b101010101 # Should trigger, 9 digits
50 50 | hex_4_digits = 0xABCD # Should not trigger, just below the threshold of 5 digits
51 |-hex_5_digits = 0xABCDE # Should trigger, 5 digits
51 |+hex_5_digits = 0xA_BCDE # Should trigger, 5 digits
52 52 | flt_4_digits = .1234 # Should not trigger, just below the threshold of 5 digits
53 53 | flt_5_digits = .12345 # Should trigger, 5 digits
RUF062.py:53:16: RUF062 [*] Large numeric literal without underscore separators
|
51 | hex_5_digits = 0xABCDE # Should trigger, 5 digits
52 | flt_4_digits = .1234 # Should not trigger, just below the threshold of 5 digits
53 | flt_5_digits = .12345 # Should trigger, 5 digits
| ^^^^^^ RUF062
|
= help: Add underscore separators to numeric literal
Safe fix
50 50 | hex_4_digits = 0xABCD # Should not trigger, just below the threshold of 5 digits
51 51 | hex_5_digits = 0xABCDE # Should trigger, 5 digits
52 52 | flt_4_digits = .1234 # Should not trigger, just below the threshold of 5 digits
53 |-flt_5_digits = .12345 # Should trigger, 5 digits
53 |+flt_5_digits = .123_45 # Should trigger, 5 digits

View file

@ -3454,6 +3454,70 @@ pub struct RuffOptions {
note = "The `allowed-markup-names` option has been moved to the `flake8-bandit` section of the configuration."
)]
pub allowed_markup_calls: Option<Vec<String>>,
/// Size of digits groups for large hexadecimal numbers.
#[option(
default = "4",
value_type = "integer",
example = "hex-digit-group-size = 4"
)]
pub hex_digit_group_size: Option<usize>,
/// Size of digits groups for large octal numbers.
#[option(
default = "4",
value_type = "integer",
example = "oct-digit-group-size = 4"
)]
pub oct_digit_group_size: Option<usize>,
/// Size of digits groups for large binary numbers.
#[option(
default = "8",
value_type = "integer",
example = "bin-digit-group-size = 8"
)]
pub bin_digit_group_size: Option<usize>,
/// Whether to use Indian-style decimal formatting
#[option(
default = r#"false"#,
value_type = "bool",
example = "use-indian-decimal-formatting = false"
)]
pub use_indian_decimal_formatting: Option<bool>,
/// Minimum number of decimal digits to trigger grouping
#[option(
default = "5",
value_type = "integer",
example = "dec-digit-grouping-threshold = 5"
)]
pub dec_digit_grouping_threshold: Option<usize>,
/// Minimum number of hexadecimal digits to trigger grouping
#[option(
default = "5",
value_type = "integer",
example = "hex-digit-grouping-threshold = 5"
)]
pub hex_digit_grouping_threshold: Option<usize>,
/// Minimum number of octal digits to trigger grouping
#[option(
default = "5",
value_type = "integer",
example = "oct-digit-grouping-threshold = 5"
)]
pub oct_digit_grouping_threshold: Option<usize>,
/// Minimum number of binary digits to trigger grouping
#[option(
default = "9",
value_type = "integer",
example = "bin-digit-grouping-threshold = 5"
)]
pub bin_digit_grouping_threshold: Option<usize>,
}
impl RuffOptions {
@ -3462,6 +3526,14 @@ impl RuffOptions {
parenthesize_tuple_in_subscript: self
.parenthesize_tuple_in_subscript
.unwrap_or_default(),
use_indian_decimal_format: self.use_indian_decimal_formatting.unwrap(),
hex_digit_group_size: self.hex_digit_group_size.unwrap_or_default(),
oct_digit_group_size: self.oct_digit_group_size.unwrap_or_default(),
bin_digit_group_size: self.bin_digit_group_size.unwrap_or_default(),
dec_digit_grouping_threshold: self.dec_digit_grouping_threshold.unwrap_or_default(),
hex_digit_grouping_threshold: self.hex_digit_grouping_threshold.unwrap_or_default(),
oct_digit_grouping_threshold: self.oct_digit_grouping_threshold.unwrap_or_default(),
bin_digit_grouping_threshold: self.bin_digit_grouping_threshold.unwrap_or_default(),
}
}
}

1
ruff.schema.json generated
View file

@ -4040,6 +4040,7 @@
"RUF06",
"RUF060",
"RUF061",
"RUF062",
"RUF063",
"RUF064",
"RUF1",