Skip to content

feat(lint): add useMemberOrdering nursery rule#9042

Open
thilllon wants to merge 1 commit intobiomejs:mainfrom
thilllon:member-ordering
Open

feat(lint): add useMemberOrdering nursery rule#9042
thilllon wants to merge 1 commit intobiomejs:mainfrom
thilllon:member-ordering

Conversation

@thilllon
Copy link

Summary

  • Added a new nursery lint rule useMemberOrdering that enforces a consistent ordering of class members, corresponding to ESLint TypeScript's @typescript-eslint/member-ordering.
  • The rule supports a fully configurable groups option with 40+ member categories (properties, methods, accessors, constructors, static blocks, with static/accessibility/hash-private variations).
  • Default order: index signatures → static properties → instance properties → static accessors → instance accessors → static methods → constructor → instance methods → static blocks.

Details

New files

  • crates/biome_rule_options/src/use_member_ordering.rsMemberGroup enum and UseMemberOrderingOptions struct
  • crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs — Rule implementation with classify_member() for all AnyJsClassMember variants

Modified files

  • crates/biome_diagnostics_categories/src/categories.rs — Added category entry
  • crates/biome_rule_options/src/lib.rs — Registered module
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs — Added @typescript-eslint/member-ordering migration mapping

Tests

  • 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 options

Test plan

  • cargo t use_member_ordering — 6 tests pass
  • cargo fmt — No formatting issues
  • Snapshot tests accepted via cargo insta accept

AI assistance disclosure

This PR was written primarily with AI assistance (Cursor + Claude).

Made with Cursor

@changeset-bot
Copy link

changeset-bot bot commented Feb 12, 2026

🦋 Changeset detected

Latest commit: f5826d8

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

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

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 A-Diagnostic Area: diagnostocis labels Feb 12, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 12, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a new nursery lint rule useMemberOrdering for JS/TS that enforces configurable class member ordering. Implements the rule with member classification (index signatures, properties, accessors, constructors, methods, static blocks, static/hash/accessibility distinctions), violation detection, diagnostics, and options handling. Introduces public UseMemberOrdering rule and MemberOrderViolation type, a UseMemberOrderingOptions module with a comprehensive MemberGroup enum and default order, and accompanying test fixtures (valid, invalid, and custom-order cases) plus option files.

Suggested reviewers

  • dyc3
  • ematipico
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: adding a new nursery lint rule for enforcing class member ordering.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, explaining the rule's purpose, configuration options, default ordering, and test coverage.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

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

@thilllon
Copy link
Author

@coderabbitai review

Copy link
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: 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 in default_order() but never classified.

classify_member maps getters → GetAccessor* and setters → SetAccessor*, but never returns the combined Accessor* 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 new Vec on every call — consider a static slice.

groups() calls default_order() which builds a fresh 44-element Vec each time. Since the default never changes, a &'static [MemberGroup] constant would avoid the allocation entirely and let groups() return a Vec only 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() and rank() signatures to accept &[MemberGroup] (which they already do on the consumer side).

@thilllon
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
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: 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 call resolve_*_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 four get_*_accessibility functions are structurally identical — only the types and variants differ. A small macro_rules! could generate them all. Purely optional, but would cut ~100 lines.

@thilllon
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
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

🤖 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 four get_*_accessibility functions 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.

@thilllon
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 12, 2026

Merging this PR will degrade performance by 6.55%

❌ 1 regressed benchmark
✅ 57 untouched benchmarks
⏩ 95 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
js_analyzer[router_17129688031671448157.ts] 30 ms 32.1 ms -6.55%

Comparing thilllon:member-ordering (68059b6) with main (cf0db6c)

Open in CodSpeed

Footnotes

  1. 95 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.

