diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 2fbd4483393b..6eb1d69c12d7 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -21,7 +21,9 @@ import { tokenCanStartExpression, tokenIsAssignment, + tokenIsIdentifier, tokenIsKeyword, + tokenIsKeywordOrIdentifier, tokenIsOperator, tokenIsPostfix, tokenIsPrefix, @@ -273,7 +275,7 @@ export default class ExpressionParser extends LValParser { ): N.Expression { const startPos = this.state.start; const startLoc = this.state.startLoc; - if (this.isContextual("yield")) { + if (this.isContextual(tt._yield)) { if (this.prodParam.hasYield) { let left = this.parseYield(); if (afterLeftParse) { @@ -290,8 +292,9 @@ export default class ExpressionParser extends LValParser { refExpressionErrors = new ExpressionErrors(); ownExpressionErrors = true; } + const { type } = this.state; - if (this.match(tt.parenL) || this.match(tt.name)) { + if (type === tt.parenL || tokenIsIdentifier(type)) { this.state.potentialArrowAt = this.state.start; } @@ -446,11 +449,7 @@ export default class ExpressionParser extends LValParser { op === tt.pipeline && this.getPluginOption("pipelineOperator", "proposal") === "minimal" ) { - if ( - this.match(tt.name) && - this.state.value === "await" && - this.prodParam.hasAwait - ) { + if (this.state.type === tt._await && this.prodParam.hasAwait) { throw this.raise( this.state.start, Errors.UnexpectedAwaitAfterPipelineBody, @@ -498,7 +497,7 @@ export default class ExpressionParser extends LValParser { case "smart": return this.withTopicBindingContext(() => { - if (this.prodParam.hasYield && this.isContextual("yield")) { + if (this.prodParam.hasYield && this.isContextual(tt._yield)) { throw this.raise( this.state.start, Errors.PipeBodyIsTighter, @@ -577,7 +576,7 @@ export default class ExpressionParser extends LValParser { ): N.Expression { const startPos = this.state.start; const startLoc = this.state.startLoc; - const isAwait = this.isContextual("await"); + const isAwait = this.isContextual(tt._await); if (isAwait && this.isAwaitAllowed()) { this.next(); @@ -1053,7 +1052,8 @@ export default class ExpressionParser extends LValParser { parseExprAtom(refExpressionErrors?: ?ExpressionErrors): N.Expression { let node; - switch (this.state.type) { + const { type } = this.state; + switch (type) { case tt._super: return this.parseSuper(); @@ -1074,61 +1074,6 @@ export default class ExpressionParser extends LValParser { this.next(); return this.finishNode(node, "ThisExpression"); - case tt.name: { - if ( - this.isContextual("module") && - this.lookaheadCharCode() === charCodes.leftCurlyBrace && - !this.hasFollowingLineBreak() - ) { - return this.parseModuleExpression(); - } - const canBeArrow = this.state.potentialArrowAt === this.state.start; - const containsEsc = this.state.containsEsc; - const id = this.parseIdentifier(); - - if (!containsEsc && id.name === "async" && !this.canInsertSemicolon()) { - if (this.match(tt._function)) { - this.resetPreviousNodeTrailingComments(id); - this.next(); - return this.parseFunction( - this.startNodeAtNode(id), - undefined, - true, - ); - } else if (this.match(tt.name)) { - // If the next token begins with "=", commit to parsing an async - // arrow function. (Peeking ahead for "=" lets us avoid a more - // expensive full-token lookahead on this common path.) - if (this.lookaheadCharCode() === charCodes.equalsTo) { - // although `id` is not used in async arrow unary function, - // we don't need to reset `async`'s trailing comments because - // it will be attached to the upcoming async arrow binding identifier - return this.parseAsyncArrowUnaryFunction( - this.startNodeAtNode(id), - ); - } else { - // Otherwise, treat "async" as an identifier and let calling code - // deal with the current tt.name token. - return id; - } - } else if (this.match(tt._do)) { - this.resetPreviousNodeTrailingComments(id); - return this.parseDo(this.startNodeAtNode(id), true); - } - } - - if (canBeArrow && this.match(tt.arrow) && !this.canInsertSemicolon()) { - this.next(); - return this.parseArrowExpression( - this.startNodeAtNode(id), - [id], - false, - ); - } - - return id; - } - case tt._do: { return this.parseDo(this.startNode(), false); } @@ -1308,7 +1253,71 @@ export default class ExpressionParser extends LValParser { // fall through default: - throw this.unexpected(); + if (tokenIsIdentifier(type)) { + if ( + this.isContextual(tt._module) && + this.lookaheadCharCode() === charCodes.leftCurlyBrace && + !this.hasFollowingLineBreak() + ) { + return this.parseModuleExpression(); + } + const canBeArrow = this.state.potentialArrowAt === this.state.start; + const containsEsc = this.state.containsEsc; + const id = this.parseIdentifier(); + + if ( + !containsEsc && + id.name === "async" && + !this.canInsertSemicolon() + ) { + const { type } = this.state; + if (type === tt._function) { + this.resetPreviousNodeTrailingComments(id); + this.next(); + return this.parseFunction( + this.startNodeAtNode(id), + undefined, + true, + ); + } else if (tokenIsIdentifier(type)) { + // If the next token begins with "=", commit to parsing an async + // arrow function. (Peeking ahead for "=" lets us avoid a more + // expensive full-token lookahead on this common path.) + if (this.lookaheadCharCode() === charCodes.equalsTo) { + // although `id` is not used in async arrow unary function, + // we don't need to reset `async`'s trailing comments because + // it will be attached to the upcoming async arrow binding identifier + return this.parseAsyncArrowUnaryFunction( + this.startNodeAtNode(id), + ); + } else { + // Otherwise, treat "async" as an identifier and let calling code + // deal with the current tt.name token. + return id; + } + } else if (type === tt._do) { + this.resetPreviousNodeTrailingComments(id); + return this.parseDo(this.startNodeAtNode(id), true); + } + } + + if ( + canBeArrow && + this.match(tt.arrow) && + !this.canInsertSemicolon() + ) { + this.next(); + return this.parseArrowExpression( + this.startNodeAtNode(id), + [id], + false, + ); + } + + return id; + } else { + throw this.unexpected(); + } } } @@ -1519,6 +1528,13 @@ export default class ExpressionParser extends LValParser { "function", ); this.next(); // eat `.` + // https://github.com/tc39/proposal-function.sent#syntax-1 + if (this.match(tt._sent)) { + this.expectPlugin("functionSent"); + } else if (!this.hasPlugin("functionSent")) { + // The code wasn't `function.sent` but just `function.`, so a simple error is less confusing. + this.unexpected(); + } return this.parseMetaProperty(node, meta, "sent"); } return this.parseFunction(node); @@ -1531,16 +1547,6 @@ export default class ExpressionParser extends LValParser { ): N.MetaProperty { node.meta = meta; - if (meta.name === "function" && propertyName === "sent") { - // https://github.com/tc39/proposal-function.sent#syntax-1 - if (this.isContextual(propertyName)) { - this.expectPlugin("functionSent"); - } else if (!this.hasPlugin("functionSent")) { - // The code wasn't `function.sent` but just `function.`, so a simple error is less confusing. - this.unexpected(); - } - } - const containsEsc = this.state.containsEsc; node.property = this.parseIdentifier(true); @@ -1562,7 +1568,7 @@ export default class ExpressionParser extends LValParser { const id = this.createIdentifier(this.startNodeAtNode(node), "import"); this.next(); // eat `.` - if (this.isContextual("meta")) { + if (this.isContextual(tt._meta)) { if (!this.inModule) { this.raise(id.start, SourceTypeModuleErrors.ImportMetaOutsideModule); } @@ -2537,10 +2543,8 @@ export default class ExpressionParser extends LValParser { const { start, type } = this.state; - if (type === tt.name) { + if (tokenIsKeywordOrIdentifier(type)) { name = this.state.value; - } else if (tokenIsKeyword(type)) { - name = tokenLabelName(type); } else { throw this.unexpected(); } diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 2b529897d05c..d07690d075c4 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -2,6 +2,7 @@ import * as N from "../types"; import { + tokenIsIdentifier, tokenIsLoop, tt, type TokenType, @@ -178,7 +179,7 @@ export default class StatementParser extends ExpressionParser { } isLet(context: ?string): boolean { - if (!this.isContextual("let")) { + if (!this.isContextual(tt._let)) { return false; } return this.isLetKeyword(context); @@ -378,7 +379,7 @@ export default class StatementParser extends ExpressionParser { const expr = this.parseExpression(); if ( - starttype === tt.name && + tokenIsIdentifier(starttype) && expr.type === "Identifier" && this.eat(tt.colon) ) { @@ -572,7 +573,7 @@ export default class StatementParser extends ExpressionParser { this.state.labels.push(loopLabel); let awaitAt = -1; - if (this.isAwaitAllowed() && this.eatContextual("await")) { + if (this.isAwaitAllowed() && this.eatContextual(tt._await)) { awaitAt = this.state.lastTokStart; } this.scope.enter(SCOPE_OTHER); @@ -585,7 +586,7 @@ export default class StatementParser extends ExpressionParser { return this.parseFor(node, null); } - const startsWithLet = this.isContextual("let"); + const startsWithLet = this.isContextual(tt._let); const isLet = startsWithLet && this.isLetKeyword(); if (this.match(tt._var) || this.match(tt._const) || isLet) { const init = this.startNode(); @@ -595,7 +596,7 @@ export default class StatementParser extends ExpressionParser { this.finishNode(init, "VariableDeclaration"); if ( - (this.match(tt._in) || this.isContextual("of")) && + (this.match(tt._in) || this.isContextual(tt._of)) && init.declarations.length === 1 ) { return this.parseForIn(node, init, awaitAt); @@ -608,12 +609,11 @@ export default class StatementParser extends ExpressionParser { // Check whether the first token is possibly a contextual keyword, so that // we can forbid `for (async of` if this turns out to be a for-of loop. - const startsWithUnescapedName = - this.match(tt.name) && !this.state.containsEsc; + const startsWithAsync = this.isContextual(tt._async); const refExpressionErrors = new ExpressionErrors(); const init = this.parseExpression(true, refExpressionErrors); - const isForOf = this.isContextual("of"); + const isForOf = this.isContextual(tt._of); if (isForOf) { // Check for leading tokens that are forbidden in for-of loops: if (startsWithLet) { @@ -621,9 +621,8 @@ export default class StatementParser extends ExpressionParser { } else if ( // `for await (async of []);` is allowed. awaitAt === -1 && - startsWithUnescapedName && - init.type === "Identifier" && - init.name === "async" + startsWithAsync && + init.type === "Identifier" ) { // This catches the case where the `async` in `for (async of` was // parsed as an identifier. If it was parsed as the start of an async @@ -1119,7 +1118,7 @@ export default class StatementParser extends ExpressionParser { } else { if ( kind === "const" && - !(this.match(tt._in) || this.isContextual("of")) + !(this.match(tt._in) || this.isContextual(tt._of)) ) { // `const` with no initializer is allowed in TypeScript. // It could be a declaration like `const x: number;`. @@ -1132,7 +1131,7 @@ export default class StatementParser extends ExpressionParser { } } else if ( decl.id.type !== "Identifier" && - !(isFor && (this.match(tt._in) || this.isContextual("of"))) + !(isFor && (this.match(tt._in) || this.isContextual(tt._of))) ) { this.raise( this.state.lastTokEnd, @@ -1219,7 +1218,9 @@ export default class StatementParser extends ExpressionParser { } parseFunctionId(requireId?: boolean): ?N.Identifier { - return requireId || this.match(tt.name) ? this.parseIdentifier() : null; + return requireId || tokenIsIdentifier(this.state.type) + ? this.parseIdentifier() + : null; } parseFunctionParams(node: N.Function, allowModifiers?: boolean): void { @@ -1405,7 +1406,7 @@ export default class StatementParser extends ExpressionParser { member: N.ClassMember, state: N.ParseClassMemberState, ): void { - const isStatic = this.isContextual("static"); + const isStatic = this.isContextual(tt._static); if (isStatic) { if (this.parseClassMemberFromModifier(classBody, member)) { @@ -1465,7 +1466,8 @@ export default class StatementParser extends ExpressionParser { return; } - const isContextual = this.match(tt.name) && !this.state.containsEsc; + const isContextual = + tokenIsIdentifier(this.state.type) && !this.state.containsEsc; const isPrivate = this.match(tt.privateName); const key = this.parseClassElementName(member); const maybeQuestionTokenStart = this.state.start; @@ -1758,7 +1760,7 @@ export default class StatementParser extends ExpressionParser { optionalId: ?boolean, bindingType: BindingTypes = BIND_CLASS, ): void { - if (this.match(tt.name)) { + if (tokenIsIdentifier(this.state.type)) { node.id = this.parseIdentifier(); if (isStatement) { this.checkLVal(node.id, "class name", bindingType); @@ -1848,7 +1850,7 @@ export default class StatementParser extends ExpressionParser { } maybeParseExportNamespaceSpecifier(node: N.Node): boolean { - if (this.isContextual("as")) { + if (this.isContextual(tt._as)) { if (!node.specifiers) node.specifiers = []; const specifier = this.startNodeAt( @@ -1891,7 +1893,7 @@ export default class StatementParser extends ExpressionParser { } isAsyncFunction(): boolean { - if (!this.isContextual("async")) return false; + if (!this.isContextual(tt._async)) return false; const next = this.nextTokenStart(); return ( !lineBreak.test(this.input.slice(this.state.pos, next)) && @@ -1941,23 +1943,23 @@ export default class StatementParser extends ExpressionParser { } isExportDefaultSpecifier(): boolean { - if (this.match(tt.name)) { - const value = this.state.value; - if ((value === "async" && !this.state.containsEsc) || value === "let") { + const { type } = this.state; + if (tokenIsIdentifier(type)) { + if ((type === tt._async && !this.state.containsEsc) || type === tt._let) { return false; } if ( - (value === "type" || value === "interface") && + (type === tt._type || type === tt._interface) && !this.state.containsEsc ) { - const l = this.lookahead(); + const { type: nextType } = this.lookahead(); // If we see any variable name other than `from` after `type` keyword, // we consider it as flow/typescript type exports // note that this approach may fail on some pedantic cases // export type from = number if ( - (l.type === tt.name && l.value !== "from") || - l.type === tt.braceL + (tokenIsIdentifier(nextType) && nextType !== tt._from) || + nextType === tt.braceL ) { this.expectOnePlugin(["flow", "typescript"]); return false; @@ -1971,7 +1973,7 @@ export default class StatementParser extends ExpressionParser { const hasFrom = this.isUnparsedContextual(next, "from"); if ( this.input.charCodeAt(next) === charCodes.comma || - (this.match(tt.name) && hasFrom) + (tokenIsIdentifier(this.state.type) && hasFrom) ) { return true; } @@ -1989,7 +1991,7 @@ export default class StatementParser extends ExpressionParser { } parseExportFrom(node: N.ExportNamedDeclaration, expect?: boolean): void { - if (this.eatContextual("from")) { + if (this.eatContextual(tt._from)) { node.source = this.parseImportSource(); this.checkExport(node); const assertions = this.maybeParseImportAssertions(); @@ -2169,7 +2171,7 @@ export default class StatementParser extends ExpressionParser { const isString = this.match(tt.string); const local = this.parseModuleExportName(); node.local = local; - if (this.eatContextual("as")) { + if (this.eatContextual(tt._as)) { node.exported = this.parseModuleExportName(); } else if (isString) { node.exported = cloneStringLiteral(local); @@ -2222,7 +2224,7 @@ export default class StatementParser extends ExpressionParser { // now we check if we need to parse the next imports // but only if they are not importing * (everything) if (parseNext && !hasStar) this.parseNamedImportSpecifiers(node); - this.expectContextual("from"); + this.expectContextual(tt._from); } node.source = this.parseImportSource(); // https://github.com/tc39/proposal-import-assertions @@ -2249,7 +2251,7 @@ export default class StatementParser extends ExpressionParser { // eslint-disable-next-line no-unused-vars shouldParseDefaultImport(node: N.ImportDeclaration): boolean { - return this.match(tt.name); + return tokenIsIdentifier(this.state.type); } parseImportSpecifierLocal( @@ -2368,7 +2370,7 @@ export default class StatementParser extends ExpressionParser { maybeParseImportAssertions() { // [no LineTerminator here] AssertClause - if (this.isContextual("assert") && !this.hasPrecedingLineBreak()) { + if (this.isContextual(tt._assert) && !this.hasPrecedingLineBreak()) { this.expectPlugin("importAssertions"); this.next(); // eat `assert` } else { @@ -2401,7 +2403,7 @@ export default class StatementParser extends ExpressionParser { if (this.match(tt.star)) { const specifier = this.startNode(); this.next(); - this.expectContextual("as"); + this.expectContextual(tt._as); this.parseImportSpecifierLocal( node, @@ -2439,7 +2441,7 @@ export default class StatementParser extends ExpressionParser { const specifier = this.startNode(); const importedIsString = this.match(tt.string); specifier.imported = this.parseModuleExportName(); - if (this.eatContextual("as")) { + if (this.eatContextual(tt._as)) { specifier.local = this.parseIdentifier(); } else { const { imported } = specifier; diff --git a/packages/babel-parser/src/parser/util.js b/packages/babel-parser/src/parser/util.js index 0ff740686c4d..adb2e94cfaca 100644 --- a/packages/babel-parser/src/parser/util.js +++ b/packages/babel-parser/src/parser/util.js @@ -2,7 +2,7 @@ import { isTokenType, - tokenIsKeyword, + tokenIsLiteralPropertyName, tokenLabelName, tt, type TokenType, @@ -68,12 +68,8 @@ export default class UtilParser extends Tokenizer { // Tests whether parsed token is a contextual keyword. - isContextual(name: string): boolean { - return ( - this.match(tt.name) && - this.state.value === name && - !this.state.containsEsc - ); + isContextual(token: TokenType): boolean { + return this.state.type === token && !this.state.containsEsc; } isUnparsedContextual(nameStart: number, name: string): boolean { @@ -98,14 +94,18 @@ export default class UtilParser extends Tokenizer { // Consumes contextual keyword if possible. - eatContextual(name: string): boolean { - return this.isContextual(name) && this.eat(tt.name); + eatContextual(token: TokenType): boolean { + if (this.isContextual(token)) { + this.next(); + return true; + } + return false; } // Asserts that following token is given contextual keyword. - expectContextual(name: string, template?: ErrorTemplate): void { - if (!this.eatContextual(name)) this.unexpected(null, template); + expectContextual(token: TokenType, template?: ErrorTemplate): void { + if (!this.eatContextual(token)) this.unexpected(null, template); } // Test whether a semicolon can be inserted at the current position. @@ -306,14 +306,7 @@ export default class UtilParser extends Tokenizer { * BigIntLiteral */ isLiteralPropertyName(): boolean { - return ( - this.match(tt.name) || - tokenIsKeyword(this.state.type) || - this.match(tt.string) || - this.match(tt.num) || - this.match(tt.bigint) || - this.match(tt.decimal) - ); + return tokenIsLiteralPropertyName(this.state.type); } /* diff --git a/packages/babel-parser/src/plugins/flow/index.js b/packages/babel-parser/src/plugins/flow/index.js index ab00576f7d0b..666a33c3a7fb 100644 --- a/packages/babel-parser/src/plugins/flow/index.js +++ b/packages/babel-parser/src/plugins/flow/index.js @@ -7,16 +7,20 @@ import type Parser from "../../parser"; import { + tokenIsIdentifier, tokenIsKeyword, + tokenIsKeywordOrIdentifier, + tokenIsLiteralPropertyName, tokenLabelName, tt, type TokenType, + tokenIsFlowInterfaceOrTypeOrOpaque, } from "../../tokenizer/types"; import * as N from "../../types"; import type { Position } from "../../util/location"; import { types as tc } from "../../tokenizer/context"; import * as charCodes from "charcodes"; -import { isIteratorStart, isKeyword } from "../../util/identifier"; +import { isIteratorStart } from "../../util/identifier"; import FlowScopeHandler from "./scope"; import { type BindingTypes, @@ -160,11 +164,8 @@ function hasTypeImportKind(node: N.Node): boolean { return node.importKind === "type" || node.importKind === "typeof"; } -function isMaybeDefaultImport(state: { type: TokenType, value: any }): boolean { - return ( - (state.type === tt.name || tokenIsKeyword(state.type)) && - state.value !== "from" - ); +function isMaybeDefaultImport(type: TokenType): boolean { + return tokenIsKeywordOrIdentifier(type) && type !== tt._from; } const exportSuggestions = { @@ -266,7 +267,7 @@ export default (superClass: Class): Class => const node = this.startNode(); const moduloPos = this.state.start; this.next(); // eat `%` - this.expectContextual("checks"); + this.expectContextual(tt._checks); // Force '%' and 'checks' to be adjacent if (this.state.lastTokStart > moduloPos + 1) { this.raise(moduloPos, FlowErrors.UnexpectedSpaceBetweenModuloChecks); @@ -360,7 +361,7 @@ export default (superClass: Class): Class => return this.flowParseDeclareFunction(node); } else if (this.match(tt._var)) { return this.flowParseDeclareVariable(node); - } else if (this.eatContextual("module")) { + } else if (this.eatContextual(tt._module)) { if (this.match(tt.dot)) { return this.flowParseDeclareModuleExports(node); } else { @@ -369,11 +370,11 @@ export default (superClass: Class): Class => } return this.flowParseDeclareModule(node); } - } else if (this.isContextual("type")) { + } else if (this.isContextual(tt._type)) { return this.flowParseDeclareTypeAlias(node); - } else if (this.isContextual("opaque")) { + } else if (this.isContextual(tt._opaque)) { return this.flowParseDeclareOpaqueType(node); - } else if (this.isContextual("interface")) { + } else if (this.isContextual(tt._interface)) { return this.flowParseDeclareInterface(node); } else if (this.match(tt._export)) { return this.flowParseDeclareExportDeclaration(node, insideModule); @@ -411,7 +412,7 @@ export default (superClass: Class): Class => if (this.match(tt._import)) { this.next(); - if (!this.isContextual("type") && !this.match(tt._typeof)) { + if (!this.isContextual(tt._type) && !this.match(tt._typeof)) { this.raise( this.state.lastTokStart, FlowErrors.InvalidNonTypeImportInDeclareModule, @@ -420,7 +421,7 @@ export default (superClass: Class): Class => this.parseImport(bodyNode); } else { this.expectContextual( - "declare", + tt._declare, FlowErrors.UnsupportedStatementInDeclareModule, ); @@ -492,7 +493,7 @@ export default (superClass: Class): Class => if ( this.match(tt._const) || this.isLet() || - ((this.isContextual("type") || this.isContextual("interface")) && + ((this.isContextual(tt._type) || this.isContextual(tt._interface)) && !insideModule) ) { const label = this.state.value; @@ -510,7 +511,7 @@ export default (superClass: Class): Class => this.match(tt._var) || // declare export var ... this.match(tt._function) || // declare export function ... this.match(tt._class) || // declare export class ... - this.isContextual("opaque") // declare export opaque .. + this.isContextual(tt._opaque) // declare export opaque .. ) { node.declaration = this.flowParseDeclare(this.startNode()); node.default = false; @@ -519,9 +520,9 @@ export default (superClass: Class): Class => } else if ( this.match(tt.star) || // declare export * from '' this.match(tt.braceL) || // declare export {} ... - this.isContextual("interface") || // declare export interface ... - this.isContextual("type") || // declare export type ... - this.isContextual("opaque") // declare export opaque type ... + this.isContextual(tt._interface) || // declare export interface ... + this.isContextual(tt._type) || // declare export type ... + this.isContextual(tt._opaque) // declare export opaque type ... ) { node = this.parseExport(node); if (node.type === "ExportNamedDeclaration") { @@ -547,7 +548,7 @@ export default (superClass: Class): Class => node: N.FlowDeclareModuleExports, ): N.FlowDeclareModuleExports { this.next(); - this.expectContextual("exports"); + this.expectContextual(tt._exports); node.typeAnnotation = this.flowParseTypeAnnotation(); this.semicolon(); @@ -615,14 +616,14 @@ export default (superClass: Class): Class => } while (!isClass && this.eat(tt.comma)); } - if (this.isContextual("mixins")) { + if (this.isContextual(tt._mixins)) { this.next(); do { node.mixins.push(this.flowParseInterfaceExtends()); } while (this.eat(tt.comma)); } - if (this.isContextual("implements")) { + if (this.isContextual(tt._implements)) { this.next(); do { node.implements.push(this.flowParseInterfaceExtends()); @@ -707,7 +708,7 @@ export default (superClass: Class): Class => node: N.FlowOpaqueType, declare: boolean, ): N.FlowOpaqueType { - this.expectContextual("type"); + this.expectContextual(tt._type); node.id = this.flowParseRestrictedIdentifier( /* liberal */ true, /* declaration */ true, @@ -844,7 +845,7 @@ export default (superClass: Class): Class => flowParseInterfaceType(): N.FlowInterfaceType { const node = this.startNode(); - this.expectContextual("interface"); + this.expectContextual(tt._interface); node.extends = []; if (this.eat(tt._extends)) { @@ -1008,7 +1009,7 @@ export default (superClass: Class): Class => let inexactStart: ?number = null; const node = this.startNode(); - if (allowProto && this.isContextual("proto")) { + if (allowProto && this.isContextual(tt._proto)) { const lookahead = this.lookahead(); if (lookahead.type !== tt.colon && lookahead.type !== tt.question) { @@ -1018,7 +1019,7 @@ export default (superClass: Class): Class => } } - if (allowStatic && this.isContextual("static")) { + if (allowStatic && this.isContextual(tt._static)) { const lookahead = this.lookahead(); // static is a valid identifier name @@ -1059,13 +1060,9 @@ export default (superClass: Class): Class => } else { let kind = "init"; - if (this.isContextual("get") || this.isContextual("set")) { + if (this.isContextual(tt._get) || this.isContextual(tt._set)) { const lookahead = this.lookahead(); - if ( - lookahead.type === tt.name || - lookahead.type === tt.string || - lookahead.type === tt.num - ) { + if (tokenIsLiteralPropertyName(lookahead.type)) { kind = this.state.value; this.next(); } @@ -1432,18 +1429,6 @@ export default (superClass: Class): Class => const oldNoAnonFunctionType = this.state.noAnonFunctionType; switch (this.state.type) { - case tt.name: - if (this.isContextual("interface")) { - return this.flowParseInterfaceType(); - } - - return this.flowIdentToTypeAnnotation( - startPos, - startLoc, - node, - this.parseIdentifier(), - ); - case tt.braceL: return this.flowParseObjectType({ allowStatic: false, @@ -1491,7 +1476,7 @@ export default (superClass: Class): Class => // Check to see if this is actually a grouped type if (!this.match(tt.parenR) && !this.match(tt.ellipsis)) { - if (this.match(tt.name) || this.match(tt._this)) { + if (tokenIsIdentifier(this.state.type) || this.match(tt._this)) { const token = this.lookahead().type; isGroupedType = token !== tt.question && token !== tt.colon; } else { @@ -1619,6 +1604,17 @@ export default (superClass: Class): Class => const label = tokenLabelName(this.state.type); this.next(); return super.createIdentifier(node, label); + } else if (tokenIsIdentifier(this.state.type)) { + if (this.isContextual(tt._interface)) { + return this.flowParseInterfaceType(); + } + + return this.flowIdentToTypeAnnotation( + startPos, + startLoc, + node, + this.parseIdentifier(), + ); } } @@ -1823,18 +1819,14 @@ export default (superClass: Class): Class => // interfaces and enums parseStatement(context: ?string, topLevel?: boolean): N.Statement { // strict mode handling of `interface` since it's a reserved word - if ( - this.state.strict && - this.match(tt.name) && - this.state.value === "interface" - ) { + if (this.state.strict && this.isContextual(tt._interface)) { const lookahead = this.lookahead(); - if (lookahead.type === tt.name || isKeyword(lookahead.value)) { + if (tokenIsKeywordOrIdentifier(lookahead.type)) { const node = this.startNode(); this.next(); return this.flowParseInterface(node); } - } else if (this.shouldParseEnums() && this.isContextual("enum")) { + } else if (this.shouldParseEnums() && this.isContextual(tt._enum)) { const node = this.startNode(); this.next(); return this.flowParseEnumDeclaration(node); @@ -1856,14 +1848,14 @@ export default (superClass: Class): Class => if (expr.name === "declare") { if ( this.match(tt._class) || - this.match(tt.name) || + tokenIsIdentifier(this.state.type) || this.match(tt._function) || this.match(tt._var) || this.match(tt._export) ) { return this.flowParseDeclare(node); } - } else if (this.match(tt.name)) { + } else if (tokenIsIdentifier(this.state.type)) { if (expr.name === "interface") { return this.flowParseInterface(node); } else if (expr.name === "type") { @@ -1879,31 +1871,30 @@ export default (superClass: Class): Class => // export type shouldParseExportDeclaration(): boolean { - return ( - this.isContextual("type") || - this.isContextual("interface") || - this.isContextual("opaque") || - (this.shouldParseEnums() && this.isContextual("enum")) || - super.shouldParseExportDeclaration() - ); + const { type } = this.state; + if ( + tokenIsFlowInterfaceOrTypeOrOpaque(type) || + (this.shouldParseEnums() && type === tt._enum) + ) { + return !this.state.containsEsc; + } + return super.shouldParseExportDeclaration(); } isExportDefaultSpecifier(): boolean { + const { type } = this.state; if ( - this.match(tt.name) && - (this.state.value === "type" || - this.state.value === "interface" || - this.state.value === "opaque" || - (this.shouldParseEnums() && this.state.value === "enum")) + tokenIsFlowInterfaceOrTypeOrOpaque(type) || + (this.shouldParseEnums() && type === tt._enum) ) { - return false; + return this.state.containsEsc; } return super.isExportDefaultSpecifier(); } parseExportDefaultExpression(): N.Expression | N.Declaration { - if (this.shouldParseEnums() && this.isContextual("enum")) { + if (this.shouldParseEnums() && this.isContextual(tt._enum)) { const node = this.startNode(); this.next(); return this.flowParseEnumDeclaration(node); @@ -2124,7 +2115,7 @@ export default (superClass: Class): Class => } parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration { - if (this.isContextual("type")) { + if (this.isContextual(tt._type)) { node.exportKind = "type"; const declarationNode = this.startNode(); @@ -2139,19 +2130,19 @@ export default (superClass: Class): Class => // export type Foo = Bar; return this.flowParseTypeAlias(declarationNode); } - } else if (this.isContextual("opaque")) { + } else if (this.isContextual(tt._opaque)) { node.exportKind = "type"; const declarationNode = this.startNode(); this.next(); // export opaque type Foo = Bar; return this.flowParseOpaqueType(declarationNode, false); - } else if (this.isContextual("interface")) { + } else if (this.isContextual(tt._interface)) { node.exportKind = "type"; const declarationNode = this.startNode(); this.next(); return this.flowParseInterface(declarationNode); - } else if (this.shouldParseEnums() && this.isContextual("enum")) { + } else if (this.shouldParseEnums() && this.isContextual(tt._enum)) { node.exportKind = "value"; const declarationNode = this.startNode(); this.next(); @@ -2164,7 +2155,7 @@ export default (superClass: Class): Class => eatExportStar(node: N.Node): boolean { if (super.eatExportStar(...arguments)) return true; - if (this.isContextual("type") && this.lookahead().type === tt.star) { + if (this.isContextual(tt._type) && this.lookahead().type === tt.star) { node.exportKind = "type"; this.next(); this.next(); @@ -2196,7 +2187,7 @@ export default (superClass: Class): Class => state: N.ParseClassMemberState, ): void { const pos = this.state.start; - if (this.isContextual("declare")) { + if (this.isContextual(tt._declare)) { if (this.parseClassMemberFromModifier(classBody, member)) { // 'declare' is a class element name return; @@ -2456,7 +2447,7 @@ export default (superClass: Class): Class => if (node.superClass && this.isRelational("<")) { node.superTypeParameters = this.flowParseTypeParameterInstantiation(); } - if (this.isContextual("implements")) { + if (this.isContextual(tt._implements)) { this.next(); const implemented: N.FlowClassImplements[] = (node.implements = []); do { @@ -2585,7 +2576,7 @@ export default (superClass: Class): Class => return super.shouldParseDefaultImport(node); } - return isMaybeDefaultImport(this.state); + return isMaybeDefaultImport(this.state.type); } parseImportSpecifierLocal( @@ -2612,21 +2603,22 @@ export default (superClass: Class): Class => let kind = null; if (this.match(tt._typeof)) { kind = "typeof"; - } else if (this.isContextual("type")) { + } else if (this.isContextual(tt._type)) { kind = "type"; } if (kind) { const lh = this.lookahead(); + const { type } = lh; // import type * is not allowed - if (kind === "type" && lh.type === tt.star) { + if (kind === "type" && type === tt.star) { this.unexpected(lh.start); } if ( - isMaybeDefaultImport(lh) || - lh.type === tt.braceL || - lh.type === tt.star + isMaybeDefaultImport(type) || + type === tt.braceL || + type === tt.star ) { this.next(); node.importKind = kind; @@ -2652,12 +2644,11 @@ export default (superClass: Class): Class => } let isBinding = false; - if (this.isContextual("as") && !this.isLookaheadContextual("as")) { + if (this.isContextual(tt._as) && !this.isLookaheadContextual("as")) { const as_ident = this.parseIdentifier(true); if ( specifierTypeKind !== null && - !this.match(tt.name) && - !tokenIsKeyword(this.state.type) + !tokenIsKeywordOrIdentifier(this.state.type) ) { // `import {type as ,` or `import {type as }` specifier.imported = as_ident; @@ -2672,7 +2663,7 @@ export default (superClass: Class): Class => } else { if ( specifierTypeKind !== null && - (this.match(tt.name) || tokenIsKeyword(this.state.type)) + tokenIsKeywordOrIdentifier(this.state.type) ) { // `import {type foo` specifier.imported = this.parseIdentifier(true); @@ -2691,7 +2682,7 @@ export default (superClass: Class): Class => specifier.importKind = null; } - if (this.eatContextual("as")) { + if (this.eatContextual(tt._as)) { specifier.local = this.parseIdentifier(); } else { isBinding = true; @@ -3546,8 +3537,8 @@ export default (superClass: Class): Class => }: { enumName: string, }): EnumExplicitType { - if (this.eatContextual("of")) { - if (!this.match(tt.name)) { + if (this.eatContextual(tt._of)) { + if (!tokenIsIdentifier(this.state.type)) { throw this.flowEnumErrorInvalidExplicitType(this.state.start, { enumName, suppliedType: null, diff --git a/packages/babel-parser/src/plugins/placeholders.js b/packages/babel-parser/src/plugins/placeholders.js index 6b44b61b98a2..16f10782a6f4 100644 --- a/packages/babel-parser/src/plugins/placeholders.js +++ b/packages/babel-parser/src/plugins/placeholders.js @@ -163,7 +163,7 @@ export default (superClass: Class): Class => // Replicate the original checks that lead to looking ahead for an // identifier. - if (!this.isContextual("let")) { + if (!this.isContextual(tt._let)) { return false; } if (context) return false; @@ -263,7 +263,7 @@ export default (superClass: Class): Class => const placeholder = this.parsePlaceholder("Identifier"); if (!placeholder) return super.parseExport(...arguments); - if (!this.isContextual("from") && !this.match(tt.comma)) { + if (!this.isContextual(tt._from) && !this.match(tt.comma)) { // export %%DECL%%; node.specifiers = []; node.source = null; @@ -324,7 +324,7 @@ export default (superClass: Class): Class => node.specifiers = []; - if (!this.isContextual("from") && !this.match(tt.comma)) { + if (!this.isContextual(tt._from) && !this.match(tt.comma)) { // import %%STRING%%; node.source = this.finishPlaceholder(placeholder, "StringLiteral"); this.semicolon(); @@ -345,7 +345,7 @@ export default (superClass: Class): Class => if (!hasStarImport) this.parseNamedImportSpecifiers(node); } - this.expectContextual("from"); + this.expectContextual(tt._from); node.source = this.parseImportSource(); this.semicolon(); return this.finishNode(node, "ImportDeclaration"); diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index bc716eb6701d..6d90951f0672 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -5,9 +5,15 @@ // Error messages are colocated with the plugin. /* eslint-disable @babel/development-internal/dry-error-messages */ -import type { TokenType } from "../../tokenizer/types"; import type State from "../../tokenizer/state"; -import { tokenOperatorPrecedence, tt } from "../../tokenizer/types"; +import { + tokenIsIdentifier, + tokenIsTSDeclarationStart, + tokenIsTSTypeOperator, + tokenOperatorPrecedence, + tt, + type TokenType, +} from "../../tokenizer/types"; import { types as ct } from "../../tokenizer/context"; import * as N from "../../types"; import type { Position } from "../../util/location"; @@ -206,7 +212,7 @@ export default (superClass: Class): Class => tsIsIdentifier(): boolean { // TODO: actually a bit more complex in TypeScript, but shouldn't matter. // See https://github.com/Microsoft/TypeScript/issues/15008 - return this.match(tt.name); + return tokenIsIdentifier(this.state.type); } tsTokenCanFollowModifier() { @@ -235,7 +241,7 @@ export default (superClass: Class): Class => allowedModifiers: T[], stopOnStartOfClassStaticBlock?: boolean, ): ?T { - if (!this.match(tt.name)) { + if (!tokenIsIdentifier(this.state.type)) { return undefined; } @@ -597,7 +603,11 @@ export default (superClass: Class): Class => tsIsUnambiguouslyIndexSignature() { this.next(); // Skip '{' - return this.eat(tt.name) && this.match(tt.colon); + if (tokenIsIdentifier(this.state.type)) { + this.next(); + return this.match(tt.colon); + } + return false; } tsTryParseIndexSignature(node: N.Node): ?N.TsIndexSignature { @@ -771,9 +781,9 @@ export default (superClass: Class): Class => tsIsStartOfMappedType(): boolean { this.next(); if (this.eat(tt.plusMin)) { - return this.isContextual("readonly"); + return this.isContextual(tt._readonly); } - if (this.isContextual("readonly")) { + if (this.isContextual(tt._readonly)) { this.next(); } if (!this.match(tt.bracketL)) { @@ -802,14 +812,14 @@ export default (superClass: Class): Class => if (this.match(tt.plusMin)) { node.readonly = this.state.value; this.next(); - this.expectContextual("readonly"); - } else if (this.eatContextual("readonly")) { + this.expectContextual(tt._readonly); + } else if (this.eatContextual(tt._readonly)) { node.readonly = true; } this.expect(tt.bracketL); node.typeParameter = this.tsParseMappedTypeParameter(); - node.nameType = this.eatContextual("as") ? this.tsParseType() : null; + node.nameType = this.eatContextual(tt._as) ? this.tsParseType() : null; this.expect(tt.bracketR); @@ -978,7 +988,7 @@ export default (superClass: Class): Class => tsParseThisTypeOrThisTypePredicate(): N.TsThisType | N.TsTypePredicate { const thisKeyword = this.tsParseThisTypeNode(); - if (this.isContextual("is") && !this.hasPrecedingLineBreak()) { + if (this.isContextual(tt._is) && !this.hasPrecedingLineBreak()) { return this.tsParseThisTypePredicate(thisKeyword); } else { return thisKeyword; @@ -987,24 +997,6 @@ export default (superClass: Class): Class => tsParseNonArrayType(): N.TsType { switch (this.state.type) { - case tt.name: - case tt._void: - case tt._null: { - const type = this.match(tt._void) - ? "TSVoidKeyword" - : this.match(tt._null) - ? "TSNullKeyword" - : keywordTypeFromName(this.state.value); - if ( - type !== undefined && - this.lookaheadCharCode() !== charCodes.dot - ) { - const node: N.TsKeywordType = this.startNode(); - this.next(); - return this.finishNode(node, type); - } - return this.tsParseTypeReference(); - } case tt.string: case tt.num: case tt.bigint: @@ -1050,6 +1042,30 @@ export default (superClass: Class): Class => return this.tsParseParenthesizedType(); case tt.backQuote: return this.tsParseTemplateLiteralType(); + default: { + const { type } = this.state; + if ( + tokenIsIdentifier(type) || + type === tt._void || + type === tt._null + ) { + const nodeType = + type === tt._void + ? "TSVoidKeyword" + : type === tt._null + ? "TSNullKeyword" + : keywordTypeFromName(this.state.value); + if ( + nodeType !== undefined && + this.lookaheadCharCode() !== charCodes.dot + ) { + const node: N.TsKeywordType = this.startNode(); + this.next(); + return this.finishNode(node, nodeType); + } + return this.tsParseTypeReference(); + } + } } throw this.unexpected(); @@ -1074,11 +1090,10 @@ export default (superClass: Class): Class => return type; } - tsParseTypeOperator( - operator: "keyof" | "unique" | "readonly", - ): N.TsTypeOperator { + tsParseTypeOperator(): N.TsTypeOperator { const node: N.TsTypeOperator = this.startNode(); - this.expectContextual(operator); + const operator = this.state.value; + this.next(); // eat operator node.operator = operator; node.typeAnnotation = this.tsParseTypeOperatorOrHigher(); @@ -1101,7 +1116,7 @@ export default (superClass: Class): Class => tsParseInferType(): N.TsInferType { const node = this.startNode(); - this.expectContextual("infer"); + this.expectContextual(tt._infer); const typeParameter = this.startNode(); typeParameter.name = this.tsParseTypeParameterName(); node.typeParameter = this.finishNode(typeParameter, "TSTypeParameter"); @@ -1109,12 +1124,11 @@ export default (superClass: Class): Class => } tsParseTypeOperatorOrHigher(): N.TsType { - const operator = ["keyof", "unique", "readonly"].find(kw => - this.isContextual(kw), - ); - return operator - ? this.tsParseTypeOperator(operator) - : this.isContextual("infer") + const isTypeOperator = + tokenIsTSTypeOperator(this.state.type) && !this.state.containsEsc; + return isTypeOperator + ? this.tsParseTypeOperator() + : this.isContextual(tt._infer) ? this.tsParseInferType() : this.tsParseArrayTypeOrHigher(); } @@ -1164,7 +1178,7 @@ export default (superClass: Class): Class => } tsSkipParameterStart(): boolean { - if (this.match(tt.name) || this.match(tt._this)) { + if (tokenIsIdentifier(this.state.type) || this.match(tt._this)) { this.next(); return true; } @@ -1309,19 +1323,19 @@ export default (superClass: Class): Class => tsParseTypePredicatePrefix(): ?N.Identifier { const id = this.parseIdentifier(); - if (this.isContextual("is") && !this.hasPrecedingLineBreak()) { + if (this.isContextual(tt._is) && !this.hasPrecedingLineBreak()) { this.next(); return id; } } tsParseTypePredicateAsserts(): boolean { - if (!this.match(tt.name) || this.state.value !== "asserts") { + if (this.state.type !== tt._asserts) { return false; } const containsEsc = this.state.containsEsc; this.next(); - if (!this.match(tt.name) && !this.match(tt._this)) { + if (!tokenIsIdentifier(this.state.type) && !this.match(tt._this)) { return false; } @@ -1366,7 +1380,9 @@ export default (superClass: Class): Class => } isAbstractConstructorSignature(): boolean { - return this.isContextual("abstract") && this.lookahead().type === tt._new; + return ( + this.isContextual(tt._abstract) && this.lookahead().type === tt._new + ); } tsParseNonConditionalType(): N.TsType { @@ -1427,7 +1443,7 @@ export default (superClass: Class): Class => tsParseInterfaceDeclaration( node: N.TsInterfaceDeclaration, ): N.TsInterfaceDeclaration { - if (this.match(tt.name)) { + if (tokenIsIdentifier(this.state.type)) { node.id = this.parseIdentifier(); this.checkLVal( node.id, @@ -1460,7 +1476,7 @@ export default (superClass: Class): Class => this.expect(tt.eq); if ( - this.isContextual("intrinsic") && + this.isContextual(tt._intrinsic) && this.lookahead().type !== tt.dot ) { const node: N.TsKeywordType = this.startNode(); @@ -1599,7 +1615,7 @@ export default (superClass: Class): Class => tsParseAmbientExternalModuleDeclaration( node: N.TsModuleDeclaration, ): N.TsModuleDeclaration { - if (this.isContextual("global")) { + if (this.isContextual(tt._global)) { node.global = true; node.id = this.parseIdentifier(); } else if (this.match(tt.string)) { @@ -1642,7 +1658,7 @@ export default (superClass: Class): Class => tsIsExternalModuleReference(): boolean { return ( - this.isContextual("require") && + this.isContextual(tt._require) && this.lookaheadCharCode() === charCodes.leftParenthesis ); } @@ -1655,7 +1671,7 @@ export default (superClass: Class): Class => tsParseExternalModuleReference(): N.TsExternalModuleReference { const node: N.TsExternalModuleReference = this.startNode(); - this.expectContextual("require"); + this.expectContextual(tt._require); this.expect(tt.parenL); if (!this.match(tt.string)) { throw this.unexpected(); @@ -1701,7 +1717,7 @@ export default (superClass: Class): Class => let starttype = this.state.type; let kind; - if (this.isContextual("let")) { + if (this.isContextual(tt._let)) { starttype = tt._var; kind = "let"; } @@ -1728,19 +1744,22 @@ export default (superClass: Class): Class => if (this.match(tt._const) && this.isLookaheadContextual("enum")) { // `const enum = 0;` not allowed because "enum" is a strict mode reserved word. this.expect(tt._const); - this.expectContextual("enum"); + this.expectContextual(tt._enum); return this.tsParseEnumDeclaration(nany, /* isConst */ true); } // falls through case tt._var: kind = kind || this.state.value; return this.parseVarStatement(nany, kind); - case tt.name: { - const value = this.state.value; - if (value === "global") { - return this.tsParseAmbientExternalModuleDeclaration(nany); - } else { - return this.tsParseDeclaration(nany, value, /* next */ true); + case tt._global: + return this.tsParseAmbientExternalModuleDeclaration(nany); + default: { + if (tokenIsIdentifier(starttype)) { + return this.tsParseDeclaration( + nany, + this.state.value, + /* next */ true, + ); } } } @@ -1798,21 +1817,24 @@ export default (superClass: Class): Class => case "abstract": if ( this.tsCheckLineTerminator(next) && - (this.match(tt._class) || this.match(tt.name)) + (this.match(tt._class) || tokenIsIdentifier(this.state.type)) ) { return this.tsParseAbstractDeclaration(node); } break; case "enum": - if (next || this.match(tt.name)) { + if (next || tokenIsIdentifier(this.state.type)) { if (next) this.next(); return this.tsParseEnumDeclaration(node, /* isConst */ false); } break; case "interface": - if (this.tsCheckLineTerminator(next) && this.match(tt.name)) { + if ( + this.tsCheckLineTerminator(next) && + tokenIsIdentifier(this.state.type) + ) { return this.tsParseInterfaceDeclaration(node); } break; @@ -1821,20 +1843,26 @@ export default (superClass: Class): Class => if (this.tsCheckLineTerminator(next)) { if (this.match(tt.string)) { return this.tsParseAmbientExternalModuleDeclaration(node); - } else if (this.match(tt.name)) { + } else if (tokenIsIdentifier(this.state.type)) { return this.tsParseModuleOrNamespaceDeclaration(node); } } break; case "namespace": - if (this.tsCheckLineTerminator(next) && this.match(tt.name)) { + if ( + this.tsCheckLineTerminator(next) && + tokenIsIdentifier(this.state.type) + ) { return this.tsParseModuleOrNamespaceDeclaration(node); } break; case "type": - if (this.tsCheckLineTerminator(next) && this.match(tt.name)) { + if ( + this.tsCheckLineTerminator(next) && + tokenIsIdentifier(this.state.type) + ) { return this.tsParseTypeAliasDeclaration(node); } break; @@ -1907,20 +1935,7 @@ export default (superClass: Class): Class => } tsIsDeclarationStart(): boolean { - if (this.match(tt.name)) { - switch (this.state.value) { - case "abstract": - case "declare": - case "enum": - case "interface": - case "module": - case "namespace": - case "type": - return true; - } - } - - return false; + return tokenIsTSDeclarationStart(this.state.type); } // ====================================================== @@ -2197,7 +2212,7 @@ export default (superClass: Class): Class => if ( tokenOperatorPrecedence(tt._in) > minPrec && !this.hasPrecedingLineBreak() && - this.isContextual("as") + this.isContextual(tt._as) ) { const node: N.TsAsExpression = this.startNodeAt( leftStartPos, @@ -2244,15 +2259,19 @@ export default (superClass: Class): Class => parseImport(node: N.Node): N.AnyImport { node.importKind = "value"; - if (this.match(tt.name) || this.match(tt.star) || this.match(tt.braceL)) { + if ( + tokenIsIdentifier(this.state.type) || + this.match(tt.star) || + this.match(tt.braceL) + ) { let ahead = this.lookahead(); if ( - this.isContextual("type") && + this.isContextual(tt._type) && // import type, { a } from "b"; ahead.type !== tt.comma && // import type from "a"; - !(ahead.type === tt.name && ahead.value === "from") && + ahead.type !== tt._from && // import type = require("a"); ahead.type !== tt.eq ) { @@ -2261,7 +2280,7 @@ export default (superClass: Class): Class => ahead = this.lookahead(); } - if (this.match(tt.name) && ahead.type === tt.eq) { + if (tokenIsIdentifier(this.state.type) && ahead.type === tt.eq) { return this.tsParseImportEqualsDeclaration(node); } } @@ -2290,7 +2309,7 @@ export default (superClass: Class): Class => // `export import A = B;` this.next(); // eat `tt._import` if ( - this.isContextual("type") && + this.isContextual(tt._type) && this.lookaheadCharCode() !== charCodes.equalsTo ) { node.importKind = "type"; @@ -2305,16 +2324,19 @@ export default (superClass: Class): Class => assign.expression = this.parseExpression(); this.semicolon(); return this.finishNode(assign, "TSExportAssignment"); - } else if (this.eatContextual("as")) { + } else if (this.eatContextual(tt._as)) { // `export as namespace A;` const decl: N.TsNamespaceExportDeclaration = node; // See `parseNamespaceExportDeclaration` in TypeScript's own parser - this.expectContextual("namespace"); + this.expectContextual(tt._namespace); decl.id = this.parseIdentifier(); this.semicolon(); return this.finishNode(decl, "TSNamespaceExportDeclaration"); } else { - if (this.isContextual("type") && this.lookahead().type === tt.braceL) { + if ( + this.isContextual(tt._type) && + this.lookahead().type === tt.braceL + ) { this.next(); node.exportKind = "type"; } else { @@ -2327,7 +2349,7 @@ export default (superClass: Class): Class => isAbstractClass(): boolean { return ( - this.isContextual("abstract") && this.lookahead().type === tt._class + this.isContextual(tt._abstract) && this.lookahead().type === tt._class ); } @@ -2342,7 +2364,7 @@ export default (superClass: Class): Class => // export default interface allowed in: // https://github.com/Microsoft/TypeScript/pull/16040 - if (this.state.value === "interface") { + if (this.match(tt._interface)) { const interfaceNode = this.startNode(); this.next(); const result = this.tsParseInterfaceDeclaration(interfaceNode); @@ -2355,10 +2377,10 @@ export default (superClass: Class): Class => parseStatementContent(context: ?string, topLevel: ?boolean): N.Statement { if (this.state.type === tt._const) { const ahead = this.lookahead(); - if (ahead.type === tt.name && ahead.value === "enum") { + if (ahead.type === tt._enum) { const node: N.TsEnumDeclaration = this.startNode(); - this.expect(tt._const); - this.expectContextual("enum"); + this.next(); // eat 'const' + this.expectContextual(tt._enum); return this.tsParseEnumDeclaration(node, /* isConst */ true); } } @@ -2380,7 +2402,7 @@ export default (superClass: Class): Class => tsIsStartOfStaticBlocks() { return ( - this.isContextual("static") && + this.isContextual(tt._static) && this.lookaheadCharCode() === charCodes.leftCurlyBrace ); } @@ -2584,11 +2606,11 @@ export default (superClass: Class): Class => const startLoc = this.state.startLoc; // "export declare" is equivalent to just "export". - const isDeclare = this.eatContextual("declare"); + const isDeclare = this.eatContextual(tt._declare); if ( isDeclare && - (this.isContextual("declare") || !this.shouldParseExportDeclaration()) + (this.isContextual(tt._declare) || !this.shouldParseExportDeclaration()) ) { throw this.raise( this.state.start, @@ -2598,7 +2620,7 @@ export default (superClass: Class): Class => let declaration: ?N.Declaration; - if (this.match(tt.name)) { + if (tokenIsIdentifier(this.state.type)) { declaration = this.tsTryParseExportDeclaration(); } if (!declaration) { @@ -2628,7 +2650,7 @@ export default (superClass: Class): Class => isStatement: boolean, optionalId: ?boolean, ): void { - if ((!isStatement || optionalId) && this.isContextual("implements")) { + if ((!isStatement || optionalId) && this.isContextual(tt._implements)) { return; } @@ -2738,7 +2760,7 @@ export default (superClass: Class): Class => if (node.superClass && this.isRelational("<")) { node.superTypeParameters = this.tsParseTypeArguments(); } - if (this.eatContextual("implements")) { + if (this.eatContextual(tt._implements)) { node.implements = this.tsParseHeritageClause("implements"); } } @@ -3225,7 +3247,7 @@ export default (superClass: Class): Class => /* isStatement */ true, /* optionalId */ false, ); - } else if (this.isContextual("interface")) { + } else if (this.isContextual(tt._interface)) { // for invalid abstract interface // To avoid diff --git a/packages/babel-parser/src/plugins/v8intrinsic.js b/packages/babel-parser/src/plugins/v8intrinsic.js index 97de881c7502..024e6a55bce3 100644 --- a/packages/babel-parser/src/plugins/v8intrinsic.js +++ b/packages/babel-parser/src/plugins/v8intrinsic.js @@ -1,5 +1,5 @@ import type Parser from "../parser"; -import { tt } from "../tokenizer/types"; +import { tokenIsIdentifier, tt } from "../tokenizer/types"; import * as N from "../types"; export default (superClass: Class): Class => @@ -9,8 +9,8 @@ export default (superClass: Class): Class => const v8IntrinsicStart = this.state.start; // let the `loc` of Identifier starts from `%` const node = this.startNode(); - this.eat(tt.modulo); - if (this.match(tt.name)) { + this.next(); // eat '%' + if (tokenIsIdentifier(this.state.type)) { const name = this.parseIdentifierName(this.state.start); const identifier = this.createIdentifier(node, name); identifier.type = "V8IntrinsicIdentifier"; diff --git a/packages/babel-parser/src/tokenizer/index.js b/packages/babel-parser/src/tokenizer/index.js index 24a45ec9e981..f8092cf585a0 100644 --- a/packages/babel-parser/src/tokenizer/index.js +++ b/packages/babel-parser/src/tokenizer/index.js @@ -180,8 +180,13 @@ export default class Tokenizer extends ParserErrors { } } - // TODO - + /** + * Whether current token matches given type + * + * @param {TokenType} type + * @returns {boolean} + * @memberof Tokenizer + */ match(type: TokenType): boolean { return this.state.type === type; } @@ -1565,8 +1570,14 @@ export default class Tokenizer extends ParserErrors { readWord(firstCode: number | void): void { const word = this.readWord1(firstCode); - const type = keywordTypes.get(word) || tt.name; - this.finishToken(type, word); + const type = keywordTypes.get(word); + if (type !== undefined) { + // We don't use word as state.value here because word is a dynamic string + // while token label is a shared constant string + this.finishToken(type, tokenLabelName(type)); + } else { + this.finishToken(tt.name, word); + } } checkKeywordEscapes(): void { diff --git a/packages/babel-parser/src/tokenizer/types.js b/packages/babel-parser/src/tokenizer/types.js index e58f2b7d5ca6..8f0efc4969d9 100644 --- a/packages/babel-parser/src/tokenizer/types.js +++ b/packages/babel-parser/src/tokenizer/types.js @@ -78,6 +78,7 @@ export class ExportedTokenType { } } +// A map from keyword/keyword-like string value to the token type export const keywords = new Map(); function createKeyword(name: string, options: TokenOptions = {}): TokenType { @@ -111,19 +112,27 @@ function createToken(name: string, options: TokenOptions = {}): TokenType { return tokenTypeCounter; } +function createKeywordLike( + name: string, + options: TokenOptions = {}, +): TokenType { + ++tokenTypeCounter; + keywords.set(name, tokenTypeCounter); + tokenLabels.push(name); + tokenBinops.push(options.binop ?? -1); + tokenBeforeExprs.push(options.beforeExpr ?? false); + tokenStartsExprs.push(options.startsExpr ?? false); + tokenPrefixes.push(options.prefix ?? false); + // In the exported token type, we set the label as "name" for backward compatibility with Babel 7 + tokenTypes.push(new ExportedTokenType("name", options)); + + return tokenTypeCounter; +} + // For performance the token type helpers depend on the following declarations order. // When adding new token types, please also check if the token helpers need update. export const tt: { [name: string]: TokenType } = { - num: createToken("num", { startsExpr }), - bigint: createToken("bigint", { startsExpr }), - decimal: createToken("decimal", { startsExpr }), - regexp: createToken("regexp", { startsExpr }), - string: createToken("string", { startsExpr }), - name: createToken("name", { startsExpr }), - privateName: createToken("#name", { startsExpr }), - eof: createToken("eof"), - // Punctuation token types. bracketL: createToken("[", { beforeExpr, startsExpr }), bracketHashL: createToken("#[", { beforeExpr, startsExpr }), @@ -207,6 +216,7 @@ export const tt: { [name: string]: TokenType } = { // Keywords // Don't forget to update packages/babel-helper-validator-identifier/src/keyword.js // when new keywords are added + // start: isLiteralPropertyName // start: isKeyword _in: createKeyword("in", { beforeExpr, binop: 7 }), _instanceof: createKeyword("instanceof", { beforeExpr, binop: 7 }), @@ -248,6 +258,63 @@ export const tt: { [name: string]: TokenType } = { // end: isLoop // end: isKeyword + // Primary literals + // start: isIdentifier + _as: createKeywordLike("as", { startsExpr }), + _assert: createKeywordLike("assert", { startsExpr }), + _async: createKeywordLike("async", { startsExpr }), + _await: createKeywordLike("await", { startsExpr }), + _from: createKeywordLike("from", { startsExpr }), + _get: createKeywordLike("get", { startsExpr }), + _let: createKeywordLike("let", { startsExpr }), + _meta: createKeywordLike("meta", { startsExpr }), + _of: createKeywordLike("of", { startsExpr }), + _sent: createKeywordLike("sent", { startsExpr }), + _set: createKeywordLike("set", { startsExpr }), + _static: createKeywordLike("static", { startsExpr }), + _yield: createKeywordLike("yield", { startsExpr }), + + // Flow and TypeScript Keywordlike + _asserts: createKeywordLike("asserts", { startsExpr }), + _checks: createKeywordLike("checks", { startsExpr }), + _exports: createKeywordLike("exports", { startsExpr }), + _global: createKeywordLike("global", { startsExpr }), + _implements: createKeywordLike("implements", { startsExpr }), + _intrinsic: createKeywordLike("intrinsic", { startsExpr }), + _infer: createKeywordLike("infer", { startsExpr }), + _is: createKeywordLike("is", { startsExpr }), + _mixins: createKeywordLike("mixins", { startsExpr }), + _proto: createKeywordLike("proto", { startsExpr }), + _require: createKeywordLike("require", { startsExpr }), + // start: isTSTypeOperator + _keyof: createKeywordLike("keyof", { startsExpr }), + _readonly: createKeywordLike("readonly", { startsExpr }), + _unique: createKeywordLike("unique", { startsExpr }), + // end: isTSTypeOperator + // start: isTSDeclarationStart + _abstract: createKeywordLike("abstract", { startsExpr }), + _declare: createKeywordLike("declare", { startsExpr }), + _enum: createKeywordLike("enum", { startsExpr }), + _module: createKeywordLike("module", { startsExpr }), + _namespace: createKeywordLike("namespace", { startsExpr }), + // start: isFlowInterfaceOrTypeOrOpaque + _interface: createKeywordLike("interface", { startsExpr }), + _type: createKeywordLike("type", { startsExpr }), + // end: isTSDeclarationStart + _opaque: createKeywordLike("opaque", { startsExpr }), + // end: isFlowInterfaceOrTypeOrOpaque + name: createToken("name", { startsExpr }), + // end: isIdentifier + + string: createToken("string", { startsExpr }), + num: createToken("num", { startsExpr }), + bigint: createToken("bigint", { startsExpr }), + decimal: createToken("decimal", { startsExpr }), + // end: isLiteralPropertyName + regexp: createToken("regexp", { startsExpr }), + privateName: createToken("#name", { startsExpr }), + eof: createToken("eof"), + // jsx plugin jsxName: createToken("jsxName"), jsxText: createToken("jsxText", { beforeExpr: true }), @@ -258,6 +325,18 @@ export const tt: { [name: string]: TokenType } = { placeholder: createToken("%%", { startsExpr: true }), }; +export function tokenIsIdentifier(token: TokenType): boolean { + return token >= tt._as && token <= tt.name; +} + +export function tokenIsKeywordOrIdentifier(token: TokenType): boolean { + return token >= tt._in && token <= tt.name; +} + +export function tokenIsLiteralPropertyName(token: TokenType): boolean { + return token >= tt._in && token <= tt.decimal; +} + export function tokenComesBeforeExpression(token: TokenType): boolean { return tokenBeforeExprs[token]; } @@ -270,6 +349,10 @@ export function tokenIsAssignment(token: TokenType): boolean { return token >= tt.eq && token <= tt.moduloAssign; } +export function tokenIsFlowInterfaceOrTypeOrOpaque(token: TokenType): boolean { + return token >= tt._interface && token <= tt._opaque; +} + export function tokenIsLoop(token: TokenType): boolean { return token >= tt._do && token <= tt._while; } @@ -290,6 +373,14 @@ export function tokenIsPrefix(token: TokenType): boolean { return tokenPrefixes[token]; } +export function tokenIsTSTypeOperator(token: TokenType): boolean { + return token >= tt._keyof && token <= tt._unique; +} + +export function tokenIsTSDeclarationStart(token: TokenType): boolean { + return token >= tt._abstract && token <= tt._type; +} + export function tokenLabelName(token: TokenType): string { return tokenLabels[token]; } diff --git a/packages/babel-parser/test/fixtures/flow/interface-types/escape-in-interface/input.js b/packages/babel-parser/test/fixtures/flow/interface-types/escape-in-interface/input.js new file mode 100644 index 000000000000..b2451f12687c --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/interface-types/escape-in-interface/input.js @@ -0,0 +1 @@ +interf\u{61}ce A {} diff --git a/packages/babel-parser/test/fixtures/flow/interface-types/escape-in-interface/output.json b/packages/babel-parser/test/fixtures/flow/interface-types/escape-in-interface/output.json new file mode 100644 index 000000000000..58f646e999a9 --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/interface-types/escape-in-interface/output.json @@ -0,0 +1,38 @@ +{ + "type": "File", + "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "errors": [ + "SyntaxError: Unexpected reserved word 'interface'. (1:0)" + ], + "program": { + "type": "Program", + "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "InterfaceDeclaration", + "start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "id": { + "type": "Identifier", + "start":15,"end":16,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":16},"identifierName":"A"}, + "name": "A" + }, + "typeParameters": null, + "extends": [], + "implements": [], + "mixins": [], + "body": { + "type": "ObjectTypeAnnotation", + "start":17,"end":19,"loc":{"start":{"line":1,"column":17},"end":{"line":1,"column":19}}, + "callProperties": [], + "properties": [], + "indexers": [], + "internalSlots": [], + "exact": false + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/flow/object-types/getter-key-is-keyword/input.js b/packages/babel-parser/test/fixtures/flow/object-types/getter-key-is-keyword/input.js new file mode 100644 index 000000000000..987f93bff76c --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/object-types/getter-key-is-keyword/input.js @@ -0,0 +1,3 @@ +type B = { + get if(): number; +} diff --git a/packages/babel-parser/test/fixtures/flow/object-types/getter-key-is-keyword/output.json b/packages/babel-parser/test/fixtures/flow/object-types/getter-key-is-keyword/output.json new file mode 100644 index 000000000000..4fc82447328b --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/object-types/getter-key-is-keyword/output.json @@ -0,0 +1,60 @@ +{ + "type": "File", + "start":0,"end":32,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "program": { + "type": "Program", + "start":0,"end":32,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "TypeAlias", + "start":0,"end":32,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "id": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":6},"identifierName":"B"}, + "name": "B" + }, + "typeParameters": null, + "right": { + "type": "ObjectTypeAnnotation", + "start":9,"end":32,"loc":{"start":{"line":1,"column":9},"end":{"line":3,"column":1}}, + "callProperties": [], + "properties": [ + { + "type": "ObjectTypeProperty", + "start":13,"end":29,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":18}}, + "key": { + "type": "Identifier", + "start":17,"end":19,"loc":{"start":{"line":2,"column":6},"end":{"line":2,"column":8},"identifierName":"if"}, + "name": "if" + }, + "static": false, + "proto": false, + "kind": "get", + "method": true, + "value": { + "type": "FunctionTypeAnnotation", + "start":13,"end":29,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":18}}, + "params": [], + "rest": null, + "typeParameters": null, + "this": null, + "returnType": { + "type": "NumberTypeAnnotation", + "start":23,"end":29,"loc":{"start":{"line":2,"column":12},"end":{"line":2,"column":18}} + } + }, + "optional": false + } + ], + "indexers": [], + "internalSlots": [], + "exact": false, + "inexact": false + } + } + ], + "directives": [] + } +} \ No newline at end of file