@@ -78,68 +78,108 @@ const TransformAsyncMethodsIntoGeneratorMethods = {
78
78
}
79
79
80
80
// Thus far, we've established that value is `myTask = task(...)`.
81
- // Now we need to check if the last argument is an async ArrowFunctionExpress
81
+ // Now we need to check if the last argument is an async ArrowFunctionExpression,
82
+ // possibly wrapped in other modifier functions such as `waitFor()`
82
83
83
- const maybeAsyncArrowPath = path . get (
84
+ // If there are modifier functions applied, this will capture the
85
+ // top-level one
86
+ let rootModifierPath ;
87
+
88
+ let maybeAsyncArrowPath = path . get (
84
89
`value.arguments.${ value . arguments . length - 1 } `
85
90
) ;
86
- if ( ! maybeAsyncArrowPath && ! maybeAsyncArrowPath . node ) {
87
- return ;
88
- }
89
- const maybeAsyncArrow = maybeAsyncArrowPath . node ;
90
- if (
91
- maybeAsyncArrow &&
92
- maybeAsyncArrow . type === 'ArrowFunctionExpression' &&
93
- maybeAsyncArrow . async
94
- ) {
95
- convertFunctionExpressionIntoGenerator (
96
- maybeAsyncArrowPath ,
97
- state ,
98
- factoryFunctionName
99
- ) ;
91
+ while ( maybeAsyncArrowPath && maybeAsyncArrowPath . node ) {
92
+ const maybeAsyncArrow = maybeAsyncArrowPath . node ;
93
+
94
+ if (
95
+ maybeAsyncArrow . type === 'ArrowFunctionExpression' &&
96
+ maybeAsyncArrow . async
97
+ ) {
98
+ // It's an async arrow function, so convert it
99
+ convertFunctionExpressionIntoGenerator (
100
+ maybeAsyncArrowPath ,
101
+ rootModifierPath ,
102
+ state ,
103
+ factoryFunctionName
104
+ ) ;
105
+ break ;
106
+ } else if ( maybeAsyncArrow . type === 'CallExpression' ) {
107
+ // It's a call expression, so save it as the modifier functions root
108
+ // if we don't already have one and then traverse into it
109
+ rootModifierPath = rootModifierPath || maybeAsyncArrowPath ;
110
+ maybeAsyncArrowPath = maybeAsyncArrowPath . get ( 'arguments.0' ) ;
111
+ } else {
112
+ break ;
113
+ }
100
114
}
101
115
}
102
116
} ,
103
117
} ;
104
118
105
119
function convertFunctionExpressionIntoGenerator (
106
- path ,
120
+ taskFnPath ,
121
+ rootModifierPath ,
107
122
state ,
108
123
factoryFunctionName
109
124
) {
110
- if ( path && path . node . async ) {
111
- if ( isArrowFunctionExpression ( path ) ) {
125
+ if ( taskFnPath && taskFnPath . node . async ) {
126
+ if ( isArrowFunctionExpression ( taskFnPath ) ) {
112
127
// At this point we have something that looks like
113
128
//
114
129
// foo = task(this?, {}?, async () => {})
115
130
//
131
+ // or (if there are modifier functions applied)
132
+ //
133
+ // foo = task(this?, {}?, modifier1(modifier2(async () => {})))
134
+ //
116
135
// and we need to convert it to
117
136
//
118
137
// foo = buildTask(contextFn, options | null, taskName, bufferPolicyName?)
119
138
//
120
139
// where conextFn is
121
140
//
122
141
// () => ({ context: this, generator: function * () { ... } })
142
+ //
143
+ // or (if there are modifier functions applied)
144
+ //
145
+ // () => ({ context: this, generator: modifier1(modifier2(function * () { ... } })))
146
+
147
+ // Before we start moving things around, let's save off the task()
148
+ // CallExpression path
149
+ const taskPath = ( rootModifierPath || taskFnPath ) . parentPath ;
123
150
124
- // Replace the async arrow fn with a generator fn
125
- let asyncArrowFnBody = path . node . body ;
151
+ // Transform the async arrow task function into a generator function
152
+ // (we'll do the actual transformation of `await`s into `yield`s below)
153
+ let asyncArrowFnBody = taskFnPath . node . body ;
126
154
if ( asyncArrowFnBody . type !== 'BlockStatement' ) {
127
155
// Need to convert `async () => expr` with `async () => { return expr }`
128
156
asyncArrowFnBody = blockStatement ( [ returnStatement ( asyncArrowFnBody ) ] ) ;
129
157
}
130
158
131
159
const taskGeneratorFn = functionExpression (
132
- path . node . id ,
133
- path . node . params ,
160
+ taskFnPath . node . id ,
161
+ taskFnPath . node . params ,
134
162
asyncArrowFnBody ,
135
163
true
136
164
) ;
137
165
166
+ // Replace the async arrow task function with the generator function
167
+ // in-place in the tree (and update `taskFnPath` to point to the new,
168
+ // generator, task function)
169
+ taskFnPath = taskFnPath . replaceWith ( taskGeneratorFn ) [ 0 ] ;
170
+
138
171
const contextFn = arrowFunctionExpression (
139
172
[ ] ,
140
173
objectExpression ( [
141
174
objectProperty ( identifier ( 'context' ) , thisExpression ( ) ) ,
142
- objectProperty ( identifier ( 'generator' ) , taskGeneratorFn ) ,
175
+ objectProperty (
176
+ identifier ( 'generator' ) ,
177
+ // We've swapped out the task fn for a generator function, possibly
178
+ // inside some modifier functions. Now we want to move that whole
179
+ // tree, including any modifier functions, into this generator
180
+ // property.
181
+ ( rootModifierPath || taskFnPath ) . node
182
+ ) ,
143
183
] )
144
184
) ;
145
185
@@ -152,15 +192,15 @@ function convertFunctionExpressionIntoGenerator(
152
192
) ;
153
193
}
154
194
155
- const originalArgs = path . parentPath . node . arguments ;
195
+ const originalArgs = taskPath . node . arguments ;
156
196
157
197
// task(this, async() => {}) was the original API, but we don't actually
158
198
// need the `this` arg (we determine the `this` context from the contextFn async arrow fn)
159
199
if ( originalArgs [ 0 ] && originalArgs [ 0 ] . type === 'ThisExpression' ) {
160
200
originalArgs . shift ( ) ;
161
201
}
162
202
163
- const taskName = extractTaskNameFromClassProperty ( path ) ;
203
+ const taskName = extractTaskNameFromClassProperty ( taskPath ) ;
164
204
let optionsOrNull ;
165
205
166
206
// remaining args should either be [options, async () => {}] or [async () => {}]
@@ -192,7 +232,7 @@ function convertFunctionExpressionIntoGenerator(
192
232
]
193
233
) ;
194
234
195
- let newPath = path . parentPath . replaceWith ( buildTaskCall ) [ 0 ] ;
235
+ let newPath = taskPath . replaceWith ( buildTaskCall ) [ 0 ] ;
196
236
newPath . traverse ( {
197
237
FunctionExpression ( path ) {
198
238
if ( ! path . node . generator ) {
@@ -224,11 +264,11 @@ const TransformAwaitIntoYield = {
224
264
* in this method we extract the name from the ClassProperty assignment so that we can pass it in
225
265
* to the options hash when constructing the Task.
226
266
*
227
- * @param {babel.NodePath<babel.types.ArrowFunctionExpression > } asyncArrowFnPath
267
+ * @param {babel.NodePath<babel.types.CallExpression > } taskPath
228
268
* @returns {string | null }
229
269
*/
230
- function extractTaskNameFromClassProperty ( asyncArrowFnPath ) {
231
- const maybeClassPropertyPath = asyncArrowFnPath . parentPath . parentPath ;
270
+ function extractTaskNameFromClassProperty ( taskPath ) {
271
+ const maybeClassPropertyPath = taskPath . parentPath ;
232
272
if (
233
273
maybeClassPropertyPath &&
234
274
maybeClassPropertyPath . node . type === 'ClassProperty'
0 commit comments