diff --git a/.typos.toml b/.typos.toml
index 28aa370e20bff..e427c79d81d52 100644
--- a/.typos.toml
+++ b/.typos.toml
@@ -7,6 +7,9 @@ extend-exclude = [
"**/*.snap",
"**/*/CHANGELOG.md",
"crates/oxc_linter/fixtures",
+ "crates/oxc_linter/src/rules/eslint/no_unused_vars/ignored.rs",
+ "crates/oxc_linter/src/rules/eslint/no_unused_vars/options.rs",
+ "crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/eslint.rs",
"crates/oxc_linter/src/rules/jsx_a11y/aria_props.rs",
"crates/oxc_linter/src/rules/jsx_a11y/img_redundant_alt.rs",
"crates/oxc_linter/src/rules/react/no_unknown_property.rs",
diff --git a/apps/oxlint/src/lint/mod.rs b/apps/oxlint/src/lint/mod.rs
index dd78c771e3e02..e19d40bbbac73 100644
--- a/apps/oxlint/src/lint/mod.rs
+++ b/apps/oxlint/src/lint/mod.rs
@@ -427,7 +427,7 @@ mod test {
];
let result = test(args);
assert_eq!(result.number_of_files, 1);
- assert_eq!(result.number_of_warnings, 2);
+ assert_eq!(result.number_of_warnings, 3);
assert_eq!(result.number_of_errors, 0);
}
@@ -441,7 +441,7 @@ mod test {
];
let result = test(args);
assert_eq!(result.number_of_files, 1);
- assert_eq!(result.number_of_warnings, 1);
+ assert_eq!(result.number_of_warnings, 2);
assert_eq!(result.number_of_errors, 0);
}
@@ -477,7 +477,7 @@ mod test {
let args = &["fixtures/svelte/debugger.svelte"];
let result = test(args);
assert_eq!(result.number_of_files, 1);
- assert_eq!(result.number_of_warnings, 1);
+ assert_eq!(result.number_of_warnings, 2);
assert_eq!(result.number_of_errors, 0);
}
diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs
index 8dd17b38b88e7..16ea8c4e93306 100644
--- a/crates/oxc_ast/src/ast_impl/js.rs
+++ b/crates/oxc_ast/src/ast_impl/js.rs
@@ -1203,6 +1203,11 @@ impl<'a> Hash for Class<'a> {
}
impl<'a> ClassElement<'a> {
+ /// Returns `true` if this is a [`ClassElement::StaticBlock`].
+ pub fn is_static_block(&self) -> bool {
+ matches!(self, Self::StaticBlock(_))
+ }
+
/// Returns `true` if this [`ClassElement`] is a static block or has a
/// static modifier.
pub fn r#static(&self) -> bool {
diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs
index 270723ae31125..358594e8b6d99 100644
--- a/crates/oxc_linter/src/rules.rs
+++ b/crates/oxc_linter/src/rules.rs
@@ -106,6 +106,7 @@ mod eslint {
pub mod no_unsafe_optional_chaining;
pub mod no_unused_labels;
pub mod no_unused_private_class_members;
+ pub mod no_unused_vars;
pub mod no_useless_catch;
pub mod no_useless_concat;
pub mod no_useless_constructor;
@@ -526,6 +527,7 @@ oxc_macros::declare_all_lint_rules! {
eslint::no_unsafe_negation,
eslint::no_unsafe_optional_chaining,
eslint::no_unused_labels,
+ eslint::no_unused_vars,
eslint::no_unused_private_class_members,
eslint::no_useless_catch,
eslint::no_useless_escape,
diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs
new file mode 100644
index 0000000000000..5c2fcee36f24d
--- /dev/null
+++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs
@@ -0,0 +1,290 @@
+//! This module checks if an unused variable is allowed. Note that this does not
+//! consider variables ignored by name pattern, but by where they are declared.
+#[allow(clippy::wildcard_imports)]
+use oxc_ast::{ast::*, AstKind};
+use oxc_semantic::{AstNode, AstNodeId, Semantic};
+use oxc_span::GetSpan;
+
+use crate::rules::eslint::no_unused_vars::binding_pattern::{BindingContext, HasAnyUsedBinding};
+
+use super::{options::ArgsOption, NoUnusedVars, Symbol};
+
+impl<'s, 'a> Symbol<'s, 'a> {
+ /// Returns `true` if this function is use.
+ ///
+ /// Checks for these cases
+ /// 1. passed as a callback to another [`CallExpression`] or [`NewExpression`]
+ /// 2. invoked as an IIFE
+ /// 3. Returned from another function
+ /// 4. Used as an attribute in a JSX element
+ #[inline]
+ pub fn is_function_or_class_declaration_used(&self) -> bool {
+ #[cfg(debug_assertions)]
+ {
+ let kind = self.declaration().kind();
+ assert!(kind.is_function_like() || matches!(kind, AstKind::Class(_)));
+ }
+
+ for parent in self.iter_parents() {
+ match parent.kind() {
+ AstKind::MemberExpression(_) | AstKind::ParenthesizedExpression(_) => {
+ continue;
+ }
+ // Returned from another function. Definitely won't be the same
+ // function because we're walking up from its declaration
+ AstKind::ReturnStatement(_)
+ //
+ | AstKind::JSXExpressionContainer(_)
+ // Function declaration is passed as an argument to another function.
+ | AstKind::CallExpression(_) | AstKind::Argument(_)
+ // e.g. `const x = { foo: function foo() {} }`
+ | AstKind::ObjectProperty(_)
+ // e.g. var foo = function bar() { }
+ // we don't want to check for violations on `bar`, just `foo`
+ | AstKind::VariableDeclarator(_)
+ => {
+ return true;
+ }
+ // !function() {}; is an IIFE
+ AstKind::UnaryExpression(expr) => return expr.operator.is_not(),
+ // function is used as a value for an assignment
+ // e.g. Array.prototype.sort ||= function sort(a, b) { }
+ AstKind::AssignmentExpression(assignment) if assignment.right.span().contains_inclusive(self.span()) => {
+ return self != &assignment.left;
+ }
+ _ => {
+ return false;
+ }
+ }
+ }
+
+ false
+ }
+
+ fn is_declared_in_for_of_loop(&self) -> bool {
+ for parent in self.iter_parents() {
+ match parent.kind() {
+ AstKind::ParenthesizedExpression(_)
+ | AstKind::VariableDeclaration(_)
+ | AstKind::BindingIdentifier(_)
+ | AstKind::SimpleAssignmentTarget(_)
+ | AstKind::AssignmentTarget(_) => continue,
+ AstKind::ForInStatement(ForInStatement { body, .. })
+ | AstKind::ForOfStatement(ForOfStatement { body, .. }) => match body {
+ Statement::ReturnStatement(_) => return true,
+ Statement::BlockStatement(b) => {
+ return b
+ .body
+ .first()
+ .is_some_and(|s| matches!(s, Statement::ReturnStatement(_)))
+ }
+ _ => return false,
+ },
+ _ => return false,
+ }
+ }
+
+ false
+ }
+
+ pub fn is_in_declared_module(&self) -> bool {
+ let scopes = self.scopes();
+ let nodes = self.nodes();
+ scopes.ancestors(self.scope_id())
+ .map(|scope_id| scopes.get_node_id(scope_id))
+ .map(|node_id| nodes.get_node(node_id))
+ .any(|node| matches!(node.kind(), AstKind::TSModuleDeclaration(namespace) if is_ambient_namespace(namespace)))
+ }
+}
+
+#[inline]
+fn is_ambient_namespace(namespace: &TSModuleDeclaration) -> bool {
+ namespace.declare || namespace.kind.is_global()
+}
+
+impl NoUnusedVars {
+ #[allow(clippy::unused_self)]
+ pub(super) fn is_allowed_class_or_function(&self, symbol: &Symbol<'_, '_>) -> bool {
+ symbol.is_function_or_class_declaration_used()
+ // || symbol.is_function_or_class_assigned_to_same_name_variable()
+ }
+
+ #[allow(clippy::unused_self)]
+ pub(super) fn is_allowed_ts_namespace<'a>(
+ &self,
+ symbol: &Symbol<'_, 'a>,
+ namespace: &TSModuleDeclaration<'a>,
+ ) -> bool {
+ if is_ambient_namespace(namespace) {
+ return true;
+ }
+ symbol.is_in_declared_module()
+ }
+
+ /// Returns `true` if this unused variable declaration should be allowed
+ /// (i.e. not reported)
+ pub(super) fn is_allowed_variable_declaration<'a>(
+ &self,
+ symbol: &Symbol<'_, 'a>,
+ decl: &VariableDeclarator<'a>,
+ ) -> bool {
+ if decl.kind.is_var() && self.vars.is_local() && symbol.is_root() {
+ return true;
+ }
+
+ // allow unused iterators, since they're required for valid syntax
+ if symbol.is_declared_in_for_of_loop() {
+ return true;
+ }
+
+ false
+ }
+
+ #[allow(clippy::unused_self)]
+ pub(super) fn is_allowed_type_parameter(
+ &self,
+ symbol: &Symbol<'_, '_>,
+ declaration_id: AstNodeId,
+ ) -> bool {
+ matches!(symbol.nodes().parent_kind(declaration_id), Some(AstKind::TSMappedType(_)))
+ }
+
+ /// Returns `true` if this unused parameter should be allowed (i.e. not
+ /// reported)
+ pub(super) fn is_allowed_argument<'a>(
+ &self,
+ semantic: &Semantic<'a>,
+ symbol: &Symbol<'_, 'a>,
+ param: &FormalParameter<'a>,
+ ) -> bool {
+ // early short-circuit when no argument checking should be performed
+ if self.args.is_none() {
+ return true;
+ }
+
+ // find FormalParameters. Should be the next parent of param, but this
+ // is safer.
+ let Some((params, params_id)) = symbol.iter_parents().find_map(|p| {
+ if let AstKind::FormalParameters(params) = p.kind() {
+ Some((params, p.id()))
+ } else {
+ None
+ }
+ }) else {
+ debug_assert!(false, "FormalParameter should always have a parent FormalParameters");
+ return false;
+ };
+
+ if Self::is_allowed_param_because_of_method(semantic, param, params_id) {
+ return true;
+ }
+
+ // Parameters are always checked. Must be done after above checks,
+ // because in those cases a parameter is required. However, even if
+ // `args` is `all`, it may be ignored using `ignoreRestSiblings` or `destructuredArrayIgnorePattern`.
+ if self.args.is_all() {
+ return false;
+ }
+
+ debug_assert_eq!(self.args, ArgsOption::AfterUsed);
+
+ // from eslint rule documentation:
+ // after-used - unused positional arguments that occur before the last
+ // used argument will not be checked, but all named arguments and all
+ // positional arguments after the last used argument will be checked.
+
+ // unused non-positional arguments are never allowed
+ if param.pattern.kind.is_destructuring_pattern() {
+ return false;
+ }
+
+ // find the index of the parameter in the parameters list. We want to
+ // check all parameters after this one for usages.
+ let position =
+ params.items.iter().enumerate().find(|(_, p)| p.span == param.span).map(|(i, _)| i);
+ debug_assert!(
+ position.is_some(),
+ "could not find FormalParameter in a FormalParameters node that is its parent."
+ );
+ let Some(position) = position else {
+ return false;
+ };
+
+ // This is the last parameter, so need to check for usages on following parameters
+ if position == params.items.len() - 1 {
+ return false;
+ }
+
+ let ctx = BindingContext { options: self, semantic };
+ params
+ .items
+ .iter()
+ .skip(position + 1)
+ // has_modifier() to handle:
+ // constructor(unused: number, public property: string) {}
+ // no need to check if param is in a constructor, because if it's
+ // not that's a parse error.
+ .any(|p| p.has_modifier() || p.pattern.has_any_used_binding(ctx))
+ }
+
+ /// `params_id` is the [`AstNodeId`] to a [`AstKind::FormalParameters`] node.
+ ///
+ /// The following allowed conditions are handled:
+ /// 1. setter parameters - removing them causes a syntax error.
+ /// 2. TS constructor property definitions - they declare class members.
+ fn is_allowed_param_because_of_method<'a>(
+ semantic: &Semantic<'a>,
+ param: &FormalParameter<'a>,
+ params_id: AstNodeId,
+ ) -> bool {
+ let mut parents_iter = semantic.nodes().iter_parents(params_id).skip(1).map(AstNode::kind);
+
+ // in function declarations, the parent immediately before the
+ // FormalParameters is a TSDeclareBlock
+ let Some(parent) = parents_iter.next() else {
+ return false;
+ };
+ if matches!(parent, AstKind::Function(f) if f.r#type == FunctionType::TSDeclareFunction) {
+ return true;
+ }
+
+ // for non-overloads, the next parent will be the function
+ let Some(maybe_method_or_fn) = parents_iter.next() else {
+ return false;
+ };
+
+ match maybe_method_or_fn {
+ // arguments inside setters are allowed. Without them, the program
+ // has invalid syntax
+ AstKind::MethodDefinition(MethodDefinition {
+ kind: MethodDefinitionKind::Set, ..
+ })
+ | AstKind::ObjectProperty(ObjectProperty { kind: PropertyKind::Set, .. }) => true,
+
+ // Allow unused parameters in function overloads
+ AstKind::Function(f)
+ if f.body.is_none() || f.r#type == FunctionType::TSDeclareFunction =>
+ {
+ true
+ }
+ // Allow unused parameters in method overloads and overrides
+ AstKind::MethodDefinition(method)
+ if method.value.r#type == FunctionType::TSEmptyBodyFunctionExpression
+ || method.r#override =>
+ {
+ true
+ }
+ // constructor property definitions are allowed because they declare
+ // class members
+ // e.g. `class Foo { constructor(public a) {} }`
+ AstKind::MethodDefinition(method) if method.kind.is_constructor() => {
+ param.has_modifier()
+ }
+ // parameters in abstract methods will never be directly used b/c
+ // abstract methods have no bodies. However, since this establishes
+ // an API contract and gets used by subclasses, it is allowed.
+ AstKind::MethodDefinition(method) if method.r#type.is_abstract() => true,
+ _ => false,
+ }
+ }
+}
diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/binding_pattern.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/binding_pattern.rs
new file mode 100644
index 0000000000000..b1cf3cccfff7e
--- /dev/null
+++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/binding_pattern.rs
@@ -0,0 +1,67 @@
+#[allow(clippy::wildcard_imports)]
+use oxc_ast::ast::*;
+use oxc_semantic::{Semantic, SymbolId};
+
+use super::{symbol::Symbol, NoUnusedVars};
+
+#[derive(Clone, Copy)]
+pub(super) struct BindingContext<'s, 'a> {
+ pub options: &'s NoUnusedVars,
+ pub semantic: &'s Semantic<'a>,
+}
+impl<'s, 'a> BindingContext<'s, 'a> {
+ #[inline]
+ pub fn symbol(&self, symbol_id: SymbolId) -> Symbol<'s, 'a> {
+ Symbol::new(self.semantic, symbol_id)
+ }
+ #[inline]
+ pub fn has_usages(&self, symbol_id: SymbolId) -> bool {
+ self.symbol(symbol_id).has_usages(self.options)
+ }
+}
+
+pub(super) trait HasAnyUsedBinding<'a> {
+ /// Returns `true` if this node contains a binding that is used or ignored.
+ fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool;
+}
+
+impl<'a> HasAnyUsedBinding<'a> for BindingPattern<'a> {
+ #[inline]
+ fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool {
+ self.kind.has_any_used_binding(ctx)
+ }
+}
+impl<'a> HasAnyUsedBinding<'a> for BindingPatternKind<'a> {
+ fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool {
+ match self {
+ Self::BindingIdentifier(id) => id.has_any_used_binding(ctx),
+ Self::AssignmentPattern(id) => id.left.has_any_used_binding(ctx),
+ Self::ObjectPattern(id) => id.has_any_used_binding(ctx),
+ Self::ArrayPattern(id) => id.has_any_used_binding(ctx),
+ }
+ }
+}
+
+impl<'a> HasAnyUsedBinding<'a> for BindingIdentifier<'a> {
+ fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool {
+ self.symbol_id.get().is_some_and(|symbol_id| ctx.has_usages(symbol_id))
+ }
+}
+impl<'a> HasAnyUsedBinding<'a> for ObjectPattern<'a> {
+ fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool {
+ if ctx.options.ignore_rest_siblings && self.rest.is_some() {
+ return true;
+ }
+ self.properties.iter().any(|p| p.value.has_any_used_binding(ctx))
+ || self.rest.as_ref().map_or(false, |rest| rest.argument.has_any_used_binding(ctx))
+ }
+}
+impl<'a> HasAnyUsedBinding<'a> for ArrayPattern<'a> {
+ fn has_any_used_binding(&self, ctx: BindingContext<'_, 'a>) -> bool {
+ self.elements.iter().flatten().any(|el| {
+ // if the destructured element is ignored, it is considered used
+ el.get_identifier().is_some_and(|name| ctx.options.is_ignored_array_destructured(&name))
+ || el.has_any_used_binding(ctx)
+ }) || self.rest.as_ref().map_or(false, |rest| rest.argument.has_any_used_binding(ctx))
+ }
+}
diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/diagnostic.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/diagnostic.rs
new file mode 100644
index 0000000000000..0a1c0e5b94bfa
--- /dev/null
+++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/diagnostic.rs
@@ -0,0 +1,82 @@
+use oxc_diagnostics::OxcDiagnostic;
+use oxc_semantic::SymbolFlags;
+use oxc_span::Span;
+
+use super::Symbol;
+
+fn pronoun_for_symbol(symbol_flags: SymbolFlags) -> &'static str {
+ if symbol_flags.is_function() {
+ "Function"
+ } else if symbol_flags.is_class() {
+ "Class"
+ } else if symbol_flags.is_interface() {
+ "Interface"
+ } else if symbol_flags.is_type_alias() {
+ "Type alias"
+ } else if symbol_flags.is_enum() {
+ "Enum"
+ } else if symbol_flags.is_enum_member() {
+ "Enum member"
+ } else if symbol_flags.is_type_import() {
+ "Type"
+ } else if symbol_flags.is_import() {
+ "Identifier"
+ } else {
+ "Variable"
+ }
+}
+
+pub fn used_ignored(symbol: &Symbol<'_, '_>) -> OxcDiagnostic {
+ let pronoun = pronoun_for_symbol(symbol.flags());
+ let name = symbol.name();
+
+ OxcDiagnostic::warn(format!("{pronoun} '{name}' is marked as ignored but is used."))
+ .with_label(symbol.span().label(format!("'{name}' is declared here")))
+ .with_help(format!("Consider renaming this {}.", pronoun.to_lowercase()))
+}
+/// Variable 'x' is declared but never used.
+pub fn declared(symbol: &Symbol<'_, '_>) -> OxcDiagnostic {
+ let (verb, help) = if symbol.flags().is_catch_variable() {
+ ("caught", "Consider handling this error.")
+ } else {
+ ("declared", "Consider removing this declaration.")
+ };
+ let pronoun = pronoun_for_symbol(symbol.flags());
+ let name = symbol.name();
+
+ OxcDiagnostic::error(format!("{pronoun} '{name}' is {verb} but never used."))
+ .with_label(symbol.span().label(format!("'{name}' is declared here")))
+ .with_help(help)
+}
+
+/// Variable 'x' is assigned a value but never used.
+pub fn assign(symbol: &Symbol<'_, '_>, assign_span: Span) -> OxcDiagnostic {
+ let pronoun = pronoun_for_symbol(symbol.flags());
+ let name = symbol.name();
+
+ OxcDiagnostic::error(format!("{pronoun} '{name}' is assigned a value but never used."))
+ .with_labels([
+ symbol.span().label(format!("'{name}' is declared here")),
+ assign_span.label("it was last assigned here"),
+ ])
+ .with_help("Did you mean to use this variable?")
+}
+
+/// Parameter 'x' is declared but never used.
+pub fn param(symbol: &Symbol<'_, '_>) -> OxcDiagnostic {
+ let name = symbol.name();
+
+ OxcDiagnostic::error(format!("Parameter '{name}' is declared but never used."))
+ .with_label(symbol.span().label(format!("'{name}' is declared here")))
+ .with_help("Consider removing this parameter.")
+}
+
+/// Identifier 'x' imported but never used.
+pub fn imported(symbol: &Symbol<'_, '_>) -> OxcDiagnostic {
+ let pronoun = pronoun_for_symbol(symbol.flags());
+ let name = symbol.name();
+
+ OxcDiagnostic::error(format!("{pronoun} '{name}' is imported but never used."))
+ .with_label(symbol.span().label(format!("'{name}' is imported here")))
+ .with_help("Consider removing this import.")
+}
diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/ignored.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/ignored.rs
new file mode 100644
index 0000000000000..12623015bd5ba
--- /dev/null
+++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/ignored.rs
@@ -0,0 +1,379 @@
+use super::{NoUnusedVars, Symbol};
+use oxc_ast::{
+ ast::{
+ ArrayAssignmentTarget, AssignmentTarget, AssignmentTargetMaybeDefault,
+ AssignmentTargetProperty, BindingPattern, BindingPatternKind, ClassElement,
+ ObjectAssignmentTarget,
+ },
+ AstKind,
+};
+use regex::Regex;
+
+#[derive(Debug, Default, Clone, Copy)]
+pub(super) enum FoundStatus {
+ /// The target identifier was not found
+ #[default]
+ NotFound,
+ /// The target identifier was found and it meets ignore criteria
+ Ignored,
+ /// The target identifier was found and does not meet ignore criteria
+ NotIgnored,
+}
+
+impl FoundStatus {
+ #[inline]
+ pub const fn is_found(self) -> bool {
+ matches!(self, Self::Ignored | Self::NotIgnored)
+ }
+
+ #[inline]
+ pub const fn is_ignored(self) -> bool {
+ matches!(self, Self::Ignored)
+ }
+
+ #[inline]
+ pub const fn found(is_found: bool) -> Self {
+ if is_found {
+ Self::NotIgnored
+ } else {
+ Self::NotFound
+ }
+ }
+
+ /// Mark a target as ignored if it's found.
+ ///
+ /// `false` does not make already ignored values not-ignored.
+ #[inline]
+ pub fn ignore(self, is_ignored: bool) -> Self {
+ match self {
+ Self::NotIgnored if is_ignored => Self::Ignored,
+ _ => self,
+ }
+ }
+}
+
+impl NoUnusedVars {
+ /// Check if a symbol should be ignored based on how it's declared.
+ ///
+ /// Does not handle ignore checks for re-assignments to array/object destructures.
+ pub(super) fn is_ignored(&self, symbol: &Symbol<'_, '_>) -> bool {
+ let declared_binding = symbol.name();
+ match symbol.declaration().kind() {
+ AstKind::BindingRestElement(_)
+ | AstKind::Function(_)
+ | AstKind::ImportDefaultSpecifier(_)
+ | AstKind::ImportNamespaceSpecifier(_)
+ | AstKind::ImportSpecifier(_)
+ | AstKind::ModuleDeclaration(_)
+ | AstKind::TSEnumDeclaration(_)
+ | AstKind::TSEnumMember(_)
+ | AstKind::TSImportEqualsDeclaration(_)
+ | AstKind::TSInterfaceDeclaration(_)
+ | AstKind::TSModuleDeclaration(_)
+ | AstKind::TSTypeAliasDeclaration(_)
+ | AstKind::TSTypeParameter(_) => self.is_ignored_var(declared_binding),
+ AstKind::Class(class) => {
+ if self.ignore_class_with_static_init_block
+ && class.body.body.iter().any(ClassElement::is_static_block)
+ {
+ return true;
+ }
+ self.is_ignored_var(declared_binding)
+ }
+ AstKind::CatchParameter(catch) => {
+ self.is_ignored_catch_err(declared_binding)
+ || self.is_ignored_binding_pattern(symbol, &catch.pattern)
+ }
+ AstKind::VariableDeclarator(decl) => {
+ self.is_ignored_var(declared_binding)
+ || self.is_ignored_binding_pattern(symbol, &decl.id)
+ }
+ AstKind::FormalParameter(param) => {
+ self.is_ignored_arg(declared_binding)
+ || self.is_ignored_binding_pattern(symbol, ¶m.pattern)
+ }
+ s => {
+ // panic when running test cases so we can find unsupported node kinds
+ debug_assert!(
+ false,
+ "is_ignored_decl did not know how to handle node of kind {}",
+ s.debug_name()
+ );
+ false
+ }
+ }
+ }
+
+ pub(super) fn is_ignored_binding_pattern<'a>(
+ &self,
+ symbol: &Symbol<'_, 'a>,
+ binding: &BindingPattern<'a>,
+ ) -> bool {
+ self.should_search_destructures()
+ && self.search_binding_pattern(symbol, binding).is_ignored()
+ }
+
+ pub(super) fn is_ignored_assignment_target<'a>(
+ &self,
+ symbol: &Symbol<'_, 'a>,
+ assignment: &AssignmentTarget<'a>,
+ ) -> bool {
+ self.should_search_destructures()
+ && self.search_assignment_target(symbol, assignment).is_ignored()
+ }
+
+ /// Do we need to search binding patterns to tell if a symbol is ignored, or
+ /// can we just rely on [`SymbolFlags`] + the symbol's name?
+ ///
+ /// [`SymbolFlags`]: oxc_semantic::SymbolFlags
+ #[inline]
+ pub fn should_search_destructures(&self) -> bool {
+ self.ignore_rest_siblings || self.destructured_array_ignore_pattern.is_some()
+ }
+
+ /// This method does the `ignoreRestNeighbors` and
+ /// `arrayDestructureIgnorePattern` ignore checks for variable declarations.
+ /// Not needed on function/class/interface/etc declarations because those
+ /// will never be destructures.
+ #[must_use]
+ fn search_binding_pattern<'a>(
+ &self,
+ target: &Symbol<'_, 'a>,
+ binding: &BindingPattern<'a>,
+ ) -> FoundStatus {
+ match &binding.kind {
+ // if found, not ignored. Ignoring only happens in destructuring patterns.
+ BindingPatternKind::BindingIdentifier(id) => FoundStatus::found(target == id.as_ref()),
+ BindingPatternKind::AssignmentPattern(id) => {
+ self.search_binding_pattern(target, &id.left)
+ }
+ BindingPatternKind::ObjectPattern(obj) => {
+ for prop in &obj.properties {
+ // check if the prop is a binding identifier (with or
+ // without an assignment) since ignore_rest_siblings does
+ // not apply to spreads that have spreads
+ //
+ // const { x: { y, z }, ...rest } = obj
+ // ^ not ignored by ignore_rest_siblings
+ let status = self.search_binding_pattern(target, &prop.value);
+ match prop.value.get_binding_identifier() {
+ // property is the target we're looking for and is on
+ // this destructure's "level" - ignore it if the config
+ // says to ignore rest neighbors and this destructure
+ // has a ...rest.
+ Some(_) if status.is_found() => {
+ return status
+ .ignore(self.is_ignored_spread_neighbor(obj.rest.is_some()));
+ }
+ // property is an array/object destructure containing
+ // the target, our search is done. However, since it's
+ // not on this destructure's "level", we don't mess
+ // with the ignored status.
+ None if status.is_found() => {
+ return status;
+ }
+ // target not found, keep looking
+ Some(_) | None => {
+ continue;
+ }
+ }
+ }
+
+ // not found in properties, check binding pattern
+ obj.rest.as_ref().map_or(FoundStatus::NotFound, |rest| {
+ self.search_binding_pattern(target, &rest.argument)
+ })
+ }
+ BindingPatternKind::ArrayPattern(arr) => {
+ for el in arr.elements.iter().flatten() {
+ let status = self.search_binding_pattern(target, el);
+ match el.get_binding_identifier() {
+ // el is a simple pattern for the symbol we're looking
+ // for. Check if it is ignored.
+ Some(id) if target == id => {
+ return status.ignore(self.is_ignored_array_destructured(&id.name));
+ }
+ // el is a destructuring pattern containing the target
+ // symbol; our search is done, propegate it upwards
+ None if status.is_found() => {
+ debug_assert!(el.kind.is_destructuring_pattern());
+ return status;
+ }
+ // el is a simple pattern for a different symbol, or is
+ // a destructuring pattern that doesn't contain the target. Keep looking
+ Some(_) | None => {
+ continue;
+ }
+ }
+ }
+
+ FoundStatus::NotFound
+ }
+ }
+ }
+
+ /// Follows the same logic as [`NoUnusedVars::search_binding_pattern`], but
+ /// for assignments instead of declarations
+ fn search_assignment_target<'a>(
+ &self,
+ target: &Symbol<'_, 'a>,
+ assignment: &AssignmentTarget<'a>,
+ ) -> FoundStatus {
+ match assignment {
+ AssignmentTarget::AssignmentTargetIdentifier(id) => {
+ FoundStatus::found(target == id.as_ref())
+ }
+ AssignmentTarget::ObjectAssignmentTarget(obj) => {
+ self.search_obj_assignment_target(target, obj.as_ref())
+ }
+ AssignmentTarget::ArrayAssignmentTarget(arr) => {
+ self.search_array_assignment_target(target, arr.as_ref())
+ }
+ // other assignments are going to be member expressions, identifier
+ // references, or one of those two wrapped in a ts annotation-like
+ // expression. Those will never have destructures and can be safely ignored.
+ _ => FoundStatus::NotFound,
+ }
+ }
+
+ pub(super) fn search_obj_assignment_target<'a>(
+ &self,
+ target: &Symbol<'_, 'a>,
+ obj: &ObjectAssignmentTarget<'a>,
+ ) -> FoundStatus {
+ for prop in &obj.properties {
+ // I'm confused about what's going on here tbh, and I wrote
+ // this function... but I don't really know what a
+ // `AssignmentTargetProperty::AssignmentTargetPropertyProperty`
+ // is, I just know that the name is confusing.
+ let status = match prop {
+ AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(id) => {
+ FoundStatus::found(target == &id.binding)
+ }
+ // recurse down nested destructured assignments
+ AssignmentTargetProperty::AssignmentTargetPropertyProperty(prop) => {
+ self.search_assignment_maybe_default(target, &prop.binding)
+ }
+ };
+
+ // found, mark `status` as ignored if this destructure
+ // contains a ...rest and the user asks for it
+ if status.is_found() {
+ let ignore_because_rest = self.is_ignored_spread_neighbor(obj.rest.is_some());
+ return status.ignore(ignore_because_rest);
+ }
+ }
+
+ // symbol not found in properties, try searching through ...rest
+ obj.rest.as_ref().map_or(FoundStatus::NotFound, |rest| {
+ self.search_assignment_target(target, &rest.target)
+ })
+ }
+
+ pub(super) fn search_array_assignment_target<'a>(
+ &self,
+ target: &Symbol<'_, 'a>,
+ arr: &ArrayAssignmentTarget<'a>,
+ ) -> FoundStatus {
+ // check each element in the array spread assignment
+ for el in arr.elements.iter().flatten() {
+ let status = self.search_assignment_maybe_default(target, el);
+
+ // if we found the target symbol and it's not nested in some
+ // other destructure, mark it as ignored if it matches the
+ // configured array destructure ignore pattern.
+ if status.is_found() {
+ return status.ignore(
+ el.is_simple_assignment_target()
+ && self.is_ignored_array_destructured(target.name()),
+ );
+ }
+ // continue with search
+ }
+
+ FoundStatus::NotFound
+ }
+
+ fn search_assignment_maybe_default<'a>(
+ &self,
+ target: &Symbol<'_, 'a>,
+ assignment: &AssignmentTargetMaybeDefault<'a>,
+ ) -> FoundStatus {
+ assignment.as_assignment_target().map_or(FoundStatus::NotFound, |assignment| {
+ self.search_assignment_target(target, assignment)
+ })
+ }
+
+ #[inline]
+ fn is_ignored_spread_neighbor(&self, has_rest: bool) -> bool {
+ self.ignore_rest_siblings && has_rest
+ }
+
+ // =========================================================================
+ // ========================= NAME/KIND-ONLY CHECKS =========================
+ // =========================================================================
+
+ #[inline]
+ pub(super) fn is_ignored_var(&self, name: &str) -> bool {
+ Self::is_none_or_match(self.vars_ignore_pattern.as_ref(), name)
+ }
+
+ #[inline]
+ pub(super) fn is_ignored_arg(&self, name: &str) -> bool {
+ Self::is_none_or_match(self.args_ignore_pattern.as_ref(), name)
+ }
+
+ #[inline]
+ pub(super) fn is_ignored_array_destructured(&self, name: &str) -> bool {
+ Self::is_none_or_match(self.destructured_array_ignore_pattern.as_ref(), name)
+ }
+
+ #[inline]
+ pub(super) fn is_ignored_catch_err(&self, name: &str) -> bool {
+ *!self.caught_errors
+ || Self::is_none_or_match(self.caught_errors_ignore_pattern.as_ref(), name)
+ }
+
+ #[inline]
+ fn is_none_or_match(re: Option<&Regex>, haystack: &str) -> bool {
+ re.map_or(false, |pat| pat.is_match(haystack))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::rule::Rule as _;
+
+ use super::super::NoUnusedVars;
+ use oxc_span::Atom;
+
+ #[test]
+ fn test_ignored() {
+ let rule = NoUnusedVars::from_configuration(serde_json::json!([
+ {
+ "varsIgnorePattern": "^_",
+ "argsIgnorePattern": "[iI]gnored",
+ "caughtErrorsIgnorePattern": "err.*",
+ "caughtErrors": "all",
+ "destructuredArrayIgnorePattern": "^_",
+ }
+ ]));
+
+ assert!(rule.is_ignored_var("_x"));
+ assert!(rule.is_ignored_var(&Atom::from("_x")));
+ assert!(!rule.is_ignored_var("notIgnored"));
+
+ assert!(rule.is_ignored_arg("ignored"));
+ assert!(rule.is_ignored_arg("alsoIgnored"));
+ assert!(rule.is_ignored_arg(&Atom::from("ignored")));
+ assert!(rule.is_ignored_arg(&Atom::from("alsoIgnored")));
+
+ assert!(rule.is_ignored_catch_err("err"));
+ assert!(rule.is_ignored_catch_err("error"));
+ assert!(!rule.is_ignored_catch_err("e"));
+
+ assert!(rule.is_ignored_array_destructured("_x"));
+ assert!(rule.is_ignored_array_destructured(&Atom::from("_x")));
+ assert!(!rule.is_ignored_array_destructured("notIgnored"));
+ }
+}
diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs
new file mode 100644
index 0000000000000..5c236926a3d1c
--- /dev/null
+++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/mod.rs
@@ -0,0 +1,319 @@
+mod allowed;
+mod binding_pattern;
+mod diagnostic;
+mod ignored;
+mod options;
+mod symbol;
+#[cfg(test)]
+mod tests;
+mod usage;
+
+use std::ops::Deref;
+
+use oxc_ast::AstKind;
+use oxc_macros::declare_oxc_lint;
+use oxc_semantic::{ScopeFlags, SymbolFlags, SymbolId};
+use oxc_span::GetSpan;
+
+use crate::{context::LintContext, rule::Rule};
+use options::NoUnusedVarsOptions;
+
+use symbol::Symbol;
+
+#[derive(Debug, Default, Clone)]
+pub struct NoUnusedVars(Box);
+
+declare_oxc_lint!(
+ /// ### What it does
+ ///
+ /// Disallows variable declarations or imports that are not used in code.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// Variables that are declared and not used anywhere in the code are most
+ /// likely an error due to incomplete refactoring. Such variables take up
+ /// space in the code and can lead to confusion by readers.
+ ///
+ /// A variable `foo` is considered to be used if any of the following are
+ /// true:
+ ///
+ /// * It is called (`foo()`) or constructed (`new foo()`)
+ /// * It is read (`var bar = foo`)
+ /// * It is passed into a function as an argument (`doSomething(foo)`)
+ /// * It is read inside of a function that is passed to another function
+ /// (`doSomething(function() { foo(); })`)
+ ///
+ /// A variable is _not_ considered to be used if it is only ever declared
+ /// (`var foo = 5`) or assigned to (`foo = 7`).
+ ///
+ /// #### Exported
+ ///
+ /// In environments outside of CommonJS or ECMAScript modules, you may use
+ /// `var` to create a global variable that may be used by other scripts. You
+ /// can use the `/* exported variableName */` comment block to indicate that
+ /// this variable is being exported and therefore should not be considered
+ /// unused.
+ ///
+ /// Note that `/* exported */` has no effect for any of the following:
+ /// * when the environment is `node` or `commonjs`
+ /// * when `parserOptions.sourceType` is `module`
+ /// * when `ecmaFeatures.globalReturn` is `true`
+ ///
+ /// The line comment `//exported variableName` will not work as `exported`
+ /// is not line-specific.
+ ///
+ /// ### Example
+ ///
+ /// Examples of **incorrect** code for this rule:
+ ///
+ /// ```javascript
+ /// /*eslint no-unused-vars: "error"*/
+ /// /*global some_unused_var*/
+ ///
+ /// // It checks variables you have defined as global
+ /// some_unused_var = 42;
+ ///
+ /// var x;
+ ///
+ /// // Write-only variables are not considered as used.
+ /// var y = 10;
+ /// y = 5;
+ ///
+ /// // A read for a modification of itself is not considered as used.
+ /// var z = 0;
+ /// z = z + 1;
+ ///
+ /// // By default, unused arguments cause warnings.
+ /// (function(foo) {
+ /// return 5;
+ /// })();
+ ///
+ /// // Unused recursive functions also cause warnings.
+ /// function fact(n) {
+ /// if (n < 2) return 1;
+ /// return n * fact(n - 1);
+ /// }
+ ///
+ /// // When a function definition destructures an array, unused entries from
+ /// // the array also cause warnings.
+ /// function getY([x, y]) {
+ /// return y;
+ /// }
+ /// ```
+ ///
+ /// Examples of **correct** code for this rule:
+ /// ```javascript
+ /// /*eslint no-unused-vars: "error"*/
+ ///
+ /// var x = 10;
+ /// alert(x);
+ ///
+ /// // foo is considered used here
+ /// myFunc(function foo() {
+ /// // ...
+ /// }.bind(this));
+ ///
+ /// (function(foo) {
+ /// return foo;
+ /// })();
+ ///
+ /// var myFunc;
+ /// myFunc = setTimeout(function() {
+ /// // myFunc is considered used
+ /// myFunc();
+ /// }, 50);
+ ///
+ /// // Only the second argument from the destructured array is used.
+ /// function getY([, y]) {
+ /// return y;
+ /// }
+ /// ```
+ ///
+ /// Examples of **correct** code for `/* exported variableName */` operation:
+ /// ```javascript
+ /// /* exported global_var */
+ ///
+ /// var global_var = 42;
+ /// ```
+ NoUnusedVars,
+ correctness
+);
+
+impl Deref for NoUnusedVars {
+ type Target = NoUnusedVarsOptions;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl Rule for NoUnusedVars {
+ fn from_configuration(value: serde_json::Value) -> Self {
+ Self(Box::new(NoUnusedVarsOptions::from(value)))
+ }
+
+ fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) {
+ let symbol = Symbol::new(ctx.semantic().as_ref(), symbol_id);
+ if Self::should_skip_symbol(&symbol) {
+ return;
+ }
+
+ self.run_on_symbol_internal(&symbol, ctx);
+ }
+
+ fn should_run(&self, ctx: &LintContext) -> bool {
+ // ignore .d.ts and vue files.
+ // 1. declarations have side effects (they get merged together)
+ // 2. vue scripts declare variables that get used in the template, which
+ // we can't detect
+ !ctx.source_type().is_typescript_definition()
+ && !ctx.file_path().extension().is_some_and(|ext| ext == "vue")
+ }
+}
+
+impl NoUnusedVars {
+ fn run_on_symbol_internal<'a>(&self, symbol: &Symbol<'_, 'a>, ctx: &LintContext<'a>) {
+ let is_ignored = self.is_ignored(symbol);
+
+ if is_ignored && !self.report_used_ignore_pattern {
+ return;
+ }
+
+ // Order matters. We want to call cheap/high "yield" functions first.
+ let is_exported = symbol.is_exported();
+ let is_used = is_exported || symbol.has_usages(self);
+
+ match (is_used, is_ignored) {
+ (true, true) => {
+ if self.report_used_ignore_pattern {
+ ctx.diagnostic(diagnostic::used_ignored(symbol));
+ }
+ return;
+ },
+ // not used but ignored, no violation
+ (false, true)
+ // used and not ignored, no violation
+ | (true, false) => {
+ return
+ },
+ // needs acceptance check and/or reporting
+ (false, false) => {}
+ }
+
+ let declaration = symbol.declaration();
+ match declaration.kind() {
+ // NOTE: match_module_declaration(AstKind) does not work here
+ AstKind::ImportDeclaration(_)
+ | AstKind::ImportSpecifier(_)
+ | AstKind::ImportExpression(_)
+ | AstKind::ImportDefaultSpecifier(_)
+ | AstKind::ImportNamespaceSpecifier(_) => {
+ if !is_ignored {
+ ctx.diagnostic(diagnostic::imported(symbol));
+ }
+ }
+ AstKind::VariableDeclarator(decl) => {
+ if self.is_allowed_variable_declaration(symbol, decl) {
+ return;
+ };
+ let report =
+ if let Some(last_write) = symbol.references().rev().find(|r| r.is_write()) {
+ // ahg
+ let span = ctx.nodes().get_node(last_write.node_id()).kind().span();
+ diagnostic::assign(symbol, span)
+ } else {
+ diagnostic::declared(symbol)
+ };
+ ctx.diagnostic(report);
+ }
+ AstKind::FormalParameter(param) => {
+ if self.is_allowed_argument(ctx.semantic().as_ref(), symbol, param) {
+ return;
+ }
+ ctx.diagnostic(diagnostic::param(symbol));
+ }
+ AstKind::Class(_) | AstKind::Function(_) => {
+ if self.is_allowed_class_or_function(symbol) {
+ return;
+ }
+ ctx.diagnostic(diagnostic::declared(symbol));
+ }
+ AstKind::TSModuleDeclaration(namespace) => {
+ if self.is_allowed_ts_namespace(symbol, namespace) {
+ return;
+ }
+ ctx.diagnostic(diagnostic::declared(symbol));
+ }
+ AstKind::TSInterfaceDeclaration(_) => {
+ if symbol.is_in_declared_module() {
+ return;
+ }
+ ctx.diagnostic(diagnostic::declared(symbol));
+ }
+ AstKind::TSTypeParameter(_) => {
+ if self.is_allowed_type_parameter(symbol, declaration.id()) {
+ return;
+ }
+ ctx.diagnostic(diagnostic::declared(symbol));
+ }
+ _ => ctx.diagnostic(diagnostic::declared(symbol)),
+ };
+ }
+
+ fn should_skip_symbol(symbol: &Symbol<'_, '_>) -> bool {
+ const AMBIENT_NAMESPACE_FLAGS: SymbolFlags =
+ SymbolFlags::NameSpaceModule.union(SymbolFlags::Ambient);
+ let flags = symbol.flags();
+
+ // 1. ignore enum members. Only enums get checked
+ // 2. ignore all ambient TS declarations, e.g. `declare class Foo {}`
+ if flags.intersects(SymbolFlags::EnumMember.union(SymbolFlags::Ambient))
+ // ambient namespaces
+ || flags == AMBIENT_NAMESPACE_FLAGS
+ || (symbol.is_in_ts() && symbol.is_in_declare_global())
+ {
+ return true;
+ }
+
+ // In some cases (e.g. "jsx": "react" in tsconfig.json), React imports
+ // get used in generated code. We don't have a way to detect
+ // "jsxPragmas" or whether TSX files are using "jsx": "react-jsx", so we
+ // just allow all cases.
+ if symbol.flags().contains(SymbolFlags::Import)
+ && symbol.is_in_jsx()
+ && symbol.is_possibly_jsx_factory()
+ {
+ return true;
+ }
+
+ false
+ }
+}
+
+impl Symbol<'_, '_> {
+ #[inline]
+ fn is_possibly_jsx_factory(&self) -> bool {
+ let name = self.name();
+ name == "React" || name == "h"
+ }
+
+ fn is_in_declare_global(&self) -> bool {
+ self.scopes()
+ .ancestors(self.scope_id())
+ .filter(|scope_id| {
+ let flags = self.scopes().get_flags(*scope_id);
+ flags.contains(ScopeFlags::TsModuleBlock)
+ })
+ .any(|ambient_module_scope_id| {
+ let AstKind::TSModuleDeclaration(module) = self
+ .nodes()
+ .get_node(self.scopes().get_node_id(ambient_module_scope_id))
+ .kind()
+ else {
+ return false;
+ };
+
+ module.kind.is_global()
+ })
+ }
+}
diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/options.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/options.rs
new file mode 100644
index 0000000000000..8f666efc4a48e
--- /dev/null
+++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/options.rs
@@ -0,0 +1,549 @@
+use std::{borrow::Cow, ops::Deref};
+
+use oxc_diagnostics::OxcDiagnostic;
+use regex::Regex;
+use serde_json::Value;
+
+/// See [ESLint - no-unused-vars config schema](https://github.com/eslint/eslint/blob/53b1ff047948e36682fade502c949f4e371e53cd/lib/rules/no-unused-vars.js#L61)
+#[derive(Debug, Default, Clone)]
+#[must_use]
+#[non_exhaustive]
+pub struct NoUnusedVarsOptions {
+ /// Controls how usage of a variable in the global scope is checked.
+ ///
+ /// This option has two settings:
+ /// 1. `all` checks all variables for usage, including those in the global
+ /// scope. This is the default setting.
+ /// 2. `local` checks only that locally-declared variables are used but will
+ /// allow global variables to be unused.
+ pub vars: VarsOption,
+
+ /// Specifies exceptions to this rule for unused variables. Variables whose
+ /// names match this pattern will be ignored.
+ ///
+ /// ## Example
+ ///
+ /// Examples of **correct** code for this option when the pattern is `^_`:
+ /// ```javascript
+ /// var _a = 10;
+ /// var b = 10;
+ /// console.log(b);
+ /// ```
+ pub vars_ignore_pattern: Option,
+
+ /// Controls how unused arguments are checked.
+ ///
+ /// This option has three settings:
+ /// 1. `after-used` - Unused positional arguments that occur before the last
+ /// used argument will not be checked, but all named arguments and all
+ /// positional arguments after the last used argument will be checked.
+ /// 2. `all` - All named arguments must be used.
+ /// 3. `none` - Do not check arguments.
+ pub args: ArgsOption,
+
+ /// Specifies exceptions to this rule for unused arguments. Arguments whose
+ /// names match this pattern will be ignored.
+ ///
+ /// ## Example
+ ///
+ /// Examples of **correct** code for this option when the pattern is `^_`:
+ ///
+ /// ```javascript
+ /// function foo(_a, b) {
+ /// console.log(b);
+ /// }
+ /// foo(1, 2);
+ /// ```
+ pub args_ignore_pattern: Option,
+
+ /// Using a Rest property it is possible to "omit" properties from an
+ /// object, but by default the sibling properties are marked as "unused".
+ /// With this option enabled the rest property's siblings are ignored.
+ ///
+ /// ## Example
+ /// Examples of **correct** code when this option is set to `true`:
+ /// ```js
+ /// // 'foo' and 'bar' were ignored because they have a rest property sibling.
+ /// var { foo, ...coords } = data;
+ ///
+ /// var bar;
+ /// ({ bar, ...coords } = data);
+ /// ```
+ pub ignore_rest_siblings: bool,
+
+ /// Used for `catch` block validation.
+ /// It has two settings:
+ /// * `none` - do not check error objects. This is the default setting
+ /// * `all` - all named arguments must be used`
+ ///
+ #[doc(hidden)]
+ /// `none` corresponds to `false`, while `all` corresponds to `true`.
+ pub caught_errors: CaughtErrors,
+
+ /// Specifies exceptions to this rule for errors caught within a `catch` block.
+ /// Variables declared within a `catch` block whose names match this pattern
+ /// will be ignored.
+ ///
+ /// ## Example
+ ///
+ /// Examples of **correct** code when the pattern is `^ignore`:
+ ///
+ /// ```javascript
+ /// try {
+ /// // ...
+ /// } catch (ignoreErr) {
+ /// console.error("Error caught in catch block");
+ /// }
+ /// ```
+ pub caught_errors_ignore_pattern: Option,
+
+ /// This option specifies exceptions within destructuring patterns that will
+ /// not be checked for usage. Variables declared within array destructuring
+ /// whose names match this pattern will be ignored.
+ ///
+ /// ## Example
+ ///
+ /// Examples of **correct** code for this option, when the pattern is `^_`:
+ /// ```javascript
+ /// const [a, _b, c] = ["a", "b", "c"];
+ /// console.log(a + c);
+ ///
+ /// const { x: [_a, foo] } = bar;
+ /// console.log(foo);
+ ///
+ /// let _m, n;
+ /// foo.forEach(item => {
+ /// [_m, n] = item;
+ /// console.log(n);
+ /// });
+ /// ```
+ pub destructured_array_ignore_pattern: Option,
+
+ /// The `ignoreClassWithStaticInitBlock` option is a boolean (default:
+ /// `false`). Static initialization blocks allow you to initialize static
+ /// variables and execute code during the evaluation of a class definition,
+ /// meaning the static block code is executed without creating a new
+ /// instance of the class. When set to true, this option ignores classes
+ /// containing static initialization blocks.
+ ///
+ /// ## Example
+ ///
+ /// Examples of **incorrect** code for the `{ "ignoreClassWithStaticInitBlock": true }` option
+ ///
+ /// ```javascript
+ /// /*eslint no-unused-vars: ["error", { "ignoreClassWithStaticInitBlock": true }]*/
+ ///
+ /// class Foo {
+ /// static myProperty = "some string";
+ /// static mymethod() {
+ /// return "some string";
+ /// }
+ /// }
+ ///
+ /// class Bar {
+ /// static {
+ /// let baz; // unused variable
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Examples of **correct** code for the `{ "ignoreClassWithStaticInitBlock": true }` option
+ ///
+ /// ```javascript
+ /// /*eslint no-unused-vars: ["error", { "ignoreClassWithStaticInitBlock": true }]*/
+ ///
+ /// class Foo {
+ /// static {
+ /// let bar = "some string";
+ ///
+ /// console.log(bar);
+ /// }
+ /// }
+ /// ```
+ pub ignore_class_with_static_init_block: bool,
+
+ /// The `reportUsedIgnorePattern` option is a boolean (default: `false`).
+ /// Using this option will report variables that match any of the valid
+ /// ignore pattern options (`varsIgnorePattern`, `argsIgnorePattern`,
+ /// `caughtErrorsIgnorePattern`, or `destructuredArrayIgnorePattern`) if
+ /// they have been used.
+ ///
+ /// ## Example
+ ///
+ /// Examples of **incorrect** code for the `{ "reportUsedIgnorePattern": true }` option:
+ ///
+ /// ```javascript
+ /// /*eslint no-unused-vars: ["error", { "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]*/
+ ///
+ /// var firstVarIgnored = 1;
+ /// var secondVar = 2;
+ /// console.log(firstVarIgnored, secondVar);
+ /// ```
+ ///
+ /// Examples of **correct** code for the `{ "reportUsedIgnorePattern": true }` option:
+ ///
+ /// ```javascript
+ /// /*eslint no-unused-vars: ["error", { "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]*/
+ ///
+ /// var firstVar = 1;
+ /// var secondVar = 2;
+ /// console.log(firstVar, secondVar);
+ /// ```
+ pub report_used_ignore_pattern: bool,
+}
+
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub enum VarsOption {
+ /// All variables are checked for usage, including those in the global scope.
+ #[default]
+ All,
+ /// Checks only that locally-declared variables are used but will allow
+ /// global variables to be unused.
+ Local,
+}
+impl VarsOption {
+ pub const fn is_local(&self) -> bool {
+ matches!(self, Self::Local)
+ }
+}
+
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub enum ArgsOption {
+ /// Unused positional arguments that occur before the last used argument
+ /// will not be checked, but all named arguments and all positional
+ /// arguments after the last used argument will be checked.
+ #[default]
+ AfterUsed,
+ /// All named arguments must be used
+ All,
+ /// Do not check arguments
+ None,
+}
+impl ArgsOption {
+ #[inline]
+ pub const fn is_after_used(&self) -> bool {
+ matches!(self, Self::AfterUsed)
+ }
+ #[inline]
+ pub const fn is_all(&self) -> bool {
+ matches!(self, Self::All)
+ }
+ #[inline]
+ pub const fn is_none(&self) -> bool {
+ matches!(self, Self::None)
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(transparent)]
+pub struct CaughtErrors(bool);
+
+impl Default for CaughtErrors {
+ fn default() -> Self {
+ Self::all()
+ }
+}
+
+impl CaughtErrors {
+ pub const fn all() -> Self {
+ Self(true)
+ }
+ pub const fn none() -> Self {
+ Self(false)
+ }
+}
+
+impl Deref for CaughtErrors {
+ type Target = bool;
+
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+impl std::ops::Not for CaughtErrors {
+ type Output = Self;
+
+ #[inline]
+ fn not(self) -> Self::Output {
+ Self(!self.0)
+ }
+}
+
+fn invalid_option_mismatch_error(option_name: &str, expected: E, actual: A) -> OxcDiagnostic
+where
+ E: IntoIterator- ,
+ A: AsRef,
+{
+ let expected = expected.into_iter();
+ let initial_capacity = expected.size_hint().0 * 8;
+ let expected =
+ expected.fold(String::with_capacity(initial_capacity), |acc, s| acc + " or " + s);
+ let actual = actual.as_ref();
+
+ invalid_option_error(option_name, format!("Expected {expected}, got {actual}"))
+}
+
+fn invalid_option_error>>(
+ option_name: &str,
+ message: M,
+) -> OxcDiagnostic {
+ let message = message.into();
+ OxcDiagnostic::error(format!("Invalid '{option_name}' option for no-unused-vars: {message}"))
+}
+
+impl TryFrom<&String> for VarsOption {
+ type Error = OxcDiagnostic;
+
+ fn try_from(value: &String) -> Result {
+ match value.as_str() {
+ "all" => Ok(Self::All),
+ "local" => Ok(Self::Local),
+ v => Err(invalid_option_mismatch_error("vars", ["all", "local"], v)),
+ }
+ }
+}
+
+impl TryFrom<&Value> for VarsOption {
+ type Error = OxcDiagnostic;
+
+ fn try_from(value: &Value) -> Result {
+ match value {
+ Value::String(s) => Self::try_from(s),
+ _ => Err(invalid_option_error("vars", format!("Expected a string, got {value}"))),
+ }
+ }
+}
+
+impl TryFrom<&Value> for ArgsOption {
+ type Error = OxcDiagnostic;
+
+ fn try_from(value: &Value) -> Result {
+ match value {
+ Value::String(s) => match s.as_str() {
+ "after-used" => Ok(Self::AfterUsed),
+ "all" => Ok(Self::All),
+ "none" => Ok(Self::None),
+ s => Err(invalid_option_mismatch_error("args", ["after-used", "all", "none"], s)),
+ },
+ v => Err(invalid_option_error("args", format!("Expected a string, got {v}"))),
+ }
+ }
+}
+
+impl TryFrom<&String> for CaughtErrors {
+ type Error = OxcDiagnostic;
+
+ fn try_from(value: &String) -> Result {
+ match value.as_str() {
+ "all" => Ok(Self(true)),
+ "none" => Ok(Self(false)),
+ v => Err(invalid_option_mismatch_error("caughtErrors", ["all", "none"], v)),
+ }
+ }
+}
+
+impl From for CaughtErrors {
+ fn from(value: bool) -> Self {
+ Self(value)
+ }
+}
+impl TryFrom<&Value> for CaughtErrors {
+ type Error = OxcDiagnostic;
+
+ fn try_from(value: &Value) -> Result {
+ match value {
+ Value::String(s) => Self::try_from(s),
+ Value::Bool(b) => Ok(Self(*b)),
+ v => Err(invalid_option_error("caughtErrors", format!("Expected a string, got {v}"))),
+ }
+ }
+}
+
+/// Parses a potential pattern into a [`Regex`] that accepts unicode characters.
+fn parse_unicode_rule(value: Option<&Value>, name: &str) -> Option {
+ value
+ .and_then(Value::as_str)
+ .map(|pattern| regex::RegexBuilder::new(pattern).unicode(true).build())
+ .transpose()
+ .map_err(|err| panic!("Invalid '{name}' option for no-unused-vars: {err}"))
+ .unwrap()
+}
+impl From for NoUnusedVarsOptions {
+ fn from(value: Value) -> Self {
+ let Some(config) = value.get(0) else { return Self::default() };
+ match config {
+ Value::String(vars) => {
+ let vars: VarsOption = vars
+ .try_into()
+ .unwrap();
+ Self { vars, ..Default::default() }
+ }
+ Value::Object(config) => {
+ let vars = config
+ .get("vars")
+ .map(|vars| {
+ let vars: VarsOption = vars
+ .try_into()
+ .unwrap();
+ vars
+ })
+ .unwrap_or_default();
+
+ let vars_ignore_pattern: Option =
+ parse_unicode_rule(config.get("varsIgnorePattern"), "varsIgnorePattern");
+
+ let args: ArgsOption = config
+ .get("args")
+ .map(|args| {
+ let args: ArgsOption = args
+ .try_into()
+ .unwrap();
+ args
+ })
+ .unwrap_or_default();
+
+ let args_ignore_pattern: Option =
+ parse_unicode_rule(config.get("argsIgnorePattern"), "argsIgnorePattern");
+
+ let caught_errors: CaughtErrors = config
+ .get("caughtErrors")
+ .map(|caught_errors| {
+ let caught_errors: CaughtErrors = caught_errors
+ .try_into()
+ .unwrap();
+ caught_errors
+ })
+ .unwrap_or_default();
+
+ let caught_errors_ignore_pattern = parse_unicode_rule(
+ config.get("caughtErrorsIgnorePattern"),
+ "caughtErrorsIgnorePattern",
+ );
+
+ let destructured_array_ignore_pattern: Option = parse_unicode_rule(
+ config.get("destructuredArrayIgnorePattern"),
+ "destructuredArrayIgnorePattern",
+ );
+
+ let ignore_rest_siblings: bool = config
+ .get("ignoreRestSiblings")
+ .map_or(Some(false), Value::as_bool)
+ .unwrap_or(false);
+
+ let ignore_class_with_static_init_block: bool = config
+ .get("ignoreClassWithStaticInitBlock")
+ .map_or(Some(false), Value::as_bool)
+ .unwrap_or(false);
+
+ let report_used_ignore_pattern: bool = config
+ .get("reportUsedIgnorePattern")
+ .map_or(Some(false), Value::as_bool)
+ .unwrap_or(false);
+
+ Self {
+ vars,
+ vars_ignore_pattern,
+ args,
+ args_ignore_pattern,
+ ignore_rest_siblings,
+ caught_errors,
+ caught_errors_ignore_pattern,
+ destructured_array_ignore_pattern,
+ ignore_class_with_static_init_block,
+ report_used_ignore_pattern
+ }
+ }
+ Value::Null => Self::default(),
+ _ => panic!(
+ "Invalid 'vars' option for no-unused-vars: Expected a string or an object, got {config}"
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use serde_json::json;
+
+ use super::*;
+
+ #[test]
+ fn test_options_default() {
+ let rule = NoUnusedVarsOptions::default();
+ assert_eq!(rule.vars, VarsOption::All);
+ assert!(rule.vars_ignore_pattern.is_none());
+ assert_eq!(rule.args, ArgsOption::AfterUsed);
+ assert!(rule.args_ignore_pattern.is_none());
+ assert_eq!(rule.caught_errors, CaughtErrors::all());
+ assert!(rule.caught_errors_ignore_pattern.is_none());
+ assert!(rule.destructured_array_ignore_pattern.is_none());
+ assert!(!rule.ignore_rest_siblings);
+ assert!(!rule.ignore_class_with_static_init_block);
+ assert!(!rule.report_used_ignore_pattern);
+ }
+
+ #[test]
+ fn test_options_from_string() {
+ let rule: NoUnusedVarsOptions = json!(["all"]).into();
+ assert_eq!(rule.vars, VarsOption::All);
+
+ let rule: NoUnusedVarsOptions = json!(["local"]).into();
+ assert_eq!(rule.vars, VarsOption::Local);
+ }
+
+ #[test]
+ fn test_options_from_object() {
+ let rule: NoUnusedVarsOptions = json!([
+ {
+ "vars": "local",
+ "varsIgnorePattern": "^_",
+ "args": "all",
+ "argsIgnorePattern": "^_",
+ "caughtErrors": "all",
+ "caughtErrorsIgnorePattern": "^_",
+ "destructuredArrayIgnorePattern": "^_",
+ "ignoreRestSiblings": true,
+ "reportUsedIgnorePattern": true
+ }
+ ])
+ .into();
+
+ assert_eq!(rule.vars, VarsOption::Local);
+ assert_eq!(rule.vars_ignore_pattern.unwrap().as_str(), "^_");
+ assert_eq!(rule.args, ArgsOption::All);
+ assert_eq!(rule.args_ignore_pattern.unwrap().as_str(), "^_");
+ assert_eq!(rule.caught_errors, CaughtErrors::all());
+ assert_eq!(rule.caught_errors_ignore_pattern.unwrap().as_str(), "^_");
+ assert_eq!(rule.destructured_array_ignore_pattern.unwrap().as_str(), "^_");
+ assert!(rule.ignore_rest_siblings);
+ assert!(!rule.ignore_class_with_static_init_block);
+ assert!(rule.report_used_ignore_pattern);
+ }
+
+ #[test]
+ fn test_options_from_null() {
+ let opts = NoUnusedVarsOptions::from(json!(null));
+ let default = NoUnusedVarsOptions::default();
+ assert_eq!(opts.vars, default.vars);
+ assert!(opts.vars_ignore_pattern.is_none());
+ assert!(default.vars_ignore_pattern.is_none());
+
+ assert_eq!(opts.args, default.args);
+ assert!(opts.args_ignore_pattern.is_none());
+ assert!(default.args_ignore_pattern.is_none());
+
+ assert_eq!(opts.caught_errors, default.caught_errors);
+ assert!(opts.caught_errors_ignore_pattern.is_none());
+ assert!(default.caught_errors_ignore_pattern.is_none());
+
+ assert_eq!(opts.ignore_rest_siblings, default.ignore_rest_siblings);
+ }
+
+ #[test]
+ fn test_parse_unicode_regex() {
+ let pat = json!("^_");
+ parse_unicode_rule(Some(&pat), "varsIgnorePattern")
+ .expect("json strings should get parsed into a regex");
+ }
+}
diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/symbol.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/symbol.rs
new file mode 100644
index 0000000000000..b61b93fe04ccb
--- /dev/null
+++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/symbol.rs
@@ -0,0 +1,221 @@
+use std::fmt;
+
+use oxc_ast::{
+ ast::{AssignmentTarget, BindingIdentifier, BindingPattern, IdentifierReference},
+ AstKind,
+};
+use oxc_semantic::{
+ AstNode, AstNodeId, AstNodes, Reference, ScopeId, ScopeTree, Semantic, SymbolFlags, SymbolId,
+ SymbolTable,
+};
+use oxc_span::Span;
+
+#[derive(Clone)]
+pub(super) struct Symbol<'s, 'a> {
+ semantic: &'s Semantic<'a>,
+ id: SymbolId,
+ flags: SymbolFlags,
+}
+
+impl PartialEq for Symbol<'_, '_> {
+ fn eq(&self, other: &Self) -> bool {
+ self.id == other.id
+ }
+}
+
+// constructor and simple getters
+impl<'s, 'a> Symbol<'s, 'a> {
+ pub fn new(semantic: &'s Semantic<'a>, symbol_id: SymbolId) -> Self {
+ let flags = semantic.symbols().get_flag(symbol_id);
+ Self { semantic, id: symbol_id, flags }
+ }
+
+ #[inline]
+ pub fn id(&self) -> SymbolId {
+ self.id
+ }
+
+ #[inline]
+ pub fn name(&self) -> &str {
+ self.symbols().get_name(self.id)
+ }
+
+ /// [`Span`] for the node declaring the [`Symbol`].
+ #[inline]
+ pub fn span(&self) -> Span {
+ self.symbols().get_span(self.id)
+ }
+
+ #[inline]
+ pub const fn flags(&self) -> SymbolFlags {
+ self.flags
+ }
+
+ #[inline]
+ pub fn scope_id(&self) -> ScopeId {
+ self.symbols().get_scope_id(self.id)
+ }
+
+ #[inline]
+ pub fn declaration(&self) -> &AstNode<'a> {
+ self.nodes().get_node(self.declaration_id())
+ }
+
+ #[inline]
+ pub fn references(&self) -> impl DoubleEndedIterator
- + '_ {
+ self.symbols().get_resolved_references(self.id)
+ }
+
+ /// Is this [`Symbol`] declared in the root scope?
+ pub fn is_root(&self) -> bool {
+ self.symbols().get_scope_id(self.id) == self.scopes().root_scope_id()
+ }
+
+ #[inline]
+ fn declaration_id(&self) -> AstNodeId {
+ self.symbols().get_declaration(self.id)
+ }
+
+ #[inline]
+ pub fn nodes(&self) -> &AstNodes<'a> {
+ self.semantic.nodes()
+ }
+
+ #[inline]
+ pub fn scopes(&self) -> &ScopeTree {
+ self.semantic.scopes()
+ }
+
+ #[inline]
+ pub fn symbols(&self) -> &SymbolTable {
+ self.semantic.symbols()
+ }
+
+ pub fn iter_parents(&self) -> impl Iterator
- > + '_ {
+ self.nodes().iter_parents(self.declaration_id()).skip(1)
+ }
+
+ pub fn iter_relevant_parents(
+ &self,
+ node_id: AstNodeId,
+ ) -> impl Iterator
- > + Clone + '_ {
+ self.nodes().iter_parents(node_id).skip(1).filter(|n| Self::is_relevant_kind(n.kind()))
+ }
+
+ pub fn iter_relevant_parent_and_grandparent_kinds(
+ &self,
+ node_id: AstNodeId,
+ ) -> impl Iterator
- , /* grandparent */ AstKind<'a>)> + Clone + '_
+ {
+ let parents_iter = self
+ .nodes()
+ .iter_parents(node_id)
+ .map(AstNode::kind)
+ // no skip
+ .filter(|kind| Self::is_relevant_kind(*kind));
+
+ let grandparents_iter = parents_iter.clone().skip(1);
+
+ parents_iter.zip(grandparents_iter)
+ }
+
+ #[inline]
+ const fn is_relevant_kind(kind: AstKind<'a>) -> bool {
+ !matches!(kind, AstKind::ParenthesizedExpression(_))
+ }
+}
+
+impl<'s, 'a> Symbol<'s, 'a> {
+ /// Is this [`Symbol`] exported?
+ ///
+ /// NOTE: does not support CJS right now.
+ pub fn is_exported(&self) -> bool {
+ let is_in_exportable_scope = self.is_root() || self.is_in_ts_namespace();
+ (is_in_exportable_scope
+ && (self.flags.contains(SymbolFlags::Export)
+ || self.semantic.module_record().exported_bindings.contains_key(self.name())))
+ || self.in_export_node()
+ }
+
+ #[inline]
+ fn is_in_ts_namespace(&self) -> bool {
+ self.scopes().get_flags(self.scope_id()).is_ts_module_block()
+ }
+
+ /// We need to do this due to limitations of [`Semantic`].
+ fn in_export_node(&self) -> bool {
+ for parent in self.nodes().iter_parents(self.declaration_id()).skip(1) {
+ match parent.kind() {
+ AstKind::ModuleDeclaration(module) => {
+ return module.is_export();
+ }
+ AstKind::VariableDeclaration(_) => {
+ continue;
+ }
+ _ => {
+ return false;
+ }
+ }
+ }
+ false
+ }
+
+ #[inline]
+ pub fn is_in_jsx(&self) -> bool {
+ self.semantic.source_type().is_jsx()
+ }
+
+ #[inline]
+ pub fn is_in_ts(&self) -> bool {
+ self.semantic.source_type().is_typescript()
+ }
+
+ #[inline]
+ pub fn get_snippet(&self, span: Span) -> &'a str {
+ span.source_text(self.semantic.source_text())
+ }
+}
+
+impl<'a> PartialEq> for Symbol<'_, 'a> {
+ fn eq(&self, other: &IdentifierReference<'a>) -> bool {
+ // cheap: no resolved reference means its a global reference
+ let Some(reference_id) = other.reference_id.get() else {
+ return false;
+ };
+ let reference = self.symbols().get_reference(reference_id);
+ reference.symbol_id().is_some_and(|symbol_id| self.id == symbol_id)
+ }
+}
+
+impl<'a> PartialEq> for Symbol<'_, 'a> {
+ fn eq(&self, id: &BindingIdentifier<'a>) -> bool {
+ id.symbol_id.get().is_some_and(|id| self.id == id)
+ }
+}
+
+impl<'a> PartialEq> for Symbol<'_, 'a> {
+ fn eq(&self, id: &BindingPattern<'a>) -> bool {
+ id.get_binding_identifier().is_some_and(|id| self == id)
+ }
+}
+
+impl<'a> PartialEq> for Symbol<'_, 'a> {
+ fn eq(&self, target: &AssignmentTarget<'a>) -> bool {
+ match target {
+ AssignmentTarget::AssignmentTargetIdentifier(id) => self == id.as_ref(),
+ _ => false,
+ }
+ }
+}
+
+impl fmt::Debug for Symbol<'_, '_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Symbol")
+ .field("id", &self.id)
+ .field("name", &self.name())
+ .field("flags", &self.flags)
+ .field("declaration_node", &self.declaration().kind().debug_name())
+ .field("references", &self.references().collect::>())
+ .finish()
+ }
+}
diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/eslint.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/eslint.rs
new file mode 100644
index 0000000000000..34c4856e68268
--- /dev/null
+++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/eslint.rs
@@ -0,0 +1,827 @@
+//! Test cases from vanilla eslint
+
+use crate::{tester::Tester, RuleMeta as _};
+
+use super::NoUnusedVars;
+
+/// These are tests from ESLint that are not passing. If you make a change that
+/// causes this test to fail, that's a good thing!
+///
+/// 1. Delete the offending test case from this function
+/// 2. Find where it is commented out in [`test`] and un-comment it. Cases that are in `pass` in
+/// [`fixme`] will be in [`fail`] in [`test`]
+/// 3. Add it to your PR :)
+#[test]
+fn fixme() {
+ let pass = vec![
+ // ESLint says this one should pass, but I disagree. foox could be
+ // safely removed here.
+ ("function foo(cb) { cb = function(a) { return cb(1 + a); }(); } foo();", None),
+ ("function foo(cb) { cb = (0, function(a) { cb(1 + a); }); } foo();", None),
+ (
+ "let x = [];
+ x = x.concat(x);",
+ None,
+ ), // { "ecmaVersion": 2015 },
+ ];
+ let fail = vec![];
+ Tester::new(NoUnusedVars::NAME, pass, fail).test();
+}
+#[test]
+fn test() {
+ let pass = vec![
+ ("var foo = 5;
+
+ label: while (true) {
+ console.log(foo);
+ break label;
+ }", None),
+("var foo = 5;
+
+ while (true) {
+ console.log(foo);
+ break;
+ }", None),
+("for (let prop in box) {
+ box[prop] = parseInt(box[prop]);
+ }", None), // { "ecmaVersion": 6 },
+("var box = {a: 2};
+ for (var prop in box) {
+ box[prop] = parseInt(box[prop]);
+ }", None),
+("f({ set foo(a) { return; } });", None),
+("a; var a;", Some(serde_json::json!(["all"]))),
+("var a=10; alert(a);", Some(serde_json::json!(["all"]))),
+("var a=10; (function() { alert(a); })();", Some(serde_json::json!(["all"]))),
+("var a=10; (function() { setTimeout(function() { alert(a); }, 0); })();", Some(serde_json::json!(["all"]))),
+("var a=10; d[a] = 0;", Some(serde_json::json!(["all"]))),
+("(function() { var a=10; return a; })();", Some(serde_json::json!(["all"]))),
+("(function g() {})()", Some(serde_json::json!(["all"]))),
+("function f(a) {alert(a);}; f();", Some(serde_json::json!(["all"]))),
+("var c = 0; function f(a){ var b = a; return b; }; f(c);", Some(serde_json::json!(["all"]))),
+("function a(x, y){ return y; }; a();", Some(serde_json::json!(["all"]))),
+("var arr1 = [1, 2]; var arr2 = [3, 4]; for (var i in arr1) { arr1[i] = 5; } for (var i in arr2) { arr2[i] = 10; }", Some(serde_json::json!(["all"]))),
+("var a=10;", Some(serde_json::json!(["local"]))),
+(r#"var min = "min"; Math[min];"#, Some(serde_json::json!(["all"]))),
+("Foo.bar = function(baz) { return baz; };", Some(serde_json::json!(["all"]))),
+("myFunc(function foo() {}.bind(this))", None),
+("myFunc(function foo(){}.toString())", None),
+("function foo(first, second) {
+ doStuff(function() {
+ console.log(second);});}; foo()", None),
+("(function() { var doSomething = function doSomething() {}; doSomething() }())", None),
+// ("/*global a */ a;", None),
+("var a=10; (function() { alert(a); })();", Some(serde_json::json!([{ "vars": "all" }]))),
+("function g(bar, baz) { return baz; }; g();", Some(serde_json::json!([{ "vars": "all" }]))),
+("function g(bar, baz) { return baz; }; g();", Some(serde_json::json!([{ "vars": "all", "args": "after-used" }]))),
+("function g(bar, baz) { return bar; }; g();", Some(serde_json::json!([{ "vars": "all", "args": "none" }]))),
+("function g(bar, baz) { return 2; }; g();", Some(serde_json::json!([{ "vars": "all", "args": "none" }]))),
+("function g(bar, baz) { return bar + baz; }; g();", Some(serde_json::json!([{ "vars": "local", "args": "all" }]))),
+("var g = function(bar, baz) { return 2; }; g();", Some(serde_json::json!([{ "vars": "all", "args": "none" }]))),
+("(function z() { z(); })();", None),
+(" ", None), // { "globals": { "a": true } },
+(r#"var who = "Paul";
+ module.exports = `Hello ${who}!`;"#, None), // { "ecmaVersion": 6 },
+("export var foo = 123;", None), // { "ecmaVersion": 6, "sourceType": "module" },
+("export function foo () {}", None), // { "ecmaVersion": 6, "sourceType": "module" },
+("let toUpper = (partial) => partial.toUpperCase; export {toUpper}", None), // { "ecmaVersion": 6, "sourceType": "module" },
+("export class foo {}", None), // { "ecmaVersion": 6, "sourceType": "module" },
+("class Foo{}; var x = new Foo(); x.foo()", None), // { "ecmaVersion": 6 },
+(r#"const foo = "hello!";function bar(foobar = foo) { foobar.replace(/!$/, " world!");}
+ bar();"#, None), // { "ecmaVersion": 6 },
+("function Foo(){}; var x = new Foo(); x.foo()", None),
+("function foo() {var foo = 1; return foo}; foo();", None),
+("function foo(foo) {return foo}; foo(1);", None),
+("function foo() {function foo() {return 1;}; return foo()}; foo();", None),
+("function foo() {var foo = 1; return foo}; foo();", None), // { "ecmaVersion": 6 },
+("function foo(foo) {return foo}; foo(1);", None), // { "ecmaVersion": 6 },
+("function foo() {function foo() {return 1;}; return foo()}; foo();", None), // { "ecmaVersion": 6 },
+("const x = 1; const [y = x] = []; foo(y);", None), // { "ecmaVersion": 6 },
+("const x = 1; const {y = x} = {}; foo(y);", None), // { "ecmaVersion": 6 },
+("const x = 1; const {z: [y = x]} = {}; foo(y);", None), // { "ecmaVersion": 6 },
+("const x = []; const {z: [y] = x} = {}; foo(y);", None), // { "ecmaVersion": 6 },
+("const x = 1; let y; [y = x] = []; foo(y);", None), // { "ecmaVersion": 6 },
+("const x = 1; let y; ({z: [y = x]} = {}); foo(y);", None), // { "ecmaVersion": 6 },
+("const x = []; let y; ({z: [y] = x} = {}); foo(y);", None), // { "ecmaVersion": 6 },
+("const x = 1; function foo(y = x) { bar(y); } foo();", None), // { "ecmaVersion": 6 },
+("const x = 1; function foo({y = x} = {}) { bar(y); } foo();", None), // { "ecmaVersion": 6 },
+("const x = 1; function foo(y = function(z = x) { bar(z); }) { y(); } foo();", None), // { "ecmaVersion": 6 },
+("const x = 1; function foo(y = function() { bar(x); }) { y(); } foo();", None), // { "ecmaVersion": 6 },
+("var x = 1; var [y = x] = []; foo(y);", None), // { "ecmaVersion": 6 },
+("var x = 1; var {y = x} = {}; foo(y);", None), // { "ecmaVersion": 6 },
+("var x = 1; var {z: [y = x]} = {}; foo(y);", None), // { "ecmaVersion": 6 },
+("var x = []; var {z: [y] = x} = {}; foo(y);", None), // { "ecmaVersion": 6 },
+("var x = 1, y; [y = x] = []; foo(y);", None), // { "ecmaVersion": 6 },
+("var x = 1, y; ({z: [y = x]} = {}); foo(y);", None), // { "ecmaVersion": 6 },
+("var x = [], y; ({z: [y] = x} = {}); foo(y);", None), // { "ecmaVersion": 6 },
+("var x = 1; function foo(y = x) { bar(y); } foo();", None), // { "ecmaVersion": 6 },
+("var x = 1; function foo({y = x} = {}) { bar(y); } foo();", None), // { "ecmaVersion": 6 },
+("var x = 1; function foo(y = function(z = x) { bar(z); }) { y(); } foo();", None), // { "ecmaVersion": 6 },
+("var x = 1; function foo(y = function() { bar(x); }) { y(); } foo();", None), // { "ecmaVersion": 6 },
+// ("/*exported toaster*/ var toaster = 'great'", None),
+// ("/*exported toaster, poster*/ var toaster = 1; poster = 0;", None),
+// ("/*exported x*/ var { x } = y", None), // { "ecmaVersion": 6 },
+// ("/*exported x, y*/ var { x, y } = z", None), // { "ecmaVersion": 6 },
+// ("/*eslint custom/use-every-a:1*/ var a;", None),
+// ("/*eslint custom/use-every-a:1*/ !function(a) { return 1; }", None),
+// ("/*eslint custom/use-every-a:1*/ !function() { var a; return 1 }", None),
+("var _a;", Some(serde_json::json!([{ "vars": "all", "varsIgnorePattern": "^_" }]))),
+("var a; function foo() { var _b; } foo();", Some(serde_json::json!([{ "vars": "local", "varsIgnorePattern": "^_" }]))),
+("function foo(_a) { } foo();", Some(serde_json::json!([{ "args": "all", "argsIgnorePattern": "^_" }]))),
+("function foo(a, _b) { return a; } foo();", Some(serde_json::json!([{ "args": "after-used", "argsIgnorePattern": "^_" }]))),
+("var [ firstItemIgnored, secondItem ] = items;
+ console.log(secondItem);", Some(serde_json::json!([{ "vars": "all", "varsIgnorePattern": "[iI]gnored" }]))), // { "ecmaVersion": 6 },
+("const [ a, _b, c ] = items;
+ console.log(a+c);", Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }]))), // { "ecmaVersion": 6 },
+("const [ [a, _b, c] ] = items;
+ console.log(a+c);", Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }]))), // { "ecmaVersion": 6 },
+("const { x: [_a, foo] } = bar;
+ console.log(foo);", Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }]))), // { "ecmaVersion": 6 },
+("function baz([_b, foo]) { foo; };
+ baz()", Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }]))), // { "ecmaVersion": 6 },
+("function baz({x: [_b, foo]}) {foo};
+ baz()", Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }]))), // { "ecmaVersion": 6 },
+("function baz([{x: [_b, foo]}]) {foo};
+ baz()", Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }]))), // { "ecmaVersion": 6 },
+("
+ let _a, b;
+ foo.forEach(item => {
+ [_a, b] = item;
+ doSomething(b);
+ });
+ ", Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }]))), // { "ecmaVersion": 6 },
+("
+ // doesn't report _x
+ let _x, y;
+ _x = 1;
+ [_x, y] = foo;
+ y;
+
+ // doesn't report _a
+ let _a, b;
+ [_a, b] = foo;
+ _a = 1;
+ b;
+ ", Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }]))), // { "ecmaVersion": 2018 },
+("
+ // doesn't report _x
+ let _x, y;
+ _x = 1;
+ [_x, y] = foo;
+ y;
+
+ // doesn't report _a
+ let _a, b;
+ _a = 1;
+ ({_a, ...b } = foo);
+ b;
+ ", Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_", "ignoreRestSiblings": true }]))), // { "ecmaVersion": 2018 },
+("try {} catch ([firstError]) {}", Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "Error$" }]))), // { "ecmaVersion": 2015 },
+("(function(obj) { var name; for ( name in obj ) return; })({});", None),
+("(function(obj) { var name; for ( name in obj ) { return; } })({});", None),
+("(function(obj) { for ( var name in obj ) { return true } })({})", None),
+("(function(obj) { for ( var name in obj ) return true })({})", None),
+("(function(obj) { let name; for ( name in obj ) return; })({});", None), // { "ecmaVersion": 6 },
+("(function(obj) { let name; for ( name in obj ) { return; } })({});", None), // { "ecmaVersion": 6 },
+("(function(obj) { for ( let name in obj ) { return true } })({})", None), // { "ecmaVersion": 6 },
+("(function(obj) { for ( let name in obj ) return true })({})", None), // { "ecmaVersion": 6 },
+("(function(obj) { for ( const name in obj ) { return true } })({})", None), // { "ecmaVersion": 6 },
+("(function(obj) { for ( const name in obj ) return true })({})", None), // { "ecmaVersion": 6 },
+("(function(iter) { let name; for ( name of iter ) return; })({});", None), // { "ecmaVersion": 6 },
+("(function(iter) { let name; for ( name of iter ) { return; } })({});", None), // { "ecmaVersion": 6 },
+("(function(iter) { for ( let name of iter ) { return true } })({})", None), // { "ecmaVersion": 6 },
+("(function(iter) { for ( let name of iter ) return true })({})", None), // { "ecmaVersion": 6 },
+("(function(iter) { for ( const name of iter ) { return true } })({})", None), // { "ecmaVersion": 6 },
+("(function(iter) { for ( const name of iter ) return true })({})", None), // { "ecmaVersion": 6 },
+("let x = 0; foo = (0, x++);", None), // { "ecmaVersion": 6 },
+("let x = 0; foo = (0, x += 1);", None), // { "ecmaVersion": 6 },
+("let x = 0; foo = (0, x = x + 1);", None), // { "ecmaVersion": 6 },
+("try{}catch(err){}", Some(serde_json::json!([{ "caughtErrors": "none" }]))),
+("try{}catch(err){console.error(err);}", Some(serde_json::json!([{ "caughtErrors": "all" }]))),
+("try{}catch(ignoreErr){}", Some(serde_json::json!([{ "caughtErrorsIgnorePattern": "^ignore" }]))),
+("try{}catch(ignoreErr){}", Some(serde_json::json!([{ "caughtErrors": "all", "caughtErrorsIgnorePattern": "^ignore" }]))),
+("try {} catch ({ message, stack }) {}", Some(serde_json::json!([{ "caughtErrorsIgnorePattern": "message|stack" }]))), // { "ecmaVersion": 2015 },
+("try {} catch ({ errors: [firstError] }) {}", Some(serde_json::json!([{ "caughtErrorsIgnorePattern": "Error$" }]))), // { "ecmaVersion": 2015 },
+("try{}catch(err){}", Some(serde_json::json!([{ "caughtErrors": "none", "vars": "all", "args": "all" }]))),
+("const data = { type: 'coords', x: 1, y: 2 };
+ const { type, ...coords } = data;
+ console.log(coords);", Some(serde_json::json!([{ "ignoreRestSiblings": true }]))), // { "ecmaVersion": 2018 },
+("try {} catch ({ foo, ...bar }) { console.log(bar); }", Some(serde_json::json!([{ "ignoreRestSiblings": true }]))), // { "ecmaVersion": 2018 },
+("var a = 0, b; b = a = a + 1; foo(b);", None),
+("var a = 0, b; b = a += a + 1; foo(b);", None),
+("var a = 0, b; b = a++; foo(b);", None),
+("function foo(a) { var b = a = a + 1; bar(b) } foo();", None),
+("function foo(a) { var b = a += a + 1; bar(b) } foo();", None),
+("function foo(a) { var b = a++; bar(b) } foo();", None),
+(r#"var unregisterFooWatcher;
+ // ...
+ unregisterFooWatcher = $scope.$watch( "foo", function() {
+ // ...some code..
+ unregisterFooWatcher();
+ });
+ "#, None),
+("var ref;
+ ref = setInterval(
+ function(){
+ clearInterval(ref);
+ }, 10);
+ ", None),
+("var _timer;
+ function f() {
+ _timer = setTimeout(function () {}, _timer ? 100 : 0);
+ }
+ f();
+ ", None),
+("function foo(cb) { cb = function() { function something(a) { cb(1 + a); } register(something); }(); } foo();", None),
+("function* foo(cb) { cb = yield function(a) { cb(1 + a); }; } foo();", None), // { "ecmaVersion": 6 },
+("function foo(cb) { cb = tag`hello${function(a) { cb(1 + a); }}`; } foo();", None), // { "ecmaVersion": 6 },
+("function foo(cb) { var b; cb = b = function(a) { cb(1 + a); }; b(); } foo();", None),
+("function someFunction() {
+ var a = 0, i;
+ for (i = 0; i < 2; i++) {
+ a = myFunction(a);
+ }
+ }
+ someFunction();
+ ", None),
+("(function(a, b, {c, d}) { d })", Some(serde_json::json!([{ "argsIgnorePattern": "c" }]))), // { "ecmaVersion": 6 },
+("(function(a, b, {c, d}) { c })", Some(serde_json::json!([{ "argsIgnorePattern": "d" }]))), // { "ecmaVersion": 6 },
+("(function(a, b, c) { c })", Some(serde_json::json!([{ "argsIgnorePattern": "c" }]))),
+("(function(a, b, {c, d}) { c })", Some(serde_json::json!([{ "argsIgnorePattern": "[cd]" }]))), // { "ecmaVersion": 6 },
+("(class { set foo(UNUSED) {} })", None), // { "ecmaVersion": 6 },
+("class Foo { set bar(UNUSED) {} } console.log(Foo)", None), // { "ecmaVersion": 6 },
+("(({a, ...rest}) => rest)", Some(serde_json::json!([{ "args": "all", "ignoreRestSiblings": true }]))), // { "ecmaVersion": 2018 },
+("let foo, rest;
+ ({ foo, ...rest } = something);
+ console.log(rest);", Some(serde_json::json!([{ "ignoreRestSiblings": true }]))), // { "ecmaVersion": 2020 },
+// ("/*eslint custom/use-every-a:1*/ !function(b, a) { return 1 }", None),
+("var a = function () { a(); }; a();", None),
+("var a = function(){ return function () { a(); } }; a();", None),
+("const a = () => { a(); }; a();", None), // { "ecmaVersion": 2015 },
+("const a = () => () => { a(); }; a();", None), // { "ecmaVersion": 2015 },
+(r#"export * as ns from "source""#, None), // { "ecmaVersion": 2020, "sourceType": "module" },
+("import.meta", None), // { "ecmaVersion": 2020, "sourceType": "module" },
+// NOTE (@DonIsaac) ESLint thinks this counts as being used, I disagree
+// ("var a; a ||= 1;", None), // { "ecmaVersion": 2021 },
+// ("var a; a &&= 1;", None), // { "ecmaVersion": 2021 },
+// ("var a; a ??= 1;", None), // { "ecmaVersion": 2021 },
+("class Foo { static {} }", Some(serde_json::json!([{ "ignoreClassWithStaticInitBlock": true }]))), // { "ecmaVersion": 2022 },
+("class Foo { static {} }", Some(serde_json::json!([{ "ignoreClassWithStaticInitBlock": true, "varsIgnorePattern": "^_" }]))), // { "ecmaVersion": 2022 },
+("class Foo { static {} }", Some(serde_json::json!([{ "ignoreClassWithStaticInitBlock": false, "varsIgnorePattern": "^Foo" }]))), // { "ecmaVersion": 2022 },
+("const a = 5; const _c = a + 5;", Some(serde_json::json!([{ "args": "all", "varsIgnorePattern": "^_", "reportUsedIgnorePattern": true }]))), // { "ecmaVersion": 6 },
+("(function foo(a, _b) { return a + 5 })(5)", Some(serde_json::json!([{ "args": "all", "argsIgnorePattern": "^_", "reportUsedIgnorePattern": true }]))),
+("const [ a, _b, c ] = items;
+ console.log(a+c);", Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_", "reportUsedIgnorePattern": true }]))), // { "ecmaVersion": 6 }
+ ];
+
+ let fail = vec![
+ ("function foox() { return foox(); }", None),
+ // ("(function() { function foox() { if (true) { return foox(); } } }())", None),
+ ("var a=10", None),
+ ("function f() { var a = 1; return function(){ f(a *= 2); }; }", None),
+ ("function f() { var a = 1; return function(){ f(++a); }; }", None),
+ // ("/*global a */", None),
+ (
+ "function foo(first, second) {
+ doStuff(function() {
+ console.log(second);});};",
+ None,
+ ),
+ ("var a=10;", Some(serde_json::json!(["all"]))),
+ ("var a=10; a=20;", Some(serde_json::json!(["all"]))),
+ ("var a=10; (function() { var a = 1; alert(a); })();", Some(serde_json::json!(["all"]))),
+ ("var a=10, b=0, c=null; alert(a+b)", Some(serde_json::json!(["all"]))),
+ (
+ "var a=10, b=0, c=null; setTimeout(function() { var b=2; alert(a+b+c); }, 0);",
+ Some(serde_json::json!(["all"])),
+ ),
+ (
+ "var a=10, b=0, c=null; setTimeout(function() { var b=2; var c=2; alert(a+b+c); }, 0);",
+ Some(serde_json::json!(["all"])),
+ ),
+ ("function f(){var a=[];return a.map(function(){});}", Some(serde_json::json!(["all"]))),
+ ("function f(){var a=[];return a.map(function g(){});}", Some(serde_json::json!(["all"]))),
+ (
+ "function foo() {function foo(x) {
+ return x; }; return function() {return foo; }; }",
+ None,
+ ),
+ (
+ "function f(){var x;function a(){x=42;}function b(){alert(x);}}",
+ Some(serde_json::json!(["all"])),
+ ),
+ ("function f(a) {}; f();", Some(serde_json::json!(["all"]))),
+ ("function a(x, y, z){ return y; }; a();", Some(serde_json::json!(["all"]))),
+ ("var min = Math.min", Some(serde_json::json!(["all"]))),
+ ("var min = {min: 1}", Some(serde_json::json!(["all"]))),
+ ("Foo.bar = function(baz) { return 1; };", Some(serde_json::json!(["all"]))),
+ ("var min = {min: 1}", Some(serde_json::json!([{ "vars": "all" }]))),
+ (
+ "function gg(baz, bar) { return baz; }; gg();",
+ Some(serde_json::json!([{ "vars": "all" }])),
+ ),
+ (
+ "(function(foo, baz, bar) { return baz; })();",
+ Some(serde_json::json!([{ "vars": "all", "args": "after-used" }])),
+ ),
+ (
+ "(function(foo, baz, bar) { return baz; })();",
+ Some(serde_json::json!([{ "vars": "all", "args": "all" }])),
+ ),
+ (
+ "(function z(foo) { var bar = 33; })();",
+ Some(serde_json::json!([{ "vars": "all", "args": "all" }])),
+ ),
+ ("(function z(foo) { z(); })();", Some(serde_json::json!([{}]))),
+ (
+ "function f() { var a = 1; return function(){ f(a = 2); }; }",
+ Some(serde_json::json!([{}])),
+ ),
+ (r#"import x from "y";"#, None), // { "ecmaVersion": 6, "sourceType": "module" },
+ (
+ "export function fn2({ x, y }) {
+ console.log(x);
+ };",
+ None,
+ ), // { "ecmaVersion": 6, "sourceType": "module" },
+ (
+ "export function fn2( x, y ) {
+ console.log(x);
+ };",
+ None,
+ ), // { "ecmaVersion": 6, "sourceType": "module" },
+ ("/*exported max*/ var max = 1, min = {min: 1}", None),
+ ("/*exported x*/ var { x, y } = z", None), // { "ecmaVersion": 6 },
+ ("var _a; var b;", Some(serde_json::json!([{ "vars": "all", "varsIgnorePattern": "^_" }]))),
+ (
+ "var a; function foo() { var _b; var c_; } foo();",
+ Some(serde_json::json!([{ "vars": "local", "varsIgnorePattern": "^_" }])),
+ ),
+ (
+ "function foo(a, _b) { } foo();",
+ Some(serde_json::json!([{ "args": "all", "argsIgnorePattern": "^_" }])),
+ ),
+ (
+ "function foo(a, _b, c) { return a; } foo();",
+ Some(serde_json::json!([{ "args": "after-used", "argsIgnorePattern": "^_" }])),
+ ),
+ (
+ "function foo(_a) { } foo();",
+ Some(serde_json::json!([{ "args": "all", "argsIgnorePattern": "[iI]gnored" }])),
+ ),
+ (
+ "var [ firstItemIgnored, secondItem ] = items;",
+ Some(serde_json::json!([{ "vars": "all", "varsIgnorePattern": "[iI]gnored" }])),
+ ), // { "ecmaVersion": 6 },
+ (
+ "
+ const array = ['a', 'b', 'c'];
+ const [a, _b, c] = array;
+ const newArray = [a, c];
+ ",
+ Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }])),
+ ), // { "ecmaVersion": 2020 },
+ (
+ "
+ const array = ['a', 'b', 'c', 'd', 'e'];
+ const [a, _b, c] = array;
+ ",
+ Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }])),
+ ), // { "ecmaVersion": 2020 },
+ (
+ "
+ const array = ['a', 'b', 'c'];
+ const [a, _b, c] = array;
+ const fooArray = ['foo'];
+ const barArray = ['bar'];
+ const ignoreArray = ['ignore'];
+ ",
+ Some(
+ serde_json::json!([{ "destructuredArrayIgnorePattern": "^_", "varsIgnorePattern": "ignore" }]),
+ ),
+ ), // { "ecmaVersion": 2020 },
+ (
+ "
+ const array = [obj];
+ const [{_a, foo}] = array;
+ console.log(foo);
+ ",
+ Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }])),
+ ), // { "ecmaVersion": 2020 },
+ (
+ "
+ function foo([{_a, bar}]) {
+ bar;
+ }
+ foo();
+ ",
+ Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }])),
+ ), // { "ecmaVersion": 2020 },
+ (
+ "
+ let _a, b;
+
+ foo.forEach(item => {
+ [a, b] = item;
+ });
+ ",
+ Some(serde_json::json!([{ "destructuredArrayIgnorePattern": "^_" }])),
+ ), // { "ecmaVersion": 2020 },
+ ("(function(obj) { var name; for ( name in obj ) { i(); return; } })({});", None),
+ ("(function(obj) { var name; for ( name in obj ) { } })({});", None),
+ ("(function(obj) { for ( var name in obj ) { } })({});", None),
+ ("(function(iter) { var name; for ( name of iter ) { i(); return; } })({});", None), // { "ecmaVersion": 6 },
+ ("(function(iter) { var name; for ( name of iter ) { } })({});", None), // { "ecmaVersion": 6 },
+ ("(function(iter) { for ( var name of iter ) { } })({});", None), // { "ecmaVersion": 6 },
+ // (
+ // "
+ // /* global foobar, foo, bar */
+ // foobar;",
+ // None,
+ // ),
+ // (
+ // "
+ // /* global foobar,
+ // foo,
+ // bar
+ // */
+ // foobar;",
+ // None,
+ // ),
+ (
+ "const data = { type: 'coords', x: 1, y: 2 };
+ const { type, ...coords } = data;
+ console.log(coords);",
+ None,
+ ), // { "ecmaVersion": 2018 },
+ (
+ "const data = { type: 'coords', x: 2, y: 2 };
+ const { type, ...coords } = data;
+ console.log(type)",
+ Some(serde_json::json!([{ "ignoreRestSiblings": true }])),
+ ), // { "ecmaVersion": 2018 },
+ (
+ "let type, coords;
+ ({ type, ...coords } = data);
+ console.log(type)",
+ Some(serde_json::json!([{ "ignoreRestSiblings": true }])),
+ ), // { "ecmaVersion": 2018 },
+ (
+ "const data = { type: 'coords', x: 3, y: 2 };
+ const { type, ...coords } = data;
+ console.log(type)",
+ None,
+ ), // { "ecmaVersion": 2018 },
+ (
+ "const data = { vars: ['x','y'], x: 1, y: 2 };
+ const { vars: [x], ...coords } = data;
+ console.log(coords)",
+ None,
+ ), // { "ecmaVersion": 2018 },
+ (
+ "const data = { defaults: { x: 0 }, x: 1, y: 2 };
+ const { defaults: { x }, ...coords } = data;
+ console.log(coords)",
+ None,
+ ), // { "ecmaVersion": 2018 },
+ (
+ "(({a, ...rest}) => {})",
+ Some(serde_json::json!([{ "args": "all", "ignoreRestSiblings": true }])),
+ ), // { "ecmaVersion": 2018 },
+ // (
+ // "/* global a$fooz,$foo */
+ // a$fooz;",
+ // None,
+ // ),
+ // (
+ // "/* globals a$fooz, $ */
+ // a$fooz;",
+ // None,
+ // ),
+ // ("/*globals $foo*/", None),
+ // ("/* global global*/", None),
+ // ("/*global foo:true*/", None),
+ // (
+ // "/*global ๅคๆฐ, ๆฐ*/
+ // ๅคๆฐ;",
+ // None,
+ // ),
+ // (
+ // "/*global ๐ ฎท๐ฉธฝ, ๐ ฎท*/
+ // \\u{20BB7}\\u{29E3D};",
+ // None,
+ // ), // { "ecmaVersion": 6 },
+ ("export default function(a) {}", None), // { "ecmaVersion": 6, "sourceType": "module" },
+ ("export default function(a, b) { console.log(a); }", None), // { "ecmaVersion": 6, "sourceType": "module" },
+ ("export default (function(a) {});", None), // { "ecmaVersion": 6, "sourceType": "module" },
+ ("export default (function(a, b) { console.log(a); });", None), // { "ecmaVersion": 6, "sourceType": "module" },
+ ("export default (a) => {};", None), // { "ecmaVersion": 6, "sourceType": "module" },
+ ("export default (a, b) => { console.log(a); };", None), // { "ecmaVersion": 6, "sourceType": "module" },
+ ("try{}catch(err){};", None),
+ ("try{}catch(err){};", Some(serde_json::json!([{ "caughtErrors": "all" }]))),
+ (
+ "try{}catch(err){};",
+ Some(
+ serde_json::json!([{ "caughtErrors": "all", "caughtErrorsIgnorePattern": "^ignore" }]),
+ ),
+ ),
+ (
+ "try{}catch(err){};",
+ Some(serde_json::json!([{ "caughtErrors": "all", "varsIgnorePattern": "^err" }])),
+ ),
+ (
+ "try{}catch(err){};",
+ Some(serde_json::json!([{ "caughtErrors": "all", "varsIgnorePattern": "^." }])),
+ ),
+ (
+ "try{}catch(ignoreErr){}try{}catch(err){};",
+ Some(
+ serde_json::json!([{ "caughtErrors": "all", "caughtErrorsIgnorePattern": "^ignore" }]),
+ ),
+ ),
+ (
+ "try{}catch(error){}try{}catch(err){};",
+ Some(
+ serde_json::json!([{ "caughtErrors": "all", "caughtErrorsIgnorePattern": "^ignore" }]),
+ ),
+ ),
+ (
+ "try{}catch(err){};",
+ Some(serde_json::json!([{ "vars": "all", "args": "all", "caughtErrors": "all" }])),
+ ),
+ (
+ "try{}catch(err){};",
+ Some(
+ serde_json::json!([ { "vars": "all", "args": "all", "caughtErrors": "all", "argsIgnorePattern": "^er" } ]),
+ ),
+ ),
+ ("var a = 0; a = a + 1;", None),
+ ("var a = 0; a = a + a;", None),
+ ("var a = 0; a += a + 1;", None),
+ ("var a = 0; a++;", None),
+ ("function foo(a) { a = a + 1 } foo();", None),
+ ("function foo(a) { a += a + 1 } foo();", None),
+ ("function foo(a) { a++ } foo();", None),
+ ("var a = 3; a = a * 5 + 6;", None),
+ ("var a = 2, b = 4; a = a * 2 + b;", None),
+ // https://github.com/oxc-project/oxc/issues/4436
+ ("function foo(cb) { cb = function(a) { cb(1 + a); }; bar(not_cb); } foo();", None),
+ ("function foo(cb) { cb = (function(a) { cb(1 + a); }, cb); } foo();", None),
+ // ("function foo(cb) { cb = (0, function(a) { cb(1 + a); }); } foo();", None),
+ (
+ "while (a) {
+ function foo(b) {
+ b = b + 1;
+ }
+ foo()
+ }",
+ None,
+ ),
+ ("(function(a, b, c) {})", Some(serde_json::json!([{ "argsIgnorePattern": "c" }]))),
+ ("(function(a, b, {c, d}) {})", Some(serde_json::json!([{ "argsIgnorePattern": "[cd]" }]))), // { "ecmaVersion": 6 },
+ ("(function(a, b, {c, d}) {})", Some(serde_json::json!([{ "argsIgnorePattern": "c" }]))), // { "ecmaVersion": 6 },
+ ("(function(a, b, {c, d}) {})", Some(serde_json::json!([{ "argsIgnorePattern": "d" }]))), // { "ecmaVersion": 6 },
+ // (
+ // "/*global
+ // foo*/",
+ // None,
+ // ),
+ ("(function ({ a }, b ) { return b; })();", None), // { "ecmaVersion": 2015 },
+ ("(function ({ a }, { b, c } ) { return b; })();", None), // { "ecmaVersion": 2015 },
+ (
+ "let x = 0;
+ x++, x = 0;",
+ None,
+ ), // { "ecmaVersion": 2015 },
+ (
+ "let x = 0;
+ x++, x = 0;
+ x=3;",
+ None,
+ ), // { "ecmaVersion": 2015 },
+ ("let x = 0; x++, 0;", None), // { "ecmaVersion": 2015 },
+ ("let x = 0; 0, x++;", None), // { "ecmaVersion": 2015 },
+ ("let x = 0; 0, (1, x++);", None), // { "ecmaVersion": 2015 },
+ ("let x = 0; foo = (x++, 0);", None), // { "ecmaVersion": 2015 },
+ ("let x = 0; foo = ((0, x++), 0);", None), // { "ecmaVersion": 2015 },
+ ("let x = 0; x += 1, 0;", None), // { "ecmaVersion": 2015 },
+ ("let x = 0; 0, x += 1;", None), // { "ecmaVersion": 2015 },
+ ("let x = 0; 0, (1, x += 1);", None), // { "ecmaVersion": 2015 },
+ ("let x = 0; foo = (x += 1, 0);", None), // { "ecmaVersion": 2015 },
+ ("let x = 0; foo = ((0, x += 1), 0);", None), // { "ecmaVersion": 2015 },
+ (
+ "let z = 0;
+ z = z + 1, z = 2;
+ ",
+ None,
+ ), // { "ecmaVersion": 2020 },
+ (
+ "let z = 0;
+ z = z+1, z = 2;
+ z = 3;",
+ None,
+ ), // { "ecmaVersion": 2020 },
+ (
+ "let z = 0;
+ z = z+1, z = 2;
+ z = z+3;
+ ",
+ None,
+ ), // { "ecmaVersion": 2020 },
+ ("let x = 0; 0, x = x+1;", None), // { "ecmaVersion": 2020 },
+ ("let x = 0; x = x+1, 0;", None), // { "ecmaVersion": 2020 },
+ // https://github.com/oxc-project/oxc/issues/4437
+ // ("let x = 0; foo = ((0, x = x + 1), 0);", None), // { "ecmaVersion": 2020 },
+ // ("let x = 0; foo = (x = x+1, 0);", None), // { "ecmaVersion": 2020 },
+ ("let x = 0; 0, (1, x=x+1);", None), // { "ecmaVersion": 2020 },
+ ("(function ({ a, b }, { c } ) { return b; })();", None), // { "ecmaVersion": 2015 },
+ ("(function ([ a ], b ) { return b; })();", None), // { "ecmaVersion": 2015 },
+ ("(function ([ a ], [ b, c ] ) { return b; })();", None), // { "ecmaVersion": 2015 },
+ ("(function ([ a, b ], [ c ] ) { return b; })();", None), // { "ecmaVersion": 2015 },
+ (
+ "(function(_a) {})();",
+ Some(serde_json::json!([{ "args": "all", "varsIgnorePattern": "^_" }])),
+ ),
+ (
+ "(function(_a) {})();",
+ Some(serde_json::json!([{ "args": "all", "caughtErrorsIgnorePattern": "^_" }])),
+ ),
+ ("var a = function() { a(); };", None),
+ ("var a = function(){ return function() { a(); } };", None),
+ ("const a = () => () => { a(); };", None), // { "ecmaVersion": 2015 },
+ (
+ "let myArray = [1,2,3,4].filter((x) => x == 0);
+ myArray = myArray.filter((x) => x == 1);",
+ None,
+ ), // { "ecmaVersion": 2015 },
+ ("const a = 1; a += 1;", None), // { "ecmaVersion": 2015 },
+ ("const a = () => { a(); };", None), // { "ecmaVersion": 2015 },
+ // TODO
+ // (
+ // "let x = [];
+ // x = x.concat(x);",
+ // None,
+ // ), // { "ecmaVersion": 2015 },
+ (
+ "let a = 'a';
+ a = 10;
+ function foo(){
+ a = 11;
+ a = () => {
+ a = 13
+ }
+ }",
+ None,
+ ), // { "ecmaVersion": 2020 },
+ (
+ "let foo;
+ init();
+ foo = foo + 2;
+ function init() {
+ foo = 1;
+ }",
+ None,
+ ), // { "ecmaVersion": 2020 },
+ (
+ "function foo(n) {
+ if (n < 2) return 1;
+ return n * foo(n - 1);
+ }",
+ None,
+ ), // { "ecmaVersion": 2020 },
+ (
+ "let c = 'c'
+ c = 10
+ function foo1() {
+ c = 11
+ c = () => {
+ c = 13
+ }
+ }
+
+ c = foo1",
+ None,
+ ), // { "ecmaVersion": 2020 },
+ (
+ "class Foo { static {} }",
+ Some(serde_json::json!([{ "ignoreClassWithStaticInitBlock": false }])),
+ ), // { "ecmaVersion": 2022 },
+ ("class Foo { static {} }", None), // { "ecmaVersion": 2022 },
+ (
+ "class Foo { static { var bar; } }",
+ Some(serde_json::json!([{ "ignoreClassWithStaticInitBlock": true }])),
+ ), // { "ecmaVersion": 2022 },
+ ("class Foo {}", Some(serde_json::json!([{ "ignoreClassWithStaticInitBlock": true }]))), // { "ecmaVersion": 2022 },
+ (
+ "class Foo { static bar; }",
+ Some(serde_json::json!([{ "ignoreClassWithStaticInitBlock": true }])),
+ ), // { "ecmaVersion": 2022 },
+ (
+ "class Foo { static bar() {} }",
+ Some(serde_json::json!([{ "ignoreClassWithStaticInitBlock": true }])),
+ ), // { "ecmaVersion": 2022 },
+ (
+ "const _a = 5;const _b = _a + 5",
+ Some(
+ serde_json::json!([{ "args": "all", "varsIgnorePattern": "^_", "reportUsedIgnorePattern": true }]),
+ ),
+ ), // { "ecmaVersion": 6 },
+ (
+ "const _a = 42; foo(() => _a);",
+ Some(
+ serde_json::json!([{ "args": "all", "varsIgnorePattern": "^_", "reportUsedIgnorePattern": true }]),
+ ),
+ ), // { "ecmaVersion": 6 },
+ (
+ "(function foo(_a) { return _a + 5 })(5)",
+ Some(
+ serde_json::json!([{ "args": "all", "argsIgnorePattern": "^_", "reportUsedIgnorePattern": true }]),
+ ),
+ ),
+ // TODO
+ (
+ "const [ a, _b ] = items;
+ console.log(a+_b);",
+ Some(
+ serde_json::json!([{ "destructuredArrayIgnorePattern": "^_", "reportUsedIgnorePattern": true }]),
+ ),
+ ), // { "ecmaVersion": 6 },
+ // (
+ // "let _x;
+ // [_x] = arr;
+ // foo(_x);",
+ // Some(
+ // serde_json::json!([{ "destructuredArrayIgnorePattern": "^_", "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]),
+ // ),
+ // ), // { "ecmaVersion": 6 },
+ (
+ "const [ignored] = arr;
+ foo(ignored);",
+ Some(
+ serde_json::json!([{ "destructuredArrayIgnorePattern": "^_", "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]),
+ ),
+ ), // { "ecmaVersion": 6 },
+ (
+ "try{}catch(_err){console.error(_err)}",
+ Some(
+ serde_json::json!([{ "caughtErrors": "all", "caughtErrorsIgnorePattern": "^_", "reportUsedIgnorePattern": true }]),
+ ),
+ ),
+ (
+ "try {} catch ({ message }) { console.error(message); }",
+ Some(
+ serde_json::json!([{ "caughtErrorsIgnorePattern": "message", "reportUsedIgnorePattern": true }]),
+ ),
+ ), // { "ecmaVersion": 2015 },
+ (
+ "try {} catch ([_a, _b]) { doSomething(_a, _b); }",
+ Some(
+ serde_json::json!([{ "caughtErrorsIgnorePattern": "^_", "reportUsedIgnorePattern": true }]),
+ ),
+ ), // { "ecmaVersion": 6 },
+ (
+ "try {} catch ([_a, _b]) { doSomething(_a, _b); }",
+ Some(
+ serde_json::json!([{ "destructuredArrayIgnorePattern": "^_", "reportUsedIgnorePattern": true }]),
+ ),
+ ), // { "ecmaVersion": 6 },
+ (
+ "
+ try {
+ } catch (_) {
+ _ = 'foo'
+ }
+ ",
+ Some(serde_json::json!([{ "caughtErrorsIgnorePattern": "foo" }])),
+ ),
+ (
+ "
+ try {
+ } catch (_) {
+ _ = 'foo'
+ }
+ ",
+ Some(
+ serde_json::json!([{ "caughtErrorsIgnorePattern": "ignored", "varsIgnorePattern": "_" }]),
+ ),
+ ),
+ (
+ "try {} catch ({ message, errors: [firstError] }) {}",
+ Some(serde_json::json!([{ "caughtErrorsIgnorePattern": "foo" }])),
+ ), // { "ecmaVersion": 2015 },
+ (
+ "try {} catch ({ stack: $ }) { $ = 'Something broke: ' + $; }",
+ Some(serde_json::json!([{ "caughtErrorsIgnorePattern": "\\w" }])),
+ ), // { "ecmaVersion": 2015 },
+ (
+ "
+ _ => { _ = _ + 1 };
+ ",
+ Some(
+ serde_json::json!([{ "argsIgnorePattern": "ignored", "varsIgnorePattern": "_" }]),
+ ),
+ ), // { "ecmaVersion": 2015 }
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail).with_snapshot_suffix("eslint").test_and_snapshot();
+}
diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/mod.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/mod.rs
new file mode 100644
index 0000000000000..56e51fd9da359
--- /dev/null
+++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/mod.rs
@@ -0,0 +1,5 @@
+mod eslint;
+mod oxc;
+mod typescript_eslint;
+
+use super::NoUnusedVars;
diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs
new file mode 100644
index 0000000000000..13f831ad63555
--- /dev/null
+++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs
@@ -0,0 +1,660 @@
+//! Test cases created by oxc maintainers
+
+use super::NoUnusedVars;
+use crate::{tester::Tester, RuleMeta as _};
+use serde_json::json;
+
+#[test]
+fn test_vars_simple() {
+ let pass = vec![
+ ("let a = 1; console.log(a)", None),
+ ("let a = 1; new Foo(a)", None),
+ ("let a = 1; let b = a + 1; console.log(b)", None),
+ ("let a = 1; if (true) { console.log(a) }", None),
+ ("let _a = 1", Some(json!([{ "varsIgnorePattern": "^_" }]))),
+ ];
+ let fail = vec![
+ ("let a = 1", None),
+ ("let a = 1; a = 2", None),
+ (
+ "let _a = 1; console.log(_a)",
+ Some(json!([{ "varsIgnorePattern": "^_", "reportUsedIgnorePattern": true }])),
+ ),
+ ("let _a = 1", Some(json!([{ "argsIgnorePattern": "^_" }]))),
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-vars-simple")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_vars_self_use() {
+ let pass = vec![
+ "
+ function foo() {
+ let bar = 0;
+ return bar++;
+ }
+ foo();
+ ",
+ ];
+ let fail = vec![
+ "
+ function foo() {
+ return foo
+ }
+ ",
+ "
+ const foo = () => {
+ return foo
+ }
+ ",
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-vars-self-use")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_vars_discarded_reads() {
+ let pass = vec![
+ // https://github.com/oxc-project/oxc/pull/4445#issuecomment-2254122889
+ "
+ (() => {
+ const t = import.meta.url,
+ s = {};
+ return '' !== t && (s.resourcesUrl = new URL('.', t).href), e(s);
+ })();
+ ",
+ "var a; b !== '' && (x = a, f(c))",
+ "
+ class Test {
+ async updateContextGroup(t, i, s = !0) {
+ s ? await this.leave(i) : await this.join(t, i), false;
+ }
+ }
+
+ new Test();
+ ",
+ ];
+
+ let fail = vec![
+ "
+ function foo(a) { return (a, 0); }
+ foo(1);
+ ",
+ "
+ const I = (e) => (l) => {
+ e.push(l), n || false;
+ };
+ ",
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-vars-discarded-read")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_vars_reassignment() {
+ let pass = vec![
+ "let i = 0; someFunction(i++);",
+ "
+ const thunk = () => 3;
+ let result = undefined;
+ result &&= thunk();
+ console.log(result);
+ ",
+ r"
+ const thunk = () => 3;
+ {
+ let a = thunk();
+ console.log(a);
+ }
+ ",
+ "let a = 0; let b = a++; f(b);",
+ "let a = 0, b = 1; let c = b = a = 1; f(c+b);",
+ // implicit returns
+ "
+ let i = 0;
+ const func = () => 'value: ' + i++;
+ func();
+ ",
+ // parenthesis are transparent
+ "let a = 0; let b = ((a++)); f(b);",
+ // type casting is transparent
+ "let a = 0; let b = a as any; f(b);",
+ "let a = 0; let b = a as unknown as string as unknown as number; f(b);",
+ "let a = 0; let b = a++ as string | number; f(b);",
+ // pathological sequence assignments
+ "let a = 0; let b = (0, a++); f(b);",
+ "let a = 0; let b = (0, (a++)); f(b);",
+ "let a = 0; let b = (0, (a++) as string | number); f(b);",
+ "let a = 0; let b = (0, (0, a++)); f(b);",
+ "let a = 0; let b = (0, (((0, a++)))); f(b);",
+ "let a = 0; let b = (0, a) + 1; f(b);",
+ // reassignment in conditions
+ "
+ function foo() {
+ if (i++ === 0) {
+ return 'zero';
+ } else {
+ return 'not zero';
+ }
+ var i = 0;
+ }
+ foo();
+ ",
+ "
+ let i = 10;
+ while (i-- > 0) {
+ console.log('countdown');
+ };
+ ",
+ "
+ let i = 10;
+ do {
+ console.log('countdown');
+ } while(i-- > 0);
+ ",
+ "let i = 0; i > 0 ? 'positive' : 'negative';",
+ "let i = 0; i > 0 && console.log('positive');",
+ ];
+
+ let fail = vec![
+ "let a = 1; a ||= 2;",
+ "let a = 0; a = a + 1;",
+ // type casting is transparent
+ "let a = 0; a = a++ as any;",
+ "let a = 0; a = a as unknown as string as unknown as number;",
+ // pathological sequence assignments
+ "let a = 0; a = ++a;",
+ "let a = 0; a = (0, ++a);",
+ "let a = 0; a = (a++, 0);",
+ "let a = 0; let b = (a++, 0); f(b);",
+ "let a = 0; let b = (0, (a++, 0)); f(b);",
+ "let a = 0; let b = ((0, a++), 0); f(b);",
+ "let a = 0; let b = (a, 0) + 1; f(b);",
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-vars-reassignment")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_vars_destructure_ignored() {
+ let pass = vec![
+ // ("const { a, ...rest } = obj; console.log(rest)", Some(json![{ "ignoreRestSiblings": true }]))
+ ];
+ let fail = vec![
+ ("const { a, ...rest } = obj", Some(json![{ "ignoreRestSiblings": true }])),
+ ("const [a, ...rest] = arr", Some(json![{ "ignoreRestSiblings": true }])),
+ (
+ "const { a: { b }, ...rest } = obj; console.log(a)",
+ Some(json![{ "ignoreRestSiblings": true }]),
+ ),
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-vars-destructure-ignored")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_vars_catch() {
+ let pass = vec![
+ // lb
+ ("try {} catch (e) { throw e }", None),
+ ("try {} catch (e) { }", Some(json!([{ "caughtErrors": "none" }]))),
+ ("try {} catch { }", None),
+ ];
+ let fail = vec![
+ // lb
+ ("try {} catch (e) { }", Some(json!([{ "caughtErrors": "all" }]))),
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-vars-catch")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_functions() {
+ let pass = vec![
+ "function foo() {}\nfoo()",
+ "const a = () => {}; a();",
+ "var foo = function foo() {}\n foo();",
+ "var foo = function bar() {}\n foo();",
+ "var foo; foo = function bar() {}; foo();",
+ "
+ const obj = {
+ foo: function foo () {}
+ }
+ f(obj)
+ ",
+ "
+ function foo() {}
+ function bar() { foo() }
+ bar()
+ ",
+ "
+ function foo() {}
+ if (true) {
+ foo()
+ }
+ ",
+ "
+ function main() {
+ function foo() {}
+ if (true) { foo() }
+ }
+ main()
+ ",
+ "
+ function foo() {
+ return function bar() {}
+ }
+ foo()()
+ ",
+ "
+ import debounce from 'debounce';
+
+ const debouncedFoo = debounce(function foo() {
+ console.log('do a thing');
+ }, 100);
+
+ debouncedFoo();
+ ",
+ // FIXME
+ "
+ const createIdFactory = ((): (() => string) => {
+ let count = 0;
+ return () => `${count++}`
+ })();
+
+ const getId = createIdFactory();
+ console.log(getId());
+ ",
+ // calls on optional chains should be valid
+ "
+ let foo = () => {};
+ foo?.();
+ ",
+ "
+ function foo(a: number): number;
+ function foo(a: number | string): number {
+ return Number(a)
+ }
+ foo();
+ ",
+ "export const Component = () => ",
+ // https://github.com/oxc-project/oxc/pull/4445#issuecomment-2254122889
+ "
+ Promise.withResolvers ||
+ (Promise.withResolvers = function withResolvers() {
+ let resolve!: (value: T | PromiseLike) => void;
+ let reject!: (reason: unknown) => void;
+
+ const promise = new this((promiseResolve, promiseReject) => {
+ resolve = promiseResolve;
+ reject = promiseReject;
+ });
+
+ return {
+ resolve,
+ reject,
+ promise,
+ };
+ });
+ ",
+ ];
+
+ let fail = vec!["function foo() {}", "function foo() { foo() }"];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-functions")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_imports() {
+ let pass = vec![
+ ("import { a } from 'b'; console.log(a)", None),
+ ("import * as a from 'a'; console.log(a)", None),
+ ("import a from 'a'; console.log(a)", None),
+ ("import { default as a } from 'a'; console.log(a)", None),
+ (
+ "import { createElement } from 'preact/compat';",
+ Some(json!([{ "varsIgnorePattern": "^(h|React|createElement)$" }])),
+ ),
+ (
+ "import { createElement } from 'preact/compat';",
+ Some(json!([{ "args": "none", "varsIgnorePattern": "^(h|React|createElement)$" }])),
+ ),
+ ];
+ let fail = vec![
+ ("import { a } from 'a'", None),
+ ("import * as a from 'a'", None),
+ ("import { a as b } from 'a'; console.log(a)", None),
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-imports")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_exports() {
+ let pass = vec![
+ "export const a = 1; console.log(a)",
+ "export function foo() {}",
+ "export default function foo() {}",
+ "export class A {}",
+ "export interface A {}",
+ "export type A = string",
+ "export enum E { }",
+ // "export enum E { A, B }",
+ "const a = 1; export { a }",
+ "const a = 1; export default a",
+ // re-exports
+ "import { a } from 'a'; export { a }",
+ "import { a as b } from 'a'; export { b }",
+ "export * as a from 'a'",
+ "export { a, b } from 'a'",
+ ];
+ let fail = vec!["import { a as b } from 'a'; export { a }"];
+
+ // these are mostly pass[] cases, so do not snapshot
+ Tester::new(NoUnusedVars::NAME, pass, fail).test();
+}
+
+#[test]
+fn test_react() {
+ let pass = vec![
+ "
+ import React from 'react';
+
+ export const Foo = () => ;
+ ",
+ "
+ // React 17 API
+ import React from 'react';
+ import ReactDOM from 'react-dom';
+
+ interface Props {}
+ const Component = React.forwardRef(
+ function Component(props, ref) {
+ return
+ }
+ );
+
+ ReactDOM.render(, document.getElementById('root'));
+ ",
+ "
+ import React from 'react';
+ export class Foo extends React.Component<{}, { i: number }> {
+ constructor(props) {
+ super(props);
+ }
+
+ getId = () => {
+ }
+ }
+ ",
+ ];
+
+ let fail = vec![
+ "
+ const React = {};
+
+ export const Foo = () =>
+ ",
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail).test();
+}
+
+#[test]
+fn test_arguments() {
+ let pass = vec![
+ ("function foo(a) { return a } foo()", None),
+ ("function foo(a, b) { return b } foo()", Some(json!([{ "args": "after-used" }]))),
+ ("let ids = arr.map(el => el.id); f(ids)", None),
+ (
+ "let targetId = '1234'; let user = users.find(user => user.id === targetId); f(user)",
+ None,
+ ),
+ (
+ "
+ const unboxed = arr.map(el => {
+ if (typeof el === 'object') return el['value']
+ else return el
+ })
+ f(unboxed)
+ ",
+ None,
+ ),
+ ];
+ let fail = vec![
+ ("function foo(a) {} foo()", None),
+ ("function foo({ a }, b) { return b } foo()", Some(json!([{ "args": "after-used" }]))),
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-arguments")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_enums() {
+ let pass = vec![
+ "export enum Foo { A, B }",
+ "enum Foo { A }\nconsole.log(Foo.A)",
+ "enum Foo { A, B }\n export { Foo }",
+ ];
+
+ let fail = vec!["enum Foo { A }"];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-enums")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_classes() {
+ let pass = vec![
+ "
+ export class Foo {
+ public a = 4;
+ private b;
+ }
+ ",
+ // TS constructor property definitions
+ "export class Foo { constructor(public a) {} }",
+ "export class Foo { constructor(private a) {} }",
+ "export class Foo { constructor(protected a) {} }",
+ "export class Foo { constructor(readonly a) {} }",
+ "export class Foo extends Bar { constructor(override a) {} }",
+ "export class Foo { constructor(public readonly a) {} }",
+ // note: abstract doesn't count, but that's a parse error
+ // setters can have unused methods
+ "export class Foo { set foo(value) { } }",
+ "export class Foo { public set foo(value) { } }",
+ "
+ class Foo { }
+ class Bar extends Foo {}
+ console.log(new Bar());
+ ",
+ "
+ export abstract class Foo {
+ public abstract bar(a: number): string;
+ }
+ ",
+ "var Foo = class Foo {}; new Foo();",
+ // override methods must have the same signature as their parent and so
+ // any unused parameters in them are allowed
+ "
+ class Foo {
+ public method(a: number, b: number): number {
+ return a + b;
+ }
+ }
+ class Bar extends Foo {
+ public override method(a: number, b: number): number {
+ return a;
+ }
+ }
+ new Bar();
+ ",
+ ];
+
+ let fail = vec![
+ // no modifier = no property
+ "export class Foo { constructor(a: number) {} }",
+ // not a setter
+ "export class Foo { set(value) { } }",
+ "
+ export abstract class Foo {
+ public bar(a: number): string {}
+ }
+ ",
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-classes")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_namespaces() {
+ let pass = vec![
+ "export namespace N {}",
+ "namespace N { export function foo() {} }\nconsole.log(N.foo());",
+ "export namespace N { export function foo() {} }",
+ "export namespace N { export const foo = 1 }",
+ "
+ export namespace N {
+ export function foo() {
+ bar()
+ }
+ function bar() {}
+ }
+ ",
+ "declare global {}",
+ "declare global { interface Window {} }",
+ "
+ declare global {
+ namespace jest {
+ interface Matcher {
+ someCustomMatcher(): void
+ }
+ }
+ }
+ ",
+ "
+ declare global {
+ const x: number;
+ }
+ ",
+ "
+ interface Foo {}
+ namespace Foo {
+ export const a = {};
+ }
+ const foo: Foo = Foo.a
+ console.log(foo)
+ ",
+ ];
+
+ let fail = vec![
+ "namespace N {}",
+ // FIXME
+ // "export namespace N { function foo() }",
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-namespaces")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_type_aliases() {
+ let pass = vec![];
+
+ let fail = vec![
+ // usages within own declaration do not count
+ "type Foo = Foo",
+ "type Foo = Array",
+ "type Unbox = B extends Box ? Unbox : B",
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-type-aliases")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_type_references() {
+ let pass = vec![
+ "type A = number; export type B = Array;",
+ "
+ type A = number;
+ type B = T;
+ export type C = B;
+ ",
+ "
+ type A = T;
+ type B = T;
+ export type C = B>;
+ ",
+ "const x: number = 1; function foo(): typeof x { return x }; foo()",
+ // not handled by typescript-eslint. Maybe we'll add this one day
+ "function foo(): typeof foo { }",
+ "function foo(): typeof foo { return foo }",
+ // ---
+ "type T = number; console.log(3 as T);",
+ "type T = number; console.log(((3) as T));",
+ "type T = Record; console.log({} as Readonly)",
+ // https://github.com/oxc-project/oxc/issues/4494
+ "
+ import type { mySchema } from './my-schema';
+ function test(arg: ReturnType) {
+ arg;
+ }
+ test('');
+ ",
+ // https://github.com/oxc-project/oxc/pull/4445#issuecomment-2254122889
+ "
+ type PermissionValues = {
+ [K in keyof T]: T[K] extends object ? PermissionValues : T[K];
+ }[keyof T];
+
+ export type ApiPermission = PermissionValues;
+
+ export const API_PERMISSIONS = {} as const;
+ ",
+ ];
+
+ let fail = vec![
+ "type T = number; function foo(a: T): T { return a as T }; foo(1)",
+ "type A = number; type B = A; console.log(3 as B<3>)",
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("oxc-type-references")
+ .test_and_snapshot();
+}
+
+// #[test]
+// fn test_template() {
+// let pass = vec![];
+
+// let fail = vec![];
+
+// Tester::new(NoUnusedVars::NAME, pass, fail)
+// .with_snapshot_suffix("")
+// .test_and_snapshot();
+// }
diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/typescript_eslint.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/typescript_eslint.rs
new file mode 100644
index 0000000000000..bc3f15eb2eb68
--- /dev/null
+++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/typescript_eslint.rs
@@ -0,0 +1,1910 @@
+use serde_json::json;
+
+use super::NoUnusedVars;
+use crate::{tester::Tester, RuleMeta as _};
+
+// TODO: port these over. I (@DonIsaac) would love some help with this...
+
+#[test]
+fn test() {
+ let pass = vec![
+ (
+ "
+ import { ClassDecoratorFactory } from 'decorators';
+ @ClassDecoratorFactory()
+ export class Foo {}
+ ",
+ None,
+ ),
+ (
+ "
+ import { ClassDecorator } from 'decorators';
+ @ClassDecorator
+ export class Foo {}
+ ",
+ None,
+ ),
+ (
+ "
+ import { AccessorDecoratorFactory } from 'decorators';
+ export class Foo {
+ @AccessorDecoratorFactory(true)
+ get bar() {}
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { AccessorDecorator } from 'decorators';
+ export class Foo {
+ @AccessorDecorator
+ set bar() {}
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { MethodDecoratorFactory } from 'decorators';
+ export class Foo {
+ @MethodDecoratorFactory(false)
+ bar() {}
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { MethodDecorator } from 'decorators';
+ export class Foo {
+ @MethodDecorator
+ static bar() {}
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { ConstructorParameterDecoratorFactory } from 'decorators';
+ export class Service {
+ constructor(
+ @ConstructorParameterDecoratorFactory(APP_CONFIG) config: AppConfig,
+ ) {
+ this.title = config.title;
+ }
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { ConstructorParameterDecorator } from 'decorators';
+ export class Foo {
+ constructor(@ConstructorParameterDecorator bar) {
+ this.bar = bar;
+ }
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { ParameterDecoratorFactory } from 'decorators';
+ export class Qux {
+ bar(@ParameterDecoratorFactory(true) baz: number) {
+ console.log(baz);
+ }
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { ParameterDecorator } from 'decorators';
+ export class Foo {
+ static greet(@ParameterDecorator name: string) {
+ return name;
+ }
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { Input, Output, EventEmitter } from 'decorators';
+ export class SomeComponent {
+ @Input() data;
+ @Output()
+ click = new EventEmitter();
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { configurable } from 'decorators';
+ export class A {
+ @configurable(true) static prop1;
+
+ @configurable(false)
+ static prop2;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { foo, bar } from 'decorators';
+ export class B {
+ @foo x;
+
+ @bar
+ y;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ interface Base {}
+ class Thing implements Base {}
+ new Thing();
+ ",
+ None,
+ ),
+ (
+ "
+ interface Base {}
+ const a: Base = {};
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Foo } from 'foo';
+ function bar(): T {}
+ bar();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Foo } from 'foo';
+ const bar = function (): T {};
+ bar();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Foo } from 'foo';
+ const bar = (): T => {};
+ bar();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Foo } from 'foo';
+ ((): T => {})();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ const a: Nullable = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'other';
+ const a: Nullable = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ const a: Nullable | undefined = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ const a: Nullable & undefined = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'other';
+ const a: Nullable = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'other';
+ const a: Nullable> = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ const a: Array = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ const a: Nullable[] = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ const a: Array = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ const a: Array> = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'other';
+ const a: Array> = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { Component } from 'react';
+ class Foo implements Component {}
+
+ new Foo();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { Component } from 'react';
+ class Foo extends Component {}
+ new Foo();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'some';
+ import { Component } from 'react';
+ class Foo extends Component, {}> {}
+ new Foo();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'some';
+ import { Component } from 'react';
+ class Foo implements Component, {}> {}
+ new Foo();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'some';
+ import { Component, Component2 } from 'react';
+ class Foo implements Component, {}>, Component2 {}
+ new Foo();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { Another } from 'some';
+ class A {
+ do = (a: Nullable) => {
+ console.log(a);
+ };
+ }
+ new A();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { Another } from 'some';
+ class A {
+ do(a: Nullable) {
+ console.log(a);
+ }
+ }
+ new A();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { Another } from 'some';
+ class A {
+ do(): Nullable {
+ return null;
+ }
+ }
+ new A();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { Another } from 'some';
+ export interface A {
+ do(a: Nullable);
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { Another } from 'some';
+ export interface A {
+ other: Nullable;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ function foo(a: Nullable) {
+ console.log(a);
+ }
+ foo();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ function foo(): Nullable {
+ return null;
+ }
+ foo();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'some';
+ class A extends Nullable {
+ other: Nullable;
+ }
+ new A();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'some';
+ import { Another } from 'some';
+ class A extends Nullable {
+ do(a: Nullable) {
+ console.log(a);
+ }
+ }
+ new A();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'some';
+ import { Another } from 'some';
+ export interface A extends Nullable {
+ other: Nullable;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'some';
+ import { Another } from 'some';
+ export interface A extends Nullable {
+ do(a: Nullable);
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { Foo } from './types';
+
+ class Bar {
+ prop: T;
+ }
+
+ new Bar();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Foo, Bar } from './types';
+
+ class Baz {
+ prop: T;
+ }
+
+ new Baz();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Foo } from './types';
+
+ class Bar {
+ prop: T;
+ }
+
+ new Bar();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Foo } from './types';
+
+ class Foo {
+ prop: T;
+ }
+
+ new Foo();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Foo } from './types';
+
+ class Foo {
+ prop: T;
+ }
+
+ new Foo();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Foo } from './types';
+
+ class Foo {
+ prop: T;
+ }
+
+ new Foo();
+ ",
+ None,
+ ),
+ // FIXME
+ (
+ "
+ type Foo = 'a' | 'b' | 'c';
+ type Bar = number;
+
+ export const map: { [name in Foo]: Bar } = {
+ a: 1,
+ b: 2,
+ c: 3,
+ };
+ ",
+ None,
+ ),
+ // 4.1 remapped mapped type
+ (
+ "
+ type Foo = 'a' | 'b' | 'c';
+ type Bar = number;
+
+ export const map: { [name in Foo as string]: Bar } = {
+ a: 1,
+ b: 2,
+ c: 3,
+ };
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ class A {
+ bar: T;
+ }
+ new A();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'other';
+ function foo(): T {}
+ foo();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'other';
+ class A {
+ bar: T;
+ }
+ new A();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'other';
+ interface A {
+ bar: T;
+ }
+ export const a: A = {
+ foo: 'bar',
+ };
+ ",
+ None,
+ ),
+ // https://github.com/bradzacher/eslint-plugin-typescript/issues/150
+ (
+ "
+ export class App {
+ constructor(private logger: Logger) {
+ console.log(this.logger);
+ }
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ export class App {
+ constructor(bar: string);
+ constructor(private logger: Logger) {
+ console.log(this.logger);
+ }
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ export class App {
+ constructor(
+ baz: string,
+ private logger: Logger,
+ ) {
+ console.log(baz);
+ console.log(this.logger);
+ }
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ export class App {
+ constructor(
+ baz: string,
+ private logger: Logger,
+ private bar: () => void,
+ ) {
+ console.log(this.logger);
+ this.bar();
+ }
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ export class App {
+ constructor(private logger: Logger) {}
+ meth() {
+ console.log(this.logger);
+ }
+ }
+ ",
+ None,
+ ),
+ // https://github.com/bradzacher/eslint-plugin-typescript/issues/126
+ (
+ "
+ import { Component, Vue } from 'vue-property-decorator';
+ import HelloWorld from './components/HelloWorld.vue';
+
+ @Component({
+ components: {
+ HelloWorld,
+ },
+ })
+ export default class App extends Vue {}
+ ",
+ None,
+ ),
+ // https://github.com/bradzacher/eslint-plugin-typescript/issues/189
+ (
+ "
+ import firebase, { User } from 'firebase/app';
+ // initialize firebase project
+ firebase.initializeApp({});
+ export function authenticated(cb: (user: User | null) => void): void {
+ firebase.auth().onAuthStateChanged(user => cb(user));
+ }
+ ",
+ None,
+ ),
+ // https://github.com/bradzacher/eslint-plugin-typescript/issues/33
+ (
+ "
+ import { Foo } from './types';
+ export class Bar {
+ prop: T;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import webpack from 'webpack';
+ export default function webpackLoader(this: webpack.loader.LoaderContext) {}
+ ",
+ None,
+ ),
+ (
+ "
+ import execa, { Options as ExecaOptions } from 'execa';
+ export function foo(options: ExecaOptions): execa {
+ options();
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { Foo, Bar } from './types';
+ export class Baz {
+ prop: F;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ // warning 'B' is defined but never used
+ export const a: Array<{ b: B }> = [];
+ ",
+ None,
+ ),
+ (
+ "
+ export enum FormFieldIds {
+ PHONE = 'phone',
+ EMAIL = 'email',
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ enum FormFieldIds {
+ PHONE = 'phone',
+ EMAIL = 'email',
+ }
+ export interface IFoo {
+ fieldName: FormFieldIds;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ enum FormFieldIds {
+ PHONE = 'phone',
+ EMAIL = 'email',
+ }
+ export interface IFoo {
+ fieldName: FormFieldIds.EMAIL;
+ }
+ ",
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/25
+ (
+ "
+ import * as fastify from 'fastify';
+ import { Server, IncomingMessage, ServerResponse } from 'http';
+ const server: fastify.FastifyInstance =
+ fastify({});
+ server.get('/ping');
+ ",
+ None,
+ ),
+ // FIXME
+ // https://github.com/typescript-eslint/typescript-eslint/issues/61
+ // (
+ // "
+ // declare namespace Foo {
+ // function bar(line: string, index: number | null, tabSize: number): number;
+ // var baz: string;
+ // }
+ // console.log(Foo);
+ // ",
+ // None,
+ // ),
+ (
+ "
+ import foo from 'foo';
+ export interface Bar extends foo.i18n {}
+ ",
+ None,
+ ),
+ (
+ "
+ import foo from 'foo';
+ import bar from 'foo';
+ export interface Bar extends foo.i18n {}
+ ",
+ None,
+ ),
+ // https://github.com/eslint/typescript-eslint-parser/issues/535
+ (
+ "
+ import { observable } from 'mobx';
+ export default class ListModalStore {
+ @observable
+ orderList: IObservableArray = observable([]);
+ }
+ ",
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/122#issuecomment-462008078
+ (
+ "
+ import { Dec, TypeA, Class } from 'test';
+ export default class Foo {
+ constructor(
+ @Dec(Class)
+ private readonly prop: TypeA,
+ ) {}
+ }
+ ",
+ None,
+ ),
+ // FIXME - parse error
+ // (
+ // "
+ // import { Dec, TypeA, Class } from 'test';
+ // export default class Foo {
+ // constructor(
+ // @Dec(Class)
+ // ...prop: TypeA
+ // ) {
+ // prop();
+ // }
+ // }
+ // ",
+ // None,
+ // ),
+ (
+ "
+ export function foo(): void;
+ export function foo(): void;
+ export function foo(): void {}
+ ",
+ None,
+ ),
+ (
+ "
+ export function foo(a: number): number;
+ export function foo(a: string): string;
+ export function foo(a: number | string): number | string {
+ return a;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ export function foo(a: number): T;
+ export function foo(a: string): T;
+ export function foo(a: number | string): T {
+ return a;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ export type T = {
+ new (): T;
+ new (arg: number): T;
+ new (arg: number): T;
+ };
+ ",
+ None,
+ ),
+ // NOTE: Parse error
+ // (
+ // "
+ // export type T = new () => T;
+ // export type T = new (arg: number) => T;
+ // export type T = new (arg: number) => T;
+ // ",
+ // None,
+ // ),
+ (
+ "
+ enum Foo {
+ a,
+ }
+ export type T = {
+ [Foo.a]: 1;
+ };
+ ",
+ None,
+ ),
+ (
+ "
+ type Foo = string;
+ export class Bar {
+ [x: Foo]: any;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ type Foo = string;
+ export class Bar {
+ [x: Foo]: Foo;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ namespace Foo {
+ export const Foo = 1;
+ }
+
+ export { Foo };
+ ",
+ None,
+ ),
+ (
+ "
+ export namespace Foo {
+ export const item: Foo = 1;
+ }
+ ",
+ None,
+ ),
+ // (
+ // "
+ // namespace foo.bar {
+ // export interface User {
+ // name: string;
+ // }
+ // }
+ // ",
+ // None,
+ // ),
+ // exported self-referencing types
+ (
+ "
+ export interface Foo {
+ bar: string;
+ baz: Foo['bar'];
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ export type Bar = Array;
+ ",
+ None,
+ ),
+ // declaration merging
+ (
+ "
+ function Foo() {}
+
+ namespace Foo {
+ export const x = 1;
+ }
+
+ export { Foo };
+ ",
+ None,
+ ),
+ (
+ "
+ class Foo {}
+
+ namespace Foo {
+ export const x = 1;
+ }
+
+ export { Foo };
+ ",
+ None,
+ ),
+ (
+ "
+ namespace Foo {}
+
+ const Foo = 1;
+
+ export { Foo };
+ ",
+ None,
+ ),
+ (
+ "
+ type Foo = {
+ error: Error | null;
+ };
+
+ export function foo() {
+ return new Promise();
+ }
+ ",
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/5152
+ (
+ "
+ function foo(value: T): T {
+ return { value };
+ }
+ export type Foo = typeof foo;
+ ",
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2331
+ (
+ "
+ export interface Event {
+ (
+ listener: (e: T) => any,
+ thisArgs?: any,
+ disposables?: Disposable[],
+ ): Disposable;
+ }
+ ",
+ Some(
+ json!( [{ "args": "after-used", "argsIgnorePattern": "^_", "ignoreRestSiblings": true, "varsIgnorePattern": "^_$"}] ),
+ ),
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2369
+ (
+ "
+ export class Test {
+ constructor(@Optional() value: number[] = []) {
+ console.log(value);
+ }
+ }
+
+ function Optional() {
+ return () => {};
+ }
+ ",
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2417
+ (
+ "
+ import { FooType } from './fileA';
+
+ export abstract class Foo {
+ protected abstract readonly type: FooType;
+ }
+ ",
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2449
+ (
+ "
+ export type F = (...a: A) => unknown;
+ ",
+ None,
+ ),
+ (
+ "
+ import { Foo } from './bar';
+ export type F = (...a: Foo) => unknown;
+ ",
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2452
+ (
+ r"
+ type StyledPaymentProps = {
+ isValid: boolean;
+ };
+
+ export const StyledPayment = styled.div``;
+ ",
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2453
+ (
+ "
+ import type { foo } from './a';
+ export type Bar = typeof foo;
+ ",
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2459
+ (
+ "
+ export type Test = U extends (k: infer I) => void ? I : never;
+ ",
+ None,
+ ),
+ (
+ "
+ export type Test = U extends { [k: string]: infer I } ? I : never;
+ ",
+ None,
+ ),
+ (
+ "
+ export type Test = U extends (arg: {
+ [k: string]: (arg2: infer I) => void;
+ }) => void
+ ? I
+ : never;
+ ",
+ None,
+ ),
+ // (
+ // "
+ // declare module 'foo' {
+ // type Test = 1;
+ // }
+ // ",
+ // None,
+ // ),
+ (
+ "
+ declare module 'foo' {
+ type Test = 1;
+ const x: Test = 1;
+ export = x;
+ }
+ ",
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2523
+ (
+ "
+ declare global {
+ interface Foo {}
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ declare global {
+ namespace jest {
+ interface Matchers {
+ toBeSeven: () => R;
+ }
+ }
+ }
+ ",
+ None,
+ ),
+ // (
+ // "
+ // export declare namespace Foo {
+ // namespace Bar {
+ // namespace Baz {
+ // namespace Bam {
+ // const x = 1;
+ // }
+ // }
+ // }
+ // }
+ // ",
+ // None,
+ // ),
+ (
+ "
+ class Foo {
+ value: T;
+ }
+ class Bar {
+ foo = Foo;
+ }
+ new Bar();
+ ",
+ None,
+ ),
+ // // 4.1 template literal types
+ (
+ r"
+ type Color = 'red' | 'blue';
+ type Quantity = 'one' | 'two';
+ export type SeussFish = `${Quantity | Color} fish`;
+ ",
+ None,
+ ),
+ (
+ r#"
+ type VerticalAlignment = "top" | "middle" | "bottom";
+ type HorizontalAlignment = "left" | "center" | "right";
+
+ export declare function setAlignment(value: `${VerticalAlignment}-${HorizontalAlignment}`): void;
+ "#,
+ None,
+ ),
+ (
+ r#"
+ type EnthusiasticGreeting = `${Uppercase} - ${Lowercase} - ${Capitalize} - ${Uncapitalize}`;
+ export type HELLO = EnthusiasticGreeting<"heLLo">;"#,
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2648
+ // ignored by pattern, even though it's only self-referenced
+ (
+ "
+ namespace _Foo {
+ export const bar = 1;
+ export const baz = Foo.bar;
+ }",
+ Some(json!([{ "varsIgnorePattern": "^_" }])),
+ ),
+ (
+ "
+ interface _Foo {
+ a: string;
+ b: _Foo;
+ }
+ ",
+ Some(json!([{ "varsIgnorePattern": "^_" }])),
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2844
+ (
+ r#"
+ /* eslint collect-unused-vars: "error" */
+ declare module 'next-auth' {
+ interface User {
+ id: string;
+ givenName: string;
+ familyName: string;
+ }
+ }
+ "#,
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2972
+ // (
+ // "
+
+ // import { TestGeneric, Test } from 'fake-module';
+
+ // declare function deco(..._param: any): any;
+ // export class TestClass {
+ // @deco
+ // public test(): TestGeneric {}
+ // }
+ // ",
+ // None,
+ // ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/5577
+ (
+ "
+ function foo() {}
+
+ export class Foo {
+ constructor() {
+ foo();
+ }
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ function foo() {}
+
+ export class Foo {
+ static {}
+
+ constructor() {
+ foo();
+ }
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ interface Foo {
+ bar: string;
+ }
+ export const Foo = 'bar';
+ ",
+ None,
+ ),
+ (
+ "
+ export const Foo = 'bar';
+ interface Foo {
+ bar: string;
+ }
+ ",
+ None,
+ ),
+ // NOTE: intentional behavior change
+ // (
+ // "
+ // let foo = 1;
+ // foo ??= 2;
+ // ",
+ // None,
+ // ),
+ // (
+ // "
+ // let foo = 1;
+ // foo &&= 2;
+ // ",
+ // None,
+ // ),
+ // (
+ // "
+ // let foo = 1;
+ // foo ||= 2;
+ // ",
+ // None,
+ // ),
+ (
+ "
+ const foo = 1;
+ export = foo;
+ ",
+ None,
+ ),
+ (
+ "
+ const Foo = 1;
+ interface Foo {
+ bar: string;
+ }
+ export = Foo;
+ ",
+ None,
+ ),
+ (
+ "
+ interface Foo {
+ bar: string;
+ }
+ export = Foo;
+ ",
+ None,
+ ),
+ (
+ "
+ type Foo = 1;
+ export = Foo;
+ ",
+ None,
+ ),
+ (
+ "
+ type Foo = 1;
+ export = {} as Foo;
+ ",
+ None,
+ ),
+ (
+ "
+ declare module 'foo' {
+ type Foo = 1;
+ export = Foo;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ namespace Foo {
+ export const foo = 1;
+ }
+ export namespace Bar {
+ export import TheFoo = Foo;
+ }
+ ",
+ None,
+ ),
+ ];
+
+ let fail = vec![
+ ("import { ClassDecoratorFactory } from 'decorators'; export class Foo {}", None),
+ (
+ "import { Foo, Bar } from 'foo';
+ function baz(): Foo {}
+ baz();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ const a: string = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'other';
+ const a: Nullable = 'hello';
+ console.log(a);
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { Another } from 'some';
+ class A {
+ do = (a: Nullable) => {
+ console.log(a);
+ };
+ }
+ new A();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { Another } from 'some';
+ class A {
+ do(a: Nullable) {
+ console.log(a);
+ }
+ }
+ new A();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { Another } from 'some';
+ class A {
+ do(): Nullable {
+ return null;
+ }
+ }
+ new A();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { Another } from 'some';
+ export interface A {
+ do(a: Nullable);
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { Another } from 'some';
+ export interface A {
+ other: Nullable;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ function foo(a: string) {
+ console.log(a);
+ }
+ foo();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ function foo(): string | null {
+ return null;
+ }
+ foo();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'some';
+ import { Another } from 'some';
+ class A extends Nullable {
+ other: Nullable;
+ }
+ new A();
+ ",
+ None,
+ ),
+ (
+ "
+ import { Nullable } from 'nullable';
+ import { SomeOther } from 'some';
+ import { Another } from 'some';
+ abstract class A extends Nullable {
+ other: Nullable;
+ }
+ new A();
+ ",
+ None,
+ ),
+ (
+ "
+ enum FormFieldIds {
+ PHONE = 'phone',
+ EMAIL = 'email',
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ import test from 'test';
+ import baz from 'baz';
+ export interface Bar extends baz.test {}
+ ",
+ None,
+ ),
+ (
+ "
+ import test from 'test';
+ import baz from 'baz';
+ export interface Bar extends baz.test {}
+ ",
+ None,
+ ),
+ (
+ "
+ import test from 'test';
+ import baz from 'baz';
+ export interface Bar extends baz().test {}
+ ",
+ None,
+ ),
+ (
+ "
+ import test from 'test';
+ import baz from 'baz';
+ export class Bar implements baz.test {}
+ ",
+ None,
+ ),
+ (
+ "
+ import test from 'test';
+ import baz from 'baz';
+ export class Bar implements baz.test {}
+ ",
+ None,
+ ),
+ // NOTE: parse error
+ // (
+ // "
+ // import test from 'test';
+ // import baz from 'baz';
+ // export class Bar implements baz().test {}
+ // ",
+ // None,
+ // ),
+ ("namespace Foo {}", None),
+ ("namespace Foo { export const Foo = 1; } ", None),
+ (
+ "
+ namespace Foo {
+ const Foo = 1;
+ console.log(Foo);
+ }
+ ",
+ None,
+ ),
+ // (
+ // "
+ // namespace Foo {
+ // export const Bar = 1;
+ // console.log(Foo.Bar);
+ // }
+ // ",
+ // None,
+ // ),
+ (
+ "
+ namespace Foo {
+ namespace Foo {
+ export const Bar = 1;
+ console.log(Foo.Bar);
+ }
+ }
+ ",
+ None,
+ ),
+ // self-referencing types
+ // (
+ // "
+ // interface Foo {
+ // bar: string;
+ // baz: Foo['bar'];
+ // }
+ // ",
+ // None,
+ // ),
+ // FIXME
+ ("type Foo = Array", None),
+ (
+ "
+ declare module 'foo' {
+ type Test = any;
+ const x = 1;
+ export = x;
+ }
+ ",
+ None,
+ ),
+ (
+ "
+ // not declared
+ export namespace Foo {
+ namespace Bar {
+ namespace Baz {
+ namespace Bam {
+ const x = 1;
+ }
+ }
+ }
+ }
+ ",
+ None,
+ ),
+ // (
+ // "
+ // interface Foo {
+ // a: string;
+ // }
+ // interface Foo {
+ // b: Foo;
+ // }
+ // ",
+ // None,
+ // ),
+ // ("let x = null; x = foo(x);", None),
+ (
+ "
+ interface Foo {
+ bar: string;
+ }
+ const Foo = 'bar';
+ ",
+ None,
+ ),
+ (
+ "
+ let foo = 1;
+ foo += 1;
+ ",
+ None,
+ ),
+ (
+ "
+ interface Foo {
+ bar: string;
+ }
+ type Bar = 1;
+ export = Bar;
+ ",
+ None,
+ ),
+ (
+ "
+ interface Foo {
+ bar: string;
+ }
+ type Bar = 1;
+ export = Foo;
+ ",
+ None,
+ ),
+ (
+ "
+ namespace Foo {
+ export const foo = 1;
+ }
+ export namespace Bar {
+ import TheFoo = Foo;
+ },
+ ",
+ None,
+ ),
+ ("const foo: number = 1;", None),
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .change_rule_path_extension("ts")
+ .with_snapshot_suffix("typescript-eslint")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_tsx() {
+ let pass = vec![
+ // https://github.com/typescript-eslint/typescript-eslint/issues/141
+ (
+ "
+ import { TypeA } from './interface';
+ export const a = />;
+ ",
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/160
+ (
+ r#"
+ const text = 'text';
+ export function Foo() {
+ return (
+
+
+
+ );
+ }"#,
+ None,
+ ),
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2455
+ (
+ "
+ import React from 'react';
+
+ export const ComponentFoo: React.FC = () => {
+ return Foo Foo
;
+ };
+ ",
+ None,
+ ),
+ // FIXME: Support JSX pragmas
+ // parserOptions: {
+ // ecmaFeatures: {
+ // jsx: true,
+ // },
+ // jsxPragma: 'h',
+ // },
+ (
+ "
+ import { h } from 'some-other-jsx-lib';
+
+ export const ComponentFoo: h.FC = () => {
+ return Foo Foo
;
+ };
+ ",
+ None,
+ ),
+ // NOTE: I'm not sure why this passes, but it does. I don't see any
+ // implicit references to `Fragment` in semantic, but I won't look a
+ // gift horse in the mouth.
+ // parserOptions: {
+ // ecmaFeatures: {
+ // jsx: true,
+ // },
+ // jsxFragmentName: 'Fragment',
+ // },
+ (
+ "
+ import { Fragment } from 'react';
+
+ export const ComponentFoo: Fragment = () => {
+ return <>Foo Foo>;
+ };
+ ",
+ None,
+ ),
+ ];
+
+ let fail = vec![
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2455
+ (
+ "
+ import React from 'react';
+ import { Fragment } from 'react';
+
+ export const ComponentFoo = () => {
+ return Foo Foo
;
+ };
+ ",
+ None,
+ ),
+ // {
+ // code: `
+ // import React from 'react';
+ // import { Fragment } from 'react';
+
+ // export const ComponentFoo = () => {
+ // return Foo Foo
;
+ // };
+ // `,
+ // parserOptions: {
+ // ecmaFeatures: {
+ // jsx: true,
+ // },
+ // },
+ // errors: [
+ // {
+ // messageId: 'unusedVar',
+ // line: 3,
+ // column: 10,
+ // data: {
+ // varName: 'Fragment',
+ // action: 'defined',
+ // additional: '',
+ // },
+ // },
+ // ],
+ // },
+ // {
+ // code: `
+ // import React from 'react';
+ // import { h } from 'some-other-jsx-lib';
+
+ // export const ComponentFoo = () => {
+ // return Foo Foo
;
+ // };
+ // `,
+ // parserOptions: {
+ // ecmaFeatures: {
+ // jsx: true,
+ // },
+ // jsxPragma: 'h',
+ // },
+ // errors: [
+ // {
+ // messageId: 'unusedVar',
+ // line: 2,
+ // column: 8,
+ // data: {
+ // varName: 'React',
+ // action: 'defined',
+ // additional: '',
+ // },
+ // },
+ // ],
+ // },
+
+ // https://github.com/typescript-eslint/typescript-eslint/issues/3303
+ // (
+ // "
+ // import React from 'react';
+
+ // export const ComponentFoo = () => {
+ // return Foo Foo
;
+ // };
+ // ",
+ // None,
+ // ),
+ // parserOptions: {
+ // ecmaFeatures: {
+ // jsx: true,
+ // },
+ // jsxPragma: null,
+ // },
+ ];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail)
+ .with_snapshot_suffix("typescript-eslint-tsx")
+ .test_and_snapshot();
+}
+
+#[test]
+fn test_d_ts() {
+ let pass = vec![
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2456
+ "
+ interface Foo {}
+ type Bar = {};
+ declare class Clazz {}
+ declare function func();
+ declare enum Enum {}
+ declare namespace Name {}
+ declare const v1 = 1;
+ declare var v2 = 1;
+ declare let v3 = 1;
+ declare const { v4 };
+ declare const { v4: v5 };
+ declare const [v6];
+ ",
+ "
+ declare namespace A {
+ export interface A {}
+ }
+ ",
+ "declare function A(A: string): string;",
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2714
+ "
+ interface IItem {
+ title: string;
+ url: string;
+ children?: IItem[];
+ }
+ ",
+ ];
+ let fail = vec![];
+
+ Tester::new(NoUnusedVars::NAME, pass, fail).change_rule_path_extension("d.ts").test();
+}
diff --git a/crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs b/crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs
new file mode 100644
index 0000000000000..e160ccec73419
--- /dev/null
+++ b/crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs
@@ -0,0 +1,696 @@
+//! This module contains logic for checking if any [`Reference`]s to a
+//! [`Symbol`] are considered a usage.
+
+#[allow(clippy::wildcard_imports)]
+use oxc_ast::{ast::*, AstKind};
+use oxc_semantic::{AstNode, AstNodeId, Reference, ScopeId, SymbolFlags, SymbolId};
+use oxc_span::{GetSpan, Span};
+
+use super::{ignored::FoundStatus, NoUnusedVars, Symbol};
+
+impl<'s, 'a> Symbol<'s, 'a> {
+ /// 1. Imported functions will never have calls to themselves within their
+ /// own declaration since they are declared outside the current module
+ /// 2. Catch variables are always parameter-like and will therefore never have
+ /// a function declaration.
+ #[inline]
+ const fn is_maybe_callable(&self) -> bool {
+ const IMPORT: SymbolFlags = SymbolFlags::Import.union(SymbolFlags::TypeImport);
+ // note: intetionally do not use `SymbolFlags::is_type` here, since that
+ // can return `true` for values
+ const TYPE: SymbolFlags = SymbolFlags::TypeAlias
+ .union(SymbolFlags::TypeLiteral)
+ .union(SymbolFlags::TypeParameter)
+ .union(SymbolFlags::Interface);
+ const ENUM: SymbolFlags = SymbolFlags::Enum.union(SymbolFlags::EnumMember);
+ const NAMESPACE_LIKE: SymbolFlags =
+ SymbolFlags::NameSpaceModule.union(SymbolFlags::ValueModule);
+
+ !self.flags().intersects(
+ IMPORT.union(TYPE).union(ENUM).union(NAMESPACE_LIKE).union(SymbolFlags::CatchVariable),
+ )
+ }
+
+ /// Note: we still need to check for reassignments to const variables since
+ /// eslint's original rule requires it. Const reassignments are not a syntax
+ /// error in JavaScript, only TypeScript.
+ #[inline]
+ const fn is_possibly_reassignable(&self) -> bool {
+ self.flags().intersects(SymbolFlags::Variable)
+ }
+
+ /// Check if this [`Symbol`] is definitely reassignable.
+ ///
+ /// Examples of non-reassignable symbols are:
+ /// - function declarations
+ /// - classes
+ /// - enums
+ /// - types (interfaces, type aliases)
+ /// - const variables
+ /// - imports
+ ///
+ /// Examples of reassinable symbols are:
+ /// - `var` and `let` variable declarations
+ /// - function parameters
+ #[inline]
+ const fn is_definitely_reassignable_variable(&self) -> bool {
+ let f = self.flags();
+ f.intersects(SymbolFlags::Variable)
+ && !f.contains(SymbolFlags::ConstVariable.union(SymbolFlags::Function))
+ }
+
+ #[inline]
+ const fn is_type_alias(&self) -> bool {
+ self.flags().contains(SymbolFlags::TypeAlias)
+ }
+
+ /// Check if this [`Symbol`] has an [`Reference`]s that are considered a usage.
+ pub fn has_usages(&self, options: &NoUnusedVars) -> bool {
+ // Use symbol flags to skip the usage checks we are certain don't need
+ // to be run.
+ let do_reassignment_checks = self.is_possibly_reassignable();
+ let do_type_self_usage_checks = self.is_type_alias();
+ let do_self_call_check = self.is_maybe_callable();
+ let do_discarded_read_checks = self.is_definitely_reassignable_variable();
+
+ for reference in self.references() {
+ // Resolved references should always contain the id of the symbol
+ // they are referencing. By making this an assertion instead of a
+ // debug assertion, the rust compiler can optimize away None checks
+ // performed down the line.
+ assert!(
+ reference.symbol_id().is_some(),
+ "Resolved reference to symbol {:?} is missing a symbol id",
+ self.id()
+ );
+ assert!(reference.symbol_id().is_some_and(|id| id == self.id()));
+
+ // Write usage checks
+ if reference.is_write() {
+ if do_reassignment_checks
+ && (self.is_assigned_to_ignored_destructure(reference, options)
+ || self.is_used_in_for_of_loop(reference))
+ {
+ return true;
+ }
+
+ // references can be both reads & writes. If this is only a
+ // write, we don't need to perform any read usage checks.
+ if !reference.is_read() {
+ continue;
+ }
+ }
+
+ // Type usage checks
+ if reference.is_type() {
+ // e.g. `type Foo = Array`
+ if do_type_self_usage_checks && self.is_type_self_usage(reference) {
+ continue;
+ }
+ return true;
+ }
+
+ // Read usage checks
+
+ // e.g. `let a = 0; a = a + 1`
+ if do_reassignment_checks && self.is_self_reassignment(reference) {
+ continue;
+ }
+
+ // e.g. reference on `a` in expression `let a = 0; let b = (a++, 0);`
+ if do_discarded_read_checks && self.is_discarded_read(reference) {
+ continue;
+ }
+
+ // e.g. `function foo() { foo() }`
+ if do_self_call_check && self.is_self_call(reference) {
+ continue;
+ }
+
+ return true;
+ }
+
+ false
+ }
+
+ /// Checks for references within for..in and for..of conditions (not
+ /// bodies). These are always considered usages since their removal would
+ /// introduce syntax and/or semantic errors.
+ ///
+ /// ## Examples
+ /// ```ts
+ /// // should return true
+ /// var a;
+ /// for (a in obj) {}
+ /// for (a of iter) {}
+ ///
+ /// // should return false
+ /// var b;
+ /// for (let a in obj) { fn(b) }
+ /// for (let a of iter) { fn(b) }
+ /// ```
+ fn is_used_in_for_of_loop(&self, reference: &Reference) -> bool {
+ for parent in self.nodes().iter_parents(reference.node_id()) {
+ match parent.kind() {
+ AstKind::ParenthesizedExpression(_)
+ | AstKind::IdentifierReference(_)
+ | AstKind::SimpleAssignmentTarget(_)
+ | AstKind::AssignmentTarget(_) => continue,
+ AstKind::ForInStatement(ForInStatement { body, .. })
+ | AstKind::ForOfStatement(ForOfStatement { body, .. }) => match body {
+ Statement::ReturnStatement(_) => return true,
+ Statement::BlockStatement(b) => {
+ return b
+ .body
+ .first()
+ .is_some_and(|s| matches!(s, Statement::ReturnStatement(_)))
+ }
+ _ => return false,
+ },
+ _ => return false,
+ }
+ }
+
+ false
+ }
+
+ /// Does this variable have a name that is ignored by the destructuring
+ /// pattern, and is also assigned inside a destructure?
+ ///
+ /// ```ts
+ /// let a, _b;
+ /// [a, _b] = [1, 2];
+ /// // ^^ this should be ignored
+ ///
+ /// console.log(a)
+ /// ```
+ fn is_assigned_to_ignored_destructure(
+ &self,
+ reference: &Reference,
+ options: &NoUnusedVars,
+ ) -> bool {
+ // Return early if no destructure ignores are configured.
+ if !options.should_search_destructures() {
+ return false;
+ }
+
+ for parent in self.nodes().iter_parents(reference.node_id()).map(AstNode::kind) {
+ match parent {
+ AstKind::IdentifierReference(_)
+ | AstKind::SimpleAssignmentTarget(_)
+ | AstKind::AssignmentTarget(_) => continue,
+ AstKind::AssignmentExpression(assignment) => {
+ return options.is_ignored_assignment_target(self, &assignment.left);
+ }
+ // Needs to be checked separately from AssignmentTarget due to
+ // weird heritage bug for object assignment patterns.
+ // when iterating over parents, after an
+ // ObjectAssignmentTarget, the next parent will be the rest
+ // expression instead of the top-level AssignmentTarget
+ AstKind::ObjectAssignmentTarget(obj) => {
+ match options.search_obj_assignment_target(self, obj) {
+ FoundStatus::Ignored => return true,
+ FoundStatus::NotIgnored => return false,
+ FoundStatus::NotFound => continue,
+ }
+ }
+ AstKind::ArrayAssignmentTarget(arr) => {
+ match options.search_array_assignment_target(self, arr) {
+ FoundStatus::Ignored => return true,
+ FoundStatus::NotIgnored => return false,
+ FoundStatus::NotFound => continue,
+ }
+ }
+ _ => {
+ return false;
+ }
+ }
+ }
+ false
+ }
+
+ /// Checks for self-usages in type declarations.
+ ///
+ /// ## Examples
+ ///
+ /// ```ts
+ /// // should return true
+ /// type Foo = Foo
+ /// type Foo = Array
+ /// type Unbox = B extends Box ? Unbox : B
+ ///
+ /// // should return false
+ /// type Foo = Bar
+ /// type Foo = Array
+ /// ```
+ fn is_type_self_usage(&self, reference: &Reference) -> bool {
+ for parent in self.iter_relevant_parents(reference.node_id()).map(AstNode::kind) {
+ match parent {
+ AstKind::TSTypeAliasDeclaration(decl) => {
+ return self == &decl.id;
+ }
+ // definitely not within a type alias, we can be sure this isn't
+ // a self-usage. Safe CPU cycles by breaking early.
+ AstKind::CallExpression(_)
+ | AstKind::BinaryExpression(_)
+ | AstKind::Function(_)
+ | AstKind::Class(_)
+ | AstKind::TSInterfaceDeclaration(_)
+ | AstKind::TSModuleDeclaration(_)
+ | AstKind::VariableDeclaration(_)
+ | AstKind::VariableDeclarator(_)
+ | AstKind::ExportNamedDeclaration(_)
+ | AstKind::ExportDefaultDeclaration(_)
+ | AstKind::ExportAllDeclaration(_)
+ | AstKind::Program(_) => {
+ return false;
+ }
+
+ _ => continue,
+ }
+ }
+ false
+ }
+
+ /// Checks if a read reference is only ever used to modify itself.
+ ///
+ /// ## Algorithm
+ /// This algorithm is a little confusing, so here's how it works:
+ ///
+ /// A reference can be a self reassignment that is used by others or not.
+ /// For example:
+ /// ```ts
+ /// let a = 0; a = a + 1
+ /// // ^^^^^^^^^ self reassignment, only used by itself.
+ /// let a = 0, b = 0; b = a = a + 1
+ /// // ^^^^^^^^^ self reassignment, but used by another variable.
+ /// ```
+ ///
+ /// Initially, all references are assumed to be used by others. This allows
+ /// for code like `let a = 0; a`, but bans code like `let a = 0; a++`;
+ ///
+ /// - We encounter a node proving that the reference is absolutely used by
+ /// another variable, we return `false` immediately.
+ /// - When we encounter an AST node that updates the value of the symbol this
+ /// reference is for, such as an [`AssignmentExpression`] with the symbol on
+ /// the LHS or a mutating [`UnaryExpression`], we mark the reference as not
+ /// being used by others.
+ /// - When we encounter a node where we are sure the value produced by an
+ /// expression will no longer be used, such as an [`ExpressionStatement`],
+ /// we end our search. This is because expression statements produce a
+ /// value and then discard it. In these cases, we return `true` if the
+ /// reference was not used by others, or `false` if it was.
+ ///
+ /// ## Examples
+ /// ```
+ /// let a = 0;
+ /// // should return true
+ /// a++;
+ /// a = a + 1;
+ /// a ||= 1;
+ ///
+ /// // should return false
+ /// let b = a;
+ /// if (a++) {}
+ /// function f() { return a }
+ /// ```
+ fn is_self_reassignment(&self, reference: &Reference) -> bool {
+ if reference.symbol_id().is_none() {
+ debug_assert!(
+ false,
+ "is_self_reassignment() should only be called on resolved symbol references"
+ );
+ return true;
+ }
+
+ // Have we seen this reference be used to update the value of another
+ // symbol, or for some other logically-relevant purpose?
+ let mut is_used_by_others = true;
+ let name = self.name();
+ let ref_span = self.get_ref_span(reference);
+
+ for node in self.nodes().iter_parents(reference.node_id()).skip(1) {
+ match node.kind() {
+ // references used in declaration of another variable are definitely
+ // used by others
+ AstKind::VariableDeclarator(v) => {
+ // let a = a; is a static semantic error, even if `a` is shadowed.
+ debug_assert!(
+ v.id.kind.get_identifier().map_or_else(|| true, |id| id != name),
+ "While traversing {name}'s reference's parent nodes, found {name}'s declaration. This algorithm assumes that variable declarations do not appear in references."
+ );
+ // definitely used, short-circuit
+ return false;
+ }
+ // When symbol is being assigned a new value, we flag the reference
+ // as only affecting itself until proven otherwise.
+ AstKind::UpdateExpression(_) | AstKind::SimpleAssignmentTarget(_) => {
+ is_used_by_others = false;
+ }
+ // RHS usage when LHS != reference's symbol is definitely used by
+ // others
+ AstKind::AssignmentExpression(AssignmentExpression { left, .. }) => {
+ match left {
+ AssignmentTarget::AssignmentTargetIdentifier(id) => {
+ if id.name == name {
+ is_used_by_others = false;
+ } else {
+ return false; // we can short-circuit
+ }
+ }
+ // variable is being used to index another variable, this is
+ // always a usage
+ // todo: check self index?
+ match_member_expression!(AssignmentTarget) => return false,
+ _ => {}
+ }
+ }
+ // `if (i++ === 0) { /* ... */ }`
+ AstKind::IfStatement(IfStatement { test, .. })
+ | AstKind::WhileStatement(WhileStatement { test, .. })
+ | AstKind::DoWhileStatement(DoWhileStatement { test, .. })
+ if test.span().contains_inclusive(ref_span) =>
+ {
+ return false;
+ }
+
+ // expression is over, save cycles by breaking
+ // todo: do we need to check if variable is used as iterator in
+ // loops?
+ AstKind::ForInStatement(_)
+ | AstKind::ForOfStatement(_)
+ | AstKind::WhileStatement(_) => {
+ break;
+ }
+ AstKind::JSXExpressionContainer(_) | AstKind::Argument(_) => {
+ return false;
+ }
+ // this is needed to handle `return () => foo++`
+ AstKind::ExpressionStatement(_) => {
+ if self.is_in_return_statement(node.id()) {
+ return false;
+ }
+ break;
+ }
+ AstKind::Function(f) if f.is_declaration() => {
+ break;
+ }
+ // implicit return in an arrow function
+ AstKind::ArrowFunctionExpression(f)
+ if f.body.statements.len() == 1
+ && !self.get_snippet(f.body.span).starts_with('{') =>
+ {
+ return false;
+ }
+ AstKind::ReturnStatement(_) => {
+ match self.get_nearest_function(node.id()) {
+ // We're definitely in a function (assuming valid
+ // syntax) so that means we're in an anonymous function,
+ // which is definitely not the current symbol โด not the
+ // current symbol โด not a self-reassignment
+ None => return false,
+ // Is this a return within the same function being declared?
+ Some(id) => return self.id() == id,
+ };
+ }
+ // function* foo() {
+ // let a = 1;
+ // a = yield a // <- still considered used b/c it's propagated to the caller
+ // }
+ AstKind::YieldExpression(_) => return false,
+ _ => { /* continue up tree */ }
+ }
+ }
+
+ !is_used_by_others
+ }
+
+ /// Check if a [`AstNode`] is within a return statement or implicit return.
+ fn is_in_return_statement(&self, node_id: AstNodeId) -> bool {
+ for parent in self.iter_relevant_parents(node_id).map(AstNode::kind) {
+ match parent {
+ AstKind::ReturnStatement(_) => return true,
+ AstKind::ExpressionStatement(_) => continue,
+ AstKind::Function(f) if f.is_expression() => continue,
+ // note: intentionally not using
+ // ArrowFunctionExpression::get_expression since it returns
+ // `Some` even if
+ // 1. there are more than one statements
+ // 2. the expression is surrounded by braces
+ AstKind::ArrowFunctionExpression(f)
+ if f.body.statements.len() == 1
+ && !self.get_snippet(f.body.span).starts_with('{') =>
+ {
+ return true;
+ }
+ x if x.is_statement() => return false,
+ _ => continue,
+ }
+ }
+ false
+ }
+
+ /// Returns `true` for read references where we are confident the read is
+ /// discarded (and therefore never used). Right now, this is only covers
+ /// expressions within [`SequenceExpression`]s that are not in the last position.
+ ///
+ /// ```ts
+ /// let a = 0; let b = (a, 0); // a is discarded
+ /// let a = 1, b = 0; let c = (b = a, 0); // a is not discarded b/c it updates b
+ /// ```
+ ///
+ /// Maybe we'll eventually handle cases like this:
+ /// ```ts
+ /// let a = 0;
+ /// a; // not really used
+ /// ```
+ ///
+ /// but doing so requires us to know if a read has side effects, which we
+ /// can't do confidently without type information. For example, this read
+ /// calls a getter that mutates state:
+ ///
+ /// ```ts
+ /// global.x = 0;
+ /// let foo = {
+ /// get bar() {
+ /// global.x += 1;
+ /// return global.x;
+ /// }
+ /// };
+ ///
+ /// foo.bar;
+ /// ```
+ fn is_discarded_read(&self, reference: &Reference) -> bool {
+ for (parent, grandparent) in
+ self.iter_relevant_parent_and_grandparent_kinds(reference.node_id())
+ {
+ let ref_span = || self.get_ref_span(reference);
+
+ match (parent, grandparent) {
+ // (foo.bar = new Foo(a), f(b))
+ // `a` should not be considered discarded
+ // first branch happens when reference is a function call,
+ // second one happens when reference is an argument to a
+ // function call
+ (
+ AstKind::IdentifierReference(id),
+ AstKind::CallExpression(_) | AstKind::NewExpression(_),
+ ) => {
+ if id.span == ref_span() {
+ continue;
+ }
+ break;
+ }
+ (_, AstKind::CallExpression(_) | AstKind::NewExpression(_)) => break,
+ // (AstKind::FunctionBody(_), _) => return true,
+ // in `(x = a, 0)`, reference to `a` should still be considered
+ // used. Note that this branch must come before the sequence
+ // expression check.
+ (AstKind::AssignmentExpression(assignment), _) if self != &assignment.left => break,
+ (AstKind::ConditionalExpression(cond), _) => {
+ if cond.test.span().contains_inclusive(ref_span()) {
+ return false;
+ }
+ }
+ (parent, AstKind::SequenceExpression(seq)) => {
+ debug_assert!(
+ !seq.expressions.is_empty(),
+ "empty SequenceExpressions should be a parse error."
+ );
+ let Some(last) = seq.expressions.last() else {
+ continue;
+ };
+ // "parent" won't always have the same span as "last" even
+ // if it's in the last position since some nodes are
+ // skipped. This means an equality check cannot be used here.
+ if !last.span().contains_inclusive(parent.span()) {
+ return true;
+ }
+ }
+ _ => continue,
+ }
+ }
+
+ false
+ }
+
+ /// Checks if a [`Reference`] is for a [`CallExpression`] or
+ /// [`NewExpression`] for a method/function/class within its own declaration.
+ /// These do not count as a usage.
+ ///
+ /// ## Examples
+ ///
+ /// ```ts
+ /// function foo() { foo() };
+ /// const a = () => () => { a() }
+ /// class Foo { bar() { return new Foo() } }
+ /// ```
+ fn is_self_call(&self, reference: &Reference) -> bool {
+ let Some(ref_node) = self.get_ref_relevant_node(reference) else {
+ return false;
+ };
+ if !matches!(ref_node.kind(), AstKind::CallExpression(_) | AstKind::NewExpression(_)) {
+ return false;
+ }
+
+ // Do the easy/fast path if possible. If we know its a class/fn from
+ // flags, that means it's declared within this file in an understandable
+ // way, and we can get a container scope id for it. This isn't possible
+ // for parameters, e.g. `function foo(cb) { cb = function() { cb() } }`
+ if self.flags().is_function() || self.flags().is_class() {
+ return self.is_self_call_simple(reference);
+ }
+
+ // check for assignment/declaration of a function expression to a variable
+ if self.is_self_function_expr_assignment(ref_node) {
+ return true;
+ }
+
+ false
+ }
+
+ fn is_self_function_expr_assignment(&self, ref_node: &AstNode<'a>) -> bool {
+ for (parent, grandparent) in self.iter_relevant_parent_and_grandparent_kinds(ref_node.id())
+ {
+ match (parent, grandparent) {
+ // const a = function() {}
+ (AstKind::Function(f), AstKind::VariableDeclarator(decl))
+ if f.is_expression() && self == &decl.id =>
+ {
+ return true;
+ }
+ // const a = () => {}
+ (AstKind::ArrowFunctionExpression(_), AstKind::VariableDeclarator(decl))
+ if self == &decl.id =>
+ {
+ return true;
+ }
+ // let a; a = function() {}
+ (AstKind::Function(f), AstKind::AssignmentExpression(assignment))
+ if f.is_expression() && self == &assignment.left =>
+ {
+ return true;
+ }
+ // let a; a = () => {}
+ (
+ AstKind::ArrowFunctionExpression(_),
+ AstKind::AssignmentExpression(assignment),
+ ) if self == &assignment.left => {
+ return true;
+ }
+ _ => {}
+ }
+ }
+
+ false
+ }
+
+ fn is_self_call_simple(&self, reference: &Reference) -> bool {
+ let decl_scope_id = self.scope_id();
+ let call_scope_id = self.get_ref_scope(reference);
+ let Some(container_id) = self.declaration().kind().get_container_scope_id() else {
+ debug_assert!(false,
+ "Found a function call or or new expr reference on a node flagged as a function or class, but the symbol's declaration node has no scope id. It should always be a container."
+ );
+ return false;
+ };
+
+ // scope ids are created in ascending order in an "E" shape
+ // (depth-first, from top to bottom). if call < decl, then it will never
+ // be within a scope contained by the declaration, and therefore never
+ // be a self-call. Similarly, if the call is within the same scope as
+ // the declaration, it will never be inside the declaration.
+ if call_scope_id <= decl_scope_id {
+ return false;
+ }
+
+ for scope_id in self.scopes().ancestors(call_scope_id) {
+ if scope_id == container_id {
+ return true;
+ } else if scope_id == decl_scope_id {
+ return false;
+ }
+ }
+
+ unreachable!();
+ }
+
+ /// Get the [`ScopeId`] where a [`Reference`] is located.
+ #[inline]
+ fn get_ref_scope(&self, reference: &Reference) -> ScopeId {
+ self.nodes().get_node(reference.node_id()).scope_id()
+ }
+
+ /// Get the [`Span`] covering the [`AstNode`] containing a [`Reference`].
+ #[inline]
+ fn get_ref_span(&self, reference: &Reference) -> Span {
+ self.nodes().get_node(reference.node_id()).kind().span()
+ }
+
+ /// Get the first "relevant" parent of the node containing a [`Reference`].
+ /// 1. References (should) always point to [`IdentifierReference`] nodes,
+ /// which isn't useful for checking kinds/usage, so we want the parent
+ /// 2. "relevant" nodes are non "transparent". For example, parenthesis are "transparent".
+ #[inline]
+ fn get_ref_relevant_node(&self, reference: &Reference) -> Option<&AstNode<'a>> {
+ self.iter_relevant_parents(reference.node_id()).next()
+ }
+
+ /// Find the [`SymbolId`] for the nearest function declaration or expression
+ /// that is a parent of `node_id`.
+ fn get_nearest_function(&self, node_id: AstNodeId) -> Option {
+ // set to `true` when we find an arrow function and we want to get its
+ // name from the variable its assigned to.
+ let mut needs_variable_identifier = false;
+
+ for parent in self.iter_relevant_parents(node_id) {
+ match parent.kind() {
+ AstKind::Function(f) => {
+ return f.id.as_ref().and_then(|id| id.symbol_id.get());
+ }
+ AstKind::ArrowFunctionExpression(_) => {
+ needs_variable_identifier = true;
+ continue;
+ }
+ AstKind::VariableDeclarator(decl) if needs_variable_identifier => {
+ return decl.id.get_binding_identifier().and_then(|id| id.symbol_id.get())
+ }
+ AstKind::AssignmentTarget(target) if needs_variable_identifier => {
+ return match target {
+ AssignmentTarget::AssignmentTargetIdentifier(id) => id
+ .reference_id
+ .get()
+ .and_then(|rid| self.symbols().get_reference(rid).symbol_id()),
+ _ => None,
+ }
+ }
+ AstKind::Program(_) => {
+ return None;
+ }
+ _ => continue,
+ }
+ }
+
+ None
+ }
+}
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@eslint.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@eslint.snap
new file mode 100644
index 0000000000000..c78a8dd96d14c
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@eslint.snap
@@ -0,0 +1,1499 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Function 'foox' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ function foox() { return foox(); }
+ ยท โโโฌโ
+ ยท โฐโโ 'foox' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var a=10
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Function 'f' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ function f() { var a = 1; return function(){ f(a *= 2); }; }
+ ยท โฌ
+ ยท โฐโโ 'f' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Function 'f' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ function f() { var a = 1; return function(){ f(++a); }; }
+ ยท โฌ
+ ยท โฐโโ 'f' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Function 'foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ function foo(first, second) {
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ 2 โ doStuff(function() {
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var a=10;
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var a=10; a=20;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var a=10; (function() { var a = 1; alert(a); })();
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'c' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:16]
+ 1 โ var a=10, b=0, c=null; alert(a+b)
+ ยท โฌ
+ ยท โฐโโ 'c' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:11]
+ 1 โ var a=10, b=0, c=null; setTimeout(function() { var b=2; alert(a+b+c); }, 0);
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:11]
+ 1 โ var a=10, b=0, c=null; setTimeout(function() { var b=2; var c=2; alert(a+b+c); }, 0);
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'c' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:16]
+ 1 โ var a=10, b=0, c=null; setTimeout(function() { var b=2; var c=2; alert(a+b+c); }, 0);
+ ยท โฌ
+ ยท โฐโโ 'c' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Function 'f' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ function f(){var a=[];return a.map(function(){});}
+ ยท โฌ
+ ยท โฐโโ 'f' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Function 'f' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ function f(){var a=[];return a.map(function g(){});}
+ ยท โฌ
+ ยท โฐโโ 'f' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Function 'foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ function foo() {function foo(x) {
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ 2 โ return x; }; return function() {return foo; }; }
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Function 'f' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ function f(){var x;function a(){x=42;}function b(){alert(x);}}
+ ยท โฌ
+ ยท โฐโโ 'f' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:29]
+ 1 โ function f(){var x;function a(){x=42;}function b(){alert(x);}}
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:48]
+ 1 โ function f(){var x;function a(){x=42;}function b(){alert(x);}}
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:12]
+ 1 โ function f(a) {}; f();
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'z' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:18]
+ 1 โ function a(x, y, z){ return y; }; a();
+ ยท โฌ
+ ยท โฐโโ 'z' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Variable 'min' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var min = Math.min
+ ยท โโฌโ
+ ยท โฐโโ 'min' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'min' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var min = {min: 1}
+ ยท โโฌโ
+ ยท โฐโโ 'min' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Parameter 'baz' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:20]
+ 1 โ Foo.bar = function(baz) { return 1; };
+ ยท โโฌโ
+ ยท โฐโโ 'baz' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Variable 'min' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var min = {min: 1}
+ ยท โโฌโ
+ ยท โฐโโ 'min' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Parameter 'bar' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:18]
+ 1 โ function gg(baz, bar) { return baz; }; gg();
+ ยท โโฌโ
+ ยท โฐโโ 'bar' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'bar' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:21]
+ 1 โ (function(foo, baz, bar) { return baz; })();
+ ยท โโฌโ
+ ยท โฐโโ 'bar' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:11]
+ 1 โ (function(foo, baz, bar) { return baz; })();
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'bar' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:21]
+ 1 โ (function(foo, baz, bar) { return baz; })();
+ ยท โโฌโ
+ ยท โฐโโ 'bar' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:13]
+ 1 โ (function z(foo) { var bar = 33; })();
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Variable 'bar' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:24]
+ 1 โ (function z(foo) { var bar = 33; })();
+ ยท โโฌโ
+ ยท โฐโโ 'bar' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Parameter 'foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:13]
+ 1 โ (function z(foo) { z(); })();
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Function 'f' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ function f() { var a = 1; return function(){ f(a = 2); }; }
+ ยท โฌ
+ ยท โฐโโ 'f' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Identifier 'x' is imported but never used.
+ โญโ[no_unused_vars.tsx:1:8]
+ 1 โ import x from "y";
+ ยท โฌ
+ ยท โฐโโ 'x' is imported here
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Parameter 'y' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:26]
+ 1 โ export function fn2({ x, y }) {
+ ยท โฌ
+ ยท โฐโโ 'y' is declared here
+ 2 โ console.log(x);
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'y' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:25]
+ 1 โ export function fn2( x, y ) {
+ ยท โฌ
+ ยท โฐโโ 'y' is declared here
+ 2 โ console.log(x);
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Variable 'max' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:22]
+ 1 โ /*exported max*/ var max = 1, min = {min: 1}
+ ยท โโฌโ
+ ยท โฐโโ 'max' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'min' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:31]
+ 1 โ /*exported max*/ var max = 1, min = {min: 1}
+ ยท โโฌโ
+ ยท โฐโโ 'min' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'x' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:22]
+ 1 โ /*exported x*/ var { x, y } = z
+ ยท โฌ
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'y' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:25]
+ 1 โ /*exported x*/ var { x, y } = z
+ ยท โฌ
+ ยท โฐโโ 'y' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:13]
+ 1 โ var _a; var b;
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'c_' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:37]
+ 1 โ var a; function foo() { var _b; var c_; } foo();
+ ยท โโฌ
+ ยท โฐโโ 'c_' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ function foo(a, _b) { } foo();
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'c' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:21]
+ 1 โ function foo(a, _b, c) { return a; } foo();
+ ยท โฌ
+ ยท โฐโโ 'c' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter '_a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ function foo(_a) { } foo();
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Variable 'secondItem' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:25]
+ 1 โ var [ firstItemIgnored, secondItem ] = items;
+ ยท โโโโโโฌโโโโ
+ ยท โฐโโ 'secondItem' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'newArray' is declared but never used.
+ โญโ[no_unused_vars.tsx:4:22]
+ 3 โ const [a, _b, c] = array;
+ 4 โ const newArray = [a, c];
+ ยท โโโโโฌโโโ
+ ยท โฐโโ 'newArray' is declared here
+ 5 โ
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:3:23]
+ 2 โ const array = ['a', 'b', 'c', 'd', 'e'];
+ 3 โ const [a, _b, c] = array;
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ 4 โ
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'c' is declared but never used.
+ โญโ[no_unused_vars.tsx:3:30]
+ 2 โ const array = ['a', 'b', 'c', 'd', 'e'];
+ 3 โ const [a, _b, c] = array;
+ ยท โฌ
+ ยท โฐโโ 'c' is declared here
+ 4 โ
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:3:23]
+ 2 โ const array = ['a', 'b', 'c'];
+ 3 โ const [a, _b, c] = array;
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ 4 โ const fooArray = ['foo'];
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'c' is declared but never used.
+ โญโ[no_unused_vars.tsx:3:30]
+ 2 โ const array = ['a', 'b', 'c'];
+ 3 โ const [a, _b, c] = array;
+ ยท โฌ
+ ยท โฐโโ 'c' is declared here
+ 4 โ const fooArray = ['foo'];
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'fooArray' is declared but never used.
+ โญโ[no_unused_vars.tsx:4:22]
+ 3 โ const [a, _b, c] = array;
+ 4 โ const fooArray = ['foo'];
+ ยท โโโโโฌโโโ
+ ยท โฐโโ 'fooArray' is declared here
+ 5 โ const barArray = ['bar'];
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'barArray' is declared but never used.
+ โญโ[no_unused_vars.tsx:5:22]
+ 4 โ const fooArray = ['foo'];
+ 5 โ const barArray = ['bar'];
+ ยท โโโโโฌโโโ
+ ยท โฐโโ 'barArray' is declared here
+ 6 โ const ignoreArray = ['ignore'];
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable '_a' is declared but never used.
+ โญโ[no_unused_vars.tsx:3:24]
+ 2 โ const array = [obj];
+ 3 โ const [{_a, foo}] = array;
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ 4 โ console.log(foo);
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Parameter '_a' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:31]
+ 1 โ
+ 2 โ function foo([{_a, bar}]) {
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ 3 โ bar;
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Variable '_a' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:20]
+ 1 โ
+ 2 โ let _a, b;
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ 3 โ
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'b' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:2:24]
+ 1 โ
+ 2 โ let _a, b;
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ 3 โ
+ 4 โ foo.forEach(item => {
+ 5 โ [a, b] = item;
+ ยท โฌ
+ ยท โฐโโ it was last assigned here
+ 6 โ });
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'name' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:22]
+ 1 โ (function(obj) { var name; for ( name in obj ) { i(); return; } })({});
+ ยท โโโฌโ โโโฌโ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'name' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'name' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:22]
+ 1 โ (function(obj) { var name; for ( name in obj ) { } })({});
+ ยท โโโฌโ โโโฌโ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'name' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'name' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:28]
+ 1 โ (function(obj) { for ( var name in obj ) { } })({});
+ ยท โโโฌโ
+ ยท โฐโโ 'name' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'name' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:23]
+ 1 โ (function(iter) { var name; for ( name of iter ) { i(); return; } })({});
+ ยท โโโฌโ โโโฌโ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'name' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'name' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:23]
+ 1 โ (function(iter) { var name; for ( name of iter ) { } })({});
+ ยท โโโฌโ โโโฌโ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'name' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'name' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:29]
+ 1 โ (function(iter) { for ( var name of iter ) { } })({});
+ ยท โโโฌโ
+ ยท โฐโโ 'name' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'type' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:12]
+ 1 โ const data = { type: 'coords', x: 1, y: 2 };
+ 2 โ const { type, ...coords } = data;
+ ยท โโโฌโ
+ ยท โฐโโ 'type' is declared here
+ 3 โ console.log(coords);
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'coords' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:21]
+ 1 โ const data = { type: 'coords', x: 2, y: 2 };
+ 2 โ const { type, ...coords } = data;
+ ยท โโโโฌโโ
+ ยท โฐโโ 'coords' is declared here
+ 3 โ console.log(type)
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'coords' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:11]
+ 1 โ let type, coords;
+ ยท โโโโฌโโ
+ ยท โฐโโ 'coords' is declared here
+ 2 โ ({ type, ...coords } = data);
+ ยท โโโโฌโโ
+ ยท โฐโโ it was last assigned here
+ 3 โ console.log(type)
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'coords' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:21]
+ 1 โ const data = { type: 'coords', x: 3, y: 2 };
+ 2 โ const { type, ...coords } = data;
+ ยท โโโโฌโโ
+ ยท โฐโโ 'coords' is declared here
+ 3 โ console.log(type)
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'x' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:19]
+ 1 โ const data = { vars: ['x','y'], x: 1, y: 2 };
+ 2 โ const { vars: [x], ...coords } = data;
+ ยท โฌ
+ ยท โฐโโ 'x' is declared here
+ 3 โ console.log(coords)
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'x' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:24]
+ 1 โ const data = { defaults: { x: 0 }, x: 1, y: 2 };
+ 2 โ const { defaults: { x }, ...coords } = data;
+ ยท โฌ
+ ยท โฐโโ 'x' is declared here
+ 3 โ console.log(coords)
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Parameter 'rest' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ (({a, ...rest}) => {})
+ ยท โโโฌโ
+ ยท โฐโโ 'rest' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:25]
+ 1 โ export default function(a) {}
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:28]
+ 1 โ export default function(a, b) { console.log(a); }
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:26]
+ 1 โ export default (function(a) {});
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:29]
+ 1 โ export default (function(a, b) { console.log(a); });
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:17]
+ 1 โ export default (a) => {};
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:20]
+ 1 โ export default (a, b) => { console.log(a); };
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Variable 'err' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:12]
+ 1 โ try{}catch(err){};
+ ยท โโฌโ
+ ยท โฐโโ 'err' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable 'err' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:12]
+ 1 โ try{}catch(err){};
+ ยท โโฌโ
+ ยท โฐโโ 'err' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable 'err' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:12]
+ 1 โ try{}catch(err){};
+ ยท โโฌโ
+ ยท โฐโโ 'err' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable 'err' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:12]
+ 1 โ try{}catch(err){};
+ ยท โโฌโ
+ ยท โฐโโ 'err' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable 'err' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:12]
+ 1 โ try{}catch(err){};
+ ยท โโฌโ
+ ยท โฐโโ 'err' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable 'err' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:35]
+ 1 โ try{}catch(ignoreErr){}try{}catch(err){};
+ ยท โโฌโ
+ ยท โฐโโ 'err' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable 'error' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:12]
+ 1 โ try{}catch(error){}try{}catch(err){};
+ ยท โโโฌโโ
+ ยท โฐโโ 'error' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable 'err' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:31]
+ 1 โ try{}catch(error){}try{}catch(err){};
+ ยท โโฌโ
+ ยท โฐโโ 'err' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable 'err' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:12]
+ 1 โ try{}catch(err){};
+ ยท โโฌโ
+ ยท โฐโโ 'err' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable 'err' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:12]
+ 1 โ try{}catch(err){};
+ ยท โโฌโ
+ ยท โฐโโ 'err' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var a = 0; a = a + 1;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var a = 0; a = a + a;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var a = 0; a += a + 1;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var a = 0; a++;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ function foo(a) { a = a + 1 } foo();
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ function foo(a) { a += a + 1 } foo();
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ function foo(a) { a++ } foo();
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var a = 3; a = a * 5 + 6;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var a = 2, b = 4; a = a * 2 + b;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Parameter 'cb' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ function foo(cb) { cb = function(a) { cb(1 + a); }; bar(not_cb); } foo();
+ ยท โโฌ
+ ยท โฐโโ 'cb' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'cb' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ function foo(cb) { cb = (function(a) { cb(1 + a); }, cb); } foo();
+ ยท โโฌ
+ ยท โฐโโ 'cb' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:21]
+ 1 โ while (a) {
+ 2 โ function foo(b) {
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ 3 โ b = b + 1;
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:11]
+ 1 โ (function(a, b, c) {})
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ (function(a, b, c) {})
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:11]
+ 1 โ (function(a, b, {c, d}) {})
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ (function(a, b, {c, d}) {})
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:11]
+ 1 โ (function(a, b, {c, d}) {})
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ (function(a, b, {c, d}) {})
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'd' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:21]
+ 1 โ (function(a, b, {c, d}) {})
+ ยท โฌ
+ ยท โฐโโ 'd' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:11]
+ 1 โ (function(a, b, {c, d}) {})
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ (function(a, b, {c, d}) {})
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'c' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:18]
+ 1 โ (function(a, b, {c, d}) {})
+ ยท โฌ
+ ยท โฐโโ 'c' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ (function ({ a }, b ) { return b; })();
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ (function ({ a }, { b, c } ) { return b; })();
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'c' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:24]
+ 1 โ (function ({ a }, { b, c } ) { return b; })();
+ ยท โฌ
+ ยท โฐโโ 'c' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0;
+ ยท โฌ
+ ยท โฐโโ 'x' is declared here
+ 2 โ x++, x = 0;
+ ยท โฌ
+ ยท โฐโโ it was last assigned here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0;
+ ยท โฌ
+ ยท โฐโโ 'x' is declared here
+ 2 โ x++, x = 0;
+ 3 โ x=3;
+ ยท โฌ
+ ยท โฐโโ it was last assigned here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; x++, 0;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; 0, x++;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; 0, (1, x++);
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; foo = (x++, 0);
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; foo = ((0, x++), 0);
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; x += 1, 0;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; 0, x += 1;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; 0, (1, x += 1);
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; foo = (x += 1, 0);
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; foo = ((0, x += 1), 0);
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'z' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let z = 0;
+ ยท โฌ
+ ยท โฐโโ 'z' is declared here
+ 2 โ z = z + 1, z = 2;
+ ยท โฌ
+ ยท โฐโโ it was last assigned here
+ 3 โ
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'z' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let z = 0;
+ ยท โฌ
+ ยท โฐโโ 'z' is declared here
+ 2 โ z = z+1, z = 2;
+ 3 โ z = 3;
+ ยท โฌ
+ ยท โฐโโ it was last assigned here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'z' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let z = 0;
+ ยท โฌ
+ ยท โฐโโ 'z' is declared here
+ 2 โ z = z+1, z = 2;
+ 3 โ z = z+3;
+ ยท โฌ
+ ยท โฐโโ it was last assigned here
+ 4 โ
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; 0, x = x+1;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; x = x+1, 0;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'x' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let x = 0; 0, (1, x=x+1);
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'x' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ (function ({ a, b }, { c } ) { return b; })();
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'c' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:24]
+ 1 โ (function ({ a, b }, { c } ) { return b; })();
+ ยท โฌ
+ ยท โฐโโ 'c' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ (function ([ a ], b ) { return b; })();
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ (function ([ a ], [ b, c ] ) { return b; })();
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'c' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:24]
+ 1 โ (function ([ a ], [ b, c ] ) { return b; })();
+ ยท โฌ
+ ยท โฐโโ 'c' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ (function ([ a, b ], [ c ] ) { return b; })();
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'c' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:24]
+ 1 โ (function ([ a, b ], [ c ] ) { return b; })();
+ ยท โฌ
+ ยท โฐโโ 'c' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter '_a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:11]
+ 1 โ (function(_a) {})();
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter '_a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:11]
+ 1 โ (function(_a) {})();
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var a = function() { a(); };
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ var a = function(){ return function() { a(); } };
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:7]
+ 1 โ const a = () => () => { a(); };
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'myArray' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let myArray = [1,2,3,4].filter((x) => x == 0);
+ ยท โโโโฌโโโ
+ ยท โฐโโ 'myArray' is declared here
+ 2 โ myArray = myArray.filter((x) => x == 1);
+ ยท โโโโฌโโโ
+ ยท โฐโโ it was last assigned here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:7]
+ 1 โ const a = 1; a += 1;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:7]
+ 1 โ const a = () => { a(); };
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 'a';
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ 2 โ a = 10;
+ โฐโโโโ
+ โญโ[no_unused_vars.tsx:6:24]
+ 5 โ a = () => {
+ 6 โ a = 13
+ ยท โฌ
+ ยท โฐโโ it was last assigned here
+ 7 โ }
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Function 'foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:3:25]
+ 2 โ a = 10;
+ 3 โ function foo(){
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ 4 โ a = 11;
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'foo' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let foo;
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ 2 โ init();
+ โฐโโโโ
+ โญโ[no_unused_vars.tsx:5:20]
+ 4 โ function init() {
+ 5 โ foo = 1;
+ ยท โโฌโ
+ ยท โฐโโ it was last assigned here
+ 6 โ }
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Function 'foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ function foo(n) {
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ 2 โ if (n < 2) return 1;
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'c' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let c = 'c'
+ ยท โฌ
+ ยท โฐโโ 'c' is declared here
+ 2 โ c = 10
+ โฐโโโโ
+ โญโ[no_unused_vars.tsx:10:4]
+ 9 โ
+ 10 โ c = foo1
+ ยท โฌ
+ ยท โฐโโ it was last assigned here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Class 'Foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:7]
+ 1 โ class Foo { static {} }
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Class 'Foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:7]
+ 1 โ class Foo { static {} }
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'bar' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:26]
+ 1 โ class Foo { static { var bar; } }
+ ยท โโฌโ
+ ยท โฐโโ 'bar' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Class 'Foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:7]
+ 1 โ class Foo {}
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Class 'Foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:7]
+ 1 โ class Foo { static bar; }
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Class 'Foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:7]
+ 1 โ class Foo { static bar() {} }
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable '_a' is marked as ignored but is used.
+ โญโ[no_unused_vars.tsx:1:7]
+ 1 โ const _a = 5;const _b = _a + 5
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ โฐโโโโ
+ help: Consider renaming this variable.
+
+ โ eslint(no-unused-vars): Variable '_a' is marked as ignored but is used.
+ โญโ[no_unused_vars.tsx:1:7]
+ 1 โ const _a = 42; foo(() => _a);
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ โฐโโโโ
+ help: Consider renaming this variable.
+
+ โ eslint(no-unused-vars): Variable '_a' is marked as ignored but is used.
+ โญโ[no_unused_vars.tsx:1:15]
+ 1 โ (function foo(_a) { return _a + 5 })(5)
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ โฐโโโโ
+ help: Consider renaming this variable.
+
+ โ eslint(no-unused-vars): Variable '_b' is marked as ignored but is used.
+ โญโ[no_unused_vars.tsx:1:12]
+ 1 โ const [ a, _b ] = items;
+ ยท โโฌ
+ ยท โฐโโ '_b' is declared here
+ 2 โ console.log(a+_b);
+ โฐโโโโ
+ help: Consider renaming this variable.
+
+ โ eslint(no-unused-vars): Variable 'ignored' is marked as ignored but is used.
+ โญโ[no_unused_vars.tsx:1:8]
+ 1 โ const [ignored] = arr;
+ ยท โโโโฌโโโ
+ ยท โฐโโ 'ignored' is declared here
+ 2 โ foo(ignored);
+ โฐโโโโ
+ help: Consider renaming this variable.
+
+ โ eslint(no-unused-vars): Variable '_err' is marked as ignored but is used.
+ โญโ[no_unused_vars.tsx:1:12]
+ 1 โ try{}catch(_err){console.error(_err)}
+ ยท โโโฌโ
+ ยท โฐโโ '_err' is declared here
+ โฐโโโโ
+ help: Consider renaming this variable.
+
+ โ eslint(no-unused-vars): Variable 'message' is marked as ignored but is used.
+ โญโ[no_unused_vars.tsx:1:17]
+ 1 โ try {} catch ({ message }) { console.error(message); }
+ ยท โโโโฌโโโ
+ ยท โฐโโ 'message' is declared here
+ โฐโโโโ
+ help: Consider renaming this variable.
+
+ โ eslint(no-unused-vars): Variable '_a' is marked as ignored but is used.
+ โญโ[no_unused_vars.tsx:1:16]
+ 1 โ try {} catch ([_a, _b]) { doSomething(_a, _b); }
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ โฐโโโโ
+ help: Consider renaming this variable.
+
+ โ eslint(no-unused-vars): Variable '_b' is marked as ignored but is used.
+ โญโ[no_unused_vars.tsx:1:20]
+ 1 โ try {} catch ([_a, _b]) { doSomething(_a, _b); }
+ ยท โโฌ
+ ยท โฐโโ '_b' is declared here
+ โฐโโโโ
+ help: Consider renaming this variable.
+
+ โ eslint(no-unused-vars): Variable '_a' is marked as ignored but is used.
+ โญโ[no_unused_vars.tsx:1:16]
+ 1 โ try {} catch ([_a, _b]) { doSomething(_a, _b); }
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ โฐโโโโ
+ help: Consider renaming this variable.
+
+ โ eslint(no-unused-vars): Variable '_b' is marked as ignored but is used.
+ โญโ[no_unused_vars.tsx:1:20]
+ 1 โ try {} catch ([_a, _b]) { doSomething(_a, _b); }
+ ยท โโฌ
+ ยท โฐโโ '_b' is declared here
+ โฐโโโโ
+ help: Consider renaming this variable.
+
+ โ eslint(no-unused-vars): Variable '_' is caught but never used.
+ โญโ[no_unused_vars.tsx:3:13]
+ 2 โ try {
+ 3 โ } catch (_) {
+ ยท โฌ
+ ยท โฐโโ '_' is declared here
+ 4 โ _ = 'foo'
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable '_' is caught but never used.
+ โญโ[no_unused_vars.tsx:3:13]
+ 2 โ try {
+ 3 โ } catch (_) {
+ ยท โฌ
+ ยท โฐโโ '_' is declared here
+ 4 โ _ = 'foo'
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable 'message' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:17]
+ 1 โ try {} catch ({ message, errors: [firstError] }) {}
+ ยท โโโโฌโโโ
+ ยท โฐโโ 'message' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable 'firstError' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:35]
+ 1 โ try {} catch ({ message, errors: [firstError] }) {}
+ ยท โโโโโโฌโโโโ
+ ยท โฐโโ 'firstError' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Variable '$' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:24]
+ 1 โ try {} catch ({ stack: $ }) { $ = 'Something broke: ' + $; }
+ ยท โฌ
+ ยท โฐโโ '$' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
+
+ โ eslint(no-unused-vars): Parameter '_' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:4]
+ 1 โ
+ 2 โ _ => { _ = _ + 1 };
+ ยท โฌ
+ ยท โฐโโ '_' is declared here
+ 3 โ
+ โฐโโโโ
+ help: Consider removing this parameter.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-arguments.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-arguments.snap
new file mode 100644
index 0000000000000..e9ecc13c0a0bf
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-arguments.snap
@@ -0,0 +1,18 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ function foo(a) {} foo()
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:16]
+ 1 โ function foo({ a }, b) { return b } foo()
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-classes.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-classes.snap
new file mode 100644
index 0000000000000..7ac81ac172fb6
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-classes.snap
@@ -0,0 +1,28 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:32]
+ 1 โ export class Foo { constructor(a: number) {} }
+ ยท โโโโโฌโโโโ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'value' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:24]
+ 1 โ export class Foo { set(value) { } }
+ ยท โโโฌโโ
+ ยท โฐโโ 'value' is declared here
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:3:24]
+ 2 โ export abstract class Foo {
+ 3 โ public bar(a: number): string {}
+ ยท โโโโโฌโโโโ
+ ยท โฐโโ 'a' is declared here
+ 4 โ }
+ โฐโโโโ
+ help: Consider removing this parameter.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-enums.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-enums.snap
new file mode 100644
index 0000000000000..cff53c7218aca
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-enums.snap
@@ -0,0 +1,10 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Enum 'Foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:6]
+ 1 โ enum Foo { A }
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-functions.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-functions.snap
new file mode 100644
index 0000000000000..b602ebcbcbfdb
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-functions.snap
@@ -0,0 +1,18 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Function 'foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ function foo() {}
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Function 'foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ function foo() { foo() }
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-imports.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-imports.snap
new file mode 100644
index 0000000000000..bcebc5c34e3fa
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-imports.snap
@@ -0,0 +1,26 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Identifier 'a' is imported but never used.
+ โญโ[no_unused_vars.tsx:1:10]
+ 1 โ import { a } from 'a'
+ ยท โฌ
+ ยท โฐโโ 'a' is imported here
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'a' is imported but never used.
+ โญโ[no_unused_vars.tsx:1:13]
+ 1 โ import * as a from 'a'
+ ยท โฌ
+ ยท โฐโโ 'a' is imported here
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'b' is imported but never used.
+ โญโ[no_unused_vars.tsx:1:15]
+ 1 โ import { a as b } from 'a'; console.log(a)
+ ยท โฌ
+ ยท โฐโโ 'b' is imported here
+ โฐโโโโ
+ help: Consider removing this import.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-namespaces.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-namespaces.snap
new file mode 100644
index 0000000000000..72abab248c5d9
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-namespaces.snap
@@ -0,0 +1,10 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Variable 'N' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:11]
+ 1 โ namespace N {}
+ ยท โฌ
+ ยท โฐโโ 'N' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-type-aliases.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-type-aliases.snap
new file mode 100644
index 0000000000000..02114b6cb8786
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-type-aliases.snap
@@ -0,0 +1,26 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Type alias 'Foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:6]
+ 1 โ type Foo = Foo
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Type alias 'Foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:6]
+ 1 โ type Foo = Array
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Type alias 'Unbox' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:6]
+ 1 โ type Unbox = B extends Box ? Unbox : B
+ ยท โโโฌโโ
+ ยท โฐโโ 'Unbox' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-type-references.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-type-references.snap
new file mode 100644
index 0000000000000..d1f6d85a110c8
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-type-references.snap
@@ -0,0 +1,18 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Type alias 'T' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:6]
+ 1 โ type T = number; function foo(a: T): T { return a as T }; foo(1)
+ ยท โฌ
+ ยท โฐโโ 'T' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Type alias 'A' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:6]
+ 1 โ type A = number; type B = A; console.log(3 as B<3>)
+ ยท โฌ
+ ยท โฐโโ 'A' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-catch.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-catch.snap
new file mode 100644
index 0000000000000..715af05cb7ed5
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-catch.snap
@@ -0,0 +1,10 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Variable 'e' is caught but never used.
+ โญโ[no_unused_vars.tsx:1:15]
+ 1 โ try {} catch (e) { }
+ ยท โฌ
+ ยท โฐโโ 'e' is declared here
+ โฐโโโโ
+ help: Consider handling this error.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-destructure-ignored.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-destructure-ignored.snap
new file mode 100644
index 0000000000000..81cb1df9e6c8f
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-destructure-ignored.snap
@@ -0,0 +1,50 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:9]
+ 1 โ const { a, ...rest } = obj
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'rest' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:15]
+ 1 โ const { a, ...rest } = obj
+ ยท โโโฌโ
+ ยท โฐโโ 'rest' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:8]
+ 1 โ const [a, ...rest] = arr
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'rest' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ const [a, ...rest] = arr
+ ยท โโโฌโ
+ ยท โฐโโ 'rest' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'b' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:14]
+ 1 โ const { a: { b }, ...rest } = obj; console.log(a)
+ ยท โฌ
+ ยท โฐโโ 'b' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'rest' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:22]
+ 1 โ const { a: { b }, ...rest } = obj; console.log(a)
+ ยท โโโฌโ
+ ยท โฐโโ 'rest' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-discarded-read.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-discarded-read.snap
new file mode 100644
index 0000000000000..d7617ddcbb4e2
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-discarded-read.snap
@@ -0,0 +1,22 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Parameter 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:22]
+ 1 โ
+ 2 โ function foo(a) { return (a, 0); }
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ 3 โ foo(1);
+ โฐโโโโ
+ help: Consider removing this parameter.
+
+ โ eslint(no-unused-vars): Variable 'I' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:15]
+ 1 โ
+ 2 โ const I = (e) => (l) => {
+ ยท โฌ
+ ยท โฐโโ 'I' is declared here
+ 3 โ e.push(l), n || false;
+ โฐโโโโ
+ help: Consider removing this declaration.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-reassignment.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-reassignment.snap
new file mode 100644
index 0000000000000..8af74b5264acc
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-reassignment.snap
@@ -0,0 +1,100 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 1; a ||= 2;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 0; a = a + 1;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 0; a = a++ as any;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 0; a = a as unknown as string as unknown as number;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 0; a = ++a;
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 0; a = (0, ++a);
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 0; a = (a++, 0);
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 0; let b = (a++, 0); f(b);
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 0; let b = (0, (a++, 0)); f(b);
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 0; let b = ((0, a++), 0); f(b);
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 0; let b = (a, 0) + 1; f(b);
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-self-use.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-self-use.snap
new file mode 100644
index 0000000000000..b3810bcf9ca62
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-self-use.snap
@@ -0,0 +1,22 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Function 'foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:18]
+ 1 โ
+ 2 โ function foo() {
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ 3 โ return foo
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'foo' is declared but never used.
+ โญโ[no_unused_vars.tsx:2:15]
+ 1 โ
+ 2 โ const foo = () => {
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ 3 โ return foo
+ โฐโโโโ
+ help: Consider removing this declaration.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-simple.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-simple.snap
new file mode 100644
index 0000000000000..b841860f1470c
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@oxc-vars-simple.snap
@@ -0,0 +1,35 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Variable 'a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 1
+ ยท โฌ
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'a' is assigned a value but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let a = 1; a = 2
+ ยท โฌ โฌ
+ ยท โ โฐโโ it was last assigned here
+ ยท โฐโโ 'a' is declared here
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Variable '_a' is marked as ignored but is used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let _a = 1; console.log(_a)
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ โฐโโโโ
+ help: Consider renaming this variable.
+
+ โ eslint(no-unused-vars): Variable '_a' is declared but never used.
+ โญโ[no_unused_vars.tsx:1:5]
+ 1 โ let _a = 1
+ ยท โโฌ
+ ยท โฐโโ '_a' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@typescript-eslint-tsx.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@typescript-eslint-tsx.snap
new file mode 100644
index 0000000000000..1374d3ea4453b
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@typescript-eslint-tsx.snap
@@ -0,0 +1,12 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Identifier 'Fragment' is imported but never used.
+ โญโ[no_unused_vars.tsx:3:18]
+ 2 โ import React from 'react';
+ 3 โ import { Fragment } from 'react';
+ ยท โโโโโฌโโโ
+ ยท โฐโโ 'Fragment' is imported here
+ 4 โ
+ โฐโโโโ
+ help: Consider removing this import.
diff --git a/crates/oxc_linter/src/snapshots/no_unused_vars@typescript-eslint.snap b/crates/oxc_linter/src/snapshots/no_unused_vars@typescript-eslint.snap
new file mode 100644
index 0000000000000..e1f771ecea33a
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_unused_vars@typescript-eslint.snap
@@ -0,0 +1,342 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ โ eslint(no-unused-vars): Identifier 'ClassDecoratorFactory' is imported but never used.
+ โญโ[no_unused_vars.ts:1:10]
+ 1 โ import { ClassDecoratorFactory } from 'decorators'; export class Foo {}
+ ยท โโโโโโโโโโโฌโโโโโโโโโโ
+ ยท โฐโโ 'ClassDecoratorFactory' is imported here
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'Foo' is imported but never used.
+ โญโ[no_unused_vars.ts:1:10]
+ 1 โ import { Foo, Bar } from 'foo';
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is imported here
+ 2 โ function baz(): Foo {}
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'Nullable' is imported but never used.
+ โญโ[no_unused_vars.ts:2:20]
+ 1 โ
+ 2 โ import { Nullable } from 'nullable';
+ ยท โโโโโฌโโโ
+ ยท โฐโโ 'Nullable' is imported here
+ 3 โ const a: string = 'hello';
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'SomeOther' is imported but never used.
+ โญโ[no_unused_vars.ts:3:20]
+ 2 โ import { Nullable } from 'nullable';
+ 3 โ import { SomeOther } from 'other';
+ ยท โโโโโฌโโโโ
+ ยท โฐโโ 'SomeOther' is imported here
+ 4 โ const a: Nullable = 'hello';
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'Another' is imported but never used.
+ โญโ[no_unused_vars.ts:3:20]
+ 2 โ import { Nullable } from 'nullable';
+ 3 โ import { Another } from 'some';
+ ยท โโโโฌโโโ
+ ยท โฐโโ 'Another' is imported here
+ 4 โ class A {
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'Another' is imported but never used.
+ โญโ[no_unused_vars.ts:3:20]
+ 2 โ import { Nullable } from 'nullable';
+ 3 โ import { Another } from 'some';
+ ยท โโโโฌโโโ
+ ยท โฐโโ 'Another' is imported here
+ 4 โ class A {
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'Another' is imported but never used.
+ โญโ[no_unused_vars.ts:3:20]
+ 2 โ import { Nullable } from 'nullable';
+ 3 โ import { Another } from 'some';
+ ยท โโโโฌโโโ
+ ยท โฐโโ 'Another' is imported here
+ 4 โ class A {
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'Another' is imported but never used.
+ โญโ[no_unused_vars.ts:3:20]
+ 2 โ import { Nullable } from 'nullable';
+ 3 โ import { Another } from 'some';
+ ยท โโโโฌโโโ
+ ยท โฐโโ 'Another' is imported here
+ 4 โ export interface A {
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'Another' is imported but never used.
+ โญโ[no_unused_vars.ts:3:20]
+ 2 โ import { Nullable } from 'nullable';
+ 3 โ import { Another } from 'some';
+ ยท โโโโฌโโโ
+ ยท โฐโโ 'Another' is imported here
+ 4 โ export interface A {
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'Nullable' is imported but never used.
+ โญโ[no_unused_vars.ts:2:20]
+ 1 โ
+ 2 โ import { Nullable } from 'nullable';
+ ยท โโโโโฌโโโ
+ ยท โฐโโ 'Nullable' is imported here
+ 3 โ function foo(a: string) {
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'Nullable' is imported but never used.
+ โญโ[no_unused_vars.ts:2:20]
+ 1 โ
+ 2 โ import { Nullable } from 'nullable';
+ ยท โโโโโฌโโโ
+ ยท โฐโโ 'Nullable' is imported here
+ 3 โ function foo(): string | null {
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'SomeOther' is imported but never used.
+ โญโ[no_unused_vars.ts:3:20]
+ 2 โ import { Nullable } from 'nullable';
+ 3 โ import { SomeOther } from 'some';
+ ยท โโโโโฌโโโโ
+ ยท โฐโโ 'SomeOther' is imported here
+ 4 โ import { Another } from 'some';
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'SomeOther' is imported but never used.
+ โญโ[no_unused_vars.ts:3:20]
+ 2 โ import { Nullable } from 'nullable';
+ 3 โ import { SomeOther } from 'some';
+ ยท โโโโโฌโโโโ
+ ยท โฐโโ 'SomeOther' is imported here
+ 4 โ import { Another } from 'some';
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Enum 'FormFieldIds' is declared but never used.
+ โญโ[no_unused_vars.ts:2:14]
+ 1 โ
+ 2 โ enum FormFieldIds {
+ ยท โโโโโโโฌโโโโโ
+ ยท โฐโโ 'FormFieldIds' is declared here
+ 3 โ PHONE = 'phone',
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Identifier 'test' is imported but never used.
+ โญโ[no_unused_vars.ts:2:18]
+ 1 โ
+ 2 โ import test from 'test';
+ ยท โโโฌโ
+ ยท โฐโโ 'test' is imported here
+ 3 โ import baz from 'baz';
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'test' is imported but never used.
+ โญโ[no_unused_vars.ts:2:18]
+ 1 โ
+ 2 โ import test from 'test';
+ ยท โโโฌโ
+ ยท โฐโโ 'test' is imported here
+ 3 โ import baz from 'baz';
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'test' is imported but never used.
+ โญโ[no_unused_vars.ts:2:18]
+ 1 โ
+ 2 โ import test from 'test';
+ ยท โโโฌโ
+ ยท โฐโโ 'test' is imported here
+ 3 โ import baz from 'baz';
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'test' is imported but never used.
+ โญโ[no_unused_vars.ts:2:18]
+ 1 โ
+ 2 โ import test from 'test';
+ ยท โโโฌโ
+ ยท โฐโโ 'test' is imported here
+ 3 โ import baz from 'baz';
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Identifier 'test' is imported but never used.
+ โญโ[no_unused_vars.ts:2:18]
+ 1 โ
+ 2 โ import test from 'test';
+ ยท โโโฌโ
+ ยท โฐโโ 'test' is imported here
+ 3 โ import baz from 'baz';
+ โฐโโโโ
+ help: Consider removing this import.
+
+ โ eslint(no-unused-vars): Variable 'Foo' is declared but never used.
+ โญโ[no_unused_vars.ts:1:11]
+ 1 โ namespace Foo {}
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'Foo' is declared but never used.
+ โญโ[no_unused_vars.ts:1:11]
+ 1 โ namespace Foo { export const Foo = 1; }
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'Foo' is declared but never used.
+ โญโ[no_unused_vars.ts:2:23]
+ 1 โ
+ 2 โ namespace Foo {
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ 3 โ const Foo = 1;
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'Foo' is declared but never used.
+ โญโ[no_unused_vars.ts:2:19]
+ 1 โ
+ 2 โ namespace Foo {
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ 3 โ namespace Foo {
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Type alias 'Foo' is declared but never used.
+ โญโ[no_unused_vars.ts:1:6]
+ 1 โ type Foo = Array
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Type alias 'Test' is declared but never used.
+ โญโ[no_unused_vars.ts:3:18]
+ 2 โ declare module 'foo' {
+ 3 โ type Test = any;
+ ยท โโโฌโ
+ ยท โฐโโ 'Test' is declared here
+ 4 โ const x = 1;
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'Bar' is declared but never used.
+ โญโ[no_unused_vars.ts:4:23]
+ 3 โ export namespace Foo {
+ 4 โ namespace Bar {
+ ยท โโฌโ
+ ยท โฐโโ 'Bar' is declared here
+ 5 โ namespace Baz {
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'Baz' is declared but never used.
+ โญโ[no_unused_vars.ts:5:27]
+ 4 โ namespace Bar {
+ 5 โ namespace Baz {
+ ยท โโฌโ
+ ยท โฐโโ 'Baz' is declared here
+ 6 โ namespace Bam {
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'Bam' is declared but never used.
+ โญโ[no_unused_vars.ts:6:31]
+ 5 โ namespace Baz {
+ 6 โ namespace Bam {
+ ยท โโฌโ
+ ยท โฐโโ 'Bam' is declared here
+ 7 โ const x = 1;
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'x' is declared but never used.
+ โญโ[no_unused_vars.ts:7:31]
+ 6 โ namespace Bam {
+ 7 โ const x = 1;
+ ยท โฌ
+ ยท โฐโโ 'x' is declared here
+ 8 โ }
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Interface 'Foo' is declared but never used.
+ โญโ[no_unused_vars.ts:2:23]
+ 1 โ
+ 2 โ interface Foo {
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ 3 โ bar: string;
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Variable 'foo' is assigned a value but never used.
+ โญโ[no_unused_vars.ts:2:15]
+ 1 โ
+ 2 โ let foo = 1;
+ ยท โโฌโ
+ ยท โฐโโ 'foo' is declared here
+ 3 โ foo += 1;
+ ยท โโฌโ
+ ยท โฐโโ it was last assigned here
+ 4 โ
+ โฐโโโโ
+ help: Did you mean to use this variable?
+
+ โ eslint(no-unused-vars): Interface 'Foo' is declared but never used.
+ โญโ[no_unused_vars.ts:2:21]
+ 1 โ
+ 2 โ interface Foo {
+ ยท โโฌโ
+ ยท โฐโโ 'Foo' is declared here
+ 3 โ bar: string;
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ โ eslint(no-unused-vars): Type alias 'Bar' is declared but never used.
+ โญโ[no_unused_vars.ts:5:16]
+ 4 โ }
+ 5 โ type Bar = 1;
+ ยท โโฌโ
+ ยท โฐโโ 'Bar' is declared here
+ 6 โ export = Foo;
+ โฐโโโโ
+ help: Consider removing this declaration.
+
+ ร Unexpected token
+ โญโ[no_unused_vars.ts:7:12]
+ 6 โ import TheFoo = Foo;
+ 7 โ },
+ ยท โ
+ 8 โ
+ โฐโโโโ
+
+ โ eslint(no-unused-vars): Variable 'foo' is declared but never used.
+ โญโ[no_unused_vars.ts:1:7]
+ 1 โ const foo: number = 1;
+ ยท โโโโโโฌโโโโโ
+ ยท โฐโโ 'foo' is declared here
+ โฐโโโโ
+ help: Consider removing this declaration.
diff --git a/crates/oxc_linter/src/tester.rs b/crates/oxc_linter/src/tester.rs
index 1ad9c32e66934..c35d7dc6dbac4 100644
--- a/crates/oxc_linter/src/tester.rs
+++ b/crates/oxc_linter/src/tester.rs
@@ -93,6 +93,10 @@ pub struct Tester {
expect_fail: Vec,
expect_fix: Vec,
snapshot: String,
+ /// Suffix added to end of snapshot name.
+ ///
+ /// See: [insta::Settings::set_snapshot_suffix]
+ snapshot_suffix: Option<&'static str>,
current_working_directory: Box,
import_plugin: bool,
jest_plugin: bool,
@@ -120,6 +124,7 @@ impl Tester {
expect_fail,
expect_fix: vec![],
snapshot: String::new(),
+ snapshot_suffix: None,
current_working_directory,
import_plugin: false,
jest_plugin: false,
@@ -142,6 +147,11 @@ impl Tester {
self
}
+ pub fn with_snapshot_suffix(mut self, suffix: &'static str) -> Self {
+ self.snapshot_suffix = Some(suffix);
+ self
+ }
+
pub fn with_import_plugin(mut self, yes: bool) -> Self {
self.import_plugin = yes;
self
@@ -210,9 +220,17 @@ impl Tester {
self.snapshot();
}
- pub fn snapshot(&self) {
+ fn snapshot(&self) {
let name = self.rule_name.replace('-', "_");
- insta::with_settings!({ prepend_module_to_snapshot => false, omit_expression => true }, {
+ let mut settings = insta::Settings::clone_current();
+
+ settings.set_prepend_module_to_snapshot(false);
+ settings.set_omit_expression(true);
+ if let Some(suffix) = self.snapshot_suffix {
+ settings.set_snapshot_suffix(suffix);
+ }
+
+ settings.bind(|| {
insta::assert_snapshot!(name, self.snapshot);
});
}
diff --git a/crates/oxc_semantic/src/node.rs b/crates/oxc_semantic/src/node.rs
index 5e60ce83cdb08..41fca28af71f9 100644
--- a/crates/oxc_semantic/src/node.rs
+++ b/crates/oxc_semantic/src/node.rs
@@ -95,7 +95,10 @@ impl<'a> AstNodes<'a> {
/// The first node produced by this iterator is the first parent of the node
/// pointed to by `node_id`. The last node will usually be a `Program`.
#[inline]
- pub fn iter_parents(&self, node_id: AstNodeId) -> impl Iterator- > + '_ {
+ pub fn iter_parents(
+ &self,
+ node_id: AstNodeId,
+ ) -> impl Iterator
- > + Clone + '_ {
AstNodeParentIter { current_node_id: Some(node_id), nodes: self }
}
@@ -195,7 +198,7 @@ impl<'a> AstNodes<'a> {
}
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct AstNodeParentIter<'s, 'a> {
current_node_id: Option,
nodes: &'s AstNodes<'a>,
diff --git a/crates/oxc_semantic/src/scope.rs b/crates/oxc_semantic/src/scope.rs
index 4592c972a281e..f594072f01b36 100644
--- a/crates/oxc_semantic/src/scope.rs
+++ b/crates/oxc_semantic/src/scope.rs
@@ -201,6 +201,12 @@ impl ScopeTree {
})
}
+ /// Iterate over bindings declared inside a scope.
+ #[inline]
+ pub fn iter_bindings_in(&self, scope_id: ScopeId) -> impl Iterator
- + '_ {
+ self.bindings[scope_id].values().copied()
+ }
+
#[inline]
pub(crate) fn get_bindings_mut(&mut self, scope_id: ScopeId) -> &mut Bindings {
&mut self.bindings[scope_id]
diff --git a/crates/oxc_semantic/src/symbol.rs b/crates/oxc_semantic/src/symbol.rs
index 7ea8a558355b4..e2ea21d0e5d90 100644
--- a/crates/oxc_semantic/src/symbol.rs
+++ b/crates/oxc_semantic/src/symbol.rs
@@ -193,7 +193,7 @@ impl SymbolTable {
pub fn get_resolved_references(
&self,
symbol_id: SymbolId,
- ) -> impl Iterator
- + '_ {
+ ) -> impl DoubleEndedIterator
- + '_ {
self.resolved_references[symbol_id]
.iter()
.map(|reference_id| &self.references[*reference_id])
diff --git a/crates/oxc_semantic/tests/integration/scopes.rs b/crates/oxc_semantic/tests/integration/scopes.rs
index ce32fd663ee08..7dd1e88081300 100644
--- a/crates/oxc_semantic/tests/integration/scopes.rs
+++ b/crates/oxc_semantic/tests/integration/scopes.rs
@@ -1,4 +1,5 @@
-use oxc_semantic::ScopeFlags;
+use oxc_ast::AstKind;
+use oxc_semantic::{ScopeFlags, SymbolFlags};
use crate::util::{Expect, SemanticTester};
@@ -139,6 +140,49 @@ fn test_catch_clause_parameters() {
.test();
}
+#[test]
+fn test_enums() {
+ let test = SemanticTester::ts(
+ "
+ enum A {
+ X,
+ Y,
+ Z
+ }",
+ );
+ test.has_root_symbol("A").contains_flags(SymbolFlags::RegularEnum).test();
+ test.has_some_symbol("X").contains_flags(SymbolFlags::EnumMember).test();
+
+ let semantic = test.build();
+ let program = semantic
+ .nodes()
+ .iter()
+ .find(|node| matches!(node.kind(), AstKind::Program(_)))
+ .expect("No program node found");
+ assert_eq!(program.scope_id(), semantic.scopes().root_scope_id());
+
+ let (enum_node, enum_decl) = semantic
+ .nodes()
+ .iter()
+ .find_map(|node| {
+ if let AstKind::TSEnumDeclaration(e) = node.kind() {
+ Some((node, e))
+ } else {
+ None
+ }
+ })
+ .expect("Expected TS test case to have an enum declaration for A.");
+
+ assert_eq!(
+ enum_node.scope_id(),
+ program.scope_id(),
+ "Expected `enum A` to be created in the top-level scope."
+ );
+ let enum_decl_scope_id = enum_decl.scope_id.get().expect("Enum declaration has no scope id");
+ assert_ne!(enum_node.scope_id(), enum_decl_scope_id, "Enum declaration nodes should contain the scope ID they create, not the scope ID they're created in.");
+ assert_eq!(enum_decl.members.len(), 3);
+}
+
#[test]
fn var_hoisting() {
SemanticTester::js(
diff --git a/crates/oxc_syntax/src/operator.rs b/crates/oxc_syntax/src/operator.rs
index 2ffad446bfef2..d88f70c9ad1a7 100644
--- a/crates/oxc_syntax/src/operator.rs
+++ b/crates/oxc_syntax/src/operator.rs
@@ -330,6 +330,13 @@ impl UnaryOperator {
matches!(self, Self::UnaryNegation | Self::UnaryPlus)
}
+ /// Returns `true` if this operator is a [`LogicalNot`].
+ ///
+ /// [`LogicalNot`]: UnaryOperator::LogicalNot
+ pub fn is_not(self) -> bool {
+ matches!(self, Self::LogicalNot)
+ }
+
pub fn is_bitwise(self) -> bool {
matches!(self, Self::BitwiseNot)
}
diff --git a/crates/oxc_syntax/src/symbol.rs b/crates/oxc_syntax/src/symbol.rs
index d18991a1b8321..ea2fed8839211 100644
--- a/crates/oxc_syntax/src/symbol.rs
+++ b/crates/oxc_syntax/src/symbol.rs
@@ -62,7 +62,7 @@ export type RedeclarationId = unknown;
"#;
bitflags! {
- #[derive(Debug, Clone, Copy)]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct SymbolFlags: u32 {
const None = 0;
@@ -163,6 +163,10 @@ impl SymbolFlags {
self.intersects(Self::Enum)
}
+ pub fn is_enum_member(&self) -> bool {
+ self.contains(Self::EnumMember)
+ }
+
pub fn is_catch_variable(&self) -> bool {
self.contains(Self::CatchVariable)
}