Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / javascript.rs
32 kB 986 lines
1mod decision; 2mod expression; 3mod import; 4#[cfg(test)] 5mod tests; 6mod typescript; 7 8use num_bigint::BigInt; 9use num_traits::ToPrimitive; 10 11use crate::build::Target; 12use crate::build::package_compiler::StdlibPackage; 13use crate::codegen::TypeScriptDeclarations; 14use crate::type_::PRELUDE_MODULE_NAME; 15use crate::{ 16 ast::{CustomType, Function, Import, ModuleConstant, TypeAlias, *}, 17 docvec, 18 line_numbers::LineNumbers, 19 pretty::*, 20}; 21use camino::Utf8Path; 22use ecow::{EcoString, eco_format}; 23use expression::Context; 24use itertools::Itertools; 25 26use self::import::{Imports, Member}; 27 28const INDENT: isize = 2; 29 30pub const PRELUDE: &str = include_str!("../templates/prelude.mjs"); 31pub const PRELUDE_TS_DEF: &str = include_str!("../templates/prelude.d.mts"); 32 33#[derive(Debug, Clone, Copy, PartialEq, Eq)] 34pub enum JavaScriptCodegenTarget { 35 JavaScript, 36 TypeScriptDeclarations, 37} 38 39#[derive(Debug)] 40pub struct Generator<'a> { 41 line_numbers: &'a LineNumbers, 42 module: &'a TypedModule, 43 tracker: UsageTracker, 44 module_scope: im::HashMap<EcoString, usize>, 45 current_module_name_segments_count: usize, 46 typescript: TypeScriptDeclarations, 47 stdlib_package: StdlibPackage, 48 /// Relative path to the module, surrounded in `"`s to make it a string, and with `\`s escaped 49 /// to `\\`. 50 src_path: EcoString, 51} 52 53impl<'a> Generator<'a> { 54 pub fn new(config: ModuleConfig<'a>) -> Self { 55 let ModuleConfig { 56 typescript, 57 stdlib_package, 58 module, 59 line_numbers, 60 src: _, 61 path: _, 62 project_root, 63 } = config; 64 let current_module_name_segments_count = module.name.split('/').count(); 65 66 let src_path = &module.type_info.src_path; 67 let src_path = src_path 68 .strip_prefix(project_root) 69 .unwrap_or(src_path) 70 .as_str(); 71 let src_path = eco_format!("\"{src_path}\"").replace("\\", "\\\\"); 72 73 Self { 74 current_module_name_segments_count, 75 line_numbers, 76 module, 77 src_path, 78 tracker: UsageTracker::default(), 79 module_scope: Default::default(), 80 typescript, 81 stdlib_package, 82 } 83 } 84 85 fn type_reference(&self) -> Document<'a> { 86 if self.typescript == TypeScriptDeclarations::None { 87 return nil(); 88 } 89 90 // Get the name of the module relative the directory (similar to basename) 91 let module = self 92 .module 93 .name 94 .as_str() 95 .split('/') 96 .next_back() 97 .expect("JavaScript generator could not identify imported module name."); 98 99 docvec!["/// <reference types=\"./", module, ".d.mts\" />", line()] 100 } 101 102 pub fn compile(&mut self) -> Document<'a> { 103 // Determine what JavaScript imports we need to generate 104 let mut imports = self.collect_imports(); 105 106 // Determine what names are defined in the module scope so we know to 107 // rename any variables that are defined within functions using the same 108 // names. 109 self.register_module_definitions_in_scope(); 110 111 // Generate JavaScript code for each statement 112 let statements = self.collect_definitions().into_iter().chain( 113 self.module 114 .definitions 115 .iter() 116 .flat_map(|definition| self.definition(definition)), 117 ); 118 119 // Two lines between each statement 120 let mut statements = Itertools::intersperse(statements, lines(2)).collect_vec(); 121 122 // Import any prelude functions that have been used 123 124 if self.tracker.ok_used { 125 self.register_prelude_usage(&mut imports, "Ok", None); 126 }; 127 128 if self.tracker.error_used { 129 self.register_prelude_usage(&mut imports, "Error", None); 130 }; 131 132 if self.tracker.list_used { 133 self.register_prelude_usage(&mut imports, "toList", None); 134 }; 135 136 if self.tracker.list_empty_class_used || self.tracker.echo_used { 137 self.register_prelude_usage(&mut imports, "Empty", Some("$Empty")); 138 }; 139 140 if self.tracker.list_non_empty_class_used || self.tracker.echo_used { 141 self.register_prelude_usage(&mut imports, "NonEmpty", Some("$NonEmpty")); 142 }; 143 144 if self.tracker.prepend_used { 145 self.register_prelude_usage(&mut imports, "prepend", Some("listPrepend")); 146 }; 147 148 if self.tracker.custom_type_used || self.tracker.echo_used { 149 self.register_prelude_usage(&mut imports, "CustomType", Some("$CustomType")); 150 }; 151 152 if self.tracker.make_error_used { 153 self.register_prelude_usage(&mut imports, "makeError", None); 154 }; 155 156 if self.tracker.int_remainder_used { 157 self.register_prelude_usage(&mut imports, "remainderInt", None); 158 }; 159 160 if self.tracker.float_division_used { 161 self.register_prelude_usage(&mut imports, "divideFloat", None); 162 }; 163 164 if self.tracker.int_division_used { 165 self.register_prelude_usage(&mut imports, "divideInt", None); 166 }; 167 168 if self.tracker.object_equality_used { 169 self.register_prelude_usage(&mut imports, "isEqual", None); 170 }; 171 172 if self.tracker.bit_array_literal_used { 173 self.register_prelude_usage(&mut imports, "toBitArray", None); 174 } 175 176 if self.tracker.bit_array_slice_used || self.tracker.echo_used { 177 self.register_prelude_usage(&mut imports, "bitArraySlice", None); 178 } 179 180 if self.tracker.bit_array_slice_to_float_used { 181 self.register_prelude_usage(&mut imports, "bitArraySliceToFloat", None); 182 } 183 184 if self.tracker.bit_array_slice_to_int_used || self.tracker.echo_used { 185 self.register_prelude_usage(&mut imports, "bitArraySliceToInt", None); 186 } 187 188 if self.tracker.sized_integer_segment_used { 189 self.register_prelude_usage(&mut imports, "sizedInt", None); 190 } 191 192 if self.tracker.string_bit_array_segment_used { 193 self.register_prelude_usage(&mut imports, "stringBits", None); 194 } 195 196 if self.tracker.string_utf16_bit_array_segment_used { 197 self.register_prelude_usage(&mut imports, "stringToUtf16", None); 198 } 199 200 if self.tracker.string_utf32_bit_array_segment_used { 201 self.register_prelude_usage(&mut imports, "stringToUtf32", None); 202 } 203 204 if self.tracker.codepoint_bit_array_segment_used { 205 self.register_prelude_usage(&mut imports, "codepointBits", None); 206 } 207 208 if self.tracker.codepoint_utf16_bit_array_segment_used { 209 self.register_prelude_usage(&mut imports, "codepointToUtf16", None); 210 } 211 212 if self.tracker.codepoint_utf32_bit_array_segment_used { 213 self.register_prelude_usage(&mut imports, "codepointToUtf32", None); 214 } 215 216 if self.tracker.float_bit_array_segment_used { 217 self.register_prelude_usage(&mut imports, "sizedFloat", None); 218 } 219 220 let echo_definition = self.echo_definition(&mut imports); 221 let type_reference = self.type_reference(); 222 let filepath_definition = self.filepath_definition(); 223 224 // Put it all together 225 226 if imports.is_empty() && statements.is_empty() { 227 docvec![ 228 type_reference, 229 filepath_definition, 230 "export {}", 231 line(), 232 echo_definition 233 ] 234 } else if imports.is_empty() { 235 statements.push(line()); 236 docvec![ 237 type_reference, 238 filepath_definition, 239 statements, 240 echo_definition 241 ] 242 } else if statements.is_empty() { 243 docvec![ 244 type_reference, 245 imports.into_doc(JavaScriptCodegenTarget::JavaScript), 246 filepath_definition, 247 echo_definition, 248 ] 249 } else { 250 docvec![ 251 type_reference, 252 imports.into_doc(JavaScriptCodegenTarget::JavaScript), 253 line(), 254 filepath_definition, 255 statements, 256 line(), 257 echo_definition 258 ] 259 } 260 } 261 262 fn echo_definition(&mut self, imports: &mut Imports<'a>) -> Document<'a> { 263 if !self.tracker.echo_used { 264 return nil(); 265 } 266 267 if StdlibPackage::Present == self.stdlib_package { 268 let value = Some(( 269 AssignName::Variable("stdlib$dict".into()), 270 SrcSpan::default(), 271 )); 272 self.register_import(imports, "gleam_stdlib", "dict", &value, &[]); 273 } 274 self.register_prelude_usage(imports, "BitArray", Some("$BitArray")); 275 self.register_prelude_usage(imports, "List", Some("$List")); 276 self.register_prelude_usage(imports, "UtfCodepoint", Some("$UtfCodepoint")); 277 docvec![line(), std::include_str!("../templates/echo.mjs"), line()] 278 } 279 280 fn register_prelude_usage( 281 &self, 282 imports: &mut Imports<'a>, 283 name: &'static str, 284 alias: Option<&'static str>, 285 ) { 286 let path = self.import_path(&self.module.type_info.package, PRELUDE_MODULE_NAME); 287 let member = Member { 288 name: name.to_doc(), 289 alias: alias.map(|a| a.to_doc()), 290 }; 291 imports.register_module(path, [], [member]); 292 } 293 294 pub fn definition(&mut self, definition: &'a TypedDefinition) -> Option<Document<'a>> { 295 match definition { 296 Definition::TypeAlias(TypeAlias { .. }) => None, 297 298 // Handled in collect_imports 299 Definition::Import(Import { .. }) => None, 300 301 // Handled in collect_definitions 302 Definition::CustomType(CustomType { .. }) => None, 303 304 // If a definition is unused then we don't need to generate code for it 305 Definition::ModuleConstant(ModuleConstant { location, .. }) 306 | Definition::Function(Function { location, .. }) 307 if self 308 .module 309 .unused_definition_positions 310 .contains(&location.start) => 311 { 312 None 313 } 314 315 Definition::ModuleConstant(ModuleConstant { 316 publicity, 317 name, 318 value, 319 documentation, 320 .. 321 }) => Some(self.module_constant(*publicity, name, value, documentation)), 322 323 Definition::Function(function) => { 324 // If there's an external JavaScript implementation then it will be imported, 325 // so we don't need to generate a function definition. 326 if function.external_javascript.is_some() { 327 return None; 328 } 329 330 // If the function does not support JavaScript then we don't need to generate 331 // a function definition. 332 if !function.implementations.supports(Target::JavaScript) { 333 return None; 334 } 335 336 Some(self.module_function(function)) 337 } 338 } 339 } 340 341 fn custom_type_definition( 342 &mut self, 343 constructors: &'a [TypedRecordConstructor], 344 publicity: Publicity, 345 opaque: bool, 346 ) -> Vec<Document<'a>> { 347 // If there's no constructors then there's nothing to do here. 348 if constructors.is_empty() { 349 return vec![]; 350 } 351 352 self.tracker.custom_type_used = true; 353 constructors 354 .iter() 355 .map(|constructor| self.record_definition(constructor, publicity, opaque)) 356 .collect() 357 } 358 359 fn record_definition( 360 &self, 361 constructor: &'a TypedRecordConstructor, 362 publicity: Publicity, 363 opaque: bool, 364 ) -> Document<'a> { 365 fn parameter((i, arg): (usize, &TypedRecordConstructorArg)) -> Document<'_> { 366 arg.label 367 .as_ref() 368 .map(|(_, s)| maybe_escape_identifier(s)) 369 .unwrap_or_else(|| eco_format!("${i}")) 370 .to_doc() 371 } 372 373 let doc = if let Some((_, documentation)) = &constructor.documentation { 374 jsdoc_comment(documentation, publicity).append(line()) 375 } else { 376 nil() 377 }; 378 379 let head = if publicity.is_private() || opaque { 380 "class " 381 } else { 382 "export class " 383 }; 384 let head = docvec![head, &constructor.name, " extends $CustomType {"]; 385 386 if constructor.arguments.is_empty() { 387 return head.append("}"); 388 }; 389 390 let parameters = join( 391 constructor.arguments.iter().enumerate().map(parameter), 392 break_(",", ", "), 393 ); 394 395 let constructor_body = join( 396 constructor.arguments.iter().enumerate().map(|(i, arg)| { 397 let var = parameter((i, arg)); 398 match &arg.label { 399 None => docvec!["this[", i, "] = ", var, ";"], 400 Some((_, name)) => { 401 docvec!["this.", maybe_escape_property(name), " = ", var, ";"] 402 } 403 } 404 }), 405 line(), 406 ); 407 408 let class_body = docvec![ 409 line(), 410 "constructor(", 411 parameters, 412 ") {", 413 docvec![line(), "super();", line(), constructor_body].nest(INDENT), 414 line(), 415 "}", 416 ] 417 .nest(INDENT); 418 419 docvec![doc, head, class_body, line(), "}"] 420 } 421 422 fn collect_definitions(&mut self) -> Vec<Document<'a>> { 423 self.module 424 .definitions 425 .iter() 426 .flat_map(|definition| match definition { 427 // If a custom type is unused then we don't need to generate code for it 428 Definition::CustomType(CustomType { location, .. }) 429 if self 430 .module 431 .unused_definition_positions 432 .contains(&location.start) => 433 { 434 vec![] 435 } 436 437 Definition::CustomType(CustomType { 438 publicity, 439 constructors, 440 opaque, 441 .. 442 }) => self.custom_type_definition(constructors, *publicity, *opaque), 443 444 Definition::Function(Function { .. }) 445 | Definition::TypeAlias(TypeAlias { .. }) 446 | Definition::Import(Import { .. }) 447 | Definition::ModuleConstant(ModuleConstant { .. }) => vec![], 448 }) 449 .collect() 450 } 451 452 fn collect_imports(&mut self) -> Imports<'a> { 453 let mut imports = Imports::new(); 454 455 for definition in &self.module.definitions { 456 match definition { 457 Definition::Import(Import { 458 module, 459 as_name, 460 unqualified_values: unqualified, 461 package, 462 .. 463 }) => { 464 self.register_import(&mut imports, package, module, as_name, unqualified); 465 } 466 467 Definition::Function(Function { 468 name: Some((_, name)), 469 publicity, 470 external_javascript: Some((module, function, _location)), 471 .. 472 }) => { 473 self.register_external_function( 474 &mut imports, 475 *publicity, 476 name, 477 module, 478 function, 479 ); 480 } 481 482 Definition::Function(Function { .. }) 483 | Definition::TypeAlias(TypeAlias { .. }) 484 | Definition::CustomType(CustomType { .. }) 485 | Definition::ModuleConstant(ModuleConstant { .. }) => (), 486 } 487 } 488 489 imports 490 } 491 492 fn import_path(&self, package: &'a str, module: &'a str) -> EcoString { 493 // TODO: strip shared prefixed between current module and imported 494 // module to avoid descending and climbing back out again 495 if package == self.module.type_info.package || package.is_empty() { 496 // Same package 497 match self.current_module_name_segments_count { 498 1 => eco_format!("./{module}.mjs"), 499 _ => { 500 let prefix = "../".repeat(self.current_module_name_segments_count - 1); 501 eco_format!("{prefix}{module}.mjs") 502 } 503 } 504 } else { 505 // Different package 506 let prefix = "../".repeat(self.current_module_name_segments_count); 507 eco_format!("{prefix}{package}/{module}.mjs") 508 } 509 } 510 511 fn register_import( 512 &mut self, 513 imports: &mut Imports<'a>, 514 package: &'a str, 515 module: &'a str, 516 as_name: &Option<(AssignName, SrcSpan)>, 517 unqualified: &[UnqualifiedImport], 518 ) { 519 let get_name = |module: &'a str| { 520 module 521 .split('/') 522 .next_back() 523 .expect("JavaScript generator could not identify imported module name.") 524 }; 525 526 let (discarded, module_name) = match as_name { 527 None => (false, get_name(module)), 528 Some((AssignName::Discard(_), _)) => (true, get_name(module)), 529 Some((AssignName::Variable(name), _)) => (false, name.as_str()), 530 }; 531 532 let module_name = eco_format!("${module_name}"); 533 let path = self.import_path(package, module); 534 let unqualified_imports = unqualified.iter().map(|i| { 535 let alias = i.as_name.as_ref().map(|n| { 536 self.register_in_scope(n); 537 maybe_escape_identifier(n).to_doc() 538 }); 539 let name = maybe_escape_identifier(&i.name).to_doc(); 540 Member { name, alias } 541 }); 542 543 let aliases = if discarded { vec![] } else { vec![module_name] }; 544 imports.register_module(path, aliases, unqualified_imports); 545 } 546 547 fn register_external_function( 548 &mut self, 549 imports: &mut Imports<'a>, 550 publicity: Publicity, 551 name: &'a str, 552 module: &'a str, 553 fun: &'a str, 554 ) { 555 let needs_escaping = !is_usable_js_identifier(name); 556 let member = Member { 557 name: fun.to_doc(), 558 alias: if name == fun && !needs_escaping { 559 None 560 } else if needs_escaping { 561 Some(escape_identifier(name).to_doc()) 562 } else { 563 Some(name.to_doc()) 564 }, 565 }; 566 if publicity.is_importable() { 567 imports.register_export(maybe_escape_identifier_string(name)) 568 } 569 imports.register_module(EcoString::from(module), [], [member]); 570 } 571 572 fn module_constant( 573 &mut self, 574 publicity: Publicity, 575 name: &'a EcoString, 576 value: &'a TypedConstant, 577 documentation: &'a Option<(u32, EcoString)>, 578 ) -> Document<'a> { 579 let head = if publicity.is_private() { 580 "const " 581 } else { 582 "export const " 583 }; 584 585 let mut generator = expression::Generator::new( 586 self.module.name.clone(), 587 self.src_path.clone(), 588 self.line_numbers, 589 "".into(), 590 vec![], 591 &mut self.tracker, 592 self.module_scope.clone(), 593 ); 594 595 let document = generator.constant_expression(Context::Constant, value); 596 597 let jsdoc = if let Some((_, documentation)) = documentation { 598 jsdoc_comment(documentation, publicity).append(line()) 599 } else { 600 nil() 601 }; 602 603 docvec![ 604 jsdoc, 605 head, 606 maybe_escape_identifier(name), 607 " = ", 608 document, 609 ";", 610 ] 611 } 612 613 fn register_in_scope(&mut self, name: &str) { 614 let _ = self.module_scope.insert(name.into(), 0); 615 } 616 617 fn module_function(&mut self, function: &'a TypedFunction) -> Document<'a> { 618 let (_, name) = function 619 .name 620 .as_ref() 621 .expect("A module's function must be named"); 622 let argument_names = function 623 .arguments 624 .iter() 625 .map(|arg| arg.names.get_variable_name()) 626 .collect(); 627 let mut generator = expression::Generator::new( 628 self.module.name.clone(), 629 self.src_path.clone(), 630 self.line_numbers, 631 name.clone(), 632 argument_names, 633 &mut self.tracker, 634 self.module_scope.clone(), 635 ); 636 637 let function_doc = match &function.documentation { 638 None => nil(), 639 Some((_, documentation)) => { 640 jsdoc_comment(documentation, function.publicity).append(line()) 641 } 642 }; 643 644 let head = if function.publicity.is_private() { 645 "function " 646 } else { 647 "export function " 648 }; 649 650 let body = generator.function_body(&function.body, function.arguments.as_slice()); 651 652 docvec![ 653 function_doc, 654 head, 655 maybe_escape_identifier(name.as_str()), 656 fun_arguments(function.arguments.as_slice(), generator.tail_recursion_used), 657 " {", 658 docvec![line(), body].nest(INDENT).group(), 659 line(), 660 "}", 661 ] 662 } 663 664 fn register_module_definitions_in_scope(&mut self) { 665 for definition in self.module.definitions.iter() { 666 match definition { 667 Definition::ModuleConstant(ModuleConstant { name, .. }) => { 668 self.register_in_scope(name) 669 } 670 671 Definition::Function(Function { name, .. }) => self.register_in_scope( 672 name.as_ref() 673 .map(|(_, name)| name) 674 .expect("Function in a definition must be named"), 675 ), 676 677 Definition::Import(Import { 678 unqualified_values: unqualified, 679 .. 680 }) => unqualified 681 .iter() 682 .for_each(|unq_import| self.register_in_scope(unq_import.used_name())), 683 684 Definition::TypeAlias(TypeAlias { .. }) 685 | Definition::CustomType(CustomType { .. }) => (), 686 } 687 } 688 } 689 690 fn filepath_definition(&self) -> Document<'a> { 691 if !self.tracker.make_error_used { 692 return nil(); 693 } 694 695 docvec!["const FILEPATH = ", self.src_path.clone(), ';', lines(2)] 696 } 697} 698 699fn jsdoc_comment(documentation: &EcoString, publicity: Publicity) -> Document<'_> { 700 let doc_lines = documentation 701 .trim_end() 702 .split('\n') 703 .map(|line| eco_format!(" *{line}", line = line.replace("*/", "*\\/")).to_doc()) 704 .collect_vec(); 705 706 // We start with the documentation of the function 707 let doc_body = join(doc_lines, line()); 708 let mut doc = docvec!["/**", line(), doc_body, line()]; 709 if !publicity.is_public() { 710 // If the function is not public we hide the documentation using 711 // the `@ignore` tag: https://jsdoc.app/tags-ignore 712 doc = docvec![doc, " * ", line(), " * @ignore", line()]; 713 } 714 // And finally we close the doc comment 715 docvec![doc, " */"] 716} 717 718#[derive(Debug)] 719pub struct ModuleConfig<'a> { 720 pub module: &'a TypedModule, 721 pub line_numbers: &'a LineNumbers, 722 pub src: &'a EcoString, 723 pub typescript: TypeScriptDeclarations, 724 pub stdlib_package: StdlibPackage, 725 pub path: &'a Utf8Path, 726 pub project_root: &'a Utf8Path, 727} 728 729pub fn module(config: ModuleConfig<'_>) -> String { 730 let document = Generator::new(config).compile(); 731 document.to_pretty_string(80) 732} 733 734pub fn ts_declaration(module: &TypedModule) -> String { 735 let document = typescript::TypeScriptGenerator::new(module).compile(); 736 document.to_pretty_string(80) 737} 738 739fn fun_arguments(arguments: &'_ [TypedArg], tail_recursion_used: bool) -> Document<'_> { 740 let mut discards = 0; 741 wrap_arguments( 742 arguments 743 .iter() 744 .map(|argument| match argument.get_variable_name() { 745 None => { 746 let doc = if discards == 0 { 747 "_".to_doc() 748 } else { 749 eco_format!("_{discards}").to_doc() 750 }; 751 discards += 1; 752 doc 753 } 754 Some(name) if tail_recursion_used => eco_format!("loop${name}").to_doc(), 755 Some(name) => maybe_escape_identifier(name).to_doc(), 756 }), 757 ) 758} 759 760fn wrap_arguments<'a, I>(arguments: I) -> Document<'a> 761where 762 I: IntoIterator<Item = Document<'a>>, 763{ 764 break_("", "") 765 .append(join(arguments, break_(",", ", "))) 766 .nest(INDENT) 767 .append(break_("", "")) 768 .surround("(", ")") 769 .group() 770} 771 772fn wrap_object<'a>( 773 items: impl IntoIterator<Item = (Document<'a>, Option<Document<'a>>)>, 774) -> Document<'a> { 775 let mut empty = true; 776 let fields = items.into_iter().map(|(key, value)| { 777 empty = false; 778 match value { 779 Some(value) => docvec![key, ": ", value], 780 None => key.to_doc(), 781 } 782 }); 783 let fields = join(fields, break_(",", ", ")); 784 785 if empty { 786 "{}".to_doc() 787 } else { 788 docvec![ 789 docvec!["{", break_("", " "), fields] 790 .nest(INDENT) 791 .append(break_("", " ")) 792 .group(), 793 "}" 794 ] 795 } 796} 797 798fn is_usable_js_identifier(word: &str) -> bool { 799 !matches!( 800 word, 801 // Keywords and reserved words 802 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar 803 "await" 804 | "arguments" 805 | "break" 806 | "case" 807 | "catch" 808 | "class" 809 | "const" 810 | "continue" 811 | "debugger" 812 | "default" 813 | "delete" 814 | "do" 815 | "else" 816 | "enum" 817 | "export" 818 | "extends" 819 | "eval" 820 | "false" 821 | "finally" 822 | "for" 823 | "function" 824 | "if" 825 | "implements" 826 | "import" 827 | "in" 828 | "instanceof" 829 | "interface" 830 | "let" 831 | "new" 832 | "null" 833 | "package" 834 | "private" 835 | "protected" 836 | "public" 837 | "return" 838 | "static" 839 | "super" 840 | "switch" 841 | "this" 842 | "throw" 843 | "true" 844 | "try" 845 | "typeof" 846 | "var" 847 | "void" 848 | "while" 849 | "with" 850 | "yield" 851 // `undefined` to avoid any unintentional overriding. 852 | "undefined" 853 // `then` to avoid a module that defines a `then` function being 854 // used as a `thenable` in JavaScript when the module is imported 855 // dynamically, which results in unexpected behaviour. 856 // It is rather unfortunate that we have to do this. 857 | "then" 858 ) 859} 860 861fn is_usable_js_property(label: &str) -> bool { 862 match label { 863 // `then` to avoid a custom type that defines a `then` function being 864 // used as a `thenable` in Javascript. 865 "then" 866 // `constructor` to avoid unintentional overriding of the constructor of 867 // records, leading to potential runtime crashes while using `withFields`. 868 | "constructor" 869 // `prototype` and `__proto__` to avoid unintentionally overriding the 870 // prototype chain. 871 | "prototype" | "__proto__" => false, 872 _ => true 873 } 874} 875 876fn maybe_escape_identifier_string(word: &str) -> EcoString { 877 if is_usable_js_identifier(word) { 878 EcoString::from(word) 879 } else { 880 escape_identifier(word) 881 } 882} 883 884fn escape_identifier(word: &str) -> EcoString { 885 eco_format!("{word}$") 886} 887 888fn maybe_escape_identifier(word: &str) -> EcoString { 889 if is_usable_js_identifier(word) { 890 EcoString::from(word) 891 } else { 892 escape_identifier(word) 893 } 894} 895 896fn maybe_escape_property(label: &str) -> EcoString { 897 if is_usable_js_property(label) { 898 EcoString::from(label) 899 } else { 900 escape_identifier(label) 901 } 902} 903 904#[derive(Debug, Default)] 905pub(crate) struct UsageTracker { 906 pub ok_used: bool, 907 pub list_used: bool, 908 pub list_empty_class_used: bool, 909 pub list_non_empty_class_used: bool, 910 pub prepend_used: bool, 911 pub error_used: bool, 912 pub int_remainder_used: bool, 913 pub make_error_used: bool, 914 pub custom_type_used: bool, 915 pub int_division_used: bool, 916 pub float_division_used: bool, 917 pub object_equality_used: bool, 918 pub bit_array_literal_used: bool, 919 pub bit_array_slice_used: bool, 920 pub bit_array_slice_to_float_used: bool, 921 pub bit_array_slice_to_int_used: bool, 922 pub sized_integer_segment_used: bool, 923 pub string_bit_array_segment_used: bool, 924 pub string_utf16_bit_array_segment_used: bool, 925 pub string_utf32_bit_array_segment_used: bool, 926 pub codepoint_bit_array_segment_used: bool, 927 pub codepoint_utf16_bit_array_segment_used: bool, 928 pub codepoint_utf32_bit_array_segment_used: bool, 929 pub float_bit_array_segment_used: bool, 930 pub echo_used: bool, 931} 932 933fn bool(bool: bool) -> Document<'static> { 934 match bool { 935 true => "true".to_doc(), 936 false => "false".to_doc(), 937 } 938} 939 940/// Int segments <= 48 bits wide in bit arrays are within JavaScript's safe range and are evaluated 941/// at compile time when all inputs are known. This is done for both bit array expressions and 942/// pattern matching. 943/// 944/// Int segments of any size could be evaluated at compile time, but currently aren't due to the 945/// potential for causing large generated JS for inputs such as `<<0:8192>>`. 946/// 947pub(crate) const SAFE_INT_SEGMENT_MAX_SIZE: usize = 48; 948 949/// Evaluates the value of an Int segment in a bit array into its corresponding bytes. This avoids 950/// needing to do the evaluation at runtime when all inputs are known at compile-time. 951/// 952pub(crate) fn bit_array_segment_int_value_to_bytes( 953 mut value: BigInt, 954 size: BigInt, 955 endianness: Endianness, 956) -> Vec<u8> { 957 // Clamp negative sizes to zero 958 let size = size.max(BigInt::ZERO); 959 960 // Convert size to u32. This is safe because this function isn't called with a size greater 961 // than `SAFE_INT_SEGMENT_MAX_SIZE`. 962 let size = size 963 .to_u32() 964 .expect("bit array segment size to be a valid u32"); 965 966 // Convert negative number to two's complement representation 967 if value < BigInt::ZERO { 968 let value_modulus = BigInt::from(2).pow(size); 969 value = &value_modulus + (value % &value_modulus); 970 } 971 972 // Convert value to the desired number of bytes 973 let mut bytes = vec![0u8; size as usize / 8]; 974 for byte in bytes.iter_mut() { 975 *byte = (&value % BigInt::from(256)) 976 .to_u8() 977 .expect("modulo result to be a valid u32"); 978 value /= BigInt::from(256); 979 } 980 981 if endianness.is_big() { 982 bytes.reverse(); 983 } 984 985 bytes 986}