diff --git a/CHANGELOG.md b/CHANGELOG.md index c1177b98f441..41daba2d8371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -164,6 +164,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b #### New features - Add [nursery/useValidAutocomplete](https://biomejs.dev/linter/rules/use-valid-autocomplete/). Contributed by @unvalley +- Add [nursery/noDynamicNamespaceImportAccess](https://biomejs.dev/linter/no-dynamic-namespace-import-access/). Contributed by @minht11 #### Bug fixes diff --git a/crates/biome_configuration/src/linter/rules.rs b/crates/biome_configuration/src/linter/rules.rs index d2b9a7ca926d..75302e1838ac 100644 --- a/crates/biome_configuration/src/linter/rules.rs +++ b/crates/biome_configuration/src/linter/rules.rs @@ -2833,6 +2833,10 @@ pub struct Nursery { #[serde(skip_serializing_if = "Option::is_none")] pub no_duplicate_selectors_keyframe_block: Option>, + #[doc = "Disallow accessing namespace imports dynamically."] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_dynamic_namespace_import_access: + Option>, #[doc = "Disallow CSS empty blocks."] #[serde(skip_serializing_if = "Option::is_none")] pub no_empty_block: Option>, @@ -2993,6 +2997,7 @@ impl Nursery { "noDuplicateFontNames", "noDuplicateJsonKeys", "noDuplicateSelectorsKeyframeBlock", + "noDynamicNamespaceImportAccess", "noEmptyBlock", "noEvolvingTypes", "noExportedImports", @@ -3067,21 +3072,21 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]), 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[10]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), - 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[14]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), 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[26]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -3134,6 +3139,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -3185,221 +3191,226 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_empty_block.as_ref() { + if let Some(rule) = self.no_dynamic_namespace_import_access.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_evolving_types.as_ref() { + if let Some(rule) = self.no_empty_block.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_exported_imports.as_ref() { + if let Some(rule) = self.no_evolving_types.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - 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[10])); } } - 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[11])); } } - 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[12])); } } - 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[13])); } } - 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[14])); } } - 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[15])); } } - 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[16])); } } - 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[17])); } } - 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[18])); } } - 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[19])); } } - 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[20])); } } - 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[21])); } } - 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[22])); } } - 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[23])); } } - 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[24])); } } - 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[25])); } } - 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[26])); } } - 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[27])); } } - 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[28])); } } - 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[29])); } } - 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[30])); } } - 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[31])); } } - 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[32])); } } - 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[33])); } } - 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[34])); } } - 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[35])); } } - 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[36])); } } - 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[37])); } } - 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[38])); } } - 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[39])); } } - 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[40])); } } - 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[41])); } } - 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[42])); } } - 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[43])); } } - 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[44])); } } - 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[45])); } } - 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[46])); } } - 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[47])); } } - 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[48])); } } - 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[49])); } } + 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[50])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> FxHashSet> { @@ -3439,221 +3450,226 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_empty_block.as_ref() { + if let Some(rule) = self.no_dynamic_namespace_import_access.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_evolving_types.as_ref() { + if let Some(rule) = self.no_empty_block.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_exported_imports.as_ref() { + if let Some(rule) = self.no_evolving_types.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - 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[10])); } } - 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[11])); } } - 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[12])); } } - 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[13])); } } - 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[14])); } } - 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[15])); } } - 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[16])); } } - 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[17])); } } - 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[18])); } } - 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[19])); } } - 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[20])); } } - 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[21])); } } - 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[22])); } } - 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[23])); } } - 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[24])); } } - 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[25])); } } - 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[26])); } } - 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[27])); } } - 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[28])); } } - 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[29])); } } - 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[30])); } } - 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[31])); } } - 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[32])); } } - 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[33])); } } - 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[34])); } } - 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[35])); } } - 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[36])); } } - 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[37])); } } - 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[38])); } } - 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[39])); } } - 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[40])); } } - 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[41])); } } - 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[42])); } } - 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[43])); } } - 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[44])); } } - 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[45])); } } - 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[46])); } } - 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[47])); } } - 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[48])); } } - 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[49])); } } + 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[50])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -3718,6 +3734,10 @@ impl Nursery { .no_duplicate_selectors_keyframe_block .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "noDynamicNamespaceImportAccess" => self + .no_dynamic_namespace_import_access + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noEmptyBlock" => self .no_empty_block .as_ref() diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 191c3952f18d..33bbcbf858d3 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -122,6 +122,7 @@ define_categories! { "lint/nursery/noDuplicateFontNames": "https://biomejs.dev/linter/rules/no-font-family-duplicate-names", "lint/nursery/noDuplicateJsonKeys": "https://biomejs.dev/linter/rules/no-duplicate-json-keys", "lint/nursery/noDuplicateSelectorsKeyframeBlock": "https://biomejs.dev/linter/rules/no-duplicate-selectors-keyframe-block", + "lint/nursery/noDynamicNamespaceImportAccess": "https://biomejs.dev/linter/rules/no-dynamic-namespace-import-access", "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", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 081d55b38299..52ac858a369c 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -5,6 +5,7 @@ use biome_analyze::declare_lint_group; pub mod no_console; pub mod no_done_callback; pub mod no_duplicate_else_if; +pub mod no_dynamic_namespace_import_access; pub mod no_evolving_types; pub mod no_exported_imports; pub mod no_label_without_control; @@ -41,6 +42,7 @@ declare_lint_group! { self :: no_console :: NoConsole , self :: no_done_callback :: NoDoneCallback , self :: no_duplicate_else_if :: NoDuplicateElseIf , + self :: no_dynamic_namespace_import_access :: NoDynamicNamespaceImportAccess , self :: no_evolving_types :: NoEvolvingTypes , self :: no_exported_imports :: NoExportedImports , self :: no_label_without_control :: NoLabelWithoutControl , diff --git a/crates/biome_js_analyze/src/lint/nursery/no_dynamic_namespace_import_access.rs b/crates/biome_js_analyze/src/lint/nursery/no_dynamic_namespace_import_access.rs new file mode 100644 index 000000000000..99ee682ce410 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_dynamic_namespace_import_access.rs @@ -0,0 +1,119 @@ +use biome_analyze::{context::RuleContext, declare_rule, Rule, RuleDiagnostic}; +use biome_console::markup; +use biome_js_semantic::ReferencesExtensions; +use biome_js_syntax::{JsComputedMemberExpression, JsImportNamespaceClause}; +use biome_rowan::{AstNode, TextRange}; + +use crate::services::semantic::Semantic; + +declare_rule! { + /// Disallow accessing namespace imports dynamically. + /// + /// Accessing namespace imports dynamically can prevent efficient tree shaking and increase bundle size. + /// This happens because the bundler cannot determine which parts of the namespace are used at compile time, + /// so it must include the entire namespace in the bundle. + /// + /// Instead, consider using named imports or if that is not possible + /// access the namespaced import properties statically. + /// + /// If you want to completely disallow namespace imports, consider using the [noNamespaceImport](https://biomejs.dev/linter/rules/no-namespace-import/) rule. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// import * as foo from "foo" + /// foo["bar"] + /// ``` + /// + /// ```js,expect_diagnostic + /// import * as foo from "foo" + /// const key = "bar" + /// foo[key] + /// ``` + /// + /// ### Valid + /// + /// ```js + /// import * as foo from "foo" + /// foo.bar + /// ``` + /// + /// ```js + /// import { bar } from "foo" + /// bar + /// ``` + /// + /// ```js + /// import messages from "i18n" + /// const knownMessagesMap = { + /// hello: messages.hello, + /// goodbye: messages.goodbye + /// } + /// + /// const dynamicKey = "hello" + /// knownMessagesMap[dynamicKey] + /// ``` + /// + pub NoDynamicNamespaceImportAccess { + version: "next", + name: "noDynamicNamespaceImportAccess", + language: "js", + recommended: false, + } +} + +impl Rule for NoDynamicNamespaceImportAccess { + type Query = Semantic; + type State = TextRange; + type Signals = Vec; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + find_dynamic_namespace_import_accesses(ctx).map_or(vec![], |range| range) + } + + fn diagnostic(_: &RuleContext, range: &Self::State) -> Option { + let diagnostic = RuleDiagnostic::new( + rule_category!(), + range, + markup! { + "Avoid accessing namespace imports dynamically, it can prevent efficient tree shaking and increase bundle size." + }, + ) + .note(markup! { + "Prefer static property access or use named imports instead." + }); + + Some(diagnostic) + } +} + +fn find_dynamic_namespace_import_accesses( + ctx: &RuleContext, +) -> Option> { + let import_namespace_clause: &JsImportNamespaceClause = ctx.query(); + + // Allow type import e.g. `import type * as foo from "foo"` + if import_namespace_clause.type_token().is_some() { + return None; + } + + let specifier = import_namespace_clause.namespace_specifier().ok()?; + let any_binding = specifier.local_name().ok()?; + let identifier = any_binding.as_js_identifier_binding()?; + let reads = identifier.all_reads(ctx.model()); + + let ranges = reads + .into_iter() + .filter_map(|read| { + let syntax = read.syntax().parent()?.parent()?; + let node = JsComputedMemberExpression::cast(syntax)?; + + Some(node.range()) + }) + .collect::>(); + + Some(ranges) +} diff --git a/crates/biome_js_analyze/src/options.rs b/crates/biome_js_analyze/src/options.rs index cbe20c1ee3e4..6c86ed8b82c0 100644 --- a/crates/biome_js_analyze/src/options.rs +++ b/crates/biome_js_analyze/src/options.rs @@ -70,6 +70,7 @@ pub type NoDuplicateJsxProps = pub type NoDuplicateObjectKeys = < lint :: suspicious :: no_duplicate_object_keys :: NoDuplicateObjectKeys as biome_analyze :: Rule > :: Options ; pub type NoDuplicateParameters = < lint :: suspicious :: no_duplicate_parameters :: NoDuplicateParameters as biome_analyze :: Rule > :: Options ; pub type NoDuplicateTestHooks = < lint :: suspicious :: no_duplicate_test_hooks :: NoDuplicateTestHooks as biome_analyze :: Rule > :: Options ; +pub type NoDynamicNamespaceImportAccess = < lint :: nursery :: no_dynamic_namespace_import_access :: NoDynamicNamespaceImportAccess as biome_analyze :: Rule > :: Options ; pub type NoEmptyBlockStatements = < lint :: suspicious :: no_empty_block_statements :: NoEmptyBlockStatements as biome_analyze :: Rule > :: Options ; pub type NoEmptyCharacterClassInRegex = < lint :: correctness :: no_empty_character_class_in_regex :: NoEmptyCharacterClassInRegex as biome_analyze :: Rule > :: Options ; pub type NoEmptyInterface = diff --git a/crates/biome_js_analyze/tests/specs/nursery/noDynamicNamespaceImportAccess/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noDynamicNamespaceImportAccess/invalid.js new file mode 100644 index 000000000000..9584102da05e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noDynamicNamespaceImportAccess/invalid.js @@ -0,0 +1,6 @@ +import * as foo from "foo" + +foo["bar"] +foo[1] +const key = "bar" +foo[key] \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noDynamicNamespaceImportAccess/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noDynamicNamespaceImportAccess/invalid.js.snap new file mode 100644 index 000000000000..7e05ef3d0ab2 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noDynamicNamespaceImportAccess/invalid.js.snap @@ -0,0 +1,62 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```jsx +import * as foo from "foo" + +foo["bar"] +foo[1] +const key = "bar" +foo[key] +``` + +# Diagnostics +``` +invalid.js:3:1 lint/nursery/noDynamicNamespaceImportAccess ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid accessing namespace imports dynamically, it can prevent efficient tree shaking and increase bundle size. + + 1 │ import * as foo from "foo" + 2 │ + > 3 │ foo["bar"] + │ ^^^^^^^^^^ + 4 │ foo[1] + 5 │ const key = "bar" + + i Prefer static property access or use named imports instead. + + +``` + +``` +invalid.js:4:1 lint/nursery/noDynamicNamespaceImportAccess ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid accessing namespace imports dynamically, it can prevent efficient tree shaking and increase bundle size. + + 3 │ foo["bar"] + > 4 │ foo[1] + │ ^^^^^^ + 5 │ const key = "bar" + 6 │ foo[key] + + i Prefer static property access or use named imports instead. + + +``` + +``` +invalid.js:6:1 lint/nursery/noDynamicNamespaceImportAccess ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Avoid accessing namespace imports dynamically, it can prevent efficient tree shaking and increase bundle size. + + 4 │ foo[1] + 5 │ const key = "bar" + > 6 │ foo[key] + │ ^^^^^^^^ + + i Prefer static property access or use named imports instead. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noDynamicNamespaceImportAccess/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noDynamicNamespaceImportAccess/valid.js new file mode 100644 index 000000000000..cbcb1e1cd1a8 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noDynamicNamespaceImportAccess/valid.js @@ -0,0 +1,5 @@ +import * as foo from "foo" + +foo.bar +const key = "bar" +foo[key] = "bar" \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noDynamicNamespaceImportAccess/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noDynamicNamespaceImportAccess/valid.js.snap new file mode 100644 index 000000000000..b646ef8de377 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noDynamicNamespaceImportAccess/valid.js.snap @@ -0,0 +1,12 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```jsx +import * as foo from "foo" + +foo.bar +const key = "bar" +foo[key] = "bar" +``` diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index bfce30a7b454..02da8a93e69c 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1057,6 +1057,10 @@ export interface Nursery { * Disallow duplicate selectors within keyframe blocks. */ noDuplicateSelectorsKeyframeBlock?: RuleConfiguration_for_Null; + /** + * Disallow accessing namespace imports dynamically. + */ + noDynamicNamespaceImportAccess?: RuleConfiguration_for_Null; /** * Disallow CSS empty blocks. */ @@ -2449,6 +2453,7 @@ export type Category = | "lint/nursery/noDuplicateFontNames" | "lint/nursery/noDuplicateJsonKeys" | "lint/nursery/noDuplicateSelectorsKeyframeBlock" + | "lint/nursery/noDynamicNamespaceImportAccess" | "lint/nursery/noEmptyBlock" | "lint/nursery/noEvolvingTypes" | "lint/nursery/noExportedImports" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index f09b44a852be..daf1fbed91e5 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1786,6 +1786,13 @@ { "type": "null" } ] }, + "noDynamicNamespaceImportAccess": { + "description": "Disallow accessing namespace imports dynamically.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noEmptyBlock": { "description": "Disallow CSS empty blocks.", "anyOf": [