Skip to content

Commit

Permalink
fix: handle async generic arrow functions
Browse files Browse the repository at this point in the history
closes #19

Signed-off-by: Ashley Claymore <aclaymore@bloomberg.net>
  • Loading branch information
acutmore committed Oct 31, 2024
1 parent d9a998f commit 8de30d6
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 16 deletions.
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ The benefits of this library are:
- No [native-addons](https://nodejs.org/api/addons.html)
- No [child process](https://nodejs.org/api/child_process.html)
- It is small
- Less than 700 lines of code and one dependency ([`TypeScript`](https://www.npmjs.com/package/typescript))
- Around 700 lines of code and one dependency ([`TypeScript`](https://www.npmjs.com/package/typescript))
- By doing so little, the code should be relatively easy to maintain
- Delegates the parsing to the [official TypeScript parser](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API)
- No need for additional SourceMap processing; see ["where are my SourceMaps?"](#where-are-my-sourcemaps)
Expand Down Expand Up @@ -159,13 +159,11 @@ statementWithNoSemiColon
("not calling above statement");
```

### Arrow function return types that introduce a new line
### Arrow function type annotations that introduce a new line

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.
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.

Therefore, in addition to removing the type annotation, the `)` is moved down to the end of the type annotation.

Example input:
#### Example one - multi-line return type:

<!-- prettier-ignore -->
```typescript
Expand All @@ -183,6 +181,24 @@ let f = (a , b
) => [a, b];
```

#### Example two - `async` with multi-line type arguments:

<!-- prettier-ignore -->
```typescript
let f = async <
T
>(v: T) => {};
```

becomes:

<!-- prettier-ignore -->
```javascript
let f = async (

v ) => {};
```

## Unsupported Syntax

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).
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ts-blank-space",
"description": "A small, fast, pure JavaScript type-stripper that uses the official TypeScript parser.",
"version": "0.4.1",
"version": "0.4.2",
"license": "Apache-2.0",
"homepage": "https://bloomberg.github.io/ts-blank-space",
"contributors": [
Expand Down
12 changes: 10 additions & 2 deletions src/blank-string.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Copyright 2024 Bloomberg Finance L.P.
// Distributed under the terms of the Apache 2.0 license.

const FLAG_REPLACE_WITH_CLOSE_PAREN = 1;
const FLAG_REPLACE_WITH_SEMI = 2;
const FLAG_REPLACE_WITH_OPEN_PAREN = 1;
const FLAG_REPLACE_WITH_CLOSE_PAREN = 2;
const FLAG_REPLACE_WITH_SEMI = 3;

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

blankButStartWithOpenParen(start: number, end: number): void {
this.__ranges.push(FLAG_REPLACE_WITH_OPEN_PAREN, start, end);
}

blankButEndWithCloseParen(start: number, end: number): void {
this.__ranges.push(0, start, end - 1);
this.__ranges.push(FLAG_REPLACE_WITH_CLOSE_PAREN, end - 1, end);
Expand Down Expand Up @@ -69,6 +74,9 @@ export default class BlankString {
} else if (flags === FLAG_REPLACE_WITH_SEMI) {
out += ";";
rangeStart += 1;
} else if (flags === FLAG_REPLACE_WITH_OPEN_PAREN) {
out += "(";
rangeStart += 1;
}

previousEnd = rangeEnd;
Expand Down
27 changes: 20 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ function visitVariableStatement(node: ts.VariableStatement): VisitResult {
function visitCallOrNewExpression(node: ts.NewExpression | ts.CallExpression): VisitResult {
visitor(node.expression);
if (node.typeArguments) {
blankGenerics(node, node.typeArguments);
blankGenerics(node, node.typeArguments, /*startWithParen*/false);
}
if (node.arguments) {
const args = node.arguments;
Expand All @@ -194,7 +194,7 @@ function visitCallOrNewExpression(node: ts.NewExpression | ts.CallExpression): V
function visitTaggedTemplate(node: ts.TaggedTemplateExpression): VisitResult {
visitor(node.tag);
if (node.typeArguments) {
blankGenerics(node, node.typeArguments);
blankGenerics(node, node.typeArguments, /*startWithParen*/false);
}
visitor(node.template);
return VISITED_JS;
Expand Down Expand Up @@ -233,7 +233,7 @@ function visitClassLike(node: ts.ClassLikeDeclaration): VisitResult {

// ... <T>
if (node.typeParameters && node.typeParameters.length) {
blankGenerics(node, node.typeParameters);
blankGenerics(node, node.typeParameters, /*startWithParen*/false);
}

const { heritageClauses } = node;
Expand All @@ -260,7 +260,7 @@ function visitClassLike(node: ts.ClassLikeDeclaration): VisitResult {
function visitExpressionWithTypeArguments(node: ts.ExpressionWithTypeArguments): VisitResult {
visitor(node.expression);
if (node.typeArguments) {
blankGenerics(node, node.typeArguments);
blankGenerics(node, node.typeArguments, /*startWithParen*/false);
}
return VISITED_JS;
}
Expand Down Expand Up @@ -311,6 +311,14 @@ function visitModifiers(modifiers: ArrayLike<ts.ModifierLike>): void {
}
}

function isAsync(modifiers: ArrayLike<ts.ModifierLike> | undefined): boolean {
if (!modifiers) return false;
for (let i = 0; i < modifiers.length; i++) {
if (modifiers[i].kind === SK.AsyncKeyword) return true;
}
return false;
}

/**
* prop: T
*/
Expand Down Expand Up @@ -394,14 +402,19 @@ function visitFunctionLikeDeclaration(node: ts.FunctionLikeDeclaration, kind: ts
visitor(node.name);
}

let moveOpenParen = false;
if (node.typeParameters && node.typeParameters.length) {
blankGenerics(node, node.typeParameters);
moveOpenParen = isAsync(node.modifiers) && spansLines(node.typeParameters.pos, node.typeParameters.end);
blankGenerics(node, node.typeParameters, moveOpenParen);
}

// method?
node.questionToken && blankExact(node.questionToken);

const params = node.parameters;
if (moveOpenParen) {
str.blank(params.pos - 1, params.pos);
}
for (let i = 0; i < params.length; i++) {
const p = params[i];
if (i === 0 && p.name.getText(ast) === "this") {
Expand Down Expand Up @@ -594,10 +607,10 @@ function blankExactAndOptionalTrailingComma(n: ts.Node): void {
/**
* `<T1, T2>`
*/
function blankGenerics(node: ts.Node, arr: ts.NodeArray<ts.Node>): void {
function blankGenerics(node: ts.Node, arr: ts.NodeArray<ts.Node>, startWithParen: boolean): void {
const start = arr.pos - 1;
const end = scanRange(arr.end, node.end, getGreaterThanToken);
str.blank(start, end);
startWithParen ? str.blankButStartWithOpenParen(start, end) : str.blank(start, end);
}

function getClosingParenthesisPos(node: ts.NodeArray<ts.ParameterDeclaration>): number {
Expand Down
18 changes: 18 additions & 0 deletions tests/fixture/cases/async-generic-arrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

// Simple case
const a = async<T>(v: T) => {};
// ^^^ ^^^

// Hard case - generic spans multiple lines
const b = async <
T
>/**/(/**/v: T) => {};
// ^ ^^^

// Harder case - generic and return type spans multiple lines
const c = async <
T
>(v: T): Promise<
// ^^^ ^^^^^^^^^^
T
> => v;
18 changes: 18 additions & 0 deletions tests/fixture/output/async-generic-arrow.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8de30d6

Please sign in to comment.