Skip to content

feat(useNumericSeparators): add options for minimum digits and group length#9571

Draft
dyc3 wants to merge 1 commit intonextfrom
dyc3/number-separator-options
Draft

feat(useNumericSeparators): add options for minimum digits and group length#9571
dyc3 wants to merge 1 commit intonextfrom
dyc3/number-separator-options

Conversation

@dyc3
Copy link
Copy Markdown
Contributor

@dyc3 dyc3 commented Mar 21, 2026

Summary

I realize the way the options are documented is a little different than what we usually do. The agent came up with it, and tbh I think the more concise summary is more preferable than having valid and invalid cases for all 4 * 2 options.

Generated by opus 4.6

discussion: #9316

Test Plan

snapshots

Docs

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 21, 2026

🦋 Changeset detected

Latest commit: bb3efae

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
@biomejs/biome Minor
@biomejs/cli-win32-x64 Minor
@biomejs/cli-win32-arm64 Minor
@biomejs/cli-darwin-x64 Minor
@biomejs/cli-darwin-arm64 Minor
@biomejs/cli-linux-x64 Minor
@biomejs/cli-linux-arm64 Minor
@biomejs/cli-linux-x64-musl Minor
@biomejs/cli-linux-arm64-musl Minor
@biomejs/wasm-web Minor
@biomejs/wasm-bundler Minor
@biomejs/wasm-nodejs Minor
@biomejs/backend-jsonrpc Patch
@biomejs/js-api Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added A-CLI Area: CLI A-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Mar 21, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 21, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 156 skipped benchmarks1


Comparing dyc3/number-separator-options (bb3efae) with next (d5913c9)

Open in CodSpeed

Footnotes

  1. 156 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@dyc3 dyc3 marked this pull request as ready for review March 21, 2026 16:15
@dyc3 dyc3 requested review from a team March 21, 2026 16:15
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 21, 2026

Walkthrough

This change introduces configurable options for the useNumericSeparators linter rule, enabling users to customise separator formatting per numeric literal type: binary, octal, decimal, and hexadecimal. Each type can independently specify minimumDigits and groupLength thresholds. The implementation includes rule option structs, migration support for ESLint's unicorn/numeric-separators-style rule, updated formatter logic, and comprehensive test cases validating both valid and invalid custom configurations across all numeric literal types.

Suggested reviewers

  • ematipico
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding configurable options for minimum digits and group length to the useNumericSeparators rule.
Description check ✅ Passed The description relates to the changeset by noting the documentation approach and acknowledging AI assistance, though it lacks detail about technical implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dyc3/number-separator-options

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/biome_rule_options/src/use_numeric_separators.rs (1)

56-109: Consider reducing duplication in accessor methods.

Each accessor follows the same pattern. You could simplify by calling resolve() unconditionally on an unwrapped-or-default reference:

♻️ Optional refactor
     /// Returns `(min_digits, group_length)` for binary literals.
     pub fn binary(&self) -> (usize, usize) {
-        match &self.binary {
-            Some(opts) => opts.resolve(
-                Self::DEFAULT_BINARY_MIN_DIGITS,
-                Self::DEFAULT_BINARY_GROUP_LENGTH,
-            ),
-            None => (
-                Self::DEFAULT_BINARY_MIN_DIGITS as usize,
-                Self::DEFAULT_BINARY_GROUP_LENGTH as usize,
-            ),
-        }
+        self.binary
+            .as_ref()
+            .unwrap_or(&NumericLiteralSeparatorOptions::default())
+            .resolve(Self::DEFAULT_BINARY_MIN_DIGITS, Self::DEFAULT_BINARY_GROUP_LENGTH)
     }

That said, the explicit match is perfectly readable — feel free to keep it if you prefer the clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_rule_options/src/use_numeric_separators.rs` around lines 56 -
109, The four accessor methods binary, octal, decimal, and hexadecimal duplicate
the same match/None pattern; refactor by centralizing the logic so each just
calls a single resolver. Add a small helper (e.g., resolve_numeric or
resolve_or_default) that accepts the Option (the fields used by
binary/octal/decimal/hexadecimal), the corresponding DEFAULT_*_MIN_DIGITS and
DEFAULT_*_GROUP_LENGTH values, and returns (usize, usize) by calling
opts.resolve(...) when Some or using the defaults when None; then have binary,
octal, decimal, and hexadecimal call that helper passing their respective
DEFAULT_* constants and the field so the resolve() invocation is not duplicated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_cli/src/execute/migrate/eslint_unicorn.rs`:
- Around line 84-98: The migration currently drops the ESLint
onlyIfContainsSeparator option silently; update the
NumericSeparatorsStyleOptions and EslintNumericSeparatorTypeOptions structs to
include an Option<bool> field named only_if_contains_separator (or similar) to
capture that flag, then modify the migration path to detect when
only_if_contains_separator is Some(true/false) and emit a migration notice
explaining Biome doesn't support it and the behavior will be different;
reference the structs NumericSeparatorsStyleOptions and
EslintNumericSeparatorTypeOptions and ensure the migration notice is produced
wherever these structs are parsed/converted so users see the delta.