impl Rule for UseMemberOrdering {
type Query = Ast<JsClassMemberList>;
type State = MemberOrderViolation;
type Signals = Vec<Self::State>;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: boxed slice instead of Vec

Suggested change
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

This option is undocumented in the rule.

Comment on lines 24 to 34
customOrder.options:2:5 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

× Found an unknown key `options`.

1 │ {
> 2 │ "options": {
│ ^^^^^^^^^
3 │ "groups": ["constructor", "method", "property"]
4 │ }

i Known keys:
Copy link
Contributor

Choose a reason for hiding this comment

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

this diagnostic shouldn't be here

class InvalidCustom {
name;
constructor() {}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

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(),
Copy link
Contributor

Choose a reason for hiding this comment

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

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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

this check doesn't look right to me. wouldn't it truncate instead of filter groups out? I could be misreading.

Comment on lines 104 to 105
.note(markup! {
"Members should be ordered by: "{order_description}"."
Copy link
Contributor

Choose a reason for hiding this comment

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

IIRC there's a footer_list helper that lets you avoid the string allocation here.

@github-actions github-actions bot added the A-Project Area: project label Feb 13, 2026
Copy link
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: 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 making default_order a const or LazyLock<Vec<…>>.

default_order() allocates a fresh Vec of ~43 elements on every call. It's invoked in both run() and diagnostic() (the latter once per violation). For a class with many members, this could mean several allocations of the same data.

A const array or a static LazyLock would 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 recomputes groups() and deduplication for every violation.

options.groups() clones the full Vec<MemberGroup> and the broad-category dedup loop runs on each diagnostic() invocation. For a class with many out-of-order members, this is repeated work. Consider caching the order description string in MemberOrderViolation or computing it once via a shared approach.

Comment on lines +152 to +210
/// 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)
}
Copy link
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

🏁 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 -50

Repository: 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 -70

Repository: 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.rs

Repository: 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 50

Repository: 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 30

Repository: 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 -20

Repository: 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 -120

Repository: 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.rs

Repository: 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 -150

Repository: 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.rs

Repository: 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 -5

Repository: 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.rs

Repository: 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.

Copy link
Contributor

Choose a reason for hiding this comment

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

this might also be applicable

@thilllon thilllon force-pushed the member-ordering branch 3 times, most recently from 32bd5ac to 6b59239 Compare February 15, 2026 10:41
@thilllon
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@thilllon
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
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.

🧹 Nitpick comments (2)
crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs (2)

310-403: Consider reducing modifier-helper duplication with a macro or trait.

There are 8 near-identical functions for extracting static/accessibility info from different modifier list types. A small declarative macro could collapse them:

💡 Sketch
macro_rules! impl_modifier_helpers {
    ($static_fn:ident, $acc_fn:ident, $list_ty:ty, $mod_enum:path, $static_variant:pat, $acc_variant:pat) => {
        fn $static_fn(modifiers: &$list_ty) -> bool {
            modifiers.into_iter().any(|m| matches!(m, $static_variant))
        }
        fn $acc_fn(modifiers: &$list_ty) -> Accessibility {
            for m in modifiers {
                if let $acc_variant = m {
                    return ts_accessibility_to_enum(&acc);
                }
            }
            Accessibility::Public
        }
    };
}

Entirely optional — the current code is clear and the repetition mirrors biome's typed-AST style.

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

In `@crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs` around lines
310 - 403, The eight near-identical helper functions
(has_static_modifier_property, get_property_accessibility,
has_static_modifier_method, get_method_accessibility, has_static_modifier_index,
has_static_modifier_ts_property, get_ts_property_accessibility,
has_static_modifier_ts_method, get_ts_method_accessibility) can be collapsed
with a small declarative macro to remove duplication; add a macro (e.g.,
impl_modifier_helpers) that takes the static fn name, accessibility fn name, the
modifier list type, the modifier enum path, the static variant pattern and the
accessibility variant pattern and expands to the two functions (static check
using .into_iter().any(|m| matches!(m, $static_variant)) and accessibility
getter iterating and returning ts_accessibility_to_enum when matching
$acc_variant, defaulting to Accessibility::Public) and replace the explicit
implementations with macro invocations for each modifier-list type.

128-151: Diagnostic rebuilds the category list on every invocation.

diagnostic() recomputes options.groups() and the seen broad-category list for each violation. For the typical number of violations per class this is negligible, but if you ever want to tighten it up, the list could be computed once in run() and stored alongside each MemberOrderViolation.

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

In `@crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs` around lines
128 - 151, The diagnostic() function recomputes options.groups() and the seen
broad-category list for every violation; instead, compute the ordered
broad-category list once in run() and attach it to the rule state or each
MemberOrderViolation so diagnostic() can reuse it. Update run() (where
MemberOrderViolation instances are created) to build the unique seen Vec from
ctx.options().groups() and store that Vec on the State or include it as a field
on MemberOrderViolation; then change diagnostic() to read that precomputed list
(instead of recomputing options.groups() and rebuilding seen) while still using
state.member_range and state.expected_before_name for the diagnostic text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/biome_js_analyze/src/lint/nursery/use_member_ordering.rs`:
- Around line 310-403: The eight near-identical helper functions
(has_static_modifier_property, get_property_accessibility,
has_static_modifier_method, get_method_accessibility, has_static_modifier_index,
has_static_modifier_ts_property, get_ts_property_accessibility,
has_static_modifier_ts_method, get_ts_method_accessibility) can be collapsed
with a small declarative macro to remove duplication; add a macro (e.g.,
impl_modifier_helpers) that takes the static fn name, accessibility fn name, the
modifier list type, the modifier enum path, the static variant pattern and the
accessibility variant pattern and expands to the two functions (static check
using .into_iter().any(|m| matches!(m, $static_variant)) and accessibility
getter iterating and returning ts_accessibility_to_enum when matching
$acc_variant, defaulting to Accessibility::Public) and replace the explicit
implementations with macro invocations for each modifier-list type.
- Around line 128-151: The diagnostic() function recomputes options.groups() and
the seen broad-category list for every violation; instead, compute the ordered
broad-category list once in run() and attach it to the rule state or each
MemberOrderViolation so diagnostic() can reuse it. Update run() (where
MemberOrderViolation instances are created) to build the unique seen Vec from
ctx.options().groups() and store that Vec on the State or include it as a field
on MemberOrderViolation; then change diagnostic() to read that precomputed list
(instead of recomputing options.groups() and rebuilding seen) while still using
state.member_range and state.expected_before_name for the diagnostic text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
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.

I haven't looked at all the code, but there are clear errors that needs to be addressed.

  • this can't be a lint rule but needs to be a code action
  • we don't do nursery code actions, so it must a minor. PR must go to next branch
  • adhere to PR template
  • all options must be camel case
  • options must have doc string comments
  • there aren't enough tests: there tons of groups, and only 3 were tested.
  • the name of the rule comply to our naming convention. See other actions to understand


impl MemberGroup {
/// Returns the default order of member groups.
pub fn default_order() -> Vec<MemberGroup> {
Copy link
Member

Choose a reason for hiding this comment

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

Implement order using PartialOrd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Project Area: project L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments