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.rs — ClassMemberAllowlist::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:
…and the maximally-trusting case:
Alternatives considered
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.
- Inline
// fallow-ignore-next-line per method — works today, but bloats every generated file and re-runs of the codegen step erase them.
- 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.
- A separate top-level config key like
usedClassMemberPatterns — viable but introduces a parallel surface area; extending members is the smaller change.
Problem
usedClassMembersscoped 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:
enter*and oneexit*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).protoc-ts,openapi-typescript,graphql-codegen) often produce one method per RPC / endpoint with the same shape.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.rs—ClassMemberAllowlist::matchesdoes 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:"*""enter*"enter."*Handler"Handler."on*Event"Recommended implementation:
memberscontaining*(or?) as a glob; existing exact strings keep their current meaning. Backward-compatible.WARNat 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
overrides[]withunused-class-members: offfor the file — works today, but is a sledgehammer: it also disables detection for genuinely-unused methods the user adds later. Bad signal-to-noise.// fallow-ignore-next-lineper method — works today, but bloats every generated file and re-runs of the codegen step erase them.usedClassMembersentry ("enterRule1") — works today, but globally suppresses any method namedenterRule1on any class anywhere, defeating the purpose of the heritage scoping.usedClassMemberPatterns— viable but introduces a parallel surface area; extendingmembersis the smaller change.