feat(linter): add missing test cases to no-empty-interface and add config (#2973)

Based on this https://github.com/oxc-project/oxc/issues/2934 I added
missing test cases and rule configuration

---------

Co-authored-by: j.buendia <j.buendia>
This commit is contained in:
Jose 2024-04-14 14:12:28 +02:00 committed by GitHub
parent c7e70c80f0
commit aa62dbbfd5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 180 additions and 42 deletions

View file

@ -5,6 +5,7 @@ use oxc_diagnostics::{
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use serde_json::Value;
use crate::{context::LintContext, rule::Rule, AstNode};
@ -21,7 +22,9 @@ struct NoEmptyInterfaceDiagnostic(#[label] pub Span);
struct NoEmptyInterfaceExtendDiagnostic(#[label] pub Span);
#[derive(Debug, Default, Clone)]
pub struct NoEmptyInterface;
pub struct NoEmptyInterface {
allow_single_extends: bool,
}
declare_oxc_lint!(
/// ### What it does
@ -44,6 +47,13 @@ declare_oxc_lint!(
);
impl Rule for NoEmptyInterface {
fn from_configuration(value: Value) -> Self {
let allow_single_extends = value.get(0).map_or(true, |config| {
config.get("allow_single_extends").and_then(Value::as_bool).unwrap_or_default()
});
Self { allow_single_extends }
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::TSInterfaceDeclaration(interface) = node.kind() {
if interface.body.body.is_empty() {
@ -51,8 +61,11 @@ impl Rule for NoEmptyInterface {
None => {
ctx.diagnostic(NoEmptyInterfaceDiagnostic(interface.span));
}
Some(extends) if extends.len() == 1 => {
ctx.diagnostic(NoEmptyInterfaceExtendDiagnostic(interface.span));
if !self.allow_single_extends {
ctx.diagnostic(NoEmptyInterfaceExtendDiagnostic(interface.span));
}
}
_ => {}
}
@ -66,25 +79,134 @@ fn test() {
use crate::tester::Tester;
let pass = vec![
"interface Foo { name: string; }",
"interface Foo { name: string; }
interface Bar { age: number; }
// valid because extending multiple interfaces can be used instead of a union type
interface Baz extends Foo, Bar {}",
(
"
interface Foo {
name: string;
}
",
None,
),
(
"
interface Foo {
name: string;
}
interface Bar {
age: number;
}
// valid because extending multiple interfaces can be used instead of a union type
interface Baz extends Foo, Bar {}
",
None,
),
(
"
interface Foo {
name: string;
}
interface Bar extends Foo {}
",
Some(serde_json::json!([{ "allow_single_extends": true }])),
),
(
"
interface Foo {
props: string;
}
interface Bar extends Foo {}
class Bar {}
",
Some(serde_json::json!([{ "allow_single_extends": true }])),
),
];
let fail = vec![
"interface Foo {}",
"interface Foo { props: string; } interface Bar extends Foo {} class Baz {}",
"interface Foo { props: string; } interface Bar extends Foo {} class Bar {}",
"interface Foo { props: string; } interface Bar extends Foo {} const bar = class Bar {};",
"interface Foo { name: string; } interface Bar extends Foo {}",
"interface Foo extends Array<number> {}",
"interface Foo extends Array<number | {}> {}",
"interface Bar { bar: string; } interface Foo extends Array<Bar> {}",
"type R = Record<string, unknown>; interface Foo extends R {}",
"interface Foo<T> extends Bar<T> {}",
"declare module FooBar { type Baz = typeof baz; export interface Bar extends Baz {} }",
("interface Foo {}", None),
(
"
interface Foo {
props: string;
}
interface Bar extends Foo {}
class Baz {}
",
Some(serde_json::json!([{ "allow_single_extends": false }])),
),
(
"
interface Foo {
props: string;
}
interface Bar extends Foo {}
class Bar {}
",
Some(serde_json::json!([{ "allow_single_extends": false }])),
),
(
"
interface Foo {
props: string;
}
interface Bar extends Foo {}
const bar = class Bar {};
",
Some(serde_json::json!([{ "allow_single_extends": false }])),
),
(
"
interface Foo {
name: string;
}
interface Bar extends Foo {}
",
Some(serde_json::json!([{ "allow_single_extends": false }])),
),
("interface Foo extends Array<number> {}", None),
("interface Foo extends Array<number | {}> {}", None),
(
"
interface Bar {
bar: string;
}
interface Foo extends Array<Bar> {}
",
None,
),
(
"
type R = Record<string, unknown>;
interface Foo extends R {}
",
None,
),
(
"
interface Foo<T> extends Bar<T> {}
",
None,
),
(
"
declare module FooBar {
type Baz = typeof baz;
export interface Bar extends Baz {}
}
",
None,
),
];
Tester::new(NoEmptyInterface::NAME, pass, fail).test_and_snapshot();

View file

@ -9,27 +9,35 @@ expression: no_empty_interface
╰────
⚠ typescript-eslint(no-empty-interface): an interface declaring no members is equivalent to its supertype
╭─[no_empty_interface.tsx:1:34]
1 │ interface Foo { props: string; } interface Bar extends Foo {} class Baz {}
· ────────────────────────────
╭─[no_empty_interface.tsx:6:4]
5 │
6 │ interface Bar extends Foo {}
· ────────────────────────────
7 │
╰────
⚠ typescript-eslint(no-empty-interface): an interface declaring no members is equivalent to its supertype
╭─[no_empty_interface.tsx:1:34]
1 │ interface Foo { props: string; } interface Bar extends Foo {} class Bar {}
· ────────────────────────────
╭─[no_empty_interface.tsx:6:4]
5 │
6 │ interface Bar extends Foo {}
· ────────────────────────────
7 │
╰────
⚠ typescript-eslint(no-empty-interface): an interface declaring no members is equivalent to its supertype
╭─[no_empty_interface.tsx:1:34]
1 │ interface Foo { props: string; } interface Bar extends Foo {} const bar = class Bar {};
· ────────────────────────────
╭─[no_empty_interface.tsx:6:4]
5 │
6 │ interface Bar extends Foo {}
· ────────────────────────────
7 │
╰────
⚠ typescript-eslint(no-empty-interface): an interface declaring no members is equivalent to its supertype
╭─[no_empty_interface.tsx:1:33]
1 │ interface Foo { name: string; } interface Bar extends Foo {}
· ────────────────────────────
╭─[no_empty_interface.tsx:6:4]
5 │
6 │ interface Bar extends Foo {}
· ────────────────────────────
7 │
╰────
⚠ typescript-eslint(no-empty-interface): an interface declaring no members is equivalent to its supertype
@ -45,25 +53,33 @@ expression: no_empty_interface
╰────
⚠ typescript-eslint(no-empty-interface): an interface declaring no members is equivalent to its supertype
╭─[no_empty_interface.tsx:1:32]
1 │ interface Bar { bar: string; } interface Foo extends Array<Bar> {}
· ───────────────────────────────────
╭─[no_empty_interface.tsx:5:4]
4 │ }
5 │ interface Foo extends Array<Bar> {}
· ───────────────────────────────────
6 │
╰────
⚠ typescript-eslint(no-empty-interface): an interface declaring no members is equivalent to its supertype
╭─[no_empty_interface.tsx:1:35]
1 │ type R = Record<string, unknown>; interface Foo extends R {}
· ──────────────────────────
╭─[no_empty_interface.tsx:3:4]
2 │ type R = Record<string, unknown>;
3 │ interface Foo extends R {}
· ──────────────────────────
4 │
╰────
⚠ typescript-eslint(no-empty-interface): an interface declaring no members is equivalent to its supertype
╭─[no_empty_interface.tsx:1:1]
1 │ interface Foo<T> extends Bar<T> {}
· ──────────────────────────────────
╭─[no_empty_interface.tsx:2:4]
1 │
2 │ interface Foo<T> extends Bar<T> {}
· ──────────────────────────────────
3 │
╰────
⚠ typescript-eslint(no-empty-interface): an interface declaring no members is equivalent to its supertype
╭─[no_empty_interface.tsx:1:55]
1 │ declare module FooBar { type Baz = typeof baz; export interface Bar extends Baz {} }
· ────────────────────────────
╭─[no_empty_interface.tsx:4:13]
3 │ type Baz = typeof baz;
4 │ export interface Bar extends Baz {}
· ────────────────────────────
5 │ }
╰────