diff --git a/.changeset/shy-sites-join.md b/.changeset/shy-sites-join.md
new file mode 100644
index 000000000000..43e61dc8b298
--- /dev/null
+++ b/.changeset/shy-sites-join.md
@@ -0,0 +1,11 @@
+---
+"@biomejs/biome": patch
+---
+
+Added the nursery rule [`noAmbiguousAnchorText`](https://biomejs.dev/linter/rules/no-ambiguous-anchor-text/), which disallows ambiguous anchor descriptions.
+
+#### Invalid
+
+```html
+learn more
+```
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 1544844998bc..e9a41071344e 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
@@ -1085,6 +1085,18 @@ pub(crate) fn migrate_eslint_any_rule(
.get_or_insert(Default::default());
rule.set_level(rule.level().max(rule_severity.into()));
}
+ "jsx-a11y/anchor-ambiguous-text" => {
+ if !options.include_nursery {
+ results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery);
+ return false;
+ }
+ let group = rules.nursery.get_or_insert_with(Default::default);
+ let rule = group
+ .unwrap_group_as_mut()
+ .no_ambiguous_anchor_text
+ .get_or_insert(Default::default());
+ rule.set_level(rule.level().max(rule_severity.into()));
+ }
"jsx-a11y/anchor-has-content" => {
let group = rules.a11y.get_or_insert_with(Default::default);
let rule = group
diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs
index 626e9b2ad3ed..83b2430d2f5b 100644
--- a/crates/biome_configuration/src/analyzer/linter/rules.rs
+++ b/crates/biome_configuration/src/analyzer/linter/rules.rs
@@ -93,6 +93,7 @@ pub enum RuleName {
NoAccumulatingSpread,
NoAdjacentSpacesInRegex,
NoAlert,
+ NoAmbiguousAnchorText,
NoApproximativeNumericConstant,
NoArguments,
NoAriaHiddenOnFocusable,
@@ -492,6 +493,7 @@ impl RuleName {
Self::NoAccumulatingSpread => "noAccumulatingSpread",
Self::NoAdjacentSpacesInRegex => "noAdjacentSpacesInRegex",
Self::NoAlert => "noAlert",
+ Self::NoAmbiguousAnchorText => "noAmbiguousAnchorText",
Self::NoApproximativeNumericConstant => "noApproximativeNumericConstant",
Self::NoArguments => "noArguments",
Self::NoAriaHiddenOnFocusable => "noAriaHiddenOnFocusable",
@@ -895,6 +897,7 @@ impl RuleName {
Self::NoAccumulatingSpread => RuleGroup::Performance,
Self::NoAdjacentSpacesInRegex => RuleGroup::Complexity,
Self::NoAlert => RuleGroup::Suspicious,
+ Self::NoAmbiguousAnchorText => RuleGroup::Nursery,
Self::NoApproximativeNumericConstant => RuleGroup::Suspicious,
Self::NoArguments => RuleGroup::Complexity,
Self::NoAriaHiddenOnFocusable => RuleGroup::A11y,
@@ -1297,6 +1300,7 @@ impl std::str::FromStr for RuleName {
"noAccumulatingSpread" => Ok(Self::NoAccumulatingSpread),
"noAdjacentSpacesInRegex" => Ok(Self::NoAdjacentSpacesInRegex),
"noAlert" => Ok(Self::NoAlert),
+ "noAmbiguousAnchorText" => Ok(Self::NoAmbiguousAnchorText),
"noApproximativeNumericConstant" => Ok(Self::NoApproximativeNumericConstant),
"noArguments" => Ok(Self::NoArguments),
"noAriaHiddenOnFocusable" => Ok(Self::NoAriaHiddenOnFocusable),
@@ -4828,10 +4832,11 @@ impl From for Correctness {
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
#[doc = r" A list of rules that belong to this group"]
-pub struct Nursery { # [doc = r" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Disallow continue statements.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_continue : Option < RuleConfiguration < biome_rule_options :: no_continue :: NoContinueOptions >> , # [doc = "Restrict imports of deprecated exports.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\".\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow JSX prop spreading the same identifier multiple times.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicated_spread_props : Option < RuleConfiguration < biome_rule_options :: no_duplicated_spread_props :: NoDuplicatedSpreadPropsOptions >> , # [doc = "Disallow empty sources.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [doc = "Require the use of === or !== for comparison with null.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_equals_to_null : Option < RuleFixConfiguration < biome_rule_options :: no_equals_to_null :: NoEqualsToNullOptions >> , # [doc = "Require Promise-like statements to be handled appropriately.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Disallow iterating using a for-in loop.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_for_in : Option < RuleConfiguration < biome_rule_options :: no_for_in :: NoForInOptions >> , # [doc = "Prevent import cycles.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallows the usage of the unary operators ++ and --.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_increment_decrement : Option < RuleConfiguration < biome_rule_options :: no_increment_decrement :: NoIncrementDecrementOptions >> , # [doc = "Disallow string literals inside JSX elements.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Prevent problematic leaked values from being rendered.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_leaked_render : Option < RuleConfiguration < biome_rule_options :: no_leaked_render :: NoLeakedRenderOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Disallow creating multiline strings by escaping newlines.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_multi_str : Option < RuleConfiguration < biome_rule_options :: no_multi_str :: NoMultiStrOptions >> , # [doc = "Prevent client components from being async functions.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_next_async_client_component : Option < RuleConfiguration < biome_rule_options :: no_next_async_client_component :: NoNextAsyncClientComponentOptions >> , # [doc = "Disallow function parameters that are only used in recursive calls.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_parameters_only_used_in_recursion : Option < RuleFixConfiguration < biome_rule_options :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursionOptions >> , # [doc = "Disallow the use of the deprecated __proto__ object property.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_proto : Option < RuleConfiguration < biome_rule_options :: no_proto :: NoProtoOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [doc = "Disallow javascript: URLs in HTML.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_script_url : Option < RuleConfiguration < biome_rule_options :: no_script_url :: NoScriptUrlOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Prevent the usage of synchronous scripts.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_sync_scripts : Option < RuleConfiguration < biome_rule_options :: no_sync_scripts :: NoSyncScriptsOptions >> , # [doc = "Disallow ternary operators.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_ternary : Option < RuleConfiguration < biome_rule_options :: no_ternary :: NoTernaryOptions >> , # [doc = "Disallow the use of undeclared environment variables.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_undeclared_env_vars : Option < RuleConfiguration < biome_rule_options :: no_undeclared_env_vars :: NoUndeclaredEnvVarsOptions >> , # [doc = "Disallow unknown DOM properties.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_unknown_attribute : Option < RuleConfiguration < biome_rule_options :: no_unknown_attribute :: NoUnknownAttributeOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_unnecessary_conditions : Option < RuleConfiguration < biome_rule_options :: no_unnecessary_conditions :: NoUnnecessaryConditionsOptions >> , # [doc = "Warn when importing non-existing exports.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Disallow expression statements that are neither a function call nor an assignment.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [doc = "Disallow the use of useless undefined.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow duplicate keys in Vue component data, methods, computed properties, and other options.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Disallow destructuring of props passed to setup in Vue projects.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_setup_props_reactivity_loss : Option < RuleConfiguration < biome_rule_options :: no_vue_setup_props_reactivity_loss :: NoVueSetupPropsReactivityLossOptions >> , # [doc = "Disallow using v-if and v-for directives on the same element.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_v_if_with_v_for : Option < RuleConfiguration < biome_rule_options :: no_vue_v_if_with_v_for :: NoVueVIfWithVForOptions >> , # [doc = "Require Array#sort and Array#toSorted calls to always provide a compareFunction.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_array_sort_compare : Option < RuleConfiguration < biome_rule_options :: use_array_sort_compare :: UseArraySortCompareOptions >> , # [doc = "Enforce that await is only used on Promise values.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_await_thenable : Option < RuleConfiguration < biome_rule_options :: use_await_thenable :: UseAwaitThenableOptions >> , # [doc = "Enforce consistent arrow function bodies.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Require all descriptions to follow the same style (either block or inline) to maintain consistency and improve readability across the schema.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_graphql_descriptions : Option < RuleConfiguration < biome_rule_options :: use_consistent_graphql_descriptions :: UseConsistentGraphqlDescriptionsOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [doc = "Require destructuring from arrays and/or objects.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_destructuring : Option < RuleConfiguration < biome_rule_options :: use_destructuring :: UseDestructuringOptions >> , # [doc = "Require switch-case statements to be exhaustive.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Enforce the use of Array.prototype.find() over Array.prototype.filter() followed by [0] when looking for a single result.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_find : Option < RuleConfiguration < biome_rule_options :: use_find :: UseFindOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [doc = "Enforce RegExp#exec over String#match if no global flag is provided.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_regexp_exec : Option < RuleConfiguration < biome_rule_options :: use_regexp_exec :: UseRegexpExecOptions >> , # [doc = "Enforce the presence of required scripts in package.json.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_required_scripts : Option < RuleConfiguration < biome_rule_options :: use_required_scripts :: UseRequiredScriptsOptions >> , # [doc = "Enforce the sorting of CSS utility classes.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Enforce the use of the spread operator over .apply().\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_spread : Option < RuleFixConfiguration < biome_rule_options :: use_spread :: UseSpreadOptions >> , # [doc = "Enforce unique operation names across a GraphQL document.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_unique_graphql_operation_name : Option < RuleConfiguration < biome_rule_options :: use_unique_graphql_operation_name :: UseUniqueGraphqlOperationNameOptions >> , # [doc = "Enforce specific order of Vue compiler macros.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_define_macros_order : Option < RuleFixConfiguration < biome_rule_options :: use_vue_define_macros_order :: UseVueDefineMacrosOrderOptions >> , # [doc = "Enforce hyphenated (kebab-case) attribute names in Vue templates.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_hyphenated_attributes : Option < RuleFixConfiguration < biome_rule_options :: use_vue_hyphenated_attributes :: UseVueHyphenatedAttributesOptions >> , # [doc = "Enforce multi-word component names in Vue components.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> , # [doc = "Forbids v-bind directives with missing arguments or invalid modifiers.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_bind : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_bind :: UseVueValidVBindOptions >> , # [doc = "Enforce valid usage of v-else.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_else : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_else :: UseVueValidVElseOptions >> , # [doc = "Enforce valid v-else-if directives.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_else_if : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_else_if :: UseVueValidVElseIfOptions >> , # [doc = "Enforce valid v-html directives.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_html : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_html :: UseVueValidVHtmlOptions >> , # [doc = "Enforces valid v-if usage for Vue templates.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_if : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_if :: UseVueValidVIfOptions >> , # [doc = "Enforce valid v-on directives with proper arguments, modifiers, and handlers.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_on : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_on :: UseVueValidVOnOptions >> , # [doc = "Enforce valid v-text Vue directives.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_text : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_text :: UseVueValidVTextOptions >> }
+pub struct Nursery { # [doc = r" Enables the recommended rules for this group"] # [serde (skip_serializing_if = "Option::is_none")] pub recommended : Option < bool > , # [doc = "Disallow ambiguous anchor descriptions.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_ambiguous_anchor_text : Option < RuleConfiguration < biome_rule_options :: no_ambiguous_anchor_text :: NoAmbiguousAnchorTextOptions >> , # [doc = "Disallow continue statements.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_continue : Option < RuleConfiguration < biome_rule_options :: no_continue :: NoContinueOptions >> , # [doc = "Restrict imports of deprecated exports.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_deprecated_imports : Option < RuleConfiguration < biome_rule_options :: no_deprecated_imports :: NoDeprecatedImportsOptions >> , # [doc = "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\".\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicate_dependencies : Option < RuleConfiguration < biome_rule_options :: no_duplicate_dependencies :: NoDuplicateDependenciesOptions >> , # [doc = "Disallow JSX prop spreading the same identifier multiple times.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_duplicated_spread_props : Option < RuleConfiguration < biome_rule_options :: no_duplicated_spread_props :: NoDuplicatedSpreadPropsOptions >> , # [doc = "Disallow empty sources.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_empty_source : Option < RuleConfiguration < biome_rule_options :: no_empty_source :: NoEmptySourceOptions >> , # [doc = "Require the use of === or !== for comparison with null.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_equals_to_null : Option < RuleFixConfiguration < biome_rule_options :: no_equals_to_null :: NoEqualsToNullOptions >> , # [doc = "Require Promise-like statements to be handled appropriately.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_floating_promises : Option < RuleFixConfiguration < biome_rule_options :: no_floating_promises :: NoFloatingPromisesOptions >> , # [doc = "Disallow iterating using a for-in loop.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_for_in : Option < RuleConfiguration < biome_rule_options :: no_for_in :: NoForInOptions >> , # [doc = "Prevent import cycles.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_import_cycles : Option < RuleConfiguration < biome_rule_options :: no_import_cycles :: NoImportCyclesOptions >> , # [doc = "Disallows the usage of the unary operators ++ and --.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_increment_decrement : Option < RuleConfiguration < biome_rule_options :: no_increment_decrement :: NoIncrementDecrementOptions >> , # [doc = "Disallow string literals inside JSX elements.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_jsx_literals : Option < RuleConfiguration < biome_rule_options :: no_jsx_literals :: NoJsxLiteralsOptions >> , # [doc = "Prevent problematic leaked values from being rendered.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_leaked_render : Option < RuleConfiguration < biome_rule_options :: no_leaked_render :: NoLeakedRenderOptions >> , # [doc = "Disallow Promises to be used in places where they are almost certainly a mistake.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_misused_promises : Option < RuleFixConfiguration < biome_rule_options :: no_misused_promises :: NoMisusedPromisesOptions >> , # [doc = "Disallow creating multiline strings by escaping newlines.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_multi_str : Option < RuleConfiguration < biome_rule_options :: no_multi_str :: NoMultiStrOptions >> , # [doc = "Prevent client components from being async functions.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_next_async_client_component : Option < RuleConfiguration < biome_rule_options :: no_next_async_client_component :: NoNextAsyncClientComponentOptions >> , # [doc = "Disallow function parameters that are only used in recursive calls.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_parameters_only_used_in_recursion : Option < RuleFixConfiguration < biome_rule_options :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursionOptions >> , # [doc = "Disallow the use of the deprecated __proto__ object property.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_proto : Option < RuleConfiguration < biome_rule_options :: no_proto :: NoProtoOptions >> , # [doc = "Replaces usages of forwardRef with passing ref as a prop.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_react_forward_ref : Option < RuleFixConfiguration < biome_rule_options :: no_react_forward_ref :: NoReactForwardRefOptions >> , # [doc = "Disallow javascript: URLs in HTML.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_script_url : Option < RuleConfiguration < biome_rule_options :: no_script_url :: NoScriptUrlOptions >> , # [doc = "Disallow variable declarations from shadowing variables declared in the outer scope.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_shadow : Option < RuleConfiguration < biome_rule_options :: no_shadow :: NoShadowOptions >> , # [doc = "Prevent the usage of synchronous scripts.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_sync_scripts : Option < RuleConfiguration < biome_rule_options :: no_sync_scripts :: NoSyncScriptsOptions >> , # [doc = "Disallow ternary operators.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_ternary : Option < RuleConfiguration < biome_rule_options :: no_ternary :: NoTernaryOptions >> , # [doc = "Disallow the use of undeclared environment variables.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_undeclared_env_vars : Option < RuleConfiguration < biome_rule_options :: no_undeclared_env_vars :: NoUndeclaredEnvVarsOptions >> , # [doc = "Disallow unknown DOM properties.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_unknown_attribute : Option < RuleConfiguration < biome_rule_options :: no_unknown_attribute :: NoUnknownAttributeOptions >> , # [doc = "Disallow unnecessary type-based conditions that can be statically determined as redundant.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_unnecessary_conditions : Option < RuleConfiguration < biome_rule_options :: no_unnecessary_conditions :: NoUnnecessaryConditionsOptions >> , # [doc = "Warn when importing non-existing exports.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_unresolved_imports : Option < RuleConfiguration < biome_rule_options :: no_unresolved_imports :: NoUnresolvedImportsOptions >> , # [doc = "Disallow expression statements that are neither a function call nor an assignment.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_unused_expressions : Option < RuleConfiguration < biome_rule_options :: no_unused_expressions :: NoUnusedExpressionsOptions >> , # [doc = "Disallow unused catch bindings.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_catch_binding : Option < RuleFixConfiguration < biome_rule_options :: no_useless_catch_binding :: NoUselessCatchBindingOptions >> , # [doc = "Disallow the use of useless undefined.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_useless_undefined : Option < RuleFixConfiguration < biome_rule_options :: no_useless_undefined :: NoUselessUndefinedOptions >> , # [doc = "Enforce that Vue component data options are declared as functions.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_data_object_declaration : Option < RuleFixConfiguration < biome_rule_options :: no_vue_data_object_declaration :: NoVueDataObjectDeclarationOptions >> , # [doc = "Disallow duplicate keys in Vue component data, methods, computed properties, and other options.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_duplicate_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_duplicate_keys :: NoVueDuplicateKeysOptions >> , # [doc = "Disallow reserved keys in Vue component data and computed properties.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_keys : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_keys :: NoVueReservedKeysOptions >> , # [doc = "Disallow reserved names to be used as props.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_reserved_props : Option < RuleConfiguration < biome_rule_options :: no_vue_reserved_props :: NoVueReservedPropsOptions >> , # [doc = "Disallow destructuring of props passed to setup in Vue projects.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_setup_props_reactivity_loss : Option < RuleConfiguration < biome_rule_options :: no_vue_setup_props_reactivity_loss :: NoVueSetupPropsReactivityLossOptions >> , # [doc = "Disallow using v-if and v-for directives on the same element.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub no_vue_v_if_with_v_for : Option < RuleConfiguration < biome_rule_options :: no_vue_v_if_with_v_for :: NoVueVIfWithVForOptions >> , # [doc = "Require Array#sort and Array#toSorted calls to always provide a compareFunction.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_array_sort_compare : Option < RuleConfiguration < biome_rule_options :: use_array_sort_compare :: UseArraySortCompareOptions >> , # [doc = "Enforce that await is only used on Promise values.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_await_thenable : Option < RuleConfiguration < biome_rule_options :: use_await_thenable :: UseAwaitThenableOptions >> , # [doc = "Enforce consistent arrow function bodies.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_arrow_return : Option < RuleFixConfiguration < biome_rule_options :: use_consistent_arrow_return :: UseConsistentArrowReturnOptions >> , # [doc = "Require all descriptions to follow the same style (either block or inline) to maintain consistency and improve readability across the schema.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_consistent_graphql_descriptions : Option < RuleConfiguration < biome_rule_options :: use_consistent_graphql_descriptions :: UseConsistentGraphqlDescriptionsOptions >> , # [doc = "Require the @deprecated directive to specify a deletion date.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_deprecated_date : Option < RuleConfiguration < biome_rule_options :: use_deprecated_date :: UseDeprecatedDateOptions >> , # [doc = "Require destructuring from arrays and/or objects.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_destructuring : Option < RuleConfiguration < biome_rule_options :: use_destructuring :: UseDestructuringOptions >> , # [doc = "Require switch-case statements to be exhaustive.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_exhaustive_switch_cases : Option < RuleFixConfiguration < biome_rule_options :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCasesOptions >> , # [doc = "Enforce types in functions, methods, variables, and parameters.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_explicit_type : Option < RuleConfiguration < biome_rule_options :: use_explicit_type :: UseExplicitTypeOptions >> , # [doc = "Enforce the use of Array.prototype.find() over Array.prototype.filter() followed by [0] when looking for a single result.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_find : Option < RuleConfiguration < biome_rule_options :: use_find :: UseFindOptions >> , # [doc = "Enforce a maximum number of parameters in function definitions.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_max_params : Option < RuleConfiguration < biome_rule_options :: use_max_params :: UseMaxParamsOptions >> , # [doc = "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_method_usage : Option < RuleConfiguration < biome_rule_options :: use_qwik_method_usage :: UseQwikMethodUsageOptions >> , # [doc = "Disallow unserializable expressions in Qwik dollar ($) scopes.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_qwik_valid_lexical_scope : Option < RuleConfiguration < biome_rule_options :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScopeOptions >> , # [doc = "Enforce RegExp#exec over String#match if no global flag is provided.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_regexp_exec : Option < RuleConfiguration < biome_rule_options :: use_regexp_exec :: UseRegexpExecOptions >> , # [doc = "Enforce the presence of required scripts in package.json.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_required_scripts : Option < RuleConfiguration < biome_rule_options :: use_required_scripts :: UseRequiredScriptsOptions >> , # [doc = "Enforce the sorting of CSS utility classes.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_sorted_classes : Option < RuleFixConfiguration < biome_rule_options :: use_sorted_classes :: UseSortedClassesOptions >> , # [doc = "Enforce the use of the spread operator over .apply().\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_spread : Option < RuleFixConfiguration < biome_rule_options :: use_spread :: UseSpreadOptions >> , # [doc = "Enforce unique operation names across a GraphQL document.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_unique_graphql_operation_name : Option < RuleConfiguration < biome_rule_options :: use_unique_graphql_operation_name :: UseUniqueGraphqlOperationNameOptions >> , # [doc = "Enforce specific order of Vue compiler macros.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_define_macros_order : Option < RuleFixConfiguration < biome_rule_options :: use_vue_define_macros_order :: UseVueDefineMacrosOrderOptions >> , # [doc = "Enforce hyphenated (kebab-case) attribute names in Vue templates.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_hyphenated_attributes : Option < RuleFixConfiguration < biome_rule_options :: use_vue_hyphenated_attributes :: UseVueHyphenatedAttributesOptions >> , # [doc = "Enforce multi-word component names in Vue components.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_multi_word_component_names : Option < RuleConfiguration < biome_rule_options :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNamesOptions >> , # [doc = "Forbids v-bind directives with missing arguments or invalid modifiers.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_bind : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_bind :: UseVueValidVBindOptions >> , # [doc = "Enforce valid usage of v-else.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_else : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_else :: UseVueValidVElseOptions >> , # [doc = "Enforce valid v-else-if directives.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_else_if : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_else_if :: UseVueValidVElseIfOptions >> , # [doc = "Enforce valid v-html directives.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_html : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_html :: UseVueValidVHtmlOptions >> , # [doc = "Enforces valid v-if usage for Vue templates.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_if : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_if :: UseVueValidVIfOptions >> , # [doc = "Enforce valid v-on directives with proper arguments, modifiers, and handlers.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_on : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_on :: UseVueValidVOnOptions >> , # [doc = "Enforce valid v-text Vue directives.\nSee "] # [serde (skip_serializing_if = "Option::is_none")] pub use_vue_valid_v_text : Option < RuleConfiguration < biome_rule_options :: use_vue_valid_v_text :: UseVueValidVTextOptions >> }
impl Nursery {
const GROUP_NAME: &'static str = "nursery";
pub(crate) const GROUP_RULES: &'static [&'static str] = &[
+ "noAmbiguousAnchorText",
"noContinue",
"noDeprecatedImports",
"noDuplicateDependencies",
@@ -4896,9 +4901,9 @@ impl Nursery {
"useVueValidVText",
];
const RECOMMENDED_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[
- 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[50]),
+ 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[51]),
];
const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]),
@@ -4963,6 +4968,7 @@ impl Nursery {
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61]),
+ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62]),
];
}
impl RuleGroupExt for Nursery {
@@ -4974,630 +4980,640 @@ impl RuleGroupExt for Nursery {
}
fn get_enabled_rules(&self) -> FxHashSet> {
let mut index_set = FxHashSet::default();
- if let Some(rule) = self.no_continue.as_ref()
+ if let Some(rule) = self.no_ambiguous_anchor_text.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]));
}
- if let Some(rule) = self.no_deprecated_imports.as_ref()
+ if let Some(rule) = self.no_continue.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]));
}
- if let Some(rule) = self.no_duplicate_dependencies.as_ref()
+ if let Some(rule) = self.no_deprecated_imports.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]));
}
- if let Some(rule) = self.no_duplicated_spread_props.as_ref()
+ if let Some(rule) = self.no_duplicate_dependencies.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3]));
}
- if let Some(rule) = self.no_empty_source.as_ref()
+ if let Some(rule) = self.no_duplicated_spread_props.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]));
}
- if let Some(rule) = self.no_equals_to_null.as_ref()
+ if let Some(rule) = self.no_empty_source.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5]));
}
- if let Some(rule) = self.no_floating_promises.as_ref()
+ if let Some(rule) = self.no_equals_to_null.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6]));
}
- if let Some(rule) = self.no_for_in.as_ref()
+ if let Some(rule) = self.no_floating_promises.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7]));
}
- if let Some(rule) = self.no_import_cycles.as_ref()
+ if let Some(rule) = self.no_for_in.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]));
}
- if let Some(rule) = self.no_increment_decrement.as_ref()
+ if let Some(rule) = self.no_import_cycles.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]));
}
- if let Some(rule) = self.no_jsx_literals.as_ref()
+ if let Some(rule) = self.no_increment_decrement.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]));
}
- if let Some(rule) = self.no_leaked_render.as_ref()
+ if let Some(rule) = self.no_jsx_literals.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]));
}
- if let Some(rule) = self.no_misused_promises.as_ref()
+ if let Some(rule) = self.no_leaked_render.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]));
}
- if let Some(rule) = self.no_multi_str.as_ref()
+ if let Some(rule) = self.no_misused_promises.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]));
}
- if let Some(rule) = self.no_next_async_client_component.as_ref()
+ if let Some(rule) = self.no_multi_str.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]));
}
- if let Some(rule) = self.no_parameters_only_used_in_recursion.as_ref()
+ if let Some(rule) = self.no_next_async_client_component.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15]));
}
- if let Some(rule) = self.no_proto.as_ref()
+ if let Some(rule) = self.no_parameters_only_used_in_recursion.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]));
}
- if let Some(rule) = self.no_react_forward_ref.as_ref()
+ if let Some(rule) = self.no_proto.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]));
}
- if let Some(rule) = self.no_script_url.as_ref()
+ if let Some(rule) = self.no_react_forward_ref.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]));
}
- if let Some(rule) = self.no_shadow.as_ref()
+ if let Some(rule) = self.no_script_url.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]));
}
- if let Some(rule) = self.no_sync_scripts.as_ref()
+ if let Some(rule) = self.no_shadow.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]));
}
- if let Some(rule) = self.no_ternary.as_ref()
+ if let Some(rule) = self.no_sync_scripts.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]));
}
- if let Some(rule) = self.no_undeclared_env_vars.as_ref()
+ if let Some(rule) = self.no_ternary.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]));
}
- if let Some(rule) = self.no_unknown_attribute.as_ref()
+ if let Some(rule) = self.no_undeclared_env_vars.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]));
}
- if let Some(rule) = self.no_unnecessary_conditions.as_ref()
+ if let Some(rule) = self.no_unknown_attribute.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]));
}
- if let Some(rule) = self.no_unresolved_imports.as_ref()
+ if let Some(rule) = self.no_unnecessary_conditions.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]));
}
- if let Some(rule) = self.no_unused_expressions.as_ref()
+ if let Some(rule) = self.no_unresolved_imports.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]));
}
- if let Some(rule) = self.no_useless_catch_binding.as_ref()
+ if let Some(rule) = self.no_unused_expressions.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]));
}
- if let Some(rule) = self.no_useless_undefined.as_ref()
+ if let Some(rule) = self.no_useless_catch_binding.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]));
}
- if let Some(rule) = self.no_vue_data_object_declaration.as_ref()
+ if let Some(rule) = self.no_useless_undefined.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]));
}
- if let Some(rule) = self.no_vue_duplicate_keys.as_ref()
+ if let Some(rule) = self.no_vue_data_object_declaration.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]));
}
- if let Some(rule) = self.no_vue_reserved_keys.as_ref()
+ if let Some(rule) = self.no_vue_duplicate_keys.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]));
}
- if let Some(rule) = self.no_vue_reserved_props.as_ref()
+ if let Some(rule) = self.no_vue_reserved_keys.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]));
}
- if let Some(rule) = self.no_vue_setup_props_reactivity_loss.as_ref()
+ if let Some(rule) = self.no_vue_reserved_props.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]));
}
- if let Some(rule) = self.no_vue_v_if_with_v_for.as_ref()
+ if let Some(rule) = self.no_vue_setup_props_reactivity_loss.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]));
}
- if let Some(rule) = self.use_array_sort_compare.as_ref()
+ if let Some(rule) = self.no_vue_v_if_with_v_for.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]));
}
- if let Some(rule) = self.use_await_thenable.as_ref()
+ if let Some(rule) = self.use_array_sort_compare.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]));
}
- if let Some(rule) = self.use_consistent_arrow_return.as_ref()
+ if let Some(rule) = self.use_await_thenable.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37]));
}
- if let Some(rule) = self.use_consistent_graphql_descriptions.as_ref()
+ if let Some(rule) = self.use_consistent_arrow_return.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38]));
}
- if let Some(rule) = self.use_deprecated_date.as_ref()
+ if let Some(rule) = self.use_consistent_graphql_descriptions.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39]));
}
- if let Some(rule) = self.use_destructuring.as_ref()
+ if let Some(rule) = self.use_deprecated_date.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]));
}
- if let Some(rule) = self.use_exhaustive_switch_cases.as_ref()
+ if let Some(rule) = self.use_destructuring.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]));
}
- if let Some(rule) = self.use_explicit_type.as_ref()
+ if let Some(rule) = self.use_exhaustive_switch_cases.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42]));
}
- if let Some(rule) = self.use_find.as_ref()
+ if let Some(rule) = self.use_explicit_type.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]));
}
- if let Some(rule) = self.use_max_params.as_ref()
+ if let Some(rule) = self.use_find.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]));
}
- if let Some(rule) = self.use_qwik_method_usage.as_ref()
+ if let Some(rule) = self.use_max_params.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]));
}
- if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref()
+ if let Some(rule) = self.use_qwik_method_usage.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]));
}
- if let Some(rule) = self.use_regexp_exec.as_ref()
+ if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]));
}
- if let Some(rule) = self.use_required_scripts.as_ref()
+ if let Some(rule) = self.use_regexp_exec.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]));
}
- if let Some(rule) = self.use_sorted_classes.as_ref()
+ if let Some(rule) = self.use_required_scripts.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]));
}
- if let Some(rule) = self.use_spread.as_ref()
+ if let Some(rule) = self.use_sorted_classes.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50]));
}
- if let Some(rule) = self.use_unique_graphql_operation_name.as_ref()
+ if let Some(rule) = self.use_spread.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51]));
}
- if let Some(rule) = self.use_vue_define_macros_order.as_ref()
+ if let Some(rule) = self.use_unique_graphql_operation_name.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52]));
}
- if let Some(rule) = self.use_vue_hyphenated_attributes.as_ref()
+ if let Some(rule) = self.use_vue_define_macros_order.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53]));
}
- if let Some(rule) = self.use_vue_multi_word_component_names.as_ref()
+ if let Some(rule) = self.use_vue_hyphenated_attributes.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54]));
}
- if let Some(rule) = self.use_vue_valid_v_bind.as_ref()
+ if let Some(rule) = self.use_vue_multi_word_component_names.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55]));
}
- if let Some(rule) = self.use_vue_valid_v_else.as_ref()
+ if let Some(rule) = self.use_vue_valid_v_bind.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56]));
}
- if let Some(rule) = self.use_vue_valid_v_else_if.as_ref()
+ if let Some(rule) = self.use_vue_valid_v_else.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57]));
}
- if let Some(rule) = self.use_vue_valid_v_html.as_ref()
+ if let Some(rule) = self.use_vue_valid_v_else_if.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58]));
}
- if let Some(rule) = self.use_vue_valid_v_if.as_ref()
+ if let Some(rule) = self.use_vue_valid_v_html.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59]));
}
- if let Some(rule) = self.use_vue_valid_v_on.as_ref()
+ if let Some(rule) = self.use_vue_valid_v_if.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60]));
}
- if let Some(rule) = self.use_vue_valid_v_text.as_ref()
+ if let Some(rule) = self.use_vue_valid_v_on.as_ref()
&& rule.is_enabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61]));
}
+ if let Some(rule) = self.use_vue_valid_v_text.as_ref()
+ && rule.is_enabled()
+ {
+ index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62]));
+ }
index_set
}
fn get_disabled_rules(&self) -> FxHashSet> {
let mut index_set = FxHashSet::default();
- if let Some(rule) = self.no_continue.as_ref()
+ if let Some(rule) = self.no_ambiguous_anchor_text.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]));
}
- if let Some(rule) = self.no_deprecated_imports.as_ref()
+ if let Some(rule) = self.no_continue.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]));
}
- if let Some(rule) = self.no_duplicate_dependencies.as_ref()
+ if let Some(rule) = self.no_deprecated_imports.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]));
}
- if let Some(rule) = self.no_duplicated_spread_props.as_ref()
+ if let Some(rule) = self.no_duplicate_dependencies.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3]));
}
- if let Some(rule) = self.no_empty_source.as_ref()
+ if let Some(rule) = self.no_duplicated_spread_props.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]));
}
- if let Some(rule) = self.no_equals_to_null.as_ref()
+ if let Some(rule) = self.no_empty_source.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5]));
}
- if let Some(rule) = self.no_floating_promises.as_ref()
+ if let Some(rule) = self.no_equals_to_null.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6]));
}
- if let Some(rule) = self.no_for_in.as_ref()
+ if let Some(rule) = self.no_floating_promises.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7]));
}
- if let Some(rule) = self.no_import_cycles.as_ref()
+ if let Some(rule) = self.no_for_in.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]));
}
- if let Some(rule) = self.no_increment_decrement.as_ref()
+ if let Some(rule) = self.no_import_cycles.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]));
}
- if let Some(rule) = self.no_jsx_literals.as_ref()
+ if let Some(rule) = self.no_increment_decrement.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]));
}
- if let Some(rule) = self.no_leaked_render.as_ref()
+ if let Some(rule) = self.no_jsx_literals.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]));
}
- if let Some(rule) = self.no_misused_promises.as_ref()
+ if let Some(rule) = self.no_leaked_render.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]));
}
- if let Some(rule) = self.no_multi_str.as_ref()
+ if let Some(rule) = self.no_misused_promises.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]));
}
- if let Some(rule) = self.no_next_async_client_component.as_ref()
+ if let Some(rule) = self.no_multi_str.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]));
}
- if let Some(rule) = self.no_parameters_only_used_in_recursion.as_ref()
+ if let Some(rule) = self.no_next_async_client_component.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15]));
}
- if let Some(rule) = self.no_proto.as_ref()
+ if let Some(rule) = self.no_parameters_only_used_in_recursion.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]));
}
- if let Some(rule) = self.no_react_forward_ref.as_ref()
+ if let Some(rule) = self.no_proto.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]));
}
- if let Some(rule) = self.no_script_url.as_ref()
+ if let Some(rule) = self.no_react_forward_ref.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]));
}
- if let Some(rule) = self.no_shadow.as_ref()
+ if let Some(rule) = self.no_script_url.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]));
}
- if let Some(rule) = self.no_sync_scripts.as_ref()
+ if let Some(rule) = self.no_shadow.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]));
}
- if let Some(rule) = self.no_ternary.as_ref()
+ if let Some(rule) = self.no_sync_scripts.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]));
}
- if let Some(rule) = self.no_undeclared_env_vars.as_ref()
+ if let Some(rule) = self.no_ternary.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]));
}
- if let Some(rule) = self.no_unknown_attribute.as_ref()
+ if let Some(rule) = self.no_undeclared_env_vars.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]));
}
- if let Some(rule) = self.no_unnecessary_conditions.as_ref()
+ if let Some(rule) = self.no_unknown_attribute.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]));
}
- if let Some(rule) = self.no_unresolved_imports.as_ref()
+ if let Some(rule) = self.no_unnecessary_conditions.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]));
}
- if let Some(rule) = self.no_unused_expressions.as_ref()
+ if let Some(rule) = self.no_unresolved_imports.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]));
}
- if let Some(rule) = self.no_useless_catch_binding.as_ref()
+ if let Some(rule) = self.no_unused_expressions.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]));
}
- if let Some(rule) = self.no_useless_undefined.as_ref()
+ if let Some(rule) = self.no_useless_catch_binding.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]));
}
- if let Some(rule) = self.no_vue_data_object_declaration.as_ref()
+ if let Some(rule) = self.no_useless_undefined.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]));
}
- if let Some(rule) = self.no_vue_duplicate_keys.as_ref()
+ if let Some(rule) = self.no_vue_data_object_declaration.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]));
}
- if let Some(rule) = self.no_vue_reserved_keys.as_ref()
+ if let Some(rule) = self.no_vue_duplicate_keys.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]));
}
- if let Some(rule) = self.no_vue_reserved_props.as_ref()
+ if let Some(rule) = self.no_vue_reserved_keys.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]));
}
- if let Some(rule) = self.no_vue_setup_props_reactivity_loss.as_ref()
+ if let Some(rule) = self.no_vue_reserved_props.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]));
}
- if let Some(rule) = self.no_vue_v_if_with_v_for.as_ref()
+ if let Some(rule) = self.no_vue_setup_props_reactivity_loss.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]));
}
- if let Some(rule) = self.use_array_sort_compare.as_ref()
+ if let Some(rule) = self.no_vue_v_if_with_v_for.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]));
}
- if let Some(rule) = self.use_await_thenable.as_ref()
+ if let Some(rule) = self.use_array_sort_compare.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]));
}
- if let Some(rule) = self.use_consistent_arrow_return.as_ref()
+ if let Some(rule) = self.use_await_thenable.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37]));
}
- if let Some(rule) = self.use_consistent_graphql_descriptions.as_ref()
+ if let Some(rule) = self.use_consistent_arrow_return.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38]));
}
- if let Some(rule) = self.use_deprecated_date.as_ref()
+ if let Some(rule) = self.use_consistent_graphql_descriptions.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39]));
}
- if let Some(rule) = self.use_destructuring.as_ref()
+ if let Some(rule) = self.use_deprecated_date.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]));
}
- if let Some(rule) = self.use_exhaustive_switch_cases.as_ref()
+ if let Some(rule) = self.use_destructuring.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]));
}
- if let Some(rule) = self.use_explicit_type.as_ref()
+ if let Some(rule) = self.use_exhaustive_switch_cases.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42]));
}
- if let Some(rule) = self.use_find.as_ref()
+ if let Some(rule) = self.use_explicit_type.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]));
}
- if let Some(rule) = self.use_max_params.as_ref()
+ if let Some(rule) = self.use_find.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]));
}
- if let Some(rule) = self.use_qwik_method_usage.as_ref()
+ if let Some(rule) = self.use_max_params.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]));
}
- if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref()
+ if let Some(rule) = self.use_qwik_method_usage.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]));
}
- if let Some(rule) = self.use_regexp_exec.as_ref()
+ if let Some(rule) = self.use_qwik_valid_lexical_scope.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]));
}
- if let Some(rule) = self.use_required_scripts.as_ref()
+ if let Some(rule) = self.use_regexp_exec.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]));
}
- if let Some(rule) = self.use_sorted_classes.as_ref()
+ if let Some(rule) = self.use_required_scripts.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]));
}
- if let Some(rule) = self.use_spread.as_ref()
+ if let Some(rule) = self.use_sorted_classes.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50]));
}
- if let Some(rule) = self.use_unique_graphql_operation_name.as_ref()
+ if let Some(rule) = self.use_spread.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51]));
}
- if let Some(rule) = self.use_vue_define_macros_order.as_ref()
+ if let Some(rule) = self.use_unique_graphql_operation_name.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52]));
}
- if let Some(rule) = self.use_vue_hyphenated_attributes.as_ref()
+ if let Some(rule) = self.use_vue_define_macros_order.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53]));
}
- if let Some(rule) = self.use_vue_multi_word_component_names.as_ref()
+ if let Some(rule) = self.use_vue_hyphenated_attributes.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54]));
}
- if let Some(rule) = self.use_vue_valid_v_bind.as_ref()
+ if let Some(rule) = self.use_vue_multi_word_component_names.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55]));
}
- if let Some(rule) = self.use_vue_valid_v_else.as_ref()
+ if let Some(rule) = self.use_vue_valid_v_bind.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[56]));
}
- if let Some(rule) = self.use_vue_valid_v_else_if.as_ref()
+ if let Some(rule) = self.use_vue_valid_v_else.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57]));
}
- if let Some(rule) = self.use_vue_valid_v_html.as_ref()
+ if let Some(rule) = self.use_vue_valid_v_else_if.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58]));
}
- if let Some(rule) = self.use_vue_valid_v_if.as_ref()
+ if let Some(rule) = self.use_vue_valid_v_html.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59]));
}
- if let Some(rule) = self.use_vue_valid_v_on.as_ref()
+ if let Some(rule) = self.use_vue_valid_v_if.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60]));
}
- if let Some(rule) = self.use_vue_valid_v_text.as_ref()
+ if let Some(rule) = self.use_vue_valid_v_on.as_ref()
&& rule.is_disabled()
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[61]));
}
+ if let Some(rule) = self.use_vue_valid_v_text.as_ref()
+ && rule.is_disabled()
+ {
+ index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[62]));
+ }
index_set
}
#[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"]
@@ -5628,6 +5644,10 @@ impl RuleGroupExt for Nursery {
rule_name: &str,
) -> Option<(RulePlainConfiguration, Option)> {
match rule_name {
+ "noAmbiguousAnchorText" => self
+ .no_ambiguous_anchor_text
+ .as_ref()
+ .map(|conf| (conf.level(), conf.get_options())),
"noContinue" => self
.no_continue
.as_ref()
@@ -5884,6 +5904,7 @@ impl From for Nursery {
fn from(value: GroupPlainConfiguration) -> Self {
Self {
recommended: None,
+ no_ambiguous_anchor_text: Some(value.into()),
no_continue: Some(value.into()),
no_deprecated_imports: Some(value.into()),
no_duplicate_dependencies: Some(value.into()),
diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs
index e4d738e79204..e9b299db5309 100644
--- a/crates/biome_diagnostics_categories/src/categories.rs
+++ b/crates/biome_diagnostics_categories/src/categories.rs
@@ -164,6 +164,7 @@ define_categories! {
"lint/correctness/useValidForDirection": "https://biomejs.dev/linter/rules/use-valid-for-direction",
"lint/correctness/useValidTypeof": "https://biomejs.dev/linter/rules/use-valid-typeof",
"lint/correctness/useYield": "https://biomejs.dev/linter/rules/use-yield",
+ "lint/nursery/noAmbiguousAnchorText": "https://biomejs.dev/linter/rules/no-ambiguous-anchor-text",
"lint/nursery/noColorInvalidHex": "https://biomejs.dev/linter/rules/no-color-invalid-hex",
"lint/nursery/noContinue": "https://biomejs.dev/linter/rules/no-continue",
"lint/nursery/noDeprecatedImports": "https://biomejs.dev/linter/rules/no-deprecated-imports",
diff --git a/crates/biome_html_analyze/src/a11y.rs b/crates/biome_html_analyze/src/a11y.rs
new file mode 100644
index 000000000000..6629417b88e5
--- /dev/null
+++ b/crates/biome_html_analyze/src/a11y.rs
@@ -0,0 +1,22 @@
+use biome_html_syntax::element_ext::AnyHtmlTagElement;
+
+/// Check the element is hidden from screen reader.
+///
+/// Ref:
+/// - https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-hidden
+/// - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/hidden
+/// - https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/v6.10.0/src/util/isHiddenFromScreenReader.js
+pub(crate) fn is_hidden_from_screen_reader(element: &AnyHtmlTagElement) -> bool {
+ let is_aria_hidden = element.has_truthy_attribute("aria-hidden");
+ if is_aria_hidden {
+ return true;
+ }
+
+ match element.name_value_token().ok() {
+ Some(name) if name.text_trimmed() == "input" => element
+ .find_attribute_by_name("type")
+ .and_then(|attribute| attribute.initializer()?.value().ok()?.string_value())
+ .is_some_and(|value| value.text() == "hidden"),
+ _ => false,
+ }
+}
diff --git a/crates/biome_html_analyze/src/lib.rs b/crates/biome_html_analyze/src/lib.rs
index 375a264c2807..30850f0f47cd 100644
--- a/crates/biome_html_analyze/src/lib.rs
+++ b/crates/biome_html_analyze/src/lib.rs
@@ -1,5 +1,6 @@
#![deny(clippy::use_self)]
+mod a11y;
mod lint;
pub mod options;
mod registry;
diff --git a/crates/biome_html_analyze/src/lint/nursery.rs b/crates/biome_html_analyze/src/lint/nursery.rs
index ef733037648e..7a0564fc9cd2 100644
--- a/crates/biome_html_analyze/src/lint/nursery.rs
+++ b/crates/biome_html_analyze/src/lint/nursery.rs
@@ -3,6 +3,7 @@
//! Generated file, do not edit by hand, see `xtask/codegen`
use biome_analyze::declare_lint_group;
+pub mod no_ambiguous_anchor_text;
pub mod no_script_url;
pub mod no_sync_scripts;
pub mod no_vue_v_if_with_v_for;
@@ -14,4 +15,4 @@ pub mod use_vue_valid_v_html;
pub mod use_vue_valid_v_if;
pub mod use_vue_valid_v_on;
pub mod use_vue_valid_v_text;
-declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_script_url :: NoScriptUrl , self :: no_sync_scripts :: NoSyncScripts , self :: no_vue_v_if_with_v_for :: NoVueVIfWithVFor , self :: use_vue_hyphenated_attributes :: UseVueHyphenatedAttributes , self :: use_vue_valid_v_bind :: UseVueValidVBind , self :: use_vue_valid_v_else :: UseVueValidVElse , self :: use_vue_valid_v_else_if :: UseVueValidVElseIf , self :: use_vue_valid_v_html :: UseVueValidVHtml , self :: use_vue_valid_v_if :: UseVueValidVIf , self :: use_vue_valid_v_on :: UseVueValidVOn , self :: use_vue_valid_v_text :: UseVueValidVText ,] } }
+declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_ambiguous_anchor_text :: NoAmbiguousAnchorText , self :: no_script_url :: NoScriptUrl , self :: no_sync_scripts :: NoSyncScripts , self :: no_vue_v_if_with_v_for :: NoVueVIfWithVFor , self :: use_vue_hyphenated_attributes :: UseVueHyphenatedAttributes , self :: use_vue_valid_v_bind :: UseVueValidVBind , self :: use_vue_valid_v_else :: UseVueValidVElse , self :: use_vue_valid_v_else_if :: UseVueValidVElseIf , self :: use_vue_valid_v_html :: UseVueValidVHtml , self :: use_vue_valid_v_if :: UseVueValidVIf , self :: use_vue_valid_v_on :: UseVueValidVOn , self :: use_vue_valid_v_text :: UseVueValidVText ,] } }
diff --git a/crates/biome_html_analyze/src/lint/nursery/no_ambiguous_anchor_text.rs b/crates/biome_html_analyze/src/lint/nursery/no_ambiguous_anchor_text.rs
new file mode 100644
index 000000000000..8b79b3cdcd87
--- /dev/null
+++ b/crates/biome_html_analyze/src/lint/nursery/no_ambiguous_anchor_text.rs
@@ -0,0 +1,194 @@
+use biome_analyze::{
+ Ast, QueryMatch, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
+};
+use biome_console::markup;
+use biome_html_syntax::{
+ AnyHtmlContent, AnyHtmlElement, HtmlElement, HtmlOpeningElement,
+ element_ext::AnyHtmlTagElement, inner_string_text,
+};
+use biome_rowan::AstNode;
+use biome_rule_options::no_ambiguous_anchor_text::NoAmbiguousAnchorTextOptions;
+use biome_string_case::StrOnlyExtension;
+
+use crate::a11y::is_hidden_from_screen_reader;
+
+declare_lint_rule! {
+ /// Disallow ambiguous anchor descriptions.
+ ///
+ /// Enforces values are not exact matches for the phrases "click here", "here", "link", "a link", or "learn more".
+ /// Screen readers announce tags as links/interactive, but rely on values for context.
+ /// Ambiguous anchor descriptions do not provide sufficient context for users.
+ ///
+ /// ## Examples
+ ///
+ /// ### Invalid
+ ///
+ /// ```html,expect_diagnostic
+ /// learn more
+ /// ```
+ ///
+ /// ### Valid
+ ///
+ /// ```html
+ /// documentation
+ /// ```
+ ///
+ /// ## Options
+ ///
+ /// ### `words`
+ ///
+ /// The words option allows users to modify the strings that can be checked for in the anchor text. Useful for specifying other words in other languages.
+ ///
+ /// Default `["click here", "here", "link", "a link", "learn more"]`
+ ///
+ /// ```json,options
+ /// {
+ /// "options": {
+ /// "words": ["click this"]
+ /// }
+ /// }
+ /// ```
+ ///
+ /// #### Invalid
+ ///
+ /// ```html,expect_diagnostic,use_options
+ /// click this
+ /// ```
+ ///
+ pub NoAmbiguousAnchorText {
+ version: "next",
+ name: "noAmbiguousAnchorText",
+ language: "html",
+ recommended: false,
+ sources: &[RuleSource::EslintJsxA11y("anchor-ambiguous-text").same()],
+ }
+}
+
+impl Rule for NoAmbiguousAnchorText {
+ type Query = Ast;
+ type State = ();
+ type Signals = Option;
+ type Options = NoAmbiguousAnchorTextOptions;
+
+ fn run(ctx: &RuleContext) -> Self::Signals {
+ let binding = ctx.query();
+ let words = ctx.options().words();
+
+ let name = binding.name().ok()?;
+ let value_token = name.value_token().ok()?;
+ if value_token.text_trimmed() != "a" {
+ return None;
+ }
+
+ let parent = HtmlElement::cast(binding.syntax().parent()?)?;
+ let text = get_accessible_child_text(&parent);
+
+ if words.contains(&text) {
+ return Some(());
+ }
+
+ None
+ }
+
+ fn diagnostic(ctx: &RuleContext, _state: &Self::State) -> Option {
+ let node = ctx.query();
+ let parent = node.syntax().parent()?;
+ Some(
+ RuleDiagnostic::new(
+ rule_category!(),
+ parent.text_range(),
+ markup! {
+ "No ambiguous anchor descriptions allowed."
+ },
+ )
+ .note(markup! {
+ "Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users."
+ }),
+ )
+ }
+}
+
+fn get_aria_label(node: &AnyHtmlTagElement) -> Option {
+ let attribute = node.attributes().find_by_name("aria-label")?;
+ let initializer = attribute.initializer()?;
+ let value = initializer.value().ok()?;
+ let html_string = value.as_html_string()?;
+ let text = html_string.inner_string_text().ok()?;
+
+ Some(text.to_string())
+}
+
+fn get_img_alt(node: &AnyHtmlTagElement) -> Option {
+ let name = node.name().ok()?;
+ let value_token = name.value_token().ok()?;
+ if value_token.text_trimmed() != "img" {
+ return None;
+ }
+
+ let attribute = node.attributes().find_by_name("alt")?;
+ let initializer = attribute.initializer()?;
+ let value = initializer.value().ok()?;
+ let html_string = value.as_html_string()?;
+ let text = html_string.inner_string_text().ok()?;
+
+ Some(text.to_string())
+}
+
+fn standardize_space_and_case(input: &str) -> String {
+ input
+ .chars()
+ .filter(|c| !matches!(c, ',' | '.' | '?' | 'ΒΏ' | '!' | 'β½' | 'Β‘' | ';' | ':'))
+ .collect::()
+ .to_lowercase_cow()
+ .split_whitespace()
+ .collect::>()
+ .join(" ")
+}
+
+fn get_accessible_text(node: &AnyHtmlTagElement) -> Option {
+ if is_hidden_from_screen_reader(node) {
+ return Some(String::new());
+ }
+
+ if let Some(aria_label) = get_aria_label(node) {
+ return Some(standardize_space_and_case(&aria_label));
+ }
+
+ if let Some(alt) = get_img_alt(node) {
+ return Some(standardize_space_and_case(&alt));
+ }
+
+ None
+}
+
+fn get_accessible_child_text(node: &HtmlElement) -> String {
+ if let Ok(opening) = node.opening_element() {
+ let any_jsx_element: AnyHtmlTagElement = opening.clone().into();
+ if let Some(accessible_text) = get_accessible_text(&any_jsx_element) {
+ return accessible_text;
+ }
+ };
+
+ let raw_child_text = node
+ .children()
+ .into_iter()
+ .map(|child| match child {
+ AnyHtmlElement::AnyHtmlContent(AnyHtmlContent::HtmlContent(content)) => {
+ if let Ok(value_token) = content.value_token() {
+ inner_string_text(&value_token).to_string()
+ } else {
+ String::new()
+ }
+ }
+ AnyHtmlElement::HtmlElement(element) => get_accessible_child_text(&element),
+ AnyHtmlElement::HtmlSelfClosingElement(element) => {
+ let any_jsx_element: AnyHtmlTagElement = element.clone().into();
+ get_accessible_text(&any_jsx_element).unwrap_or_default()
+ }
+ _ => String::new(),
+ })
+ .collect::>()
+ .join(" ");
+
+ standardize_space_and_case(&raw_child_text)
+}
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/invalid.html b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/invalid.html
new file mode 100644
index 000000000000..47b8e487b6ae
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/invalid.html
@@ -0,0 +1,49 @@
+/* should generate diagnostics */
+
+here
+
+HERE
+
+click here
+
+learn more
+
+learn more
+
+learn more.
+
+learn more?
+
+learn more,
+
+learn more!
+
+learn more;
+
+learn more:
+
+link
+
+a link
+
+something
+
+ a link
+
+a link
+
+a link
+
+click here
+
+ click here
+
+more textlearn more
+
+more textlearn more
+
+
+
+click here
+
+click here
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/invalid.html.snap b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/invalid.html.snap
new file mode 100644
index 000000000000..5f80988c63a2
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/invalid.html.snap
@@ -0,0 +1,465 @@
+---
+source: crates/biome_html_analyze/tests/spec_tests.rs
+expression: invalid.html
+---
+# Input
+```html
+/* should generate diagnostics */
+
+here
+
+HERE
+
+click here
+
+learn more
+
+learn more
+
+learn more.
+
+learn more?
+
+learn more,
+
+learn more!
+
+learn more;
+
+learn more:
+
+link
+
+a link
+
+something
+
+ a link
+
+a link
+
+a link
+
+click here
+
+ click here
+
+more textlearn more
+
+more textlearn more
+
+
+
+click here
+
+click here
+
+```
+
+# Diagnostics
+```
+invalid.html:3:1 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 1 β /* should generate diagnostics */
+ 2 β
+ > 3 β here
+ β ^^^^^^^^^^^
+ 4 β
+ 5 β HERE
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:5:1 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 3 β here
+ 4 β
+ > 5 β HERE
+ β ^^^^^^^^^^^
+ 6 β
+ 7 β click here
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:7:1 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 5 β HERE
+ 6 β
+ > 7 β click here
+ β ^^^^^^^^^^^^^^^^^
+ 8 β
+ 9 β learn more
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:9:1 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 7 β click here
+ 8 β
+ > 9 β learn more
+ β ^^^^^^^^^^^^^^^^^
+ 10 β
+ 11 β learn more
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:11:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 9 β learn more
+ 10 β
+ > 11 β learn more
+ β ^^^^^^^^^^^^^^^^^^^^^^
+ 12 β
+ 13 β learn more.
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:13:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 11 β learn more
+ 12 β
+ > 13 β learn more.
+ β ^^^^^^^^^^^^^^^^^^
+ 14 β
+ 15 β learn more?
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:15:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 13 β learn more.
+ 14 β
+ > 15 β learn more?
+ β ^^^^^^^^^^^^^^^^^^
+ 16 β
+ 17 β learn more,
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:17:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 15 β learn more?
+ 16 β
+ > 17 β learn more,
+ β ^^^^^^^^^^^^^^^^^^
+ 18 β
+ 19 β learn more!
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:19:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 17 β learn more,
+ 18 β
+ > 19 β learn more!
+ β ^^^^^^^^^^^^^^^^^^
+ 20 β
+ 21 β learn more;
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:21:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 19 β learn more!
+ 20 β
+ > 21 β learn more;
+ β ^^^^^^^^^^^^^^^^^^
+ 22 β
+ 23 β learn more:
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:23:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 21 β learn more;
+ 22 β
+ > 23 β learn more:
+ β ^^^^^^^^^^^^^^^^^^
+ 24 β
+ 25 β link
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:25:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 23 β learn more:
+ 24 β
+ > 25 β link
+ β ^^^^^^^^^^^
+ 26 β
+ 27 β a link
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:27:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 25 β link
+ 26 β
+ > 27 β a link
+ β ^^^^^^^^^^^^^
+ 28 β
+ 29 β something
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:29:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 27 β a link
+ 28 β
+ > 29 β something
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 30 β
+ 31 β a link
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:31:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 29 β something
+ 30 β
+ > 31 β a link
+ β ^^^^^^^^^^^^^^^
+ 32 β
+ 33 β a link
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:33:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 31 β a link
+ 32 β
+ > 33 β a link
+ β ^^^^^^^^^^^^^^^^^^^^
+ 34 β
+ 35 β a link
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:35:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 33 β a link
+ 34 β
+ > 35 β a link
+ β ^^^^^^^^^^^^^^^^^^^^
+ 36 β
+ 37 β click here
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:37:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 35 β a link
+ 36 β
+ > 37 β click here
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 38 β
+ 39 β click here
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:39:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 37 β click here
+ 38 β
+ > 39 β click here
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 40 β
+ 41 β more textlearn more
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:41:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 39 β click here
+ 40 β
+ > 41 β more textlearn more
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 42 β
+ 43 β more textlearn more
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:43:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 41 β more textlearn more
+ 42 β
+ > 43 β more textlearn more
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 44 β
+ 45 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:45:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 43 β more textlearn more
+ 44 β
+ > 45 β
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 46 β
+ 47 β click here
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:47:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 45 β
+ 46 β
+ > 47 β click here
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 48 β
+ 49 β click here
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.html:49:1 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 47 β click here
+ 48 β
+ > 49 β click here
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 50 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/valid.html b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/valid.html
new file mode 100644
index 000000000000..196755756e0c
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/valid.html
@@ -0,0 +1,8 @@
+/* should not generate diagnostics */
+documentation
+
+click here
+
+click here
+
+
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/valid.html.snap b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/valid.html.snap
new file mode 100644
index 000000000000..e508049e9d92
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/valid.html.snap
@@ -0,0 +1,16 @@
+---
+source: crates/biome_html_analyze/tests/spec_tests.rs
+expression: valid.html
+---
+# Input
+```html
+/* should not generate diagnostics */
+documentation
+
+click here
+
+click here
+
+
+
+```
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.html b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.html
new file mode 100644
index 000000000000..7df1e6c9c78e
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.html
@@ -0,0 +1,2 @@
+/* should generate diagnostics */
+a disallowed word
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.html.snap b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.html.snap
new file mode 100644
index 000000000000..0421c74e6d0f
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.html.snap
@@ -0,0 +1,26 @@
+---
+source: crates/biome_html_analyze/tests/spec_tests.rs
+expression: invalid.html
+---
+# Input
+```html
+/* should generate diagnostics */
+a disallowed word
+
+```
+
+# Diagnostics
+```
+invalid.html:2:1 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 1 β /* should generate diagnostics */
+ > 2 β a disallowed word
+ β ^^^^^^^^^^^^^^^^^^^^^^^^
+ 3 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.options.json b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.options.json
new file mode 100644
index 000000000000..1a5640867583
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.options.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "../../../../../../../packages/@biomejs/biome/configuration_schema.json",
+ "linter": {
+ "rules": {
+ "nursery": {
+ "noAmbiguousAnchorText": {
+ "level": "on",
+ "options": {
+ "words": [
+ "a disallowed word"
+ ]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.html b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.html
new file mode 100644
index 000000000000..bab7b1f01d6e
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.html
@@ -0,0 +1,2 @@
+/* should not generate diagnostics */
+click here
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.html.snap b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.html.snap
new file mode 100644
index 000000000000..6d37d63ce5bf
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.html.snap
@@ -0,0 +1,10 @@
+---
+source: crates/biome_html_analyze/tests/spec_tests.rs
+expression: valid.html
+---
+# Input
+```html
+/* should not generate diagnostics */
+click here
+
+```
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.options.json b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.options.json
new file mode 100644
index 000000000000..7b821b9baefc
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.options.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "../../../../../../../packages/@biomejs/biome/configuration_schema.json",
+ "linter": {
+ "rules": {
+ "nursery": {
+ "noAmbiguousAnchorText": {
+ "level": "on",
+ "options": {
+ "words": [
+ "disabling the defaults"
+ ]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/crates/biome_html_syntax/src/element_ext.rs b/crates/biome_html_syntax/src/element_ext.rs
index ed51618c4d13..50a35513284d 100644
--- a/crates/biome_html_syntax/src/element_ext.rs
+++ b/crates/biome_html_syntax/src/element_ext.rs
@@ -1,6 +1,7 @@
use crate::{
- AnyHtmlElement, AstroEmbeddedContent, HtmlAttribute, HtmlElement, HtmlEmbeddedContent,
- HtmlSelfClosingElement, HtmlSyntaxToken, HtmlTagName, ScriptType, inner_string_text,
+ AnyHtmlElement, AstroEmbeddedContent, HtmlAttribute, HtmlAttributeList, HtmlElement,
+ HtmlEmbeddedContent, HtmlOpeningElement, HtmlSelfClosingElement, HtmlSyntaxToken, HtmlTagName,
+ ScriptType, inner_string_text,
};
use biome_rowan::{AstNodeList, SyntaxResult, TokenText, declare_node_union};
@@ -88,25 +89,29 @@ impl HtmlSelfClosingElement {
}
}
+impl HtmlOpeningElement {
+ pub fn find_attribute_by_name(&self, name_to_lookup: &str) -> Option {
+ self.attributes().iter().find_map(|attr| {
+ let attribute = attr.as_html_attribute()?;
+ let name = attribute.name().ok()?;
+ let name_token = name.value_token().ok()?;
+ if name_token
+ .text_trimmed()
+ .eq_ignore_ascii_case(name_to_lookup)
+ {
+ Some(attribute.clone())
+ } else {
+ None
+ }
+ })
+ }
+}
+
impl HtmlElement {
pub fn find_attribute_by_name(&self, name_to_lookup: &str) -> Option {
self.opening_element()
.ok()?
- .attributes()
- .iter()
- .find_map(|attr| {
- let attribute = attr.as_html_attribute()?;
- let name = attribute.name().ok()?;
- let name_token = name.value_token().ok()?;
- if name_token
- .text_trimmed()
- .eq_ignore_ascii_case(name_to_lookup)
- {
- Some(attribute.clone())
- } else {
- None
- }
- })
+ .find_attribute_by_name(name_to_lookup)
}
pub fn is_javascript_tag(&self) -> bool {
@@ -225,6 +230,48 @@ impl HtmlTagName {
}
}
+declare_node_union! {
+ pub AnyHtmlTagElement = HtmlOpeningElement | HtmlSelfClosingElement
+}
+
+impl AnyHtmlTagElement {
+ pub fn name(&self) -> SyntaxResult {
+ match self {
+ Self::HtmlOpeningElement(element) => element.name(),
+ Self::HtmlSelfClosingElement(element) => element.name(),
+ }
+ }
+
+ pub fn attributes(&self) -> HtmlAttributeList {
+ match self {
+ Self::HtmlOpeningElement(element) => element.attributes(),
+ Self::HtmlSelfClosingElement(element) => element.attributes(),
+ }
+ }
+
+ pub fn name_value_token(&self) -> SyntaxResult {
+ self.name()?.value_token()
+ }
+
+ pub fn find_attribute_by_name(&self, name_to_lookup: &str) -> Option {
+ match self {
+ Self::HtmlOpeningElement(element) => element.find_attribute_by_name(name_to_lookup),
+ Self::HtmlSelfClosingElement(element) => element.find_attribute_by_name(name_to_lookup),
+ }
+ }
+
+ pub fn has_truthy_attribute(&self, name_to_lookup: &str) -> bool {
+ self.find_attribute_by_name(name_to_lookup)
+ .is_some_and(|attribute| {
+ attribute
+ .initializer()
+ .and_then(|init| init.value().ok())
+ .and_then(|value| value.string_value())
+ .is_none_or(|value| value != "false")
+ })
+ }
+}
+
#[cfg(test)]
mod tests {
use biome_html_factory::syntax::HtmlElement;
diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs
index a846d45dbb12..ca59a4210045 100644
--- a/crates/biome_js_analyze/src/lint/nursery.rs
+++ b/crates/biome_js_analyze/src/lint/nursery.rs
@@ -3,6 +3,7 @@
//! Generated file, do not edit by hand, see `xtask/codegen`
use biome_analyze::declare_lint_group;
+pub mod no_ambiguous_anchor_text;
pub mod no_continue;
pub mod no_deprecated_imports;
pub mod no_duplicated_spread_props;
@@ -51,4 +52,4 @@ pub mod use_sorted_classes;
pub mod use_spread;
pub mod use_vue_define_macros_order;
pub mod use_vue_multi_word_component_names;
-declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_duplicated_spread_props :: NoDuplicatedSpreadProps , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , self :: no_floating_promises :: NoFloatingPromises , self :: no_for_in :: NoForIn , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_leaked_render :: NoLeakedRender , self :: no_misused_promises :: NoMisusedPromises , self :: no_multi_str :: NoMultiStr , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_proto :: NoProto , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_script_url :: NoScriptUrl , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , self :: no_undeclared_env_vars :: NoUndeclaredEnvVars , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: no_vue_setup_props_reactivity_loss :: NoVueSetupPropsReactivityLoss , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_await_thenable :: UseAwaitThenable , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_destructuring :: UseDestructuring , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_regexp_exec :: UseRegexpExec , self :: use_sorted_classes :: UseSortedClasses , self :: use_spread :: UseSpread , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } }
+declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_ambiguous_anchor_text :: NoAmbiguousAnchorText , self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_duplicated_spread_props :: NoDuplicatedSpreadProps , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , self :: no_floating_promises :: NoFloatingPromises , self :: no_for_in :: NoForIn , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_leaked_render :: NoLeakedRender , self :: no_misused_promises :: NoMisusedPromises , self :: no_multi_str :: NoMultiStr , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_proto :: NoProto , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_script_url :: NoScriptUrl , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , self :: no_undeclared_env_vars :: NoUndeclaredEnvVars , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: no_vue_setup_props_reactivity_loss :: NoVueSetupPropsReactivityLoss , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_await_thenable :: UseAwaitThenable , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_destructuring :: UseDestructuring , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_regexp_exec :: UseRegexpExec , self :: use_sorted_classes :: UseSortedClasses , self :: use_spread :: UseSpread , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } }
diff --git a/crates/biome_js_analyze/src/lint/nursery/no_ambiguous_anchor_text.rs b/crates/biome_js_analyze/src/lint/nursery/no_ambiguous_anchor_text.rs
new file mode 100644
index 000000000000..37cb3b7b9ac4
--- /dev/null
+++ b/crates/biome_js_analyze/src/lint/nursery/no_ambiguous_anchor_text.rs
@@ -0,0 +1,194 @@
+use biome_analyze::{
+ Ast, QueryMatch, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
+};
+use biome_console::markup;
+use biome_js_syntax::{
+ AnyJsxChild, JsxElement, JsxOpeningElement, inner_string_text, jsx_ext::AnyJsxElement,
+};
+use biome_rowan::AstNode;
+use biome_rule_options::no_ambiguous_anchor_text::NoAmbiguousAnchorTextOptions;
+use biome_string_case::StrOnlyExtension;
+
+use crate::a11y::is_hidden_from_screen_reader;
+
+declare_lint_rule! {
+ /// Disallow ambiguous anchor descriptions.
+ ///
+ /// Enforces values are not exact matches for the phrases "click here", "here", "link", "a link", or "learn more".
+ /// Screen readers announce tags as links/interactive, but rely on values for context.
+ /// Ambiguous anchor descriptions do not provide sufficient context for users.
+ ///
+ /// ## Examples
+ ///
+ /// ### Invalid
+ ///
+ /// ```jsx,expect_diagnostic
+ /// const Invalid = () => learn more;
+ /// ```
+ ///
+ /// ### Valid
+ ///
+ /// ```jsx
+ /// const Valid = () => documentation;
+ /// ```
+ ///
+ /// ## Options
+ ///
+ /// ### `words`
+ ///
+ /// The words option allows users to modify the strings that can be checked for in the anchor text. Useful for specifying other words in other languages.
+ ///
+ /// Default `["click here", "here", "link", "a link", "learn more"]`
+ ///
+ /// ```json,options
+ /// {
+ /// "options": {
+ /// "words": ["click this"]
+ /// }
+ /// }
+ /// ```
+ ///
+ /// #### Invalid
+ ///
+ /// ```jsx,expect_diagnostic,use_options
+ /// const Invalid = () => click this;
+ /// ```
+ ///
+ pub NoAmbiguousAnchorText {
+ version: "next",
+ name: "noAmbiguousAnchorText",
+ language: "js",
+ recommended: false,
+ sources: &[RuleSource::EslintJsxA11y("anchor-ambiguous-text").same()],
+ }
+}
+
+impl Rule for NoAmbiguousAnchorText {
+ type Query = Ast;
+ type State = ();
+ type Signals = Option;
+ type Options = NoAmbiguousAnchorTextOptions;
+
+ fn run(ctx: &RuleContext) -> Self::Signals {
+ let binding = ctx.query();
+ let words = ctx.options().words();
+
+ let name = binding.name().ok()?;
+ let jsx_name = name.as_jsx_name()?;
+ let value_token = jsx_name.value_token().ok()?;
+ if value_token.text_trimmed() != "a" {
+ return None;
+ }
+
+ let parent = JsxElement::cast(binding.syntax().parent()?)?;
+ let text = get_accessible_child_text(&parent);
+
+ if words.contains(&text) {
+ return Some(());
+ }
+
+ None
+ }
+
+ fn diagnostic(ctx: &RuleContext, _state: &Self::State) -> Option {
+ let node = ctx.query();
+ let parent = node.syntax().parent()?;
+ Some(
+ RuleDiagnostic::new(
+ rule_category!(),
+ parent.text_range(),
+ markup! {
+ "No ambiguous anchor descriptions allowed."
+ },
+ )
+ .note(markup! {
+ "Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users."
+ }),
+ )
+ }
+}
+
+fn get_aria_label(node: &AnyJsxElement) -> Option {
+ let attribute = node.attributes().find_by_name("aria-label")?;
+ let initializer = attribute.initializer()?;
+ let value = initializer.value().ok()?;
+ let text = value.as_jsx_string()?.inner_string_text().ok()?;
+
+ Some(text.to_string())
+}
+
+fn get_img_alt(node: &AnyJsxElement) -> Option {
+ let name = node.name().ok()?;
+ let jsx_name = name.as_jsx_name()?;
+ let value_token = jsx_name.value_token().ok()?;
+ if value_token.text_trimmed() != "img" {
+ return None;
+ }
+
+ let attribute = node.attributes().find_by_name("alt")?;
+ let initializer = attribute.initializer()?;
+ let value = initializer.value().ok()?;
+ let jsx_string = value.as_jsx_string()?;
+ let text = jsx_string.inner_string_text().ok()?;
+
+ Some(text.to_string())
+}
+
+fn standardize_space_and_case(input: &str) -> String {
+ input
+ .chars()
+ .filter(|c| !matches!(c, ',' | '.' | '?' | 'ΒΏ' | '!' | 'β½' | 'Β‘' | ';' | ':'))
+ .collect::()
+ .to_lowercase_cow()
+ .split_whitespace()
+ .collect::>()
+ .join(" ")
+}
+
+fn get_accessible_text(node: &AnyJsxElement) -> Option {
+ if is_hidden_from_screen_reader(node) {
+ return Some(String::new());
+ }
+
+ if let Some(aria_label) = get_aria_label(node) {
+ return Some(standardize_space_and_case(&aria_label));
+ }
+
+ if let Some(alt) = get_img_alt(node) {
+ return Some(standardize_space_and_case(&alt));
+ }
+
+ None
+}
+
+fn get_accessible_child_text(node: &JsxElement) -> String {
+ if let Ok(opening) = node.opening_element() {
+ let any_jsx_element: AnyJsxElement = opening.clone().into();
+ if let Some(accessible_text) = get_accessible_text(&any_jsx_element) {
+ return accessible_text;
+ }
+ };
+
+ let raw_child_text = node
+ .children()
+ .into_iter()
+ .map(|child| match child {
+ AnyJsxChild::JsxText(element) => {
+ if let Ok(value_token) = element.value_token() {
+ inner_string_text(&value_token).to_string()
+ } else {
+ String::new()
+ }
+ }
+ AnyJsxChild::JsxElement(element) => get_accessible_child_text(&element),
+ AnyJsxChild::JsxSelfClosingElement(element) => {
+ let any_jsx_element: AnyJsxElement = element.clone().into();
+ get_accessible_text(&any_jsx_element).unwrap_or_default()
+ }
+ _ => String::new(),
+ })
+ .collect::>()
+ .join(" ");
+
+ standardize_space_and_case(&raw_child_text)
+}
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/invalid.jsx
new file mode 100644
index 000000000000..8f4457e2bca5
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/invalid.jsx
@@ -0,0 +1,100 @@
+/* should generate diagnostics */
+const Invalid1 = () => {
+ return here;
+}
+
+const Invalid2 = () => {
+ return HERE;
+}
+
+const Invalid3 = () => {
+ return click here;
+}
+
+const Invalid4 = () => {
+ return learn more;
+}
+
+const Invalid5 = () => {
+ return learn more;
+}
+
+const Invalid6 = () => {
+ return learn more.;
+}
+
+const Invalid7 = () => {
+ return learn more?;
+}
+
+const Invalid8 = () => {
+ return learn more,;
+}
+
+const Invalid9 = () => {
+ return learn more!;
+}
+
+const Invalid10 = () => {
+ return learn more;;
+}
+
+const Invalid11 = () => {
+ return learn more:;
+}
+
+const Invalid12 = () => {
+ return link;
+}
+
+const Invalid13 = () => {
+ return a link;
+}
+
+const Invalid14 = () => {
+ return something;
+}
+
+const Invalid15 = () => {
+ return a link ;
+}
+
+const Invalid16 = () => {
+ return a link;
+}
+
+const Invalid17 = () => {
+ return a link;
+}
+
+const Invalid18 = () => {
+ return click here;
+}
+
+const Invalid19 = () => {
+ return click here;
+}
+
+const Invalid20 = () => {
+ return more textlearn more;
+}
+
+const Invalid21 = () => {
+ return more textlearn more;
+}
+
+const Invalid22 = () => {
+ return
;
+}
+
+const Invalid23 = () => {
+ return click here;
+}
+
+const Invalid24 = () => {
+ return click here;
+}
+
+const Invalid25 = () => {
+ return click here;
+}
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/invalid.jsx.snap
new file mode 100644
index 000000000000..3c9c16cd1f88
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/invalid.jsx.snap
@@ -0,0 +1,510 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid.jsx
+---
+# Input
+```jsx
+/* should generate diagnostics */
+const Invalid1 = () => {
+ return here;
+}
+
+const Invalid2 = () => {
+ return HERE;
+}
+
+const Invalid3 = () => {
+ return click here;
+}
+
+const Invalid4 = () => {
+ return learn more;
+}
+
+const Invalid5 = () => {
+ return learn more;
+}
+
+const Invalid6 = () => {
+ return learn more.;
+}
+
+const Invalid7 = () => {
+ return learn more?;
+}
+
+const Invalid8 = () => {
+ return learn more,;
+}
+
+const Invalid9 = () => {
+ return learn more!;
+}
+
+const Invalid10 = () => {
+ return learn more;;
+}
+
+const Invalid11 = () => {
+ return learn more:;
+}
+
+const Invalid12 = () => {
+ return link;
+}
+
+const Invalid13 = () => {
+ return a link;
+}
+
+const Invalid14 = () => {
+ return something;
+}
+
+const Invalid15 = () => {
+ return a link ;
+}
+
+const Invalid16 = () => {
+ return a link;
+}
+
+const Invalid17 = () => {
+ return a link;
+}
+
+const Invalid18 = () => {
+ return click here;
+}
+
+const Invalid19 = () => {
+ return click here;
+}
+
+const Invalid20 = () => {
+ return more textlearn more;
+}
+
+const Invalid21 = () => {
+ return more textlearn more;
+}
+
+const Invalid22 = () => {
+ return
;
+}
+
+const Invalid23 = () => {
+ return click here;
+}
+
+const Invalid24 = () => {
+ return click here;
+}
+
+const Invalid25 = () => {
+ return click here;
+}
+
+```
+
+# Diagnostics
+```
+invalid.jsx:3:9 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 1 β /* should generate diagnostics */
+ 2 β const Invalid1 = () => {
+ > 3 β return here;
+ β ^^^^^^^^^^^
+ 4 β }
+ 5 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:7:9 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 6 β const Invalid2 = () => {
+ > 7 β return HERE;
+ β ^^^^^^^^^^^
+ 8 β }
+ 9 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:11:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 10 β const Invalid3 = () => {
+ > 11 β return click here;
+ β ^^^^^^^^^^^^^^^^^
+ 12 β }
+ 13 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:15:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 14 β const Invalid4 = () => {
+ > 15 β return learn more;
+ β ^^^^^^^^^^^^^^^^^
+ 16 β }
+ 17 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:19:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 18 β const Invalid5 = () => {
+ > 19 β return learn more;
+ β ^^^^^^^^^^^^^^^^^^^^^^
+ 20 β }
+ 21 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:23:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 22 β const Invalid6 = () => {
+ > 23 β return learn more.;
+ β ^^^^^^^^^^^^^^^^^^
+ 24 β }
+ 25 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:27:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 26 β const Invalid7 = () => {
+ > 27 β return learn more?;
+ β ^^^^^^^^^^^^^^^^^^
+ 28 β }
+ 29 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:31:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 30 β const Invalid8 = () => {
+ > 31 β return learn more,;
+ β ^^^^^^^^^^^^^^^^^^
+ 32 β }
+ 33 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:35:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 34 β const Invalid9 = () => {
+ > 35 β return learn more!;
+ β ^^^^^^^^^^^^^^^^^^
+ 36 β }
+ 37 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:39:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 38 β const Invalid10 = () => {
+ > 39 β return learn more;;
+ β ^^^^^^^^^^^^^^^^^^
+ 40 β }
+ 41 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:43:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 42 β const Invalid11 = () => {
+ > 43 β return learn more:;
+ β ^^^^^^^^^^^^^^^^^^
+ 44 β }
+ 45 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:47:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 46 β const Invalid12 = () => {
+ > 47 β return link;
+ β ^^^^^^^^^^^
+ 48 β }
+ 49 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:51:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 50 β const Invalid13 = () => {
+ > 51 β return a link;
+ β ^^^^^^^^^^^^^
+ 52 β }
+ 53 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:55:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 54 β const Invalid14 = () => {
+ > 55 β return something;
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 56 β }
+ 57 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:59:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 58 β const Invalid15 = () => {
+ > 59 β return a link ;
+ β ^^^^^^^^^^^^^^^
+ 60 β }
+ 61 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:63:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 62 β const Invalid16 = () => {
+ > 63 β return a link;
+ β ^^^^^^^^^^^^^^^^^^^^
+ 64 β }
+ 65 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:67:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 66 β const Invalid17 = () => {
+ > 67 β return a link;
+ β ^^^^^^^^^^^^^^^^^^^^
+ 68 β }
+ 69 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:71:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 70 β const Invalid18 = () => {
+ > 71 β return click here;
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 72 β }
+ 73 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:75:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 74 β const Invalid19 = () => {
+ > 75 β return click here;
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 76 β }
+ 77 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:79:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 78 β const Invalid20 = () => {
+ > 79 β return more textlearn more;
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 80 β }
+ 81 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:83:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 82 β const Invalid21 = () => {
+ > 83 β return more textlearn more;
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 84 β }
+ 85 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:87:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 86 β const Invalid22 = () => {
+ > 87 β return
;
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 88 β }
+ 89 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:91:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 90 β const Invalid23 = () => {
+ > 91 β return click here;
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 92 β }
+ 93 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:95:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 94 β const Invalid24 = () => {
+ > 95 β return click here;
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 96 β }
+ 97 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
+
+```
+invalid.jsx:99:9 lint/nursery/noAmbiguousAnchorText ββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 98 β const Invalid25 = () => {
+ > 99 β return click here;
+ β ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 100 β }
+ 101 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/valid.jsx
new file mode 100644
index 000000000000..ff7f52696513
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/valid.jsx
@@ -0,0 +1,20 @@
+/* should not generate diagnostics */
+const Valid1 = () => {
+ return documentation;
+}
+
+const Valid2 = () => {
+ return ${here};
+}
+
+const Valid3 = () => {
+ return click here;
+}
+
+const Valid4 = () => {
+ return click here;
+}
+
+const Valid5 = () => {
+ return
;
+}
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/valid.jsx.snap
new file mode 100644
index 000000000000..5269326bd007
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/valid.jsx.snap
@@ -0,0 +1,28 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: valid.jsx
+---
+# Input
+```jsx
+/* should not generate diagnostics */
+const Valid1 = () => {
+ return documentation;
+}
+
+const Valid2 = () => {
+ return ${here};
+}
+
+const Valid3 = () => {
+ return click here;
+}
+
+const Valid4 = () => {
+ return click here;
+}
+
+const Valid5 = () => {
+ return
;
+}
+
+```
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.jsx
new file mode 100644
index 000000000000..8c37cb53b461
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.jsx
@@ -0,0 +1,4 @@
+/* should generate diagnostics */
+const Invalid = () => {
+ return a disallowed word;
+}
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.jsx.snap
new file mode 100644
index 000000000000..367a62483145
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.jsx.snap
@@ -0,0 +1,30 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid.jsx
+---
+# Input
+```jsx
+/* should generate diagnostics */
+const Invalid = () => {
+ return a disallowed word;
+}
+
+```
+
+# Diagnostics
+```
+invalid.jsx:3:9 lint/nursery/noAmbiguousAnchorText βββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ i No ambiguous anchor descriptions allowed.
+
+ 1 β /* should generate diagnostics */
+ 2 β const Invalid = () => {
+ > 3 β return a disallowed word;
+ β ^^^^^^^^^^^^^^^^^^^^^^^^
+ 4 β }
+ 5 β
+
+ i Ambiguous anchor descriptions do not provide sufficient context for screen reader users. Provide more context to these users.
+
+
+```
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.options.json b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.options.json
new file mode 100644
index 000000000000..1a5640867583
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/invalid.options.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "../../../../../../../packages/@biomejs/biome/configuration_schema.json",
+ "linter": {
+ "rules": {
+ "nursery": {
+ "noAmbiguousAnchorText": {
+ "level": "on",
+ "options": {
+ "words": [
+ "a disallowed word"
+ ]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.jsx
new file mode 100644
index 000000000000..6eecccca2224
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.jsx
@@ -0,0 +1,4 @@
+/* should not generate diagnostics */
+const Valid = () => {
+ return click here;
+}
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.jsx.snap
new file mode 100644
index 000000000000..53ead33df97c
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.jsx.snap
@@ -0,0 +1,12 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: valid.jsx
+---
+# Input
+```jsx
+/* should not generate diagnostics */
+const Valid = () => {
+ return click here;
+}
+
+```
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.options.json b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.options.json
new file mode 100644
index 000000000000..7b821b9baefc
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noAmbiguousAnchorText/words/valid.options.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "../../../../../../../packages/@biomejs/biome/configuration_schema.json",
+ "linter": {
+ "rules": {
+ "nursery": {
+ "noAmbiguousAnchorText": {
+ "level": "on",
+ "options": {
+ "words": [
+ "disabling the defaults"
+ ]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs
index 53ce9f652467..f240a977c58f 100644
--- a/crates/biome_rule_options/src/lib.rs
+++ b/crates/biome_rule_options/src/lib.rs
@@ -6,6 +6,7 @@ pub mod no_access_key;
pub mod no_accumulating_spread;
pub mod no_adjacent_spaces_in_regex;
pub mod no_alert;
+pub mod no_ambiguous_anchor_text;
pub mod no_approximative_numeric_constant;
pub mod no_arguments;
pub mod no_aria_hidden_on_focusable;
diff --git a/crates/biome_rule_options/src/no_ambiguous_anchor_text.rs b/crates/biome_rule_options/src/no_ambiguous_anchor_text.rs
new file mode 100644
index 000000000000..c2dd9a51aacd
--- /dev/null
+++ b/crates/biome_rule_options/src/no_ambiguous_anchor_text.rs
@@ -0,0 +1,25 @@
+use biome_deserialize_macros::{Deserializable, Merge};
+use serde::{Deserialize, Serialize};
+#[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)]
+#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
+#[serde(rename_all = "camelCase", deny_unknown_fields, default)]
+pub struct NoAmbiguousAnchorTextOptions {
+ /// It allows users to modify the strings that can be checked for in the anchor text. Useful for specifying other words in other languages
+ pub words: Option>,
+}
+
+impl NoAmbiguousAnchorTextOptions {
+ pub const DEFAULT_AMBIGUOUS_WORDS: [&str; 5] =
+ ["click here", "here", "link", "a link", "learn more"];
+
+ /// Returns [`Self::words`] if it is set.
+ /// Otherwise, returns [`Self::DEFAULT_AMBIGUOUS_WORDS`].
+ pub fn words(&self) -> Vec {
+ self.words.clone().unwrap_or_else(|| {
+ Self::DEFAULT_AMBIGUOUS_WORDS
+ .iter()
+ .map(|s| (*s).to_string())
+ .collect()
+ })
+ }
+}
diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts
index 6e9f257dc8ec..a9841808934d 100644
--- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts
+++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts
@@ -1857,6 +1857,11 @@ See
* A list of rules that belong to this group
*/
export interface Nursery {
+ /**
+ * Disallow ambiguous anchor descriptions.
+See
+ */
+ noAmbiguousAnchorText?: NoAmbiguousAnchorTextConfiguration;
/**
* Disallow continue statements.
See
@@ -3595,6 +3600,9 @@ export type UseValidTypeofConfiguration =
export type UseYieldConfiguration =
| RulePlainConfiguration
| RuleWithUseYieldOptions;
+export type NoAmbiguousAnchorTextConfiguration =
+ | RulePlainConfiguration
+ | RuleWithNoAmbiguousAnchorTextOptions;
export type NoContinueConfiguration =
| RulePlainConfiguration
| RuleWithNoContinueOptions;
@@ -5008,6 +5016,10 @@ export interface RuleWithUseYieldOptions {
level: RulePlainConfiguration;
options?: UseYieldOptions;
}
+export interface RuleWithNoAmbiguousAnchorTextOptions {
+ level: RulePlainConfiguration;
+ options?: NoAmbiguousAnchorTextOptions;
+}
export interface RuleWithNoContinueOptions {
level: RulePlainConfiguration;
options?: NoContinueOptions;
@@ -6365,6 +6377,12 @@ to a DOM element id.
export type UseValidForDirectionOptions = {};
export type UseValidTypeofOptions = {};
export type UseYieldOptions = {};
+export interface NoAmbiguousAnchorTextOptions {
+ /**
+ * It allows users to modify the strings that can be checked for in the anchor text. Useful for specifying other words in other languages
+ */
+ words?: string[];
+}
export type NoContinueOptions = {};
export type NoDeprecatedImportsOptions = {};
export type NoDuplicateDependenciesOptions = {};
@@ -7249,6 +7267,7 @@ export type Category =
| "lint/correctness/useValidForDirection"
| "lint/correctness/useValidTypeof"
| "lint/correctness/useYield"
+ | "lint/nursery/noAmbiguousAnchorText"
| "lint/nursery/noColorInvalidHex"
| "lint/nursery/noContinue"
| "lint/nursery/noDeprecatedImports"
diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json
index 34315eb44297..9f4f892be818 100644
--- a/packages/@biomejs/biome/configuration_schema.json
+++ b/packages/@biomejs/biome/configuration_schema.json
@@ -2474,6 +2474,24 @@
]
},
"NoAlertOptions": { "type": "object", "additionalProperties": false },
+ "NoAmbiguousAnchorTextConfiguration": {
+ "oneOf": [
+ { "$ref": "#/$defs/RulePlainConfiguration" },
+ { "$ref": "#/$defs/RuleWithNoAmbiguousAnchorTextOptions" }
+ ]
+ },
+ "NoAmbiguousAnchorTextOptions": {
+ "type": "object",
+ "properties": {
+ "words": {
+ "description": "It allows users to modify the strings that can be checked for in the anchor text. Useful for specifying other words in other languages",
+ "type": ["array", "null"],
+ "default": null,
+ "items": { "type": "string" }
+ }
+ },
+ "additionalProperties": false
+ },
"NoApproximativeNumericConstantConfiguration": {
"oneOf": [
{ "$ref": "#/$defs/RulePlainConfiguration" },
@@ -5123,6 +5141,13 @@
"description": "A list of rules that belong to this group",
"type": "object",
"properties": {
+ "noAmbiguousAnchorText": {
+ "description": "Disallow ambiguous anchor descriptions.\nSee ",
+ "anyOf": [
+ { "$ref": "#/$defs/NoAmbiguousAnchorTextConfiguration" },
+ { "type": "null" }
+ ]
+ },
"noContinue": {
"description": "Disallow continue statements.\nSee ",
"anyOf": [
@@ -6115,6 +6140,15 @@
"additionalProperties": false,
"required": ["level"]
},
+ "RuleWithNoAmbiguousAnchorTextOptions": {
+ "type": "object",
+ "properties": {
+ "level": { "$ref": "#/$defs/RulePlainConfiguration" },
+ "options": { "$ref": "#/$defs/NoAmbiguousAnchorTextOptions" }
+ },
+ "additionalProperties": false,
+ "required": ["level"]
+ },
"RuleWithNoApproximativeNumericConstantOptions": {
"type": "object",
"properties": {