Skip to content

usedClassMembers: support wildcard / glob patterns in members ("*", "enter*", "*Handler") #254

@OmerGronich

Description

@OmerGronich

Problem

usedClassMembers scoped rules require an exhaustive enumeration of every member name to suppress. For framework patterns where the framework dispatches on a member-name prefix (or just "every method on the subclass"), this is impractical.

Examples — all consume their methods reflectively from outside the class:

  • Parser-generator listeners (ANTLR, etc.) declare one enter* and one exit* method per grammar rule. A non-trivial grammar produces 50–200+ methods, all dispatched by the parser runtime. Hand-listing every method is fragile (the list rots whenever the grammar changes).
  • Code-generated bridges (protoc-ts, openapi-typescript, graphql-codegen) often produce one method per RPC / endpoint with the same shape.
  • Abstract framework bases define dozens of methods consumed by the framework — the user wants "every method on the subclass."

Today's only working option is to enumerate every method name. The "intuitive" members: ["*"] is silently a no-op — neither rejected at config-load nor matched at runtime — and there is no glob form like "enter*" either. Source: crates/core/src/analyze/unused_members.rsClassMemberAllowlist::matches does an exact-string lookup (self.global.contains(member_name) / self.scoped.get(member_name)); a literal "*" is just inserted as the exact name "*" and never matches a real method.

Proposed solution

Add wildcard / glob support to members:

Pattern Meaning
"*" Match every member declared on a class that satisfies the heritage filter.
"enter*" Match every member whose name starts with enter.
"*Handler" Match every member whose name ends with Handler.
"on*Event" Combined prefix + suffix.

Recommended implementation:

  • Treat any string in members containing * (or ?) as a glob; existing exact strings keep their current meaning. Backward-compatible.
  • If a glob matches zero members across the codebase, emit a WARN at the end of the run with the rule's heritage clause and the unmatched pattern. This both surfaces dead allowlist entries and (as a side effect) catches today's silent no-op of ["*"].

Example — the prefix-glob case:

{ "extends": "GrammarBaseListener", "members": ["enter*", "exit*"] }

…and the maximally-trusting case:

{ "extends": "GrammarBaseListener", "members": ["*"] }

Alternatives considered

  1. overrides[] with unused-class-members: off for the file — works today, but is a sledgehammer: it also disables detection for genuinely-unused methods the user adds later. Bad signal-to-noise.
  2. Inline // fallow-ignore-next-line per method — works today, but bloats every generated file and re-runs of the codegen step erase them.
  3. A plain-name usedClassMembers entry ("enterRule1") — works today, but globally suppresses any method named enterRule1 on any class anywhere, defeating the purpose of the heritage scoping.
  4. A separate top-level config key like usedClassMemberPatterns — viable but introduces a parallel surface area; extending members is the smaller change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions