@@ -7,12 +7,14 @@ use biome_rowan::TextRange;
77use biome_rowan:: TextSize ;
88use oak_index:: builder:: build;
99use oak_index:: external:: file_layers;
10+ use oak_index:: external:: package_root_layers;
1011use oak_index:: external:: resolve_external_name;
1112use oak_index:: external:: BindingSource ;
1213use oak_index:: external:: ExternalDefinition ;
1314use oak_package:: library:: Library ;
1415use oak_package:: package:: Package ;
1516use oak_package:: package_description:: Description ;
17+ use oak_package:: package_namespace:: Import ;
1618use oak_package:: package_namespace:: Namespace ;
1719use url:: Url ;
1820
@@ -368,11 +370,11 @@ fn test_chained_scope_predecessor_files() {
368370 scope. extend ( file_layers ( file_url ( "a.R" ) , & index_a) ) ;
369371
370372 // Resolve from predecessor file b
373+ // Predecessor file export from b.R
371374 let result = resolve_external_name ( & library, & scope, "helper_b" ) ;
372- assert ! ( matches!(
373- result,
374- Some ( ExternalDefinition :: ProjectFile { .. } )
375- ) ) ;
375+ assert_matches ! ( result, Some ( ExternalDefinition :: ProjectFile { file, .. } ) => {
376+ assert_eq!( file, file_url( "b.R" ) ) ;
377+ } ) ;
376378
377379 // Resolve from predecessor file a
378380 let result = resolve_external_name ( & library, & scope, "helper_a" ) ;
@@ -391,3 +393,166 @@ fn test_chained_scope_predecessor_files() {
391393 } )
392394 ) ;
393395}
396+
397+ // --- root_layers ---
398+
399+ #[ test]
400+ fn test_root_layers_from_namespace_imports ( ) {
401+ let ns = Namespace {
402+ package_imports : vec ! [ "rlang" . to_string( ) , "cli" . to_string( ) ] ,
403+ ..Default :: default ( )
404+ } ;
405+ let layers = package_root_layers ( & ns) ;
406+ assert_eq ! ( layers. len( ) , 2 ) ;
407+ assert_matches ! ( & layers[ 0 ] , BindingSource :: PackageExports ( pkg) => {
408+ assert_eq!( pkg, "rlang" ) ;
409+ } ) ;
410+ assert_matches ! ( & layers[ 1 ] , BindingSource :: PackageExports ( pkg) => {
411+ assert_eq!( pkg, "cli" ) ;
412+ } ) ;
413+ }
414+
415+ #[ test]
416+ fn test_root_layers_empty_namespace ( ) {
417+ let ns = Namespace :: default ( ) ;
418+ let layers = package_root_layers ( & ns) ;
419+ assert ! ( layers. is_empty( ) ) ;
420+ }
421+
422+ #[ test]
423+ fn test_root_layers_includes_importfrom ( ) {
424+ let ns = Namespace {
425+ imports : vec ! [
426+ Import {
427+ name: "median" . to_string( ) ,
428+ package: "stats" . to_string( ) ,
429+ } ,
430+ Import {
431+ name: "head" . to_string( ) ,
432+ package: "utils" . to_string( ) ,
433+ } ,
434+ ] ,
435+ ..Default :: default ( )
436+ } ;
437+ let layers = package_root_layers ( & ns) ;
438+ assert_eq ! ( layers. len( ) , 1 ) ;
439+ assert_matches ! ( & layers[ 0 ] , BindingSource :: PackageImports ( map) => {
440+ assert_eq!( map. get( "median" ) . unwrap( ) , "stats" ) ;
441+ assert_eq!( map. get( "head" ) . unwrap( ) , "utils" ) ;
442+ } ) ;
443+ }
444+
445+ #[ test]
446+ fn test_root_layers_importfrom_before_package_exports ( ) {
447+ let ns = Namespace {
448+ imports : vec ! [ Import {
449+ name: "filter" . to_string( ) ,
450+ package: "stats" . to_string( ) ,
451+ } ] ,
452+ package_imports : vec ! [ "dplyr" . to_string( ) ] ,
453+ ..Default :: default ( )
454+ } ;
455+ let layers = package_root_layers ( & ns) ;
456+ assert_eq ! ( layers. len( ) , 2 ) ;
457+ assert_matches ! ( & layers[ 0 ] , BindingSource :: PackageImports ( _) ) ;
458+ assert_matches ! ( & layers[ 1 ] , BindingSource :: PackageExports ( pkg) => {
459+ assert_eq!( pkg, "dplyr" ) ;
460+ } ) ;
461+ }
462+
463+ // --- scope chain assembly ---
464+
465+ #[ test]
466+ fn test_scope_chain_combines_predecessors_and_root ( ) {
467+ let library = test_library ( vec ! [
468+ ( "rlang" , vec![ "sym" , "expr" ] ) ,
469+ ( "dplyr" , vec![ "filter" , "mutate" ] ) ,
470+ ] ) ;
471+
472+ let index_a = index_source ( "helper_a <- 1" ) ;
473+ let index_b = index_source ( "library(dplyr)\n helper_b <- 2" ) ;
474+
475+ let ns = Namespace {
476+ package_imports : vec ! [ "rlang" . to_string( ) ] ,
477+ ..Default :: default ( )
478+ } ;
479+
480+ let mut scope = Vec :: new ( ) ;
481+ scope. extend ( file_layers ( file_url ( "b.R" ) , & index_b) ) ;
482+ scope. extend ( file_layers ( file_url ( "a.R" ) , & index_a) ) ;
483+ scope. extend ( package_root_layers ( & ns) ) ;
484+
485+ // Predecessor file export
486+ let result = resolve_external_name ( & library, & scope, "helper_b" ) ;
487+ assert_eq ! (
488+ result,
489+ Some ( ExternalDefinition :: ProjectFile {
490+ file: file_url( "b.R" ) ,
491+ name: "helper_b" . to_string( ) ,
492+ range: range( 15 , 23 ) ,
493+ } )
494+ ) ;
495+
496+ // Predecessor library() directive
497+ let result = resolve_external_name ( & library, & scope, "filter" ) ;
498+ assert_eq ! (
499+ result,
500+ Some ( ExternalDefinition :: Package {
501+ package: "dplyr" . to_string( ) ,
502+ name: "filter" . to_string( ) ,
503+ } )
504+ ) ;
505+
506+ // Root layer (NAMESPACE import)
507+ let result = resolve_external_name ( & library, & scope, "sym" ) ;
508+ assert_eq ! (
509+ result,
510+ Some ( ExternalDefinition :: Package {
511+ package: "rlang" . to_string( ) ,
512+ name: "sym" . to_string( ) ,
513+ } )
514+ ) ;
515+
516+ // Miss
517+ assert_eq ! ( resolve_external_name( & library, & scope, "unknown" ) , None ) ;
518+ }
519+
520+ #[ test]
521+ fn test_scope_chain_predecessors_shadow_root ( ) {
522+ let library = test_library ( vec ! [ ( "rlang" , vec![ "expr" ] ) ] ) ;
523+
524+ let index = index_source ( "expr <- function() NULL" ) ;
525+
526+ let ns = Namespace {
527+ package_imports : vec ! [ "rlang" . to_string( ) ] ,
528+ ..Default :: default ( )
529+ } ;
530+
531+ let mut scope = Vec :: new ( ) ;
532+ scope. extend ( file_layers ( file_url ( "utils.R" ) , & index) ) ;
533+ scope. extend ( package_root_layers ( & ns) ) ;
534+
535+ // File export shadows the rlang root layer
536+ let result = resolve_external_name ( & library, & scope, "expr" ) ;
537+ assert ! ( matches!(
538+ result,
539+ Some ( ExternalDefinition :: ProjectFile { .. } )
540+ ) ) ;
541+ }
542+
543+ #[ test]
544+ fn test_scope_chain_later_predecessor_shadows_earlier ( ) {
545+ // b.R is loaded after a.R, so b.R's layers come first in the scope
546+ // and its `helper` should shadow a.R's `helper`.
547+ let index_a = index_source ( "helper <- 1" ) ;
548+ let index_b = index_source ( "helper <- 2" ) ;
549+
550+ let mut scope = Vec :: new ( ) ;
551+ scope. extend ( file_layers ( file_url ( "b.R" ) , & index_b) ) ;
552+ scope. extend ( file_layers ( file_url ( "a.R" ) , & index_a) ) ;
553+
554+ let result = resolve_external_name ( & empty_library ( ) , & scope, "helper" ) ;
555+ assert_matches ! ( result, Some ( ExternalDefinition :: ProjectFile { file, .. } ) => {
556+ assert_eq!( file, file_url( "b.R" ) ) ;
557+ } ) ;
558+ }
0 commit comments