From 5e0b2fbb9562c6b81393e339d780e194665e9793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 15 Sep 2021 11:49:55 -0400 Subject: [PATCH 1/6] refactor: add more identifier token helpers --- .../babel-parser/src/parser/expression.js | 135 ++++++++++-------- packages/babel-parser/src/parser/statement.js | 22 +-- packages/babel-parser/src/parser/util.js | 20 ++- .../babel-parser/src/plugins/flow/index.js | 58 ++++---- .../src/plugins/typescript/index.js | 53 ++++--- .../babel-parser/src/plugins/v8intrinsic.js | 6 +- packages/babel-parser/src/tokenizer/index.js | 10 +- packages/babel-parser/src/tokenizer/types.js | 34 +++-- .../getter-key-is-keyword/input.js | 3 + .../getter-key-is-keyword/output.json | 60 ++++++++ 10 files changed, 256 insertions(+), 145 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/flow/object-types/getter-key-is-keyword/input.js create mode 100644 packages/babel-parser/test/fixtures/flow/object-types/getter-key-is-keyword/output.json diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 2fbd4483393b..6f0007f733ef 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, @@ -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; } @@ -447,7 +450,7 @@ export default class ExpressionParser extends LValParser { this.getPluginOption("pipelineOperator", "proposal") === "minimal" ) { if ( - this.match(tt.name) && + tokenIsIdentifier(this.state.type) && this.state.value === "await" && this.prodParam.hasAwait ) { @@ -1053,7 +1056,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 +1078,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 +1257,71 @@ export default class ExpressionParser extends LValParser { // fall through default: - throw this.unexpected(); + if (tokenIsIdentifier(type)) { + 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() + ) { + 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(); + } } } @@ -2537,10 +2550,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..514fd1580884 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, @@ -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) ) { @@ -609,7 +610,7 @@ 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; + tokenIsIdentifier(this.state.type) && !this.state.containsEsc; const refExpressionErrors = new ExpressionErrors(); const init = this.parseExpression(true, refExpressionErrors); @@ -1219,7 +1220,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 { @@ -1465,7 +1468,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 +1762,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); @@ -1941,7 +1945,7 @@ export default class StatementParser extends ExpressionParser { } isExportDefaultSpecifier(): boolean { - if (this.match(tt.name)) { + if (tokenIsIdentifier(this.state.type)) { const value = this.state.value; if ((value === "async" && !this.state.containsEsc) || value === "let") { return false; @@ -1956,7 +1960,7 @@ export default class StatementParser extends ExpressionParser { // note that this approach may fail on some pedantic cases // export type from = number if ( - (l.type === tt.name && l.value !== "from") || + (tokenIsIdentifier(l.type) && l.value !== "from") || l.type === tt.braceL ) { this.expectOnePlugin(["flow", "typescript"]); @@ -1971,7 +1975,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; } @@ -2249,7 +2253,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( diff --git a/packages/babel-parser/src/parser/util.js b/packages/babel-parser/src/parser/util.js index 0ff740686c4d..2a177ede9bfe 100644 --- a/packages/babel-parser/src/parser/util.js +++ b/packages/babel-parser/src/parser/util.js @@ -2,7 +2,8 @@ import { isTokenType, - tokenIsKeyword, + tokenIsIdentifier, + tokenIsLiteralPropertyName, tokenLabelName, tt, type TokenType, @@ -70,7 +71,7 @@ export default class UtilParser extends Tokenizer { isContextual(name: string): boolean { return ( - this.match(tt.name) && + tokenIsIdentifier(this.state.type) && this.state.value === name && !this.state.containsEsc ); @@ -99,7 +100,11 @@ export default class UtilParser extends Tokenizer { // Consumes contextual keyword if possible. eatContextual(name: string): boolean { - return this.isContextual(name) && this.eat(tt.name); + if (this.isContextual(name)) { + this.next(); + return true; + } + return false; } // Asserts that following token is given contextual keyword. @@ -306,14 +311,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..39bbbdacae63 100644 --- a/packages/babel-parser/src/plugins/flow/index.js +++ b/packages/babel-parser/src/plugins/flow/index.js @@ -7,7 +7,10 @@ import type Parser from "../../parser"; import { + tokenIsIdentifier, tokenIsKeyword, + tokenIsKeywordOrIdentifier, + tokenIsLiteralPropertyName, tokenLabelName, tt, type TokenType, @@ -16,7 +19,7 @@ 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, @@ -161,10 +164,7 @@ function hasTypeImportKind(node: N.Node): boolean { } function isMaybeDefaultImport(state: { type: TokenType, value: any }): boolean { - return ( - (state.type === tt.name || tokenIsKeyword(state.type)) && - state.value !== "from" - ); + return tokenIsKeywordOrIdentifier(state.type) && state.value !== "from"; } const exportSuggestions = { @@ -1061,11 +1061,7 @@ export default (superClass: Class): Class => if (this.isContextual("get") || this.isContextual("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 +1428,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 +1475,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 +1603,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("interface")) { + return this.flowParseInterfaceType(); + } + + return this.flowIdentToTypeAnnotation( + startPos, + startLoc, + node, + this.parseIdentifier(), + ); } } @@ -1825,11 +1820,11 @@ export default (superClass: Class): Class => // strict mode handling of `interface` since it's a reserved word if ( this.state.strict && - this.match(tt.name) && + tokenIsIdentifier(this.state.type) && this.state.value === "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); @@ -1856,14 +1851,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") { @@ -1890,7 +1885,7 @@ export default (superClass: Class): Class => isExportDefaultSpecifier(): boolean { if ( - this.match(tt.name) && + tokenIsIdentifier(this.state.type) && (this.state.value === "type" || this.state.value === "interface" || this.state.value === "opaque" || @@ -2656,7 +2651,7 @@ export default (superClass: Class): Class => const as_ident = this.parseIdentifier(true); if ( specifierTypeKind !== null && - !this.match(tt.name) && + !tokenIsIdentifier(this.state.type) && !tokenIsKeyword(this.state.type) ) { // `import {type as ,` or `import {type as }` @@ -2672,7 +2667,8 @@ export default (superClass: Class): Class => } else { if ( specifierTypeKind !== null && - (this.match(tt.name) || tokenIsKeyword(this.state.type)) + (tokenIsIdentifier(this.state.type) || + tokenIsKeyword(this.state.type)) ) { // `import {type foo` specifier.imported = this.parseIdentifier(true); @@ -3547,7 +3543,7 @@ export default (superClass: Class): Class => enumName: string, }): EnumExplicitType { if (this.eatContextual("of")) { - if (!this.match(tt.name)) { + if (!tokenIsIdentifier(this.state.type)) { throw this.flowEnumErrorInvalidExplicitType(this.state.start, { enumName, suppliedType: null, diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index bc716eb6701d..7d317c17a2a9 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -5,7 +5,7 @@ // Error messages are colocated with the plugin. /* eslint-disable @babel/development-internal/dry-error-messages */ -import type { TokenType } from "../../tokenizer/types"; +import { tokenIsIdentifier, TokenType } from "../../tokenizer/types"; import type State from "../../tokenizer/state"; import { tokenOperatorPrecedence, tt } from "../../tokenizer/types"; import { types as ct } from "../../tokenizer/context"; @@ -206,7 +206,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 +235,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 +597,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 { @@ -1164,7 +1168,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; } @@ -1316,12 +1320,12 @@ export default (superClass: Class): Class => } tsParseTypePredicateAsserts(): boolean { - if (!this.match(tt.name) || this.state.value !== "asserts") { + if (!tokenIsIdentifier(this.state.type) || this.state.value !== "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; } @@ -1427,7 +1431,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, @@ -1798,21 +1802,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 +1828,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,7 +1920,7 @@ export default (superClass: Class): Class => } tsIsDeclarationStart(): boolean { - if (this.match(tt.name)) { + if (tokenIsIdentifier(this.state.type)) { switch (this.state.value) { case "abstract": case "declare": @@ -2244,7 +2257,11 @@ 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 ( @@ -2261,7 +2278,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); } } @@ -2598,7 +2615,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) { 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..19d59e872da3 100644 --- a/packages/babel-parser/src/tokenizer/index.js +++ b/packages/babel-parser/src/tokenizer/index.js @@ -1565,8 +1565,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..bc5228b42a91 100644 --- a/packages/babel-parser/src/tokenizer/types.js +++ b/packages/babel-parser/src/tokenizer/types.js @@ -115,15 +115,6 @@ function createToken(name: string, options: TokenOptions = {}): TokenType { // 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 }), @@ -248,6 +239,19 @@ export const tt: { [name: string]: TokenType } = { // end: isLoop // end: isKeyword + // Primary literals + // start: isIdentifier + name: createToken("name", { startsExpr }), + // end: isIdentifier + + string: createToken("string", { startsExpr }), + num: createToken("num", { startsExpr }), + bigint: createToken("bigint", { startsExpr }), + decimal: createToken("decimal", { startsExpr }), + regexp: createToken("regexp", { startsExpr }), + privateName: createToken("#name", { startsExpr }), + eof: createToken("eof"), + // jsx plugin jsxName: createToken("jsxName"), jsxText: createToken("jsxText", { beforeExpr: true }), @@ -258,6 +262,18 @@ export const tt: { [name: string]: TokenType } = { placeholder: createToken("%%", { startsExpr: true }), }; +export function tokenIsIdentifier(token: TokenType): boolean { + return 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]; } 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 From ca89b5c3d8924fb3ddb0200063df77031c24739e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 15 Sep 2021 15:42:19 -0400 Subject: [PATCH 2/6] refactor: explode tt.name into multiple tokens --- .../babel-parser/src/parser/expression.js | 33 ++-- packages/babel-parser/src/parser/statement.js | 54 +++--- packages/babel-parser/src/parser/util.js | 17 +- .../babel-parser/src/plugins/flow/index.js | 76 ++++---- .../babel-parser/src/plugins/placeholders.js | 8 +- .../src/plugins/typescript/index.js | 171 +++++++++--------- packages/babel-parser/src/tokenizer/index.js | 9 +- packages/babel-parser/src/tokenizer/types.js | 74 +++++++- 8 files changed, 255 insertions(+), 187 deletions(-) diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 6f0007f733ef..6eb1d69c12d7 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -275,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) { @@ -449,11 +449,7 @@ export default class ExpressionParser extends LValParser { op === tt.pipeline && this.getPluginOption("pipelineOperator", "proposal") === "minimal" ) { - if ( - tokenIsIdentifier(this.state.type) && - this.state.value === "await" && - this.prodParam.hasAwait - ) { + if (this.state.type === tt._await && this.prodParam.hasAwait) { throw this.raise( this.state.start, Errors.UnexpectedAwaitAfterPipelineBody, @@ -501,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, @@ -580,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(); @@ -1259,7 +1255,7 @@ export default class ExpressionParser extends LValParser { default: if (tokenIsIdentifier(type)) { if ( - this.isContextual("module") && + this.isContextual(tt._module) && this.lookaheadCharCode() === charCodes.leftCurlyBrace && !this.hasFollowingLineBreak() ) { @@ -1532,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); @@ -1544,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); @@ -1575,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); } diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 514fd1580884..d07690d075c4 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -179,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); @@ -573,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); @@ -586,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(); @@ -596,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); @@ -609,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 = - tokenIsIdentifier(this.state.type) && !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) { @@ -622,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 @@ -1120,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;`. @@ -1133,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, @@ -1408,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)) { @@ -1852,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( @@ -1895,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)) && @@ -1945,23 +1943,23 @@ export default class StatementParser extends ExpressionParser { } isExportDefaultSpecifier(): boolean { - if (tokenIsIdentifier(this.state.type)) { - 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 ( - (tokenIsIdentifier(l.type) && l.value !== "from") || - l.type === tt.braceL + (tokenIsIdentifier(nextType) && nextType !== tt._from) || + nextType === tt.braceL ) { this.expectOnePlugin(["flow", "typescript"]); return false; @@ -1993,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(); @@ -2173,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); @@ -2226,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 @@ -2372,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 { @@ -2405,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, @@ -2443,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 2a177ede9bfe..adb2e94cfaca 100644 --- a/packages/babel-parser/src/parser/util.js +++ b/packages/babel-parser/src/parser/util.js @@ -2,7 +2,6 @@ import { isTokenType, - tokenIsIdentifier, tokenIsLiteralPropertyName, tokenLabelName, tt, @@ -69,12 +68,8 @@ export default class UtilParser extends Tokenizer { // Tests whether parsed token is a contextual keyword. - isContextual(name: string): boolean { - return ( - tokenIsIdentifier(this.state.type) && - 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 { @@ -99,8 +94,8 @@ export default class UtilParser extends Tokenizer { // Consumes contextual keyword if possible. - eatContextual(name: string): boolean { - if (this.isContextual(name)) { + eatContextual(token: TokenType): boolean { + if (this.isContextual(token)) { this.next(); return true; } @@ -109,8 +104,8 @@ export default class UtilParser extends Tokenizer { // 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. diff --git a/packages/babel-parser/src/plugins/flow/index.js b/packages/babel-parser/src/plugins/flow/index.js index 39bbbdacae63..6343f011316c 100644 --- a/packages/babel-parser/src/plugins/flow/index.js +++ b/packages/babel-parser/src/plugins/flow/index.js @@ -266,7 +266,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 +360,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 +369,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 +411,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 +420,7 @@ export default (superClass: Class): Class => this.parseImport(bodyNode); } else { this.expectContextual( - "declare", + tt._declare, FlowErrors.UnsupportedStatementInDeclareModule, ); @@ -492,7 +492,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 +510,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 +519,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 +547,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 +615,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 +707,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 +844,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 +1008,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 +1018,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,7 +1059,7 @@ 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 (tokenIsLiteralPropertyName(lookahead.type)) { kind = this.state.value; @@ -1604,7 +1604,7 @@ export default (superClass: Class): Class => this.next(); return super.createIdentifier(node, label); } else if (tokenIsIdentifier(this.state.type)) { - if (this.isContextual("interface")) { + if (this.isContextual(tt._interface)) { return this.flowParseInterfaceType(); } @@ -1829,7 +1829,7 @@ export default (superClass: Class): Class => 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); @@ -1875,10 +1875,10 @@ export default (superClass: Class): Class => // export type shouldParseExportDeclaration(): boolean { return ( - this.isContextual("type") || - this.isContextual("interface") || - this.isContextual("opaque") || - (this.shouldParseEnums() && this.isContextual("enum")) || + this.isContextual(tt._type) || + this.isContextual(tt._interface) || + this.isContextual(tt._opaque) || + (this.shouldParseEnums() && this.isContextual(tt._enum)) || super.shouldParseExportDeclaration() ); } @@ -1898,7 +1898,7 @@ export default (superClass: Class): Class => } 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); @@ -2119,7 +2119,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(); @@ -2134,19 +2134,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(); @@ -2159,7 +2159,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(); @@ -2191,7 +2191,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; @@ -2451,7 +2451,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 { @@ -2607,7 +2607,7 @@ 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) { @@ -2647,7 +2647,7 @@ 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 && @@ -2687,7 +2687,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; @@ -3542,7 +3542,7 @@ export default (superClass: Class): Class => }: { enumName: string, }): EnumExplicitType { - if (this.eatContextual("of")) { + if (this.eatContextual(tt._of)) { if (!tokenIsIdentifier(this.state.type)) { throw this.flowEnumErrorInvalidExplicitType(this.state.start, { enumName, 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 7d317c17a2a9..1a59d527ae86 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 { tokenIsIdentifier, 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"; @@ -775,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)) { @@ -806,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); @@ -982,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; @@ -991,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: @@ -1054,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(); @@ -1078,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(); @@ -1105,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"); @@ -1113,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(); } @@ -1313,14 +1323,14 @@ 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 (!tokenIsIdentifier(this.state.type) || this.state.value !== "asserts") { + if (this.state.type !== tt._asserts) { return false; } const containsEsc = this.state.containsEsc; @@ -1370,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 { @@ -1464,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(); @@ -1603,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)) { @@ -1646,7 +1658,7 @@ export default (superClass: Class): Class => tsIsExternalModuleReference(): boolean { return ( - this.isContextual("require") && + this.isContextual(tt._require) && this.lookaheadCharCode() === charCodes.leftParenthesis ); } @@ -1659,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(); @@ -1705,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"; } @@ -1732,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, + ); } } } @@ -1920,20 +1935,7 @@ export default (superClass: Class): Class => } tsIsDeclarationStart(): boolean { - if (tokenIsIdentifier(this.state.type)) { - 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); } // ====================================================== @@ -2210,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, @@ -2265,11 +2267,11 @@ export default (superClass: Class): Class => 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 ) { @@ -2307,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"; @@ -2322,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 { @@ -2344,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 ); } @@ -2372,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); } } @@ -2397,7 +2402,7 @@ export default (superClass: Class): Class => tsIsStartOfStaticBlocks() { return ( - this.isContextual("static") && + this.isContextual(tt._static) && this.lookaheadCharCode() === charCodes.leftCurlyBrace ); } @@ -2601,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, @@ -2645,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; } @@ -2755,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"); } } @@ -3242,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/tokenizer/index.js b/packages/babel-parser/src/tokenizer/index.js index 19d59e872da3..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; } diff --git a/packages/babel-parser/src/tokenizer/types.js b/packages/babel-parser/src/tokenizer/types.js index bc5228b42a91..0a9e41c7bdeb 100644 --- a/packages/babel-parser/src/tokenizer/types.js +++ b/packages/babel-parser/src/tokenizer/types.js @@ -111,6 +111,23 @@ 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. @@ -241,6 +258,49 @@ export const tt: { [name: string]: TokenType } = { // 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 @@ -263,7 +323,7 @@ export const tt: { [name: string]: TokenType } = { }; export function tokenIsIdentifier(token: TokenType): boolean { - return token === tt.name; + return token >= tt._as && token <= tt.name; } export function tokenIsKeywordOrIdentifier(token: TokenType): boolean { @@ -286,6 +346,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; } @@ -306,6 +370,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]; } From bebcfb8fbfd22a45c56061000ca9a91a1f4bc1bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 15 Sep 2021 16:32:26 -0400 Subject: [PATCH 3/6] fix: disallow escape in interface keyword --- .../babel-parser/src/plugins/flow/index.js | 12 ++---- .../escape-in-interface/input.js | 1 + .../escape-in-interface/output.json | 38 +++++++++++++++++++ 3 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/flow/interface-types/escape-in-interface/input.js create mode 100644 packages/babel-parser/test/fixtures/flow/interface-types/escape-in-interface/output.json diff --git a/packages/babel-parser/src/plugins/flow/index.js b/packages/babel-parser/src/plugins/flow/index.js index 6343f011316c..0429cba7c1fc 100644 --- a/packages/babel-parser/src/plugins/flow/index.js +++ b/packages/babel-parser/src/plugins/flow/index.js @@ -1818,11 +1818,7 @@ 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 && - tokenIsIdentifier(this.state.type) && - this.state.value === "interface" - ) { + if (this.state.strict && this.isContextual(tt._interface)) { const lookahead = this.lookahead(); if (tokenIsKeywordOrIdentifier(lookahead.type)) { const node = this.startNode(); @@ -2651,8 +2647,7 @@ export default (superClass: Class): Class => const as_ident = this.parseIdentifier(true); if ( specifierTypeKind !== null && - !tokenIsIdentifier(this.state.type) && - !tokenIsKeyword(this.state.type) + !tokenIsKeywordOrIdentifier(this.state.type) ) { // `import {type as ,` or `import {type as }` specifier.imported = as_ident; @@ -2667,8 +2662,7 @@ export default (superClass: Class): Class => } else { if ( specifierTypeKind !== null && - (tokenIsIdentifier(this.state.type) || - tokenIsKeyword(this.state.type)) + tokenIsKeywordOrIdentifier(this.state.type) ) { // `import {type foo` specifier.imported = this.parseIdentifier(true); 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 From 4a83f4b9f253fe69702382db2812662962bb9eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 17 Sep 2021 10:43:52 -0400 Subject: [PATCH 4/6] refactor: simplify isMaybeDefaultImport --- packages/babel-parser/src/plugins/flow/index.js | 15 ++++++++------- packages/babel-parser/src/tokenizer/types.js | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/babel-parser/src/plugins/flow/index.js b/packages/babel-parser/src/plugins/flow/index.js index 0429cba7c1fc..aff52572a5e2 100644 --- a/packages/babel-parser/src/plugins/flow/index.js +++ b/packages/babel-parser/src/plugins/flow/index.js @@ -163,8 +163,8 @@ function hasTypeImportKind(node: N.Node): boolean { return node.importKind === "type" || node.importKind === "typeof"; } -function isMaybeDefaultImport(state: { type: TokenType, value: any }): boolean { - return tokenIsKeywordOrIdentifier(state.type) && state.value !== "from"; +function isMaybeDefaultImport(type: TokenType): boolean { + return tokenIsKeywordOrIdentifier(type) && type !== tt._from; } const exportSuggestions = { @@ -2576,7 +2576,7 @@ export default (superClass: Class): Class => return super.shouldParseDefaultImport(node); } - return isMaybeDefaultImport(this.state); + return isMaybeDefaultImport(this.state.type); } parseImportSpecifierLocal( @@ -2608,16 +2608,17 @@ export default (superClass: Class): Class => } 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; diff --git a/packages/babel-parser/src/tokenizer/types.js b/packages/babel-parser/src/tokenizer/types.js index 0a9e41c7bdeb..5a0915722c8c 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 { From b99a449d5514c2e62608b7a726f004c54272ca29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 17 Sep 2021 11:24:47 -0400 Subject: [PATCH 5/6] review comments --- .../babel-parser/src/plugins/flow/index.js | 26 +++++++++---------- packages/babel-parser/src/tokenizer/types.js | 2 ++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/babel-parser/src/plugins/flow/index.js b/packages/babel-parser/src/plugins/flow/index.js index aff52572a5e2..666a33c3a7fb 100644 --- a/packages/babel-parser/src/plugins/flow/index.js +++ b/packages/babel-parser/src/plugins/flow/index.js @@ -14,6 +14,7 @@ import { tokenLabelName, tt, type TokenType, + tokenIsFlowInterfaceOrTypeOrOpaque, } from "../../tokenizer/types"; import * as N from "../../types"; import type { Position } from "../../util/location"; @@ -1870,24 +1871,23 @@ export default (superClass: Class): Class => // export type shouldParseExportDeclaration(): boolean { - return ( - this.isContextual(tt._type) || - this.isContextual(tt._interface) || - this.isContextual(tt._opaque) || - (this.shouldParseEnums() && this.isContextual(tt._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 ( - tokenIsIdentifier(this.state.type) && - (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(); diff --git a/packages/babel-parser/src/tokenizer/types.js b/packages/babel-parser/src/tokenizer/types.js index 5a0915722c8c..8f0efc4969d9 100644 --- a/packages/babel-parser/src/tokenizer/types.js +++ b/packages/babel-parser/src/tokenizer/types.js @@ -216,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 }), @@ -309,6 +310,7 @@ export const tt: { [name: string]: TokenType } = { 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"), From 6fdea76782b6d7026652ada273c936ab8e4a07e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 17 Sep 2021 11:28:25 -0400 Subject: [PATCH 6/6] refactor: avoid string comparison --- packages/babel-parser/src/plugins/typescript/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index 1a59d527ae86..6d90951f0672 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -2364,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);