Repository: denoland/deno_lint (rust)

Issue:
Fallthrough is not allowed when adding comment above switch case
I often do stuff like this:

```ts
switch (something) {
  case 1:
  // some comment about case 2
  case 2:
    console.log(1);
    break;
  default:
    console.log(2);
    break;
}
```

There is no fallthrough here, but deno lint warns about it because of the comment on line 3

Pull request:
fix: allow blank lines and comments in cases that do not stop execution
Fixes #1196

This PR allows:
```ts
switch (number) {
  case 1:
    // comment
  case 2:
    doSomething();
}
```
```ts
switch (number) {
  case 1:
    
  case 2:
    doSomething();
}
```
but does not allow:
```ts
switch (number) {
  case 1:
    // comment
    doSomething();
  case 2:
    doSomething();
}
```

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index da329f7..3628873 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -12,6 +12,19 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
 [[package]]
 name = "aho-corasick"
 version = "0.7.18"
@@ -86,6 +99,12 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
 [[package]]
 name = "better_scoped_tls"
 version = "0.1.1"
@@ -107,6 +126,15 @@ version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
 
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
 [[package]]
 name = "bstr"
 version = "0.2.17"
@@ -178,6 +206,15 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
 
+[[package]]
+name = "cpufeatures"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "crossbeam-deque"
 version = "0.8.1"
