char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
7b01146
main
1use std:: future:: Future ; 2use std:: io::{ self , BufRead , IsTerminal , Write }; 3use std:: sync:: Arc ; 4use std:: sync:: atomic::{ AtomicBool , Ordering }; 5use std:: sync:: mpsc::{ self , Receiver }; 6use std:: task:: Poll ; 7use std:: thread; 8use std:: time:: Duration ; 9 10use avril_core::{ Error , Runtime }; 11use futures:: StreamExt ; 12use futures:: channel:: mpsc::{ self as async_mpsc, UnboundedReceiver , UnboundedSender }; 13use futures:: future:: poll_fn; 14use oxc:: allocator:: Allocator ; 15use oxc:: ast:: ast::{ Expression , ImportAttributeKey , ImportDeclarationSpecifier , Statement }; 16use oxc:: parser:: Parser ; 17use oxc:: span::{ GetSpan , SourceType }; 18use reedline:: Signal ; 19use serde_json::{ Value , json}; 20use url:: Url ; 21 22mod line_editor; 23 24enum Input { 25Line ( String ), 26Eof , 27Cancel , 28Interrupt , 29Error ( String ), 30} 31 32enum Activity { 33Input ( Option < Input >), 34RuntimeError ( Error ), 35StalledEvaluation , 36Background , 37} 38 39struct InterruptState { 40active : Arc < AtomicBool >, 41interrupted : Arc < AtomicBool >, 42handle : avril_core:: v8:: IsolateHandle , 43} 44 45pub ( crate ) fn run ( 46runtime : & mut Runtime , 47referrer : & str , 48stdio : & avril_ext_stdio:: Stdio , 49host : & tokio:: runtime:: Handle , 50reporter : Option < & crate :: progress:: Reporter >, 51) ->Result <(), Error > { 52let source_url =Url :: from_file_path ( referrer) 53. map_err ( |_|Error :: JavaScript ( format! ( "Invalid REPL referrer: {referrer}" ))) ? 54. to_string (); 55 runtime. enable_repl () ?; 56 57let active =Arc :: new ( AtomicBool :: new ( false )); 58let interrupted = stdio. interrupt_flag (); 59let handle = runtime. isolate_handle (); 60let ( input_sender, input_receiver) = async_mpsc:: unbounded (); 61let ( request, requests) = mpsc:: channel ::<()>(); 62let terminal = io:: stdin (). is_terminal () && io:: stderr (). is_terminal (); 63let input_thread_sender = input_sender. clone (); 64let input_thread = thread:: Builder :: new () 65. name ( "avril:repl-input" . into ()) 66. spawn ( move ||read_input ( terminal, requests, input_thread_sender)) 67. map_err ( |error|Error :: JavaScript ( error. to_string ())); 68 69let signal_active =Arc :: clone ( & active); 70let signal_interrupted =Arc :: clone ( & interrupted); 71let signal_handle = handle. clone (); 72let signal_sender = input_sender; 73let result =match input_thread{ 74Err ( error) =>Err ( error), 75Ok ( _input_thread) =>{ 76// Route Ctrl-C through the shared chaining registry instead of 77// ctrlc's overwriting handler, so REPL listeners observe the 78// interrupt rather than being clobbered by it. 79let on_interrupt =move ||{ 80 signal_interrupted. store ( true , Ordering :: SeqCst ); 81let _ = signal_sender. unbounded_send ( Input :: Interrupt ); 82if signal_active. load ( Ordering :: SeqCst ) { 83// A blocking runSync cannot observe V8 termination, so 84// stop exactly the subprocess it is waiting on. Other 85// session work is left alone. 86 avril_ext_process:: Tree :: global (). stop_sync_processes (); 87 signal_handle. terminate_execution (); 88} 89}; 90# [ cfg ( unix )] 91 avril_ext_process:: Tree :: global () 92. claim_interrupt ( host, Arc :: new ( on_interrupt)) 93. map_err ( |error|Error :: JavaScript ( error. to_string ())) ?; 94# [ cfg ( not ( unix ))] 95 ctrlc:: set_handler ( on_interrupt) 96. map_err ( |error|Error :: JavaScript ( error. to_string ())) ?; 97run_session ( Repl { 98 runtime, 99 host, 100source_url : & source_url, 101 request, 102input : input_receiver, 103interrupts : InterruptState { active, interrupted, handle}, 104 stdio, 105 terminal, 106 reporter, 107}) 108} 109}; 110let disabled = runtime. disable_repl (); 111 result?; 112 disabled 113} 114 115/// One interactive session's runtime, input plumbing, and terminal state. 116struct Repl < ' a > { 117runtime : &' a mut Runtime , 118host : &' a tokio:: runtime:: Handle , 119source_url : &' a str , 120request : mpsc:: Sender <()>, 121input : UnboundedReceiver < Input >, 122interrupts : InterruptState , 123stdio : &' a avril_ext_stdio:: Stdio , 124terminal : bool , 125reporter : Option < &' a crate :: progress:: Reporter >, 126} 127 128fn run_session ( 129Repl { 130 runtime, 131 host, 132 source_url, 133 request, 134 input, 135 interrupts, 136 stdio, 137 terminal, 138 reporter, 139}: Repl < ' _ >, 140) ->Result <(), Error > { 141let mut input = input; 142 runtime. execute_script ::<()>( "<repl>" , "globalThis._ = undefined" ) ?; 143let InterruptState { active, interrupted, handle} =& interrupts; 144let mut source =String :: new (); 145let mut input_pending =false ; 146' session: loop { 147if !input_pending{ 148 request 149. send (()) 150. map_err ( |error|Error :: JavaScript ( error. to_string ())) ?; 151 input_pending =true ; 152} 153let line =loop { 154match host. block_on ( next_activity ( runtime, & mut input, active, None , terminal)) { 155Activity :: Input ( Some ( Input :: Line ( line))) =>{ 156 input_pending =false ; 157if interrupted. swap ( false , Ordering :: SeqCst ) { 158 handle. cancel_terminate_execution (); 159 source. clear (); 160continue ' session; 161} 162break Some ( line); 163} 164Activity :: Input ( Some ( Input :: Eof )) |Activity :: Input ( None ) =>break None , 165Activity :: Input ( Some ( Input :: Cancel )) =>{ 166 interrupted. store ( false , Ordering :: SeqCst ); 167 handle. cancel_terminate_execution (); 168 source. clear (); 169 input_pending =false ; 170continue ' session; 171} 172Activity :: Input ( Some ( Input :: Interrupt )) =>{ 173 interrupted. store ( false , Ordering :: SeqCst ); 174 handle. cancel_terminate_execution (); 175 source. clear (); 176continue ' session; 177} 178Activity :: Input ( Some ( Input :: Error ( error))) =>{ 179return Err ( Error :: JavaScript ( error)); 180} 181Activity :: RuntimeError ( error) =>{ 182if interrupted. swap ( false , Ordering :: SeqCst ) { 183 handle. cancel_terminate_execution (); 184eprintln! ( "Evaluation interrupted" ); 185 source. clear (); 186} else { 187eprintln! ( "{error}" ); 188} 189} 190Activity :: Background |Activity :: StalledEvaluation =>{} 191} 192}; 193let Some ( line) = lineelse { 194break ; 195}; 196if source. is_empty () { 197match line. trim () { 198".exit" |".quit" =>break , 199".help" =>{ 200println! ( 201"Enter JavaScript; incomplete expressions continue on the next line.\n.help Show this help\n.exit Exit the REPL" 202); 203continue ; 204} 205 _ =>{} 206} 207} 208if line. trim (). is_empty () && source. is_empty () { 209continue ; 210} 211 source. push_str ( & line); 212 source. push ( '\n' ); 213if is_incomplete ( & source) { 214continue ; 215} 216 217let expression =prepare_imports ( & prepare_expression ( & source)); 218 source. clear (); 219let shown =Session { 220runtime : & mut * runtime, 221 host, 222input : & mut input, 223interrupts : & interrupts, 224 stdio, 225 terminal, 226 reporter, 227} 228. show ( & expression, source_url); 229 runtime. release_repl_objects () ?; 230if shown? ==Waited :: Eof { 231break ; 232} 233} 234Ok (()) 235} 236 237// Results go through avril:std's inspect so they match console.log and honor 238// custom inspectors; the inspector can only reach it from the guest realm. 239const INSPECT : & str =r#"async function (value, colors, remember) { 240if (remember) this._ = value; 241const { inspect } = await import("avril:std"); 242return inspect(value, { colors }); 243}"# ; 244 245# [ derive ( PartialEq )] 246enum Waited { 247Response ( String ), 248Abandoned , 249Eof , 250} 251 252struct Session < ' a > { 253runtime : &' a mut Runtime , 254host : &' a tokio:: runtime:: Handle , 255input : &' a mut UnboundedReceiver < Input >, 256interrupts : &' a InterruptState , 257stdio : &' a avril_ext_stdio:: Stdio , 258terminal : bool , 259reporter : Option < &' a crate :: progress:: Reporter >, 260} 261 262struct ProgressScope < ' a >( &' a crate :: progress:: Reporter ); 263 264impl Drop for ProgressScope < ' _ > { 265fn drop ( & mut self ) { 266self . 0 . scope ( false ); 267} 268} 269 270impl Session < ' _ > { 271fn show ( & mut self , expression : & str , source_url : & str ) ->Result < Waited , Error > { 272let response =match self . run ( |runtime| runtime. evaluate_repl ( expression, source_url)) ?{ 273Waited :: Response ( response) => response, 274 waited =>return Ok ( waited), 275}; 276let ( value, stderr) =match completion ( & response) { 277Ok ( completion) => completion, 278Err ( message) =>{ 279eprintln! ( "{message}" ); 280return Ok ( Waited :: Abandoned ); 281} 282}; 283let colors =json! ({ "value" : avril_ext_console:: colors ( stderr) }); 284let remember =json! ({ "value" : !stderr}); 285let response =match self 286. run ( |runtime| runtime. call_repl_function ( INSPECT , & [ value, colors, remember])) ? 287{ 288Waited :: Response ( response) => response, 289 waited =>return Ok ( waited), 290}; 291let text =inspected ( & response); 292if stderr{ 293eprintln! ( "{text}" ); 294} else { 295println! ( "{text}" ); 296} 297 io:: stdout () 298. flush () 299. map_err ( |error|Error :: JavaScript ( error. to_string ())) ?; 300Ok ( Waited :: Abandoned ) 301} 302 303/// Dispatch an inspector command and drive the event loop until it answers, 304/// unless the user interrupts it or input ends first. 305fn run ( 306& mut self , 307dispatch : impl FnOnce ( & mut Runtime ) ->Result < i32 , Error >, 308) ->Result < Waited , Error > { 309let progress_scope =self . reporter . map ( |reporter|{ 310 reporter. scope ( true ); 311ProgressScope ( reporter) 312}); 313let InterruptState { active, interrupted, handle} =self . interrupts ; 314 active. store ( true , Ordering :: SeqCst ); 315if interrupted. swap ( false , Ordering :: SeqCst ) { 316 active. store ( false , Ordering :: SeqCst ); 317 handle. cancel_terminate_execution (); 318return Ok ( Waited :: Abandoned ); 319} 320let dispatched =dispatch ( self . runtime ); 321 active. store ( false , Ordering :: SeqCst ); 322let id =match dispatched{ 323Ok ( id) => id, 324Err ( _) if interrupted. swap ( false , Ordering :: SeqCst ) =>{ 325 handle. cancel_terminate_execution (); 326drop ( progress_scope); 327eprintln! ( "Evaluation interrupted" ); 328return Ok ( Waited :: Abandoned ); 329} 330Err ( error) =>return Err ( error), 331}; 332loop { 333if let Some ( response) =self . runtime . take_repl_response ( id) { 334self . stdio 335. flush_interruptible ( || interrupted. load ( Ordering :: SeqCst )) 336. map_err ( |error|Error :: JavaScript ( error. to_string ())) ?; 337if interrupted. swap ( false , Ordering :: SeqCst ) { 338 handle. cancel_terminate_execution (); 339drop ( progress_scope); 340eprintln! ( "Evaluation interrupted" ); 341return Ok ( Waited :: Abandoned ); 342} 343return Ok ( Waited :: Response ( response)); 344} 345match self . host . block_on ( next_activity ( 346self . runtime , 347self . input , 348 active, 349Some ( id), 350self . terminal , 351)) { 352Activity :: Input ( Some ( Input :: Interrupt |Input :: Cancel )) =>{ 353if interrupted. swap ( false , Ordering :: SeqCst ) { 354self . runtime . terminate_repl_execution () ?; 355self . abandon ( id); 356drop ( progress_scope); 357eprintln! ( "Evaluation interrupted" ); 358return Ok ( Waited :: Abandoned ); 359} 360} 361Activity :: RuntimeError ( error) =>{ 362if interrupted. swap ( false , Ordering :: SeqCst ) { 363self . abandon ( id); 364drop ( progress_scope); 365eprintln! ( "Evaluation interrupted" ); 366return Ok ( Waited :: Abandoned ); 367} 368if let Some ( reporter) =self . reporter { 369 reporter. flush (); 370} 371eprintln! ( "{error}" ); 372} 373Activity :: Input ( Some ( Input :: Error ( error))) =>{ 374return Err ( Error :: JavaScript ( error)); 375} 376Activity :: Input ( Some ( Input :: Eof )) |Activity :: Input ( None ) =>{ 377self . runtime . terminate_repl_execution () ?; 378self . abandon ( id); 379return Ok ( Waited :: Eof ); 380} 381Activity :: StalledEvaluation =>{ 382self . runtime . terminate_repl_execution () ?; 383self . abandon ( id); 384drop ( progress_scope); 385eprintln! ( "REPL evaluation is still pending with no runnable host work" ); 386return Ok ( Waited :: Abandoned ); 387} 388Activity :: Input ( Some ( Input :: Line ( _))) |Activity :: Background =>{} 389} 390} 391} 392 393fn abandon ( & mut self , id : i32 ) { 394self . interrupts . handle . cancel_terminate_execution (); 395let _ =self . runtime . take_repl_response ( id); 396} 397} 398 399fn next_activity < ' a >( 400runtime : &' a mut Runtime , 401input : &' a mut UnboundedReceiver < Input >, 402active : &' a AtomicBool , 403evaluation : Option < i32 >, 404terminal : bool , 405) ->impl Future < Output =Activity > +' a { 406let mut background_delay =None ; 407poll_fn ( move |context|{ 408if evaluation. is_some_and ( |id| runtime. has_repl_response ( id)) { 409return Poll :: Ready ( Activity :: Background ); 410} 411if let Poll :: Ready ( input) = input. poll_next_unpin ( context) { 412return Poll :: Ready ( Activity :: Input ( input)); 413} 414let background_pending = runtime. has_pending_background_tasks (); 415 active. store ( true , Ordering :: SeqCst ); 416let result = runtime. poll_event_loop ( context); 417 active. store ( false , Ordering :: SeqCst ); 418match result{ 419Err ( error) =>return Poll :: Ready ( Activity :: RuntimeError ( error)), 420Ok ( true ) =>return Poll :: Ready ( Activity :: Background ), 421Ok ( false ) =>{} 422} 423// The checkpoint can finish an inspector evaluation without a native completion. 424if evaluation. is_some_and ( |id| runtime. has_repl_response ( id)) { 425return Poll :: Ready ( Activity :: Background ); 426} 427// The default V8 platform does not notify the host about foreground tasks. 428if background_pending || runtime. has_pending_background_tasks () { 429let delay = background_delay 430. get_or_insert_with ( ||Box :: pin ( tokio:: time:: sleep ( Duration :: from_millis ( 20 )))); 431if delay. as_mut (). poll ( context). is_ready () { 432return Poll :: Ready ( Activity :: Background ); 433} 434} else { 435 background_delay =None ; 436if !terminal && evaluation. is_some () && !runtime. has_pending_work () { 437return Poll :: Ready ( Activity :: StalledEvaluation ); 438} 439} 440Poll :: Pending 441}) 442} 443 444fn read_input ( terminal : bool , requests : Receiver <()>, input : UnboundedSender < Input >) { 445if terminal{ 446let mut editor = line_editor:: create (); 447for () in requests{ 448let line =match editor. read_line ( & line_editor:: Prompt ) { 449Ok ( Signal :: Success ( line)) =>Input :: Line ( line), 450Ok ( Signal :: CtrlC ) =>Input :: Cancel , 451Ok ( Signal :: CtrlD ) =>Input :: Eof , 452Err ( error) =>Input :: Error ( error. to_string ()), 453}; 454let eof =matches! ( line, Input :: Eof |Input :: Error ( _)); 455if input. unbounded_send ( line). is_err () || eof{ 456break ; 457} 458} 459return ; 460} 461 462let stdin = io:: stdin (); 463let mut stdin = stdin. lock (); 464for _in requests{ 465let mut line =String :: new (); 466let line =loop { 467match stdin. read_line ( & mut line) { 468Ok ( 0 ) =>break Input :: Eof , 469Ok ( _) =>{ 470break Input :: Line ( line. trim_end_matches ([ '\r' , '\n' ]). to_owned ()); 471} 472Err ( error) if error. kind () == io:: ErrorKind :: Interrupted =>continue , 473Err ( error) =>break Input :: Error ( error. to_string ()), 474} 475}; 476let eof =matches! ( line, Input :: Eof |Input :: Error ( _)); 477if input. unbounded_send ( line). is_err () || eof{ 478break ; 479} 480} 481} 482 483fn is_incomplete ( source : & str ) ->bool { 484let allocator =Allocator :: default (); 485let parsed =Parser :: new ( & allocator, source, SourceType :: mjs ()). parse (); 486if parsed. diagnostics . is_empty () ||prepare_expression ( source) != source{ 487return false ; 488} 489let incomplete = |candidate : & str |{ 490let allocator =Allocator :: default (); 491let parsed =Parser :: new ( & allocator, candidate, SourceType :: mjs ()). parse (); 492let eof = candidate. len (); 493 !parsed. diagnostics . is_empty () 494 && parsed. diagnostics . iter (). all ( |error|{ 495let message = error. message . to_ascii_lowercase (); 496let at_end = error 497. labels 498. iter () 499. any ( |label| label. offset (). saturating_add ( label. len ()) as usize >= eof); 500if message. contains ( "unterminated string" ) { 501 error. labels . iter (). any ( |label|{ 502 candidate. as_bytes (). get ( label. offset () as usize ) ==Some ( & b'`' ) 503}) || candidate. ends_with ( "\\\n" ) 504} else { 505 at_end && !message. contains ( "unterminated regular expression" ) 506} 507}) 508}; 509incomplete ( source) ||incomplete ( & format! ( "({source}" )) 510} 511 512fn prepare_expression ( source : & str ) ->String { 513let wrapped =format! ( "({source})" ); 514let allocator =Allocator :: default (); 515let parsed =Parser :: new ( & allocator, & wrapped, SourceType :: mjs ()). parse (); 516if parsed. diagnostics . is_empty () 517 &&matches! ( 518 parsed. program . body . as_slice (), 519[ Statement :: ExpressionStatement ( statement)] 520if matches! ( statement. expression . without_parentheses (), Expression :: ObjectExpression ( _)) 521) 522{ 523return wrapped; 524} 525 source. to_owned () 526} 527 528fn prepare_imports ( source : & str ) ->String { 529let allocator =Allocator :: default (); 530let parsed =Parser :: new ( & allocator, source, SourceType :: mjs ()). parse (); 531if !parsed. diagnostics . is_empty () { 532return source. to_owned (); 533} 534let mut result =String :: new (); 535let mut offset =0 ; 536for statementin & parsed. program . body { 537let Statement :: ImportDeclaration ( import) = statementelse { 538continue ; 539}; 540if import. phase . is_some () { 541return source. to_owned (); 542} 543let mut properties =Vec :: new (); 544let mut namespace =None ; 545for specifierin import. specifiers . iter (). flatten () { 546match specifier{ 547ImportDeclarationSpecifier :: ImportDefaultSpecifier ( specifier) =>{ 548 properties. push ( format! ( "\"default\": {}" , specifier. local . name )); 549} 550ImportDeclarationSpecifier :: ImportSpecifier ( specifier) =>{ 551let span = specifier. imported . span (); 552let key =& source[ span. start as usize ..span. end as usize ]; 553 properties. push ( format! ( "{key}: {}" , specifier. local . name )); 554} 555ImportDeclarationSpecifier :: ImportNamespaceSpecifier ( specifier) =>{ 556 namespace =Some ( specifier. local . name . as_str ()); 557} 558} 559} 560let mut options =String :: new (); 561if let Some ( clause) =& import. with_clause { 562let attributes = clause 563. with_entries 564. iter () 565. map ( |attribute|{ 566let key =match & attribute. key { 567ImportAttributeKey :: Identifier ( identifier) =>{ 568 serde_json:: to_string ( identifier. name . as_str ()). unwrap () 569} 570ImportAttributeKey :: StringLiteral ( literal) => source 571[ literal. span . start as usize ..literal. span . end as usize ] 572. to_owned (), 573}; 574let value =& source 575[ attribute. value . span . start as usize ..attribute. value . span . end as usize ]; 576format! ( "[{key}]: {value}" ) 577}) 578. collect ::< Vec < _ >>() 579. join ( ", " ); 580 options =format! ( ", {{ with: {{ {attributes} }} }}" ); 581} 582let specifier =& source[ import. source . span . start as usize ..import. source . span . end as usize ]; 583let initializer =format! ( "await import({specifier}{options})" ); 584let mut declarations =Vec :: new (); 585if let Some ( namespace) = namespace{ 586 declarations. push ( format! ( "{namespace} = {initializer}" )); 587} 588if !properties. is_empty () || namespace. is_none () { 589 declarations. push ( format! ( 590"{{ {} }} = {}" , 591 properties. join ( ", " ), 592 namespace. unwrap_or ( & initializer) 593)); 594} 595let start = import. span . start as usize ; 596let end = import. span . end as usize ; 597 result. push_str ( & source[ offset..start]); 598 result. push_str ( & format! ( "const {};" , declarations. join ( ", " ))); 599// Keep diagnostics for subsequent statements on their original lines. 600 result. extend ( 601 source[ start..end] 602. chars () 603. filter ( |ch|matches! ( ch, '\r' |'\n' |'\u{2028}' |'\u{2029}' )), 604); 605 offset = end; 606} 607 result. push_str ( & source[ offset..]); 608 result 609} 610 611/// An evaluation's completion or thrown value as an inspector `CallArgument`, 612/// and whether it was thrown. 613fn completion ( response : & str ) ->Result <( Value , bool ), String > { 614let response = serde_json:: from_str ::< Value >( response) 615. map_err ( |_|"Invalid V8 inspector response" . to_owned ()) ?; 616if let Some ( error) = response. get ( "error" ) { 617return Err ( error[ "message" ] 618. as_str () 619. unwrap_or ( "Inspector error" ) 620. to_owned ()); 621} 622let result =& response[ "result" ]; 623if let Some ( exception) = result. get ( "exceptionDetails" ) { 624return match exception. get ( "exception" ) { 625Some ( thrown) =>Ok (( call_argument ( thrown), true )), 626None =>Err ( exception[ "text" ] 627. as_str () 628. unwrap_or ( "JavaScript exception" ) 629. to_owned ()), 630}; 631} 632Ok (( call_argument ( & result[ "result" ]), false )) 633} 634 635fn call_argument ( remote : & Value ) ->Value { 636for keyin [ "objectId" , "unserializableValue" , "value" ] { 637if let Some ( value) = remote. get ( key) { 638return json! ({ key: value}); 639} 640} 641json! ({}) 642} 643 644fn inspected ( response : & str ) ->String { 645let response = serde_json:: from_str ::< Value >( response). unwrap_or_default (); 646let result =& response[ "result" ]; 647 result[ "result" ][ "value" ] 648. as_str () 649. or_else ( || result[ "exceptionDetails" ][ "exception" ][ "description" ]. as_str ()) 650. or_else ( || response[ "error" ][ "message" ]. as_str ()) 651. unwrap_or ( "Inspection failed" ) 652. to_owned () 653} 654 655# [ cfg ( test )] 656mod tests{ 657use super ::{ is_incomplete, prepare_expression, prepare_imports}; 658 659# [ test ] 660fn multiline_completeness_uses_the_javascript_parser () { 661assert! ( is_incomplete ( "function greet() {\n" )); 662assert! ( is_incomplete ( "const value = {\n answer: 42,\n" )); 663assert! ( !is_incomplete ( "const value = 42;\n" )); 664assert! ( !is_incomplete ( "const = 42;\n" )); 665assert! ( is_incomplete ( "const value =\n" )); 666assert! ( is_incomplete ( "{\n answer: 42,\n" )); 667assert! ( is_incomplete ( "`brace {\n" )); 668assert! ( is_incomplete ( "/* comment\n" )); 669assert! ( !is_incomplete ( "'broken\n" )); 670assert! ( !is_incomplete ( "/broken\n" )); 671assert! ( is_incomplete ( "import {\n inspect as show,\n" )); 672assert! ( is_incomplete ( "import { inspect } from\n" )); 673assert! ( !is_incomplete ( "import { inspect } from 'avril:std'\n" )); 674} 675 676# [ test ] 677fn imports_preserve_all_javascript_line_terminators () { 678for newlinein [ "\n" , "\r" , "\r\n" , "\u{2028}" , "\u{2029}" ] { 679let source =format! ( 680"import {{{newline}inspect{newline}}} from 'avril:std';{newline}throw Error('original line');" 681); 682let prepared =prepare_imports ( & source); 683let original_line = source 684. split ( newline) 685. position ( |line| line. contains ( "throw Error" )) 686. unwrap (); 687let prepared_line = prepared 688. split ( newline) 689. position ( |line| line. contains ( "throw Error" )) 690. unwrap (); 691assert_eq! ( prepared_line, original_line, "{newline:?}" ); 692} 693} 694 695# [ test ] 696fn object_literals_are_evaluated_as_expressions () { 697assert_eq! ( prepare_expression ( "{ answer: 42 }\n" ), "({ answer: 42 }\n)" ); 698assert_eq! ( 699prepare_expression ( "/* comment */ { answer: 42 }\n" ), 700"(/* comment */ { answer: 42 }\n)" 701); 702assert_eq! ( prepare_expression ( "if (true) {}\n" ), "if (true) {}\n" ); 703} 704}