Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions crates/oxc_linter/src/rules/eslint/no_unused_vars/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,34 @@ pub struct NoUnusedVarsOptions {
/// console.log(firstVar, secondVar);
/// ```
pub report_used_ignore_pattern: bool,

/// The `reportVarsOnlyUsedAsTypes` option is a boolean (default: `false`).
///
/// If `true`, the rule will also report variables that are only used as types.
///
/// ## Examples
///
/// Examples of **incorrect** code for the `{ "reportVarsOnlyUsedAsTypes": true }` option:
///
/// ```javascript
/// /* eslint no-unused-vars: ["error", { "reportVarsOnlyUsedAsTypes": true }] */
///
/// const myNumber: number = 4;
/// export type MyNumber = typeof myNumber
/// ```
///
/// Examples of **correct** code for the `{ "reportVarsOnlyUsedAsTypes": true }` option:
///
/// ```javascript
/// export type MyNumber = number;
/// ```
///
/// Note: even with `{ "reportVarsOnlyUsedAsTypes": false }`, cases where the value is
/// only used a type within itself will still be reported:
/// ```javascript
/// function foo(): typeof foo {}
/// ```
pub report_vars_only_used_as_types: bool,
}

/// Represents an `Option<Regex>` with an additional `Default` variant,
Expand Down Expand Up @@ -314,6 +342,7 @@ impl Default for NoUnusedVarsOptions {
destructured_array_ignore_pattern: IgnorePattern::None,
ignore_class_with_static_init_block: false,
report_used_ignore_pattern: false,
report_vars_only_used_as_types: false,
}
}
}
Expand Down Expand Up @@ -555,6 +584,11 @@ impl TryFrom<Value> for NoUnusedVarsOptions {
.map_or(Some(false), Value::as_bool)
.unwrap_or(false);

let report_vars_only_used_as_types: bool = config
.get("reportVarsOnlyUsedAsTypes")
.map_or(Some(false), Value::as_bool)
.unwrap_or(false);

Ok(Self {
vars,
vars_ignore_pattern,
Expand All @@ -566,6 +600,7 @@ impl TryFrom<Value> for NoUnusedVarsOptions {
destructured_array_ignore_pattern,
ignore_class_with_static_init_block,
report_used_ignore_pattern,
report_vars_only_used_as_types,
})
}
Value::Null => Ok(Self::default()),
Expand Down
27 changes: 27 additions & 0 deletions crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,33 @@ fn test_loops() {
Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail).expect_fix(fix).test();
}

#[test]
fn test_report_vars_only_used_as_types() {
let pass = vec![
("const foo = 123; export type Foo = typeof foo;", None),
(
"const foo = 123; export type Foo = typeof foo;",
Some(json!([{ "reportVarsOnlyUsedAsTypes": false, "varsIgnorePattern": "^_" }])),
),
(
"export const foo = 123; export type Foo = typeof foo;",
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
),
];

let fail = vec![
(
"const foo = 123; export type Foo = typeof foo;",
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
),
("function foo(): typeof foo {}", None),
];

Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail)
.intentionally_allow_no_fix_tests()
.test();
}

// #[test]
// fn test_template() {
// let pass = vec![];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ use serde_json::json;
use super::NoUnusedVars;
use crate::{RuleMeta as _, tester::Tester};

// TODO: port these over. I (@DonIsaac) would love some help with this...

#[test]
fn test() {
let pass = vec![
Expand Down Expand Up @@ -1731,28 +1729,28 @@ fn test() {
const foo: number = 1;
export type Foo = typeof foo;
",
None,
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
),
(
"
declare const foo: number;
export type Foo = typeof foo;
",
None,
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
),
(
"
const foo: number = 1;
export type Foo = typeof foo | string;
",
None,
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
),
(
"
const foo: number = 1;
export type Foo = (typeof foo | string) & { __brand: 'foo' };
",
None,
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
),
(
"
Expand All @@ -1763,7 +1761,7 @@ fn test() {
};
export type Bar = typeof foo.bar;
",
None,
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
),
(
"
Expand All @@ -1774,7 +1772,7 @@ fn test() {
};
export type Bar = (typeof foo)['bar'];
",
None,
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
),
];

Expand Down
16 changes: 15 additions & 1 deletion crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,25 @@ impl<'a> Symbol<'_, 'a> {
continue;
}

if !self.flags().intersects(SymbolFlags::TypeImport.union(SymbolFlags::Import))
// ```ts
// const foo = 123;
// export type Foo = typeof foo
// ```
if options.report_vars_only_used_as_types
&& !self.flags().intersects(SymbolFlags::TypeImport.union(SymbolFlags::Import))
&& self.reference_contains_type_query(reference)
{
continue;
}
// ```
// function foo(): foo { }
// ```
if self
.get_ref_relevant_node(reference)
.is_some_and(|node| self.declaration().span().contains_inclusive(node.span()))
{
continue;
}

return true;
}
Expand Down
Loading