Skip to content

Commit 9cd7a67

Browse files
committed
feat(linter/no-unused-vars): support ignoreUsingDeclarations option (#14632)
fixes #14628
1 parent 5238891 commit 9cd7a67

File tree

6 files changed

+1132
-3
lines changed

6 files changed

+1132
-3
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ impl NoUnusedVars {
111111
return true;
112112
}
113113

114+
if self.ignore_using_declarations && decl.kind.is_using() {
115+
return true;
116+
}
117+
114118
false
115119
}
116120

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,26 @@ pub struct NoUnusedVarsOptions {
177177
/// ```
178178
pub ignore_class_with_static_init_block: bool,
179179

180+
/// The `ignoreUsingDeclarations` option is a boolean (default: `false`).
181+
/// When set to `true`, the rule will ignore variables declared with
182+
/// `using` or `await using` declarations, even if they are unused.
183+
///
184+
/// This is useful when working with resources that need to be disposed
185+
/// via the explicit resource management proposal, where the primary
186+
/// purpose is the disposal side effect rather than using the resource.
187+
///
188+
/// ## Example
189+
///
190+
/// Examples of **correct** code for the `{ "ignoreUsingDeclarations": true }` option:
191+
///
192+
/// ```javascript
193+
/// /*eslint no-unused-vars: ["error", { "ignoreUsingDeclarations": true }]*/
194+
///
195+
/// using resource = getResource();
196+
/// await using anotherResource = getAnotherResource();
197+
/// ```
198+
pub ignore_using_declarations: bool,
199+
180200
/// The `reportUsedIgnorePattern` option is a boolean (default: `false`).
181201
/// Using this option will report variables that match any of the valid
182202
/// ignore pattern options (`varsIgnorePattern`, `argsIgnorePattern`,
@@ -341,6 +361,7 @@ impl Default for NoUnusedVarsOptions {
341361
caught_errors_ignore_pattern: IgnorePattern::None,
342362
destructured_array_ignore_pattern: IgnorePattern::None,
343363
ignore_class_with_static_init_block: false,
364+
ignore_using_declarations: false,
344365
report_used_ignore_pattern: false,
345366
report_vars_only_used_as_types: false,
346367
}
@@ -579,6 +600,11 @@ impl TryFrom<Value> for NoUnusedVarsOptions {
579600
.map_or(Some(false), Value::as_bool)
580601
.unwrap_or(false);
581602

603+
let ignore_using_declarations: bool = config
604+
.get("ignoreUsingDeclarations")
605+
.map_or(Some(false), Value::as_bool)
606+
.unwrap_or(false);
607+
582608
let report_used_ignore_pattern: bool = config
583609
.get("reportUsedIgnorePattern")
584610
.map_or(Some(false), Value::as_bool)
@@ -599,6 +625,7 @@ impl TryFrom<Value> for NoUnusedVarsOptions {
599625
caught_errors_ignore_pattern,
600626
destructured_array_ignore_pattern,
601627
ignore_class_with_static_init_block,
628+
ignore_using_declarations,
602629
report_used_ignore_pattern,
603630
report_vars_only_used_as_types,
604631
})
@@ -629,6 +656,7 @@ mod tests {
629656
assert!(rule.destructured_array_ignore_pattern.is_none());
630657
assert!(!rule.ignore_rest_siblings);
631658
assert!(!rule.ignore_class_with_static_init_block);
659+
assert!(!rule.ignore_using_declarations);
632660
assert!(!rule.report_used_ignore_pattern);
633661
}
634662

@@ -668,6 +696,7 @@ mod tests {
668696
assert!(rule.destructured_array_ignore_pattern.is_default());
669697
assert!(rule.ignore_rest_siblings);
670698
assert!(!rule.ignore_class_with_static_init_block);
699+
assert!(!rule.ignore_using_declarations);
671700
assert!(rule.report_used_ignore_pattern);
672701
}
673702

@@ -707,6 +736,7 @@ mod tests {
707736
.try_into()
708737
.unwrap();
709738
assert!(rule.ignore_rest_siblings);
739+
assert!(!rule.ignore_using_declarations);
710740
// an options object is provided, so no default pattern is set.
711741
assert!(rule.vars_ignore_pattern.is_none());
712742
}

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

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,20 @@ fn test() {
440440
Some(
441441
serde_json::json!([{ "destructuredArrayIgnorePattern": "^_", "reportUsedIgnorePattern": true }]),
442442
),
443-
), // { "ecmaVersion": 6 }
443+
), // { "ecmaVersion": 6 },
444+
(
445+
"using resource = getResource();
446+
resource;",
447+
None,
448+
), // { "sourceType": "module", "ecmaVersion": 2026, },
449+
(
450+
"using resource = getResource();",
451+
Some(serde_json::json!([{ "ignoreUsingDeclarations": true }])),
452+
), // { "sourceType": "module", "ecmaVersion": 2026, },
453+
(
454+
"await using resource = getResource();",
455+
Some(serde_json::json!([{ "ignoreUsingDeclarations": true }])),
456+
), // { "sourceType": "module", "ecmaVersion": 2026, }
444457
];
445458

