feat(es/analyzer): Extract the analyzer from the minifier to a separate crate (#6586)

This commit is contained in:
Alex Kirszenberg 2022-12-07 12:53:49 +01:00 committed by GitHub
parent da5e18e522
commit e1d01d8b7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 635 additions and 564 deletions

18
Cargo.lock generated
View File

@ -3294,6 +3294,7 @@ dependencies = [
"swc_ecma_transforms_react",
"swc_ecma_transforms_testing",
"swc_ecma_transforms_typescript",
"swc_ecma_usage_analyzer",
"swc_ecma_utils",
"swc_ecma_visit",
"swc_node_base",
@ -3653,6 +3654,7 @@ dependencies = [
"swc_ecma_testing",
"swc_ecma_transforms_base",
"swc_ecma_transforms_optimization",
"swc_ecma_usage_analyzer",
"swc_ecma_utils",
"swc_ecma_visit",
"swc_node_base",
@ -4013,6 +4015,22 @@ dependencies = [
"testing",
]
[[package]]
name = "swc_ecma_usage_analyzer"
version = "0.1.0"
dependencies = [
"ahash",
"indexmap",
"rustc-hash",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_utils",
"swc_ecma_visit",
"swc_timer",
"tracing",
]
[[package]]
name = "swc_ecma_utils"
version = "0.106.5"

View File

@ -146,6 +146,9 @@ ecma_minifier = ["__ecma", "swc_ecma_minifier"]
# Enable swc_ecma_preset_env
ecma_preset_env = ["__ecma", "swc_ecma_preset_env"]
# Enable swc_ecma_usage_analyzer
ecma_usage_analyzer = ["__ecma", "swc_ecma_usage_analyzer"]
# Enable swc_css
css_ast = ["__css", "swc_css_ast"]
css_codegen = ["__css", "swc_css_codegen"]
@ -366,6 +369,7 @@ swc_ecma_transforms_proposal = { optional = true, version = "0.145.5", path
swc_ecma_transforms_react = { optional = true, version = "0.156.5", path = "../swc_ecma_transforms_react" }
swc_ecma_transforms_testing = { optional = true, version = "0.115.6", path = "../swc_ecma_transforms_testing" }
swc_ecma_transforms_typescript = { optional = true, version = "0.160.5", path = "../swc_ecma_transforms_typescript" }
swc_ecma_usage_analyzer = { optional = true, version = "0.1.0", path = "../swc_ecma_usage_analyzer" }
swc_ecma_utils = { optional = true, version = "0.106.5", path = "../swc_ecma_utils" }
swc_ecma_visit = { optional = true, version = "0.81.3", path = "../swc_ecma_visit" }
swc_node_base = { optional = true, version = "0.5.8", path = "../swc_node_base" }

View File

@ -109,6 +109,12 @@ pub mod ecma {
pub use swc_ecma_preset_env::*;
}
#[cfg(feature = "ecma_usage_analyzer")]
#[cfg_attr(docsrs, doc(cfg(feature = "ecma_usage_analyzer")))]
pub mod usage_analyzer {
pub use swc_ecma_usage_analyzer::*;
}
// visit* interfaces
#[cfg(feature = "__visit")]
#[cfg_attr(docsrs, doc(cfg(feature = "__visit")))]

View File

@ -54,6 +54,7 @@ swc_ecma_codegen = { version = "0.128.5", path = "../swc_ecma_co
swc_ecma_parser = { version = "0.123.5", path = "../swc_ecma_parser" }
swc_ecma_transforms_base = { version = "0.112.5", path = "../swc_ecma_transforms_base" }
swc_ecma_transforms_optimization = { version = "0.168.5", path = "../swc_ecma_transforms_optimization" }
swc_ecma_usage_analyzer = { version = "0.1.0", path = "../swc_ecma_usage_analyzer" }
swc_ecma_utils = { version = "0.106.5", path = "../swc_ecma_utils" }
swc_ecma_visit = { version = "0.81.3", path = "../swc_ecma_visit" }
swc_timer = { version = "0.17.20", path = "../swc_timer" }

View File

@ -2,12 +2,13 @@
use rayon::prelude::*;
use swc_common::{collections::AHashSet, pass::Repeated, util::take::Take, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_usage_analyzer::analyzer::UsageAnalyzer;
use swc_ecma_utils::{find_pat_ids, StmtLike};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
use super::util::drop_invalid_stmts;
use crate::{
analyzer::{ProgramData, UsageAnalyzer},
program_data::ProgramData,
util::{is_hoisted_var_decl_without_init, sort::is_sorted_by, IsModuleItem, ModuleItemExt},
};
@ -45,7 +46,7 @@ impl Hoister<'_> {
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
where
T: StmtLike + IsModuleItem + ModuleItemExt,
Vec<T>: for<'aa> VisitMutWith<Hoister<'aa>> + VisitWith<UsageAnalyzer>,
Vec<T>: for<'aa> VisitMutWith<Hoister<'aa>> + VisitWith<UsageAnalyzer<ProgramData>>,
{
stmts.visit_mut_children_with(self);
let len = stmts.len();

View File

@ -17,6 +17,7 @@ use swc_ecma_ast::*;
use swc_ecma_transforms_optimization::simplify::{
dead_branch_remover, expr_simplifier, ExprSimplifierConfig,
};
use swc_ecma_usage_analyzer::{analyzer::UsageAnalyzer, marks::Marks};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
use swc_timer::timer;
use tracing::{debug, error};
@ -24,12 +25,11 @@ use tracing::{debug, error};
pub(crate) use self::pure::{pure_optimizer, PureOptimizerConfig};
use self::{hoist_decls::DeclHoisterConfig, optimize::optimizer};
use crate::{
analyzer::{analyze, ModuleInfo, UsageAnalyzer},
compress::hoist_decls::decl_hoister,
debug::{dump, AssertValid},
marks::Marks,
mode::Mode,
option::CompressOptions,
program_data::{analyze, ModuleInfo, ProgramData},
util::{now, unit::CompileUnit},
};
@ -100,7 +100,7 @@ where
fn optimize_unit_repeatedly<N>(&mut self, n: &mut N)
where
N: CompileUnit
+ VisitWith<UsageAnalyzer>
+ VisitWith<UsageAnalyzer<ProgramData>>
+ for<'aa> VisitMutWith<Compressor<'aa, M>>
+ VisitWith<AssertValid>,
{
@ -147,7 +147,7 @@ where
fn optimize_unit<N>(&mut self, n: &mut N)
where
N: CompileUnit
+ VisitWith<UsageAnalyzer>
+ VisitWith<UsageAnalyzer<ProgramData>>
+ for<'aa> VisitMutWith<Compressor<'aa, M>>
+ VisitWith<AssertValid>,
{

View File

@ -2,15 +2,15 @@ use swc_atoms::js_word;
use swc_common::{util::take::Take, EqIgnoreSpan, Spanned};
use swc_ecma_ast::*;
use swc_ecma_transforms_optimization::simplify::expr_simplifier;
use swc_ecma_usage_analyzer::alias::{collect_infects_from, AliasConfig};
use swc_ecma_utils::{class_has_side_effect, find_pat_ids, ExprExt};
use swc_ecma_visit::VisitMutWith;
use super::Optimizer;
use crate::{
alias::{collect_infects_from, AliasConfig},
analyzer::VarUsageInfo,
compress::optimize::util::is_valid_for_lhs,
mode::Mode,
program_data::VarUsageInfo,
util::{
idents_captured_by, idents_used_by, idents_used_by_ignoring_nested, size::SizeWithCtxt,
},

View File

@ -8,6 +8,7 @@ use swc_common::{
};
use swc_ecma_ast::*;
use swc_ecma_transforms_optimization::debug_assert_valid;
use swc_ecma_usage_analyzer::{analyzer::UsageAnalyzer, marks::Marks};
use swc_ecma_utils::{
prepend_stmts, undefined, ExprCtx, ExprExt, ExprFactory, IsEmpty, ModuleItemLike, StmtLike,
Type, Value,
@ -25,13 +26,12 @@ use super::util::{drop_invalid_stmts, is_fine_for_if_cons};
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{
analyzer::{ModuleInfo, ProgramData, UsageAnalyzer},
compress::util::is_pure_undefined,
debug::AssertValid,
marks::Marks,
maybe_par,
mode::Mode,
option::CompressOptions,
program_data::{ModuleInfo, ProgramData},
util::{
contains_eval, contains_leaping_continue_with_label, make_number, ExprOptExt, ModuleItemExt,
},
@ -332,7 +332,7 @@ where
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
where
T: StmtLike + ModuleItemLike + ModuleItemExt + VisitMutWith<Self> + VisitWith<AssertValid>,
Vec<T>: VisitMutWith<Self> + VisitWith<UsageAnalyzer> + VisitWith<AssertValid>,
Vec<T>: VisitMutWith<Self> + VisitWith<UsageAnalyzer<ProgramData>> + VisitWith<AssertValid>,
{
let mut use_asm = false;
let prepend_stmts = self.prepend_stmts.take();

View File

@ -3,6 +3,10 @@ use std::mem::take;
use swc_atoms::js_word;
use swc_common::{util::take::Take, Spanned, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_usage_analyzer::{
alias::{collect_infects_from, AccessKind, AliasConfig},
util::is_global_var_with_pure_property_access,
};
use swc_ecma_utils::{
contains_arguments, contains_this_expr, prepend_stmts, undefined, ExprExt, IdentUsageFinder,
StmtLike,
@ -15,17 +19,13 @@ use super::{is_pure_undefined, Optimizer};
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{
alias::{collect_infects_from, AccessKind, AliasConfig},
compress::{
optimize::{unused::PropertyAccessOpts, util::replace_id_with_expr},
util::{is_directive, is_ident_used_by, replace_expr},
},
mode::Mode,
option::CompressOptions,
util::{
idents_used_by, idents_used_by_ignoring_nested, is_global_var_with_pure_property_access,
ExprOptExt, ModuleItemExt,
},
util::{idents_used_by, idents_used_by_ignoring_nested, ExprOptExt, ModuleItemExt},
};
/// Methods related to the option `sequences`. All methods are noop if
@ -1170,8 +1170,8 @@ where
.expand_infected(self.module_info, ids_used_by_a_init, 64);
let deps = match deps {
Ok(v) => v,
Err(()) => return false,
Some(v) => v,
_ => return false,
};
if deps.contains(&(e.to_id(), AccessKind::Reference))
|| deps.contains(&(e.to_id(), AccessKind::Call))

View File

@ -1,6 +1,7 @@
use swc_atoms::js_word;
use swc_common::{util::take::Take, Span, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_usage_analyzer::util::is_global_var_with_pure_property_access;
use swc_ecma_utils::contains_ident_ref;
use super::Optimizer;
@ -8,7 +9,6 @@ use super::Optimizer;
use crate::debug::dump;
use crate::{
compress::optimize::util::extract_class_side_effect, mode::Mode, option::PureGetterOption,
util::is_global_var_with_pure_property_access,
};
#[derive(Debug, Default, Clone, Copy)]

View File

@ -4,20 +4,17 @@ use swc_atoms::js_word;
use swc_common::{iter::IdentifyLast, util::take::Take, Span, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_optimization::debug_assert_valid;
use swc_ecma_usage_analyzer::util::is_global_var_with_pure_property_access;
use swc_ecma_utils::{
ExprExt, ExprFactory, IdentUsageFinder, Type,
Value::{self, Known},
};
use super::Pure;
use crate::{
compress::{
pure::strings::{convert_str_value_to_tpl_cooked, convert_str_value_to_tpl_raw},
util::is_pure_undefined,
},
util::is_global_var_with_pure_property_access,
use crate::compress::{
pure::strings::{convert_str_value_to_tpl_cooked, convert_str_value_to_tpl_raw},
util::is_pure_undefined,
};
impl Pure<'_> {
pub(super) fn remove_invalid(&mut self, e: &mut Expr) {
match e {

View File

@ -5,6 +5,7 @@ use rayon::prelude::*;
use swc_common::{pass::Repeated, util::take::Take, SyntaxContext, DUMMY_SP, GLOBALS};
use swc_ecma_ast::*;
use swc_ecma_transforms_optimization::debug_assert_valid;
use swc_ecma_usage_analyzer::marks::Marks;
use swc_ecma_utils::{undefined, ExprCtx};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
#[cfg(feature = "debug")]
@ -14,7 +15,7 @@ use self::{ctx::Ctx, misc::DropOpts};
#[cfg(feature = "debug")]
use crate::debug::dump;
use crate::{
analyzer::ProgramData, debug::AssertValid, marks::Marks, maybe_par, option::CompressOptions,
debug::AssertValid, maybe_par, option::CompressOptions, program_data::ProgramData,
util::ModuleItemExt,
};

View File

@ -5,12 +5,12 @@ use swc_atoms::js_word;
use swc_common::{collections::AHashMap, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_optimization::simplify::{expr_simplifier, ExprSimplifierConfig};
use swc_ecma_usage_analyzer::marks::Marks;
use swc_ecma_utils::{undefined, ExprCtx, ExprExt};
use swc_ecma_visit::VisitMutWith;
use crate::{
compress::{compressor, pure_optimizer, PureOptimizerConfig},
marks::Marks,
mode::Mode,
};

View File

@ -41,14 +41,13 @@ use once_cell::sync::Lazy;
use swc_common::{comments::Comments, pass::Repeated, sync::Lrc, SourceMap, SyntaxContext};
use swc_ecma_ast::*;
use swc_ecma_transforms_optimization::debug_assert_valid;
use swc_ecma_usage_analyzer::marks::Marks;
use swc_ecma_visit::VisitMutWith;
use swc_timer::timer;
pub use crate::pass::unique_scope::unique_scope;
use crate::{
analyzer::ModuleInfo,
compress::{compressor, pure_optimizer, PureOptimizerConfig},
marks::Marks,
metadata::info_marker,
mode::{Minification, Mode},
option::{CompressOptions, ExtraOptions, MinifyOptions},
@ -61,25 +60,28 @@ use crate::{
postcompress::postcompress_optimizer,
precompress::precompress_optimizer,
},
program_data::ModuleInfo,
timing::Timings,
util::base54::CharFreq,
};
#[macro_use]
mod macros;
mod alias;
mod analyzer;
mod compress;
mod debug;
pub mod eval;
pub mod marks;
mod metadata;
mod mode;
pub mod option;
mod pass;
mod program_data;
pub mod timing;
mod util;
pub mod marks {
pub use swc_ecma_usage_analyzer::marks::Marks;
}
const DISABLE_BUGGY_PASSES: bool = true;
pub(crate) static CPU_COUNT: Lazy<usize> = Lazy::new(num_cpus::get);

View File

@ -3,12 +3,13 @@ use swc_common::{
EqIgnoreSpan, Span, SyntaxContext,
};
use swc_ecma_ast::*;
use swc_ecma_usage_analyzer::marks::Marks;
use swc_ecma_utils::find_pat_ids;
use swc_ecma_visit::{
noop_visit_mut_type, noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith,
};
use crate::{marks::Marks, option::CompressOptions};
use crate::option::CompressOptions;
#[cfg(test)]
mod tests;

View File

@ -10,8 +10,8 @@ use swc_ecma_ast::{
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
use crate::{
analyzer::{analyze, ModuleInfo, ProgramData},
option::ManglePropertiesOptions,
program_data::{analyze, ModuleInfo, ProgramData},
util::base54::Base54Chars,
};

View File

@ -1,14 +1,207 @@
use std::collections::hash_map::Entry;
use swc_common::collections::AHashSet;
use swc_ecma_ast::*;
use super::{ScopeDataLike, Storage, VarDataLike};
use crate::{
alias::Access,
analyzer::{ctx::Ctx, ProgramData, ScopeData, ScopeKind, VarUsageInfo},
use std::{
collections::{hash_map::Entry, HashSet},
hash::BuildHasherDefault,
};
use indexmap::IndexSet;
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use swc_atoms::JsWord;
use swc_common::{
collections::{AHashMap, AHashSet},
SyntaxContext,
};
use swc_ecma_ast::*;
use swc_ecma_usage_analyzer::{
alias::{Access, AccessKind},
analyzer::{
analyze_with_storage,
storage::{ScopeDataLike, Storage, VarDataLike},
Ctx, ScopeKind, UsageAnalyzer,
},
marks::Marks,
};
use swc_ecma_visit::VisitWith;
pub(crate) fn analyze<N>(n: &N, _module_info: &ModuleInfo, marks: Option<Marks>) -> ProgramData
where
N: VisitWith<UsageAnalyzer<ProgramData>>,
{
analyze_with_storage::<ProgramData, _>(n, marks)
}
/// Analyzed info of a whole program we are working on.
#[derive(Debug, Default)]
pub(crate) struct ProgramData {
pub(crate) vars: FxHashMap<Id, VarUsageInfo>,
pub(crate) top: ScopeData,
pub(crate) scopes: FxHashMap<SyntaxContext, ScopeData>,
initialized_vars: IndexSet<Id, ahash::RandomState>,
}
#[derive(Debug, Default, Clone)]
pub(crate) struct ScopeData {
pub(crate) has_with_stmt: bool,
pub(crate) has_eval_call: bool,
pub(crate) used_arguments: bool,
}
#[derive(Debug, Default)]
pub(crate) struct ModuleInfo {
/// Imported identifiers which should be treated as a black box.
///
/// Imports from `@swc/helpers` are excluded as helpers are not modified by
/// accessing/calling other modules.
pub(crate) blackbox_imports: AHashSet<Id>,
}
#[derive(Debug, Clone)]
pub(crate) struct VarUsageInfo {
pub(crate) inline_prevented: bool,
/// The number of direct reference to this identifier.
pub(crate) ref_count: u32,
/// `true` if a variable is conditionally initialized.
pub(crate) cond_init: bool,
/// `false` if it's only used.
pub(crate) declared: bool,
pub(crate) declared_count: u32,
/// `true` if the enclosing function defines this variable as a parameter.
pub(crate) declared_as_fn_param: bool,
pub(crate) declared_as_fn_decl: bool,
pub(crate) declared_as_fn_expr: bool,
pub(crate) assign_count: u32,
pub(crate) mutation_by_call_count: u32,
/// The number of direct and indirect reference to this identifier.
/// ## Things to note
///
/// - Update is counted as usage, but assign is not
pub(crate) usage_count: u32,
/// The variable itself is modified.
reassigned_with_assignment: bool,
reassigned_with_var_decl: bool,
/// The variable itself or a property of it is modified.
pub(crate) mutated: bool,
pub(crate) has_property_access: bool,
pub(crate) has_property_mutation: bool,
pub(crate) exported: bool,
/// True if used **above** the declaration or in init. (Not eval order).
pub(crate) used_above_decl: bool,
/// `true` if it's declared by function parameters or variables declared in
/// a closest function and used only within it and not used by child
/// functions.
pub(crate) is_fn_local: bool,
pub(crate) executed_multiple_time: bool,
pub(crate) used_in_cond: bool,
pub(crate) var_kind: Option<VarDeclKind>,
pub(crate) var_initialized: bool,
pub(crate) declared_as_catch_param: bool,
pub(crate) no_side_effect_for_member_access: bool,
pub(crate) callee_count: u32,
pub(crate) used_as_arg: bool,
pub(crate) indexed_with_dynamic_key: bool,
pub(crate) pure_fn: bool,
/// `infects_to`. This should be renamed, but it will be done with another
/// PR. (because it's hard to review)
infects: Vec<Access>,
pub(crate) used_in_non_child_fn: bool,
/// Only **string** properties.
pub(crate) accessed_props: Box<AHashMap<JsWord, u32>>,
pub(crate) used_recursively: bool,
}
impl Default for VarUsageInfo {
fn default() -> Self {
Self {
inline_prevented: Default::default(),
ref_count: Default::default(),
cond_init: Default::default(),
declared: Default::default(),
declared_count: Default::default(),
declared_as_fn_param: Default::default(),
declared_as_fn_decl: Default::default(),
declared_as_fn_expr: Default::default(),
assign_count: Default::default(),
mutation_by_call_count: Default::default(),
usage_count: Default::default(),
reassigned_with_assignment: Default::default(),
reassigned_with_var_decl: Default::default(),
mutated: Default::default(),
has_property_access: Default::default(),
has_property_mutation: Default::default(),
exported: Default::default(),
used_above_decl: Default::default(),
is_fn_local: true,
executed_multiple_time: Default::default(),
used_in_cond: Default::default(),
var_kind: Default::default(),
var_initialized: Default::default(),
declared_as_catch_param: Default::default(),
no_side_effect_for_member_access: Default::default(),
callee_count: Default::default(),
used_as_arg: Default::default(),
indexed_with_dynamic_key: Default::default(),
pure_fn: Default::default(),
infects: Default::default(),
used_in_non_child_fn: Default::default(),
accessed_props: Default::default(),
used_recursively: Default::default(),
}
}
}
impl VarUsageInfo {
pub(crate) fn is_mutated_only_by_one_call(&self) -> bool {
self.assign_count == 0 && self.mutation_by_call_count == 1
}
pub(crate) fn is_infected(&self) -> bool {
!self.infects.is_empty()
}
pub(crate) fn reassigned(&self) -> bool {
self.reassigned_with_assignment
|| self.reassigned_with_var_decl
|| (u32::from(self.var_initialized)
+ u32::from(self.declared_as_catch_param)
+ u32::from(self.declared_as_fn_param)
+ self.assign_count)
> 1
}
pub(crate) fn can_inline_var(&self) -> bool {
!self.mutated
|| (self.assign_count == 0 && !self.reassigned() && !self.has_property_mutation)
}
pub(crate) fn can_inline_fn_once(&self) -> bool {
self.callee_count > 0
|| !self.executed_multiple_time && (self.is_fn_local || !self.used_in_non_child_fn)
}
}
impl Storage for ProgramData {
type ScopeData = ScopeData;
type VarData = VarUsageInfo;
@ -156,7 +349,10 @@ impl Storage for ProgramData {
let v = self.vars.entry(i.to_id()).or_default();
if has_init && (v.declared || v.var_initialized) {
trace_op!("declare_decl(`{}`): Already declared", i);
#[cfg(feature = "debug")]
{
tracing::trace!("declare_decl(`{}`): Already declared", i);
}
v.mutated = true;
v.reassigned_with_var_decl = true;
@ -217,6 +413,185 @@ impl ScopeDataLike for ScopeData {
}
}
impl VarDataLike for VarUsageInfo {
fn mark_declared_as_fn_param(&mut self) {
self.declared_as_fn_param = true;
}
fn mark_declared_as_fn_decl(&mut self) {
self.declared_as_fn_decl = true;
}
fn mark_declared_as_fn_expr(&mut self) {
self.declared_as_fn_expr = true;
}
fn mark_has_property_access(&mut self) {
self.has_property_access = true;
}
fn mark_has_property_mutation(&mut self) {
self.has_property_mutation = true;
}
fn mark_used_as_callee(&mut self) {
self.callee_count += 1;
}
fn mark_used_as_arg(&mut self) {
self.used_as_arg = true
}
fn mark_indexed_with_dynamic_key(&mut self) {
self.indexed_with_dynamic_key = true;
}
fn add_accessed_property(&mut self, name: swc_atoms::JsWord) {
*self.accessed_props.entry(name).or_default() += 1;
}
fn mark_mutated(&mut self) {
self.mutated = true;
}
fn mark_reassigned_with_assign(&mut self) {
self.reassigned_with_assignment = true;
}
fn add_infects_to(&mut self, other: Access) {
self.infects.push(other);
}
fn prevent_inline(&mut self) {
self.inline_prevented = true;
}
fn mark_initialized_with_safe_value(&mut self) {
self.no_side_effect_for_member_access = true;
}
fn mark_as_pure_fn(&mut self) {
self.pure_fn = true;
}
fn mark_used_above_decl(&mut self) {
self.used_above_decl = true;
}
fn mark_used_recursively(&mut self) {
self.used_recursively = true;
}
}
impl ProgramData {
pub(crate) fn expand_infected(
&self,
module_info: &ModuleInfo,
ids: FxHashSet<Access>,
max_num: usize,
) -> Option<FxHashSet<Access>> {
let init =
HashSet::with_capacity_and_hasher(max_num, BuildHasherDefault::<FxHasher>::default());
ids.into_iter()
.try_fold(init, |mut res, id| {
let mut ids = Vec::with_capacity(max_num);
ids.push(id);
let mut ranges = vec![0..1usize];
loop {
let range = ranges.remove(0);
for index in range {
let iid = ids.get(index).unwrap();
// Abort on imported variables, because we can't analyze them
if module_info.blackbox_imports.contains(&iid.0) {
return Err(());
}
if !res.insert(iid.clone()) {
continue;
}
if res.len() >= max_num {
return Err(());
}
if let Some(info) = self.vars.get(&iid.0) {
let infects = &info.infects;
if !infects.is_empty() {
let old_len = ids.len();
// This is not a call, so effects from call can be skipped
let can_skip_non_call = matches!(iid.1, AccessKind::Reference)
|| (info.declared_count == 1
&& info.declared_as_fn_decl
&& !info.reassigned());
if can_skip_non_call {
ids.extend(
infects
.iter()
.filter(|(_, kind)| *kind != AccessKind::Call)
.cloned(),
);
} else {
ids.extend_from_slice(infects.as_slice());
}
let new_len = ids.len();
ranges.push(old_len..new_len);
}
}
}
if ranges.is_empty() {
break;
}
}
Ok(res)
})
.ok()
}
pub(crate) fn contains_unresolved(&self, e: &Expr) -> bool {
match e {
Expr::Ident(i) => {
if let Some(v) = self.vars.get(&i.to_id()) {
return !v.declared;
}
true
}
Expr::Member(MemberExpr { obj, prop, .. }) => {
if self.contains_unresolved(obj) {
return true;
}
if let MemberProp::Computed(prop) = prop {
if self.contains_unresolved(&prop.expr) {
return true;
}
}
false
}
Expr::Call(CallExpr {
callee: Callee::Expr(callee),
args,
..
}) => {
if self.contains_unresolved(callee) {
return true;
}
if args.iter().any(|arg| self.contains_unresolved(&arg.expr)) {
return true;
}
false
}
_ => false,
}
}
}
impl ProgramData {
fn report(&mut self, i: Id, ctx: Ctx, is_modify: bool, dejavu: &mut AHashSet<Id>) {
// trace!("report({}{:?})", i.0, i.1);
@ -293,73 +668,3 @@ impl ProgramData {
}
}
}
impl VarDataLike for VarUsageInfo {
fn mark_declared_as_fn_param(&mut self) {
self.declared_as_fn_param = true;
}
fn mark_declared_as_fn_decl(&mut self) {
self.declared_as_fn_decl = true;
}
fn mark_declared_as_fn_expr(&mut self) {
self.declared_as_fn_expr = true;
}
fn mark_has_property_access(&mut self) {
self.has_property_access = true;
}
fn mark_has_property_mutation(&mut self) {
self.has_property_mutation = true;
}
fn mark_used_as_callee(&mut self) {
self.callee_count += 1;
}
fn mark_used_as_arg(&mut self) {
self.used_as_arg = true
}
fn mark_indexed_with_dynamic_key(&mut self) {
self.indexed_with_dynamic_key = true;
}
fn add_accessed_property(&mut self, name: swc_atoms::JsWord) {
*self.accessed_props.entry(name).or_default() += 1;
}
fn mark_mutated(&mut self) {
self.mutated = true;
}
fn mark_reassigned_with_assign(&mut self) {
self.reassigned_with_assignment = true;
}
fn add_infects_to(&mut self, other: Access) {
self.infects.push(other);
}
fn prevent_inline(&mut self) {
self.inline_prevented = true;
}
fn mark_initialized_with_safe_value(&mut self) {
self.no_side_effect_for_member_access = true;
}
fn mark_as_pure_fn(&mut self) {
self.pure_fn = true;
}
fn mark_used_above_decl(&mut self) {
self.used_above_decl = true;
}
fn mark_used_recursively(&mut self) {
self.used_recursively = true;
}
}

View File

@ -1,7 +1,7 @@
use std::time::Instant;
use rustc_hash::FxHashSet;
use swc_atoms::{js_word, JsWord};
use swc_atoms::js_word;
use swc_common::{util::take::Take, Mark, Span, Spanned, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_utils::{ModuleItemLike, StmtLike, Value};
@ -12,57 +12,6 @@ pub(crate) mod size;
pub(crate) mod sort;
pub(crate) mod unit;
pub(crate) fn is_global_var_with_pure_property_access(s: &JsWord) -> bool {
match *s {
js_word!("JSON")
| js_word!("Array")
| js_word!("String")
| js_word!("Object")
| js_word!("Number")
| js_word!("Date")
| js_word!("BigInt")
| js_word!("Boolean")
| js_word!("Math")
| js_word!("Error") => return true,
_ => {}
}
matches!(
&**s,
"console"
| "clearInterval"
| "clearTimeout"
| "setInterval"
| "setTimeout"
| "btoa"
| "decodeURI"
| "decodeURIComponent"
| "encodeURI"
| "encodeURIComponent"
| "escape"
| "eval"
| "EvalError"
| "Function"
| "isFinite"
| "isNaN"
| "JSON"
| "parseFloat"
| "parseInt"
| "RegExp"
| "RangeError"
| "ReferenceError"
| "SyntaxError"
| "TypeError"
| "unescape"
| "URIError"
| "atob"
| "globalThis"
| "NaN"
| "Symbol"
| "Promise"
)
}
///
pub(crate) fn make_number(span: Span, value: f64) -> Expr {
trace_op!("Creating a numeric literal");
@ -504,43 +453,6 @@ where
v.ids
}
pub(crate) fn can_end_conditionally(s: &Stmt) -> bool {
///
///`ignore_always`: If true, [Stmt::Return] will be ignored.
fn can_end(s: &Stmt, ignore_always: bool) -> bool {
match s {
Stmt::If(s) => {
can_end(&s.cons, false)
|| s.alt
.as_deref()
.map(|s| can_end(s, false))
.unwrap_or_default()
}
Stmt::Switch(s) => s
.cases
.iter()
.any(|case| case.cons.iter().any(|s| can_end(s, false))),
Stmt::DoWhile(s) => can_end(&s.body, false),
Stmt::While(s) => can_end(&s.body, false),
Stmt::For(s) => can_end(&s.body, false),
Stmt::ForOf(s) => can_end(&s.body, false),
Stmt::ForIn(s) => can_end(&s.body, false),
Stmt::Return(..) | Stmt::Break(..) | Stmt::Continue(..) => !ignore_always,
_ => false,
}
}
can_end(s, true)
}
pub fn now() -> Option<Instant> {
#[cfg(target_arch = "wasm32")]
{

View File

@ -0,0 +1,34 @@
[package]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
description = "EcmaScript variable usage analyzer"
documentation = "https://rustdoc.swc.rs/swc_ecma_usage_analyzer/"
edition = "2021"
include = ["Cargo.toml", "src/**/*.rs"]
license = "Apache-2.0"
name = "swc_ecma_usage_analyzer"
repository = "https://github.com/swc-project/swc.git"
version = "0.1.0"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lib]
bench = false
[features]
# This enables global concurrent mode
concurrent = ["swc_common/concurrent", "indexmap/rayon"]
trace-ast = []
[dependencies]
ahash = "0.7.6"
indexmap = "1.6.1"
rustc-hash = "1.1.0"
swc_atoms = { version = "0.4.25", path = "../swc_atoms" }
swc_common = { version = "0.29.19", path = "../swc_common" }
swc_ecma_ast = { version = "0.95.3", path = "../swc_ecma_ast" }
swc_ecma_utils = { version = "0.106.5", path = "../swc_ecma_utils" }
swc_ecma_visit = { version = "0.81.3", path = "../swc_ecma_visit" }
swc_timer = { version = "0.17.20", path = "../swc_timer" }
tracing = "0.1.32"

View File

@ -15,7 +15,7 @@ impl<'a> InfectionCollector<'a> {
}
#[derive(Debug, Default, Clone, Copy)]
pub(super) struct Ctx {
pub struct Ctx {
pub track_expr_ident: bool,
pub is_callee: bool,
}

View File

@ -6,13 +6,13 @@ use swc_ecma_ast::*;
use swc_ecma_utils::{collect_decls, BindingCollector};
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use self::ctx::Ctx;
pub use self::ctx::Ctx;
use crate::{marks::Marks, util::is_global_var_with_pure_property_access};
mod ctx;
#[derive(Default)]
pub(crate) struct AliasConfig {
pub struct AliasConfig {
pub marks: Option<Marks>,
pub ignore_nested: bool,
/// TODO(kdy1): This field is used for sequential inliner.
@ -20,7 +20,7 @@ pub(crate) struct AliasConfig {
pub need_all: bool,
}
pub(crate) trait InfectableNode {
pub trait InfectableNode {
fn is_fn_or_arrow_expr(&self) -> bool;
}
@ -32,10 +32,7 @@ impl InfectableNode for Function {
impl InfectableNode for Expr {
fn is_fn_or_arrow_expr(&self) -> bool {
match self {
Expr::Arrow(..) | Expr::Fn(..) => true,
_ => false,
}
matches!(self, Expr::Arrow(..) | Expr::Fn(..))
}
}
@ -50,14 +47,14 @@ where
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum AccessKind {
pub enum AccessKind {
Reference,
Call,
}
pub(crate) type Access = (Id, AccessKind);
pub type Access = (Id, AccessKind);
pub(crate) fn collect_infects_from<N>(node: &N, config: AliasConfig) -> FxHashSet<Access>
pub fn collect_infects_from<N>(node: &N, config: AliasConfig) -> FxHashSet<Access>
where
N: InfectableNode
+ VisitWith<BindingCollector<Id>>
@ -89,7 +86,7 @@ where
visitor.accesses
}
pub(crate) struct InfectionCollector<'a> {
pub struct InfectionCollector<'a> {
#[allow(unused)]
config: AliasConfig,
unresolved_ctxt: Option<SyntaxContext>,
@ -107,10 +104,8 @@ impl InfectionCollector<'_> {
return;
}
if self.unresolved_ctxt == Some(e.1) {
if is_global_var_with_pure_property_access(&e.0) {
return;
}
if self.unresolved_ctxt == Some(e.1) && is_global_var_with_pure_property_access(&e.0) {
return;
}
self.accesses.insert((

View File

@ -21,45 +21,45 @@ where
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct Ctx {
pub struct Ctx {
/// See [crate::marks::Marks]
pub(super) skip_standalone: bool,
pub skip_standalone: bool,
pub(super) var_decl_kind_of_pat: Option<VarDeclKind>,
pub var_decl_kind_of_pat: Option<VarDeclKind>,
pub(super) in_decl_with_no_side_effect_for_member_access: bool,
pub in_decl_with_no_side_effect_for_member_access: bool,
pub(super) in_pat_of_var_decl: bool,
pub(super) in_pat_of_var_decl_with_init: bool,
pub(super) in_pat_of_param: bool,
pub(super) in_catch_param: bool,
pub in_pat_of_var_decl: bool,
pub in_pat_of_var_decl_with_init: bool,
pub in_pat_of_param: bool,
pub in_catch_param: bool,
/// `true` for `foo.bar` and `false` for `foo` in `foo.bar++`
pub(super) is_exact_reassignment: bool,
pub is_exact_reassignment: bool,
pub(super) is_callee: bool,
pub is_callee: bool,
/// `true` for arguments of [swc_ecma_ast::Expr::Call] or
/// [swc_ecma_ast::Expr::New]
pub(super) in_call_arg: bool,
pub in_call_arg: bool,
/// `false` for `array` in `array.length.
pub(super) is_exact_arg: bool,
pub is_exact_arg: bool,
pub(super) in_await_arg: bool,
pub in_await_arg: bool,
pub(super) is_delete_arg: bool,
pub is_delete_arg: bool,
pub(super) in_left_of_for_loop: bool,
pub in_left_of_for_loop: bool,
pub(super) executed_multiple_time: bool,
pub executed_multiple_time: bool,
/// Are we handling argument of an update expression.
pub(super) in_update_arg: bool,
pub(super) in_assign_lhs: bool,
pub(super) in_cond: bool,
pub in_update_arg: bool,
pub in_assign_lhs: bool,
pub in_cond: bool,
pub(super) inline_prevented: bool,
pub inline_prevented: bool,
pub(super) is_op_assign: bool,
pub is_op_assign: bool,
}
pub(super) struct WithCtx<'a, S>

View File

@ -1,51 +1,26 @@
use std::{collections::HashSet, hash::BuildHasherDefault};
use indexmap::IndexSet;
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use swc_atoms::{js_word, JsWord};
use swc_common::{
collections::{AHashMap, AHashSet},
SyntaxContext,
};
use swc_atoms::js_word;
use swc_common::{collections::AHashMap, SyntaxContext};
use swc_ecma_ast::*;
use swc_ecma_utils::{find_pat_ids, IsEmpty, StmtExt};
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
use swc_timer::timer;
use self::{
ctx::Ctx,
storage::{Storage, *},
};
pub use self::ctx::Ctx;
use self::storage::{Storage, *};
use crate::{
alias::{collect_infects_from, Access, AccessKind, AliasConfig},
alias::{collect_infects_from, AliasConfig},
marks::Marks,
util::can_end_conditionally,
};
mod ctx;
pub(crate) mod storage;
#[derive(Debug, Default)]
pub(crate) struct ModuleInfo {
/// Imported identifiers which should be treated as a black box.
///
/// Imports from `@swc/helpers` are excluded as helpers are not modified by
/// accessing/calling other modules.
pub blackbox_imports: AHashSet<Id>,
}
pub(crate) fn analyze<N>(n: &N, _module_info: &ModuleInfo, marks: Option<Marks>) -> ProgramData
where
N: VisitWith<UsageAnalyzer>,
{
analyze_with_storage::<ProgramData, _>(n, marks)
}
pub mod storage;
/// TODO: Track assignments to variables via `arguments`.
/// TODO: Scope-local. (Including block)
///
/// If `marks` is [None], markers are ignored.
pub(crate) fn analyze_with_storage<S, N>(n: &N, marks: Option<Marks>) -> S
pub fn analyze_with_storage<S, N>(n: &N, marks: Option<Marks>) -> S
where
S: Storage,
N: VisitWith<UsageAnalyzer<S>>,
@ -68,292 +43,21 @@ where
v.data
}
#[derive(Debug, Clone)]
pub(crate) struct VarUsageInfo {
pub inline_prevented: bool,
/// The number of direct reference to this identifier.
pub ref_count: u32,
/// `true` if a variable is conditionally initialized.
pub cond_init: bool,
/// `false` if it's only used.
pub declared: bool,
pub declared_count: u32,
/// `true` if the enclosing function defines this variable as a parameter.
pub declared_as_fn_param: bool,
pub declared_as_fn_decl: bool,
pub declared_as_fn_expr: bool,
pub assign_count: u32,
pub mutation_by_call_count: u32,
/// The number of direct and indirect reference to this identifier.
/// ## Things to note
///
/// - Update is counted as usage, but assign is not
pub usage_count: u32,
/// The variable itself is modified.
reassigned_with_assignment: bool,
reassigned_with_var_decl: bool,
/// The variable itself or a property of it is modified.
pub mutated: bool,
pub has_property_access: bool,
pub has_property_mutation: bool,
pub exported: bool,
/// True if used **above** the declaration or in init. (Not eval order).
pub used_above_decl: bool,
/// `true` if it's declared by function parameters or variables declared in
/// a closest function and used only within it and not used by child
/// functions.
pub is_fn_local: bool,
pub executed_multiple_time: bool,
pub used_in_cond: bool,
pub var_kind: Option<VarDeclKind>,
pub var_initialized: bool,
pub declared_as_catch_param: bool,
pub no_side_effect_for_member_access: bool,
pub callee_count: u32,
pub used_as_arg: bool,
pub indexed_with_dynamic_key: bool,
pub pure_fn: bool,
/// `infects_to`. This should be renamed, but it will be done with another
/// PR. (because it's hard to review)
infects: Vec<Access>,
pub used_in_non_child_fn: bool,
/// Only **string** properties.
pub accessed_props: Box<AHashMap<JsWord, u32>>,
pub used_recursively: bool,
}
impl Default for VarUsageInfo {
fn default() -> Self {
Self {
inline_prevented: Default::default(),
ref_count: Default::default(),
cond_init: Default::default(),
declared: Default::default(),
declared_count: Default::default(),
declared_as_fn_param: Default::default(),
declared_as_fn_decl: Default::default(),
declared_as_fn_expr: Default::default(),
assign_count: Default::default(),
mutation_by_call_count: Default::default(),
usage_count: Default::default(),
reassigned_with_assignment: Default::default(),
reassigned_with_var_decl: Default::default(),
mutated: Default::default(),
has_property_access: Default::default(),
has_property_mutation: Default::default(),
exported: Default::default(),
used_above_decl: Default::default(),
is_fn_local: true,
executed_multiple_time: Default::default(),
used_in_cond: Default::default(),
var_kind: Default::default(),
var_initialized: Default::default(),
declared_as_catch_param: Default::default(),
no_side_effect_for_member_access: Default::default(),
callee_count: Default::default(),
used_as_arg: Default::default(),
indexed_with_dynamic_key: Default::default(),
pure_fn: Default::default(),
infects: Default::default(),
used_in_non_child_fn: Default::default(),
accessed_props: Default::default(),
used_recursively: Default::default(),
}
}
}
impl VarUsageInfo {
pub fn is_mutated_only_by_one_call(&self) -> bool {
self.assign_count == 0 && self.mutation_by_call_count == 1
}
pub fn is_infected(&self) -> bool {
!self.infects.is_empty()
}
pub fn reassigned(&self) -> bool {
self.reassigned_with_assignment
|| self.reassigned_with_var_decl
|| (u32::from(self.var_initialized)
+ u32::from(self.declared_as_catch_param)
+ u32::from(self.declared_as_fn_param)
+ self.assign_count)
> 1
}
pub fn can_inline_var(&self) -> bool {
!self.mutated
|| (self.assign_count == 0 && !self.reassigned() && !self.has_property_mutation)
}
pub fn can_inline_fn_once(&self) -> bool {
self.callee_count > 0
|| !self.executed_multiple_time && (self.is_fn_local || !self.used_in_non_child_fn)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ScopeKind {
pub enum ScopeKind {
Fn,
Block,
}
#[derive(Debug, Default, Clone)]
pub(crate) struct ScopeData {
pub has_with_stmt: bool,
pub has_eval_call: bool,
pub used_arguments: bool,
}
#[derive(Debug, Clone)]
enum RecursiveUsage {
FnOrClass,
Var { can_ignore: bool },
}
/// Analyzed info of a whole program we are working on.
#[derive(Debug, Default)]
pub(crate) struct ProgramData {
pub vars: FxHashMap<Id, VarUsageInfo>,
pub top: ScopeData,
pub scopes: FxHashMap<SyntaxContext, ScopeData>,
initialized_vars: IndexSet<Id, ahash::RandomState>,
}
impl ProgramData {
pub(crate) fn expand_infected(
&self,
module_info: &ModuleInfo,
ids: FxHashSet<Access>,
max_num: usize,
) -> Result<FxHashSet<Access>, ()> {
let init =
HashSet::with_capacity_and_hasher(max_num, BuildHasherDefault::<FxHasher>::default());
ids.into_iter().try_fold(init, |mut res, id| {
let mut ids = Vec::with_capacity(max_num);
ids.push(id);
let mut ranges = vec![0..1usize];
loop {
let range = ranges.remove(0);
for index in range {
let iid = ids.get(index).unwrap();
// Abort on imported variables, because we can't analyze them
if module_info.blackbox_imports.contains(&iid.0) {
return Err(());
}
if !res.insert(iid.clone()) {
continue;
}
if res.len() >= max_num {
return Err(());
}
if let Some(info) = self.vars.get(&iid.0) {
let infects = &info.infects;
if !infects.is_empty() {
let old_len = ids.len();
// This is not a call, so effects from call can be skipped
let can_skip_non_call = matches!(iid.1, AccessKind::Reference)
|| (info.declared_count == 1
&& info.declared_as_fn_decl
&& !info.reassigned());
if can_skip_non_call {
ids.extend(
infects
.iter()
.filter(|(_, kind)| *kind != AccessKind::Call)
.cloned(),
);
} else {
ids.extend_from_slice(infects.as_slice());
}
let new_len = ids.len();
ranges.push(old_len..new_len);
}
}
}
if ranges.is_empty() {
break;
}
}
Ok(res)
})
}
pub(crate) fn contains_unresolved(&self, e: &Expr) -> bool {
match e {
Expr::Ident(i) => {
if let Some(v) = self.vars.get(&i.to_id()) {
return !v.declared;
}
true
}
Expr::Member(MemberExpr { obj, prop, .. }) => {
if self.contains_unresolved(obj) {
return true;
}
if let MemberProp::Computed(prop) = prop {
if self.contains_unresolved(&prop.expr) {
return true;
}
}
false
}
Expr::Call(CallExpr {
callee: Callee::Expr(callee),
args,
..
}) => {
if self.contains_unresolved(callee) {
return true;
}
if args.iter().any(|arg| self.contains_unresolved(&arg.expr)) {
return true;
}
false
}
_ => false,
}
}
}
/// This assumes there are no two variable with same name and same span hygiene.
#[derive(Debug)]
pub(crate) struct UsageAnalyzer<S = ProgramData>
pub struct UsageAnalyzer<S>
where
S: Storage,
{
@ -1504,10 +1208,8 @@ where
} = e
{
// TODO: merge with may_have_side_effects
let can_ignore = match &**init {
Expr::Call(call) if call.span.has_mark(marks.pure) => true,
_ => false,
};
let can_ignore =
matches!(&**init, Expr::Call(call) if call.span.has_mark(marks.pure));
let id = id.to_id();
self.used_recursively
.insert(id.clone(), RecursiveUsage::Var { can_ignore });

View File

@ -5,9 +5,7 @@ use swc_ecma_ast::*;
use super::{ctx::Ctx, ScopeKind};
use crate::alias::Access;
pub mod normal;
pub(crate) trait Storage: Sized + Default {
pub trait Storage: Sized + Default {
type ScopeData: ScopeDataLike;
type VarData: VarDataLike;
@ -33,7 +31,7 @@ pub(crate) trait Storage: Sized + Default {
fn truncate_initialized_cnt(&mut self, len: usize);
}
pub(crate) trait ScopeDataLike: Sized + Default + Clone {
pub trait ScopeDataLike: Sized + Default + Clone {
fn add_declared_symbol(&mut self, id: &Ident);
fn merge(&mut self, other: Self, is_child: bool);
@ -45,7 +43,7 @@ pub(crate) trait ScopeDataLike: Sized + Default + Clone {
fn mark_with_stmt(&mut self);
}
pub(crate) trait VarDataLike: Sized {
pub trait VarDataLike: Sized {
/// See `declared_as_fn_param` of [crate::analyzer::VarUsageInfo].
fn mark_declared_as_fn_param(&mut self);

View File

@ -0,0 +1,4 @@
pub mod alias;
pub mod analyzer;
pub mod marks;
pub mod util;

View File

@ -9,13 +9,13 @@ pub struct Marks {
///
/// In other words, AST nodes marked with this mark will not be treated as a
/// top level item, even if it's in the top level scope.
pub(crate) non_top_level: Mark,
pub non_top_level: Mark,
/// Indicates that a sequence expression is generated by the minifier.
///
/// This is required because `sequences` option is ignored for synthesized
/// sequences.
pub(crate) synthesized_seq: Mark,
pub synthesized_seq: Mark,
/// Treat this function as a top level module.
///
@ -29,25 +29,25 @@ pub struct Marks {
///
/// This is only applied to [swc_ecma_ast::Function] and it should not be
/// nested.
pub(crate) standalone: Mark,
pub standalone: Mark,
//// Applied to [swc_ecma_ast::Module].
pub(crate) bundle_of_standalone: Mark,
pub bundle_of_standalone: Mark,
/// `/** @const */`.
pub(crate) const_ann: Mark,
pub const_ann: Mark,
/// Check for `/*#__NOINLINE__*/`
pub(crate) noinline: Mark,
pub noinline: Mark,
/// Check for `/*#__PURE__*/`
pub(crate) pure: Mark,
pub pure: Mark,
/// This is applied to [swc_ecma_ast::BlockStmt] which is injected to
/// preserve the side effects.
pub(crate) fake_block: Mark,
pub fake_block: Mark,
pub(crate) unresolved_mark: Mark,
pub unresolved_mark: Mark,
}
impl Marks {

View File

@ -0,0 +1,90 @@
use swc_atoms::{js_word, JsWord};
use swc_ecma_ast::Stmt;
pub fn is_global_var_with_pure_property_access(s: &JsWord) -> bool {
match *s {
js_word!("JSON")
| js_word!("Array")
| js_word!("String")
| js_word!("Object")
| js_word!("Number")
| js_word!("Date")
| js_word!("BigInt")
| js_word!("Boolean")
| js_word!("Math")
| js_word!("Error") => return true,
_ => {}
}
matches!(
&**s,
"console"
| "clearInterval"
| "clearTimeout"
| "setInterval"
| "setTimeout"
| "btoa"
| "decodeURI"
| "decodeURIComponent"
| "encodeURI"
| "encodeURIComponent"
| "escape"
| "eval"
| "EvalError"
| "Function"
| "isFinite"
| "isNaN"
| "JSON"
| "parseFloat"
| "parseInt"
| "RegExp"
| "RangeError"
| "ReferenceError"
| "SyntaxError"
| "TypeError"
| "unescape"
| "URIError"
| "atob"
| "globalThis"
| "NaN"
| "Symbol"
| "Promise"
)
}
pub fn can_end_conditionally(s: &Stmt) -> bool {
///
///`ignore_always`: If true, [Stmt::Return] will be ignored.
fn can_end(s: &Stmt, ignore_always: bool) -> bool {
match s {
Stmt::If(s) => {
can_end(&s.cons, false)
|| s.alt
.as_deref()
.map(|s| can_end(s, false))
.unwrap_or_default()
}
Stmt::Switch(s) => s
.cases
.iter()
.any(|case| case.cons.iter().any(|s| can_end(s, false))),
Stmt::DoWhile(s) => can_end(&s.body, false),
Stmt::While(s) => can_end(&s.body, false),
Stmt::For(s) => can_end(&s.body, false),
Stmt::ForOf(s) => can_end(&s.body, false),
Stmt::ForIn(s) => can_end(&s.body, false),
Stmt::Return(..) | Stmt::Break(..) | Stmt::Continue(..) => !ignore_always,
_ => false,
}
}
can_end(s, true)
}