TypeScript parser plugin (#523)

This commit is contained in:
Andy
2017-06-28 07:57:50 -07:00
committed by Henry Zhu
parent f7547fd35a
commit 97c23461f9
296 changed files with 33129 additions and 185 deletions

View File

@@ -3,7 +3,7 @@
// @flow
import * as N from "../types";
import { types as tt, TokenType } from "../tokenizer/types";
import { types as tt, type TokenType } from "../tokenizer/types";
import ExpressionParser from "./expression";
import type { Position } from "../util/location";
import { lineBreak } from "../util/whitespace";
@@ -15,7 +15,6 @@ const empty = [];
const loopLabel = { kind: "loop" }, switchLabel = { kind: "switch" };
export default class StatementParser extends ExpressionParser {
// ### Statement parsing
// Parse a program. Initializes the parser, reads any number of
@@ -66,7 +65,10 @@ export default class StatementParser extends ExpressionParser {
if (this.match(tt.at)) {
this.parseDecorators(true);
}
return this.parseStatementContent(declaration, topLevel);
}
parseStatementContent(declaration: boolean, topLevel: ?boolean): N.Statement {
const starttype = this.state.type;
const node = this.startNode();
@@ -119,7 +121,13 @@ export default class StatementParser extends ExpressionParser {
this.raise(this.state.start, `'import' and 'export' may appear only with 'sourceType: "module"'`);
}
}
return starttype === tt._import ? this.parseImport(node) : this.parseExport(node);
this.next();
if (starttype == tt._import) {
return this.parseImport(node);
} else {
return this.parseExport(node);
}
case tt.name:
if (this.state.value === "async") {
@@ -522,7 +530,11 @@ export default class StatementParser extends ExpressionParser {
parseBlockBody(node: N.BlockStatementLike, allowDirectives: ?boolean, topLevel: boolean, end: TokenType): void {
const body = node.body = [];
const directives = node.directives = [];
this.parseBlockOrModuleBlockBody(body, allowDirectives ? directives : undefined, topLevel, end);
}
// Undefined directives means that directives are not allowed.
parseBlockOrModuleBlockBody(body: N.Statement[], directives: ?N.Directive[], topLevel: boolean, end: TokenType): void {
let parsedNonDirective = false;
let oldStrict;
let octalPosition;
@@ -534,7 +546,7 @@ export default class StatementParser extends ExpressionParser {
const stmt = this.parseStatement(true, topLevel);
if (allowDirectives && !parsedNonDirective && this.isValidDirective(stmt)) {
if (directives && !parsedNonDirective && this.isValidDirective(stmt)) {
const directive = this.stmtToDirective(stmt);
directives.push(directive);
@@ -607,11 +619,15 @@ export default class StatementParser extends ExpressionParser {
this.parseVarHead(decl);
if (this.eat(tt.eq)) {
decl.init = this.parseMaybeAssign(isFor);
} else if (kind === tt._const && !(this.match(tt._in) || this.isContextual("of"))) {
this.unexpected();
} else if (decl.id.type !== "Identifier" && !(isFor && (this.match(tt._in) || this.isContextual("of")))) {
this.raise(this.state.lastTokEnd, "Complex binding patterns require an initialization value");
} else {
if (kind === tt._const && !(this.match(tt._in) || this.isContextual("of"))) {
// `const` with no initializer is allowed in TypeScript. It could be a declaration `const x: number;`.
if (!this.hasPlugin("typescript")) {
this.unexpected();
}
} else if (decl.id.type !== "Identifier" && !(isFor && (this.match(tt._in) || this.isContextual("of")))) {
this.raise(this.state.lastTokEnd, "Complex binding patterns require an initialization value");
}
decl.init = null;
}
declarations.push(this.finishNode(decl, "VariableDeclarator"));
@@ -652,14 +668,12 @@ export default class StatementParser extends ExpressionParser {
}
this.parseFunctionParams(node);
this.parseFunctionBody(node, allowExpressionBody);
this.parseFunctionBodyAndFinish(node, isStatement ? "FunctionDeclaration" : "FunctionExpression", allowExpressionBody);
this.state.inMethod = oldInMethod;
return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
return node;
}
parseFunctionParams(node: N.NormalFunction): void {
parseFunctionParams(node: N.Function): void {
this.expect(tt.parenL);
node.params = this.parseBindingList(tt.parenR);
}
@@ -667,7 +681,7 @@ export default class StatementParser extends ExpressionParser {
// Parse a class declaration or literal (depending on the
// `isStatement` parameter).
parseClass(node: N.Class, isStatement: boolean, optionalId?: boolean): N.Class {
parseClass<T : N.Class>(node: T, isStatement: /* T === ClassDeclaration */boolean, optionalId?: boolean): T {
this.next();
this.takeDecorators(node);
this.parseClassId(node, isStatement, optionalId);
@@ -698,8 +712,8 @@ export default class StatementParser extends ExpressionParser {
this.state.classLevel++;
const state = { hadConstructor: false };
let decorators = [];
const classBody = this.startNode();
let decorators: N.Decorator[] = [];
const classBody: N.ClassBody = this.startNode();
classBody.body = [];
@@ -749,7 +763,6 @@ export default class StatementParser extends ExpressionParser {
parseClassMember(classBody: N.ClassBody, member: N.ClassMember, state: { hadConstructor: boolean }): void {
// Use the appropriate variable to represent `member` once a more specific type is known.
const memberAny: any = member;
const methodOrProp: N.ClassMethod | N.ClassProperty = memberAny;
const method: N.ClassMethod = memberAny;
const prop: N.ClassProperty = memberAny;
@@ -761,7 +774,7 @@ export default class StatementParser extends ExpressionParser {
return;
}
methodOrProp.static = false;
let isStatic = false;
if (this.match(tt.name) && this.state.value === "static") {
const key = this.parseIdentifier(true); // eats 'static'
if (this.isClassMethod()) {
@@ -769,19 +782,32 @@ export default class StatementParser extends ExpressionParser {
method.kind = "method";
method.computed = false;
method.key = key;
this.parseClassMethod(classBody, method, false, false);
method.static = false;
this.parseClassMethod(classBody, method, false, false, /* isConstructor */ false);
return;
} else if (this.isClassProperty()) {
// a property named 'static'
prop.computed = false;
prop.key = key;
prop.static = false;
classBody.body.push(this.parseClassProperty(prop));
return;
}
// otherwise something static
methodOrProp.static = true;
isStatic = true;
}
this.parseClassMemberWithIsStatic(classBody, member, state, isStatic);
}
parseClassMemberWithIsStatic(classBody: N.ClassBody, member: N.ClassMember, state: { hadConstructor: boolean }, isStatic: boolean) {
const memberAny: any = member;
const methodOrProp: N.ClassMethod | N.ClassProperty = memberAny;
const method: N.ClassMethod = memberAny;
const prop: N.ClassProperty = memberAny;
methodOrProp.static = isStatic;
if (this.eat(tt.star)) {
// a generator
method.kind = "method";
@@ -792,36 +818,40 @@ export default class StatementParser extends ExpressionParser {
if (!method.computed && method.static && (method.key.name === "prototype" || method.key.value === "prototype")) {
this.raise(method.key.start, "Classes may not have static property named prototype");
}
this.parseClassMethod(classBody, method, true, false);
this.parseClassMethod(classBody, method, true, false, /* isConstructor */ false);
return;
}
const isSimple = this.match(tt.name);
const key = this.parsePropertyName(methodOrProp);
if (!methodOrProp.computed && methodOrProp.static && (methodOrProp.key.name === "prototype" || methodOrProp.key.value === "prototype")) {
this.raise(methodOrProp.key.start, "Classes may not have static property named prototype");
}
const key = this.parseClassPropertyName(methodOrProp);
this.parsePostMemberNameModifiers(methodOrProp);
if (this.isClassMethod()) {
// a normal method
if (this.isNonstaticConstructor(method)) {
if (state.hadConstructor) {
this.raise(key.start, "Duplicate constructor in the same class");
} else if (method.decorators) {
this.raise(method.start, "You can't attach decorators to a class constructor");
}
state.hadConstructor = true;
const isConstructor = this.isNonstaticConstructor(method);
if (isConstructor) {
method.kind = "constructor";
} else {
method.kind = "method";
}
this.parseClassMethod(classBody, method, false, false);
} else if (this.isClassProperty()) {
// a normal property
if (this.isNonstaticConstructor(prop)) {
this.raise(prop.key.start, "Classes may not have a non-static field named 'constructor'");
if (isConstructor) {
if (method.decorators) {
this.raise(method.start, "You can't attach decorators to a class constructor");
}
// TypeScript allows multiple overloaded constructor declarations.
if (state.hadConstructor && !this.hasPlugin("typescript")) {
this.raise(key.start, "Duplicate constructor in the same class");
}
state.hadConstructor = true;
}
classBody.body.push(this.parseClassProperty(prop));
this.parseClassMethod(classBody, method, false, false, isConstructor);
} else if (this.isClassProperty()) {
this.pushClassProperty(classBody, prop);
} else if (isSimple && key.name === "async" && !this.isLineTerminator()) {
// an async method
const isGenerator = this.hasPlugin("asyncGenerators") && this.eat(tt.star);
@@ -830,7 +860,7 @@ export default class StatementParser extends ExpressionParser {
if (this.isNonstaticConstructor(method)) {
this.raise(method.key.start, "Constructor can't be an async function");
}
this.parseClassMethod(classBody, method, isGenerator, true);
this.parseClassMethod(classBody, method, isGenerator, true, /* isConstructor */ false);
} else if (isSimple && (key.name === "get" || key.name === "set") && !(this.isLineTerminator() && this.match(tt.star))) { // `get\n*` is an uninitialized property named 'get' followed by a generator.
// a getter or setter
method.kind = key.name;
@@ -838,7 +868,7 @@ export default class StatementParser extends ExpressionParser {
if (this.isNonstaticConstructor(method)) {
this.raise(method.key.start, "Constructor can't have get/set modifier");
}
this.parseClassMethod(classBody, method, false, false);
this.parseClassMethod(classBody, method, false, false, /* isConstructor */ false);
this.checkGetterSetterParamCount(method);
} else if (this.isLineTerminator()) {
// an uninitialized class property (due to ASI, since we don't otherwise recognize the next token)
@@ -851,6 +881,30 @@ export default class StatementParser extends ExpressionParser {
}
}
parseClassPropertyName(methodOrProp: N.ClassMethod | N.ClassProperty): N.Expression {
const key = this.parsePropertyName(methodOrProp);
if (!methodOrProp.computed && methodOrProp.static && (methodOrProp.key.name === "prototype" || methodOrProp.key.value === "prototype")) {
this.raise(methodOrProp.key.start, "Classes may not have static property named prototype");
}
return key;
}
pushClassProperty(classBody: N.ClassBody, prop: N.ClassProperty) {
if (this.isNonstaticConstructor(prop)) {
this.raise(prop.key.start, "Classes may not have a non-static field named 'constructor'");
}
classBody.body.push(this.parseClassProperty(prop));
}
// Overridden in typescript.js
// eslint-disable-next-line no-unused-vars
parsePostMemberNameModifiers(methodOrProp: N.ClassMethod | N.ClassProperty): void {}
// Overridden in typescript.js
parseAccessModifier(): ?N.Accessibility {
return undefined;
}
parsePrivateClassProperty(node: N.ClassPrivateProperty): N.ClassPrivateProperty {
this.state.inClassProperty = true;
@@ -865,9 +919,8 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "ClassPrivateProperty");
}
parseClassProperty(node: N.ClassProperty): N.ClassProperty {
const hasPlugin = this.hasPlugin("classProperties");
const hasPlugin = this.hasPlugin("classProperties") || this.hasPlugin("typescript");
const noPluginMsg = "You can only use Class Properties when the 'classProperties' plugin is enabled.";
if (!node.typeAnnotation && !hasPlugin) {
this.raise(node.start, noPluginMsg);
@@ -887,9 +940,8 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "ClassProperty");
}
parseClassMethod(classBody: N.ClassBody, method: N.ClassMethod, isGenerator: boolean, isAsync: boolean): void {
this.parseMethod(method, isGenerator, isAsync);
classBody.body.push(this.finishNode(method, "ClassMethod"));
parseClassMethod(classBody: N.ClassBody, method: N.ClassMethod, isGenerator: boolean, isAsync: boolean, isConstructor: boolean): void {
classBody.body.push(this.parseMethod(method, isGenerator, isAsync, isConstructor, "ClassMethod"));
}
parseClassId(node: N.Class, isStatement: boolean, optionalId: ?boolean): void {
@@ -910,9 +962,8 @@ export default class StatementParser extends ExpressionParser {
// Parses module export declaration.
parseExport(node: N.ExportNamedDeclaration): N.ExportNamedDeclaration {
this.eat(tt._export);
// TODO: better type. Node is an N.AnyExport.
parseExport(node: N.Node): N.Node {
// export * from '...'
if (this.match(tt.star)) {
const specifier = this.startNode();
@@ -960,7 +1011,6 @@ export default class StatementParser extends ExpressionParser {
needsSemi = true;
expr = this.parseMaybeAssign();
}
// $FlowFixMe
node.declaration = expr;
if (needsSemi) this.semicolon();
this.checkExport(node, true, true);
@@ -1026,7 +1076,7 @@ export default class StatementParser extends ExpressionParser {
|| this.isContextual("async");
}
checkExport(node: N.ExportNamedDeclaration, checkNames: ?boolean, isDefault: ?boolean): void {
checkExport(node: N.ExportNamedDeclaration, checkNames: ?boolean, isDefault?: boolean): void {
if (checkNames) {
// Check for duplicate exports
if (isDefault) {
@@ -1131,9 +1181,7 @@ export default class StatementParser extends ExpressionParser {
// Parses import declaration.
parseImport(node: N.ImportDeclaration): N.ImportDeclaration {
this.eat(tt._import);
parseImport(node: N.Node): N.ImportDeclaration | N.TsImportEqualsDeclaration {
// import '...'
if (this.match(tt.string)) {
node.specifiers = [];