fix(es/resolver): Fix handling of block scoped functions (#5092)

This commit is contained in:
Austaras 2022-07-05 13:04:49 +08:00 committed by GitHub
parent 6d6e4dd096
commit 9519e801ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 830 additions and 280 deletions

View File

@ -0,0 +1,6 @@
try {
var fx
function fx(){}
} catch {
}

View File

@ -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
`----

View File

@ -0,0 +1,5 @@
switch (a) {
case 'a':
var foo
function foo() {}
}

View File

@ -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
`----

View File

@ -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();
});
}

View File

@ -0,0 +1,6 @@
export class A {
static {
var fx
function fx(){}
}
}

View File

@ -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: [],
},

View File

@ -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;
}

View File

@ -1,7 +1,7 @@
switch(function() {
return a;
return xxx;
}){
case a:
case xxx:
for(; console.log("FAIL");){
function a() {}
}

View File

@ -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: [],
},

View File

@ -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;
}

View File

@ -1,8 +1,8 @@
"use strict";
switch(function() {
return a;
return xxx;
}){
case a:
case xxx:
for(; console.log("FAIL");){
function a() {}
}

View File

@ -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: [],
},

View File

@ -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;
}

View File

@ -1,7 +1,7 @@
switch(function() {
return a;
return xxx;
}){
case a:
case xxx:
if (console.log("FAIL")) {
function a() {}
}

View File

@ -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: [],
},

View File

@ -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;
}

View File

@ -1,8 +1,8 @@
"use strict";
switch(function() {
return a;
return xxx;
}){
case a:
case xxx:
if (console.log("FAIL")) {
function a() {}
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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: [],
},

View File

@ -1,4 +1,4 @@
var foo, bar;
var foo, bar1;
var x = 10, y;
var moo;
bar();

View File

@ -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();

View File

@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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);

View File

@ -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) {

View File

@ -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.

View File

@ -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);

View File

@ -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));

View File

@ -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,
}
}
}

View File

@ -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());
}

View File

@ -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());
}