@@ -230,6 +230,23 @@ function resolveScopedArgument(
230230 return { expression : current , resolver }
231231}
232232
233+ function collectFunctionLocalBindings ( fn : SyntaxNode , locals : Map < string , SyntaxNode > ) : void {
234+ const visit = ( node : SyntaxNode ) => {
235+ if ( node !== fn && FUNCTION_NODE_TYPES . has ( node . type ) ) return
236+ if (
237+ node . type === 'VariableDeclarator' &&
238+ isSyntaxNode ( node . id ) &&
239+ node . id . type === 'Identifier' &&
240+ typeof node . id . name === 'string' &&
241+ isSyntaxNode ( node . init )
242+ ) {
243+ locals . set ( node . id . name , node . init )
244+ }
245+ for ( const child of getChildNodes ( node ) ) visit ( child )
246+ }
247+ visit ( fn )
248+ }
249+
233250function isInternalPathExpression (
234251 expression : SyntaxNode ,
235252 resolver : SelfHopResolver ,
@@ -251,6 +268,31 @@ function isInternalPathExpression(
251268 return isInternalPathExpression ( binding . expression , binding . resolver , nextSeen )
252269 }
253270
271+ if ( current . type === 'CallExpression' || current . type === 'OptionalCallExpression' ) {
272+ if ( ! isSyntaxNode ( current . callee ) ) return false
273+ const callee = unwrapExpression ( current . callee )
274+ if ( callee . type === 'Identifier' && typeof callee . name === 'string' ) {
275+ const key = `${ resolver . file } :path-call:${ callee . name } `
276+ if ( seen . has ( key ) ) return false
277+ const binding = resolveScopedIdentifier ( callee . name , resolver )
278+ if ( binding && FUNCTION_NODE_TYPES . has ( unwrapExpression ( binding . expression ) . type ) ) {
279+ const nextSeen = new Set ( seen )
280+ nextSeen . add ( key )
281+ const argumentsList = Array . isArray ( current . arguments )
282+ ? current . arguments
283+ . filter ( isSyntaxNode )
284+ . map ( ( argument ) => resolveScopedArgument ( argument , resolver ) )
285+ : [ ]
286+ return functionReturnsInternalPath (
287+ binding . expression ,
288+ binding . resolver ,
289+ argumentsList ,
290+ nextSeen
291+ )
292+ }
293+ }
294+ }
295+
254296 if ( current . type === 'ConditionalExpression' ) {
255297 return (
256298 ( isSyntaxNode ( current . consequent ) &&
@@ -275,6 +317,59 @@ function isInternalPathExpression(
275317 return false
276318}
277319
320+ function functionReturnsInternalPath (
321+ fn : SyntaxNode ,
322+ resolver : SelfHopResolver ,
323+ argumentsList : readonly ScopedExpression [ ] ,
324+ seen : ReadonlySet < string >
325+ ) : boolean {
326+ const current = unwrapExpression ( fn )
327+ if ( ! FUNCTION_NODE_TYPES . has ( current . type ) ) return false
328+ const locals = new Map ( resolver . locals )
329+ const scopedLocals = new Map ( resolver . scopedLocals )
330+ const parameters = Array . isArray ( current . params ) ? current . params : [ ]
331+ for ( const [ index , parameter ] of parameters . entries ( ) ) {
332+ if (
333+ isSyntaxNode ( parameter ) &&
334+ parameter . type === 'Identifier' &&
335+ typeof parameter . name === 'string' &&
336+ argumentsList [ index ]
337+ ) {
338+ const argument = argumentsList [ index ]
339+ scopedLocals . set ( parameter . name , argument )
340+ if ( argument . resolver === resolver ) locals . set ( parameter . name , argument . expression )
341+ }
342+ }
343+ collectFunctionLocalBindings ( current , locals )
344+ const localResolver = { ...resolver , locals, scopedLocals }
345+ if ( current . type === 'ArrowFunctionExpression' && isSyntaxNode ( current . body ) ) {
346+ const body = unwrapExpression ( current . body )
347+ if ( body . type !== 'BlockStatement' ) {
348+ return isInternalPathExpression ( body , localResolver , new Set ( seen ) )
349+ }
350+ }
351+ let found = false
352+ const visit = ( node : SyntaxNode ) => {
353+ if ( found || ( node !== current && FUNCTION_NODE_TYPES . has ( node . type ) ) ) return
354+ if (
355+ node . type === 'ReturnStatement' &&
356+ isSyntaxNode ( node . argument ) &&
357+ isInternalPathExpression ( node . argument , localResolver , new Set ( seen ) )
358+ ) {
359+ found = true
360+ return
361+ }
362+ for ( const child of getChildNodes ( node ) ) visit ( child )
363+ }
364+ visit ( current )
365+ return found
366+ }
367+
368+ function isOriginPreservingStaticSuffix ( expression : SyntaxNode ) : boolean {
369+ const suffix = getStaticString ( expression )
370+ return suffix !== undefined && ( suffix === '' || / ^ [ / ? # ] / . test ( suffix ) )
371+ }
372+
278373function isSimOriginExpression (
279374 expression : SyntaxNode ,
280375 resolver : SelfHopResolver ,
@@ -321,6 +416,34 @@ function isSimOriginExpression(
321416 }
322417 }
323418 }
419+ if (
420+ current . type === 'BinaryExpression' &&
421+ current . operator === '+' &&
422+ isSyntaxNode ( current . left ) &&
423+ isSyntaxNode ( current . right )
424+ ) {
425+ return (
426+ isSimOriginExpression ( current . left , resolver , new Set ( seen ) ) &&
427+ isOriginPreservingStaticSuffix ( current . right )
428+ )
429+ }
430+ if (
431+ current . type === 'TemplateLiteral' &&
432+ Array . isArray ( current . expressions ) &&
433+ Array . isArray ( current . quasis ) &&
434+ current . expressions . length > 0 &&
435+ current . quasis . length === current . expressions . length + 1 &&
436+ current . expressions . every ( isSyntaxNode ) &&
437+ current . quasis . every ( isSyntaxNode ) &&
438+ getTemplateQuasiValue ( current . quasis [ 0 ] ) === '' &&
439+ isSimOriginExpression ( current . expressions [ 0 ] , resolver , new Set ( seen ) )
440+ ) {
441+ const suffix = getTemplateQuasiValue ( current . quasis [ 1 ] )
442+ return (
443+ suffix !== undefined &&
444+ ( suffix === '' ? current . expressions . length === 1 : / ^ [ / ? # ] / . test ( suffix ) )
445+ )
446+ }
324447 if ( current . type === 'ConditionalExpression' ) {
325448 return (
326449 ( isSyntaxNode ( current . consequent ) &&
@@ -362,6 +485,7 @@ function functionReturnsSimOrigin(
362485 if ( argument . resolver === resolver ) locals . set ( parameter . name , argument . expression )
363486 }
364487 }
488+ collectFunctionLocalBindings ( current , locals )
365489 const localResolver = { ...resolver , locals, scopedLocals }
366490 if ( current . type === 'ArrowFunctionExpression' && isSyntaxNode ( current . body ) ) {
367491 const body = unwrapExpression ( current . body )
@@ -744,22 +868,7 @@ function functionContainsInternalRoute(
744868 }
745869 const localResolver : SelfHopResolver = { ...resolver , locals, scopedLocals }
746870
747- const collectLocals = ( node : SyntaxNode ) => {
748- if ( node !== current && FUNCTION_NODE_TYPES . has ( node . type ) ) {
749- return
750- }
751- if (
752- node . type === 'VariableDeclarator' &&
753- isSyntaxNode ( node . id ) &&
754- node . id . type === 'Identifier' &&
755- typeof node . id . name === 'string' &&
756- isSyntaxNode ( node . init )
757- ) {
758- locals . set ( node . id . name , node . init )
759- }
760- for ( const child of getChildNodes ( node ) ) collectLocals ( child )
761- }
762- collectLocals ( current )
871+ collectFunctionLocalBindings ( current , locals )
763872
764873 if ( current . type === 'ArrowFunctionExpression' && isSyntaxNode ( current . body ) ) {
765874 const body = unwrapExpression ( current . body )
0 commit comments