diff --git a/CHANGELOG.md b/CHANGELOG.md index 9638bfd00a9f..624aae3eab47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -337,6 +337,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b Contributed by @Conaclos - Add [nursery/useConsistentMemberAccessibility](https://biomejs.dev/linter/rules/use-consistent-member-accessibility/). Contributed by @seitarof +- Add [nursery/noCommonJs](https://biomejs.dev/linter/rules/no-common-js/). Contributed by @minht11 #### Enhancements diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs index acbcf4c7d733..cb084537b1f3 100644 --- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs +++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs @@ -175,6 +175,14 @@ pub(crate) fn migrate_eslint_any_rule( let rule = group.no_redeclare.get_or_insert(Default::default()); rule.set_level(rule_severity.into()); } + "@typescript-eslint/no-require-imports" => { + if !options.include_nursery { + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group.no_common_js.get_or_insert(Default::default()); + rule.set_level(rule_severity.into()); + } "@typescript-eslint/no-restricted-imports" => { if !options.include_nursery { return false; @@ -408,6 +416,14 @@ pub(crate) fn migrate_eslint_any_rule( .get_or_insert(Default::default()); rule.set_level(rule_severity.into()); } + "import/no-commonjs" => { + if !options.include_nursery { + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group.no_common_js.get_or_insert(Default::default()); + rule.set_level(rule_severity.into()); + } "import/no-default-export" => { let group = rules.style.get_or_insert_with(Default::default); let rule = group.no_default_export.get_or_insert(Default::default()); diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index c1130f987ae0..41d368c95f63 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -2869,6 +2869,9 @@ pub struct Nursery { #[doc = r" It enables ALL rules for this group."] #[serde(skip_serializing_if = "Option::is_none")] pub all: Option, + #[doc = "Disallow use of CommonJs module system in favor of ESM style imports."] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_common_js: Option>, #[doc = "Disallow the use of console."] #[serde(skip_serializing_if = "Option::is_none")] pub no_console: Option>, @@ -3130,6 +3133,7 @@ impl DeserializableValidator for Nursery { impl Nursery { const GROUP_NAME: &'static str = "nursery"; pub(crate) const GROUP_RULES: &'static [&'static str] = &[ + "noCommonJs", "noConsole", "noDoneCallback", "noDuplicateAtImportRules", @@ -3225,33 +3229,33 @@ impl Nursery { "useStrictMode", ]; const RECOMMENDED_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3]), 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[9]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -3318,6 +3322,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[63]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[64]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -3334,650 +3339,660 @@ impl Nursery { } pub(crate) fn get_enabled_rules(&self) -> FxHashSet> { let mut index_set = FxHashSet::default(); - if let Some(rule) = self.no_console.as_ref() { + if let Some(rule) = self.no_common_js.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0])); } } - if let Some(rule) = self.no_done_callback.as_ref() { + if let Some(rule) = self.no_console.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1])); } } - if let Some(rule) = self.no_duplicate_at_import_rules.as_ref() { + if let Some(rule) = self.no_done_callback.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2])); } } - if let Some(rule) = self.no_duplicate_custom_properties.as_ref() { + if let Some(rule) = self.no_duplicate_at_import_rules.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } } - if let Some(rule) = self.no_duplicate_else_if.as_ref() { + if let Some(rule) = self.no_duplicate_custom_properties.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_duplicate_font_names.as_ref() { + if let Some(rule) = self.no_duplicate_else_if.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_duplicate_selectors_keyframe_block.as_ref() { + if let Some(rule) = self.no_duplicate_font_names.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_duplicated_fields.as_ref() { + if let Some(rule) = self.no_duplicate_selectors_keyframe_block.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_dynamic_namespace_import_access.as_ref() { + if let Some(rule) = self.no_duplicated_fields.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - 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[9])); } } - if let Some(rule) = self.no_enum.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[10])); } } - if let Some(rule) = self.no_evolving_types.as_ref() { + if let Some(rule) = self.no_enum.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - 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[12])); } } - 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[13])); } } - 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[14])); } } - if let Some(rule) = self.no_invalid_grid_areas.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[15])); } } - if let Some(rule) = self.no_invalid_position_at_import_rule.as_ref() { + if let Some(rule) = self.no_invalid_grid_areas.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.no_irregular_whitespace.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[17])); } } - if let Some(rule) = self.no_label_without_control.as_ref() { + if let Some(rule) = self.no_irregular_whitespace.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } } - 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[19])); } } - 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[20])); } } - 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[21])); } } - if let Some(rule) = self.no_restricted_types.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[22])); } } - if let Some(rule) = self.no_secrets.as_ref() { + if let Some(rule) = self.no_restricted_types.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.no_shorthand_property_overrides.as_ref() { + if let Some(rule) = self.no_secrets.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.no_static_element_interactions.as_ref() { + if let Some(rule) = self.no_shorthand_property_overrides.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.no_substr.as_ref() { + if let Some(rule) = self.no_static_element_interactions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - 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[27])); } } - 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[28])); } } - 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[29])); } } - 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[30])); } } - if let Some(rule) = self.no_unknown_pseudo_class.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[31])); } } - if let Some(rule) = self.no_unknown_pseudo_element.as_ref() { + if let Some(rule) = self.no_unknown_pseudo_class.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.no_unknown_unit.as_ref() { + if let Some(rule) = self.no_unknown_pseudo_element.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - 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[34])); } } - 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[35])); } } - if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { + if let Some(rule) = self.no_unused_function_parameters.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.no_useless_string_concat.as_ref() { + if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - 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[38])); } } - if let Some(rule) = self.no_value_at_rule.as_ref() { + if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.no_yoda_expression.as_ref() { + if let Some(rule) = self.no_value_at_rule.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - 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[41])); } } - if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() { + if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { + if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_consistent_curly_braces.as_ref() { + if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_consistent_member_accessibility.as_ref() { + if let Some(rule) = self.use_consistent_curly_braces.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } - if let Some(rule) = self.use_date_now.as_ref() { + if let Some(rule) = self.use_consistent_member_accessibility.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } } - 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[47])); } } - 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[48])); } } - 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[49])); } } - 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[50])); } } - 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[51])); } } - 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[52])); } } - 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[53])); } } - 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[54])); } } - 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[55])); } } - 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[56])); } } - 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[57])); } } - if let Some(rule) = self.use_strict_mode.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); } } - if let Some(rule) = self.use_throw_new_error.as_ref() { + if let Some(rule) = self.use_strict_mode.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); } } - 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[60])); } } - 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[61])); } } - if let Some(rule) = self.use_trim_start_end.as_ref() { + if let Some(rule) = self.use_top_level_regex.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62])); } } - if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if let Some(rule) = self.use_trim_start_end.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[63])); } } + 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[64])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> FxHashSet> { let mut index_set = FxHashSet::default(); - if let Some(rule) = self.no_console.as_ref() { + if let Some(rule) = self.no_common_js.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0])); } } - if let Some(rule) = self.no_done_callback.as_ref() { + if let Some(rule) = self.no_console.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1])); } } - if let Some(rule) = self.no_duplicate_at_import_rules.as_ref() { + if let Some(rule) = self.no_done_callback.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2])); } } - if let Some(rule) = self.no_duplicate_custom_properties.as_ref() { + if let Some(rule) = self.no_duplicate_at_import_rules.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } } - if let Some(rule) = self.no_duplicate_else_if.as_ref() { + if let Some(rule) = self.no_duplicate_custom_properties.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_duplicate_font_names.as_ref() { + if let Some(rule) = self.no_duplicate_else_if.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_duplicate_selectors_keyframe_block.as_ref() { + if let Some(rule) = self.no_duplicate_font_names.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_duplicated_fields.as_ref() { + if let Some(rule) = self.no_duplicate_selectors_keyframe_block.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_dynamic_namespace_import_access.as_ref() { + if let Some(rule) = self.no_duplicated_fields.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - 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[9])); } } - if let Some(rule) = self.no_enum.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[10])); } } - if let Some(rule) = self.no_evolving_types.as_ref() { + if let Some(rule) = self.no_enum.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - 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[12])); } } - 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[13])); } } - 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[14])); } } - if let Some(rule) = self.no_invalid_grid_areas.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[15])); } } - if let Some(rule) = self.no_invalid_position_at_import_rule.as_ref() { + if let Some(rule) = self.no_invalid_grid_areas.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.no_irregular_whitespace.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[17])); } } - if let Some(rule) = self.no_label_without_control.as_ref() { + if let Some(rule) = self.no_irregular_whitespace.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } } - 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[19])); } } - 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[20])); } } - 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[21])); } } - if let Some(rule) = self.no_restricted_types.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[22])); } } - if let Some(rule) = self.no_secrets.as_ref() { + if let Some(rule) = self.no_restricted_types.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.no_shorthand_property_overrides.as_ref() { + if let Some(rule) = self.no_secrets.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.no_static_element_interactions.as_ref() { + if let Some(rule) = self.no_shorthand_property_overrides.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.no_substr.as_ref() { + if let Some(rule) = self.no_static_element_interactions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - 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[27])); } } - 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[28])); } } - 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[29])); } } - 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[30])); } } - if let Some(rule) = self.no_unknown_pseudo_class.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[31])); } } - if let Some(rule) = self.no_unknown_pseudo_element.as_ref() { + if let Some(rule) = self.no_unknown_pseudo_class.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.no_unknown_unit.as_ref() { + if let Some(rule) = self.no_unknown_pseudo_element.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - 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[34])); } } - 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[35])); } } - if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { + if let Some(rule) = self.no_unused_function_parameters.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.no_useless_string_concat.as_ref() { + if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - 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[38])); } } - if let Some(rule) = self.no_value_at_rule.as_ref() { + if let Some(rule) = self.no_useless_undefined_initialization.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.no_yoda_expression.as_ref() { + if let Some(rule) = self.no_value_at_rule.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - 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[41])); } } - if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() { + if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { + if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_consistent_curly_braces.as_ref() { + if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_consistent_member_accessibility.as_ref() { + if let Some(rule) = self.use_consistent_curly_braces.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } - if let Some(rule) = self.use_date_now.as_ref() { + if let Some(rule) = self.use_consistent_member_accessibility.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); } } - 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[47])); } } - 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[48])); } } - 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[49])); } } - 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[50])); } } - 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[51])); } } - 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[52])); } } - 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[53])); } } - 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[54])); } } - 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[55])); } } - 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[56])); } } - 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[57])); } } - if let Some(rule) = self.use_strict_mode.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58])); } } - if let Some(rule) = self.use_throw_new_error.as_ref() { + if let Some(rule) = self.use_strict_mode.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59])); } } - 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[60])); } } - 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[61])); } } - if let Some(rule) = self.use_trim_start_end.as_ref() { + if let Some(rule) = self.use_top_level_regex.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62])); } } - if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if let Some(rule) = self.use_trim_start_end.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[63])); } } + 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[64])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -4014,6 +4029,10 @@ impl Nursery { rule_name: &str, ) -> Option<(RulePlainConfiguration, Option)> { match rule_name { + "noCommonJs" => self + .no_common_js + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noConsole" => self .no_console .as_ref() diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 5ea8b10e1fa6..46b25ae2547b 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -115,6 +115,7 @@ define_categories! { "lint/correctness/useYield": "https://biomejs.dev/linter/rules/use-yield", "lint/nursery/colorNoInvalidHex": "https://biomejs.dev/linter/rules/color-no-invalid-hex", "lint/nursery/noColorInvalidHex": "https://biomejs.dev/linter/rules/no-color-invalid-hex", + "lint/nursery/noCommonJs": "https://biomejs.dev/linter/rules/no-common-js", "lint/nursery/noConsole": "https://biomejs.dev/linter/rules/no-console", "lint/nursery/noDoneCallback": "https://biomejs.dev/linter/rules/no-done-callback", "lint/nursery/noDuplicateAtImportRules": "https://biomejs.dev/linter/rules/no-duplicate-at-import-rules", diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs index 722be8b4782a..77383817caa2 100644 --- a/crates/biome_js_analyze/src/lint/nursery.rs +++ b/crates/biome_js_analyze/src/lint/nursery.rs @@ -2,6 +2,7 @@ use biome_analyze::declare_lint_group; +pub mod no_common_js; pub mod no_console; pub mod no_done_callback; pub mod no_duplicate_else_if; @@ -50,6 +51,7 @@ declare_lint_group! { pub Nursery { name : "nursery" , rules : [ + self :: no_common_js :: NoCommonJs , self :: no_console :: NoConsole , self :: no_done_callback :: NoDoneCallback , self :: no_duplicate_else_if :: NoDuplicateElseIf , diff --git a/crates/biome_js_analyze/src/lint/nursery/no_common_js.rs b/crates/biome_js_analyze/src/lint/nursery/no_common_js.rs new file mode 100644 index 000000000000..a6a2ee742598 --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_common_js.rs @@ -0,0 +1,153 @@ +use biome_analyze::{context::RuleContext, declare_lint_rule, Rule, RuleDiagnostic, RuleSource}; +use biome_console::markup; +use biome_js_syntax::{ + global_identifier, JsCallExpression, JsReferenceIdentifier, JsStaticMemberAssignment, +}; +use biome_rowan::{declare_node_union, AstNode}; + +use crate::services::semantic::Semantic; + +declare_lint_rule! { + /// Disallow use of CommonJs module system in favor of ESM style imports. + /// + /// ESM-style `import`s are modern alternative to CommonJS `require` imports. Supported by all modern browsers and Node.js versions. + /// Tooling can more easily statically analyze and tree-shake ESM `import`s compared to CommonJs. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// require('node:fs'); + /// ``` + /// ```js,expect_diagnostic + /// module.exports = { a: 'b' } + /// ``` + /// ```js,expect_diagnostic + /// exports.a = 'b'; + /// ``` + /// + /// ### Valid + /// + /// ```js + /// import fs from 'node:fs'; + /// ``` + /// ```js + /// import('node:fs') + /// ``` + /// ```js + /// export const a = 'b'; + /// ``` + /// ```js + /// export default { a: 'b' }; + /// ``` + /// + /// ## Caveats + /// + /// Rule is automatically disabled inside `.cjs` and `.cts` files, because they are explicitly CommonJs files. + /// + /// This rule could be helpful if you are migrating from CommonJs to ESM, + /// but if you wish to continue using CommonJs, you can safely disable it. + /// + pub NoCommonJs { + version: "1.9.0", + name: "noCommonJs", + language: "js", + sources: &[ + RuleSource::EslintTypeScript("no-require-imports"), + RuleSource::EslintImport("no-commonjs"), + ], + recommended: false, + } +} + +impl Rule for NoCommonJs { + type Query = Semantic; + type State = bool; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let file_ext = ctx.file_path().extension(); + // cjs and cts files can only use CommonJs modules + if file_ext.is_some_and(|file_ext| matches!(file_ext.as_encoded_bytes(), b"cjs" | b"cts")) { + return None; + } + + let expression = ctx.query(); + let (reference, is_export) = match expression { + CommonJsImportExport::JsCallExpression(node) => { + Some((is_require_call_expression(node)?, false)) + } + CommonJsImportExport::JsStaticMemberAssignment(node) => { + Some((is_common_js_exports(node)?, true)) + } + }?; + + if ctx.model().binding(&reference).is_none() { + return Some(is_export); + } + + None + } + + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { + let node = ctx.query(); + let (es_name, common_js_name) = match state { + true => ("export", "module.exports"), + false => ("import", "require"), + }; + + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Use ESM "{es_name}"s instead of "{common_js_name}"." + }, + ) + .note(markup! { + "ESM-style "{es_name}" statements are more easily statically analyzable and tree-shakable compared to CommonJs "{common_js_name}"." + }), + ) + } +} + +declare_node_union! { + pub CommonJsImportExport = JsStaticMemberAssignment | JsCallExpression +} + +fn is_require_call_expression(node: &JsCallExpression) -> Option { + let callee = node.callee().ok()?; + let (reference, name) = global_identifier(&callee.omit_parentheses())?; + + if name.text() == "require" { + return Some(reference); + } + + None +} + +fn is_common_js_exports(node: &JsStaticMemberAssignment) -> Option { + let object = node.object().ok()?; + let (reference, name) = global_identifier(&object.omit_parentheses())?; + let object_name = name.text(); + + // exports.* + if object_name == "exports" { + return Some(reference); + } + + // module.exports.* + if object_name != "module" { + return None; + } + + let value_token = node.member().ok()?.value_token().ok()?; + let member_name = value_token.text_trimmed(); + if member_name == "exports" { + return Some(reference); + } + + None +} diff --git a/crates/biome_js_analyze/src/options.rs b/crates/biome_js_analyze/src/options.rs index 17aa70d613f8..a465eb7395ef 100644 --- a/crates/biome_js_analyze/src/options.rs +++ b/crates/biome_js_analyze/src/options.rs @@ -30,6 +30,7 @@ pub type NoCommaOperator = ::Options; pub type NoCommentText = ::Options; +pub type NoCommonJs = ::Options; pub type NoCompareNegZero = ::Options; pub type NoConfusingLabels = diff --git a/crates/biome_js_analyze/tests/specs/nursery/noCommonJs/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noCommonJs/invalid.js new file mode 100644 index 000000000000..75ef8839ee3e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noCommonJs/invalid.js @@ -0,0 +1,8 @@ +require('node:fs') + +if (true) { + require('node:fs') +} + +module.exports = 'path' +exports.path = 'path' diff --git a/crates/biome_js_analyze/tests/specs/nursery/noCommonJs/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noCommonJs/invalid.js.snap new file mode 100644 index 000000000000..c3128609ac5d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noCommonJs/invalid.js.snap @@ -0,0 +1,80 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```jsx +require('node:fs') + +if (true) { + require('node:fs') +} + +module.exports = 'path' +exports.path = 'path' + +``` + +# Diagnostics +``` +invalid.js:1:1 lint/nursery/noCommonJs ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Use ESM imports instead of require. + + > 1 │ require('node:fs') + │ ^^^^^^^^^^^^^^^^^^ + 2 │ + 3 │ if (true) { + + i ESM-style import statements are more easily statically analyzable and tree-shakable compared to CommonJs require. + + +``` + +``` +invalid.js:4:5 lint/nursery/noCommonJs ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Use ESM imports instead of require. + + 3 │ if (true) { + > 4 │ require('node:fs') + │ ^^^^^^^^^^^^^^^^^^ + 5 │ } + 6 │ + + i ESM-style import statements are more easily statically analyzable and tree-shakable compared to CommonJs require. + + +``` + +``` +invalid.js:7:1 lint/nursery/noCommonJs ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Use ESM exports instead of module.exports. + + 5 │ } + 6 │ + > 7 │ module.exports = 'path' + │ ^^^^^^^^^^^^^^ + 8 │ exports.path = 'path' + 9 │ + + i ESM-style export statements are more easily statically analyzable and tree-shakable compared to CommonJs module.exports. + + +``` + +``` +invalid.js:8:1 lint/nursery/noCommonJs ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Use ESM exports instead of module.exports. + + 7 │ module.exports = 'path' + > 8 │ exports.path = 'path' + │ ^^^^^^^^^^^^ + 9 │ + + i ESM-style export statements are more easily statically analyzable and tree-shakable compared to CommonJs module.exports. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noCommonJs/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noCommonJs/valid.js new file mode 100644 index 000000000000..93afe32d81b5 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noCommonJs/valid.js @@ -0,0 +1,14 @@ +import 'node:fs' +import { join } from 'node:path' + +import('node:fs/promises') + +const require = () => {} + +require('node:fs') + +const module = {} +module.exports = 'path' + +const exports = {} +exports.path = 'path' \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noCommonJs/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noCommonJs/valid.js.snap new file mode 100644 index 000000000000..ad9eed4fbcbd --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noCommonJs/valid.js.snap @@ -0,0 +1,21 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```jsx +import 'node:fs' +import { join } from 'node:path' + +import('node:fs/promises') + +const require = () => {} + +require('node:fs') + +const module = {} +module.exports = 'path' + +const exports = {} +exports.path = 'path' +``` diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 69a4b0bf5789..51764e741798 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1147,6 +1147,10 @@ export interface Nursery { * It enables ALL rules for this group. */ all?: boolean; + /** + * Disallow use of CommonJs module system in favor of ESM style imports. + */ + noCommonJs?: RuleConfiguration_for_Null; /** * Disallow the use of console. */ @@ -2748,6 +2752,7 @@ export type Category = | "lint/correctness/useYield" | "lint/nursery/colorNoInvalidHex" | "lint/nursery/noColorInvalidHex" + | "lint/nursery/noCommonJs" | "lint/nursery/noConsole" | "lint/nursery/noDoneCallback" | "lint/nursery/noDuplicateAtImportRules" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 03ec20f40887..6293a65a63c2 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1937,6 +1937,13 @@ "description": "It enables ALL rules for this group.", "type": ["boolean", "null"] }, + "noCommonJs": { + "description": "Disallow use of CommonJs module system in favor of ESM style imports.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noConsole": { "description": "Disallow the use of console.", "anyOf": [