From 30cbbd1e5517b37730076b1fd752eb1727ea9637 Mon Sep 17 00:00:00 2001 From: Justinas Delinda <8914032+minht11@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:58:22 +0300 Subject: [PATCH] implement no-restricted-types --- CHANGELOG.md | 1 + .../migrate/eslint_any_rule_to_biome.rs | 8 + .../biome_configuration/src/linter/rules.rs | 185 ++--- .../src/categories.rs | 3 +- crates/biome_js_analyze/src/lint/nursery.rs | 2 + .../src/lint/nursery/no_restricted_types.rs | 356 ++++++++++ crates/biome_js_analyze/src/options.rs | 2 + .../nursery/noRestrictedTypes/invalid.ts | 45 ++ .../nursery/noRestrictedTypes/invalid.ts.snap | 643 ++++++++++++++++++ .../invalidCustom.options.json | 24 + .../noRestrictedTypes/invalidCustom.ts | 6 + .../noRestrictedTypes/invalidCustom.ts.snap | 36 + .../specs/nursery/noRestrictedTypes/valid.ts | 32 + .../nursery/noRestrictedTypes/valid.ts.snap | 40 ++ .../validCustom.options.json | 17 + .../nursery/noRestrictedTypes/validCustom.ts | 45 ++ .../noRestrictedTypes/validCustom.ts.snap | 53 ++ .../@biomejs/backend-jsonrpc/src/workspace.ts | 28 +- .../@biomejs/biome/configuration_schema.json | 55 ++ 19 files changed, 1496 insertions(+), 85 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/invalid.ts create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalid.ts.snap 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/valid.ts create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/valid.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 db7cf9ee1e60..18516aa22a82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -125,6 +125,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 - [noUndeclaredVariables](https://biomejs.dev/linter/rules/no-undeclared-variables/) no longer reports a direct reference to an enum member ([#2974](https://github.com/biomejs/biome/issues/2974)). 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 1605aded2ca2..468171e97ed4 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 @@ -175,6 +175,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/linter/rules.rs b/crates/biome_configuration/src/linter/rules.rs index 83a926d9e9ab..7633a02b035a 100644 --- a/crates/biome_configuration/src/linter/rules.rs +++ b/crates/biome_configuration/src/linter/rules.rs @@ -2878,6 +2878,9 @@ pub struct Nursery { #[doc = "Disallow specified modules when loaded by import or require."] #[serde(skip_serializing_if = "Option::is_none")] pub no_restricted_imports: Option>, + #[doc = "Disallow primitive type aliases, misleading or 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: Option>, @@ -3035,6 +3038,7 @@ impl Nursery { "noMisplacedAssertion", "noReactSpecificProps", "noRestrictedImports", + "noRestrictedTypes", "noShorthandPropertyOverrides", "noStaticElementInteractions", "noSubstr", @@ -3113,18 +3117,18 @@ 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[17]), - 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[22]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]), 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[31]), - 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[32]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -3186,6 +3190,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -3307,196 +3312,201 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - 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[21])); } } - 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[22])); } } - 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[23])); } } - 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[24])); } } - 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[25])); } } - 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[26])); } } - 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[27])); } } - 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[28])); } } - 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[29])); } } - 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[30])); } } - 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[31])); } } - 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[32])); } } - if let Some(rule) = self.no_useless_string_concat.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[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_consistent_builtin_instantiation.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_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[39])); } } - 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[40])); } } - if let Some(rule) = self.use_date_now.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[41])); } } - 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[42])); } } - 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[43])); } } - 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[44])); } } - 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[45])); } } - 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[46])); } } - 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[47])); } } - 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[48])); } } - 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[49])); } } - 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[50])); } } - 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[51])); } } - 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[52])); } } - 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[53])); } } - 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[54])); } } - 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[55])); } } - 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[56])); } } - 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[57])); } } - 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[58])); } } + 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[59])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> FxHashSet> { @@ -3606,196 +3616,201 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - 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[21])); } } - 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[22])); } } - 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[23])); } } - 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[24])); } } - 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[25])); } } - 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[26])); } } - 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[27])); } } - 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[28])); } } - 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[29])); } } - 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[30])); } } - 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[31])); } } - 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[32])); } } - if let Some(rule) = self.no_useless_string_concat.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[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_consistent_builtin_instantiation.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_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[39])); } } - 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[40])); } } - if let Some(rule) = self.use_date_now.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[41])); } } - 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[42])); } } - 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[43])); } } - 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[44])); } } - 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[45])); } } - 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[46])); } } - 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[47])); } } - 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[48])); } } - 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[49])); } } - 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[50])); } } - 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[51])); } } - 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[52])); } } - 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[53])); } } - 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[54])); } } - 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[55])); } } - 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[56])); } } - 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[57])); } } - 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[58])); } } + 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[59])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -3916,6 +3931,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 21c00ee04b8d..a8eb52d5d751 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -137,8 +137,9 @@ 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/noStaticElementInteractions": "https://biomejs.dev/linter/rules/no-static-element-interactions", + "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", "lint/nursery/noUndeclaredDependencies": "https://biomejs.dev/linter/rules/no-undeclared-dependencies", "lint/nursery/noUnknownFunction": "https://biomejs.dev/linter/rules/no-unknown-function", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 6bd9825bc64f..39e217ba6600 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -13,6 +13,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; @@ -55,6 +56,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..91442a88fbd3 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_restricted_types.rs @@ -0,0 +1,356 @@ +use std::fmt::Display; + +use ::serde::{Deserialize, Serialize}; +use biome_analyze::context::RuleContext; +use biome_analyze::{declare_lint_rule, ActionCategory, FixKind, Rule, RuleDiagnostic, RuleSource}; +use biome_console::markup; +use biome_deserialize_macros::Deserializable; +use biome_js_factory::make; +use biome_js_syntax::{ + JsReferenceIdentifier, TextRange, TsIntersectionTypeElementList, TsObjectType, TsReferenceType, + TsTypeConstraintClause, +}; +use biome_rowan::{declare_node_union, AstNode, AstNodeList, BatchMutationExt}; +use rustc_hash::FxHashMap; + +use crate::services::semantic::Semantic; +use crate::JsRuleAction; + +#[cfg(feature = "schemars")] +use schemars::JsonSchema; + +declare_lint_rule! { + /// Disallow primitive type aliases, misleading or user defined types. + /// + /// - Enforce consistent names for primitive types + /// + /// Primitive types have aliases. + /// For example, `Number` is an alias of `number`. + /// The rule recommends the lowercase primitive type names. + /// + /// - Disallow the `Function` type + /// + /// The `Function` type is loosely typed and is thus considered dangerous or harmful. + /// `Function` is equivalent to the type `(...rest: any[]) => any` that uses the unsafe `any` type. + /// + /// - Disallow the misleading non-nullable type `{}` + /// + /// In TypeScript, the type `{}` doesn't represent an empty object. + /// It represents any value except `null` and `undefined`. + /// The following TypeScript example is perfectly valid: + /// + /// ```ts,expect_diagnostic + /// const n: {} = 0 + /// ``` + /// + /// To represent an empty object, you should use `{ [k: string]: never }` or `Record`. + /// + /// To avoid any confusion, the rule forbids the use of the type `{}`, except in two situations: + /// + /// 1. In type constraints to restrict a generic type to non-nullable types: + /// + /// ```ts + /// function f(x: T) { + /// assert(x != null); + /// } + /// ``` + /// + /// 2. In a type intersection to narrow a type to its non-nullable equivalent type: + /// + /// ```ts + /// type NonNullableMyType = MyType & {}; + /// ``` + /// + /// In this last case, you can also use the `NonNullable` utility type: + /// + /// ```ts + /// type NonNullableMyType = NonNullable; + /// ``` + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```ts,expect_diagnostic + /// let foo: String = "bar"; + /// ``` + /// + /// ```ts,expect_diagnostic + /// let bool = true as Boolean; + /// ``` + /// + /// ```ts,expect_diagnostic + /// let invalidTuple: [string, Boolean] = ["foo", false]; + /// ``` + /// + /// ### Valid + /// + /// ```ts + /// let foo: string = "bar"; + /// ``` + /// + /// ```ts + /// let tuple: [boolean, string] = [false, "foo"]; + /// ``` + /// + /// ## 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", + /// "fixWith": "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. + /// + /// This rule provides predefined list of restricted types, however if you do not want to use the predefined list + /// you can disable it by setting option `extendDefaults` to `false`. + /// ```json + /// { + /// "//": "...", + /// "options": { + /// "extendDefaults": false, + /// "types": {} + /// } + /// } + /// + pub NoRestrictedTypes { + version: "next", + name: "noRestrictedTypes", + language: "ts", + sources: &[ + RuleSource::EslintTypeScript("no-restricted-types"), + // TODO: Add this source once `noBannedTypes` is deprecated + // RuleSource::EslintTypeScript("ban-types") + ], + recommended: false, + fix_kind: FixKind::Safe, + + } +} + +impl Rule for NoRestrictedTypes { + type Query = Semantic; + type State = State; + type Signals = Option; + type Options = NoRestrictedTypesOptions; + + fn run(ctx: &RuleContext) -> Self::Signals { + let query = ctx.query(); + let model = ctx.model(); + let options = ctx.options(); + let should_extend_defaults = options.extend_defaults.unwrap_or(true); + + match query { + TsRestrictedType::TsObjectType(ts_object_type) => { + if !should_extend_defaults { + return None; + } + + // Allow empty object type for type constraint and intersections. + // ```js + // type AssertNonNullGeneric = T + // type NonNull = T & {} + // ``` + if ts_object_type.members().is_empty() + && (ts_object_type.parent::().is_none() + && ts_object_type + .parent::() + .is_none()) + { + return Some(State { + restricted_type: RestrictedType::EmptyObject, + range: ts_object_type.range(), + reference_identifier: None, + }); + } + } + TsRestrictedType::TsReferenceType(ts_reference_type) => { + let ts_any_name = ts_reference_type.name().ok()?; + let reference_identifier = ts_any_name.as_js_reference_identifier()?; + let identifier_token = reference_identifier.value_token().ok()?; + let token_name = identifier_token.text_trimmed(); + + let restricted_type = + if should_extend_defaults && model.binding(reference_identifier).is_none() { + RestrictedType::from_str(token_name) + } else { + options.types.get(token_name).map(|custom_restricted_type| { + RestrictedType::Custom(( + token_name.to_string(), + custom_restricted_type.clone(), + )) + }) + }?; + + return Some(State { + restricted_type, + range: identifier_token.text_trimmed_range(), + reference_identifier: Some(reference_identifier.clone()), + }); + } + } + + None + } + + fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option { + let restricted_type = &state.restricted_type; + + let message = match restricted_type { + RestrictedType::Custom((_, data)) => &data.message, + _ => &format!("Don't use '{}' as a type.", restricted_type), + }; + + let mut diagnostic = RuleDiagnostic::new( + rule_category!(), + state.range, + markup! { {message} }.to_owned(), + ); + + if let Some(additional_note) = restricted_type.additional_note() { + diagnostic = diagnostic.note(markup! { {additional_note} }.to_owned()); + } + + Some(diagnostic) + } + + fn action(ctx: &RuleContext, state: &Self::State) -> Option { + let mut mutation = ctx.root().begin(); + let suggested_type = state.restricted_type.suggested_fix_type()?; + + let prev_token = state.reference_identifier.clone()?.value_token().ok()?; + let new_token = make::ident(suggested_type.as_str()); + + 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 { + extend_defaults: Option, + 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, + fix_with: Option, +} + +declare_node_union! { + pub TsRestrictedType = TsReferenceType | TsObjectType +} + +pub struct State { + /// Name of the restricted type. + restricted_type: RestrictedType, + /// Text range used to diagnostic the banned type. + range: TextRange, + /// Reference to the node to be replaced in the action. + /// This is optional because we don't replace empty objects references. + reference_identifier: Option, +} + +#[derive(Debug)] +pub enum RestrictedType { + BigInt, + Boolean, + Function, + Number, + Object, + String, + Symbol, + /// {} + EmptyObject, + /// User provided custom restricted type + Custom((String, CustomRestrictedType)), +} + +impl RestrictedType { + /// construct a [RestrictedType] from the textual name of a JavaScript type + fn from_str(s: &str) -> Option { + Some(match s { + "BigInt" => Self::BigInt, + "Boolean" => Self::Boolean, + "Function" => Self::Function, + "Number" => Self::Number, + "Object" => Self::Object, + "String" => Self::String, + "Symbol" => Self::Symbol, + "{}" => Self::EmptyObject, + _ => return None, + }) + } + + /// Retrieves a diagnostic message from a [RestrictedType] + fn additional_note(&self) -> Option<&str> { + Some(match *self { + | Self::BigInt + | Self::Boolean + | Self::Number + | Self::String + | Self::Symbol => "Use lowercase primitives for consistency.", + Self::Function => + "Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.", + Self::Object => + "Prefer explicitly define the object shape. This type means \"any non-nullable value\", which is slightly better than 'unknown', but it's still a broad type.", + Self::EmptyObject => "Prefer explicitly define the object shape. '{}' means \"any non-nullable value\".", + _ => return None, + }) + } + + fn suggested_fix_type(&self) -> Option { + Some(match self { + Self::BigInt => "bigint".to_string(), + Self::Boolean => "boolean".to_string(), + Self::Number => "number".to_string(), + Self::String => "string".to_string(), + Self::Symbol => "symbol".to_string(), + Self::Custom((_, data)) => data.fix_with.clone()?, + _ => return None, + }) + } +} + +impl Display for RestrictedType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let representation = match self { + Self::BigInt => "BigInt", + Self::Boolean => "Boolean", + Self::Function => "Function", + Self::Number => "Number", + Self::Object => "Object", + Self::String => "String", + Self::Symbol => "Symbol", + Self::EmptyObject => "{}", + Self::Custom((name, _)) => name, + }; + write!(f, "{representation}") + } +} diff --git a/crates/biome_js_analyze/src/options.rs b/crates/biome_js_analyze/src/options.rs index 0a23b9b0481f..9118c39b28ac 100644 --- a/crates/biome_js_analyze/src/options.rs +++ b/crates/biome_js_analyze/src/options.rs @@ -174,6 +174,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/invalid.ts b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalid.ts new file mode 100644 index 000000000000..f2e18dda902f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalid.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/invalid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalid.ts.snap new file mode 100644 index 000000000000..cd6787639fe7 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalid.ts.snap @@ -0,0 +1,643 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.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' }; + +``` + +# Diagnostics +``` +invalid.ts:1:8 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'String' as a type. + + > 1 │ let a: String; + │ ^^^^^^ + 2 │ + 3 │ let e: Object; + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'string' instead + + 1 │ - let·a:·String; + 1 │ + let·a:·string; + 2 2 │ + 3 3 │ let e: Object; + + +``` + +``` +invalid.ts:3:8 lint/nursery/noRestrictedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'Object' as a type. + + 1 │ let a: String; + 2 │ + > 3 │ let e: Object; + │ ^^^^^^ + 4 │ + 5 │ let b: { c: String }; + + i Prefer explicitly define the object shape. This type means "any non-nullable value", which is slightly better than 'unknown', but it's still a broad type. + + +``` + +``` +invalid.ts:5:13 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'String' as a type. + + 3 │ let e: Object; + 4 │ + > 5 │ let b: { c: String }; + │ ^^^^^^ + 6 │ + 7 │ function foo(a: String) {} + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'string' instead + + 3 3 │ let e: Object; + 4 4 │ + 5 │ - let·b:·{·c:·String·}; + 5 │ + let·b:·{·c:·string·}; + 6 6 │ + 7 7 │ function foo(a: String) {} + + +``` + +``` +invalid.ts:7:17 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'String' as a type. + + 5 │ let b: { c: String }; + 6 │ + > 7 │ function foo(a: String) {} + │ ^^^^^^ + 8 │ + 9 │ 'a' as String; + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'string' instead + + 5 5 │ let b: { c: String }; + 6 6 │ + 7 │ - function·foo(a:·String)·{} + 7 │ + function·foo(a:·string)·{} + 8 8 │ + 9 9 │ 'a' as String; + + +``` + +``` +invalid.ts:9:8 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'String' as a type. + + 7 │ function foo(a: String) {} + 8 │ + > 9 │ 'a' as String; + │ ^^^^^^ + 10 │ + 11 │ class Foo extends Bar implements Baz { + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'string' instead + + 7 7 │ function foo(a: String) {} + 8 8 │ + 9 │ - 'a'·as·String; + 9 │ + 'a'·as·string; + 10 10 │ + 11 11 │ class Foo extends Bar implements Baz { + + +``` + +``` +invalid.ts:11:15 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'String' as a type. + + 9 │ 'a' as String; + 10 │ + > 11 │ class Foo extends Bar implements Baz { + │ ^^^^^^ + 12 │ constructor(foo: String | Object) {} + 13 │ + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'string' instead + + 9 9 │ 'a' as String; + 10 10 │ + 11 │ - class·Foo·extends·Bar·implements·Baz·{ + 11 │ + class·Foo·extends·Bar·implements·Baz·{ + 12 12 │ constructor(foo: String | Object) {} + 13 13 │ + + +``` + +``` +invalid.ts:11:35 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'String' as a type. + + 9 │ 'a' as String; + 10 │ + > 11 │ class Foo extends Bar implements Baz { + │ ^^^^^^ + 12 │ constructor(foo: String | Object) {} + 13 │ + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'string' instead + + 9 9 │ 'a' as String; + 10 10 │ + 11 │ - class·Foo·extends·Bar·implements·Baz·{ + 11 │ + class·Foo·extends·Bar·implements·Baz·{ + 12 12 │ constructor(foo: String | Object) {} + 13 13 │ + + +``` + +``` +invalid.ts:11:58 lint/nursery/noRestrictedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'Object' as a type. + + 9 │ 'a' as String; + 10 │ + > 11 │ class Foo extends Bar implements Baz { + │ ^^^^^^ + 12 │ constructor(foo: String | Object) {} + 13 │ + + i Prefer explicitly define the object shape. This type means "any non-nullable value", which is slightly better than 'unknown', but it's still a broad type. + + +``` + +``` +invalid.ts:12:20 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'String' as a type. + + 11 │ class Foo extends Bar implements Baz { + > 12 │ constructor(foo: String | Object) {} + │ ^^^^^^ + 13 │ + 14 │ exit(): Array { + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'string' instead + + 10 10 │ + 11 11 │ class Foo extends Bar implements Baz { + 12 │ - ··constructor(foo:·String·|·Object)·{} + 12 │ + ··constructor(foo:·string·|·Object)·{} + 13 13 │ + 14 14 │ exit(): Array { + + +``` + +``` +invalid.ts:12:29 lint/nursery/noRestrictedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'Object' as a type. + + 11 │ class Foo extends Bar implements Baz { + > 12 │ constructor(foo: String | Object) {} + │ ^^^^^^ + 13 │ + 14 │ exit(): Array { + + i Prefer explicitly define the object shape. This type means "any non-nullable value", which is slightly better than 'unknown', but it's still a broad type. + + +``` + +``` +invalid.ts:14:17 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'String' as a type. + + 12 │ constructor(foo: String | Object) {} + 13 │ + > 14 │ exit(): Array { + │ ^^^^^^ + 15 │ const foo: String = 1 as String; + 16 │ } + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'string' instead + + 12 12 │ constructor(foo: String | Object) {} + 13 13 │ + 14 │ - ··exit():·Array·{ + 14 │ + ··exit():·Array·{ + 15 15 │ const foo: String = 1 as String; + 16 16 │ } + + +``` + +``` +invalid.ts:15:16 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'String' as a type. + + 14 │ exit(): Array { + > 15 │ const foo: String = 1 as String; + │ ^^^^^^ + 16 │ } + 17 │ } + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'string' instead + + 13 13 │ + 14 14 │ exit(): Array { + 15 │ - ····const·foo:·String·=·1·as·String; + 15 │ + ····const·foo:·string·=·1·as·String; + 16 16 │ } + 17 17 │ } + + +``` + +``` +invalid.ts:15:30 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'String' as a type. + + 14 │ exit(): Array { + > 15 │ const foo: String = 1 as String; + │ ^^^^^^ + 16 │ } + 17 │ } + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'string' instead + + 13 13 │ + 14 14 │ exit(): Array { + 15 │ - ····const·foo:·String·=·1·as·String; + 15 │ + ····const·foo:·String·=·1·as·string; + 16 16 │ } + 17 17 │ } + + +``` + +``` +invalid.ts:19:20 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'Boolean' as a type. + + 17 │ } + 18 │ + > 19 │ let baz: [boolean, Boolean] = [true, false]; + │ ^^^^^^^ + 20 │ + 21 │ let z = true as Boolean; + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'boolean' instead + + 17 17 │ } + 18 18 │ + 19 │ - let·baz:·[boolean,·Boolean]·=·[true,·false]; + 19 │ + let·baz:·[boolean,·boolean]·=·[true,·false]; + 20 20 │ + 21 21 │ let z = true as Boolean; + + +``` + +``` +invalid.ts:21:17 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'Boolean' as a type. + + 19 │ let baz: [boolean, Boolean] = [true, false]; + 20 │ + > 21 │ let z = true as Boolean; + │ ^^^^^^^ + 22 │ + 23 │ type Props = {}; + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'boolean' instead + + 19 19 │ let baz: [boolean, Boolean] = [true, false]; + 20 20 │ + 21 │ - let·z·=·true·as·Boolean; + 21 │ + let·z·=·true·as·boolean; + 22 22 │ + 23 23 │ type Props = {}; + + +``` + +``` +invalid.ts:23:14 lint/nursery/noRestrictedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use '{}' as a type. + + 21 │ let z = true as Boolean; + 22 │ + > 23 │ type Props = {}; + │ ^^ + 24 │ + 25 │ let fn: Function = () => true + + i Prefer explicitly define the object shape. '{}' means "any non-nullable value". + + +``` + +``` +invalid.ts:25:9 lint/nursery/noRestrictedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'Function' as a type. + + 23 │ type Props = {}; + 24 │ + > 25 │ let fn: Function = () => true + │ ^^^^^^^^ + 26 │ + 27 │ const str: String = 'foo'; + + i Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs. + + +``` + +``` +invalid.ts:27:12 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'String' as a type. + + 25 │ let fn: Function = () => true + 26 │ + > 27 │ const str: String = 'foo'; + │ ^^^^^^ + 28 │ + 29 │ const bool: Boolean = true; + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'string' instead + + 25 25 │ let fn: Function = () => true + 26 26 │ + 27 │ - const·str:·String·=·'foo'; + 27 │ + const·str:·string·=·'foo'; + 28 28 │ + 29 29 │ const bool: Boolean = true; + + +``` + +``` +invalid.ts:29:13 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'Boolean' as a type. + + 27 │ const str: String = 'foo'; + 28 │ + > 29 │ const bool: Boolean = true; + │ ^^^^^^^ + 30 │ + 31 │ const num: Number = 1; + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'boolean' instead + + 27 27 │ const str: String = 'foo'; + 28 28 │ + 29 │ - const·bool:·Boolean·=·true; + 29 │ + const·bool:·boolean·=·true; + 30 30 │ + 31 31 │ const num: Number = 1; + + +``` + +``` +invalid.ts:31:12 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'Number' as a type. + + 29 │ const bool: Boolean = true; + 30 │ + > 31 │ const num: Number = 1; + │ ^^^^^^ + 32 │ + 33 │ const symb: Symbol = Symbol('foo'); + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'number' instead + + 29 29 │ const bool: Boolean = true; + 30 30 │ + 31 │ - const·num:·Number·=·1; + 31 │ + const·num:·number·=·1; + 32 32 │ + 33 33 │ const symb: Symbol = Symbol('foo'); + + +``` + +``` +invalid.ts:33:13 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'Symbol' as a type. + + 31 │ const num: Number = 1; + 32 │ + > 33 │ const symb: Symbol = Symbol('foo'); + │ ^^^^^^ + 34 │ + 35 │ const bigInt: BigInt = 1n; + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'symbol' instead + + 31 31 │ const num: Number = 1; + 32 32 │ + 33 │ - const·symb:·Symbol·=·Symbol('foo'); + 33 │ + const·symb:·symbol·=·Symbol('foo'); + 34 34 │ + 35 35 │ const bigInt: BigInt = 1n; + + +``` + +``` +invalid.ts:35:15 lint/nursery/noRestrictedTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'BigInt' as a type. + + 33 │ const symb: Symbol = Symbol('foo'); + 34 │ + > 35 │ const bigInt: BigInt = 1n; + │ ^^^^^^ + 36 │ + 37 │ const lowerObj: Object = {}; + + i Use lowercase primitives for consistency. + + i Safe fix: Use 'bigint' instead + + 33 33 │ const symb: Symbol = Symbol('foo'); + 34 34 │ + 35 │ - const·bigInt:·BigInt·=·1n; + 35 │ + const·bigInt:·bigint·=·1n; + 36 36 │ + 37 37 │ const lowerObj: Object = {}; + + +``` + +``` +invalid.ts:37:17 lint/nursery/noRestrictedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'Object' as a type. + + 35 │ const bigInt: BigInt = 1n; + 36 │ + > 37 │ const lowerObj: Object = {}; + │ ^^^^^^ + 38 │ + 39 │ const capitalObj: Object = { a: 'string' }; + + i Prefer explicitly define the object shape. This type means "any non-nullable value", which is slightly better than 'unknown', but it's still a broad type. + + +``` + +``` +invalid.ts:39:19 lint/nursery/noRestrictedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use 'Object' as a type. + + 37 │ const lowerObj: Object = {}; + 38 │ + > 39 │ const capitalObj: Object = { a: 'string' }; + │ ^^^^^^ + 40 │ + 41 │ const curly1: { + + i Prefer explicitly define the object shape. This type means "any non-nullable value", which is slightly better than 'unknown', but it's still a broad type. + + +``` + +``` +invalid.ts:41:15 lint/nursery/noRestrictedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use '{}' as a type. + + 39 │ const capitalObj: Object = { a: 'string' }; + 40 │ + > 41 │ const curly1: { + │ ^ + > 42 │ + > 43 │ } = 1; + │ ^ + 44 │ + 45 │ const curly2: {} = { a: 'string' }; + + i Prefer explicitly define the object shape. '{}' means "any non-nullable value". + + +``` + +``` +invalid.ts:45:15 lint/nursery/noRestrictedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Don't use '{}' as a type. + + 43 │ } = 1; + 44 │ + > 45 │ const curly2: {} = { a: 'string' }; + │ ^^ + 46 │ + + i Prefer explicitly define the object shape. '{}' means "any non-nullable value". + + +``` 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..24f880a68d7d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.options.json @@ -0,0 +1,24 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "enabled": true, + "rules": { + "nursery": { + "noRestrictedTypes": { + "level": "error", + "options": { + "types": { + "CustomType": { + "message": "Only CustomType2 is allowed", + "fixWith": "CustomType2" + }, + "Bar": { + "message": "Replace Bar with Foo" + } + } + } + } + } + } + } +} 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..15ae9e9bc21d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.ts @@ -0,0 +1,6 @@ +type CustomType = unknown +function fn2(arg: CustomType) { + return arg; +} + +const foo: Bar = 1; \ 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..7e8375163856 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/invalidCustom.ts.snap @@ -0,0 +1,36 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalidCustom.ts +--- +# Input +```ts +type CustomType = unknown +function fn2(arg: CustomType) { + return arg; +} + +const foo: Bar = 1; +``` + +# 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 │ } + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/valid.ts b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/valid.ts new file mode 100644 index 000000000000..50506125f228 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/valid.ts @@ -0,0 +1,32 @@ +// let f = Object(); + +// let foo: { x: number; y: number } = { x: 1, y: 1 }; + +// let g = Object.create(null); + +// let h = String(false); + +// let b: undefined; + +// let c: null; + +// let a: []; + +// let tuple: [boolean, string] = [true, "hello"]; + +// type Props = { +// foo: string; +// } + +// namespace X { +// // Allow user aliases +// type Number = number +// function f(): Number { +// return 0; +// } +// } + +// type PhoneNumber = number | null | undefined; +// type NonNullablePhoneNumber = PhoneNumber & {}; + +// function consumeNonNullableValue(value: T) {} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/valid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/valid.ts.snap new file mode 100644 index 000000000000..8d0ae4643b1f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/valid.ts.snap @@ -0,0 +1,40 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.ts +--- +# Input +```ts +// let f = Object(); + +// let foo: { x: number; y: number } = { x: 1, y: 1 }; + +// let g = Object.create(null); + +// let h = String(false); + +// let b: undefined; + +// let c: null; + +// let a: []; + +// let tuple: [boolean, string] = [true, "hello"]; + +// type Props = { +// foo: string; +// } + +// namespace X { +// // Allow user aliases +// type Number = number +// function f(): Number { +// return 0; +// } +// } + +// type PhoneNumber = number | null | undefined; +// type NonNullablePhoneNumber = PhoneNumber & {}; + +// function consumeNonNullableValue(value: T) {} + +``` 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..734aa24f8b87 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noRestrictedTypes/validCustom.options.json @@ -0,0 +1,17 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "linter": { + "enabled": true, + "rules": { + "nursery": { + "noRestrictedTypes": { + "level": "error", + "options": { + "extendDefaults": false, + "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 e346c2a8000e..6c5b2e995c92 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1133,6 +1133,10 @@ export interface Nursery { * Disallow specified modules when loaded by import or require. */ noRestrictedImports?: RuleConfiguration_for_RestrictedImportsOptions; + /** + * Disallow primitive type aliases, misleading or user defined types. + */ + noRestrictedTypes?: RuleFixConfiguration_for_NoRestrictedTypesOptions; /** * Disallow shorthand properties that override related longhand properties. */ @@ -1837,6 +1841,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 RuleFixConfiguration_for_UseImportExtensionsOptions = | RulePlainConfiguration | RuleWithFixOptions_for_UseImportExtensionsOptions; @@ -1947,6 +1954,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 RuleWithFixOptions_for_UseImportExtensionsOptions { /** * The kind of the code actions emitted by the rule @@ -2086,6 +2107,10 @@ export interface RestrictedImportsOptions { */ paths: {}; } +export interface NoRestrictedTypesOptions { + extendDefaults?: boolean; + types: {}; +} export interface UseImportExtensionsOptions { /** * A map of custom import extension mappings, where the key is the inspected file extension, and the value is a pair of `module` extension and `component` import extension @@ -2543,8 +2568,9 @@ export type Category = | "lint/nursery/noMissingGenericFamilyKeyword" | "lint/nursery/noReactSpecificProps" | "lint/nursery/noRestrictedImports" - | "lint/nursery/noStaticElementInteractions" + | "lint/nursery/noRestrictedTypes" | "lint/nursery/noShorthandPropertyOverrides" + | "lint/nursery/noStaticElementInteractions" | "lint/nursery/noSubstr" | "lint/nursery/noUndeclaredDependencies" | "lint/nursery/noUnknownFunction" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 4bca8b03e4e4..9f4ec6663815 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -975,6 +975,15 @@ }, "additionalProperties": false }, + "CustomRestrictedType": { + "type": "object", + "required": ["message"], + "properties": { + "fixWith": { "type": ["string", "null"] }, + "message": { "type": "string" } + }, + "additionalProperties": false + }, "DeprecatedHooksConfiguration": { "anyOf": [ { "$ref": "#/definitions/RulePlainConfiguration" }, @@ -1750,6 +1759,26 @@ }, "additionalProperties": false }, + "NoRestrictedTypesConfiguration": { + "anyOf": [ + { "$ref": "#/definitions/RulePlainConfiguration" }, + { "$ref": "#/definitions/RuleWithNoRestrictedTypesOptions" } + ] + }, + "NoRestrictedTypesOptions": { + "type": "object", + "required": ["types"], + "properties": { + "extendDefaults": { "type": ["boolean", "null"] }, + "types": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/CustomRestrictedType" + } + } + }, + "additionalProperties": false + }, "Nursery": { "description": "A list of rules that belong to this group", "type": "object", @@ -1905,6 +1934,13 @@ { "type": "null" } ] }, + "noRestrictedTypes": { + "description": "Disallow primitive type aliases, misleading or user defined types.", + "anyOf": [ + { "$ref": "#/definitions/NoRestrictedTypesConfiguration" }, + { "type": "null" } + ] + }, "noShorthandPropertyOverrides": { "description": "Disallow shorthand properties that override related longhand properties.", "anyOf": [ @@ -2584,6 +2620,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"],