Utilities for working with TypeScript + ESLint together
—
The TSESLint namespace provides TypeScript-enhanced ESLint types, classes, and interfaces for building type-safe ESLint rules and configurations.
import { TSESLint } from '@typescript-eslint/utils';namespace AST {
// Token types from TSESTree
type TokenType = TSESTree.Token;
type Token = TSESTree.Token;
// Source location and range
type SourceLocation = TSESTree.SourceLocation;
type Range = TSESTree.Range;
}
// Usage examples
function processToken(token: TSESLint.AST.Token) {
const location: TSESLint.AST.SourceLocation = token.loc;
const range: TSESLint.AST.Range = token.range;
}namespace SharedConfig {
// Global configuration settings
interface GlobalsConfig {
[name: string]: boolean | 'readonly' | 'writable' | 'off';
}
// Language options
interface LanguageOptions {
ecmaVersion?: EcmaVersion;
sourceType?: 'script' | 'module';
globals?: GlobalsConfig;
parser?: Parser.LooseParserModule;
parserOptions?: ParserOptions;
}
// Linter options
interface LinterOptions {
noInlineConfig?: boolean;
reportUnusedDisableDirectives?: boolean | 'error' | 'warn' | 'off';
}
// Rule configuration levels
type RuleLevel = 'off' | 'warn' | 'error' | 0 | 1 | 2;
// Rule configuration entry
type RuleEntry<Options extends readonly unknown[] = readonly unknown[]> =
| RuleLevel
| [RuleLevel]
| [RuleLevel, ...Options];
// Rules configuration
type RulesRecord = Record<string, RuleEntry>;
}namespace ClassicConfig {
// Environment configuration
interface EnvironmentConfig {
[name: string]: boolean;
}
// Classic ESLint configuration format
interface Config {
// Rule and environment settings
rules?: SharedConfig.RulesRecord;
env?: EnvironmentConfig;
globals?: SharedConfig.GlobalsConfig;
// Parser settings
parser?: string;
parserOptions?: ParserOptions;
// Plugin and extension settings
plugins?: string[];
extends?: string | string[];
// File pattern settings
files?: string | string[];
excludedFiles?: string | string[];
// Inheritance settings
root?: boolean;
ignorePatterns?: string | string[];
// Override configurations
overrides?: ConfigOverride[];
// Processor settings
processor?: string;
// Settings object
settings?: Record<string, unknown>;
}
// Configuration override
interface ConfigOverride extends Config {
files: string | string[];
excludedFiles?: string | string[];
}
}namespace FlatConfig {
// Flat configuration format (ESLint 9+)
interface Config {
// File matching
files?: string[];
ignores?: string[];
// Language and linter options
languageOptions?: SharedConfig.LanguageOptions;
linterOptions?: SharedConfig.LinterOptions;
// Processor configuration
processor?: string | Processor.ProcessorModule;
// Plugin configuration
plugins?: Record<string, Plugin>;
// Rule configuration
rules?: SharedConfig.RulesRecord;
// Settings
settings?: Record<string, unknown>;
// Name for configuration identification
name?: string;
}
// Plugin interface
interface Plugin {
configs?: Record<string, Config | Config[]>;
environments?: Record<string, SharedConfig.GlobalsConfig>;
processors?: Record<string, Processor.ProcessorModule>;
rules?: Record<string, Rule.RuleModule>;
// Metadata
meta?: {
name?: string;
version?: string;
};
}
// Configuration array
type ConfigArray = Config[];
}interface RuleModule<
MessageIds extends string = string,
Options extends readonly unknown[] = readonly unknown[],
Docs extends Record<string, unknown> = Record<string, unknown>
> {
// Rule metadata
meta: RuleMetaData<MessageIds, Docs, Options>;
// Rule implementation
create: (
context: RuleContext<MessageIds, Options>
) => RuleListener;
// Default options
defaultOptions?: Options;
}
// Rule metadata
interface RuleMetaData<
MessageIds extends string,
Docs extends Record<string, unknown>,
Options extends readonly unknown[]
> {
type: 'problem' | 'suggestion' | 'layout';
// Documentation
docs?: RuleMetaDataDocs<Docs>;
// Messages for reporting
messages: Record<MessageIds, string>;
// Configuration schema
schema: JSONSchema4 | readonly JSONSchema4[];
// Fixability
fixable?: 'code' | 'whitespace';
hasSuggestions?: boolean;
// Deprecation
deprecated?: boolean;
replacedBy?: readonly string[];
}
// Rule documentation metadata
interface RuleMetaDataDocs<Docs extends Record<string, unknown>>
extends Docs {
description?: string;
url?: string;
}interface RuleContext<
MessageIds extends string = string,
Options extends readonly unknown[] = readonly unknown[]
> {
// Rule identification
id: string;
options: Options;
settings: Record<string, unknown>;
parserOptions: ParserOptions;
// File information
filename: string;
physicalFilename: string;
cwd: string;
// Source code access
sourceCode: SourceCode;
// Legacy methods (deprecated)
getAncestors(): TSESTree.Node[];
getDeclaredVariables(node: TSESTree.Node): Scope.Variable[];
getFilename(): string;
getPhysicalFilename(): string;
getCwd(): string;
getScope(): Scope.Scope;
getSourceCode(): SourceCode;
markVariableAsUsed(name: string): boolean;
// Reporting
report(descriptor: ReportDescriptor<MessageIds>): void;
}// Rule visitor methods
type RuleListener = {
[K in TSESTree.Node as K['type']]?: (node: K) => void;
} & {
[K in TSESTree.Node as `${K['type']}:exit`]?: (node: K) => void;
} & {
[K in `${TSESTree.Node['type']}:exit`]?: (node: TSESTree.Node) => void;
} & {
Program?: (node: TSESTree.Program) => void;
'Program:exit'?: (node: TSESTree.Program) => void;
};
// Usage example
const ruleListener: TSESLint.RuleListener = {
// Enter node handlers
FunctionDeclaration(node) {
// Handle function declarations
},
CallExpression(node) {
// Handle call expressions
},
// Exit node handlers
'FunctionDeclaration:exit'(node) {
// Handle leaving function declarations
},
// Program-level handlers
Program(node) {
// Handle program start
},
'Program:exit'(node) {
// Handle program end
}
};interface ReportDescriptor<MessageIds extends string> {
// Target specification (one required)
node?: TSESTree.Node;
loc?: TSESTree.SourceLocation | TSESTree.LineAndColumnData;
// Message specification
messageId: MessageIds;
data?: Record<string, unknown>;
// Fix and suggestions
fix?: ReportFixFunction;
suggest?: SuggestionReportDescriptor<MessageIds>[];
}
// Fix function type
type ReportFixFunction = (fixer: RuleFixer) => null | RuleFix | readonly RuleFix[];
// Suggestion descriptor
interface SuggestionReportDescriptor<MessageIds extends string> {
messageId: MessageIds;
data?: Record<string, unknown>;
fix: ReportFixFunction;
}
// Usage example
context.report({
node,
messageId: 'unexpectedToken',
data: { token: 'function' },
fix: (fixer) => fixer.replaceText(node, 'const'),
suggest: [
{
messageId: 'useConst',
fix: (fixer) => fixer.replaceText(node, 'const')
},
{
messageId: 'useLet',
fix: (fixer) => fixer.replaceText(node, 'let')
}
]
});interface RuleFixer {
// Text insertion
insertTextAfter(nodeOrToken: TSESTree.Node | TSESTree.Token, text: string): RuleFix;
insertTextBefore(nodeOrToken: TSESTree.Node | TSESTree.Token, text: string): RuleFix;
insertTextAfterRange(range: TSESTree.Range, text: string): RuleFix;
insertTextBeforeRange(range: TSESTree.Range, text: string): RuleFix;
// Text replacement
replaceText(nodeOrToken: TSESTree.Node | TSESTree.Token, text: string): RuleFix;
replaceTextRange(range: TSESTree.Range, text: string): RuleFix;
// Text removal
remove(nodeOrToken: TSESTree.Node | TSESTree.Token): RuleFix;
removeRange(range: TSESTree.Range): RuleFix;
}
// Rule fix object
interface RuleFix {
range: TSESTree.Range;
text: string;
}// Main ESLint class (alias for FlatESLint)
declare const ESLint: typeof FlatESLint;
class FlatESLint {
constructor(options?: FlatESLint.Options);
// Linting methods
lintFiles(patterns: string | string[]): Promise<FlatESLint.LintResult[]>;
lintText(code: string, options?: FlatESLint.LintTextOptions): Promise<FlatESLint.LintResult[]>;
// Result processing
static outputFixes(results: FlatESLint.LintResult[]): Promise<void>;
static getErrorResults(results: FlatESLint.LintResult[]): FlatESLint.LintResult[];
// Configuration
calculateConfigForFile(filePath: string): Promise<FlatConfig.Config>;
isPathIgnored(filePath: string): Promise<boolean>;
// Formatter loading
loadFormatter(nameOrPath?: string): Promise<FlatESLint.Formatter>;
// Version information
static readonly version: string;
}
// ESLint options
namespace FlatESLint {
interface Options {
// Configuration
overrideConfigFile?: string | boolean;
overrideConfig?: FlatConfig.Config | FlatConfig.Config[];
// File handling
cwd?: string;
errorOnUnmatchedPattern?: boolean;
ignore?: boolean;
ignorePath?: string;
// Processing
fix?: boolean | ((message: Linter.LintMessage) => boolean);
fixTypes?: Array<Rule.RuleMetaData['type']>;
// Plugin handling
plugins?: Record<string, FlatConfig.Plugin>;
// Cache settings
cache?: boolean;
cacheLocation?: string;
cacheStrategy?: 'metadata' | 'content';
}
// Lint result
interface LintResult {
filePath: string;
messages: Linter.LintMessage[];
errorCount: number;
warningCount: number;
fixableErrorCount: number;
fixableWarningCount: number;
source?: string;
output?: string;
usedDeprecatedRules: DeprecatedRuleInfo[];
suppressedMessages: SuppressedMessage[];
}
// Text linting options
interface LintTextOptions {
filePath?: string;
warnIgnored?: boolean;
}
// Formatter interface
interface Formatter {
format(results: LintResult[], context?: FormatterContext): string | Promise<string>;
}
}class LegacyESLint {
constructor(options?: LegacyESLint.Options);
// Similar interface to FlatESLint but for classic config
lintFiles(patterns: string | string[]): Promise<LegacyESLint.LintResult[]>;
lintText(code: string, options?: LegacyESLint.LintTextOptions): Promise<LegacyESLint.LintResult[]>;
// Configuration methods
calculateConfigForFile(filePath: string): Promise<ClassicConfig.Config>;
isPathIgnored(filePath: string): Promise<boolean>;
// Static methods
static outputFixes(results: LegacyESLint.LintResult[]): Promise<void>;
static getErrorResults(results: LegacyESLint.LintResult[]): LegacyESLint.LintResult[];
}class Linter {
constructor(options?: Linter.LinterOptions);
// Core linting
verifyAndFix(code: string, config: Linter.Config, options?: Linter.VerifyOptions): Linter.FixReport;
verify(code: string, config: Linter.Config, options?: Linter.VerifyOptions): Linter.LintMessage[];
// Source code operations
getSourceCode(filename?: string): SourceCode;
// Rule management
defineRule(ruleId: string, ruleModule: Rule.RuleModule): void;
defineRules(rules: Record<string, Rule.RuleModule>): void;
getRules(): Map<string, Rule.RuleModule>;
// Parser management
defineParser(parserId: string, parserModule: Parser.ParserModule): void;
// Configuration
static readonly version: string;
}
namespace Linter {
// Linter configuration
type Config = FlatConfig.Config;
// Linter options
interface LinterOptions {
cwd?: string;
}
// Verification options
interface VerifyOptions {
filename?: string;
allowInlineConfig?: boolean;
reportUnusedDisableDirectives?: boolean | 'error' | 'warn' | 'off';
disableFixes?: boolean;
}
// Lint message
interface LintMessage {
ruleId: string | null;
severity: Severity;
message: string;
line: number;
column: number;
endLine?: number;
endColumn?: number;
fix?: Rule.RuleFix;
suggestions?: Suggestion[];
nodeType: string;
messageId?: string;
}
// Message severity
type Severity = 1 | 2; // 1 = warning, 2 = error
// Fix report
interface FixReport {
fixed: boolean;
messages: LintMessage[];
output: string;
}
// Suggestion
interface Suggestion {
desc: string;
messageId?: string;
fix: Rule.RuleFix;
}
}namespace Parser {
// Parser module interface
interface ParserModule {
parse(code: string, options?: ParserOptions): ParseResult;
parseForESLint?(code: string, options?: ParserOptions): ParseResult;
// Metadata
meta?: ParserMeta;
}
// Loose parser for configurations
interface LooseParserModule {
parse?: ParserModule['parse'];
parseForESLint?: ParserModule['parseForESLint'];
meta?: ParserMeta;
}
// Parse result
interface ParseResult {
ast: TSESTree.Program;
services?: ParserServices;
scopeManager?: Scope.ScopeManager;
visitorKeys?: VisitorKeys;
}
// Parser metadata
interface ParserMeta {
name?: string;
version?: string;
}
// Visitor keys for AST traversal
interface VisitorKeys {
[nodeType: string]: string[];
}
}namespace Processor {
// Processor module interface
interface ProcessorModule {
preprocess?(text: string, filename: string): Array<string | TextInfo>;
postprocess?(messages: Linter.LintMessage[][], filename: string): Linter.LintMessage[];
// Metadata
meta?: ProcessorMeta;
// Support for supportsAutofix
supportsAutofix?: boolean;
}
// Loose processor for configurations
interface LooseProcessorModule {
preprocess?: ProcessorModule['preprocess'];
postprocess?: ProcessorModule['postprocess'];
meta?: ProcessorMeta;
supportsAutofix?: boolean;
}
// Processor metadata
interface ProcessorMeta {
name?: string;
version?: string;
}
// Text information for processing
interface TextInfo {
text: string;
filename: string;
}
// Pre-process function type
type PreProcess = NonNullable<ProcessorModule['preprocess']>;
// Post-process function type
type PostProcess = NonNullable<ProcessorModule['postprocess']>;
}class SourceCode {
constructor(text: string, ast: TSESTree.Program);
constructor(config: SourceCode.Config);
// Text access
text: string;
ast: TSESTree.Program;
lines: string[];
hasBOM: boolean;
// Token access
getAllComments(): TSESTree.Comment[];
getComments(node: TSESTree.Node): { leading: TSESTree.Comment[]; trailing: TSESTree.Comment[] };
getCommentsAfter(nodeOrToken: TSESTree.Node | TSESTree.Token): TSESTree.Comment[];
getCommentsBefore(nodeOrToken: TSESTree.Node | TSESTree.Token): TSESTree.Comment[];
getCommentsInside(node: TSESTree.Node): TSESTree.Comment[];
// Token retrieval
getFirstToken(node: TSESTree.Node, options?: SourceCode.CursorWithSkipOptions): TSESTree.Token | null;
getFirstTokens(node: TSESTree.Node, options?: SourceCode.CursorWithCountOptions): TSESTree.Token[];
getLastToken(node: TSESTree.Node, options?: SourceCode.CursorWithSkipOptions): TSESTree.Token | null;
getLastTokens(node: TSESTree.Node, options?: SourceCode.CursorWithCountOptions): TSESTree.Token[];
getTokenAfter(nodeOrToken: TSESTree.Node | TSESTree.Token, options?: SourceCode.CursorWithSkipOptions): TSESTree.Token | null;
getTokenBefore(nodeOrToken: TSESTree.Node | TSESTree.Token, options?: SourceCode.CursorWithSkipOptions): TSESTree.Token | null;
getTokensAfter(nodeOrToken: TSESTree.Node | TSESTree.Token, options?: SourceCode.CursorWithCountOptions): TSESTree.Token[];
getTokensBefore(nodeOrToken: TSESTree.Node | TSESTree.Token, options?: SourceCode.CursorWithCountOptions): TSESTree.Token[];
getTokens(node: TSESTree.Node, beforeCount?: number, afterCount?: number): TSESTree.Token[];
getTokensByRangeStart(range: TSESTree.Range, options?: SourceCode.FilterPredicate): TSESTree.Token[];
// Text operations
getText(node?: TSESTree.Node | TSESTree.Token, beforeCount?: number, afterCount?: number): string;
getLines(): string[];
// Location utilities
getLocFromIndex(index: number): TSESTree.LineAndColumnData;
getIndexFromLoc(location: TSESTree.LineAndColumnData): number;
}
namespace SourceCode {
// Configuration interface
interface Config {
text: string;
ast: TSESTree.Program;
parserServices?: ParserServices;
scopeManager?: Scope.ScopeManager;
visitorKeys?: Parser.VisitorKeys;
}
// Token filtering options
interface CursorWithSkipOptions {
skip?: number;
includeComments?: boolean;
filter?: FilterPredicate;
}
interface CursorWithCountOptions {
count?: number;
includeComments?: boolean;
filter?: FilterPredicate;
}
// Filter predicate
type FilterPredicate = (tokenOrComment: TSESTree.Token | TSESTree.Comment) => boolean;
}namespace Scope {
// Main scope manager
interface ScopeManager {
scopes: Scope[];
globalScope: GlobalScope | null;
acquire(node: TSESTree.Node, inner?: boolean): Scope | null;
getDeclaredVariables(node: TSESTree.Node): Variable[];
}
// Base scope interface
interface Scope {
type: ScopeType;
isStrict: boolean;
upper: Scope | null;
childScopes: Scope[];
variableScope: Scope;
block: TSESTree.Node;
variables: Variable[];
references: Reference[];
// Scope methods
set: Map<string, Variable>;
through: Reference[];
}
// Scope types
type ScopeType =
| 'block'
| 'catch'
| 'class'
| 'for'
| 'function'
| 'function-expression-name'
| 'global'
| 'module'
| 'switch'
| 'with'
| 'TSDeclareFunction'
| 'TSModuleBlock';
// Global scope
interface GlobalScope extends Scope {
type: 'global';
childScopes: Scope[];
through: Reference[];
}
// Variable definition
interface Variable {
name: string;
identifiers: TSESTree.Identifier[];
references: Reference[];
defs: Definition[];
// Variable properties
scope: Scope;
eslintExplicitGlobal?: boolean;
eslintExplicitGlobalComments?: TSESTree.Comment[];
eslintImplicitGlobalSetting?: 'readonly' | 'writable';
writeable?: boolean;
}
// Variable reference
interface Reference {
identifier: TSESTree.Identifier;
from: Scope;
resolved: Variable | null;
writeExpr: TSESTree.Node | null;
init: boolean;
// Reference flags
isWrite(): boolean;
isRead(): boolean;
isWriteOnly(): boolean;
isReadOnly(): boolean;
isReadWrite(): boolean;
}
// Variable definition
interface Definition {
type: DefinitionType;
name: TSESTree.Identifier;
node: TSESTree.Node;
parent?: TSESTree.Node;
index?: number;
kind?: string;
}
// Definition types
type DefinitionType =
| 'CatchClause'
| 'ClassName'
| 'FunctionName'
| 'ImplicitGlobalVariable'
| 'ImportBinding'
| 'Parameter'
| 'TSEnumName'
| 'TSEnumMemberName'
| 'TSModuleName'
| 'Variable';
}import { ESLintUtils, TSESLint, TSESTree, AST_NODE_TYPES } from '@typescript-eslint/utils';
type Options = [{
allowShortCircuit?: boolean;
allowTernary?: boolean;
allowTaggedTemplates?: boolean;
}];
type MessageIds = 'unusedExpression' | 'unusedValue';
const createRule = ESLintUtils.RuleCreator(name => `https://example.com/${name}`);
export default createRule<Options, MessageIds>({
name: 'no-unused-expressions',
meta: {
type: 'suggestion',
docs: {
description: 'Disallow unused expressions',
recommended: false
},
messages: {
unusedExpression: 'Expected an assignment or function call and instead saw an expression.',
unusedValue: 'Expected an assignment or function call and instead saw an unused value.'
},
schema: [{
type: 'object',
properties: {
allowShortCircuit: { type: 'boolean' },
allowTernary: { type: 'boolean' },
allowTaggedTemplates: { type: 'boolean' }
},
additionalProperties: false
}]
},
defaultOptions: [{
allowShortCircuit: false,
allowTernary: false,
allowTaggedTemplates: false
}],
create(context: TSESLint.RuleContext<MessageIds, Options>, [options]) {
const sourceCode = context.sourceCode;
function isDirectiveComment(node: TSESTree.Node): boolean {
const comments = sourceCode.getCommentsBefore(node);
return comments.some(comment =>
comment.type === 'Line' && comment.value.trim().startsWith('eslint-')
);
}
function checkExpressionStatement(node: TSESTree.ExpressionStatement): void {
if (isDirectiveComment(node)) {
return;
}
const { expression } = node;
let messageId: MessageIds = 'unusedExpression';
// Check for allowed patterns
if (options.allowShortCircuit) {
if (expression.type === AST_NODE_TYPES.LogicalExpression &&
(expression.operator === '&&' || expression.operator === '||')) {
return;
}
}
if (options.allowTernary && expression.type === AST_NODE_TYPES.ConditionalExpression) {
return;
}
if (options.allowTaggedTemplates && expression.type === AST_NODE_TYPES.TaggedTemplateExpression) {
return;
}
// Determine appropriate message
if (expression.type === AST_NODE_TYPES.AssignmentExpression ||
expression.type === AST_NODE_TYPES.CallExpression ||
expression.type === AST_NODE_TYPES.NewExpression) {
return; // These are generally allowed
}
if (expression.type === AST_NODE_TYPES.UnaryExpression ||
expression.type === AST_NODE_TYPES.BinaryExpression) {
messageId = 'unusedValue';
}
context.report({
node: expression,
messageId
});
}
const ruleListener: TSESLint.RuleListener = {
ExpressionStatement: checkExpressionStatement,
Program() {
// Program-level setup
const scope = context.sourceCode.scopeManager?.acquire(context.sourceCode.ast, false);
if (scope) {
// Analyze global scope if needed
}
}
};
return ruleListener;
}
});// Rule tester (deprecated - use @typescript-eslint/rule-tester instead)
class RuleTester {
constructor(config?: RuleTester.Config);
run<MessageIds extends string, Options extends readonly unknown[]>(
name: string,
rule: TSESLint.RuleModule<MessageIds, Options>,
tests: {
valid: RuleTester.ValidTestCase<Options>[];
invalid: RuleTester.InvalidTestCase<MessageIds, Options>[];
}
): void;
}
namespace RuleTester {
interface Config {
parser?: string;
parserOptions?: ParserOptions;
globals?: Record<string, boolean>;
settings?: Record<string, unknown>;
}
interface ValidTestCase<Options extends readonly unknown[]> {
code: string;
options?: Options;
filename?: string;
parserOptions?: ParserOptions;
settings?: Record<string, unknown>;
globals?: Record<string, boolean>;
}
interface InvalidTestCase<MessageIds extends string, Options extends readonly unknown[]>
extends ValidTestCase<Options> {
errors: TestCaseError<MessageIds>[];
output?: string | null;
}
interface TestCaseError<MessageIds extends string> {
messageId: MessageIds;
data?: Record<string, unknown>;
type?: string;
line?: number;
column?: number;
endLine?: number;
endColumn?: number;
suggestions?: SuggestionOutput<MessageIds>[];
}
interface SuggestionOutput<MessageIds extends string> {
messageId: MessageIds;
output: string;
data?: Record<string, unknown>;
}
}Install with Tessl CLI
npx tessl i tessl/npm-typescript-eslint--utils