Tokenize keywords-like identifier as new tokens (#13769)

* refactor: add more identifier token helpers

* refactor: explode tt.name into multiple tokens

* fix: disallow escape in interface keyword

* refactor: simplify isMaybeDefaultImport

* review comments

* refactor: avoid string comparison
This commit is contained in:
Huáng Jùnliàng 2021-09-23 10:54:44 -04:00 committed by GitHub
parent 613ae6fac7
commit 178d43ff17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 555 additions and 339 deletions

View File

@ -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,9 +1253,73 @@ export default class ExpressionParser extends LValParser {
// fall through
default:
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();
}
}
}
// This helper method attempts to finish the given `node`
// into a topic-reference node for the given `pipeProposal`.
@ -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();
}

View File

@ -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;

View File

@ -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);
}
/*

View File

@ -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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
}
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
this.parseImport(bodyNode);
} else {
this.expectContextual(
"declare",
tt._declare,
FlowErrors.UnsupportedStatementInDeclareModule,
);
@ -492,7 +493,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
} 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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
} 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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
}
}
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<Parser>): Class<Parser> =>
} 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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
// 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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
// 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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
// 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<Parser>): Class<Parser> =>
}
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<Parser>): Class<Parser> =>
// 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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
return super.shouldParseDefaultImport(node);
}
return isMaybeDefaultImport(this.state);
return isMaybeDefaultImport(this.state.type);
}
parseImportSpecifierLocal(
@ -2612,21 +2603,22 @@ export default (superClass: Class<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
}
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<Parser>): Class<Parser> =>
} 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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
}: {
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,

View File

@ -163,7 +163,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// 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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
if (!hasStarImport) this.parseNamedImportSpecifiers(node);
}
this.expectContextual("from");
this.expectContextual(tt._from);
node.source = this.parseImportSource();
this.semicolon();
return this.finishNode(node, "ImportDeclaration");

View File

@ -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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
}
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<Parser>): Class<Parser> =>
}
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
}
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
tsIsExternalModuleReference(): boolean {
return (
this.isContextual("require") &&
this.isContextual(tt._require) &&
this.lookaheadCharCode() === charCodes.leftParenthesis
);
}
@ -1655,7 +1671,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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") {
case tt._global:
return this.tsParseAmbientExternalModuleDeclaration(nany);
} else {
return this.tsParseDeclaration(nany, value, /* next */ true);
default: {
if (tokenIsIdentifier(starttype)) {
return this.tsParseDeclaration(
nany,
this.state.value,
/* next */ true,
);
}
}
}
@ -1798,21 +1817,24 @@ export default (superClass: Class<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
}
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
// `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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
// 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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
tsIsStartOfStaticBlocks() {
return (
this.isContextual("static") &&
this.isContextual(tt._static) &&
this.lookaheadCharCode() === charCodes.leftCurlyBrace
);
}
@ -2584,11 +2606,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
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<Parser>): Class<Parser> =>
/* isStatement */ true,
/* optionalId */ false,
);
} else if (this.isContextual("interface")) {
} else if (this.isContextual(tt._interface)) {
// for invalid abstract interface
// To avoid

View File

@ -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<Parser>): Class<Parser> =>
@ -9,8 +9,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
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";

View File

@ -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 {

View File

@ -78,6 +78,7 @@ export class ExportedTokenType {
}
}
// A map from keyword/keyword-like string value to the token type
export const keywords = new Map<string, TokenType>();
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];
}

View File

@ -0,0 +1 @@
interf\u{61}ce A {}

View File

@ -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": []
}
}

View File

@ -0,0 +1,3 @@
type B = {
get if(): number;
}

View File

@ -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": []
}
}