diff --git a/CHANGELOG.md b/CHANGELOG.md index 78e04d548243..3bb41438e71b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,10 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b ### Linter +#### New features + +- Add [nursery/noExportedImports](https://biomejs.dev/linter/rules/no-exported-imports/). Contributed by @Conaclos + #### Bug fixes - `useConsistentArrayType` and `useShorthandArrayType` now ignore `Array` in the `extends` and `implements` clauses. Fix [#3247](https://github.com/biomejs/biome/issues/3247). Contributed by @Conaclos diff --git a/crates/biome_configuration/src/linter/rules.rs b/crates/biome_configuration/src/linter/rules.rs index a8f9afa3af6a..e5f7b95b82ed 100644 --- a/crates/biome_configuration/src/linter/rules.rs +++ b/crates/biome_configuration/src/linter/rules.rs @@ -2838,6 +2838,9 @@ pub struct Nursery { #[doc = "Disallow variables from evolving into any type through reassignments."] #[serde(skip_serializing_if = "Option::is_none")] pub no_evolving_types: Option>, + #[doc = "Disallow exporting an imported variable."] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_exported_imports: Option>, #[doc = "Disallow invalid !important within keyframe declarations"] #[serde(skip_serializing_if = "Option::is_none")] pub no_important_in_keyframe: Option>, @@ -2988,6 +2991,7 @@ impl Nursery { "noDuplicateSelectorsKeyframeBlock", "noEmptyBlock", "noEvolvingTypes", + "noExportedImports", "noImportantInKeyframe", "noInvalidDirectionInLinearGradient", "noInvalidPositionAtImportRule", @@ -3059,20 +3063,20 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]), 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[39]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -3123,6 +3127,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -3184,201 +3189,206 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_important_in_keyframe.as_ref() { + if let Some(rule) = self.no_exported_imports.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.no_invalid_direction_in_linear_gradient.as_ref() { + if let Some(rule) = self.no_important_in_keyframe.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.no_invalid_position_at_import_rule.as_ref() { + if let Some(rule) = self.no_invalid_direction_in_linear_gradient.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_label_without_control.as_ref() { + if let Some(rule) = self.no_invalid_position_at_import_rule.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_misplaced_assertion.as_ref() { + if let Some(rule) = self.no_label_without_control.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.no_react_specific_props.as_ref() { + if let Some(rule) = self.no_misplaced_assertion.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.no_restricted_imports.as_ref() { + if let Some(rule) = self.no_react_specific_props.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.no_shorthand_property_overrides.as_ref() { + if let Some(rule) = self.no_restricted_imports.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.no_substr.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[17])); } } - 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[18])); } } - 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[19])); } } - 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[20])); } } - 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[21])); } } - 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[22])); } } - 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[23])); } } - 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[24])); } } - 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[25])); } } - 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[26])); } } - 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[27])); } } - 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[28])); } } - if let Some(rule) = self.no_yoda_expression.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[29])); } } - 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[30])); } } - 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[31])); } } - if let Some(rule) = self.use_consistent_grid_areas.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[32])); } } - 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[33])); } } - 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[34])); } } - if let Some(rule) = self.use_error_message.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[35])); } } - 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[36])); } } - 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[37])); } } - 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[38])); } } - 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[39])); } } - 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[40])); } } - 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[41])); } } - 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[42])); } } - 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[43])); } } - if let Some(rule) = self.use_throw_new_error.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[44])); } } - 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[45])); } } - 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[46])); } } - if let Some(rule) = self.use_valid_autocomplete.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[47])); } } + 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[48])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> FxHashSet> { @@ -3428,201 +3438,206 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_important_in_keyframe.as_ref() { + if let Some(rule) = self.no_exported_imports.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.no_invalid_direction_in_linear_gradient.as_ref() { + if let Some(rule) = self.no_important_in_keyframe.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.no_invalid_position_at_import_rule.as_ref() { + if let Some(rule) = self.no_invalid_direction_in_linear_gradient.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_label_without_control.as_ref() { + if let Some(rule) = self.no_invalid_position_at_import_rule.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_misplaced_assertion.as_ref() { + if let Some(rule) = self.no_label_without_control.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.no_react_specific_props.as_ref() { + if let Some(rule) = self.no_misplaced_assertion.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.no_restricted_imports.as_ref() { + if let Some(rule) = self.no_react_specific_props.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.no_shorthand_property_overrides.as_ref() { + if let Some(rule) = self.no_restricted_imports.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.no_substr.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[17])); } } - 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[18])); } } - 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[19])); } } - 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[20])); } } - 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[21])); } } - 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[22])); } } - 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[23])); } } - 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[24])); } } - 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[25])); } } - 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[26])); } } - 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[27])); } } - 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[28])); } } - if let Some(rule) = self.no_yoda_expression.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[29])); } } - 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[30])); } } - 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[31])); } } - if let Some(rule) = self.use_consistent_grid_areas.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[32])); } } - 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[33])); } } - 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[34])); } } - if let Some(rule) = self.use_error_message.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[35])); } } - 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[36])); } } - 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[37])); } } - 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[38])); } } - 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[39])); } } - 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[40])); } } - 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[41])); } } - 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[42])); } } - 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[43])); } } - if let Some(rule) = self.use_throw_new_error.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[44])); } } - 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[45])); } } - 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[46])); } } - if let Some(rule) = self.use_valid_autocomplete.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[47])); } } + 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[48])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -3695,6 +3710,10 @@ impl Nursery { .no_evolving_types .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "noExportedImports" => self + .no_exported_imports + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noImportantInKeyframe" => self .no_important_in_keyframe .as_ref() diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index cfc037b7e21e..647ec5dbe041 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -124,6 +124,7 @@ define_categories! { "lint/nursery/noDuplicateSelectorsKeyframeBlock": "https://biomejs.dev/linter/rules/no-duplicate-selectors-keyframe-block", "lint/nursery/noEmptyBlock": "https://biomejs.dev/linter/rules/no-empty-block", "lint/nursery/noEvolvingTypes": "https://biomejs.dev/linter/rules/no-evolving-any", + "lint/nursery/noExportedImports": "https://biomejs.dev/linter/rules/no-exported-imports", "lint/nursery/noImportantInKeyframe": "https://biomejs.dev/linter/rules/no-important-in-keyframe", "lint/nursery/noInvalidDirectionInLinearGradient": "https://biomejs.dev/linter/rules/no-invalid-direction-in-linear-gradient", "lint/nursery/noInvalidPositionAtImportRule": "https://biomejs.dev/linter/rules/no-invalid-position-at-import-rule", diff --git a/crates/biome_js_analyze/src/lint/correctness/no_nodejs_modules.rs b/crates/biome_js_analyze/src/lint/correctness/no_nodejs_modules.rs index 18406ed5fe2e..bdc6b291f334 100644 --- a/crates/biome_js_analyze/src/lint/correctness/no_nodejs_modules.rs +++ b/crates/biome_js_analyze/src/lint/correctness/no_nodejs_modules.rs @@ -1,7 +1,7 @@ use crate::globals::is_node_builtin_module; use biome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic, RuleSource}; use biome_console::markup; -use biome_js_syntax::{inner_string_text, AnyJsImportSpecifierLike}; +use biome_js_syntax::{inner_string_text, AnyJsImportLike}; use biome_rowan::TextRange; declare_rule! { @@ -36,7 +36,7 @@ declare_rule! { } impl Rule for NoNodejsModules { - type Query = Ast; + type Query = Ast; type State = TextRange; type Signals = Option; type Options = (); diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 7f080f742fa9..081d55b38299 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -6,6 +6,7 @@ pub mod no_console; pub mod no_done_callback; pub mod no_duplicate_else_if; pub mod no_evolving_types; +pub mod no_exported_imports; pub mod no_label_without_control; pub mod no_misplaced_assertion; pub mod no_react_specific_props; @@ -41,6 +42,7 @@ declare_lint_group! { self :: no_done_callback :: NoDoneCallback , self :: no_duplicate_else_if :: NoDuplicateElseIf , self :: no_evolving_types :: NoEvolvingTypes , + self :: no_exported_imports :: NoExportedImports , self :: no_label_without_control :: NoLabelWithoutControl , self :: no_misplaced_assertion :: NoMisplacedAssertion , self :: no_react_specific_props :: NoReactSpecificProps , diff --git a/crates/biome_js_analyze/src/lint/nursery/no_exported_imports.rs b/crates/biome_js_analyze/src/lint/nursery/no_exported_imports.rs new file mode 100644 index 000000000000..37cd3c0d0e83 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_exported_imports.rs @@ -0,0 +1,85 @@ +use biome_analyze::{context::RuleContext, declare_rule, Rule, RuleDiagnostic}; +use biome_console::markup; +use biome_js_semantic::CanBeImportedExported; +use biome_js_syntax::AnyJsImportSpecifier; +use biome_rowan::AstNode; + +use crate::services::semantic::Semantic; + +declare_rule! { + /// Disallow exporting an imported variable. + /// + /// In JavaScript, you can re-export a variable either by using `export from` or + /// by first importing the variable and then exporting it with a regular `export`. + /// + /// You may prefer to use the first approach, as it clearly communicates the intention + /// to re-export an import, and can make static analysis easier. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// import { A } from "mod"; + /// export { A }; + /// ``` + /// + /// ```js,expect_diagnostic + /// import * as ns from "mod"; + /// export { ns }; + /// ``` + /// + /// ```js,expect_diagnostic + /// import D from "mod"; + /// export { D }; + /// ``` + /// + /// ### Valid + /// + /// ```js + /// export { A } from "mod"; + /// export * as ns from "mod"; + /// export { default as D } from "mod"; + /// ``` + /// + pub NoExportedImports { + version: "next", + name: "noExportedImports", + language: "js", + recommended: false, + } +} + +impl Rule for NoExportedImports { + type Query = Semantic; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let specifier = ctx.query(); + let local_name = specifier.local_name().ok()?; + let local_name = local_name.as_js_identifier_binding()?; + if local_name.is_exported(ctx.model()) { + Some(()) + } else { + None + } + } + + fn diagnostic(ctx: &RuleContext, _state: &Self::State) -> Option { + let specifier = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + specifier.range(), + markup! { + "An import should not be exported. Use ""export from""instead." + }, + ) + .note(markup! { + "export from"" makes it clearer that the intention is to re-export a variable." + }), + ) + } +} diff --git a/crates/biome_js_analyze/src/lint/nursery/no_restricted_imports.rs b/crates/biome_js_analyze/src/lint/nursery/no_restricted_imports.rs index fd2f4be81c82..1dd6212a8b86 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_restricted_imports.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_restricted_imports.rs @@ -2,7 +2,7 @@ use biome_analyze::context::RuleContext; use biome_analyze::{declare_rule, Ast, Rule, RuleDiagnostic, RuleSource}; use biome_console::markup; use biome_deserialize_macros::Deserializable; -use biome_js_syntax::{inner_string_text, AnyJsImportSpecifierLike}; +use biome_js_syntax::{inner_string_text, AnyJsImportLike}; use biome_rowan::TextRange; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; @@ -47,7 +47,7 @@ pub struct RestrictedImportsOptions { } impl Rule for NoRestrictedImports { - type Query = Ast; + type Query = Ast; type State = (TextRange, String); type Signals = Option; type Options = Box; diff --git a/crates/biome_js_analyze/src/lint/nursery/no_undeclared_dependencies.rs b/crates/biome_js_analyze/src/lint/nursery/no_undeclared_dependencies.rs index 2eaeb6d25d91..672b761422d3 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_undeclared_dependencies.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_undeclared_dependencies.rs @@ -1,7 +1,7 @@ use crate::services::manifest::Manifest; use biome_analyze::{context::RuleContext, declare_rule, Rule, RuleDiagnostic}; use biome_console::markup; -use biome_js_syntax::AnyJsImportSpecifierLike; +use biome_js_syntax::AnyJsImportLike; use biome_rowan::AstNode; declare_rule! { @@ -41,7 +41,7 @@ declare_rule! { } impl Rule for NoUndeclaredDependencies { - type Query = Manifest; + type Query = Manifest; type State = (); type Signals = Option; type Options = (); diff --git a/crates/biome_js_analyze/src/lint/nursery/use_import_extensions.rs b/crates/biome_js_analyze/src/lint/nursery/use_import_extensions.rs index e4388435b086..02a4344503b5 100644 --- a/crates/biome_js_analyze/src/lint/nursery/use_import_extensions.rs +++ b/crates/biome_js_analyze/src/lint/nursery/use_import_extensions.rs @@ -5,7 +5,7 @@ use biome_analyze::{ }; use biome_console::markup; use biome_js_factory::make; -use biome_js_syntax::{inner_string_text, AnyJsImportSpecifierLike, JsLanguage}; +use biome_js_syntax::{inner_string_text, AnyJsImportLike, JsLanguage}; use biome_rowan::{BatchMutationExt, SyntaxToken}; use crate::JsRuleAction; @@ -83,7 +83,7 @@ declare_rule! { } impl Rule for UseImportExtensions { - type Query = Ast; + type Query = Ast; type State = UseImportExtensionsState; type Signals = Option; type Options = (); @@ -145,7 +145,7 @@ pub struct UseImportExtensionsState { fn get_extensionless_import( file_ext: &str, - node: &AnyJsImportSpecifierLike, + node: &AnyJsImportLike, ) -> Option { let module_name_token = node.module_name_token()?; let module_path = inner_string_text(&module_name_token); diff --git a/crates/biome_js_analyze/src/lint/style/use_node_assert_strict.rs b/crates/biome_js_analyze/src/lint/style/use_node_assert_strict.rs index 4df497346968..8671db774b04 100644 --- a/crates/biome_js_analyze/src/lint/style/use_node_assert_strict.rs +++ b/crates/biome_js_analyze/src/lint/style/use_node_assert_strict.rs @@ -3,7 +3,7 @@ use biome_analyze::{ context::RuleContext, declare_rule, ActionCategory, Ast, FixKind, Rule, RuleDiagnostic, }; use biome_console::markup; -use biome_js_syntax::{inner_string_text, AnyJsImportSpecifierLike, JsSyntaxKind, JsSyntaxToken}; +use biome_js_syntax::{inner_string_text, AnyJsImportLike, JsSyntaxKind, JsSyntaxToken}; use biome_rowan::BatchMutationExt; declare_rule! { @@ -35,7 +35,7 @@ declare_rule! { } impl Rule for UseNodeAssertStrict { - type Query = Ast; + type Query = Ast; type State = JsSyntaxToken; type Signals = Option; type Options = (); diff --git a/crates/biome_js_analyze/src/lint/style/use_nodejs_import_protocol.rs b/crates/biome_js_analyze/src/lint/style/use_nodejs_import_protocol.rs index a66e946de1e1..4310d1e7d88d 100644 --- a/crates/biome_js_analyze/src/lint/style/use_nodejs_import_protocol.rs +++ b/crates/biome_js_analyze/src/lint/style/use_nodejs_import_protocol.rs @@ -3,7 +3,7 @@ use biome_analyze::{ RuleSource, }; use biome_console::markup; -use biome_js_syntax::{inner_string_text, AnyJsImportSpecifierLike, JsSyntaxKind, JsSyntaxToken}; +use biome_js_syntax::{inner_string_text, AnyJsImportLike, JsSyntaxKind, JsSyntaxToken}; use biome_rowan::BatchMutationExt; use crate::{globals::is_node_builtin_module, JsRuleAction}; @@ -51,7 +51,7 @@ declare_rule! { } impl Rule for UseNodejsImportProtocol { - type Query = Ast; + type Query = Ast; type State = JsSyntaxToken; type Signals = Option; type Options = (); diff --git a/crates/biome_js_analyze/src/lint/suspicious/no_import_assign.rs b/crates/biome_js_analyze/src/lint/suspicious/no_import_assign.rs index 2da148df3e22..442b2b5208ff 100644 --- a/crates/biome_js_analyze/src/lint/suspicious/no_import_assign.rs +++ b/crates/biome_js_analyze/src/lint/suspicious/no_import_assign.rs @@ -2,12 +2,9 @@ use crate::services::semantic::Semantic; use biome_analyze::{context::RuleContext, declare_rule, Rule, RuleDiagnostic, RuleSource}; use biome_console::markup; use biome_js_semantic::ReferencesExtensions; -use biome_js_syntax::{ - JsDefaultImportSpecifier, JsIdentifierAssignment, JsIdentifierBinding, JsNamedImportSpecifier, - JsNamespaceImportSpecifier, JsShorthandNamedImportSpecifier, -}; +use biome_js_syntax::{AnyJsImportSpecifier, JsIdentifierAssignment, JsIdentifierBinding}; -use biome_rowan::{declare_node_union, AstNode}; +use biome_rowan::AstNode; declare_rule! { /// Disallow assigning to imported bindings @@ -59,7 +56,7 @@ declare_rule! { } impl Rule for NoImportAssign { - type Query = Semantic; + type Query = Semantic; /// The first element of the tuple is the invalid `JsIdentifierAssignment`, the second element of the tuple is the imported `JsIdentifierBinding`. type State = (JsIdentifierAssignment, JsIdentifierBinding); type Signals = Vec; @@ -71,22 +68,26 @@ impl Rule for NoImportAssign { let local_name_binding = match label_statement { // `import {x as xx} from 'y'` // ^^^^^^^ - AnyJsImportLike::JsNamedImportSpecifier(specifier) => specifier.local_name().ok(), + AnyJsImportSpecifier::JsNamedImportSpecifier(specifier) => specifier.local_name().ok(), // `import {x} from 'y'` // ^ - AnyJsImportLike::JsShorthandNamedImportSpecifier(specifier) => { + AnyJsImportSpecifier::JsShorthandNamedImportSpecifier(specifier) => { specifier.local_name().ok() } // `import * as xxx from 'y'` // ^^^^^^^^ // `import a, * as b from 'y'` // ^^^^^^ - AnyJsImportLike::JsNamespaceImportSpecifier(specifier) => specifier.local_name().ok(), + AnyJsImportSpecifier::JsNamespaceImportSpecifier(specifier) => { + specifier.local_name().ok() + } // `import xx from 'y'` // ^^ // `import a, * as b from 'y'` // ^ - AnyJsImportLike::JsDefaultImportSpecifier(specifier) => specifier.local_name().ok(), + AnyJsImportSpecifier::JsDefaultImportSpecifier(specifier) => { + specifier.local_name().ok() + } }; local_name_binding .and_then(|binding| { @@ -125,7 +126,3 @@ impl Rule for NoImportAssign { ) } } - -declare_node_union! { - pub AnyJsImportLike = JsNamedImportSpecifier | JsShorthandNamedImportSpecifier | JsNamespaceImportSpecifier | JsDefaultImportSpecifier -} diff --git a/crates/biome_js_analyze/src/options.rs b/crates/biome_js_analyze/src/options.rs index 7fdc6c1feaee..cbe20c1ee3e4 100644 --- a/crates/biome_js_analyze/src/options.rs +++ b/crates/biome_js_analyze/src/options.rs @@ -83,6 +83,8 @@ pub type NoExcessiveCognitiveComplexity = < lint :: complexity :: no_excessive_c pub type NoExcessiveNestedTestSuites = < lint :: complexity :: no_excessive_nested_test_suites :: NoExcessiveNestedTestSuites as biome_analyze :: Rule > :: Options ; pub type NoExplicitAny = ::Options; +pub type NoExportedImports = + ::Options; pub type NoExportsInTest = ::Options; pub type NoExtraBooleanCast = diff --git a/crates/biome_js_analyze/tests/specs/nursery/noExportedImports/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noExportedImports/invalid.js new file mode 100644 index 000000000000..d01f0fd64ea7 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noExportedImports/invalid.js @@ -0,0 +1,8 @@ +import { A } from "mod"; +export { A }; + +import * as ns from "mod"; +export { ns }; + +import D from "mod"; +export { D }; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noExportedImports/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noExportedImports/invalid.js.snap new file mode 100644 index 000000000000..f281aa2de0fe --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noExportedImports/invalid.js.snap @@ -0,0 +1,64 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```jsx +import { A } from "mod"; +export { A }; + +import * as ns from "mod"; +export { ns }; + +import D from "mod"; +export { D }; +``` + +# Diagnostics +``` +invalid.js:1:10 lint/nursery/noExportedImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! An import should not be exported. Use export frominstead. + + > 1 │ import { A } from "mod"; + │ ^ + 2 │ export { A }; + 3 │ + + i export from makes it clearer that the intention is to re-export a variable. + + +``` + +``` +invalid.js:4:8 lint/nursery/noExportedImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! An import should not be exported. Use export frominstead. + + 2 │ export { A }; + 3 │ + > 4 │ import * as ns from "mod"; + │ ^^^^^^^ + 5 │ export { ns }; + 6 │ + + i export from makes it clearer that the intention is to re-export a variable. + + +``` + +``` +invalid.js:7:8 lint/nursery/noExportedImports ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! An import should not be exported. Use export frominstead. + + 5 │ export { ns }; + 6 │ + > 7 │ import D from "mod"; + │ ^ + 8 │ export { D }; + + i export from makes it clearer that the intention is to re-export a variable. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noExportedImports/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noExportedImports/valid.js new file mode 100644 index 000000000000..fabe6a966fd3 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noExportedImports/valid.js @@ -0,0 +1,3 @@ +export { A } from "mod"; +export * as ns from "mod"; +export { default as D } from "mod"; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noExportedImports/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noExportedImports/valid.js.snap new file mode 100644 index 000000000000..e903439246b4 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noExportedImports/valid.js.snap @@ -0,0 +1,10 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```jsx +export { A } from "mod"; +export * as ns from "mod"; +export { default as D } from "mod"; +``` diff --git a/crates/biome_js_syntax/src/import_ext.rs b/crates/biome_js_syntax/src/import_ext.rs index 36b3df872813..485723c978b5 100644 --- a/crates/biome_js_syntax/src/import_ext.rs +++ b/crates/biome_js_syntax/src/import_ext.rs @@ -1,7 +1,8 @@ use crate::{ inner_string_text, AnyJsBinding, AnyJsImportClause, AnyJsNamedImportSpecifier, - JsCallExpression, JsImport, JsImportAssertion, JsImportCallExpression, JsModuleSource, - JsSyntaxToken, TsExternalModuleDeclaration, + JsCallExpression, JsDefaultImportSpecifier, JsImport, JsImportAssertion, + JsImportCallExpression, JsModuleSource, JsNamedImportSpecifier, JsNamespaceImportSpecifier, + JsShorthandNamedImportSpecifier, JsSyntaxToken, TsExternalModuleDeclaration, }; use biome_rowan::{declare_node_union, AstNode, SyntaxNodeOptionExt, SyntaxResult, TokenText}; @@ -202,27 +203,26 @@ declare_node_union! { /// import("lodash") /// // ^^^^^^^^^^^^^^^^ /// ``` - pub AnyJsImportSpecifierLike = JsModuleSource | JsCallExpression | JsImportCallExpression + pub AnyJsImportLike = JsModuleSource | JsCallExpression | JsImportCallExpression } -impl AnyJsImportSpecifierLike { +impl AnyJsImportLike { /// Returns the inner text of specifier: /// /// ## Examples /// /// ``` /// use biome_js_factory::make; - /// use biome_js_syntax::AnyJsImportSpecifierLike; - /// use biome_rowan::TriviaPieceKind; + /// use biome_js_syntax::AnyJsImportLike; /// /// let source_name = make::js_module_source(make::js_string_literal("foo")); - /// let any_import_specifier = AnyJsImportSpecifierLike::JsModuleSource(source_name); + /// let any_import_specifier = AnyJsImportLike::JsModuleSource(source_name); /// assert_eq!(any_import_specifier.inner_string_text().unwrap().text(), "foo") /// ``` pub fn inner_string_text(&self) -> Option { match self { - AnyJsImportSpecifierLike::JsModuleSource(source) => source.inner_string_text().ok(), - AnyJsImportSpecifierLike::JsCallExpression(expression) => { + AnyJsImportLike::JsModuleSource(source) => source.inner_string_text().ok(), + AnyJsImportLike::JsCallExpression(expression) => { let callee = expression.callee().ok()?; let name = callee.as_js_reference_identifier()?.value_token().ok()?; if name.text_trimmed() == "require" { @@ -240,7 +240,7 @@ impl AnyJsImportSpecifierLike { None } } - AnyJsImportSpecifierLike::JsImportCallExpression(import_call) => { + AnyJsImportLike::JsImportCallExpression(import_call) => { let [Some(argument)] = import_call.arguments().ok()?.get_arguments_by_index([0]) else { return None; @@ -261,17 +261,16 @@ impl AnyJsImportSpecifierLike { /// /// ``` /// use biome_js_factory::make; - /// use biome_js_syntax::AnyJsImportSpecifierLike; - /// use biome_rowan::TriviaPieceKind; + /// use biome_js_syntax::AnyJsImportLike; /// /// let source_name = make::js_module_source(make::js_string_literal("foo")); - /// let any_import_specifier = AnyJsImportSpecifierLike::JsModuleSource(source_name); + /// let any_import_specifier = AnyJsImportLike::JsModuleSource(source_name); /// assert_eq!(any_import_specifier.module_name_token().unwrap().text(), "\"foo\"") /// ``` pub fn module_name_token(&self) -> Option { match self { - AnyJsImportSpecifierLike::JsModuleSource(source) => source.value_token().ok(), - AnyJsImportSpecifierLike::JsCallExpression(expression) => { + AnyJsImportLike::JsModuleSource(source) => source.value_token().ok(), + AnyJsImportLike::JsCallExpression(expression) => { let callee = expression.callee().ok()?; let name = callee.as_js_reference_identifier()?.value_token().ok()?; if name.text_trimmed() == "require" { @@ -289,7 +288,7 @@ impl AnyJsImportSpecifierLike { None } } - AnyJsImportSpecifierLike::JsImportCallExpression(import_call) => { + AnyJsImportLike::JsImportCallExpression(import_call) => { let [Some(argument)] = import_call.arguments().ok()?.get_arguments_by_index([0]) else { return None; @@ -312,22 +311,21 @@ impl AnyJsImportSpecifierLike { /// /// ``` /// use biome_js_factory::make; - /// use biome_js_syntax::{AnyJsImportSpecifierLike, JsSyntaxKind, JsSyntaxToken}; - /// use biome_rowan::TriviaPieceKind; + /// use biome_js_syntax::{AnyJsImportLike, JsSyntaxKind, JsSyntaxToken}; /// /// let module_token = JsSyntaxToken::new_detached(JsSyntaxKind::MODULE_KW, "module", [], []); /// let module_source = make::js_module_source(make::js_string_literal("foo")); /// let module_declaration = make::ts_external_module_declaration(module_token, module_source).build(); - /// let any_import_specifier = AnyJsImportSpecifierLike::JsModuleSource(module_declaration.source().expect("module source")); + /// let any_import_specifier = AnyJsImportLike::JsModuleSource(module_declaration.source().expect("module source")); /// assert!(any_import_specifier.is_in_ts_module_declaration()); /// /// let module_source = make::js_module_source(make::js_string_literal("bar")); - /// let any_import_specifier = AnyJsImportSpecifierLike::JsModuleSource(module_source); + /// let any_import_specifier = AnyJsImportLike::JsModuleSource(module_source); /// assert!(!any_import_specifier.is_in_ts_module_declaration()); /// ``` pub fn is_in_ts_module_declaration(&self) -> bool { // It first has to be a JsModuleSource - if !matches!(self, AnyJsImportSpecifierLike::JsModuleSource(_)) { + if !matches!(self, AnyJsImportLike::JsModuleSource(_)) { return false; } // Then test whether its parent is a TsExternalModuleDeclaration @@ -337,3 +335,22 @@ impl AnyJsImportSpecifierLike { false } } + +declare_node_union! { + pub AnyJsImportSpecifier = JsNamedImportSpecifier + | JsShorthandNamedImportSpecifier + | JsNamespaceImportSpecifier + | JsDefaultImportSpecifier +} + +impl AnyJsImportSpecifier { + /// Imported name of this import specifier. + pub fn local_name(&self) -> SyntaxResult { + match self { + Self::JsNamedImportSpecifier(specifier) => specifier.local_name(), + Self::JsShorthandNamedImportSpecifier(specifier) => specifier.local_name(), + Self::JsNamespaceImportSpecifier(specifier) => specifier.local_name(), + Self::JsDefaultImportSpecifier(specifier) => specifier.local_name(), + } + } +} diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 7acc1f4959ff..65ef1303b82c 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1010,6 +1010,10 @@ export interface Nursery { * Disallow variables from evolving into any type through reassignments. */ noEvolvingTypes?: RuleConfiguration_for_Null; + /** + * Disallow exporting an imported variable. + */ + noExportedImports?: RuleConfiguration_for_Null; /** * Disallow invalid !important within keyframe declarations */ @@ -2378,6 +2382,7 @@ export type Category = | "lint/nursery/noDuplicateSelectorsKeyframeBlock" | "lint/nursery/noEmptyBlock" | "lint/nursery/noEvolvingTypes" + | "lint/nursery/noExportedImports" | "lint/nursery/noImportantInKeyframe" | "lint/nursery/noInvalidDirectionInLinearGradient" | "lint/nursery/noInvalidPositionAtImportRule" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 3ebf0888ed32..359cd91b994d 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1727,6 +1727,13 @@ { "type": "null" } ] }, + "noExportedImports": { + "description": "Disallow exporting an imported variable.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noImportantInKeyframe": { "description": "Disallow invalid !important within keyframe declarations", "anyOf": [