diff --git a/Cargo.lock b/Cargo.lock index e0b07cd2b68..32e978e62ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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]] diff --git a/crates/swc_ecma_lints/Cargo.toml b/crates/swc_ecma_lints/Cargo.toml index 79e60617af6..a144eda0207 100644 --- a/crates/swc_ecma_lints/Cargo.toml +++ b/crates/swc_ecma_lints/Cargo.toml @@ -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"} diff --git a/crates/swc_ecma_lints/src/rules/duplicate_bindings.rs b/crates/swc_ecma_lints/src/rules/duplicate_bindings.rs index cdb0acf3a34..799e776fb1d 100644 --- a/crates/swc_ecma_lints/src/rules/duplicate_bindings.rs +++ b/crates/swc_ecma_lints/src/rules/duplicate_bindings.rs @@ -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); } diff --git a/crates/swc_ecma_lints/tests/fixture.rs b/crates/swc_ecma_lints/tests/fixture.rs new file mode 100644 index 00000000000..5a8b00b0e37 --- /dev/null +++ b/crates/swc_ecma_lints/tests/fixture.rs @@ -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(); +} diff --git a/crates/swc_ecma_lints/tests/pass/issue-3193/1/input.ts b/crates/swc_ecma_lints/tests/pass/issue-3193/1/input.ts new file mode 100644 index 00000000000..ddeea8e27ec --- /dev/null +++ b/crates/swc_ecma_lints/tests/pass/issue-3193/1/input.ts @@ -0,0 +1,3 @@ +function test(); +function test(a, b) { +} \ No newline at end of file diff --git a/crates/swc_ecma_lints/tests/pass/vercel/1/input.js b/crates/swc_ecma_lints/tests/pass/vercel/1/input.js new file mode 100644 index 00000000000..b15210952c1 --- /dev/null +++ b/crates/swc_ecma_lints/tests/pass/vercel/1/input.js @@ -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!'))); + } + + } +} \ No newline at end of file diff --git a/crates/swc_ecma_transforms_base/src/resolver/mod.rs b/crates/swc_ecma_transforms_base/src/resolver/mod.rs index 2162a0524fc..f8bd9b935f3 100644 --- a/crates/swc_ecma_transforms_base/src/resolver/mod.rs +++ b/crates/swc_ecma_transforms_base/src/resolver/mod.rs @@ -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) { 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) { + 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 diff --git a/crates/swc_ecma_transforms_base/tests/ts-resolver/issue-2627/1/input.ts b/crates/swc_ecma_transforms_base/tests/ts-resolver/issue-2627/1/input.ts new file mode 100644 index 00000000000..85e5a4df720 --- /dev/null +++ b/crates/swc_ecma_transforms_base/tests/ts-resolver/issue-2627/1/input.ts @@ -0,0 +1,8 @@ +{ + let a = 1; + const b = 1; + var c = 1; +} +console.log(a); +console.log(b); +console.log(c); \ No newline at end of file diff --git a/crates/swc_ecma_transforms_base/tests/ts-resolver/issue-2627/1/output.ts b/crates/swc_ecma_transforms_base/tests/ts-resolver/issue-2627/1/output.ts new file mode 100644 index 00000000000..752c8ab629c --- /dev/null +++ b/crates/swc_ecma_transforms_base/tests/ts-resolver/issue-2627/1/output.ts @@ -0,0 +1,7 @@ +{ + let a__2 = 1; + const b__2 = 1; + var c = 1; +}console.log(a); +console.log(b); +console.log(c); diff --git a/crates/swc_ecma_transforms_base/tests/ts-resolver/vercel/1/input.ts b/crates/swc_ecma_transforms_base/tests/ts-resolver/vercel/1/input.ts new file mode 100644 index 00000000000..38cd3006304 --- /dev/null +++ b/crates/swc_ecma_transforms_base/tests/ts-resolver/vercel/1/input.ts @@ -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!'))); + } + + } +} \ No newline at end of file diff --git a/crates/swc_ecma_transforms_base/tests/ts-resolver/vercel/1/output.ts b/crates/swc_ecma_transforms_base/tests/ts-resolver/vercel/1/output.ts new file mode 100644 index 00000000000..26a89ffe105 --- /dev/null +++ b/crates/swc_ecma_transforms_base/tests/ts-resolver/vercel/1/output.ts @@ -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!')) + ); + } + } +}