@@ -1617,6 +1617,16 @@ fn is_named_reexport(imp: &ImportInfo) -> bool {
16171617 imp. reexport && !imp. wildcard_reexport
16181618}
16191619
1620+ /// True for a genuine wildcard re-export (`export * from 'Y'`). Emitted as a
1621+ /// distinct file-level marker edge (`reexports-wildcard`) alongside the
1622+ /// generic `reexports` edge so the query layer can tell a target reached
1623+ /// only by named specifiers apart from one that's also reached by a
1624+ /// wildcard — even when a *different* statement in the same file names
1625+ /// specific symbols from that exact target (#1849 review).
1626+ fn is_wildcard_reexport ( imp : & ImportInfo ) -> bool {
1627+ imp. reexport && imp. wildcard_reexport
1628+ }
1629+
16201630/// For a `type` import or a named re-export targeting a barrel or resolved
16211631/// file, emit one symbol-level edge per named symbol so the target symbols
16221632/// receive fan-in credit and aren't misclassified as dead code
@@ -1751,6 +1761,15 @@ fn process_single_import(
17511761 }
17521762 if is_named_reexport ( imp) {
17531763 emit_named_symbol_edges ( edges, file_input, imp, resolved_path, "reexports" , ctx) ;
1764+ } else if is_wildcard_reexport ( imp) {
1765+ edges. push ( ComputedEdge {
1766+ source_id : file_input. file_node_id ,
1767+ target_id : target_node_id,
1768+ kind : "reexports-wildcard" . to_string ( ) ,
1769+ confidence : 1.0 ,
1770+ dynamic : 0 ,
1771+ dynamic_kind : None ,
1772+ } ) ;
17541773 }
17551774 emit_barrel_through_edges ( edges, file_input, imp, resolved_path, edge_kind, ctx) ;
17561775}
@@ -1857,9 +1876,12 @@ mod import_edge_tests {
18571876
18581877 #[ test]
18591878 fn wildcard_reexport_emits_no_symbol_level_edge ( ) {
1860- // `export * from './utils'` carries no specific names, so only the
1861- // file-level `reexports` edge is emitted — the query layer falls
1862- // back to the target's full export list for genuine wildcards.
1879+ // `export * from './utils'` carries no specific names, so no
1880+ // symbol-level edge is emitted. It does get the dedicated
1881+ // `reexports-wildcard` file-level marker (alongside the generic
1882+ // `reexports` edge) so the query layer can always apply full-export
1883+ // semantics for genuine wildcards, even when a *different* statement
1884+ // to the same target also names specific symbols (#1849 review).
18631885 let files = vec ! [ make_file( "src/index.ts" , 1 , vec![
18641886 ImportInfo {
18651887 source: "./utils" . to_string( ) ,
@@ -1887,9 +1909,61 @@ mod import_edge_tests {
18871909 "/root" . to_string ( ) ,
18881910 Some ( symbol_nodes) ,
18891911 ) ;
1890- assert_eq ! ( edges. len( ) , 1 ) ;
1912+ assert_eq ! ( edges. len( ) , 2 ) ;
18911913 assert_eq ! ( edges[ 0 ] . kind, "reexports" ) ;
18921914 assert_eq ! ( edges[ 0 ] . target_id, 2 ) ;
1915+ assert_eq ! ( edges[ 1 ] . kind, "reexports-wildcard" ) ;
1916+ assert_eq ! ( edges[ 1 ] . target_id, 2 ) ;
1917+ }
1918+
1919+ #[ test]
1920+ fn named_and_wildcard_reexport_of_same_target_both_marked ( ) {
1921+ // `export { foo } from './utils'` AND `export * from './utils'` in
1922+ // the same file, both targeting utils.ts. The wildcard's full-export
1923+ // semantics must stay independently signalled (via the dedicated
1924+ // `reexports-wildcard` marker) rather than being suppressed by the
1925+ // named specifier's symbol-level edge — otherwise the query layer
1926+ // would report only `foo` and silently drop every other export of
1927+ // utils.ts that the wildcard was meant to surface (#1849 review).
1928+ let files = vec ! [ make_file( "src/index.ts" , 1 , vec![
1929+ make_import( "./utils" , vec![ "foo" ] , true , false , false ) ,
1930+ ImportInfo {
1931+ source: "./utils" . to_string( ) ,
1932+ names: vec![ ] ,
1933+ reexport: true ,
1934+ type_only: false ,
1935+ dynamic_import: false ,
1936+ wildcard_reexport: true ,
1937+ } ,
1938+ ] , vec![ ] ) ] ;
1939+ let resolved = vec ! [ make_resolved( "/root/src/index.ts" , "./utils" , "src/utils.ts" ) ] ;
1940+ let node_ids = vec ! [ make_node_entry( "src/index.ts" , 1 ) , make_node_entry( "src/utils.ts" , 2 ) ] ;
1941+ let symbol_nodes = vec ! [ SymbolNodeEntry {
1942+ name: "foo" . to_string( ) ,
1943+ file: "src/utils.ts" . to_string( ) ,
1944+ node_id: 99 ,
1945+ } ] ;
1946+
1947+ let edges = build_import_edges (
1948+ files,
1949+ resolved,
1950+ vec ! [ ] ,
1951+ node_ids,
1952+ vec ! [ ] ,
1953+ "/root" . to_string ( ) ,
1954+ Some ( symbol_nodes) ,
1955+ ) ;
1956+ assert_eq ! ( edges. len( ) , 4 ) ;
1957+ // Named statement: file-level `reexports` + symbol-level `reexports` to foo.
1958+ assert_eq ! ( edges[ 0 ] . kind, "reexports" ) ;
1959+ assert_eq ! ( edges[ 0 ] . target_id, 2 ) ;
1960+ assert_eq ! ( edges[ 1 ] . kind, "reexports" ) ;
1961+ assert_eq ! ( edges[ 1 ] . target_id, 99 ) ;
1962+ // Wildcard statement: file-level `reexports` + the `reexports-wildcard` marker.
1963+ assert_eq ! ( edges[ 2 ] . kind, "reexports" ) ;
1964+ assert_eq ! ( edges[ 2 ] . target_id, 2 ) ;
1965+ assert_eq ! ( edges[ 3 ] . kind, "reexports-wildcard" ) ;
1966+ assert_eq ! ( edges[ 3 ] . target_id, 2 ) ;
18931967 }
18941968
18951969 #[ test]
0 commit comments