---

Nitpick comments:
In `@crates/biome_rule_options/src/use_numeric_separators.rs`:
- Around line 56-109: The four accessor methods binary, octal, decimal, and
hexadecimal duplicate the same match/None pattern; refactor by centralizing the
logic so each just calls a single resolver. Add a small helper (e.g.,
resolve_numeric or resolve_or_default) that accepts the Option (the fields used
by binary/octal/decimal/hexadecimal), the corresponding DEFAULT_*_MIN_DIGITS and
DEFAULT_*_GROUP_LENGTH values, and returns (usize, usize) by calling
opts.resolve(...) when Some or using the defaults when None; then have binary,
octal, decimal, and hexadecimal call that helper passing their respective
DEFAULT_* constants and the field so the resolve() invocation is not duplicated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d550d887-f987-44fc-909a-30ad207630d7

📥 Commits

Reviewing files that changed from the base of the PR and between d5913c9 and bb3efae.

⛔ Files ignored due to path filters (4)
  • crates/biome_js_analyze/tests/specs/style/useNumericSeparators/invalidCustomOptions.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/style/useNumericSeparators/validCustomOptions.js.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (10)
  • .changeset/use-numeric-separators-options.md
  • crates/biome_cli/src/execute/migrate/eslint_eslint.rs
  • crates/biome_cli/src/execute/migrate/eslint_to_biome.rs
  • crates/biome_cli/src/execute/migrate/eslint_unicorn.rs
  • crates/biome_js_analyze/src/lint/style/use_numeric_separators.rs
  • crates/biome_js_analyze/tests/specs/style/useNumericSeparators/invalidCustomOptions.js
  • crates/biome_js_analyze/tests/specs/style/useNumericSeparators/invalidCustomOptions.options.json
  • crates/biome_js_analyze/tests/specs/style/useNumericSeparators/validCustomOptions.js
  • crates/biome_js_analyze/tests/specs/style/useNumericSeparators/validCustomOptions.options.json
  • crates/biome_rule_options/src/use_numeric_separators.rs

Comment on lines +84 to +98
/// Note: ESLint uses `number` for decimal literals, while Biome uses `decimal`.
/// The `onlyIfContainsSeparator` option is not supported and is ignored.
#[derive(Clone, Debug, Default, Deserializable)]
pub(crate) struct NumericSeparatorsStyleOptions {
number: EslintNumericSeparatorTypeOptions,
binary: EslintNumericSeparatorTypeOptions,
octal: EslintNumericSeparatorTypeOptions,
hexadecimal: EslintNumericSeparatorTypeOptions,
}

#[derive(Clone, Debug, Default, Deserializable)]
pub(crate) struct EslintNumericSeparatorTypeOptions {
minimum_digits: Option<u8>,
group_length: Option<u8>,
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In eslint-plugin-unicorn's numeric-separators-stylerule, isonlyIfContainsSeparator supported both at top-level and per numeric type?

💡 Result:

Yes. The rule supports onlyIfContainsSeparator both:

