diff --git a/crates/oxc_linter/src/rules/typescript/no_invalid_void_type.rs b/crates/oxc_linter/src/rules/typescript/no_invalid_void_type.rs index d06c0e939f247..c25b296bfb33b 100644 --- a/crates/oxc_linter/src/rules/typescript/no_invalid_void_type.rs +++ b/crates/oxc_linter/src/rules/typescript/no_invalid_void_type.rs @@ -151,12 +151,10 @@ impl Rule for NoInvalidVoidType { if let AstKind::TSTypeParameterInstantiation(_) = parent.kind() { let grand_parent_node = ctx.nodes().parent_node(parent.id()); - if let AstKind::TSTypeReference(type_reference) = grand_parent_node.kind() { - self.check_generic_type_argument( - keyword.span, - ctx.source_range(type_reference.type_name.span()), - ctx, - ); + if let Some(fully_qualified_name) = + get_generic_type_argument_name(parent.kind().span(), grand_parent_node.kind(), ctx) + { + self.check_generic_type_argument(keyword.span, fully_qualified_name, ctx); return; } @@ -267,6 +265,32 @@ impl NoInvalidVoidType { } } +fn get_generic_type_argument_name<'a>( + type_arguments_span: Span, + grand_parent: AstKind<'a>, + ctx: &LintContext<'a>, +) -> Option<&'a str> { + match grand_parent { + AstKind::TSTypeReference(type_reference) => { + Some(ctx.source_range(type_reference.type_name.span())) + } + AstKind::TSClassImplements(class_implements) => { + Some(ctx.source_range(class_implements.expression.span())) + } + AstKind::TSInterfaceHeritage(interface_heritage) => { + Some(ctx.source_range(interface_heritage.expression.span())) + } + AstKind::Class(class) => class + .super_type_arguments + .as_ref() + .filter(|type_arguments| type_arguments.span == type_arguments_span) + .and_then(|_| { + class.super_class.as_ref().map(|super_class| ctx.source_range(super_class.span())) + }), + _ => None, + } +} + fn is_valid_return_type_annotation(parent: AstKind<'_>, grand_parent: Option>) -> bool { let AstKind::TSTypeAnnotation(type_annotation) = parent else { return false; @@ -829,6 +853,32 @@ fn test() { ", Some(serde_json::json!([{ "allowAsThisParameter": true }])), ), + ( + " + interface Producer { + get: () => T; + } + + export class Test implements Producer { + get = () => null; + } + ", + None, + ), + ("class Test extends Base {}", None), + ("interface Test extends Base {}", None), + ( + " + interface Producer { + get: () => T; + } + + export class Test implements Producer { + get = () => null; + } + ", + Some(serde_json::json!([{ "allowInGenericTypeArguments": ["Producer"] }])), + ), ]; let fail = vec![ @@ -973,6 +1023,18 @@ fn test() { "type BannedVoid = Ex.Mx.Tx;", Some(serde_json::json!([{ "allowInGenericTypeArguments": ["Tx"] }])), ), + ( + " + interface Producer { + get: () => T; + } + + export class Test implements Producer { + get = () => null; + } + ", + Some(serde_json::json!([{ "allowInGenericTypeArguments": ["Base"] }])), + ), ( "function takeVoid(thing: void) {}", Some(serde_json::json!([{ "allowInGenericTypeArguments": ["Allowed"] }])), @@ -995,6 +1057,26 @@ fn test() { serde_json::json!([ { "allowAsThisParameter": true, "allowInGenericTypeArguments": false }, ]), ), ), + ( + " + interface Producer { + get: () => T; + } + + export class Test implements Producer { + get = () => null; + } + ", + Some(serde_json::json!([{ "allowInGenericTypeArguments": false }])), + ), + ( + "class Test extends Base {}", + Some(serde_json::json!([{ "allowInGenericTypeArguments": false }])), + ), + ( + "interface Test extends Base {}", + Some(serde_json::json!([{ "allowInGenericTypeArguments": false }])), + ), ]; Tester::new(NoInvalidVoidType::NAME, NoInvalidVoidType::PLUGIN, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/snapshots/typescript_no_invalid_void_type.snap b/crates/oxc_linter/src/snapshots/typescript_no_invalid_void_type.snap index ba021e21eb412..b13e286befea6 100644 --- a/crates/oxc_linter/src/snapshots/typescript_no_invalid_void_type.snap +++ b/crates/oxc_linter/src/snapshots/typescript_no_invalid_void_type.snap @@ -327,6 +327,15 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Replace this `void` type with an allowed type, or keep `void` only in a valid return position. + ⚠ typescript-eslint(no-invalid-void-type): Do not use `void` as a type argument for `Producer`. + ╭─[no_invalid_void_type.tsx:6:51] + 5 │ + 6 │ export class Test implements Producer { + · ──── + 7 │ get = () => null; + ╰──── + help: Replace this `void` type with an allowed type, or keep `void` only in a valid return position. + ⚠ typescript-eslint(no-invalid-void-type): Use `void` only as a return type or generic type argument. ╭─[no_invalid_void_type.tsx:1:26] 1 │ function takeVoid(thing: void) {} @@ -354,3 +363,26 @@ source: crates/oxc_linter/src/tester.rs · ──── ╰──── help: Replace this `void` type with an allowed type, or keep `void` only in a valid return position. + + ⚠ typescript-eslint(no-invalid-void-type): Use `void` only as a return type. + ╭─[no_invalid_void_type.tsx:6:51] + 5 │ + 6 │ export class Test implements Producer { + · ──── + 7 │ get = () => null; + ╰──── + help: Replace this `void` type with an allowed type, or keep `void` only in a valid return position. + + ⚠ typescript-eslint(no-invalid-void-type): Use `void` only as a return type. + ╭─[no_invalid_void_type.tsx:1:25] + 1 │ class Test extends Base {} + · ──── + ╰──── + help: Replace this `void` type with an allowed type, or keep `void` only in a valid return position. + + ⚠ typescript-eslint(no-invalid-void-type): Use `void` only as a return type. + ╭─[no_invalid_void_type.tsx:1:29] + 1 │ interface Test extends Base {} + · ──── + ╰──── + help: Replace this `void` type with an allowed type, or keep `void` only in a valid return position.