This PR implements a comprehensive Type Hierarchy feature for the TypeScript language service, providing IDE support for navigating class, interface, and type alias hierarchies. The feature supports the LSP Type Hierarchy protocol and enables rich navigation across supertypes and subtypes.
- Prepare Type Hierarchy: Resolves the type hierarchy declaration at a given position
- Provide Supertypes: Returns base classes, implemented interfaces, and referenced types
- Provide Subtypes: Returns derived classes, implementing classes, and intersection type aliases
- On-Demand Loading: Following LSP protocol, the tree is loaded lazily - one level at a time when expanded in the UI
- Classes (named and anonymous class expressions)
- Interfaces (including declaration merging)
- Type Aliases (with semantic relationship tracking)
- Mixin Variables (
const Mixed = Mixin(Base)patterns) - Type Parameters (with constraint tracking)
The implementation handles complex TypeScript patterns:
| Pattern | Supertype Support | Subtype Support |
|---|---|---|
extends clauses |
✅ | ✅ |
implements clauses |
✅ | ✅ |
Intersection types (A & B) |
✅ (members) | ✅ (as structural subtype) |
Union types (A | B) |
✅ (members) | ❌ (semantically incorrect) |
| Conditional types | ✅ (extends clause) | ✅ (as possible subtype) |
| Mapped types | ✅ (referenced types) | ❌ |
| Generic instantiations | ✅ (original generic) | ✅ |
| Re-exports/module aliases | ✅ | ✅ |
| Mixin patterns | ✅ (composition chain) | ✅ (reverse lookup) |
| Type parameter constraints | ✅ | N/A |
Types are annotated with descriptive kindModifiers to help distinguish different type relationships:
| Modifier | Description | Example |
|---|---|---|
mixin |
Mixin variable | const Mixed = Mixin(Base) |
alias |
Simple type alias | type Foo = Bar |
conditional,extends |
Simple conditional type | T extends U ? X : Y |
conditional,infer |
Inference conditional type | T extends (...) => infer R ? R : never |
intersection |
Intersection type | A & B |
union |
Union type | A | B |
mapped |
Mapped type | { [K in keyof T]: ... } |
tuple |
Tuple type | [A, B] |
template |
Template literal type | `Hello ${string}` |
indexed |
Indexed access type | T["key"] |
keyof |
Keyof operator | keyof T |
readonly |
Readonly operator | readonly T[] |
-
src/services/typeHierarchy.ts(new, ~1000 lines)- Core type hierarchy implementation
resolveTypeHierarchyDeclaration()- Entry point for type hierarchy requestscreateTypeHierarchyItem()- Creates hierarchy items with proper metadatagetSupertypes()- Collects base types, implemented interfaces, and type parameter constraintsgetSubtypes()- Finds derived types using hybrid approach with performance limitsgetTypeHierarchyKindModifiers()- Returns modifiers based on type patternfindMixinVariablesUsingSymbol()- Reverse mixin lookupcollectTypeParameterConstraints()- Collects type parameter constraints as supertypes
-
src/harness/fourslashImpl.ts(modified)- Added
kindModifiersdisplay in type hierarchy baselines
- Added
-
Test Files (27 new fourslash tests)
- Comprehensive coverage of all supported patterns
- Multi-file tests for cross-file scenarios
- Edge case and negative case testing
- Uses
getEffectiveBaseTypeNode()for class inheritance - Iterates heritage clauses for
extends/implements - Handles type aliases by analyzing their type structure
- Traces mixin composition chains recursively
- Collects type parameter constraints
- FindAllReferences with
{ implementations: true }for efficient heritage clause lookup - Manual traversal for type alias subtypes (intersection types only)
- Reverse mixin lookup for finding mixin variables that use a base class
- Results limit (1000 per level) to prevent performance issues in large codebases
- Properly resolves import aliases using
skipAlias() - Handles declaration merging via
getMergedSymbol() - Resolves generic type instantiations to their base types
The implementation recognizes and supports TypeScript mixin patterns:
// Mixin function
function Timestamped<T extends new (...args: any[]) => any>(Base: T) {
return class extends Base { timestamp = Date.now(); };
}
// Mixin variable - recognized as TypeHierarchyDeclaration
const TimestampedUser = Timestamped(User);
// Querying MixinBase finds all mixin variables using it
export class MixinBase {}
export const Full = Serializable(Activatable(Timestamped(MixinBase)));
// MixinBase → subtypes: [TimestampedBase, ActivatableTimestamped, FullMixin]Type hierarchy shows type parameter constraints as supertypes:
interface Entity { id: number; }
function process<T extends Entity>(item: T) { ... }
// T → supertypes: [Entity]- 27 fourslash tests covering:
- Basic class/interface inheritance
- Abstract classes
- Multi-file scenarios
- Generic types and instantiations
- Type parameter constraints
- Conditional types
- Mapped types
- Intersection/union types
- Template literal types
- Indexed access types
- Mixin patterns (forward and reverse lookup)
- Re-exports and module aliases
- Declaration merging
- Project references
- Edge cases and negative cases
# Run all type hierarchy tests
npx hereby runtests --tests=typeHierarchy
# Run specific test
npx hereby runtests --tests=tests/cases/fourslash/typeHierarchyMixinsDeclarations.ts
# Run full test suite
npx hereby runtests-parallelThe implementation correctly models type relationships:
- Intersection types (
A & B): A subtype of both A and B (has ALL properties) - Union types (
A | B): A supertype of A and B (not a subtype!)
type Pet = Dog | Cat; // Pet is NOT a subtype of Dog - relationship reversed!
type DogPet = Dog & Pet; // DogPet IS a subtype of both Dog and PetConditional types like ExtractDog<T> = T extends Dog ? T : never are shown as "possible subtypes" when the condition could be satisfied. The conditional,extends or conditional,infer modifiers help distinguish these from structural subtypes.
- On-Demand Loading: Follows LSP protocol - tree is loaded one level at a time as user expands nodes
- FindAllReferences: Uses efficient name index for subtype lookup
- AST Traversal: Avoided when possible; uses targeted lookups
- Seen Sets: Prevents duplicate processing
- Cancellation Tokens: Supports responsiveness during long operations
- Results Limit: Subtype collection is capped at 1000 results per level to prevent issues in very large codebases
- Deeply nested generics: May not fully trace through complex generic type transformations
- Type-level computation: Runtime-computed types (e.g., complex mapped types) may not show all relationships
- Anonymous classes from mixins: Mixin functions return anonymous classes; we show the mixin variable instead
- satisfies expressions: Not applicable -
satisfiesis an expression-level operator, not a type-level relationship
- Handle more complex generic type inference
- Show inferred type widening in hierarchy
- Enhanced cross-project reference support
- Add truncation indicator when results exceed limit
This PR was developed with assistance from GitHub Copilot (Claude Opus 4.5). The AI helped with:
- Code implementation and algorithm design
- Test case creation and coverage analysis
- Documentation and PR description writing
- Code review and bug identification
All code has been reviewed by the contributor @kbrilla and tested against the full TypeScript test suite (99,267 tests passing).
Fixes #TBD (Type Hierarchy LSP Support - please link to the appropriate issue)