UNPKG

@typescript-eslint/typescript-estree

Version:

A parser that converts TypeScript source code into an ESTree compatible form

441 lines (440 loc) • 23.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.checkSyntaxError = checkSyntaxError; const ts = __importStar(require("typescript")); const check_modifiers_1 = require("./check-modifiers"); const node_utils_1 = require("./node-utils"); const SyntaxKind = ts.SyntaxKind; function checkSyntaxError(tsNode, parent, allowPattern) { (0, check_modifiers_1.checkModifiers)(tsNode); const node = tsNode; switch (node.kind) { case SyntaxKind.SwitchStatement: if (node.caseBlock.clauses.filter(switchCase => switchCase.kind === SyntaxKind.DefaultClause).length > 1) { throw (0, node_utils_1.createError)(node, "A 'default' clause cannot appear more than once in a 'switch' statement."); } break; case SyntaxKind.ThrowStatement: if (node.expression.end === node.expression.pos) { throw (0, node_utils_1.createError)(node, 'A throw statement must throw an expression.'); } break; case SyntaxKind.CatchClause: if (node.variableDeclaration?.initializer) { throw (0, node_utils_1.createError)(node.variableDeclaration.initializer, 'Catch clause variable cannot have an initializer.'); } break; case SyntaxKind.FunctionDeclaration: { const isDeclare = (0, node_utils_1.hasModifier)(SyntaxKind.DeclareKeyword, node); const isAsync = (0, node_utils_1.hasModifier)(SyntaxKind.AsyncKeyword, node); const isGenerator = !!node.asteriskToken; if (isDeclare) { if (node.body) { throw (0, node_utils_1.createError)(node, 'An implementation cannot be declared in ambient contexts.'); } else if (isAsync) { throw (0, node_utils_1.createError)(node, "'async' modifier cannot be used in an ambient context."); } else if (isGenerator) { throw (0, node_utils_1.createError)(node, 'Generators are not allowed in an ambient context.'); } } else if (!node.body && isGenerator) { throw (0, node_utils_1.createError)(node, 'A function signature cannot be declared as a generator.'); } break; } case SyntaxKind.VariableDeclaration: { const hasExclamationToken = !!node.exclamationToken; if (hasExclamationToken) { if (node.initializer) { throw (0, node_utils_1.createError)(node, 'Declarations with initializers cannot also have definite assignment assertions.'); } else if (node.name.kind !== SyntaxKind.Identifier || !node.type) { throw (0, node_utils_1.createError)(node, 'Declarations with definite assignment assertions must also have type annotations.'); } } if (node.parent.kind === SyntaxKind.VariableDeclarationList) { const variableDeclarationList = node.parent; const kind = (0, node_utils_1.getDeclarationKind)(variableDeclarationList); if (kind === 'using' || kind === 'await using') { if (variableDeclarationList.parent.kind === SyntaxKind.ForInStatement) { throw (0, node_utils_1.createError)(variableDeclarationList, `The left-hand side of a 'for...in' statement cannot be a '${kind}' declaration.`); } if (variableDeclarationList.parent.kind === SyntaxKind.ForStatement || variableDeclarationList.parent.kind === SyntaxKind.VariableStatement) { if (!node.initializer) { throw (0, node_utils_1.createError)(node, `'${kind}' declarations must be initialized.`); } if (node.name.kind !== SyntaxKind.Identifier) { throw (0, node_utils_1.createError)(node.name, `'${kind}' declarations may not have binding patterns.`); } } } if (variableDeclarationList.parent.kind === SyntaxKind.VariableStatement) { const variableStatement = variableDeclarationList.parent; const hasDeclareKeyword = (0, node_utils_1.hasModifier)(SyntaxKind.DeclareKeyword, variableStatement); // Definite assignment only allowed for non-declare let and var if ((hasDeclareKeyword || ['await using', 'const', 'using'].includes(kind)) && hasExclamationToken) { throw (0, node_utils_1.createError)(node, `A definite assignment assertion '!' is not permitted in this context.`); } if (hasDeclareKeyword && node.initializer && (['let', 'var'].includes(kind) || node.type)) { throw (0, node_utils_1.createError)(node, `Initializers are not permitted in ambient contexts.`); } // Theoretically, only certain initializers are allowed for declare const, // (TS1254: A 'const' initializer in an ambient context must be a string // or numeric literal or literal enum reference.) but we just allow // all expressions // Note! No-declare does not mean the variable is not ambient, because // it can be further nested in other declare contexts. Therefore we cannot // check for const initializers. } } break; } case SyntaxKind.VariableStatement: { const declarations = node.declarationList.declarations; if (!declarations.length) { throw (0, node_utils_1.createError)(node, 'A variable declaration list must have at least one variable declarator.'); } break; } case SyntaxKind.PropertyAssignment: { // eslint-disable-next-line @typescript-eslint/no-deprecated const { exclamationToken, questionToken } = node; if (questionToken) { throw (0, node_utils_1.createError)(questionToken, 'A property assignment cannot have a question token.'); } if (exclamationToken) { throw (0, node_utils_1.createError)(exclamationToken, 'A property assignment cannot have an exclamation token.'); } break; } case SyntaxKind.ShorthandPropertyAssignment: { // eslint-disable-next-line @typescript-eslint/no-deprecated const { exclamationToken, modifiers, questionToken } = node; if (modifiers) { throw (0, node_utils_1.createError)(modifiers[0], 'A shorthand property assignment cannot have modifiers.'); } if (questionToken) { throw (0, node_utils_1.createError)(questionToken, 'A shorthand property assignment cannot have a question token.'); } if (exclamationToken) { throw (0, node_utils_1.createError)(exclamationToken, 'A shorthand property assignment cannot have an exclamation token.'); } break; } case SyntaxKind.PropertyDeclaration: { const isAbstract = (0, node_utils_1.hasModifier)(SyntaxKind.AbstractKeyword, node); if (isAbstract && node.initializer) { throw (0, node_utils_1.createError)(node.initializer, `Abstract property cannot have an initializer.`); } if (node.name.kind === SyntaxKind.StringLiteral && node.name.text === 'constructor') { throw (0, node_utils_1.createError)(node.name, "Classes may not have a field named 'constructor'."); } break; } case SyntaxKind.TaggedTemplateExpression: if (node.tag.flags & ts.NodeFlags.OptionalChain) { throw (0, node_utils_1.createError)(node, 'Tagged template expressions are not permitted in an optional chain.'); } break; case SyntaxKind.BinaryExpression: if (node.operatorToken.kind !== SyntaxKind.InKeyword && node.left.kind === SyntaxKind.PrivateIdentifier) { throw (0, node_utils_1.createError)(node.left, "Private identifiers cannot appear on the right-hand-side of an 'in' expression."); } else if (node.right.kind === SyntaxKind.PrivateIdentifier) { throw (0, node_utils_1.createError)(node.right, "Private identifiers are only allowed on the left-hand-side of an 'in' expression."); } break; case SyntaxKind.MappedType: if (node.members && node.members.length > 0) { throw (0, node_utils_1.createError)(node.members[0], 'A mapped type may not declare properties or methods.'); } break; case SyntaxKind.PropertySignature: { // eslint-disable-next-line @typescript-eslint/no-deprecated const { initializer } = node; if (initializer) { throw (0, node_utils_1.createError)(initializer, 'A property signature cannot have an initializer.'); } break; } case SyntaxKind.FunctionType: { // eslint-disable-next-line @typescript-eslint/no-deprecated const { modifiers } = node; if (modifiers) { throw (0, node_utils_1.createError)(modifiers[0], 'A function type cannot have modifiers.'); } break; } case SyntaxKind.EnumMember: { const computed = node.name.kind === ts.SyntaxKind.ComputedPropertyName; if (computed) { throw (0, node_utils_1.createError)(node.name, 'Computed property names are not allowed in enums.'); } if (node.name.kind === SyntaxKind.NumericLiteral || node.name.kind === SyntaxKind.BigIntLiteral) { throw (0, node_utils_1.createError)(node.name, 'An enum member cannot have a numeric name.'); } break; } case SyntaxKind.ExternalModuleReference: if (node.expression.kind !== SyntaxKind.StringLiteral) { throw (0, node_utils_1.createError)(node.expression, 'String literal expected.'); } break; case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.PostfixUnaryExpression: { const operator = (0, node_utils_1.getTextForTokenKind)(node.operator); /** * ESTree uses UpdateExpression for ++/-- */ if ((operator === '++' || operator === '--') && !(0, node_utils_1.isValidAssignmentTarget)(node.operand)) { throw (0, node_utils_1.createError)(node.operand, 'Invalid left-hand side expression in unary operation'); } break; } case SyntaxKind.ImportDeclaration: { const { importClause } = node; if ( // TODO swap to `phaseModifier` once we add support for `import defer` // https://github.com/estree/estree/issues/328 // eslint-disable-next-line @typescript-eslint/no-deprecated importClause?.isTypeOnly && importClause.name && importClause.namedBindings) { throw (0, node_utils_1.createError)(importClause, 'A type-only import can specify a default import or named bindings, but not both.'); } assertModuleSpecifier(node, false); break; } case SyntaxKind.ExportDeclaration: assertModuleSpecifier(node, node.exportClause?.kind === SyntaxKind.NamedExports); break; case SyntaxKind.ExportSpecifier: { const local = node.propertyName ?? node.name; if (local.kind === SyntaxKind.StringLiteral && parent.kind === SyntaxKind.ExportDeclaration && parent.moduleSpecifier?.kind !== SyntaxKind.StringLiteral) { throw (0, node_utils_1.createError)(local, 'A string literal cannot be used as a local exported binding without `from`.'); } break; } case SyntaxKind.CallExpression: if (node.expression.kind === SyntaxKind.ImportKeyword && node.arguments.length !== 1 && node.arguments.length !== 2) { throw (0, node_utils_1.createError)(node.arguments.length > 1 ? node.arguments[2] : node, 'Dynamic import requires exactly one or two arguments.'); } break; case SyntaxKind.ClassDeclaration: if (!node.name && (!(0, node_utils_1.hasModifier)(ts.SyntaxKind.ExportKeyword, node) || !(0, node_utils_1.hasModifier)(ts.SyntaxKind.DefaultKeyword, node))) { throw (0, node_utils_1.createError)(node, "A class declaration without the 'default' modifier must have a name."); } // intentional fallthrough case SyntaxKind.ClassExpression: { const heritageClauses = node.heritageClauses ?? []; let seenExtendsClause = false; let seenImplementsClause = false; for (const heritageClause of heritageClauses) { const { token, types } = heritageClause; if (types.length === 0) { throw (0, node_utils_1.createError)(heritageClause, `'${ts.tokenToString(token)}' list cannot be empty.`); } if (token === SyntaxKind.ExtendsKeyword) { if (seenExtendsClause) { throw (0, node_utils_1.createError)(heritageClause, "'extends' clause already seen."); } if (seenImplementsClause) { throw (0, node_utils_1.createError)(heritageClause, "'extends' clause must precede 'implements' clause."); } if (types.length > 1) { throw (0, node_utils_1.createError)(types[1], 'Classes can only extend a single class.'); } seenExtendsClause = true; } else { // `implements` if (seenImplementsClause) { throw (0, node_utils_1.createError)(heritageClause, "'implements' clause already seen."); } for (const heritageType of heritageClause.types) { if (!(0, node_utils_1.isEntityNameExpression)(heritageType.expression) || ts.isOptionalChain(heritageType.expression)) { throw (0, node_utils_1.createError)(heritageType, 'A class can only implement an identifier/qualified-name with optional type arguments.'); } } seenImplementsClause = true; } } break; } case SyntaxKind.InterfaceDeclaration: { const interfaceHeritageClauses = node.heritageClauses ?? []; let seenExtendsClause = false; for (const heritageClause of interfaceHeritageClauses) { const { token, types } = heritageClause; if (token === SyntaxKind.ImplementsKeyword) { throw (0, node_utils_1.createError)(heritageClause, "Interface declaration cannot have 'implements' clause."); } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (token !== SyntaxKind.ExtendsKeyword) { throw (0, node_utils_1.createError)(heritageClause, 'Unexpected token.'); } if (types.length === 0) { throw (0, node_utils_1.createError)(heritageClause, `'${ts.tokenToString(token)}' list cannot be empty.`); } if (seenExtendsClause) { throw (0, node_utils_1.createError)(heritageClause, "'extends' clause already seen."); } seenExtendsClause = true; for (const heritageType of heritageClause.types) { if (!(0, node_utils_1.isEntityNameExpression)(heritageType.expression) || ts.isOptionalChain(heritageType.expression)) { throw (0, node_utils_1.createError)(heritageType, 'Interface declaration can only extend an identifier/qualified name with optional type arguments.'); } } } break; } case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: if (node.parent.kind === SyntaxKind.InterfaceDeclaration || node.parent.kind === SyntaxKind.TypeLiteral) { return; } // otherwise, it is a non-type accessor - intentional fallthrough case SyntaxKind.MethodDeclaration: { const isAbstract = (0, node_utils_1.hasModifier)(SyntaxKind.AbstractKeyword, node); if (isAbstract && node.body) { throw (0, node_utils_1.createError)(node.name, node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor ? 'An abstract accessor cannot have an implementation.' : `Method '${(0, node_utils_1.declarationNameToString)(node.name)}' cannot have an implementation because it is marked abstract.`); } break; } case SyntaxKind.ObjectLiteralExpression: { if (!allowPattern) { for (const property of node.properties) { if ((property.kind === SyntaxKind.GetAccessor || property.kind === SyntaxKind.SetAccessor || property.kind === SyntaxKind.MethodDeclaration) && !property.body) { throw (0, node_utils_1.createError)(property.end - 1, "'{' expected.", node.getSourceFile()); } } } break; } case SyntaxKind.ImportEqualsDeclaration: if (node.isTypeOnly && node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { throw (0, node_utils_1.createError)(node, "An import alias cannot use 'import type'"); } break; case SyntaxKind.ModuleDeclaration: { if (node.flags & ts.NodeFlags.GlobalAugmentation) { const { body } = node; if (body == null || body.kind === SyntaxKind.ModuleDeclaration) { throw (0, node_utils_1.createError)(node.body ?? node, 'Expected a valid module body'); } const { name } = node; if (name.kind !== ts.SyntaxKind.Identifier) { throw (0, node_utils_1.createError)(name, 'global module augmentation must have an Identifier id'); } return; } if (ts.isStringLiteral(node.name)) { return; } if (node.body == null) { throw (0, node_utils_1.createError)(node, 'Expected a module body'); } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Fixme: confirm if it's possible if (node.name.kind !== ts.SyntaxKind.Identifier) { throw (0, node_utils_1.createError)(node.name, '`namespace`s must have an Identifier id'); } break; } case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: { checkForStatementDeclaration(node); break; } // No default } } function checkForStatementDeclaration(node) { const { initializer, kind } = node; const loop = kind === SyntaxKind.ForInStatement ? 'for...in' : 'for...of'; if (ts.isVariableDeclarationList(initializer)) { if (initializer.declarations.length !== 1) { throw (0, node_utils_1.createError)(initializer, `Only a single variable declaration is allowed in a '${loop}' statement.`); } const declaration = initializer.declarations[0]; if (declaration.initializer) { throw (0, node_utils_1.createError)(declaration, `The variable declaration of a '${loop}' statement cannot have an initializer.`); } else if (declaration.type) { throw (0, node_utils_1.createError)(declaration, `The variable declaration of a '${loop}' statement cannot have a type annotation.`); } } else if (!(0, node_utils_1.isValidAssignmentTarget)(initializer) && initializer.kind !== SyntaxKind.ObjectLiteralExpression && initializer.kind !== SyntaxKind.ArrayLiteralExpression) { throw (0, node_utils_1.createError)(initializer, `The left-hand side of a '${loop}' statement must be a variable or a property access.`); } } function assertModuleSpecifier(node, allowNull) { if (!allowNull && node.moduleSpecifier == null) { throw (0, node_utils_1.createError)(node, 'Module specifier must be a string literal.'); } if (node.moduleSpecifier && node.moduleSpecifier.kind !== SyntaxKind.StringLiteral) { throw (0, node_utils_1.createError)(node.moduleSpecifier, 'Module specifier must be a string literal.'); } }