mirror of
https://github.com/swc-project/swc.git
synced 2024-11-27 04:47:03 +03:00
fix(es/resolver): Fix handling of block scoped functions (#5092)
This commit is contained in:
parent
6d6e4dd096
commit
9519e801ea
@ -0,0 +1,6 @@
|
||||
try {
|
||||
var fx
|
||||
function fx(){}
|
||||
} catch {
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
|
||||
x the name `fx` is defined multiple times
|
||||
,-[2:5]
|
||||
2 | var fx
|
||||
: ^|
|
||||
: `-- previous definition of `fx` here
|
||||
3 | function fx(){}
|
||||
: ^|
|
||||
: `-- `fx` redefined here
|
||||
`----
|
@ -0,0 +1,5 @@
|
||||
switch (a) {
|
||||
case 'a':
|
||||
var foo
|
||||
function foo() {}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
|
||||
x the name `foo` is defined multiple times
|
||||
,-[3:7]
|
||||
3 | var foo
|
||||
: ^|^
|
||||
: `-- previous definition of `foo` here
|
||||
4 | function foo() {}
|
||||
: ^|^
|
||||
: `-- `foo` redefined here
|
||||
`----
|
@ -1,18 +1,19 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use swc_atoms::JsWord;
|
||||
use swc_common::{
|
||||
collections::{AHashMap, AHashSet},
|
||||
errors::HANDLER,
|
||||
Span,
|
||||
};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::StmtLike;
|
||||
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
|
||||
|
||||
use crate::rule::{visitor_rule, Rule};
|
||||
|
||||
pub fn duplicate_bindings() -> Box<dyn Rule> {
|
||||
visitor_rule(DuplicateBindings {
|
||||
top_level: true,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
@ -21,6 +22,7 @@ pub fn duplicate_bindings() -> Box<dyn Rule> {
|
||||
struct BindingInfo {
|
||||
span: Span,
|
||||
unique: bool,
|
||||
is_function: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@ -31,47 +33,29 @@ struct DuplicateBindings {
|
||||
var_decl_kind: Option<VarDeclKind>,
|
||||
is_pat_decl: bool,
|
||||
|
||||
is_module: bool,
|
||||
|
||||
top_level: bool,
|
||||
/// at the top level of script of function, function behaves like var
|
||||
/// in other scope it behaves like let
|
||||
lexical_function: bool,
|
||||
}
|
||||
|
||||
impl DuplicateBindings {
|
||||
/// Add a binding.
|
||||
fn add(&mut self, id: &Ident, unique: bool) {
|
||||
match self.bindings.entry(id.to_id()) {
|
||||
fn add(&mut self, id: JsWord, info: BindingInfo) {
|
||||
match self.bindings.entry((id.clone(), info.span.ctxt())) {
|
||||
Entry::Occupied(mut prev) => {
|
||||
if unique || prev.get().unique {
|
||||
let name = &id.sym;
|
||||
|
||||
HANDLER.with(|handler| {
|
||||
handler
|
||||
.struct_span_err(
|
||||
id.span,
|
||||
&format!("the name `{}` is defined multiple times", name),
|
||||
)
|
||||
.span_label(
|
||||
prev.get().span,
|
||||
&format!("previous definition of `{}` here", name),
|
||||
)
|
||||
.span_label(id.span, &format!("`{}` redefined here", name))
|
||||
.emit();
|
||||
});
|
||||
if !(info.is_function && prev.get().is_function)
|
||||
&& (info.unique || prev.get().unique)
|
||||
{
|
||||
emit_error(&id, info.span, prev.get().span);
|
||||
}
|
||||
|
||||
// Next span.
|
||||
if unique || !prev.get().unique {
|
||||
*prev.get_mut() = BindingInfo {
|
||||
span: id.span,
|
||||
unique,
|
||||
}
|
||||
if info.unique || !prev.get().unique {
|
||||
*prev.get_mut() = info
|
||||
}
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(BindingInfo {
|
||||
span: id.span,
|
||||
unique,
|
||||
});
|
||||
e.insert(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -97,11 +81,34 @@ impl DuplicateBindings {
|
||||
self.var_decl_kind = old_var_decl_kind;
|
||||
}
|
||||
|
||||
fn visit_with_fn_scope<V: VisitWith<Self>>(&mut self, e: &V) {
|
||||
let top_level = self.top_level;
|
||||
self.top_level = false;
|
||||
e.visit_children_with(self);
|
||||
self.top_level = top_level;
|
||||
// this is for the wired case:
|
||||
// in non strict mode, function in non top level or function scope
|
||||
// is hoisted, while still error when collides with same level lexical var
|
||||
fn visit_with_stmt_like<T: StmtLike + VisitWith<Self>>(&mut self, s: &[T]) {
|
||||
let mut fn_name = AHashMap::default();
|
||||
for s in s {
|
||||
if let Some(Stmt::Decl(Decl::Fn(s))) = s.as_stmt() {
|
||||
if let Some(prev) = fn_name.get(&s.ident.sym) {
|
||||
emit_error(&s.ident.sym, s.ident.span, *prev)
|
||||
} else {
|
||||
fn_name.insert(s.ident.sym.clone(), s.ident.span);
|
||||
}
|
||||
}
|
||||
|
||||
s.visit_with(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_with_stmts(&mut self, s: &[Stmt], lexical_function: bool) {
|
||||
let old = self.lexical_function;
|
||||
self.lexical_function = lexical_function;
|
||||
|
||||
if lexical_function {
|
||||
self.visit_with_stmt_like(s);
|
||||
} else {
|
||||
s.visit_children_with(self);
|
||||
}
|
||||
self.lexical_function = old;
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,20 +119,59 @@ impl Visit for DuplicateBindings {
|
||||
p.visit_children_with(self);
|
||||
|
||||
if self.is_pat_decl {
|
||||
self.add(&p.key, self.is_unique_var_kind());
|
||||
self.add(
|
||||
p.key.sym.clone(),
|
||||
BindingInfo {
|
||||
span: p.key.span,
|
||||
unique: self.is_unique_var_kind(),
|
||||
is_function: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_function(&mut self, f: &Function) {
|
||||
self.visit_with_fn_scope(f)
|
||||
// in case any new parts is added
|
||||
let Function {
|
||||
body,
|
||||
params,
|
||||
decorators,
|
||||
span: _,
|
||||
is_generator: _,
|
||||
is_async: _,
|
||||
type_params: _,
|
||||
return_type: _,
|
||||
} = f;
|
||||
params.visit_with(self);
|
||||
decorators.visit_with(self);
|
||||
if let Some(body) = body {
|
||||
self.visit_with_stmts(&body.stmts, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_arrow_expr(&mut self, a: &ArrowExpr) {
|
||||
self.visit_with_fn_scope(a)
|
||||
let ArrowExpr {
|
||||
params,
|
||||
body,
|
||||
span: _,
|
||||
is_async: _,
|
||||
is_generator: _,
|
||||
type_params: _,
|
||||
return_type: _,
|
||||
} = a;
|
||||
params.visit_with(self);
|
||||
if let BlockStmtOrExpr::BlockStmt(b) = body {
|
||||
self.visit_with_stmts(&b.stmts, false)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_class(&mut self, c: &Class) {
|
||||
self.visit_with_fn_scope(c)
|
||||
fn visit_static_block(&mut self, c: &StaticBlock) {
|
||||
self.visit_with_stmts(&c.body.stmts, false)
|
||||
}
|
||||
|
||||
// block stmt and case block
|
||||
fn visit_stmts(&mut self, b: &[Stmt]) {
|
||||
self.visit_with_stmts(b, true)
|
||||
}
|
||||
|
||||
fn visit_catch_clause(&mut self, c: &CatchClause) {
|
||||
@ -133,7 +179,14 @@ impl Visit for DuplicateBindings {
|
||||
}
|
||||
|
||||
fn visit_class_decl(&mut self, d: &ClassDecl) {
|
||||
self.add(&d.ident, true);
|
||||
self.add(
|
||||
d.ident.sym.clone(),
|
||||
BindingInfo {
|
||||
span: d.ident.span,
|
||||
unique: true,
|
||||
is_function: false,
|
||||
},
|
||||
);
|
||||
|
||||
d.visit_children_with(self);
|
||||
}
|
||||
@ -153,7 +206,14 @@ impl Visit for DuplicateBindings {
|
||||
|
||||
fn visit_fn_decl(&mut self, d: &FnDecl) {
|
||||
if d.function.body.is_some() {
|
||||
self.add(&d.ident, self.is_module && self.top_level);
|
||||
self.add(
|
||||
d.ident.sym.clone(),
|
||||
BindingInfo {
|
||||
span: d.ident.span,
|
||||
unique: self.lexical_function,
|
||||
is_function: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
d.visit_children_with(self);
|
||||
@ -171,7 +231,14 @@ impl Visit for DuplicateBindings {
|
||||
s.visit_children_with(self);
|
||||
|
||||
if !self.type_bindings.contains(&s.local.to_id()) {
|
||||
self.add(&s.local, true);
|
||||
self.add(
|
||||
s.local.sym.clone(),
|
||||
BindingInfo {
|
||||
span: s.local.span,
|
||||
unique: true,
|
||||
is_function: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,7 +246,14 @@ impl Visit for DuplicateBindings {
|
||||
s.visit_children_with(self);
|
||||
|
||||
if !s.is_type_only && !self.type_bindings.contains(&s.local.to_id()) {
|
||||
self.add(&s.local, true);
|
||||
self.add(
|
||||
s.local.sym.clone(),
|
||||
BindingInfo {
|
||||
span: s.local.span,
|
||||
unique: true,
|
||||
is_function: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,7 +261,14 @@ impl Visit for DuplicateBindings {
|
||||
s.visit_children_with(self);
|
||||
|
||||
if !self.type_bindings.contains(&s.local.to_id()) {
|
||||
self.add(&s.local, true);
|
||||
self.add(
|
||||
s.local.sym.clone(),
|
||||
BindingInfo {
|
||||
span: s.local.span,
|
||||
unique: true,
|
||||
is_function: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,8 +277,9 @@ impl Visit for DuplicateBindings {
|
||||
type_bindings: &mut self.type_bindings,
|
||||
});
|
||||
|
||||
self.is_module = true;
|
||||
m.visit_children_with(self);
|
||||
self.lexical_function = true;
|
||||
|
||||
self.visit_with_stmt_like(&m.body);
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, p: &Pat) {
|
||||
@ -205,7 +287,14 @@ impl Visit for DuplicateBindings {
|
||||
|
||||
if let Pat::Ident(p) = p {
|
||||
if self.is_pat_decl {
|
||||
self.add(&p.id, self.is_unique_var_kind());
|
||||
self.add(
|
||||
p.id.sym.clone(),
|
||||
BindingInfo {
|
||||
span: p.id.span,
|
||||
unique: self.is_unique_var_kind(),
|
||||
is_function: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -215,7 +304,7 @@ impl Visit for DuplicateBindings {
|
||||
type_bindings: &mut self.type_bindings,
|
||||
});
|
||||
|
||||
s.visit_children_with(self);
|
||||
s.body.visit_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_var_decl(&mut self, d: &VarDecl) {
|
||||
@ -240,3 +329,19 @@ impl Visit for TypeCollector<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_error(name: &str, span: Span, prev_span: Span) {
|
||||
HANDLER.with(|handler| {
|
||||
handler
|
||||
.struct_span_err(
|
||||
span,
|
||||
&format!("the name `{}` is defined multiple times", name),
|
||||
)
|
||||
.span_label(
|
||||
prev_span,
|
||||
&format!("previous definition of `{}` here", name),
|
||||
)
|
||||
.span_label(span, &format!("`{}` redefined here", name))
|
||||
.emit();
|
||||
});
|
||||
}
|
||||
|
6
crates/swc_ecma_lints/tests/pass/issue-4907/2/input.js
Normal file
6
crates/swc_ecma_lints/tests/pass/issue-4907/2/input.js
Normal file
@ -0,0 +1,6 @@
|
||||
export class A {
|
||||
static {
|
||||
var fx
|
||||
function fx(){}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('console' type=inline),
|
||||
#2,
|
||||
#1,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
@ -46,9 +46,9 @@ TestSnapshot {
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 2,
|
||||
cond_init: true,
|
||||
declared: true,
|
||||
declared_count: 1,
|
||||
cond_init: false,
|
||||
declared: false,
|
||||
declared_count: 0,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
@ -72,6 +72,44 @@ TestSnapshot {
|
||||
no_side_effect_for_member_access: false,
|
||||
used_as_callee: false,
|
||||
used_as_arg: false,
|
||||
pure_fn: false,
|
||||
infects: [],
|
||||
},
|
||||
),
|
||||
(
|
||||
(
|
||||
Atom('xxx' type=inline),
|
||||
#2,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 0,
|
||||
cond_init: true,
|
||||
declared: true,
|
||||
declared_count: 1,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
mutation_by_call_count: 0,
|
||||
usage_count: 0,
|
||||
reassigned_with_assignment: false,
|
||||
reassigned_with_var_decl: false,
|
||||
mutated: false,
|
||||
has_property_access: false,
|
||||
has_property_mutation: false,
|
||||
accessed_props: {},
|
||||
exported: false,
|
||||
used_above_decl: false,
|
||||
is_fn_local: true,
|
||||
used_by_nested_fn: false,
|
||||
executed_multiple_time: false,
|
||||
used_in_cond: false,
|
||||
var_kind: None,
|
||||
var_initialized: true,
|
||||
declared_as_catch_param: false,
|
||||
no_side_effect_for_member_access: true,
|
||||
used_as_callee: false,
|
||||
used_as_arg: false,
|
||||
pure_fn: true,
|
||||
infects: [],
|
||||
},
|
||||
|
@ -1,11 +1,9 @@
|
||||
switch (
|
||||
function () {
|
||||
return xxx;
|
||||
}
|
||||
) {
|
||||
switch(function() {
|
||||
return xxx;
|
||||
}){
|
||||
case xxx:
|
||||
for (; console.log("FAIL"); ) {
|
||||
function xxx() {}
|
||||
for(; console.log("FAIL");){
|
||||
function xxx1() {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
switch(function() {
|
||||
return a;
|
||||
return xxx;
|
||||
}){
|
||||
case a:
|
||||
case xxx:
|
||||
for(; console.log("FAIL");){
|
||||
function a() {}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('console' type=inline),
|
||||
#2,
|
||||
#1,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
@ -46,9 +46,9 @@ TestSnapshot {
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 2,
|
||||
cond_init: true,
|
||||
declared: true,
|
||||
declared_count: 1,
|
||||
cond_init: false,
|
||||
declared: false,
|
||||
declared_count: 0,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
@ -72,6 +72,44 @@ TestSnapshot {
|
||||
no_side_effect_for_member_access: false,
|
||||
used_as_callee: false,
|
||||
used_as_arg: false,
|
||||
pure_fn: false,
|
||||
infects: [],
|
||||
},
|
||||
),
|
||||
(
|
||||
(
|
||||
Atom('xxx' type=inline),
|
||||
#2,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 0,
|
||||
cond_init: true,
|
||||
declared: true,
|
||||
declared_count: 1,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
mutation_by_call_count: 0,
|
||||
usage_count: 0,
|
||||
reassigned_with_assignment: false,
|
||||
reassigned_with_var_decl: false,
|
||||
mutated: false,
|
||||
has_property_access: false,
|
||||
has_property_mutation: false,
|
||||
accessed_props: {},
|
||||
exported: false,
|
||||
used_above_decl: false,
|
||||
is_fn_local: true,
|
||||
used_by_nested_fn: false,
|
||||
executed_multiple_time: false,
|
||||
used_in_cond: false,
|
||||
var_kind: None,
|
||||
var_initialized: true,
|
||||
declared_as_catch_param: false,
|
||||
no_side_effect_for_member_access: true,
|
||||
used_as_callee: false,
|
||||
used_as_arg: false,
|
||||
pure_fn: true,
|
||||
infects: [],
|
||||
},
|
||||
|
@ -1,12 +1,10 @@
|
||||
"use strict";
|
||||
switch (
|
||||
function () {
|
||||
return xxx;
|
||||
}
|
||||
) {
|
||||
switch(function() {
|
||||
return xxx;
|
||||
}){
|
||||
case xxx:
|
||||
for (; console.log("FAIL"); ) {
|
||||
function xxx() {}
|
||||
for(; console.log("FAIL");){
|
||||
function xxx1() {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
"use strict";
|
||||
switch(function() {
|
||||
return a;
|
||||
return xxx;
|
||||
}){
|
||||
case a:
|
||||
case xxx:
|
||||
for(; console.log("FAIL");){
|
||||
function a() {}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('console' type=inline),
|
||||
#2,
|
||||
#1,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
@ -46,9 +46,9 @@ TestSnapshot {
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 2,
|
||||
cond_init: true,
|
||||
declared: true,
|
||||
declared_count: 1,
|
||||
cond_init: false,
|
||||
declared: false,
|
||||
declared_count: 0,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
@ -72,6 +72,44 @@ TestSnapshot {
|
||||
no_side_effect_for_member_access: false,
|
||||
used_as_callee: false,
|
||||
used_as_arg: false,
|
||||
pure_fn: false,
|
||||
infects: [],
|
||||
},
|
||||
),
|
||||
(
|
||||
(
|
||||
Atom('xxx' type=inline),
|
||||
#2,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 0,
|
||||
cond_init: true,
|
||||
declared: true,
|
||||
declared_count: 1,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
mutation_by_call_count: 0,
|
||||
usage_count: 0,
|
||||
reassigned_with_assignment: false,
|
||||
reassigned_with_var_decl: false,
|
||||
mutated: false,
|
||||
has_property_access: false,
|
||||
has_property_mutation: false,
|
||||
accessed_props: {},
|
||||
exported: false,
|
||||
used_above_decl: false,
|
||||
is_fn_local: true,
|
||||
used_by_nested_fn: false,
|
||||
executed_multiple_time: false,
|
||||
used_in_cond: false,
|
||||
var_kind: None,
|
||||
var_initialized: true,
|
||||
declared_as_catch_param: false,
|
||||
no_side_effect_for_member_access: true,
|
||||
used_as_callee: false,
|
||||
used_as_arg: false,
|
||||
pure_fn: true,
|
||||
infects: [],
|
||||
},
|
||||
|
@ -1,9 +1,7 @@
|
||||
switch (
|
||||
function () {
|
||||
return xxx;
|
||||
}
|
||||
) {
|
||||
switch(function() {
|
||||
return xxx;
|
||||
}){
|
||||
case xxx:
|
||||
if (console.log("FAIL")) function xxx() {}
|
||||
if (console.log("FAIL")) function xxx1() {}
|
||||
break;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
switch(function() {
|
||||
return a;
|
||||
return xxx;
|
||||
}){
|
||||
case a:
|
||||
case xxx:
|
||||
if (console.log("FAIL")) {
|
||||
function a() {}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('console' type=inline),
|
||||
#2,
|
||||
#1,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
@ -46,9 +46,9 @@ TestSnapshot {
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 2,
|
||||
cond_init: true,
|
||||
declared: true,
|
||||
declared_count: 1,
|
||||
cond_init: false,
|
||||
declared: false,
|
||||
declared_count: 0,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
@ -72,6 +72,44 @@ TestSnapshot {
|
||||
no_side_effect_for_member_access: false,
|
||||
used_as_callee: false,
|
||||
used_as_arg: false,
|
||||
pure_fn: false,
|
||||
infects: [],
|
||||
},
|
||||
),
|
||||
(
|
||||
(
|
||||
Atom('xxx' type=inline),
|
||||
#2,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 0,
|
||||
cond_init: true,
|
||||
declared: true,
|
||||
declared_count: 1,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
mutation_by_call_count: 0,
|
||||
usage_count: 0,
|
||||
reassigned_with_assignment: false,
|
||||
reassigned_with_var_decl: false,
|
||||
mutated: false,
|
||||
has_property_access: false,
|
||||
has_property_mutation: false,
|
||||
accessed_props: {},
|
||||
exported: false,
|
||||
used_above_decl: false,
|
||||
is_fn_local: true,
|
||||
used_by_nested_fn: false,
|
||||
executed_multiple_time: false,
|
||||
used_in_cond: false,
|
||||
var_kind: None,
|
||||
var_initialized: true,
|
||||
declared_as_catch_param: false,
|
||||
no_side_effect_for_member_access: true,
|
||||
used_as_callee: false,
|
||||
used_as_arg: false,
|
||||
pure_fn: true,
|
||||
infects: [],
|
||||
},
|
||||
|
@ -1,12 +1,10 @@
|
||||
"use strict";
|
||||
switch (
|
||||
function () {
|
||||
return xxx;
|
||||
}
|
||||
) {
|
||||
switch(function() {
|
||||
return xxx;
|
||||
}){
|
||||
case xxx:
|
||||
if (console.log("FAIL")) {
|
||||
function xxx() {}
|
||||
function xxx1() {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
"use strict";
|
||||
switch(function() {
|
||||
return a;
|
||||
return xxx;
|
||||
}){
|
||||
case a:
|
||||
case xxx:
|
||||
if (console.log("FAIL")) {
|
||||
function a() {}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('bar' type=inline),
|
||||
#1,
|
||||
#2,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
@ -81,7 +81,7 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('console' type=inline),
|
||||
#2,
|
||||
#3,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
|
@ -43,7 +43,7 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('bar' type=inline),
|
||||
#1,
|
||||
#2,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
@ -121,7 +121,7 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('console' type=inline),
|
||||
#2,
|
||||
#3,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
|
@ -43,7 +43,7 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('bar' type=inline),
|
||||
#1,
|
||||
#2,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
@ -81,7 +81,7 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('console' type=inline),
|
||||
#2,
|
||||
#3,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
|
@ -41,14 +41,14 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('bar' type=inline),
|
||||
#1,
|
||||
#2,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 2,
|
||||
cond_init: true,
|
||||
declared: true,
|
||||
declared_count: 1,
|
||||
cond_init: false,
|
||||
declared: false,
|
||||
declared_count: 0,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
@ -72,6 +72,44 @@ TestSnapshot {
|
||||
no_side_effect_for_member_access: false,
|
||||
used_as_callee: true,
|
||||
used_as_arg: false,
|
||||
pure_fn: false,
|
||||
infects: [],
|
||||
},
|
||||
),
|
||||
(
|
||||
(
|
||||
Atom('bar' type=inline),
|
||||
#3,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 0,
|
||||
cond_init: true,
|
||||
declared: true,
|
||||
declared_count: 1,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
mutation_by_call_count: 0,
|
||||
usage_count: 0,
|
||||
reassigned_with_assignment: false,
|
||||
reassigned_with_var_decl: false,
|
||||
mutated: false,
|
||||
has_property_access: false,
|
||||
has_property_mutation: false,
|
||||
accessed_props: {},
|
||||
exported: false,
|
||||
used_above_decl: false,
|
||||
is_fn_local: true,
|
||||
used_by_nested_fn: false,
|
||||
executed_multiple_time: false,
|
||||
used_in_cond: false,
|
||||
var_kind: None,
|
||||
var_initialized: true,
|
||||
declared_as_catch_param: false,
|
||||
no_side_effect_for_member_access: true,
|
||||
used_as_callee: false,
|
||||
used_as_arg: false,
|
||||
pure_fn: true,
|
||||
infects: [],
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
var foo, bar;
|
||||
var foo, bar1;
|
||||
var x = 10, y;
|
||||
var moo;
|
||||
bar();
|
||||
|
@ -1,4 +1,4 @@
|
||||
while(!((a && b) || c + "0")){
|
||||
while(!((a && bar) || c + "0")){
|
||||
console.log("unreachable");
|
||||
var a;
|
||||
function b() {}
|
||||
@ -8,4 +8,4 @@ for(var c = 10, d; c && (d || c) && !typeof c; ++c){
|
||||
a();
|
||||
var e;
|
||||
}
|
||||
b();
|
||||
bar();
|
||||
|
@ -41,19 +41,57 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('f' type=inline),
|
||||
#1,
|
||||
#2,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 6,
|
||||
ref_count: 4,
|
||||
cond_init: false,
|
||||
declared: false,
|
||||
declared_count: 0,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
mutation_by_call_count: 4,
|
||||
usage_count: 4,
|
||||
reassigned_with_assignment: false,
|
||||
reassigned_with_var_decl: false,
|
||||
mutated: true,
|
||||
has_property_access: false,
|
||||
has_property_mutation: false,
|
||||
accessed_props: {},
|
||||
exported: false,
|
||||
used_above_decl: true,
|
||||
is_fn_local: true,
|
||||
used_by_nested_fn: false,
|
||||
executed_multiple_time: false,
|
||||
used_in_cond: true,
|
||||
var_kind: None,
|
||||
var_initialized: false,
|
||||
declared_as_catch_param: false,
|
||||
no_side_effect_for_member_access: false,
|
||||
used_as_callee: false,
|
||||
used_as_arg: false,
|
||||
pure_fn: false,
|
||||
infects: [],
|
||||
},
|
||||
),
|
||||
(
|
||||
(
|
||||
Atom('f' type=inline),
|
||||
#3,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 2,
|
||||
cond_init: true,
|
||||
declared: true,
|
||||
declared_count: 1,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
mutation_by_call_count: 6,
|
||||
usage_count: 6,
|
||||
mutation_by_call_count: 2,
|
||||
usage_count: 2,
|
||||
reassigned_with_assignment: false,
|
||||
reassigned_with_var_decl: false,
|
||||
mutated: true,
|
||||
|
@ -1,9 +1,9 @@
|
||||
console.log(typeof a, typeof b, 1);
|
||||
if (console.log(typeof a, typeof b, 2)) console.log(typeof a, typeof b, 3);
|
||||
console.log(typeof f, typeof b, 1);
|
||||
if (console.log(typeof f, typeof b, 2)) console.log(typeof f, typeof b, 3);
|
||||
else {
|
||||
console.log(typeof a, typeof b, 4);
|
||||
function a() {}
|
||||
console.log(typeof a, typeof b, 5);
|
||||
}
|
||||
function b() {}
|
||||
console.log(typeof a, typeof b, 6);
|
||||
console.log(typeof f, typeof b, 6);
|
||||
|
@ -41,19 +41,57 @@ TestSnapshot {
|
||||
(
|
||||
(
|
||||
Atom('f' type=inline),
|
||||
#1,
|
||||
#2,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 6,
|
||||
ref_count: 4,
|
||||
cond_init: false,
|
||||
declared: false,
|
||||
declared_count: 0,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
mutation_by_call_count: 4,
|
||||
usage_count: 4,
|
||||
reassigned_with_assignment: false,
|
||||
reassigned_with_var_decl: false,
|
||||
mutated: true,
|
||||
has_property_access: false,
|
||||
has_property_mutation: false,
|
||||
accessed_props: {},
|
||||
exported: false,
|
||||
used_above_decl: true,
|
||||
is_fn_local: true,
|
||||
used_by_nested_fn: false,
|
||||
executed_multiple_time: false,
|
||||
used_in_cond: true,
|
||||
var_kind: None,
|
||||
var_initialized: false,
|
||||
declared_as_catch_param: false,
|
||||
no_side_effect_for_member_access: false,
|
||||
used_as_callee: false,
|
||||
used_as_arg: false,
|
||||
pure_fn: false,
|
||||
infects: [],
|
||||
},
|
||||
),
|
||||
(
|
||||
(
|
||||
Atom('f' type=inline),
|
||||
#3,
|
||||
),
|
||||
VarUsageInfo {
|
||||
inline_prevented: false,
|
||||
ref_count: 2,
|
||||
cond_init: true,
|
||||
declared: true,
|
||||
declared_count: 1,
|
||||
declared_as_fn_param: false,
|
||||
declared_as_fn_expr: false,
|
||||
assign_count: 0,
|
||||
mutation_by_call_count: 6,
|
||||
usage_count: 6,
|
||||
mutation_by_call_count: 2,
|
||||
usage_count: 2,
|
||||
reassigned_with_assignment: false,
|
||||
reassigned_with_var_decl: false,
|
||||
mutated: true,
|
||||
|
@ -1,10 +1,10 @@
|
||||
"use strict";
|
||||
console.log(typeof a, typeof b, 1);
|
||||
if (console.log(typeof a, typeof b, 2)) console.log(typeof a, typeof b, 3);
|
||||
console.log(typeof f, typeof b, 1);
|
||||
if (console.log(typeof f, typeof b, 2)) console.log(typeof f, typeof b, 3);
|
||||
else {
|
||||
console.log(typeof a, typeof b, 4);
|
||||
function a() {}
|
||||
console.log(typeof a, typeof b, 5);
|
||||
}
|
||||
function b() {}
|
||||
console.log(typeof a, typeof b, 6);
|
||||
console.log(typeof f, typeof b, 6);
|
||||
|
@ -42,6 +42,12 @@ use testing::assert_eq;
|
||||
"drop_unused/issue_1656",
|
||||
"transform/condition_evaluate",
|
||||
"reduce_vars/var_assign_3",
|
||||
// should error
|
||||
"blocks/issue_1672_if_strict",
|
||||
"blocks/issue_1672_for_strict",
|
||||
// need support for script mode
|
||||
"blocks/issue_1672_if",
|
||||
"blocks/issue_1672_for",
|
||||
)
|
||||
)]
|
||||
fn terser_exec(input: PathBuf) {
|
||||
|
@ -8,108 +8,6 @@ use crate::rename::{renamer, Renamer};
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
macro_rules! track_ident_mut {
|
||||
() => {
|
||||
fn visit_mut_export_specifier(&mut self, s: &mut ExportSpecifier) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Ref;
|
||||
s.visit_mut_children_with(self);
|
||||
self.ident_type = old;
|
||||
}
|
||||
|
||||
fn visit_mut_import_specifier(&mut self, s: &mut ImportSpecifier) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Binding;
|
||||
|
||||
match s {
|
||||
ImportSpecifier::Named(ImportNamedSpecifier { imported: None, .. })
|
||||
| ImportSpecifier::Namespace(..)
|
||||
| ImportSpecifier::Default(..) => s.visit_mut_children_with(self),
|
||||
ImportSpecifier::Named(s) => s.local.visit_mut_with(self),
|
||||
};
|
||||
|
||||
self.ident_type = old;
|
||||
}
|
||||
|
||||
fn visit_mut_getter_prop(&mut self, f: &mut GetterProp) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Ref;
|
||||
f.key.visit_mut_with(self);
|
||||
self.ident_type = old;
|
||||
|
||||
f.type_ann.visit_mut_with(self);
|
||||
|
||||
f.body.visit_mut_with(self);
|
||||
}
|
||||
|
||||
// impl<'a> Fold for $T<'a> {
|
||||
// fn fold(&mut self, f: GetterProp) -> GetterProp {
|
||||
// let body = f.body.visit_mut_with(self);
|
||||
|
||||
// GetterProp { body, ..c }
|
||||
// }
|
||||
// }
|
||||
|
||||
fn visit_mut_labeled_stmt(&mut self, s: &mut LabeledStmt) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Label;
|
||||
s.label.visit_mut_with(self);
|
||||
self.ident_type = old;
|
||||
|
||||
s.body.visit_mut_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_break_stmt(&mut self, s: &mut BreakStmt) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Label;
|
||||
s.label.visit_mut_with(self);
|
||||
self.ident_type = old;
|
||||
}
|
||||
|
||||
fn visit_mut_continue_stmt(&mut self, s: &mut ContinueStmt) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Label;
|
||||
s.label.visit_mut_with(self);
|
||||
self.ident_type = old;
|
||||
}
|
||||
|
||||
fn visit_mut_key_value_pat_prop(&mut self, n: &mut KeyValuePatProp) {
|
||||
n.key.visit_mut_with(self);
|
||||
n.value.visit_mut_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_class(&mut self, c: &mut Class) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Ref;
|
||||
c.decorators.visit_mut_with(self);
|
||||
|
||||
self.ident_type = IdentType::Ref;
|
||||
c.super_class.visit_mut_with(self);
|
||||
|
||||
self.ident_type = IdentType::Binding;
|
||||
c.type_params.visit_mut_with(self);
|
||||
|
||||
self.ident_type = IdentType::Ref;
|
||||
c.super_type_params.visit_mut_with(self);
|
||||
|
||||
self.ident_type = IdentType::Ref;
|
||||
c.implements.visit_mut_with(self);
|
||||
self.ident_type = old;
|
||||
|
||||
c.body.visit_mut_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_prop_name(&mut self, n: &mut PropName) {
|
||||
match n {
|
||||
PropName::Computed(c) => {
|
||||
c.visit_mut_with(self);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Config {
|
||||
/// If true, the `hygiene` pass will preserve class names.
|
||||
|
@ -1,14 +1,17 @@
|
||||
use rustc_hash::FxHashSet;
|
||||
use swc_atoms::{js_word, JsWord};
|
||||
use swc_common::{collections::AHashSet, Mark, SyntaxContext};
|
||||
use swc_common::{
|
||||
collections::{AHashMap, AHashSet},
|
||||
Mark, SyntaxContext,
|
||||
};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::find_pat_ids;
|
||||
use swc_ecma_utils::{find_pat_ids, IsDirective};
|
||||
use swc_ecma_visit::{
|
||||
as_folder, noop_visit_mut_type, visit_mut_obj_and_computed, Fold, VisitMut, VisitMutWith,
|
||||
};
|
||||
use tracing::{debug, span, Level};
|
||||
|
||||
use crate::scope::{IdentType, ScopeKind};
|
||||
use crate::scope::{DeclKind, IdentType, ScopeKind};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@ -143,6 +146,8 @@ pub fn resolver(
|
||||
ident_type: IdentType::Ref,
|
||||
in_type: false,
|
||||
in_ts_module: false,
|
||||
decl_kind: DeclKind::Lexical,
|
||||
strict_mode: false,
|
||||
config: InnerConfig {
|
||||
handle_types: typescript,
|
||||
unresolved_mark,
|
||||
@ -162,7 +167,7 @@ struct Scope<'a> {
|
||||
mark: Mark,
|
||||
|
||||
/// All declarations in the scope
|
||||
declared_symbols: AHashSet<JsWord>,
|
||||
declared_symbols: AHashMap<JsWord, DeclKind>,
|
||||
|
||||
/// All types declared in the scope
|
||||
declared_types: AHashSet<JsWord>,
|
||||
@ -179,12 +184,10 @@ impl<'a> Scope<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_declared(&self, symbol: &JsWord) -> bool {
|
||||
if self.declared_symbols.contains(symbol) {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.parent.map_or(false, |p| p.is_declared(symbol))
|
||||
fn is_declared(&self, symbol: &JsWord) -> Option<&DeclKind> {
|
||||
self.declared_symbols
|
||||
.get(symbol)
|
||||
.or_else(|| self.parent?.is_declared(symbol))
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,6 +201,8 @@ struct Resolver<'a> {
|
||||
ident_type: IdentType,
|
||||
in_type: bool,
|
||||
in_ts_module: bool,
|
||||
decl_kind: DeclKind,
|
||||
strict_mode: bool,
|
||||
|
||||
config: InnerConfig,
|
||||
}
|
||||
@ -217,6 +222,8 @@ impl<'a> Resolver<'a> {
|
||||
in_type: false,
|
||||
in_ts_module: false,
|
||||
config,
|
||||
decl_kind: DeclKind::Lexical,
|
||||
strict_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,6 +237,8 @@ impl<'a> Resolver<'a> {
|
||||
config: self.config,
|
||||
in_type: self.in_type,
|
||||
in_ts_module: self.in_ts_module,
|
||||
decl_kind: self.decl_kind,
|
||||
strict_mode: self.strict_mode,
|
||||
};
|
||||
|
||||
op(&mut child);
|
||||
@ -290,7 +299,7 @@ impl<'a> Resolver<'a> {
|
||||
let mut scope = Some(&self.current);
|
||||
|
||||
while let Some(cur) = scope {
|
||||
if cur.declared_symbols.contains(sym) {
|
||||
if cur.declared_symbols.contains_key(sym) {
|
||||
if mark == Mark::root() {
|
||||
return None;
|
||||
}
|
||||
@ -311,7 +320,7 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
|
||||
/// Modifies a binding identifier.
|
||||
fn modify(&mut self, ident: &mut Ident, kind: Option<VarDeclKind>) {
|
||||
fn modify(&mut self, ident: &mut Ident, kind: DeclKind) {
|
||||
if cfg!(debug_assertions) && LOG {
|
||||
debug!(
|
||||
"Binding (type = {}) {}{:?} {:?}",
|
||||
@ -344,7 +353,9 @@ impl<'a> Resolver<'a> {
|
||||
|
||||
let mark = self.current.mark;
|
||||
|
||||
self.current.declared_symbols.insert(ident.sym.clone());
|
||||
self.current
|
||||
.declared_symbols
|
||||
.insert(ident.sym.clone(), kind);
|
||||
|
||||
ident.span = if mark == Mark::root() {
|
||||
ident.span
|
||||
@ -515,7 +526,96 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
// TODO: How should I handle this?
|
||||
typed!(visit_mut_ts_namespace_export_decl, TsNamespaceExportDecl);
|
||||
|
||||
track_ident_mut!();
|
||||
fn visit_mut_export_specifier(&mut self, s: &mut ExportSpecifier) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Ref;
|
||||
s.visit_mut_children_with(self);
|
||||
self.ident_type = old;
|
||||
}
|
||||
|
||||
fn visit_mut_import_specifier(&mut self, s: &mut ImportSpecifier) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Binding;
|
||||
|
||||
match s {
|
||||
ImportSpecifier::Named(ImportNamedSpecifier { imported: None, .. })
|
||||
| ImportSpecifier::Namespace(..)
|
||||
| ImportSpecifier::Default(..) => s.visit_mut_children_with(self),
|
||||
ImportSpecifier::Named(s) => s.local.visit_mut_with(self),
|
||||
};
|
||||
|
||||
self.ident_type = old;
|
||||
}
|
||||
|
||||
fn visit_mut_getter_prop(&mut self, f: &mut GetterProp) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Ref;
|
||||
f.key.visit_mut_with(self);
|
||||
self.ident_type = old;
|
||||
|
||||
f.type_ann.visit_mut_with(self);
|
||||
|
||||
f.body.visit_mut_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_labeled_stmt(&mut self, s: &mut LabeledStmt) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Label;
|
||||
s.label.visit_mut_with(self);
|
||||
self.ident_type = old;
|
||||
|
||||
s.body.visit_mut_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_break_stmt(&mut self, s: &mut BreakStmt) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Label;
|
||||
s.label.visit_mut_with(self);
|
||||
self.ident_type = old;
|
||||
}
|
||||
|
||||
fn visit_mut_continue_stmt(&mut self, s: &mut ContinueStmt) {
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Label;
|
||||
s.label.visit_mut_with(self);
|
||||
self.ident_type = old;
|
||||
}
|
||||
|
||||
fn visit_mut_key_value_pat_prop(&mut self, n: &mut KeyValuePatProp) {
|
||||
n.key.visit_mut_with(self);
|
||||
n.value.visit_mut_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_class(&mut self, c: &mut Class) {
|
||||
let old_strict_mode = self.strict_mode;
|
||||
self.strict_mode = true;
|
||||
|
||||
let old = self.ident_type;
|
||||
self.ident_type = IdentType::Ref;
|
||||
c.decorators.visit_mut_with(self);
|
||||
|
||||
self.ident_type = IdentType::Ref;
|
||||
c.super_class.visit_mut_with(self);
|
||||
|
||||
self.ident_type = IdentType::Binding;
|
||||
c.type_params.visit_mut_with(self);
|
||||
|
||||
self.ident_type = IdentType::Ref;
|
||||
c.super_type_params.visit_mut_with(self);
|
||||
|
||||
self.ident_type = IdentType::Ref;
|
||||
c.implements.visit_mut_with(self);
|
||||
self.ident_type = old;
|
||||
|
||||
c.body.visit_mut_with(self);
|
||||
self.strict_mode = old_strict_mode;
|
||||
}
|
||||
|
||||
fn visit_mut_prop_name(&mut self, n: &mut PropName) {
|
||||
if let PropName::Computed(c) = n {
|
||||
c.visit_mut_with(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_arrow_expr(&mut self, e: &mut ArrowExpr) {
|
||||
self.with_child(ScopeKind::Fn, |child| {
|
||||
@ -528,7 +628,16 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
|
||||
{
|
||||
match &mut e.body {
|
||||
BlockStmtOrExpr::BlockStmt(s) => s.stmts.visit_mut_with(child),
|
||||
BlockStmtOrExpr::BlockStmt(s) => {
|
||||
let old_strict_mode = child.strict_mode;
|
||||
child.strict_mode = s
|
||||
.stmts
|
||||
.first()
|
||||
.map(|stmt| stmt.is_use_strict())
|
||||
.unwrap_or(false);
|
||||
s.stmts.visit_mut_with(child);
|
||||
child.strict_mode = old_strict_mode;
|
||||
}
|
||||
BlockStmtOrExpr::Expr(e) => e.visit_mut_with(child),
|
||||
}
|
||||
}
|
||||
@ -578,7 +687,7 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
}
|
||||
|
||||
fn visit_mut_class_decl(&mut self, n: &mut ClassDecl) {
|
||||
self.modify(&mut n.ident, None);
|
||||
self.modify(&mut n.ident, DeclKind::Lexical);
|
||||
|
||||
n.class.decorators.visit_mut_with(self);
|
||||
|
||||
@ -732,7 +841,7 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
|
||||
self.with_child(ScopeKind::Fn, |child| {
|
||||
if let Some(ident) = &mut e.ident {
|
||||
child.modify(ident, None)
|
||||
child.modify(ident, DeclKind::Function)
|
||||
}
|
||||
e.function.visit_mut_with(child);
|
||||
});
|
||||
@ -783,8 +892,15 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
self.ident_type = IdentType::Ref;
|
||||
match &mut f.body {
|
||||
Some(body) => {
|
||||
let old_strict_mode = self.strict_mode;
|
||||
self.strict_mode = body
|
||||
.stmts
|
||||
.first()
|
||||
.map(|stmt| stmt.is_use_strict())
|
||||
.unwrap_or(false);
|
||||
// Prevent creating new scope.
|
||||
body.visit_mut_children_with(self);
|
||||
self.strict_mode = old_strict_mode;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
@ -796,7 +912,7 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
}
|
||||
|
||||
match self.ident_type {
|
||||
IdentType::Binding => self.modify(i, None),
|
||||
IdentType::Binding => self.modify(i, self.decl_kind),
|
||||
IdentType::Ref => {
|
||||
let Ident { span, sym, .. } = i;
|
||||
|
||||
@ -833,7 +949,7 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
|
||||
i.span = span;
|
||||
// Support hoisting
|
||||
self.modify(i, None)
|
||||
self.modify(i, self.decl_kind)
|
||||
}
|
||||
}
|
||||
// We currently does not touch labels
|
||||
@ -876,8 +992,8 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
// Phase 1: Handle hoisting
|
||||
{
|
||||
let mut hoister = Hoister {
|
||||
kind: self.decl_kind,
|
||||
resolver: self,
|
||||
kind: None,
|
||||
in_block: false,
|
||||
in_catch_body: false,
|
||||
catch_param_decls: Default::default(),
|
||||
@ -968,8 +1084,8 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
};
|
||||
|
||||
let mut hoister = Hoister {
|
||||
kind: self.decl_kind,
|
||||
resolver: self,
|
||||
kind: None,
|
||||
in_block: false,
|
||||
in_catch_body: false,
|
||||
catch_param_decls: Default::default(),
|
||||
@ -1043,13 +1159,13 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
}
|
||||
|
||||
fn visit_mut_ts_enum_decl(&mut self, decl: &mut TsEnumDecl) {
|
||||
self.modify(&mut decl.id, Some(VarDeclKind::Let));
|
||||
self.modify(&mut decl.id, DeclKind::Lexical);
|
||||
|
||||
self.with_child(ScopeKind::Block, |child| {
|
||||
// add the enum member names as declared symbols for this scope
|
||||
// Ex. `enum Foo { a, b = a }`
|
||||
let member_names = decl.members.iter().filter_map(|m| match &m.id {
|
||||
TsEnumMemberId::Ident(id) => Some(id.sym.clone()),
|
||||
TsEnumMemberId::Ident(id) => Some((id.sym.clone(), DeclKind::Lexical)),
|
||||
TsEnumMemberId::Str(_) => None,
|
||||
});
|
||||
child.current.declared_symbols.extend(member_names);
|
||||
@ -1081,7 +1197,7 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
}
|
||||
|
||||
fn visit_mut_ts_import_equals_decl(&mut self, n: &mut TsImportEqualsDecl) {
|
||||
self.modify(&mut n.id, None);
|
||||
self.modify(&mut n.id, DeclKind::Lexical);
|
||||
|
||||
n.module_ref.visit_mut_with(self);
|
||||
}
|
||||
@ -1090,7 +1206,7 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
// always resolve the identifier for type stripping purposes
|
||||
let old_in_type = self.in_type;
|
||||
self.in_type = true;
|
||||
self.modify(&mut n.id, None);
|
||||
self.modify(&mut n.id, DeclKind::Type);
|
||||
|
||||
if !self.config.handle_types {
|
||||
self.in_type = old_in_type;
|
||||
@ -1141,7 +1257,7 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
fn visit_mut_ts_module_decl(&mut self, decl: &mut TsModuleDecl) {
|
||||
match &mut decl.id {
|
||||
TsModuleName::Ident(i) => {
|
||||
self.modify(i, Some(VarDeclKind::Let));
|
||||
self.modify(i, DeclKind::Lexical);
|
||||
}
|
||||
TsModuleName::Str(_) => {}
|
||||
}
|
||||
@ -1154,7 +1270,7 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
}
|
||||
|
||||
fn visit_mut_ts_namespace_decl(&mut self, n: &mut TsNamespaceDecl) {
|
||||
self.modify(&mut n.id, Some(VarDeclKind::Let));
|
||||
self.modify(&mut n.id, DeclKind::Lexical);
|
||||
|
||||
n.body.visit_mut_with(self);
|
||||
}
|
||||
@ -1209,7 +1325,7 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
// always resolve the identifier for type stripping purposes
|
||||
let old_in_type = self.in_type;
|
||||
self.in_type = true;
|
||||
self.modify(&mut n.id, None);
|
||||
self.modify(&mut n.id, DeclKind::Type);
|
||||
|
||||
if !self.config.handle_types {
|
||||
self.in_type = old_in_type;
|
||||
@ -1254,7 +1370,10 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
}
|
||||
|
||||
fn visit_mut_var_decl(&mut self, decl: &mut VarDecl) {
|
||||
let old_kind = self.decl_kind;
|
||||
self.decl_kind = decl.kind.into();
|
||||
decl.decls.visit_mut_with(self);
|
||||
self.decl_kind = old_kind;
|
||||
}
|
||||
|
||||
fn visit_mut_var_declarator(&mut self, decl: &mut VarDeclarator) {
|
||||
@ -1267,12 +1386,26 @@ impl<'a> VisitMut for Resolver<'a> {
|
||||
|
||||
decl.init.visit_mut_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_script(&mut self, script: &mut Script) {
|
||||
self.strict_mode = script
|
||||
.body
|
||||
.first()
|
||||
.map(|stmt| stmt.is_use_strict())
|
||||
.unwrap_or(false);
|
||||
script.visit_mut_children_with(self)
|
||||
}
|
||||
|
||||
fn visit_mut_module(&mut self, module: &mut Module) {
|
||||
self.strict_mode = true;
|
||||
module.visit_mut_children_with(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// The folder which handles var / function hoisting.
|
||||
struct Hoister<'a, 'b> {
|
||||
resolver: &'a mut Resolver<'b>,
|
||||
kind: Option<VarDeclKind>,
|
||||
kind: DeclKind,
|
||||
/// Hoister should not touch let / const in the block.
|
||||
in_block: bool,
|
||||
|
||||
@ -1397,8 +1530,7 @@ impl VisitMut for Hoister<'_, '_> {
|
||||
if self.in_block {
|
||||
return;
|
||||
}
|
||||
self.resolver
|
||||
.modify(&mut node.ident, Some(VarDeclKind::Let));
|
||||
self.resolver.modify(&mut node.ident, DeclKind::Lexical);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -1413,14 +1545,14 @@ impl VisitMut for Hoister<'_, '_> {
|
||||
Decl::TsInterface(i) => {
|
||||
let old_in_type = self.resolver.in_type;
|
||||
self.resolver.in_type = true;
|
||||
self.resolver.modify(&mut i.id, None);
|
||||
self.resolver.modify(&mut i.id, DeclKind::Type);
|
||||
self.resolver.in_type = old_in_type;
|
||||
}
|
||||
|
||||
Decl::TsTypeAlias(a) => {
|
||||
let old_in_type = self.resolver.in_type;
|
||||
self.resolver.in_type = true;
|
||||
self.resolver.modify(&mut a.id, None);
|
||||
self.resolver.modify(&mut a.id, DeclKind::Type);
|
||||
self.resolver.in_type = old_in_type;
|
||||
}
|
||||
|
||||
@ -1428,7 +1560,7 @@ impl VisitMut for Hoister<'_, '_> {
|
||||
if !self.in_block {
|
||||
let old_in_type = self.resolver.in_type;
|
||||
self.resolver.in_type = false;
|
||||
self.resolver.modify(&mut e.id, None);
|
||||
self.resolver.modify(&mut e.id, DeclKind::Lexical);
|
||||
self.resolver.in_type = old_in_type;
|
||||
}
|
||||
}
|
||||
@ -1441,7 +1573,7 @@ impl VisitMut for Hoister<'_, '_> {
|
||||
if !self.in_block {
|
||||
let old_in_type = self.resolver.in_type;
|
||||
self.resolver.in_type = false;
|
||||
self.resolver.modify(id, None);
|
||||
self.resolver.modify(id, DeclKind::Lexical);
|
||||
self.resolver.in_type = old_in_type;
|
||||
}
|
||||
}
|
||||
@ -1456,14 +1588,14 @@ impl VisitMut for Hoister<'_, '_> {
|
||||
match &mut node.decl {
|
||||
DefaultDecl::Fn(f) => {
|
||||
if let Some(id) = &mut f.ident {
|
||||
self.resolver.modify(id, Some(VarDeclKind::Var));
|
||||
self.resolver.modify(id, DeclKind::Var);
|
||||
}
|
||||
|
||||
f.visit_mut_with(self)
|
||||
}
|
||||
DefaultDecl::Class(c) => {
|
||||
if let Some(id) = &mut c.ident {
|
||||
self.resolver.modify(id, Some(VarDeclKind::Let));
|
||||
self.resolver.modify(id, DeclKind::Lexical);
|
||||
}
|
||||
|
||||
c.visit_mut_with(self)
|
||||
@ -1483,17 +1615,20 @@ impl VisitMut for Hoister<'_, '_> {
|
||||
}
|
||||
|
||||
if self.in_block {
|
||||
// If we are in nested block, and variable named `foo` is declared, we should
|
||||
// ignore function foo while handling upper scopes.
|
||||
let i = node.ident.clone();
|
||||
|
||||
if self.resolver.current.is_declared(&i.sym) {
|
||||
// function declaration is block scoped in strict mode
|
||||
if self.resolver.strict_mode {
|
||||
return;
|
||||
}
|
||||
// If we are in nested block, and variable named `foo` is lexically declared or
|
||||
// a parameter, we should ignore function foo while handling upper scopes.
|
||||
if let Some(DeclKind::Lexical | DeclKind::Param) =
|
||||
self.resolver.current.is_declared(&node.ident.sym)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.resolver
|
||||
.modify(&mut node.ident, Some(VarDeclKind::Var));
|
||||
self.resolver.modify(&mut node.ident, DeclKind::Function);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -1502,19 +1637,19 @@ impl VisitMut for Hoister<'_, '_> {
|
||||
fn visit_mut_import_default_specifier(&mut self, n: &mut ImportDefaultSpecifier) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
self.resolver.modify(&mut n.local, None);
|
||||
self.resolver.modify(&mut n.local, DeclKind::Lexical);
|
||||
}
|
||||
|
||||
fn visit_mut_import_named_specifier(&mut self, n: &mut ImportNamedSpecifier) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
self.resolver.modify(&mut n.local, None);
|
||||
self.resolver.modify(&mut n.local, DeclKind::Lexical);
|
||||
}
|
||||
|
||||
fn visit_mut_import_star_as_specifier(&mut self, n: &mut ImportStarAsSpecifier) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
self.resolver.modify(&mut n.local, None);
|
||||
self.resolver.modify(&mut n.local, DeclKind::Lexical);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -1563,7 +1698,7 @@ impl VisitMut for Hoister<'_, '_> {
|
||||
}
|
||||
|
||||
let old_kind = self.kind;
|
||||
self.kind = Some(node.kind);
|
||||
self.kind = node.kind.into();
|
||||
|
||||
node.visit_mut_children_with(self);
|
||||
|
||||
|
@ -76,7 +76,10 @@ fn test_mark_for() {
|
||||
unresolved_mark: Mark::fresh(Mark::root()),
|
||||
},
|
||||
);
|
||||
folder2.current.declared_symbols.insert("foo".into());
|
||||
folder2
|
||||
.current
|
||||
.declared_symbols
|
||||
.insert("foo".into(), DeclKind::Var);
|
||||
|
||||
let mut folder3 = Resolver::new(
|
||||
Scope::new(ScopeKind::Block, mark3, Some(&folder2.current)),
|
||||
@ -85,7 +88,10 @@ fn test_mark_for() {
|
||||
unresolved_mark: Mark::fresh(Mark::root()),
|
||||
},
|
||||
);
|
||||
folder3.current.declared_symbols.insert("bar".into());
|
||||
folder3
|
||||
.current
|
||||
.declared_symbols
|
||||
.insert("bar".into(), DeclKind::Var);
|
||||
assert_eq!(folder3.mark_for_ref(&"bar".into()), Some(mark3));
|
||||
|
||||
let mut folder4 = Resolver::new(
|
||||
@ -95,7 +101,10 @@ fn test_mark_for() {
|
||||
unresolved_mark: Mark::fresh(Mark::root()),
|
||||
},
|
||||
);
|
||||
folder4.current.declared_symbols.insert("foo".into());
|
||||
folder4
|
||||
.current
|
||||
.declared_symbols
|
||||
.insert("foo".into(), DeclKind::Var);
|
||||
|
||||
assert_eq!(folder4.mark_for_ref(&"foo".into()), Some(mark4));
|
||||
assert_eq!(folder4.mark_for_ref(&"bar".into()), Some(mark3));
|
||||
|
@ -1,3 +1,5 @@
|
||||
use swc_ecma_ast::VarDeclKind;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ScopeKind {
|
||||
Block,
|
||||
@ -16,3 +18,22 @@ pub enum IdentType {
|
||||
Ref,
|
||||
Label,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DeclKind {
|
||||
Lexical,
|
||||
Param,
|
||||
Var,
|
||||
Function,
|
||||
/// don't actually get stored
|
||||
Type,
|
||||
}
|
||||
|
||||
impl From<VarDeclKind> for DeclKind {
|
||||
fn from(kind: VarDeclKind) -> Self {
|
||||
match kind {
|
||||
VarDeclKind::Const | VarDeclKind::Let => Self::Lexical,
|
||||
VarDeclKind::Var => Self::Var,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
function foo() {
|
||||
const r = () => 1;
|
||||
if (true) {
|
||||
function r() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(r());
|
||||
}
|
||||
|
||||
function bar() {
|
||||
var r = () => 1;
|
||||
if (true) {
|
||||
function r() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(r());
|
||||
}
|
||||
|
||||
function baz() {
|
||||
function r() {
|
||||
return 1;
|
||||
}
|
||||
if (true) {
|
||||
function r() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(r());
|
||||
}
|
||||
|
||||
function quz(r = () => 1) {
|
||||
if (true) {
|
||||
function r() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(r());
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
function foo__1() {
|
||||
const r__2 = ()=>1;
|
||||
if (true) {
|
||||
function r__3() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
console.log(r__2());
|
||||
}
|
||||
function bar__1() {
|
||||
var r__5 = ()=>1;
|
||||
if (true) {
|
||||
function r__5() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
console.log(r__5());
|
||||
}
|
||||
function baz__1() {
|
||||
function r__6() {
|
||||
return 1;
|
||||
}
|
||||
if (true) {
|
||||
function r__6() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
console.log(r__6());
|
||||
}
|
||||
function quz__1(r__7 = ()=>1) {
|
||||
if (true) {
|
||||
function r__8() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
console.log(r__7());
|
||||
}
|
Loading…
Reference in New Issue
Block a user