Type-check StatementParser (#489)

This commit is contained in:
Andy 2017-05-11 07:28:12 -07:00 committed by Daniel Tschinder
parent f6020aecbf
commit 7a8b64c0d3
2 changed files with 76 additions and 69 deletions

View File

@ -1,7 +1,11 @@
/* eslint max-len: 0 */
import { types as tt } from "../tokenizer/types";
// @flow
import * as N from "../types";
import { types as tt, TokenType } from "../tokenizer/types";
import ExpressionParser from "./expression";
import type { Position } from "../util/location";
import { lineBreak } from "../util/whitespace";
// Reused empty array added for node fields that are always empty.
@ -19,7 +23,7 @@ export default class StatementParser extends ExpressionParser {
// `program` argument. If present, the statements will be appended
// to its body instead of creating a new node.
parseTopLevel(file, program) {
parseTopLevel(file: N.File, program: N.Program): N.File {
program.sourceType = this.options.sourceType;
this.parseBlockBody(program, true, true, tt.eof);
@ -33,7 +37,7 @@ export default class StatementParser extends ExpressionParser {
// TODO
stmtToDirective(stmt) {
stmtToDirective(stmt: N.Statement): N.Directive {
const expr = stmt.expression;
const directiveLiteral = this.startNodeAt(expr.start, expr.loc.start);
@ -57,7 +61,7 @@ export default class StatementParser extends ExpressionParser {
// `if (foo) /blah/.exec(foo)`, where looking at the previous token
// does not help.
parseStatement(declaration, topLevel) {
parseStatement(declaration: boolean, topLevel?: boolean): N.Statement {
if (this.match(tt.at)) {
this.parseDecorators(true);
}
@ -143,14 +147,14 @@ export default class StatementParser extends ExpressionParser {
}
}
takeDecorators(node) {
takeDecorators(node: N.HasDecorators): void {
if (this.state.decorators.length) {
node.decorators = this.state.decorators;
this.state.decorators = [];
}
}
parseDecorators(allowExport) {
parseDecorators(allowExport?: boolean): void {
while (this.match(tt.at)) {
const decorator = this.parseDecorator();
this.state.decorators.push(decorator);
@ -165,7 +169,7 @@ export default class StatementParser extends ExpressionParser {
}
}
parseDecorator() {
parseDecorator(): N.Decorator {
if (!this.hasPlugin("decorators")) {
this.unexpected();
}
@ -175,7 +179,7 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "Decorator");
}
parseBreakContinueStatement(node, keyword) {
parseBreakContinueStatement(node: N.BreakStatement | N.ContinueStatement, keyword: string): N.BreakStatement | N.ContinueStatement {
const isBreak = keyword === "break";
this.next();
@ -202,13 +206,13 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
}
parseDebuggerStatement(node) {
parseDebuggerStatement(node: N.DebuggerStatement): N.DebuggerStatement {
this.next();
this.semicolon();
return this.finishNode(node, "DebuggerStatement");
}
parseDoStatement(node) {
parseDoStatement(node: N.DoWhileStatement): N.DoWhileStatement {
this.next();
this.state.labels.push(loopLabel);
node.body = this.parseStatement(false);
@ -227,7 +231,7 @@ export default class StatementParser extends ExpressionParser {
// part (semicolon immediately after the opening parenthesis), it
// is a regular `for` loop.
parseForStatement(node) {
parseForStatement(node: N.Node): N.ForLike {
this.next();
this.state.labels.push(loopLabel);
@ -279,12 +283,12 @@ export default class StatementParser extends ExpressionParser {
return this.parseFor(node, init);
}
parseFunctionStatement(node) {
parseFunctionStatement(node: N.FunctionDeclaration): N.FunctionDeclaration {
this.next();
return this.parseFunction(node, true);
}
parseIfStatement(node) {
parseIfStatement(node: N.IfStatement): N.IfStatement {
this.next();
node.test = this.parseParenExpression();
node.consequent = this.parseStatement(false);
@ -292,7 +296,7 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "IfStatement");
}
parseReturnStatement(node) {
parseReturnStatement(node: N.ReturnStatement): N.ReturnStatement {
if (!this.state.inFunction && !this.options.allowReturnOutsideFunction) {
this.raise(this.state.start, "'return' outside of function");
}
@ -313,10 +317,10 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "ReturnStatement");
}
parseSwitchStatement(node) {
parseSwitchStatement(node: N.SwitchStatement): N.SwitchStatement {
this.next();
node.discriminant = this.parseParenExpression();
node.cases = [];
const cases = node.cases = [];
this.expect(tt.braceL);
this.state.labels.push(switchLabel);
@ -329,7 +333,7 @@ export default class StatementParser extends ExpressionParser {
if (this.match(tt._case) || this.match(tt._default)) {
const isCase = this.match(tt._case);
if (cur) this.finishNode(cur, "SwitchCase");
node.cases.push(cur = this.startNode());
cases.push(cur = this.startNode());
cur.consequent = [];
this.next();
if (isCase) {
@ -354,7 +358,7 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "SwitchStatement");
}
parseThrowStatement(node) {
parseThrowStatement(node: N.ThrowStatement): N.ThrowStatement {
this.next();
if (lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start)))
this.raise(this.state.lastTokEnd, "Illegal newline after throw");
@ -363,7 +367,7 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "ThrowStatement");
}
parseTryStatement(node) {
parseTryStatement(node: N.TryStatement): N.TryStatement {
this.next();
node.block = this.parseBlock();
@ -392,14 +396,14 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "TryStatement");
}
parseVarStatement(node, kind) {
parseVarStatement(node: N.VariableDeclaration, kind: TokenType): N.VariableDeclaration {
this.next();
this.parseVar(node, false, kind);
this.semicolon();
return this.finishNode(node, "VariableDeclaration");
}
parseWhileStatement(node) {
parseWhileStatement(node: N.WhileStatement): N.WhileStatement {
this.next();
node.test = this.parseParenExpression();
this.state.labels.push(loopLabel);
@ -408,7 +412,7 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "WhileStatement");
}
parseWithStatement(node) {
parseWithStatement(node: N.WithStatement): N.WithStatement {
if (this.state.strict) this.raise(this.state.start, "'with' in strict mode");
this.next();
node.object = this.parseParenExpression();
@ -416,13 +420,13 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "WithStatement");
}
parseEmptyStatement(node) {
parseEmptyStatement(node: N.EmptyStatement): N.EmptyStatement {
this.next();
return this.finishNode(node, "EmptyStatement");
}
parseLabeledStatement(node, maybeName, expr) {
for (const label of (this.state.labels: Array<Object>)) {
parseLabeledStatement(node: N.LabeledStatement, maybeName: string, expr: N.Identifier): N.LabeledStatement {
for (const label of this.state.labels) {
if (label.name === maybeName) {
this.raise(expr.start, `Label '${maybeName}' is already declared`);
}
@ -446,7 +450,7 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "LabeledStatement");
}
parseExpressionStatement(node, expr) {
parseExpressionStatement(node: N.ExpressionStatement, expr: N.Expression): N.ExpressionStatement {
node.expression = expr;
this.semicolon();
return this.finishNode(node, "ExpressionStatement");
@ -456,22 +460,22 @@ export default class StatementParser extends ExpressionParser {
// strict"` declarations when `allowStrict` is true (used for
// function bodies).
parseBlock(allowDirectives?) {
parseBlock(allowDirectives?: boolean): N.BlockStatement {
const node = this.startNode();
this.expect(tt.braceL);
this.parseBlockBody(node, allowDirectives, false, tt.braceR);
return this.finishNode(node, "BlockStatement");
}
isValidDirective(stmt) {
isValidDirective(stmt: N.Statement): boolean {
return stmt.type === "ExpressionStatement" &&
stmt.expression.type === "StringLiteral" &&
!stmt.expression.extra.parenthesized;
}
parseBlockBody(node, allowDirectives, topLevel, end) {
node.body = [];
node.directives = [];
parseBlockBody(node: N.BlockStatementLike, allowDirectives: ?boolean, topLevel: boolean, end: TokenType): void {
const body = node.body = [];
const directives = node.directives = [];
let parsedNonDirective = false;
let oldStrict;
@ -486,7 +490,7 @@ export default class StatementParser extends ExpressionParser {
if (allowDirectives && !parsedNonDirective && this.isValidDirective(stmt)) {
const directive = this.stmtToDirective(stmt);
node.directives.push(directive);
directives.push(directive);
if (oldStrict === undefined && directive.value.value === "use strict") {
oldStrict = this.state.strict;
@ -501,7 +505,7 @@ export default class StatementParser extends ExpressionParser {
}
parsedNonDirective = true;
node.body.push(stmt);
body.push(stmt);
}
if (oldStrict === false) {
@ -513,7 +517,7 @@ export default class StatementParser extends ExpressionParser {
// `parseStatement` will already have parsed the init statement or
// expression.
parseFor(node, init) {
parseFor(node: N.ForStatement, init: ?(N.VariableDeclaration | N.Expression)): N.ForStatement {
node.init = init;
this.expect(tt.semi);
node.test = this.match(tt.semi) ? null : this.parseExpression();
@ -528,7 +532,7 @@ export default class StatementParser extends ExpressionParser {
// Parse a `for`/`in` and `for`/`of` loop, which are almost
// same from parser's perspective.
parseForIn(node, init, forAwait) {
parseForIn(node: N.ForInOf, init: N.VariableDeclaration, forAwait: boolean): N.ForInOf {
const type = this.match(tt._in) ? "ForInStatement" : "ForOfStatement";
if (forAwait) {
this.eatContextual("of");
@ -548,8 +552,8 @@ export default class StatementParser extends ExpressionParser {
// Parse a list of variable declarations.
parseVar(node, isFor, kind) {
node.declarations = [];
parseVar(node: N.VariableDeclaration, isFor: boolean, kind: TokenType): N.VariableDeclaration {
const declarations = node.declarations = [];
node.kind = kind.keyword;
for (;;) {
const decl = this.startNode();
@ -563,13 +567,13 @@ export default class StatementParser extends ExpressionParser {
} else {
decl.init = null;
}
node.declarations.push(this.finishNode(decl, "VariableDeclarator"));
declarations.push(this.finishNode(decl, "VariableDeclarator"));
if (!this.eat(tt.comma)) break;
}
return node;
}
parseVarHead(decl) {
parseVarHead(decl: N.VariableDeclarator): void {
decl.id = this.parseBindingAtom();
this.checkLVal(decl.id, true, undefined, "variable declaration");
}
@ -577,7 +581,7 @@ export default class StatementParser extends ExpressionParser {
// Parse a function declaration or literal (depending on the
// `isStatement` parameter).
parseFunction(node, isStatement, allowExpressionBody, isAsync, optionalId) {
parseFunction<T : N.NormalFunction>(node: T, isStatement: boolean, allowExpressionBody?: boolean, isAsync?: boolean, optionalId?: boolean): T {
const oldInMethod = this.state.inMethod;
this.state.inMethod = false;
@ -608,7 +612,7 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
}
parseFunctionParams(node) {
parseFunctionParams(node: N.NormalFunction): void {
this.expect(tt.parenL);
node.params = this.parseBindingList(tt.parenR);
}
@ -616,7 +620,7 @@ export default class StatementParser extends ExpressionParser {
// Parse a class declaration or literal (depending on the
// `isStatement` parameter).
parseClass(node, isStatement, optionalId) {
parseClass(node: N.Class, isStatement: boolean, optionalId?: boolean): N.Class {
this.next();
this.takeDecorators(node);
this.parseClassId(node, isStatement, optionalId);
@ -625,22 +629,22 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression");
}
isClassProperty() {
isClassProperty(): boolean {
return this.match(tt.eq) || this.match(tt.semi) || this.match(tt.braceR);
}
isClassMethod() {
isClassMethod(): boolean {
return this.match(tt.parenL);
}
isNonstaticConstructor(method) {
isNonstaticConstructor(method: N.ClassMethod): boolean {
return !method.computed && !method.static && (
(method.key.name === "constructor") || // Identifier
(method.key.value === "constructor") // Literal
);
}
parseClassBody(node) {
parseClassBody(node: N.Class): void {
// class bodies are implicitly strict
const oldStrict = this.state.strict;
this.state.strict = true;
@ -771,7 +775,7 @@ export default class StatementParser extends ExpressionParser {
this.state.strict = oldStrict;
}
parseClassProperty(node) {
parseClassProperty(node: N.ClassProperty): N.ClassProperty {
const hasPlugin = this.hasPlugin("classProperties");
const noPluginMsg = "You can only use Class Properties when the 'classProperties' plugin is enabled.";
if (!node.typeAnnotation && !hasPlugin) {
@ -792,12 +796,12 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "ClassProperty");
}
parseClassMethod(classBody, method, isGenerator, isAsync) {
parseClassMethod(classBody: N.ClassBody, method: N.ClassMethod, isGenerator: boolean, isAsync: boolean): void {
this.parseMethod(method, isGenerator, isAsync);
classBody.body.push(this.finishNode(method, "ClassMethod"));
}
parseClassId(node, isStatement, optionalId) {
parseClassId(node: N.Class, isStatement: boolean, optionalId: ?boolean): void {
if (this.match(tt.name)) {
node.id = this.parseIdentifier();
} else {
@ -809,13 +813,13 @@ export default class StatementParser extends ExpressionParser {
}
}
parseClassSuper(node) {
parseClassSuper(node: N.Class): void {
node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null;
}
// Parses module export declaration.
parseExport(node) {
parseExport(node: N.ExportNamedDeclaration): N.ExportNamedDeclaration {
this.eat(tt._export);
// export * from '...'
@ -834,14 +838,15 @@ export default class StatementParser extends ExpressionParser {
} else if (this.hasPlugin("exportExtensions") && this.isExportDefaultSpecifier()) {
const specifier = this.startNode();
specifier.exported = this.parseIdentifier(true);
node.specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")];
const specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")];
node.specifiers = specifiers;
if (this.match(tt.comma) && this.lookahead().type === tt.star) {
this.expect(tt.comma);
const specifier = this.startNode();
this.expect(tt.star);
this.expectContextual("as");
specifier.exported = this.parseIdentifier();
node.specifiers.push(this.finishNode(specifier, "ExportNamespaceSpecifier"));
specifiers.push(this.finishNode(specifier, "ExportNamespaceSpecifier"));
} else {
this.parseExportSpecifiersMaybe(node);
}
@ -864,6 +869,7 @@ export default class StatementParser extends ExpressionParser {
needsSemi = true;
expr = this.parseMaybeAssign();
}
// $FlowFixMe
node.declaration = expr;
if (needsSemi) this.semicolon();
this.checkExport(node, true, true);
@ -881,11 +887,11 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "ExportNamedDeclaration");
}
parseExportDeclaration() {
parseExportDeclaration(): N.Declaration {
return this.parseStatement(true);
}
isExportDefaultSpecifier() {
isExportDefaultSpecifier(): boolean {
if (this.match(tt.name)) {
return this.state.value !== "type"
&& this.state.value !== "async"
@ -900,13 +906,13 @@ export default class StatementParser extends ExpressionParser {
return lookahead.type === tt.comma || (lookahead.type === tt.name && lookahead.value === "from");
}
parseExportSpecifiersMaybe(node) {
parseExportSpecifiersMaybe(node: N.ExportNamedDeclaration): void {
if (this.eat(tt.comma)) {
node.specifiers = node.specifiers.concat(this.parseExportSpecifiers());
}
}
parseExportFrom(node, expect?) {
parseExportFrom(node: N.ExportNamedDeclaration, expect?: boolean): void {
if (this.eatContextual("from")) {
node.source = this.match(tt.string) ? this.parseExprAtom() : this.unexpected();
this.checkExport(node);
@ -930,7 +936,7 @@ export default class StatementParser extends ExpressionParser {
|| this.isContextual("async");
}
checkExport(node, checkNames, isDefault) {
checkExport(node: N.ExportNamedDeclaration, checkNames: ?boolean, isDefault: ?boolean): void {
if (checkNames) {
// Check for duplicate exports
if (isDefault) {
@ -956,15 +962,16 @@ export default class StatementParser extends ExpressionParser {
if (this.state.decorators.length) {
const isClass = node.declaration && (node.declaration.type === "ClassDeclaration" || node.declaration.type === "ClassExpression");
if (!node.declaration || !isClass) {
this.raise(node.start, "You can only use decorators on an export when exporting a class");
throw this.raise(node.start, "You can only use decorators on an export when exporting a class");
}
this.takeDecorators(node.declaration);
}
}
checkDeclaration(node) {
checkDeclaration(node: N.Pattern): void {
if (node.type === "ObjectPattern") {
for (const prop of node.properties) {
// $FlowFixMe (prop may be an AssignmentProperty, in which case this does nothing?)
this.checkDeclaration(prop);
}
} else if (node.type === "ArrayPattern") {
@ -982,15 +989,15 @@ export default class StatementParser extends ExpressionParser {
}
}
checkDuplicateExports(node, name) {
checkDuplicateExports(node: N.Identifier | N.ExportNamedDeclaration | N.ExportSpecifier, name: string): void {
if (this.state.exportedIdentifiers.indexOf(name) > -1) {
this.raiseDuplicateExportError(node, name);
}
this.state.exportedIdentifiers.push(name);
}
raiseDuplicateExportError(node, name) {
this.raise(node.start, name === "default" ?
raiseDuplicateExportError(node: N.Identifier | N.ExportNamedDeclaration | N.ExportSpecifier, name: string): empty {
throw this.raise(node.start, name === "default" ?
"Only one default export allowed per module." :
`\`${name}\` has already been exported. Exported identifiers must be unique.`
);
@ -998,7 +1005,7 @@ export default class StatementParser extends ExpressionParser {
// Parses a comma-separated list of module exports.
parseExportSpecifiers() {
parseExportSpecifiers(): $ReadOnlyArray<N.ExportSpecifier> {
const nodes = [];
let first = true;
let needsFrom;
@ -1033,7 +1040,7 @@ export default class StatementParser extends ExpressionParser {
// Parses import declaration.
parseImport(node) {
parseImport(node: N.ImportDeclaration): N.ImportDeclaration {
this.eat(tt._import);
// import '...'
@ -1052,7 +1059,7 @@ export default class StatementParser extends ExpressionParser {
// Parses a comma-separated list of module imports.
parseImportSpecifiers(node) {
parseImportSpecifiers(node: N.ImportDeclaration): void {
let first = true;
if (this.match(tt.name)) {
// import defaultObj, { x, y as z } from '...'
@ -1090,7 +1097,7 @@ export default class StatementParser extends ExpressionParser {
}
}
parseImportSpecifier(node) {
parseImportSpecifier(node: N.ImportDeclaration): void {
const specifier = this.startNode();
specifier.imported = this.parseIdentifier(true);
if (this.eatContextual("as")) {
@ -1103,7 +1110,7 @@ export default class StatementParser extends ExpressionParser {
node.specifiers.push(this.finishNode(specifier, "ImportSpecifier"));
}
parseImportSpecifierDefault(id, startPos, startLoc) {
parseImportSpecifierDefault(id: N.Identifier, startPos: number, startLoc: Position): N.ImportDefaultSpecifier {
const node = this.startNodeAt(startPos, startLoc);
node.local = id;
this.checkLVal(node.local, true, undefined, "default import specifier");

View File

@ -646,7 +646,7 @@ export type ImportNamespaceSpecifier = ModuleSpecifier & {
export type ExportNamedDeclaration = NodeBase & {
type: "ExportNamedDeclaration";
declaration: ?Declaration;
specifiers: Array<ExportSpecifier>; // TODO: $ReadOnlyArray
specifiers: $ReadOnlyArray<ExportSpecifier>;
source: ?Literal;
exportKind?: "type" | "value"; // TODO: Not in spec