Skip to content
This repository was archived by the owner on Aug 31, 2023. It is now read-only.

Commit 7f72d15

Browse files
nikeeeematipicoConaclos
authored
feat(rome_js_analyze): noUselessTypeConstraint (#4484)
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> Co-authored-by: Victorien ELVINGER <victorien@elvinger.fr>
1 parent 61abdc3 commit 7f72d15

File tree

16 files changed

+678
-17
lines changed

16 files changed

+678
-17
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ the correct rules to apply [#4502](https://github.com/rome/tools/issues/4502)
2727

2828
### Linter
2929

30+
#### New rules
31+
- [`noUselessTypeConstraint`](https://docs.rome.tools/lint/rules/noUselessTypeConstraint/)
32+
3033
#### Other changes
3134

3235
- `noInnerDeclarations`: allow function declarations in nested block inside an _ES module_ [#4492](https://github.com/rome/tools/compare/main...Conaclos:noInnerDeclarations/4492?expand=1).
@@ -54,7 +57,6 @@ the correct rules to apply [#4502](https://github.com/rome/tools/issues/4502)
5457
- Fix an issue where the `noAssignInExpressions` rule replaced the operator with an invalid token, which caused other lint rules to crash. [#4464](https://github.com/rome/tools/issues/4464)
5558
- Fix an issue that [`noUnusedVariables`](https://docs.rome.tools/lint/rules/nounusedvariables/) rule did not correctly detect exports when a variable and an `interface` had the same name [#4468](https://github.com/rome/tools/pull/4468)
5659

57-
5860
## 12.1.0
5961

6062
### CLI

crates/rome_diagnostics_categories/src/categories.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ define_categories! {
4242
"lint/complexity/noUselessLabel":"https://docs.rome.tools/lint/rules/noUselessLabel",
4343
"lint/complexity/noUselessRename": "https://docs.rome.tools/lint/rules/noUselessRename",
4444
"lint/complexity/noUselessSwitchCase": "https://docs.rome.tools/lint/rules/noUselessSwitchCase",
45+
"lint/complexity/noUselessTypeConstraint": "https://docs.rome.tools/lint/rules/noUselessTypeConstraint",
4546
"lint/complexity/noWith": "https://docs.rome.tools/lint/rules/noWith",
4647
"lint/complexity/useFlatMap": "https://docs.rome.tools/lint/rules/useFlatMap",
4748
"lint/complexity/useOptionalChain": "https://docs.rome.tools/lint/rules/useOptionalChain",

crates/rome_js_analyze/src/analyzers/complexity.rs

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
use rome_analyze::context::RuleContext;
2+
use rome_analyze::{declare_rule, ActionCategory, Ast, Rule, RuleDiagnostic};
3+
use rome_console::markup;
4+
5+
use rome_diagnostics::Applicability;
6+
use rome_js_syntax::TsTypeConstraintClause;
7+
use rome_rowan::{AstNode, BatchMutationExt};
8+
9+
use crate::JsRuleAction;
10+
11+
declare_rule! {
12+
/// Disallow using `any` or `unknown` as type constraint.
13+
///
14+
/// Generic type parameters (`<T>`) in TypeScript may be **constrained** with [`extends`](https://www.typescriptlang.org/docs/handbook/generics.html#generic-constraints).
15+
/// A supplied type must then be a subtype of the supplied constraint.
16+
/// All types are subtypes of `any` and `unknown`.
17+
/// It is thus useless to extend from `any` or `unknown`.
18+
///
19+
/// Source: https://typescript-eslint.io/rules/no-unnecessary-type-constraint/
20+
///
21+
/// ## Examples
22+
///
23+
/// ### Invalid
24+
///
25+
/// ```ts,expect_diagnostic
26+
/// interface FooAny<T extends any> {}
27+
/// ```
28+
/// ```ts,expect_diagnostic
29+
/// type BarAny<T extends any> = {};
30+
/// ```
31+
/// ```ts,expect_diagnostic
32+
/// class BazAny<T extends any> {
33+
/// }
34+
/// ```
35+
/// ```ts,expect_diagnostic
36+
/// class BazAny {
37+
/// quxAny<U extends any>() {}
38+
/// }
39+
/// ```
40+
/// ```ts,expect_diagnostic
41+
/// const QuuxAny = <T extends any>() => {};
42+
/// ```
43+
/// ```ts,expect_diagnostic
44+
/// function QuuzAny<T extends any>() {}
45+
/// ```
46+
///
47+
/// ```ts,expect_diagnostic
48+
/// interface FooUnknown<T extends unknown> {}
49+
/// ```
50+
/// ```ts,expect_diagnostic
51+
/// type BarUnknown<T extends unknown> = {};
52+
/// ```
53+
/// ```ts,expect_diagnostic
54+
/// class BazUnknown<T extends unknown> {
55+
/// }
56+
/// ```ts,expect_diagnostic
57+
/// class BazUnknown {
58+
/// quxUnknown<U extends unknown>() {}
59+
/// }
60+
/// ```
61+
/// ```ts,expect_diagnostic
62+
/// const QuuxUnknown = <T extends unknown>() => {};
63+
/// ```
64+
/// ```ts,expect_diagnostic
65+
/// function QuuzUnknown<T extends unknown>() {}
66+
/// ```
67+
///
68+
/// ### Valid
69+
///
70+
/// ```ts
71+
/// interface Foo<T> {}
72+
///
73+
/// type Bar<T> = {};
74+
///```
75+
pub(crate) NoUselessTypeConstraint {
76+
version: "next",
77+
name: "noUselessTypeConstraint",
78+
recommended: true,
79+
}
80+
}
81+
82+
impl Rule for NoUselessTypeConstraint {
83+
type Query = Ast<TsTypeConstraintClause>;
84+
type State = ();
85+
type Signals = Option<Self::State>;
86+
type Options = ();
87+
88+
fn run(ctx: &RuleContext<Self>) -> Option<Self::State> {
89+
let node = ctx.query();
90+
let ty = node.ty().ok()?;
91+
92+
if ty.as_ts_any_type().is_some() || ty.as_ts_unknown_type().is_some() {
93+
Some(())
94+
} else {
95+
None
96+
}
97+
}
98+
99+
fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> {
100+
let node = ctx.query();
101+
Some(
102+
RuleDiagnostic::new(
103+
rule_category!(),
104+
node.syntax().text_trimmed_range(),
105+
markup! {
106+
"Constraining a type parameter to "<Emphasis>"any"</Emphasis>" or "<Emphasis>"unknown"</Emphasis>" is useless."
107+
},
108+
)
109+
.note(markup! {
110+
"All types are subtypes of "<Emphasis>"any"</Emphasis>" and "<Emphasis>"unknown"</Emphasis>"."
111+
}),
112+
)
113+
}
114+
115+
fn action(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<JsRuleAction> {
116+
let node = ctx.query();
117+
let mut mutation = ctx.root().begin();
118+
mutation.remove_node(node.clone());
119+
120+
Some(JsRuleAction {
121+
category: ActionCategory::QuickFix,
122+
applicability: Applicability::MaybeIncorrect,
123+
message: markup! { "Remove the constraint." }.to_owned(),
124+
mutation,
125+
})
126+
}
127+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
interface FooAny1<T extends any> {
2+
field: T;
3+
}
4+
5+
interface FooAny2<T extends unknown> {
6+
field: T;
7+
}
8+
9+
class BazAny<T extends any> {
10+
quxAny<U extends any>() {}
11+
}
12+
13+
const QuuxAny = <T extends any>() => {};
14+
15+
function QuuzAny<T extends any>() {}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
---
2+
source: crates/rome_js_analyze/tests/spec_tests.rs
3+
assertion_line: 96
4+
expression: invalid.ts
5+
---
6+
# Input
7+
```js
8+
interface FooAny1<T extends any> {
9+
field: T;
10+
}
11+
12+
interface FooAny2<T extends unknown> {
13+
field: T;
14+
}
15+
16+
class BazAny<T extends any> {
17+
quxAny<U extends any>() {}
18+
}
19+
20+
const QuuxAny = <T extends any>() => {};
21+
22+
function QuuzAny<T extends any>() {}
23+
24+
```
25+
26+
# Diagnostics
27+
```
28+
invalid.ts:1:21 lint/complexity/noUselessTypeConstraint FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
29+
30+
! Constraining a type parameter to any or unknown is useless.
31+
32+
> 1 │ interface FooAny1<T extends any> {
33+
^^^^^^^^^^^
34+
2field: T;
35+
3}
36+
37+
i All types are subtypes of any and unknown.
38+
39+
i Suggested fix: Remove the constraint.
40+
41+
1 │ interface·FooAny1<T·extends·any>·{
42+
-----------
43+
44+
```
45+
46+
```
47+
invalid.ts:5:21 lint/complexity/noUselessTypeConstraint FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
48+
49+
! Constraining a type parameter to any or unknown is useless.
50+
51+
3}
52+
4 │
53+
> 5 │ interface FooAny2<T extends unknown> {
54+
^^^^^^^^^^^^^^^
55+
6field: T;
56+
7}
57+
58+
i All types are subtypes of any and unknown.
59+
60+
i Suggested fix: Remove the constraint.
61+
62+
5 │ interface·FooAny2<T·extends·unknown>·{
63+
---------------
64+
65+
```
66+
67+
```
68+
invalid.ts:9:16 lint/complexity/noUselessTypeConstraint FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
69+
70+
! Constraining a type parameter to any or unknown is useless.
71+
72+
7}
73+
8 │
74+
> 9 │ class BazAny<T extends any> {
75+
^^^^^^^^^^^
76+
10quxAny<U extends any>() {}
77+
11}
78+
79+
i All types are subtypes of any and unknown.
80+
81+
i Suggested fix: Remove the constraint.
82+
83+
9 │ class·BazAny<T·extends·any>·{
84+
-----------
85+
86+
```
87+
88+
```
89+
invalid.ts:10:12 lint/complexity/noUselessTypeConstraint FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
90+
91+
! Constraining a type parameter to any or unknown is useless.
92+
93+
9class BazAny<T extends any> {
94+
> 10 │ quxAny<U extends any>() {}
95+
^^^^^^^^^^^
96+
11 │ }
97+
12
98+
99+
i All types are subtypes of any and unknown.
100+
101+
i Suggested fix: Remove the constraint.
102+
103+
10 │ ··quxAny<U·extends·any>()·{}
104+
-----------
105+
106+
```
107+
108+
```
109+
invalid.ts:13:20 lint/complexity/noUselessTypeConstraint FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
110+
111+
! Constraining a type parameter to any or unknown is useless.
112+
113+
11}
114+
12 │
115+
> 13 │ const QuuxAny = <T extends any>() => {};
116+
│ ^^^^^^^^^^^
117+
14 │
118+
15 │ function QuuzAny<T extends any>() {}
119+
120+
i All types are subtypes of any and unknown.
121+
122+
i Suggested fix: Remove the constraint.
123+
124+
13 │ const·QuuxAny·=·<T·extends·any>()·=>·{};
125+
│ -----------
126+
127+
```
128+
129+
```
130+
invalid.ts:15:20 lint/complexity/noUselessTypeConstraint FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
131+
132+
! Constraining a type parameter to any or unknown is useless.
133+
134+
13 │ const QuuxAny = <T extends any>() => {};
135+
14 │
136+
> 15 │ function QuuzAny<T extends any>() {}
137+
│ ^^^^^^^^^^^
138+
16 │
139+
140+
i All types are subtypes of any and unknown.
141+
142+
i Suggested fix: Remove the constraint.
143+
144+
15 │ function·QuuzAny<T·extends·any>()·{}
145+
│ -----------
146+
147+
```
148+
149+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
interface FooAny0<T> {
2+
field: T;
3+
}
4+
5+
interface FooNotAny0<T extends string> {
6+
field: T;
7+
}
8+
9+
type Bar<T> = {};
10+
11+
type Bar2<T extends string> = {};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
source: crates/rome_js_analyze/tests/spec_tests.rs
3+
expression: valid.ts
4+
---
5+
# Input
6+
```js
7+
interface FooAny0<T> {
8+
field: T;
9+
}
10+
11+
interface FooNotAny0<T extends string> {
12+
field: T;
13+
}
14+
15+
type Bar<T> = {};
16+
17+
type Bar2<T extends string> = {};
18+
19+
```
20+
21+

0 commit comments

Comments
 (0)