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

TypeScript parser #523

Merged
merged 74 commits into from Jun 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
aa2566d
WIP: TypeScript parser
May 12, 2017
fc81c23
Use '.indexOf' instead of '.includes'
May 12, 2017
7e239a7
"access" -> "accessibility" (Fix tsep-babylon-test#14)
May 12, 2017
f5c6ba1
Merge branch 'master' into ts-wip
May 17, 2017
4a69f1f
Change TSIndexSignatureDeclaration to TSIndexSignature
May 17, 2017
6a35a87
Merge pull request #1 from andy-ms/ts-indexSignature
May 17, 2017
e67d239
Change TSModuleBlock `.statements` to `.body`
May 17, 2017
7f52f62
Merge pull request #2 from andy-ms/ts-namespace-body
May 17, 2017
cd1941e
Use TypeParameterDeclaration and TypeParameter nodes instead of a TsT…
May 17, 2017
2dc2d63
Merge pull request #3 from andy-ms/ts-TypeParameterDeclaration
May 17, 2017
ce3f89e
Use TypeAnnotation node for type annotations
May 17, 2017
225204a
Merge pull request #4 from andy-ms/ts-TypeAnnotation
May 17, 2017
e408668
Remove optional "name" on TSTypeElementBase, because some type elemen…
May 17, 2017
c0b32ce
Add TSInterfaceBody intermediate node
May 17, 2017
d596f55
Merge pull request #5 from andy-ms/ts-interface-body-body
May 17, 2017
65d9043
Change "name" fields to "id" or "key"
May 17, 2017
7643fa8
Merge pull request #6 from andy-ms/ts-name-id-key
May 17, 2017
a339739
Introduce TSDeclareFunction and TSDeclareMethod node types
May 17, 2017
00e680b
Merge pull request #7 from andy-ms/ts-declarefunction
May 17, 2017
ac4255c
Change TypeParameter name to a string to match flow
May 18, 2017
78df03f
Use TypeParameterInstantiation nodes
May 18, 2017
b26c1bc
Fix bug where TypeParameter.name was an Identifier instead of a string
May 18, 2017
a605559
Use TypeParameterInstantiation for call/new type arguments
May 18, 2017
25f8779
Fix bug: arrow function parameter should have TypeAnnotation node
May 19, 2017
30a9e47
Add TSParameterProperty
May 19, 2017
303a081
Merge pull request #8 from andy-ms/ts-ParameterProperty
May 19, 2017
d2619b9
Use options object for maybeParseFunctionBody
May 19, 2017
5569e15
Attach decorators to the TSParameterProperty, not to the Identifier
May 19, 2017
1c4f875
Merge pull request #9 from andy-ms/ts-parameter-property-decorator
May 19, 2017
c75745f
Make TSTypePredicate store a TypeAnnotation node instead of directly …
May 21, 2017
3537272
Merge pull request #10 from andy-ms/ts-TypePredicate-annotation
May 21, 2017
733943a
parseExprOp: Factor out typescript-specific code to overrideable methods
May 22, 2017
a06eb46
Merge branch 'master' into ts-wip
May 22, 2017
e6210f7
expression.js: Remove forward-declarations and use overrides instead
May 22, 2017
7834a35
Inline parseCallExpression
May 22, 2017
345930d
Remove `parsePropertyNameInfo`, no longer needed
May 22, 2017
c01500c
Remove TsTypeElementBase. Call/construct/index signatures are never o…
May 22, 2017
c20dfa7
Introduce BodilessFunctionOrMethodBase type
May 22, 2017
7ebe5dc
Don't expose tsParseEnumDeclaration; instead, override `parseStatemen…
May 22, 2017
5a88758
Prefer switch statements to long if...else-if chains
May 22, 2017
6a10fd4
Clean up some statement.js changes
May 22, 2017
fe13a1e
Implicit return -> explicit return
May 25, 2017
4a0c4a1
Merge branch 'master' into ts-wip
Jun 12, 2017
3e25e55
Merge branch 'master' into ts-wip
Jun 12, 2017
8524c48
Move handling of special imports/exports to typescript.js
Jun 12, 2017
42fb31e
No need to disable export checks
Jun 12, 2017
307de59
Always set VariableDeclaration.init
Jun 12, 2017
0424c79
Move TypeScript-specific code out of `parseClassMember`
Jun 12, 2017
b6b2eb6
Rename typeArguments to typeParameters
Jun 12, 2017
ab0dcc0
Merge pull request #11 from andy-ms/ts-typeParameters
Jun 12, 2017
a4cb112
Be more strict about when optional properties are allowed
Jun 12, 2017
dd115de
Don't check for duplicate exports
Jun 12, 2017
07cd896
Fix type of TSDeclareMethod
Jun 13, 2017
a127a06
Move checkReservedWord handling to override
Jun 13, 2017
bee125d
Use an override for parseExprOp
Jun 13, 2017
024f18e
Factor typescript-specific code out of parseSubscript
Jun 13, 2017
78e8061
Refactor maybeParseFunctionBody to avoid checking hasPlugin("typescri…
Jun 13, 2017
434ab3e
Remove useless hasPlugin("typescript") from within typescript plugin
Jun 13, 2017
53d3683
Remove doParseFunctionParams
Jun 13, 2017
d95aec6
Use atPossibleAsync in one more place
Jun 13, 2017
7fd24e6
Combine parseFunctionReturnTypeAndBodyAndFinish with parseFunctionBod…
Jun 13, 2017
8280b95
Handle false positive for `new` with type arguments
Jun 13, 2017
db0b2f5
Undo runFixtureTests change
Jun 15, 2017
7d393d1
Merge branch 'master' into ts-wip
Jun 16, 2017
f494cab
Move several changes out of lval.js and into overloads
Jun 16, 2017
5b56e59
Move parseBindingList changes out of lval.js and into an override
Jun 16, 2017
eb5e8dc
Make type annotation range start at the ':' or '=>'
Jun 20, 2017
6a4aefb
Merge pull request #12 from andy-ms/type-annotation-range
Jun 20, 2017
a500a47
Merge branch 'master' into ts-wip
Jun 20, 2017
f55e24f
Remove unused method
Jun 23, 2017
bb42b09
Add test fow optional binding element in flow
Jun 23, 2017
87564dc
Merge branch 'master' into ts-wip
Jun 23, 2017
102be0e
Factor out overridable parseNewArguments to replace parseNewTypeArgum…
Jun 26, 2017
e272c44
Fail on `new C<T>;` with no parentheses
Jun 27, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -294,7 +294,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 @@ -332,10 +332,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 @@ -363,23 +360,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 @@ -399,6 +386,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 @@ -555,10 +560,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 @@ -613,6 +615,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 @@ -784,19 +793,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 @@ -978,19 +988,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 @@ -1028,9 +1035,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 @@ -1048,7 +1058,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 @@ -1057,14 +1067,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 @@ -1078,7 +1089,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 @@ -1090,8 +1101,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 @@ -1181,26 +1198,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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw is not needed -- #unexpected() already throws

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flow will complain without the throw since it's not clear that control flow can't reach the end without assigning name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I didn't think about that

}

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