Repository: denoland/deno_lint
Pull request #632 (2021-03-08 17:31:05)

refactor(no-setter-return): refine with ast-view and add sufficient tests
Part of #330, #431

This PR does:

- rewrite with dprint_swc_ecma_ast_view
- refine the implementation to avoid false negatives
- add lots of tests (most come from ESLint)

magurotuna: @lucacasonato do you have any idea about this error? https://github.com/denoland/deno_lint/pull/632/checks?check_run_id=2058739263#step:17:307

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9373171..4d3c76e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -24,7 +24,7 @@ jobs:
       - name: Install rust
         uses: hecrj/setup-rust-action@v1.3.4
         with:
-          rust-version: 1.50.0
+          rust-version: 1.51.0
 
       - name: Install clippy and rustfmt
         run: |
diff --git a/Cargo.lock b/Cargo.lock
index e19b821..e8dd443 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -244,7 +244,7 @@ dependencies = [
 
 [[package]]
 name = "deno_lint"
-version = "0.2.19"
+version = "0.2.20"
 dependencies = [
  "annotate-snippets",
  "anyhow",
diff --git a/Cargo.toml b/Cargo.toml
index 00bda2e..fb633fc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "deno_lint"
-version = "0.2.19"
+version = "0.2.20"
 edition = "2018"
 description = "lint for deno"
 authors = ["the Deno authors"]
@@ -22,7 +22,7 @@ serde_json = "1.0.62"
 swc_atoms = "0.2.5"
 swc_common = "0.10.12"
 swc_ecmascript = { version = "0.24.1", features = ["parser", "transforms", "utils", "visit"] }
-regex = "1.4.3"
+regex = "=1.4.3"
 once_cell = "1.5.2"
 derive_more = { version = "0.99.11", features = ["display"] }
 anyhow = "1.0.38"
diff --git a/README.md b/README.md
index 33019ae..80b3ed4 100644
--- a/README.md
+++ b/README.md
@@ -130,12 +130,12 @@ For more concrete implementation visit [`deno`](https://github.com/denoland/deno
 
 ## Developing
 
-Make sure to have latest stable version of Rust installed (1.50.0).
+Make sure to have latest stable version of Rust installed (1.51.0).
 
 ```shell
 // check version
 $ rustc --version
-rustc 1.50.0 (cb75ad5db 2021-02-10)
+rustc 1.51.0 (2fd73fabe 2021-03-23)
 
 // build all targets
 $ cargo build --all-targets
diff --git a/examples/dlint/js.rs b/examples/dlint/js.rs
index 3ad2a33..862f3aa 100644
--- a/examples/dlint/js.rs
+++ b/examples/dlint/js.rs
@@ -17,7 +17,7 @@ use swc_common::Span;
 use swc_ecmascript::ast::Program;
 
 #[derive(Deserialize)]
-struct DiagnosticsFromJS {
+struct DiagnosticsFromJs {
   code: String,
   diagnostics: Vec<InnerDiagnostics>,
 }
@@ -43,7 +43,7 @@ fn op_add_diagnostics(
   args: Value,
   _bufs: &mut [ZeroCopyBuf],
 ) -> anyhow::Result<Value> {
-  let DiagnosticsFromJS { code, diagnostics } =
+  let DiagnosticsFromJs { code, diagnostics } =
     serde_json::from_value(args).unwrap();
 
   let mut stored = state.try_take::<Diagnostics>().unwrap_or_else(HashMap::new);
@@ -80,10 +80,10 @@ fn op_query_control_flow_by_span(
     .context("ControlFlow is not set")?;
 
   #[derive(Deserialize)]
-  struct SpanFromJS {
+  struct SpanFromJs {
     span: Span,
   }
-  let span_from_js: SpanFromJS = serde_json::from_value(args).unwrap();
+  let span_from_js: SpanFromJs = serde_json::from_value(args).unwrap();
   let meta = control_flow.meta(span_from_js.span.lo());
 
   let is_reachable = meta.map(|m| !m.unreachable);
diff --git a/src/control_flow.rs b/src/control_flow.rs
index f6d7cfc..dedfbdd 100644
--- a/src/control_flow.rs
+++ b/src/control_flow.rs
@@ -261,7 +261,7 @@ impl Analyzer<'_> {
         BlockKind::Function => {
           match end {
             End::Forced { .. } | End::Continue => self.mark_as_end(lo, end),
-            _ => unreachable!(),
+            _ => { /* valid code is supposed to be unreachable here */ }
           }
           self.scope.end = prev_end;
         }
@@ -1530,4 +1530,21 @@ throw err;
     assert_flow!(flow, 54, false, Some(End::forced_return())); // return stmt
     assert_flow!(flow, 70, false, Some(End::forced_throw())); // throw stmt
   }
+
+  // https://github.com/denoland/deno_lint/issues/644
+  #[test]
+  fn issue_644() {
+    let src = r#"
+function foo() {
+  break;
+}
+
+function bar() {
+  continue;
+}
+"#;
+
+    // Confirms that no panic happens even if there's invalid `break` or `continue` statement
+    let _ = analyze_flow(src);
+  }
 }
diff --git a/src/globals.rs b/src/globals.rs
index e2bdb7a..a736d72 100644
--- a/src/globals.rs
+++ b/src/globals.rs
@@ -92,6 +92,7 @@ pub static GLOBALS: &[(&str, bool)] = &[
   ("hasOwnProperty", false),
   ("Headers", false),
   ("Infinity", false),
+  ("Intl", false),
   ("Int16Array", false),
   ("Int32Array", false),
   ("Int8Array", false),
diff --git a/src/handler.rs b/src/handler.rs
index 5673336..bba0862 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -1,3 +1,4 @@
+// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license.
 use crate::linter::Context;
 use dprint_swc_ecma_ast_view::{self as AstView, NodeTrait};
 
diff --git a/src/js_regex/mod.rs b/src/js_regex/mod.rs
index 795bcfe..ae3d04a 100644
--- a/src/js_regex/mod.rs
+++ b/src/js_regex/mod.rs
@@ -12,7 +12,7 @@ mod tests {
 
   #[test]
   fn valid_flags() {
-    let validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_eq!(validator.validate_flags("gimuys"), Ok(()));
     assert_eq!(validator.validate_flags("gimuy"), Ok(()));
     assert_eq!(validator.validate_flags("gim"), Ok(()));
@@ -31,7 +31,7 @@ mod tests {
 
   #[test]
   fn duplicate_flags() {
-    let validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_eq!(
       validator.validate_flags("gimgu"),
       Err("Duplicated flag g".to_string())
@@ -69,7 +69,7 @@ mod tests {
 
   #[test]
   fn invalid_flags() {
-    let validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_eq!(
       validator.validate_flags("gimuf"),
       Err("Invalid flag f".to_string())
@@ -90,7 +90,7 @@ mod tests {
 
   #[test]
   fn validate_pattern_test() {
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_eq!(validator.validate_pattern("", false), Ok(()));
     assert_eq!(validator.validate_pattern("[abc]de|fg", false), Ok(()));
     assert_eq!(validator.validate_pattern("[abc]de|fg", true), Ok(()));
@@ -149,7 +149,7 @@ mod tests {
 
   #[test]
   fn character_range_order() {
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_ne!(validator.validate_pattern("^[z-a]$", false), Ok(()));
     assert_ne!(validator.validate_pattern("[b-ac-e]", false), Ok(()));
     assert_ne!(validator.validate_pattern("[c-eb-a]", false), Ok(()));
@@ -198,7 +198,7 @@ mod tests {
 
   #[test]
   fn unicode_quantifier_without_atom() {
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_ne!(validator.validate_pattern("*", true), Ok(()));
     assert_ne!(validator.validate_pattern("+", true), Ok(()));
     assert_ne!(validator.validate_pattern("?", true), Ok(()));
@@ -216,7 +216,7 @@ mod tests {
 
   #[test]
   fn unicode_incomplete_quantifier() {
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_ne!(validator.validate_pattern("a{", true), Ok(()));
     assert_ne!(validator.validate_pattern("a{1", true), Ok(()));
     assert_ne!(validator.validate_pattern("a{1,", true), Ok(()));
@@ -230,7 +230,7 @@ mod tests {
 
   #[test]
   fn unicode_single_bracket() {
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_ne!(validator.validate_pattern("(", true), Ok(()));
     assert_ne!(validator.validate_pattern(")", true), Ok(()));
     assert_ne!(validator.validate_pattern("[", true), Ok(()));
@@ -241,7 +241,7 @@ mod tests {
 
   #[test]
   fn unicode_escapes() {
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_eq!(validator.validate_pattern("\\u{10ffff}", true), Ok(()));
     assert_ne!(validator.validate_pattern("\\u{110000}", true), Ok(()));
     assert_eq!(validator.validate_pattern("\\u{110000}", false), Ok(()));
@@ -295,7 +295,7 @@ mod tests {
   #[test]
   fn basic_valid() {
     // source: https://github.com/mysticatea/regexpp/blob/master/test/fixtures/visitor/full.json
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_eq!(validator.validate_pattern("foo", false), Ok(()));
     assert_eq!(validator.validate_pattern("foo|bar", false), Ok(()));
     assert_eq!(validator.validate_pattern("||||", false), Ok(()));
@@ -866,7 +866,7 @@ mod tests {
   #[test]
   fn basic_invalid() {
     // source: https://github.com/mysticatea/regexpp/blob/master/test/fixtures/parser/literal/basic-invalid.json
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES5);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es5);
     assert_ne!(validator.validate_pattern("(", false), Ok(()));
     assert_ne!(validator.validate_pattern("(?", false), Ok(()));
     assert_ne!(validator.validate_pattern("(?=", false), Ok(()));
@@ -913,7 +913,7 @@ mod tests {
   #[test]
   fn basic_invalid_2015() {
     // source: https://github.com/mysticatea/regexpp/blob/master/test/fixtures/parser/literal/basic-invalid-2015.json
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2015);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2015);
     assert_ne!(validator.validate_pattern("(", false), Ok(()));
     assert_ne!(validator.validate_pattern("(?", false), Ok(()));
     assert_ne!(validator.validate_pattern("(?=", false), Ok(()));
@@ -968,7 +968,7 @@ mod tests {
   #[test]
   fn basic_invalid_2015_unicode() {
     // source: https://github.com/mysticatea/regexpp/blob/master/test/fixtures/parser/literal/basic-invalid-2015-u.json
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2015);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2015);
     assert_ne!(validator.validate_pattern("(", true), Ok(()));
     assert_ne!(validator.validate_pattern("(?", true), Ok(()));
     assert_ne!(validator.validate_pattern("(?=", true), Ok(()));
@@ -1094,7 +1094,7 @@ mod tests {
   #[test]
   fn lookbehind_assertion_invalid_2017() {
     // source: https://github.com/mysticatea/regexpp/blob/master/test/fixtures/parser/literal/lookbehind-assertion-invalid-2017.json
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2017);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2017);
     assert_ne!(validator.validate_pattern("(?<a)", false), Ok(()));
     assert_ne!(validator.validate_pattern("(?<a)", true), Ok(()));
     assert_ne!(validator.validate_pattern("(?<=a)", false), Ok(()));
@@ -1106,7 +1106,7 @@ mod tests {
   #[test]
   fn lookbehind_assertion_invalid_2018() {
     // source: https://github.com/mysticatea/regexpp/blob/master/test/fixtures/parser/literal/lookbehind-assertion-invalid-2018.json
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_ne!(validator.validate_pattern("(?<a)", false), Ok(()));
     assert_ne!(validator.validate_pattern("(?<a)", true), Ok(()));
     assert_ne!(validator.validate_pattern("(?<=a)?", false), Ok(()));
@@ -1130,7 +1130,7 @@ mod tests {
   #[test]
   fn named_capturing_group_invalid_2017() {
     // source: https://github.com/mysticatea/regexpp/blob/master/test/fixtures/parser/literal/named-capturing-group-invalid-2017.json
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2017);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2017);
     assert_ne!(validator.validate_pattern("\\k", true), Ok(()));
     assert_ne!(validator.validate_pattern("\\k<a>", true), Ok(()));
     assert_ne!(validator.validate_pattern("(?<a>a)\\k<", false), Ok(()));
@@ -1144,7 +1144,7 @@ mod tests {
   #[test]
   fn named_capturing_group_invalid_2018() {
     // source: https://github.com/mysticatea/regexpp/blob/master/test/fixtures/parser/literal/named-capturing-group-invalid-2018.json
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_ne!(validator.validate_pattern("(?a", false), Ok(()));
     assert_ne!(validator.validate_pattern("(?a)", false), Ok(()));
     assert_ne!(validator.validate_pattern("(?<", false), Ok(()));
@@ -1184,7 +1184,7 @@ mod tests {
   #[test]
   fn unicode_group_names_invalid_2020() {
     // source: https://github.com/mysticatea/regexpp/blob/master/test/fixtures/parser/literal/unicode-group-names-invalid.json
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2020);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2020);
     assert_ne!(
       validator.validate_pattern("(?<\\ud83d\\ude80>.)", false),
       Ok(())
@@ -1205,7 +1205,7 @@ mod tests {
   #[test]
   fn unicode_property_escape_invalid_2017() {
     // source: https://github.com/mysticatea/regexpp/blob/master/test/fixtures/parser/literal/unicode-property-escape-invalid-2017.json
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2017);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2017);
     assert_ne!(validator.validate_pattern("\\p", true), Ok(()));
     assert_ne!(validator.validate_pattern("\\p{", true), Ok(()));
     assert_ne!(validator.validate_pattern("\\p{ASCII", true), Ok(()));
@@ -1215,7 +1215,7 @@ mod tests {
   #[test]
   fn unicode_property_escape_invalid_2018() {
     // source: https://github.com/mysticatea/regexpp/blob/master/test/fixtures/parser/literal/unicode-property-escape-invalid-2018.json
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     assert_ne!(validator.validate_pattern("\\p", true), Ok(()));
     assert_ne!(validator.validate_pattern("\\p{", true), Ok(()));
     assert_ne!(validator.validate_pattern("\\p{ASCII", true), Ok(()));
diff --git a/src/js_regex/unicode.rs b/src/js_regex/unicode.rs
index 4c6c56a..d4beb27 100644
--- a/src/js_regex/unicode.rs
+++ b/src/js_regex/unicode.rs
@@ -554,15 +554,15 @@ pub fn is_valid_unicode_property(
   value: &str,
 ) -> bool {
   if GC_NAME_PATTERN.contains(name)
-    && version >= EcmaVersion::ES2018
+    && version >= EcmaVersion::Es2018
     && GC_VALUE_PATTERNS.es2018.contains(value)
   {
     true
   } else if SC_NAME_PATTERN.contains(name) {
-    (version >= EcmaVersion::ES2018 && SC_VALUE_PATTERNS.es2018.contains(value))
-      || (version >= EcmaVersion::ES2019
+    (version >= EcmaVersion::Es2018 && SC_VALUE_PATTERNS.es2018.contains(value))
+      || (version >= EcmaVersion::Es2019
         && SC_VALUE_PATTERNS.es2019.contains(value))
-      || (version >= EcmaVersion::ES2020
+      || (version >= EcmaVersion::Es2020
         && SC_VALUE_PATTERNS.es2020.contains(value))
   } else {
     false
@@ -573,9 +573,9 @@ pub fn is_valid_lone_unicode_property(
   version: EcmaVersion,
   value: &str,
 ) -> bool {
-  (version >= EcmaVersion::ES2018
+  (version >= EcmaVersion::Es2018
     && BIN_PROPERTY_PATTERNS.es2018.contains(value))
-    || (version >= EcmaVersion::ES2019
+    || (version >= EcmaVersion::Es2019
       && BIN_PROPERTY_PATTERNS.es2019.contains(value))
 }
 
diff --git a/src/js_regex/validator.rs b/src/js_regex/validator.rs
index d5d8596..cdb2a4e 100644
--- a/src/js_regex/validator.rs
+++ b/src/js_regex/validator.rs
@@ -94,14 +94,14 @@ fn combine_surrogate_pair(lead: i64, trail: i64) -> i64 {
 #[allow(dead_code)]
 #[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
 pub enum EcmaVersion {
-  ES5,
-  ES2015,
-  ES2016,
-  ES2017,
-  ES2018,
-  ES2019,
-  ES2020,
-  ES2021,
+  Es5,
+  Es2015,
+  Es2016,
+  Es2017,
+  Es2018,
+  Es2019,
+  Es2020,
+  Es2021,
 }
 
 #[derive(Debug)]
@@ -171,9 +171,9 @@ impl EcmaRegexValidator {
       if flag == 'g'
         || flag == 'i'
         || flag == 'm'
-        || (flag == 'u' && self.ecma_version >= EcmaVersion::ES2015)
-        || (flag == 'y' && self.ecma_version >= EcmaVersion::ES2015)
-        || (flag == 's' && self.ecma_version >= EcmaVersion::ES2018)
+        || (flag == 'u' && self.ecma_version >= EcmaVersion::Es2015)
+        || (flag == 'y' && self.ecma_version >= EcmaVersion::Es2015)
+        || (flag == 's' && self.ecma_version >= EcmaVersion::Es2018)
       {
         // do nothing
       } else {
@@ -190,14 +190,14 @@ impl EcmaRegexValidator {
     u_flag: bool,
   ) -> Result<(), String> {
     self.strict = u_flag; // TODO: allow toggling strict independently of u flag
-    self.u_flag = u_flag && self.ecma_version >= EcmaVersion::ES2015;
-    self.n_flag = u_flag && self.ecma_version >= EcmaVersion::ES2018;
+    self.u_flag = u_flag && self.ecma_version >= EcmaVersion::Es2015;
+    self.n_flag = u_flag && self.ecma_version >= EcmaVersion::Es2018;
     //self.reset(source, 0, source.len(), u_flag);
     self.reset(source, 0, source.chars().count(), u_flag);
     self.consume_pattern()?;
 
     if !self.n_flag
-      && self.ecma_version >= EcmaVersion::ES2018
+      && self.ecma_version >= EcmaVersion::Es2018
       && !self.group_names.is_empty()
     {
       self.n_flag = true;
@@ -348,7 +348,7 @@ impl EcmaRegexValidator {
     // Lookahead / Lookbehind
     if self.eat2('(', '?') {
       let lookbehind =
-        self.ecma_version >= EcmaVersion::ES2018 && self.eat('<');
+        self.ecma_version >= EcmaVersion::Es2018 && self.eat('<');
       let mut flag = self.eat('=');
       if !flag {
         flag = self.eat('!');
@@ -499,7 +499,7 @@ impl EcmaRegexValidator {
       return Ok(false);
     }
 
-    if self.ecma_version >= EcmaVersion::ES2018 {
+    if self.ecma_version >= EcmaVersion::Es2018 {
       self.consume_group_specifier()?;
     } else if self.code_point_with_offset(0) == Some('?') {
       return Err("Invalid group".to_string());
@@ -716,7 +716,7 @@ impl EcmaRegexValidator {
     }
 
     if self.u_flag
-      && self.ecma_version >= EcmaVersion::ES2018
+      && self.ecma_version >= EcmaVersion::Es2018
       && (self.eat('p') || self.eat('P'))
     {
       self.last_int_value = -1;
@@ -984,7 +984,7 @@ impl EcmaRegexValidator {
   /// Returns `true` if it ate the next characters successfully.
   fn eat_regexp_identifier_start(&mut self) -> Result<bool, String> {
     let start = self.index();
-    let force_u_flag = !self.u_flag && self.ecma_version >= EcmaVersion::ES2020;
+    let force_u_flag = !self.u_flag && self.ecma_version >= EcmaVersion::Es2020;
 
     if let Some(mut cp) = self.code_point_with_offset(0) {
       self.advance();
@@ -1032,7 +1032,7 @@ impl EcmaRegexValidator {
   /// Returns `true` if it ate the next characters successfully.
   fn eat_regexp_identifier_part(&mut self) -> Result<bool, String> {
     let start = self.index();
-    let force_u_flag = !self.u_flag && self.ecma_version >= EcmaVersion::ES2020;
+    let force_u_flag = !self.u_flag && self.ecma_version >= EcmaVersion::Es2020;
     let mut cp = self.code_point_with_offset(0);
     self.advance();
     let cp1 = self.code_point_with_offset(0);
@@ -1594,7 +1594,7 @@ mod tests {
 
   #[test]
   fn count_capturing_parens_test() {
-    let mut validator = EcmaRegexValidator::new(EcmaVersion::ES2018);
+    let mut validator = EcmaRegexValidator::new(EcmaVersion::Es2018);
     let source = "foo|(abc)de";
     validator.reset(source, 0, source.len(), false);
     assert_eq!(validator.count_capturing_parens(), 1);
diff --git a/src/rules/camelcase.rs b/src/rules/camelcase.rs
index 3a9ba71..a375409 100644
--- a/src/rules/camelcase.rs
+++ b/src/rules/camelcase.rs
@@ -464,10 +464,8 @@ impl<'c> CamelcaseVisitor<'c> {
         self.check_ident(ident, IdentToCheck::variable(&ident.id));
       }
       Pat::Array(ArrayPat { ref elems, .. }) => {
-        for elem in elems {
-          if let Some(pat) = elem {
-            self.check_pat(pat);
-          }
+        for pat in elems.iter().flatten() {
+          self.check_pat(pat);
         }
       }
       Pat::Rest(RestPat { ref arg, .. }) => {
diff --git a/src/rules/no_const_assign.rs b/src/rules/no_const_assign.rs
index 862cff9..633b840 100644
--- a/src/rules/no_const_assign.rs
+++ b/src/rules/no_const_assign.rs
@@ -105,10 +105,8 @@ impl<'c> NoConstAssignVisitor<'c> {
     span: Span,
   ) {
     if !array.elems.is_empty() {
-      for elem in array.elems.iter() {
-        if let Some(element) = elem {
-          self.check_pat(element, span);
-        }
+      for elem in array.elems.iter().flatten() {
+        self.check_pat(elem, span);
       }
     }
   }
diff --git a/src/rules/no_empty_pattern.rs b/src/rules/no_empty_pattern.rs
index bbadc8b..3cee1e8 100644
--- a/src/rules/no_empty_pattern.rs
+++ b/src/rules/no_empty_pattern.rs
@@ -118,13 +118,11 @@ impl<'c> Visit for NoEmptyPatternVisitor<'c> {
         .context
         .add_diagnostic_with_hint(arr_pat.span, CODE, MESSAGE, HINT)
     } else {
-      for elem in &arr_pat.elems {
-        if let Some(element) = elem {
-          if let swc_ecmascript::ast::Pat::Object(obj_pat) = element {
-            self.visit_object_pat(&obj_pat, _parent);
-          } else if let swc_ecmascript::ast::Pat::Array(arr_pat) = element {
-            self.visit_array_pat(&arr_pat, _parent);
-          }
+      for element in arr_pat.elems.iter().flatten() {
+        if let swc_ecmascript::ast::Pat::Object(obj_pat) = element {
+          self.visit_object_pat(&obj_pat, _parent);
+        } else if let swc_ecmascript::ast::Pat::Array(arr_pat) = element {
+          self.visit_array_pat(&arr_pat, _parent);
         }
       }
     }
diff --git a/src/rules/no_extra_non_null_assertion.rs b/src/rules/no_extra_non_null_assertion.rs
index 756738f..aa64ecc 100644
--- a/src/rules/no_extra_non_null_assertion.rs
+++ b/src/rules/no_extra_non_null_assertion.rs
@@ -127,10 +127,8 @@ impl<'c> VisitAll for NoExtraNonNullAssertionVisitor<'c> {
       _ => None,
     };
 
-    if let Some(expr_or_super) = maybe_expr_or_super {
-      if let ExprOrSuper::Expr(expr) = &expr_or_super {
-        self.check_expr_for_nested_non_null_assert(opt_chain_expr.span, expr);
-      }
+    if let Some(ExprOrSuper::Expr(expr)) = maybe_expr_or_super {
+      self.check_expr_for_nested_non_null_assert(opt_chain_expr.span, expr);
     }
   }
 }
diff --git a/src/rules/no_invalid_regexp.rs b/src/rules/no_invalid_regexp.rs
index 87bba96..65bf398 100644
--- a/src/rules/no_invalid_regexp.rs
+++ b/src/rules/no_invalid_regexp.rs
@@ -72,7 +72,7 @@ impl<'c> NoInvalidRegexpVisitor<'c> {
   fn new(context: &'c mut Context) -> Self {
     Self {
       context,
-      validator: EcmaRegexValidator::new(EcmaVersion::ES2018),
+      validator: EcmaRegexValidator::new(EcmaVersion::Es2018),
     }
   }
 
diff --git a/src/rules/no_namespace.rs b/src/rules/no_namespace.rs
index d879c8a..942ae04 100644
--- a/src/rules/no_namespace.rs
+++ b/src/rules/no_namespace.rs
@@ -1,8 +1,8 @@
 // Copyright 2020-2021 the Deno authors. All rights reserved. MIT license.
-use super::{Context, LintRule, ProgramRef, DUMMY_NODE};
-use swc_ecmascript::ast::{TsModuleDecl, TsModuleName};
-use swc_ecmascript::visit::Node;
-use swc_ecmascript::visit::{VisitAll, VisitAllWith};
+use super::{Context, LintRule, ProgramRef};
+use crate::handler::{Handler, Traverse};
+use dprint_swc_ecma_ast_view::{self as AstView, NodeTrait};
+use swc_common::Spanned;
 
 pub struct NoNamespace;
 
@@ -22,32 +22,45 @@ impl LintRule for NoNamespace {
     CODE
   }
 
-  fn lint_program(&self, context: &mut Context, program: ProgramRef<'_>) {
-    let mut visitor = NoNamespaceVisitor::new(context);
-    match program {
-      ProgramRef::Module(ref m) => m.visit_all_with(&DUMMY_NODE, &mut visitor),
-      ProgramRef::Script(ref s) => s.visit_all_with(&DUMMY_NODE, &mut visitor),
-    }
+  fn lint_program(&self, _context: &mut Context, _program: ProgramRef<'_>) {
+    unreachable!();
   }
-}
 
-struct NoNamespaceVisitor<'c> {
-  context: &'c mut Context,
-}
+  fn lint_program_with_ast_view(
+    &self,
+    context: &mut Context,
+    program: dprint_swc_ecma_ast_view::Program<'_>,
+  ) {
+    if context.file_name.ends_with(".d.ts") {
+      return;
+    }
 
-impl<'c> NoNamespaceVisitor<'c> {
-  fn new(context: &'c mut Context) -> Self {
-    Self { context }
+    NoNamespaceHandler.traverse(program, context);
   }
 }
 
-impl<'c> VisitAll for NoNamespaceVisitor<'c> {
-  fn visit_ts_module_decl(&mut self, mod_decl: &TsModuleDecl, _: &dyn Node) {
-    if !mod_decl.global && !mod_decl.declare {
-      if let TsModuleName::Ident(_) = mod_decl.id {
-        self.context.add_diagnostic(mod_decl.span, CODE, MESSAGE);
+struct NoNamespaceHandler;
+
+impl Handler for NoNamespaceHandler {
+  fn ts_module_decl(
+    &self,
+    module_decl: &AstView::TsModuleDecl,
+    ctx: &mut Context,
+  ) {
+    fn inside_ambient_context(current_node: AstView::Node) -> bool {
+      use AstView::Node::*;
+      match current_node {
+        TsModuleDecl(module_decl) if module_decl.declare() => true,
+        _ => match current_node.parent() {
+          Some(p) => inside_ambient_context(p),
+          None => false,
+        },
       }
     }
+
+    if !inside_ambient_context(module_decl.into_node()) {
+      ctx.add_diagnostic(module_decl.span(), CODE, MESSAGE);
+    }
   }
 }
 
@@ -63,6 +76,59 @@ mod tests {
       r#"declare module 'foo' {}"#,
       r#"declare module foo {}"#,
       r#"declare namespace foo {}"#,
+      r#"
+declare global {
+  namespace foo {}
+}
+      "#,
+      r#"
+declare module foo {
+  namespace bar {}
+}
+      "#,
+      r#"
+declare global {
+  namespace foo {
+    namespace bar {}
+  }
+}
+      "#,
+      r#"
+declare namespace foo {
+  namespace bar {
+    namespace baz {}
+  }
+}
+      "#,
+      {
+        src: r#"namespace foo {}"#,
+        filename: "test.d.ts",
+      },
+      {
+        src: r#"module foo {}"#,
+        filename: "test.d.ts",
+      },
+      {
+        // https://github.com/denoland/deno_lint/issues/633
+        src: r#"
+export declare namespace Utility {
+  export namespace Matcher {
+    export type CharSchema<
+      T extends string,
+      Schema extends string,
+      _Rest extends string = T
+    > = _Rest extends `${infer $First}${infer $Rest}`
+      ? $First extends Schema
+        ? CharSchema<T, Schema, $Rest>
+        : never
+      : "" extends _Rest
+      ? T
+      : never;
+  }
+}
+        "#,
+        filename: "test.d.ts",
+      },
     };
   }
 
@@ -70,8 +136,9 @@ mod tests {
   fn no_namespace_invalid() {
     assert_lint_err! {
       NoNamespace,
-      "module foo {}": [{col: 0, message: MESSAGE }],
-      "namespace foo {}": [{col: 0, message: MESSAGE }],
+      "module foo {}": [{ col: 0, message: MESSAGE }],
+      "namespace foo {}": [{ col: 0, message: MESSAGE }],
+      "namespace Foo.Bar {}": [{ col: 0, message: MESSAGE }],
       "namespace Foo.Bar { namespace Baz.Bas {} }": [
         {
           col: 0,
diff --git a/src/rules/no_setter_return.rs b/src/rules/no_setter_return.rs
index a47e2de..da278aa 100644
--- a/src/rules/no_setter_return.rs
+++ b/src/rules/no_setter_return.rs
@@ -1,17 +1,13 @@
 // Copyright 2020-2021 the Deno authors. All rights reserved. MIT license.
-use super::{Context, LintRule, ProgramRef, DUMMY_NODE};
-use swc_ecmascript::ast::BlockStmt;
-use swc_ecmascript::ast::Class;
-use swc_ecmascript::ast::ClassMember;
-use swc_ecmascript::ast::MethodKind;
-use swc_ecmascript::ast::SetterProp;
-use swc_ecmascript::ast::Stmt;
-use swc_ecmascript::visit::noop_visit_type;
-use swc_ecmascript::visit::Node;
-use swc_ecmascript::visit::Visit;
+use super::{Context, LintRule, ProgramRef};
+use crate::handler::{Handler, Traverse};
+use dprint_swc_ecma_ast_view::{self as AstView, NodeTrait, Spanned};
 
 pub struct NoSetterReturn;
 
+const CODE: &str = "no-setter-return";
+const MESSAGE: &str = "Setter cannot return a value";
+
 impl LintRule for NoSetterReturn {
   fn new() -> Box<Self> {
     Box::new(NoSetterReturn)
@@ -22,74 +18,49 @@ impl LintRule for NoSetterReturn {
   }
 
   fn code(&self) -> &'static str {
-    "no-setter-return"
+    CODE
   }
 
-  fn lint_program(&self, context: &mut Context, program: ProgramRef<'_>) {
-    let mut visitor = NoSetterReturnVisitor::new(context);
-    match program {
-      ProgramRef::Module(ref m) => visitor.visit_module(m, &DUMMY_NODE),
-      ProgramRef::Script(ref s) => visitor.visit_script(s, &DUMMY_NODE),
-    }
+  fn lint_program(&self, _context: &mut Context, _program: ProgramRef<'_>) {
+    unreachable!()
   }
-}
 
-struct NoSetterReturnVisitor<'c> {
-  context: &'c mut Context,
+  fn lint_program_with_ast_view(
+    &self,
+    context: &mut Context,
+    program: dprint_swc_ecma_ast_view::Program<'_>,
+  ) {
+    NoSetterReturnHandler.traverse(program, context);
+  }
 }
 
-impl<'c> NoSetterReturnVisitor<'c> {
-  fn new(context: &'c mut Context) -> Self {
-    Self { context }
-  }
+struct NoSetterReturnHandler;
 
-  fn check_block_stmt(&mut self, block_stmt: &BlockStmt) {
-    for stmt in &block_stmt.stmts {
-      if let Stmt::Return(return_stmt) = stmt {
-        if return_stmt.arg.is_some() {
-          self.context.add_diagnostic(
-            return_stmt.span,
-            "no-setter-return",
-            "Setter cannot return a value",
-          );
-        }
-      }
+impl Handler for NoSetterReturnHandler {
+  fn return_stmt(&self, return_stmt: &AstView::ReturnStmt, ctx: &mut Context) {
+    // return without a value is allowed
+    if return_stmt.arg.is_none() {
+      return;
     }
-  }
-}
 
-impl<'c> Visit for NoSetterReturnVisitor<'c> {
-  noop_visit_type!();
-
-  fn visit_class(&mut self, class: &Class, _parent: &dyn Node) {
-    for member in &class.body {
-      match member {
-        ClassMember::Method(class_method) => {
-          if class_method.kind == MethodKind::Setter {
-            if let Some(block_stmt) = &class_method.function.body {
-              self.check_block_stmt(block_stmt);
-            }
+    fn inside_setter(node: AstView::Node) -> bool {
+      use AstView::Node::*;
+      match node {
+        SetterProp(_) => true,
+        ClassMethod(method) => method.kind() == AstView::MethodKind::Setter,
+        FnDecl(_) | FnExpr(_) | ArrowExpr(_) => false,
+        _ => {
+          if let Some(parent) = node.parent() {
+            inside_setter(parent)
+          } else {
+            false
           }
         }
-        ClassMember::PrivateMethod(private_method) => {
-          if private_method.kind == MethodKind::Setter {
-            if let Some(block_stmt) = &private_method.function.body {
-              self.check_block_stmt(block_stmt);
-            }
-          }
-        }
-        _ => {}
       }
     }
-  }
 
-  fn visit_setter_prop(
-    &mut self,
-    setter_prop: &SetterProp,
-    _parent: &dyn Node,
-  ) {
-    if let Some(block_stmt) = &setter_prop.body {
-      self.check_block_stmt(block_stmt);
+    if inside_setter(return_stmt.into_node()) {
+      ctx.add_diagnostic(return_stmt.span(), CODE, MESSAGE);
     }
   }
 }
@@ -97,15 +68,125 @@ impl<'c> Visit for NoSetterReturnVisitor<'c> {
 #[cfg(test)]
 mod tests {
   use super::*;
-  use crate::test_util::*;
+
+  // Some tests are derived from
+  // https://github.com/eslint/eslint/blob/v7.21.0/tests/lib/rules/no-setter-return.js
+  // MIT Licensed.
+
+  #[test]
+  fn no_setter_return_valid() {
+    assert_lint_ok! {
+      NoSetterReturn,
+      "function foo() { return 1; }",
+      "function set(val) { return 1; }",
+      "var foo = function() { return 1; };",
+      "var foo = function set() { return 1; };",
+      "var set = function() { return 1; };",
+      "var set = function set(val) { return 1; };",
+      "var set = val => { return 1; };",
+      "var set = val => 1;",
+      "({ set a(val) { }}); function foo() { return 1; }",
+      "({ set a(val) { }}); (function () { return 1; });",
+      "({ set a(val) { }}); (() => { return 1; });",
+      "({ set a(val) { }}); (() => 1);",
+
+      //------------------------------------------------------------------------------
+      // Object literals and classes
+      //------------------------------------------------------------------------------
+
+      // return without a value is allowed
+      "({ set foo(val) { return; } })",
+      "({ set foo(val) { if (val) { return; } } })",
+      "class A { set foo(val) { return; } }",
+      "(class { set foo(val) { if (val) { return; } else { return; } return; } })",
+      "class A { set foo(val) { try {} catch(e) { return; } } }",
+
+      // not a setter
+      "({ get foo() { return 1; } })",
+      "({ get set() { return 1; } })",
+      "({ set(val) { return 1; } })",
+      "({ set: function(val) { return 1; } })",
+      "({ foo: function set(val) { return 1; } })",
+      "({ set: function set(val) { return 1; } })",
+      "({ set: (val) => { return 1; } })",
+      "({ set: (val) => 1 })",
+      "set = { foo(val) { return 1; } };",
+      "class A { constructor(val) { return 1; } }",
+      "class set { constructor(val) { return 1; } }",
+      "class set { foo(val) { return 1; } }",
+      "var set = class { foo(val) { return 1; } }",
+      "(class set { foo(val) { return 1; } })",
+      "class A { get foo() { return val; } }",
+      "class A { get set() { return val; } }",
+      "class A { set(val) { return 1; } }",
+      "class A { static set(val) { return 1; } }",
+      "({ set: set = function set(val) { return 1; } } = {})",
+      "({ set: set = (val) => 1 } = {})",
+
+      // not returning from the setter
+      "({ set foo(val) { function foo(val) { return 1; } } })",
+      "({ set foo(val) { var foo = function(val) { return 1; } } })",
+      "({ set foo(val) { var foo = (val) => { return 1; } } })",
+      "({ set foo(val) { var foo = (val) => 1; } })",
+      "({ set [function() { return 1; }](val) {} })",
+      "({ set [() => { return 1; }](val) {} })",
+      "({ set [() => 1](val) {} })",
+      "({ set foo(val = function() { return 1; }) {} })",
+      "({ set foo(val = v => 1) {} })",
+      "(class { set foo(val) { function foo(val) { return 1; } } })",
+      "(class { set foo(val) { var foo = function(val) { return 1; } } })",
+      "(class { set foo(val) { var foo = (val) => { return 1; } } })",
+      "(class { set foo(val) { var foo = (val) => 1; } })",
+      "(class { set [function() { return 1; }](val) {} })",
+      "(class { set [() => { return 1; }](val) {} })",
+      "(class { set [() => 1](val) {} })",
+      "(class { set foo(val = function() { return 1; }) {} })",
+      "(class { set foo(val = (v) => 1) {} })",
+
+      //------------------------------------------------------------------------------
+      // Property descriptors
+      //------------------------------------------------------------------------------
+
+      // return without a value is allowed
+      "Object.defineProperty(foo, 'bar', { set(val) { return; } })",
+      "Reflect.defineProperty(foo, 'bar', { set(val) { if (val) { return; } } })",
+      "Object.defineProperties(foo, { bar: { set(val) { try { return; } catch(e){} } } })",
+      "Object.create(foo, { bar: { set: function(val) { return; } } })",
+
+      // not a setter
+      "x = { set(val) { return 1; } }",
+      "x = { foo: { set(val) { return 1; } } }",
+      "Object.defineProperty(foo, 'bar', { value(val) { return 1; } })",
+      "Reflect.defineProperty(foo, 'bar', { value: function set(val) { return 1; } })",
+      "Object.defineProperties(foo, { bar: { [set](val) { return 1; } } })",
+      "Object.create(foo, { bar: { 'set ': function(val) { return 1; } } })",
+      "Object.defineProperty(foo, 'bar', { [`set `]: (val) => { return 1; } })",
+      "Reflect.defineProperty(foo, 'bar', { Set(val) { return 1; } })",
+      "Object.defineProperties(foo, { bar: { value: (val) => 1 } })",
+      "Object.create(foo, { set: { value: function(val) { return 1; } } })",
+      "Object.defineProperty(foo, 'bar', { baz(val) { return 1; } })",
+      "Reflect.defineProperty(foo, 'bar', { get(val) { return 1; } })",
+      "Object.create(foo, { set: function(val) { return 1; } })",
+      "Object.defineProperty(foo, { set: (val) => 1 })",
+
+      // not returning from the setter
+      "Object.defineProperty(foo, 'bar', { set(val) { function foo() { return 1; } } })",
+      "Reflect.defineProperty(foo, 'bar', { set(val) { var foo = function() { return 1; } } })",
+      "Object.defineProperties(foo, { bar: { set(val) { () => { return 1 }; } } })",
+      "Object.create(foo, { bar: { set: (val) => { (val) => 1; } } })",
+    };
+  }
 
   #[test]
   fn no_setter_return_invalid() {
-    assert_lint_err::<NoSetterReturn>(
-      r#"const a = { set setter(a) { return "something"; } };"#,
-      28,
-    );
-    assert_lint_err_on_line_n::<NoSetterReturn>(
+    assert_lint_err! {
+      NoSetterReturn,
+      r#"const a = { set setter(a) { return "something"; } };"#: [
+        {
+          col: 28,
+          message: "Setter cannot return a value",
+        }
+      ],
       r#"
 class b {
   set setterA(a) {
@@ -115,8 +196,254 @@ class b {
     return "something";
   }
 }
-      "#,
-      vec![(4, 4), (7, 4)],
-    );
+      "#: [
+        {
+          line: 4,
+          col: 4,
+          message: MESSAGE,
+        },
+        {
+          line: 7,
+          col: 4,
+          message: MESSAGE,
+        }
+      ],
+      "({ set a(val){ return val + 1; } })": [
+        {
+          col: 15,
+          message: MESSAGE,
+        }
+      ],
+      "({ set a(val) { return 1; } })": [
+        {
+          col: 16,
+          message: MESSAGE,
+        }
+      ],
+      "class A { set a(val) { return 1; } }": [
+        {
+          col: 23,
+          message: MESSAGE,
+        }
+      ],
+      "class A { static set a(val) { return 1; } }": [
+        {
+          col: 30,
+          message: MESSAGE,
+        }
+      ],
+      "(class { set a(val) { return 1; } })": [
+        {
+          col: 22,
+          message: MESSAGE,
+        }
+      ],
+      "({ set a(val) { return val; } })": [
+        {
+          col: 16,
+          message: MESSAGE,
+        }
+      ],
+      "class A { set a(val) { return undefined; } }": [
+        {
+          col: 23,
+          message: MESSAGE,
+        }
+      ],
+      "(class { set a(val) { return null; } })": [
+        {
+          col: 22,
+          message: MESSAGE,
+        }
+      ],
+      "({ set a(val) { return x + y; } })": [
+        {
+          col: 16,
+          message: MESSAGE,
+        }
+      ],
+      "class A { set a(val) { return foo(); } }": [
+        {
+          col: 23,
+          message: MESSAGE,
+        }
+      ],
+      "(class { set a(val) { return this._a; } })": [
+        {
+          col: 22,
+          message: MESSAGE,
+        }
+      ],
+      "({ set a(val) { return this.a; } })": [
+        {
+          col: 16,
+          message: MESSAGE,
+        }
+      ],
+      "({ set a(val) { if (foo) { return 1; }; } })": [
+        {
+          col: 27,
+          message: MESSAGE,
+        }
+      ],
+      "class A { set a(val) { try { return 1; } catch(e) {} } }": [
+        {
+          col: 29,
+          message: MESSAGE,
+        }
+      ],
+      "(class { set a(val) { while (foo){ if (bar) break; else return 1; } } })": [
+        {
+          col: 56,
+          message: MESSAGE,
+        }
+      ],
+      "({ set a(val) { return 1; }, set b(val) { return 1; } })": [
+        {
+          col: 16,
+          message: MESSAGE,
+        },
+        {
+          col: 42,
+          message: MESSAGE,
+        }
+      ],
+      "class A { set a(val) { return 1; } set b(val) { return 1; } }": [
+        {
+          col: 23,
+          message: MESSAGE,
+        },
+        {
+          col: 48,
+          message: MESSAGE,
+        },
+      ],
+      "(class { set a(val) { return 1; } static set b(val) { return 1; } })": [
+        {
+          col: 22,
+          message: MESSAGE,
+        },
+        {
+          col: 54,
+          message: MESSAGE,
+        },
+      ],
+      "({ set a(val) { if(val) { return 1; } else { return 2 }; } })": [
+        {
+          col: 26,
+          message: MESSAGE,
+        },
+        {
+          col: 45,
+          message: MESSAGE,
+        },
+      ],
+      "class A { set a(val) { switch(val) { case 1: return x; case 2: return y; default: return z } } }": [
+        {
+          col: 45,
+          message: MESSAGE,
+        },
+        {
+          col: 63,
+          message: MESSAGE,
+        },
+        {
+          col: 82,
+          message: MESSAGE,
+        },
+      ],
+      "(class { static set a(val) { if (val > 0) { this._val = val; return val; } return false; } })": [
+        {
+          col: 61,
+          message: MESSAGE,
+        },
+        {
+          col: 75,
+          message: MESSAGE,
+        },
+      ],
+      "({ set a(val) { if(val) { return 1; } else { return; }; } })": [
+        {
+          col: 26,
+          message: MESSAGE,
+        }
+      ],
+      "class A { set a(val) { switch(val) { case 1: return x; case 2: return; default: return z } } }": [
+        {
+          col: 45,
+          message: MESSAGE,
+        },
+        {
+          col: 80,
+          message: MESSAGE,
+        },
+      ],
+      "(class { static set a(val) { if (val > 0) { this._val = val; return; } return false; } })": [
+        {
+          col: 71,
+          message: MESSAGE,
+        }
+      ],
+      "({ set a(val) { function b(){} return b(); } })": [
+        {
+          col: 31,
+          message: MESSAGE,
+        }
+      ],
+      "class A { set a(val) { return () => {}; } }": [
+        {
+          col: 23,
+          message: MESSAGE,
+        }
+      ],
+      "(class { set a(val) { function b(){ return 1; } return 2; } })": [
+        {
+          col: 48,
+          message: MESSAGE,
+        }
+      ],
+      "({ set a(val) { function b(){ return; } return 1; } })": [
+        {
+          col: 40,
+          message: MESSAGE,
+        }
+      ],
+      "class A { set a(val) { var x = function() { return 1; }; return 2; } }": [
+        {
+          col: 57,
+          message: MESSAGE,
+        }
+      ],
+      "(class { set a(val) { var x = () => { return; }; return 2; } })": [
+        {
+          col: 49,
+          message: MESSAGE,
+        }
+      ],
+      "function f(){}; ({ set a(val) { return 1; } });": [
+        {
+          col: 32,
+          message: MESSAGE,
+        }
+      ],
+      "x = function f(){}; class A { set a(val) { return 1; } };": [
+        {
+          col: 43,
+          message: MESSAGE,
+        }
+      ],
+      "x = () => {}; A = class { set a(val) { return 1; } };": [
+        {
+          col: 39,
+          message: MESSAGE,
+        }
+      ],
+      "return; ({ set a(val) { return 1; } }); return 2;": [
+        {
+          col: 24,
+          message: MESSAGE,
+        }
+      ],
+    };
   }
 }
diff --git a/src/rules/no_this_before_super.rs b/src/rules/no_this_before_super.rs
index 8d351d8..3171112 100644
--- a/src/rules/no_this_before_super.rs
+++ b/src/rules/no_this_before_super.rs
@@ -241,7 +241,8 @@ class C extends D {
     );
 
     // inline super class
-    assert_lint_ok::<NoThisBeforeSuper>(
+    assert_lint_ok! {
+      NoThisBeforeSuper,
       r#"
 class A extends class extends B {
   constructor() {
@@ -255,7 +256,7 @@ class A extends class extends B {
     }
 }
       "#,
-    );
+    };
 
     assert_lint_err_on_line::<NoThisBeforeSuper>(
       r#"
@@ -296,7 +297,8 @@ class A extends class extends B {
 
   #[test]
   fn no_this_before_super_nested_class() {
-    assert_lint_ok::<NoThisBeforeSuper>(
+    assert_lint_ok! {
+      NoThisBeforeSuper,
       r#"
 class A extends B {
   constructor() {
@@ -313,7 +315,7 @@ class A extends B {
   }
 }
       "#,
-    );
+    };
 
     assert_lint_err_on_line::<NoThisBeforeSuper>(
       r#"
diff --git a/src/rules/no_undef.rs b/src/rules/no_undef.rs
index c2cd06d..ba9fd3d 100644
--- a/src/rules/no_undef.rs
+++ b/src/rules/no_undef.rs
@@ -98,6 +98,23 @@ impl VisitAll for BindingCollector {
     self.declare(i.local.to_id());
   }
 
+  fn visit_export_default_decl(&mut self, e: &ExportDefaultDecl, _: &dyn Node) {
+    if let DefaultDecl::Class(ClassExpr {
+      ident: Some(ref ident),
+      ..
+    })
+    | DefaultDecl::Fn(FnExpr {
+      ident: Some(ref ident),
+      ..
+    })
+    | DefaultDecl::TsInterfaceDecl(TsInterfaceDecl {
+      id: ref ident, ..
+    }) = e.decl
+    {
+      self.declare(ident.to_id());
+    }
+  }
+
   fn visit_var_declarator(&mut self, v: &VarDeclarator, _: &dyn Node) {
     let ids: Vec<Id> = find_ids(&v.name);
     for id in ids {
@@ -391,6 +408,7 @@ mod tests {
       r#"type Foo = string | number; export default Foo;"#,
       r#"type Foo<T> = { bar: T }; export default Foo;"#,
       r#"type Foo = string | undefined; export type { Foo };"#,
+
       // https://github.com/denoland/deno_lint/issues/596
       r#"
       const f = (
@@ -398,6 +416,11 @@ mod tests {
         b: boolean,
       ) => {};
       "#,
+
+      // https://github.com/denoland/deno_lint/issues/643
+      "export default function foo() {} foo();",
+      "export default class Foo {} const foo = new Foo();",
+      "export default interface Foo {} const foo: Foo = {};",
     };
   }
 
diff --git a/src/rules/no_unused_vars.rs b/src/rules/no_unused_vars.rs
index 8ed712c..4ceb5d7 100644
--- a/src/rules/no_unused_vars.rs
+++ b/src/rules/no_unused_vars.rs
@@ -1549,18 +1549,16 @@ export class Bar implements baz().test {}
   #[test]
   #[ignore = "typescript property analysis is not implemented yet"]
   fn no_unused_vars_ts_ok_12() {
-    assert_lint_ok::<NoUnusedVars>(
-      "
+    assert_lint_ok! {
+      NoUnusedVars,
+    "
 export class App {
   constructor(private logger: Logger) {
     console.log(this.logger);
   }
 }
       ",
-    );
-
-    assert_lint_ok::<NoUnusedVars>(
-      "
+    "
 export class App {
   constructor(bar: string);
   constructor(private logger: Logger) {
@@ -1568,10 +1566,7 @@ export class App {
   }
 }
       ",
-    );
-
-    assert_lint_ok::<NoUnusedVars>(
-      "
+    "
 export class App {
   constructor(baz: string, private logger: Logger) {
     console.log(baz);
@@ -1579,10 +1574,7 @@ export class App {
   }
 }
       ",
-    );
-
-    assert_lint_ok::<NoUnusedVars>(
-      "
+    "
 export class App {
   constructor(baz: string, private logger: Logger, private bar: () => void) {
     console.log(this.logger);
@@ -1590,10 +1582,7 @@ export class App {
   }
 }
       ",
-    );
-
-    assert_lint_ok::<NoUnusedVars>(
-      "
+    "
 export class App {
   constructor(private logger: Logger) {}
   meth() {
@@ -1601,6 +1590,6 @@ export class App {
   }
 }
       ",
-    );
+    };
   }
 }
diff --git a/src/rules/prefer_const.rs b/src/rules/prefer_const.rs
index 20dc019..2955791 100644
--- a/src/rules/prefer_const.rs
+++ b/src/rules/prefer_const.rs
@@ -608,10 +608,8 @@ where
   match pat {
     Pat::Ident(ident) => op(ExtractIdentsArgs::Ident(&ident.id)),
     Pat::Array(array_pat) => {
-      for elem in &array_pat.elems {
-        if let Some(elem_pat) = elem {
-          extract_idents_from_pat_with(elem_pat, op);
-        }
+      for elem_pat in array_pat.elems.iter().flatten() {
+        extract_idents_from_pat_with(elem_pat, op);
       }
     }
     Pat::Rest(rest_pat) => extract_idents_from_pat_with(&*rest_pat.arg, op),


Test patch:
diff --git a/src/test_util.rs b/src/test_util.rs
index f97d7fe..a8cf122 100644
--- a/src/test_util.rs
+++ b/src/test_util.rs
@@ -12,9 +12,10 @@ use swc_ecmascript::ast::Program;
 
 #[macro_export]
 macro_rules! assert_lint_ok {
-  ($rule:ty, $($src:literal),* $(,)?) => {
+  ($rule:ty, $($test:tt),+ $(,)?) => {
     $(
-      $crate::test_util::assert_lint_ok::<$rule>($src);
+      let (src, filename) = parse_ok_test!($test);
+      $crate::test_util::assert_lint_ok::<$rule>(src, filename);
     )*
   };
 }
@@ -23,32 +24,17 @@ macro_rules! assert_lint_ok {
 macro_rules! assert_lint_err {
   (
     $rule:ty,
-    $(
-      $src:literal : [
-        $(
-          {
-            $($field:ident : $value:expr),* $(,)?
-          }
-        ),* $(,)?
-      ]
-    ),+ $(,)?
+    $($src:literal : $test:tt),+
+    $(,)?
   ) => {
     $(
-      let mut errors = Vec::new();
-      $(
-        let mut builder = $crate::test_util::LintErrBuilder::new();
-        $(
-          builder.$field($value);
-        )*
-        let e = builder.build();
-        errors.push(e);
-      )*
-      let t = $crate::test_util::LintErrTester::<$rule> {
-        src: $src,
+      let (errors, filename) = parse_err_test!($test);
+      let tester = $crate::test_util::LintErrTester::<$rule>::new(
+        $src,
         errors,
-        rule: std::marker::PhantomData,
-      };
-      t.run();
+        filename,
+      );
+      tester.run();
     )*
   };
 }
@@ -67,11 +53,104 @@ macro_rules! variant {
   }};
 }
 
+macro_rules! parse_ok_test {
+  ($src:literal) => {{
+    ($src, None)
+  }};
+  ({ src : $src:literal, filename : $filename:literal $(,)? }) => {{
+    ($src, Some($filename))
+  }};
+}
+
+macro_rules! parse_err_test {
+  (
+    [
+      $(
+        {
+          $($field:ident : $value:expr),* $(,)?
+        }
+      ),* $(,)?
+    ]
+  ) => {{
+    let filename = std::option::Option::<&str>::None;
+    let mut errors = Vec::new();
+    $(
+      let mut builder = $crate::test_util::LintErrBuilder::new();
+      $(
+        builder.$field($value);
+      )*
+      let e = builder.build();
+      errors.push(e);
+    )*
+    (errors, filename)
+  }};
+  (
+    {
+      filename : $filename:literal,
+      errors : $errors:tt $(,)?
+    }
+  ) => {{
+    let (errors, _) = parse_err_test!($errors);
+    (errors, $filename)
+  }};
+}
+
 #[derive(Default)]
 pub struct LintErrTester<T: LintRule + 'static> {
-  pub src: &'static str,
-  pub errors: Vec<LintErr>,
-  pub rule: PhantomData<T>,
+  src: &'static str,
+  errors: Vec<LintErr>,
+  filename: String,
+  rule: PhantomData<T>,
+}
+
+impl<T: LintRule + 'static> LintErrTester<T> {
+  pub fn new(
+    src: &'static str,
+    errors: Vec<LintErr>,
+    filename: Option<&str>,
+  ) -> Self {
+    Self {
+      src,
+      errors,
+      filename: match filename {
+        Some(f) => f.to_string(),
+        None => "deno_lint_err_test.tsx".to_string(),
+      },
+      rule: PhantomData,
+    }
+  }
+
+  pub fn run(self) {
+    let rule = T::new();
+    let rule_code = rule.code();
+    let diagnostics = lint(rule, self.src, self.filename);
+    assert_eq!(
+      self.errors.len(),
+      diagnostics.len(),
+      "{} diagnostics expected, but got {}.\n\nsource:\n{}\n",
+      self.errors.len(),
+      diagnostics.len(),
+      self.src,
+    );
+
+    for (error, diagnostic) in self.errors.iter().zip(&diagnostics) {
+      let LintErr {
+        line,
+        col,
+        message,
+        hint,
+      } = error;
+      assert_diagnostic_2(
+        diagnostic,
+        rule_code,
+        *line,
+        *col,
+        self.src,
+        message,
+        hint.as_deref(),
+      );
+    }
+  }
 }
 
 #[derive(Default)]
@@ -127,41 +206,11 @@ impl LintErrBuilder {
   }
 }
 
-impl<T: LintRule + 'static> LintErrTester<T> {
-  pub fn run(&self) {
-    let rule = T::new();
-    let rule_code = rule.code();
-    let diagnostics = lint(rule, self.src);
-    assert_eq!(
-      self.errors.len(),
-      diagnostics.len(),
-      "{} diagnostics expected, but got {}.\n\nsource:\n{}\n",
-      self.errors.len(),
-      diagnostics.len(),
-      self.src,
-    );
-
-    for (error, diagnostic) in self.errors.iter().zip(&diagnostics) {
-      let LintErr {
-        line,
-        col,
-        message,
-        hint,
-      } = error;
-      assert_diagnostic_2(
-        diagnostic,
-        rule_code,
-        *line,
-        *col,
-        self.src,
-        message,
-        hint.as_deref(),
-      );
-    }
-  }
-}
-
-fn lint(rule: Box<dyn LintRule>, source: &str) -> Vec<LintDiagnostic> {
+fn lint(
+  rule: Box<dyn LintRule>,
+  source: &str,
+  filename: String,
+) -> Vec<LintDiagnostic> {
   let mut linter = LinterBuilder::default()
     .lint_unused_ignore_directives(false)
     .lint_unknown_rules(false)
@@ -170,7 +219,7 @@ fn lint(rule: Box<dyn LintRule>, source: &str) -> Vec<LintDiagnostic> {
     .build();
 
   let (_, diagnostics) = linter
-    .lint("deno_lint_test.tsx".to_string(), source.to_string())
+    .lint(filename, source.to_string())
     .unwrap_or_else(|_| panic!("Failed to lint.\n[source code]\n{}", source));
   diagnostics
 }
@@ -188,7 +237,7 @@ pub fn assert_diagnostic(
   {
     return;
   }
-  panic!(format!(
+  panic!(
     "expect diagnostics {} at {}:{} to be {} at {}:{}\n\nsource:\n{}\n",
     diagnostic.code,
     diagnostic.range.start.line,
@@ -197,7 +246,7 @@ pub fn assert_diagnostic(
     line,
     col,
     source,
-  ));
+  );
 }
 
 fn assert_diagnostic_2(
@@ -239,9 +288,16 @@ fn assert_diagnostic_2(
   );
 }
 
-pub fn assert_lint_ok<T: LintRule + 'static>(source: &str) {
+pub fn assert_lint_ok<T: LintRule + 'static>(
+  source: &str,
+  filename: Option<&str>,
+) {
   let rule = T::new();
-  let diagnostics = lint(rule, source);
+  let filename = match filename {
+    Some(f) => f.to_string(),
+    None => "deno_lint_ok_test.tsx".to_string(),
+  };
+  let diagnostics = lint(rule, source, filename);
   if !diagnostics.is_empty() {
     panic!(
       "Unexpected diagnostics found:\n{:#?}\n\nsource:\n{}\n",
@@ -261,7 +317,7 @@ pub fn assert_lint_err_on_line<T: LintRule + 'static>(
 ) {
   let rule = T::new();
   let rule_code = rule.code();
-  let diagnostics = lint(rule, source);
+  let diagnostics = lint(rule, source, "deno_lint_err_test.tsx".to_string());
   assert_eq!(
     diagnostics.len(),
     1,
@@ -289,7 +345,7 @@ pub fn assert_lint_err_on_line_n<T: LintRule + 'static>(
 ) {
   let rule = T::new();
   let rule_code = rule.code();
-  let diagnostics = lint(rule, source);
+  let diagnostics = lint(rule, source, "deno_lint_err_test.tsx".to_string());
   assert_eq!(
     diagnostics.len(),
     expected.len(),