Utilities for working with TypeScript + ESLint together
—
The ASTUtils namespace provides comprehensive utilities for Abstract Syntax Tree manipulation and analysis in TypeScript ESLint rules.
import { ASTUtils } from '@typescript-eslint/utils';// Generic node type checker
function isNodeOfType<NodeType extends AST_NODE_TYPES>(
nodeType: NodeType
): (node: TSESTree.Node | null | undefined) => node is Extract<TSESTree.Node, { type: NodeType }>;
// Multiple node types checker
function isNodeOfTypes<NodeTypes extends readonly AST_NODE_TYPES[]>(
nodeTypes: NodeTypes
): (node: TSESTree.Node | null | undefined) => node is Extract<TSESTree.Node, { type: NodeTypes[number] }>;
// Usage examples
const isFunctionDeclaration = ASTUtils.isNodeOfType(AST_NODE_TYPES.FunctionDeclaration);
const isVariableOrFunction = ASTUtils.isNodeOfTypes([
AST_NODE_TYPES.VariableDeclaration,
AST_NODE_TYPES.FunctionDeclaration
]);
if (isFunctionDeclaration(node)) {
// node is typed as TSESTree.FunctionDeclaration
console.log(node.id?.name);
}// Node type with additional conditions
function isNodeOfTypeWithConditions<
NodeType extends AST_NODE_TYPES,
ExtractedNode extends Extract<TSESTree.Node, { type: NodeType }>,
Conditions extends Partial<ExtractedNode>
>(
nodeType: NodeType,
conditions: Conditions
): (node: TSESTree.Node | null | undefined) => node is ExtractedNode & Conditions;
// Usage example
const isAsyncFunction = ASTUtils.isNodeOfTypeWithConditions(
AST_NODE_TYPES.FunctionDeclaration,
{ async: true }
);
if (isAsyncFunction(node)) {
// node is TSESTree.FunctionDeclaration with async: true
}// Check for any function node type
function isFunction(node: TSESTree.Node | null | undefined): node is TSESTree.Function;
// Check for TypeScript function type
function isFunctionType(node: TSESTree.Node | null | undefined): node is TSESTree.TSFunctionType;
// Check for function or function type
function isFunctionOrFunctionType(
node: TSESTree.Node | null | undefined
): node is TSESTree.Function | TSESTree.TSFunctionType;
// Specific TypeScript function types
function isTSFunctionType(node: TSESTree.Node | null | undefined): node is TSESTree.TSFunctionType;
function isTSConstructorType(node: TSESTree.Node | null | undefined): node is TSESTree.TSConstructorType;
// Usage examples
if (ASTUtils.isFunction(node)) {
// Handle arrow functions, function declarations, function expressions
const params = node.params;
const body = node.body;
}
if (ASTUtils.isFunctionType(node)) {
// Handle TypeScript function type annotations
const returnType = node.returnType;
}// Optional chaining call expressions
function isOptionalCallExpression(node: TSESTree.Node | null | undefined): node is TSESTree.CallExpression & { optional: true };
// Logical OR expressions
function isLogicalOrOperator(node: TSESTree.Node | null | undefined): node is TSESTree.LogicalExpression & { operator: '||' };
// Type assertions
function isTypeAssertion(node: TSESTree.Node | null | undefined): node is TSESTree.TSTypeAssertion | TSESTree.TSAsExpression;
// Await expressions
function isAwaitExpression(node: TSESTree.Node | null | undefined): node is TSESTree.AwaitExpression;
// Usage examples
if (ASTUtils.isOptionalCallExpression(node)) {
// Handle obj?.method() calls
const callee = node.callee; // has optional property
}
if (ASTUtils.isTypeAssertion(node)) {
// Handle both <Type>expr and expr as Type
const expression = node.expression;
}// Variable declarators
function isVariableDeclarator(node: TSESTree.Node | null | undefined): node is TSESTree.VariableDeclarator;
// Loop statements
function isLoop(node: TSESTree.Node | null | undefined): node is TSESTree.DoWhileStatement | TSESTree.ForStatement | TSESTree.ForInStatement | TSESTree.ForOfStatement | TSESTree.WhileStatement;
// Identifiers
function isIdentifier(node: TSESTree.Node | null | undefined): node is TSESTree.Identifier;
// Usage examples
if (ASTUtils.isLoop(node)) {
// Handle all loop types uniformly
const body = node.body;
}
if (ASTUtils.isVariableDeclarator(node)) {
// Handle variable declarations
const id = node.id;
const init = node.init;
}// Class or type element nodes
function isClassOrTypeElement(node: TSESTree.Node | null | undefined): node is TSESTree.ClassElement | TSESTree.TypeElement;
// Constructor methods
function isConstructor(node: TSESTree.Node | null | undefined): node is TSESTree.MethodDefinition & { kind: 'constructor' };
// Setter methods
function isSetter(node: TSESTree.Node | undefined): node is TSESTree.MethodDefinition & { kind: 'set' } | TSESTree.PropertyDefinition & { kind: 'set' };
// Usage examples
if (ASTUtils.isConstructor(node)) {
// Handle constructor methods specifically
const params = node.value.params;
}
if (ASTUtils.isSetter(node)) {
// Handle setter methods
const key = node.key;
}// Optional chaining punctuator (?.)
function isOptionalChainPunctuator(token: TSESTree.Token | null | undefined): token is TSESTree.PunctuatorToken & { value: '?.' };
function isNotOptionalChainPunctuator(token: TSESTree.Token | null | undefined): token is Exclude<TSESTree.Token, TSESTree.PunctuatorToken & { value: '?.' }>;
// Non-null assertion punctuator (!)
function isNonNullAssertionPunctuator(token: TSESTree.Token | null | undefined): token is TSESTree.PunctuatorToken & { value: '!' };
function isNotNonNullAssertionPunctuator(token: TSESTree.Token | null | undefined): token is Exclude<TSESTree.Token, TSESTree.PunctuatorToken & { value: '!' }>;
// Common punctuators
function isArrowToken(token: TSESTree.Token | null | undefined): token is TSESTree.PunctuatorToken & { value: '=>' };
function isCommaToken(token: TSESTree.Token | null | undefined): token is TSESTree.PunctuatorToken & { value: ',' };
function isSemicolonToken(token: TSESTree.Token | null | undefined): token is TSESTree.PunctuatorToken & { value: ';' };
function isColonToken(token: TSESTree.Token | null | undefined): token is TSESTree.PunctuatorToken & { value: ':' };
// Usage examples
const tokens = sourceCode.getTokens(node);
const arrowToken = tokens.find(ASTUtils.isArrowToken);
if (arrowToken) {
// Handle arrow function tokens
}// Opening tokens
function isOpeningBraceToken(token: TSESTree.Token | null | undefined): token is TSESTree.PunctuatorToken & { value: '{' };
function isOpeningBracketToken(token: TSESTree.Token | null | undefined): token is TSESTree.PunctuatorToken & { value: '[' };
function isOpeningParenToken(token: TSESTree.Token | null | undefined): token is TSESTree.PunctuatorToken & { value: '(' };
// Closing tokens
function isClosingBraceToken(token: TSESTree.Token | null | undefined): token is TSESTree.PunctuatorToken & { value: '}' };
function isClosingBracketToken(token: TSESTree.Token | null | undefined): token is TSESTree.PunctuatorToken & { value: ']' };
function isClosingParenToken(token: TSESTree.Token | null | undefined): token is TSESTree.PunctuatorToken & { value: ')' };
// Negated versions available for all punctuators
function isNotOpeningBraceToken(token: TSESTree.Token | null | undefined): boolean;
function isNotClosingParenToken(token: TSESTree.Token | null | undefined): boolean;
// ... (similar pattern for all punctuators)
// Usage examples
const openBrace = sourceCode.getTokenAfter(node, ASTUtils.isOpeningBraceToken);
const closeBrace = sourceCode.getTokenAfter(node, ASTUtils.isClosingBraceToken);// Keyword token predicates
function isAwaitKeyword(token: TSESTree.Token | null | undefined): token is TSESTree.KeywordToken & { value: 'await' };
function isTypeKeyword(token: TSESTree.Token | null | undefined): token is TSESTree.KeywordToken & { value: 'type' };
function isImportKeyword(token: TSESTree.Token | null | undefined): token is TSESTree.KeywordToken & { value: 'import' };
// Usage examples
const awaitToken = sourceCode.getFirstToken(node, ASTUtils.isAwaitKeyword);
if (awaitToken) {
// Handle await keyword positioning
}// Comment token predicates
function isCommentToken(token: TSESTree.Token | null | undefined): token is TSESTree.Comment;
function isNotCommentToken(token: TSESTree.Token | null | undefined): token is Exclude<TSESTree.Token, TSESTree.Comment>;
// Usage examples
const nonCommentTokens = sourceCode.getTokens(node).filter(ASTUtils.isNotCommentToken);// Token type with conditions
function isTokenOfTypeWithConditions<
TokenType extends AST_TOKEN_TYPES,
ExtractedToken extends Extract<TSESTree.Token, { type: TokenType }>,
Conditions extends Partial<ExtractedToken>
>(
tokenType: TokenType,
conditions: Conditions
): (token: TSESTree.Token | null | undefined) => token is ExtractedToken & Conditions;
// Negated token conditions
function isNotTokenOfTypeWithConditions<
TokenType extends AST_TOKEN_TYPES,
ExtractedToken extends Extract<TSESTree.Token, { type: TokenType }>,
Conditions extends Partial<ExtractedToken>
>(
tokenType: TokenType,
conditions: Conditions
): (token: TSESTree.Token | null | undefined) => token is Exclude<TSESTree.Token, ExtractedToken & Conditions>;
// Usage examples
const isSpecificPunctuator = ASTUtils.isTokenOfTypeWithConditions(
AST_TOKEN_TYPES.Punctuator,
{ value: '=>' as const }
);
const isNotArrowFunction = ASTUtils.isNotTokenOfTypeWithConditions(
AST_TOKEN_TYPES.Punctuator,
{ value: '=>' as const }
);// Check if tokens are on the same line
function isTokenOnSameLine(
left: TSESTree.Node | TSESTree.Token,
right: TSESTree.Node | TSESTree.Token
): boolean;
// Line break matcher regex
const LINEBREAK_MATCHER: RegExp; // /\r\n|[\r\n\u2028\u2029]/
// Usage examples
if (ASTUtils.isTokenOnSameLine(node, nextToken)) {
// Tokens are on the same line
}
const hasLineBreak = ASTUtils.LINEBREAK_MATCHER.test(sourceCode.getText(node));// Get function head location for reporting
function getFunctionHeadLocation(
node: TSESTree.Function,
sourceCode: TSESLint.SourceCode
): TSESTree.SourceLocation;
// Get function name with kind description
function getFunctionNameWithKind(
node: TSESTree.Function,
sourceCode?: TSESLint.SourceCode
): string;
// Usage examples
const location = ASTUtils.getFunctionHeadLocation(node, context.getSourceCode());
context.report({
loc: location,
messageId: 'error'
});
const functionDescription = ASTUtils.getFunctionNameWithKind(node);
// Returns strings like "function 'myFunc'", "arrow function", "method 'myMethod'"// Get property name from property-like nodes
function getPropertyName(
node: TSESTree.Property | TSESTree.MethodDefinition | TSESTree.PropertyDefinition,
initialScope?: TSESLint.Scope.Scope
): string | null;
// Usage examples
const propName = ASTUtils.getPropertyName(node);
if (propName) {
// Handle known property names
}// Get static value if determinable
function getStaticValue(
node: TSESTree.Node,
initialScope?: TSESLint.Scope.Scope
): { value: unknown } | null;
// Get string value if constant
function getStringIfConstant(
node: TSESTree.Node,
initialScope?: TSESLint.Scope.Scope
): string | null;
// Usage examples
const staticValue = ASTUtils.getStaticValue(node);
if (staticValue) {
console.log('Static value:', staticValue.value);
}
const stringValue = ASTUtils.getStringIfConstant(node);
if (stringValue) {
// Handle constant string values
}// Check if node has side effects
function hasSideEffect(
node: TSESTree.Node,
sourceCode: TSESLint.SourceCode,
options?: { considerGetters?: boolean; considerImplicitTypeConversion?: boolean }
): boolean;
// Usage examples
if (!ASTUtils.hasSideEffect(node, context.getSourceCode())) {
// Safe to remove or reorder this node
}// Check if node is parenthesized (overloaded)
function isParenthesized(node: TSESTree.Node, sourceCode: TSESLint.SourceCode): boolean;
function isParenthesized(times: number, node: TSESTree.Node, sourceCode: TSESLint.SourceCode): boolean;
// Usage examples
if (ASTUtils.isParenthesized(node, sourceCode)) {
// Node has parentheses around it
}
if (ASTUtils.isParenthesized(2, node, sourceCode)) {
// Node has at least 2 levels of parentheses: ((node))
}class PatternMatcher {
constructor(pattern: RegExp, options?: { escaped?: boolean });
// Execute pattern matching
execAll(str: string): IterableIterator<RegExpExecArray>;
// Test if pattern matches
test(str: string): boolean;
// Replace matches in string
[Symbol.replace](str: string, replacer: string | ((match: string, ...args: any[]) => string)): string;
}
// Usage examples
const matcher = new ASTUtils.PatternMatcher(/function\s+(\w+)/g);
for (const match of matcher.execAll(sourceCode.getText())) {
console.log('Found function:', match[1]);
}class ReferenceTracker {
constructor(globalScope: TSESLint.Scope.Scope, options?: {
mode?: 'strict' | 'legacy';
globalObjectNames?: readonly string[];
});
// Iterate over global references
iterateGlobalReferences<T>(
traceMap: ReferenceTracker.TraceMap<T>
): IterableIterator<ReferenceTracker.FoundReference<T>>;
// Iterate over CommonJS references
iterateCjsReferences<T>(
traceMap: ReferenceTracker.TraceMap<T>
): IterableIterator<ReferenceTracker.FoundReference<T>>;
// Iterate over ES module references
iterateEsModuleReferences<T>(
traceMap: ReferenceTracker.TraceMap<T>
): IterableIterator<ReferenceTracker.FoundReference<T>>;
}
// Usage examples
const tracker = new ASTUtils.ReferenceTracker(context.getScope());
const traceMap = {
React: {
createElement: { [ASTUtils.ReferenceTracker.READ]: true }
}
};
for (const { node, path } of tracker.iterateGlobalReferences(traceMap)) {
// Handle React.createElement references
}// Find variable by name or node
function findVariable(
initialScope: TSESLint.Scope.Scope,
nameOrNode: string | TSESTree.Identifier
): TSESLint.Scope.Variable | null;
// Get innermost scope containing node
function getInnermostScope(
initialScope: TSESLint.Scope.Scope,
node: TSESTree.Node
): TSESLint.Scope.Scope;
// Usage examples
const variable = ASTUtils.findVariable(context.getScope(), 'myVar');
if (variable) {
// Check variable references and definitions
const refs = variable.references;
const defs = variable.defs;
}
const innerScope = ASTUtils.getInnermostScope(context.getScope(), node);
// Get scope that directly contains the nodeimport { ESLintUtils, ASTUtils, TSESTree } from '@typescript-eslint/utils';
const createRule = ESLintUtils.RuleCreator(name => `https://example.com/${name}`);
export default createRule({
name: 'comprehensive-ast-example',
meta: {
type: 'suggestion',
docs: { description: 'Comprehensive AST utilities example' },
messages: {
optionalCall: 'Consider using optional chaining',
parenthesized: 'Unnecessary parentheses detected',
sideEffect: 'Expression has side effects'
},
schema: []
},
defaultOptions: [],
create(context) {
const sourceCode = context.getSourceCode();
return {
CallExpression(node) {
// Check for optional calls
if (ASTUtils.isOptionalCallExpression(node)) {
context.report({
node,
messageId: 'optionalCall'
});
}
// Check if parenthesized
if (ASTUtils.isParenthesized(node, sourceCode)) {
context.report({
node,
messageId: 'parenthesized'
});
}
// Check for side effects
if (ASTUtils.hasSideEffect(node, sourceCode)) {
context.report({
node,
messageId: 'sideEffect'
});
}
},
'Program:exit'() {
// Use reference tracker
const tracker = new ASTUtils.ReferenceTracker(context.getScope());
const traceMap = {
console: {
log: { [ASTUtils.ReferenceTracker.READ]: true }
}
};
for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
// Handle console.log references
}
}
};
}
});Install with Tessl CLI
npx tessl i tessl/npm-typescript-eslint--utils