Skip to content

Commit

Permalink
Properly handle async arrow functions with multiline type parameters
Browse files Browse the repository at this point in the history
Fixes #396

This is similar to another case: we detect it as a special case, then move the (
to right after the async, so that all newlines will be in parens so it won't be
detected as the end of a statement by the parser.
  • Loading branch information
alangpierce committed Mar 31, 2019
1 parent b5ea65b commit 34f2c11
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/transformers/FlowTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default class FlowTransformer extends Transformer {
process(): boolean {
return (
this.rootTransformer.processPossibleArrowParamEnd() ||
this.rootTransformer.processPossibleAsyncArrowWithTypeParams() ||
this.rootTransformer.processPossibleTypeRange()
);
}
Expand Down
44 changes: 44 additions & 0 deletions src/transformers/RootTransformer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Options, SucraseContext, Transform} from "../index";
import NameManager from "../NameManager";
import {ContextualKeyword} from "../parser/tokenizer/keywords";
import {TokenType as tt} from "../parser/tokenizer/types";
import TokenProcessor from "../TokenProcessor";
import getClassInfo, {ClassInfo} from "../util/getClassInfo";
Expand Down Expand Up @@ -340,6 +341,49 @@ export default class RootTransformer {
return false;
}

/**
* An async arrow function might be of the form:
*
* async <
* T
* >() => {}
*
* in which case, removing the type parameters will cause a syntax error. Detect this case and
* move the open-paren earlier.
*/
processPossibleAsyncArrowWithTypeParams(): boolean {
if (
!this.tokens.matchesContextual(ContextualKeyword._async) &&
!this.tokens.matches1(tt._async)
) {
return false;
}
const nextToken = this.tokens.tokenAtRelativeIndex(1);
if (nextToken.type !== tt.lessThan || !nextToken.isType) {
return false;
}

let nextNonTypeIndex = this.tokens.currentIndex() + 1;
// Look ahead to see if this is an arrow function or something else.
while (this.tokens.tokens[nextNonTypeIndex].isType) {
nextNonTypeIndex++;
}
if (this.tokens.matches1AtIndex(nextNonTypeIndex, tt.parenL)) {
this.tokens.replaceToken("async (");
this.tokens.removeInitialToken();
while (this.tokens.currentIndex() < nextNonTypeIndex) {
this.tokens.removeToken();
}
this.tokens.removeToken();
// We ate a ( token, so we need to process the tokens in between and then the ) token so that
// we remain balanced.
this.processBalancedCode();
this.processToken();
return true;
}
return false;
}

processPossibleTypeRange(): boolean {
if (this.tokens.currentToken().isType) {
this.tokens.removeInitialToken();
Expand Down
1 change: 1 addition & 0 deletions src/transformers/TypeScriptTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default class TypeScriptTransformer extends Transformer {
process(): boolean {
if (
this.rootTransformer.processPossibleArrowParamEnd() ||
this.rootTransformer.processPossibleAsyncArrowWithTypeParams() ||
this.rootTransformer.processPossibleTypeRange()
) {
return true;
Expand Down
16 changes: 15 additions & 1 deletion test/types-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ describe("type transforms", () => {
async <T>(1, 2, 3);
`,
`"use strict";
async (1, 2, 3);
async ( 1, 2, 3);
`,
);
});
Expand Down Expand Up @@ -507,6 +507,20 @@ describe("type transforms", () => {
);
});

it("properly handles multiline type parameters in an async arrow function", () => {
assertTypeScriptAndFlowExpectations(
`
const multilineGenerics = async <
A
>(x) => {
setOutput(5);
};
multilineGenerics();
`,
{expectedOutput: 5},
);
});

it("allows keywords as identifiers in a type context", () => {
assertTypeScriptAndFlowResult(
`
Expand Down

0 comments on commit 34f2c11

Please sign in to comment.