mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
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:
parent
c7e70c80f0
commit
aa62dbbfd5
2 changed files with 180 additions and 42 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 │ }
|
||||
╰────
|
||||
|
|
|
|||
Loading…
Reference in a new issue