Skip to content

Commit 3344ea9

Browse files
committed
Handle generically typed parameters
1 parent 157a9f5 commit 3344ea9

5 files changed

Lines changed: 286 additions & 31 deletions

File tree

lib/parse/ClassIndex.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ClassDeclaration, TSInterfaceDeclaration } from '@typescript-eslint/types/dist/ts-estree';
1+
import { ClassDeclaration, TSInterfaceDeclaration, TypeNode } from '@typescript-eslint/types/dist/ts-estree';
22
import { AST, TSESTreeOptions } from '@typescript-eslint/typescript-estree';
33

44
/**
@@ -43,6 +43,15 @@ export interface ClassLoaded extends ClassReference {
4343
abstract?: boolean;
4444
// The tsdoc comment of this class
4545
comment?: string;
46+
// The generic types of this class
47+
generics: GenericTypes;
48+
}
49+
50+
/**
51+
* A hash of generic type name to its properties.
52+
*/
53+
export interface GenericTypes {
54+
[name: string]: { type?: TypeNode };
4655
}
4756

4857
/**
@@ -62,4 +71,6 @@ export interface InterfaceLoaded extends ClassReference {
6271
superInterfaces?: InterfaceLoaded[];
6372
// The tsdoc comment of this class
6473
comment?: string;
74+
// The generic types of this class
75+
generics: GenericTypes;
6576
}

lib/parse/ClassLoader.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import * as Path from 'path';
22
import { ClassDeclaration, TSInterfaceDeclaration } from '@typescript-eslint/types/dist/ts-estree';
3-
import { AST, TSESTreeOptions, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree';
3+
import { AST, AST_NODE_TYPES, TSESTreeOptions } from '@typescript-eslint/typescript-estree';
44
import { ResolutionContext } from '../resolution/ResolutionContext';
5-
import { ClassLoaded, ClassReference, ClassReferenceLoaded, InterfaceLoaded } from './ClassIndex';
5+
import { ClassLoaded, ClassReference, ClassReferenceLoaded, GenericTypes, InterfaceLoaded } from './ClassIndex';
66
import { CommentLoader } from './CommentLoader';
77

88
/**
@@ -77,45 +77,53 @@ export class ClassLoader {
7777

7878
// If the class has been exported in this file, return directly
7979
if (classReference.localName in exportedClasses) {
80+
const declaration = exportedClasses[classReference.localName];
8081
return <any> this.enhanceLoadedWithComment(<ClassLoaded> {
8182
type: 'class',
8283
...classReference,
83-
declaration: exportedClasses[classReference.localName],
84+
declaration,
8485
ast,
85-
abstract: exportedClasses[classReference.localName].abstract,
86+
abstract: declaration.abstract,
87+
generics: this.collectGenericTypes(declaration),
8688
});
8789
}
8890

8991
// If the class has been declared in this file, return directly
9092
if (classReference.localName in declaredClasses) {
93+
const declaration = declaredClasses[classReference.localName];
9194
return <any> this.enhanceLoadedWithComment(<ClassLoaded> {
9295
type: 'class',
9396
...classReference,
94-
declaration: declaredClasses[classReference.localName],
97+
declaration,
9598
ast,
96-
abstract: declaredClasses[classReference.localName].abstract,
99+
abstract: declaration.abstract,
100+
generics: this.collectGenericTypes(declaration),
97101
});
98102
}
99103

100104
// Only consider interfaces if explicitly enabled
101105
if (considerInterfaces) {
102106
// If the interface has been exported in this file, return directly
103107
if (classReference.localName in exportedInterfaces) {
108+
const declaration = exportedInterfaces[classReference.localName];
104109
return <any> this.enhanceLoadedWithComment(<InterfaceLoaded> {
105110
type: 'interface',
106111
...classReference,
107-
declaration: exportedInterfaces[classReference.localName],
112+
declaration,
108113
ast,
114+
generics: this.collectGenericTypes(declaration),
109115
});
110116
}
111117

112118
// If the interface has been declared in this file, return directly
113119
if (classReference.localName in declaredInterfaces) {
120+
const declaration = declaredInterfaces[classReference.localName];
114121
return <any> this.enhanceLoadedWithComment(<InterfaceLoaded> {
115122
type: 'interface',
116123
...classReference,
117-
declaration: declaredInterfaces[classReference.localName],
124+
declaration,
118125
ast,
126+
generics: this.collectGenericTypes(declaration),
119127
});
120128
}
121129
}
@@ -143,6 +151,20 @@ export class ClassLoader {
143151
throw new Error(`Could not load ${considerInterfaces ? 'class or interface' : 'class'} ${classReference.localName} from ${classReference.fileName}`);
144152
}
145153

154+
/**
155+
* Create a hash of generic types in the given class declaration.
156+
* @param classDeclaration A class or interface declaration.
157+
*/
158+
public collectGenericTypes(classDeclaration: ClassDeclaration | TSInterfaceDeclaration): GenericTypes {
159+
const genericTypes: GenericTypes = {};
160+
if (classDeclaration.typeParameters) {
161+
for (const param of classDeclaration.typeParameters.params) {
162+
genericTypes[param.name.name] = { type: param.constraint };
163+
}
164+
}
165+
return genericTypes;
166+
}
167+
146168
/**
147169
* Annotate the given loaded class or interface with a comment if it is present on the declaration.
148170
* @param classLoaded A loaded class or interface.

lib/parse/ParameterLoader.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,17 @@ export class ParameterLoader {
155155
throw new Error(`Found invalid Array field type at ${this.getFieldName(field)
156156
} in ${this.classLoaded.localName} at ${this.classLoaded.fileName}`);
157157
default:
158+
// First check if the type is be a generic type
159+
if (typeNode.typeName.name in this.classLoaded.generics) {
160+
const genericProperties = this.classLoaded.generics[typeNode.typeName.name];
161+
if (!genericProperties.type) {
162+
throw new Error(`Found untyped generic field type at ${this.getFieldName(field)
163+
} in ${this.classLoaded.localName} at ${this.classLoaded.fileName}`);
164+
}
165+
return this.getRangeFromTypeNode(genericProperties.type, field);
166+
}
167+
168+
// Otherwise, assume we have an interface/class parameter
158169
return { type: 'interface', value: typeNode.typeName.name };
159170
}
160171
}

0 commit comments

Comments
 (0)