@@ -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
@@ -796,41 +814,44 @@ class ESMLoader {
796814 ) ;
797815 }
798816 const chain = this . #resolvers;
817+ const context = {
818+ conditions : DEFAULT_CONDITIONS ,
819+ importAssertions,
820+ parentURL,
821+ } ;
799822 const meta = {
800823 chainFinished : null ,
824+ context,
801825 hookErrIdentifier : '' ,
802826 hookIndex : chain . length - 1 ,
803827 hookName : 'resolve' ,
804828 shortCircuited : false ,
805829 } ;
806830
807- const context = {
808- conditions : DEFAULT_CONDITIONS ,
809- importAssertions,
810- parentURL,
811- } ;
812- const validate = ( hookErrIdentifier , { 0 : suppliedSpecifier , 1 : ctx } ) => {
813-
831+ const validateArgs = ( hookErrIdentifier , { 0 : suppliedSpecifier , 1 : ctx } ) => {
814832 validateString (
815833 suppliedSpecifier ,
816834 `${ hookErrIdentifier } specifier` ,
817835 ) ; // non-strings can be coerced to a url string
818836
819- validateObject ( ctx , `${ hookErrIdentifier } context` ) ;
837+ if ( ctx ) validateObject ( ctx , `${ hookErrIdentifier } context` ) ;
838+ } ;
839+ const validateOutput = ( hookErrIdentifier , output ) => {
840+ if ( typeof output !== 'object' || output === null ) { // [2]
841+ throw new ERR_INVALID_RETURN_VALUE (
842+ 'an object' ,
843+ hookErrIdentifier ,
844+ output ,
845+ ) ;
846+ }
820847 } ;
821848
822- const nextResolve = nextHookFactory ( chain , meta , validate ) ;
849+ const nextResolve = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
823850
824851 const resolution = await nextResolve ( originalSpecifier , context ) ;
825852 const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
826853
827- if ( typeof resolution !== 'object' ) { // [2]
828- throw new ERR_INVALID_RETURN_VALUE (
829- 'an object' ,
830- hookErrIdentifier ,
831- resolution ,
832- ) ;
833- }
854+ validateOutput ( hookErrIdentifier , resolution ) ;
834855
835856 if ( resolution ?. shortCircuit === true ) { meta . shortCircuited = true ; }
836857
0 commit comments