@@ -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
@@ -796,41 +814,44 @@ class ESMLoader {
796
814
) ;
797
815
}
798
816
const chain = this . #resolvers;
817
+ const context = {
818
+ conditions : DEFAULT_CONDITIONS ,
819
+ importAssertions,
820
+ parentURL,
821
+ } ;
799
822
const meta = {
800
823
chainFinished : null ,
824
+ context,
801
825
hookErrIdentifier : '' ,
802
826
hookIndex : chain . length - 1 ,
803
827
hookName : 'resolve' ,
804
828
shortCircuited : false ,
805
829
} ;
806
830
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 } ) => {
814
832
validateString (
815
833
suppliedSpecifier ,
816
834
`${ hookErrIdentifier } specifier` ,
817
835
) ; // non-strings can be coerced to a url string
818
836
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
+ }
820
847
} ;
821
848
822
- const nextResolve = nextHookFactory ( chain , meta , validate ) ;
849
+ const nextResolve = nextHookFactory ( chain , meta , { validateArgs , validateOutput } ) ;
823
850
824
851
const resolution = await nextResolve ( originalSpecifier , context ) ;
825
852
const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
826
853
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 ) ;
834
855
835
856
if ( resolution ?. shortCircuit === true ) { meta . shortCircuited = true ; }
836
857
0 commit comments