@@ -114,7 +114,7 @@ let emittedSpecifierResolutionWarning = false;
114
114
* validation within MUST throw.
115
115
* @returns {function next<HookName>(...hookArgs) } The next hook in the chain.
116
116
*/
117
- function nextHookFactory ( chain , meta , validate ) {
117
+ function nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) {
118
118
// First, prepare the current
119
119
const { hookName } = meta ;
120
120
const {
@@ -137,7 +137,7 @@ function nextHookFactory(chain, meta, validate) {
137
137
// factory generates the next link in the chain.
138
138
meta . hookIndex -- ;
139
139
140
- nextNextHook = nextHookFactory ( chain , meta , validate ) ;
140
+ nextNextHook = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
141
141
} else {
142
142
// eslint-disable-next-line func-name-matching
143
143
nextNextHook = function chainAdvancedTooFar ( ) {
@@ -152,14 +152,28 @@ function nextHookFactory(chain, meta, validate) {
152
152
// Update only when hook is invoked to avoid fingering the wrong filePath
153
153
meta . hookErrIdentifier = `${ hookFilePath } '${ hookName } '` ;
154
154
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 } ()` ;
156
158
157
159
// Set when next<HookName> is actually called, not just generated.
158
160
if ( generatedHookIndex === 0 ) { meta . chainFinished = true ; }
159
161
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
+
160
172
ArrayPrototypePush ( args , nextNextHook ) ;
161
173
const output = await ReflectApply ( hook , undefined , args ) ;
162
174
175
+ validateOutput ( outputErrIdentifier , output ) ;
176
+
163
177
if ( output ?. shortCircuit === true ) { meta . shortCircuited = true ; }
164
178
return output ;
165
179
@@ -554,13 +568,14 @@ class ESMLoader {
554
568
const chain = this . #loaders;
555
569
const meta = {
556
570
chainFinished : null ,
571
+ context,
557
572
hookErrIdentifier : '' ,
558
573
hookIndex : chain . length - 1 ,
559
574
hookName : 'load' ,
560
575
shortCircuited : false ,
561
576
} ;
562
577
563
- const validate = ( hookErrIdentifier , { 0 : nextUrl , 1 : ctx } ) => {
578
+ const validateArgs = ( hookErrIdentifier , { 0 : nextUrl , 1 : ctx } ) => {
564
579
if ( typeof nextUrl !== 'string' ) {
565
580
// non-strings can be coerced to a url string
566
581
// validateString() throws a less-specific error
@@ -584,21 +599,24 @@ class ESMLoader {
584
599
}
585
600
}
586
601
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
+ }
588
612
} ;
589
613
590
- const nextLoad = nextHookFactory ( chain , meta , validate ) ;
614
+ const nextLoad = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
591
615
592
616
const loaded = await nextLoad ( url , context ) ;
593
617
const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
594
618
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 ) ;
602
620
603
621
if ( loaded ?. shortCircuit === true ) { meta . shortCircuited = true ; }
604
622
@@ -797,41 +815,44 @@ class ESMLoader {
797
815
) ;
798
816
}
799
817
const chain = this . #resolvers;
818
+ const context = {
819
+ conditions : DEFAULT_CONDITIONS ,
820
+ importAssertions,
821
+ parentURL,
822
+ } ;
800
823
const meta = {
801
824
chainFinished : null ,
825
+ context,
802
826
hookErrIdentifier : '' ,
803
827
hookIndex : chain . length - 1 ,
804
828
hookName : 'resolve' ,
805
829
shortCircuited : false ,
806
830
} ;
807
831
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 } ) => {
815
833
validateString (
816
834
suppliedSpecifier ,
817
835
`${ hookErrIdentifier } specifier` ,
818
836
) ; // non-strings can be coerced to a url string
819
837
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
+ }
821
848
} ;
822
849
823
- const nextResolve = nextHookFactory ( chain , meta , validate ) ;
850
+ const nextResolve = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
824
851
825
852
const resolution = await nextResolve ( originalSpecifier , context ) ;
826
853
const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
827
854
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 ) ;
835
856
836
857
if ( resolution ?. shortCircuit === true ) { meta . shortCircuited = true ; }
837
858
0 commit comments