char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
ab5df08
main
1use std:: collections::{ BTreeMap , BTreeSet , HashMap , HashSet }; 2use std:: path:: Path ; 3use std:: rc:: Rc ; 4 5use avril_core::{ Error , ModuleFormat , ModuleLoader , ModuleSource }; 6use futures:: future:: LocalBoxFuture ; 7use oxc:: allocator:: Allocator ; 8use oxc:: codegen::{ Codegen , CodegenOptions }; 9use oxc:: diagnostics::{ Diagnostics , NamedSource }; 10use oxc:: parser:: Parser ; 11use oxc:: semantic:: SemanticBuilder ; 12use oxc:: span:: SourceType ; 13use oxc:: transformer::{ EnvOptions , Module , TransformOptions , Transformer }; 14use oxc_sourcemap:: Token ; 15 16use crate :: data_modules:: DataModule ; 17use crate :: local_packages:: ResolveMode ; 18use crate :: modules::{ ModuleGraph , ResolutionMode , ResolutionTarget , data_url, http_url}; 19use crate :: registry:: npm:: package:: NpmModuleKind ; 20 21# [ derive ( Default )] 22pub ( crate ) struct LoaderState { 23pub ( crate ) entry : Option < String >, 24pub ( crate ) records : HashMap < String , ModuleRecord >, 25} 26 27# [ derive ( Clone , Copy , PartialEq , Eq )] 28enum ModuleState { 29Ready , 30Complete , 31} 32 33pub ( crate ) struct ModuleRecord { 34state : ModuleState , 35source : Option < ModuleSource >, 36specifiers : Option < Vec < String >>, 37imports : HashMap < String , String >, 38source_map : Option < Vec < Token >>, 39} 40 41/// Warm-only notice for a skipped edge: the unsupported specifier plus the 42/// importing module. Strict walks never invoke it. 43pub type BuiltinWarning =Rc < dyn Fn ( & str , & str )>; 44 45impl ModuleGraph { 46pub fn set_entry ( & self , name : & str ) { 47self . loader . borrow_mut (). entry =Some ( name. to_owned ()); 48} 49 50pub fn entry_url ( & self ) ->String { 51self . loader 52. borrow () 53. entry 54. as_deref () 55. and_then ( |name|self . module_url ( name)) 56. unwrap_or_default () 57} 58 59pub fn info ( & self , root : & str ) ->Result < serde_json:: Value , Error > { 60let complete: Vec <( String , HashMap < String , String >)> ={ 61let loader =self . loader . borrow (); 62let records =& loader. records ; 63 records 64. iter () 65. filter ( |( _, record) | record. state ==ModuleState :: Complete ) 66. map ( |( name, record) |( name. clone (), record. imports . clone ())) 67. collect () 68}; 69let modules: BTreeMap < _ , _ > = complete 70. into_iter () 71. map ( |( name, imports) |{ 72self . check_access ( & name) ?; 73let imports: BTreeMap < _ , _ > = imports. into_iter (). collect (); 74let metadata =self . package_metadata ( & name) ?; 75Ok (( name. clone (), serde_json:: json!({ "url" : self . module_url ( & name), "imports" : imports, "package" : metadata}))) 76}) 77. collect ::< Result < _ , Error >>() ?; 78Ok ( serde_json:: json!({ "root" : root, "modules" : modules})) 79} 80 81/// Local execution format by extension. `.js` stays ESM per avril 82/// policy (no `package.json` `type` behavior); explicit `.cjs`/`.cts` 83/// are CommonJS and `.json` is JSON for `require`. 84fn local_format ( name : & str ) ->ModuleFormat { 85let lower = name. to_ascii_lowercase (); 86if lower. ends_with ( ".cjs" ) || lower. ends_with ( ".cts" ) { 87ModuleFormat :: CommonJs 88} else if lower. ends_with ( ".json" ) { 89ModuleFormat :: Json 90} else { 91ModuleFormat :: Esm 92} 93} 94 95fn module_format_sync ( & self , name : & str ) ->Result < ModuleFormat , Error > { 96if name. starts_with ( "avril:" ) ||http_url ( name). is_some () ||data_url ( name). is_some () { 97// Remote CJS stays explicitly unsupported; remote files execute 98// as ESM so `require` of them fails with the ESM message. 99return Ok ( ModuleFormat :: Esm ); 100} 101if self . npm . is_reserved_npm_path ( Path :: new ( name)) { 102return match self . npm . check_access ( Path :: new ( name)) { 103Ok ( NpmModuleKind :: Esm ) |Ok ( NpmModuleKind :: TypeScript ) =>Ok ( ModuleFormat :: Esm ), 104Ok ( NpmModuleKind :: CommonJs ) =>Ok ( ModuleFormat :: CommonJs ), 105Ok ( NpmModuleKind :: Json ) =>Ok ( ModuleFormat :: Json ), 106Ok ( NpmModuleKind :: Declaration ) =>Err ( Error :: Module ( format! ( 107"{name}: npm declaration files have no runtime code" 108))), 109Err ( error) =>Err ( Error :: Module ( error. to_string ())), 110}; 111} 112Ok ( Self :: local_format ( name)) 113} 114 115pub fn prepare_for_add ( 116& self , 117specifier : String , 118referrer : String , 119warn_unsupported : BuiltinWarning , 120) ->LocalBoxFuture < ' static , Result < String , Error >> { 121self . prepare_with ( specifier, referrer, Some ( warn_unsupported)) 122} 123 124fn prepare_with ( 125& self , 126specifier : String , 127referrer : String , 128warn_unsupported : Option < BuiltinWarning >, 129) ->LocalBoxFuture < ' static , Result < String , Error >> { 130let graph =self . clone (); 131Box :: pin ( async move { 132let ( root, request) =if referrer. is_empty () { 133( data_url ( & specifier). map_or ( specifier, String :: from), None ) 134} else { 135 graph. resolve_tracked ( & specifier, & referrer). await ? 136}; 137let mut requests: BTreeSet < _ > = request. into_iter (). collect (); 138let mut pending =vec! [ root. clone ()]; 139let mut seen =HashSet :: new (); 140let mut links =HashMap :: new (); 141while let Some ( name) = pending. pop () { 142if name. starts_with ( "avril:" ) || !seen. insert ( name. clone ()) { 143continue ; 144} 145 graph. check_access ( & name) ?; 146 graph. initialize_package ( & name). await ?; 147let is_complete ={ 148 graph 149. loader 150. borrow () 151. records 152. get ( & name) 153. is_some_and ( |record| record. state ==ModuleState :: Complete ) 154}; 155if is_complete{ 156continue ; 157} 158let cached ={ 159 graph. loader . borrow (). records . get ( & name). and_then ( |record|{ 160Some (( record. source . clone () ?, record. specifiers . clone () ?)) 161}) 162}; 163let format = graph. module_format_sync ( & name) ?; 164if format !=ModuleFormat :: Esm { 165if cached. is_none () { 166let raw = graph. original_source ( & name). await ?; 167let ( code, maps) =if format ==ModuleFormat :: Json { 168( raw, None ) 169} else if name. to_ascii_lowercase (). ends_with ( ".cts" ) { 170let source_type = graph. source_type ( & name) ?; 171let ( code, maps) = 172transpile ( & name, & raw, source_type, TransformOptions :: default ()) ?; 173( code, if maps. is_empty () { None } else { Some ( maps) }) 174} else { 175( raw, None ) 176}; 177let source: ModuleSource = code. into (); 178{ 179let mut loader = graph. loader . borrow_mut (); 180let records =& mut loader. records ; 181match records. get_mut ( & name) { 182Some ( record) =>{ 183 record. source =Some ( source); 184 record. specifiers =Some ( Vec :: new ()); 185 record. source_map = maps; 186} 187None =>{ 188 records. insert ( 189 name. clone (), 190ModuleRecord { 191state : ModuleState :: Ready , 192source : Some ( source), 193specifiers : Some ( Vec :: new ()), 194imports : HashMap :: new (), 195source_map : maps, 196}, 197); 198} 199} 200} 201} 202 requests. extend ( graph. prepare_npm_roots ( & name, format, & []). await ?); 203 links. insert ( name, HashMap :: new ()); 204continue ; 205} 206let specifiers =match cached{ 207Some (( _, specifiers)) => specifiers, 208None =>{ 209let raw =if name. starts_with ( graph. store . options (). jsr_registry . as_str ()) { 210let hints = graph. store . hints ( & name). await . unwrap_or_default (); 211let prefetch = futures:: future:: join_all ( 212 hints 213. iter () 214. filter ( |hint| graph. check_access ( hint). is_ok ()) 215. map ( |hint| graph. store . source ( hint)), 216); 217let ( source, _) = 218 futures:: future:: join ( graph. original_source ( & name), prefetch). await ; 219 source? 220} else { 221 graph. original_source ( & name). await ? 222}; 223let ( code, mappings) =match graph. source_type ( & name) ?{ 224 source_typeif source_type. is_typescript () || source_type. is_jsx () =>{ 225let options =if source_type. is_jsx () { 226 graph. jsx_config ( & name) ?. transform_options (). map_err ( 227 |error|Error :: Module ( format! ( "{name}: {error}" )), 228) ? 229} else { 230TransformOptions :: default () 231}; 232let ( code, mappings) = 233transpile ( & name, & raw, source_type, options) ?; 234( code, Some ( mappings)) 235} 236 _ =>( raw, None ), 237}; 238let specifiers =match import_specifiers ( & name, & code) { 239Ok ( specifiers) => specifiers, 240Err ( error) =>{ 241// Another prepare may have stored or completed the record 242// while this one awaited; never clear its source, state, 243// or imports, only fill a missing source map. 244if let Some ( mappings) = mappings{ 245let mut loader = graph. loader . borrow_mut (); 246let records =& mut loader. records ; 247match records. get_mut ( & name) { 248Some ( record) 249if record. state !=ModuleState :: Complete 250 && record. source . is_none () => 251{ 252 record. source_map =Some ( mappings); 253} 254None =>{ 255 records. insert ( 256 name. clone (), 257ModuleRecord { 258state : ModuleState :: Ready , 259source : None , 260specifiers : None , 261imports : HashMap :: new (), 262source_map : Some ( mappings), 263}, 264); 265} 266 _ =>{} 267} 268} 269return Err ( error); 270} 271}; 272let source: ModuleSource = code. into (); 273{ 274let mut loader = graph. loader . borrow_mut (); 275let records =& mut loader. records ; 276match records. get_mut ( & name) { 277Some ( record) =>{ 278// Refresh only the fetch artifacts; a concurrent 279// prepare's state and imports must survive. 280 record. source =Some ( source); 281 record. specifiers =Some ( specifiers. clone ()); 282 record. source_map = mappings; 283} 284None =>{ 285 records. insert ( 286 name. clone (), 287ModuleRecord { 288state : ModuleState :: Ready , 289source : Some ( source), 290specifiers : Some ( specifiers. clone ()), 291imports : HashMap :: new (), 292source_map : mappings, 293}, 294); 295} 296} 297} 298 specifiers 299} 300}; 301 graph. prepare_npm_roots ( & name, format, & specifiers). await ?; 302let mut dependencies =Vec :: new (); 303let mut imports =HashMap :: new (); 304for specifierin & specifiers{ 305if let Some ( warn) =& warn_unsupported 306 && specifier. starts_with ( "node:" ) 307{ 308warn ( specifier, & name); 309continue ; 310} 311let ( dependency, request) = graph. resolve_tracked ( specifier, & name). await ?; 312 requests. extend ( request); 313 imports. insert ( specifier. clone (), dependency. clone ()); 314 dependencies. push ( dependency); 315} 316 pending. extend ( dependencies. into_iter (). rev ()); 317 links. insert ( name, imports); 318} 319 graph 320. store 321. save_graph ( & requests, & seen) 322. map_err ( |error|Error :: Module ( error. to_string ())) ?; 323{ 324let mut loader = graph. loader . borrow_mut (); 325let records =& mut loader. records ; 326// Every name needing completion is in links; names in seen but not 327// in links were already Complete and skipped above. 328for ( name, imports) in links{ 329if let Some ( record) = records. get_mut ( & name) 330 && record. state !=ModuleState :: Complete 331{ 332 record. imports = imports; 333// Warm-only walks cache sources without completing 334// records, so a later strict walk still validates 335// every edge including skipped builtins. 336if warn_unsupported. is_none () { 337 record. state =ModuleState :: Complete ; 338} 339} 340} 341} 342Ok ( root) 343}) 344} 345} 346 347impl ModuleLoader for ModuleGraph { 348fn resolve ( & self , specifier : & str , referrer : & str ) ->Result < String , Error > { 349if specifier. starts_with ( "avril:" ) { 350return Ok ( specifier. to_owned ()); 351} 352self . loader 353. borrow () 354. records 355. get ( referrer) 356. and_then ( |record| record. imports . get ( specifier)) 357. cloned () 358. ok_or_else ( ||{ 359Error :: Module ( format! ( 360"Import was not prepared: {specifier} from {referrer}" 361)) 362}) 363} 364 365fn check_access ( & self , name : & str ) ->Result <(), Error > { 366if data_url ( name). is_some () { 367DataModule :: parse ( name) ?; 368return Ok (()); 369} 370if name. starts_with ( "avril:" ) { 371return Err ( Error :: Module ( format! ( 372"Unsupported module specifier: {name} (unknown builtin)" 373))); 374} 375if let Some ( url) =http_url ( name) { 376if !url. username (). is_empty () || url. password (). is_some () { 377return Err ( Error :: Module ( format! ( 378"HTTP module credentials are not supported: {name}" 379))); 380} 381if name. starts_with ( self . store . options (). jsr_registry . as_str ()) { 382self . store 383. module_base ( name) 384. map_err ( |error|Error :: Module ( error. to_string ())) ?; 385} 386return self 387. store 388. check_acquired_remote ( name, & self . authority ) 389. map_err ( |error|Error :: Module ( error. to_string ())); 390} 391// Reserved npm paths always authorize through virtual-URL 392// authority: project filesystem grants never cover them, and 393// unowned cache paths fail instead of loading as local files. 394// Declarations have no executable code even when readable. 395if self . npm . is_reserved_npm_path ( Path :: new ( name)) { 396return match self . npm . check_access ( Path :: new ( name)) { 397Ok ( NpmModuleKind :: Declaration ) =>Err ( Error :: Module ( format! ( 398"{name}: npm declaration files have no runtime code" 399))), 400Ok ( _) =>Ok (()), 401Err ( error) =>Err ( Error :: Module ( error. to_string ())), 402}; 403} 404self . target ( name). map ( |_|()) 405} 406 407fn load ( & self , name : & str ) ->Result < ModuleSource , Error > { 408self . check_access ( name) ?; 409if let Some ( source) =self 410. loader 411. borrow () 412. records 413. get ( name) 414. and_then ( |record| record. source . clone ()) 415{ 416return Ok ( source); 417} 418let format =self . module_format_sync ( name) ?; 419match format{ 420ModuleFormat :: Esm =>Err ( Error :: Module ( format! ( "Module was not prepared: {name}" ))), 421ModuleFormat :: CommonJs |ModuleFormat :: Json =>{ 422let ( code, maps) =if matches! ( format, ModuleFormat :: CommonJs ) 423 && name. to_ascii_lowercase (). ends_with ( ".cts" ) 424{ 425let raw =self . sync_source ( name) ?; 426let source_type =self . source_type ( name) ?; 427let ( code, maps) = 428transpile ( name, & raw, source_type, TransformOptions :: default ()) ?; 429( code, if maps. is_empty () { None } else { Some ( maps) }) 430} else { 431( self . sync_source ( name) ?, None ) 432}; 433let source: ModuleSource = code. into (); 434let mut loader =self . loader . borrow_mut (); 435let records =& mut loader. records ; 436match records. get_mut ( name) { 437Some ( record) =>{ 438 record. source =Some ( source. clone ()); 439 record. specifiers =Some ( Vec :: new ()); 440 record. source_map = maps; 441} 442None =>{ 443 records. insert ( 444 name. to_owned (), 445ModuleRecord { 446state : ModuleState :: Complete , 447source : Some ( source. clone ()), 448specifiers : Some ( Vec :: new ()), 449imports : HashMap :: new (), 450source_map : maps, 451}, 452); 453} 454} 455Ok ( source) 456} 457} 458} 459 460fn module_format ( & self , name : & str ) ->Result < ModuleFormat , Error > { 461self . module_format_sync ( name) 462} 463 464fn require_resolve ( & self , specifier : & str , referrer : & str ) ->Result < String , Error > { 465self . require_resolve_sync ( specifier, referrer) 466} 467 468fn cjs_named_exports ( & self , name : & str ) ->Result < Vec < String >, Error > { 469self . check_access ( name) ?; 470let source =self . load ( name) ?; 471self . check_access ( name) ?; 472Ok ( crate :: commonjs:: named_exports ( & source)) 473} 474 475fn prepare ( 476& self , 477specifier : String , 478referrer : String , 479) ->LocalBoxFuture < ' static , Result < String , Error >> { 480self . prepare_with ( specifier, referrer, None ) 481} 482 483/// Mirrors `resolve_tracked`'s decision order, but never acquires: unprepared 484/// jsr/npm packages error instead of fetching, missing targets are allowed, 485/// local names present as `file:` URLs, and no lock key is produced. 486fn resolve_import_meta ( & self , specifier : & str , referrer : & str ) ->Result < String , Error > { 487let referrer =self . classify_referrer ( referrer) ?; 488if let Some ( name) =Self :: early_scheme ( specifier) ?{ 489return Ok ( name); 490} 491if let Some ( path) = referrer. path . as_deref () 492 && referrer. npm 493{ 494if let Some ( url) =data_url ( specifier) { 495return Ok ( url. to_string ()); 496} 497return self 498. npm 499. resolve_from_file ( specifier, path, ResolutionMode :: Import ) 500. map_err ( |error| error. to_string ()) 501. and_then ( |resolved|{ 502let path = resolved 503. path 504. to_str () 505. ok_or_else ( ||"Invalid module path" . to_owned ()) ?; 506 url:: Url :: from_file_path ( path) 507. map ( String :: from) 508. map_err ( |_|"Invalid module path" . to_owned ()) 509}) 510. map_err ( Error :: Module ); 511} 512let mapped =if referrer. inline { 513None 514} else if referrer. remote { 515self . remote . resolve_import_meta ( specifier, & referrer. name ) ? 516} else { 517self . packages 518. resolve_import_meta ( specifier, & referrer. name , & self . remote ) ? 519}; 520let effective =match & mapped{ 521Some ( crate :: local_packages:: Resolution :: Specifier ( target)) => target. as_str (), 522 _ => specifier, 523}; 524if effective. starts_with ( "npm:" ) { 525return self . resolve_import_meta_npm ( effective, & referrer. name , referrer. remote ); 526} 527match self . resolve_target ( specifier, & referrer, mapped, ResolveMode :: PreparedOnly ) ?{ 528ResolutionTarget :: Jsr ( specifier) =>crate :: registry:: parse_jsr_request ( & specifier) 529. and_then ( |request|self . store . resolve_import_meta_request ( & request)) 530. map_err ( |error|Error :: Module ( error. to_string ())), 531ResolutionTarget :: Npm ( specifier) =>{ 532self . resolve_import_meta_npm ( & specifier, & referrer. name , referrer. remote ) 533} 534ResolutionTarget :: Remote ( name) =>Ok ( name), 535ResolutionTarget :: Module ( name) 536if name. starts_with ( "avril:" ) 537 ||http_url ( & name). is_some () 538 ||data_url ( & name). is_some () => 539{ 540Ok ( name) 541} 542ResolutionTarget :: Module ( name) =>self 543. module_url ( & name) 544. ok_or_else ( ||Error :: Module ( "Invalid module path" . into ())), 545} 546} 547 548fn module_url ( & self , name : & str ) ->Option < String > { 549if http_url ( name). is_some () ||data_url ( name). is_some () { 550return Some ( name. to_owned ()); 551} 552 url:: Url :: from_file_path ( name). ok (). map ( String :: from) 553} 554 555fn map_source_location ( 556& self , 557name : & str , 558line : u32 , 559column : u32 , 560) ->Option <( String , u32 , u32 )> { 561let loader =self . loader . borrow (); 562let records =& loader. records ; 563let mappings = records. get ( name) ?. source_map . as_ref () ?; 564let position =( line. checked_sub ( 1 ) ?, column. checked_sub ( 1 ) ?); 565let index = mappings 566. partition_point ( |token|( token. get_dst_line (), token. get_dst_col ()) <= position) 567. checked_sub ( 1 ) ?; 568let token = mappings[ index]; 569 token. get_source_id () ?; 570( token. get_dst_line () == position. 0 ). then ( ||{ 571( 572 name. to_owned (), 573 token. get_src_line () +1 , 574 token. get_src_col () +1 , 575) 576}) 577} 578} 579 580fn import_specifiers ( name : & str , source : & str ) ->Result < Vec < String >, Error > { 581let allocator =Allocator :: default (); 582let parsed =Parser :: new ( & allocator, source, SourceType :: mjs ()). parse (); 583if !parsed. diagnostics . is_empty () { 584let diagnostics: Vec < _ > = parsed 585. diagnostics 586. into_iter () 587. map ( |diagnostic| diagnostic. render_with_source_code ( NamedSource :: new ( name, source))) 588. collect (); 589return Err ( Error :: Syntax ( format! ( "{name}\n{}" , diagnostics. join ( "" )))); 590} 591let mut imports: Vec < _ > = parsed. module_record . requested_modules . iter (). collect (); 592 imports. sort_by_key ( |( _, requests) | requests. iter (). map ( |request| request. span . start ). min ()); 593Ok ( imports 594. into_iter () 595. map ( |( specifier, _) | specifier. to_string ()) 596. collect ()) 597} 598 599pub ( crate ) fn transpile ( 600name : & str , 601source : & str , 602source_type : SourceType , 603options : TransformOptions , 604) ->Result <( String , Vec < Token >), Error > { 605let ( code, map) =compile ( name, Path :: new ( name), source, source_type, options) ?; 606Ok (( code, map. get_tokens (). collect ())) 607} 608 609/// Strips types and compiles JSX, keeping CommonJS source types' module 610/// syntax. `path` is the file name the transformer sees, as in JSX 611/// development metadata; maps always name `name`. 612pub ( crate ) fn compile ( 613name : & str , 614path : & Path , 615source : & str , 616source_type : SourceType , 617mut options : TransformOptions , 618) ->Result <( String , oxc_sourcemap:: SourceMap < ' static >), Error > { 619if source_type. is_typescript_definition () { 620return Err ( Error :: Module ( format! ( 621"{name}: TypeScript declaration files have no runtime code" 622))); 623} 624let commonjs = source_type. is_commonjs (); 625let report = |diagnostics : Diagnostics |{ 626let rendered: Vec < _ > = diagnostics 627. into_iter () 628. map ( |diagnostic| diagnostic. render_with_source_code ( NamedSource :: new ( name, source))) 629. collect (); 630format! ( 631"Failed to transpile {name}\n{}" , 632 rendered. join ( "" ). trim_end () 633) 634}; 635 636let allocator =Allocator :: default (); 637let parsed =Parser :: new ( 638& allocator, 639 source, 640if commonjs{ 641 source_type 642} else { 643 source_type. with_module ( true ) 644}, 645) 646. parse (); 647if !parsed. diagnostics . is_empty () { 648return Err ( Error :: Syntax ( report ( parsed. diagnostics ))); 649} 650let mut program = parsed. program ; 651let semantic =SemanticBuilder :: new_compiler () 652. with_enum_eval ( true ) 653. build ( & program); 654if !semantic. diagnostics . is_empty () { 655return Err ( Error :: Syntax ( report ( semantic. diagnostics ))); 656} 657 options. env =EnvOptions { 658module : if commonjs{ 659Module :: Preserve 660} else { 661Module :: Esm 662}, 663 ..EnvOptions :: default () 664}; 665let transformed =Transformer :: new ( & allocator, path, & options) 666. build_with_scoping ( semantic. semantic . into_scoping (), & mut program); 667// Oxc warns where its output would not match TypeScript, such as for CommonJS-only syntax. 668if !transformed. diagnostics . is_empty () { 669return Err ( Error :: Module ( report ( transformed. diagnostics ))); 670} 671 672let generated =Codegen :: new () 673. with_options ( CodegenOptions { 674source_map_path : Some ( name. into ()), 675 ..CodegenOptions :: default () 676}) 677. build ( & program); 678Ok (( 679 generated. code , 680 generated. map . expect ( "maps are requested" ). into_owned (), 681)) 682} 683 684# [ cfg ( test )] 685mod warm_builtin_tests{ 686use avril_core:: ModuleLoader ; 687use std:: cell:: RefCell ; 688use std:: rc:: Rc ; 689 690# [ test ] 691fn strict_prepare_still_rejects_builtin_after_warm () { 692let dir = tempfile:: tempdir (). unwrap (); 693 std:: fs:: write ( dir. path (). join ( "avril.conf" ), "" ). unwrap (); 694 std:: fs:: write ( 695 dir. path (). join ( "parent.mjs" ), 696"import './child.mjs';\nexport default 1;\n" , 697) 698. unwrap (); 699 std:: fs:: write ( 700 dir. path (). join ( "child.mjs" ), 701"import 'node:fs';\nimport './sibling.mjs';\nexport default 2;\n" , 702) 703. unwrap (); 704 std:: fs:: write ( dir. path (). join ( "sibling.mjs" ), "export default 3;\n" ). unwrap (); 705let ( _runtime, graph) =crate :: modules:: offline_graph ( dir. path ()); 706let warned =Rc :: new ( RefCell :: new ( Vec :: new ())); 707let warn ={ 708let warned = warned. clone (); 709Rc :: new ( move |specifier : & str , referrer : & str |{ 710 warned 711. borrow_mut () 712. push (( specifier. to_owned (), referrer. to_owned ())); 713}) 714}; 715let parent = dir. path (). join ( "parent.mjs" ). to_string_lossy (). into_owned (); 716 futures:: executor:: block_on ( graph. prepare_for_add ( parent. clone (), String :: new (), warn)) 717. unwrap (); 718assert! ( 719 warned. borrow (). iter (). any ( |( specifier, referrer) |{ 720 specifier =="node:fs" && referrer. ends_with ( "child.mjs" ) 721}), 722"{warned:?}" , 723); 724let error = futures:: executor:: block_on ( graph. prepare ( parent, String :: new ())). unwrap_err (); 725assert! ( 726 error 727. to_string () 728. contains ( "builtin modules are not supported" ), 729"{error:?}" 730); 731} 732}