446459
let fail = vec![
@@ -589,6 +602,8 @@ fn test() {
589602
("(function(obj) { var name; for ( name in obj ) { i(); return; } })({});", None),
590603
("(function(obj) { var name; for ( name in obj ) { } })({});", None),
591604
("(function(obj) { for ( var name in obj ) { } })({});", None),
605+
("for ( var { foo } in bar ) { }", None), // { "ecmaVersion": 6 },
606+
("for ( var [ foo ] in bar ) { }", None), // { "ecmaVersion": 6 },
592607
("(function(iter) { var name; for ( name of iter ) { i(); return; } })({});", None), // { "ecmaVersion": 6 },
593608
("(function(iter) { var name; for ( name of iter ) { } })({});", None), // { "ecmaVersion": 6 },
594609
("(function(iter) { for ( var name of iter ) { } })({});", None), // { "ecmaVersion": 6 },
@@ -725,6 +740,7 @@ fn test() {
725740
("var a = 2, b = 4; a = a * 2 + b;", None),
726741
// https://github.com/oxc-project/oxc/issues/4436
727742
("function foo(cb) { cb = function(a) { cb(1 + a); }; bar(not_cb); } foo();", None),
743+
// ("function foo(cb) { cb = function(a) { return cb(1 + a); }(); } foo();", None),
728744
("function foo(cb) { cb = (function(a) { cb(1 + a); }, cb); } foo();", None),
729745
// ("function foo(cb) { cb = (0, function(a) { cb(1 + a); }); } foo();", None),
730746
(
@@ -975,6 +991,139 @@ fn test() {
975991
",
976992
Some(serde_json::json!([{ "argsIgnorePattern": "ignored", "varsIgnorePattern": "_" }])),
977993
), // { "ecmaVersion": 2015 }
994+
("const [a, b, c] = foo; alert(a + c);", None), // { "ecmaVersion": 6 },
995+
("const [a = aDefault] = foo;", None), // { "ecmaVersion": 6 },
996+
("const [[a = aDefault]]= foo;", None), // { "ecmaVersion": 6 },
997+
("const [[a = aDefault], b]= foo;", None), // { "ecmaVersion": 6 },
998+
("const [a = aDefault, b] = foo; alert(b);", None), // { "ecmaVersion": 6 },
999+
("function a([a = aDefault]) { } a();", None), // { "ecmaVersion": 6 },
1000+
("function a([[a = aDefault]]) { } a();", None), // { "ecmaVersion": 6 },
1001+
("function a([a = aDefault, b]) { alert(b); } a();", None), // { "ecmaVersion": 6 },
1002+
("function a([[a = aDefault, b]]) { alert(b); } a();", None), // { "ecmaVersion": 6 },
1003+
("const { a: a1 } = foo", None), // { "ecmaVersion": 6 },
1004+
("const { a: a1, b: b1 } = foo; alert(b1);", None), // { "ecmaVersion": 6 },
1005+
("const { a: a1, b: b1 } = foo; alert(a1);", None), // { "ecmaVersion": 6 },
1006+
("function a({ a: a1 }) {} a();", None), // { "ecmaVersion": 6 },
1007+
("const { a: a1 = aDefault } = foo;", None), // { "ecmaVersion": 6 },
1008+
("const [{ a: a1 = aDefault }] = foo;", None), // { "ecmaVersion": 6 },
1009+
("const { a = aDefault } = foo;", None), // { "ecmaVersion": 6 },
1010+
("const { a = aDefault, b } = foo; alert(b);", None), // { "ecmaVersion": 6 },
1011+
("const { a, b = bDefault } = foo; alert(a);", None), // { "ecmaVersion": 6 },
1012+
("const { a, b = bDefault, c } = foo; alert(a + c);", None), // { "ecmaVersion": 6 },
1013+
("const { [key]: a } = foo;", None), // { "ecmaVersion": 6 },
1014+
("const [...{ a, b }] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1015+
("function foo (...rest) {} foo();", None), // { "ecmaVersion": 2023 },
1016+
("function foo (a, ...rest) { alert(a); } foo();", None), // { "ecmaVersion": 2023 },
1017+
("const {...rest} = foo;", None), // { "ecmaVersion": 2023 },
1018+
("const {a, ...rest} = foo; alert(a);", None), // { "ecmaVersion": 2023 },
1019+
("const {...rest} = foo, a = bar; alert(a);", None), // { "ecmaVersion": 2023 },
1020+
("const a = bar, {...rest} = foo; alert(a);", None), // { "ecmaVersion": 2023 },
1021+
("function foo ({...rest}) { } foo();", None), // { "ecmaVersion": 2023 },
1022+
("function foo (a, {...rest}) { alert(a); } foo();", None), // { "ecmaVersion": 2023 },
1023+
("function foo ({...rest}, a) { alert(a); } foo();", None), // { "ecmaVersion": 2023 },
1024+
("const [...rest] = foo;", None), // { "ecmaVersion": 2023 },
1025+
("const [[...rest]] = foo;", None), // { "ecmaVersion": 2023 },
1026+
("const [a, ...rest] = foo; alert(a);", None), // { "ecmaVersion": 2023 },
1027+
("function foo ([...rest]) { } foo();", None), // { "ecmaVersion": 2023 },
1028+
("const [a, ...{ b }] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1029+
("const [[a, ...{ b }]] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1030+
("const [...[a]] = array;", None), // { "ecmaVersion": 2023 },
1031+
("const [[...[a]]] = array;", None), // { "ecmaVersion": 2023 },
1032+
("const [...[a, b]] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1033+
("const [a, ...[b]] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1034+
("const [[a, ...[b]]] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1035+
("const [a, ...[b]] = array; alert(b);", None), // { "ecmaVersion": 2023 },
1036+
("const [a, ...[[ b ]]] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1037+
("const [a, ...[{ b }]] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1038+
("function foo([a, ...[[ b ]]]) {} foo();", None), // { "ecmaVersion": 2023 },
1039+
("function foo([a, ...[{ b }]]) {} foo();", None), // { "ecmaVersion": 2023 },
1040+
("function foo(...[[ a ]]) {} foo();", None), // { "ecmaVersion": 2023 },
1041+
("function foo(...[{ a }]) {} foo();", None), // { "ecmaVersion": 2023 },
1042+
("function foo(a, ...[b]) { alert(a); } foo();", None), // { "ecmaVersion": 2023 },
1043+
("const [a, [b]] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1044+
("const [[a, [b]]] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1045+
("const [a, [[b]]] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1046+
("function a([[b]]) {} a();", None), // { "ecmaVersion": 2023 },
1047+
("function a([[b], c]) { alert(c); } a();", None), // { "ecmaVersion": 2023 },
1048+
("const [{b}, a] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1049+
("const [[{b}, a]] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1050+
("const [[[{b}], a]] = array; alert(a);", None), // { "ecmaVersion": 2023 },
1051+
("function a([{b}]) {} a();", None), // { "ecmaVersion": 2023 },
1052+
("function a([{b}, c]) { alert(c); } a();", None), // { "ecmaVersion": 2023 },
1053+
("const { a: { b }, c } = foo; alert(c);", None), // { "ecmaVersion": 2023 },
1054+
("const { c, a: { b } } = foo; alert(c);", None), // { "ecmaVersion": 2023 },
1055+
("const { a: { b: { c }, d } } = foo; alert(d);", None), // { "ecmaVersion": 2023 },
1056+
("const { a: { b: { c: { e } }, d } } = foo; alert(d);", None), // { "ecmaVersion": 2023 },
1057+
("const [{ a: { b }, c }] = foo; alert(c);", None), // { "ecmaVersion": 2023 },
1058+
("const { a: [{ b }]} = foo;", None), // { "ecmaVersion": 2023 },
1059+
("const { a: [[ b ]]} = foo;", None), // { "ecmaVersion": 2023 },
1060+
("const [{ a: [{ b }]}] = foo;", None), // { "ecmaVersion": 2023 },
1061+
("const { a: [{ b }], c} = foo; alert(c);", None), // { "ecmaVersion": 2023 },
1062+
("function foo({ a: [{ b }]}) {} foo();", None), // { "ecmaVersion": 2023 },
1063+
("function foo({ a: [[ b ]]}) {} foo();", None), // { "ecmaVersion": 2023 },
1064+
("let a = foo, b = 'bar'; alert(b);", None), // { "ecmaVersion": 2023 },
1065+
("let a = foo, b = 'bar'; alert(a);", None), // { "ecmaVersion": 2023 },
1066+
("let { a } = foo, bar = 'hello'; alert(bar);", None), // { "ecmaVersion": 2023 },
1067+
("let bar = 'hello', { a } = foo; alert(bar);", None), // { "ecmaVersion": 2023 },
1068+
("import a from 'module';", None), // { "ecmaVersion": 6, "sourceType": "module" },
1069+
("import * as foo from 'module';", None), // { "ecmaVersion": 6, "sourceType": "module" },
1070+
("import a, * as foo from 'module'; a();", None), // { "ecmaVersion": 6, "sourceType": "module" },
1071+
("import a, * as foo from 'module'; foo.hello;", None), // { "ecmaVersion": 6, "sourceType": "module" },
1072+
("import { a } from 'module';", None), // { "ecmaVersion": 6, "sourceType": "module" },
1073+
("import { a, b } from 'module'; alert(b);", None), // { "ecmaVersion": 6, "sourceType": "module" },
1074+
("import { a, b } from 'module'; alert(a);", None), // { "ecmaVersion": 6, "sourceType": "module" },
1075+
("import { a as foo } from 'module';", None), // { "ecmaVersion": 6, "sourceType": "module" },
1076+
("import { a as foo, b } from 'module'; alert(b);", None), // { "ecmaVersion": 6, "sourceType": "module" },
1077+
("import { a, b as foo } from 'module'; alert(a);", None), // { "ecmaVersion": 6, "sourceType": "module" },
1078+
("import { default as foo, a } from 'module'; alert(a);", None), // { "ecmaVersion": 6, "sourceType": "module" },
1079+
("import foo, { a } from 'module'; alert(a);", None), // { "ecmaVersion": 6, "sourceType": "module" },
1080+
("import foo, { a } from 'module'; foo();", None), // { "ecmaVersion": 6, "sourceType": "module" },
1081+
("let a; a = foo;", None), // { "ecmaVersion": 6 },
1082+
("array.forEach(a => {})", None), // { "ecmaVersion": 6 },
1083+
("if (foo()) var bar;", None), // { "ecmaVersion": 6 },
1084+
("for (;;) var foo;", None), // { "ecmaVersion": 6 },
1085+
("for (a in b) var foo;", None), // { "ecmaVersion": 6 },
1086+
("for (a of b) var foo;", None), // { "ecmaVersion": 6 },
1087+
("while (a) var foo;", None), // { "ecmaVersion": 6 },
1088+
("do var foo; while (b);", None), // { "ecmaVersion": 6 },
1089+
("with (a) var foo;", None), // { "ecmaVersion": 6 },
1090+
("var a;'use strict';b(00);", None),
1091+
("var [a] = foo;'use strict';b(00);", None), // { "ecmaVersion": 6 },
1092+
("var [...a] = foo;'use strict';b(00);", None), // { "ecmaVersion": 6 },
1093+
("var {a} = foo;'use strict';b(00);", None), // { "ecmaVersion": 6 },
1094+
(
1095+
"console.log('foo')
1096+
var a
1097+
+b > 0 ? bar() : baz()",
1098+
None,
1099+
),
1100+
(
1101+
"console.log('foo')
1102+
var [a] = foo;
1103+
+b > 0 ? bar() : baz()",
1104+
None,
1105+
), // { "ecmaVersion": 6 },
1106+
(
1107+
"console.log('foo')
1108+
var {a} = foo;
1109+
+b > 0 ? bar() : baz()",
1110+
None,
1111+
), // { "ecmaVersion": 6 },
1112+
(
1113+
"let x;
1114+
() => x = 1;",
1115+
None,
1116+
), // { "ecmaVersion": 6 },
1117+
(
1118+
"let [a = 1] = arr;
1119+
a = 2;",
1120+
None,
1121+
), // { "ecmaVersion": 6 },
1122+
// ("function foo(a = 1, b){alert(b);} foo();", None), // { "ecmaVersion": 6 },
1123+
("function foo(a = 1) {a = 2;} foo();", None), // { "ecmaVersion": 6 },
1124+
("function foo(a = 1, b) {a = 2;} foo();", None), // { "ecmaVersion": 6 },
1125+
("using resource = getResource();", None), // { "sourceType": "module", "ecmaVersion": 2026, },
1126+
("await using resource = getResource();", None), // { "sourceType": "module", "ecmaVersion": 2026, }
9781127
];
9791128

9801129
Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail)

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,15 +526,20 @@ fn test_vars_catch() {
526526

527527
#[test]
528528
fn test_vars_using() {
529-
let pass = vec![("using a = 1; console.log(a)", None)];
529+
let pass = vec![
530+
("using a = 1; console.log(a)", None),
531+
("using a = 1;", Some(serde_json::json!([{ "ignoreUsingDeclarations": true }]))),
532+
("await using a = 1;", Some(serde_json::json!([{ "ignoreUsingDeclarations": true }]))),
533+
];
530534

531-
let fail = vec![("using a = 1;", None)];
535+
let fail = vec![("using a = 1;", None), ("await using a = 1;", None)];
532536

533537
Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail)
534538
.intentionally_allow_no_fix_tests()
535539
.with_snapshot_suffix("oxc-vars-using")
536540
.test_and_snapshot();
537541
}
542+
538543
#[test]
539544
fn test_functions() {
540545
let pass = vec![

0 commit comments

Comments
 (0)