Skip to content

Commit cc0112f

Browse files
committed
feat(linter): no-unused-vars add setting for reportVarsOnlyUsedAsTypes (#11009)
closes #11006 adds an option called `reportVarsOnlyUsedAsType`, defaulting to false when enabled, it will report code such as ```ts const x = 123; export type X = typeof x; ```
1 parent c049765 commit cc0112f

File tree

4 files changed

+83
-9
lines changed

4 files changed

+83
-9
lines changed

crates/oxc_linter/src/rules/eslint/no_unused_vars/options.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,34 @@ pub struct NoUnusedVarsOptions {
205205
/// console.log(firstVar, secondVar);
206206
/// ```
207207
pub report_used_ignore_pattern: bool,
208+
209+
/// The `reportVarsOnlyUsedAsTypes` option is a boolean (default: `false`).
210+
///
211+
/// If `true`, the rule will also report variables that are only used as types.
212+
///
213+
/// ## Examples
214+
///
215+
/// Examples of **incorrect** code for the `{ "reportVarsOnlyUsedAsTypes": true }` option:
216+
///
217+
/// ```javascript
218+
/// /* eslint no-unused-vars: ["error", { "reportVarsOnlyUsedAsTypes": true }] */
219+
///
220+
/// const myNumber: number = 4;
221+
/// export type MyNumber = typeof myNumber
222+
/// ```
223+
///
224+
/// Examples of **correct** code for the `{ "reportVarsOnlyUsedAsTypes": true }` option:
225+
///
226+
/// ```javascript
227+
/// export type MyNumber = number;
228+
/// ```
229+
///
230+
/// Note: even with `{ "reportVarsOnlyUsedAsTypes": false }`, cases where the value is
231+
/// only used a type within itself will still be reported:
232+
/// ```javascript
233+
/// function foo(): typeof foo {}
234+
/// ```
235+
pub report_vars_only_used_as_types: bool,
208236
}
209237

210238
/// Represents an `Option<Regex>` with an additional `Default` variant,
@@ -314,6 +342,7 @@ impl Default for NoUnusedVarsOptions {
314342
destructured_array_ignore_pattern: IgnorePattern::None,
315343
ignore_class_with_static_init_block: false,
316344
report_used_ignore_pattern: false,
345+
report_vars_only_used_as_types: false,
317346
}
318347
}
319348
}
@@ -555,6 +584,11 @@ impl TryFrom<Value> for NoUnusedVarsOptions {
555584
.map_or(Some(false), Value::as_bool)
556585
.unwrap_or(false);
557586

587+
let report_vars_only_used_as_types: bool = config
588+
.get("reportVarsOnlyUsedAsTypes")
589+
.map_or(Some(false), Value::as_bool)
590+
.unwrap_or(false);
591+
558592
Ok(Self {
559593
vars,
560594
vars_ignore_pattern,
@@ -566,6 +600,7 @@ impl TryFrom<Value> for NoUnusedVarsOptions {
566600
destructured_array_ignore_pattern,
567601
ignore_class_with_static_init_block,
568602
report_used_ignore_pattern,
603+
report_vars_only_used_as_types,
569604
})
570605
}
571606
Value::Null => Ok(Self::default()),

crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,33 @@ fn test_loops() {
12531253
Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail).expect_fix(fix).test();
12541254
}
12551255

1256+
#[test]
1257+
fn test_report_vars_only_used_as_types() {
1258+
let pass = vec![
1259+
("const foo = 123; export type Foo = typeof foo;", None),
1260+
(
1261+
"const foo = 123; export type Foo = typeof foo;",
1262+
Some(json!([{ "reportVarsOnlyUsedAsTypes": false, "varsIgnorePattern": "^_" }])),
1263+
),
1264+
(
1265+
"export const foo = 123; export type Foo = typeof foo;",
1266+
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
1267+
),
1268+
];
1269+
1270+
let fail = vec![
1271+
(
1272+
"const foo = 123; export type Foo = typeof foo;",
1273+
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
1274+
),
1275+
("function foo(): typeof foo {}", None),
1276+
];
1277+
1278+
Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail)
1279+
.intentionally_allow_no_fix_tests()
1280+
.test();
1281+
}
1282+
12561283
// #[test]
12571284
// fn test_template() {
12581285
// let pass = vec![];

crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/typescript_eslint.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ use serde_json::json;
33
use super::NoUnusedVars;
44
use crate::{RuleMeta as _, tester::Tester};
55

6-
// TODO: port these over. I (@DonIsaac) would love some help with this...
7-
86
#[test]
97
fn test() {
108
let pass = vec![
@@ -1731,28 +1729,28 @@ fn test() {
17311729
const foo: number = 1;
17321730
export type Foo = typeof foo;
17331731
",
1734-
None,
1732+
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
17351733
),
17361734
(
17371735
"
17381736
declare const foo: number;
17391737
export type Foo = typeof foo;
17401738
",
1741-
None,
1739+
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
17421740
),
17431741
(
17441742
"
17451743
const foo: number = 1;
17461744
export type Foo = typeof foo | string;
17471745
",
1748-
None,
1746+
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
17491747
),
17501748
(
17511749
"
17521750
const foo: number = 1;
17531751
export type Foo = (typeof foo | string) & { __brand: 'foo' };
17541752
",
1755-
None,
1753+
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
17561754
),
17571755
(
17581756
"
@@ -1763,7 +1761,7 @@ fn test() {
17631761
};
17641762
export type Bar = typeof foo.bar;
17651763
",
1766-
None,
1764+
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
17671765
),
17681766
(
17691767
"
@@ -1774,7 +1772,7 @@ fn test() {
17741772
};
17751773
export type Bar = (typeof foo)['bar'];
17761774
",
1777-
None,
1775+
Some(json!([{ "reportVarsOnlyUsedAsTypes": true, "varsIgnorePattern": "^_" }])),
17781776
),
17791777
];
17801778

crates/oxc_linter/src/rules/eslint/no_unused_vars/usage.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,25 @@ impl<'a> Symbol<'_, 'a> {
137137
continue;
138138
}
139139

140-
if !self.flags().intersects(SymbolFlags::TypeImport.union(SymbolFlags::Import))
140+
// ```ts
141+
// const foo = 123;
142+
// export type Foo = typeof foo
143+
// ```
144+
if options.report_vars_only_used_as_types
145+
&& !self.flags().intersects(SymbolFlags::TypeImport.union(SymbolFlags::Import))
141146
&& self.reference_contains_type_query(reference)
142147
{
143148
continue;
144149
}
150+
// ```
151+
// function foo(): foo { }
152+
// ```
153+
if self
154+
.get_ref_relevant_node(reference)
155+
.is_some_and(|node| self.declaration().span().contains_inclusive(node.span()))
156+
{
157+
continue;
158+
}
145159

146160
return true;
147161
}

0 commit comments

Comments
 (0)