@@ -15,6 +15,8 @@ const {
1515 ObjectDefineProperty,
1616 ObjectSetPrototypeOf,
1717 PromiseAll,
18+ PromiseResolve,
19+ PromisePrototypeThen,
1820 ReflectApply,
1921 RegExpPrototypeExec,
2022 SafeArrayIterator,
@@ -109,12 +111,14 @@ let emittedSpecifierResolutionWarning = false;
109111 * position in the hook chain.
110112 * @param {string } meta.hookName The kind of hook the chain is (ex 'resolve')
111113 * @param {boolean } meta.shortCircuited Whether a hook signaled a short-circuit.
112- * @param {(hookErrIdentifier, hookArgs) => void } validate A wrapper function
114+ * @param {object } validators A wrapper function
113115 * containing all validation of a custom loader hook's intermediary output. Any
114116 * validation within MUST throw.
117+ * @param {(hookErrIdentifier, hookArgs) => void } validateArgs
118+ * @param {(hookErrIdentifier, output) => void } validateOutput
115119 * @returns {function next<HookName>(...hookArgs) } The next hook in the chain.
116120 */
117- function nextHookFactory ( chain , meta , validate ) {
121+ function nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) {
118122 // First, prepare the current
119123 const { hookName } = meta ;
120124 const {
@@ -137,7 +141,7 @@ function nextHookFactory(chain, meta, validate) {
137141 // factory generates the next link in the chain.
138142 meta . hookIndex -- ;
139143
140- nextNextHook = nextHookFactory ( chain , meta , validate ) ;
144+ nextNextHook = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
141145 } else {
142146 // eslint-disable-next-line func-name-matching
143147 nextNextHook = function chainAdvancedTooFar ( ) {
@@ -152,34 +156,25 @@ function nextHookFactory(chain, meta, validate) {
152156 // Update only when hook is invoked to avoid fingering the wrong filePath
153157 meta . hookErrIdentifier = `${ hookFilePath } '${ hookName } '` ;
154158
155- validate ( `${ meta . hookErrIdentifier } hook's ${ nextHookName } ()` , args ) ;
159+ validateArgs ( `${ meta . hookErrIdentifier } hook's ${ nextHookName } ()` , args ) ;
160+
161+ const outputErrIdentifier = `${ chain [ generatedHookIndex ] . url } '${ hookName } ' hook's ${ nextHookName } ()` ;
156162
157163 // Set when next<HookName> is actually called, not just generated.
158164 if ( generatedHookIndex === 0 ) { meta . chainFinished = true ; }
159165
160166 ArrayPrototypePush ( args , nextNextHook ) ;
161- const output = ReflectApply ( hook , undefined , args ) ;
167+ let output = ReflectApply ( hook , undefined , args ) ;
168+
169+ validateOutput ( outputErrIdentifier , output ) ;
162170
163171 function checkShortCircuited ( output ) {
164172 if ( output ?. shortCircuit === true ) { meta . shortCircuited = true ; }
165173 }
166174
167- const then = output ?. then ;
168- if ( typeof then === 'function' ) {
169- if ( ! meta . isChainAsync ) {
170- throw ERR_INVALID_RETURN_VALUE (
171- 'an object' ,
172- // MUST use generatedHookIndex because the chain has already advanced,
173- // causing meta.hookIndex to advance
174- `${ chain [ generatedHookIndex ] . url } '${ hookName } ' hook's ${ nextHookName } ()` ,
175- output ,
176- ) ;
177- }
178-
179- ReflectApply ( then , output , [
180- checkShortCircuited ,
181- // TODO: handle error case
182- ] ) ;
175+ if ( meta . isChainAsync ) {
176+ output = PromiseResolve ( output ) ;
177+ PromisePrototypeThen ( output , checkShortCircuited ) ;
183178 } else {
184179 checkShortCircuited ( output ) ;
185180 }
@@ -586,7 +581,7 @@ class ESMLoader {
586581 shortCircuited : false ,
587582 } ;
588583
589- const validate = ( hookErrIdentifier , { 0 : nextUrl , 1 : ctx } ) => {
584+ const validateArgs = ( hookErrIdentifier , { 0 : nextUrl , 1 : ctx } ) => {
590585 if ( typeof nextUrl !== 'string' ) {
591586 // non-strings can be coerced to a url string
592587 // validateString() throws a less-specific error
@@ -612,19 +607,22 @@ class ESMLoader {
612607
613608 validateObject ( ctx , `${ hookErrIdentifier } context` ) ;
614609 } ;
610+ const validateOutput = ( hookErrIdentifier , output ) => {
611+ if ( typeof output !== 'object' ) { // [2]
612+ throw new ERR_INVALID_RETURN_VALUE (
613+ 'an object' ,
614+ hookErrIdentifier ,
615+ output ,
616+ ) ;
617+ }
618+ } ;
615619
616- const nextLoad = nextHookFactory ( chain , meta , validate ) ;
620+ const nextLoad = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
617621
618622 const loaded = await nextLoad ( url , context ) ;
619623 const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
620624
621- if ( typeof loaded !== 'object' ) { // [2]
622- throw new ERR_INVALID_RETURN_VALUE (
623- 'an object' ,
624- hookErrIdentifier ,
625- loaded ,
626- ) ;
627- }
625+ validateOutput ( hookErrIdentifier , loaded ) ;
628626
629627 if ( loaded ?. shortCircuit === true ) { meta . shortCircuited = true ; }
630628
@@ -837,7 +835,7 @@ class ESMLoader {
837835 parentURL,
838836 } ;
839837
840- const validate = ( hookErrIdentifier , output ) => {
838+ const validateArgs = ( hookErrIdentifier , output ) => {
841839 const { 0 : suppliedSpecifier , 1 : ctx } = output ;
842840
843841 validateString (
@@ -847,22 +845,25 @@ class ESMLoader {
847845
848846 validateObject ( ctx , `${ hookErrIdentifier } context` ) ;
849847 } ;
848+ const validateOutput = ( hookErrIdentifier , output ) => {
849+ if (
850+ typeof output !== 'object' || // [2]
851+ typeof output . then === 'function'
852+ ) {
853+ throw new ERR_INVALID_RETURN_VALUE (
854+ 'an object' ,
855+ hookErrIdentifier ,
856+ output ,
857+ ) ;
858+ }
859+ } ;
850860
851- const nextResolve = nextHookFactory ( chain , meta , validate ) ;
861+ const nextResolve = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
852862
853863 const resolution = nextResolve ( originalSpecifier , context ) ;
854864 const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
855865
856- if (
857- typeof resolution !== 'object' || // [2]
858- typeof resolution ?. then === 'function'
859- ) {
860- throw new ERR_INVALID_RETURN_VALUE (
861- 'an object' ,
862- hookErrIdentifier ,
863- resolution ,
864- ) ;
865- }
866+ validateOutput ( hookErrIdentifier , resolution ) ;
866867
867868 if ( resolution ?. shortCircuit === true ) { meta . shortCircuited = true ; }
868869
0 commit comments