Skip to content
This repository has been archived by the owner on May 19, 2018. It is now read-only.

Commit

Permalink
TypeScript parser plugin (#523)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy authored and hzoo committed Jun 28, 2017
1 parent f7547fd commit 97c2346
Show file tree
Hide file tree
Showing 296 changed files with 33,128 additions and 184 deletions.
15 changes: 14 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import type { Expression, File } from "./types";
import estreePlugin from "./plugins/estree";
import flowPlugin from "./plugins/flow";
import jsxPlugin from "./plugins/jsx";
import typescriptPlugin from "./plugins/typescript";
plugins.estree = estreePlugin;
plugins.flow = flowPlugin;
plugins.jsx = jsxPlugin;
plugins.typescript = typescriptPlugin;

export function parse(input: string, options?: Options): File {
return getParser(options, input).parse();
Expand Down Expand Up @@ -53,14 +55,25 @@ function getParserClass(pluginsFromOptions: $ReadOnlyArray<string>): Class<Parse
}

// Filter out just the plugins that have an actual mixin associated with them.
let pluginList = pluginsFromOptions.filter((p) => p === "estree" || p === "flow" || p === "jsx");
let pluginList = pluginsFromOptions.filter((p) =>
p === "estree" || p === "flow" || p === "jsx" || p === "typescript");

if (pluginList.indexOf("flow") >= 0) {
// ensure flow plugin loads last
pluginList = pluginList.filter((plugin) => plugin !== "flow");
pluginList.push("flow");
}

if (pluginList.indexOf("flow") >= 0 && pluginList.indexOf("typescript") >= 0) {
throw new Error("Cannot combine flow and typescript plugins.");
}

if (pluginList.indexOf("typescript") >= 0) {
// ensure typescript plugin loads last
pluginList = pluginList.filter((plugin) => plugin !== "typescript");
pluginList.push("typescript");
}

if (pluginList.indexOf("estree") >= 0) {
// ensure estree plugin loads first
pluginList = pluginList.filter((plugin) => plugin !== "estree");
Expand Down
124 changes: 74 additions & 50 deletions src/parser/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ export default class ExpressionParser extends LValParser {
return this.parseSubscripts(expr, startPos, startLoc);
}

parseSubscripts(base: N.Expression, startPos: number, startLoc: Position, noCalls?: ?boolean) {
parseSubscripts(base: N.Expression, startPos: number, startLoc: Position, noCalls?: ?boolean): N.Expression {
const state = { stop: false };
do {
base = this.parseSubscript(base, startPos, startLoc, noCalls, state);
Expand Down Expand Up @@ -340,10 +340,7 @@ export default class ExpressionParser extends LValParser {
this.expect(tt.bracketR);
return this.finishNode(node, "MemberExpression");
} else if (this.eat(tt.parenL)) {
const possibleAsync = this.state.potentialArrowAt === base.start &&
base.type === "Identifier" &&
base.name === "async" &&
!this.canInsertSemicolon();
const possibleAsync = this.atPossibleAsync(base);

node.callee = base;
node.arguments = this.parseCallExpressionArguments(tt.parenR, possibleAsync);
Expand Down Expand Up @@ -371,23 +368,13 @@ export default class ExpressionParser extends LValParser {
this.expect(tt.bracketR);
return this.finishNode(node, "MemberExpression");
} else if (!noCalls && this.match(tt.parenL)) {
const possibleAsync = this.state.potentialArrowAt === base.start && base.type === "Identifier" && base.name === "async" && !this.canInsertSemicolon();
const possibleAsync = this.atPossibleAsync(base);
this.next();

const node = this.startNodeAt(startPos, startLoc);
node.callee = base;
node.arguments = this.parseCallExpressionArguments(tt.parenR, possibleAsync);
if (node.callee.type === "Import") {
if (node.arguments.length !== 1) {
this.raise(node.start, "import() requires exactly one argument");
}

const importArg = node.arguments[0];
if (importArg && importArg.type === "SpreadElement") {
this.raise(importArg.start, "... is not allowed in import()");
}
}
this.finishNode(node, "CallExpression");
this.finishCallExpression(node);

if (possibleAsync && this.shouldParseAsyncArrow()) {
state.stop = true;
Expand All @@ -407,6 +394,24 @@ export default class ExpressionParser extends LValParser {
}
}

atPossibleAsync(base: N.Expression): boolean {
return this.state.potentialArrowAt === base.start && base.type === "Identifier" && base.name === "async" && !this.canInsertSemicolon();
}

finishCallExpression(node: N.CallExpression): N.CallExpression {
if (node.callee.type === "Import") {
if (node.arguments.length !== 1) {
this.raise(node.start, "import() requires exactly one argument");
}

const importArg = node.arguments[0];
if (importArg && importArg.type === "SpreadElement") {
this.raise(importArg.start, "... is not allowed in import()");
}
}
return this.finishNode(node, "CallExpression");
}

parseCallExpressionArguments(close: TokenType, possibleAsyncArrow: boolean): $ReadOnlyArray<?N.Expression> {
const elts = [];
let innerParenStart;
Expand Down Expand Up @@ -566,10 +571,7 @@ export default class ExpressionParser extends LValParser {
return this.finishNode(node, "NullLiteral");

case tt._true: case tt._false:
node = this.startNode();
node.value = this.match(tt._true);
this.next();
return this.finishNode(node, "BooleanLiteral");
return this.parseBooleanLiteral();

case tt.parenL:
return this.parseParenAndDistinguishExpression(canBeArrow);
Expand Down Expand Up @@ -624,6 +626,13 @@ export default class ExpressionParser extends LValParser {
}
}

parseBooleanLiteral(): N.BooleanLiteral {
const node = this.startNode();
node.value = this.match(tt._true);
this.next();
return this.finishNode(node, "BooleanLiteral");
}

parseMaybePrivateName(): N.PrivateName | N.Identifier {
const isPrivate = this.eat(tt.hash);

Expand Down Expand Up @@ -795,19 +804,20 @@ export default class ExpressionParser extends LValParser {
}

node.callee = this.parseNoCallExpr();
const optional = this.eat(tt.questionDot);
if (this.eat(tt.questionDot)) node.optional = true;
this.parseNewArguments(node);
return this.finishNode(node, "NewExpression");
}

parseNewArguments(node: N.NewExpression): void {
if (this.eat(tt.parenL)) {
node.arguments = this.parseExprList(tt.parenR);
this.toReferencedList(node.arguments);
const args = this.parseExprList(tt.parenR);
this.toReferencedList(args);
// $FlowFixMe (parseExprList should be all non-null in this case)
node.arguments = args;
} else {
node.arguments = [];
}
if (optional) {
node.optional = true;
}

return this.finishNode(node, "NewExpression");
}

// Parse template expression.
Expand Down Expand Up @@ -989,19 +999,16 @@ export default class ExpressionParser extends LValParser {
if (isPattern) this.unexpected();
prop.kind = "method";
prop.method = true;
this.parseMethod(prop, isGenerator, isAsync);

return this.finishNode(prop, "ObjectMethod");
return this.parseMethod(prop, isGenerator, isAsync, /* isConstructor */ false, "ObjectMethod");
}

if (this.isGetterOrSetterMethod(prop, isPattern)) {
if (isGenerator || isAsync) this.unexpected();
prop.kind = prop.key.name;
this.parsePropertyName(prop);
this.parseMethod(prop);
this.parseMethod(prop, /* isGenerator */false, /* isAsync */ false, /* isConstructor */ false, "ObjectMethod");
this.checkGetterSetterParamCount(prop);

return this.finishNode(prop, "ObjectMethod");
return prop;
}
}

Expand Down Expand Up @@ -1039,9 +1046,12 @@ export default class ExpressionParser extends LValParser {
this.parseObjectProperty(prop, startPos, startLoc, isPattern, refShorthandDefaultPos);

if (!node) this.unexpected();

// $FlowFixMe
return node;
}

parsePropertyName(prop: N.ObjectOrClassMember): N.Expression {
parsePropertyName(prop: N.ObjectOrClassMember | N.TsNamedTypeElementBase): N.Expression {
if (this.eat(tt.bracketL)) {
prop.computed = true;
prop.key = this.parseMaybeAssign();
Expand All @@ -1059,7 +1069,7 @@ export default class ExpressionParser extends LValParser {

// Initialize empty function node.

initFunction(node: N.Function, isAsync: ?boolean): void {
initFunction(node: N.BodilessFunctionOrMethodBase, isAsync: ?boolean): void {
node.id = null;
node.generator = false;
node.expression = false;
Expand All @@ -1068,14 +1078,15 @@ export default class ExpressionParser extends LValParser {

// Parse object or class method.

parseMethod(node: N.MethodLike, isGenerator?: boolean, isAsync?: boolean): N.MethodLike {
parseMethod<T : N.MethodLike>(node: T, isGenerator: boolean, isAsync: boolean, isConstructor: boolean, type: string): T {
const oldInMethod = this.state.inMethod;
this.state.inMethod = node.kind || true;
this.initFunction(node, isAsync);
this.expect(tt.parenL);
node.params = this.parseBindingList(tt.parenR);
const allowModifiers = isConstructor; // For TypeScript parameter properties
node.params = this.parseBindingList(tt.parenR, /* allowEmpty */ false, allowModifiers);
node.generator = !!isGenerator;
this.parseFunctionBody(node);
this.parseFunctionBodyAndFinish(node, type);
this.state.inMethod = oldInMethod;
return node;
}
Expand All @@ -1089,7 +1100,7 @@ export default class ExpressionParser extends LValParser {
return this.finishNode(node, "ArrowFunctionExpression");
}

isStrictBody(node: { body: N.BlockStatement }, isExpression?: boolean): boolean {
isStrictBody(node: { body: N.BlockStatement }, isExpression: ?boolean): boolean {
if (!isExpression && node.body.directives.length) {
for (const directive of node.body.directives) {
if (directive.value.value === "use strict") {
Expand All @@ -1101,8 +1112,14 @@ export default class ExpressionParser extends LValParser {
return false;
}

parseFunctionBodyAndFinish(node: N.BodilessFunctionOrMethodBase, type: string, allowExpressionBody?: boolean): void {
// $FlowIgnore (node is not bodiless if we get here)
this.parseFunctionBody(node, allowExpressionBody);
this.finishNode(node, type);
}

// Parse function body and check parameters.
parseFunctionBody(node: N.Function, allowExpression?: boolean): void {
parseFunctionBody(node: N.Function, allowExpression: ?boolean): void {
const isExpression = allowExpression && !this.match(tt.braceL);

const oldInAsync = this.state.inAsync;
Expand Down Expand Up @@ -1192,26 +1209,33 @@ export default class ExpressionParser extends LValParser {

parseIdentifier(liberal?: boolean): N.Identifier {
const node = this.startNode();
const name = this.parseIdentifierName(node.start, liberal);
node.name = name;
node.loc.identifierName = name;
return this.finishNode(node, "Identifier");
}

parseIdentifierName(pos: number, liberal?: boolean): string {
if (!liberal) {
this.checkReservedWord(this.state.value, this.state.start, !!this.state.type.keyword, false);
}

let name: string;

if (this.match(tt.name)) {
node.name = this.state.value;
name = this.state.value;
} else if (this.state.type.keyword) {
node.name = this.state.type.keyword;
name = this.state.type.keyword;
} else {
this.unexpected();
throw this.unexpected();
}

if (!liberal && node.name === "await" && this.state.inAsync) {
this.raise(node.start, "invalid use of await inside of an async function");
if (!liberal && name === "await" && this.state.inAsync) {
this.raise(pos, "invalid use of await inside of an async function");
}

node.loc.identifierName = node.name;

this.next();
return this.finishNode(node, "Identifier");
return name;
}

checkReservedWord(word: string, startLoc: number, checkKeywords: boolean, isBinding: boolean): void {
Expand Down
29 changes: 19 additions & 10 deletions src/parser/lval.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @flow

import { types as tt, type TokenType } from "../tokenizer/types";
import type { Decorator, Expression, Identifier, Node, ObjectExpression, ObjectPattern, Pattern, RestElement,
SpreadElement } from "../types";
import type { TSParameterProperty, Decorator, Expression, Identifier, Node, ObjectExpression,
ObjectPattern, Pattern, RestElement, SpreadElement } from "../types";
import type { Pos, Position } from "../util/location";
import { NodeUtils } from "./node";

Expand Down Expand Up @@ -167,8 +167,12 @@ export default class LValParser extends NodeUtils {
}
}

parseBindingList(close: TokenType, allowEmpty?: boolean): $ReadOnlyArray<Pattern> {
const elts = [];
parseBindingList(
close: TokenType,
allowEmpty?: boolean,
allowModifiers?: boolean
): $ReadOnlyArray<Pattern | TSParameterProperty> {
const elts: Array<Pattern | TSParameterProperty> = [];
let first = true;
while (!this.eat(close)) {
if (first) {
Expand All @@ -193,17 +197,22 @@ export default class LValParser extends NodeUtils {
while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
}
const left = this.parseMaybeDefault();
if (decorators.length) {
left.decorators = decorators;
}
this.parseAssignableListItemTypes(left);
elts.push(this.parseMaybeDefault(left.start, left.loc.start, left));
elts.push(this.parseAssignableListItem(allowModifiers, decorators));
}
}
return elts;
}

parseAssignableListItem(allowModifiers: ?boolean, decorators: Decorator[]): Pattern | TSParameterProperty {
const left = this.parseMaybeDefault();
this.parseAssignableListItemTypes(left);
const elt = this.parseMaybeDefault(left.start, left.loc.start, left);
if (decorators.length) {
left.decorators = decorators;
}
return elt;
}

parseAssignableListItemTypes(param: Pattern): Pattern {
return param;
}
Expand Down
5 changes: 5 additions & 0 deletions src/parser/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ export class NodeUtils extends UtilParser {
return new Node(this, pos, loc);
}

/** Start a new node with a previous node's location. */
startNodeAtNode<T : NodeType>(type: NodeType): T {
return this.startNodeAt(type.start, type.loc.start);
}

// Finish an AST node, adding `type` and `end` properties.

finishNode<T : NodeType>(node: T, type: string): T {
Expand Down
Loading

0 comments on commit 97c2346

Please sign in to comment.