fix(es/lints): Fix incorrect duplicate binding error (#3194)

swc_ecma_transforms_base:
 - `resolver`: Fix resolving of `const` and `let`.

swc_ecma_lints:
 - `duplicate_bindings`: Don't mark ambient function declararions as a binding. (Closes #3193)
This commit is contained in:
Donny/강동윤 2022-01-05 11:59:10 +09:00 committed by GitHub
parent e2b5a31882
commit 913c82a2ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 206 additions and 11 deletions

4
Cargo.lock generated
View File

@ -2998,8 +2998,12 @@ dependencies = [
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_codegen",
"swc_ecma_parser",
"swc_ecma_transforms_base",
"swc_ecma_utils",
"swc_ecma_visit",
"testing",
]
[[package]]

View File

@ -17,3 +17,9 @@ swc_common = {version = "0.16.0", path = "../swc_common"}
swc_ecma_ast = {version = "0.62.0", path = "../swc_ecma_ast"}
swc_ecma_utils = {version = "0.59.0", path = "../swc_ecma_utils"}
swc_ecma_visit = {version = "0.48.0", path = "../swc_ecma_visit"}
[dev-dependencies]
swc_ecma_codegen = {version = "0.86.0", path = "../swc_ecma_codegen"}
swc_ecma_parser = {version = "0.84.0", path = "../swc_ecma_parser"}
swc_ecma_transforms_base = {version = "0.52.0", path = "../swc_ecma_transforms_base"}
testing = {version = "0.17.0", path = "../testing"}

View File

@ -77,7 +77,9 @@ impl Visit for DuplicateBindings {
}
fn visit_fn_decl(&mut self, d: &FnDecl) {
self.add(&d.ident, false);
if d.function.body.is_some() {
self.add(&d.ident, false);
}
d.visit_children_with(self);
}

View File

@ -0,0 +1,57 @@
use std::path::PathBuf;
use swc_common::input::SourceFileInput;
use swc_ecma_ast::EsVersion;
use swc_ecma_lints::rules::all;
use swc_ecma_parser::{lexer::Lexer, Parser, Syntax};
use swc_ecma_transforms_base::resolver::resolver;
use swc_ecma_utils::HANDLER;
use swc_ecma_visit::VisitMutWith;
#[testing::fixture("tests/pass/**/input.js")]
#[testing::fixture("tests/pass/**/input.ts")]
fn pass(input: PathBuf) {
testing::run_test(false, |cm, handler| {
let fm = cm.load_file(&input).unwrap();
let lexer = Lexer::new(
if input.extension().unwrap() == "ts" {
Syntax::Typescript(swc_ecma_parser::TsConfig {
..Default::default()
})
} else if input.extension().unwrap() == "tsx" {
Syntax::Typescript(swc_ecma_parser::TsConfig {
tsx: true,
..Default::default()
})
} else {
Syntax::Es(swc_ecma_parser::EsConfig {
..Default::default()
})
},
EsVersion::latest(),
SourceFileInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
let mut m = parser.parse_module().unwrap();
m.visit_mut_with(&mut resolver());
let rules = all();
HANDLER.set(&handler, || {
for mut rule in rules {
rule.lint_module(&m);
}
});
if handler.has_errors() {
return Err(());
}
Ok(())
})
.unwrap();
}

View File

@ -0,0 +1,3 @@
function test();
function test(a, b) {
}

View File

@ -0,0 +1,25 @@
'use strict';
var n = t(3957);
function o(e) {
const r = e.nextUrl;
if ('/log' !== r.pathname) {
if ('/throw-error-internal' === r.pathname) {
function r() {
return e();
}
try {
r();
} catch (o) {
console.error(o);
}
return new Promise((e, r) => r(new Error('oh no!')));
}
}
}

View File

@ -5,11 +5,12 @@ use swc_common::{collections::AHashSet, Mark, SyntaxContext};
use swc_ecma_ast::*;
use swc_ecma_utils::{find_ids, Id};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
use tracing::{debug, span, Level};
#[cfg(test)]
mod tests;
const LOG: bool = false;
const LOG: bool = false && cfg!(debug_assertions);
/// See [resolver_with_mark] for docs.
pub fn resolver() -> impl 'static + Fold + VisitMut {
@ -119,6 +120,14 @@ impl<'a> Scope<'a> {
declared_types: Default::default(),
}
}
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))
}
}
/// # Phases
@ -211,8 +220,8 @@ impl<'a> Resolver<'a> {
/// Modifies a binding identifier.
fn modify(&mut self, ident: &mut Ident, kind: Option<VarDeclKind>) {
if cfg!(debug_assertions) && LOG {
eprintln!(
"resolver: Binding (type = {}) {}{:?} {:?}",
debug!(
"Binding (type = {}) {}{:?} {:?}",
self.in_type,
ident.sym,
ident.span.ctxt(),
@ -233,7 +242,7 @@ impl<'a> Resolver<'a> {
} else {
let span = ident.span.apply_mark(mark);
if cfg!(debug_assertions) && LOG {
eprintln!("\t-> {:?}", span.ctxt());
debug!("\t-> {:?}", span.ctxt());
}
span
};
@ -317,7 +326,7 @@ impl<'a> Resolver<'a> {
} else {
let span = ident.span.apply_mark(mark);
if cfg!(debug_assertions) && LOG {
eprintln!("\t-> {:?}", span.ctxt());
debug!("\t-> {:?}", span.ctxt());
}
span
};
@ -615,6 +624,12 @@ impl<'a> VisitMut for Resolver<'a> {
}
fn visit_mut_expr(&mut self, expr: &mut Expr) {
let _span = if LOG {
Some(span!(Level::ERROR, "visit_mut_expr").entered())
} else {
None
};
self.in_type = false;
let old = self.ident_type;
self.ident_type = IdentType::Ref;
@ -726,8 +741,8 @@ impl<'a> VisitMut for Resolver<'a> {
let Ident { span, sym, .. } = i;
if cfg!(debug_assertions) && LOG {
eprintln!(
"resolver: IdentRef (type = {}) {}{:?}",
debug!(
"IdentRef (type = {}) {}{:?}",
self.in_type,
sym,
span.ctxt()
@ -742,12 +757,12 @@ impl<'a> VisitMut for Resolver<'a> {
let span = span.apply_mark(mark);
if cfg!(debug_assertions) && LOG {
eprintln!("\t -> {:?}", span.ctxt());
debug!("\t -> {:?}", span.ctxt());
}
i.span = span;
} else {
if cfg!(debug_assertions) && LOG {
eprintln!("\t -> Unresolved");
debug!("\t -> Unresolved");
}
let mark = {
@ -771,7 +786,7 @@ impl<'a> VisitMut for Resolver<'a> {
let span = span.apply_mark(mark);
if cfg!(debug_assertions) && LOG {
eprintln!("\t -> {:?}", span.ctxt());
debug!("\t -> {:?}", span.ctxt());
}
i.span = span;
@ -917,8 +932,20 @@ impl<'a> VisitMut for Resolver<'a> {
}
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
let _span = if LOG {
Some(span!(Level::ERROR, "visit_mut_stmts").entered())
} else {
None
};
// Phase 1: Handle hoisting
{
let _span = if LOG {
Some(span!(Level::ERROR, "hoist").entered())
} else {
None
};
let mut hoister = Hoister {
resolver: self,
kind: None,
@ -1386,6 +1413,21 @@ impl VisitMut for Hoister<'_, '_> {
if self.catch_param_decls.contains(&node.ident.sym) {
return;
}
let _span = if LOG {
Some(span!(Level::ERROR, "Hoister.visit_mut_fn_decl").entered())
} else {
None
};
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) {
return;
}
}
self.resolver.in_type = false;
self.resolver

View File

@ -0,0 +1,8 @@
{
let a = 1;
const b = 1;
var c = 1;
}
console.log(a);
console.log(b);
console.log(c);

View File

@ -0,0 +1,7 @@
{
let a__2 = 1;
const b__2 = 1;
var c = 1;
}console.log(a);
console.log(b);
console.log(c);

View File

@ -0,0 +1,23 @@
var n = t(3957);
function o(e) {
const r = e.nextUrl;
if ('/log' !== r.pathname) {
if ('/throw-error-internal' === r.pathname) {
function r() {
return e();
}
try {
r();
} catch (o) {
console.error(o);
}
return new Promise((e, r) => r(new Error('oh no!')));
}
}
}

View File

@ -0,0 +1,18 @@
var n = t(3957);
function o(e__2) {
const r__2 = e__2.nextUrl;
if ('/log' !== r__2.pathname) {
if ('/throw-error-internal' === r__2.pathname) {
function r__3() {
return e__2();
}
try {
r__3();
} catch (o__4) {
console.error(o__4);
}
return new Promise((e__5, r__5)=>r__5(new Error('oh no!'))
);
}
}
}