feat(lint): add useMemberOrdering nursery rule#9042
feat(lint): add useMemberOrdering nursery rule#9042thilllon wants to merge 6 commits intobiomejs:mainfrom
Conversation
🦋 Changeset detectedLatest commit: 8d96045 The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
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 |
a3f8fb1 to
7922bd8
Compare
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a new nursery lint rule Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs`:
- Around line 90-96: The diagnostic currently emits a hardcoded note in the
.note(markup! { "Members should be ordered by: properties, constructors,
methods." }) call which is misleading for non-default or user-configured orders;
update the code in use_member_ordering.rs to either remove this static note or
build the note text dynamically from the active ordering settings (the rule's
configured order) and use that generated string in the .note(...) invocation
(refer to the place where .note(markup! { ... }) is called and the state/options
that contain the ordering to compose the message).
- Line 54: The rule registration currently uses
RuleSource::EslintTypeScript("member-ordering").same() which incorrectly claims
identical behavior; change it to
RuleSource::EslintTypeScript("member-ordering").inspired() to indicate we only
mirror some class-member ordering behavior (not full
interface/type/enum/memberTypes/optionalityOrder/order support). Update the
entry where sources: &[RuleSource::EslintTypeScript("member-ordering").same()]
is defined to use .inspired() so the rule source accurately reflects
partial/modified behavior.
- Around line 427-451: In find_order_violations, prev_rank currently stores the
last seen member's rank which lets lower-ranked violating members lower the
baseline and mask later violations; change the logic so prev_rank tracks the
maximum rank seen so far (and its label) instead of the immediate predecessor:
when you compute rank = group.rank(order), if rank < prev_r push the
MemberOrderViolation (using member_range and expected_before_name), but do NOT
set prev_rank to the lower rank—instead set prev_rank = Some((max(prev_r, rank),
label_for_max)) so prev_rank always holds the highest rank and its corresponding
label; update references to prev_label/prev_r accordingly in the loop.
🧹 Nitpick comments (2)
crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs (1)
102-229:Accessor/StaticAccessor/ etc. groups are indefault_order()but never classified.
classify_membermaps getters →GetAccessor*and setters →SetAccessor*, but never returns the combinedAccessor*variants. Those combined groups occupy rank positions in the default order, effectively acting as dead slots. This isn't a bug per se, but it means the combined accessor groups are only useful in custom configurations, which could confuse users expecting them to work out of the box.Consider either documenting this clearly or removing the combined accessor entries from
default_order().crates/biome_rule_options/src/use_member_ordering.rs (1)
134-192:default_order()allocates a newVecon every call — consider a static slice.
groups()callsdefault_order()which builds a fresh 44-elementVeceach time. Since the default never changes, a&'static [MemberGroup]constant would avoid the allocation entirely and letgroups()return aVeconly when the user provides a custom order.Not urgent — this is called once per class — but it's a tidy improvement.
Sketch
const DEFAULT_ORDER: &[MemberGroup] = &[ MemberGroup::IndexSignature, MemberGroup::StaticIndexSignature, // ... all variants ... MemberGroup::StaticBlock, ]; impl MemberGroup { pub fn default_order() -> &'static [MemberGroup] { DEFAULT_ORDER } }Then adjust
groups()andrank()signatures to accept&[MemberGroup](which they already do on the consumer side).
crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs
Outdated
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs
Outdated
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs
Outdated
Show resolved
Hide resolved
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs`:
- Around line 88-100: The current deduplication uses
label.split_whitespace().last() which truncates meaningful group names (symbols:
seen, order, label, broad, order_description) — change this to use the full
group label (or map each MemberGroup variant to an explicit, human-friendly
broad category) and dedupe while preserving order: iterate over order, derive
the full label or mapped category from the MemberGroup variant instead of using
split_whitespace().last(), track seen categories with a HashSet (or Vec +
contains) to avoid duplicates, push the full category strings into seen, and
then join seen into order_description so the resulting message uses clear,
complete group names.
- Around line 455-467: The current logic uses group.rank(order) and treats the
sentinel value order.len() like a normal rank, which causes unlisted groups to
inflate max_rank and wrongly trigger MemberOrderViolation; change the comparison
in the member-order loop so that when group.rank(order) == order.len() you skip
that member (do not compare against or assign to max_rank and do not push a
violation for it), leaving max_rank unchanged for subsequent listed groups;
reference the symbols max_rank, group.rank(order), MemberOrderViolation,
member_range and expected_before_name when locating and updating the code.
🧹 Nitpick comments (2)
crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs (2)
117-244: Consider extracting shared classification logic to reduce repetition.The property, method, getter, and setter branches all follow the same pattern: extract
is_static,is_hash_private,accessibility, then callresolve_*_group. A small generic helper could collapse these ~120 lines significantly. Not urgent, but would help maintainability as the rule evolves.
263-370: Could reduce boilerplate with a declarative macro.The five
has_static_modifier_*and fourget_*_accessibilityfunctions are structurally identical — only the types and variants differ. A smallmacro_rules!could generate them all. Purely optional, but would cut ~100 lines.
crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs
Outdated
Show resolved
Hide resolved
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs`:
- Around line 210-219: The TS getter/setter signature branches hardcode
is_hash_private=false; instead determine hash-private from the member name like
other cases: call is_hash_private_name on the signature's name (e.g. use
getter.name() / setter.name() to get the AnyJsClassMemberName and pass it to
is_hash_private_name) and pass that boolean into resolve_get_accessor_group /
resolve_set_accessor_group; keep existing is_static
(has_static_modifier_ts_method) and accessibility logic intact.
🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs (1)
258-365: Consider a macro to reduce the modifier-helper boilerplate.Five
has_static_modifier_*and fourget_*_accessibilityfunctions are structurally identical, differing only in the modifier list type and variant path. A declarative macro could generate all of them from a table of(fn_name, list_type, variant_prefix)tuples, cutting ~110 lines down to ~20.Not blocking — the current code is correct and the types unfortunately don't share a trait.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Merging this PR will degrade performance by 6.55%
Performance Changes
Comparing Footnotes
|
| impl Rule for UseMemberOrdering { | ||
| type Query = Ast<JsClassMemberList>; | ||
| type State = MemberOrderViolation; | ||
| type Signals = Vec<Self::State>; |
There was a problem hiding this comment.
nit: boxed slice instead of Vec
| type Signals = Vec<Self::State>; | |
| type Signals = Box<[Self::State]>; |
| } | ||
| ``` | ||
|
|
||
| The default order is: index signatures, properties, accessors, constructors, methods, static blocks. The order is fully configurable via a `groups` option. |
There was a problem hiding this comment.
This option is undocumented in the rule.
| customOrder.options:2:5 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | ||
|
|
||
| × Found an unknown key `options`. | ||
|
|
||
| 1 │ { | ||
| > 2 │ "options": { | ||
| │ ^^^^^^^^^ | ||
| 3 │ "groups": ["constructor", "method", "property"] | ||
| 4 │ } | ||
|
|
||
| i Known keys: |
There was a problem hiding this comment.
this diagnostic shouldn't be here
| class InvalidCustom { | ||
| name; | ||
| constructor() {} | ||
| } |
There was a problem hiding this comment.
split this into 2, one valid and the other invalid. include the magic comments at the top to flag which one should or shouldn't emit diagnostics:
/* should not generate diagnostics *//* should generate diagnostics */
| if rank < max_r { | ||
| violations.push(MemberOrderViolation { | ||
| member_range: *range, | ||
| expected_before_name: max_label.to_string(), |
There was a problem hiding this comment.
avoid string allocation, use TokenText instead
| }; | ||
| let rank = group.rank(order); | ||
| // Skip members whose group is not listed in the configured order | ||
| if rank == sentinel { |
There was a problem hiding this comment.
this check doesn't look right to me. wouldn't it truncate instead of filter groups out? I could be misreading.
| .note(markup! { | ||
| "Members should be ordered by: "{order_description}"." |
There was a problem hiding this comment.
IIRC there's a footer_list helper that lets you avoid the string allocation here.
Add a new nursery lint rule `useMemberOrdering` that enforces a consistent ordering of class members. This rule corresponds to ESLint TypeScript's `@typescript-eslint/member-ordering`. The default order is: index signatures, static properties, instance properties, static accessors, instance accessors, static methods, constructor, instance methods, static blocks. The order is fully configurable via a `groups` option. Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
- Change RuleSource from .same() to .inspired() since this implementation covers only class members, not interfaces/type literals/enums. - Generate diagnostic note dynamically from the active ordering config instead of using a hardcoded message. - Fix bug in find_order_violations: track maximum rank seen so far instead of previous member's rank, so violations after a lower-ranked member are not masked. Co-authored-by: Cursor <[email protected]>
- Add broad_category() method to MemberGroup for clear diagnostic messages (e.g., "index signatures", "properties", "accessors", "constructors", "methods", "static blocks") instead of truncated last-word heuristic. - Skip unlisted groups in find_order_violations to prevent sentinel rank values from inflating max_rank and causing false violations in custom configurations. Co-authored-by: Cursor <[email protected]>
Replace hardcoded `false` for is_hash_private on TsGetterSignatureClassMember and TsSetterSignatureClassMember with proper name checking via is_hash_private_name(), consistent with all other member type branches. Co-authored-by: Cursor <[email protected]>
- Use Box<[Self::State]> instead of Vec for type Signals - Change expected_before_name from String to &'static str to avoid allocation - Use footer_list helper in diagnostic instead of string join - Add clarifying comment for sentinel check logic - Split customOrder test into separate valid/invalid files with magic comments - Document groups option in rule docstring and changeset - Fix clippy warnings (explicit_into_iter_loop, map_unwrap_or) Co-Authored-By: Claude Opus 4.6 <[email protected]>
e88e4f5 to
8d96045
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs`:
- Around line 152-210: classify_member() currently only returns GetAccessor and
SetAccessor but the MemberGroup enum and default_order() include combined
variants like Accessor/StaticAccessor/ProtectedAccessor that will never be
produced; either remove those combined Accessor variants and their ranks from
MemberGroup and default_order() or, if they are reserved for TC39 auto-accessor
support, add a clear comment on the enum and in default_order() explaining they
are placeholders for the proposal and why classify_member() doesn't produce
them; update any code that matches on these combined variants (e.g.,
default_order(), ranking logic) to reflect the removal or to document planned
future support so custom orders like ["property","accessor","method"] behave as
expected.
- Around line 58-88: The rustdoc lists member group names in camelCase (e.g.,
indexSignature, hashPrivateProperty, staticBlock) but the actual deserialised
names are defined by the MemberGroup enum via #[serde(rename = "...")] and the
Deserializable macro (which expects kebab-case and `#private-`… forms); update the
documentation comment above use_member_ordering.rs to list the exact JSON keys
that the enum accepts (the serde-renamed variants such as index-signature,
static-index-signature, property, static-property, `#private-property` or
`#private-`… notation used in the enum, constructor, static-block, etc.) so copied
examples match the MemberGroup enum/Deserializable expectations (refer to
MemberGroup enum and the Deserializable macro for the canonical names to
document).
🧹 Nitpick comments (2)
crates/biome_rule_options/src/use_member_ordering.rs (1)
136-192: Consider makingdefault_ordera const orLazyLock<Vec<…>>.
default_order()allocates a freshVecof ~43 elements on every call. It's invoked in bothrun()anddiagnostic()(the latter once per violation). For a class with many members, this could mean several allocations of the same data.A
constarray or astatic LazyLockwould avoid the repeated heap allocation.♻️ Sketch using a const array
- pub fn default_order() -> Vec<MemberGroup> { - vec![ + pub const DEFAULT_ORDER: &'static [MemberGroup] = &[ // Index signatures MemberGroup::IndexSignature, MemberGroup::StaticIndexSignature, // ...same entries... MemberGroup::StaticBlock, - ] - } + ]; + + pub fn default_order() -> Vec<MemberGroup> { + Self::DEFAULT_ORDER.to_vec() + }Then the callers that only need
&[MemberGroup]can reference the const directly.crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs (1)
126-149: Diagnostic recomputesgroups()and deduplication for every violation.
options.groups()clones the fullVec<MemberGroup>and the broad-category dedup loop runs on eachdiagnostic()invocation. For a class with many out-of-order members, this is repeated work. Consider caching the order description string inMemberOrderViolationor computing it once via a shared approach.
| /// Available member groups: | ||
| /// - `indexSignature`, `staticIndexSignature` | ||
| /// - `property`, `staticProperty`, `protectedProperty`, `protectedStaticProperty`, | ||
| /// `privateProperty`, `privateStaticProperty`, `hashPrivateProperty`, `hashPrivateStaticProperty` | ||
| /// - `accessor`, `staticAccessor`, `protectedAccessor`, `protectedStaticAccessor`, | ||
| /// `privateAccessor`, `privateStaticAccessor`, `hashPrivateAccessor`, `hashPrivateStaticAccessor` | ||
| /// - `getAccessor`, `staticGetAccessor`, `protectedGetAccessor`, `protectedStaticGetAccessor`, | ||
| /// `privateGetAccessor`, `privateStaticGetAccessor`, `hashPrivateGetAccessor`, `hashPrivateStaticGetAccessor` | ||
| /// - `setAccessor`, `staticSetAccessor`, `protectedSetAccessor`, `protectedStaticSetAccessor`, | ||
| /// `privateSetAccessor`, `privateStaticSetAccessor`, `hashPrivateSetAccessor`, `hashPrivateStaticSetAccessor` | ||
| /// - `method`, `staticMethod`, `protectedMethod`, `protectedStaticMethod`, | ||
| /// `privateMethod`, `privateStaticMethod`, `hashPrivateMethod`, `hashPrivateStaticMethod` | ||
| /// - `constructor` | ||
| /// - `staticBlock` | ||
| /// | ||
| /// Example custom order: | ||
| /// ```json | ||
| /// { | ||
| /// "linter": { | ||
| /// "rules": { | ||
| /// "nursery": { | ||
| /// "useMemberOrdering": { | ||
| /// "options": { | ||
| /// "groups": ["constructor", "method", "property"] | ||
| /// } | ||
| /// } | ||
| /// } | ||
| /// } | ||
| /// } | ||
| /// } | ||
| /// ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find and examine the MemberGroup enum definition
rg -n "enum MemberGroup" crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs -A 50Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
# Check the exact serde rename attributes used
rg -n "serde.*rename" crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs -B 1 -A 1 | head -100Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
# Search for test files or snapshots for useMemberOrdering rule
fd -i "usememberordering" crates/biome_js_analyze/tests/ --type fRepository: biomejs/biome
Length of output: 39
🏁 Script executed:
# Check if there are any JSON config examples in tests
find crates/biome_js_analyze/tests -name "*.json" | xargs grep -l "useMemberOrdering" 2>/dev/null | head -5Repository: biomejs/biome
Length of output: 244
🏁 Script executed:
# Examine the Deserializable macro to understand its behaviour with serde rename
rg -n "impl.*Deserializable" crates/biome_deserialize/src/lib.rs -A 20 | head -80Repository: biomejs/biome
Length of output: 2777
🏁 Script executed:
# Let's read the test JSON files to see the actual format used
cat crates/biome_js_analyze/tests/specs/nursery/useMemberOrdering/invalid_customOrder.options.jsonRepository: biomejs/biome
Length of output: 332
🏁 Script executed:
cat crates/biome_js_analyze/tests/specs/nursery/useMemberOrdering/valid_customOrder.options.jsonRepository: biomejs/biome
Length of output: 332
🏁 Script executed:
# Now find and examine the MemberGroup enum definition more carefully
cat crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs | head -150Repository: biomejs/biome
Length of output: 5169
🏁 Script executed:
# Search more broadly for MemberGroup enum
rg -A 80 "^enum MemberGroup" crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rsRepository: biomejs/biome
Length of output: 39
🏁 Script executed:
# Check the biome_deserialize_macros to understand how Deserializable handles serde(rename)
rg "serde.*rename" crates/biome_deserialize_macros/src/ -A 2 -B 2 | head -100Repository: biomejs/biome
Length of output: 2475
🏁 Script executed:
# Find where MemberGroup is defined - it's likely in biome_rule_options
fd "use_member_ordering" crates/biome_rule_options/src/ --type fRepository: biomejs/biome
Length of output: 109
🏁 Script executed:
# Read the rule options file containing MemberGroup
find crates/biome_rule_options -name "*use_member_ordering*" -type f -exec cat {} \;Repository: biomejs/biome
Length of output: 12863
🏁 Script executed:
# Also check if there are more comprehensive test JSON files
find crates/biome_js_analyze/tests/specs/nursery/useMemberOrdering -type f -exec cat {} \;Repository: biomejs/biome
Length of output: 17248
Correct the rustdoc group names to match the accepted JSON format.
The documentation lists available member groups in camelCase (e.g., indexSignature, hashPrivateProperty, staticBlock), but the MemberGroup enum variants are renamed via #[serde(rename)] to use kebab-case and #private-… notation, which the Deserializable macro honours. Users copying these names directly from the docs into their biome.json will encounter deserialisation errors.
Correction required for lines 59–71
/// Available member groups:
-/// - `indexSignature`, `staticIndexSignature`
-/// - `property`, `staticProperty`, `protectedProperty`, `protectedStaticProperty`,
-/// `privateProperty`, `privateStaticProperty`, `hashPrivateProperty`, `hashPrivateStaticProperty`
-/// - `accessor`, `staticAccessor`, `protectedAccessor`, `protectedStaticAccessor`,
-/// `privateAccessor`, `privateStaticAccessor`, `hashPrivateAccessor`, `hashPrivateStaticAccessor`
-/// - `getAccessor`, `staticGetAccessor`, `protectedGetAccessor`, `protectedStaticGetAccessor`,
-/// `privateGetAccessor`, `privateStaticGetAccessor`, `hashPrivateGetAccessor`, `hashPrivateStaticGetAccessor`
-/// - `setAccessor`, `staticSetAccessor`, `protectedSetAccessor`, `protectedStaticSetAccessor`,
-/// `privateSetAccessor`, `privateStaticSetAccessor`, `hashPrivateSetAccessor`, `hashPrivateStaticSetAccessor`
-/// - `method`, `staticMethod`, `protectedMethod`, `protectedStaticMethod`,
-/// `privateMethod`, `privateStaticMethod`, `hashPrivateMethod`, `hashPrivateStaticMethod`
+/// - `index-signature`, `static-index-signature`
+/// - `property`, `static-property`, `protected-property`, `protected-static-property`,
+/// `private-property`, `private-static-property`, `#private-property`, `#private-static-property`
+/// - `accessor`, `static-accessor`, `protected-accessor`, `protected-static-accessor`,
+/// `private-accessor`, `private-static-accessor`, `#private-accessor`, `#private-static-accessor`
+/// - `get-accessor`, `static-get-accessor`, `protected-get-accessor`, `protected-static-get-accessor`,
+/// `private-get-accessor`, `private-static-get-accessor`, `#private-get-accessor`, `#private-static-get-accessor`
+/// - `set-accessor`, `static-set-accessor`, `protected-set-accessor`, `protected-static-set-accessor`,
+/// `private-set-accessor`, `private-static-set-accessor`, `#private-set-accessor`, `#private-static-set-accessor`
+/// - `method`, `static-method`, `protected-method`, `protected-static-method`,
+/// `private-method`, `private-static-method`, `#private-method`, `#private-static-method`🤖 Prompt for AI Agents
In `@crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs` around lines
58 - 88, The rustdoc lists member group names in camelCase (e.g.,
indexSignature, hashPrivateProperty, staticBlock) but the actual deserialised
names are defined by the MemberGroup enum via #[serde(rename = "...")] and the
Deserializable macro (which expects kebab-case and `#private-`… forms); update the
documentation comment above use_member_ordering.rs to list the exact JSON keys
that the enum accepts (the serde-renamed variants such as index-signature,
static-index-signature, property, static-property, `#private-property` or
`#private-`… notation used in the enum, constructor, static-block, etc.) so copied
examples match the MemberGroup enum/Deserializable expectations (refer to
MemberGroup enum and the Deserializable macro for the canonical names to
document).
| /// Classify a class member into its `MemberGroup`. | ||
| fn classify_member(member: &AnyJsClassMember) -> Option<MemberGroup> { | ||
| match member { | ||
| AnyJsClassMember::JsConstructorClassMember(_) => Some(MemberGroup::Constructor), | ||
| AnyJsClassMember::JsPropertyClassMember(prop) => { | ||
| let is_static = has_static_modifier_property(&prop.modifiers()); | ||
| let is_hash_private = prop | ||
| .name() | ||
| .ok() | ||
| .is_some_and(|n| is_hash_private_name(&n)); | ||
| let accessibility = get_property_accessibility(&prop.modifiers()); | ||
| Some(resolve_property_group( | ||
| is_static, | ||
| &accessibility, | ||
| is_hash_private, | ||
| )) | ||
| } | ||
| AnyJsClassMember::JsMethodClassMember(method) => { | ||
| let is_static = has_static_modifier_method(&method.modifiers()); | ||
| let is_hash_private = method | ||
| .name() | ||
| .ok() | ||
| .is_some_and(|n| is_hash_private_name(&n)); | ||
| let accessibility = get_method_accessibility(&method.modifiers()); | ||
| Some(resolve_method_group( | ||
| is_static, | ||
| &accessibility, | ||
| is_hash_private, | ||
| )) | ||
| } | ||
| AnyJsClassMember::JsGetterClassMember(getter) => { | ||
| let is_static = has_static_modifier_method(&getter.modifiers()); | ||
| let is_hash_private = getter | ||
| .name() | ||
| .ok() | ||
| .is_some_and(|n| is_hash_private_name(&n)); | ||
| let accessibility = get_method_accessibility(&getter.modifiers()); | ||
| Some(resolve_get_accessor_group( | ||
| is_static, | ||
| &accessibility, | ||
| is_hash_private, | ||
| )) | ||
| } | ||
| AnyJsClassMember::JsSetterClassMember(setter) => { | ||
| let is_static = has_static_modifier_method(&setter.modifiers()); | ||
| let is_hash_private = setter | ||
| .name() | ||
| .ok() | ||
| .is_some_and(|n| is_hash_private_name(&n)); | ||
| let accessibility = get_method_accessibility(&setter.modifiers()); | ||
| Some(resolve_set_accessor_group( | ||
| is_static, | ||
| &accessibility, | ||
| is_hash_private, | ||
| )) | ||
| } | ||
| AnyJsClassMember::JsStaticInitializationBlockClassMember(_) => { | ||
| Some(MemberGroup::StaticBlock) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find the MemberGroup enum definition
rg -A 30 "enum MemberGroup" crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs | head -50Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
# Check the AnyJsClassMember definition in biome_js_syntax
fd "nodes.rs" crates/biome_js_syntax/src/generated/ | head -1 | xargs rg -A 50 "enum AnyJsClassMember" | head -70Repository: biomejs/biome
Length of output: 2748
🏁 Script executed:
# Look for the default_order function to see what MemberGroup variants are listed
rg -B 5 -A 50 "fn default_order" crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rsRepository: biomejs/biome
Length of output: 39
🏁 Script executed:
# Find MemberGroup enum definition more directly
rg "enum MemberGroup" crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs -A 50Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
# Find the default_order function
rg "default_order\|fn.*default" crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs -B 2 -A 30Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
# Check if there are any mentions of "Accessor" in MemberGroup or default_order context
rg "Accessor" crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs | head -20Repository: biomejs/biome
Length of output: 1765
🏁 Script executed:
# Read the file to find MemberGroup enum definition
cat -n crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs | grep -A 100 "^[[:space:]]*[0-9]*[[:space:]]*enum MemberGroup" | head -120Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
# Also check the file structure - get line count and see the enum at beginning
wc -l crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rsRepository: biomejs/biome
Length of output: 124
🏁 Script executed:
# Read from the start of the file to find MemberGroup
head -200 crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs | tail -150Repository: biomejs/biome
Length of output: 5869
🏁 Script executed:
# Read the beginning of the file to find MemberGroup enum
head -100 crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rsRepository: biomejs/biome
Length of output: 3569
🏁 Script executed:
# Find the MemberGroup enum definition in biome_rule_options
find . -name "*.rs" -path "*/biome_rule_options/*" | xargs grep -l "enum MemberGroup" | head -5Repository: biomejs/biome
Length of output: 111
🏁 Script executed:
# Broader search for MemberGroup enum definition
fd "use_member_ordering" --type f | xargs grep -l "enum MemberGroup"Repository: biomejs/biome
Length of output: 109
🏁 Script executed:
# Read the MemberGroup enum definition file
cat -n crates/biome_rule_options/src/use_member_ordering.rsRepository: biomejs/biome
Length of output: 14949
Combined Accessor groups are dead entries — they'll never match getters or setters.
classify_member() produces only GetAccessor and SetAccessor variants. The combined Accessor, StaticAccessor, ProtectedAccessor, etc. live in the enum and default_order() but no code path ever creates them. This means:
- In the default order: inert rank slots (noise, no functional bug).
- In a custom order like
["property", "accessor", "method"]: users would expect getters and setters to match"accessor", but they won't—they'll silently fall through to the sentinel rank.
If these are placeholders for the TC39 auto-accessor proposal (accessor name;), add a comment explaining that. If not, remove them and their ranks from default_order().
🤖 Prompt for AI Agents
In `@crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs` around lines
152 - 210, classify_member() currently only returns GetAccessor and SetAccessor
but the MemberGroup enum and default_order() include combined variants like
Accessor/StaticAccessor/ProtectedAccessor that will never be produced; either
remove those combined Accessor variants and their ranks from MemberGroup and
default_order() or, if they are reserved for TC39 auto-accessor support, add a
clear comment on the enum and in default_order() explaining they are
placeholders for the proposal and why classify_member() doesn't produce them;
update any code that matches on these combined variants (e.g., default_order(),
ranking logic) to reflect the removal or to document planned future support so
custom orders like ["property","accessor","method"] behave as expected.
There was a problem hiding this comment.
this might also be applicable
Summary
useMemberOrderingthat enforces a consistent ordering of class members, corresponding to ESLint TypeScript's@typescript-eslint/member-ordering.groupsoption with 40+ member categories (properties, methods, accessors, constructors, static blocks, with static/accessibility/hash-private variations).Details
New files
crates/biome_rule_options/src/use_member_ordering.rs—MemberGroupenum andUseMemberOrderingOptionsstructcrates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs— Rule implementation withclassify_member()for allAnyJsClassMembervariantsModified files
crates/biome_diagnostics_categories/src/categories.rs— Added category entrycrates/biome_rule_options/src/lib.rs— Registered modulecrates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs— Added@typescript-eslint/member-orderingmigration mappingTests
valid.js/valid.ts— Correct ordering cases (no diagnostics)invalid.js/invalid.ts— Incorrect ordering cases (diagnostics expected)customOrder.js+customOrder.options.json— Custom group order via optionsTest plan
cargo t use_member_ordering— 6 tests passcargo fmt— No formatting issuescargo insta acceptAI assistance disclosure
Made with Cursor