class-bright-line
a class written where none of the four criteria that license one hold
Agent checkadvisory by defaultbasecode
The argument
When to Use a Class — The Bright Line
Default to functions. Create a class if and only if at least one of these is true:
| # | Criterion | Example |
|---|---|---|
| a | Mutable state persists across method calls | RateLimiter (remaining tokens), a cache, a connection pool |
| b | 3+ operations share injected config/dependencies | HttpClient (baseUrl, retries, credentials injected once, used by every method) |
| c | Multiple implementations of a shared interface | FileSource / S3Source behind one RecordSource contract |
| d | The framework requires it | NestJS services, resolvers, guards (DI needs classes) |
If none apply: functions in a module. Gut-check: is "how many of these exist right now?" a meaningful question? Two HttpClients pointed at different APIs — meaningful → class. Two formatDates — nonsensical → function.
Functional vs Class-Based
Prefer functions by default. Create a class only per the bright-line criteria in classes.md (persistent state, 3+ operations sharing injected deps, interface polymorphism, framework mandate). Static-only classes are banned.
The proof
failsrc/pricing/PriceFormatter.ts
interface ConstructorParams {
locale: string;
}
// None of the four criteria hold: nothing persists between calls, one operation
// uses the injected value, there is no second implementation and no framework
// asked for a class. "How many PriceFormatters exist?" is not a question.
export class PriceFormatter {
private readonly locale: string;
constructor({ locale }: ConstructorParams) {
this.locale = locale;
}
format({ amount }: { amount: number }): string {
return amount.toLocaleString(this.locale);
}
}
passsrc/rate/RateLimiter.ts
interface ConstructorParams {
tokens: number;
}
// Criterion (a): the remaining tokens persist across calls, so "how many rate
// limiters exist right now?" is a question with a meaningful answer.
export class RateLimiter {
private remaining: number;
constructor({ tokens }: ConstructorParams) {
this.remaining = tokens;
}
take(): boolean {
const allowed = this.remaining > 0;
if (allowed) {
this.remaining -= 1;
}
return allowed;
}
refill({ tokens }: { tokens: number }): void {
this.remaining = tokens;
}
}
Turn it down
Both lines go in your lightsout.config.json.
"standards-checks": { "class-bright-line": "advisory" }"standards-checks": { "class-bright-line": "off" }