From 55e0b47d4f5ae9929cd8700e4524c3675776865a Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 23 Nov 2025 12:43:44 +0800 Subject: [PATCH 1/3] chore: refactor `createError` for easier use --- .../typescript-estree/src/check-modifiers.ts | 50 +++++++++---------- packages/typescript-estree/src/convert.ts | 14 +----- packages/typescript-estree/src/node-utils.ts | 28 +++++++++-- 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/packages/typescript-estree/src/check-modifiers.ts b/packages/typescript-estree/src/check-modifiers.ts index 423604a70e01..34494e8f8cd0 100644 --- a/packages/typescript-estree/src/check-modifiers.ts +++ b/packages/typescript-estree/src/check-modifiers.ts @@ -128,18 +128,13 @@ function nodeHasIllegalDecorators( ); } -function throwError(node: ts.Node, message: string): never { - const ast = node.getSourceFile(); - const start = node.getStart(ast); - const end = node.getEnd(); - - throw createError(message, ast, start, end); -} - export function checkModifiers(node: ts.Node): void { // typescript<5.0.0 if (nodeHasIllegalDecorators(node)) { - throwError(node.illegalDecorators[0], 'Decorators are not valid here.'); + throw createError( + node.illegalDecorators[0], + 'Decorators are not valid here.', + ); } for (const decorator of getDecorators( @@ -149,12 +144,12 @@ export function checkModifiers(node: ts.Node): void { // `checkGrammarModifiers` function in typescript if (!nodeCanBeDecorated(node as TSNode)) { if (ts.isMethodDeclaration(node) && !nodeIsPresent(node.body)) { - throwError( + throw createError( decorator, 'A decorator can only decorate a method implementation, not an overload.', ); } else { - throwError(decorator, 'Decorators are not valid here.'); + throw createError(decorator, 'Decorators are not valid here.'); } } } @@ -168,7 +163,7 @@ export function checkModifiers(node: ts.Node): void { node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature ) { - throwError( + throw createError( modifier, `'${ts.tokenToString( modifier.kind, @@ -181,7 +176,7 @@ export function checkModifiers(node: ts.Node): void { (modifier.kind !== SyntaxKind.StaticKeyword || !ts.isClassLike(node.parent)) ) { - throwError( + throw createError( modifier, `'${ts.tokenToString( modifier.kind, @@ -196,7 +191,7 @@ export function checkModifiers(node: ts.Node): void { modifier.kind !== SyntaxKind.ConstKeyword && node.kind === SyntaxKind.TypeParameter ) { - throwError( + throw createError( modifier, `'${ts.tokenToString( modifier.kind, @@ -214,7 +209,7 @@ export function checkModifiers(node: ts.Node): void { ts.isTypeAliasDeclaration(node.parent) )) ) { - throwError( + throw createError( modifier, `'${ts.tokenToString( modifier.kind, @@ -229,7 +224,7 @@ export function checkModifiers(node: ts.Node): void { node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.Parameter ) { - throwError( + throw createError( modifier, "'readonly' modifier can only appear on a property declaration or index signature.", ); @@ -240,7 +235,7 @@ export function checkModifiers(node: ts.Node): void { ts.isClassLike(node.parent) && !ts.isPropertyDeclaration(node) ) { - throwError( + throw createError( modifier, `'${ts.tokenToString( modifier.kind, @@ -254,7 +249,7 @@ export function checkModifiers(node: ts.Node): void { ) { const declarationKind = getDeclarationKind(node.declarationList); if (declarationKind === 'using' || declarationKind === 'await using') { - throwError( + throw createError( modifier, `'declare' modifier cannot appear on a '${declarationKind}' declaration.`, ); @@ -270,7 +265,7 @@ export function checkModifiers(node: ts.Node): void { node.kind !== SyntaxKind.GetAccessor && node.kind !== SyntaxKind.SetAccessor ) { - throwError( + throw createError( modifier, `'${ts.tokenToString( modifier.kind, @@ -286,7 +281,7 @@ export function checkModifiers(node: ts.Node): void { (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) ) { - throwError( + throw createError( modifier, `'${ts.tokenToString( modifier.kind, @@ -298,7 +293,7 @@ export function checkModifiers(node: ts.Node): void { modifier.kind === SyntaxKind.AccessorKeyword && node.kind !== SyntaxKind.PropertyDeclaration ) { - throwError( + throw createError( modifier, "'accessor' modifier can only appear on a property declaration.", ); @@ -312,7 +307,7 @@ export function checkModifiers(node: ts.Node): void { node.kind !== SyntaxKind.FunctionExpression && node.kind !== SyntaxKind.ArrowFunction ) { - throwError(modifier, "'async' modifier cannot be used here."); + throw createError(modifier, "'async' modifier cannot be used here."); } // `checkGrammarModifiers` function in `typescript` @@ -323,7 +318,7 @@ export function checkModifiers(node: ts.Node): void { modifier.kind === SyntaxKind.DeclareKeyword || modifier.kind === SyntaxKind.AsyncKeyword) ) { - throwError( + throw createError( modifier, `'${ts.tokenToString( modifier.kind, @@ -344,7 +339,10 @@ export function checkModifiers(node: ts.Node): void { anotherModifier.kind === SyntaxKind.ProtectedKeyword || anotherModifier.kind === SyntaxKind.PrivateKeyword) ) { - throwError(anotherModifier, `Accessibility modifier already seen.`); + throw createError( + anotherModifier, + `Accessibility modifier already seen.`, + ); } } } @@ -365,7 +363,7 @@ export function checkModifiers(node: ts.Node): void { if ( !(func?.kind === SyntaxKind.Constructor && nodeIsPresent(func.body)) ) { - throwError( + throw createError( modifier, 'A parameter property is only allowed in a constructor implementation.', ); @@ -379,7 +377,7 @@ export function checkModifiers(node: ts.Node): void { node.kind === SyntaxKind.MethodDeclaration && node.parent.kind === SyntaxKind.ObjectLiteralExpression ) { - throwError( + throw createError( modifier, `'${ts.tokenToString(modifier.kind)}' modifier cannot be used here.`, ); diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index d578c2b012cb..6b4c8acbf379 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -57,9 +57,9 @@ export function convertError( error: SemanticOrSyntacticError | ts.DiagnosticWithLocation, ): TSError { return createError( + error.start!, ('message' in error && error.message) || (error.messageText as string), error.file!, - error.start!, ); } @@ -166,18 +166,8 @@ export class Converter { if (this.options.allowInvalidAST) { return; } - let start; - let end; - if (Array.isArray(node)) { - [start, end] = node; - } else if (typeof node === 'number') { - start = end = node; - } else { - start = node.getStart(this.ast); - end = node.getEnd(); - } - throw createError(message, this.ast, start, end); + throw createError(node, message, this.ast); } /** diff --git a/packages/typescript-estree/src/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index b5990014e589..0f654a639f08 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -678,18 +678,36 @@ export class TSError extends Error { } } +export function createError(node: ts.Node, message: string): TSError; export function createError( + node: number | ts.Node | TSESTree.Range, message: string, - ast: ts.SourceFile, - startIndex: number, - endIndex: number = startIndex, + sourceFile: ts.SourceFile, +): TSError; +export function createError( + node: number | TSESTree.Range | ts.Node, + message: string, + sourceFile?: ts.SourceFile, ): TSError { + let startIndex; + let endIndex; + if (Array.isArray(node)) { + [startIndex, endIndex] = node; + } else if (typeof node === 'number') { + startIndex = endIndex = node; + } else { + sourceFile ??= node.getSourceFile(); + startIndex = node.getStart(sourceFile); + endIndex = node.getEnd(); + } + const [start, end] = [startIndex, endIndex].map(offset => { const { character: column, line } = - ast.getLineAndCharacterOfPosition(offset); + sourceFile!.getLineAndCharacterOfPosition(offset); return { column, line: line + 1, offset }; }); - return new TSError(message, ast.fileName, { end, start }); + + return new TSError(message, sourceFile!.fileName, { end, start }); } export function nodeHasTokens(n: ts.Node, ast: ts.SourceFile): boolean { From 0976749be92e85d8fa4fc58593082b1099db82ad Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 23 Nov 2025 12:51:24 +0800 Subject: [PATCH 2/3] reorder union type --- packages/typescript-estree/src/node-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-estree/src/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index 0f654a639f08..476d2b05ff38 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -685,7 +685,7 @@ export function createError( sourceFile: ts.SourceFile, ): TSError; export function createError( - node: number | TSESTree.Range | ts.Node, + node: number | ts.Node | TSESTree.Range, message: string, sourceFile?: ts.SourceFile, ): TSError { From 763e7fb3ff58140c2fc49fdba427bc1bbdf65155 Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 23 Nov 2025 13:19:40 +0800 Subject: [PATCH 3/3] Linting --- packages/typescript-estree/src/node-utils.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/typescript-estree/src/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index 476d2b05ff38..fb0349f7fbb3 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -701,13 +701,17 @@ export function createError( endIndex = node.getEnd(); } + if (!sourceFile) { + throw new Error('`sourceFile` is required.'); + } + const [start, end] = [startIndex, endIndex].map(offset => { const { character: column, line } = - sourceFile!.getLineAndCharacterOfPosition(offset); + sourceFile.getLineAndCharacterOfPosition(offset); return { column, line: line + 1, offset }; }); - return new TSError(message, sourceFile!.fileName, { end, start }); + return new TSError(message, sourceFile.fileName, { end, start }); } export function nodeHasTokens(n: ts.Node, ast: ts.SourceFile): boolean {