0
# Rules
1
2
TSLint includes 163+ built-in rules organized by category and provides comprehensive APIs for developing custom rules.
3
4
## Built-in Rules
5
6
### Rule Categories
7
8
TSLint rules are organized into five categories:
9
10
1. **Functionality** (65 rules) - Correctness and best practices
11
2. **Maintainability** (14 rules) - Code maintainability
12
3. **Style** (79 rules) - Coding style and conventions
13
4. **TypeScript** (rules in other categories marked as TypeScript-specific)
14
5. **Formatting** (5 rules) - Code formatting
15
16
### Functionality Rules
17
18
These rules catch common errors and enforce best practices:
19
20
#### Core Functionality Rules
21
22
- `await-promise` - Requires that the results of async functions be awaited
23
- `ban-comma-operator` - Disallows the comma operator
24
- `ban-ts-ignore` - Bans `@ts-ignore` comments from being used
25
- `ban` - Bans the use of specific functions or global methods
26
- `curly` - Enforces braces for if/for/do/while statements
27
- `forin` - Requires a `for ... in` statement to be filtered with an `if` statement
28
- `function-constructor` - Prevents using the built-in Function constructor
29
- `import-blacklist` - Disallows importing the specified modules
30
- `label-position` - Only allows labels in sensible locations
31
- `no-arg` - Disallows use of `arguments.caller` and `arguments.callee`
32
33
#### Variable and Scope Rules
34
35
- `no-duplicate-variable` - Disallows duplicate variable declarations
36
- `no-shadowed-variable` - Disallows shadowing variable declarations
37
- `no-unused-expression` - Disallows unused expression statements
38
- `no-unused-variable` - Disallows unused imports, variables, functions and private members
39
- `no-use-before-declare` - Disallows usage of variables before their declaration
40
- `no-var-keyword` - Disallows usage of the `var` keyword
41
- `no-var-requires` - Disallows the use of require statements except in import statements
42
43
#### Type Safety Rules
44
45
- `no-unsafe-any` - Warns when using an expression of type 'any' in unsafe way
46
- `no-unsafe-finally` - Disallows control flow statements in finally blocks
47
- `restrict-plus-operands` - Requires both operands of addition to be the same type
48
- `strict-boolean-expressions` - Restricts the types allowed in boolean expressions
49
- `strict-type-predicates` - Warns for type predicates that are always true or always false
50
- `use-isnan` - Enforces use of the `isNaN()` function
51
52
### Maintainability Rules
53
54
Rules focused on code maintainability and complexity:
55
56
- `cyclomatic-complexity` - Enforces a threshold of cyclomatic complexity
57
- `deprecation` - Warns when deprecated APIs are used
58
- `max-classes-per-file` - Maximum number of classes per file
59
- `max-file-line-count` - Requires files to remain under a certain number of lines
60
- `max-line-length` - Requires lines to be under a certain max length
61
- `no-default-export` - Disallows default exports in ES6-style modules
62
- `no-default-import` - Avoid import statements with side-effect imports
63
- `no-duplicate-imports` - Disallows multiple import statements from the same module
64
- `no-mergeable-namespace` - Disallows mergeable namespaces in the same file
65
- `no-require-imports` - Disallows invocation of `require()`
66
- `object-literal-sort-keys` - Checks ordering of keys in object literals
67
- `prefer-const` - Requires that variable declarations use `const` instead of `let` if possible
68
- `prefer-readonly` - Requires that private variables are marked as `readonly` if they're never modified outside of the constructor
69
- `trailing-comma` - Requires or disallows trailing commas in array and object literals, destructuring assignments, function typings, named imports and exports and function parameters
70
71
### Style Rules
72
73
Rules for consistent coding style:
74
75
#### Naming and Casing
76
77
- `class-name` - Enforces PascalCase for classes and interfaces
78
- `file-name-casing` - Enforces consistent file naming conventions
79
- `interface-name` - Requires interface names to begin with a capital 'I'
80
- `match-default-export-name` - Requires that a default import have the same name as the declaration it imports
81
- `variable-name` - Checks variable names for various errors
82
83
#### Comments and Documentation
84
85
- `comment-format` - Enforces formatting rules for single-line comments
86
- `comment-type` - Requires or forbids JSDoc style comments for classes, interfaces, functions, etc.
87
- `completed-docs` - Enforces documentation for important items be filled out
88
- `file-header` - Enforces a certain header comment for all files, matched by a regular expression
89
- `jsdoc-format` - Enforces basic format rules for JSDoc comments
90
- `no-redundant-jsdoc` - Forbids JSDoc which duplicates TypeScript functionality
91
92
#### Import and Module Style
93
94
- `import-spacing` - Ensures proper spacing between import statement elements
95
- `no-import-side-effect` - Avoid import statements with side-effect imports
96
- `no-reference` - Disallows `/// <reference>` imports
97
- `no-reference-import` - Don't `<reference types="foo" />` if you import `foo` anyway
98
- `ordered-imports` - Requires that import statements be alphabetized and grouped
99
100
### Formatting Rules
101
102
Rules for consistent code formatting:
103
104
- `eofline` - Ensures the file ends with a newline
105
- `indent` - Enforces indentation with tabs or spaces
106
- `linebreak-style` - Enforces consistent linebreak style
107
- `semicolon` - Enforces consistent semicolon usage
108
- `whitespace` - Enforces whitespace style conventions
109
110
## Custom Rule Development
111
112
TSLint provides comprehensive APIs for developing custom rules.
113
114
### Rule Base Classes
115
116
```typescript { .api }
117
import { Rules } from 'tslint';
118
119
// Base rule class
120
abstract class AbstractRule implements IRule {
121
static metadata: IRuleMetadata;
122
123
constructor(options: IOptions)
124
getOptions(): IOptions
125
isEnabled(): boolean
126
apply(sourceFile: ts.SourceFile): RuleFailure[]
127
applyWithWalker(walker: IWalker): RuleFailure[]
128
129
// Helper method for simple rules
130
applyWithFunction(
131
sourceFile: ts.SourceFile,
132
walkFn: (ctx: WalkContext<T>) => void,
133
options?: T
134
): RuleFailure[]
135
}
136
137
// Type-aware rule base class
138
abstract class TypedRule extends AbstractRule implements ITypedRule {
139
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[]
140
}
141
142
// Optional type-aware rule base class
143
abstract class OptionallyTypedRule extends AbstractRule {
144
apply(sourceFile: ts.SourceFile): RuleFailure[]
145
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[]
146
}
147
```
148
149
### Rule Metadata
150
151
```typescript { .api }
152
interface IRuleMetadata {
153
ruleName: string;
154
type: "functionality" | "maintainability" | "style" | "typescript" | "formatting";
155
deprecationMessage?: string;
156
description: string;
157
descriptionDetails?: string;
158
hasFix?: boolean;
159
optionsDescription: string;
160
options: any;
161
optionExamples?: Array<true | any[]> | string[] | Array<{ options: any }>;
162
rationale?: string;
163
requiresTypeInfo?: boolean;
164
typescriptOnly: boolean;
165
codeExamples?: ICodeExample[];
166
}
167
168
interface ICodeExample {
169
description: string;
170
config: string;
171
pass?: string;
172
fail?: string;
173
}
174
```
175
176
### Rule Options Interface
177
178
```typescript { .api }
179
interface IOptions {
180
ruleArguments: any[];
181
ruleSeverity: "warning" | "error" | "off";
182
ruleName: string;
183
disabledIntervals: IDisabledInterval[];
184
}
185
```
186
187
### Rule Failure System
188
189
```typescript { .api }
190
class RuleFailure {
191
constructor(
192
sourceFile: ts.SourceFile,
193
start: number,
194
end: number,
195
failure: string,
196
ruleName: string,
197
fix?: Fix
198
)
199
200
// Position methods
201
getFileName(): string
202
getStartPosition(): RuleFailurePosition
203
getEndPosition(): RuleFailurePosition
204
205
// Content methods
206
getFailure(): string
207
getRuleName(): string
208
getRuleSeverity(): RuleSeverity
209
setRuleSeverity(value: RuleSeverity): void
210
211
// Fix methods
212
hasFix(): boolean
213
getFix(): Fix | undefined
214
215
// Serialization
216
toJson(): IRuleFailureJson
217
equals(other: RuleFailure): boolean
218
static compare(a: RuleFailure, b: RuleFailure): number
219
}
220
221
interface RuleFailurePosition {
222
character: number;
223
line: number;
224
position: number;
225
}
226
227
interface IRuleFailureJson {
228
endPosition: IRuleFailurePositionJson;
229
failure: string;
230
fix?: FixJson;
231
name: string;
232
ruleSeverity: string;
233
ruleName: string;
234
startPosition: IRuleFailurePositionJson;
235
}
236
237
interface IRuleFailurePositionJson {
238
character: number;
239
line: number;
240
position: number;
241
}
242
243
interface ReplacementJson {
244
innerStart: number;
245
innerLength: number;
246
innerText: string;
247
}
248
249
type FixJson = ReplacementJson | ReplacementJson[];
250
type RuleSeverity = "warning" | "error" | "off";
251
type Fix = Replacement | Replacement[];
252
```
253
254
### Replacement System
255
256
```typescript { .api }
257
class Replacement {
258
constructor(start: number, length: number, text: string)
259
260
// Static factory methods
261
static replaceNode(node: ts.Node, text: string, sourceFile?: ts.SourceFile): Replacement
262
static replaceFromTo(start: number, end: number, text: string): Replacement
263
static deleteText(start: number, length: number): Replacement
264
static deleteFromTo(start: number, end: number): Replacement
265
static appendText(start: number, text: string): Replacement
266
267
// Application methods
268
apply(content: string): string
269
static applyAll(content: string, replacements: Replacement[]): string
270
static applyFixes(content: string, fixes: Fix[]): string
271
}
272
```
273
274
## Custom Rule Examples
275
276
### Simple Rule Example
277
278
```typescript
279
import { Rules, RuleFailure, RuleWalker } from 'tslint';
280
import * as ts from 'typescript';
281
282
export class Rule extends Rules.AbstractRule {
283
public static metadata: Rules.IRuleMetadata = {
284
ruleName: 'no-console-log',
285
description: 'Disallows console.log() calls',
286
optionsDescription: 'Not configurable.',
287
options: null,
288
optionExamples: [true],
289
type: 'functionality',
290
typescriptOnly: false,
291
};
292
293
public apply(sourceFile: ts.SourceFile): RuleFailure[] {
294
return this.applyWithFunction(sourceFile, walk);
295
}
296
}
297
298
function walk(ctx: Rules.WalkContext<void>) {
299
function cb(node: ts.Node): void {
300
if (ts.isCallExpression(node) &&
301
ts.isPropertyAccessExpression(node.expression) &&
302
node.expression.expression.getText() === 'console' &&
303
node.expression.name.text === 'log') {
304
305
ctx.addFailureAtNode(node, 'console.log() is not allowed');
306
}
307
return ts.forEachChild(node, cb);
308
}
309
return ts.forEachChild(ctx.sourceFile, cb);
310
}
311
```
312
313
### Rule with Options
314
315
```typescript
316
import { Rules, RuleFailure } from 'tslint';
317
import * as ts from 'typescript';
318
319
interface Options {
320
maxLength: number;
321
}
322
323
export class Rule extends Rules.AbstractRule {
324
public static metadata: Rules.IRuleMetadata = {
325
ruleName: 'max-identifier-length',
326
description: 'Enforces maximum identifier length',
327
optionsDescription: 'Maximum identifier length (default: 50)',
328
options: {
329
type: 'object',
330
properties: {
331
maxLength: { type: 'number' }
332
}
333
},
334
optionExamples: [
335
[true, { maxLength: 30 }]
336
],
337
type: 'style',
338
typescriptOnly: false,
339
};
340
341
public apply(sourceFile: ts.SourceFile): RuleFailure[] {
342
const options: Options = {
343
maxLength: 50,
344
...this.ruleArguments[0]
345
};
346
347
return this.applyWithFunction(sourceFile, walk, options);
348
}
349
}
350
351
function walk(ctx: Rules.WalkContext<Options>) {
352
function cb(node: ts.Node): void {
353
if (ts.isIdentifier(node) &&
354
node.text.length > ctx.options.maxLength) {
355
356
ctx.addFailureAtNode(
357
node,
358
`Identifier '${node.text}' exceeds maximum length of ${ctx.options.maxLength}`
359
);
360
}
361
return ts.forEachChild(node, cb);
362
}
363
return ts.forEachChild(ctx.sourceFile, cb);
364
}
365
```
366
367
### Type-Aware Rule
368
369
```typescript
370
import { Rules, RuleFailure } from 'tslint';
371
import * as ts from 'typescript';
372
373
export class Rule extends Rules.TypedRule {
374
public static metadata: Rules.IRuleMetadata = {
375
ruleName: 'no-unused-promise',
376
description: 'Disallows unused Promise return values',
377
optionsDescription: 'Not configurable.',
378
options: null,
379
optionExamples: [true],
380
type: 'functionality',
381
typescriptOnly: false,
382
requiresTypeInfo: true,
383
};
384
385
public applyWithProgram(
386
sourceFile: ts.SourceFile,
387
program: ts.Program
388
): RuleFailure[] {
389
return this.applyWithFunction(
390
sourceFile,
391
walk,
392
undefined,
393
program.getTypeChecker()
394
);
395
}
396
}
397
398
function walk(ctx: Rules.WalkContext<void>, tc: ts.TypeChecker) {
399
function cb(node: ts.Node): void {
400
if (ts.isExpressionStatement(node) &&
401
ts.isCallExpression(node.expression)) {
402
403
const type = tc.getTypeAtLocation(node.expression);
404
const typeString = tc.typeToString(type);
405
406
if (typeString.includes('Promise<')) {
407
ctx.addFailureAtNode(
408
node.expression,
409
'Promise return value is unused'
410
);
411
}
412
}
413
return ts.forEachChild(node, cb);
414
}
415
return ts.forEachChild(ctx.sourceFile, cb);
416
}
417
```
418
419
### Rule with Auto-fix
420
421
```typescript
422
import { Rules, RuleFailure, Replacement } from 'tslint';
423
import * as ts from 'typescript';
424
425
export class Rule extends Rules.AbstractRule {
426
public static metadata: Rules.IRuleMetadata = {
427
ruleName: 'prefer-const-assertion',
428
description: 'Prefer const assertion over type annotation',
429
optionsDescription: 'Not configurable.',
430
options: null,
431
optionExamples: [true],
432
type: 'style',
433
typescriptOnly: true,
434
hasFix: true,
435
};
436
437
public apply(sourceFile: ts.SourceFile): RuleFailure[] {
438
return this.applyWithFunction(sourceFile, walk);
439
}
440
}
441
442
function walk(ctx: Rules.WalkContext<void>) {
443
function cb(node: ts.Node): void {
444
if (ts.isVariableDeclaration(node) &&
445
node.type &&
446
node.initializer &&
447
ts.isAsExpression(node.initializer)) {
448
449
const fix = [
450
Replacement.deleteFromTo(node.type.pos, node.type.end),
451
Replacement.replaceNode(
452
node.initializer,
453
`${node.initializer.expression.getText()} as const`
454
)
455
];
456
457
ctx.addFailureAtNode(
458
node,
459
'Prefer const assertion over type annotation',
460
fix
461
);
462
}
463
return ts.forEachChild(node, cb);
464
}
465
return ts.forEachChild(ctx.sourceFile, cb);
466
}
467
```
468
469
## Walker System (Legacy)
470
471
TSLint includes a walker system for AST traversal, though `applyWithFunction` is now preferred.
472
473
### Walker Classes
474
475
```typescript { .api }
476
// Basic walker interface
477
interface IWalker {
478
getSourceFile(): ts.SourceFile;
479
walk(sourceFile: ts.SourceFile): void;
480
getFailures(): RuleFailure[];
481
}
482
483
// Abstract walker base class
484
abstract class AbstractWalker<T = undefined> extends WalkContext<T> implements IWalker {
485
abstract walk(sourceFile: ts.SourceFile): void;
486
getSourceFile(): ts.SourceFile;
487
getFailures(): RuleFailure[];
488
}
489
490
// Base syntax walker class
491
class SyntaxWalker {
492
walk(node: ts.Node): void;
493
protected visitNode(node: ts.Node): void;
494
protected walkChildren(node: ts.Node): void;
495
}
496
497
// Legacy rule walker (deprecated)
498
class RuleWalker extends SyntaxWalker {
499
// Prefer applyWithFunction over extending RuleWalker
500
}
501
```
502
503
### Walk Context
504
505
```typescript { .api }
506
class WalkContext<T = undefined> {
507
constructor(sourceFile: ts.SourceFile, ruleName: string, options?: T)
508
509
// Failure reporting
510
addFailure(start: number, end: number, message: string, fix?: Fix): void
511
addFailureAt(position: number, width: number, message: string, fix?: Fix): void
512
addFailureAtNode(node: ts.Node, message: string, fix?: Fix): void
513
514
// Properties
515
readonly sourceFile: ts.SourceFile
516
readonly failures: RuleFailure[]
517
readonly options: T | undefined
518
readonly ruleName: string
519
}
520
```
521
522
## Rule Loading and Registration
523
524
### Custom Rules Directory
525
526
```typescript
527
// In tslint.json
528
{
529
"rulesDirectory": [
530
"./custom-rules",
531
"node_modules/custom-tslint-rules/lib"
532
],
533
"rules": {
534
"my-custom-rule": true
535
}
536
}
537
```
538
539
### Rule Naming Convention
540
541
Rules should be exported with PascalCase class names and kebab-case file names:
542
543
- File: `no-console-log.ts` or `no-console-log.js`
544
- Class: `export class Rule extends Rules.AbstractRule`
545
546
### Rule Distribution
547
548
```typescript
549
// Package custom rules as npm module
550
// package.json
551
{
552
"name": "my-tslint-rules",
553
"main": "lib/index.js",
554
"files": ["lib/"]
555
}
556
557
// lib/noConsoleLogRule.ts
558
export class Rule extends Rules.AbstractRule {
559
// Rule implementation
560
}
561
```
562
563
## Best Practices
564
565
### Rule Development Guidelines
566
567
1. **Use `applyWithFunction`** instead of extending RuleWalker
568
2. **Provide comprehensive metadata** including examples and rationale
569
3. **Include auto-fixes** where appropriate using the Replacement API
570
4. **Handle edge cases** and provide clear error messages
571
5. **Test thoroughly** using TSLint's testing framework
572
6. **Follow naming conventions** for rule files and classes
573
7. **Document rule behavior** with clear descriptions and examples
574
575
### Performance Considerations
576
577
1. **Minimize AST traversal** - Use targeted node visitors
578
2. **Cache expensive computations** - Reuse TypeScript type checker results
579
3. **Avoid deep recursion** - Use iterative approaches where possible
580
4. **Limit rule scope** - Don't check unnecessary node types
581
582
### Error Handling
583
584
```typescript
585
function walk(ctx: Rules.WalkContext<void>) {
586
function cb(node: ts.Node): void {
587
try {
588
// Rule logic here
589
if (someCondition(node)) {
590
ctx.addFailureAtNode(node, 'Rule violation');
591
}
592
} catch (error) {
593
// Log error but don't fail linting
594
console.warn(`Rule error: ${error.message}`);
595
}
596
return ts.forEachChild(node, cb);
597
}
598
return ts.forEachChild(ctx.sourceFile, cb);
599
}
600
```