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 { import {
tokenCanStartExpression, tokenCanStartExpression,
tokenIsAssignment, tokenIsAssignment,
tokenIsIdentifier,
tokenIsKeyword, tokenIsKeyword,
tokenIsKeywordOrIdentifier,
tokenIsOperator, tokenIsOperator,
tokenIsPostfix, tokenIsPostfix,
tokenIsPrefix, tokenIsPrefix,
@ -273,7 +275,7 @@ export default class ExpressionParser extends LValParser {
): N.Expression { ): N.Expression {
const startPos = this.state.start; const startPos = this.state.start;
const startLoc = this.state.startLoc; const startLoc = this.state.startLoc;
if (this.isContextual("yield")) { if (this.isContextual(tt._yield)) {
if (this.prodParam.hasYield) { if (this.prodParam.hasYield) {
let left = this.parseYield(); let left = this.parseYield();
if (afterLeftParse) { if (afterLeftParse) {
@ -290,8 +292,9 @@ export default class ExpressionParser extends LValParser {
refExpressionErrors = new ExpressionErrors(); refExpressionErrors = new ExpressionErrors();
ownExpressionErrors = true; 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; this.state.potentialArrowAt = this.state.start;
} }
@ -446,11 +449,7 @@ export default class ExpressionParser extends LValParser {
op === tt.pipeline && op === tt.pipeline &&
this.getPluginOption("pipelineOperator", "proposal") === "minimal" this.getPluginOption("pipelineOperator", "proposal") === "minimal"
) { ) {
if ( if (this.state.type === tt._await && this.prodParam.hasAwait) {
this.match(tt.name) &&
this.state.value === "await" &&
this.prodParam.hasAwait
) {
throw this.raise( throw this.raise(
this.state.start, this.state.start,
Errors.UnexpectedAwaitAfterPipelineBody, Errors.UnexpectedAwaitAfterPipelineBody,
@ -498,7 +497,7 @@ export default class ExpressionParser extends LValParser {
case "smart": case "smart":
return this.withTopicBindingContext(() => { return this.withTopicBindingContext(() => {
if (this.prodParam.hasYield && this.isContextual("yield")) { if (this.prodParam.hasYield && this.isContextual(tt._yield)) {
throw this.raise( throw this.raise(
this.state.start, this.state.start,
Errors.PipeBodyIsTighter, Errors.PipeBodyIsTighter,
@ -577,7 +576,7 @@ export default class ExpressionParser extends LValParser {
): N.Expression { ): N.Expression {
const startPos = this.state.start; const startPos = this.state.start;
const startLoc = this.state.startLoc; const startLoc = this.state.startLoc;
const isAwait = this.isContextual("await"); const isAwait = this.isContextual(tt._await);
if (isAwait && this.isAwaitAllowed()) { if (isAwait && this.isAwaitAllowed()) {
this.next(); this.next();
@ -1053,7 +1052,8 @@ export default class ExpressionParser extends LValParser {
parseExprAtom(refExpressionErrors?: ?ExpressionErrors): N.Expression { parseExprAtom(refExpressionErrors?: ?ExpressionErrors): N.Expression {
let node; let node;
switch (this.state.type) { const { type } = this.state;
switch (type) {
case tt._super: case tt._super:
return this.parseSuper(); return this.parseSuper();
@ -1074,61 +1074,6 @@ export default class ExpressionParser extends LValParser {
this.next(); this.next();
return this.finishNode(node, "ThisExpression"); 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: { case tt._do: {
return this.parseDo(this.startNode(), false); return this.parseDo(this.startNode(), false);
} }
@ -1308,9 +1253,73 @@ export default class ExpressionParser extends LValParser {
// fall through // fall through
default: 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(); throw this.unexpected();
} }
} }
}
// This helper method attempts to finish the given `node` // This helper method attempts to finish the given `node`
// into a topic-reference node for the given `pipeProposal`. // into a topic-reference node for the given `pipeProposal`.
@ -1519,6 +1528,13 @@ export default class ExpressionParser extends LValParser {
"function", "function",
); );
this.next(); // eat `.` 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.parseMetaProperty(node, meta, "sent");
} }
return this.parseFunction(node); return this.parseFunction(node);
@ -1531,16 +1547,6 @@ export default class ExpressionParser extends LValParser {
): N.MetaProperty { ): N.MetaProperty {
node.meta = meta; 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; const containsEsc = this.state.containsEsc;
node.property = this.parseIdentifier(true); node.property = this.parseIdentifier(true);
@ -1562,7 +1568,7 @@ export default class ExpressionParser extends LValParser {
const id = this.createIdentifier(this.startNodeAtNode(node), "import"); const id = this.createIdentifier(this.startNodeAtNode(node), "import");
this.next(); // eat `.` this.next(); // eat `.`
if (this.isContextual("meta")) { if (this.isContextual(tt._meta)) {
if (!this.inModule) { if (!this.inModule) {
this.raise(id.start, SourceTypeModuleErrors.ImportMetaOutsideModule); this.raise(id.start, SourceTypeModuleErrors.ImportMetaOutsideModule);
} }
@ -2537,10 +2543,8 @@ export default class ExpressionParser extends LValParser {
const { start, type } = this.state; const { start, type } = this.state;
if (type === tt.name) { if (tokenIsKeywordOrIdentifier(type)) {
name = this.state.value; name = this.state.value;
} else if (tokenIsKeyword(type)) {
name = tokenLabelName(type);
} else { } else {
throw this.unexpected(); throw this.unexpected();
} }

View File

@ -2,6 +2,7 @@
import * as N from "../types"; import * as N from "../types";
import { import {
tokenIsIdentifier,
tokenIsLoop, tokenIsLoop,
tt, tt,
type TokenType, type TokenType,
@ -178,7 +179,7 @@ export default class StatementParser extends ExpressionParser {
} }
isLet(context: ?string): boolean { isLet(context: ?string): boolean {
if (!this.isContextual("let")) { if (!this.isContextual(tt._let)) {
return false; return false;
} }
return this.isLetKeyword(context); return this.isLetKeyword(context);
@ -378,7 +379,7 @@ export default class StatementParser extends ExpressionParser {
const expr = this.parseExpression(); const expr = this.parseExpression();
if ( if (
starttype === tt.name && tokenIsIdentifier(starttype) &&
expr.type === "Identifier" && expr.type === "Identifier" &&
this.eat(tt.colon) this.eat(tt.colon)
) { ) {
@ -572,7 +573,7 @@ export default class StatementParser extends ExpressionParser {
this.state.labels.push(loopLabel); this.state.labels.push(loopLabel);
let awaitAt = -1; let awaitAt = -1;
if (this.isAwaitAllowed() && this.eatContextual("await")) { if (this.isAwaitAllowed() && this.eatContextual(tt._await)) {
awaitAt = this.state.lastTokStart; awaitAt = this.state.lastTokStart;
} }
this.scope.enter(SCOPE_OTHER); this.scope.enter(SCOPE_OTHER);
@ -585,7 +586,7 @@ export default class StatementParser extends ExpressionParser {
return this.parseFor(node, null); return this.parseFor(node, null);
} }
const startsWithLet = this.isContextual("let"); const startsWithLet = this.isContextual(tt._let);
const isLet = startsWithLet && this.isLetKeyword(); const isLet = startsWithLet && this.isLetKeyword();
if (this.match(tt._var) || this.match(tt._const) || isLet) { if (this.match(tt._var) || this.match(tt._const) || isLet) {
const init = this.startNode(); const init = this.startNode();
@ -595,7 +596,7 @@ export default class StatementParser extends ExpressionParser {
this.finishNode(init, "VariableDeclaration"); this.finishNode(init, "VariableDeclaration");
if ( if (
(this.match(tt._in) || this.isContextual("of")) && (this.match(tt._in) || this.isContextual(tt._of)) &&
init.declarations.length === 1 init.declarations.length === 1
) { ) {
return this.parseForIn(node, init, awaitAt); 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 // 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. // we can forbid `for (async of` if this turns out to be a for-of loop.
const startsWithUnescapedName = const startsWithAsync = this.isContextual(tt._async);
this.match(tt.name) && !this.state.containsEsc;
const refExpressionErrors = new ExpressionErrors(); const refExpressionErrors = new ExpressionErrors();
const init = this.parseExpression(true, refExpressionErrors); const init = this.parseExpression(true, refExpressionErrors);
const isForOf = this.isContextual("of"); const isForOf = this.isContextual(tt._of);
if (isForOf) { if (isForOf) {
// Check for leading tokens that are forbidden in for-of loops: // Check for leading tokens that are forbidden in for-of loops:
if (startsWithLet) { if (startsWithLet) {
@ -621,9 +621,8 @@ export default class StatementParser extends ExpressionParser {
} else if ( } else if (
// `for await (async of []);` is allowed. // `for await (async of []);` is allowed.
awaitAt === -1 && awaitAt === -1 &&
startsWithUnescapedName && startsWithAsync &&
init.type === "Identifier" && init.type === "Identifier"
init.name === "async"
) { ) {
// This catches the case where the `async` in `for (async of` was // 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 // 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 { } else {
if ( if (
kind === "const" && 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. // `const` with no initializer is allowed in TypeScript.
// It could be a declaration like `const x: number;`. // It could be a declaration like `const x: number;`.
@ -1132,7 +1131,7 @@ export default class StatementParser extends ExpressionParser {
} }
} else if ( } else if (
decl.id.type !== "Identifier" && decl.id.type !== "Identifier" &&
!(isFor && (this.match(tt._in) || this.isContextual("of"))) !(isFor && (this.match(tt._in) || this.isContextual(tt._of)))
) { ) {
this.raise( this.raise(
this.state.lastTokEnd, this.state.lastTokEnd,
@ -1219,7 +1218,9 @@ export default class StatementParser extends ExpressionParser {
} }
parseFunctionId(requireId?: boolean): ?N.Identifier { 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 { parseFunctionParams(node: N.Function, allowModifiers?: boolean): void {
@ -1405,7 +1406,7 @@ export default class StatementParser extends ExpressionParser {
member: N.ClassMember, member: N.ClassMember,
state: N.ParseClassMemberState, state: N.ParseClassMemberState,
): void { ): void {
const isStatic = this.isContextual("static"); const isStatic = this.isContextual(tt._static);
if (isStatic) { if (isStatic) {
if (this.parseClassMemberFromModifier(classBody, member)) { if (this.parseClassMemberFromModifier(classBody, member)) {
@ -1465,7 +1466,8 @@ export default class StatementParser extends ExpressionParser {
return; 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 isPrivate = this.match(tt.privateName);
const key = this.parseClassElementName(member); const key = this.parseClassElementName(member);
const maybeQuestionTokenStart = this.state.start; const maybeQuestionTokenStart = this.state.start;
@ -1758,7 +1760,7 @@ export default class StatementParser extends ExpressionParser {
optionalId: ?boolean, optionalId: ?boolean,
bindingType: BindingTypes = BIND_CLASS, bindingType: BindingTypes = BIND_CLASS,
): void { ): void {
if (this.match(tt.name)) { if (tokenIsIdentifier(this.state.type)) {
node.id = this.parseIdentifier(); node.id = this.parseIdentifier();
if (isStatement) { if (isStatement) {
this.checkLVal(node.id, "class name", bindingType); this.checkLVal(node.id, "class name", bindingType);
@ -1848,7 +1850,7 @@ export default class StatementParser extends ExpressionParser {
} }
maybeParseExportNamespaceSpecifier(node: N.Node): boolean { maybeParseExportNamespaceSpecifier(node: N.Node): boolean {
if (this.isContextual("as")) { if (this.isContextual(tt._as)) {
if (!node.specifiers) node.specifiers = []; if (!node.specifiers) node.specifiers = [];
const specifier = this.startNodeAt( const specifier = this.startNodeAt(
@ -1891,7 +1893,7 @@ export default class StatementParser extends ExpressionParser {
} }
isAsyncFunction(): boolean { isAsyncFunction(): boolean {
if (!this.isContextual("async")) return false; if (!this.isContextual(tt._async)) return false;
const next = this.nextTokenStart(); const next = this.nextTokenStart();
return ( return (
!lineBreak.test(this.input.slice(this.state.pos, next)) && !lineBreak.test(this.input.slice(this.state.pos, next)) &&
@ -1941,23 +1943,23 @@ export default class StatementParser extends ExpressionParser {
} }
isExportDefaultSpecifier(): boolean { isExportDefaultSpecifier(): boolean {
if (this.match(tt.name)) { const { type } = this.state;
const value = this.state.value; if (tokenIsIdentifier(type)) {
if ((value === "async" && !this.state.containsEsc) || value === "let") { if ((type === tt._async && !this.state.containsEsc) || type === tt._let) {
return false; return false;
} }
if ( if (
(value === "type" || value === "interface") && (type === tt._type || type === tt._interface) &&
!this.state.containsEsc !this.state.containsEsc
) { ) {
const l = this.lookahead(); const { type: nextType } = this.lookahead();
// If we see any variable name other than `from` after `type` keyword, // If we see any variable name other than `from` after `type` keyword,
// we consider it as flow/typescript type exports // we consider it as flow/typescript type exports
// note that this approach may fail on some pedantic cases // note that this approach may fail on some pedantic cases
// export type from = number // export type from = number
if ( if (
(l.type === tt.name && l.value !== "from") || (tokenIsIdentifier(nextType) && nextType !== tt._from) ||
l.type === tt.braceL nextType === tt.braceL
) { ) {
this.expectOnePlugin(["flow", "typescript"]); this.expectOnePlugin(["flow", "typescript"]);
return false; return false;
@ -1971,7 +1973,7 @@ export default class StatementParser extends ExpressionParser {
const hasFrom = this.isUnparsedContextual(next, "from"); const hasFrom = this.isUnparsedContextual(next, "from");
if ( if (
this.input.charCodeAt(next) === charCodes.comma || this.input.charCodeAt(next) === charCodes.comma ||
(this.match(tt.name) && hasFrom) (tokenIsIdentifier(this.state.type) && hasFrom)
) { ) {
return true; return true;
} }
@ -1989,7 +1991,7 @@ export default class StatementParser extends ExpressionParser {
} }
parseExportFrom(node: N.ExportNamedDeclaration, expect?: boolean): void { parseExportFrom(node: N.ExportNamedDeclaration, expect?: boolean): void {
if (this.eatContextual("from")) { if (this.eatContextual(tt._from)) {
node.source = this.parseImportSource(); node.source = this.parseImportSource();
this.checkExport(node); this.checkExport(node);
const assertions = this.maybeParseImportAssertions(); const assertions = this.maybeParseImportAssertions();
@ -2169,7 +2171,7 @@ export default class StatementParser extends ExpressionParser {
const isString = this.match(tt.string); const isString = this.match(tt.string);
const local = this.parseModuleExportName(); const local = this.parseModuleExportName();
node.local = local; node.local = local;
if (this.eatContextual("as")) { if (this.eatContextual(tt._as)) {
node.exported = this.parseModuleExportName(); node.exported = this.parseModuleExportName();
} else if (isString) { } else if (isString) {
node.exported = cloneStringLiteral(local); 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 // now we check if we need to parse the next imports
// but only if they are not importing * (everything) // but only if they are not importing * (everything)
if (parseNext && !hasStar) this.parseNamedImportSpecifiers(node); if (parseNext && !hasStar) this.parseNamedImportSpecifiers(node);
this.expectContextual("from"); this.expectContextual(tt._from);
} }
node.source = this.parseImportSource(); node.source = this.parseImportSource();
// https://github.com/tc39/proposal-import-assertions // https://github.com/tc39/proposal-import-assertions
@ -2249,7 +2251,7 @@ export default class StatementParser extends ExpressionParser {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
shouldParseDefaultImport(node: N.ImportDeclaration): boolean { shouldParseDefaultImport(node: N.ImportDeclaration): boolean {
return this.match(tt.name); return tokenIsIdentifier(this.state.type);
} }
parseImportSpecifierLocal( parseImportSpecifierLocal(
@ -2368,7 +2370,7 @@ export default class StatementParser extends ExpressionParser {
maybeParseImportAssertions() { maybeParseImportAssertions() {
// [no LineTerminator here] AssertClause // [no LineTerminator here] AssertClause
if (this.isContextual("assert") && !this.hasPrecedingLineBreak()) { if (this.isContextual(tt._assert) && !this.hasPrecedingLineBreak()) {
this.expectPlugin("importAssertions"); this.expectPlugin("importAssertions");
this.next(); // eat `assert` this.next(); // eat `assert`
} else { } else {
@ -2401,7 +2403,7 @@ export default class StatementParser extends ExpressionParser {
if (this.match(tt.star)) { if (this.match(tt.star)) {
const specifier = this.startNode(); const specifier = this.startNode();
this.next(); this.next();
this.expectContextual("as"); this.expectContextual(tt._as);
this.parseImportSpecifierLocal( this.parseImportSpecifierLocal(
node, node,
@ -2439,7 +2441,7 @@ export default class StatementParser extends ExpressionParser {
const specifier = this.startNode(); const specifier = this.startNode();
const importedIsString = this.match(tt.string); const importedIsString = this.match(tt.string);
specifier.imported = this.parseModuleExportName(); specifier.imported = this.parseModuleExportName();
if (this.eatContextual("as")) { if (this.eatContextual(tt._as)) {
specifier.local = this.parseIdentifier(); specifier.local = this.parseIdentifier();
} else { } else {
const { imported } = specifier; const { imported } = specifier;

View File

@ -2,7 +2,7 @@
import { import {
isTokenType, isTokenType,
tokenIsKeyword, tokenIsLiteralPropertyName,
tokenLabelName, tokenLabelName,
tt, tt,
type TokenType, type TokenType,
@ -68,12 +68,8 @@ export default class UtilParser extends Tokenizer {
// Tests whether parsed token is a contextual keyword. // Tests whether parsed token is a contextual keyword.
isContextual(name: string): boolean { isContextual(token: TokenType): boolean {
return ( return this.state.type === token && !this.state.containsEsc;
this.match(tt.name) &&
this.state.value === name &&
!this.state.containsEsc
);
} }
isUnparsedContextual(nameStart: number, name: string): boolean { isUnparsedContextual(nameStart: number, name: string): boolean {
@ -98,14 +94,18 @@ export default class UtilParser extends Tokenizer {
// Consumes contextual keyword if possible. // Consumes contextual keyword if possible.
eatContextual(name: string): boolean { eatContextual(token: TokenType): boolean {
return this.isContextual(name) && this.eat(tt.name); if (this.isContextual(token)) {
this.next();
return true;
}
return false;
} }
// Asserts that following token is given contextual keyword. // Asserts that following token is given contextual keyword.
expectContextual(name: string, template?: ErrorTemplate): void { expectContextual(token: TokenType, template?: ErrorTemplate): void {
if (!this.eatContextual(name)) this.unexpected(null, template); if (!this.eatContextual(token)) this.unexpected(null, template);
} }
// Test whether a semicolon can be inserted at the current position. // Test whether a semicolon can be inserted at the current position.
@ -306,14 +306,7 @@ export default class UtilParser extends Tokenizer {
* BigIntLiteral * BigIntLiteral
*/ */
isLiteralPropertyName(): boolean { isLiteralPropertyName(): boolean {
return ( return tokenIsLiteralPropertyName(this.state.type);
this.match(tt.name) ||
tokenIsKeyword(this.state.type) ||
this.match(tt.string) ||
this.match(tt.num) ||
this.match(tt.bigint) ||
this.match(tt.decimal)
);
} }
/* /*

View File

@ -7,16 +7,20 @@
import type Parser from "../../parser"; import type Parser from "../../parser";
import { import {
tokenIsIdentifier,
tokenIsKeyword, tokenIsKeyword,
tokenIsKeywordOrIdentifier,
tokenIsLiteralPropertyName,
tokenLabelName, tokenLabelName,
tt, tt,
type TokenType, type TokenType,
tokenIsFlowInterfaceOrTypeOrOpaque,
} from "../../tokenizer/types"; } from "../../tokenizer/types";
import * as N from "../../types"; import * as N from "../../types";
import type { Position } from "../../util/location"; import type { Position } from "../../util/location";
import { types as tc } from "../../tokenizer/context"; import { types as tc } from "../../tokenizer/context";
import * as charCodes from "charcodes"; import * as charCodes from "charcodes";
import { isIteratorStart, isKeyword } from "../../util/identifier"; import { isIteratorStart } from "../../util/identifier";
import FlowScopeHandler from "./scope"; import FlowScopeHandler from "./scope";
import { import {
type BindingTypes, type BindingTypes,
@ -160,11 +164,8 @@ function hasTypeImportKind(node: N.Node): boolean {
return node.importKind === "type" || node.importKind === "typeof"; return node.importKind === "type" || node.importKind === "typeof";
} }
function isMaybeDefaultImport(state: { type: TokenType, value: any }): boolean { function isMaybeDefaultImport(type: TokenType): boolean {
return ( return tokenIsKeywordOrIdentifier(type) && type !== tt._from;
(state.type === tt.name || tokenIsKeyword(state.type)) &&
state.value !== "from"
);
} }
const exportSuggestions = { const exportSuggestions = {
@ -266,7 +267,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
const node = this.startNode(); const node = this.startNode();
const moduloPos = this.state.start; const moduloPos = this.state.start;
this.next(); // eat `%` this.next(); // eat `%`
this.expectContextual("checks"); this.expectContextual(tt._checks);
// Force '%' and 'checks' to be adjacent // Force '%' and 'checks' to be adjacent
if (this.state.lastTokStart > moduloPos + 1) { if (this.state.lastTokStart > moduloPos + 1) {
this.raise(moduloPos, FlowErrors.UnexpectedSpaceBetweenModuloChecks); this.raise(moduloPos, FlowErrors.UnexpectedSpaceBetweenModuloChecks);
@ -360,7 +361,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.flowParseDeclareFunction(node); return this.flowParseDeclareFunction(node);
} else if (this.match(tt._var)) { } else if (this.match(tt._var)) {
return this.flowParseDeclareVariable(node); return this.flowParseDeclareVariable(node);
} else if (this.eatContextual("module")) { } else if (this.eatContextual(tt._module)) {
if (this.match(tt.dot)) { if (this.match(tt.dot)) {
return this.flowParseDeclareModuleExports(node); return this.flowParseDeclareModuleExports(node);
} else { } else {
@ -369,11 +370,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} }
return this.flowParseDeclareModule(node); return this.flowParseDeclareModule(node);
} }
} else if (this.isContextual("type")) { } else if (this.isContextual(tt._type)) {
return this.flowParseDeclareTypeAlias(node); return this.flowParseDeclareTypeAlias(node);
} else if (this.isContextual("opaque")) { } else if (this.isContextual(tt._opaque)) {
return this.flowParseDeclareOpaqueType(node); return this.flowParseDeclareOpaqueType(node);
} else if (this.isContextual("interface")) { } else if (this.isContextual(tt._interface)) {
return this.flowParseDeclareInterface(node); return this.flowParseDeclareInterface(node);
} else if (this.match(tt._export)) { } else if (this.match(tt._export)) {
return this.flowParseDeclareExportDeclaration(node, insideModule); return this.flowParseDeclareExportDeclaration(node, insideModule);
@ -411,7 +412,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if (this.match(tt._import)) { if (this.match(tt._import)) {
this.next(); this.next();
if (!this.isContextual("type") && !this.match(tt._typeof)) { if (!this.isContextual(tt._type) && !this.match(tt._typeof)) {
this.raise( this.raise(
this.state.lastTokStart, this.state.lastTokStart,
FlowErrors.InvalidNonTypeImportInDeclareModule, FlowErrors.InvalidNonTypeImportInDeclareModule,
@ -420,7 +421,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
this.parseImport(bodyNode); this.parseImport(bodyNode);
} else { } else {
this.expectContextual( this.expectContextual(
"declare", tt._declare,
FlowErrors.UnsupportedStatementInDeclareModule, FlowErrors.UnsupportedStatementInDeclareModule,
); );
@ -492,7 +493,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if ( if (
this.match(tt._const) || this.match(tt._const) ||
this.isLet() || this.isLet() ||
((this.isContextual("type") || this.isContextual("interface")) && ((this.isContextual(tt._type) || this.isContextual(tt._interface)) &&
!insideModule) !insideModule)
) { ) {
const label = this.state.value; 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._var) || // declare export var ...
this.match(tt._function) || // declare export function ... this.match(tt._function) || // declare export function ...
this.match(tt._class) || // declare export class ... 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.declaration = this.flowParseDeclare(this.startNode());
node.default = false; node.default = false;
@ -519,9 +520,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} else if ( } else if (
this.match(tt.star) || // declare export * from '' this.match(tt.star) || // declare export * from ''
this.match(tt.braceL) || // declare export {} ... this.match(tt.braceL) || // declare export {} ...
this.isContextual("interface") || // declare export interface ... this.isContextual(tt._interface) || // declare export interface ...
this.isContextual("type") || // declare export type ... this.isContextual(tt._type) || // declare export type ...
this.isContextual("opaque") // declare export opaque type ... this.isContextual(tt._opaque) // declare export opaque type ...
) { ) {
node = this.parseExport(node); node = this.parseExport(node);
if (node.type === "ExportNamedDeclaration") { if (node.type === "ExportNamedDeclaration") {
@ -547,7 +548,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
node: N.FlowDeclareModuleExports, node: N.FlowDeclareModuleExports,
): N.FlowDeclareModuleExports { ): N.FlowDeclareModuleExports {
this.next(); this.next();
this.expectContextual("exports"); this.expectContextual(tt._exports);
node.typeAnnotation = this.flowParseTypeAnnotation(); node.typeAnnotation = this.flowParseTypeAnnotation();
this.semicolon(); this.semicolon();
@ -615,14 +616,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} while (!isClass && this.eat(tt.comma)); } while (!isClass && this.eat(tt.comma));
} }
if (this.isContextual("mixins")) { if (this.isContextual(tt._mixins)) {
this.next(); this.next();
do { do {
node.mixins.push(this.flowParseInterfaceExtends()); node.mixins.push(this.flowParseInterfaceExtends());
} while (this.eat(tt.comma)); } while (this.eat(tt.comma));
} }
if (this.isContextual("implements")) { if (this.isContextual(tt._implements)) {
this.next(); this.next();
do { do {
node.implements.push(this.flowParseInterfaceExtends()); node.implements.push(this.flowParseInterfaceExtends());
@ -707,7 +708,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
node: N.FlowOpaqueType, node: N.FlowOpaqueType,
declare: boolean, declare: boolean,
): N.FlowOpaqueType { ): N.FlowOpaqueType {
this.expectContextual("type"); this.expectContextual(tt._type);
node.id = this.flowParseRestrictedIdentifier( node.id = this.flowParseRestrictedIdentifier(
/* liberal */ true, /* liberal */ true,
/* declaration */ true, /* declaration */ true,
@ -844,7 +845,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
flowParseInterfaceType(): N.FlowInterfaceType { flowParseInterfaceType(): N.FlowInterfaceType {
const node = this.startNode(); const node = this.startNode();
this.expectContextual("interface"); this.expectContextual(tt._interface);
node.extends = []; node.extends = [];
if (this.eat(tt._extends)) { if (this.eat(tt._extends)) {
@ -1008,7 +1009,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
let inexactStart: ?number = null; let inexactStart: ?number = null;
const node = this.startNode(); const node = this.startNode();
if (allowProto && this.isContextual("proto")) { if (allowProto && this.isContextual(tt._proto)) {
const lookahead = this.lookahead(); const lookahead = this.lookahead();
if (lookahead.type !== tt.colon && lookahead.type !== tt.question) { 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(); const lookahead = this.lookahead();
// static is a valid identifier name // static is a valid identifier name
@ -1059,13 +1060,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} else { } else {
let kind = "init"; let kind = "init";
if (this.isContextual("get") || this.isContextual("set")) { if (this.isContextual(tt._get) || this.isContextual(tt._set)) {
const lookahead = this.lookahead(); const lookahead = this.lookahead();
if ( if (tokenIsLiteralPropertyName(lookahead.type)) {
lookahead.type === tt.name ||
lookahead.type === tt.string ||
lookahead.type === tt.num
) {
kind = this.state.value; kind = this.state.value;
this.next(); this.next();
} }
@ -1432,18 +1429,6 @@ export default (superClass: Class<Parser>): Class<Parser> =>
const oldNoAnonFunctionType = this.state.noAnonFunctionType; const oldNoAnonFunctionType = this.state.noAnonFunctionType;
switch (this.state.type) { 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: case tt.braceL:
return this.flowParseObjectType({ return this.flowParseObjectType({
allowStatic: false, allowStatic: false,
@ -1491,7 +1476,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// Check to see if this is actually a grouped type // Check to see if this is actually a grouped type
if (!this.match(tt.parenR) && !this.match(tt.ellipsis)) { 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; const token = this.lookahead().type;
isGroupedType = token !== tt.question && token !== tt.colon; isGroupedType = token !== tt.question && token !== tt.colon;
} else { } else {
@ -1619,6 +1604,17 @@ export default (superClass: Class<Parser>): Class<Parser> =>
const label = tokenLabelName(this.state.type); const label = tokenLabelName(this.state.type);
this.next(); this.next();
return super.createIdentifier(node, label); 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 // interfaces and enums
parseStatement(context: ?string, topLevel?: boolean): N.Statement { parseStatement(context: ?string, topLevel?: boolean): N.Statement {
// strict mode handling of `interface` since it's a reserved word // strict mode handling of `interface` since it's a reserved word
if ( if (this.state.strict && this.isContextual(tt._interface)) {
this.state.strict &&
this.match(tt.name) &&
this.state.value === "interface"
) {
const lookahead = this.lookahead(); const lookahead = this.lookahead();
if (lookahead.type === tt.name || isKeyword(lookahead.value)) { if (tokenIsKeywordOrIdentifier(lookahead.type)) {
const node = this.startNode(); const node = this.startNode();
this.next(); this.next();
return this.flowParseInterface(node); return this.flowParseInterface(node);
} }
} else if (this.shouldParseEnums() && this.isContextual("enum")) { } else if (this.shouldParseEnums() && this.isContextual(tt._enum)) {
const node = this.startNode(); const node = this.startNode();
this.next(); this.next();
return this.flowParseEnumDeclaration(node); return this.flowParseEnumDeclaration(node);
@ -1856,14 +1848,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if (expr.name === "declare") { if (expr.name === "declare") {
if ( if (
this.match(tt._class) || this.match(tt._class) ||
this.match(tt.name) || tokenIsIdentifier(this.state.type) ||
this.match(tt._function) || this.match(tt._function) ||
this.match(tt._var) || this.match(tt._var) ||
this.match(tt._export) this.match(tt._export)
) { ) {
return this.flowParseDeclare(node); return this.flowParseDeclare(node);
} }
} else if (this.match(tt.name)) { } else if (tokenIsIdentifier(this.state.type)) {
if (expr.name === "interface") { if (expr.name === "interface") {
return this.flowParseInterface(node); return this.flowParseInterface(node);
} else if (expr.name === "type") { } else if (expr.name === "type") {
@ -1879,31 +1871,30 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// export type // export type
shouldParseExportDeclaration(): boolean { shouldParseExportDeclaration(): boolean {
return ( const { type } = this.state;
this.isContextual("type") || if (
this.isContextual("interface") || tokenIsFlowInterfaceOrTypeOrOpaque(type) ||
this.isContextual("opaque") || (this.shouldParseEnums() && type === tt._enum)
(this.shouldParseEnums() && this.isContextual("enum")) || ) {
super.shouldParseExportDeclaration() return !this.state.containsEsc;
); }
return super.shouldParseExportDeclaration();
} }
isExportDefaultSpecifier(): boolean { isExportDefaultSpecifier(): boolean {
const { type } = this.state;
if ( if (
this.match(tt.name) && tokenIsFlowInterfaceOrTypeOrOpaque(type) ||
(this.state.value === "type" || (this.shouldParseEnums() && type === tt._enum)
this.state.value === "interface" ||
this.state.value === "opaque" ||
(this.shouldParseEnums() && this.state.value === "enum"))
) { ) {
return false; return this.state.containsEsc;
} }
return super.isExportDefaultSpecifier(); return super.isExportDefaultSpecifier();
} }
parseExportDefaultExpression(): N.Expression | N.Declaration { parseExportDefaultExpression(): N.Expression | N.Declaration {
if (this.shouldParseEnums() && this.isContextual("enum")) { if (this.shouldParseEnums() && this.isContextual(tt._enum)) {
const node = this.startNode(); const node = this.startNode();
this.next(); this.next();
return this.flowParseEnumDeclaration(node); return this.flowParseEnumDeclaration(node);
@ -2124,7 +2115,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} }
parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration { parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration {
if (this.isContextual("type")) { if (this.isContextual(tt._type)) {
node.exportKind = "type"; node.exportKind = "type";
const declarationNode = this.startNode(); const declarationNode = this.startNode();
@ -2139,19 +2130,19 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// export type Foo = Bar; // export type Foo = Bar;
return this.flowParseTypeAlias(declarationNode); return this.flowParseTypeAlias(declarationNode);
} }
} else if (this.isContextual("opaque")) { } else if (this.isContextual(tt._opaque)) {
node.exportKind = "type"; node.exportKind = "type";
const declarationNode = this.startNode(); const declarationNode = this.startNode();
this.next(); this.next();
// export opaque type Foo = Bar; // export opaque type Foo = Bar;
return this.flowParseOpaqueType(declarationNode, false); return this.flowParseOpaqueType(declarationNode, false);
} else if (this.isContextual("interface")) { } else if (this.isContextual(tt._interface)) {
node.exportKind = "type"; node.exportKind = "type";
const declarationNode = this.startNode(); const declarationNode = this.startNode();
this.next(); this.next();
return this.flowParseInterface(declarationNode); return this.flowParseInterface(declarationNode);
} else if (this.shouldParseEnums() && this.isContextual("enum")) { } else if (this.shouldParseEnums() && this.isContextual(tt._enum)) {
node.exportKind = "value"; node.exportKind = "value";
const declarationNode = this.startNode(); const declarationNode = this.startNode();
this.next(); this.next();
@ -2164,7 +2155,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
eatExportStar(node: N.Node): boolean { eatExportStar(node: N.Node): boolean {
if (super.eatExportStar(...arguments)) return true; 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"; node.exportKind = "type";
this.next(); this.next();
this.next(); this.next();
@ -2196,7 +2187,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
state: N.ParseClassMemberState, state: N.ParseClassMemberState,
): void { ): void {
const pos = this.state.start; const pos = this.state.start;
if (this.isContextual("declare")) { if (this.isContextual(tt._declare)) {
if (this.parseClassMemberFromModifier(classBody, member)) { if (this.parseClassMemberFromModifier(classBody, member)) {
// 'declare' is a class element name // 'declare' is a class element name
return; return;
@ -2456,7 +2447,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if (node.superClass && this.isRelational("<")) { if (node.superClass && this.isRelational("<")) {
node.superTypeParameters = this.flowParseTypeParameterInstantiation(); node.superTypeParameters = this.flowParseTypeParameterInstantiation();
} }
if (this.isContextual("implements")) { if (this.isContextual(tt._implements)) {
this.next(); this.next();
const implemented: N.FlowClassImplements[] = (node.implements = []); const implemented: N.FlowClassImplements[] = (node.implements = []);
do { do {
@ -2585,7 +2576,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return super.shouldParseDefaultImport(node); return super.shouldParseDefaultImport(node);
} }
return isMaybeDefaultImport(this.state); return isMaybeDefaultImport(this.state.type);
} }
parseImportSpecifierLocal( parseImportSpecifierLocal(
@ -2612,21 +2603,22 @@ export default (superClass: Class<Parser>): Class<Parser> =>
let kind = null; let kind = null;
if (this.match(tt._typeof)) { if (this.match(tt._typeof)) {
kind = "typeof"; kind = "typeof";
} else if (this.isContextual("type")) { } else if (this.isContextual(tt._type)) {
kind = "type"; kind = "type";
} }
if (kind) { if (kind) {
const lh = this.lookahead(); const lh = this.lookahead();
const { type } = lh;
// import type * is not allowed // import type * is not allowed
if (kind === "type" && lh.type === tt.star) { if (kind === "type" && type === tt.star) {
this.unexpected(lh.start); this.unexpected(lh.start);
} }
if ( if (
isMaybeDefaultImport(lh) || isMaybeDefaultImport(type) ||
lh.type === tt.braceL || type === tt.braceL ||
lh.type === tt.star type === tt.star
) { ) {
this.next(); this.next();
node.importKind = kind; node.importKind = kind;
@ -2652,12 +2644,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} }
let isBinding = false; let isBinding = false;
if (this.isContextual("as") && !this.isLookaheadContextual("as")) { if (this.isContextual(tt._as) && !this.isLookaheadContextual("as")) {
const as_ident = this.parseIdentifier(true); const as_ident = this.parseIdentifier(true);
if ( if (
specifierTypeKind !== null && specifierTypeKind !== null &&
!this.match(tt.name) && !tokenIsKeywordOrIdentifier(this.state.type)
!tokenIsKeyword(this.state.type)
) { ) {
// `import {type as ,` or `import {type as }` // `import {type as ,` or `import {type as }`
specifier.imported = as_ident; specifier.imported = as_ident;
@ -2672,7 +2663,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} else { } else {
if ( if (
specifierTypeKind !== null && specifierTypeKind !== null &&
(this.match(tt.name) || tokenIsKeyword(this.state.type)) tokenIsKeywordOrIdentifier(this.state.type)
) { ) {
// `import {type foo` // `import {type foo`
specifier.imported = this.parseIdentifier(true); specifier.imported = this.parseIdentifier(true);
@ -2691,7 +2682,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
specifier.importKind = null; specifier.importKind = null;
} }
if (this.eatContextual("as")) { if (this.eatContextual(tt._as)) {
specifier.local = this.parseIdentifier(); specifier.local = this.parseIdentifier();
} else { } else {
isBinding = true; isBinding = true;
@ -3546,8 +3537,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}: { }: {
enumName: string, enumName: string,
}): EnumExplicitType { }): EnumExplicitType {
if (this.eatContextual("of")) { if (this.eatContextual(tt._of)) {
if (!this.match(tt.name)) { if (!tokenIsIdentifier(this.state.type)) {
throw this.flowEnumErrorInvalidExplicitType(this.state.start, { throw this.flowEnumErrorInvalidExplicitType(this.state.start, {
enumName, enumName,
suppliedType: null, 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 // Replicate the original checks that lead to looking ahead for an
// identifier. // identifier.
if (!this.isContextual("let")) { if (!this.isContextual(tt._let)) {
return false; return false;
} }
if (context) return false; if (context) return false;
@ -263,7 +263,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
const placeholder = this.parsePlaceholder("Identifier"); const placeholder = this.parsePlaceholder("Identifier");
if (!placeholder) return super.parseExport(...arguments); 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%%; // export %%DECL%%;
node.specifiers = []; node.specifiers = [];
node.source = null; node.source = null;
@ -324,7 +324,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
node.specifiers = []; node.specifiers = [];
if (!this.isContextual("from") && !this.match(tt.comma)) { if (!this.isContextual(tt._from) && !this.match(tt.comma)) {
// import %%STRING%%; // import %%STRING%%;
node.source = this.finishPlaceholder(placeholder, "StringLiteral"); node.source = this.finishPlaceholder(placeholder, "StringLiteral");
this.semicolon(); this.semicolon();
@ -345,7 +345,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if (!hasStarImport) this.parseNamedImportSpecifiers(node); if (!hasStarImport) this.parseNamedImportSpecifiers(node);
} }
this.expectContextual("from"); this.expectContextual(tt._from);
node.source = this.parseImportSource(); node.source = this.parseImportSource();
this.semicolon(); this.semicolon();
return this.finishNode(node, "ImportDeclaration"); return this.finishNode(node, "ImportDeclaration");

View File

@ -5,9 +5,15 @@
// Error messages are colocated with the plugin. // Error messages are colocated with the plugin.
/* eslint-disable @babel/development-internal/dry-error-messages */ /* eslint-disable @babel/development-internal/dry-error-messages */
import type { TokenType } from "../../tokenizer/types";
import type State from "../../tokenizer/state"; 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 { types as ct } from "../../tokenizer/context";
import * as N from "../../types"; import * as N from "../../types";
import type { Position } from "../../util/location"; import type { Position } from "../../util/location";
@ -206,7 +212,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsIsIdentifier(): boolean { tsIsIdentifier(): boolean {
// TODO: actually a bit more complex in TypeScript, but shouldn't matter. // TODO: actually a bit more complex in TypeScript, but shouldn't matter.
// See https://github.com/Microsoft/TypeScript/issues/15008 // See https://github.com/Microsoft/TypeScript/issues/15008
return this.match(tt.name); return tokenIsIdentifier(this.state.type);
} }
tsTokenCanFollowModifier() { tsTokenCanFollowModifier() {
@ -235,7 +241,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
allowedModifiers: T[], allowedModifiers: T[],
stopOnStartOfClassStaticBlock?: boolean, stopOnStartOfClassStaticBlock?: boolean,
): ?T { ): ?T {
if (!this.match(tt.name)) { if (!tokenIsIdentifier(this.state.type)) {
return undefined; return undefined;
} }
@ -597,7 +603,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsIsUnambiguouslyIndexSignature() { tsIsUnambiguouslyIndexSignature() {
this.next(); // Skip '{' 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 { tsTryParseIndexSignature(node: N.Node): ?N.TsIndexSignature {
@ -771,9 +781,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsIsStartOfMappedType(): boolean { tsIsStartOfMappedType(): boolean {
this.next(); this.next();
if (this.eat(tt.plusMin)) { 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(); this.next();
} }
if (!this.match(tt.bracketL)) { if (!this.match(tt.bracketL)) {
@ -802,14 +812,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if (this.match(tt.plusMin)) { if (this.match(tt.plusMin)) {
node.readonly = this.state.value; node.readonly = this.state.value;
this.next(); this.next();
this.expectContextual("readonly"); this.expectContextual(tt._readonly);
} else if (this.eatContextual("readonly")) { } else if (this.eatContextual(tt._readonly)) {
node.readonly = true; node.readonly = true;
} }
this.expect(tt.bracketL); this.expect(tt.bracketL);
node.typeParameter = this.tsParseMappedTypeParameter(); 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); this.expect(tt.bracketR);
@ -978,7 +988,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsParseThisTypeOrThisTypePredicate(): N.TsThisType | N.TsTypePredicate { tsParseThisTypeOrThisTypePredicate(): N.TsThisType | N.TsTypePredicate {
const thisKeyword = this.tsParseThisTypeNode(); const thisKeyword = this.tsParseThisTypeNode();
if (this.isContextual("is") && !this.hasPrecedingLineBreak()) { if (this.isContextual(tt._is) && !this.hasPrecedingLineBreak()) {
return this.tsParseThisTypePredicate(thisKeyword); return this.tsParseThisTypePredicate(thisKeyword);
} else { } else {
return thisKeyword; return thisKeyword;
@ -987,24 +997,6 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsParseNonArrayType(): N.TsType { tsParseNonArrayType(): N.TsType {
switch (this.state.type) { 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.string:
case tt.num: case tt.num:
case tt.bigint: case tt.bigint:
@ -1050,6 +1042,30 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.tsParseParenthesizedType(); return this.tsParseParenthesizedType();
case tt.backQuote: case tt.backQuote:
return this.tsParseTemplateLiteralType(); 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(); throw this.unexpected();
@ -1074,11 +1090,10 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return type; return type;
} }
tsParseTypeOperator( tsParseTypeOperator(): N.TsTypeOperator {
operator: "keyof" | "unique" | "readonly",
): N.TsTypeOperator {
const node: N.TsTypeOperator = this.startNode(); const node: N.TsTypeOperator = this.startNode();
this.expectContextual(operator); const operator = this.state.value;
this.next(); // eat operator
node.operator = operator; node.operator = operator;
node.typeAnnotation = this.tsParseTypeOperatorOrHigher(); node.typeAnnotation = this.tsParseTypeOperatorOrHigher();
@ -1101,7 +1116,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsParseInferType(): N.TsInferType { tsParseInferType(): N.TsInferType {
const node = this.startNode(); const node = this.startNode();
this.expectContextual("infer"); this.expectContextual(tt._infer);
const typeParameter = this.startNode(); const typeParameter = this.startNode();
typeParameter.name = this.tsParseTypeParameterName(); typeParameter.name = this.tsParseTypeParameterName();
node.typeParameter = this.finishNode(typeParameter, "TSTypeParameter"); node.typeParameter = this.finishNode(typeParameter, "TSTypeParameter");
@ -1109,12 +1124,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} }
tsParseTypeOperatorOrHigher(): N.TsType { tsParseTypeOperatorOrHigher(): N.TsType {
const operator = ["keyof", "unique", "readonly"].find(kw => const isTypeOperator =
this.isContextual(kw), tokenIsTSTypeOperator(this.state.type) && !this.state.containsEsc;
); return isTypeOperator
return operator ? this.tsParseTypeOperator()
? this.tsParseTypeOperator(operator) : this.isContextual(tt._infer)
: this.isContextual("infer")
? this.tsParseInferType() ? this.tsParseInferType()
: this.tsParseArrayTypeOrHigher(); : this.tsParseArrayTypeOrHigher();
} }
@ -1164,7 +1178,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} }
tsSkipParameterStart(): boolean { tsSkipParameterStart(): boolean {
if (this.match(tt.name) || this.match(tt._this)) { if (tokenIsIdentifier(this.state.type) || this.match(tt._this)) {
this.next(); this.next();
return true; return true;
} }
@ -1309,19 +1323,19 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsParseTypePredicatePrefix(): ?N.Identifier { tsParseTypePredicatePrefix(): ?N.Identifier {
const id = this.parseIdentifier(); const id = this.parseIdentifier();
if (this.isContextual("is") && !this.hasPrecedingLineBreak()) { if (this.isContextual(tt._is) && !this.hasPrecedingLineBreak()) {
this.next(); this.next();
return id; return id;
} }
} }
tsParseTypePredicateAsserts(): boolean { tsParseTypePredicateAsserts(): boolean {
if (!this.match(tt.name) || this.state.value !== "asserts") { if (this.state.type !== tt._asserts) {
return false; return false;
} }
const containsEsc = this.state.containsEsc; const containsEsc = this.state.containsEsc;
this.next(); this.next();
if (!this.match(tt.name) && !this.match(tt._this)) { if (!tokenIsIdentifier(this.state.type) && !this.match(tt._this)) {
return false; return false;
} }
@ -1366,7 +1380,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} }
isAbstractConstructorSignature(): boolean { isAbstractConstructorSignature(): boolean {
return this.isContextual("abstract") && this.lookahead().type === tt._new; return (
this.isContextual(tt._abstract) && this.lookahead().type === tt._new
);
} }
tsParseNonConditionalType(): N.TsType { tsParseNonConditionalType(): N.TsType {
@ -1427,7 +1443,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsParseInterfaceDeclaration( tsParseInterfaceDeclaration(
node: N.TsInterfaceDeclaration, node: N.TsInterfaceDeclaration,
): N.TsInterfaceDeclaration { ): N.TsInterfaceDeclaration {
if (this.match(tt.name)) { if (tokenIsIdentifier(this.state.type)) {
node.id = this.parseIdentifier(); node.id = this.parseIdentifier();
this.checkLVal( this.checkLVal(
node.id, node.id,
@ -1460,7 +1476,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
this.expect(tt.eq); this.expect(tt.eq);
if ( if (
this.isContextual("intrinsic") && this.isContextual(tt._intrinsic) &&
this.lookahead().type !== tt.dot this.lookahead().type !== tt.dot
) { ) {
const node: N.TsKeywordType = this.startNode(); const node: N.TsKeywordType = this.startNode();
@ -1599,7 +1615,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsParseAmbientExternalModuleDeclaration( tsParseAmbientExternalModuleDeclaration(
node: N.TsModuleDeclaration, node: N.TsModuleDeclaration,
): N.TsModuleDeclaration { ): N.TsModuleDeclaration {
if (this.isContextual("global")) { if (this.isContextual(tt._global)) {
node.global = true; node.global = true;
node.id = this.parseIdentifier(); node.id = this.parseIdentifier();
} else if (this.match(tt.string)) { } else if (this.match(tt.string)) {
@ -1642,7 +1658,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsIsExternalModuleReference(): boolean { tsIsExternalModuleReference(): boolean {
return ( return (
this.isContextual("require") && this.isContextual(tt._require) &&
this.lookaheadCharCode() === charCodes.leftParenthesis this.lookaheadCharCode() === charCodes.leftParenthesis
); );
} }
@ -1655,7 +1671,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsParseExternalModuleReference(): N.TsExternalModuleReference { tsParseExternalModuleReference(): N.TsExternalModuleReference {
const node: N.TsExternalModuleReference = this.startNode(); const node: N.TsExternalModuleReference = this.startNode();
this.expectContextual("require"); this.expectContextual(tt._require);
this.expect(tt.parenL); this.expect(tt.parenL);
if (!this.match(tt.string)) { if (!this.match(tt.string)) {
throw this.unexpected(); throw this.unexpected();
@ -1701,7 +1717,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
let starttype = this.state.type; let starttype = this.state.type;
let kind; let kind;
if (this.isContextual("let")) { if (this.isContextual(tt._let)) {
starttype = tt._var; starttype = tt._var;
kind = "let"; kind = "let";
} }
@ -1728,19 +1744,22 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if (this.match(tt._const) && this.isLookaheadContextual("enum")) { if (this.match(tt._const) && this.isLookaheadContextual("enum")) {
// `const enum = 0;` not allowed because "enum" is a strict mode reserved word. // `const enum = 0;` not allowed because "enum" is a strict mode reserved word.
this.expect(tt._const); this.expect(tt._const);
this.expectContextual("enum"); this.expectContextual(tt._enum);
return this.tsParseEnumDeclaration(nany, /* isConst */ true); return this.tsParseEnumDeclaration(nany, /* isConst */ true);
} }
// falls through // falls through
case tt._var: case tt._var:
kind = kind || this.state.value; kind = kind || this.state.value;
return this.parseVarStatement(nany, kind); return this.parseVarStatement(nany, kind);
case tt.name: { case tt._global:
const value = this.state.value;
if (value === "global") {
return this.tsParseAmbientExternalModuleDeclaration(nany); return this.tsParseAmbientExternalModuleDeclaration(nany);
} else { default: {
return this.tsParseDeclaration(nany, value, /* next */ true); 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": case "abstract":
if ( if (
this.tsCheckLineTerminator(next) && this.tsCheckLineTerminator(next) &&
(this.match(tt._class) || this.match(tt.name)) (this.match(tt._class) || tokenIsIdentifier(this.state.type))
) { ) {
return this.tsParseAbstractDeclaration(node); return this.tsParseAbstractDeclaration(node);
} }
break; break;
case "enum": case "enum":
if (next || this.match(tt.name)) { if (next || tokenIsIdentifier(this.state.type)) {
if (next) this.next(); if (next) this.next();
return this.tsParseEnumDeclaration(node, /* isConst */ false); return this.tsParseEnumDeclaration(node, /* isConst */ false);
} }
break; break;
case "interface": case "interface":
if (this.tsCheckLineTerminator(next) && this.match(tt.name)) { if (
this.tsCheckLineTerminator(next) &&
tokenIsIdentifier(this.state.type)
) {
return this.tsParseInterfaceDeclaration(node); return this.tsParseInterfaceDeclaration(node);
} }
break; break;
@ -1821,20 +1843,26 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if (this.tsCheckLineTerminator(next)) { if (this.tsCheckLineTerminator(next)) {
if (this.match(tt.string)) { if (this.match(tt.string)) {
return this.tsParseAmbientExternalModuleDeclaration(node); return this.tsParseAmbientExternalModuleDeclaration(node);
} else if (this.match(tt.name)) { } else if (tokenIsIdentifier(this.state.type)) {
return this.tsParseModuleOrNamespaceDeclaration(node); return this.tsParseModuleOrNamespaceDeclaration(node);
} }
} }
break; break;
case "namespace": case "namespace":
if (this.tsCheckLineTerminator(next) && this.match(tt.name)) { if (
this.tsCheckLineTerminator(next) &&
tokenIsIdentifier(this.state.type)
) {
return this.tsParseModuleOrNamespaceDeclaration(node); return this.tsParseModuleOrNamespaceDeclaration(node);
} }
break; break;
case "type": case "type":
if (this.tsCheckLineTerminator(next) && this.match(tt.name)) { if (
this.tsCheckLineTerminator(next) &&
tokenIsIdentifier(this.state.type)
) {
return this.tsParseTypeAliasDeclaration(node); return this.tsParseTypeAliasDeclaration(node);
} }
break; break;
@ -1907,20 +1935,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} }
tsIsDeclarationStart(): boolean { tsIsDeclarationStart(): boolean {
if (this.match(tt.name)) { return tokenIsTSDeclarationStart(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;
} }
// ====================================================== // ======================================================
@ -2197,7 +2212,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if ( if (
tokenOperatorPrecedence(tt._in) > minPrec && tokenOperatorPrecedence(tt._in) > minPrec &&
!this.hasPrecedingLineBreak() && !this.hasPrecedingLineBreak() &&
this.isContextual("as") this.isContextual(tt._as)
) { ) {
const node: N.TsAsExpression = this.startNodeAt( const node: N.TsAsExpression = this.startNodeAt(
leftStartPos, leftStartPos,
@ -2244,15 +2259,19 @@ export default (superClass: Class<Parser>): Class<Parser> =>
parseImport(node: N.Node): N.AnyImport { parseImport(node: N.Node): N.AnyImport {
node.importKind = "value"; 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(); let ahead = this.lookahead();
if ( if (
this.isContextual("type") && this.isContextual(tt._type) &&
// import type, { a } from "b"; // import type, { a } from "b";
ahead.type !== tt.comma && ahead.type !== tt.comma &&
// import type from "a"; // import type from "a";
!(ahead.type === tt.name && ahead.value === "from") && ahead.type !== tt._from &&
// import type = require("a"); // import type = require("a");
ahead.type !== tt.eq ahead.type !== tt.eq
) { ) {
@ -2261,7 +2280,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
ahead = this.lookahead(); 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); return this.tsParseImportEqualsDeclaration(node);
} }
} }
@ -2290,7 +2309,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// `export import A = B;` // `export import A = B;`
this.next(); // eat `tt._import` this.next(); // eat `tt._import`
if ( if (
this.isContextual("type") && this.isContextual(tt._type) &&
this.lookaheadCharCode() !== charCodes.equalsTo this.lookaheadCharCode() !== charCodes.equalsTo
) { ) {
node.importKind = "type"; node.importKind = "type";
@ -2305,16 +2324,19 @@ export default (superClass: Class<Parser>): Class<Parser> =>
assign.expression = this.parseExpression(); assign.expression = this.parseExpression();
this.semicolon(); this.semicolon();
return this.finishNode(assign, "TSExportAssignment"); return this.finishNode(assign, "TSExportAssignment");
} else if (this.eatContextual("as")) { } else if (this.eatContextual(tt._as)) {
// `export as namespace A;` // `export as namespace A;`
const decl: N.TsNamespaceExportDeclaration = node; const decl: N.TsNamespaceExportDeclaration = node;
// See `parseNamespaceExportDeclaration` in TypeScript's own parser // See `parseNamespaceExportDeclaration` in TypeScript's own parser
this.expectContextual("namespace"); this.expectContextual(tt._namespace);
decl.id = this.parseIdentifier(); decl.id = this.parseIdentifier();
this.semicolon(); this.semicolon();
return this.finishNode(decl, "TSNamespaceExportDeclaration"); return this.finishNode(decl, "TSNamespaceExportDeclaration");
} else { } else {
if (this.isContextual("type") && this.lookahead().type === tt.braceL) { if (
this.isContextual(tt._type) &&
this.lookahead().type === tt.braceL
) {
this.next(); this.next();
node.exportKind = "type"; node.exportKind = "type";
} else { } else {
@ -2327,7 +2349,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isAbstractClass(): boolean { isAbstractClass(): boolean {
return ( 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: // export default interface allowed in:
// https://github.com/Microsoft/TypeScript/pull/16040 // https://github.com/Microsoft/TypeScript/pull/16040
if (this.state.value === "interface") { if (this.match(tt._interface)) {
const interfaceNode = this.startNode(); const interfaceNode = this.startNode();
this.next(); this.next();
const result = this.tsParseInterfaceDeclaration(interfaceNode); const result = this.tsParseInterfaceDeclaration(interfaceNode);
@ -2355,10 +2377,10 @@ export default (superClass: Class<Parser>): Class<Parser> =>
parseStatementContent(context: ?string, topLevel: ?boolean): N.Statement { parseStatementContent(context: ?string, topLevel: ?boolean): N.Statement {
if (this.state.type === tt._const) { if (this.state.type === tt._const) {
const ahead = this.lookahead(); const ahead = this.lookahead();
if (ahead.type === tt.name && ahead.value === "enum") { if (ahead.type === tt._enum) {
const node: N.TsEnumDeclaration = this.startNode(); const node: N.TsEnumDeclaration = this.startNode();
this.expect(tt._const); this.next(); // eat 'const'
this.expectContextual("enum"); this.expectContextual(tt._enum);
return this.tsParseEnumDeclaration(node, /* isConst */ true); return this.tsParseEnumDeclaration(node, /* isConst */ true);
} }
} }
@ -2380,7 +2402,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
tsIsStartOfStaticBlocks() { tsIsStartOfStaticBlocks() {
return ( return (
this.isContextual("static") && this.isContextual(tt._static) &&
this.lookaheadCharCode() === charCodes.leftCurlyBrace this.lookaheadCharCode() === charCodes.leftCurlyBrace
); );
} }
@ -2584,11 +2606,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
const startLoc = this.state.startLoc; const startLoc = this.state.startLoc;
// "export declare" is equivalent to just "export". // "export declare" is equivalent to just "export".
const isDeclare = this.eatContextual("declare"); const isDeclare = this.eatContextual(tt._declare);
if ( if (
isDeclare && isDeclare &&
(this.isContextual("declare") || !this.shouldParseExportDeclaration()) (this.isContextual(tt._declare) || !this.shouldParseExportDeclaration())
) { ) {
throw this.raise( throw this.raise(
this.state.start, this.state.start,
@ -2598,7 +2620,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
let declaration: ?N.Declaration; let declaration: ?N.Declaration;
if (this.match(tt.name)) { if (tokenIsIdentifier(this.state.type)) {
declaration = this.tsTryParseExportDeclaration(); declaration = this.tsTryParseExportDeclaration();
} }
if (!declaration) { if (!declaration) {
@ -2628,7 +2650,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isStatement: boolean, isStatement: boolean,
optionalId: ?boolean, optionalId: ?boolean,
): void { ): void {
if ((!isStatement || optionalId) && this.isContextual("implements")) { if ((!isStatement || optionalId) && this.isContextual(tt._implements)) {
return; return;
} }
@ -2738,7 +2760,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if (node.superClass && this.isRelational("<")) { if (node.superClass && this.isRelational("<")) {
node.superTypeParameters = this.tsParseTypeArguments(); node.superTypeParameters = this.tsParseTypeArguments();
} }
if (this.eatContextual("implements")) { if (this.eatContextual(tt._implements)) {
node.implements = this.tsParseHeritageClause("implements"); node.implements = this.tsParseHeritageClause("implements");
} }
} }
@ -3225,7 +3247,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
/* isStatement */ true, /* isStatement */ true,
/* optionalId */ false, /* optionalId */ false,
); );
} else if (this.isContextual("interface")) { } else if (this.isContextual(tt._interface)) {
// for invalid abstract interface // for invalid abstract interface
// To avoid // To avoid

View File

@ -1,5 +1,5 @@
import type Parser from "../parser"; import type Parser from "../parser";
import { tt } from "../tokenizer/types"; import { tokenIsIdentifier, tt } from "../tokenizer/types";
import * as N from "../types"; import * as N from "../types";
export default (superClass: Class<Parser>): Class<Parser> => export default (superClass: Class<Parser>): Class<Parser> =>
@ -9,8 +9,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
const v8IntrinsicStart = this.state.start; const v8IntrinsicStart = this.state.start;
// let the `loc` of Identifier starts from `%` // let the `loc` of Identifier starts from `%`
const node = this.startNode(); const node = this.startNode();
this.eat(tt.modulo); this.next(); // eat '%'
if (this.match(tt.name)) { if (tokenIsIdentifier(this.state.type)) {
const name = this.parseIdentifierName(this.state.start); const name = this.parseIdentifierName(this.state.start);
const identifier = this.createIdentifier(node, name); const identifier = this.createIdentifier(node, name);
identifier.type = "V8IntrinsicIdentifier"; 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 { match(type: TokenType): boolean {
return this.state.type === type; return this.state.type === type;
} }
@ -1565,8 +1570,14 @@ export default class Tokenizer extends ParserErrors {
readWord(firstCode: number | void): void { readWord(firstCode: number | void): void {
const word = this.readWord1(firstCode); const word = this.readWord1(firstCode);
const type = keywordTypes.get(word) || tt.name; const type = keywordTypes.get(word);
this.finishToken(type, 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 { 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>(); export const keywords = new Map<string, TokenType>();
function createKeyword(name: string, options: TokenOptions = {}): TokenType { function createKeyword(name: string, options: TokenOptions = {}): TokenType {
@ -111,19 +112,27 @@ function createToken(name: string, options: TokenOptions = {}): TokenType {
return tokenTypeCounter; 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. // 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. // When adding new token types, please also check if the token helpers need update.
export const tt: { [name: string]: TokenType } = { 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. // Punctuation token types.
bracketL: createToken("[", { beforeExpr, startsExpr }), bracketL: createToken("[", { beforeExpr, startsExpr }),
bracketHashL: createToken("#[", { beforeExpr, startsExpr }), bracketHashL: createToken("#[", { beforeExpr, startsExpr }),
@ -207,6 +216,7 @@ export const tt: { [name: string]: TokenType } = {
// Keywords // Keywords
// Don't forget to update packages/babel-helper-validator-identifier/src/keyword.js // Don't forget to update packages/babel-helper-validator-identifier/src/keyword.js
// when new keywords are added // when new keywords are added
// start: isLiteralPropertyName
// start: isKeyword // start: isKeyword
_in: createKeyword("in", { beforeExpr, binop: 7 }), _in: createKeyword("in", { beforeExpr, binop: 7 }),
_instanceof: createKeyword("instanceof", { beforeExpr, binop: 7 }), _instanceof: createKeyword("instanceof", { beforeExpr, binop: 7 }),
@ -248,6 +258,63 @@ export const tt: { [name: string]: TokenType } = {
// end: isLoop // end: isLoop
// end: isKeyword // 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 // jsx plugin
jsxName: createToken("jsxName"), jsxName: createToken("jsxName"),
jsxText: createToken("jsxText", { beforeExpr: true }), jsxText: createToken("jsxText", { beforeExpr: true }),
@ -258,6 +325,18 @@ export const tt: { [name: string]: TokenType } = {
placeholder: createToken("%%", { startsExpr: true }), 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 { export function tokenComesBeforeExpression(token: TokenType): boolean {
return tokenBeforeExprs[token]; return tokenBeforeExprs[token];
} }
@ -270,6 +349,10 @@ export function tokenIsAssignment(token: TokenType): boolean {
return token >= tt.eq && token <= tt.moduloAssign; 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 { export function tokenIsLoop(token: TokenType): boolean {
return token >= tt._do && token <= tt._while; return token >= tt._do && token <= tt._while;
} }
@ -290,6 +373,14 @@ export function tokenIsPrefix(token: TokenType): boolean {
return tokenPrefixes[token]; 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 { export function tokenLabelName(token: TokenType): string {
return tokenLabels[token]; 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": []
}
}