replaceDoneWithPromise.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. /**
  2. * @typedef {import('jscodeshift').FileInfo} FileInfo
  3. * @typedef {import('jscodeshift').API} API
  4. * @typedef {import('jscodeshift').Collection} Collection
  5. */
  6. module.exports = function transformer(file, api) {
  7. const j = api.jscodeshift
  8. const root = j(file.source)
  9. const mochaFunctionNames = new Set([
  10. 'it',
  11. 'specify',
  12. 'before',
  13. 'after',
  14. 'beforeEach',
  15. 'afterEach',
  16. ])
  17. root
  18. .find(j.CallExpression, {
  19. callee: {
  20. type: 'Identifier',
  21. name: name => mochaFunctionNames.has(name),
  22. },
  23. })
  24. .forEach(path => {
  25. let callbackFunctionArg = null
  26. let funcArgIndex = -1
  27. for (let i = path.node.arguments.length - 1; i >= 0; i--) {
  28. const arg = path.node.arguments[i]
  29. if (
  30. arg &&
  31. (arg.type === 'FunctionExpression' ||
  32. arg.type === 'ArrowFunctionExpression')
  33. ) {
  34. callbackFunctionArg = arg
  35. funcArgIndex = i
  36. break
  37. }
  38. }
  39. if (!callbackFunctionArg) {
  40. return
  41. }
  42. if (callbackFunctionArg.async) {
  43. return
  44. }
  45. const params = callbackFunctionArg.params
  46. if (!params || params.length === 0) {
  47. return
  48. }
  49. const lastParam = params[params.length - 1]
  50. if (
  51. !lastParam ||
  52. lastParam.type !== 'Identifier' ||
  53. lastParam.name !== 'done'
  54. ) {
  55. return
  56. }
  57. const doneParamName = lastParam.name
  58. callbackFunctionArg.params.pop()
  59. const originalBody = callbackFunctionArg.body
  60. const bodyCollection = j(originalBody)
  61. bodyCollection
  62. .find(j.Identifier, { name: doneParamName })
  63. .forEach(identifierPath => {
  64. const parentNode = identifierPath.parentPath.node
  65. if (
  66. parentNode.type === 'MemberExpression' &&
  67. parentNode.property === identifierPath.node &&
  68. !parentNode.computed
  69. ) {
  70. return
  71. }
  72. if (
  73. parentNode.type === 'Property' &&
  74. parentNode.key === identifierPath.node &&
  75. !parentNode.shorthand
  76. ) {
  77. return
  78. }
  79. if (
  80. parentNode.type === 'LabeledStatement' &&
  81. parentNode.label === identifierPath.node
  82. ) {
  83. return
  84. }
  85. identifierPath.node.name = 'resolve'
  86. })
  87. const resolveIdentifier = j.identifier('resolve')
  88. let newBodyBlock
  89. if (originalBody.type === 'BlockStatement') {
  90. newBodyBlock = originalBody
  91. } else {
  92. newBodyBlock = j.blockStatement([j.expressionStatement(originalBody)])
  93. }
  94. const promiseCallback = j.arrowFunctionExpression(
  95. [resolveIdentifier],
  96. newBodyBlock,
  97. false
  98. )
  99. promiseCallback.async = false
  100. const newPromiseExpression = j.newExpression(j.identifier('Promise'), [
  101. promiseCallback,
  102. ])
  103. const newFunctionBody = j.expressionStatement(
  104. j.awaitExpression(newPromiseExpression)
  105. )
  106. callbackFunctionArg.body = j.blockStatement([newFunctionBody])
  107. callbackFunctionArg.async = true
  108. console.log(
  109. `Transformed function in ${file.path} (argument ${funcArgIndex} of ${path.node.callee.name})`
  110. )
  111. })
  112. return root.toSource({ quote: 'single' })
  113. }