@@ -506,6 +506,91 @@ describe('trace mode (deferred violations)', () => {
506506 expect ( result . message ) . toContain ( 'entry.js' )
507507 } )
508508
509+ it ( 'finds snippet via fallback when rawId differs from source specifier' , async ( ) => {
510+ // Simulates frameworks like Nuxt where alias resolution rewrites import specifiers
511+ // before resolveId sees them (e.g. ../server/api/test → ~~/server/api/test)
512+ const plugins = ImpoundPlugin . rollup ( { trace : true , cwd : '/root' , patterns : [ [ / s e r v e r \/ a p i / , 'Not allowed' ] ] } )
513+ const pluginArray = Array . isArray ( plugins ) ? plugins : [ plugins ]
514+ const impoundPlugin = pluginArray . find ( p => p . name === 'impound' ) !
515+ const tracePlugin = pluginArray . find ( p => p . name === 'impound:trace' ) !
516+
517+ const errors : string [ ] = [ ]
518+ const context = { error : ( msg : string ) => errors . push ( msg ) }
519+
520+ // Transform with source code using a relative specifier
521+ await ( tracePlugin as any ) . transform ( 'import api from "../server/api/test";\nconsole.log(api)' , '/root/app/app.vue' )
522+
523+ // But resolveId receives an absolute pre-resolved path (as if a bundler alias resolved it)
524+ // rawId will be the absolute path, not the relative specifier from source
525+ await ( impoundPlugin as any ) . resolveId . call ( context , '/root/server/api/test' , '/root/app/app.vue' )
526+
527+ expect ( errors ) . toHaveLength ( 1 )
528+ expect ( errors [ 0 ] ) . toContain ( 'Not allowed' )
529+ // Fallback search should find the snippet by resolving '../server/api/test' relative to importer
530+ expect ( errors [ 0 ] ) . toContain ( 'Code:' )
531+ expect ( errors [ 0 ] ) . toContain ( 'import api from "../server/api/test"' )
532+ expect ( errors [ 0 ] ) . toContain ( '^' )
533+ } )
534+
535+ it ( 'handles fallback when no specifier matches the resolved id' , async ( ) => {
536+ // The fallback loop runs but no specifier matches — snippet remains undefined
537+ const plugins = ImpoundPlugin . rollup ( { trace : true , patterns : [ [ / s e c r e t / , 'Not allowed' ] ] } )
538+ const pluginArray = Array . isArray ( plugins ) ? plugins : [ plugins ]
539+ const impoundPlugin = pluginArray . find ( p => p . name === 'impound' ) !
540+ const tracePlugin = pluginArray . find ( p => p . name === 'impound:trace' ) !
541+
542+ const errors : string [ ] = [ ]
543+ const context = { error : ( msg : string ) => errors . push ( msg ) }
544+
545+ // Transform with an import that doesn't match the resolved id at all
546+ await ( tracePlugin as any ) . transform ( 'import foo from "unrelated-module";\nexport default foo' , 'middle.js' )
547+ // resolveId with a completely different id
548+ await ( impoundPlugin as any ) . resolveId . call ( context , 'secret' , 'middle.js' )
549+
550+ expect ( errors ) . toHaveLength ( 1 )
551+ expect ( errors [ 0 ] ) . toContain ( 'Not allowed' )
552+ // No Code: since no specifier in the fallback matched
553+ expect ( errors [ 0 ] ) . not . toContain ( 'Code:' )
554+ } )
555+
556+ it ( 'finds snippet via fallback without cwd' , async ( ) => {
557+ // Exercises the fallback path where cwd is undefined
558+ const plugins = ImpoundPlugin . rollup ( { trace : true , patterns : [ [ / s e r v e r \/ a p i / , 'Not allowed' ] ] } )
559+ const pluginArray = Array . isArray ( plugins ) ? plugins : [ plugins ]
560+ const impoundPlugin = pluginArray . find ( p => p . name === 'impound' ) !
561+ const tracePlugin = pluginArray . find ( p => p . name === 'impound:trace' ) !
562+
563+ const errors : string [ ] = [ ]
564+ const context = { error : ( msg : string ) => errors . push ( msg ) }
565+
566+ await ( tracePlugin as any ) . transform ( 'import api from "~~/server/api/test";\nconsole.log(api)' , 'app.vue' )
567+ await ( impoundPlugin as any ) . resolveId . call ( context , 'server/api/test' , 'app.vue' )
568+
569+ expect ( errors ) . toHaveLength ( 1 )
570+ expect ( errors [ 0 ] ) . toContain ( 'Code:' )
571+ } )
572+
573+ it ( 'finds snippet via suffix match when specifier uses aliases' , async ( ) => {
574+ // When the source has an alias like ~~/server/api/test and the resolved id is server/api/test
575+ const plugins = ImpoundPlugin . rollup ( { trace : true , patterns : [ [ / s e r v e r \/ a p i / , 'Not allowed' ] ] } )
576+ const pluginArray = Array . isArray ( plugins ) ? plugins : [ plugins ]
577+ const impoundPlugin = pluginArray . find ( p => p . name === 'impound' ) !
578+ const tracePlugin = pluginArray . find ( p => p . name === 'impound:trace' ) !
579+
580+ const errors : string [ ] = [ ]
581+ const context = { error : ( msg : string ) => errors . push ( msg ) }
582+
583+ // Transform with aliased import
584+ await ( tracePlugin as any ) . transform ( 'import api from "~~/server/api/test";\nconsole.log(api)' , 'app.vue' )
585+
586+ // resolveId receives the resolved form (without alias)
587+ await ( impoundPlugin as any ) . resolveId . call ( context , 'server/api/test' , 'app.vue' )
588+
589+ expect ( errors ) . toHaveLength ( 1 )
590+ expect ( errors [ 0 ] ) . toContain ( 'Code:' )
591+ expect ( errors [ 0 ] ) . toContain ( '~~/server/api/test' )
592+ } )
593+
509594 it ( 'handles dynamic imports with non-literal specifiers in transform' , async ( ) => {
510595 const plugins = ImpoundPlugin . rollup ( { trace : true , patterns : [ [ 'secret' ] ] } )
511596 const pluginArray = Array . isArray ( plugins ) ? plugins : [ plugins ]
0 commit comments