Design Principles
Dependency-inversion and interface-segregation findings the type checker can prove: constructor parameters typed by nominal concrete classes, services constructed inside methods instead of injected, and behavioral interfaces whose consumers each use a sliver.
Syntax-only "SOLID checkers" guess class identity from names, and the guess is wrong often enough to make them unreliable — this codebase's own name-based concrete-import heuristic was measured at 60 % false on schema-heavy files. This family runs in the same tsconfig-driven, fully type-checked pass as the type-aware checks and reports only what the checker proves: a constructor parameter whose concrete class carries private or protected members is NOMINAL (TypeScript compares it by declaration identity, so no structurally-shaped test double can satisfy it); a service constructed inside an instance method is a dependency the caller cannot see or replace; a behavioral interface whose consumers each touch a small share of its methods is carrying several roles under one name. Every exemption is a measured false-positive boundary: composition roots are recognised by how many collaborators they construct (never by name), conditional construction is a factory, value objects may be constructed freely, and data-record interfaces are never judged for segregation. Findings are informational: they name the mechanism, and the principle is the explanation, not a verdict.
Severity guide
- info
- All three templates are informational — design seams to examine, ranked within the repository, never scored.
- warning
- Not emitted by this family.
- critical
- Not emitted by this family.
Examples
Before
class Scheduler {
constructor(private readonly snapshots: HistoricalSnapshotService) {}
}After
interface SnapshotSource { latest(): Promise<Snapshot | null> }
class Scheduler {
constructor(private readonly snapshots: SnapshotSource) {}
}`HistoricalSnapshotService` declares private members, so the parameter type is nominal: tests must construct the real service or cast through `unknown`. An interface of the members actually used is satisfiable by any structurally compatible object.
Remediation
Depend on the shape you use: inject collaborators, type them by interfaces, and split interfaces along consumer seams.
For nominal dependencies, declare an interface (or a Pick<> of the members used) and type the constructor parameter by it; the concrete class still satisfies it. For hidden collaborators, move construction to the composition root and pass the instance in — or accept a factory when the collaborator must be created per call. For fat interfaces, extract a role interface per consumer seam and let the original extend them; implementors change nothing.