sucrase
Version:
Super-fast alternative to Babel for when you can target modern JS runtimes
1,323 lines • 50.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const context_1 = require("../tokenizer/context");
const types_1 = require("../tokenizer/types");
const charCodes = require("../util/charcodes");
function nonNull(x) {
if (x == null) {
// $FlowIgnore
throw new Error(`Unexpected ${x} value.`);
}
return x;
}
function assert(x) {
if (!x) {
throw new Error("Assert fail");
}
}
// Doesn't handle "void" or "null" because those are keywords, not identifiers.
function isTypeKeyword(value) {
switch (value) {
case "any":
case "boolean":
case "never":
case "number":
case "object":
case "string":
case "symbol":
case "undefined":
return true;
default:
return false;
}
}
exports.default = (superClass) => class extends superClass {
tsIsIdentifier() {
// TODO: actually a bit more complex in TypeScript, but shouldn't matter.
// See https://github.com/Microsoft/TypeScript/issues/15008
return this.match(types_1.types.name);
}
tsNextTokenCanFollowModifier() {
// Note: TypeScript's implementation is much more complicated because
// more things are considered modifiers there.
// This implementation only handles modifiers not handled by babylon itself. And "static".
// TODO: Would be nice to avoid lookahead. Want a hasLineBreakUpNext() method...
this.next();
return (!this.hasPrecedingLineBreak() &&
!this.match(types_1.types.parenL) &&
!this.match(types_1.types.colon) &&
!this.match(types_1.types.eq) &&
!this.match(types_1.types.question));
}
/** Parses a modifier matching one the given modifier names. */
tsParseModifier(allowedModifiers) {
if (!this.match(types_1.types.name)) {
return null;
}
const modifier = this.state.value;
if (allowedModifiers.indexOf(modifier) !== -1 &&
this.tsTryParse(() => this.tsNextTokenCanFollowModifier())) {
switch (modifier) {
case "readonly":
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._readonly;
break;
case "abstract":
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._abstract;
break;
case "static":
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._static;
break;
case "public":
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._public;
break;
case "private":
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._private;
break;
case "protected":
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._protected;
break;
default:
break;
}
return modifier;
}
return null;
}
tsIsListTerminator(kind) {
switch (kind) {
case "EnumMembers":
case "TypeMembers":
return this.match(types_1.types.braceR);
case "HeritageClauseElement":
return this.match(types_1.types.braceL);
case "TupleElementTypes":
return this.match(types_1.types.bracketR);
case "TypeParametersOrArguments":
return this.match(types_1.types.greaterThan);
default:
break;
}
throw new Error("Unreachable");
}
tsParseList(kind, parseElement) {
while (!this.tsIsListTerminator(kind)) {
// Skipping "parseListElement" from the TS source since that's just for error handling.
parseElement();
}
}
tsParseDelimitedList(kind, parseElement) {
this.tsParseDelimitedListWorker(kind, parseElement);
}
/**
* If !expectSuccess, returns undefined instead of failing to parse.
* If expectSuccess, parseElement should always return a defined value.
*/
tsParseDelimitedListWorker(kind, parseElement) {
while (true) {
if (this.tsIsListTerminator(kind)) {
break;
}
parseElement();
if (this.eat(types_1.types.comma)) {
continue;
}
if (this.tsIsListTerminator(kind)) {
break;
}
}
}
tsParseBracketedList(kind, parseElement, bracket, skipFirstToken) {
if (!skipFirstToken) {
if (bracket) {
this.expect(types_1.types.bracketL);
}
else {
this.expect(types_1.types.lessThan);
}
}
this.tsParseDelimitedList(kind, parseElement);
if (bracket) {
this.expect(types_1.types.bracketR);
}
else {
this.expect(types_1.types.greaterThan);
}
}
tsParseEntityName() {
this.parseIdentifier();
while (this.eat(types_1.types.dot)) {
this.parseIdentifier();
}
}
tsParseTypeReference() {
this.tsParseEntityName();
if (!this.hasPrecedingLineBreak() && this.match(types_1.types.lessThan)) {
this.tsParseTypeArguments();
}
}
tsParseThisTypePredicate() {
this.next();
this.tsParseTypeAnnotation(/* eatColon */ false);
}
tsParseThisTypeNode() {
this.next();
}
tsParseTypeQuery() {
this.expect(types_1.types._typeof);
this.tsParseEntityName();
}
tsParseTypeParameter() {
this.parseIdentifier();
if (this.eat(types_1.types._extends)) {
this.tsParseType();
}
if (this.eat(types_1.types.eq)) {
this.tsParseType();
}
}
tsTryParseTypeParameters() {
if (this.match(types_1.types.lessThan)) {
this.tsParseTypeParameters();
}
}
tsParseTypeParameters() {
this.runInTypeContext(0, () => {
if (this.match(types_1.types.lessThan) || this.match(types_1.types.typeParameterStart)) {
this.next();
}
else {
this.unexpected();
}
this.tsParseBracketedList("TypeParametersOrArguments", this.tsParseTypeParameter.bind(this),
/* bracket */ false,
/* skipFirstToken */ true);
});
}
// Note: In TypeScript implementation we must provide `yieldContext` and `awaitContext`,
// but here it's always false, because this is only used for types.
tsFillSignature(returnToken) {
// Arrow fns *must* have return token (`=>`). Normal functions can omit it.
const returnTokenRequired = returnToken === types_1.types.arrow;
this.tsTryParseTypeParameters();
this.expect(types_1.types.parenL);
this.tsParseBindingListForSignature(false /* isBlockScope */);
if (returnTokenRequired) {
this.tsParseTypeOrTypePredicateAnnotation(returnToken);
}
else if (this.match(returnToken)) {
this.tsParseTypeOrTypePredicateAnnotation(returnToken);
}
}
tsParseBindingListForSignature(isBlockScope) {
this.parseBindingList(types_1.types.parenR, isBlockScope);
}
tsParseTypeMemberSemicolon() {
if (!this.eat(types_1.types.comma)) {
this.semicolon();
}
}
tsParseSignatureMember(kind) {
if (kind === "TSConstructSignatureDeclaration") {
this.expect(types_1.types._new);
}
this.tsFillSignature(types_1.types.colon);
this.tsParseTypeMemberSemicolon();
}
tsIsUnambiguouslyIndexSignature() {
this.next(); // Skip '{'
return this.eat(types_1.types.name) && this.match(types_1.types.colon);
}
tsTryParseIndexSignature() {
if (!(this.match(types_1.types.bracketL) &&
this.tsLookAhead(this.tsIsUnambiguouslyIndexSignature.bind(this)))) {
return false;
}
this.expect(types_1.types.bracketL);
this.parseIdentifier();
this.expect(types_1.types.colon);
this.tsParseTypeAnnotation(/* eatColon */ false);
this.expect(types_1.types.bracketR);
this.tsTryParseTypeAnnotation();
this.tsParseTypeMemberSemicolon();
return true;
}
tsParsePropertyOrMethodSignature(readonly) {
this.parsePropertyName(-1 /* Types don't need context IDs. */);
this.eat(types_1.types.question);
if (!readonly && (this.match(types_1.types.parenL) || this.match(types_1.types.lessThan))) {
this.tsFillSignature(types_1.types.colon);
this.tsParseTypeMemberSemicolon();
}
else {
this.tsTryParseTypeAnnotation();
this.tsParseTypeMemberSemicolon();
}
}
tsParseTypeMember() {
if (this.match(types_1.types.parenL) || this.match(types_1.types.lessThan)) {
this.tsParseSignatureMember("TSCallSignatureDeclaration");
return;
}
if (this.match(types_1.types._new) && this.tsLookAhead(this.tsIsStartOfConstructSignature.bind(this))) {
this.tsParseSignatureMember("TSConstructSignatureDeclaration");
return;
}
const readonly = !!this.tsParseModifier(["readonly"]);
const found = this.tsTryParseIndexSignature();
if (found) {
return;
}
this.tsParsePropertyOrMethodSignature(readonly);
}
tsIsStartOfConstructSignature() {
this.next();
return this.match(types_1.types.parenL) || this.match(types_1.types.lessThan);
}
tsParseTypeLiteral() {
this.tsParseObjectTypeMembers();
}
tsParseObjectTypeMembers() {
this.expect(types_1.types.braceL);
this.tsParseList("TypeMembers", this.tsParseTypeMember.bind(this));
this.expect(types_1.types.braceR);
}
tsIsStartOfMappedType() {
this.next();
if (this.isContextual("readonly")) {
this.next();
}
if (!this.match(types_1.types.bracketL)) {
return false;
}
this.next();
if (!this.tsIsIdentifier()) {
return false;
}
this.next();
return this.match(types_1.types._in);
}
tsParseMappedTypeParameter() {
this.parseIdentifier();
this.expect(types_1.types._in);
this.tsParseType();
}
tsParseMappedType() {
this.expect(types_1.types.braceL);
this.eatContextual("readonly");
this.expect(types_1.types.bracketL);
this.tsParseMappedTypeParameter();
this.expect(types_1.types.bracketR);
this.eat(types_1.types.question);
this.tsTryParseType();
this.semicolon();
this.expect(types_1.types.braceR);
}
tsParseTupleType() {
this.tsParseBracketedList("TupleElementTypes", this.tsParseType.bind(this),
/* bracket */ true,
/* skipFirstToken */ false);
}
tsParseParenthesizedType() {
this.expect(types_1.types.parenL);
this.tsParseType();
this.expect(types_1.types.parenR);
}
tsParseFunctionOrConstructorType(type) {
if (type === "TSConstructorType") {
this.expect(types_1.types._new);
}
this.tsFillSignature(types_1.types.arrow);
}
tsParseNonArrayType() {
switch (this.state.type) {
case types_1.types.name:
case types_1.types._void:
case types_1.types._null: {
if (this.match(types_1.types._void) || this.match(types_1.types._null) || isTypeKeyword(this.state.value)) {
this.next();
return;
}
this.tsParseTypeReference();
return;
}
case types_1.types.string:
case types_1.types.num:
case types_1.types._true:
case types_1.types._false:
this.parseLiteral();
return;
case types_1.types.plusMin:
// Allow negative signs but not plus signs before numbers.
if (this.state.value === "-") {
this.next();
this.parseLiteral();
return;
}
break;
case types_1.types._this: {
this.tsParseThisTypeNode();
if (this.isContextual("is") && !this.hasPrecedingLineBreak()) {
this.tsParseThisTypePredicate();
}
return;
}
case types_1.types._typeof:
this.tsParseTypeQuery();
return;
case types_1.types.braceL:
if (this.tsLookAhead(this.tsIsStartOfMappedType.bind(this))) {
this.tsParseMappedType();
}
else {
this.tsParseTypeLiteral();
}
return;
case types_1.types.bracketL:
this.tsParseTupleType();
return;
case types_1.types.parenL:
this.tsParseParenthesizedType();
return;
default:
break;
}
throw this.unexpected();
}
tsParseArrayTypeOrHigher() {
this.tsParseNonArrayType();
while (!this.hasPrecedingLineBreak() && this.eat(types_1.types.bracketL)) {
if (!this.eat(types_1.types.bracketR)) {
// If we hit ] immediately, this is an array type, otherwise it's an indexed access type.
this.tsParseType();
this.expect(types_1.types.bracketR);
}
}
}
tsParseTypeOperator(operator) {
this.expectContextual(operator);
this.tsParseTypeOperatorOrHigher();
}
tsParseTypeOperatorOrHigher() {
if (this.isContextual("keyof")) {
this.tsParseTypeOperator("keyof");
}
else {
this.tsParseArrayTypeOrHigher();
}
}
tsParseUnionOrIntersectionType(kind, parseConstituentType, operator) {
this.eat(operator);
parseConstituentType();
if (this.match(operator)) {
while (this.eat(operator)) {
parseConstituentType();
}
}
}
tsParseIntersectionTypeOrHigher() {
this.tsParseUnionOrIntersectionType("TSIntersectionType", this.tsParseTypeOperatorOrHigher.bind(this), types_1.types.bitwiseAND);
}
tsParseUnionTypeOrHigher() {
this.tsParseUnionOrIntersectionType("TSUnionType", this.tsParseIntersectionTypeOrHigher.bind(this), types_1.types.bitwiseOR);
}
tsIsStartOfFunctionType() {
if (this.match(types_1.types.lessThan)) {
return true;
}
return (this.match(types_1.types.parenL) &&
this.tsLookAhead(this.tsIsUnambiguouslyStartOfFunctionType.bind(this)));
}
tsSkipParameterStart() {
if (this.match(types_1.types.name) || this.match(types_1.types._this)) {
this.next();
return true;
}
return false;
}
tsIsUnambiguouslyStartOfFunctionType() {
this.next();
if (this.match(types_1.types.parenR) || this.match(types_1.types.ellipsis)) {
// ( )
// ( ...
return true;
}
if (this.tsSkipParameterStart()) {
if (this.match(types_1.types.colon) ||
this.match(types_1.types.comma) ||
this.match(types_1.types.question) ||
this.match(types_1.types.eq)) {
// ( xxx :
// ( xxx ,
// ( xxx ?
// ( xxx =
return true;
}
if (this.match(types_1.types.parenR)) {
this.next();
if (this.match(types_1.types.arrow)) {
// ( xxx ) =>
return true;
}
}
}
return false;
}
tsParseTypeOrTypePredicateAnnotation(returnToken) {
this.runInTypeContext(0, () => {
this.expect(returnToken);
this.tsTryParse(() => this.tsParseTypePredicatePrefix());
// Regardless of whether we found an "is" token, there's now just a regular type in front of
// us.
this.tsParseTypeAnnotation(/* eatColon */ false);
});
}
tsTryParseTypeOrTypePredicateAnnotation() {
if (this.match(types_1.types.colon)) {
this.tsParseTypeOrTypePredicateAnnotation(types_1.types.colon);
}
}
tsTryParseTypeAnnotation() {
if (this.match(types_1.types.colon)) {
this.tsParseTypeAnnotation();
}
}
tsTryParseType() {
if (this.eat(types_1.types.colon)) {
this.tsParseType();
}
}
tsParseTypePredicatePrefix() {
this.parseIdentifier();
if (this.isContextual("is") && !this.hasPrecedingLineBreak()) {
this.next();
return true;
}
return false;
}
tsParseTypeAnnotation(eatColon = true) {
this.runInTypeContext(0, () => {
if (eatColon) {
this.expect(types_1.types.colon);
}
this.tsParseType();
});
}
tsParseType() {
// Need to set `state.inType` so that we don't parse JSX in a type context.
const oldInType = this.state.inType;
this.state.inType = true;
try {
if (this.tsIsStartOfFunctionType()) {
this.tsParseFunctionOrConstructorType("TSFunctionType");
return;
}
if (this.match(types_1.types._new)) {
// As in `new () => Date`
this.tsParseFunctionOrConstructorType("TSConstructorType");
return;
}
this.tsParseUnionTypeOrHigher();
}
finally {
this.state.inType = oldInType;
}
}
tsParseTypeAssertion() {
this.runInTypeContext(1, () => {
this.tsParseType();
this.expect(types_1.types.greaterThan);
});
this.parseMaybeUnary();
}
// Returns true if parsing was successful.
tsTryParseTypeArgumentsInExpression() {
return this.tsTryParseAndCatch(() => {
this.runInTypeContext(0, () => {
this.expect(types_1.types.lessThan);
this.state.tokens[this.state.tokens.length - 1].type = types_1.types.typeParameterStart;
this.tsParseDelimitedList("TypeParametersOrArguments", this.tsParseType.bind(this));
this.expect(types_1.types.greaterThan);
});
this.expect(types_1.types.parenL);
});
}
tsParseHeritageClause() {
this.tsParseDelimitedList("HeritageClauseElement", this.tsParseExpressionWithTypeArguments.bind(this));
}
tsParseExpressionWithTypeArguments() {
// Note: TS uses parseLeftHandSideExpressionOrHigher,
// then has grammar errors later if it's not an EntityName.
this.tsParseEntityName();
if (this.match(types_1.types.lessThan)) {
this.tsParseTypeArguments();
}
}
tsParseInterfaceDeclaration() {
this.parseIdentifier();
this.tsTryParseTypeParameters();
if (this.eat(types_1.types._extends)) {
this.tsParseHeritageClause();
}
this.tsParseObjectTypeMembers();
}
tsParseTypeAliasDeclaration() {
this.parseIdentifier();
this.tsTryParseTypeParameters();
this.expect(types_1.types.eq);
this.tsParseType();
this.semicolon();
}
tsParseEnumMember() {
// Computed property names are grammar errors in an enum, so accept just string literal or identifier.
if (this.match(types_1.types.string)) {
this.parseLiteral();
}
else {
this.parseIdentifier();
}
if (this.eat(types_1.types.eq)) {
const eqIndex = this.state.tokens.length - 1;
this.parseMaybeAssign();
this.state.tokens[eqIndex].rhsEndIndex = this.state.tokens.length;
}
}
tsParseEnumDeclaration() {
this.parseIdentifier();
this.expect(types_1.types.braceL);
this.tsParseDelimitedList("EnumMembers", this.tsParseEnumMember.bind(this));
this.expect(types_1.types.braceR);
}
tsParseModuleBlock() {
this.expect(types_1.types.braceL);
// Inside of a module block is considered "top-level", meaning it can have imports and exports.
this.parseBlockBody(/* topLevel */ true, /* end */ types_1.types.braceR);
}
tsParseModuleOrNamespaceDeclaration() {
this.parseIdentifier();
if (this.eat(types_1.types.dot)) {
this.tsParseModuleOrNamespaceDeclaration();
}
else {
this.tsParseModuleBlock();
}
}
tsParseAmbientExternalModuleDeclaration() {
if (this.isContextual("global")) {
this.parseIdentifier();
}
else if (this.match(types_1.types.string)) {
this.parseExprAtom();
}
else {
this.unexpected();
}
if (this.match(types_1.types.braceL)) {
this.tsParseModuleBlock();
}
else {
this.semicolon();
}
}
tsParseImportEqualsDeclaration() {
this.parseIdentifier();
this.expect(types_1.types.eq);
this.tsParseModuleReference();
this.semicolon();
}
tsIsExternalModuleReference() {
return this.isContextual("require") && this.lookaheadType() === types_1.types.parenL;
}
tsParseModuleReference() {
if (this.tsIsExternalModuleReference()) {
this.tsParseExternalModuleReference();
}
else {
this.tsParseEntityName();
}
}
tsParseExternalModuleReference() {
this.expectContextual("require");
this.expect(types_1.types.parenL);
if (!this.match(types_1.types.string)) {
throw this.unexpected();
}
this.parseLiteral();
this.expect(types_1.types.parenR);
}
// Utilities
tsLookAhead(f) {
const snapshot = this.state.snapshot();
const res = f();
this.state.restoreFromSnapshot(snapshot);
return res;
}
// Returns true if parsing was successful.
tsTryParseAndCatch(f) {
const snapshot = this.state.snapshot();
try {
f();
return true;
}
catch (e) {
if (e instanceof SyntaxError) {
this.state.restoreFromSnapshot(snapshot);
return false;
}
throw e;
}
}
// The function should return true if the parse was successful. If not, we revert the state to
// before we started parsing.
tsTryParse(f) {
const snapshot = this.state.snapshot();
const wasSuccessful = f();
if (wasSuccessful) {
return true;
}
else {
this.state.restoreFromSnapshot(snapshot);
return false;
}
}
// Returns true if a statement matched.
tsTryParseDeclare() {
switch (this.state.type) {
case types_1.types._function:
this.runInTypeContext(1, () => {
this.next();
// We don't need to precisely get the function start here, since it's only used to mark
// the function as a type if it's bodiless, and it's already a type here.
const functionStart = this.state.start;
this.parseFunction(functionStart, /* isStatement */ true);
});
return true;
case types_1.types._class:
this.runInTypeContext(1, () => {
this.parseClass(/* isStatement */ true, /* optionalId */ false);
});
return true;
case types_1.types._const:
if (this.match(types_1.types._const) && this.isLookaheadContextual("enum")) {
this.runInTypeContext(1, () => {
// `const enum = 0;` not allowed because "enum" is a strict mode reserved word.
this.expect(types_1.types._const);
this.expectContextual("enum");
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._enum;
this.tsParseEnumDeclaration();
});
return true;
}
// falls through
case types_1.types._var:
case types_1.types._let:
this.runInTypeContext(1, () => {
this.parseVarStatement(this.state.type);
});
return true;
case types_1.types.name: {
return this.runInTypeContext(1, () => {
const value = this.state.value;
if (value === "global") {
this.tsParseAmbientExternalModuleDeclaration();
return true;
}
else {
return this.tsParseDeclaration(value, /* next */ true);
}
});
}
default:
return false;
}
}
// Note: this won't be called unless the keyword is allowed in `shouldParseExportDeclaration`.
// Returns true if it matched a declaration.
tsTryParseExportDeclaration() {
return this.tsParseDeclaration(this.state.value, /* next */ true);
}
// Returns true if it matched a statement.
tsParseExpressionStatement(name) {
switch (name) {
case "declare": {
const declareTokenIndex = this.state.tokens.length - 1;
const matched = this.tsTryParseDeclare();
if (matched) {
this.state.tokens[declareTokenIndex].type = types_1.types._declare;
return true;
}
break;
}
case "global":
// `global { }` (with no `declare`) may appear inside an ambient module declaration.
// Would like to use tsParseAmbientExternalModuleDeclaration here, but already ran past "global".
if (this.match(types_1.types.braceL)) {
this.tsParseModuleBlock();
return true;
}
break;
default:
return this.tsParseDeclaration(name, /* next */ false);
}
return false;
}
// Common to tsTryParseDeclare, tsTryParseExportDeclaration, and tsParseExpressionStatement.
// Returns true if it matched a declaration.
tsParseDeclaration(name, next) {
switch (name) {
case "abstract":
if (next || this.match(types_1.types._class)) {
if (next)
this.next();
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._abstract;
this.parseClass(/* isStatement */ true, /* optionalId */ false);
return true;
}
break;
case "enum":
if (next || this.match(types_1.types.name)) {
if (next)
this.next();
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._enum;
this.tsParseEnumDeclaration();
return true;
}
break;
case "interface":
if (next || this.match(types_1.types.name)) {
// `next` is true in "export" and "declare" contexts, so we want to remove that token
// as well.
this.runInTypeContext(1, () => {
if (next)
this.next();
this.tsParseInterfaceDeclaration();
});
return true;
}
break;
case "module":
if (next)
this.next();
if (this.match(types_1.types.string)) {
this.runInTypeContext(next ? 2 : 1, () => {
this.tsParseAmbientExternalModuleDeclaration();
});
return true;
}
else if (next || this.match(types_1.types.name)) {
this.runInTypeContext(next ? 2 : 1, () => {
this.tsParseModuleOrNamespaceDeclaration();
});
return true;
}
break;
case "namespace":
if (next || this.match(types_1.types.name)) {
this.runInTypeContext(1, () => {
if (next)
this.next();
this.tsParseModuleOrNamespaceDeclaration();
});
return true;
}
break;
case "type":
if (next || this.match(types_1.types.name)) {
this.runInTypeContext(1, () => {
if (next)
this.next();
this.tsParseTypeAliasDeclaration();
});
return true;
}
break;
default:
break;
}
return false;
}
// Returns true if there was a generic async arrow function.
tsTryParseGenericAsyncArrowFunction() {
const matched = this.tsTryParseAndCatch(() => {
this.tsParseTypeParameters();
// Don't use overloaded parseFunctionParams which would look for "<" again.
super.parseFunctionParams();
this.tsTryParseTypeOrTypePredicateAnnotation();
this.expect(types_1.types.arrow);
});
if (!matched) {
return false;
}
// We don't need to be precise about the function start since it's only used if this is a
// bodiless function, which isn't valid here.
const functionStart = this.state.start;
this.parseFunctionBody(functionStart, false /* isGenerator */, true);
return true;
}
tsParseTypeArguments() {
this.runInTypeContext(0, () => {
this.expect(types_1.types.lessThan);
this.tsParseDelimitedList("TypeParametersOrArguments", this.tsParseType.bind(this));
this.expect(types_1.types.greaterThan);
});
}
tsIsDeclarationStart() {
if (this.match(types_1.types.name)) {
switch (this.state.value) {
case "abstract":
case "declare":
case "enum":
case "interface":
case "module":
case "namespace":
case "type":
return true;
default:
break;
}
}
return false;
}
// ======================================================
// OVERRIDES
// ======================================================
isExportDefaultSpecifier() {
if (this.tsIsDeclarationStart())
return false;
return super.isExportDefaultSpecifier();
}
parseAssignableListItem(allowModifiers, isBlockScope) {
if (allowModifiers) {
this.parseAccessModifier();
this.tsParseModifier(["readonly"]);
}
this.parseMaybeDefault(isBlockScope);
this.parseAssignableListItemTypes();
this.parseMaybeDefault(isBlockScope, true);
}
parseFunctionBodyAndFinish(functionStart, isGenerator, allowExpressionBody, funcContextId) {
// For arrow functions, `parseArrow` handles the return type itself.
if (!allowExpressionBody && this.match(types_1.types.colon)) {
this.tsParseTypeOrTypePredicateAnnotation(types_1.types.colon);
}
// The original code checked the node type to make sure this function type allows a missing
// body, but we skip that to avoid sending around the node type. We instead just use the
// allowExpressionBody boolean to make sure it's not an arrow function.
if (!allowExpressionBody && !this.match(types_1.types.braceL) && this.isLineTerminator()) {
// Retroactively mark the function declaration as a type.
let i = this.state.tokens.length - 1;
while (i >= 0 &&
(this.state.tokens[i].start >= functionStart ||
this.state.tokens[i].type === types_1.types._default ||
this.state.tokens[i].type === types_1.types._export)) {
this.state.tokens[i].isType = true;
i--;
}
return;
}
super.parseFunctionBodyAndFinish(functionStart, isGenerator, allowExpressionBody, funcContextId);
}
parseSubscript(startPos, noCalls, state) {
if (!this.hasPrecedingLineBreak() && this.eat(types_1.types.bang)) {
return;
}
if (!noCalls && this.match(types_1.types.lessThan)) {
if (this.atPossibleAsync()) {
// Almost certainly this is a generic async function `async <T>() => ...
// But it might be a call with a type argument `async<T>();`
const asyncArrowFn = this.tsTryParseGenericAsyncArrowFunction();
if (asyncArrowFn) {
return;
}
}
// May be passing type arguments. But may just be the `<` operator.
const typeArguments = this.tsTryParseTypeArgumentsInExpression(); // Also eats the "("
if (typeArguments) {
// possibleAsync always false here, because we would have handled it above.
this.parseCallExpressionArguments(types_1.types.parenR);
}
}
super.parseSubscript(startPos, noCalls, state);
}
parseNewArguments() {
if (this.match(types_1.types.lessThan)) {
// tsTryParseAndCatch is expensive, so avoid if not necessary.
// 99% certain this is `new C<T>();`. But may be `new C < T;`, which is also legal.
this.tsTryParseAndCatch(() => {
this.state.type = types_1.types.typeParameterStart;
this.tsParseTypeArguments();
if (!this.match(types_1.types.parenL)) {
this.unexpected();
}
});
}
super.parseNewArguments();
}
parseExprOp(minPrec, noIn) {
if (nonNull(types_1.types._in.binop) > minPrec &&
!this.hasPrecedingLineBreak() &&
this.eatContextual("as")) {
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._as;
this.runInTypeContext(1, () => {
this.tsParseType();
});
this.parseExprOp(minPrec, noIn);
return;
}
super.parseExprOp(minPrec, noIn);
}
/*
Don't bother doing this check in TypeScript code because:
1. We may have a nested export statement with the same name:
export const x = 0;
export namespace N {
export const x = 1;
}
2. We have a type checker to warn us about this sort of thing.
*/
checkDuplicateExports() { }
parseImport() {
if (this.match(types_1.types.name) && this.lookaheadType() === types_1.types.eq) {
this.tsParseImportEqualsDeclaration();
return;
}
super.parseImport();
}
parseExport() {
if (this.match(types_1.types._import)) {
// `export import A = B;`
this.expect(types_1.types._import);
this.tsParseImportEqualsDeclaration();
}
else if (this.eat(types_1.types.eq)) {
// `export = x;`
this.parseExpression();
this.semicolon();
}
else if (this.eatContextual("as")) {
// `export as namespace A;`
// See `parseNamespaceExportDeclaration` in TypeScript's own parser
this.expectContextual("namespace");
this.parseIdentifier();
this.semicolon();
}
else {
super.parseExport();
}
}
parseExportDefaultExpression() {
if (this.isContextual("abstract") && this.lookaheadType() === types_1.types._class) {
this.state.type = types_1.types._abstract;
this.next(); // Skip "abstract"
this.parseClass(true, true);
return;
}
super.parseExportDefaultExpression();
}
parseStatementContent(declaration, topLevel = false) {
if (this.state.type === types_1.types._const) {
const ahead = this.lookaheadTypeAndValue();
if (ahead.type === types_1.types.name && ahead.value === "enum") {
this.expect(types_1.types._const);
this.expectContextual("enum");
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._enum;
this.tsParseEnumDeclaration();
return;
}
}
super.parseStatementContent(declaration, topLevel);
}
parseAccessModifier() {
this.tsParseModifier(["public", "protected", "private"]);
}
parseClassMember(memberStart, classContextId) {
this.parseAccessModifier();
super.parseClassMember(memberStart, classContextId);
}
parseClassMemberWithIsStatic(memberStart, isStatic, classContextId) {
let isAbstract = false;
let isReadonly = false;
const mod = this.tsParseModifier(["abstract", "readonly"]);
switch (mod) {
case "readonly":
isReadonly = true;
isAbstract = !!this.tsParseModifier(["abstract"]);
break;
case "abstract":
isAbstract = true;
isReadonly = !!this.tsParseModifier(["readonly"]);
break;
default:
break;
}
// We no longer check for public/private/etc, but tsTryParseIndexSignature should just return
// false in that case for valid code.
if (!isAbstract && !isStatic) {
const found = this.tsTryParseIndexSignature();
if (found) {
return;
}
}
if (isReadonly) {
// Must be a property (if not an index signature).
this.parseClassPropertyName(classContextId);
this.parsePostMemberNameModifiers();
this.parseClassProperty();
return;
}
super.parseClassMemberWithIsStatic(memberStart, isStatic, classContextId);
}
parsePostMemberNameModifiers() {
this.eat(types_1.types.question);
}
// Note: The reason we do this in `parseIdentifierStatement` and not `parseStatement`
// is that e.g. `type()` is valid JS, so we must try parsing that first.
// If it's really a type, we will parse `type` as the statement, and can correct it here
// by parsing the rest.
parseIdentifierStatement(name) {
const matched = this.tsParseExpressionStatement(name);
if (!matched) {
super.parseIdentifierStatement(name);
}
}
// export type
// Should be true for anything parsed by `tsTryParseExportDeclaration`.
shouldParseExportDeclaration() {
if (this.tsIsDeclarationStart())
return true;
return super.shouldParseExportDeclaration();
}
// An apparent conditional expression could actually be an optional parameter in an arrow function.
parseConditional(noIn, startPos) {
// only do the expensive clone if there is a question mark
// and if we come from inside parens
if (!this.match(types_1.types.question)) {
super.parseConditional(noIn, startPos);
return;
}
const snapshot = this.state.snapshot();
try {
super.parseConditional(noIn, startPos);
return;
}
catch (err) {
if (!(err instanceof SyntaxError)) {
// istanbul ignore next: no such error is expected
throw err;
}
this.state.restoreFromSnapshot(snapshot);
}
}
// Note: These "type casts" are *not* valid TS expressions.
// But we parse them here and change them when completing the arrow function.
parseParenItem() {
super.parseParenItem();
if (this.eat(types_1.types.question)) {
this.state.tokens[this.state.tokens.length - 1].isType = true;
}
if (this.match(types_1.types.colon)) {
this.tsParseTypeAnnotation();
}
}
parseExportDeclaration() {
// "export declare" is equivalent to just "export".
const isDeclare = this.eatContextual("declare");
if (isDeclare) {
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._declare;
}
let matchedDeclaration = false;
if (this.match(types_1.types.name)) {
if (isDeclare) {
matchedDeclaration = this.runInTypeContext(2, () => this.tsTryParseExportDeclaration());
}
else {
matchedDeclaration = this.tsTryParseExportDeclaration();
}
}
if (!matchedDeclaration) {
if (isDeclare) {
this.runInTypeContext(2, () => {
super.parseExportDeclaration();
});
}
else {
super.parseExportDeclaration();
}
}
}
parseClassId(isStatement, optionalId = false) {
if ((!isStatement || optionalId) && this.isContextual("implements")) {
return;
}
super.parseClassId(isStatement, optionalId);
this.tsTryParseTypeParameters();
}
parseClassProperty() {
this.tsTryParseTypeAnnotation();
super.parseClassProperty();
}
parseClassMethod(functionStart, isGenerator, isConstructor) {
this.tsTryParseTypeParameters();
super.parseClassMethod(functionStart, isGenerator, isConstructor);
}
parseClassSuper() {
const hasSuper = super.parseClassSuper();
if (hasSuper && this.match(types_1.types.lessThan)) {
this.tsParseTypeArguments();
}
if (this.eatContextual("implements")) {
this.state.tokens[this.state.tokens.length - 1].type = types_1.types._implements;
this.runInTypeContext(1, () => {
this.tsParseHeritageClause();
});
}
return hasSuper;
}
parseObjPropValue(isGenerator, isPattern, isBlockScope, objectContextId) {
if (this.match(types_1.types.lessThan)) {
throw new Error("TODO");
}
super.parseObjPropValue(isGenerator, isPattern, isBlockScope, objectContextId);
}
parseFunctionParams(allowModifiers, contextId) {
this.tsTryParseTypeParameters();
super.parseFunctionParams(allowModifiers, contextId);
}
// `let x: number;`
parseVarHead(isBlockScope) {
super.parseVarHead(isBlockScope);
this.tsTryParseTypeAnnotation();
}
// parse the return type of an async arrow function - let foo = (async (): number => {});
parseAsyncArrowFromCallExpression(functionStart, startTokenIndex) {
if (this.match(types_1.types.colon)) {
this.tsParseTypeAnnotation();
}
super.parseAsyncArrowFromCallExpression(functionStart, startTokenIndex);
}
// Returns true if the expression was an arrow function.
parseMaybeAssign(noIn = null, afterLeftParse) {
// Note: When the JSX plugin is on, type assertions (`<T> x`) aren't valid syntax.
let jsxError = null;
if (this.match(types_1.types.jsxTagStart)) {
const context = this.curContext();
assert(context === context_1.types.j_oTag);
// Only time j_oTag is pushed is right after j_expr.
assert(this.state.context[this.state.context.length - 2] === context_1.types.j_expr);
// Prefer to parse JSX if possible. But may be an arrow fn.
const snapshot = this.state.snapshot();
try {
return super.parseMaybeAssign(noIn, afterLeftParse);
}
catch (err) {
if (!(err instanceof SyntaxError)) {
// istanbul ignore next: no such error is expected
throw err;
}
this.state.restoreFromSnapshot(snapshot);
this.state.type = types_1.types.typeParameterStart;
// Pop the context added by the jsxTagStart.
assert(this.curContext() === context_1.types.j_oTag);
this.state.context.pop();
assert(this.curContext() === context_1.types.j_expr);
this.state.context.pop();
jsxError = err;
}
}
if (jsxError === null && !this.match(types_1.types.lessThan)) {
return super.parseMaybeAssign(noIn, afterLeftParse);
}
// Either way, we're looking at a '<': tt.typeParameterStart or relational.
let wasArrow = false;
const snapshot = this.state.snapshot();
try {
// This is similar to TypeScript's `tryParseParenthesizedArrowFunctionExpression`.
this.runInTypeContext(0, () => {
this.tsParseTypeParameters();
});
wasArrow = super.parseMaybeAssign(noIn, afterLeftParse);
if (!wasArrow) {
this.unexpected(); // Go to the catch block (needs a SyntaxError).
}
}
catch (err) {
if (!(err instanceof SyntaxError)) {
// istanbul ignore next: no such error is expected
throw err;
}
if (jsxError) {
throw jsxError;
}
// Try parsing a type cast instead of an arrow function.
// This will never happen outside of JSX.
// (Because in JSX the '<' should be a jsxTagStart and not a relational.
assert(!this.hasPlugin("jsx"));
// Parsing an arrow function failed, so try a type cast.
this.state.restoreFromSnapshot(snapshot);
// This will start with a type assertion (via parseMaybeUnary).
// But don't directly call `this.tsParseTypeAssertion` because we want to handle any binary after it.
return super.parseMaybeAssign(noIn, afterLeftParse);
}
return wasArrow;
}
// Handle type assertions
parseMaybeUnary() {
if (!this.hasPlugin("jsx") && this.eat(types_1.types.lessThan)) {
this.tsParseTypeAssertion();
return false;
}
else {
return super.parseMaybeUnary();
}
}
parseArrow() {
if (this.match(types_1.types.colon)) {
// This is different from how the TS parser does it.
// TS uses lookahead. Babylon parses it as a parenthesized expression and converts.
const snapshot = this.state.snapshot();
try {
this.tsParseTypeOrTypePredicateAnnotation(types_1.types.colon);
if (this.canInsertSemicolon())
this.unexpected();
if (!this.match(types_1.types.arrow))
this.unexpected();
}
catch (err) {
if (err instanceof SyntaxError) {
this.state.restoreFromSnapshot(snapshot);
}
else {
// istanbul ignore next: no such error is expected
throw err;
}
}
}
return super.parseArrow();
}
// Allow type annotations inside of a parameter list.
parseAssignableListItemTypes() {
this.runInTypeContext(0, () => {
this.eat(types_1.types.question);
this.tsTryParseTypeAnnotation();
});
}
parseBindingAtom(isBlockScope) {
switch (this.state.type) {
case types_1.types._this:
// "this" may be the name of a parameter, so allow it.
this.runInTypeContext(0, () => {
this.parseIdentifier();
});
return;
default:
super.parseBindingAtom(isBlockScope);
}
}
// === === === === === === === === === === === === === === === ===
// Note: All below methods are duplicates of something in flow.js.
// Not sure what the best way to combine these is.
// === === === === === === === === === === === === === === === ===
isClassMethod() {
return this.match(types_1.types.lessThan) || super.isClassMethod();
}
isClassProperty() {
return this.match(types_1.