@studnicky/interfaces-compose-named-types
Requires valid contract interfaces to reference named schema-derived entity types for inline pure-data portions.
The rule examines inline object literals and mapped types inside retained contract interfaces. It reports a portion only when the shared classifier determines it is pure data. Inline callable, constructor, runtime, readonly, brand, or other contract objects are legitimate interface structure. Bare string, number, and boolean members do not need extraction.
Fixable: No · Options: No · Suggested severity: error
Rule boundary
The rule runs after interface declaration-shape classification:
interface-must-be-contractowns an interface that contains only pure data.- This rule skips that pure-data interface to avoid a second root diagnostic.
- This rule inspects a retained contract interface for inline pure-data portions.
- Each pure-data portion is extracted to a schema-derived entity and referenced through its named
Type.
The constraint declaration of a generic type parameter is outside this rule. A member that refers to that parameter is still checked through its resolved constraint, so a pure-data shape cannot be hidden behind T. A bare type-parameter reference and an indexed-access reference into a separately-declared shape (a: Big['a']) each launder an inline pure-data shape away from direct inspection — the parameter's own extends { ... } constraint, or the indexed property's own declared type, is the actual shape a consumer sees, even though neither is written inline at the member. For an indexed-access member, the rule resolves the reference to the TypeNode that declares the referenced property's shape and classifies that resolved shape as if it were written inline at the member; without this resolution the indexed-access node itself reads to the interface contract classifier as inert type-level computation, and the shape escapes detection entirely.
A bare string/number/boolean member is never extracted, even when the enclosing interface is a retained contract: there is no shape worth naming for a scalar with no structure. This is scoped to a direct interface member only — a bare scalar nested inside a union, tuple, or array member (values: string[], where the array itself is the shape worth extracting) is unaffected, and a type-alias root of type IdType = string; is separately rejected by type-alias-invariants as a primitive forwarding alias.
✗ Incorrect
interface UserReaderInterface {
read(): {
id: string;
name: string;
};
}interface RegistryInterface {
readonly entries: {
[key: string]: {
id: string;
enabled: boolean;
};
};
}interface BigInterface {
a: { x: string; y: string };
b: string;
}
interface WrapperInterface {
run(): void;
value: BigInterface['a'];
}interface HandlerInterface<T extends { a: string; b: string } = never> {
run(): void;
handler: T;
}✓ Correct
interface ServiceInterface {
run(): void;
}interface FetchOptionsInterface {
(): void;
readonly 'headers'?: Record<string, string>;
}interface SchedulerInterface {
(): void;
readonly 'handle': ReturnType<typeof setTimeout>;
}interface DispatcherInterface {
handler: (() => void) | { a: 1 };
}Each pure-data portion is extracted to a schema-derived entity and referenced through its named Type.
Scoped exceptions
Source comments and per-member allow lists do not change classification. Disable the rule only for an explicitly scoped flat-config file set:
export default [
{
files: ['src/**/*.ts'],
rules: {
'@studnicky/interfaces-compose-named-types': 'error'
}
},
{
files: ['generated/**/*.ts'],
rules: {
'@studnicky/interfaces-compose-named-types': 'off'
}
}
];Related rules
interface-must-be-contractowns pure-data interface declarations.type-alias-invariantsverifies canonical alias provenance and declaration shape.all-types-are-entitiesowns canonical alias placement.no-mixed-callable-shapesowns a member whose type mixes a callable constituent with data — this rule skips that constituent rather than telling the consumer to extract it, since the underlying shape must split instead.