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
38 changes: 37 additions & 1 deletion crates/oxc_linter/src/rules/eslint/no_unused_vars/allowed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,43 @@ impl NoUnusedVars {
symbol: &Symbol<'_, '_>,
declaration_id: NodeId,
) -> bool {
matches!(symbol.nodes().parent_kind(declaration_id), AstKind::TSMappedType(_))
let nodes = symbol.nodes();
let scoping = symbol.scoping();

if matches!(nodes.parent_kind(declaration_id), AstKind::TSMappedType(_)) {
return true;
}

let is_interface_type_parameter = match nodes.parent_kind(declaration_id) {
AstKind::TSInterfaceDeclaration(_) => true,
AstKind::TSTypeParameterDeclaration(_) => {
matches!(
nodes.parent_kind(nodes.parent_id(declaration_id)),
AstKind::TSInterfaceDeclaration(_)
)
}
_ => false,
};
if !is_interface_type_parameter {
return false;
}

// type parameters used within type declarations in ambient ts module
// blocks are required for declaration merging to work, since signatures
// must match.
let Some(parent_scope_id) = scoping.scope_parent_id(symbol.scope_id()) else {
return false;
};
let scope_flags = scoping.scope_flags(parent_scope_id);
if scope_flags.is_ts_module_block() {
// get declaration node for the parent scope
let parent_node_id = scoping.get_node_id(parent_scope_id);
if let AstKind::TSModuleDeclaration(namespace) = nodes.get_node(parent_node_id).kind() {
return namespace.declare;
}
}

false
}

/// Returns `true` if this unused parameter should be allowed (i.e. not
Expand Down
21 changes: 20 additions & 1 deletion crates/oxc_linter/src/rules/eslint/no_unused_vars/tests/oxc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1246,9 +1246,28 @@ fn test_namespaces() {
export { Foo }
",
"declare module 'tsdown' { function bar(): void; }",
"
declare module 'vitest' {
interface Matchers<T> {
toBeFoo(value: unknown): unknown;
}
}
",
];

let fail = vec!["namespace N {}", "export namespace N { function foo() }"];
let fail = vec![
"namespace N {}",
"export namespace N { function foo() }",
"
export namespace NonAmbientModuleDeclaration {
export interface Matchers<T> extends MatcherOverride {
toBeFoo(value: unknown): unknown;
}
}
",
"declare module 'bun:test' { type Matchers2<T> = {} }",
"declare module 'bun:test' { class MyClass<T> {} }",
];

Tester::new(NoUnusedVars::NAME, NoUnusedVars::PLUGIN, pass, fail)
.intentionally_allow_no_fix_tests()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,37 @@ source: crates/oxc_linter/src/tester.rs
· ╰── 'foo' is declared here
╰────
help: Consider removing this declaration.

⚠ eslint(no-unused-vars): Variable 'T' is declared but never used. Unused variables should start with a '_'.
╭─[no_unused_vars.tsx:3:39]
2 │ export namespace NonAmbientModuleDeclaration {
3 │ export interface Matchers<T> extends MatcherOverride {
· ┬
· ╰── 'T' is declared here
4 │ toBeFoo(value: unknown): unknown;
╰────
help: Consider removing this declaration.

⚠ eslint(no-unused-vars): Variable 'T' is declared but never used. Unused variables should start with a '_'.
╭─[no_unused_vars.tsx:1:44]
1 │ declare module 'bun:test' { type Matchers2<T> = {} }
· ┬
· ╰── 'T' is declared here
╰────
help: Consider removing this declaration.

⚠ eslint(no-unused-vars): Class 'MyClass' is declared but never used.
╭─[no_unused_vars.tsx:1:35]
1 │ declare module 'bun:test' { class MyClass<T> {} }
· ───┬───
· ╰── 'MyClass' is declared here
╰────
help: Consider removing this declaration.

⚠ eslint(no-unused-vars): Variable 'T' is declared but never used. Unused variables should start with a '_'.
╭─[no_unused_vars.tsx:1:43]
1 │ declare module 'bun:test' { class MyClass<T> {} }
· ┬
· ╰── 'T' is declared here
╰────
help: Consider removing this declaration.
Loading