Skip to content

Commit 49dd1dc

Browse files
authored
fix: handle async generic arrow functions (#20)
Closes #19 This updates the transform to detect `async` arrow functions that have type parameters which span over multiple lines. When this is detected the `(` at the start of the functions parameters is moved to the start of the type parameters. Avoiding a new line character to appear directly after the `async` keyword. Signed-off-by: Ashley Claymore <[email protected]>
1 parent d9a998f commit 49dd1dc

File tree

6 files changed

+89
-16
lines changed

6 files changed

+89
-16
lines changed

README.md

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ The benefits of this library are:
5555
- No [native-addons](https://nodejs.org/api/addons.html)
5656
- No [child process](https://nodejs.org/api/child_process.html)
5757
- It is small
58-
- Less than 700 lines of code and one dependency ([`TypeScript`](https://www.npmjs.com/package/typescript))
58+
- Around 700 lines of code and one dependency ([`TypeScript`](https://www.npmjs.com/package/typescript))
5959
- By doing so little, the code should be relatively easy to maintain
6060
- Delegates the parsing to the [official TypeScript parser](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API)
6161
- No need for additional SourceMap processing; see ["where are my SourceMaps?"](#where-are-my-sourcemaps)
@@ -159,13 +159,11 @@ statementWithNoSemiColon
159159
("not calling above statement");
160160
```
161161

162-
### Arrow function return types that introduce a new line
162+
### Arrow function type annotations that introduce a new line
163163

164-
If the annotation marking the return type of an arrow function introduces a new line before the `=>`, then only replacing it with blank space would be incorrect.
164+
If the type annotations around an arrow function's parameters introduce a new line then only replacing them with blank space can be incorrect. Therefore, in addition to removing the type annotation, the `(` or `)` surrounding the function parameters may also be moved.
165165

166-
Therefore, in addition to removing the type annotation, the `)` is moved down to the end of the type annotation.
167-
168-
Example input:
166+
#### Example one - multi-line return type:
169167

170168
<!-- prettier-ignore -->
171169
```typescript
@@ -183,6 +181,24 @@ let f = (a , b
183181
) => [a, b];
184182
```
185183

184+
#### Example two - `async` with multi-line type arguments:
185+
186+
<!-- prettier-ignore -->
187+
```typescript
188+
let f = async <
189+
T
190+
>(v: T) => {};
191+
```
192+
193+
becomes:
194+
195+
<!-- prettier-ignore -->
196+
```javascript
197+
let f = async (
198+
199+
v ) => {};
200+
```
201+
186202
## Unsupported Syntax
187203

188204
Some parts of TypeScript are not supported because they can't be erased in place due to having runtime semantics. See [unsupported_syntax.md](./docs/unsupported_syntax.md).

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ts-blank-space",
33
"description": "A small, fast, pure JavaScript type-stripper that uses the official TypeScript parser.",
4-
"version": "0.4.1",
4+
"version": "0.4.2",
55
"license": "Apache-2.0",
66
"homepage": "https://bloomberg.github.io/ts-blank-space",
77
"contributors": [

src/blank-string.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// Copyright 2024 Bloomberg Finance L.P.
22
// Distributed under the terms of the Apache 2.0 license.
33

4-
const FLAG_REPLACE_WITH_CLOSE_PAREN = 1;
5-
const FLAG_REPLACE_WITH_SEMI = 2;
4+
const FLAG_REPLACE_WITH_OPEN_PAREN = 1;
5+
const FLAG_REPLACE_WITH_CLOSE_PAREN = 2;
6+
const FLAG_REPLACE_WITH_SEMI = 3;
67

78
function getSpace(input: string, start: number, end: number): string {
89
let out = "";
@@ -31,6 +32,10 @@ export default class BlankString {
3132
this.__ranges = [];
3233
}
3334

35+
blankButStartWithOpenParen(start: number, end: number): void {
36+
this.__ranges.push(FLAG_REPLACE_WITH_OPEN_PAREN, start, end);
37+
}
38+
3439
blankButEndWithCloseParen(start: number, end: number): void {
3540
this.__ranges.push(0, start, end - 1);
3641
this.__ranges.push(FLAG_REPLACE_WITH_CLOSE_PAREN, end - 1, end);
@@ -69,6 +74,9 @@ export default class BlankString {
6974
} else if (flags === FLAG_REPLACE_WITH_SEMI) {
7075
out += ";";
7176
rangeStart += 1;
77+
} else if (flags === FLAG_REPLACE_WITH_OPEN_PAREN) {
78+
out += "(";
79+
rangeStart += 1;
7280
}
7381

7482
previousEnd = rangeEnd;

src/index.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ function visitVariableStatement(node: ts.VariableStatement): VisitResult {
177177
function visitCallOrNewExpression(node: ts.NewExpression | ts.CallExpression): VisitResult {
178178
visitor(node.expression);
179179
if (node.typeArguments) {
180-
blankGenerics(node, node.typeArguments);
180+
blankGenerics(node, node.typeArguments, /*startWithParen*/ false);
181181
}
182182
if (node.arguments) {
183183
const args = node.arguments;
@@ -194,7 +194,7 @@ function visitCallOrNewExpression(node: ts.NewExpression | ts.CallExpression): V
194194
function visitTaggedTemplate(node: ts.TaggedTemplateExpression): VisitResult {
195195
visitor(node.tag);
196196
if (node.typeArguments) {
197-
blankGenerics(node, node.typeArguments);
197+
blankGenerics(node, node.typeArguments, /*startWithParen*/ false);
198198
}
199199
visitor(node.template);
200200
return VISITED_JS;
@@ -233,7 +233,7 @@ function visitClassLike(node: ts.ClassLikeDeclaration): VisitResult {
233233

234234
// ... <T>
235235
if (node.typeParameters && node.typeParameters.length) {
236-
blankGenerics(node, node.typeParameters);
236+
blankGenerics(node, node.typeParameters, /*startWithParen*/ false);
237237
}
238238

239239
const { heritageClauses } = node;
@@ -260,7 +260,7 @@ function visitClassLike(node: ts.ClassLikeDeclaration): VisitResult {
260260
function visitExpressionWithTypeArguments(node: ts.ExpressionWithTypeArguments): VisitResult {
261261
visitor(node.expression);
262262
if (node.typeArguments) {
263-
blankGenerics(node, node.typeArguments);
263+
blankGenerics(node, node.typeArguments, /*startWithParen*/ false);
264264
}
265265
return VISITED_JS;
266266
}
@@ -311,6 +311,14 @@ function visitModifiers(modifiers: ArrayLike<ts.ModifierLike>): void {
311311
}
312312
}
313313

314+
function isAsync(modifiers: ArrayLike<ts.ModifierLike> | undefined): boolean {
315+
if (!modifiers) return false;
316+
for (let i = 0; i < modifiers.length; i++) {
317+
if (modifiers[i].kind === SK.AsyncKeyword) return true;
318+
}
319+
return false;
320+
}
321+
314322
/**
315323
* prop: T
316324
*/
@@ -394,14 +402,19 @@ function visitFunctionLikeDeclaration(node: ts.FunctionLikeDeclaration, kind: ts
394402
visitor(node.name);
395403
}
396404

405+
let moveOpenParen = false;
397406
if (node.typeParameters && node.typeParameters.length) {
398-
blankGenerics(node, node.typeParameters);
407+
moveOpenParen = isAsync(node.modifiers) && spansLines(node.typeParameters.pos, node.typeParameters.end);
408+
blankGenerics(node, node.typeParameters, moveOpenParen);
399409
}
400410

401411
// method?
402412
node.questionToken && blankExact(node.questionToken);
403413

404414
const params = node.parameters;
415+
if (moveOpenParen) {
416+
str.blank(params.pos - 1, params.pos);
417+
}
405418
for (let i = 0; i < params.length; i++) {
406419
const p = params[i];
407420
if (i === 0 && p.name.getText(ast) === "this") {
@@ -594,10 +607,10 @@ function blankExactAndOptionalTrailingComma(n: ts.Node): void {
594607
/**
595608
* `<T1, T2>`
596609
*/
597-
function blankGenerics(node: ts.Node, arr: ts.NodeArray<ts.Node>): void {
610+
function blankGenerics(node: ts.Node, arr: ts.NodeArray<ts.Node>, startWithParen: boolean): void {
598611
const start = arr.pos - 1;
599612
const end = scanRange(arr.end, node.end, getGreaterThanToken);
600-
str.blank(start, end);
613+
startWithParen ? str.blankButStartWithOpenParen(start, end) : str.blank(start, end);
601614
}
602615

603616
function getClosingParenthesisPos(node: ts.NodeArray<ts.ParameterDeclaration>): number {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
// Simple case
3+
const a = async<T>(v: T) => {};
4+
// ^^^ ^^^
5+
6+
// Hard case - generic spans multiple lines
7+
const b = async <
8+
T
9+
>/**/(/**/v: T) => {};
10+
// ^ ^^^
11+
12+
// Harder case - generic and return type spans multiple lines
13+
const c = async <
14+
T
15+
>(v: T): Promise<
16+
// ^^^ ^^^^^^^^^^
17+
T
18+
> => v;

tests/fixture/output/async-generic-arrow.js

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)