0
# Type Specifiers
1
2
Advanced type matching system using specifiers to identify types from specific packages, files, or the TypeScript standard library. This system provides flexible type identification beyond simple name matching.
3
4
## Capabilities
5
6
### Type Specifier Definitions
7
8
Core types and interfaces for the type specifier system.
9
10
```typescript { .api }
11
interface FileSpecifier {
12
from: 'file';
13
name: string | string[];
14
path?: string;
15
}
16
17
interface LibSpecifier {
18
from: 'lib';
19
name: string | string[];
20
}
21
22
interface PackageSpecifier {
23
from: 'package';
24
name: string | string[];
25
package: string;
26
}
27
28
type TypeOrValueSpecifier = string | FileSpecifier | LibSpecifier | PackageSpecifier;
29
```
30
31
**Constants:**
32
33
```typescript { .api }
34
const typeOrValueSpecifiersSchema: JSONSchema4;
35
```
36
37
### Type Matching Functions
38
39
Functions for matching types against specifiers.
40
41
```typescript { .api }
42
/**
43
* Checks if a type matches a specific TypeOrValueSpecifier
44
*/
45
function typeMatchesSpecifier(
46
type: ts.Type,
47
specifier: TypeOrValueSpecifier,
48
program: ts.Program
49
): boolean;
50
51
/**
52
* Checks if a type matches any of the provided specifiers
53
*/
54
function typeMatchesSomeSpecifier(
55
type: ts.Type,
56
specifiers: TypeOrValueSpecifier[],
57
program: ts.Program
58
): boolean;
59
```
60
61
### Value Matching Functions
62
63
Functions for matching ESTree nodes against specifiers.
64
65
```typescript { .api }
66
/**
67
* Checks if a value node matches a specific TypeOrValueSpecifier
68
*/
69
function valueMatchesSpecifier(
70
node: TSESTree.Node,
71
specifier: TypeOrValueSpecifier,
72
program: ts.Program,
73
type: ts.Type
74
): boolean;
75
76
/**
77
* Checks if a value node matches any of the provided specifiers
78
*/
79
function valueMatchesSomeSpecifier(
80
node: TSESTree.Node,
81
specifiers: TypeOrValueSpecifier[],
82
program: ts.Program,
83
type: ts.Type
84
): boolean;
85
```
86
87
## Usage Examples
88
89
### Basic Type Specifier Usage
90
91
```typescript
92
import {
93
typeMatchesSpecifier,
94
typeMatchesSomeSpecifier,
95
TypeOrValueSpecifier
96
} from "@typescript-eslint/type-utils";
97
98
// In an ESLint rule
99
export default {
100
create(context) {
101
const services = context.parserServices;
102
const program = services.program;
103
const checker = program.getTypeChecker();
104
105
// Define allowed types using specifiers
106
const allowedTypes: TypeOrValueSpecifier[] = [
107
// Simple string name
108
"string",
109
110
// Built-in lib types
111
{ from: "lib", name: "Promise" },
112
{ from: "lib", name: ["Array", "ReadonlyArray"] },
113
114
// Package types
115
{ from: "package", name: "Observable", package: "rxjs" },
116
{ from: "package", name: ["Component", "Injectable"], package: "@angular/core" },
117
118
// File-specific types
119
{ from: "file", name: "UserType", path: "./types/user.ts" },
120
{ from: "file", name: "ApiResponse" } // Any file
121
];
122
123
return {
124
VariableDeclarator(node) {
125
if (node.init) {
126
const tsNode = services.esTreeNodeToTSNodeMap.get(node.init);
127
const type = checker.getTypeAtLocation(tsNode);
128
129
// Check if type matches any allowed specifier
130
if (!typeMatchesSomeSpecifier(type, allowedTypes, program)) {
131
context.report({
132
node: node.init,
133
messageId: "disallowedType",
134
data: {
135
typeName: checker.typeToString(type)
136
}
137
});
138
}
139
}
140
}
141
};
142
}
143
};
144
```
145
146
### Package-Specific Type Checking
147
148
```typescript
149
import { typeMatchesSpecifier, PackageSpecifier } from "@typescript-eslint/type-utils";
150
151
// Check for specific React types
152
const reactSpecifiers: PackageSpecifier[] = [
153
{ from: "package", name: "Component", package: "react" },
154
{ from: "package", name: "FC", package: "react" },
155
{ from: "package", name: "ReactNode", package: "react" }
156
];
157
158
export default {
159
create(context) {
160
const services = context.parserServices;
161
const program = services.program;
162
const checker = program.getTypeChecker();
163
164
return {
165
ClassDeclaration(node) {
166
if (node.superClass) {
167
const tsNode = services.esTreeNodeToTSNodeMap.get(node.superClass);
168
const type = checker.getTypeAtLocation(tsNode);
169
170
const isReactComponent = reactSpecifiers.some(spec =>
171
typeMatchesSpecifier(type, spec, program)
172
);
173
174
if (isReactComponent) {
175
// This class extends a React component
176
console.log("React component detected");
177
}
178
}
179
}
180
};
181
}
182
};
183
```
184
185
### File-Based Type Restrictions
186
187
```typescript
188
import { typeMatchesSpecifier, FileSpecifier } from "@typescript-eslint/type-utils";
189
190
// Restrict types to specific files
191
const internalTypeSpecifiers: FileSpecifier[] = [
192
{ from: "file", name: ["InternalAPI", "PrivateType"], path: "./internal" },
193
{ from: "file", name: "ConfigType", path: "./config/types.ts" }
194
];
195
196
export default {
197
create(context) {
198
const services = context.parserServices;
199
const program = services.program;
200
const checker = program.getTypeChecker();
201
202
return {
203
TSTypeReference(node) {
204
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
205
const type = checker.getTypeAtLocation(tsNode);
206
207
const isInternalType = internalTypeSpecifiers.some(spec =>
208
typeMatchesSpecifier(type, spec, program)
209
);
210
211
if (isInternalType) {
212
// Check if this usage is in an appropriate location
213
const sourceFile = node.getSourceFile?.() || context.getSourceCode().ast;
214
const fileName = sourceFile.filename || "";
215
216
if (!fileName.includes("internal")) {
217
context.report({
218
node,
219
messageId: "internalTypeInPublicAPI"
220
});
221
}
222
}
223
}
224
};
225
}
226
};
227
```
228
229
### Value Specifier Usage
230
231
```typescript
232
import {
233
valueMatchesSpecifier,
234
valueMatchesSomeSpecifier,
235
TypeOrValueSpecifier
236
} from "@typescript-eslint/type-utils";
237
238
// Check values (not just types)
239
const dangerousFunctions: TypeOrValueSpecifier[] = [
240
{ from: "lib", name: "eval" },
241
{ from: "package", name: "exec", package: "child_process" },
242
{ from: "file", name: "unsafeOperation", path: "./unsafe-utils.ts" }
243
];
244
245
export default {
246
create(context) {
247
const services = context.parserServices;
248
const program = services.program;
249
const checker = program.getTypeChecker();
250
251
return {
252
CallExpression(node) {
253
const tsNode = services.esTreeNodeToTSNodeMap.get(node.callee);
254
const type = checker.getTypeAtLocation(tsNode);
255
256
if (valueMatchesSomeSpecifier(node.callee, dangerousFunctions, program, type)) {
257
context.report({
258
node: node.callee,
259
messageId: "dangerousFunctionCall"
260
});
261
}
262
}
263
};
264
}
265
};
266
```
267
268
## Advanced Specifier Patterns
269
270
### Dynamic Specifier Generation
271
272
```typescript
273
// Example: Creating specifiers based on configuration
274
import { TypeOrValueSpecifier } from "@typescript-eslint/type-utils";
275
276
interface AllowedTypeConfig {
277
packageName: string;
278
allowedTypes: string[];
279
allowedFiles?: string[];
280
}
281
282
function createSpecifiersFromConfig(configs: AllowedTypeConfig[]): TypeOrValueSpecifier[] {
283
const specifiers: TypeOrValueSpecifier[] = [];
284
285
configs.forEach(config => {
286
// Add package specifiers
287
config.allowedTypes.forEach(typeName => {
288
specifiers.push({
289
from: "package",
290
name: typeName,
291
package: config.packageName
292
});
293
});
294
295
// Add file specifiers if specified
296
config.allowedFiles?.forEach(filePath => {
297
config.allowedTypes.forEach(typeName => {
298
specifiers.push({
299
from: "file",
300
name: typeName,
301
path: filePath
302
});
303
});
304
});
305
});
306
307
return specifiers;
308
}
309
310
// Usage
311
const typeConfigs: AllowedTypeConfig[] = [
312
{
313
packageName: "lodash",
314
allowedTypes: ["Dictionary", "List"],
315
allowedFiles: ["./types/lodash.d.ts"]
316
},
317
{
318
packageName: "rxjs",
319
allowedTypes: ["Observable", "Subject", "BehaviorSubject"]
320
}
321
];
322
323
const allowedSpecifiers = createSpecifiersFromConfig(typeConfigs);
324
```
325
326
### Conditional Type Matching
327
328
```typescript
329
// Example: Conditional type matching based on context
330
import { typeMatchesSpecifier, TypeOrValueSpecifier } from "@typescript-eslint/type-utils";
331
332
function createContextualTypeChecker(
333
baseSpecifiers: TypeOrValueSpecifier[],
334
contextRules: Map<string, TypeOrValueSpecifier[]>
335
) {
336
return function checkTypeInContext(
337
type: ts.Type,
338
program: ts.Program,
339
context: string
340
): boolean {
341
// Check base allowed types first
342
const baseAllowed = baseSpecifiers.some(spec =>
343
typeMatchesSpecifier(type, spec, program)
344
);
345
346
if (baseAllowed) return true;
347
348
// Check context-specific rules
349
const contextSpecifiers = contextRules.get(context);
350
if (contextSpecifiers) {
351
return contextSpecifiers.some(spec =>
352
typeMatchesSpecifier(type, spec, program)
353
);
354
}
355
356
return false;
357
};
358
}
359
360
// Usage
361
const baseTypes: TypeOrValueSpecifier[] = [
362
"string", "number", "boolean",
363
{ from: "lib", name: "Promise" }
364
];
365
366
const contextRules = new Map([
367
["test", [
368
{ from: "package", name: "jest", package: "@types/jest" },
369
{ from: "package", name: "TestingLibrary", package: "@testing-library/react" }
370
]],
371
["api", [
372
{ from: "package", name: "Request", package: "express" },
373
{ from: "package", name: "Response", package: "express" }
374
]]
375
]);
376
377
const typeChecker = createContextualTypeChecker(baseTypes, contextRules);
378
```
379
380
### Hierarchical Type Checking
381
382
```typescript
383
// Example: Hierarchical type matching with inheritance
384
import { typeMatchesSpecifier, LibSpecifier } from "@typescript-eslint/type-utils";
385
386
interface TypeHierarchy {
387
base: TypeOrValueSpecifier[];
388
derived: Map<string, TypeOrValueSpecifier[]>;
389
}
390
391
function checkTypeHierarchy(
392
type: ts.Type,
393
program: ts.Program,
394
hierarchy: TypeHierarchy,
395
checker: ts.TypeChecker
396
): { matches: boolean; level: string } {
397
// Check base level first
398
const baseMatches = hierarchy.base.some(spec =>
399
typeMatchesSpecifier(type, spec, program)
400
);
401
402
if (baseMatches) {
403
return { matches: true, level: "base" };
404
}
405
406
// Check derived levels
407
for (const [level, specifiers] of hierarchy.derived) {
408
const derivedMatches = specifiers.some(spec =>
409
typeMatchesSpecifier(type, spec, program)
410
);
411
412
if (derivedMatches) {
413
return { matches: true, level };
414
}
415
}
416
417
return { matches: false, level: "none" };
418
}
419
420
// Usage for React component hierarchy
421
const reactHierarchy: TypeHierarchy = {
422
base: [
423
{ from: "lib", name: "HTMLElement" },
424
{ from: "package", name: "ReactNode", package: "react" }
425
],
426
derived: new Map([
427
["component", [
428
{ from: "package", name: "Component", package: "react" },
429
{ from: "package", name: "PureComponent", package: "react" }
430
]],
431
["functional", [
432
{ from: "package", name: "FC", package: "react" },
433
{ from: "package", name: "FunctionComponent", package: "react" }
434
]]
435
])
436
};
437
```