@@ -114,7 +114,7 @@ let emittedSpecifierResolutionWarning = false;
114114 * validation within MUST throw.
115115 * @returns {function next<HookName>(...hookArgs) } The next hook in the chain.
116116 */
117- function nextHookFactory ( chain , meta , validate ) {
117+ function nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) {
118118 // First, prepare the current
119119 const { hookName } = meta ;
120120 const {
@@ -137,7 +137,7 @@ function nextHookFactory(chain, meta, validate) {
137137 // factory generates the next link in the chain.
138138 meta . hookIndex -- ;
139139
140- nextNextHook = nextHookFactory ( chain , meta , validate ) ;
140+ nextNextHook = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
141141 } else {
142142 // eslint-disable-next-line func-name-matching
143143 nextNextHook = function chainAdvancedTooFar ( ) {
@@ -152,14 +152,28 @@ function nextHookFactory(chain, meta, validate) {
152152 // Update only when hook is invoked to avoid fingering the wrong filePath
153153 meta . hookErrIdentifier = `${ hookFilePath } '${ hookName } '` ;
154154
155- validate ( `${ meta . hookErrIdentifier } hook's ${ nextHookName } ()` , args ) ;
155+ validateArgs ( `${ meta . hookErrIdentifier } hook's ${ nextHookName } ()` , args ) ;
156+
157+ const outputErrIdentifier = `${ chain [ generatedHookIndex ] . url } '${ hookName } ' hook's ${ nextHookName } ()` ;
156158
157159 // Set when next<HookName> is actually called, not just generated.
158160 if ( generatedHookIndex === 0 ) { meta . chainFinished = true ; }
159161
162+ // `context` is an optional argument that only needs to be passed when changed
163+ switch ( args . length ) {
164+ case 1 : // It was omitted, so supply the cached value
165+ ArrayPrototypePush ( args , meta . context ) ;
166+ break ;
167+ case 2 : // Overrides were supplied, so update cached value
168+ ObjectAssign ( meta . context , args [ 1 ] ) ;
169+ break ;
170+ }
171+
160172 ArrayPrototypePush ( args , nextNextHook ) ;
161173 const output = await ReflectApply ( hook , undefined , args ) ;
162174
175+ validateOutput ( outputErrIdentifier , output ) ;
176+
163177 if ( output ?. shortCircuit === true ) { meta . shortCircuited = true ; }
164178 return output ;
165179
@@ -554,13 +568,14 @@ class ESMLoader {
554568 const chain = this . #loaders;
555569 const meta = {
556570 chainFinished : null ,
571+ context,
557572 hookErrIdentifier : '' ,
558573 hookIndex : chain . length - 1 ,
559574 hookName : 'load' ,
560575 shortCircuited : false ,
561576 } ;
562577
563- const validate = ( hookErrIdentifier , { 0 : nextUrl , 1 : ctx } ) => {
578+ const validateArgs = ( hookErrIdentifier , { 0 : nextUrl , 1 : ctx } ) => {
564579 if ( typeof nextUrl !== 'string' ) {
565580 // non-strings can be coerced to a url string
566581 // validateString() throws a less-specific error
@@ -584,21 +599,24 @@ class ESMLoader {
584599 }
585600 }
586601
587- validateObject ( ctx , `${ hookErrIdentifier } context` ) ;
602+ if ( ctx ) validateObject ( ctx , `${ hookErrIdentifier } context` ) ;
603+ } ;
604+ const validateOutput = ( hookErrIdentifier , output ) => {
605+ if ( typeof output !== 'object' || output === null ) { // [2]
606+ throw new ERR_INVALID_RETURN_VALUE (
607+ 'an object' ,
608+ hookErrIdentifier ,
609+ output ,
610+ ) ;
611+ }
588612 } ;
589613
590- const nextLoad = nextHookFactory ( chain , meta , validate ) ;
614+ const nextLoad = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
591615
592616 const loaded = await nextLoad ( url , context ) ;
593617 const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
594618
595- if ( typeof loaded !== 'object' ) { // [2]
596- throw new ERR_INVALID_RETURN_VALUE (
597- 'an object' ,
598- hookErrIdentifier ,
599- loaded ,
600- ) ;
601- }
619+ validateOutput ( hookErrIdentifier , loaded ) ;
602620
603621 if ( loaded ?. shortCircuit === true ) { meta . shortCircuited = true ; }
604622
@@ -797,41 +815,44 @@ class ESMLoader {
797815 ) ;
798816 }
799817 const chain = this . #resolvers;
818+ const context = {
819+ conditions : DEFAULT_CONDITIONS ,
820+ importAssertions,
821+ parentURL,
822+ } ;
800823 const meta = {
801824 chainFinished : null ,
825+ context,
802826 hookErrIdentifier : '' ,
803827 hookIndex : chain . length - 1 ,
804828 hookName : 'resolve' ,
805829 shortCircuited : false ,
806830 } ;
807831
808- const context = {
809- conditions : DEFAULT_CONDITIONS ,
810- importAssertions,
811- parentURL,
812- } ;
813- const validate = ( hookErrIdentifier , { 0 : suppliedSpecifier , 1 : ctx } ) => {
814-
832+ const validateArgs = ( hookErrIdentifier , { 0 : suppliedSpecifier , 1 : ctx } ) => {
815833 validateString (
816834 suppliedSpecifier ,
817835 `${ hookErrIdentifier } specifier` ,
818836 ) ; // non-strings can be coerced to a url string
819837
820- validateObject ( ctx , `${ hookErrIdentifier } context` ) ;
838+ if ( ctx ) validateObject ( ctx , `${ hookErrIdentifier } context` ) ;
839+ } ;
840+ const validateOutput = ( hookErrIdentifier , output ) => {
841+ if ( typeof output !== 'object' || output === null ) { // [2]
842+ throw new ERR_INVALID_RETURN_VALUE (
843+ 'an object' ,
844+ hookErrIdentifier ,
845+ output ,
846+ ) ;
847+ }
821848 } ;
822849
823- const nextResolve = nextHookFactory ( chain , meta , validate ) ;
850+ const nextResolve = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
824851
825852 const resolution = await nextResolve ( originalSpecifier , context ) ;
826853 const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
827854
828- if ( typeof resolution !== 'object' ) { // [2]
829- throw new ERR_INVALID_RETURN_VALUE (
830- 'an object' ,
831- hookErrIdentifier ,
832- resolution ,
833- ) ;
834- }
855+ validateOutput ( hookErrIdentifier , resolution ) ;
835856
836857 if ( resolution ?. shortCircuit === true ) { meta . shortCircuited = true ; }
837858
0 commit comments