Skip to content

Commit

Permalink
feat(lint): add rule useComponentExportOnlyModules (#3576)
Browse files Browse the repository at this point in the history
Co-authored-by: Yusuke Abe <[email protected]>
Co-authored-by: Emanuele Stoppa <[email protected]>
  • Loading branch information
3 people committed Sep 19, 2024
1 parent 638c250 commit 00760d4
Show file tree
Hide file tree
Showing 79 changed files with 1,805 additions and 61 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b
#### New features

- Add [nursery/noMissingVarFunction](https://biomejs.dev/linter/rules/no-missing-var-function). Contributed by @michellocana
- Add [nursery/useComponentExportOnlyModules]((https://biomejs.dev/linter/rules/use-component-export-only-modules). Use this rule in React projects to enforce a code styling that fits React Refresh. Contributed by @GunseiKPaseri

#### Bug fixes

Expand Down
6 changes: 6 additions & 0 deletions crates/biome_analyze/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ pub enum RuleSource {
EslintReact(&'static str),
/// Rules from [Eslint Plugin React Hooks](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/README.md)
EslintReactHooks(&'static str),
/// Rules from [Eslint Plugin React Refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh)
EslintReactRefresh(&'static str),
/// Rules from [Eslint Plugin Solid](https://github.com/solidjs-community/eslint-plugin-solid)
EslintSolid(&'static str),
/// Rules from [Eslint Plugin Sonar](https://github.com/SonarSource/eslint-plugin-sonarjs)
Expand Down Expand Up @@ -146,6 +148,7 @@ impl std::fmt::Display for RuleSource {
Self::EslintJsxA11y(_) => write!(f, "eslint-plugin-jsx-a11y"),
Self::EslintReact(_) => write!(f, "eslint-plugin-react"),
Self::EslintReactHooks(_) => write!(f, "eslint-plugin-react-hooks"),
Self::EslintReactRefresh(_) => write!(f, "eslint-plugin-react-refresh"),
Self::EslintSolid(_) => write!(f, "eslint-plugin-solid"),
Self::EslintSonarJs(_) => write!(f, "eslint-plugin-sonarjs"),
Self::EslintStylistic(_) => write!(f, "eslint-plugin-stylistic"),
Expand Down Expand Up @@ -194,6 +197,7 @@ impl RuleSource {
| Self::EslintJsxA11y(rule_name)
| Self::EslintReact(rule_name)
| Self::EslintReactHooks(rule_name)
| Self::EslintReactRefresh(rule_name)
| Self::EslintTypeScript(rule_name)
| Self::EslintSolid(rule_name)
| Self::EslintSonarJs(rule_name)
Expand All @@ -217,6 +221,7 @@ impl RuleSource {
Self::EslintJsxA11y(rule_name) => format!("jsx-a11y/{rule_name}"),
Self::EslintReact(rule_name) => format!("react/{rule_name}"),
Self::EslintReactHooks(rule_name) => format!("react-hooks/{rule_name}"),
Self::EslintReactRefresh(rule_name) => format!("react-refresh/{rule_name}"),
Self::EslintTypeScript(rule_name) => format!("@typescript-eslint/{rule_name}"),
Self::EslintSolid(rule_name) => format!("solidjs/{rule_name}"),
Self::EslintSonarJs(rule_name) => format!("sonarjs/{rule_name}"),
Expand All @@ -241,6 +246,7 @@ impl RuleSource {
Self::EslintJsxA11y(rule_name) => format!("https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/{rule_name}.md"),
Self::EslintReact(rule_name) => format!("https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/{rule_name}.md"),
Self::EslintReactHooks(_) => "https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/README.md".to_string(),
Self::EslintReactRefresh(_) => "https://github.com/ArnaudBarre/eslint-plugin-react-refresh".to_string(),
Self::EslintTypeScript(rule_name) => format!("https://typescript-eslint.io/rules/{rule_name}"),
Self::EslintSolid(rule_name) => format!("https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/{rule_name}.md"),
Self::EslintSonarJs(rule_name) => format!("https://github.com/SonarSource/eslint-plugin-sonarjs/blob/HEAD/docs/rules/{rule_name}.md"),
Expand Down
14 changes: 14 additions & 0 deletions crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 38 additions & 18 deletions crates/biome_configuration/src/analyzer/linter/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3349,6 +3349,10 @@ pub struct Nursery {
#[serde(skip_serializing_if = "Option::is_none")]
pub use_aria_props_supported_by_role:
Option<RuleConfiguration<biome_js_analyze::options::UseAriaPropsSupportedByRole>>,
#[doc = "Enforce declaring components only within modules that export React Components exclusively."]
#[serde(skip_serializing_if = "Option::is_none")]
pub use_component_export_only_modules:
Option<RuleConfiguration<biome_js_analyze::options::UseComponentExportOnlyModules>>,
#[doc = "This rule enforces consistent use of curly braces inside JSX attributes and JSX children."]
#[serde(skip_serializing_if = "Option::is_none")]
pub use_consistent_curly_braces:
Expand Down Expand Up @@ -3419,6 +3423,7 @@ impl Nursery {
"noValueAtRule",
"useAdjacentOverloadSignatures",
"useAriaPropsSupportedByRole",
"useComponentExportOnlyModules",
"useConsistentCurlyBraces",
"useConsistentMemberAccessibility",
"useDeprecatedReason",
Expand Down Expand Up @@ -3450,9 +3455,9 @@ impl Nursery {
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]),
];
const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]),
Expand Down Expand Up @@ -3484,6 +3489,7 @@ impl Nursery {
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]),
];
#[doc = r" Retrieves the recommended rules"]
pub(crate) fn is_recommended_true(&self) -> bool {
Expand Down Expand Up @@ -3605,46 +3611,51 @@ impl Nursery {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]));
}
}
if let Some(rule) = self.use_consistent_curly_braces.as_ref() {
if let Some(rule) = self.use_component_export_only_modules.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]));
}
}
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[22]));
}
}
if let Some(rule) = self.use_deprecated_reason.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[23]));
}
}
if let Some(rule) = self.use_import_restrictions.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[24]));
}
}
if let Some(rule) = self.use_sorted_classes.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[25]));
}
}
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[26]));
}
}
if let Some(rule) = self.use_trim_start_end.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[27]));
}
}
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[28]));
}
}
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[29]));
}
}
index_set
}
pub(crate) fn get_disabled_rules(&self) -> FxHashSet<RuleFilter<'static>> {
Expand Down Expand Up @@ -3754,46 +3765,51 @@ impl Nursery {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]));
}
}
if let Some(rule) = self.use_consistent_curly_braces.as_ref() {
if let Some(rule) = self.use_component_export_only_modules.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]));
}
}
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[22]));
}
}
if let Some(rule) = self.use_deprecated_reason.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[23]));
}
}
if let Some(rule) = self.use_import_restrictions.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[24]));
}
}
if let Some(rule) = self.use_sorted_classes.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[25]));
}
}
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[26]));
}
}
if let Some(rule) = self.use_trim_start_end.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[27]));
}
}
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[28]));
}
}
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[29]));
}
}
index_set
}
#[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"]
Expand Down Expand Up @@ -3914,6 +3930,10 @@ impl Nursery {
.use_aria_props_supported_by_role
.as_ref()
.map(|conf| (conf.level(), conf.get_options())),
"useComponentExportOnlyModules" => self
.use_component_export_only_modules
.as_ref()
.map(|conf| (conf.level(), conf.get_options())),
"useConsistentCurlyBraces" => self
.use_consistent_curly_braces
.as_ref()
Expand Down
1 change: 1 addition & 0 deletions crates/biome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ define_categories! {
"lint/nursery/useAdjacentOverloadSignatures": "https://biomejs.dev/linter/rules/use-adjacent-overload-signatures",
"lint/nursery/useAriaPropsSupportedByRole": "https://biomejs.dev/linter/rules/use-aria-props-supported-by-role",
"lint/nursery/useBiomeSuppressionComment": "https://biomejs.dev/linter/rules/use-biome-suppression-comment",
"lint/nursery/useComponentExportOnlyModules": "https://biomejs.dev/linter/rules/use-components-only-module",
"lint/nursery/useConsistentCurlyBraces": "https://biomejs.dev/linter/rules/use-consistent-curly-braces",
"lint/nursery/useConsistentMemberAccessibility": "https://biomejs.dev/linter/rules/use-consistent-member-accessibility",
"lint/nursery/useDeprecatedReason": "https://biomejs.dev/linter/rules/use-deprecated-reason",
Expand Down
2 changes: 2 additions & 0 deletions crates/biome_js_analyze/src/lint/nursery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod no_substr;
pub mod no_useless_escape_in_regex;
pub mod use_adjacent_overload_signatures;
pub mod use_aria_props_supported_by_role;
pub mod use_component_export_only_modules;
pub mod use_consistent_curly_braces;
pub mod use_consistent_member_accessibility;
pub mod use_import_restrictions;
Expand Down Expand Up @@ -44,6 +45,7 @@ declare_lint_group! {
self :: no_useless_escape_in_regex :: NoUselessEscapeInRegex ,
self :: use_adjacent_overload_signatures :: UseAdjacentOverloadSignatures ,
self :: use_aria_props_supported_by_role :: UseAriaPropsSupportedByRole ,
self :: use_component_export_only_modules :: UseComponentExportOnlyModules ,
self :: use_consistent_curly_braces :: UseConsistentCurlyBraces ,
self :: use_consistent_member_accessibility :: UseConsistentMemberAccessibility ,
self :: use_import_restrictions :: UseImportRestrictions ,
Expand Down
Loading

0 comments on commit 00760d4

Please sign in to comment.