  • Top-level ({ onlyIfContainsSeparator: true }), applying as the default for all numeric types, and
  • Per numeric type (binary / 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:

cat -n crates/biome_cli/src/execute/migrate/eslint_unicorn.rs | sed -n '111,130p'

Repository: biomejs/biome

Length of output: 837


🏁 Script executed:

rg -t rs "struct.*UseNumericSeparatorsOptions" -A 15

Repository: biomejs/biome

Length of output: 83


🏁 Script executed:

rg "struct.*UseNumericSeparatorsOptions" -A 15

Repository: biomejs/biome

Length of output: 1731


🏁 Script executed:

rg "struct.*NumericLiteralSeparatorOptions" -A 10

Repository: biomejs/biome

Length of output: 1137


Capture onlyIfContainsSeparator for 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 NumericLiteralSeparatorOptions doesn'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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Note: ESLint uses `number` for decimal literals, while Biome uses `decimal`.
/// The `onlyIfContainsSeparator` option is not supported and is ignored.
#[derive(Clone, Debug, Default, Deserializable)]
pub(crate) struct NumericSeparatorsStyleOptions {
number: EslintNumericSeparatorTypeOptions,
binary: EslintNumericSeparatorTypeOptions,
octal: EslintNumericSeparatorTypeOptions,
hexadecimal: EslintNumericSeparatorTypeOptions,
}
#[derive(Clone, Debug, Default, Deserializable)]
pub(crate) struct EslintNumericSeparatorTypeOptions {
minimum_digits: Option<u8>,
group_length: Option<u8>,
}
/// Note: ESLint uses `number` for decimal literals, while Biome uses `decimal`.
/// The `onlyIfContainsSeparator` option is not supported and is ignored.
#[derive(Clone, Debug, Default, Deserializable)]
pub(crate) struct NumericSeparatorsStyleOptions {
only_if_contains_separator: Option<bool>,
number: EslintNumericSeparatorTypeOptions,
binary: EslintNumericSeparatorTypeOptions,
octal: EslintNumericSeparatorTypeOptions,
hexadecimal: EslintNumericSeparatorTypeOptions,
}
#[derive(Clone, Debug, Default, Deserializable)]
pub(crate) struct EslintNumericSeparatorTypeOptions {
only_if_contains_separator: Option<bool>,
minimum_digits: Option<u8>,
group_length: Option<u8>,
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_cli/src/execute/migrate/eslint_unicorn.rs` around lines 84 - 98,
The migration currently drops the ESLint onlyIfContainsSeparator option
silently; update the NumericSeparatorsStyleOptions and
EslintNumericSeparatorTypeOptions structs to include an Option<bool> field named
only_if_contains_separator (or similar) to capture that flag, then modify the
migration path to detect when only_if_contains_separator is Some(true/false) and
emit a migration notice explaining Biome doesn't support it and the behavior
will be different; reference the structs NumericSeparatorsStyleOptions and
EslintNumericSeparatorTypeOptions and ensure the migration notice is produced
wherever these structs are parsed/converted so users see the delta.

Copy link
Copy Markdown
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs needs some rework to match our standards

Comment on lines +59 to +72
/// ```json,options
/// {
/// "options": {
/// "decimal": {
/// "minimumDigits": 7,
/// "groupLength": 3
/// },
/// "hexadecimal": {
/// "minimumDigits": 4,
/// "groupLength": 2
/// }
/// }
/// }
/// ```
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To remove, as it doesn't follow the writing style for options

Comment on lines +74 to +94
/// ### `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`**
///
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

  1. list the top level options and then provide examples that use both sub-options,
### `decimal`

[description]
[default values for both sub options]
[example options block, both sub options]
[valid/invalid examples]
  1. document the sub-options object, and explain they go under the numeric literal type. so we would have entries like:
### `<numeric literal type>.minimumDigits`

Minimum number of digits required before adding separators. For example, with `minimumDigits` set to `5`, the number `1234` would not require separators, but `12345` would be expected to be formatted as `12_345`.

Default values for `minimumDigits` vary by numeric literal type:
 - `binary`: `0`
 - `octal`: `0`
- `decimal`: `5`
 - `hexadecimal`: `0`

[valid/invalid examples, only one number type]

Which do you think is better? I'm personally leaning towards 2.

Comment on lines +7 to +14
pub struct NumericLiteralSeparatorOptions {
/// Minimum number of digits required before adding separators.
#[serde(skip_serializing_if = "Option::<_>::is_none")]
pub minimum_digits: Option<u8>,
/// Number of digits between separators.
#[serde(skip_serializing_if = "Option::<_>::is_none")]
pub group_length: Option<u8>,
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that both options are optional is weird, becase it means you can have an empty object e.g. "binary": {}. How would that work in this case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would effectively be a no-op. They need to be Option so that merging configs makes sense.

I'll add validation at deserialization to make that impossible.

@dyc3
Copy link
Copy Markdown
Contributor Author

dyc3 commented Apr 17, 2026

I just remembered that our parser doesn't even allow octal numbers. Will fix.

@dyc3 dyc3 marked this pull request as draft April 17, 2026 22:24
@dyc3 dyc3 added this to the Biome v2.5 milestone Apr 17, 2026
@dyc3 dyc3 self-assigned this Apr 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Linter Area: linter L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants