From 70557f27b049d1019a2cb5df675e091559243616 Mon Sep 17 00:00:00 2001 From: Justinas Delinda <8914032+minht11@users.noreply.github.com> Date: Tue, 3 Sep 2024 23:31:32 +0300 Subject: [PATCH] feat(linter): implement noRestrictedTypes (#3585) --- CHANGELOG.md | 1 + .../migrate/eslint_any_rule_to_biome.rs | 8 + .../src/analyzer/linter/rules.rs | 204 ++++++++++-------- .../src/categories.rs | 1 + crates/biome_js_analyze/src/lint/nursery.rs | 2 + .../src/lint/nursery/no_restricted_types.rs | 130 +++++++++++ crates/biome_js_analyze/src/options.rs | 2 + .../invalidCustom.options.json | 28 +++ .../noRestrictedTypes/invalidCustom.ts | 18 ++ .../noRestrictedTypes/invalidCustom.ts.snap | 187 ++++++++++++++++ .../validCustom.options.json | 16 ++ .../nursery/noRestrictedTypes/validCustom.ts | 45 ++++ .../noRestrictedTypes/validCustom.ts.snap | 53 +++++ .../@biomejs/backend-jsonrpc/src/workspace.ts | 25 +++ .../@biomejs/biome/configuration_schema.json | 54 +++++ 15 files changed, 682 insertions(+), 92 deletions(-) create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_restricted_types.rs create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.options.json create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.ts create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.ts.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.options.json create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.ts create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.ts.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index f47c972a47c5..22fe5da856d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -300,6 +300,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b #### New features +- Add [nursery/noRestrictedTypes](https://biomejs.dev/linter/no-restricted-types/). Contributed by @minht11 - Add support for GraphQL linting. Contributed by @ematipico - Add [nursery/noDynamicNamespaceImportAccess](https://biomejs.dev/linter/no-dynamic-namespace-import-access/). Contributed by @minht11 diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs index de440e186128..a22791f3e745 100644 --- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs +++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs @@ -185,6 +185,14 @@ pub(crate) fn migrate_eslint_any_rule( .get_or_insert(Default::default()); rule.set_level(rule_severity.into()); } + "@typescript-eslint/no-restricted-types" => { + if !options.include_nursery { + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group.no_restricted_types.get_or_insert(Default::default()); + rule.set_level(rule_severity.into()); + } "@typescript-eslint/no-this-alias" => { if !options.include_inspired { results.has_inspired_rules = true; diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index d3a38e187187..e383a1117fa2 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -2944,6 +2944,10 @@ pub struct Nursery { #[serde(skip_serializing_if = "Option::is_none")] pub no_restricted_imports: Option>, + #[doc = "Disallow user defined types."] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_restricted_types: + Option>, #[doc = "Disallow shorthand properties that override related longhand properties."] #[serde(skip_serializing_if = "Option::is_none")] pub no_shorthand_property_overrides: @@ -3139,6 +3143,7 @@ impl Nursery { "noMisplacedAssertion", "noReactSpecificProps", "noRestrictedImports", + "noRestrictedTypes", "noShorthandPropertyOverrides", "noStaticElementInteractions", "noSubstr", @@ -3221,21 +3226,21 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -3299,6 +3304,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -3415,211 +3421,216 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } - if let Some(rule) = self.no_shorthand_property_overrides.as_ref() { + if let Some(rule) = self.no_restricted_types.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.no_static_element_interactions.as_ref() { + if let Some(rule) = self.no_shorthand_property_overrides.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.no_substr.as_ref() { + if let Some(rule) = self.no_static_element_interactions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.no_undeclared_dependencies.as_ref() { + if let Some(rule) = self.no_substr.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.no_unknown_function.as_ref() { + if let Some(rule) = self.no_undeclared_dependencies.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.no_unknown_media_feature_name.as_ref() { + if let Some(rule) = self.no_unknown_function.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.no_unknown_property.as_ref() { + if let Some(rule) = self.no_unknown_media_feature_name.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - if let Some(rule) = self.no_unknown_pseudo_class_selector.as_ref() { + if let Some(rule) = self.no_unknown_property.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - if let Some(rule) = self.no_unknown_selector_pseudo_element.as_ref() { + if let Some(rule) = self.no_unknown_pseudo_class_selector.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } } - if let Some(rule) = self.no_unknown_unit.as_ref() { + if let Some(rule) = self.no_unknown_selector_pseudo_element.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.no_unmatchable_anb_selector.as_ref() { + if let Some(rule) = self.no_unknown_unit.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } } - if let Some(rule) = self.no_unused_function_parameters.as_ref() { + if let Some(rule) = self.no_unmatchable_anb_selector.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { + if let Some(rule) = self.no_unused_function_parameters.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.no_useless_string_concat.as_ref() { + if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { + if let Some(rule) = self.no_useless_string_concat.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } } - if let Some(rule) = self.no_value_at_rule.as_ref() { + if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.no_yoda_expression.as_ref() { + if let Some(rule) = self.no_value_at_rule.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() { + if let Some(rule) = self.no_yoda_expression.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() { + if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { + if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.use_consistent_curly_braces.as_ref() { + if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - if let Some(rule) = self.use_consistent_grid_areas.as_ref() { + if let Some(rule) = self.use_consistent_curly_braces.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } } - if let Some(rule) = self.use_consistent_member_accessibility.as_ref() { + if let Some(rule) = self.use_consistent_grid_areas.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_date_now.as_ref() { + if let Some(rule) = self.use_consistent_member_accessibility.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_default_switch_clause.as_ref() { + if let Some(rule) = self.use_date_now.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_deprecated_reason.as_ref() { + if let Some(rule) = self.use_default_switch_clause.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } - if let Some(rule) = self.use_error_message.as_ref() { + if let Some(rule) = self.use_deprecated_reason.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } } - if let Some(rule) = self.use_explicit_length_check.as_ref() { + if let Some(rule) = self.use_error_message.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } } - if let Some(rule) = self.use_focusable_interactive.as_ref() { + if let Some(rule) = self.use_explicit_length_check.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); } } - if let Some(rule) = self.use_generic_font_names.as_ref() { + if let Some(rule) = self.use_focusable_interactive.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49])); } } - if let Some(rule) = self.use_import_extensions.as_ref() { + if let Some(rule) = self.use_generic_font_names.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_import_extensions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51])); } } - if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52])); } } - if let Some(rule) = self.use_semantic_elements.as_ref() { + if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_semantic_elements.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54])); } } - if let Some(rule) = self.use_strict_mode.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55])); } } - if let Some(rule) = self.use_throw_new_error.as_ref() { + if let Some(rule) = self.use_strict_mode.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56])); } } - if let Some(rule) = self.use_throw_only_error.as_ref() { + if let Some(rule) = self.use_throw_new_error.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57])); } } - if let Some(rule) = self.use_top_level_regex.as_ref() { + if let Some(rule) = self.use_throw_only_error.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); } } - if let Some(rule) = self.use_trim_start_end.as_ref() { + if let Some(rule) = self.use_top_level_regex.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); } } - if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if let Some(rule) = self.use_trim_start_end.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60])); } } + if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> FxHashSet> { @@ -3724,211 +3735,216 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } - if let Some(rule) = self.no_shorthand_property_overrides.as_ref() { + if let Some(rule) = self.no_restricted_types.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.no_static_element_interactions.as_ref() { + if let Some(rule) = self.no_shorthand_property_overrides.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.no_substr.as_ref() { + if let Some(rule) = self.no_static_element_interactions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.no_undeclared_dependencies.as_ref() { + if let Some(rule) = self.no_substr.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.no_unknown_function.as_ref() { + if let Some(rule) = self.no_undeclared_dependencies.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.no_unknown_media_feature_name.as_ref() { + if let Some(rule) = self.no_unknown_function.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.no_unknown_property.as_ref() { + if let Some(rule) = self.no_unknown_media_feature_name.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - if let Some(rule) = self.no_unknown_pseudo_class_selector.as_ref() { + if let Some(rule) = self.no_unknown_property.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - if let Some(rule) = self.no_unknown_selector_pseudo_element.as_ref() { + if let Some(rule) = self.no_unknown_pseudo_class_selector.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } } - if let Some(rule) = self.no_unknown_unit.as_ref() { + if let Some(rule) = self.no_unknown_selector_pseudo_element.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.no_unmatchable_anb_selector.as_ref() { + if let Some(rule) = self.no_unknown_unit.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } } - if let Some(rule) = self.no_unused_function_parameters.as_ref() { + if let Some(rule) = self.no_unmatchable_anb_selector.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { + if let Some(rule) = self.no_unused_function_parameters.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.no_useless_string_concat.as_ref() { + if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { + if let Some(rule) = self.no_useless_string_concat.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } } - if let Some(rule) = self.no_value_at_rule.as_ref() { + if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.no_yoda_expression.as_ref() { + if let Some(rule) = self.no_value_at_rule.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() { + if let Some(rule) = self.no_yoda_expression.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() { + if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { + if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.use_consistent_curly_braces.as_ref() { + if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - if let Some(rule) = self.use_consistent_grid_areas.as_ref() { + if let Some(rule) = self.use_consistent_curly_braces.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } } - if let Some(rule) = self.use_consistent_member_accessibility.as_ref() { + if let Some(rule) = self.use_consistent_grid_areas.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_date_now.as_ref() { + if let Some(rule) = self.use_consistent_member_accessibility.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_default_switch_clause.as_ref() { + if let Some(rule) = self.use_date_now.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_deprecated_reason.as_ref() { + if let Some(rule) = self.use_default_switch_clause.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } - if let Some(rule) = self.use_error_message.as_ref() { + if let Some(rule) = self.use_deprecated_reason.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } } - if let Some(rule) = self.use_explicit_length_check.as_ref() { + if let Some(rule) = self.use_error_message.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47])); } } - if let Some(rule) = self.use_focusable_interactive.as_ref() { + if let Some(rule) = self.use_explicit_length_check.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48])); } } - if let Some(rule) = self.use_generic_font_names.as_ref() { + if let Some(rule) = self.use_focusable_interactive.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49])); } } - if let Some(rule) = self.use_import_extensions.as_ref() { + if let Some(rule) = self.use_generic_font_names.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_import_extensions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51])); } } - if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52])); } } - if let Some(rule) = self.use_semantic_elements.as_ref() { + if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53])); } } - if let Some(rule) = self.use_sorted_classes.as_ref() { + if let Some(rule) = self.use_semantic_elements.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54])); } } - if let Some(rule) = self.use_strict_mode.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55])); } } - if let Some(rule) = self.use_throw_new_error.as_ref() { + if let Some(rule) = self.use_strict_mode.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56])); } } - if let Some(rule) = self.use_throw_only_error.as_ref() { + if let Some(rule) = self.use_throw_new_error.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57])); } } - if let Some(rule) = self.use_top_level_regex.as_ref() { + if let Some(rule) = self.use_throw_only_error.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); } } - if let Some(rule) = self.use_trim_start_end.as_ref() { + if let Some(rule) = self.use_top_level_regex.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); } } - if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if let Some(rule) = self.use_trim_start_end.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60])); } } + if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -4045,6 +4061,10 @@ impl Nursery { .no_restricted_imports .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "noRestrictedTypes" => self + .no_restricted_types + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noShorthandPropertyOverrides" => self .no_shorthand_property_overrides .as_ref() diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index c88edb0635a0..32392bbdb70b 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -136,6 +136,7 @@ define_categories! { "lint/nursery/noMissingGenericFamilyKeyword": "https://biomejs.dev/linter/rules/no-missing-generic-family-keyword", "lint/nursery/noReactSpecificProps": "https://biomejs.dev/linter/rules/no-react-specific-props", "lint/nursery/noRestrictedImports": "https://biomejs.dev/linter/rules/no-restricted-imports", + "lint/nursery/noRestrictedTypes": "https://biomejs.dev/linter/rules/no-restricted-types", "lint/nursery/noShorthandPropertyOverrides": "https://biomejs.dev/linter/rules/no-shorthand-property-overrides", "lint/nursery/noStaticElementInteractions": "https://biomejs.dev/linter/rules/no-static-element-interactions", "lint/nursery/noSubstr": "https://biomejs.dev/linter/rules/no-substr", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index d0e1e2146e4e..fff265745b51 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -14,6 +14,7 @@ pub mod no_label_without_control; pub mod no_misplaced_assertion; pub mod no_react_specific_props; pub mod no_restricted_imports; +pub mod no_restricted_types; pub mod no_static_element_interactions; pub mod no_substr; pub mod no_undeclared_dependencies; @@ -60,6 +61,7 @@ declare_lint_group! { self :: no_misplaced_assertion :: NoMisplacedAssertion , self :: no_react_specific_props :: NoReactSpecificProps , self :: no_restricted_imports :: NoRestrictedImports , + self :: no_restricted_types :: NoRestrictedTypes , self :: no_static_element_interactions :: NoStaticElementInteractions , self :: no_substr :: NoSubstr , self :: no_undeclared_dependencies :: NoUndeclaredDependencies , diff --git a/crates/biome_js_analyze/src/lint/nursery/no_restricted_types.rs b/crates/biome_js_analyze/src/lint/nursery/no_restricted_types.rs new file mode 100644 index 000000000000..dbe0e325b35c --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_restricted_types.rs @@ -0,0 +1,130 @@ +use crate::JsRuleAction; +use ::serde::{Deserialize, Serialize}; +use biome_analyze::context::RuleContext; +use biome_analyze::{ + declare_lint_rule, ActionCategory, Ast, FixKind, Rule, RuleDiagnostic, RuleSource, +}; +use biome_console::markup; +use biome_deserialize_macros::Deserializable; +use biome_js_factory::make; +use biome_js_syntax::TsReferenceType; +use biome_rowan::AstNode; +use biome_rowan::BatchMutationExt; +use biome_unicode_table::is_js_ident; +use rustc_hash::FxHashMap; + +#[cfg(feature = "schemars")] +use schemars::JsonSchema; + +declare_lint_rule! { + /// Disallow user defined types. + /// + /// This rule allows you to specify type names that you don’t want to use in your application. + /// + /// To prevent use of commonly misleading types, you can refer to [noBannedTypes](https://biomejs.dev/linter/rules/no-banned-types/) + /// + /// ## Options + /// + /// Use the options to specify additional types that you want to restrict in your + /// source code. + /// + /// ```json + /// { + /// "//": "...", + /// "options": { + /// "types": { + /// "Foo": { + /// "message": "Only bar is allowed", + /// "use": "bar" + /// }, + /// "OldAPI": { + /// "message": "Use NewAPI instead" + /// } + /// } + /// } + /// } + /// ``` + /// + /// In the example above, the rule will emit a diagnostics if tried to use `Foo` or `OldAPI` are used. + /// + pub NoRestrictedTypes { + version: "next", + name: "noRestrictedTypes", + language: "ts", + sources: &[ + RuleSource::EslintTypeScript("no-restricted-types"), + ], + recommended: false, + fix_kind: FixKind::Safe, + } +} + +impl Rule for NoRestrictedTypes { + type Query = Ast; + type State = CustomRestrictedType; + type Signals = Option; + type Options = NoRestrictedTypesOptions; + + fn run(ctx: &RuleContext) -> Self::Signals { + let ts_reference_type = ctx.query(); + let options = ctx.options(); + + let ts_any_name = ts_reference_type.name().ok()?; + let identifier = ts_any_name.as_js_reference_identifier()?; + let identifier_token = identifier.value_token().ok()?; + let token_name = identifier_token.text_trimmed(); + + let restricted_type = options.types.get(token_name)?.clone(); + + Some(restricted_type) + } + + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { + Some(RuleDiagnostic::new( + rule_category!(), + ctx.query().range(), + markup! { {state.message} }.to_owned(), + )) + } + + fn action(ctx: &RuleContext, state: &Self::State) -> Option { + let suggested_type = state.use_instead.as_ref()?; + if !is_js_ident(suggested_type) { + return None; + } + + let mut mutation = ctx.root().begin(); + + let ts_reference_type = ctx.query(); + let ts_any_name = ts_reference_type.name().ok()?; + let identifier = ts_any_name.as_js_reference_identifier()?; + let prev_token = identifier.value_token().ok()?; + + let new_token = make::ident(suggested_type); + + mutation.replace_element(prev_token.into(), new_token.into()); + + Some(JsRuleAction::new( + ActionCategory::QuickFix, + ctx.metadata().applicability(), + markup! { "Use '"{suggested_type}"' instead" }.to_owned(), + mutation, + )) + } +} + +#[derive(Clone, Debug, Default, Deserializable, Deserialize, Serialize, Eq, PartialEq)] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct NoRestrictedTypesOptions { + types: FxHashMap, +} + +#[derive(Debug, Clone, Default, Deserializable, Deserialize, Serialize, Eq, PartialEq)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct CustomRestrictedType { + message: String, + #[serde(rename = "use")] + use_instead: Option, +} diff --git a/crates/biome_js_analyze/src/options.rs b/crates/biome_js_analyze/src/options.rs index 856c9e0b5724..16d545deb65d 100644 --- a/crates/biome_js_analyze/src/options.rs +++ b/crates/biome_js_analyze/src/options.rs @@ -176,6 +176,8 @@ pub type NoRestrictedGlobals = ::Options; pub type NoRestrictedImports = ::Options; +pub type NoRestrictedTypes = + ::Options; pub type NoSelfAssign = ::Options; pub type NoSelfCompare = diff --git a/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.options.json b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.options.json new file mode 100644 index 000000000000..a5aefa4941dd --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.options.json @@ -0,0 +1,28 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "enabled": true, + "rules": { + "nursery": { + "noRestrictedTypes": { + "level": "error", + "options": { + "types": { + "CustomType": { + "message": "Only CustomType2 is allowed", + "use": "CustomType2" + }, + "Bar": { + "message": "Replace Bar with Foo" + }, + "InvalidUse": { + "message": "Do not use this type", + "use": "@" + } + } + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.ts b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.ts new file mode 100644 index 000000000000..aa6af2fe26fe --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.ts @@ -0,0 +1,18 @@ +type CustomType = unknown +function fn2(arg: CustomType) { + return arg; +} + + +class Foo extends Bar implements CustomType { + constructor(foo: String | Object) {} + + exit(): CustomType { + const foo: String = 1 as CustomType; + } +} + + +const foo: Bar = 1; + +const identifier: InvalidUse = 'foo'; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.ts.snap new file mode 100644 index 000000000000..58dad5c03852 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.ts.snap @@ -0,0 +1,187 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalidCustom.ts +--- +# Input +```ts +type CustomType = unknown +function fn2(arg: CustomType) { + return arg; +} + + +class Foo extends Bar implements CustomType { + constructor(foo: String | Object) {} + + exit(): CustomType { + const foo: String = 1 as CustomType; + } +} + + +const foo: Bar = 1; + +const identifier: InvalidUse = 'foo'; +``` + +# Diagnostics +``` +invalidCustom.ts:2:19 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Only CustomType2 is allowed + + 1 │ type CustomType = unknown + > 2 │ function fn2(arg: CustomType) { + │ ^^^^^^^^^^ + 3 │ return arg; + 4 │ } + + i Safe fix: Use 'CustomType2' instead + + 1 1 │ type CustomType = unknown + 2 │ - function·fn2(arg:·CustomType)·{ + 2 │ + function·fn2(arg:·CustomType2)·{ + 3 3 │ return arg; + 4 4 │ } + + +``` + +``` +invalidCustom.ts:7:21 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Only CustomType2 is allowed + + > 7 │ class Foo extends Bar implements CustomType { + │ ^^^^^^^^^^ + 8 │ constructor(foo: String | Object) {} + 9 │ + + i Safe fix: Use 'CustomType2' instead + + 5 5 │ + 6 6 │ + 7 │ - class·Foo·extends·Bar·implements·CustomType·{ + 7 │ + class·Foo·extends·Bar·implements·CustomType·{ + 8 8 │ constructor(foo: String | Object) {} + 9 9 │ + + +``` + +``` +invalidCustom.ts:7:54 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Only CustomType2 is allowed + + > 7 │ class Foo extends Bar implements CustomType { + │ ^^^^^^^^^^ + 8 │ constructor(foo: String | Object) {} + 9 │ + + i Safe fix: Use 'CustomType2' instead + + 5 5 │ + 6 6 │ + 7 │ - class·Foo·extends·Bar·implements·CustomType·{ + 7 │ + class·Foo·extends·Bar·implements·CustomType·{ + 8 8 │ constructor(foo: String | Object) {} + 9 9 │ + + +``` + +``` +invalidCustom.ts:7:77 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Only CustomType2 is allowed + + > 7 │ class Foo extends Bar implements CustomType { + │ ^^^^^^^^^^^^^^^^^^ + 8 │ constructor(foo: String | Object) {} + 9 │ + + i Safe fix: Use 'CustomType2' instead + + 5 5 │ + 6 6 │ + 7 │ - class·Foo·extends·Bar·implements·CustomType·{ + 7 │ + class·Foo·extends·Bar·implements·CustomType2·{ + 8 8 │ constructor(foo: String | Object) {} + 9 9 │ + + +``` + +``` +invalidCustom.ts:10:11 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Only CustomType2 is allowed + + 8 │ constructor(foo: String | Object) {} + 9 │ + > 10 │ exit(): CustomType { + │ ^^^^^^^^^^^^^^^^^^ + 11 │ const foo: String = 1 as CustomType; + 12 │ } + + i Safe fix: Use 'CustomType2' instead + + 8 8 │ constructor(foo: String | Object) {} + 9 9 │ + 10 │ - ··exit():·CustomType·{ + 10 │ + ··exit():·CustomType2·{ + 11 11 │ const foo: String = 1 as CustomType; + 12 12 │ } + + +``` + +``` +invalidCustom.ts:11:30 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Only CustomType2 is allowed + + 10 │ exit(): CustomType { + > 11 │ const foo: String = 1 as CustomType; + │ ^^^^^^^^^^ + 12 │ } + 13 │ } + + i Safe fix: Use 'CustomType2' instead + + 9 9 │ + 10 10 │ exit(): CustomType { + 11 │ - ····const·foo:·String·=·1·as·CustomType; + 11 │ + ····const·foo:·String·=·1·as·CustomType2; + 12 12 │ } + 13 13 │ } + + +``` + +``` +invalidCustom.ts:16:12 lint/nursery/noRestrictedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Replace Bar with Foo + + > 16 │ const foo: Bar = 1; + │ ^^^ + 17 │ + 18 │ const identifier: InvalidUse = 'foo'; + + +``` + +``` +invalidCustom.ts:18:19 lint/nursery/noRestrictedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Do not use this type + + 16 │ const foo: Bar = 1; + 17 │ + > 18 │ const identifier: InvalidUse = 'foo'; + │ ^^^^^^^^^^ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.options.json b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.options.json new file mode 100644 index 000000000000..232a199c7aa4 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.options.json @@ -0,0 +1,16 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "enabled": true, + "rules": { + "nursery": { + "noRestrictedTypes": { + "level": "error", + "options": { + "types": {} + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.ts b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.ts new file mode 100644 index 000000000000..f2e18dda902f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.ts @@ -0,0 +1,45 @@ +let a: String; + +let e: Object; + +let b: { c: String }; + +function foo(a: String) {} + +'a' as String; + +class Foo extends Bar implements Baz { + constructor(foo: String | Object) {} + + exit(): Array { + const foo: String = 1 as String; + } +} + +let baz: [boolean, Boolean] = [true, false]; + +let z = true as Boolean; + +type Props = {}; + +let fn: Function = () => true + +const str: String = 'foo'; + +const bool: Boolean = true; + +const num: Number = 1; + +const symb: Symbol = Symbol('foo'); + +const bigInt: BigInt = 1n; + +const lowerObj: Object = {}; + +const capitalObj: Object = { a: 'string' }; + +const curly1: { + +} = 1; + +const curly2: {} = { a: 'string' }; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.ts.snap new file mode 100644 index 000000000000..553dfb5862cc --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.ts.snap @@ -0,0 +1,53 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: validCustom.ts +--- +# Input +```ts +let a: String; + +let e: Object; + +let b: { c: String }; + +function foo(a: String) {} + +'a' as String; + +class Foo extends Bar implements Baz { + constructor(foo: String | Object) {} + + exit(): Array { + const foo: String = 1 as String; + } +} + +let baz: [boolean, Boolean] = [true, false]; + +let z = true as Boolean; + +type Props = {}; + +let fn: Function = () => true + +const str: String = 'foo'; + +const bool: Boolean = true; + +const num: Number = 1; + +const symb: Symbol = Symbol('foo'); + +const bigInt: BigInt = 1n; + +const lowerObj: Object = {}; + +const capitalObj: Object = { a: 'string' }; + +const curly1: { + +} = 1; + +const curly2: {} = { a: 'string' }; + +``` diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 564220a419cd..08a6cacdd3d2 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1227,6 +1227,10 @@ export interface Nursery { * Disallow specified modules when loaded by import or require. */ noRestrictedImports?: RuleConfiguration_for_RestrictedImportsOptions; + /** + * Disallow user defined types. + */ + noRestrictedTypes?: RuleFixConfiguration_for_NoRestrictedTypesOptions; /** * Disallow shorthand properties that override related longhand properties. */ @@ -1947,6 +1951,9 @@ export type RuleConfiguration_for_NoLabelWithoutControlOptions = export type RuleConfiguration_for_RestrictedImportsOptions = | RulePlainConfiguration | RuleWithOptions_for_RestrictedImportsOptions; +export type RuleFixConfiguration_for_NoRestrictedTypesOptions = + | RulePlainConfiguration + | RuleWithFixOptions_for_NoRestrictedTypesOptions; export type RuleConfiguration_for_ConsistentMemberAccessibilityOptions = | RulePlainConfiguration | RuleWithOptions_for_ConsistentMemberAccessibilityOptions; @@ -2077,6 +2084,20 @@ export interface RuleWithOptions_for_RestrictedImportsOptions { */ options: RestrictedImportsOptions; } +export interface RuleWithFixOptions_for_NoRestrictedTypesOptions { + /** + * The kind of the code actions emitted by the rule + */ + fix?: FixKind; + /** + * The severity of the emitted diagnostics by the rule + */ + level: RulePlainConfiguration; + /** + * Rule's options + */ + options: NoRestrictedTypesOptions; +} export interface RuleWithOptions_for_ConsistentMemberAccessibilityOptions { /** * The severity of the emitted diagnostics by the rule @@ -2246,6 +2267,9 @@ export interface RestrictedImportsOptions { */ paths: {}; } +export interface NoRestrictedTypesOptions { + types: {}; +} export interface ConsistentMemberAccessibilityOptions { accessibility: Accessibility; } @@ -2714,6 +2738,7 @@ export type Category = | "lint/nursery/noMissingGenericFamilyKeyword" | "lint/nursery/noReactSpecificProps" | "lint/nursery/noRestrictedImports" + | "lint/nursery/noRestrictedTypes" | "lint/nursery/noShorthandPropertyOverrides" | "lint/nursery/noStaticElementInteractions" | "lint/nursery/noSubstr" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 1fda7fe4248b..73eef6b05b48 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1061,6 +1061,15 @@ }, "additionalProperties": false }, + "CustomRestrictedType": { + "type": "object", + "required": ["message"], + "properties": { + "message": { "type": "string" }, + "use": { "type": ["string", "null"] } + }, + "additionalProperties": false + }, "DeprecatedHooksConfiguration": { "anyOf": [ { "$ref": "#/definitions/RulePlainConfiguration" }, @@ -1883,6 +1892,25 @@ }, "additionalProperties": false }, + "NoRestrictedTypesConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoRestrictedTypesOptions" } + ] + }, + "NoRestrictedTypesOptions": { + "type": "object", + "required": ["types"], + "properties": { + "types": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/CustomRestrictedType" + } + } + }, + "additionalProperties": false + }, "Nursery": { "description": "A list of rules that belong to this group", "type": "object", @@ -2031,6 +2059,13 @@ { "type": "null" } ] }, + "noRestrictedTypes": { + "description": "Disallow user defined types.", + "anyOf": [ + { "$ref": "#/definitions/NoRestrictedTypesConfiguration" }, + { "type": "null" } + ] + }, "noShorthandPropertyOverrides": { "description": "Disallow shorthand properties that override related longhand properties.", "anyOf": [ @@ -2780,6 +2815,25 @@ }, "additionalProperties": false }, + "RuleWithNoRestrictedTypesOptions": { + "type": "object", + "required": ["level", "options"], + "properties": { + "fix": { + "description": "The kind of the code actions emitted by the rule", + "anyOf": [{ "$ref": "#/definitions/FixKind" }, { "type": "null" }] + }, + "level": { + "description": "The severity of the emitted diagnostics by the rule", + "allOf": [{ "$ref": "#/definitions/RulePlainConfiguration" }] + }, + "options": { + "description": "Rule's options", + "allOf": [{ "$ref": "#/definitions/NoRestrictedTypesOptions" }] + } + }, + "additionalProperties": false + }, "RuleWithRestrictedGlobalsOptions": { "type": "object", "required": ["level", "options"],