diff --git a/packages/babylon/src/plugins/typescript.js b/packages/babylon/src/plugins/typescript.js index 664e45c6f9..3116d52a70 100644 --- a/packages/babylon/src/plugins/typescript.js +++ b/packages/babylon/src/plugins/typescript.js @@ -259,14 +259,8 @@ export default (superClass: Class): Class => tsParseTypeParameter(): N.TsTypeParameter { const node: N.TsTypeParameter = this.startNode(); node.name = this.parseIdentifierName(node.start); - if (this.eat(tt._extends)) { - node.constraint = this.tsParseType(); - } - - if (this.eat(tt.eq)) { - node.default = this.tsParseType(); - } - + node.constraint = this.tsEatThenParseType(tt._extends); + node.default = this.tsEatThenParseType(tt.eq); return this.finishNode(node, "TSTypeParameter"); } @@ -463,8 +457,7 @@ export default (superClass: Class): Class => tsParseMappedTypeParameter(): N.TsTypeParameter { const node: N.TsTypeParameter = this.startNode(); node.name = this.parseIdentifierName(node.start); - this.expect(tt._in); - node.constraint = this.tsParseType(); + node.constraint = this.tsExpectThenParseType(tt._in); return this.finishNode(node, "TSTypeParameter"); } @@ -720,26 +713,28 @@ export default (superClass: Class): Class => tsParseTypeOrTypePredicateAnnotation( returnToken: TokenType, ): N.TsTypeAnnotation { - const t: N.TsTypeAnnotation = this.startNode(); - this.expect(returnToken); + return this.tsInType(() => { + const t: N.TsTypeAnnotation = this.startNode(); + this.expect(returnToken); - const typePredicateVariable = - this.tsIsIdentifier() && - this.tsTryParse(this.tsParseTypePredicatePrefix.bind(this)); + const typePredicateVariable = + this.tsIsIdentifier() && + this.tsTryParse(this.tsParseTypePredicatePrefix.bind(this)); - if (!typePredicateVariable) { - return this.tsParseTypeAnnotation(/* eatColon */ false, t); - } + if (!typePredicateVariable) { + return this.tsParseTypeAnnotation(/* eatColon */ false, t); + } - const type = this.tsParseTypeAnnotation(/* eatColon */ false); + const type = this.tsParseTypeAnnotation(/* eatColon */ false); - const node: N.TsTypePredicate = this.startNodeAtNode( - typePredicateVariable, - ); - node.parameterName = typePredicateVariable; - node.typeAnnotation = type; - t.typeAnnotation = this.finishNode(node, "TSTypePredicate"); - return this.finishNode(t, "TSTypeAnnotation"); + const node: N.TsTypePredicate = this.startNodeAtNode( + typePredicateVariable, + ); + node.parameterName = typePredicateVariable; + node.typeAnnotation = type; + t.typeAnnotation = this.finishNode(node, "TSTypePredicate"); + return this.finishNode(t, "TSTypeAnnotation"); + }); } tsTryParseTypeOrTypePredicateAnnotation(): ?N.TsTypeAnnotation { @@ -753,7 +748,7 @@ export default (superClass: Class): Class => } tsTryParseType(): ?N.TsType { - return this.eat(tt.colon) ? this.tsParseType() : undefined; + return this.tsEatThenParseType(tt.colon); } tsParseTypePredicatePrefix(): ?N.Identifier { @@ -768,32 +763,32 @@ export default (superClass: Class): Class => eatColon = true, t: N.TsTypeAnnotation = this.startNode(), ): N.TsTypeAnnotation { - if (eatColon) this.expect(tt.colon); - t.typeAnnotation = this.tsParseType(); + this.tsInType(() => { + if (eatColon) this.expect(tt.colon); + t.typeAnnotation = this.tsParseType(); + }); return this.finishNode(t, "TSTypeAnnotation"); } + /** Be sure to be in a type context before calling this, using `tsInType`. */ tsParseType(): N.TsType { // 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()) { - return this.tsParseFunctionOrConstructorType("TSFunctionType"); - } - if (this.match(tt._new)) { - // As in `new () => Date` - return this.tsParseFunctionOrConstructorType("TSConstructorType"); - } - return this.tsParseUnionTypeOrHigher(); - } finally { - this.state.inType = oldInType; + assert(this.state.inType); + if (this.tsIsStartOfFunctionType()) { + return this.tsParseFunctionOrConstructorType("TSFunctionType"); } + if (this.match(tt._new)) { + // As in `new () => Date` + return this.tsParseFunctionOrConstructorType("TSConstructorType"); + } + return this.tsParseUnionTypeOrHigher(); } tsParseTypeAssertion(): N.TsTypeAssertion { const node: N.TsTypeAssertion = this.startNode(); - node.typeAnnotation = this.tsParseType(); + // Not actually necessary to set state.inType because we never reach here if JSX plugin is enabled, + // but need `tsInType` to satisfy the assertion in `tsParseType`. + node.typeAnnotation = this.tsInType(() => this.tsParseType()); this.expectRelational(">"); node.expression = this.parseMaybeUnary(); return this.finishNode(node, "TSTypeAssertion"); @@ -801,15 +796,7 @@ export default (superClass: Class): Class => tsTryParseTypeArgumentsInExpression(): ?N.TsTypeParameterInstantiation { return this.tsTryParseAndCatch(() => { - const res: N.TsTypeParameterInstantiation = this.startNode(); - this.expectRelational("<"); - const typeArguments = this.tsParseDelimitedList( - "TypeParametersOrArguments", - this.tsParseType.bind(this), - ); - this.expectRelational(">"); - res.params = typeArguments; - this.finishNode(res, "TSTypeParameterInstantiation"); + const res = this.tsParseTypeArguments(); this.expect(tt.parenL); return res; }); @@ -853,12 +840,45 @@ export default (superClass: Class): Class => ): N.TsTypeAliasDeclaration { node.id = this.parseIdentifier(); node.typeParameters = this.tsTryParseTypeParameters(); - this.expect(tt.eq); - node.typeAnnotation = this.tsParseType(); + node.typeAnnotation = this.tsExpectThenParseType(tt.eq); this.semicolon(); return this.finishNode(node, "TSTypeAliasDeclaration"); } + /** + * Runs `cb` in a type context. + * This should be called one token *before* the first type token, + * so that the call to `next()` is run in type context. + */ + tsInType(cb: () => T): T { + const oldInType = this.state.inType; + this.state.inType = true; + try { + return cb(); + } finally { + this.state.inType = oldInType; + } + } + + tsEatThenParseType(token: TokenType): N.TsType | undefined { + return !this.match(token) ? undefined : this.tsNextThenParseType(); + } + + tsExpectThenParseType(token: TokenType): N.TsType { + return this.tsDoThenParseType(() => this.expect(token)); + } + + tsNextThenParseType(): N.TsType { + return this.tsDoThenParseType(() => this.next()); + } + + tsDoThenParseType(cb: () => void): N.TsType { + return this.tsInType(() => { + cb(); + return this.tsParseType(); + }); + } + tsParseEnumMember(): N.TsEnumMember { const node: N.TsEnumMember = this.startNode(); // Computed property names are grammar errors in an enum, so accept just string literal or identifier. @@ -1179,11 +1199,13 @@ export default (superClass: Class): Class => tsParseTypeArguments(): N.TsTypeParameterInstantiation { const node = this.startNode(); - this.expectRelational("<"); - node.params = this.tsParseDelimitedList( - "TypeParametersOrArguments", - this.tsParseType.bind(this), - ); + node.params = this.tsInType(() => { + this.expectRelational("<"); + return this.tsParseDelimitedList( + "TypeParametersOrArguments", + this.tsParseType.bind(this), + ); + }); this.expectRelational(">"); return this.finishNode(node, "TSTypeParameterInstantiation"); } @@ -1349,14 +1371,14 @@ export default (superClass: Class): Class => if ( nonNull(tt._in.binop) > minPrec && !this.hasPrecedingLineBreak() && - this.eatContextual("as") + this.isContextual("as") ) { const node: N.TsAsExpression = this.startNodeAt( leftStartPos, leftStartLoc, ); node.expression = left; - node.typeAnnotation = this.tsParseType(); + node.typeAnnotation = this.tsNextThenParseType(); this.finishNode(node, "TSAsExpression"); return this.parseExprOp( node, diff --git a/packages/babylon/test/fixtures/typescript/tsx/type-arguments/input.js b/packages/babylon/test/fixtures/typescript/tsx/type-arguments/input.js new file mode 100644 index 0000000000..b97ab6cac0 --- /dev/null +++ b/packages/babylon/test/fixtures/typescript/tsx/type-arguments/input.js @@ -0,0 +1,3 @@ +f(); +new C(); +type A = T; \ No newline at end of file diff --git a/packages/babylon/test/fixtures/typescript/tsx/type-arguments/output.json b/packages/babylon/test/fixtures/typescript/tsx/type-arguments/output.json new file mode 100644 index 0000000000..21ad36b06b --- /dev/null +++ b/packages/babylon/test/fixtures/typescript/tsx/type-arguments/output.json @@ -0,0 +1,341 @@ +{ + "type": "File", + "start": 0, + "end": 34, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 14 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 34, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 14 + } + }, + "sourceType": "module", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 7, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 7 + } + }, + "expression": { + "type": "CallExpression", + "start": 0, + "end": 6, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 6 + } + }, + "callee": { + "type": "Identifier", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + }, + "identifierName": "f" + }, + "name": "f" + }, + "arguments": [], + "typeParameters": { + "type": "TSTypeParameterInstantiation", + "start": 1, + "end": 4, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 4 + } + }, + "params": [ + { + "type": "TSTypeReference", + "start": 2, + "end": 3, + "loc": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 3 + } + }, + "typeName": { + "type": "Identifier", + "start": 2, + "end": 3, + "loc": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 3 + }, + "identifierName": "T" + }, + "name": "T" + } + } + ] + } + } + }, + { + "type": "ExpressionStatement", + "start": 8, + "end": 19, + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 11 + } + }, + "expression": { + "type": "NewExpression", + "start": 8, + "end": 18, + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 10 + } + }, + "callee": { + "type": "Identifier", + "start": 12, + "end": 13, + "loc": { + "start": { + "line": 2, + "column": 4 + }, + "end": { + "line": 2, + "column": 5 + }, + "identifierName": "C" + }, + "name": "C" + }, + "typeParameters": { + "type": "TSTypeParameterInstantiation", + "start": 13, + "end": 16, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 8 + } + }, + "params": [ + { + "type": "TSTypeReference", + "start": 14, + "end": 15, + "loc": { + "start": { + "line": 2, + "column": 6 + }, + "end": { + "line": 2, + "column": 7 + } + }, + "typeName": { + "type": "Identifier", + "start": 14, + "end": 15, + "loc": { + "start": { + "line": 2, + "column": 6 + }, + "end": { + "line": 2, + "column": 7 + }, + "identifierName": "T" + }, + "name": "T" + } + } + ] + }, + "arguments": [] + } + }, + { + "type": "TSTypeAliasDeclaration", + "start": 20, + "end": 34, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 14 + } + }, + "id": { + "type": "Identifier", + "start": 25, + "end": 26, + "loc": { + "start": { + "line": 3, + "column": 5 + }, + "end": { + "line": 3, + "column": 6 + }, + "identifierName": "A" + }, + "name": "A" + }, + "typeAnnotation": { + "type": "TSTypeReference", + "start": 29, + "end": 33, + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "typeName": { + "type": "Identifier", + "start": 29, + "end": 30, + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 10 + }, + "identifierName": "T" + }, + "name": "T" + }, + "typeParameters": { + "type": "TSTypeParameterInstantiation", + "start": 30, + "end": 33, + "loc": { + "start": { + "line": 3, + "column": 10 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "params": [ + { + "type": "TSTypeReference", + "start": 31, + "end": 32, + "loc": { + "start": { + "line": 3, + "column": 11 + }, + "end": { + "line": 3, + "column": 12 + } + }, + "typeName": { + "type": "Identifier", + "start": 31, + "end": 32, + "loc": { + "start": { + "line": 3, + "column": 11 + }, + "end": { + "line": 3, + "column": 12 + }, + "identifierName": "T" + }, + "name": "T" + } + } + ] + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babylon/test/fixtures/typescript/tsx/type-parameters/input.js b/packages/babylon/test/fixtures/typescript/tsx/type-parameters/input.js new file mode 100644 index 0000000000..ce70d24526 --- /dev/null +++ b/packages/babylon/test/fixtures/typescript/tsx/type-parameters/input.js @@ -0,0 +1 @@ +function f(): () => number {} diff --git a/packages/babylon/test/fixtures/typescript/tsx/type-parameters/output.json b/packages/babylon/test/fixtures/typescript/tsx/type-parameters/output.json new file mode 100644 index 0000000000..0535cb0f6b --- /dev/null +++ b/packages/babylon/test/fixtures/typescript/tsx/type-parameters/output.json @@ -0,0 +1,180 @@ +{ + "type": "File", + "start": 0, + "end": 32, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 32 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 32, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 32 + } + }, + "sourceType": "module", + "body": [ + { + "type": "FunctionDeclaration", + "start": 0, + "end": 32, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 32 + } + }, + "id": { + "type": "Identifier", + "start": 9, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 10 + }, + "identifierName": "f" + }, + "name": "f" + }, + "generator": false, + "async": false, + "params": [], + "returnType": { + "type": "TSTypeAnnotation", + "start": 12, + "end": 29, + "loc": { + "start": { + "line": 1, + "column": 12 + }, + "end": { + "line": 1, + "column": 29 + } + }, + "typeAnnotation": { + "type": "TSFunctionType", + "start": 14, + "end": 29, + "loc": { + "start": { + "line": 1, + "column": 14 + }, + "end": { + "line": 1, + "column": 29 + } + }, + "typeParameters": { + "type": "TSTypeParameterDeclaration", + "start": 14, + "end": 17, + "loc": { + "start": { + "line": 1, + "column": 14 + }, + "end": { + "line": 1, + "column": 17 + } + }, + "params": [ + { + "type": "TSTypeParameter", + "start": 15, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "name": "T" + } + ] + }, + "parameters": [], + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start": 20, + "end": 29, + "loc": { + "start": { + "line": 1, + "column": 20 + }, + "end": { + "line": 1, + "column": 29 + } + }, + "typeAnnotation": { + "type": "TSNumberKeyword", + "start": 23, + "end": 29, + "loc": { + "start": { + "line": 1, + "column": 23 + }, + "end": { + "line": 1, + "column": 29 + } + } + } + } + } + }, + "body": { + "type": "BlockStatement", + "start": 30, + "end": 32, + "loc": { + "start": { + "line": 1, + "column": 30 + }, + "end": { + "line": 1, + "column": 32 + } + }, + "body": [], + "directives": [] + } + } + ], + "directives": [] + } +} \ No newline at end of file