From 83565f9c87ff77ae9444ba4ef6f94d6f336367fd Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Thu, 6 Jun 2024 16:27:58 +0200 Subject: [PATCH] feat(noExportedImports): add lint rule --- CHANGELOG.md | 4 + .../biome_configuration/src/linter/rules.rs | 161 ++++++++++-------- .../src/categories.rs | 1 + .../src/lint/correctness/no_nodejs_modules.rs | 4 +- crates/biome_js_analyze/src/lint/nursery.rs | 2 + .../src/lint/nursery/no_exported_imports.rs | 85 +++++++++ .../src/lint/nursery/no_restricted_imports.rs | 4 +- .../nursery/no_undeclared_dependencies.rs | 4 +- .../src/lint/nursery/use_import_extensions.rs | 6 +- .../src/lint/style/use_node_assert_strict.rs | 4 +- .../lint/style/use_nodejs_import_protocol.rs | 4 +- .../src/lint/suspicious/no_import_assign.rs | 25 ++- crates/biome_js_analyze/src/options.rs | 2 + .../nursery/noExportedImports/invalid.js | 8 + .../nursery/noExportedImports/invalid.js.snap | 64 +++++++ .../specs/nursery/noExportedImports/valid.js | 3 + .../nursery/noExportedImports/valid.js.snap | 10 ++ crates/biome_js_syntax/src/import_ext.rs | 59 ++++--- .../@biomejs/backend-jsonrpc/src/workspace.ts | 5 + .../@biomejs/biome/configuration_schema.json | 7 + 20 files changed, 343 insertions(+), 119 deletions(-) create mode 100644 crates/biome_js_analyze/src/lint/nursery/no_exported_imports.rs create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noExportedImports/invalid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noExportedImports/invalid.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noExportedImports/valid.js create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noExportedImports/valid.js.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 93e05df209ad..63cda5fa9498 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,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 - The `no-empty-block` css lint rule now treats empty blocks containing comments as valid ones. Contributed by @Sec-ant diff --git a/crates/biome_configuration/src/linter/rules.rs b/crates/biome_configuration/src/linter/rules.rs index 936bdfbc9418..64511161cc9c 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>, @@ -2969,6 +2972,7 @@ impl Nursery { "noDuplicateSelectorsKeyframeBlock", "noEmptyBlock", "noEvolvingTypes", + "noExportedImports", "noImportantInKeyframe", "noInvalidPositionAtImportRule", "noLabelWithoutControl", @@ -3030,16 +3034,16 @@ 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[16]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]), + 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[21]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -3084,6 +3088,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -3145,171 +3150,176 @@ 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_position_at_import_rule.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_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[11])); } } - 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[12])); } } - 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[13])); } } - 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[14])); } } - if let Some(rule) = self.no_undeclared_dependencies.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[15])); } } - 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[16])); } } - 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[17])); } } - 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[18])); } } - if let Some(rule) = self.no_unknown_selector_pseudo_element.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[19])); } } - 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[20])); } } - 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[21])); } } - 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[22])); } } - 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[23])); } } - 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[24])); } } - 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[25])); } } - 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[26])); } } - 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[27])); } } - if let Some(rule) = self.use_date_now.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[28])); } } - 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[29])); } } - 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[30])); } } - 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[31])); } } - 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[32])); } } - 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[33])); } } - 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[34])); } } - 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[35])); } } - 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[36])); } } - 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[37])); } } - 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[38])); } } - 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[39])); } } - 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[40])); } } - 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[41])); } } + 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[42])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> FxHashSet> { @@ -3359,171 +3369,176 @@ 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_position_at_import_rule.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_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[11])); } } - 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[12])); } } - 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[13])); } } - 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[14])); } } - if let Some(rule) = self.no_undeclared_dependencies.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[15])); } } - 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[16])); } } - 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[17])); } } - 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[18])); } } - if let Some(rule) = self.no_unknown_selector_pseudo_element.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[19])); } } - 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[20])); } } - 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[21])); } } - 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[22])); } } - 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[23])); } } - 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[24])); } } - 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[25])); } } - 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[26])); } } - 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[27])); } } - if let Some(rule) = self.use_date_now.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[28])); } } - 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[29])); } } - 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[30])); } } - 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[31])); } } - 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[32])); } } - 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[33])); } } - 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[34])); } } - 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[35])); } } - 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[36])); } } - 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[37])); } } - 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[38])); } } - 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[39])); } } - 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[40])); } } - 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[41])); } } + 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[42])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -3596,6 +3611,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 6e64584aef95..891fafbdafb9 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/noInvalidPositionAtImportRule": "https://biomejs.dev/linter/rules/no-invalid-position-at-import-rule", "lint/nursery/noLabelWithoutControl": "https://biomejs.dev/linter/rules/no-label-without-control", 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 1e471549b22c..f79084942d42 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; @@ -39,6 +40,7 @@ declare_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 146f573086a9..383538336e67 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! { @@ -38,7 +38,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 8aab5991f57e..b274d9905c6e 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; @@ -80,7 +80,7 @@ declare_rule! { } impl Rule for UseImportExtensions { - type Query = Ast; + type Query = Ast; type State = UseImportExtensionsState; type Signals = Option; type Options = (); @@ -142,7 +142,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 2c49f4048603..66a094f8b1b7 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 c45980171337..5f1d6f7dec5c 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1009,6 +1009,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 */ @@ -2332,6 +2336,7 @@ export type Category = | "lint/nursery/noDuplicateSelectorsKeyframeBlock" | "lint/nursery/noEmptyBlock" | "lint/nursery/noEvolvingTypes" + | "lint/nursery/noExportedImports" | "lint/nursery/noImportantInKeyframe" | "lint/nursery/noInvalidPositionAtImportRule" | "lint/nursery/noLabelWithoutControl" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 862c46972ecc..5a0cc5aaaec3 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1730,6 +1730,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": [