Skip to content

Commit e1d01d8

Browse files
authoredDec 7, 2022
feat(es/analyzer): Extract the analyzer from the minifier to a separate crate (#6586)
1 parent da5e18e commit e1d01d8

File tree

27 files changed

+633
-562
lines changed

27 files changed

+633
-562
lines changed
 

‎Cargo.lock

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/swc_core/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ ecma_minifier = ["__ecma", "swc_ecma_minifier"]
146146
# Enable swc_ecma_preset_env
147147
ecma_preset_env = ["__ecma", "swc_ecma_preset_env"]
148148

149+
# Enable swc_ecma_usage_analyzer
150+
ecma_usage_analyzer = ["__ecma", "swc_ecma_usage_analyzer"]
151+
149152
# Enable swc_css
150153
css_ast = ["__css", "swc_css_ast"]
151154
css_codegen = ["__css", "swc_css_codegen"]
@@ -366,6 +369,7 @@ swc_ecma_transforms_proposal = { optional = true, version = "0.145.5", path
366369
swc_ecma_transforms_react = { optional = true, version = "0.156.5", path = "../swc_ecma_transforms_react" }
367370
swc_ecma_transforms_testing = { optional = true, version = "0.115.6", path = "../swc_ecma_transforms_testing" }
368371
swc_ecma_transforms_typescript = { optional = true, version = "0.160.5", path = "../swc_ecma_transforms_typescript" }
372+
swc_ecma_usage_analyzer = { optional = true, version = "0.1.0", path = "../swc_ecma_usage_analyzer" }
369373
swc_ecma_utils = { optional = true, version = "0.106.5", path = "../swc_ecma_utils" }
370374
swc_ecma_visit = { optional = true, version = "0.81.3", path = "../swc_ecma_visit" }
371375
swc_node_base = { optional = true, version = "0.5.8", path = "../swc_node_base" }

‎crates/swc_core/src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ pub mod ecma {
109109
pub use swc_ecma_preset_env::*;
110110
}
111111

112+
#[cfg(feature = "ecma_usage_analyzer")]
113+
#[cfg_attr(docsrs, doc(cfg(feature = "ecma_usage_analyzer")))]
114+
pub mod usage_analyzer {
115+
pub use swc_ecma_usage_analyzer::*;
116+
}
117+
112118
// visit* interfaces
113119
#[cfg(feature = "__visit")]
114120
#[cfg_attr(docsrs, doc(cfg(feature = "__visit")))]

‎crates/swc_ecma_minifier/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ swc_ecma_codegen = { version = "0.128.5", path = "../swc_ecma_co
5454
swc_ecma_parser = { version = "0.123.5", path = "../swc_ecma_parser" }
5555
swc_ecma_transforms_base = { version = "0.112.5", path = "../swc_ecma_transforms_base" }
5656
swc_ecma_transforms_optimization = { version = "0.168.5", path = "../swc_ecma_transforms_optimization" }
57+
swc_ecma_usage_analyzer = { version = "0.1.0", path = "../swc_ecma_usage_analyzer" }
5758
swc_ecma_utils = { version = "0.106.5", path = "../swc_ecma_utils" }
5859
swc_ecma_visit = { version = "0.81.3", path = "../swc_ecma_visit" }
5960
swc_timer = { version = "0.17.20", path = "../swc_timer" }

‎crates/swc_ecma_minifier/src/compress/hoist_decls.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
use rayon::prelude::*;
33
use swc_common::{collections::AHashSet, pass::Repeated, util::take::Take, DUMMY_SP};
44
use swc_ecma_ast::*;
5+
use swc_ecma_usage_analyzer::analyzer::UsageAnalyzer;
56
use swc_ecma_utils::{find_pat_ids, StmtLike};
67
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
78

89
use super::util::drop_invalid_stmts;
910
use crate::{
10-
analyzer::{ProgramData, UsageAnalyzer},
11+
program_data::ProgramData,
1112
util::{is_hoisted_var_decl_without_init, sort::is_sorted_by, IsModuleItem, ModuleItemExt},
1213
};
1314

@@ -45,7 +46,7 @@ impl Hoister<'_> {
4546
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
4647
where
4748
T: StmtLike + IsModuleItem + ModuleItemExt,
48-
Vec<T>: for<'aa> VisitMutWith<Hoister<'aa>> + VisitWith<UsageAnalyzer>,
49+
Vec<T>: for<'aa> VisitMutWith<Hoister<'aa>> + VisitWith<UsageAnalyzer<ProgramData>>,
4950
{
5051
stmts.visit_mut_children_with(self);
5152
let len = stmts.len();

‎crates/swc_ecma_minifier/src/compress/mod.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,19 @@ use swc_ecma_ast::*;
1717
use swc_ecma_transforms_optimization::simplify::{
1818
dead_branch_remover, expr_simplifier, ExprSimplifierConfig,
1919
};
20+
use swc_ecma_usage_analyzer::{analyzer::UsageAnalyzer, marks::Marks};
2021
use swc_ecma_visit::{as_folder, noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
2122
use swc_timer::timer;
2223
use tracing::{debug, error};
2324

2425
pub(crate) use self::pure::{pure_optimizer, PureOptimizerConfig};
2526
use self::{hoist_decls::DeclHoisterConfig, optimize::optimizer};
2627
use crate::{
27-
analyzer::{analyze, ModuleInfo, UsageAnalyzer},
2828
compress::hoist_decls::decl_hoister,
2929
debug::{dump, AssertValid},
30-
marks::Marks,
3130
mode::Mode,
3231
option::CompressOptions,
32+
program_data::{analyze, ModuleInfo, ProgramData},
3333
util::{now, unit::CompileUnit},
3434
};
3535

@@ -100,7 +100,7 @@ where
100100
fn optimize_unit_repeatedly<N>(&mut self, n: &mut N)
101101
where
102102
N: CompileUnit
103-
+ VisitWith<UsageAnalyzer>
103+
+ VisitWith<UsageAnalyzer<ProgramData>>
104104
+ for<'aa> VisitMutWith<Compressor<'aa, M>>
105105
+ VisitWith<AssertValid>,
106106
{
@@ -147,7 +147,7 @@ where
147147
fn optimize_unit<N>(&mut self, n: &mut N)
148148
where
149149
N: CompileUnit
150-
+ VisitWith<UsageAnalyzer>
150+
+ VisitWith<UsageAnalyzer<ProgramData>>
151151
+ for<'aa> VisitMutWith<Compressor<'aa, M>>
152152
+ VisitWith<AssertValid>,
153153
{

‎crates/swc_ecma_minifier/src/compress/optimize/inline.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ use swc_atoms::js_word;
22
use swc_common::{util::take::Take, EqIgnoreSpan, Spanned};
33
use swc_ecma_ast::*;
44
use swc_ecma_transforms_optimization::simplify::expr_simplifier;
5+
use swc_ecma_usage_analyzer::alias::{collect_infects_from, AliasConfig};
56
use swc_ecma_utils::{class_has_side_effect, find_pat_ids, ExprExt};
67
use swc_ecma_visit::VisitMutWith;
78

89
use super::Optimizer;
910
use crate::{
10-
alias::{collect_infects_from, AliasConfig},
11-
analyzer::VarUsageInfo,
1211
compress::optimize::util::is_valid_for_lhs,
1312
mode::Mode,
13+
program_data::VarUsageInfo,
1414
util::{
1515
idents_captured_by, idents_used_by, idents_used_by_ignoring_nested, size::SizeWithCtxt,
1616
},

‎crates/swc_ecma_minifier/src/compress/optimize/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use swc_common::{
88
};
99
use swc_ecma_ast::*;
1010
use swc_ecma_transforms_optimization::debug_assert_valid;
11+
use swc_ecma_usage_analyzer::{analyzer::UsageAnalyzer, marks::Marks};
1112
use swc_ecma_utils::{
1213
prepend_stmts, undefined, ExprCtx, ExprExt, ExprFactory, IsEmpty, ModuleItemLike, StmtLike,
1314
Type, Value,
@@ -25,13 +26,12 @@ use super::util::{drop_invalid_stmts, is_fine_for_if_cons};
2526
#[cfg(feature = "debug")]
2627
use crate::debug::dump;
2728
use crate::{
28-
analyzer::{ModuleInfo, ProgramData, UsageAnalyzer},
2929
compress::util::is_pure_undefined,
3030
debug::AssertValid,
31-
marks::Marks,
3231
maybe_par,
3332
mode::Mode,
3433
option::CompressOptions,
34+
program_data::{ModuleInfo, ProgramData},
3535
util::{
3636
contains_eval, contains_leaping_continue_with_label, make_number, ExprOptExt, ModuleItemExt,
3737
},
@@ -332,7 +332,7 @@ where
332332
fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
333333
where
334334
T: StmtLike + ModuleItemLike + ModuleItemExt + VisitMutWith<Self> + VisitWith<AssertValid>,
335-
Vec<T>: VisitMutWith<Self> + VisitWith<UsageAnalyzer> + VisitWith<AssertValid>,
335+
Vec<T>: VisitMutWith<Self> + VisitWith<UsageAnalyzer<ProgramData>> + VisitWith<AssertValid>,
336336
{
337337
let mut use_asm = false;
338338
let prepend_stmts = self.prepend_stmts.take();

‎crates/swc_ecma_minifier/src/compress/optimize/sequences.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ use std::mem::take;
33
use swc_atoms::js_word;
44
use swc_common::{util::take::Take, Spanned, DUMMY_SP};
55
use swc_ecma_ast::*;
6+
use swc_ecma_usage_analyzer::{
7+
alias::{collect_infects_from, AccessKind, AliasConfig},
8+
util::is_global_var_with_pure_property_access,
9+
};
610
use swc_ecma_utils::{
711
contains_arguments, contains_this_expr, prepend_stmts, undefined, ExprExt, IdentUsageFinder,
812
StmtLike,
@@ -15,17 +19,13 @@ use super::{is_pure_undefined, Optimizer};
1519
#[cfg(feature = "debug")]
1620
use crate::debug::dump;
1721
use crate::{
18-
alias::{collect_infects_from, AccessKind, AliasConfig},
1922
compress::{
2023
optimize::{unused::PropertyAccessOpts, util::replace_id_with_expr},
2124
util::{is_directive, is_ident_used_by, replace_expr},
2225
},
2326
mode::Mode,
2427
option::CompressOptions,
25-
util::{
26-
idents_used_by, idents_used_by_ignoring_nested, is_global_var_with_pure_property_access,
27-
ExprOptExt, ModuleItemExt,
28-
},
28+
util::{idents_used_by, idents_used_by_ignoring_nested, ExprOptExt, ModuleItemExt},
2929
};
3030

3131
/// Methods related to the option `sequences`. All methods are noop if
@@ -1170,8 +1170,8 @@ where
11701170
.expand_infected(self.module_info, ids_used_by_a_init, 64);
11711171

11721172
let deps = match deps {
1173-
Ok(v) => v,
1174-
Err(()) => return false,
1173+
Some(v) => v,
1174+
_ => return false,
11751175
};
11761176
if deps.contains(&(e.to_id(), AccessKind::Reference))
11771177
|| deps.contains(&(e.to_id(), AccessKind::Call))

‎crates/swc_ecma_minifier/src/compress/optimize/unused.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use swc_atoms::js_word;
22
use swc_common::{util::take::Take, Span, DUMMY_SP};
33
use swc_ecma_ast::*;
4+
use swc_ecma_usage_analyzer::util::is_global_var_with_pure_property_access;
45
use swc_ecma_utils::contains_ident_ref;
56

67
use super::Optimizer;
78
#[cfg(feature = "debug")]
89
use crate::debug::dump;
910
use crate::{
1011
compress::optimize::util::extract_class_side_effect, mode::Mode, option::PureGetterOption,
11-
util::is_global_var_with_pure_property_access,
1212
};
1313

1414
#[derive(Debug, Default, Clone, Copy)]

‎crates/swc_ecma_minifier/src/compress/pure/misc.rs

+4-7
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,17 @@ use swc_atoms::js_word;
44
use swc_common::{iter::IdentifyLast, util::take::Take, Span, DUMMY_SP};
55
use swc_ecma_ast::*;
66
use swc_ecma_transforms_optimization::debug_assert_valid;
7+
use swc_ecma_usage_analyzer::util::is_global_var_with_pure_property_access;
78
use swc_ecma_utils::{
89
ExprExt, ExprFactory, IdentUsageFinder, Type,
910
Value::{self, Known},
1011
};
1112

1213
use super::Pure;
13-
use crate::{
14-
compress::{
15-
pure::strings::{convert_str_value_to_tpl_cooked, convert_str_value_to_tpl_raw},
16-
util::is_pure_undefined,
17-
},
18-
util::is_global_var_with_pure_property_access,
14+
use crate::compress::{
15+
pure::strings::{convert_str_value_to_tpl_cooked, convert_str_value_to_tpl_raw},
16+
util::is_pure_undefined,
1917
};
20-
2118
impl Pure<'_> {
2219
pub(super) fn remove_invalid(&mut self, e: &mut Expr) {
2320
match e {

‎crates/swc_ecma_minifier/src/compress/pure/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use rayon::prelude::*;
55
use swc_common::{pass::Repeated, util::take::Take, SyntaxContext, DUMMY_SP, GLOBALS};
66
use swc_ecma_ast::*;
77
use swc_ecma_transforms_optimization::debug_assert_valid;
8+
use swc_ecma_usage_analyzer::marks::Marks;
89
use swc_ecma_utils::{undefined, ExprCtx};
910
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
1011
#[cfg(feature = "debug")]
@@ -14,7 +15,7 @@ use self::{ctx::Ctx, misc::DropOpts};
1415
#[cfg(feature = "debug")]
1516
use crate::debug::dump;
1617
use crate::{
17-
analyzer::ProgramData, debug::AssertValid, marks::Marks, maybe_par, option::CompressOptions,
18+
debug::AssertValid, maybe_par, option::CompressOptions, program_data::ProgramData,
1819
util::ModuleItemExt,
1920
};
2021

‎crates/swc_ecma_minifier/src/eval.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ use swc_atoms::js_word;
55
use swc_common::{collections::AHashMap, SyntaxContext, DUMMY_SP};
66
use swc_ecma_ast::*;
77
use swc_ecma_transforms_optimization::simplify::{expr_simplifier, ExprSimplifierConfig};
8+
use swc_ecma_usage_analyzer::marks::Marks;
89
use swc_ecma_utils::{undefined, ExprCtx, ExprExt};
910
use swc_ecma_visit::VisitMutWith;
1011

1112
use crate::{
1213
compress::{compressor, pure_optimizer, PureOptimizerConfig},
13-
marks::Marks,
1414
mode::Mode,
1515
};
1616

‎crates/swc_ecma_minifier/src/lib.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,13 @@ use once_cell::sync::Lazy;
4141
use swc_common::{comments::Comments, pass::Repeated, sync::Lrc, SourceMap, SyntaxContext};
4242
use swc_ecma_ast::*;
4343
use swc_ecma_transforms_optimization::debug_assert_valid;
44+
use swc_ecma_usage_analyzer::marks::Marks;
4445
use swc_ecma_visit::VisitMutWith;
4546
use swc_timer::timer;
4647

4748
pub use crate::pass::unique_scope::unique_scope;
4849
use crate::{
49-
analyzer::ModuleInfo,
5050
compress::{compressor, pure_optimizer, PureOptimizerConfig},
51-
marks::Marks,
5251
metadata::info_marker,
5352
mode::{Minification, Mode},
5453
option::{CompressOptions, ExtraOptions, MinifyOptions},
@@ -61,25 +60,28 @@ use crate::{
6160
postcompress::postcompress_optimizer,
6261
precompress::precompress_optimizer,
6362
},
63+
program_data::ModuleInfo,
6464
timing::Timings,
6565
util::base54::CharFreq,
6666
};
6767

6868
#[macro_use]
6969
mod macros;
70-
mod alias;
71-
mod analyzer;
7270
mod compress;
7371
mod debug;
7472
pub mod eval;
75-
pub mod marks;
7673
mod metadata;
7774
mod mode;
7875
pub mod option;
7976
mod pass;
77+
mod program_data;
8078
pub mod timing;
8179
mod util;
8280

81+
pub mod marks {
82+
pub use swc_ecma_usage_analyzer::marks::Marks;
83+
}
84+
8385
const DISABLE_BUGGY_PASSES: bool = true;
8486

8587
pub(crate) static CPU_COUNT: Lazy<usize> = Lazy::new(num_cpus::get);

‎crates/swc_ecma_minifier/src/metadata/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ use swc_common::{
33
EqIgnoreSpan, Span, SyntaxContext,
44
};
55
use swc_ecma_ast::*;
6+
use swc_ecma_usage_analyzer::marks::Marks;
67
use swc_ecma_utils::find_pat_ids;
78
use swc_ecma_visit::{
89
noop_visit_mut_type, noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith,
910
};
1011

11-
use crate::{marks::Marks, option::CompressOptions};
12+
use crate::option::CompressOptions;
1213

1314
#[cfg(test)]
1415
mod tests;

‎crates/swc_ecma_minifier/src/pass/mangle_props.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use swc_ecma_ast::{
1010
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
1111

1212
use crate::{
13-
analyzer::{analyze, ModuleInfo, ProgramData},
1413
option::ManglePropertiesOptions,
14+
program_data::{analyze, ModuleInfo, ProgramData},
1515
util::base54::Base54Chars,
1616
};
1717

‎crates/swc_ecma_minifier/src/analyzer/storage/normal.rs ‎crates/swc_ecma_minifier/src/program_data.rs

+383-78
Large diffs are not rendered by default.

‎crates/swc_ecma_minifier/src/util/mod.rs

+1-89
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::time::Instant;
22

33
use rustc_hash::FxHashSet;
4-
use swc_atoms::{js_word, JsWord};
4+
use swc_atoms::js_word;
55
use swc_common::{util::take::Take, Mark, Span, Spanned, DUMMY_SP};
66
use swc_ecma_ast::*;
77
use swc_ecma_utils::{ModuleItemLike, StmtLike, Value};
@@ -12,57 +12,6 @@ pub(crate) mod size;
1212
pub(crate) mod sort;
1313
pub(crate) mod unit;
1414

15-
pub(crate) fn is_global_var_with_pure_property_access(s: &JsWord) -> bool {
16-
match *s {
17-
js_word!("JSON")
18-
| js_word!("Array")
19-
| js_word!("String")
20-
| js_word!("Object")
21-
| js_word!("Number")
22-
| js_word!("Date")
23-
| js_word!("BigInt")
24-
| js_word!("Boolean")
25-
| js_word!("Math")
26-
| js_word!("Error") => return true,
27-
_ => {}
28-
}
29-
30-
matches!(
31-
&**s,
32-
"console"
33-
| "clearInterval"
34-
| "clearTimeout"
35-
| "setInterval"
36-
| "setTimeout"
37-
| "btoa"
38-
| "decodeURI"
39-
| "decodeURIComponent"
40-
| "encodeURI"
41-
| "encodeURIComponent"
42-
| "escape"
43-
| "eval"
44-
| "EvalError"
45-
| "Function"
46-
| "isFinite"
47-
| "isNaN"
48-
| "JSON"
49-
| "parseFloat"
50-
| "parseInt"
51-
| "RegExp"
52-
| "RangeError"
53-
| "ReferenceError"
54-
| "SyntaxError"
55-
| "TypeError"
56-
| "unescape"
57-
| "URIError"
58-
| "atob"
59-
| "globalThis"
60-
| "NaN"
61-
| "Symbol"
62-
| "Promise"
63-
)
64-
}
65-
6615
///
6716
pub(crate) fn make_number(span: Span, value: f64) -> Expr {
6817
trace_op!("Creating a numeric literal");
@@ -504,43 +453,6 @@ where
504453
v.ids
505454
}
506455

507-
pub(crate) fn can_end_conditionally(s: &Stmt) -> bool {
508-
///
509-
///`ignore_always`: If true, [Stmt::Return] will be ignored.
510-
fn can_end(s: &Stmt, ignore_always: bool) -> bool {
511-
match s {
512-
Stmt::If(s) => {
513-
can_end(&s.cons, false)
514-
|| s.alt
515-
.as_deref()
516-
.map(|s| can_end(s, false))
517-
.unwrap_or_default()
518-
}
519-
520-
Stmt::Switch(s) => s
521-
.cases
522-
.iter()
523-
.any(|case| case.cons.iter().any(|s| can_end(s, false))),
524-
525-
Stmt::DoWhile(s) => can_end(&s.body, false),
526-
527-
Stmt::While(s) => can_end(&s.body, false),
528-
529-
Stmt::For(s) => can_end(&s.body, false),
530-
531-
Stmt::ForOf(s) => can_end(&s.body, false),
532-
533-
Stmt::ForIn(s) => can_end(&s.body, false),
534-
535-
Stmt::Return(..) | Stmt::Break(..) | Stmt::Continue(..) => !ignore_always,
536-
537-
_ => false,
538-
}
539-
}
540-
541-
can_end(s, true)
542-
}
543-
544456
pub fn now() -> Option<Instant> {
545457
#[cfg(target_arch = "wasm32")]
546458
{
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[package]
2+
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
3+
description = "EcmaScript variable usage analyzer"
4+
documentation = "https://rustdoc.swc.rs/swc_ecma_usage_analyzer/"
5+
edition = "2021"
6+
include = ["Cargo.toml", "src/**/*.rs"]
7+
license = "Apache-2.0"
8+
name = "swc_ecma_usage_analyzer"
9+
repository = "https://github.com/swc-project/swc.git"
10+
version = "0.1.0"
11+
12+
[package.metadata.docs.rs]
13+
all-features = true
14+
rustdoc-args = ["--cfg", "docsrs"]
15+
16+
[lib]
17+
bench = false
18+
19+
[features]
20+
# This enables global concurrent mode
21+
concurrent = ["swc_common/concurrent", "indexmap/rayon"]
22+
trace-ast = []
23+
24+
[dependencies]
25+
ahash = "0.7.6"
26+
indexmap = "1.6.1"
27+
rustc-hash = "1.1.0"
28+
swc_atoms = { version = "0.4.25", path = "../swc_atoms" }
29+
swc_common = { version = "0.29.19", path = "../swc_common" }
30+
swc_ecma_ast = { version = "0.95.3", path = "../swc_ecma_ast" }
31+
swc_ecma_utils = { version = "0.106.5", path = "../swc_ecma_utils" }
32+
swc_ecma_visit = { version = "0.81.3", path = "../swc_ecma_visit" }
33+
swc_timer = { version = "0.17.20", path = "../swc_timer" }
34+
tracing = "0.1.32"

‎crates/swc_ecma_minifier/src/alias/ctx.rs ‎crates/swc_ecma_usage_analyzer/src/alias/ctx.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ impl<'a> InfectionCollector<'a> {
1515
}
1616

1717
#[derive(Debug, Default, Clone, Copy)]
18-
pub(super) struct Ctx {
18+
pub struct Ctx {
1919
pub track_expr_ident: bool,
2020
pub is_callee: bool,
2121
}

‎crates/swc_ecma_minifier/src/alias/mod.rs ‎crates/swc_ecma_usage_analyzer/src/alias/mod.rs

+10-15
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@ use swc_ecma_ast::*;
66
use swc_ecma_utils::{collect_decls, BindingCollector};
77
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
88

9-
use self::ctx::Ctx;
9+
pub use self::ctx::Ctx;
1010
use crate::{marks::Marks, util::is_global_var_with_pure_property_access};
1111

1212
mod ctx;
1313

1414
#[derive(Default)]
15-
pub(crate) struct AliasConfig {
15+
pub struct AliasConfig {
1616
pub marks: Option<Marks>,
1717
pub ignore_nested: bool,
1818
/// TODO(kdy1): This field is used for sequential inliner.
1919
/// It should be renamed to some correct name.
2020
pub need_all: bool,
2121
}
2222

23-
pub(crate) trait InfectableNode {
23+
pub trait InfectableNode {
2424
fn is_fn_or_arrow_expr(&self) -> bool;
2525
}
2626

@@ -32,10 +32,7 @@ impl InfectableNode for Function {
3232

3333
impl InfectableNode for Expr {
3434
fn is_fn_or_arrow_expr(&self) -> bool {
35-
match self {
36-
Expr::Arrow(..) | Expr::Fn(..) => true,
37-
_ => false,
38-
}
35+
matches!(self, Expr::Arrow(..) | Expr::Fn(..))
3936
}
4037
}
4138

@@ -50,14 +47,14 @@ where
5047

5148
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
5249

53-
pub(crate) enum AccessKind {
50+
pub enum AccessKind {
5451
Reference,
5552
Call,
5653
}
5754

58-
pub(crate) type Access = (Id, AccessKind);
55+
pub type Access = (Id, AccessKind);
5956

60-
pub(crate) fn collect_infects_from<N>(node: &N, config: AliasConfig) -> FxHashSet<Access>
57+
pub fn collect_infects_from<N>(node: &N, config: AliasConfig) -> FxHashSet<Access>
6158
where
6259
N: InfectableNode
6360
+ VisitWith<BindingCollector<Id>>
@@ -89,7 +86,7 @@ where
8986
visitor.accesses
9087
}
9188

92-
pub(crate) struct InfectionCollector<'a> {
89+
pub struct InfectionCollector<'a> {
9390
#[allow(unused)]
9491
config: AliasConfig,
9592
unresolved_ctxt: Option<SyntaxContext>,
@@ -107,10 +104,8 @@ impl InfectionCollector<'_> {
107104
return;
108105
}
109106

110-
if self.unresolved_ctxt == Some(e.1) {
111-
if is_global_var_with_pure_property_access(&e.0) {
112-
return;
113-
}
107+
if self.unresolved_ctxt == Some(e.1) && is_global_var_with_pure_property_access(&e.0) {
108+
return;
114109
}
115110

116111
self.accesses.insert((

‎crates/swc_ecma_minifier/src/analyzer/ctx.rs ‎crates/swc_ecma_usage_analyzer/src/analyzer/ctx.rs

+21-21
Original file line numberDiff line numberDiff line change
@@ -21,45 +21,45 @@ where
2121
}
2222

2323
#[derive(Debug, Default, Clone, Copy)]
24-
pub(crate) struct Ctx {
24+
pub struct Ctx {
2525
/// See [crate::marks::Marks]
26-
pub(super) skip_standalone: bool,
26+
pub skip_standalone: bool,
2727

28-
pub(super) var_decl_kind_of_pat: Option<VarDeclKind>,
28+
pub var_decl_kind_of_pat: Option<VarDeclKind>,
2929

30-
pub(super) in_decl_with_no_side_effect_for_member_access: bool,
30+
pub in_decl_with_no_side_effect_for_member_access: bool,
3131

32-
pub(super) in_pat_of_var_decl: bool,
33-
pub(super) in_pat_of_var_decl_with_init: bool,
34-
pub(super) in_pat_of_param: bool,
35-
pub(super) in_catch_param: bool,
32+
pub in_pat_of_var_decl: bool,
33+
pub in_pat_of_var_decl_with_init: bool,
34+
pub in_pat_of_param: bool,
35+
pub in_catch_param: bool,
3636
/// `true` for `foo.bar` and `false` for `foo` in `foo.bar++`
37-
pub(super) is_exact_reassignment: bool,
37+
pub is_exact_reassignment: bool,
3838

39-
pub(super) is_callee: bool,
39+
pub is_callee: bool,
4040

4141
/// `true` for arguments of [swc_ecma_ast::Expr::Call] or
4242
/// [swc_ecma_ast::Expr::New]
43-
pub(super) in_call_arg: bool,
43+
pub in_call_arg: bool,
4444

4545
/// `false` for `array` in `array.length.
46-
pub(super) is_exact_arg: bool,
46+
pub is_exact_arg: bool,
4747

48-
pub(super) in_await_arg: bool,
48+
pub in_await_arg: bool,
4949

50-
pub(super) is_delete_arg: bool,
50+
pub is_delete_arg: bool,
5151

52-
pub(super) in_left_of_for_loop: bool,
52+
pub in_left_of_for_loop: bool,
5353

54-
pub(super) executed_multiple_time: bool,
54+
pub executed_multiple_time: bool,
5555
/// Are we handling argument of an update expression.
56-
pub(super) in_update_arg: bool,
57-
pub(super) in_assign_lhs: bool,
58-
pub(super) in_cond: bool,
56+
pub in_update_arg: bool,
57+
pub in_assign_lhs: bool,
58+
pub in_cond: bool,
5959

60-
pub(super) inline_prevented: bool,
60+
pub inline_prevented: bool,
6161

62-
pub(super) is_op_assign: bool,
62+
pub is_op_assign: bool,
6363
}
6464

6565
pub(super) struct WithCtx<'a, S>

‎crates/swc_ecma_minifier/src/analyzer/mod.rs ‎crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs

+11-309
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,26 @@
1-
use std::{collections::HashSet, hash::BuildHasherDefault};
2-
3-
use indexmap::IndexSet;
4-
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
5-
use swc_atoms::{js_word, JsWord};
6-
use swc_common::{
7-
collections::{AHashMap, AHashSet},
8-
SyntaxContext,
9-
};
1+
use swc_atoms::js_word;
2+
use swc_common::{collections::AHashMap, SyntaxContext};
103
use swc_ecma_ast::*;
114
use swc_ecma_utils::{find_pat_ids, IsEmpty, StmtExt};
125
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
136
use swc_timer::timer;
147

15-
use self::{
16-
ctx::Ctx,
17-
storage::{Storage, *},
18-
};
8+
pub use self::ctx::Ctx;
9+
use self::storage::{Storage, *};
1910
use crate::{
20-
alias::{collect_infects_from, Access, AccessKind, AliasConfig},
11+
alias::{collect_infects_from, AliasConfig},
2112
marks::Marks,
2213
util::can_end_conditionally,
2314
};
2415

2516
mod ctx;
26-
pub(crate) mod storage;
27-
28-
#[derive(Debug, Default)]
29-
pub(crate) struct ModuleInfo {
30-
/// Imported identifiers which should be treated as a black box.
31-
///
32-
/// Imports from `@swc/helpers` are excluded as helpers are not modified by
33-
/// accessing/calling other modules.
34-
pub blackbox_imports: AHashSet<Id>,
35-
}
36-
37-
pub(crate) fn analyze<N>(n: &N, _module_info: &ModuleInfo, marks: Option<Marks>) -> ProgramData
38-
where
39-
N: VisitWith<UsageAnalyzer>,
40-
{
41-
analyze_with_storage::<ProgramData, _>(n, marks)
42-
}
17+
pub mod storage;
4318

4419
/// TODO: Track assignments to variables via `arguments`.
4520
/// TODO: Scope-local. (Including block)
4621
///
4722
/// If `marks` is [None], markers are ignored.
48-
pub(crate) fn analyze_with_storage<S, N>(n: &N, marks: Option<Marks>) -> S
23+
pub fn analyze_with_storage<S, N>(n: &N, marks: Option<Marks>) -> S
4924
where
5025
S: Storage,
5126
N: VisitWith<UsageAnalyzer<S>>,
@@ -68,292 +43,21 @@ where
6843
v.data
6944
}
7045

71-
#[derive(Debug, Clone)]
72-
pub(crate) struct VarUsageInfo {
73-
pub inline_prevented: bool,
74-
75-
/// The number of direct reference to this identifier.
76-
pub ref_count: u32,
77-
78-
/// `true` if a variable is conditionally initialized.
79-
pub cond_init: bool,
80-
81-
/// `false` if it's only used.
82-
pub declared: bool,
83-
pub declared_count: u32,
84-
85-
/// `true` if the enclosing function defines this variable as a parameter.
86-
pub declared_as_fn_param: bool,
87-
88-
pub declared_as_fn_decl: bool,
89-
pub declared_as_fn_expr: bool,
90-
91-
pub assign_count: u32,
92-
pub mutation_by_call_count: u32,
93-
94-
/// The number of direct and indirect reference to this identifier.
95-
/// ## Things to note
96-
///
97-
/// - Update is counted as usage, but assign is not
98-
pub usage_count: u32,
99-
100-
/// The variable itself is modified.
101-
reassigned_with_assignment: bool,
102-
reassigned_with_var_decl: bool,
103-
/// The variable itself or a property of it is modified.
104-
pub mutated: bool,
105-
106-
pub has_property_access: bool,
107-
pub has_property_mutation: bool,
108-
109-
pub exported: bool,
110-
/// True if used **above** the declaration or in init. (Not eval order).
111-
pub used_above_decl: bool,
112-
/// `true` if it's declared by function parameters or variables declared in
113-
/// a closest function and used only within it and not used by child
114-
/// functions.
115-
pub is_fn_local: bool,
116-
117-
pub executed_multiple_time: bool,
118-
pub used_in_cond: bool,
119-
120-
pub var_kind: Option<VarDeclKind>,
121-
pub var_initialized: bool,
122-
123-
pub declared_as_catch_param: bool,
124-
125-
pub no_side_effect_for_member_access: bool,
126-
127-
pub callee_count: u32,
128-
129-
pub used_as_arg: bool,
130-
131-
pub indexed_with_dynamic_key: bool,
132-
133-
pub pure_fn: bool,
134-
135-
/// `infects_to`. This should be renamed, but it will be done with another
136-
/// PR. (because it's hard to review)
137-
infects: Vec<Access>,
138-
139-
pub used_in_non_child_fn: bool,
140-
/// Only **string** properties.
141-
pub accessed_props: Box<AHashMap<JsWord, u32>>,
142-
143-
pub used_recursively: bool,
144-
}
145-
146-
impl Default for VarUsageInfo {
147-
fn default() -> Self {
148-
Self {
149-
inline_prevented: Default::default(),
150-
ref_count: Default::default(),
151-
cond_init: Default::default(),
152-
declared: Default::default(),
153-
declared_count: Default::default(),
154-
declared_as_fn_param: Default::default(),
155-
declared_as_fn_decl: Default::default(),
156-
declared_as_fn_expr: Default::default(),
157-
assign_count: Default::default(),
158-
mutation_by_call_count: Default::default(),
159-
usage_count: Default::default(),
160-
reassigned_with_assignment: Default::default(),
161-
reassigned_with_var_decl: Default::default(),
162-
mutated: Default::default(),
163-
has_property_access: Default::default(),
164-
has_property_mutation: Default::default(),
165-
exported: Default::default(),
166-
used_above_decl: Default::default(),
167-
is_fn_local: true,
168-
executed_multiple_time: Default::default(),
169-
used_in_cond: Default::default(),
170-
var_kind: Default::default(),
171-
var_initialized: Default::default(),
172-
declared_as_catch_param: Default::default(),
173-
no_side_effect_for_member_access: Default::default(),
174-
callee_count: Default::default(),
175-
used_as_arg: Default::default(),
176-
indexed_with_dynamic_key: Default::default(),
177-
pure_fn: Default::default(),
178-
infects: Default::default(),
179-
used_in_non_child_fn: Default::default(),
180-
accessed_props: Default::default(),
181-
used_recursively: Default::default(),
182-
}
183-
}
184-
}
185-
186-
impl VarUsageInfo {
187-
pub fn is_mutated_only_by_one_call(&self) -> bool {
188-
self.assign_count == 0 && self.mutation_by_call_count == 1
189-
}
190-
191-
pub fn is_infected(&self) -> bool {
192-
!self.infects.is_empty()
193-
}
194-
195-
pub fn reassigned(&self) -> bool {
196-
self.reassigned_with_assignment
197-
|| self.reassigned_with_var_decl
198-
|| (u32::from(self.var_initialized)
199-
+ u32::from(self.declared_as_catch_param)
200-
+ u32::from(self.declared_as_fn_param)
201-
+ self.assign_count)
202-
> 1
203-
}
204-
205-
pub fn can_inline_var(&self) -> bool {
206-
!self.mutated
207-
|| (self.assign_count == 0 && !self.reassigned() && !self.has_property_mutation)
208-
}
209-
210-
pub fn can_inline_fn_once(&self) -> bool {
211-
self.callee_count > 0
212-
|| !self.executed_multiple_time && (self.is_fn_local || !self.used_in_non_child_fn)
213-
}
214-
}
215-
21646
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217-
pub(crate) enum ScopeKind {
47+
pub enum ScopeKind {
21848
Fn,
21949
Block,
22050
}
22151

222-
#[derive(Debug, Default, Clone)]
223-
pub(crate) struct ScopeData {
224-
pub has_with_stmt: bool,
225-
pub has_eval_call: bool,
226-
pub used_arguments: bool,
227-
}
228-
22952
#[derive(Debug, Clone)]
23053
enum RecursiveUsage {
23154
FnOrClass,
23255
Var { can_ignore: bool },
23356
}
23457

235-
/// Analyzed info of a whole program we are working on.
236-
#[derive(Debug, Default)]
237-
pub(crate) struct ProgramData {
238-
pub vars: FxHashMap<Id, VarUsageInfo>,
239-
240-
pub top: ScopeData,
241-
242-
pub scopes: FxHashMap<SyntaxContext, ScopeData>,
243-
244-
initialized_vars: IndexSet<Id, ahash::RandomState>,
245-
}
246-
247-
impl ProgramData {
248-
pub(crate) fn expand_infected(
249-
&self,
250-
module_info: &ModuleInfo,
251-
ids: FxHashSet<Access>,
252-
max_num: usize,
253-
) -> Result<FxHashSet<Access>, ()> {
254-
let init =
255-
HashSet::with_capacity_and_hasher(max_num, BuildHasherDefault::<FxHasher>::default());
256-
ids.into_iter().try_fold(init, |mut res, id| {
257-
let mut ids = Vec::with_capacity(max_num);
258-
ids.push(id);
259-
let mut ranges = vec![0..1usize];
260-
loop {
261-
let range = ranges.remove(0);
262-
for index in range {
263-
let iid = ids.get(index).unwrap();
264-
265-
// Abort on imported variables, because we can't analyze them
266-
if module_info.blackbox_imports.contains(&iid.0) {
267-
return Err(());
268-
}
269-
if !res.insert(iid.clone()) {
270-
continue;
271-
}
272-
if res.len() >= max_num {
273-
return Err(());
274-
}
275-
if let Some(info) = self.vars.get(&iid.0) {
276-
let infects = &info.infects;
277-
if !infects.is_empty() {
278-
let old_len = ids.len();
279-
280-
// This is not a call, so effects from call can be skipped
281-
let can_skip_non_call = matches!(iid.1, AccessKind::Reference)
282-
|| (info.declared_count == 1
283-
&& info.declared_as_fn_decl
284-
&& !info.reassigned());
285-
286-
if can_skip_non_call {
287-
ids.extend(
288-
infects
289-
.iter()
290-
.filter(|(_, kind)| *kind != AccessKind::Call)
291-
.cloned(),
292-
);
293-
} else {
294-
ids.extend_from_slice(infects.as_slice());
295-
}
296-
let new_len = ids.len();
297-
ranges.push(old_len..new_len);
298-
}
299-
}
300-
}
301-
if ranges.is_empty() {
302-
break;
303-
}
304-
}
305-
Ok(res)
306-
})
307-
}
308-
309-
pub(crate) fn contains_unresolved(&self, e: &Expr) -> bool {
310-
match e {
311-
Expr::Ident(i) => {
312-
if let Some(v) = self.vars.get(&i.to_id()) {
313-
return !v.declared;
314-
}
315-
316-
true
317-
}
318-
319-
Expr::Member(MemberExpr { obj, prop, .. }) => {
320-
if self.contains_unresolved(obj) {
321-
return true;
322-
}
323-
324-
if let MemberProp::Computed(prop) = prop {
325-
if self.contains_unresolved(&prop.expr) {
326-
return true;
327-
}
328-
}
329-
330-
false
331-
}
332-
333-
Expr::Call(CallExpr {
334-
callee: Callee::Expr(callee),
335-
args,
336-
..
337-
}) => {
338-
if self.contains_unresolved(callee) {
339-
return true;
340-
}
341-
342-
if args.iter().any(|arg| self.contains_unresolved(&arg.expr)) {
343-
return true;
344-
}
345-
346-
false
347-
}
348-
349-
_ => false,
350-
}
351-
}
352-
}
353-
35458
/// This assumes there are no two variable with same name and same span hygiene.
35559
#[derive(Debug)]
356-
pub(crate) struct UsageAnalyzer<S = ProgramData>
60+
pub struct UsageAnalyzer<S>
35761
where
35862
S: Storage,
35963
{
@@ -1504,10 +1208,8 @@ where
15041208
} = e
15051209
{
15061210
// TODO: merge with may_have_side_effects
1507-
let can_ignore = match &**init {
1508-
Expr::Call(call) if call.span.has_mark(marks.pure) => true,
1509-
_ => false,
1510-
};
1211+
let can_ignore =
1212+
matches!(&**init, Expr::Call(call) if call.span.has_mark(marks.pure));
15111213
let id = id.to_id();
15121214
self.used_recursively
15131215
.insert(id.clone(), RecursiveUsage::Var { can_ignore });

‎crates/swc_ecma_minifier/src/analyzer/storage/mod.rs ‎crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ use swc_ecma_ast::*;
55
use super::{ctx::Ctx, ScopeKind};
66
use crate::alias::Access;
77

8-
pub mod normal;
9-
10-
pub(crate) trait Storage: Sized + Default {
8+
pub trait Storage: Sized + Default {
119
type ScopeData: ScopeDataLike;
1210
type VarData: VarDataLike;
1311

@@ -33,7 +31,7 @@ pub(crate) trait Storage: Sized + Default {
3331
fn truncate_initialized_cnt(&mut self, len: usize);
3432
}
3533

36-
pub(crate) trait ScopeDataLike: Sized + Default + Clone {
34+
pub trait ScopeDataLike: Sized + Default + Clone {
3735
fn add_declared_symbol(&mut self, id: &Ident);
3836

3937
fn merge(&mut self, other: Self, is_child: bool);
@@ -45,7 +43,7 @@ pub(crate) trait ScopeDataLike: Sized + Default + Clone {
4543
fn mark_with_stmt(&mut self);
4644
}
4745

48-
pub(crate) trait VarDataLike: Sized {
46+
pub trait VarDataLike: Sized {
4947
/// See `declared_as_fn_param` of [crate::analyzer::VarUsageInfo].
5048
fn mark_declared_as_fn_param(&mut self);
5149

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub mod alias;
2+
pub mod analyzer;
3+
pub mod marks;
4+
pub mod util;

‎crates/swc_ecma_minifier/src/marks.rs ‎crates/swc_ecma_usage_analyzer/src/marks.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ pub struct Marks {
99
///
1010
/// In other words, AST nodes marked with this mark will not be treated as a
1111
/// top level item, even if it's in the top level scope.
12-
pub(crate) non_top_level: Mark,
12+
pub non_top_level: Mark,
1313

1414
/// Indicates that a sequence expression is generated by the minifier.
1515
///
1616
/// This is required because `sequences` option is ignored for synthesized
1717
/// sequences.
18-
pub(crate) synthesized_seq: Mark,
18+
pub synthesized_seq: Mark,
1919

2020
/// Treat this function as a top level module.
2121
///
@@ -29,25 +29,25 @@ pub struct Marks {
2929
///
3030
/// This is only applied to [swc_ecma_ast::Function] and it should not be
3131
/// nested.
32-
pub(crate) standalone: Mark,
32+
pub standalone: Mark,
3333

3434
//// Applied to [swc_ecma_ast::Module].
35-
pub(crate) bundle_of_standalone: Mark,
35+
pub bundle_of_standalone: Mark,
3636

3737
/// `/** @const */`.
38-
pub(crate) const_ann: Mark,
38+
pub const_ann: Mark,
3939

4040
/// Check for `/*#__NOINLINE__*/`
41-
pub(crate) noinline: Mark,
41+
pub noinline: Mark,
4242

4343
/// Check for `/*#__PURE__*/`
44-
pub(crate) pure: Mark,
44+
pub pure: Mark,
4545

4646
/// This is applied to [swc_ecma_ast::BlockStmt] which is injected to
4747
/// preserve the side effects.
48-
pub(crate) fake_block: Mark,
48+
pub fake_block: Mark,
4949

50-
pub(crate) unresolved_mark: Mark,
50+
pub unresolved_mark: Mark,
5151
}
5252

5353
impl Marks {
+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use swc_atoms::{js_word, JsWord};
2+
use swc_ecma_ast::Stmt;
3+
4+
pub fn is_global_var_with_pure_property_access(s: &JsWord) -> bool {
5+
match *s {
6+
js_word!("JSON")
7+
| js_word!("Array")
8+
| js_word!("String")
9+
| js_word!("Object")
10+
| js_word!("Number")
11+
| js_word!("Date")
12+
| js_word!("BigInt")
13+
| js_word!("Boolean")
14+
| js_word!("Math")
15+
| js_word!("Error") => return true,
16+
_ => {}
17+
}
18+
19+
matches!(
20+
&**s,
21+
"console"
22+
| "clearInterval"
23+
| "clearTimeout"
24+
| "setInterval"
25+
| "setTimeout"
26+
| "btoa"
27+
| "decodeURI"
28+
| "decodeURIComponent"
29+
| "encodeURI"
30+
| "encodeURIComponent"
31+
| "escape"
32+
| "eval"
33+
| "EvalError"
34+
| "Function"
35+
| "isFinite"
36+
| "isNaN"
37+
| "JSON"
38+
| "parseFloat"
39+
| "parseInt"
40+
| "RegExp"
41+
| "RangeError"
42+
| "ReferenceError"
43+
| "SyntaxError"
44+
| "TypeError"
45+
| "unescape"
46+
| "URIError"
47+
| "atob"
48+
| "globalThis"
49+
| "NaN"
50+
| "Symbol"
51+
| "Promise"
52+
)
53+
}
54+
55+
pub fn can_end_conditionally(s: &Stmt) -> bool {
56+
///
57+
///`ignore_always`: If true, [Stmt::Return] will be ignored.
58+
fn can_end(s: &Stmt, ignore_always: bool) -> bool {
59+
match s {
60+
Stmt::If(s) => {
61+
can_end(&s.cons, false)
62+
|| s.alt
63+
.as_deref()
64+
.map(|s| can_end(s, false))
65+
.unwrap_or_default()
66+
}
67+
68+
Stmt::Switch(s) => s
69+
.cases
70+
.iter()
71+
.any(|case| case.cons.iter().any(|s| can_end(s, false))),
72+
73+
Stmt::DoWhile(s) => can_end(&s.body, false),
74+
75+
Stmt::While(s) => can_end(&s.body, false),
76+
77+
Stmt::For(s) => can_end(&s.body, false),
78+
79+
Stmt::ForOf(s) => can_end(&s.body, false),
80+
81+
Stmt::ForIn(s) => can_end(&s.body, false),
82+
83+
Stmt::Return(..) | Stmt::Break(..) | Stmt::Continue(..) => !ignore_always,
84+
85+
_ => false,
86+
}
87+
}
88+
89+
can_end(s, true)
90+
}

0 commit comments

Comments
 (0)
Please sign in to comment.