1#![deny(clippy::all)]
2#![allow(dead_code)]
3#![recursion_limit = "256"]
4
5use std::{path::PathBuf, sync::Arc};
6
7use preset_env_base::query::targets_to_versions;
8pub use preset_env_base::{query::Targets, version::Version, BrowserData, Versions};
9use rustc_hash::FxHashSet;
10use serde::Deserialize;
11use swc_atoms::{atom, Atom};
12use swc_common::{comments::Comments, pass::Optional, FromVariant, Mark, SyntaxContext, DUMMY_SP};
13use swc_ecma_ast::*;
14use swc_ecma_transforms::{
15 compat::{
16 bugfixes,
17 class_fields_use_set::class_fields_use_set,
18 es2015::{self, generator::generator},
19 es2016, es2017, es2018, es2019, es2020, es2022, es3,
20 regexp::{self, regexp},
21 },
22 Assumptions,
23};
24use swc_ecma_utils::{prepend_stmts, ExprFactory};
25use swc_ecma_visit::{visit_mut_pass, VisitMut, VisitMutWith, VisitWith};
26
27pub use self::transform_data::Feature;
28
29#[macro_use]
30mod util;
31mod corejs2;
32mod corejs3;
33mod node_colon_prefix_strip;
34mod regenerator;
35mod transform_data;
36
37pub trait Caniuse {
38 fn caniuse(&self, feature: Feature) -> bool;
39}
40
41fn transform_internal<C>(
42 unresolved_mark: Mark,
43 comments: Option<C>,
44 assumptions: Assumptions,
45 loose: bool,
46 dynamic_import: bool,
47 debug: bool,
48 caniuse: impl (Fn(Feature) -> bool),
49) -> impl Pass
50where
51 C: Comments + Clone,
52{
53 let pass = noop_pass();
54
55 macro_rules! add {
56 ($prev:expr, $feature:ident, $pass:expr) => {{
57 add!($prev, $feature, $pass, false)
58 }};
59 ($prev:expr, $feature:ident, $pass:expr, $default:expr) => {{
60 let f = transform_data::Feature::$feature;
61
62 let enable = !caniuse(f);
63
64 if debug {
65 println!("{}: {:?}", f.as_str(), enable);
66 }
67 ($prev, Optional::new($pass, enable))
68 }};
69 }
70
71 macro_rules! add_compiler {
72 ($($feature:ident)|*) => {{
73 let mut feature = swc_ecma_compiler::Features::empty();
74 $(
75 {
76 let f = transform_data::Feature::$feature;
77 let enable = !caniuse(f);
78
79 if debug {
80 println!("{}: {:?}", f.as_str(), enable);
81 }
82
83 if enable {
84 feature |= swc_ecma_compiler::Features::from(f);
85 }
86 }
87 )*
88 feature
89 }};
90 (| $($feature:ident)|*) => {{
91 add_compiler!($($feature)|*)
92 }};
93 ($prev:expr, $($feature:ident)|*) => {{
94 let feature = add_compiler!($($feature)|*);
95 (
96 $prev,
97 swc_ecma_compiler::Compiler::new(swc_ecma_compiler::Config {
98 includes: feature,
99 assumptions,
100 ..Default::default()
101 }),
102 )
103 }};
104 ($prev:expr, | $($feature:ident)|*) => {{
105 add_compiler!($prev, $($feature)|*)
106 }};
107 }
108
109 let pass = (
110 pass,
111 Optional::new(
112 class_fields_use_set(assumptions.pure_getters),
113 assumptions.set_public_class_fields,
114 ),
115 );
116
117 let pass = {
118 let enable_dot_all_regex = !caniuse(Feature::DotAllRegex);
119 let enable_named_capturing_groups_regex = !caniuse(Feature::NamedCapturingGroupsRegex);
120 let enable_sticky_regex = !caniuse(Feature::StickyRegex);
121 let enable_unicode_property_regex = !caniuse(Feature::UnicodePropertyRegex);
122 let enable_unicode_regex = !caniuse(Feature::UnicodeRegex);
123 let enable_unicode_sets_regex = !caniuse(Feature::UnicodeSetsRegex);
124
125 let enable = enable_dot_all_regex
126 || enable_named_capturing_groups_regex
127 || enable_sticky_regex
128 || enable_unicode_property_regex
129 || enable_unicode_regex;
130
131 (
132 pass,
133 Optional::new(
134 regexp(regexp::Config {
135 dot_all_regex: enable_dot_all_regex,
136 has_indices: false,
138 lookbehind_assertion: false,
140 named_capturing_groups_regex: enable_named_capturing_groups_regex,
141 sticky_regex: enable_sticky_regex,
142 unicode_property_regex: enable_unicode_property_regex,
143 unicode_regex: enable_unicode_regex,
144 unicode_sets_regex: enable_unicode_sets_regex,
145 }),
146 enable,
147 ),
148 )
149 };
150
151 let pass = add_compiler!(pass, ClassStaticBlock);
158 let pass = add!(
159 pass,
160 ClassProperties,
161 es2022::class_properties(
162 es2022::class_properties::Config {
163 private_as_properties: loose || assumptions.private_fields_as_properties,
164 set_public_fields: loose || assumptions.set_public_class_fields,
165 constant_super: loose || assumptions.constant_super,
166 no_document_all: loose || assumptions.no_document_all,
167 pure_getter: loose || assumptions.pure_getters,
168 },
169 unresolved_mark
170 )
171 );
172
173 #[rustfmt::skip]
174 let pass = add_compiler!(
175 pass,
176 | PrivatePropertyInObject
177 | LogicalAssignmentOperators
178 | ExportNamespaceFrom
179 );
180
181 let pass = add!(
183 pass,
184 NullishCoalescing,
185 es2020::nullish_coalescing(es2020::nullish_coalescing::Config {
186 no_document_all: loose || assumptions.no_document_all
187 })
188 );
189
190 let pass = add!(
191 pass,
192 OptionalChaining,
193 es2020::optional_chaining(
194 es2020::optional_chaining::Config {
195 no_document_all: loose || assumptions.no_document_all,
196 pure_getter: loose || assumptions.pure_getters
197 },
198 unresolved_mark
199 )
200 );
201
202 let pass = add!(pass, OptionalCatchBinding, es2019::optional_catch_binding());
204
205 let pass = add!(
207 pass,
208 ObjectRestSpread,
209 es2018::object_rest_spread(es2018::object_rest_spread::Config {
210 no_symbol: loose || assumptions.object_rest_no_symbols,
211 set_property: loose || assumptions.set_spread_properties,
212 pure_getters: loose || assumptions.pure_getters
213 })
214 );
215
216 let pass = add!(
218 pass,
219 AsyncToGenerator,
220 es2017::async_to_generator(
221 es2017::async_to_generator::Config {
222 ignore_function_length: loose || assumptions.ignore_function_length,
223 },
224 unresolved_mark
225 )
226 );
227
228 let pass = add!(pass, ExponentiationOperator, es2016::exponentiation());
230
231 let pass = add!(pass, BlockScopedFunctions, es2015::block_scoped_functions());
233 let pass = add!(
234 pass,
235 TemplateLiterals,
236 es2015::template_literal(es2015::template_literal::Config {
237 ignore_to_primitive: loose || assumptions.ignore_to_primitive_hint,
238 mutable_template: loose || assumptions.mutable_template_object
239 }),
240 true
241 );
242 let pass = add!(
243 pass,
244 Classes,
245 es2015::classes(es2015::classes::Config {
246 constant_super: loose || assumptions.constant_super,
247 no_class_calls: loose || assumptions.no_class_calls,
248 set_class_methods: loose || assumptions.set_class_methods,
249 super_is_callable_constructor: loose || assumptions.super_is_callable_constructor,
250 })
251 );
252 let pass = add!(pass, NewTarget, es2015::new_target(), true);
253 let pass = add!(
254 pass,
255 Spread,
256 es2015::spread(es2015::spread::Config { loose }),
257 true
258 );
259 let pass = add!(pass, ObjectSuper, es2015::object_super());
260 let pass = add!(pass, ShorthandProperties, es2015::shorthand());
261 let pass = add!(pass, FunctionName, es2015::function_name());
262 let pass = add!(
263 pass,
264 ForOf,
265 es2015::for_of(es2015::for_of::Config {
266 loose,
267 assume_array: loose || assumptions.iterable_is_array
268 }),
269 true
270 );
271 let pass = add!(
272 pass,
273 Parameters,
274 es2015::parameters(
275 es2015::parameters::Config {
276 ignore_function_length: loose || assumptions.ignore_function_length
277 },
278 unresolved_mark
279 )
280 );
281 let pass = add!(pass, ArrowFunctions, es2015::arrow(unresolved_mark));
282 let pass = add!(pass, DuplicateKeys, es2015::duplicate_keys());
283 let pass = add!(pass, StickyRegex, es2015::sticky_regex());
284 let pass = add!(pass, TypeOfSymbol, es2015::instance_of());
285 let pass = add!(pass, TypeOfSymbol, es2015::typeof_symbol());
286 let pass = add!(
287 pass,
288 ComputedProperties,
289 es2015::computed_properties(es2015::computed_props::Config { loose }),
290 true
291 );
292 let pass = add!(
293 pass,
294 Destructuring,
295 es2015::destructuring(es2015::destructuring::Config { loose }),
296 true
297 );
298 let pass = add!(
299 pass,
300 BlockScoping,
301 es2015::block_scoping(unresolved_mark),
302 true
303 );
304 let pass = add!(
305 pass,
306 Regenerator,
307 generator(unresolved_mark, comments),
308 true
309 );
310
311 let pass = add!(pass, PropertyLiterals, es3::property_literals());
323 let pass = add!(
324 pass,
325 MemberExpressionLiterals,
326 es3::member_expression_literals()
327 );
328 let pass = add!(pass, ReservedWords, es3::reserved_words(dynamic_import));
329
330 let pass = add!(pass, BugfixEdgeDefaultParam, bugfixes::edge_default_param());
332 let pass = add!(
333 pass,
334 BugfixAsyncArrowsInClass,
335 bugfixes::async_arrows_in_class(unresolved_mark)
336 );
337 let pass = add!(
338 pass,
339 BugfixTaggedTemplateCaching,
340 bugfixes::template_literal_caching()
341 );
342
343 add!(
344 pass,
345 BugfixSafariIdDestructuringCollisionInFunctionExpression,
346 bugfixes::safari_id_destructuring_collision_in_function_expression()
347 )
348}
349
350pub fn transform_from_env<C>(
351 unresolved_mark: Mark,
352 comments: Option<C>,
353 env_config: EnvConfig,
354 assumptions: Assumptions,
355) -> impl Pass
356where
357 C: Comments + Clone,
358{
359 let pass = Optional::new(
360 node_colon_prefix_strip::strip_node_colon_prefix(unresolved_mark),
361 env_config.feature_config.targets.node.is_some_and(|v| {
362 v < Version {
364 major: 14,
365 minor: 18,
366 patch: 0,
367 } || v.major == 15
368 }),
369 );
370
371 let pass = (
372 pass,
373 transform_internal(
374 unresolved_mark,
375 comments,
376 assumptions,
377 env_config.config.loose,
378 env_config.config.dynamic_import,
379 env_config.config.debug,
380 move |f| env_config.feature_config.caniuse(f),
381 ),
382 );
383
384 if env_config.config.debug {
385 println!("Targets: {:?}", &env_config.core_js_config.targets);
386 }
387
388 (
389 pass,
390 visit_mut_pass(Polyfills {
391 mode: env_config.config.mode,
392 regenerator: false,
393 corejs: env_config.config.core_js.unwrap_or(Version {
394 major: 3,
395 minor: 0,
396 patch: 0,
397 }),
398 shipped_proposals: env_config.config.shipped_proposals,
399 targets: env_config.core_js_config.targets,
400 includes: env_config.core_js_config.included_modules,
401 excludes: env_config.core_js_config.excluded_modules,
402 unresolved_mark,
403 }),
404 )
405}
406
407pub fn transform_from_es_version<C>(
408 unresolved_mark: Mark,
409 comments: Option<C>,
410 es_version: EsVersion,
411 assumptions: Assumptions,
412 loose: bool,
413) -> impl Pass
414where
415 C: Comments + Clone,
416{
417 transform_internal(
418 unresolved_mark,
419 comments,
420 assumptions,
421 loose,
422 true,
423 false,
424 move |f| es_version.caniuse(f),
425 )
426}
427
428#[derive(Debug)]
429struct Polyfills {
430 mode: Option<Mode>,
431 targets: Arc<Versions>,
432 shipped_proposals: bool,
433 corejs: Version,
434 regenerator: bool,
435 includes: FxHashSet<String>,
436 excludes: FxHashSet<String>,
437 unresolved_mark: Mark,
438}
439impl Polyfills {
440 fn collect<T>(&mut self, m: &mut T) -> Vec<Atom>
441 where
442 T: VisitWith<corejs2::UsageVisitor>
443 + VisitWith<corejs3::UsageVisitor>
444 + VisitMutWith<corejs2::Entry>
445 + VisitMutWith<corejs3::Entry>,
446 {
447 let required = match self.mode {
448 None => Default::default(),
449 Some(Mode::Usage) => {
450 let mut r = match self.corejs {
451 Version { major: 2, .. } => {
452 let mut v = corejs2::UsageVisitor::new(self.targets.clone());
453 m.visit_with(&mut v);
454
455 v.required
456 }
457 Version { major: 3, .. } => {
458 let mut v = corejs3::UsageVisitor::new(
459 self.targets.clone(),
460 self.shipped_proposals,
461 self.corejs,
462 );
463 m.visit_with(&mut v);
464 v.required
465 }
466
467 _ => unimplemented!("corejs version other than 2 / 3"),
468 };
469
470 if regenerator::is_required(m) {
471 r.insert("regenerator-runtime/runtime.js");
472 }
473
474 r
475 }
476 Some(Mode::Entry) => match self.corejs {
477 Version { major: 2, .. } => {
478 let mut v = corejs2::Entry::new(self.targets.clone(), self.regenerator);
479 m.visit_mut_with(&mut v);
480 v.imports
481 }
482
483 Version { major: 3, .. } => {
484 let mut v =
485 corejs3::Entry::new(self.targets.clone(), self.corejs, !self.regenerator);
486 m.visit_mut_with(&mut v);
487 v.imports
488 }
489
490 _ => unimplemented!("corejs version other than 2 / 3"),
491 },
492 };
493 required
494 .iter()
495 .filter(|s| {
496 !s.starts_with("esnext") || !required.contains(&s.replace("esnext", "es").as_str())
497 })
498 .filter(|s| !self.excludes.contains(&***s))
499 .map(|s| -> Atom {
500 if *s != "regenerator-runtime/runtime.js" {
501 format!("core-js/modules/{s}.js").into()
502 } else {
503 "regenerator-runtime/runtime.js".to_string().into()
504 }
505 })
506 .chain(self.includes.iter().map(|s| {
507 if s != "regenerator-runtime/runtime.js" {
508 format!("core-js/modules/{s}.js").into()
509 } else {
510 "regenerator-runtime/runtime.js".to_string().into()
511 }
512 }))
513 .collect::<Vec<_>>()
514 }
515}
516impl VisitMut for Polyfills {
517 fn visit_mut_module(&mut self, m: &mut Module) {
518 let span = m.span;
519 let required = self.collect(m);
520 if cfg!(debug_assertions) {
521 let mut v = required.into_iter().collect::<Vec<_>>();
522 v.sort();
523 prepend_stmts(
524 &mut m.body,
525 v.into_iter().map(|src| {
526 ImportDecl {
527 span,
528 specifiers: Vec::new(),
529 src: Str {
530 span: DUMMY_SP,
531 raw: None,
532 value: src,
533 }
534 .into(),
535 type_only: false,
536 with: None,
537 phase: Default::default(),
538 }
539 .into()
540 }),
541 );
542 } else {
543 prepend_stmts(
544 &mut m.body,
545 required.into_iter().map(|src| {
546 ImportDecl {
547 span,
548 specifiers: Vec::new(),
549 src: Str {
550 span: DUMMY_SP,
551 raw: None,
552 value: src,
553 }
554 .into(),
555 type_only: false,
556 with: None,
557 phase: Default::default(),
558 }
559 .into()
560 }),
561 );
562 }
563
564 m.body.retain(|item| !matches!(item, ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { src, .. })) if src.span == DUMMY_SP && src.value == atom!("")));
565 }
566
567 fn visit_mut_script(&mut self, m: &mut Script) {
568 let span = m.span;
569 let required = self.collect(m);
570 if cfg!(debug_assertions) {
571 let mut v = required.into_iter().collect::<Vec<_>>();
572 v.sort();
573 prepend_stmts(
574 &mut m.body,
575 v.into_iter().map(|src| {
576 ExprStmt {
577 span: DUMMY_SP,
578 expr: CallExpr {
579 span,
580 callee: Ident {
581 ctxt: SyntaxContext::empty().apply_mark(self.unresolved_mark),
582 sym: atom!("require"),
583 ..Default::default()
584 }
585 .as_callee(),
586 args: vec![Str {
587 span: DUMMY_SP,
588 value: src,
589 raw: None,
590 }
591 .as_arg()],
592 type_args: None,
593 ..Default::default()
594 }
595 .into(),
596 }
597 .into()
598 }),
599 );
600 } else {
601 prepend_stmts(
602 &mut m.body,
603 required.into_iter().map(|src| {
604 ExprStmt {
605 span: DUMMY_SP,
606 expr: CallExpr {
607 span,
608 callee: Ident {
609 ctxt: SyntaxContext::empty().apply_mark(self.unresolved_mark),
610 sym: atom!("require"),
611 ..Default::default()
612 }
613 .as_callee(),
614 args: vec![Str {
615 span: DUMMY_SP,
616 value: src,
617 raw: None,
618 }
619 .as_arg()],
620 ..Default::default()
621 }
622 .into(),
623 }
624 .into()
625 }),
626 );
627 }
628 }
629}
630
631#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
632pub enum Mode {
633 #[serde(rename = "usage")]
634 Usage,
635 #[serde(rename = "entry")]
636 Entry,
637}
638
639#[derive(Debug, Clone, Deserialize, Default)]
640#[serde(rename_all = "camelCase")]
641pub struct Config {
642 #[serde(default)]
643 pub mode: Option<Mode>,
644
645 #[serde(default)]
646 pub debug: bool,
647
648 #[serde(default)]
649 pub dynamic_import: bool,
650
651 #[serde(default)]
652 pub loose: bool,
653
654 #[serde(default)]
659 pub skip: Vec<Atom>,
660
661 #[serde(default)]
662 pub include: Vec<FeatureOrModule>,
663
664 #[serde(default)]
665 pub exclude: Vec<FeatureOrModule>,
666
667 #[serde(default)]
669 pub core_js: Option<Version>,
670
671 #[serde(default)]
672 pub targets: Option<Targets>,
673
674 #[serde(default)]
675 pub path: Option<PathBuf>,
676
677 #[serde(default)]
678 pub shipped_proposals: bool,
679
680 #[serde(default)]
681 pub force_all_transforms: bool,
682
683 #[serde(default)]
684 pub bugfixes: bool,
685}
686
687#[derive(Debug, Clone, Default)]
688pub struct FeatureConfig {
689 targets: Arc<Versions>,
690 include: Vec<Feature>,
691 exclude: Vec<Feature>,
692 is_any_target: bool,
693 force_all_transforms: bool,
694 bugfixes: bool,
695}
696
697struct CoreJSConfig {
698 targets: Arc<Versions>,
699 included_modules: FxHashSet<String>,
700 excluded_modules: FxHashSet<String>,
701}
702
703pub struct EnvConfig {
704 config: Config,
705 feature_config: Arc<FeatureConfig>,
706 core_js_config: CoreJSConfig,
707}
708
709impl From<Config> for EnvConfig {
710 fn from(mut config: Config) -> Self {
711 let targets = targets_to_versions(config.targets.take(), config.path.take())
712 .expect("failed to parse targets");
713 let is_any_target = targets.is_any_target();
714
715 let (include, included_modules) = FeatureOrModule::split(config.include.clone());
716 let (exclude, excluded_modules) = FeatureOrModule::split(config.exclude.clone());
717
718 let feature_config = FeatureConfig {
719 targets: Arc::clone(&targets),
720 include,
721 exclude,
722 is_any_target,
723 force_all_transforms: config.force_all_transforms,
724 bugfixes: config.bugfixes,
725 };
726 let core_js_config = CoreJSConfig {
727 targets: Arc::clone(&targets),
728 included_modules,
729 excluded_modules,
730 };
731 Self {
732 config,
733 feature_config: Arc::new(feature_config),
734 core_js_config,
735 }
736 }
737}
738
739impl EnvConfig {
740 pub fn get_feature_config(&self) -> Arc<FeatureConfig> {
741 Arc::clone(&self.feature_config)
742 }
743}
744
745impl Caniuse for FeatureConfig {
746 fn caniuse(&self, feature: Feature) -> bool {
747 if self.exclude.contains(&feature) {
748 return true;
749 }
750
751 if self.force_all_transforms || self.is_any_target {
752 return false;
753 }
754
755 if self.include.contains(&feature) {
756 return false;
757 }
758
759 !feature.should_enable(&self.targets, self.bugfixes, false)
760 }
761}
762
763#[derive(Debug, Clone, Deserialize, FromVariant)]
764#[serde(untagged)]
765pub enum FeatureOrModule {
766 Feature(Feature),
767 CoreJsModule(String),
768}
769
770impl FeatureOrModule {
771 pub fn split(vec: Vec<FeatureOrModule>) -> (Vec<Feature>, FxHashSet<String>) {
772 let mut features: Vec<_> = Default::default();
773 let mut modules: FxHashSet<_> = Default::default();
774
775 for v in vec {
776 match v {
777 FeatureOrModule::Feature(f) => features.push(f),
778 FeatureOrModule::CoreJsModule(m) => {
779 modules.insert(m);
780 }
781 }
782 }
783
784 (features, modules)
785 }
786}
787
788impl Caniuse for EsVersion {
789 fn caniuse(&self, feature: Feature) -> bool {
790 if self == &EsVersion::EsNext {
792 return true;
793 }
794
795 match feature {
796 Feature::ClassProperties
798 | Feature::ClassStaticBlock
799 | Feature::PrivatePropertyInObject => *self >= EsVersion::Es2022,
800
801 Feature::LogicalAssignmentOperators => *self >= EsVersion::Es2021,
803
804 Feature::ExportNamespaceFrom
806 | Feature::NullishCoalescing
807 | Feature::OptionalChaining => *self >= EsVersion::Es2020,
808
809 Feature::OptionalCatchBinding => *self >= EsVersion::Es2019,
811
812 Feature::ObjectRestSpread
814 | Feature::DotAllRegex
815 | Feature::NamedCapturingGroupsRegex
816 | Feature::UnicodePropertyRegex => *self >= EsVersion::Es2018,
817
818 Feature::AsyncToGenerator => *self >= EsVersion::Es2017,
820
821 Feature::ExponentiationOperator => *self >= EsVersion::Es2016,
823
824 Feature::ArrowFunctions
826 | Feature::BlockScopedFunctions
827 | Feature::BlockScoping
828 | Feature::Classes
829 | Feature::ComputedProperties
830 | Feature::Destructuring
831 | Feature::DuplicateKeys
832 | Feature::ForOf
833 | Feature::FunctionName
834 | Feature::NewTarget
835 | Feature::ObjectSuper
836 | Feature::Parameters
837 | Feature::Regenerator
838 | Feature::ShorthandProperties
839 | Feature::Spread
840 | Feature::StickyRegex
841 | Feature::TemplateLiterals
842 | Feature::TypeOfSymbol
843 | Feature::UnicodeRegex => *self >= EsVersion::Es2015,
844
845 Feature::PropertyLiterals
847 | Feature::MemberExpressionLiterals
848 | Feature::ReservedWords => *self >= EsVersion::Es5,
849
850 Feature::BugfixAsyncArrowsInClass
852 | Feature::BugfixEdgeDefaultParam
853 | Feature::BugfixTaggedTemplateCaching
854 | Feature::BugfixSafariIdDestructuringCollisionInFunctionExpression
855 | Feature::BugfixTransformEdgeFunctionName
856 | Feature::BugfixTransformSafariBlockShadowing
857 | Feature::BugfixTransformSafariForShadowing
858 | Feature::BugfixTransformV8SpreadParametersInOptionalChaining
859 | Feature::BugfixTransformV8StaticClassFieldsRedefineReadonly
860 | Feature::BugfixTransformFirefoxClassInComputedClassKey
861 | Feature::BugfixTransformSafariClassFieldInitializerScope => true,
862
863 _ => true,
864 }
865 }
866}