From 34f2c1190cbac21063469200ed2debbf458ad937 Mon Sep 17 00:00:00 2001 From: Alan Pierce Date: Sun, 31 Mar 2019 15:19:43 -0700 Subject: [PATCH] Properly handle async arrow functions with multiline type parameters 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. --- src/transformers/FlowTransformer.ts | 1 + src/transformers/RootTransformer.ts | 44 +++++++++++++++++++++++ src/transformers/TypeScriptTransformer.ts | 1 + test/types-test.ts | 16 ++++++++- 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/transformers/FlowTransformer.ts b/src/transformers/FlowTransformer.ts index 36eaa32f..c9ab528a 100644 --- a/src/transformers/FlowTransformer.ts +++ b/src/transformers/FlowTransformer.ts @@ -10,6 +10,7 @@ export default class FlowTransformer extends Transformer { process(): boolean { return ( this.rootTransformer.processPossibleArrowParamEnd() || + this.rootTransformer.processPossibleAsyncArrowWithTypeParams() || this.rootTransformer.processPossibleTypeRange() ); } diff --git a/src/transformers/RootTransformer.ts b/src/transformers/RootTransformer.ts index c5744add..42500dcc 100644 --- a/src/transformers/RootTransformer.ts +++ b/src/transformers/RootTransformer.ts @@ -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"; @@ -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(); diff --git a/src/transformers/TypeScriptTransformer.ts b/src/transformers/TypeScriptTransformer.ts index b169752e..caaa2f00 100644 --- a/src/transformers/TypeScriptTransformer.ts +++ b/src/transformers/TypeScriptTransformer.ts @@ -16,6 +16,7 @@ export default class TypeScriptTransformer extends Transformer { process(): boolean { if ( this.rootTransformer.processPossibleArrowParamEnd() || + this.rootTransformer.processPossibleAsyncArrowWithTypeParams() || this.rootTransformer.processPossibleTypeRange() ) { return true; diff --git a/test/types-test.ts b/test/types-test.ts index cd7c89d2..8266c252 100644 --- a/test/types-test.ts +++ b/test/types-test.ts @@ -471,7 +471,7 @@ describe("type transforms", () => { async (1, 2, 3); `, `"use strict"; - async (1, 2, 3); + async ( 1, 2, 3); `, ); }); @@ -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( `