11// Copyright 2018-2024 the Deno authors. MIT license.
22
3+ use std:: borrow:: Cow ;
34use std:: cell:: RefCell ;
45use std:: collections:: HashMap ;
56use std:: collections:: HashSet ;
@@ -49,51 +50,50 @@ impl Mappings {
4950 ) -> Result < Self > {
5051 let mut mappings = HashMap :: new ( ) ;
5152 let mut mapped_filepaths_no_ext = HashSet :: new ( ) ;
53+ let local_files = LocalFile :: resolve_all ( specifiers) ?;
5254 // the main files keep their paths relative to the main files' root so that
5355 // where the tests happen to live doesn't shift the distributed code
54- let main_specifiers = specifiers
55- . local
56- . iter ( )
57- . filter ( |s| !specifiers. test_modules . contains ( s) )
58- . cloned ( )
59- . collect :: < Vec < _ > > ( ) ;
60- let main_base_dir = if main_specifiers. is_empty ( ) {
61- get_base_dir ( & specifiers. local ) ?
62- } else {
63- get_base_dir ( & main_specifiers) ?
56+ let main_base_dir = {
57+ let mut main_dirs = local_files
58+ . iter ( )
59+ . filter ( |f| !f. is_test )
60+ . filter_map ( |f| f. file_path . parent ( ) )
61+ . peekable ( ) ;
62+ if main_dirs. peek ( ) . is_some ( ) {
63+ get_base_dir ( main_dirs) ?
64+ } else {
65+ get_base_dir ( local_files. iter ( ) . filter_map ( |f| f. file_path . parent ( ) ) ) ?
66+ }
6467 } ;
6568 // the tests may be in a directory outside the main files' root, so they get
6669 // a root that encompasses both
67- let mut test_base_dir_candidates = vec ! [ main_base_dir. clone( ) ] ;
68- for specifier in specifiers
69- . local
70- . iter ( )
71- . filter ( |s| specifiers. test_modules . contains ( s) )
72- {
73- let file_path = url_to_file_path ( specifier) ?;
74- test_base_dir_candidates. push ( file_path. parent ( ) . unwrap ( ) . to_path_buf ( ) ) ;
75- }
76- let test_base_dir = get_common_dir ( test_base_dir_candidates) ;
70+ let test_base_dir = get_common_dir (
71+ std:: iter:: once ( main_base_dir. as_path ( ) ) . chain (
72+ local_files
73+ . iter ( )
74+ . filter ( |f| f. is_test )
75+ . filter_map ( |f| f. file_path . parent ( ) ) ,
76+ ) ,
77+ ) ;
7778 ensure_nonempty_base_dir ( & test_base_dir) ?;
7879 let mut root_local_dirs = HashSet :: new ( ) ;
7980
80- for specifier in specifiers. local . iter ( ) {
81- let file_path = url_to_file_path ( specifier) ?;
82- let base_dir = if specifiers. test_modules . contains ( specifier) {
81+ for local_file in local_files. iter ( ) {
82+ let base_dir = if local_file. is_test {
8383 & test_base_dir
8484 } else {
8585 & main_base_dir
8686 } ;
8787 let relative_file_path =
88- file_path. strip_prefix ( base_dir) . map_err ( |_| {
88+ local_file . file_path . strip_prefix ( base_dir) . map_err ( |_| {
8989 anyhow:: anyhow!(
9090 "Error stripping prefix of {} with base {}" ,
91- file_path. display( ) ,
91+ local_file . file_path. display( ) ,
9292 base_dir. display( )
9393 )
9494 } ) ?;
9595 mappings. insert (
96- specifier. clone ( ) ,
96+ local_file . specifier . clone ( ) ,
9797 get_mapped_file_path (
9898 MediaType :: from_path ( relative_file_path) ,
9999 relative_file_path,
@@ -217,6 +217,33 @@ impl Mappings {
217217 }
218218}
219219
220+ /// A local file with its path and test status resolved once up front, since
221+ /// both are needed several times while working out the output paths.
222+ struct LocalFile < ' a > {
223+ specifier : & ' a ModuleSpecifier ,
224+ file_path : PathBuf ,
225+ is_test : bool ,
226+ }
227+
228+ impl < ' a > LocalFile < ' a > {
229+ pub fn resolve_all ( specifiers : & ' a Specifiers ) -> Result < Vec < Self > > {
230+ if specifiers. local . is_empty ( ) {
231+ bail ! ( "Did not find any local files. Specifying only remote files is not currently supported." ) ;
232+ }
233+ specifiers
234+ . local
235+ . iter ( )
236+ . map ( |specifier| {
237+ Ok ( Self {
238+ specifier,
239+ file_path : url_to_file_path ( specifier) ?,
240+ is_test : specifiers. test_modules . contains ( specifier) ,
241+ } )
242+ } )
243+ . collect ( )
244+ }
245+ }
246+
220247/// Takes a group of remote specifiers for the provided base directory
221248/// and gets their output paths.
222249fn remote_specifiers_to_paths < ' a > (
@@ -525,18 +552,10 @@ fn is_banned_segment_char(c: char) -> bool {
525552 matches ! ( c, '/' | '\\' ) || is_banned_path_char ( c)
526553}
527554
528- fn get_base_dir ( specifiers : & [ ModuleSpecifier ] ) -> Result < PathBuf > {
529- if specifiers. is_empty ( ) {
530- bail ! ( "Did not find any local files. Specifying only remote files is not currently supported." ) ;
531- }
532- let base_dir = get_common_dir (
533- specifiers
534- . iter ( )
535- . map ( |specifier| {
536- Ok ( url_to_file_path ( specifier) ?. parent ( ) . unwrap ( ) . into ( ) )
537- } )
538- . collect :: < Result < Vec < PathBuf > > > ( ) ?,
539- ) ;
555+ fn get_base_dir < ' a > (
556+ dirs : impl IntoIterator < Item = & ' a Path > ,
557+ ) -> Result < PathBuf > {
558+ let base_dir = get_common_dir ( dirs) ;
540559 ensure_nonempty_base_dir ( & base_dir) ?;
541560 Ok ( base_dir)
542561}
@@ -548,17 +567,19 @@ fn ensure_nonempty_base_dir(base_dir: &Path) -> Result<()> {
548567 Ok ( ( ) )
549568}
550569
551- fn get_common_dir ( dirs : impl IntoIterator < Item = PathBuf > ) -> PathBuf {
570+ fn get_common_dir < ' a > ( dirs : impl IntoIterator < Item = & ' a Path > ) -> PathBuf {
552571 // todo(dsherret): should maybe error on windows when the files
553572 // span different drives...
554573 let mut dirs = dirs. into_iter ( ) ;
555- let Some ( mut base_dir ) = dirs. next ( ) else {
574+ let Some ( first_dir ) = dirs. next ( ) else {
556575 return PathBuf :: new ( ) ;
557576 } ;
577+ // only allocates when the directories actually diverge
578+ let mut base_dir = Cow :: Borrowed ( first_dir) ;
558579 for parent_dir in dirs {
559580 if base_dir != parent_dir {
560- if base_dir. starts_with ( & parent_dir) {
561- base_dir = parent_dir;
581+ if base_dir. starts_with ( parent_dir) {
582+ base_dir = Cow :: Borrowed ( parent_dir) ;
562583 } else {
563584 let mut final_path = PathBuf :: new ( ) ;
564585 for ( a, b) in base_dir. components ( ) . zip ( parent_dir. components ( ) ) {
@@ -568,11 +589,11 @@ fn get_common_dir(dirs: impl IntoIterator<Item = PathBuf>) -> PathBuf {
568589 break ;
569590 }
570591 }
571- base_dir = final_path;
592+ base_dir = Cow :: Owned ( final_path) ;
572593 }
573594 }
574595 }
575- base_dir
596+ base_dir. into_owned ( )
576597}
577598
578599#[ cfg( test) ]
@@ -609,13 +630,12 @@ mod test {
609630 ) ;
610631
611632 fn run_test ( urls : Vec < & str > , expected : & str ) {
612- let result = get_base_dir (
613- & urls
614- . into_iter ( )
615- . map ( |u| ModuleSpecifier :: parse ( u) . unwrap ( ) )
616- . collect :: < Vec < _ > > ( ) ,
617- )
618- . unwrap ( ) ;
633+ let file_paths = urls
634+ . into_iter ( )
635+ . map ( |u| url_to_file_path ( & ModuleSpecifier :: parse ( u) . unwrap ( ) ) . unwrap ( ) )
636+ . collect :: < Vec < _ > > ( ) ;
637+ let result =
638+ get_base_dir ( file_paths. iter ( ) . filter_map ( |p| p. parent ( ) ) ) . unwrap ( ) ;
619639 assert_eq ! ( result, PathBuf :: from( expected) ) ;
620640 }
621641 }
0 commit comments