@@ -1621,6 +1621,16 @@ fn is_named_reexport(imp: &ImportInfo) -> bool {
16211621 imp. reexport && !imp. wildcard_reexport
16221622}
16231623
1624+ /// True for a genuine wildcard re-export (`export * from 'Y'`). Emitted as a
1625+ /// distinct file-level marker edge (`reexports-wildcard`) alongside the
1626+ /// generic `reexports` edge so the query layer can tell a target reached
1627+ /// only by named specifiers apart from one that's also reached by a
1628+ /// wildcard — even when a *different* statement in the same file names
1629+ /// specific symbols from that exact target (#1849 review).
1630+ fn is_wildcard_reexport ( imp : & ImportInfo ) -> bool {
1631+ imp. reexport && imp. wildcard_reexport
1632+ }
1633+
16241634/// For a `type` import or a named re-export targeting a barrel or resolved
16251635/// file, emit one symbol-level edge per named symbol so the target symbols
16261636/// receive fan-in credit and aren't misclassified as dead code
@@ -1755,6 +1765,15 @@ fn process_single_import(
17551765 }
17561766 if is_named_reexport ( imp) {
17571767 emit_named_symbol_edges ( edges, file_input, imp, resolved_path, "reexports" , ctx) ;
1768+ } else if is_wildcard_reexport ( imp) {
1769+ edges. push ( ComputedEdge {
1770+ source_id : file_input. file_node_id ,
1771+ target_id : target_node_id,
1772+ kind : "reexports-wildcard" . to_string ( ) ,
1773+ confidence : 1.0 ,
1774+ dynamic : 0 ,
1775+ dynamic_kind : None ,
1776+ } ) ;
17581777 }
17591778 emit_barrel_through_edges ( edges, file_input, imp, resolved_path, edge_kind, ctx) ;
17601779}
@@ -1861,9 +1880,12 @@ mod import_edge_tests {
18611880
18621881 #[ test]
18631882 fn wildcard_reexport_emits_no_symbol_level_edge ( ) {
1864- // `export * from './utils'` carries no specific names, so only the
1865- // file-level `reexports` edge is emitted — the query layer falls
1866- // back to the target's full export list for genuine wildcards.
1883+ // `export * from './utils'` carries no specific names, so no
1884+ // symbol-level edge is emitted. It does get the dedicated
1885+ // `reexports-wildcard` file-level marker (alongside the generic
1886+ // `reexports` edge) so the query layer can always apply full-export
1887+ // semantics for genuine wildcards, even when a *different* statement
1888+ // to the same target also names specific symbols (#1849 review).
18671889 let files = vec ! [ make_file( "src/index.ts" , 1 , vec![
18681890 ImportInfo {
18691891 source: "./utils" . to_string( ) ,
@@ -1891,9 +1913,61 @@ mod import_edge_tests {
18911913 "/root" . to_string ( ) ,
18921914 Some ( symbol_nodes) ,
18931915 ) ;
1894- assert_eq ! ( edges. len( ) , 1 ) ;
1916+ assert_eq ! ( edges. len( ) , 2 ) ;
18951917 assert_eq ! ( edges[ 0 ] . kind, "reexports" ) ;
18961918 assert_eq ! ( edges[ 0 ] . target_id, 2 ) ;
1919+ assert_eq ! ( edges[ 1 ] . kind, "reexports-wildcard" ) ;
1920+ assert_eq ! ( edges[ 1 ] . target_id, 2 ) ;
1921+ }
1922+
1923+ #[ test]
1924+ fn named_and_wildcard_reexport_of_same_target_both_marked ( ) {
1925+ // `export { foo } from './utils'` AND `export * from './utils'` in
1926+ // the same file, both targeting utils.ts. The wildcard's full-export
1927+ // semantics must stay independently signalled (via the dedicated
1928+ // `reexports-wildcard` marker) rather than being suppressed by the
1929+ // named specifier's symbol-level edge — otherwise the query layer
1930+ // would report only `foo` and silently drop every other export of
1931+ // utils.ts that the wildcard was meant to surface (#1849 review).
1932+ let files = vec ! [ make_file( "src/index.ts" , 1 , vec![
1933+ make_import( "./utils" , vec![ "foo" ] , true , false , false ) ,
1934+ ImportInfo {
1935+ source: "./utils" . to_string( ) ,
1936+ names: vec![ ] ,
1937+ reexport: true ,
1938+ type_only: false ,
1939+ dynamic_import: false ,
1940+ wildcard_reexport: true ,
1941+ } ,
1942+ ] , vec![ ] ) ] ;
1943+ let resolved = vec ! [ make_resolved( "/root/src/index.ts" , "./utils" , "src/utils.ts" ) ] ;
1944+ let node_ids = vec ! [ make_node_entry( "src/index.ts" , 1 ) , make_node_entry( "src/utils.ts" , 2 ) ] ;
1945+ let symbol_nodes = vec ! [ SymbolNodeEntry {
1946+ name: "foo" . to_string( ) ,
1947+ file: "src/utils.ts" . to_string( ) ,
1948+ node_id: 99 ,
1949+ } ] ;
1950+
1951+ let edges = build_import_edges (
1952+ files,
1953+ resolved,
1954+ vec ! [ ] ,
1955+ node_ids,
1956+ vec ! [ ] ,
1957+ "/root" . to_string ( ) ,
1958+ Some ( symbol_nodes) ,
1959+ ) ;
1960+ assert_eq ! ( edges. len( ) , 4 ) ;
1961+ // Named statement: file-level `reexports` + symbol-level `reexports` to foo.
1962+ assert_eq ! ( edges[ 0 ] . kind, "reexports" ) ;
1963+ assert_eq ! ( edges[ 0 ] . target_id, 2 ) ;
1964+ assert_eq ! ( edges[ 1 ] . kind, "reexports" ) ;
1965+ assert_eq ! ( edges[ 1 ] . target_id, 99 ) ;
1966+ // Wildcard statement: file-level `reexports` + the `reexports-wildcard` marker.
1967+ assert_eq ! ( edges[ 2 ] . kind, "reexports" ) ;
1968+ assert_eq ! ( edges[ 2 ] . target_id, 2 ) ;
1969+ assert_eq ! ( edges[ 3 ] . kind, "reexports-wildcard" ) ;
1970+ assert_eq ! ( edges[ 3 ] . target_id, 2 ) ;
18971971 }
18981972
18991973 #[ test]
0 commit comments