🌐 AI搜索 & 代理 主页
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions packages/typescript-estree/src/check-syntax-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as ts from 'typescript';

import type { TSNode } from './ts-estree';

import { checkModifiers } from './check-modifiers';
import { isValidAssignmentTarget, createError } from './node-utils';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also define a

const SyntaxKind = ts.SyntaxKind;

here, https://github.com/Lonercode/typescript-eslint/blob/ee1ada9db5229902f70faa19dc27950dc457cc7d/packages/typescript-estree/src/convert.ts#L42

so other checks can be copied easier to here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ehh should we? We don't generally do namespace property shorthands like that in the typescript-eslint monorepo. IMO it just adds a bit of clutter.

Copy link
Contributor

@fisker fisker Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll add lots of cases in switch, better align with convert.ts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least keep it before it's done?

const SyntaxKind = ts.SyntaxKind;

export function checkSyntaxError(tsNode: ts.Node): void {
checkModifiers(tsNode);

const node = tsNode as TSNode;

switch (node.kind) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Refactor] I don't think this is a positive change for the logic? Converter functions generally directly call the checkFor* logic they need. For example, for-in and for-of statements directly call checkForStatementDeclaration. Now this new checkSyntaxError function has to again switch on node.kind. That's extra code structure and work.

@fisker please correct me if I'm wrong here, but I think it'd still be in the spirit of the issue to have checkForStatementDeclaration be a standalone function separated out from convert.ts, and still called directly?

As in, the diffs to convert.ts would look more like:

+ import { checkForStatementDeclaration } from "./checks/...";
...
      case SyntaxKind.ForInStatement:
-       this.#checkForStatementDeclaration(node.initializer, node.kind);
+       checkForStatementDeclaration(node.initializer, node.kind);
        return this.createNode<TSESTree.ForInStatement>(node, {
...
-  #checkForStatementDeclaration(...) { ... }

Copy link
Contributor

@fisker fisker Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take this

if (
node.caseBlock.clauses.filter(
switchCase => switchCase.kind === SyntaxKind.DefaultClause,
).length > 1
) {
this.#throwError(
node,
"A 'default' clause cannot appear more than once in a 'switch' statement.",
);
}
as example, normally the check just few lines (there are even simpler checks, eg: 1). Not every node need such long code like SyntaxKind.ForInStatement.

If we call from covert.ts directly after the case ..., we'll have lot of lines to add. and forcing many functions to import/export.

I think a single call like checkModifiers() used did and duplicate the big switch(){} in check-syntax-error.ts is much easier to maintain.

Most kind of node check will inline, instead of separate function.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoshuaKGoldberg @fisker Would it prove more efficient to separate out checkForStatmentDeclaration? I thought the point of the issue was to extract those checks?

case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement: {
checkForStatementDeclaration(node);
break;
}
default: {
break;
}
Comment on lines +21 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this because ESLint complaint? Will this work?

Suggested change
default: {
break;
}
// No default

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's valid, exiting with the default, for now at least.

}
}

function checkForStatementDeclaration(
node: ts.ForInStatement | ts.ForOfStatement,
): void {
const { initializer, kind } = node;
const loop = kind === SyntaxKind.ForInStatement ? 'for...in' : 'for...of';
if (ts.isVariableDeclarationList(initializer)) {
if (initializer.declarations.length !== 1) {
throw createError(
initializer,
`Only a single variable declaration is allowed in a '${loop}' statement.`,
);
}
const declaration = initializer.declarations[0];
if (declaration.initializer) {
throw createError(
declaration,
`The variable declaration of a '${loop}' statement cannot have an initializer.`,
);
} else if (declaration.type) {
throw createError(
declaration,
`The variable declaration of a '${loop}' statement cannot have a type annotation.`,
);
}
if (
kind === SyntaxKind.ForInStatement &&
initializer.flags & ts.NodeFlags.Using
) {
throw createError(
initializer,
"The left-hand side of a 'for...in' statement cannot be a 'using' declaration.",
);
}
} else if (
!isValidAssignmentTarget(initializer) &&
initializer.kind !== SyntaxKind.ObjectLiteralExpression &&
initializer.kind !== SyntaxKind.ArrayLiteralExpression
) {
throw createError(
initializer,
`The left-hand side of a '${loop}' statement must be a variable or a property access.`,
);
}
}
56 changes: 4 additions & 52 deletions packages/typescript-estree/src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
import type { SemanticOrSyntacticError } from './semantic-or-syntactic-errors';
import type { TSESTree, TSESTreeToTSNode, TSNode } from './ts-estree';

import { checkModifiers } from './check-modifiers';
import { checkSyntaxError } from './check-syntax-errors';
import { getDecorators, getModifiers } from './getModifiers';
import {
canContainDirective,
Expand Down Expand Up @@ -105,58 +105,12 @@ export class Converter {
this.options = { ...options };
}

#checkForStatementDeclaration(
initializer: ts.ForInitializer,
kind: ts.SyntaxKind.ForInStatement | ts.SyntaxKind.ForOfStatement,
): void {
const loop =
kind === ts.SyntaxKind.ForInStatement ? 'for...in' : 'for...of';
if (ts.isVariableDeclarationList(initializer)) {
if (initializer.declarations.length !== 1) {
this.#throwError(
initializer,
`Only a single variable declaration is allowed in a '${loop}' statement.`,
);
}
const declaration = initializer.declarations[0];
if (declaration.initializer) {
this.#throwError(
declaration,
`The variable declaration of a '${loop}' statement cannot have an initializer.`,
);
} else if (declaration.type) {
this.#throwError(
declaration,
`The variable declaration of a '${loop}' statement cannot have a type annotation.`,
);
}
if (
kind === ts.SyntaxKind.ForInStatement &&
initializer.flags & ts.NodeFlags.Using
) {
this.#throwError(
initializer,
"The left-hand side of a 'for...in' statement cannot be a 'using' declaration.",
);
}
} else if (
!isValidAssignmentTarget(initializer) &&
initializer.kind !== ts.SyntaxKind.ObjectLiteralExpression &&
initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression
) {
this.#throwError(
initializer,
`The left-hand side of a '${loop}' statement must be a variable or a property access.`,
);
}
}

#checkModifiers(node: ts.Node): void {
#checkSyntaxError(node: ts.Node): void {
if (this.options.allowInvalidAST) {
return;
}

checkModifiers(node);
checkSyntaxError(node);
}

#throwError(
Expand Down Expand Up @@ -528,7 +482,7 @@ export class Converter {
return null;
}

this.#checkModifiers(node);
this.#checkSyntaxError(node);

const pattern = this.allowPattern;
if (allowPattern != null) {
Expand Down Expand Up @@ -896,7 +850,6 @@ export class Converter {
});

case SyntaxKind.ForInStatement:
this.#checkForStatementDeclaration(node.initializer, node.kind);
return this.createNode<TSESTree.ForInStatement>(node, {
type: AST_NODE_TYPES.ForInStatement,
body: this.convertChild(node.statement),
Expand All @@ -905,7 +858,6 @@ export class Converter {
});

case SyntaxKind.ForOfStatement: {
this.#checkForStatementDeclaration(node.initializer, node.kind);
return this.createNode<TSESTree.ForOfStatement>(node, {
type: AST_NODE_TYPES.ForOfStatement,
await: Boolean(
Expand Down