diff --git a/packages/transformers/js/core/src/collect.rs b/packages/transformers/js/core/src/collect.rs index bf5f823ac1b..3bf6c2ddf12 100644 --- a/packages/transformers/js/core/src/collect.rs +++ b/packages/transformers/js/core/src/collect.rs @@ -1,7 +1,7 @@ use crate::id; use crate::utils::{ - match_export_name, match_export_name_ident, match_import, match_member_expr, match_property_name, - match_require, Bailout, BailoutReason, SourceLocation, + is_unresolved, match_export_name, match_export_name_ident, match_import, match_member_expr, + match_property_name, match_require, Bailout, BailoutReason, SourceLocation, }; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; @@ -49,7 +49,7 @@ pub struct Export { pub struct Collect { pub source_map: Lrc, - pub decls: HashSet, + pub unresolved_mark: Mark, pub ignore_mark: Mark, pub global_mark: Mark, pub static_cjs_exports: bool, @@ -117,7 +117,7 @@ pub struct CollectResult { impl Collect { pub fn new( source_map: Lrc, - decls: HashSet, + unresolved_mark: Mark, ignore_mark: Mark, global_mark: Mark, trace_bailouts: bool, @@ -125,7 +125,7 @@ impl Collect { ) -> Self { Collect { source_map, - decls, + unresolved_mark, ignore_mark, global_mark, is_module, @@ -629,17 +629,17 @@ impl Visit for Collect { // if exports, ensure only static member expression // if require, could be static access (handle in fold) - if match_member_expr(node, vec!["module", "exports"], &self.decls) { + if match_member_expr(node, vec!["module", "exports"], self.unresolved_mark) { self.static_cjs_exports = false; self.has_cjs_exports = true; return; } - if match_member_expr(node, vec!["module", "hot"], &self.decls) { + if match_member_expr(node, vec!["module", "hot"], self.unresolved_mark) { return; } - if match_member_expr(node, vec!["module", "require"], &self.decls) { + if match_member_expr(node, vec!["module", "require"], self.unresolved_mark) { return; } @@ -665,7 +665,7 @@ impl Visit for Collect { match &*node.obj { Expr::Member(member) => { - if match_member_expr(member, vec!["module", "exports"], &self.decls) { + if match_member_expr(member, vec!["module", "exports"], self.unresolved_mark) { handle_export!(); return; } else { @@ -673,9 +673,9 @@ impl Visit for Collect { } } Expr::Ident(ident) => { - if &*ident.sym == "exports" && !self.decls.contains(&id!(ident)) { + if &*ident.sym == "exports" && is_unresolved(&ident, self.unresolved_mark) { handle_export!(); - } else if ident.sym == js_word!("module") && !self.decls.contains(&id!(ident)) { + } else if ident.sym == js_word!("module") && is_unresolved(&ident, self.unresolved_mark) { self.has_cjs_exports = true; self.static_cjs_exports = false; self.should_wrap = true; @@ -713,7 +713,7 @@ impl Visit for Collect { if node.op == UnaryOp::TypeOf { match &*node.arg { Expr::Ident(ident) - if ident.sym == js_word!("module") && !self.decls.contains(&id!(ident)) => + if ident.sym == js_word!("module") && is_unresolved(&ident, self.unresolved_mark) => { // Do nothing to avoid the ident visitor from marking the module as non-static. } @@ -751,7 +751,7 @@ impl Visit for Collect { // Bail if `module` or `exports` are accessed non-statically. let is_module = ident.sym == js_word!("module"); let is_exports = &*ident.sym == "exports"; - if (is_module || is_exports) && !self.decls.contains(&id!(ident)) { + if (is_module || is_exports) && is_unresolved(&ident, self.unresolved_mark) { self.has_cjs_exports = true; self.static_cjs_exports = false; if is_module { @@ -823,7 +823,7 @@ impl Visit for Collect { node.right.visit_with(self); if let PatOrExpr::Pat(pat) = &node.left { - if has_binding_identifier(pat, &"exports".into(), &self.decls) { + if has_binding_identifier(pat, &"exports".into(), self.unresolved_mark) { // Must wrap for cases like // ``` // function logExports() { @@ -838,7 +838,7 @@ impl Visit for Collect { self.has_cjs_exports = true; self.should_wrap = true; self.add_bailout(node.span, BailoutReason::ExportsReassignment); - } else if has_binding_identifier(pat, &"module".into(), &self.decls) { + } else if has_binding_identifier(pat, &"module".into(), self.unresolved_mark) { // Same for `module`. If it is reassigned we can't correctly statically analyze. self.static_cjs_exports = false; self.has_cjs_exports = true; @@ -910,7 +910,7 @@ impl Visit for Collect { if let Callee::Expr(expr) = &node.callee { match &**expr { Expr::Ident(ident) => { - if ident.sym == js_word!("eval") && !self.decls.contains(&id!(ident)) { + if ident.sym == js_word!("eval") && is_unresolved(&ident, self.unresolved_mark) { self.should_wrap = true; self.add_bailout(node.span, BailoutReason::Eval); } @@ -950,7 +950,7 @@ impl Visit for Collect { impl Collect { pub fn match_require(&self, node: &Expr) -> Option { - match_require(node, &self.decls, self.ignore_mark) + match_require(node, self.unresolved_mark, self.ignore_mark) } fn add_pat_imports(&mut self, node: &Pat, src: &JsWord, kind: ImportKind) { @@ -1124,10 +1124,10 @@ impl Collect { } } -fn has_binding_identifier(node: &Pat, sym: &JsWord, decls: &HashSet) -> bool { +fn has_binding_identifier(node: &Pat, sym: &JsWord, unresolved_mark: Mark) -> bool { match node { Pat::Ident(ident) => { - if ident.id.sym == *sym && !decls.contains(&id!(ident.id)) { + if ident.id.sym == *sym && is_unresolved(&ident, unresolved_mark) { return true; } } @@ -1135,17 +1135,17 @@ fn has_binding_identifier(node: &Pat, sym: &JsWord, decls: &HashSet) -> bool for prop in &object.props { match prop { ObjectPatProp::KeyValue(kv) => { - if has_binding_identifier(&kv.value, sym, decls) { + if has_binding_identifier(&kv.value, sym, unresolved_mark) { return true; } } ObjectPatProp::Assign(assign) => { - if assign.key.sym == *sym && !decls.contains(&id!(assign.key)) { + if assign.key.sym == *sym && is_unresolved(&assign.key, unresolved_mark) { return true; } } ObjectPatProp::Rest(rest) => { - if has_binding_identifier(&rest.arg, sym, decls) { + if has_binding_identifier(&rest.arg, sym, unresolved_mark) { return true; } } @@ -1154,7 +1154,7 @@ fn has_binding_identifier(node: &Pat, sym: &JsWord, decls: &HashSet) -> bool } Pat::Array(array) => { for el in array.elems.iter().flatten() { - if has_binding_identifier(el, sym, decls) { + if has_binding_identifier(el, sym, unresolved_mark) { return true; } } diff --git a/packages/transformers/js/core/src/decl_collector.rs b/packages/transformers/js/core/src/decl_collector.rs deleted file mode 100644 index 3d9417c6b6b..00000000000 --- a/packages/transformers/js/core/src/decl_collector.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::collections::HashSet; - -use swc_core::ecma::ast::{self, Id}; -use swc_core::ecma::visit::{Visit, VisitWith}; - -/// This pass collects all declarations in a module into a single HashSet of tuples -/// containing identifier names and their associated syntax context (scope). -/// This is used later to determine whether an identifier references a declared variable. -pub fn collect_decls(module: &ast::Module) -> HashSet { - let mut c = DeclCollector { - decls: HashSet::new(), - in_var: false, - }; - module.visit_with(&mut c); - c.decls -} - -struct DeclCollector { - decls: HashSet, - in_var: bool, -} - -impl Visit for DeclCollector { - fn visit_decl(&mut self, node: &ast::Decl) { - use ast::Decl::*; - - match node { - Class(class) => { - self - .decls - .insert((class.ident.sym.clone(), class.ident.span.ctxt())); - } - Fn(f) => { - self - .decls - .insert((f.ident.sym.clone(), f.ident.span.ctxt())); - } - _ => {} - } - - node.visit_children_with(self); - } - - fn visit_var_declarator(&mut self, node: &ast::VarDeclarator) { - self.in_var = true; - node.name.visit_with(self); - self.in_var = false; - if let Some(init) = &node.init { - init.visit_with(self); - } - } - - fn visit_binding_ident(&mut self, node: &ast::BindingIdent) { - if self.in_var { - self.decls.insert((node.id.sym.clone(), node.id.span.ctxt)); - } - } - - fn visit_assign_pat_prop(&mut self, node: &ast::AssignPatProp) { - if self.in_var { - self - .decls - .insert((node.key.sym.clone(), node.key.span.ctxt)); - } - } - - fn visit_function(&mut self, node: &ast::Function) { - self.in_var = true; - for param in &node.params { - param.visit_with(self); - } - self.in_var = false; - - node.body.visit_with(self); - } - - fn visit_arrow_expr(&mut self, node: &ast::ArrowExpr) { - self.in_var = true; - for param in &node.params { - param.visit_with(self); - } - self.in_var = false; - - node.body.visit_with(self); - } - - fn visit_import_specifier(&mut self, node: &ast::ImportSpecifier) { - use ast::ImportSpecifier::*; - swc_core::ecma::visit::visit_import_specifier(self, node); - - match node { - Default(default) => { - self - .decls - .insert((default.local.sym.clone(), default.local.span.ctxt())); - } - Named(named) => { - self - .decls - .insert((named.local.sym.clone(), named.local.span.ctxt())); - } - Namespace(namespace) => { - self - .decls - .insert((namespace.local.sym.clone(), namespace.local.span.ctxt())); - } - } - } -} diff --git a/packages/transformers/js/core/src/dependency_collector.rs b/packages/transformers/js/core/src/dependency_collector.rs index cfe43890145..329c09355d0 100644 --- a/packages/transformers/js/core/src/dependency_collector.rs +++ b/packages/transformers/js/core/src/dependency_collector.rs @@ -1,11 +1,11 @@ use path_slash::PathBufExt; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::fmt; use std::path::Path; use serde::{Deserialize, Serialize}; use swc_core::common::{Mark, SourceMap, Span, DUMMY_SP}; -use swc_core::ecma::ast::{self, Callee, Id, MemberProp}; +use swc_core::ecma::ast::{self, Callee, MemberProp}; use swc_core::ecma::atoms::{js_word, JsWord}; use swc_core::ecma::visit::{Fold, FoldWith}; @@ -59,7 +59,6 @@ pub struct DependencyDescriptor { pub fn dependency_collector<'a>( source_map: &'a SourceMap, items: &'a mut Vec, - decls: &'a HashSet, ignore_mark: swc_core::common::Mark, unresolved_mark: swc_core::common::Mark, config: &'a Config, @@ -71,7 +70,6 @@ pub fn dependency_collector<'a>( in_try: false, in_promise: false, require_node: None, - decls, ignore_mark, unresolved_mark, config, @@ -86,7 +84,6 @@ struct DependencyCollector<'a> { in_try: bool, in_promise: bool, require_node: Option, - decls: &'a HashSet, ignore_mark: swc_core::common::Mark, unresolved_mark: swc_core::common::Mark, config: &'a Config, @@ -202,7 +199,7 @@ impl<'a> DependencyCollector<'a> { } fn create_require(&mut self, specifier: JsWord) -> ast::CallExpr { - let mut res = create_require(specifier); + let mut res = create_require(specifier, self.unresolved_mark); // For scripts, we replace with __parcel__require__, which is later replaced // by a real parcelRequire of the resolved asset in the packager. @@ -237,12 +234,12 @@ impl<'a> DependencyCollector<'a> { } } -fn rewrite_require_specifier(node: ast::CallExpr) -> ast::CallExpr { +fn rewrite_require_specifier(node: ast::CallExpr, unresolved_mark: Mark) -> ast::CallExpr { if let Some(arg) = node.args.first() { if let Some((value, _)) = match_str(&arg.expr) { if value.starts_with("node:") { // create_require will take care of replacing the node: prefix... - return create_require(value); + return create_require(value, unresolved_mark); } } } @@ -368,7 +365,7 @@ impl<'a> Fold for DependencyCollector<'a> { match &**expr { Ident(ident) => { // Bail if defined in scope - if self.decls.contains(&ident.to_id()) { + if !is_unresolved(&ident, self.unresolved_mark) { return node.fold_children_with(self); } @@ -461,18 +458,22 @@ impl<'a> Fold for DependencyCollector<'a> { } } Member(member) => { - if match_member_expr(member, vec!["module", "require"], self.decls) { + if match_member_expr(member, vec!["module", "require"], self.unresolved_mark) { DependencyKind::Require } else if self.config.is_browser && match_member_expr( member, vec!["navigator", "serviceWorker", "register"], - self.decls, + self.unresolved_mark, ) { DependencyKind::ServiceWorker } else if self.config.is_browser - && match_member_expr(member, vec!["CSS", "paintWorklet", "addModule"], self.decls) + && match_member_expr( + member, + vec!["CSS", "paintWorklet", "addModule"], + self.unresolved_mark, + ) { DependencyKind::Worklet } else { @@ -480,9 +481,11 @@ impl<'a> Fold for DependencyCollector<'a> { // Match compiled dynamic imports (Parcel) // Promise.resolve(require('foo')) - if match_member_expr(member, vec!["Promise", "resolve"], self.decls) { + if match_member_expr(member, vec!["Promise", "resolve"], self.unresolved_mark) { if let Some(expr) = node.args.first() { - if match_require(&expr.expr, self.decls, Mark::fresh(Mark::root())).is_some() { + if match_require(&expr.expr, self.unresolved_mark, Mark::fresh(Mark::root())) + .is_some() + { self.in_promise = true; let node = node.fold_children_with(self); self.in_promise = was_in_promise; @@ -500,7 +503,7 @@ impl<'a> Fold for DependencyCollector<'a> { if let Call(call) = &*member.obj { if let Callee::Expr(e) = &call.callee { if let Member(m) = &**e { - if match_member_expr(m, vec!["Promise", "resolve"], self.decls) && + if match_member_expr(m, vec!["Promise", "resolve"], self.unresolved_mark) && // Make sure the arglist is empty. // I.e. do not proceed with the below unless Promise.resolve has an empty arglist // because build_promise_chain() will not work in this case. @@ -588,7 +591,7 @@ impl<'a> Fold for DependencyCollector<'a> { }; let mut node = node.clone(); - let (specifier, span) = if let Some(s) = self.match_new_url(&arg.expr, self.decls) { + let (specifier, span) = if let Some(s) = self.match_new_url(&arg.expr) { s } else if let Lit(ast::Lit::Str(str_)) = &*arg.expr { let (msg, docs) = if kind == DependencyKind::ServiceWorker { @@ -686,12 +689,12 @@ impl<'a> Fold for DependencyCollector<'a> { call.args.truncate(1); // Track the returned require call to be replaced with a promise chain. - let rewritten_call = rewrite_require_specifier(call); + let rewritten_call = rewrite_require_specifier(call, self.unresolved_mark); self.require_node = Some(rewritten_call.clone()); rewritten_call } else if kind == DependencyKind::Require { // Don't continue traversing so that the `require` isn't replaced with undefined - rewrite_require_specifier(node) + rewrite_require_specifier(node, self.unresolved_mark) } else { node.fold_children_with(self) } @@ -705,8 +708,8 @@ impl<'a> Fold for DependencyCollector<'a> { .. } = &node { - if let ast::Expr::Ident(ast::Ident { sym, .. }) = &**arg { - if sym == &js_word!("require") && !self.decls.contains(&(sym.clone(), node.span.ctxt())) { + if let ast::Expr::Ident(ident) = &**arg { + if ident.sym == js_word!("require") && is_unresolved(&ident, self.unresolved_mark) { return node; } } @@ -722,7 +725,7 @@ impl<'a> Fold for DependencyCollector<'a> { Ident(id) => { if id.sym == "Worker" || id.sym == "SharedWorker" { // Bail if defined in scope - self.config.is_browser && !self.decls.contains(&id.to_id()) + self.config.is_browser && is_unresolved(&id, self.unresolved_mark) } else if id.sym == "Promise" { // Match requires inside promises (e.g. Rollup compiled dynamic imports) // new Promise(resolve => resolve(require('foo'))) @@ -754,7 +757,7 @@ impl<'a> Fold for DependencyCollector<'a> { if let Some(args) = &node.args { if !args.is_empty() { - let (specifier, span) = if let Some(s) = self.match_new_url(&args[0].expr, self.decls) { + let (specifier, span) = if let Some(s) = self.match_new_url(&args[0].expr) { s } else if let Lit(ast::Lit::Str(str_)) = &*args[0].expr { let constructor = match &*node.callee { @@ -829,7 +832,7 @@ impl<'a> Fold for DependencyCollector<'a> { return self.get_import_meta_url(); } - if let Some((specifier, span)) = self.match_new_url(&node, self.decls) { + if let Some((specifier, span)) = self.match_new_url(&node) { let url = self.add_url_dependency( specifier, span, @@ -855,14 +858,14 @@ impl<'a> Fold for DependencyCollector<'a> { } let is_require = match &node { - Expr::Ident(Ident { sym, span, .. }) => { + Expr::Ident(ident) => { // Free `require` -> undefined - sym == &js_word!("require") && !self.decls.contains(&(sym.clone(), span.ctxt())) + ident.sym == js_word!("require") && is_unresolved(&ident, self.unresolved_mark) } Expr::Member(MemberExpr { obj: expr, .. }) => { // e.g. `require.extensions` -> undefined - if let Expr::Ident(Ident { sym, span, .. }) = &**expr { - sym == &js_word!("require") && !self.decls.contains(&(sym.clone(), span.ctxt())) + if let Expr::Ident(ident) = &**expr { + ident.sym == js_word!("require") && is_unresolved(&ident, self.unresolved_mark) } else { false } @@ -920,7 +923,9 @@ impl<'a> DependencyCollector<'a> { if let ast::Expr::Ident(id) = &**callee { if id.to_id() == resolve_id { if let Some(arg) = call.args.first() { - if match_require(&arg.expr, self.decls, Mark::fresh(Mark::root())).is_some() { + if match_require(&arg.expr, self.unresolved_mark, Mark::fresh(Mark::root())) + .is_some() + { let was_in_promise = self.in_promise; self.in_promise = true; let node = node.fold_children_with(self); @@ -1136,16 +1141,12 @@ impl Fold for PromiseTransformer { } impl<'a> DependencyCollector<'a> { - fn match_new_url( - &mut self, - expr: &ast::Expr, - decls: &HashSet, - ) -> Option<(JsWord, swc_core::common::Span)> { + fn match_new_url(&mut self, expr: &ast::Expr) -> Option<(JsWord, swc_core::common::Span)> { use ast::*; if let Expr::New(new) = expr { let is_url = match &*new.callee { - Expr::Ident(id) => id.sym == js_word!("URL") && !decls.contains(&id.to_id()), + Expr::Ident(id) => id.sym == js_word!("URL") && is_unresolved(&id, self.unresolved_mark), _ => false, }; diff --git a/packages/transformers/js/core/src/env_replacer.rs b/packages/transformers/js/core/src/env_replacer.rs index 24d54e17b22..e6355f53ea5 100644 --- a/packages/transformers/js/core/src/env_replacer.rs +++ b/packages/transformers/js/core/src/env_replacer.rs @@ -13,7 +13,6 @@ pub struct EnvReplacer<'a> { pub replace_env: bool, pub is_browser: bool, pub env: &'a HashMap, - pub decls: &'a HashSet, pub used_env: &'a mut HashSet, pub source_map: &'a swc_core::common::SourceMap, pub diagnostics: &'a mut Vec, @@ -28,7 +27,8 @@ impl<'a> Fold for EnvReplacer<'a> { if let PatOrExpr::Pat(ref pat) = assign.left { if let Pat::Expr(ref expr) = &**pat { if let Expr::Member(ref member) = &**expr { - if self.is_browser && match_member_expr(member, vec!["process", "browser"], self.decls) + if self.is_browser + && match_member_expr(member, vec!["process", "browser"], self.unresolved_mark) { let mut res = assign.clone(); res.right = Box::new(Expr::Lit(Lit::Bool(Bool { @@ -46,7 +46,7 @@ impl<'a> Fold for EnvReplacer<'a> { match &node { Expr::Bin(binary) if binary.op == BinaryOp::In => { if let (Expr::Lit(Lit::Str(left)), Expr::Member(member)) = (&*binary.left, &*binary.right) { - if match_member_expr(member, vec!["process", "env"], self.decls) { + if match_member_expr(member, vec!["process", "env"], self.unresolved_mark) { return Expr::Lit(Lit::Bool(Bool { value: self.env.contains_key(&left.value), span: DUMMY_SP, @@ -58,7 +58,9 @@ impl<'a> Fold for EnvReplacer<'a> { } if let Expr::Member(ref member) = node { - if self.is_browser && match_member_expr(member, vec!["process", "browser"], self.decls) { + if self.is_browser + && match_member_expr(member, vec!["process", "browser"], self.unresolved_mark) + { return Expr::Lit(Lit::Bool(Bool { value: true, span: DUMMY_SP, @@ -70,7 +72,7 @@ impl<'a> Fold for EnvReplacer<'a> { } if let Expr::Member(obj) = &*member.obj { - if match_member_expr(obj, vec!["process", "env"], self.decls) { + if match_member_expr(obj, vec!["process", "env"], self.unresolved_mark) { if let Some((sym, _)) = match_property_name(member) { if let Some(replacement) = self.replace(&sym, true) { return replacement; @@ -91,7 +93,7 @@ impl<'a> Fold for EnvReplacer<'a> { Some(&**expr) } else if let Expr::Member(member) = &*assign.right { if assign.op == AssignOp::Assign - && match_member_expr(member, vec!["process", "env"], self.decls) + && match_member_expr(member, vec!["process", "env"], self.unresolved_mark) { let mut decls = vec![]; self.collect_pat_bindings(pat, &mut decls); @@ -132,7 +134,7 @@ impl<'a> Fold for EnvReplacer<'a> { if let Some(Expr::Member(MemberExpr { obj, .. })) = &expr { if let Expr::Member(member) = &**obj { - if match_member_expr(member, vec!["process", "env"], self.decls) { + if match_member_expr(member, vec!["process", "env"], self.unresolved_mark) { self.emit_mutating_error(assign.span); return *assign.right.clone().fold_with(self); } @@ -148,7 +150,7 @@ impl<'a> Fold for EnvReplacer<'a> { Expr::Update(UpdateExpr { arg, span, .. }) => { if let Expr::Member(MemberExpr { ref obj, .. }) = &**arg { if let Expr::Member(member) = &**obj { - if match_member_expr(member, vec!["process", "env"], self.decls) { + if match_member_expr(member, vec!["process", "env"], self.unresolved_mark) { self.emit_mutating_error(*span); return match &node { Expr::Unary(_) => Expr::Lit(Lit::Bool(Bool { span: *span, value: true })), @@ -175,7 +177,7 @@ impl<'a> Fold for EnvReplacer<'a> { for decl in &node.decls { if let Some(init) = &decl.init { if let Expr::Member(member) = &**init { - if match_member_expr(member, vec!["process", "env"], self.decls) { + if match_member_expr(member, vec!["process", "env"], self.unresolved_mark) { self.collect_pat_bindings(&decl.name, &mut decls); continue; } diff --git a/packages/transformers/js/core/src/fs.rs b/packages/transformers/js/core/src/fs.rs index 9814fc72fc3..5cb4ce1fe2b 100644 --- a/packages/transformers/js/core/src/fs.rs +++ b/packages/transformers/js/core/src/fs.rs @@ -3,7 +3,6 @@ use crate::dependency_collector::{DependencyDescriptor, DependencyKind}; use crate::id; use crate::utils::SourceLocation; use data_encoding::{BASE64, HEXLOWER}; -use std::collections::HashSet; use std::path::{Path, PathBuf}; use swc_core::common::{Mark, Span, DUMMY_SP}; use swc_core::ecma::ast::*; @@ -13,7 +12,7 @@ use swc_core::ecma::visit::{Fold, FoldWith, VisitWith}; pub fn inline_fs<'a>( filename: &str, source_map: swc_core::common::sync::Lrc, - decls: HashSet, + unresolved_mark: Mark, global_mark: Mark, project_root: &'a str, deps: &'a mut Vec, @@ -23,13 +22,12 @@ pub fn inline_fs<'a>( filename: Path::new(filename).to_path_buf(), collect: Collect::new( source_map, - decls, + unresolved_mark, Mark::fresh(Mark::root()), global_mark, false, is_module, ), - global_mark, project_root, deps, } @@ -38,7 +36,6 @@ pub fn inline_fs<'a>( struct InlineFS<'a> { filename: PathBuf, collect: Collect, - global_mark: Mark, project_root: &'a str, deps: &'a mut Vec, } @@ -188,7 +185,7 @@ impl<'a> InlineFS<'a> { callee: Callee::Expr(Box::new(Expr::Member(MemberExpr { obj: Box::new(Expr::Ident(Ident::new( "Buffer".into(), - DUMMY_SP.apply_mark(self.global_mark), + DUMMY_SP.apply_mark(self.collect.unresolved_mark), ))), prop: MemberProp::Ident(Ident::new("from".into(), DUMMY_SP)), span: DUMMY_SP, diff --git a/packages/transformers/js/core/src/global_replacer.rs b/packages/transformers/js/core/src/global_replacer.rs index 5264c1171f4..0af4d05a07c 100644 --- a/packages/transformers/js/core/src/global_replacer.rs +++ b/packages/transformers/js/core/src/global_replacer.rs @@ -1,15 +1,16 @@ use indexmap::IndexMap; use path_slash::PathBufExt; -use std::collections::HashSet; use std::path::Path; use swc_core::common::{Mark, SourceMap, SyntaxContext, DUMMY_SP}; -use swc_core::ecma::ast::{self, ComputedPropName, Id}; +use swc_core::ecma::ast::{self, ComputedPropName}; use swc_core::ecma::atoms::{js_word, JsWord}; use swc_core::ecma::visit::{Fold, FoldWith}; use crate::dependency_collector::{DependencyDescriptor, DependencyKind}; -use crate::utils::{create_global_decl_stmt, create_require, SourceLocation, SourceType}; +use crate::utils::{ + create_global_decl_stmt, create_require, is_unresolved, SourceLocation, SourceType, +}; pub struct GlobalReplacer<'a> { pub source_map: &'a SourceMap, @@ -18,7 +19,7 @@ pub struct GlobalReplacer<'a> { pub globals: IndexMap, pub project_root: &'a Path, pub filename: &'a Path, - pub decls: &'a mut HashSet, + pub unresolved_mark: Mark, pub scope_hoist: bool, } @@ -47,13 +48,16 @@ impl<'a> Fold for GlobalReplacer<'a> { if let Ident(id) = &mut node { // Only handle global variables - if self.decls.contains(&(id.sym.clone(), id.span.ctxt())) { + if !is_unresolved(&id, self.unresolved_mark) { return node; } + let unresolved_mark = self.unresolved_mark; match id.sym.to_string().as_str() { "process" => { - if self.update_binding(id, |_| Call(create_require(js_word!("process")))) { + if self.update_binding(id, |_| { + Call(create_require(js_word!("process"), unresolved_mark)) + }) { let specifier = id.sym.clone(); self.items.push(DependencyDescriptor { kind: DependencyKind::Require, @@ -71,7 +75,7 @@ impl<'a> Fold for GlobalReplacer<'a> { let specifier = swc_core::ecma::atoms::JsWord::from("buffer"); if self.update_binding(id, |_| { Member(MemberExpr { - obj: Box::new(Call(create_require(specifier.clone()))), + obj: Box::new(Call(create_require(specifier.clone(), unresolved_mark))), prop: MemberProp::Ident(ast::Ident::new("Buffer".into(), DUMMY_SP)), span: DUMMY_SP, }) @@ -168,7 +172,6 @@ impl GlobalReplacer<'_> { id.span.ctxt = ctxt; self.globals.insert(id.sym.clone(), (ctxt, decl)); - self.decls.insert(id.to_id()); true } diff --git a/packages/transformers/js/core/src/hoist.rs b/packages/transformers/js/core/src/hoist.rs index 500c1e9a5e6..6c701d39eff 100644 --- a/packages/transformers/js/core/src/hoist.rs +++ b/packages/transformers/js/core/src/hoist.rs @@ -1,6 +1,7 @@ use crate::collect::{Collect, Export, Import, ImportKind}; use crate::utils::{ - get_undefined_ident, match_export_name, match_export_name_ident, match_property_name, + get_undefined_ident, is_unresolved, match_export_name, match_export_name_ident, + match_property_name, }; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; @@ -374,7 +375,7 @@ impl<'a> Fold for Hoist<'a> { if let Some(init) = &v.init { // Match var x = require('foo'); if let Some(source) = - match_require(init, &self.collect.decls, self.collect.ignore_mark) + match_require(init, self.unresolved_mark, self.collect.ignore_mark) { // If the require is accessed in a way we cannot analyze, do not replace. // e.g. const {x: {y: z}} = require('x'); @@ -420,7 +421,7 @@ impl<'a> Fold for Hoist<'a> { if let Expr::Member(member) = &**init { // Match var x = require('foo').bar; if let Some(source) = - match_require(&member.obj, &self.collect.decls, self.collect.ignore_mark) + match_require(&member.obj, self.unresolved_mark, self.collect.ignore_mark) { if !self.collect.non_static_requires.contains(&source) { // If this is not the first declarator in the variable declaration, we need to @@ -504,7 +505,7 @@ impl<'a> Fold for Hoist<'a> { } Stmt::Expr(ExprStmt { expr, span }) => { if let Some(source) = - match_require(&expr, &self.collect.decls, self.collect.ignore_mark) + match_require(&expr, self.unresolved_mark, self.collect.ignore_mark) { // Require in statement position (`require('other');`) should behave just // like `import 'other';` in that it doesn't add any symbols (not even '*'). @@ -563,12 +564,12 @@ impl<'a> Fold for Hoist<'a> { } Expr::Member(member) => { if !self.collect.should_wrap { - if match_member_expr(&member, vec!["module", "exports"], &self.collect.decls) { + if match_member_expr(&member, vec!["module", "exports"], self.unresolved_mark) { self.self_references.insert("*".into()); return Expr::Ident(self.get_export_ident(member.span, &"*".into())); } - if match_member_expr(&member, vec!["module", "hot"], &self.collect.decls) { + if match_member_expr(&member, vec!["module", "hot"], self.unresolved_mark) { return Expr::Lit(Lit::Null(Null { span: member.span })); } } @@ -625,7 +626,7 @@ impl<'a> Fold for Hoist<'a> { // exports.foo -> $id$export$foo if &*ident.sym == "exports" - && !self.collect.decls.contains(&id!(ident)) + && is_unresolved(&ident, self.unresolved_mark) && self.collect.static_cjs_exports && !self.collect.should_wrap { @@ -636,7 +637,7 @@ impl<'a> Fold for Hoist<'a> { Expr::Call(_) => { // require('foo').bar -> $id$import$foo$bar if let Some(source) = - match_require(&member.obj, &self.collect.decls, self.collect.ignore_mark) + match_require(&member.obj, self.unresolved_mark, self.collect.ignore_mark) { self.add_require(&source, ImportKind::Require); return Expr::Ident(self.get_import_ident( @@ -652,7 +653,7 @@ impl<'a> Fold for Hoist<'a> { // module.exports.foo -> $id$export$foo if self.collect.static_cjs_exports && !self.collect.should_wrap - && match_member_expr(mem, vec!["module", "exports"], &self.collect.decls) + && match_member_expr(mem, vec!["module", "exports"], self.unresolved_mark) { self.self_references.insert(key.clone()); return Expr::Ident(self.get_export_ident(member.span, &key)); @@ -681,7 +682,7 @@ impl<'a> Fold for Hoist<'a> { } Expr::Call(ref call) => { // require('foo') -> $id$import$foo - if let Some(source) = match_require(&node, &self.collect.decls, self.collect.ignore_mark) { + if let Some(source) = match_require(&node, self.unresolved_mark, self.collect.ignore_mark) { self.add_require(&source, ImportKind::Require); return Expr::Ident(self.get_import_ident( call.span, @@ -754,7 +755,7 @@ impl<'a> Fold for Hoist<'a> { .enumerate() .map(|(i, expr)| { if i != len - 1 - && match_require(&expr, &self.collect.decls, self.collect.ignore_mark).is_some() + && match_require(&expr, self.unresolved_mark, self.collect.ignore_mark).is_some() { return Box::new(Expr::Unary(UnaryExpr { op: UnaryOp::Bang, @@ -846,19 +847,19 @@ impl<'a> Fold for Hoist<'a> { } if &*node.sym == "exports" - && !self.collect.decls.contains(&id!(node)) + && is_unresolved(&node, self.unresolved_mark) && !self.collect.should_wrap { self.self_references.insert("*".into()); return self.get_export_ident(node.span, &"*".into()); } - if node.sym == js_word!("global") && !self.collect.decls.contains(&id!(node)) { + if node.sym == js_word!("global") && is_unresolved(&node, self.unresolved_mark) { return Ident::new("$parcel$global".into(), node.span); } if node.span.has_mark(self.collect.global_mark) - && self.collect.decls.contains(&id!(node)) + && !is_unresolved(&node, self.unresolved_mark) && !self.collect.should_wrap { let new_name: JsWord = format!("${}$var${}", self.module_id, node.sym).into(); @@ -882,7 +883,7 @@ impl<'a> Fold for Hoist<'a> { }; if let Expr::Member(member) = &**expr { - if match_member_expr(member, vec!["module", "exports"], &self.collect.decls) { + if match_member_expr(member, vec!["module", "exports"], self.unresolved_mark) { let ident = BindingIdent::from(self.get_export_ident(member.span, &"*".into())); return AssignExpr { span: node.span, @@ -894,9 +895,11 @@ impl<'a> Fold for Hoist<'a> { let is_cjs_exports = match &*member.obj { Expr::Member(member) => { - match_member_expr(member, vec!["module", "exports"], &self.collect.decls) + match_member_expr(member, vec!["module", "exports"], self.unresolved_mark) + } + Expr::Ident(ident) => { + &*ident.sym == "exports" && is_unresolved(&ident, self.unresolved_mark) } - Expr::Ident(ident) => &*ident.sym == "exports" && !self.collect.decls.contains(&id!(ident)), Expr::This(_) if !self.in_function_scope => true, _ => false, }; @@ -1119,7 +1122,6 @@ impl<'a> Hoist<'a> { #[cfg(test)] mod tests { use super::*; - use crate::collect_decls; use crate::utils::BailoutReason; use std::iter::FromIterator; use swc_core::common::chain; @@ -1167,7 +1169,7 @@ mod tests { let mut collect = Collect::new( source_map.clone(), - collect_decls(&module), + unresolved_mark, Mark::fresh(Mark::root()), global_mark, true, diff --git a/packages/transformers/js/core/src/lib.rs b/packages/transformers/js/core/src/lib.rs index 8e755a64568..1b4c91b404b 100644 --- a/packages/transformers/js/core/src/lib.rs +++ b/packages/transformers/js/core/src/lib.rs @@ -1,6 +1,5 @@ mod collect; mod constant_module; -mod decl_collector; mod dependency_collector; mod env_replacer; mod fs; @@ -41,7 +40,6 @@ use swc_core::ecma::transforms::{ use swc_core::ecma::visit::{FoldWith, VisitWith}; use collect::{Collect, CollectResult}; -use decl_collector::*; use dependency_collector::*; use env_replacer::*; use fs::inline_fs; @@ -276,8 +274,6 @@ pub fn transform( config.is_jsx, )); - let mut decls = collect_decls(&module); - let mut preset_env_config = swc_core::ecma::preset_env::Config { dynamic_import: true, ..Default::default() @@ -320,7 +316,7 @@ pub fn transform( let module = { let mut passes = chain!( Optional::new( - TypeofReplacer { decls: &decls }, + TypeofReplacer { unresolved_mark }, config.source_type != SourceType::Script ), // Inline process.env and process.browser @@ -329,7 +325,6 @@ pub fn transform( replace_env: config.replace_env, env: &config.env, is_browser: config.is_browser, - decls: &decls, used_env: &mut result.used_env, source_map: &source_map, diagnostics: &mut diagnostics, @@ -347,8 +342,7 @@ pub fn transform( inline_fs( config.filename.as_str(), source_map.clone(), - // TODO this clone is unnecessary if we get the lifetimes right - decls.clone(), + unresolved_mark, global_mark, &config.project_root, &mut fs_deps, @@ -371,7 +365,7 @@ pub fn transform( globals: HashMap::new(), project_root: Path::new(&config.project_root), filename: Path::new(&config.filename), - decls: &mut decls, + unresolved_mark, scope_hoist: config.scope_hoist, has_node_replacements: &mut result.has_node_replacements, }, @@ -390,7 +384,7 @@ pub fn transform( globals: IndexMap::new(), project_root: Path::new(&config.project_root), filename: Path::new(&config.filename), - decls: &mut decls, + unresolved_mark, scope_hoist: config.scope_hoist }, config.insert_node_globals @@ -415,18 +409,16 @@ pub fn transform( // Flush Id=(JsWord, SyntaxContexts) into unique names and reresolve to // set global_mark for all nodes, even generated ones. - // - This changes the syntax context ids and therefore invalidates decls // - This will also remove any other other marks (like ignore_mark) // This only needs to be done if preset_env ran because all other transforms // insert declarations with global_mark (even though they are generated). - let (decls, module) = if config.scope_hoist && should_run_preset_env { - let module = module.fold_with(&mut chain!( + let module = if config.scope_hoist && should_run_preset_env { + module.fold_with(&mut chain!( hygiene(), resolver(unresolved_mark, global_mark, false) - )); - (collect_decls(&module), module) + )) } else { - (decls, module) + module }; let ignore_mark = Mark::fresh(Mark::root()); @@ -435,7 +427,6 @@ pub fn transform( &mut dependency_collector( &source_map, &mut result.dependencies, - &decls, ignore_mark, unresolved_mark, &config, @@ -455,7 +446,7 @@ pub fn transform( let mut collect = Collect::new( source_map.clone(), - decls, + unresolved_mark, ignore_mark, global_mark, config.trace_bailouts, diff --git a/packages/transformers/js/core/src/modules.rs b/packages/transformers/js/core/src/modules.rs index 8d3602b22c1..58f84b6dbcf 100644 --- a/packages/transformers/js/core/src/modules.rs +++ b/packages/transformers/js/core/src/modules.rs @@ -95,7 +95,10 @@ impl ESMFold { decls: vec![VarDeclarator { span: DUMMY_SP, name: Pat::Ident(ident.into()), - init: Some(Box::new(Expr::Call(crate::utils::create_require(src)))), + init: Some(Box::new(Expr::Call(crate::utils::create_require( + src, + self.unresolved_mark, + )))), definite: false, }], declare: false, @@ -556,6 +559,7 @@ impl Fold for ESMFold { ), init: Some(Box::new(Expr::Call(crate::utils::create_require( "@parcel/transformer-js/src/esmodule-helpers.js".into(), + self.unresolved_mark, )))), definite: false, }], diff --git a/packages/transformers/js/core/src/node_replacer.rs b/packages/transformers/js/core/src/node_replacer.rs index 85718685b9a..58b65467440 100644 --- a/packages/transformers/js/core/src/node_replacer.rs +++ b/packages/transformers/js/core/src/node_replacer.rs @@ -1,14 +1,16 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::ffi::OsStr; use std::path::Path; use swc_core::common::{Mark, SourceMap, SyntaxContext, DUMMY_SP}; -use swc_core::ecma::ast::{self, Id}; +use swc_core::ecma::ast; use swc_core::ecma::atoms::JsWord; use swc_core::ecma::visit::{Fold, FoldWith}; use crate::dependency_collector::{DependencyDescriptor, DependencyKind}; -use crate::utils::{create_global_decl_stmt, create_require, SourceLocation, SourceType}; +use crate::utils::{ + create_global_decl_stmt, create_require, is_unresolved, SourceLocation, SourceType, +}; pub struct NodeReplacer<'a> { pub source_map: &'a SourceMap, @@ -17,7 +19,7 @@ pub struct NodeReplacer<'a> { pub globals: HashMap, pub project_root: &'a Path, pub filename: &'a Path, - pub decls: &'a mut HashSet, + pub unresolved_mark: Mark, pub scope_hoist: bool, pub has_node_replacements: &'a mut bool, } @@ -47,10 +49,11 @@ impl<'a> Fold for NodeReplacer<'a> { if let Ident(id) = &mut node { // Only handle global variables - if self.decls.contains(&(id.sym.clone(), id.span.ctxt())) { + if !is_unresolved(&id, self.unresolved_mark) { return node; } + let unresolved_mark = self.unresolved_mark; match id.sym.to_string().as_str() { "__filename" => { let specifier = swc_core::ecma::atoms::JsWord::from("path"); @@ -95,7 +98,7 @@ impl<'a> Fold for NodeReplacer<'a> { ], callee: ast::Callee::Expr(Box::new(ast::Expr::Member(ast::MemberExpr { span: DUMMY_SP, - obj: (Box::new(Call(create_require(specifier.clone())))), + obj: (Box::new(Call(create_require(specifier.clone(), unresolved_mark)))), prop: MemberProp::Ident(ast::Ident::new("resolve".into(), DUMMY_SP)), }))), }) @@ -143,7 +146,7 @@ impl<'a> Fold for NodeReplacer<'a> { ], callee: ast::Callee::Expr(Box::new(ast::Expr::Member(ast::MemberExpr { span: DUMMY_SP, - obj: (Box::new(Call(create_require(specifier.clone())))), + obj: (Box::new(Call(create_require(specifier.clone(), unresolved_mark)))), prop: MemberProp::Ident(ast::Ident::new("resolve".into(), DUMMY_SP)), }))), }) @@ -199,8 +202,6 @@ impl NodeReplacer<'_> { id_ref.span.ctxt = ctxt; self.globals.insert(id_ref.sym.clone(), (ctxt, decl)); - self.decls.insert(id_ref.to_id()); - true } } diff --git a/packages/transformers/js/core/src/typeof_replacer.rs b/packages/transformers/js/core/src/typeof_replacer.rs index e0502f9160f..3b7ed37fd7e 100644 --- a/packages/transformers/js/core/src/typeof_replacer.rs +++ b/packages/transformers/js/core/src/typeof_replacer.rs @@ -1,30 +1,28 @@ -use std::collections::HashSet; - -use swc_core::ecma::ast::{Expr, Id, Lit, Str, UnaryOp}; +use crate::utils::is_unresolved; +use swc_core::common::Mark; +use swc_core::ecma::ast::{Expr, Lit, Str, UnaryOp}; use swc_core::ecma::atoms::js_word; use swc_core::ecma::visit::{Fold, FoldWith}; -use crate::id; - -pub struct TypeofReplacer<'a> { - pub decls: &'a HashSet, +pub struct TypeofReplacer { + pub unresolved_mark: Mark, } -impl<'a> Fold for TypeofReplacer<'a> { +impl Fold for TypeofReplacer { fn fold_expr(&mut self, node: Expr) -> Expr { if let Expr::Unary(ref unary) = node { // typeof require -> "function" // typeof module -> "object" if unary.op == UnaryOp::TypeOf { if let Expr::Ident(ident) = &*unary.arg { - if ident.sym == js_word!("require") && !self.decls.contains(&id!(ident)) { + if ident.sym == js_word!("require") && is_unresolved(&ident, self.unresolved_mark) { return Expr::Lit(Lit::Str(Str { span: unary.span, value: js_word!("function"), raw: None, })); } - if &*ident.sym == "exports" && !self.decls.contains(&id!(ident)) { + if &*ident.sym == "exports" && is_unresolved(&ident, self.unresolved_mark) { return Expr::Lit(Lit::Str(Str { span: unary.span, value: js_word!("object"), @@ -32,7 +30,7 @@ impl<'a> Fold for TypeofReplacer<'a> { })); } - if ident.sym == js_word!("module") && !self.decls.contains(&id!(ident)) { + if ident.sym == js_word!("module") && is_unresolved(&ident, self.unresolved_mark) { return Expr::Lit(Lit::Str(Str { span: unary.span, value: js_word!("object"), diff --git a/packages/transformers/js/core/src/utils.rs b/packages/transformers/js/core/src/utils.rs index b3d6b8b41c1..aacd2bc5ea3 100644 --- a/packages/transformers/js/core/src/utils.rs +++ b/packages/transformers/js/core/src/utils.rs @@ -1,15 +1,16 @@ -use std::cmp::Ordering; -use std::collections::HashSet; - -use crate::id; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; use swc_core::common::errors::{DiagnosticBuilder, Emitter}; use swc_core::common::{Mark, SourceMap, Span, SyntaxContext, DUMMY_SP}; -use swc_core::ecma::ast::{self, Id}; +use swc_core::ecma::ast::{self, Ident}; use swc_core::ecma::atoms::{js_word, JsWord}; -pub fn match_member_expr(expr: &ast::MemberExpr, idents: Vec<&str>, decls: &HashSet) -> bool { - use ast::{Expr, Ident, Lit, MemberProp, Str}; +pub fn is_unresolved(ident: &Ident, unresolved_mark: Mark) -> bool { + ident.span.ctxt.outer() == unresolved_mark +} + +pub fn match_member_expr(expr: &ast::MemberExpr, idents: Vec<&str>, unresolved_mark: Mark) -> bool { + use ast::{Expr, Lit, MemberProp, Str}; let mut member = expr; let mut idents = idents; @@ -34,7 +35,9 @@ pub fn match_member_expr(expr: &ast::MemberExpr, idents: Vec<&str>, decls: &Hash match &*member.obj { Expr::Member(m) => member = m, Expr::Ident(id) => { - return idents.len() == 1 && id.sym == idents.pop().unwrap() && !decls.contains(&id!(id)); + return idents.len() == 1 + && id.sym == idents.pop().unwrap() + && is_unresolved(&id, unresolved_mark); } _ => return false, } @@ -43,7 +46,10 @@ pub fn match_member_expr(expr: &ast::MemberExpr, idents: Vec<&str>, decls: &Hash false } -pub fn create_require(specifier: swc_core::ecma::atoms::JsWord) -> ast::CallExpr { +pub fn create_require( + specifier: swc_core::ecma::atoms::JsWord, + unresolved_mark: Mark, +) -> ast::CallExpr { let mut normalized_specifier = specifier; if normalized_specifier.starts_with("node:") { normalized_specifier = normalized_specifier.replace("node:", "").into(); @@ -52,7 +58,7 @@ pub fn create_require(specifier: swc_core::ecma::atoms::JsWord) -> ast::CallExpr ast::CallExpr { callee: ast::Callee::Expr(Box::new(ast::Expr::Ident(ast::Ident::new( "require".into(), - DUMMY_SP, + DUMMY_SP.apply_mark(unresolved_mark), )))), args: vec![ast::ExprOrSpread { expr: Box::new(ast::Expr::Lit(ast::Lit::Str(normalized_specifier.into()))), @@ -115,7 +121,7 @@ pub fn match_export_name_ident(name: &ast::ModuleExportName) -> &ast::Ident { } } -pub fn match_require(node: &ast::Expr, decls: &HashSet, ignore_mark: Mark) -> Option { +pub fn match_require(node: &ast::Expr, unresolved_mark: Mark, ignore_mark: Mark) -> Option { use ast::*; match node { @@ -123,7 +129,7 @@ pub fn match_require(node: &ast::Expr, decls: &HashSet, ignore_mark: Mark) - Callee::Expr(expr) => match &**expr { Expr::Ident(ident) => { if ident.sym == js_word!("require") - && !decls.contains(&(ident.sym.clone(), ident.span.ctxt)) + && is_unresolved(&ident, unresolved_mark) && !is_marked(ident.span, ignore_mark) { if let Some(arg) = call.args.first() { @@ -134,7 +140,7 @@ pub fn match_require(node: &ast::Expr, decls: &HashSet, ignore_mark: Mark) - None } Expr::Member(member) => { - if match_member_expr(member, vec!["module", "require"], decls) { + if match_member_expr(member, vec!["module", "require"], unresolved_mark) { if let Some(arg) = call.args.first() { return match_str(&arg.expr).map(|(name, _)| name); }