From 7f72d1575f38073b7625f4cd69157a79f70f7211 Mon Sep 17 00:00:00 2001 From: Niklas Mollenhauer Date: Thu, 18 May 2023 11:50:31 +0200 Subject: [PATCH] feat(rome_js_analyze): `noUselessTypeConstraint` (#4484) Co-authored-by: Emanuele Stoppa Co-authored-by: Victorien ELVINGER --- CHANGELOG.md | 4 +- .../src/categories.rs | 1 + .../src/analyzers/complexity.rs | 3 +- .../complexity/no_useless_type_constraint.rs | 127 +++++++++ .../noUselessTypeConstraint/invalid.ts | 15 + .../noUselessTypeConstraint/invalid.ts.snap | 149 ++++++++++ .../noUselessTypeConstraint/valid.ts | 11 + .../noUselessTypeConstraint/valid.ts.snap | 21 ++ .../src/configuration/linter/rules.rs | 52 +++- .../src/configuration/parse/json/rules.rs | 19 ++ editors/vscode/configuration_schema.json | 7 + npm/backend-jsonrpc/src/workspace.ts | 5 + npm/rome/configuration_schema.json | 7 + .../components/generated/NumberOfRules.astro | 2 +- website/src/pages/lint/rules/index.mdx | 7 + .../lint/rules/noUselessTypeConstraint.md | 265 ++++++++++++++++++ 16 files changed, 678 insertions(+), 17 deletions(-) create mode 100644 crates/rome_js_analyze/src/analyzers/complexity/no_useless_type_constraint.rs create mode 100644 crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/invalid.ts create mode 100644 crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/invalid.ts.snap create mode 100644 crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/valid.ts create mode 100644 crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/valid.ts.snap create mode 100644 website/src/pages/lint/rules/noUselessTypeConstraint.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 249f7711d56..aa602aa5495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ the correct rules to apply [#4502](https://github.com/rome/tools/issues/4502) ### Linter +#### New rules +- [`noUselessTypeConstraint`](https://docs.rome.tools/lint/rules/noUselessTypeConstraint/) + #### Other changes - `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) - 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) - 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) - ## 12.1.0 ### CLI diff --git a/crates/rome_diagnostics_categories/src/categories.rs b/crates/rome_diagnostics_categories/src/categories.rs index 7bdbfbceb7a..b5ce1bdbf0c 100644 --- a/crates/rome_diagnostics_categories/src/categories.rs +++ b/crates/rome_diagnostics_categories/src/categories.rs @@ -42,6 +42,7 @@ define_categories! { "lint/complexity/noUselessLabel":"https://docs.rome.tools/lint/rules/noUselessLabel", "lint/complexity/noUselessRename": "https://docs.rome.tools/lint/rules/noUselessRename", "lint/complexity/noUselessSwitchCase": "https://docs.rome.tools/lint/rules/noUselessSwitchCase", + "lint/complexity/noUselessTypeConstraint": "https://docs.rome.tools/lint/rules/noUselessTypeConstraint", "lint/complexity/noWith": "https://docs.rome.tools/lint/rules/noWith", "lint/complexity/useFlatMap": "https://docs.rome.tools/lint/rules/useFlatMap", "lint/complexity/useOptionalChain": "https://docs.rome.tools/lint/rules/useOptionalChain", diff --git a/crates/rome_js_analyze/src/analyzers/complexity.rs b/crates/rome_js_analyze/src/analyzers/complexity.rs index aa61989d7f7..a22cfba7e4b 100644 --- a/crates/rome_js_analyze/src/analyzers/complexity.rs +++ b/crates/rome_js_analyze/src/analyzers/complexity.rs @@ -9,8 +9,9 @@ mod no_useless_constructor; mod no_useless_label; mod no_useless_rename; mod no_useless_switch_case; +mod no_useless_type_constraint; mod no_with; mod use_flat_map; mod use_optional_chain; mod use_simplified_logic_expression; -declare_group! { pub (crate) Complexity { name : "complexity" , rules : [self :: no_extra_boolean_cast :: NoExtraBooleanCast , self :: no_extra_semicolon :: NoExtraSemicolon , self :: no_multiple_spaces_in_regular_expression_literals :: NoMultipleSpacesInRegularExpressionLiterals , self :: no_useless_catch :: NoUselessCatch , self :: no_useless_constructor :: NoUselessConstructor , self :: no_useless_label :: NoUselessLabel , self :: no_useless_rename :: NoUselessRename , self :: no_useless_switch_case :: NoUselessSwitchCase , self :: no_with :: NoWith , self :: use_flat_map :: UseFlatMap , self :: use_optional_chain :: UseOptionalChain , self :: use_simplified_logic_expression :: UseSimplifiedLogicExpression ,] } } +declare_group! { pub (crate) Complexity { name : "complexity" , rules : [self :: no_extra_boolean_cast :: NoExtraBooleanCast , self :: no_extra_semicolon :: NoExtraSemicolon , self :: no_multiple_spaces_in_regular_expression_literals :: NoMultipleSpacesInRegularExpressionLiterals , self :: no_useless_catch :: NoUselessCatch , self :: no_useless_constructor :: NoUselessConstructor , self :: no_useless_label :: NoUselessLabel , self :: no_useless_rename :: NoUselessRename , self :: no_useless_switch_case :: NoUselessSwitchCase , self :: no_useless_type_constraint :: NoUselessTypeConstraint , self :: no_with :: NoWith , self :: use_flat_map :: UseFlatMap , self :: use_optional_chain :: UseOptionalChain , self :: use_simplified_logic_expression :: UseSimplifiedLogicExpression ,] } } diff --git a/crates/rome_js_analyze/src/analyzers/complexity/no_useless_type_constraint.rs b/crates/rome_js_analyze/src/analyzers/complexity/no_useless_type_constraint.rs new file mode 100644 index 00000000000..98fb83586e2 --- /dev/null +++ b/crates/rome_js_analyze/src/analyzers/complexity/no_useless_type_constraint.rs @@ -0,0 +1,127 @@ +use rome_analyze::context::RuleContext; +use rome_analyze::{declare_rule, ActionCategory, Ast, Rule, RuleDiagnostic}; +use rome_console::markup; + +use rome_diagnostics::Applicability; +use rome_js_syntax::TsTypeConstraintClause; +use rome_rowan::{AstNode, BatchMutationExt}; + +use crate::JsRuleAction; + +declare_rule! { + /// Disallow using `any` or `unknown` as type constraint. + /// + /// Generic type parameters (``) in TypeScript may be **constrained** with [`extends`](https://www.typescriptlang.org/docs/handbook/generics.html#generic-constraints). + /// A supplied type must then be a subtype of the supplied constraint. + /// All types are subtypes of `any` and `unknown`. + /// It is thus useless to extend from `any` or `unknown`. + /// + /// Source: https://typescript-eslint.io/rules/no-unnecessary-type-constraint/ + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```ts,expect_diagnostic + /// interface FooAny {} + /// ``` + /// ```ts,expect_diagnostic + /// type BarAny = {}; + /// ``` + /// ```ts,expect_diagnostic + /// class BazAny { + /// } + /// ``` + /// ```ts,expect_diagnostic + /// class BazAny { + /// quxAny() {} + /// } + /// ``` + /// ```ts,expect_diagnostic + /// const QuuxAny = () => {}; + /// ``` + /// ```ts,expect_diagnostic + /// function QuuzAny() {} + /// ``` + /// + /// ```ts,expect_diagnostic + /// interface FooUnknown {} + /// ``` + /// ```ts,expect_diagnostic + /// type BarUnknown = {}; + /// ``` + /// ```ts,expect_diagnostic + /// class BazUnknown { + /// } + /// ```ts,expect_diagnostic + /// class BazUnknown { + /// quxUnknown() {} + /// } + /// ``` + /// ```ts,expect_diagnostic + /// const QuuxUnknown = () => {}; + /// ``` + /// ```ts,expect_diagnostic + /// function QuuzUnknown() {} + /// ``` + /// + /// ### Valid + /// + /// ```ts + /// interface Foo {} + /// + /// type Bar = {}; + ///``` + pub(crate) NoUselessTypeConstraint { + version: "next", + name: "noUselessTypeConstraint", + recommended: true, + } +} + +impl Rule for NoUselessTypeConstraint { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Option { + let node = ctx.query(); + let ty = node.ty().ok()?; + + if ty.as_ts_any_type().is_some() || ty.as_ts_unknown_type().is_some() { + Some(()) + } else { + None + } + } + + fn diagnostic(ctx: &RuleContext, _state: &Self::State) -> Option { + let node = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + node.syntax().text_trimmed_range(), + markup! { + "Constraining a type parameter to ""any"" or ""unknown"" is useless." + }, + ) + .note(markup! { + "All types are subtypes of ""any"" and ""unknown""." + }), + ) + } + + fn action(ctx: &RuleContext, _state: &Self::State) -> Option { + let node = ctx.query(); + let mut mutation = ctx.root().begin(); + mutation.remove_node(node.clone()); + + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::MaybeIncorrect, + message: markup! { "Remove the constraint." }.to_owned(), + mutation, + }) + } +} diff --git a/crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/invalid.ts b/crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/invalid.ts new file mode 100644 index 00000000000..f6865e0f55b --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/invalid.ts @@ -0,0 +1,15 @@ +interface FooAny1 { + field: T; +} + +interface FooAny2 { + field: T; +} + +class BazAny { + quxAny() {} +} + +const QuuxAny = () => {}; + +function QuuzAny() {} diff --git a/crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/invalid.ts.snap b/crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/invalid.ts.snap new file mode 100644 index 00000000000..76cf22f6882 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/invalid.ts.snap @@ -0,0 +1,149 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +assertion_line: 96 +expression: invalid.ts +--- +# Input +```js +interface FooAny1 { + field: T; +} + +interface FooAny2 { + field: T; +} + +class BazAny { + quxAny() {} +} + +const QuuxAny = () => {}; + +function QuuzAny() {} + +``` + +# Diagnostics +``` +invalid.ts:1:21 lint/complexity/noUselessTypeConstraint FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Constraining a type parameter to any or unknown is useless. + + > 1 │ interface FooAny1 { + │ ^^^^^^^^^^^ + 2 │ field: T; + 3 │ } + + i All types are subtypes of any and unknown. + + i Suggested fix: Remove the constraint. + + 1 │ interface·FooAny1·{ + │ ----------- + +``` + +``` +invalid.ts:5:21 lint/complexity/noUselessTypeConstraint FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Constraining a type parameter to any or unknown is useless. + + 3 │ } + 4 │ + > 5 │ interface FooAny2 { + │ ^^^^^^^^^^^^^^^ + 6 │ field: T; + 7 │ } + + i All types are subtypes of any and unknown. + + i Suggested fix: Remove the constraint. + + 5 │ interface·FooAny2·{ + │ --------------- + +``` + +``` +invalid.ts:9:16 lint/complexity/noUselessTypeConstraint FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Constraining a type parameter to any or unknown is useless. + + 7 │ } + 8 │ + > 9 │ class BazAny { + │ ^^^^^^^^^^^ + 10 │ quxAny() {} + 11 │ } + + i All types are subtypes of any and unknown. + + i Suggested fix: Remove the constraint. + + 9 │ class·BazAny·{ + │ ----------- + +``` + +``` +invalid.ts:10:12 lint/complexity/noUselessTypeConstraint FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Constraining a type parameter to any or unknown is useless. + + 9 │ class BazAny { + > 10 │ quxAny() {} + │ ^^^^^^^^^^^ + 11 │ } + 12 │ + + i All types are subtypes of any and unknown. + + i Suggested fix: Remove the constraint. + + 10 │ ··quxAny()·{} + │ ----------- + +``` + +``` +invalid.ts:13:20 lint/complexity/noUselessTypeConstraint FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Constraining a type parameter to any or unknown is useless. + + 11 │ } + 12 │ + > 13 │ const QuuxAny = () => {}; + │ ^^^^^^^^^^^ + 14 │ + 15 │ function QuuzAny() {} + + i All types are subtypes of any and unknown. + + i Suggested fix: Remove the constraint. + + 13 │ const·QuuxAny·=·()·=>·{}; + │ ----------- + +``` + +``` +invalid.ts:15:20 lint/complexity/noUselessTypeConstraint FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Constraining a type parameter to any or unknown is useless. + + 13 │ const QuuxAny = () => {}; + 14 │ + > 15 │ function QuuzAny() {} + │ ^^^^^^^^^^^ + 16 │ + + i All types are subtypes of any and unknown. + + i Suggested fix: Remove the constraint. + + 15 │ function·QuuzAny()·{} + │ ----------- + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/valid.ts b/crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/valid.ts new file mode 100644 index 00000000000..06d0bfbb26e --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/valid.ts @@ -0,0 +1,11 @@ +interface FooAny0 { + field: T; +} + +interface FooNotAny0 { + field: T; +} + +type Bar = {}; + +type Bar2 = {}; diff --git a/crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/valid.ts.snap b/crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/valid.ts.snap new file mode 100644 index 00000000000..498b1ff12e4 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/complexity/noUselessTypeConstraint/valid.ts.snap @@ -0,0 +1,21 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: valid.ts +--- +# Input +```js +interface FooAny0 { + field: T; +} + +interface FooNotAny0 { + field: T; +} + +type Bar = {}; + +type Bar2 = {}; + +``` + + diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index bf70dfe31e3..b01ddb0016e 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -895,6 +895,15 @@ pub struct Complexity { )] #[serde(skip_serializing_if = "Option::is_none")] pub no_useless_switch_case: Option, + #[doc = "Disallow using any or unknown as type constraint."] + #[bpaf( + long("no-useless-type-constraint"), + argument("on|off|warn"), + optional, + hide + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_useless_type_constraint: Option, #[doc = "Disallow with statements in non-strict contexts."] #[bpaf(long("no-with"), argument("on|off|warn"), optional, hide)] #[serde(skip_serializing_if = "Option::is_none")] @@ -919,7 +928,7 @@ pub struct Complexity { } impl Complexity { const GROUP_NAME: &'static str = "complexity"; - pub(crate) const GROUP_RULES: [&'static str; 13] = [ + pub(crate) const GROUP_RULES: [&'static str; 14] = [ "noExtraBooleanCast", "noExtraSemicolon", "noMultipleSpacesInRegularExpressionLiterals", @@ -929,12 +938,13 @@ impl Complexity { "noUselessLabel", "noUselessRename", "noUselessSwitchCase", + "noUselessTypeConstraint", "noWith", "useFlatMap", "useOptionalChain", "useSimplifiedLogicExpression", ]; - const RECOMMENDED_RULES: [&'static str; 11] = [ + const RECOMMENDED_RULES: [&'static str; 12] = [ "noExtraBooleanCast", "noExtraSemicolon", "noMultipleSpacesInRegularExpressionLiterals", @@ -943,11 +953,12 @@ impl Complexity { "noUselessLabel", "noUselessRename", "noUselessSwitchCase", + "noUselessTypeConstraint", "noWith", "useFlatMap", "useOptionalChain", ]; - const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 11] = [ + const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 12] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), @@ -959,8 +970,9 @@ impl Complexity { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]), ]; - const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 13] = [ + const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 14] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), @@ -974,6 +986,7 @@ impl Complexity { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended(&self) -> bool { matches!(self.recommended, Some(true)) } @@ -1032,26 +1045,31 @@ impl Complexity { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_with.as_ref() { + if let Some(rule) = self.no_useless_type_constraint.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.use_flat_map.as_ref() { + if let Some(rule) = self.no_with.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.use_optional_chain.as_ref() { + if let Some(rule) = self.use_flat_map.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.use_simplified_logic_expression.as_ref() { + if let Some(rule) = self.use_optional_chain.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } + if let Some(rule) = self.use_simplified_logic_expression.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -1104,26 +1122,31 @@ impl Complexity { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_with.as_ref() { + if let Some(rule) = self.no_useless_type_constraint.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.use_flat_map.as_ref() { + if let Some(rule) = self.no_with.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.use_optional_chain.as_ref() { + if let Some(rule) = self.use_flat_map.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.use_simplified_logic_expression.as_ref() { + if let Some(rule) = self.use_optional_chain.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } + if let Some(rule) = self.use_simplified_logic_expression.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -1132,10 +1155,10 @@ impl Complexity { pub(crate) fn is_recommended_rule(rule_name: &str) -> bool { Self::RECOMMENDED_RULES.contains(&rule_name) } - pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 11] { + pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 12] { Self::RECOMMENDED_RULES_AS_FILTERS } - pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 13] { Self::ALL_RULES_AS_FILTERS } + pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 14] { Self::ALL_RULES_AS_FILTERS } #[doc = r" Select preset rules"] pub(crate) fn collect_preset_rules( &self, @@ -1167,6 +1190,7 @@ impl Complexity { "noUselessLabel" => self.no_useless_label.as_ref(), "noUselessRename" => self.no_useless_rename.as_ref(), "noUselessSwitchCase" => self.no_useless_switch_case.as_ref(), + "noUselessTypeConstraint" => self.no_useless_type_constraint.as_ref(), "noWith" => self.no_with.as_ref(), "useFlatMap" => self.use_flat_map.as_ref(), "useOptionalChain" => self.use_optional_chain.as_ref(), diff --git a/crates/rome_service/src/configuration/parse/json/rules.rs b/crates/rome_service/src/configuration/parse/json/rules.rs index 72e607433d6..9dd1eb5a552 100644 --- a/crates/rome_service/src/configuration/parse/json/rules.rs +++ b/crates/rome_service/src/configuration/parse/json/rules.rs @@ -563,6 +563,7 @@ impl VisitNode for Complexity { "noUselessLabel", "noUselessRename", "noUselessSwitchCase", + "noUselessTypeConstraint", "noWith", "useFlatMap", "useOptionalChain", @@ -748,6 +749,24 @@ impl VisitNode for Complexity { )); } }, + "noUselessTypeConstraint" => match value { + AnyJsonValue::JsonStringValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_known_string(&value, name_text, &mut configuration, diagnostics)?; + self.no_useless_type_constraint = Some(configuration); + } + AnyJsonValue::JsonObjectValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_object(&value, name_text, &mut configuration, diagnostics)?; + self.no_useless_type_constraint = Some(configuration); + } + _ => { + diagnostics.push(DeserializationDiagnostic::new_incorrect_type( + "object or string", + value.range(), + )); + } + }, "noWith" => match value { AnyJsonValue::JsonStringValue(_) => { let mut configuration = RuleConfiguration::default(); diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 8850d7c3576..a38e16a1ccd 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -282,6 +282,13 @@ { "type": "null" } ] }, + "noUselessTypeConstraint": { + "description": "Disallow using any or unknown as type constraint.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noWith": { "description": "Disallow with statements in non-strict contexts.", "anyOf": [ diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index 7ff4e288395..00fdb640d8b 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -339,6 +339,10 @@ export interface Complexity { * Disallow useless case in switch statements. */ noUselessSwitchCase?: RuleConfiguration; + /** + * Disallow using any or unknown as type constraint. + */ + noUselessTypeConstraint?: RuleConfiguration; /** * Disallow with statements in non-strict contexts. */ @@ -979,6 +983,7 @@ export type Category = | "lint/complexity/noUselessLabel" | "lint/complexity/noUselessRename" | "lint/complexity/noUselessSwitchCase" + | "lint/complexity/noUselessTypeConstraint" | "lint/complexity/noWith" | "lint/complexity/useFlatMap" | "lint/complexity/useOptionalChain" diff --git a/npm/rome/configuration_schema.json b/npm/rome/configuration_schema.json index 8850d7c3576..a38e16a1ccd 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -282,6 +282,13 @@ { "type": "null" } ] }, + "noUselessTypeConstraint": { + "description": "Disallow using any or unknown as type constraint.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noWith": { "description": "Disallow with statements in non-strict contexts.", "anyOf": [ diff --git a/website/src/components/generated/NumberOfRules.astro b/website/src/components/generated/NumberOfRules.astro index ba99dbb3511..2202c4a8ac0 100644 --- a/website/src/components/generated/NumberOfRules.astro +++ b/website/src/components/generated/NumberOfRules.astro @@ -1,2 +1,2 @@ -

Rome's linter has a total of 140 rules

\ No newline at end of file +

Rome's linter has a total of 141 rules

\ No newline at end of file diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index 9759009cd65..c09bfffcfb4 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -231,6 +231,13 @@ Disallow renaming import, export, and destructured assignments to the same name. Disallow useless case in switch statements.

+

+ noUselessTypeConstraint + recommended +

+Disallow using any or unknown as type constraint. +
+

noWith recommended diff --git a/website/src/pages/lint/rules/noUselessTypeConstraint.md b/website/src/pages/lint/rules/noUselessTypeConstraint.md new file mode 100644 index 00000000000..3a9e76dfd3a --- /dev/null +++ b/website/src/pages/lint/rules/noUselessTypeConstraint.md @@ -0,0 +1,265 @@ +--- +title: Lint Rule noUselessTypeConstraint +parent: lint/rules/index +--- + +# noUselessTypeConstraint (since vnext) + +> This rule is recommended by Rome. + +Disallow using `any` or `unknown` as type constraint. + +Generic type parameters (``) in TypeScript may be **constrained** with [`extends`](https://www.typescriptlang.org/docs/handbook/generics.html#generic-constraints). +A supplied type must then be a subtype of the supplied constraint. +All types are subtypes of `any` and `unknown`. +It is thus useless to extend from `any` or `unknown`. + +Source: https://typescript-eslint.io/rules/no-unnecessary-type-constraint/ + +## Examples + +### Invalid + +```ts +interface FooAny {} +``` + +
complexity/noUselessTypeConstraint.js:1:20 lint/complexity/noUselessTypeConstraint  FIXABLE  ━━━━━━━━━━
+
+   Constraining a type parameter to any or unknown is useless.
+  
+  > 1 │ interface FooAny<T extends any> {}
+                      ^^^^^^^^^^^
+    2 │ 
+  
+   All types are subtypes of any and unknown.
+  
+   Suggested fix: Remove the constraint.
+  
+    1 │ interface·FooAny<T·extends·any>·{}
+                     -----------    
+
+ +```ts +type BarAny = {}; +``` + +
complexity/noUselessTypeConstraint.js:1:15 lint/complexity/noUselessTypeConstraint  FIXABLE  ━━━━━━━━━━
+
+   Constraining a type parameter to any or unknown is useless.
+  
+  > 1 │ type BarAny<T extends any> = {};
+                 ^^^^^^^^^^^
+    2 │ 
+  
+   All types are subtypes of any and unknown.
+  
+   Suggested fix: Remove the constraint.
+  
+    1 │ type·BarAny<T·extends·any>·=·{};
+                -----------       
+
+ +```ts +class BazAny { +} +``` + +
complexity/noUselessTypeConstraint.js:1:16 lint/complexity/noUselessTypeConstraint  FIXABLE  ━━━━━━━━━━
+
+   Constraining a type parameter to any or unknown is useless.
+  
+  > 1 │ class BazAny<T extends any> {
+                  ^^^^^^^^^^^
+    2 │ }
+    3 │ 
+  
+   All types are subtypes of any and unknown.
+  
+   Suggested fix: Remove the constraint.
+  
+    1 │ class·BazAny<T·extends·any>·{
+                 -----------   
+
+ +```ts +class BazAny { + quxAny() {} +} +``` + +
complexity/noUselessTypeConstraint.js:2:12 lint/complexity/noUselessTypeConstraint  FIXABLE  ━━━━━━━━━━
+
+   Constraining a type parameter to any or unknown is useless.
+  
+    1 │ class BazAny {
+  > 2 │   quxAny<U extends any>() {}
+              ^^^^^^^^^^^
+    3 │ }
+    4 │ 
+  
+   All types are subtypes of any and unknown.
+  
+   Suggested fix: Remove the constraint.
+  
+    2 │ ··quxAny<U·extends·any>()·{}
+             -----------      
+
+ +```ts +const QuuxAny = () => {}; +``` + +
complexity/noUselessTypeConstraint.js:1:20 lint/complexity/noUselessTypeConstraint  FIXABLE  ━━━━━━━━━━
+
+   Constraining a type parameter to any or unknown is useless.
+  
+  > 1 │ const QuuxAny = <T extends any>() => {};
+                      ^^^^^^^^^^^
+    2 │ 
+  
+   All types are subtypes of any and unknown.
+  
+   Suggested fix: Remove the constraint.
+  
+    1 │ const·QuuxAny·=·<T·extends·any>()·=>·{};
+                     -----------          
+
+ +```ts +function QuuzAny() {} +``` + +
complexity/noUselessTypeConstraint.js:1:20 lint/complexity/noUselessTypeConstraint  FIXABLE  ━━━━━━━━━━
+
+   Constraining a type parameter to any or unknown is useless.
+  
+  > 1 │ function QuuzAny<T extends any>() {}
+                      ^^^^^^^^^^^
+    2 │ 
+  
+   All types are subtypes of any and unknown.
+  
+   Suggested fix: Remove the constraint.
+  
+    1 │ function·QuuzAny<T·extends·any>()·{}
+                     -----------      
+
+ +```ts +interface FooUnknown {} +``` + +
complexity/noUselessTypeConstraint.js:1:24 lint/complexity/noUselessTypeConstraint  FIXABLE  ━━━━━━━━━━
+
+   Constraining a type parameter to any or unknown is useless.
+  
+  > 1 │ interface FooUnknown<T extends unknown> {}
+                          ^^^^^^^^^^^^^^^
+    2 │ 
+  
+   All types are subtypes of any and unknown.
+  
+   Suggested fix: Remove the constraint.
+  
+    1 │ interface·FooUnknown<T·extends·unknown>·{}
+                         ---------------    
+
+ +```ts +type BarUnknown = {}; +``` + +
complexity/noUselessTypeConstraint.js:1:19 lint/complexity/noUselessTypeConstraint  FIXABLE  ━━━━━━━━━━
+
+   Constraining a type parameter to any or unknown is useless.
+  
+  > 1 │ type BarUnknown<T extends unknown> = {};
+                     ^^^^^^^^^^^^^^^
+    2 │ 
+  
+   All types are subtypes of any and unknown.
+  
+   Suggested fix: Remove the constraint.
+  
+    1 │ type·BarUnknown<T·extends·unknown>·=·{};
+                    ---------------       
+
+ +```ts +class BazUnknown { +} +```ts,expect_diagnostic +class BazUnknown { + quxUnknown() {} +} +``` + +
complexity/noUselessTypeConstraint.js:3:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   unterminated template literal
+  
+    1 │ class BazUnknown<T extends unknown> {
+    2 │ }
+  > 3 │ ```ts,expect_diagnostic
+      ^^^^^^^^^^^^^^^^^^^^
+  > 4 │ class BazUnknown {
+  > 5 │   quxUnknown<U extends unknown>() {}
+  > 6 │ }
+  > 7 │ 
+   
+  
+
+ +```ts +const QuuxUnknown = () => {}; +``` + +
complexity/noUselessTypeConstraint.js:1:24 lint/complexity/noUselessTypeConstraint  FIXABLE  ━━━━━━━━━━
+
+   Constraining a type parameter to any or unknown is useless.
+  
+  > 1 │ const QuuxUnknown = <T extends unknown>() => {};
+                          ^^^^^^^^^^^^^^^
+    2 │ 
+  
+   All types are subtypes of any and unknown.
+  
+   Suggested fix: Remove the constraint.
+  
+    1 │ const·QuuxUnknown·=·<T·extends·unknown>()·=>·{};
+                         ---------------          
+
+ +```ts +function QuuzUnknown() {} +``` + +
complexity/noUselessTypeConstraint.js:1:24 lint/complexity/noUselessTypeConstraint  FIXABLE  ━━━━━━━━━━
+
+   Constraining a type parameter to any or unknown is useless.
+  
+  > 1 │ function QuuzUnknown<T extends unknown>() {}
+                          ^^^^^^^^^^^^^^^
+    2 │ 
+  
+   All types are subtypes of any and unknown.
+  
+   Suggested fix: Remove the constraint.
+  
+    1 │ function·QuuzUnknown<T·extends·unknown>()·{}
+                         ---------------      
+
+ +### Valid + +```ts +interface Foo {} + +type Bar = {}; +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options)