-
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(to-promise-all): new command, close #5
- Loading branch information
Showing
5 changed files
with
300 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { toPromiseAll as command } from './to-promise-all' | ||
import { d, run } from './_test-utils' | ||
|
||
run( | ||
command, | ||
// Program level | ||
{ | ||
code: d` | ||
/// to-promise-all | ||
const a = await foo() | ||
const b = await bar() | ||
`, | ||
output: d` | ||
const [ | ||
a, | ||
b, | ||
] = await Promise.all([ | ||
foo(), | ||
bar(), | ||
]) | ||
`, | ||
errors: ['command-removal', 'command-fix'], | ||
}, | ||
// Function declaration | ||
{ | ||
filename: 'index.ts', | ||
code: d` | ||
async function fn() { | ||
/// to-promise-all | ||
const a = await foo() | ||
const b = await bar() | ||
} | ||
`, | ||
output: d` | ||
async function fn() { | ||
const [ | ||
a, | ||
b, | ||
] = await Promise.all([ | ||
foo(), | ||
bar(), | ||
] as const) | ||
} | ||
`, | ||
errors: ['command-removal', 'command-fix'], | ||
}, | ||
// If Statement | ||
{ | ||
code: d` | ||
if (true) { | ||
/// to-promise-all | ||
const a = await foo() | ||
.then(() => {}) | ||
const b = await import('bar').then(m => m.default) | ||
} | ||
`, | ||
output: d` | ||
if (true) { | ||
const [ | ||
a, | ||
b, | ||
] = await Promise.all([ | ||
foo() | ||
.then(() => {}), | ||
import('bar').then(m => m.default), | ||
]) | ||
} | ||
`, | ||
errors: ['command-removal', 'command-fix'], | ||
}, | ||
// Mixed declarations | ||
{ | ||
code: d` | ||
on('event', async () => { | ||
/// to-promise-all | ||
let a = await foo() | ||
.then(() => {}) | ||
const { foo, bar } = await import('bar').then(m => m.default) | ||
const b = await baz(), c = await qux(), d = foo() | ||
}) | ||
`, | ||
output: d` | ||
on('event', async () => { | ||
let [ | ||
a, | ||
{ foo, bar }, | ||
b, | ||
c, | ||
d, | ||
] = await Promise.all([ | ||
foo() | ||
.then(() => {}), | ||
import('bar').then(m => m.default), | ||
baz(), | ||
qux(), | ||
foo(), | ||
]) | ||
}) | ||
`, | ||
errors: ['command-removal', 'command-fix'], | ||
}, | ||
// Await expressions | ||
{ | ||
code: d` | ||
/// to-promise-all | ||
const a = await bar() | ||
await foo() | ||
const b = await baz() | ||
doSomething() | ||
const nonTarget = await qux() | ||
`, | ||
output: d` | ||
const [ | ||
a, | ||
/* discarded */, | ||
b, | ||
] = await Promise.all([ | ||
bar(), | ||
foo(), | ||
baz(), | ||
]) | ||
doSomething() | ||
const nonTarget = await qux() | ||
`, | ||
errors: ['command-removal', 'command-fix'], | ||
}, | ||
// Should stop on first non-await expression | ||
{ | ||
code: d` | ||
/// to-promise-all | ||
const a = await bar() | ||
let b = await foo() | ||
let c = baz() | ||
const d = await qux() | ||
`, | ||
output: d` | ||
let [ | ||
a, | ||
b, | ||
] = await Promise.all([ | ||
bar(), | ||
foo(), | ||
]) | ||
let c = baz() | ||
const d = await qux() | ||
`, | ||
errors: ['command-removal', 'command-fix'], | ||
}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import type { Command, Tree } from '../types' | ||
|
||
type TargetNode = Tree.VariableDeclaration | Tree.ExpressionStatement | ||
type TargetDeclarator = Tree.VariableDeclarator | Tree.AwaitExpression | ||
|
||
export const toPromiseAll: Command = { | ||
name: 'to-promise-all', | ||
match: /^[\/@:]\s*(?:to-|2)(?:promise-all|pa)$/, | ||
action(ctx) { | ||
const parent = ctx.getParentBlock() | ||
const nodeStart = ctx.findNodeBelow(isTarget) as TargetNode | ||
let nodeEnd: Tree.Node = nodeStart | ||
if (!nodeStart) | ||
return ctx.reportError('Unable to find variable declaration') | ||
if (!parent.body.includes(nodeStart)) | ||
return ctx.reportError('Variable declaration is not in the same block') | ||
|
||
function isTarget(node: Tree.Node): node is TargetNode { | ||
if (node.type === 'VariableDeclaration') | ||
return node.declarations.some(declarator => declarator.init?.type === 'AwaitExpression') | ||
else if (node.type === 'ExpressionStatement') | ||
return node.expression.type === 'AwaitExpression' | ||
return false | ||
} | ||
|
||
function getDeclarators(node: TargetNode): TargetDeclarator[] { | ||
if (node.type === 'VariableDeclaration') | ||
return node.declarations | ||
if (node.expression.type === 'AwaitExpression') | ||
return [node.expression] | ||
return [] | ||
} | ||
|
||
let declarationType = 'const' | ||
const declarators: TargetDeclarator[] = [] | ||
for (let i = parent.body.indexOf(nodeStart); i < parent.body.length; i++) { | ||
const node = parent.body[i] | ||
if (isTarget(node)) { | ||
declarators.push(...getDeclarators(node)) | ||
nodeEnd = node | ||
if (node.type === 'VariableDeclaration' && node.kind !== 'const') | ||
declarationType = 'let' | ||
} | ||
else { | ||
break | ||
} | ||
} | ||
|
||
ctx.removeComment() | ||
ctx.report({ | ||
loc: { | ||
start: nodeStart.loc.start, | ||
end: nodeEnd.loc.end, | ||
}, | ||
message: 'Convert to `await Promise.all`', | ||
fix(fixer) { | ||
const lineIndent = ctx.getIndentOfLine(nodeStart.loc.start.line) | ||
const isTs = ctx.context.filename.match(/\.[mc]?tsx?$/) | ||
|
||
function unwrapAwait(node: Tree.Node | null) { | ||
if (node?.type === 'AwaitExpression') | ||
return node.argument | ||
return node | ||
} | ||
|
||
function getId(declarator: TargetDeclarator) { | ||
if (declarator.type === 'AwaitExpression') | ||
return '/* discarded */' | ||
return ctx.getTextOf(declarator.id) | ||
} | ||
|
||
function getInit(declarator: TargetDeclarator) { | ||
if (declarator.type === 'AwaitExpression') | ||
return ctx.getTextOf(declarator.argument) | ||
return ctx.getTextOf(unwrapAwait(declarator.init)) | ||
} | ||
|
||
const str = [ | ||
`${declarationType} [`, | ||
...declarators | ||
.map(declarator => `${getId(declarator)},`), | ||
'] = await Promise.all([', | ||
...declarators | ||
.map(declarator => `${getInit(declarator)},`), | ||
isTs ? '] as const)' : '])', | ||
] | ||
.map((line, idx) => idx ? lineIndent + line : line) | ||
.join('\n') | ||
|
||
return fixer.replaceTextRange([ | ||
nodeStart.range[0], | ||
nodeEnd.range[1], | ||
], str) | ||
}, | ||
}) | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters