Skip to content

Commit 45149a9

Browse files
author
Asa Rudick
committed
add transform implementation for converting then() calls to async/await pattern
1 parent b960f69 commit 45149a9

File tree

8 files changed

+68
-67
lines changed

8 files changed

+68
-67
lines changed

bin/importTypescript.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import ts from 'typescript';
22
import fs from 'fs';
33
import requireFromString from 'require-from-string';
4+
import path from 'path';
45

56
// There's no alternative way to import a typescript file in what will be JS (after building).
6-
export function importTypescript(path) {
7-
const contents = fs.readFileSync(path);
7+
export function importTypescript(src) {
8+
const contents = fs.readFileSync(src);
89
let result = ts.transpileModule(contents.toString(), {
9-
compilerOptions: { module: ts.ModuleKind.CommonJS },
10+
compilerOptions: {
11+
module: ts.ModuleKind.CommonJS,
12+
target: ts.ScriptTarget.ES2020,
13+
},
1014
}).outputText;
11-
return requireFromString(result);
15+
16+
return requireFromString(result, src, { prependPaths: [path.dirname(src)] });
1217
}

bin/sasquatch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ class Cli {
128128
let config: any;
129129

130130
const finalPath = path ?? configPath;
131-
131+
132132
try {
133133
config = importTypescript(finalPath).default;
134134
console.log(chalk.green(`Using ${finalPath}`));

mock/input.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ export class Foo {
4949
'f' || true || false || true || false || true || false;
5050
}
5151

52-
baz() {
53-
this.promiseReturner().then(a => {});
52+
async baz() {
53+
const a = await this.promiseReturner();
54+
setTimeout(() => {}, 0);
5455
}
5556
static $inject: string[] = ['$rootScope', '$timeout', '$window', '$q'];
5657

@@ -60,6 +61,18 @@ export class Foo {
6061
});
6162
}
6263
}
64+
class a {
65+
promiseReturner() {
66+
return new Promise((resolve, reject) => {
67+
'a';
68+
});
69+
}
70+
}
71+
const fn = async () => {
72+
const b = new a();
73+
await b.promiseReturner()
74+
let c = 'something';
75+
};
6376
//
6477
// function Bar($rootScope: ng.IRootScopeService) {
6578
// 'ngInject';

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"scripts": {
2828
"start": "npm run build && node ./lib/bin/sasquatch.js",
2929
"build": "tsc -p \"./tsconfig.json\"",
30-
"test": "npm run build && node ./lib/bin/sasquatch.js -- \"./mock/input.ts\" \"./src/default.config.ts\"",
30+
"test": "npm run build && node ./lib/bin/sasquatch.js -- \"./mock/input.ts\"",
3131
"debug": "npm run build && node --inspect-brk ./lib/bin/sasquatch.js"
3232
},
3333
"bugs": "https://github.com/asarudick/sasquatch/issues/",

res/default.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { StandardModule, SasquatchConfig } from 'sasquatch';
1+
import { StandardModule, SasquatchConfig } from '../src/';
22
import { IndentationText, QuoteKind } from 'ts-morph';
33

44
export default {

src/modules/standard.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Module } from '../types';
22
import * as analyzers from '../analyzers';
3+
import * as transforms from '../transforms';
34

45
// The 'standard' module. Comes with the most fundamental and basic transforms
56
// and analyzers.
@@ -11,6 +12,7 @@ import * as analyzers from '../analyzers';
1112
export const StandardModule: Module = {
1213
plugin: {
1314
analyzers,
15+
transforms,
1416
},
1517
analyzers: {
1618
use: {
@@ -34,4 +36,9 @@ export const StandardModule: Module = {
3436
},
3537
},
3638
},
39+
transforms: {
40+
use: {
41+
promiseToAsync: {},
42+
},
43+
},
3744
};

src/transforms/promiseToAsync.ts

Lines changed: 33 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import {
1111
Block,
1212
FunctionExpression,
1313
Statement,
14+
FunctionDeclaration,
15+
Node,
16+
Expression,
1417
} from 'ts-morph';
1518

1619
function hasThenCallExpression(expression: CallExpression): boolean {
@@ -22,7 +25,7 @@ function hasThenCallExpression(expression: CallExpression): boolean {
2225
function isThenPropertyAccessExpression(
2326
expression: PropertyAccessExpression,
2427
): boolean {
25-
return expression.getName() === 'then';
28+
return expression.getName && expression.getName() === 'then';
2629
}
2730

2831
function getCaller(expression) {}
@@ -72,86 +75,59 @@ export function promiseToAsync(sources: SourceFile[], options) {
7275
// First then call.
7376
const call: CallExpression = calls[0];
7477

75-
// Reference to then property access.
76-
const expression = call.getExpression() as PropertyAccessExpression;
77-
7878
// The arguments passed to then().
79-
const args: any = call.getArguments();
79+
const args = call.getArguments();
8080

8181
// The arrow function. e.g. promise.then(() => {})
82-
const fn: FunctionExpression = args.length && args[0];
82+
const fn: FunctionExpression =
83+
args.length && (args[0] as FunctionExpression);
8384

8485
let s: Statement;
8586

86-
if (fn) {
87-
// Create awaited assignment statement with all args as destructured assignment.
88-
s = source.addVariableStatement({
87+
// Reference to then property access.
88+
const expression = call.getExpression() as PropertyAccessExpression;
89+
90+
// The containing parent block.
91+
const block: Block = statement.getParent() as Block;
92+
93+
const params = fn.getParameters();
94+
95+
// We only care to declare a variable is there is a corresponding
96+
// parameter in a function passed to the then() call.1
97+
if (fn && params.length) {
98+
// Create awaited assignment statement with all args as an assignment.
99+
// TODO: Account for multiple params with destructuring
100+
s = block.insertVariableStatement(statement.getChildIndex(), {
89101
declarationKind: VariableDeclarationKind.Const, // defaults to "let"
90102
declarations: [
91103
{
92104
// Move the variable from argument list in the then arrow function
93105
// to a constant reference.
94-
name: args.getParameters()[0].toString(),
106+
name: params[0].getName(),
95107

96108
// 'await' and the name of the promise being awaited.
97-
initializer: 'await ' + expression.getExpression().getFullText(),
109+
initializer: 'await ' + expression.getExpression().getText(),
98110
},
99111
],
100112
});
101113
} else {
102-
s = source.addStatements([
103-
'await ' + expression.getExpression().getFullText(),
114+
s = block.addStatements([
115+
'await ' + expression.getExpression().getText(),
104116
])[0];
105117
}
106118

107-
// The containing parent block.
108-
const parent: Block = statement.getParent() as Block;
109-
110119
// Copy the body of the function expression to below the new await expression.
111-
parent.insertStatements(
112-
s.getChildIndex(),
113-
fn.getStatementsWithComments().map(i => i.toString()),
114-
);
120+
fn &&
121+
block.insertStatements(
122+
s.getChildIndex() + 1,
123+
fn.getStatementsWithComments().map((s: Statement) => s.getText()),
124+
);
125+
126+
const functionDeclaration: FunctionDeclaration = block.getParent() as FunctionDeclaration;
127+
functionDeclaration.toggleModifier('async', true);
115128

116129
// Clean up.
117130
statement.remove();
118-
119-
// If only 1 argument, don't wrap with destructuring.
120131
});
121132
});
122133
}
123-
// export default (sources, options) => {
124-
// sources.forEach(source => {
125-
// const classes = source.getClasses();
126-
//
127-
// classes.forEach(c => {
128-
// const constructors = c.getConstructors();
129-
// constructors.forEach(constructor => {
130-
// const body = constructor.getBody();
131-
// const statements = body.getStatements();
132-
//
133-
// // Find ngInject
134-
// const ngInject = statements.find((statement: Statement) => {
135-
// return statement.getText().includes("'ngInject'");
136-
// });
137-
//
138-
// if (!ngInject) {
139-
// return;
140-
// }
141-
//
142-
// ngInject.remove();
143-
//
144-
// const arr = constructor.getParameters().map(param => param.getName());
145-
//
146-
// const prop = c.addProperty({
147-
// isStatic: true,
148-
// name: '$inject',
149-
// type: 'string[]',
150-
// });
151-
// const expression =
152-
// '[' + arr.map(i => "'" + i.toString() + "'").join(', ') + ']';
153-
// prop.setInitializer(expression);
154-
// });
155-
// });
156-
// });
157-
// };

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"app/*": ["src/app/*"]
77
},
88
"compilerOptions": {
9-
"lib": ["ES2015", "ES2016", "ES2017", "ES2018", "ES2019"],
9+
"lib": ["ES2015", "ES2016", "ES2017", "ES2018", "ES2019", "ES2020"],
1010
"allowSyntheticDefaultImports": true,
1111
"module": "commonjs",
1212
"moduleResolution": "node",

0 commit comments

Comments
 (0)