0
# TSESLint Types
1
2
The `TSESLint` namespace provides TypeScript-enhanced ESLint types, classes, and interfaces for building type-safe ESLint rules and configurations.
3
4
## Import
5
6
```typescript { .api }
7
import { TSESLint } from '@typescript-eslint/utils';
8
```
9
10
## AST Types
11
12
### AST Namespace
13
14
```typescript { .api }
15
namespace AST {
16
// Token types from TSESTree
17
type TokenType = TSESTree.Token;
18
type Token = TSESTree.Token;
19
20
// Source location and range
21
type SourceLocation = TSESTree.SourceLocation;
22
type Range = TSESTree.Range;
23
}
24
25
// Usage examples
26
function processToken(token: TSESLint.AST.Token) {
27
const location: TSESLint.AST.SourceLocation = token.loc;
28
const range: TSESLint.AST.Range = token.range;
29
}
30
```
31
32
## Configuration Types
33
34
### Shared Configuration
35
36
```typescript { .api }
37
namespace SharedConfig {
38
// Global configuration settings
39
interface GlobalsConfig {
40
[name: string]: boolean | 'readonly' | 'writable' | 'off';
41
}
42
43
// Language options
44
interface LanguageOptions {
45
ecmaVersion?: EcmaVersion;
46
sourceType?: 'script' | 'module';
47
globals?: GlobalsConfig;
48
parser?: Parser.LooseParserModule;
49
parserOptions?: ParserOptions;
50
}
51
52
// Linter options
53
interface LinterOptions {
54
noInlineConfig?: boolean;
55
reportUnusedDisableDirectives?: boolean | 'error' | 'warn' | 'off';
56
}
57
58
// Rule configuration levels
59
type RuleLevel = 'off' | 'warn' | 'error' | 0 | 1 | 2;
60
61
// Rule configuration entry
62
type RuleEntry<Options extends readonly unknown[] = readonly unknown[]> =
63
| RuleLevel
64
| [RuleLevel]
65
| [RuleLevel, ...Options];
66
67
// Rules configuration
68
type RulesRecord = Record<string, RuleEntry>;
69
}
70
```
71
72
### Classic Configuration
73
74
```typescript { .api }
75
namespace ClassicConfig {
76
// Environment configuration
77
interface EnvironmentConfig {
78
[name: string]: boolean;
79
}
80
81
// Classic ESLint configuration format
82
interface Config {
83
// Rule and environment settings
84
rules?: SharedConfig.RulesRecord;
85
env?: EnvironmentConfig;
86
globals?: SharedConfig.GlobalsConfig;
87
88
// Parser settings
89
parser?: string;
90
parserOptions?: ParserOptions;
91
92
// Plugin and extension settings
93
plugins?: string[];
94
extends?: string | string[];
95
96
// File pattern settings
97
files?: string | string[];
98
excludedFiles?: string | string[];
99
100
// Inheritance settings
101
root?: boolean;
102
ignorePatterns?: string | string[];
103
104
// Override configurations
105
overrides?: ConfigOverride[];
106
107
// Processor settings
108
processor?: string;
109
110
// Settings object
111
settings?: Record<string, unknown>;
112
}
113
114
// Configuration override
115
interface ConfigOverride extends Config {
116
files: string | string[];
117
excludedFiles?: string | string[];
118
}
119
}
120
```
121
122
### Flat Configuration
123
124
```typescript { .api }
125
namespace FlatConfig {
126
// Flat configuration format (ESLint 9+)
127
interface Config {
128
// File matching
129
files?: string[];
130
ignores?: string[];
131
132
// Language and linter options
133
languageOptions?: SharedConfig.LanguageOptions;
134
linterOptions?: SharedConfig.LinterOptions;
135
136
// Processor configuration
137
processor?: string | Processor.ProcessorModule;
138
139
// Plugin configuration
140
plugins?: Record<string, Plugin>;
141
142
// Rule configuration
143
rules?: SharedConfig.RulesRecord;
144
145
// Settings
146
settings?: Record<string, unknown>;
147
148
// Name for configuration identification
149
name?: string;
150
}
151
152
// Plugin interface
153
interface Plugin {
154
configs?: Record<string, Config | Config[]>;
155
environments?: Record<string, SharedConfig.GlobalsConfig>;
156
processors?: Record<string, Processor.ProcessorModule>;
157
rules?: Record<string, Rule.RuleModule>;
158
159
// Metadata
160
meta?: {
161
name?: string;
162
version?: string;
163
};
164
}
165
166
// Configuration array
167
type ConfigArray = Config[];
168
}
169
```
170
171
## Rule System
172
173
### Rule Module Interface
174
175
```typescript { .api }
176
interface RuleModule<
177
MessageIds extends string = string,
178
Options extends readonly unknown[] = readonly unknown[],
179
Docs extends Record<string, unknown> = Record<string, unknown>
180
> {
181
// Rule metadata
182
meta: RuleMetaData<MessageIds, Docs, Options>;
183
184
// Rule implementation
185
create: (
186
context: RuleContext<MessageIds, Options>
187
) => RuleListener;
188
189
// Default options
190
defaultOptions?: Options;
191
}
192
193
// Rule metadata
194
interface RuleMetaData<
195
MessageIds extends string,
196
Docs extends Record<string, unknown>,
197
Options extends readonly unknown[]
198
> {
199
type: 'problem' | 'suggestion' | 'layout';
200
201
// Documentation
202
docs?: RuleMetaDataDocs<Docs>;
203
204
// Messages for reporting
205
messages: Record<MessageIds, string>;
206
207
// Configuration schema
208
schema: JSONSchema4 | readonly JSONSchema4[];
209
210
// Fixability
211
fixable?: 'code' | 'whitespace';
212
hasSuggestions?: boolean;
213
214
// Deprecation
215
deprecated?: boolean;
216
replacedBy?: readonly string[];
217
}
218
219
// Rule documentation metadata
220
interface RuleMetaDataDocs<Docs extends Record<string, unknown>>
221
extends Docs {
222
description?: string;
223
url?: string;
224
}
225
```
226
227
### Rule Context
228
229
```typescript { .api }
230
interface RuleContext<
231
MessageIds extends string = string,
232
Options extends readonly unknown[] = readonly unknown[]
233
> {
234
// Rule identification
235
id: string;
236
options: Options;
237
settings: Record<string, unknown>;
238
parserOptions: ParserOptions;
239
240
// File information
241
filename: string;
242
physicalFilename: string;
243
cwd: string;
244
245
// Source code access
246
sourceCode: SourceCode;
247
248
// Legacy methods (deprecated)
249
getAncestors(): TSESTree.Node[];
250
getDeclaredVariables(node: TSESTree.Node): Scope.Variable[];
251
getFilename(): string;
252
getPhysicalFilename(): string;
253
getCwd(): string;
254
getScope(): Scope.Scope;
255
getSourceCode(): SourceCode;
256
markVariableAsUsed(name: string): boolean;
257
258
// Reporting
259
report(descriptor: ReportDescriptor<MessageIds>): void;
260
}
261
```
262
263
### Rule Listener
264
265
```typescript { .api }
266
// Rule visitor methods
267
type RuleListener = {
268
[K in TSESTree.Node as K['type']]?: (node: K) => void;
269
} & {
270
[K in TSESTree.Node as `${K['type']}:exit`]?: (node: K) => void;
271
} & {
272
[K in `${TSESTree.Node['type']}:exit`]?: (node: TSESTree.Node) => void;
273
} & {
274
Program?: (node: TSESTree.Program) => void;
275
'Program:exit'?: (node: TSESTree.Program) => void;
276
};
277
278
// Usage example
279
const ruleListener: TSESLint.RuleListener = {
280
// Enter node handlers
281
FunctionDeclaration(node) {
282
// Handle function declarations
283
},
284
285
CallExpression(node) {
286
// Handle call expressions
287
},
288
289
// Exit node handlers
290
'FunctionDeclaration:exit'(node) {
291
// Handle leaving function declarations
292
},
293
294
// Program-level handlers
295
Program(node) {
296
// Handle program start
297
},
298
299
'Program:exit'(node) {
300
// Handle program end
301
}
302
};
303
```
304
305
### Report Descriptor
306
307
```typescript { .api }
308
interface ReportDescriptor<MessageIds extends string> {
309
// Target specification (one required)
310
node?: TSESTree.Node;
311
loc?: TSESTree.SourceLocation | TSESTree.LineAndColumnData;
312
313
// Message specification
314
messageId: MessageIds;
315
data?: Record<string, unknown>;
316
317
// Fix and suggestions
318
fix?: ReportFixFunction;
319
suggest?: SuggestionReportDescriptor<MessageIds>[];
320
}
321
322
// Fix function type
323
type ReportFixFunction = (fixer: RuleFixer) => null | RuleFix | readonly RuleFix[];
324
325
// Suggestion descriptor
326
interface SuggestionReportDescriptor<MessageIds extends string> {
327
messageId: MessageIds;
328
data?: Record<string, unknown>;
329
fix: ReportFixFunction;
330
}
331
332
// Usage example
333
context.report({
334
node,
335
messageId: 'unexpectedToken',
336
data: { token: 'function' },
337
fix: (fixer) => fixer.replaceText(node, 'const'),
338
suggest: [
339
{
340
messageId: 'useConst',
341
fix: (fixer) => fixer.replaceText(node, 'const')
342
},
343
{
344
messageId: 'useLet',
345
fix: (fixer) => fixer.replaceText(node, 'let')
346
}
347
]
348
});
349
```
350
351
### Rule Fixer
352
353
```typescript { .api }
354
interface RuleFixer {
355
// Text insertion
356
insertTextAfter(nodeOrToken: TSESTree.Node | TSESTree.Token, text: string): RuleFix;
357
insertTextBefore(nodeOrToken: TSESTree.Node | TSESTree.Token, text: string): RuleFix;
358
insertTextAfterRange(range: TSESTree.Range, text: string): RuleFix;
359
insertTextBeforeRange(range: TSESTree.Range, text: string): RuleFix;
360
361
// Text replacement
362
replaceText(nodeOrToken: TSESTree.Node | TSESTree.Token, text: string): RuleFix;
363
replaceTextRange(range: TSESTree.Range, text: string): RuleFix;
364
365
// Text removal
366
remove(nodeOrToken: TSESTree.Node | TSESTree.Token): RuleFix;
367
removeRange(range: TSESTree.Range): RuleFix;
368
}
369
370
// Rule fix object
371
interface RuleFix {
372
range: TSESTree.Range;
373
text: string;
374
}
375
```
376
377
## ESLint Classes
378
379
### ESLint Main Class
380
381
```typescript { .api }
382
// Main ESLint class (alias for FlatESLint)
383
declare const ESLint: typeof FlatESLint;
384
385
class FlatESLint {
386
constructor(options?: FlatESLint.Options);
387
388
// Linting methods
389
lintFiles(patterns: string | string[]): Promise<FlatESLint.LintResult[]>;
390
lintText(code: string, options?: FlatESLint.LintTextOptions): Promise<FlatESLint.LintResult[]>;
391
392
// Result processing
393
static outputFixes(results: FlatESLint.LintResult[]): Promise<void>;
394
static getErrorResults(results: FlatESLint.LintResult[]): FlatESLint.LintResult[];
395
396
// Configuration
397
calculateConfigForFile(filePath: string): Promise<FlatConfig.Config>;
398
isPathIgnored(filePath: string): Promise<boolean>;
399
400
// Formatter loading
401
loadFormatter(nameOrPath?: string): Promise<FlatESLint.Formatter>;
402
403
// Version information
404
static readonly version: string;
405
}
406
407
// ESLint options
408
namespace FlatESLint {
409
interface Options {
410
// Configuration
411
overrideConfigFile?: string | boolean;
412
overrideConfig?: FlatConfig.Config | FlatConfig.Config[];
413
414
// File handling
415
cwd?: string;
416
errorOnUnmatchedPattern?: boolean;
417
ignore?: boolean;
418
ignorePath?: string;
419
420
// Processing
421
fix?: boolean | ((message: Linter.LintMessage) => boolean);
422
fixTypes?: Array<Rule.RuleMetaData['type']>;
423
424
// Plugin handling
425
plugins?: Record<string, FlatConfig.Plugin>;
426
427
// Cache settings
428
cache?: boolean;
429
cacheLocation?: string;
430
cacheStrategy?: 'metadata' | 'content';
431
}
432
433
// Lint result
434
interface LintResult {
435
filePath: string;
436
messages: Linter.LintMessage[];
437
errorCount: number;
438
warningCount: number;
439
fixableErrorCount: number;
440
fixableWarningCount: number;
441
source?: string;
442
output?: string;
443
usedDeprecatedRules: DeprecatedRuleInfo[];
444
suppressedMessages: SuppressedMessage[];
445
}
446
447
// Text linting options
448
interface LintTextOptions {
449
filePath?: string;
450
warnIgnored?: boolean;
451
}
452
453
// Formatter interface
454
interface Formatter {
455
format(results: LintResult[], context?: FormatterContext): string | Promise<string>;
456
}
457
}
458
```
459
460
### Legacy ESLint Class
461
462
```typescript { .api }
463
class LegacyESLint {
464
constructor(options?: LegacyESLint.Options);
465
466
// Similar interface to FlatESLint but for classic config
467
lintFiles(patterns: string | string[]): Promise<LegacyESLint.LintResult[]>;
468
lintText(code: string, options?: LegacyESLint.LintTextOptions): Promise<LegacyESLint.LintResult[]>;
469
470
// Configuration methods
471
calculateConfigForFile(filePath: string): Promise<ClassicConfig.Config>;
472
isPathIgnored(filePath: string): Promise<boolean>;
473
474
// Static methods
475
static outputFixes(results: LegacyESLint.LintResult[]): Promise<void>;
476
static getErrorResults(results: LegacyESLint.LintResult[]): LegacyESLint.LintResult[];
477
}
478
```
479
480
### Linter Class
481
482
```typescript { .api }
483
class Linter {
484
constructor(options?: Linter.LinterOptions);
485
486
// Core linting
487
verifyAndFix(code: string, config: Linter.Config, options?: Linter.VerifyOptions): Linter.FixReport;
488
verify(code: string, config: Linter.Config, options?: Linter.VerifyOptions): Linter.LintMessage[];
489
490
// Source code operations
491
getSourceCode(filename?: string): SourceCode;
492
493
// Rule management
494
defineRule(ruleId: string, ruleModule: Rule.RuleModule): void;
495
defineRules(rules: Record<string, Rule.RuleModule>): void;
496
getRules(): Map<string, Rule.RuleModule>;
497
498
// Parser management
499
defineParser(parserId: string, parserModule: Parser.ParserModule): void;
500
501
// Configuration
502
static readonly version: string;
503
}
504
505
namespace Linter {
506
// Linter configuration
507
type Config = FlatConfig.Config;
508
509
// Linter options
510
interface LinterOptions {
511
cwd?: string;
512
}
513
514
// Verification options
515
interface VerifyOptions {
516
filename?: string;
517
allowInlineConfig?: boolean;
518
reportUnusedDisableDirectives?: boolean | 'error' | 'warn' | 'off';
519
disableFixes?: boolean;
520
}
521
522
// Lint message
523
interface LintMessage {
524
ruleId: string | null;
525
severity: Severity;
526
message: string;
527
line: number;
528
column: number;
529
endLine?: number;
530
endColumn?: number;
531
fix?: Rule.RuleFix;
532
suggestions?: Suggestion[];
533
nodeType: string;
534
messageId?: string;
535
}
536
537
// Message severity
538
type Severity = 1 | 2; // 1 = warning, 2 = error
539
540
// Fix report
541
interface FixReport {
542
fixed: boolean;
543
messages: LintMessage[];
544
output: string;
545
}
546
547
// Suggestion
548
interface Suggestion {
549
desc: string;
550
messageId?: string;
551
fix: Rule.RuleFix;
552
}
553
}
554
```
555
556
## Parser System
557
558
### Parser Namespace
559
560
```typescript { .api }
561
namespace Parser {
562
// Parser module interface
563
interface ParserModule {
564
parse(code: string, options?: ParserOptions): ParseResult;
565
parseForESLint?(code: string, options?: ParserOptions): ParseResult;
566
567
// Metadata
568
meta?: ParserMeta;
569
}
570
571
// Loose parser for configurations
572
interface LooseParserModule {
573
parse?: ParserModule['parse'];
574
parseForESLint?: ParserModule['parseForESLint'];
575
meta?: ParserMeta;
576
}
577
578
// Parse result
579
interface ParseResult {
580
ast: TSESTree.Program;
581
services?: ParserServices;
582
scopeManager?: Scope.ScopeManager;
583
visitorKeys?: VisitorKeys;
584
}
585
586
// Parser metadata
587
interface ParserMeta {
588
name?: string;
589
version?: string;
590
}
591
592
// Visitor keys for AST traversal
593
interface VisitorKeys {
594
[nodeType: string]: string[];
595
}
596
}
597
```
598
599
## Processor System
600
601
### Processor Namespace
602
603
```typescript { .api }
604
namespace Processor {
605
// Processor module interface
606
interface ProcessorModule {
607
preprocess?(text: string, filename: string): Array<string | TextInfo>;
608
postprocess?(messages: Linter.LintMessage[][], filename: string): Linter.LintMessage[];
609
610
// Metadata
611
meta?: ProcessorMeta;
612
613
// Support for supportsAutofix
614
supportsAutofix?: boolean;
615
}
616
617
// Loose processor for configurations
618
interface LooseProcessorModule {
619
preprocess?: ProcessorModule['preprocess'];
620
postprocess?: ProcessorModule['postprocess'];
621
meta?: ProcessorMeta;
622
supportsAutofix?: boolean;
623
}
624
625
// Processor metadata
626
interface ProcessorMeta {
627
name?: string;
628
version?: string;
629
}
630
631
// Text information for processing
632
interface TextInfo {
633
text: string;
634
filename: string;
635
}
636
637
// Pre-process function type
638
type PreProcess = NonNullable<ProcessorModule['preprocess']>;
639
640
// Post-process function type
641
type PostProcess = NonNullable<ProcessorModule['postprocess']>;
642
}
643
```
644
645
## Source Code
646
647
### SourceCode Class
648
649
```typescript { .api }
650
class SourceCode {
651
constructor(text: string, ast: TSESTree.Program);
652
constructor(config: SourceCode.Config);
653
654
// Text access
655
text: string;
656
ast: TSESTree.Program;
657
lines: string[];
658
hasBOM: boolean;
659
660
// Token access
661
getAllComments(): TSESTree.Comment[];
662
getComments(node: TSESTree.Node): { leading: TSESTree.Comment[]; trailing: TSESTree.Comment[] };
663
getCommentsAfter(nodeOrToken: TSESTree.Node | TSESTree.Token): TSESTree.Comment[];
664
getCommentsBefore(nodeOrToken: TSESTree.Node | TSESTree.Token): TSESTree.Comment[];
665
getCommentsInside(node: TSESTree.Node): TSESTree.Comment[];
666
667
// Token retrieval
668
getFirstToken(node: TSESTree.Node, options?: SourceCode.CursorWithSkipOptions): TSESTree.Token | null;
669
getFirstTokens(node: TSESTree.Node, options?: SourceCode.CursorWithCountOptions): TSESTree.Token[];
670
getLastToken(node: TSESTree.Node, options?: SourceCode.CursorWithSkipOptions): TSESTree.Token | null;
671
getLastTokens(node: TSESTree.Node, options?: SourceCode.CursorWithCountOptions): TSESTree.Token[];
672
673
getTokenAfter(nodeOrToken: TSESTree.Node | TSESTree.Token, options?: SourceCode.CursorWithSkipOptions): TSESTree.Token | null;
674
getTokenBefore(nodeOrToken: TSESTree.Node | TSESTree.Token, options?: SourceCode.CursorWithSkipOptions): TSESTree.Token | null;
675
getTokensAfter(nodeOrToken: TSESTree.Node | TSESTree.Token, options?: SourceCode.CursorWithCountOptions): TSESTree.Token[];
676
getTokensBefore(nodeOrToken: TSESTree.Node | TSESTree.Token, options?: SourceCode.CursorWithCountOptions): TSESTree.Token[];
677
678
getTokens(node: TSESTree.Node, beforeCount?: number, afterCount?: number): TSESTree.Token[];
679
getTokensByRangeStart(range: TSESTree.Range, options?: SourceCode.FilterPredicate): TSESTree.Token[];
680
681
// Text operations
682
getText(node?: TSESTree.Node | TSESTree.Token, beforeCount?: number, afterCount?: number): string;
683
getLines(): string[];
684
685
// Location utilities
686
getLocFromIndex(index: number): TSESTree.LineAndColumnData;
687
getIndexFromLoc(location: TSESTree.LineAndColumnData): number;
688
}
689
690
namespace SourceCode {
691
// Configuration interface
692
interface Config {
693
text: string;
694
ast: TSESTree.Program;
695
parserServices?: ParserServices;
696
scopeManager?: Scope.ScopeManager;
697
visitorKeys?: Parser.VisitorKeys;
698
}
699
700
// Token filtering options
701
interface CursorWithSkipOptions {
702
skip?: number;
703
includeComments?: boolean;
704
filter?: FilterPredicate;
705
}
706
707
interface CursorWithCountOptions {
708
count?: number;
709
includeComments?: boolean;
710
filter?: FilterPredicate;
711
}
712
713
// Filter predicate
714
type FilterPredicate = (tokenOrComment: TSESTree.Token | TSESTree.Comment) => boolean;
715
}
716
```
717
718
## Scope Analysis
719
720
### Scope Namespace
721
722
```typescript { .api }
723
namespace Scope {
724
// Main scope manager
725
interface ScopeManager {
726
scopes: Scope[];
727
globalScope: GlobalScope | null;
728
acquire(node: TSESTree.Node, inner?: boolean): Scope | null;
729
getDeclaredVariables(node: TSESTree.Node): Variable[];
730
}
731
732
// Base scope interface
733
interface Scope {
734
type: ScopeType;
735
isStrict: boolean;
736
upper: Scope | null;
737
childScopes: Scope[];
738
variableScope: Scope;
739
block: TSESTree.Node;
740
variables: Variable[];
741
references: Reference[];
742
743
// Scope methods
744
set: Map<string, Variable>;
745
through: Reference[];
746
}
747
748
// Scope types
749
type ScopeType =
750
| 'block'
751
| 'catch'
752
| 'class'
753
| 'for'
754
| 'function'
755
| 'function-expression-name'
756
| 'global'
757
| 'module'
758
| 'switch'
759
| 'with'
760
| 'TSDeclareFunction'
761
| 'TSModuleBlock';
762
763
// Global scope
764
interface GlobalScope extends Scope {
765
type: 'global';
766
childScopes: Scope[];
767
through: Reference[];
768
}
769
770
// Variable definition
771
interface Variable {
772
name: string;
773
identifiers: TSESTree.Identifier[];
774
references: Reference[];
775
defs: Definition[];
776
777
// Variable properties
778
scope: Scope;
779
eslintExplicitGlobal?: boolean;
780
eslintExplicitGlobalComments?: TSESTree.Comment[];
781
eslintImplicitGlobalSetting?: 'readonly' | 'writable';
782
writeable?: boolean;
783
}
784
785
// Variable reference
786
interface Reference {
787
identifier: TSESTree.Identifier;
788
from: Scope;
789
resolved: Variable | null;
790
writeExpr: TSESTree.Node | null;
791
init: boolean;
792
793
// Reference flags
794
isWrite(): boolean;
795
isRead(): boolean;
796
isWriteOnly(): boolean;
797
isReadOnly(): boolean;
798
isReadWrite(): boolean;
799
}
800
801
// Variable definition
802
interface Definition {
803
type: DefinitionType;
804
name: TSESTree.Identifier;
805
node: TSESTree.Node;
806
parent?: TSESTree.Node;
807
index?: number;
808
kind?: string;
809
}
810
811
// Definition types
812
type DefinitionType =
813
| 'CatchClause'
814
| 'ClassName'
815
| 'FunctionName'
816
| 'ImplicitGlobalVariable'
817
| 'ImportBinding'
818
| 'Parameter'
819
| 'TSEnumName'
820
| 'TSEnumMemberName'
821
| 'TSModuleName'
822
| 'Variable';
823
}
824
```
825
826
## Complete Rule Example
827
828
### Type-Safe Rule Implementation
829
830
```typescript { .api }
831
import { ESLintUtils, TSESLint, TSESTree, AST_NODE_TYPES } from '@typescript-eslint/utils';
832
833
type Options = [{
834
allowShortCircuit?: boolean;
835
allowTernary?: boolean;
836
allowTaggedTemplates?: boolean;
837
}];
838
839
type MessageIds = 'unusedExpression' | 'unusedValue';
840
841
const createRule = ESLintUtils.RuleCreator(name => `https://example.com/${name}`);
842
843
export default createRule<Options, MessageIds>({
844
name: 'no-unused-expressions',
845
meta: {
846
type: 'suggestion',
847
docs: {
848
description: 'Disallow unused expressions',
849
recommended: false
850
},
851
messages: {
852
unusedExpression: 'Expected an assignment or function call and instead saw an expression.',
853
unusedValue: 'Expected an assignment or function call and instead saw an unused value.'
854
},
855
schema: [{
856
type: 'object',
857
properties: {
858
allowShortCircuit: { type: 'boolean' },
859
allowTernary: { type: 'boolean' },
860
allowTaggedTemplates: { type: 'boolean' }
861
},
862
additionalProperties: false
863
}]
864
},
865
defaultOptions: [{
866
allowShortCircuit: false,
867
allowTernary: false,
868
allowTaggedTemplates: false
869
}],
870
create(context: TSESLint.RuleContext<MessageIds, Options>, [options]) {
871
const sourceCode = context.sourceCode;
872
873
function isDirectiveComment(node: TSESTree.Node): boolean {
874
const comments = sourceCode.getCommentsBefore(node);
875
return comments.some(comment =>
876
comment.type === 'Line' && comment.value.trim().startsWith('eslint-')
877
);
878
}
879
880
function checkExpressionStatement(node: TSESTree.ExpressionStatement): void {
881
if (isDirectiveComment(node)) {
882
return;
883
}
884
885
const { expression } = node;
886
let messageId: MessageIds = 'unusedExpression';
887
888
// Check for allowed patterns
889
if (options.allowShortCircuit) {
890
if (expression.type === AST_NODE_TYPES.LogicalExpression &&
891
(expression.operator === '&&' || expression.operator === '||')) {
892
return;
893
}
894
}
895
896
if (options.allowTernary && expression.type === AST_NODE_TYPES.ConditionalExpression) {
897
return;
898
}
899
900
if (options.allowTaggedTemplates && expression.type === AST_NODE_TYPES.TaggedTemplateExpression) {
901
return;
902
}
903
904
// Determine appropriate message
905
if (expression.type === AST_NODE_TYPES.AssignmentExpression ||
906
expression.type === AST_NODE_TYPES.CallExpression ||
907
expression.type === AST_NODE_TYPES.NewExpression) {
908
return; // These are generally allowed
909
}
910
911
if (expression.type === AST_NODE_TYPES.UnaryExpression ||
912
expression.type === AST_NODE_TYPES.BinaryExpression) {
913
messageId = 'unusedValue';
914
}
915
916
context.report({
917
node: expression,
918
messageId
919
});
920
}
921
922
const ruleListener: TSESLint.RuleListener = {
923
ExpressionStatement: checkExpressionStatement,
924
925
Program() {
926
// Program-level setup
927
const scope = context.sourceCode.scopeManager?.acquire(context.sourceCode.ast, false);
928
if (scope) {
929
// Analyze global scope if needed
930
}
931
}
932
};
933
934
return ruleListener;
935
}
936
});
937
```
938
939
### Rule Testing Interface
940
941
```typescript { .api }
942
// Rule tester (deprecated - use @typescript-eslint/rule-tester instead)
943
class RuleTester {
944
constructor(config?: RuleTester.Config);
945
946
run<MessageIds extends string, Options extends readonly unknown[]>(
947
name: string,
948
rule: TSESLint.RuleModule<MessageIds, Options>,
949
tests: {
950
valid: RuleTester.ValidTestCase<Options>[];
951
invalid: RuleTester.InvalidTestCase<MessageIds, Options>[];
952
}
953
): void;
954
}
955
956
namespace RuleTester {
957
interface Config {
958
parser?: string;
959
parserOptions?: ParserOptions;
960
globals?: Record<string, boolean>;
961
settings?: Record<string, unknown>;
962
}
963
964
interface ValidTestCase<Options extends readonly unknown[]> {
965
code: string;
966
options?: Options;
967
filename?: string;
968
parserOptions?: ParserOptions;
969
settings?: Record<string, unknown>;
970
globals?: Record<string, boolean>;
971
}
972
973
interface InvalidTestCase<MessageIds extends string, Options extends readonly unknown[]>
974
extends ValidTestCase<Options> {
975
errors: TestCaseError<MessageIds>[];
976
output?: string | null;
977
}
978
979
interface TestCaseError<MessageIds extends string> {
980
messageId: MessageIds;
981
data?: Record<string, unknown>;
982
type?: string;
983
line?: number;
984
column?: number;
985
endLine?: number;
986
endColumn?: number;
987
suggestions?: SuggestionOutput<MessageIds>[];
988
}
989
990
interface SuggestionOutput<MessageIds extends string> {
991
messageId: MessageIds;
992
output: string;
993
data?: Record<string, unknown>;
994
}
995
}
996
```