@@ -212,6 +249,29 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "dashmap"
+version = "5.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
+dependencies = [
+ "cfg-if",
+ "hashbrown 0.14.3",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
 [[package]]
 name = "data-url"
 version = "0.3.0"
@@ -232,10 +292,14 @@ dependencies = [
  "serde",
  "swc_atoms",
  "swc_common",
+ "swc_config",
+ "swc_config_macro",
  "swc_ecma_ast",
  "swc_ecma_loader",
  "swc_ecma_parser",
  "swc_ecma_transforms_base",
+ "swc_ecma_transforms_macros",
+ "swc_ecma_transforms_react",
  "swc_ecma_utils",
  "swc_ecma_visit",
  "swc_eq_ignore_macros",
@@ -250,7 +314,7 @@ dependencies = [
 
 [[package]]
 name = "deno_lint"
-version = "0.58.4"
+version = "0.59.1"
 dependencies = [
  "ansi_term",
  "anyhow",
@@ -306,6 +370,16 @@ dependencies = [
  "syn 1.0.94",
 ]
 
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
 [[package]]
 name = "dprint-swc-ext"
 version = "0.16.0"
@@ -384,6 +458,16 @@ dependencies = [
  "syn 2.0.48",
 ]
 
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
 [[package]]
 name = "getopts"
 version = "0.2.21"
@@ -393,6 +477,17 @@ dependencies = [
  "unicode-width",
 ]
 
+[[package]]
+name = "getrandom"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
 [[package]]
 name = "globset"
 version = "0.4.8"
@@ -565,6 +660,16 @@ version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
 
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
 [[package]]
 name = "log"
 version = "0.4.20"
@@ -655,6 +760,19 @@ version = "6.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
 
+[[package]]
+name = "parking_lot_core"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.52.0",
+]
+
 [[package]]
 name = "pathdiff"
 version = "0.2.1"
@@ -789,6 +907,15 @@ dependencies = [
  "crossbeam-utils",
 ]
 
+[[package]]
+name = "redox_syscall"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
+dependencies = [
+ "bitflags 2.5.0",
+]
+
 [[package]]
 name = "regex"
 version = "1.10.2"
@@ -910,6 +1037,17 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "sha-1"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
 [[package]]
 name = "siphasher"
 version = "0.3.10"
@@ -988,6 +1126,20 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "swc_cached"
+version = "0.3.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83406221c501860fce9c27444f44125eafe9e598b8b81be7563d7036784cd05c"
+dependencies = [
+ "ahash",
+ "anyhow",
+ "dashmap",
+ "once_cell",
+ "regex",
+ "serde",
+]
+
 [[package]]
 name = "swc_common"
 version = "0.33.25"
@@ -1013,6 +1165,32 @@ dependencies = [
  "url",
 ]
 
+[[package]]
+name = "swc_config"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ada712ac5e28a301683c8af957e8a56deca675cbc376473dd207a527b989efb5"
+dependencies = [
+ "anyhow",
+ "indexmap 2.1.0",
+ "serde",
+ "serde_json",
+ "swc_cached",
+ "swc_config_macro",
+]
+
+[[package]]
+name = "swc_config_macro"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2574f75082322a27d990116cd2a24de52945fc94172b24ca0b3e9e2a6ceb6b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "swc_macros_common",
+ "syn 2.0.48",
+]
+
 [[package]]
 name = "swc_ecma_ast"
 version = "0.113.0"
@@ -1090,6 +1268,42 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "swc_ecma_transforms_macros"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17e309b88f337da54ef7fe4c5b99c2c522927071f797ee6c9fb8b6bf2d100481"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "swc_macros_common",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "swc_ecma_transforms_react"
+version = "0.184.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69c87599f4a10987fe2687967e5448858b458f2924faa62f044acd56f4e3ffda"
+dependencies = [
+ "base64",
+ "dashmap",
+ "indexmap 2.1.0",
+ "once_cell",
+ "serde",
+ "sha-1",
+ "string_enum",
+ "swc_atoms",
+ "swc_common",
+ "swc_config",
+ "swc_ecma_ast",
+ "swc_ecma_parser",
+ "swc_ecma_transforms_base",
+ "swc_ecma_transforms_macros",
+ "swc_ecma_utils",
+ "swc_ecma_visit",
+]
+
 [[package]]
 name = "swc_ecma_utils"
 version = "0.128.0"
@@ -1304,6 +1518,12 @@ version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
 
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
 [[package]]
 name = "unicase"
 version = "2.6.0"
@@ -1414,6 +1634,12 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -1576,3 +1802,23 @@ name = "windows_x86_64_msvc"
 version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
diff --git a/Cargo.toml b/Cargo.toml
index ae66f27..9787ac6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "deno_lint"
-version = "0.58.4"
+version = "0.59.1"
 edition = "2021"
 description = "lint for deno"
 authors = ["the Deno authors"]
@@ -25,7 +25,7 @@ default = []
 docs = []
 
 [dependencies]
-deno_ast = { version = "0.38.0", features = ["scopes", "transforms", "utils", "visit", "view"] }
+deno_ast = { version = "0.38.0", features = ["scopes", "transforms", "utils", "visit", "view", "react"] }
 log = "0.4.20"
 serde = { version = "1.0.195", features = ["derive"] }
 serde_json = "1.0.111"
diff --git a/docs/rules/no_unused_vars.md b/docs/rules/no_unused_vars.md
index c3a25a5..cd07708 100644
--- a/docs/rules/no_unused_vars.md
+++ b/docs/rules/no_unused_vars.md
@@ -1,4 +1,4 @@
-Enforces all variables used at least once.
+Enforces all variables are used at least once.
 
 If there are variables that are declared but not used anywhere, it's most likely
 because of incomplete refactoring. This lint rule detects and warns such unused
diff --git a/examples/dlint/main.rs b/examples/dlint/main.rs
index 29eedee..1b5f721 100644
--- a/examples/dlint/main.rs
+++ b/examples/dlint/main.rs
@@ -7,6 +7,7 @@ use clap::Command;
 use core::panic;
 use deno_ast::MediaType;
 use deno_ast::ModuleSpecifier;
+use deno_lint::linter::LintConfig;
 use deno_lint::linter::LintFileOptions;
 use deno_lint::linter::LinterBuilder;
 use deno_lint::rules::{get_filtered_rules, get_recommended_rules};
@@ -118,6 +119,10 @@ fn run_linter(
           ),
           source_code,
           media_type: MediaType::from_path(file_path),
+          config: LintConfig {
+            default_jsx_factory: Some("React.createElement".to_string()),
+            default_jsx_fragment_factory: Some("React.Fragment".to_string()),
+          },
         })?;
 
       error_counts.fetch_add(diagnostics.len(), Ordering::Relaxed);
