-
-
Notifications
You must be signed in to change notification settings - Fork 982
feat(useNumericSeparators): add options for minimum digits and group length #9571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| --- | ||
| "@biomejs/biome": minor | ||
| --- | ||
|
|
||
| Added configurable options to the [`useNumericSeparators`](https://biomejs.dev/linter/rules/use-numeric-separators/) rule. Users can now customize the minimum number of digits required before adding separators and the group length for each type of numeric literal (`binary`, `octal`, `decimal`, `hexadecimal`). | ||
|
|
||
| ```json | ||
| { | ||
| "linter": { | ||
| "rules": { | ||
| "style": { | ||
| "useNumericSeparators": { | ||
| "level": "error", | ||
| "options": { | ||
| "decimal": { | ||
| "minimumDigits": 7, | ||
| "groupLength": 3 | ||
| }, | ||
| "hexadecimal": { | ||
| "minimumDigits": 4, | ||
| "groupLength": 2 | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,6 +52,53 @@ declare_lint_rule! { | |
| /// var a = 0b1100_1100; | ||
| /// ``` | ||
| /// | ||
| /// ## Options | ||
| /// | ||
| /// Each numeric literal type can be configured with its own options object. | ||
| /// | ||
| /// ```json,options | ||
| /// { | ||
| /// "options": { | ||
| /// "decimal": { | ||
| /// "minimumDigits": 7, | ||
| /// "groupLength": 3 | ||
| /// }, | ||
| /// "hexadecimal": { | ||
| /// "minimumDigits": 4, | ||
| /// "groupLength": 2 | ||
| /// } | ||
| /// } | ||
| /// } | ||
| /// ``` | ||
|
Comment on lines
+59
to
+72
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To remove, as it doesn't follow the writing style for options |
||
| /// | ||
| /// ### `binary` | ||
| /// | ||
| /// Options for binary literals (e.g., `0b1010_0001`). | ||
| /// | ||
| /// - `minimumDigits` — Minimum number of digits required before adding separators. **Default: `0`** | ||
| /// - `groupLength` — Number of digits between separators. **Default: `4`** | ||
| /// | ||
| /// ### `octal` | ||
| /// | ||
| /// Options for octal literals (e.g., `0o1234_5670`). | ||
| /// | ||
| /// - `minimumDigits` — Minimum number of digits required before adding separators. **Default: `0`** | ||
| /// - `groupLength` — Number of digits between separators. **Default: `4`** | ||
| /// | ||
| /// ### `decimal` | ||
| /// | ||
| /// Options for decimal literals (e.g., `1_234_567`). | ||
| /// | ||
| /// - `minimumDigits` — Minimum number of digits required before adding separators. **Default: `5`** | ||
| /// - `groupLength` — Number of digits between separators. **Default: `3`** | ||
| /// | ||
|
Comment on lines
+74
to
+94
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None of these options follows our pattern. Each option should have an option code block and a code block
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't want to overload users with repeated information, which was my original intention. I have 2 ideas, but I'm kinda struggling to decide what would be better.
Which do you think is better? I'm personally leaning towards 2. |
||
| /// ### `hexadecimal` | ||
| /// | ||
| /// Options for hexadecimal literals (e.g., `0xAB_CD`). | ||
| /// | ||
| /// - `minimumDigits` — Minimum number of digits required before adding separators. **Default: `0`** | ||
| /// - `groupLength` — Number of digits between separators. **Default: `2`** | ||
| /// | ||
| pub UseNumericSeparators { | ||
| version: "2.0.0", | ||
| name: "useNumericSeparators", | ||
|
|
@@ -77,7 +124,7 @@ impl Rule for UseNumericSeparators { | |
| return None; | ||
| } | ||
|
|
||
| let expected = format_numeric_literal(raw); | ||
| let expected = format_numeric_literal(raw, ctx.options()); | ||
|
|
||
| if raw == expected { | ||
| None | ||
|
|
@@ -122,7 +169,7 @@ impl Rule for UseNumericSeparators { | |
|
|
||
| fn action(ctx: &RuleContext<Self>, state: &Self::State) -> Option<JsRuleAction> { | ||
| let token = ctx.query().value_token().ok()?; | ||
| let num = format_numeric_literal(token.text_trimmed()); | ||
| let num = format_numeric_literal(token.text_trimmed(), ctx.options()); | ||
|
|
||
| let new_token = JsSyntaxToken::new_detached(token.kind(), &num, [], []); | ||
| let mut mutation = ctx.root().begin(); | ||
|
|
@@ -148,14 +195,8 @@ pub enum State { | |
| UnnecessaryGrouping, | ||
| } | ||
|
|
||
| // Options for the minimum length of a number required before adding separators and the length of digit groups between separators, respectively. | ||
| const BINARY_OPTS: (usize, usize) = (0, 4); | ||
| const OCTAL_OPTS: (usize, usize) = (0, 4); | ||
| const DECIMAL_OPTS: (usize, usize) = (5, 3); | ||
| const HEXADECIMAL_OPTS: (usize, usize) = (0, 2); | ||
|
|
||
| /// Formats all parts of a numeric literal by adding separators between groups of digits when appropriate. | ||
| fn format_numeric_literal(raw: &str) -> String { | ||
| fn format_numeric_literal(raw: &str, options: &UseNumericSeparatorsOptions) -> String { | ||
| let mut bytes = raw.bytes().peekable(); | ||
| let mut result = Vec::new(); | ||
| let mut current_num = Vec::new(); | ||
|
|
@@ -164,17 +205,17 @@ fn format_numeric_literal(raw: &str) -> String { | |
| let mut in_fraction = false; | ||
| let mut prefix_parsed = false; | ||
|
|
||
| let (mut min_digits, mut group_len) = DECIMAL_OPTS; | ||
| let (mut min_digits, mut group_len) = options.decimal(); | ||
|
|
||
| while let Some(b) = bytes.next() { | ||
| match b { | ||
| b'_' => {} | ||
| b'0' if !prefix_parsed && !in_fraction => { | ||
| if let Some(&next) = bytes.peek() { | ||
| let opts = match next { | ||
| b'b' | b'B' => Some(BINARY_OPTS), | ||
| b'o' | b'O' | b'0'..=b'7' => Some(OCTAL_OPTS), | ||
| b'x' | b'X' => Some(HEXADECIMAL_OPTS), | ||
| b'b' | b'B' => Some(options.binary()), | ||
| b'o' | b'O' | b'0'..=b'7' => Some(options.octal()), | ||
| b'x' | b'X' => Some(options.hexadecimal()), | ||
| _ => None, | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| /* should generate diagnostics */ | ||
|
|
||
| let foo; | ||
|
|
||
| // Decimal: minimumDigits: 7, so 7+ digits without separators are invalid | ||
| foo = 1234567; // 1_234_567 | ||
| foo = 1234567890; // 1_234_567_890 | ||
|
|
||
| // Hexadecimal: minimumDigits: 4, so 4+ digits without separators are invalid | ||
| foo = 0xABCD; // 0xAB_CD | ||
| foo = 0xABCDEF; // 0xAB_CD_EF | ||
|
|
||
| // Binary: groupLength: 2, wrong grouping | ||
| foo = 0b11001100; // 0b11_00_11_00 | ||
| foo = 0b1010_0001; // 0b10_10_00_01 | ||
|
|
||
| // Octal: groupLength: 2, wrong grouping | ||
| foo = 0o12345670; // 0o12_34_56_70 | ||
| foo = 0o1234_5670; // 0o12_34_56_70 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
In eslint-plugin-unicorn'snumeric-separators-stylerule, isonlyIfContainsSeparatorsupported both at top-level and per numeric type?💡 Result:
Yes. The rule supports
onlyIfContainsSeparatorboth:{ onlyIfContainsSeparator: true }), applying as the default for all numeric types, andbinary/octal/hexadecimal/number), overriding the top-level value for that type. The docs state this explicitly, and the implementation merges top-level into each type while allowing the per-type option to override it. (raw.githubusercontent.com)Citations:
🏁 Script executed:
Repository: biomejs/biome
Length of output: 837
🏁 Script executed:
rg -t rs "struct.*UseNumericSeparatorsOptions" -A 15Repository: biomejs/biome
Length of output: 83
🏁 Script executed:
rg "struct.*UseNumericSeparatorsOptions" -A 15Repository: biomejs/biome
Length of output: 1731
🏁 Script executed:
rg "struct.*NumericLiteralSeparatorOptions" -A 10Repository: biomejs/biome
Length of output: 1137
Capture
onlyIfContainsSeparatorfor migration notice—Biome doesn't support it yet, but silently dropping it can surprise users.ESLint's rule supports this option at both top-level and per-type, but Biome's
NumericLiteralSeparatorOptionsdoesn't include it. Rather than silently dropping it (lines 84–98 and 111–130), capture it in the structs and emit a migration notice when present, so the behaviour delta is explicit.Suggested capture
pub(crate) struct NumericSeparatorsStyleOptions { + only_if_contains_separator: Option<bool>, number: EslintNumericSeparatorTypeOptions, binary: EslintNumericSeparatorTypeOptions, octal: EslintNumericSeparatorTypeOptions, hexadecimal: EslintNumericSeparatorTypeOptions, } pub(crate) struct EslintNumericSeparatorTypeOptions { + only_if_contains_separator: Option<bool>, minimum_digits: Option<u8>, group_length: Option<u8>, }📝 Committable suggestion
🤖 Prompt for AI Agents