0
# ESLint Integration Utilities
1
2
Rule creation, testing, and configuration utilities specifically designed for TypeScript ESLint rules. These utilities provide a complete toolkit for developing, testing, and configuring ESLint rules with full TypeScript integration.
3
4
## Capabilities
5
6
### Rule Creation
7
8
Utilities for creating TypeScript-aware ESLint rules with automatic documentation and type safety.
9
10
```typescript { .api }
11
/**
12
* Creates a rule creator function with automatic documentation URL generation
13
* @param urlCreator - Function that generates documentation URLs from rule names
14
* @returns Function for creating rules with automatic docs URLs
15
*/
16
function RuleCreator(urlCreator: (ruleName: string) => string): <
17
TOptions extends readonly unknown[],
18
TMessageIds extends string,
19
TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener
20
>(ruleDefinition: Readonly<TSESLint.RuleModule<TMessageIds, TOptions, TRuleListener>>) => TSESLint.RuleModule<TMessageIds, TOptions>;
21
22
/**
23
* Creates a rule without automatic documentation URL
24
* @param ruleDefinition - Rule definition object
25
* @returns ESLint rule module
26
*/
27
declare namespace RuleCreator {
28
function withoutDocs<
29
TOptions extends readonly unknown[],
30
TMessageIds extends string,
31
TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener
32
>(ruleDefinition: Readonly<TSESLint.RuleModule<TMessageIds, TOptions, TRuleListener>>): TSESLint.RuleModule<TMessageIds, TOptions>;
33
}
34
```
35
36
**Usage Example:**
37
38
```typescript
39
import { ESLintUtils, TSESLint } from "@typescript-eslint/experimental-utils";
40
41
const createRule = ESLintUtils.RuleCreator(
42
name => `https://typescript-eslint.io/rules/${name}`
43
);
44
45
const rule = createRule({
46
name: 'my-rule',
47
meta: {
48
type: 'problem',
49
messages: {
50
error: 'This is an error message'
51
},
52
schema: []
53
},
54
defaultOptions: [],
55
create(context: TSESLint.RuleContext<'error', []>) {
56
return {
57
FunctionDeclaration(node) {
58
context.report({
59
node,
60
messageId: 'error'
61
});
62
}
63
};
64
}
65
});
66
```
67
68
### Parser Services
69
70
Utilities for accessing TypeScript compiler services within ESLint rules.
71
72
```typescript { .api }
73
/**
74
* Retrieves TypeScript parser services from the rule context
75
* @param context - ESLint rule context
76
* @param allowWithoutFullTypeInformation - Whether to allow partial type information
77
* @returns Parser services object with TypeScript compiler access
78
* @throws Error if parser services are not available
79
*/
80
function getParserServices<TMessageIds extends string, TOptions extends readonly unknown[]>(
81
context: TSESLint.RuleContext<TMessageIds, TOptions>,
82
allowWithoutFullTypeInformation?: boolean
83
): TSESTree.ParserServices;
84
```
85
86
**Usage Example:**
87
88
```typescript
89
import { ESLintUtils, TSESTree } from "@typescript-eslint/experimental-utils";
90
91
const rule = ESLintUtils.RuleCreator(url => url)({
92
name: 'type-aware-rule',
93
meta: {
94
type: 'problem',
95
messages: { error: 'Type error' },
96
schema: []
97
},
98
defaultOptions: [],
99
create(context) {
100
const services = ESLintUtils.getParserServices(context);
101
const checker = services.program?.getTypeChecker();
102
103
return {
104
Identifier(node) {
105
if (checker) {
106
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
107
const type = checker.getTypeAtLocation(tsNode);
108
// Use TypeScript type information
109
}
110
}
111
};
112
}
113
});
114
```
115
116
### Configuration Utilities
117
118
Utilities for handling rule options and configuration merging.
119
120
```typescript { .api }
121
/**
122
* Applies default options to user-provided options using deep merge
123
* @param defaultOptions - Default option values
124
* @param userOptions - User-provided options (can be null)
125
* @returns Merged options with defaults applied
126
*/
127
function applyDefault<TUser, TDefault>(
128
defaultOptions: Readonly<TDefault>,
129
userOptions: Readonly<TUser> | null
130
): TDefault;
131
132
/**
133
* Deep merges two objects, combining properties recursively
134
* @param first - First object to merge
135
* @param second - Second object to merge
136
* @returns Merged object with combined properties
137
*/
138
function deepMerge(first?: ObjectLike, second?: ObjectLike): Record<string, unknown>;
139
140
/**
141
* Type guard to check if a value is an object but not an array
142
* @param obj - Value to check
143
* @returns True if the value is an object (not array)
144
*/
145
function isObjectNotArray<T = Record<string, unknown>>(obj: unknown): obj is T;
146
147
interface ObjectLike {
148
[key: string]: unknown;
149
}
150
```
151
152
**Usage Example:**
153
154
```typescript
155
import { ESLintUtils } from "@typescript-eslint/experimental-utils";
156
157
interface MyRuleOptions {
158
checkTypes: boolean;
159
ignorePatterns: string[];
160
severity: 'error' | 'warn';
161
}
162
163
const defaultOptions: MyRuleOptions = {
164
checkTypes: true,
165
ignorePatterns: [],
166
severity: 'error'
167
};
168
169
const rule = ESLintUtils.RuleCreator(url => url)({
170
name: 'my-rule',
171
meta: {
172
type: 'problem',
173
messages: { error: 'Error' },
174
schema: [/* schema */]
175
},
176
defaultOptions: [defaultOptions],
177
create(context, [userOptions]) {
178
const options = ESLintUtils.applyDefault(defaultOptions, userOptions);
179
// options now has all properties with defaults applied
180
181
return {
182
// rule implementation
183
};
184
}
185
});
186
```
187
188
### Type Utilities
189
190
Type utilities for inferring types from rule definitions.
191
192
```typescript { .api }
193
/**
194
* Infers the options type from a rule module
195
*/
196
type InferOptionsTypeFromRule<T> = T extends TSESLint.RuleModule<string, infer TOptions, TSESLint.RuleListener> ? TOptions : unknown;
197
198
/**
199
* Infers the message IDs type from a rule module
200
*/
201
type InferMessageIdsTypeFromRule<T> = T extends TSESLint.RuleModule<infer TMessageIds, readonly unknown[], TSESLint.RuleListener> ? TMessageIds : string;
202
```
203
204
### Utility Functions
205
206
Helper functions for common rule development tasks.
207
208
```typescript { .api }
209
/**
210
* Non-null assertion with custom error message
211
* @param value - Value to check for null/undefined
212
* @param message - Error message if value is null/undefined
213
* @returns Non-null value
214
* @throws Error if value is null or undefined
215
*/
216
function nullThrows<T>(value: T | null | undefined, message: string): T;
217
218
/**
219
* Common error messages for nullThrows function
220
*/
221
const NullThrowsReasons: {
222
readonly MissingParent: "Expected node to have a parent.";
223
readonly MissingToken: "Expected to find a token.";
224
};
225
```
226
227
**Usage Example:**
228
229
```typescript
230
import { ESLintUtils, TSESTree } from "@typescript-eslint/experimental-utils";
231
232
function analyzeNode(node: TSESTree.Node): void {
233
const parent = ESLintUtils.nullThrows(
234
node.parent,
235
ESLintUtils.NullThrowsReasons.MissingParent
236
);
237
// parent is now guaranteed to be non-null
238
}
239
```
240
241
### Rule Testing
242
243
Enhanced rule testing utilities with TypeScript support and batch test processing.
244
245
```typescript { .api }
246
/**
247
* Enhanced ESLint rule tester with TypeScript support
248
*/
249
class RuleTester {
250
/**
251
* Creates a new rule tester with base configuration
252
* @param baseOptions - Base configuration for the rule tester
253
*/
254
constructor(baseOptions: TSESLint.RuleTesterConfig);
255
256
/**
257
* Runs tests for a specific rule with TypeScript support
258
* @param name - Name of the rule being tested
259
* @param rule - Rule module to test
260
* @param tests - Test cases (valid and invalid)
261
*/
262
run<TMessageIds extends string, TOptions extends readonly unknown[]>(
263
name: string,
264
rule: TSESLint.RuleModule<TMessageIds, TOptions>,
265
tests: TSESLint.RunTests<TMessageIds, TOptions>
266
): void;
267
268
/**
269
* Static cleanup function called after all tests
270
*/
271
static afterAll: (() => void) | undefined;
272
}
273
274
/**
275
* Template tag to mark code as "no format" for testing purposes
276
* @param raw - Template strings array
277
* @param keys - Template substitution values
278
* @returns Formatted string marked as no-format
279
*/
280
function noFormat(raw: TemplateStringsArray, ...keys: string[]): string;
281
282
/**
283
* Converts batch test cases into individual test cases
284
* @param test - Batch test configuration
285
* @returns Array of individual test cases
286
*/
287
function batchedSingleLineTests<TOptions extends readonly unknown[]>(test: {
288
code: string;
289
options?: TOptions;
290
skip?: boolean;
291
only?: boolean;
292
}): TSESLint.ValidTestCase<TOptions>[];
293
294
function batchedSingleLineTests<TMessageIds extends string, TOptions extends readonly unknown[]>(test: {
295
code: string;
296
options?: TOptions;
297
skip?: boolean;
298
only?: boolean;
299
errors: TestCaseError<TMessageIds>[];
300
output?: string | null;
301
}): TSESLint.InvalidTestCase<TMessageIds, TOptions>[];
302
303
interface TestCaseError<TMessageIds extends string> {
304
messageId: TMessageIds;
305
data?: Record<string, unknown>;
306
type?: string;
307
line?: number;
308
column?: number;
309
endLine?: number;
310
endColumn?: number;
311
suggestions?: SuggestionOutput<TMessageIds>[];
312
}
313
314
interface SuggestionOutput<TMessageIds extends string> {
315
messageId: TMessageIds;
316
data?: Record<string, unknown>;
317
output: string;
318
desc?: string;
319
}
320
```
321
322
**Usage Example:**
323
324
```typescript
325
import { ESLintUtils, TSESLint } from "@typescript-eslint/experimental-utils";
326
327
const ruleTester = new ESLintUtils.RuleTester({
328
parser: '@typescript-eslint/parser',
329
parserOptions: {
330
ecmaVersion: 2020,
331
sourceType: 'module',
332
project: './tsconfig.json'
333
}
334
});
335
336
const rule = ESLintUtils.RuleCreator(url => url)({
337
name: 'test-rule',
338
meta: {
339
type: 'problem',
340
messages: {
341
error: 'Found error in {{name}}'
342
},
343
schema: []
344
},
345
defaultOptions: [],
346
create(context) {
347
return {
348
FunctionDeclaration(node) {
349
context.report({
350
node,
351
messageId: 'error',
352
data: { name: node.id?.name || 'unknown' }
353
});
354
}
355
};
356
}
357
});
358
359
ruleTester.run('test-rule', rule, {
360
valid: [
361
'const x = 1;',
362
'class MyClass {}',
363
{
364
code: 'const arrow = () => {};',
365
parserOptions: { ecmaVersion: 6 }
366
}
367
],
368
invalid: [
369
{
370
code: 'function foo() {}',
371
errors: [{
372
messageId: 'error',
373
data: { name: 'foo' },
374
line: 1,
375
column: 1
376
}]
377
},
378
{
379
code: ESLintUtils.noFormat`
380
function bar() {
381
// some code
382
}
383
`,
384
errors: [{ messageId: 'error' }]
385
}
386
]
387
});
388
389
// Using batched tests
390
const batchTests = ESLintUtils.batchedSingleLineTests({
391
code: `
392
function a() {}
393
function b() {}
394
function c() {}
395
`,
396
errors: [
397
{ messageId: 'error', line: 2 },
398
{ messageId: 'error', line: 3 },
399
{ messageId: 'error', line: 4 }
400
]
401
});
402
```
403
404
### Dependency Constraints
405
406
Utilities for checking package version dependencies in tests.
407
408
```typescript { .api }
409
/**
410
* Checks if all specified dependency version constraints are satisfied
411
* @param dependencyConstraints - Object mapping package names to version constraints
412
* @returns True if all constraints are satisfied
413
*/
414
function satisfiesAllDependencyConstraints(
415
dependencyConstraints?: DependencyConstraint
416
): boolean;
417
418
/**
419
* Interface for specifying package version constraints
420
*/
421
interface DependencyConstraint {
422
[packageName: string]: VersionConstraint;
423
}
424
425
type VersionConstraint = string | Range | SemVer;
426
427
interface Range {
428
range: string;
429
raw: string;
430
loose: boolean;
431
includePrerelease: boolean;
432
test(version: string | SemVer): boolean;
433
intersects(range: Range): boolean;
434
}
435
436
interface SemVer {
437
version: string;
438
major: number;
439
minor: number;
440
patch: number;
441
prerelease: readonly (string | number)[];
442
build: readonly string[];
443
compare(other: string | SemVer): -1 | 0 | 1;
444
compareMain(other: string | SemVer): -1 | 0 | 1;
445
}
446
```
447
448
**Usage Example:**
449
450
```typescript
451
import { ESLintUtils } from "@typescript-eslint/experimental-utils";
452
453
// Check if TypeScript version meets requirements
454
const hasRequiredTypescript = ESLintUtils.satisfiesAllDependencyConstraints({
455
typescript: '>=4.0.0'
456
});
457
458
if (hasRequiredTypescript) {
459
// Run TypeScript-specific tests
460
} else {
461
// Skip tests that require newer TypeScript
462
}
463
```
464
465
## Test Case Types
466
467
```typescript { .api }
468
/**
469
* Valid test case configuration
470
*/
471
interface ValidTestCase<TOptions extends readonly unknown[]> {
472
/** Source code that should not trigger any errors */
473
code: string;
474
/** Rule options to use for this test */
475
options?: TOptions;
476
/** Filename for the test case */
477
filename?: string;
478
/** Parser options specific to this test */
479
parserOptions?: TSESLint.ParserOptions;
480
/** ESLint settings for this test */
481
settings?: Record<string, unknown>;
482
/** Parser to use (defaults to configured parser) */
483
parser?: string;
484
/** Global variables available in this test */
485
globals?: Record<string, boolean>;
486
/** Environment settings for this test */
487
env?: TSESLint.Linter.Environment;
488
/** Skip this test case */
489
skip?: boolean;
490
/** Run only this test case */
491
only?: boolean;
492
/** Dependency constraints for this test */
493
dependencyConstraints?: DependencyConstraint;
494
}
495
496
/**
497
* Invalid test case configuration
498
*/
499
interface InvalidTestCase<TMessageIds extends string, TOptions extends readonly unknown[]>
500
extends ValidTestCase<TOptions> {
501
/** Expected errors from this test case */
502
errors: TestCaseError<TMessageIds>[];
503
/** Expected output after fixes are applied */
504
output?: string | null;
505
}
506
507
/**
508
* Complete test suite for a rule
509
*/
510
interface RunTests<TMessageIds extends string, TOptions extends readonly unknown[]> {
511
/** Test cases that should not produce any errors */
512
valid: (string | ValidTestCase<TOptions>)[];
513
/** Test cases that should produce errors */
514
invalid: InvalidTestCase<TMessageIds, TOptions>[];
515
}
516
```
517
518
## Rule Tester Configuration
519
520
```typescript { .api }
521
/**
522
* Configuration for the ESLint rule tester
523
*/
524
interface RuleTesterConfig {
525
/** Parser to use for parsing test code */
526
parser?: string;
527
/** Parser options passed to the parser */
528
parserOptions?: TSESLint.ParserOptions;
529
/** Global variables available during testing */
530
globals?: Record<string, boolean>;
531
/** Environment settings */
532
env?: TSESLint.Linter.Environment;
533
/** ESLint settings object */
534
settings?: Record<string, unknown>;
535
/** Language options for the parser */
536
languageOptions?: {
537
ecmaVersion?: TSESLint.ParserOptions['ecmaVersion'];
538
sourceType?: TSESLint.ParserOptions['sourceType'];
539
globals?: Record<string, boolean>;
540
parser?: { parse(text: string, options?: any): any };
541
parserOptions?: Record<string, unknown>;
542
};
543
/** Linter options */
544
linterOptions?: {
545
noInlineConfig?: boolean;
546
reportUnusedDisableDirectives?: boolean;
547
};
548
}
549
```