diff --git a/src/context.rs b/src/context.rs
index 3ca7787..5a85e98 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -8,8 +8,10 @@ use crate::ignore_directives::{
 };
 use crate::linter::LinterContext;
 use crate::rules::{self, get_all_rules, LintRule};
+use deno_ast::swc::ast::Expr;
 use deno_ast::swc::common::comments::Comment;
-use deno_ast::swc::common::SyntaxContext;
+use deno_ast::swc::common::util::take::Take;
+use deno_ast::swc::common::{SourceMap, SyntaxContext};
 use deno_ast::Scope;
 use deno_ast::SourceTextInfo;
 use deno_ast::{
@@ -17,6 +19,7 @@ use deno_ast::{
 };
 use deno_ast::{MediaType, ModuleSpecifier};
 use std::collections::{HashMap, HashSet};
+use std::sync::Arc;
 
 /// `Context` stores all data needed to perform linting of a particular file.
 pub struct Context<'view> {
@@ -29,6 +32,10 @@ pub struct Context<'view> {
   control_flow: ControlFlow,
   traverse_flow: TraverseFlow,
   check_unknown_rules: bool,
+  #[allow(clippy::redundant_allocation)] // This type comes from SWC.
+  jsx_factory: Option<Arc<Box<Expr>>>,
+  #[allow(clippy::redundant_allocation)] // This type comes from SWC.
+  jsx_fragment_factory: Option<Arc<Box<Expr>>>,
 }
 
 impl<'view> Context<'view> {
@@ -37,6 +44,8 @@ impl<'view> Context<'view> {
     parsed_source: ParsedSource,
     program: ast_view::Program<'view>,
     file_ignore_directive: Option<FileIgnoreDirective>,
+    default_jsx_factory: Option<String>,
+    default_jsx_fragment_factory: Option<String>,
   ) -> Self {
     let line_ignore_directives = parse_line_ignore_directives(
       &linter_ctx.ignore_diagnostic_directive,
@@ -46,6 +55,50 @@ impl<'view> Context<'view> {
     let control_flow =
       ControlFlow::analyze(program, parsed_source.unresolved_context());
 
+    let mut jsx_factory = None;
+    let mut jsx_fragment_factory = None;
+
+    parsed_source.globals().with(|marks| {
+      let top_level_mark = marks.top_level;
+
+      if let Some(leading_comments) = parsed_source.get_leading_comments() {
+        let jsx_directives =
+          deno_ast::swc::transforms::react::JsxDirectives::from_comments(
+            &SourceMap::default(),
+            #[allow(clippy::disallowed_types)]
+            deno_ast::swc::common::Span::dummy(),
+            leading_comments,
+            top_level_mark,
+          );
+
+        jsx_factory = jsx_directives.pragma;
+        jsx_fragment_factory = jsx_directives.pragma_frag;
+      }
+
+      if jsx_factory.is_none() {
+        if let Some(factory) = default_jsx_factory {
+          jsx_factory =
+            Some(deno_ast::swc::transforms::react::parse_expr_for_jsx(
+              &SourceMap::default(),
+              "jsx",
+              factory,
+              top_level_mark,
+            ));
+        }
+      }
+      if jsx_fragment_factory.is_none() {
+        if let Some(factory) = default_jsx_fragment_factory {
+          jsx_fragment_factory =
+            Some(deno_ast::swc::transforms::react::parse_expr_for_jsx(
+              &SourceMap::default(),
+              "jsxFragment",
+              factory,
+              top_level_mark,
+            ));
+        }
+      }
+    });
+
     Self {
       file_ignore_directive,
       line_ignore_directives,
@@ -56,6 +109,8 @@ impl<'view> Context<'view> {
       diagnostics: Vec::new(),
       traverse_flow: TraverseFlow::default(),
       check_unknown_rules: linter_ctx.check_unknown_rules,
+      jsx_factory,
+      jsx_fragment_factory,
     }
   }
 
@@ -107,6 +162,22 @@ impl<'view> Context<'view> {
     &self.control_flow
   }
 
+  /// Get the JSX factory expression for this file, if one is specified (via
+  /// pragma or using a default). If this file is not JSX, uses the automatic
+  /// transform, or the default factory is not specified, this will return
+  /// `None`.
+  pub fn jsx_factory(&self) -> Option<Arc<Box<Expr>>> {
+    self.jsx_factory.clone()
+  }
+
+  /// Get the JSX fragment factory expression for this file, if one is specified
+  /// (via pragma or using a default). If this file is not JSX, uses the
+  /// automatic transform, or the default factory is not specified, this will
+  /// return `None`.
+  pub fn jsx_fragment_factory(&self) -> Option<Arc<Box<Expr>>> {
+    self.jsx_fragment_factory.clone()
+  }
+
   /// The `SyntaxContext` of any unresolved identifiers
   pub(crate) fn unresolved_ctxt(&self) -> SyntaxContext {
     self.parsed_source.unresolved_context()
diff --git a/src/lib.rs b/src/lib.rs
index da1386b..d15c1e5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -46,6 +46,10 @@ mod lint_tests {
         specifier: ModuleSpecifier::parse("file:///lint_test.ts").unwrap(),
         source_code: source.to_string(),
         media_type: MediaType::TypeScript,
+        config: LintConfig {
+          default_jsx_factory: None,
+          default_jsx_fragment_factory: None,
+        },
       })
       .expect("Failed to lint");
     diagnostics
@@ -56,8 +60,13 @@ mod lint_tests {
     rules: Vec<&'static dyn LintRule>,
   ) -> Vec<LintDiagnostic> {
     let linter = LinterBuilder::default().rules(rules).build();
-
-    linter.lint_with_ast(parsed_source)
+    linter.lint_with_ast(
+      parsed_source,
+      LintConfig {
+        default_jsx_factory: None,
+        default_jsx_fragment_factory: None,
+      },
+    )
   }
 
   fn lint_recommended_rules(source: &str) -> Vec<LintDiagnostic> {
diff --git a/src/linter.rs b/src/linter.rs
index 6760957..5df1b03 100644
--- a/src/linter.rs
+++ b/src/linter.rs
@@ -103,6 +103,13 @@ pub struct LintFileOptions {
   pub specifier: ModuleSpecifier,
   pub source_code: String,
   pub media_type: MediaType,
+  pub config: LintConfig,
+}
+
+#[derive(Debug, Clone)]
+pub struct LintConfig {
+  pub default_jsx_factory: Option<String>,
+  pub default_jsx_fragment_factory: Option<String>,
 }
 
 impl Linter {
@@ -138,7 +145,11 @@ impl Linter {
     };
 
     let parsed_source = parse_result?;
-    let diagnostics = self.lint_inner(&parsed_source);
+    let diagnostics = self.lint_inner(
+      &parsed_source,
+      options.config.default_jsx_factory,
+      options.config.default_jsx_fragment_factory,
+    );
 
     Ok((parsed_source, diagnostics))
   }
@@ -150,9 +161,14 @@ impl Linter {
   pub fn lint_with_ast(
     &self,
     parsed_source: &ParsedSource,
+    config: LintConfig,
   ) -> Vec<LintDiagnostic> {
     let _mark = PerformanceMark::new("Linter::lint_with_ast");
-    self.lint_inner(parsed_source)
+    self.lint_inner(
+      parsed_source,
+      config.default_jsx_factory,
+      config.default_jsx_fragment_factory,
+    )
   }
 
   // TODO(bartlomieju): this struct does too much - not only it checks for ignored
@@ -173,7 +189,12 @@ impl Linter {
     diagnostics
   }
 
-  fn lint_inner(&self, parsed_source: &ParsedSource) -> Vec<LintDiagnostic> {
+  fn lint_inner(
+    &self,
+    parsed_source: &ParsedSource,
+    default_jsx_factory: Option<String>,
+    default_jsx_fragment_factory: Option<String>,
+  ) -> Vec<LintDiagnostic> {
     let _mark = PerformanceMark::new("Linter::lint_inner");
 
     let diagnostics = parsed_source.with_view(|pg| {
@@ -205,6 +226,8 @@ impl Linter {
         parsed_source.clone(),
         pg,
         file_ignore_directive,
+        default_jsx_factory,
+        default_jsx_fragment_factory,
       );
 
       // Run configured lint rules.
diff --git a/src/rules/no_fallthrough.rs b/src/rules/no_fallthrough.rs
index 7520819..05ec0b3 100644
--- a/src/rules/no_fallthrough.rs
+++ b/src/rules/no_fallthrough.rs
@@ -9,7 +9,7 @@ use deno_ast::swc::{
   ast::*,
   visit::{noop_visit_type, Visit, VisitWith},
 };
-use deno_ast::{SourceRange, SourceRanged, SourceRangedForSpanned};
+use deno_ast::SourceRangedForSpanned;
 use derive_more::Display;
 
 #[derive(Debug)]
@@ -70,7 +70,7 @@ impl<'c, 'view> Visit for NoFallthroughVisitor<'c, 'view> {
     let mut should_emit_err = false;
     let mut prev_range = None;
 
-    'cases: for (case_idx, case) in cases.iter().enumerate() {
+    'cases: for case in cases.iter() {
       case.visit_with(self);
 
       if should_emit_err {
@@ -112,21 +112,8 @@ impl<'c, 'view> Visit for NoFallthroughVisitor<'c, 'view> {
       let empty = case.cons.is_empty()
         || matches!(case.cons.as_slice(), [Stmt::Block(b)] if b.stmts.is_empty());
 
-      if case_idx + 1 < cases.len() && empty {
-        let range = SourceRange::new(case.start(), cases[case_idx + 1].start());
-        // todo(dsherret): use `range.line_start_fast(context.program)` and
-        // `line_end_fast` when switching to ast_view
-        let range_line_count = range
-          .text_fast(self.context.text_info())
-          .chars()
-          .filter(|c| *c == '\n')
-          .count()
-          + 1;
-        // When the case body contains only new lines `case.cons` will be empty.
-        // This means there are no statements detected so we must detect case
-        // bodies made up of only new lines by counting the total amount of new lines.
-        // If there's more than 2 new lines and `case.cons` is empty this indicates the case body only contains new lines.
-        should_emit_err = range_line_count > 2;
+      if empty {
+        should_emit_err = false;
       }
 
       prev_range = Some(case.range());
@@ -186,6 +173,8 @@ mod tests {
       "switch('test') { case 'symbol':\n case 'function': default: b(); }",
       "switch('test') { case 'symbol':\n case 'function':\n default: b(); }",
       "switch('test') { case 'symbol': case 'function': default: b(); }",
+      "switch(foo) { case 1:\n\n default: a(); }",
+      "switch(foo) { case 1:\n// comment\n default: a(); }",
 
       // https://github.com/denoland/deno_lint/issues/746
       r#"
@@ -255,13 +244,6 @@ switch(someValue) {
           hint: NoFallthroughHint::BreakOrComment,
         }
       ],
-      "switch(foo) { case 0:\n\n default: b() }": [
-        {
-          col: 14,
-          message: NoFallthroughMessage::Unexpected,
-          hint: NoFallthroughHint::BreakOrComment,
-        }
-      ],
       "switch(foo) { case 0:\n\n b()\n default: b() }": [
         {
           col: 14,
@@ -269,13 +251,6 @@ switch(someValue) {
           hint: NoFallthroughHint::BreakOrComment,
         }
       ],
-      "switch(foo) { case 0:\n // comment\n default: b() }": [
-        {
-          col: 14,
-          message: NoFallthroughMessage::Unexpected,
-          hint: NoFallthroughHint::BreakOrComment,
-        }
-      ],
       "switch(foo) { case 0: a(); /* falling through */ default: b() }": [
         {
           col: 14,
diff --git a/src/rules/no_unused_vars.rs b/src/rules/no_unused_vars.rs
index 3451fc2..33c7215 100644
--- a/src/rules/no_unused_vars.rs
+++ b/src/rules/no_unused_vars.rs
@@ -9,23 +9,24 @@ use deno_ast::swc::ast::{
   ClassDecl, ClassMethod, ClassProp, Constructor, Decl, DefaultDecl,
   ExportDecl, ExportDefaultDecl, ExportNamedSpecifier, Expr, FnDecl, FnExpr,
   Function, Ident, ImportDefaultSpecifier, ImportNamedSpecifier,
-  ImportStarAsSpecifier, MemberExpr, MemberProp, MethodKind, ModuleExportName,
-  NamedExport, Param, Pat, PrivateMethod, Prop, PropName, SetterProp,
-  TsEntityName, TsEnumDecl, TsExprWithTypeArgs, TsImportEqualsDecl,
-  TsInterfaceDecl, TsModuleDecl, TsModuleRef, TsNamespaceDecl,
-  TsPropertySignature, TsTypeAliasDecl, TsTypeQueryExpr, TsTypeRef, VarDecl,
-  VarDeclarator,
+  ImportStarAsSpecifier, JSXElementName, JSXFragment, JSXObject, MemberExpr,
+  MemberProp, MethodKind, ModuleExportName, NamedExport, Param, Pat,
+  PrivateMethod, Prop, PropName, SetterProp, TsEntityName, TsEnumDecl,
+  TsExprWithTypeArgs, TsImportEqualsDecl, TsInterfaceDecl, TsModuleDecl,
+  TsModuleRef, TsNamespaceDecl, TsPropertySignature, TsTypeAliasDecl,
+  TsTypeQueryExpr, TsTypeRef, VarDecl, VarDeclarator,
 };
 use deno_ast::swc::ast::{Id, SimpleAssignTarget};
 use deno_ast::swc::atoms::js_word;
 use deno_ast::swc::utils::find_pat_ids;
 use deno_ast::swc::visit::{Visit, VisitWith};
 use deno_ast::view::AssignOp;
-use deno_ast::{MediaType, SourceRangedForSpanned};
+use deno_ast::SourceRangedForSpanned;
 use derive_more::Display;
 use if_chain::if_chain;
 use std::collections::HashSet;
 use std::iter;
+use std::sync::Arc;
 
 #[derive(Debug)]
 pub struct NoUnusedVars;
@@ -68,14 +69,14 @@ impl LintRule for NoUnusedVars {
     program: Program<'view>,
   ) {
     let program = program_ref(program);
-    // Skip linting this file to avoid emitting false positives about `jsxFactory` and `jsxFragmentFactory`
-    // if it's a JSX or TSX file.
-    // See https://github.com/denoland/deno_lint/pull/664#discussion_r614692736
-    if is_jsx_file(context.media_type()) {
-      return;
-    }
 
-    let mut collector = Collector::default();
+    let mut collector = Collector {
+      cur_defining: vec![],
+      used_types: Default::default(),
+      used_vars: Default::default(),
+      jsx_factory: context.jsx_factory(),
+      jsx_fragment_factory: context.jsx_fragment_factory(),
+    };
     match program {
       ProgramRef::Module(m) => m.visit_with(&mut collector),
       ProgramRef::Script(s) => s.visit_with(&mut collector),
@@ -98,12 +99,7 @@ impl LintRule for NoUnusedVars {
   }
 }
 
-fn is_jsx_file(media_type: MediaType) -> bool {
-  media_type == MediaType::Jsx || media_type == MediaType::Tsx
-}
-
 /// Collects information about variable usages.
-#[derive(Default)]
 struct Collector {
   used_vars: HashSet<Id>,
   used_types: HashSet<Id>,
@@ -118,6 +114,10 @@ struct Collector {
   /// Type of this should be hashset, but we don't have a way to
   /// restore hashset after handling bindings
   cur_defining: Vec<Id>,
+  #[allow(clippy::redundant_allocation)] // This type comes from SWC.
+  jsx_factory: Option<Arc<Box<Expr>>>,
+  #[allow(clippy::redundant_allocation)] // This type comes from SWC.
+  jsx_fragment_factory: Option<Arc<Box<Expr>>>,
 }
 
 impl Collector {
@@ -260,6 +260,37 @@ impl Visit for Collector {
     }
   }
 
+  fn visit_jsx_element_name(&mut self, n: &JSXElementName) {
+    if let Some(factory) = self.jsx_factory.take() {
+      factory.visit_with(self)
+    }
+    match n {
+      JSXElementName::Ident(i) => {
+        if !i.sym.starts_with(|c: char| c.is_ascii_lowercase()) {
+          self.mark_as_usage(i)
+        }
+      }
+      JSXElementName::JSXMemberExpr(n) => n.visit_with(self),
+      JSXElementName::JSXNamespacedName(_) => {
+        // This is a string literal.
+      }
+    }
+  }
+
+  fn visit_jsx_object(&mut self, n: &JSXObject) {
+    match n {
+      JSXObject::Ident(i) => self.mark_as_usage(i),
+      JSXObject::JSXMemberExpr(n) => n.visit_with(self),
+    }
+  }
+
+  fn visit_jsx_fragment(&mut self, n: &JSXFragment) {
+    if let Some(factory) = self.jsx_fragment_factory.take() {
+      factory.visit_with(self)
+    }
+    n.visit_children_with(self);
+  }
+
   fn visit_simple_assign_target(&mut self, n: &SimpleAssignTarget) {
     match n {
       SimpleAssignTarget::Ident(ident) => {
@@ -1485,6 +1516,17 @@ import React from "./dummy.ts";
 class Component extends React.Component { render() { return null; } }
 export default <Component />;
       "#,
+
+      r#"
+/** @jsx h */
+import { h } from "./dummy.ts";
+export default <foo />;
+      "#,
+      r#"
+/** @jsxFrag Fragment */
+import { Fragment } from "./dummy.ts";
+export default <></>;
+      "#,
     };
   }
 
@@ -2282,6 +2324,37 @@ export class Foo {
           },
         ],
     };
+
+    // jsx/tsx
+    assert_lint_err! {
+      NoUnusedVars,
+      filename: "file:///foo.tsx",
+      r#"
+import React from 'react';
+export const Foo = () => {
+  return "string";
+}"#: [
+        {
+          line: 2,
+          col: 7,
+          message: variant!(NoUnusedVarsMessage, NeverUsed, "React"),
+          hint: variant!(NoUnusedVarsHint, AddPrefix, "React"),
+        }
+      ],
+      r#"
+/** @jsx h */ /** @jsxFrag Fragment */
+import { h, Fragment } from "preact";
+export const Foo = () => {
+  return <></>;
+}"#: [
+        {
+          line: 3,
+          col: 9,
+          message: variant!(NoUnusedVarsMessage, NeverUsed, "h"),
+          hint: variant!(NoUnusedVarsHint, Alias, "h"),
+        }
+      ]
+    }
   }
 
   // TODO(magurotuna): deals with this using ControlFlow
diff --git a/www/static/docs.json b/www/static/docs.json
index 7c68b69..084532d 100644
--- a/www/static/docs.json
+++ b/www/static/docs.json
@@ -557,7 +557,7 @@
   },
   {
     "code": "no-unused-vars",
-    "docs": "Enforces all variables used at least once.\n\nIf there are variables that are declared but not used anywhere, it's most likely\nbecause of incomplete refactoring. This lint rule detects and warns such unused\nvariables.\n\nVariable `a` is considered to be \"used\" if any of the following conditions are\nsatisfied:\n\n- its value is read out, like `console.log(a)` or `let otherVariable = a;`\n- it's called or constructed, like `a()` or `new a()`\n- it's exported, like `export const a = 42;`\n\nIf a variable is just assigned to a value but never read out, then it's\nconsidered to be _\"not used\"_.\n\n```typescript\nlet a;\na = 42;\n\n// `a` is never read out\n```\n\nIf you want to declare unused variables intentionally, prefix them with the\nunderscore character `_`, like `_a`. This rule ignores variables that are\nprefixed with `_`.\n\n### Invalid:\n\n```typescript\nconst a = 0;\n\nconst b = 0; // this `b` is never used\nfunction foo() {\n  const b = 1; // this `b` is used\n  console.log(b);\n}\nfoo();\n\nlet c = 2;\nc = 3;\n\n// recursive function calls are not considered to be used, because only when `d`\n// is called from outside the function body can we say that `d` is actually\n// called after all.\nfunction d() {\n  d();\n}\n\n// `x` is never used\nexport function e(x: number): number {\n  return 42;\n}\n\nconst f = \"unused variable\";\n```\n\n### Valid:\n\n```typescript\nconst a = 0;\nconsole.log(a);\n\nconst b = 0;\nfunction foo() {\n  const b = 1;\n  console.log(b);\n}\nfoo();\nconsole.log(b);\n\nlet c = 2;\nc = 3;\nconsole.log(c);\n\nfunction d() {\n  d();\n}\nd();\n\nexport function e(x: number): number {\n  return x + 42;\n}\n\nexport const f = \"exported variable\";\n```\n",
+    "docs": "Enforces all variables are used at least once.\n\nIf there are variables that are declared but not used anywhere, it's most likely\nbecause of incomplete refactoring. This lint rule detects and warns such unused\nvariables.\n\nVariable `a` is considered to be \"used\" if any of the following conditions are\nsatisfied:\n\n- its value is read out, like `console.log(a)` or `let otherVariable = a;`\n- it's called or constructed, like `a()` or `new a()`\n- it's exported, like `export const a = 42;`\n\nIf a variable is just assigned to a value but never read out, then it's\nconsidered to be _\"not used\"_.\n\n```typescript\nlet a;\na = 42;\n\n// `a` is never read out\n```\n\nIf you want to declare unused variables intentionally, prefix them with the\nunderscore character `_`, like `_a`. This rule ignores variables that are\nprefixed with `_`.\n\n### Invalid:\n\n```typescript\nconst a = 0;\n\nconst b = 0; // this `b` is never used\nfunction foo() {\n  const b = 1; // this `b` is used\n  console.log(b);\n}\nfoo();\n\nlet c = 2;\nc = 3;\n\n// recursive function calls are not considered to be used, because only when `d`\n// is called from outside the function body can we say that `d` is actually\n// called after all.\nfunction d() {\n  d();\n}\n\n// `x` is never used\nexport function e(x: number): number {\n  return 42;\n}\n\nconst f = \"unused variable\";\n```\n\n### Valid:\n\n```typescript\nconst a = 0;\nconsole.log(a);\n\nconst b = 0;\nfunction foo() {\n  const b = 1;\n  console.log(b);\n}\nfoo();\nconsole.log(b);\n\nlet c = 2;\nc = 3;\nconsole.log(c);\n\nfunction d() {\n  d();\n}\nd();\n\nexport function e(x: number): number {\n  return x + 42;\n}\n\nexport const f = \"exported variable\";\n```\n",
     "tags": [
       "recommended"
     ]


Test patch:
diff --git a/src/test_util.rs b/src/test_util.rs
index 4ce4e1a..cc9c15c 100644
--- a/src/test_util.rs
+++ b/src/test_util.rs
@@ -2,6 +2,7 @@
 
 use crate::ast_parser;
 use crate::diagnostic::LintDiagnostic;
+use crate::linter::LintConfig;
 use crate::linter::LintFileOptions;
 use crate::linter::LinterBuilder;
 use crate::rules::LintRule;
@@ -326,6 +327,10 @@ fn lint(
     specifier,
     source_code: source.to_string(),
     media_type,
+    config: LintConfig {
+      default_jsx_factory: Some("React.createElement".to_owned()),
+      default_jsx_fragment_factory: Some("React.Fragment".to_owned()),
+    },
   });
   match lint_result {
     Ok((source, diagnostics)) => (source, diagnostics),