diff --git a/.changeset/add-no-script-url-rule.md b/.changeset/add-no-script-url-rule.md
new file mode 100644
index 000000000000..6a0ccc52c112
--- /dev/null
+++ b/.changeset/add-no-script-url-rule.md
@@ -0,0 +1,10 @@
+---
+"@biomejs/biome": patch
+---
+Added the nursery rule [`noScriptUrl`](https://biomejs.dev/linter/rules/no-script-url/).
+
+This rule disallows the use of `javascript:` URLs, which are considered a form of `eval` and can pose security risks such as XSS vulnerabilities.
+
+```jsx
+Click me
+```
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 fd4d22d100c6..716706ff1210 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
@@ -9,6 +9,18 @@ pub(crate) fn migrate_eslint_any_rule(
results: &mut eslint_to_biome::MigrationResults,
) -> bool {
match eslint_name {
+ "@eslint-react/dom-no-script-url" => {
+ 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_script_url
+ .get_or_insert(Default::default());
+ rule.set_level(rule.level().max(rule_severity.into()));
+ }
"@eslint-react/no-forward-ref" => {
if !options.include_nursery {
results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery);
@@ -1897,6 +1909,18 @@ pub(crate) fn migrate_eslint_any_rule(
.get_or_insert(Default::default());
rule.set_level(rule.level().max(rule_severity.into()));
}
+ "no-script-url" => {
+ 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_script_url
+ .get_or_insert(Default::default());
+ rule.set_level(rule.level().max(rule_severity.into()));
+ }
"no-secrets/no-secrets" => {
if !options.include_inspired {
results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Inspired);
@@ -2373,6 +2397,18 @@ pub(crate) fn migrate_eslint_any_rule(
.get_or_insert(Default::default());
rule.set_level(rule.level().max(rule_severity.into()));
}
+ "qwik/jsx-no-script-url" => {
+ 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_script_url
+ .get_or_insert(Default::default());
+ rule.set_level(rule.level().max(rule_severity.into()));
+ }
"qwik/no-react-props" => {
let group = rules.suspicious.get_or_insert_with(Default::default);
let rule = group
@@ -2585,6 +2621,18 @@ pub(crate) fn migrate_eslint_any_rule(
.get_or_insert(Default::default());
rule.set_level(rule.level().max(rule_severity.into()));
}
+ "react/jsx-no-script-url" => {
+ 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_script_url
+ .get_or_insert(Default::default());
+ rule.set_level(rule.level().max(rule_severity.into()));
+ }
"react/jsx-no-target-blank" => {
if !options.include_inspired {
results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Inspired);
@@ -2721,6 +2769,18 @@ pub(crate) fn migrate_eslint_any_rule(
.get_or_insert(Default::default());
rule.set_level(rule.level().max(rule_severity.into()));
}
+ "solid/jsx-no-script-url" => {
+ 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_script_url
+ .get_or_insert(Default::default());
+ rule.set_level(rule.level().max(rule_severity.into()));
+ }
"solid/no-destructure" => {
if !options.include_inspired {
results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Inspired);
diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs
index 802d93233676..3e1d311626f1 100644
--- a/crates/biome_configuration/src/analyzer/linter/rules.rs
+++ b/crates/biome_configuration/src/analyzer/linter/rules.rs
@@ -257,6 +257,7 @@ pub enum RuleName {
NoRestrictedGlobals,
NoRestrictedImports,
NoRestrictedTypes,
+ NoScriptUrl,
NoSecrets,
NoSelfAssign,
NoSelfCompare,
@@ -658,6 +659,7 @@ impl RuleName {
Self::NoRestrictedGlobals => "noRestrictedGlobals",
Self::NoRestrictedImports => "noRestrictedImports",
Self::NoRestrictedTypes => "noRestrictedTypes",
+ Self::NoScriptUrl => "noScriptUrl",
Self::NoSecrets => "noSecrets",
Self::NoSelfAssign => "noSelfAssign",
Self::NoSelfCompare => "noSelfCompare",
@@ -1055,6 +1057,7 @@ impl RuleName {
Self::NoRestrictedGlobals => RuleGroup::Style,
Self::NoRestrictedImports => RuleGroup::Style,
Self::NoRestrictedTypes => RuleGroup::Style,
+ Self::NoScriptUrl => RuleGroup::Nursery,
Self::NoSecrets => RuleGroup::Security,
Self::NoSelfAssign => RuleGroup::Correctness,
Self::NoSelfCompare => RuleGroup::Suspicious,
@@ -1461,6 +1464,7 @@ impl std::str::FromStr for RuleName {
"noRestrictedGlobals" => Ok(Self::NoRestrictedGlobals),
"noRestrictedImports" => Ok(Self::NoRestrictedImports),
"noRestrictedTypes" => Ok(Self::NoRestrictedTypes),
+ "noScriptUrl" => Ok(Self::NoScriptUrl),
"noSecrets" => Ok(Self::NoSecrets),
"noSelfAssign" => Ok(Self::NoSelfAssign),
"noSelfCompare" => Ok(Self::NoSelfCompare),
@@ -4820,7 +4824,7 @@ 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 __proto__ 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 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 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 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 __proto__ 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 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] = &[
@@ -4842,6 +4846,7 @@ impl Nursery {
"noParametersOnlyUsedInRecursion",
"noProto",
"noReactForwardRef",
+ "noScriptUrl",
"noShadow",
"noSyncScripts",
"noTernary",
@@ -4887,7 +4892,8 @@ impl Nursery {
];
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[48]),
+ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]),
+ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]),
];
const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]),
@@ -4950,6 +4956,7 @@ impl Nursery {
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[57]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[58]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[59]),
+ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[60]),
];
}
impl RuleGroupExt for Nursery {
@@ -5051,216 +5058,221 @@ impl RuleGroupExt for Nursery {
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]));
}
- 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[18]));
}
- 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[19]));
}
- 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[20]));
}
- if let Some(rule) = self.no_unknown_attribute.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[21]));
}
- 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[22]));
}
- 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[23]));
}
- 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[24]));
}
- 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[25]));
}
- 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[26]));
}
- 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[27]));
}
- 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[28]));
}
- 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[29]));
}
- 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[30]));
}
- 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[31]));
}
- 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[32]));
}
- 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[33]));
}
- 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[34]));
}
- 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[35]));
}
- 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[36]));
}
- 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[37]));
}
- 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[38]));
}
- 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[39]));
}
- 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[40]));
}
- 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[41]));
}
- 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[42]));
}
- 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[43]));
}
- 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[44]));
}
- 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[45]));
}
- 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[46]));
}
- 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[47]));
}
- 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[48]));
}
- 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[49]));
}
- 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[50]));
}
- 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[51]));
}
- 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[52]));
}
- 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[53]));
}
- 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[54]));
}
- 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[55]));
}
- 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[56]));
}
- 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[57]));
}
- 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[58]));
}
- 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[59]));
}
+ 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[60]));
+ }
index_set
}
fn get_disabled_rules(&self) -> FxHashSet> {
@@ -5355,216 +5367,221 @@ impl RuleGroupExt for Nursery {
{
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]));
}
- 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[18]));
}
- 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[19]));
}
- 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[20]));
}
- if let Some(rule) = self.no_unknown_attribute.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[21]));
}
- 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[22]));
}
- 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[23]));
}
- 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[24]));
}
- 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[25]));
}
- 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[26]));
}
- 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[27]));
}
- 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[28]));
}
- 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[29]));
}
- 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[30]));
}
- 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[31]));
}
- 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[32]));
}
- 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[33]));
}
- 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[34]));
}
- 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[35]));
}
- 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[36]));
}
- 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[37]));
}
- 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[38]));
}
- 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[39]));
}
- 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[40]));
}
- 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[41]));
}
- 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[42]));
}
- 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[43]));
}
- 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[44]));
}
- 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[45]));
}
- 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[46]));
}
- 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[47]));
}
- 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[48]));
}
- 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[49]));
}
- 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[50]));
}
- 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[51]));
}
- 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[52]));
}
- 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[53]));
}
- 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[54]));
}
- 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[55]));
}
- 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[56]));
}
- 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[57]));
}
- 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[58]));
}
- 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[59]));
}
+ 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[60]));
+ }
index_set
}
#[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"]
@@ -5667,6 +5684,10 @@ impl RuleGroupExt for Nursery {
.no_react_forward_ref
.as_ref()
.map(|conf| (conf.level(), conf.get_options())),
+ "noScriptUrl" => self
+ .no_script_url
+ .as_ref()
+ .map(|conf| (conf.level(), conf.get_options())),
"noShadow" => self
.no_shadow
.as_ref()
@@ -5861,6 +5882,7 @@ impl From for Nursery {
no_parameters_only_used_in_recursion: Some(value.into()),
no_proto: Some(value.into()),
no_react_forward_ref: Some(value.into()),
+ no_script_url: Some(value.into()),
no_shadow: Some(value.into()),
no_sync_scripts: Some(value.into()),
no_ternary: Some(value.into()),
diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs
index 654e2a62845f..cd8771bf19eb 100644
--- a/crates/biome_diagnostics_categories/src/categories.rs
+++ b/crates/biome_diagnostics_categories/src/categories.rs
@@ -253,6 +253,7 @@ define_categories! {
"lint/security/noDangerouslySetInnerHtml": "https://biomejs.dev/linter/rules/no-dangerously-set-inner-html",
"lint/security/noDangerouslySetInnerHtmlWithChildren": "https://biomejs.dev/linter/rules/no-dangerously-set-inner-html-with-children",
"lint/security/noGlobalEval": "https://biomejs.dev/linter/rules/no-global-eval",
+ "lint/nursery/noScriptUrl": "https://biomejs.dev/linter/rules/no-script-url",
"lint/security/noSecrets": "https://biomejs.dev/linter/rules/no-secrets",
"lint/style/noCommonJs": "https://biomejs.dev/linter/rules/no-common-js",
"lint/style/noDefaultExport": "https://biomejs.dev/linter/rules/no-default-export",
diff --git a/crates/biome_html_analyze/src/lint/nursery.rs b/crates/biome_html_analyze/src/lint/nursery.rs
index 57159a909a01..ef733037648e 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_script_url;
pub mod no_sync_scripts;
pub mod no_vue_v_if_with_v_for;
pub mod use_vue_hyphenated_attributes;
@@ -13,4 +14,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_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_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_script_url.rs b/crates/biome_html_analyze/src/lint/nursery/no_script_url.rs
new file mode 100644
index 000000000000..d844fd117921
--- /dev/null
+++ b/crates/biome_html_analyze/src/lint/nursery/no_script_url.rs
@@ -0,0 +1,106 @@
+use biome_analyze::{
+ Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
+};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_html_syntax::{AnyHtmlAttributeInitializer, HtmlOpeningElement, inner_string_text};
+use biome_rowan::{AstNode, TextRange};
+use biome_rule_options::no_script_url::NoScriptUrlOptions;
+use biome_string_case::StrOnlyExtension;
+
+declare_lint_rule! {
+ /// Disallow `javascript:` URLs in HTML.
+ ///
+ /// Using `javascript:` URLs is considered a form of `eval` and can be a security risk.
+ /// These URLs can execute arbitrary JavaScript code, which can lead to cross-site scripting (XSS) vulnerabilities.
+ ///
+ /// ## Examples
+ ///
+ /// ### Invalid
+ ///
+ /// ```html,expect_diagnostic
+ /// Click me
+ /// ```
+ ///
+ /// ```html,expect_diagnostic
+ /// Click me
+ /// ```
+ ///
+ /// ### Valid
+ ///
+ /// ```html
+ /// Click me
+ /// Click me
+ /// Click me
+ /// Not a real href
+ /// ```
+ ///
+ pub NoScriptUrl {
+ version: "next",
+ name: "noScriptUrl",
+ language: "html",
+ // Show equivalents from related ecosystems
+ sources: &[
+ RuleSource::Eslint("no-script-url").same(),
+ RuleSource::EslintReact("jsx-no-script-url").same(),
+ RuleSource::EslintQwik("jsx-no-script-url").same(),
+ RuleSource::EslintSolid("jsx-no-script-url").same(),
+ RuleSource::EslintReactXyz("dom-no-script-url").same(),
+ ],
+ recommended: true,
+ severity: Severity::Error,
+ }
+}
+
+impl Rule for NoScriptUrl {
+ type Query = Ast;
+ type State = TextRange;
+ type Signals = Option;
+ type Options = NoScriptUrlOptions;
+
+ fn run(ctx: &RuleContext) -> Option {
+ let element = ctx.query();
+
+ // Only check elements for HTML (unlike JSX where components/custom elements exist)
+ let name = element.name().ok()?;
+ let token = name.value_token().ok()?;
+ let tag = token.text_trimmed();
+ if !tag.eq_ignore_ascii_case("a") {
+ return None;
+ }
+
+ let attrs = element.attributes();
+ let attr = attrs.find_by_name("href")?;
+ let initializer = attr.initializer()?;
+ let value = initializer.value().ok()?;
+
+ if let AnyHtmlAttributeInitializer::HtmlString(html_string) = value
+ && let Ok(token) = html_string.value_token()
+ {
+ let inner = inner_string_text(&token);
+ if inner.trim().to_lowercase_cow().starts_with("javascript:") {
+ return Some(initializer.range());
+ }
+ }
+
+ None
+ }
+
+ fn diagnostic(_ctx: &RuleContext, range: &Self::State) -> Option {
+ Some(
+ RuleDiagnostic::new(
+ rule_category!(),
+ *range,
+ markup! {
+ "Avoid using ""javascript:"" URLs, as they can be a security risk."
+ },
+ )
+ .note(markup! {
+ "Using ""javascript:"" URLs can lead to security vulnerabilities such as cross-site scripting (XSS)."
+ })
+ .note(markup! {
+ "Consider using regular URLs, or if you need to handle click events, use event handlers instead."
+ }),
+ )
+ }
+}
diff --git a/crates/biome_html_analyze/tests/spec_tests.rs b/crates/biome_html_analyze/tests/spec_tests.rs
index a979683d675d..5eceac7540d2 100644
--- a/crates/biome_html_analyze/tests/spec_tests.rs
+++ b/crates/biome_html_analyze/tests/spec_tests.rs
@@ -13,8 +13,8 @@ use camino::Utf8Path;
use std::ops::Deref;
use std::{fs::read_to_string, slice};
-tests_macros::gen_tests! {"tests/specs/**/*.{html,vue,json,jsonc}", crate::run_test, "module"}
-tests_macros::gen_tests! {"tests/suppression/**/*.{html,vue,json,jsonc}", crate::run_suppression_test, "module"}
+tests_macros::gen_tests! {"tests/specs/**/*.{html,vue,astro,svelte,json,jsonc}", crate::run_test, "module"}
+tests_macros::gen_tests! {"tests/suppression/**/*.{html,vue,astro,svelte,json,jsonc}", crate::run_suppression_test, "module"}
fn run_test(input: &'static str, _: &str, _: &str, _: &str) {
register_leak_checker();
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.astro b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.astro
new file mode 100644
index 000000000000..077382fa6db5
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.astro
@@ -0,0 +1,7 @@
+---
+// Astro invalid cases - should trigger the rule
+---
+
+Void
+Prompt
+Uppercase
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.astro.snap b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.astro.snap
new file mode 100644
index 000000000000..30b86209b708
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.astro.snap
@@ -0,0 +1,71 @@
+---
+source: crates/biome_html_analyze/tests/spec_tests.rs
+expression: invalid.astro
+---
+# Input
+```html
+---
+// Astro invalid cases - should trigger the rule
+---
+
+Void
+Prompt
+Uppercase
+
+```
+
+# Diagnostics
+```
+invalid.astro:5:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 3 │ ---
+ 4 │
+ > 5 │ Void
+ │ ^^^^^^^^^^^^^^^^^^^^^
+ 6 │ Prompt
+ 7 │ Uppercase
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.astro:6:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 5 │ Void
+ > 6 │ Prompt
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 7 │ Uppercase
+ 8 │
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.astro:7:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 5 │ Void
+ 6 │ Prompt
+ > 7 │ Uppercase
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 8 │
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.html b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.html
new file mode 100644
index 000000000000..2c25c53f23a3
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.html
@@ -0,0 +1,9 @@
+
+
+Link
+
+Link
+
+Link
+
+Link
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.html.snap b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.html.snap
new file mode 100644
index 000000000000..9b84c3d15018
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.html.snap
@@ -0,0 +1,93 @@
+---
+source: crates/biome_html_analyze/tests/spec_tests.rs
+expression: invalid.html
+---
+# Input
+```html
+
+
+Link
+
+Link
+
+Link
+
+Link
+
+```
+
+# Diagnostics
+```
+invalid.html:3:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 1 │
+ 2 │
+ > 3 │ Link
+ │ ^^^^^^^^^^^^^^^^^^^^^
+ 4 │
+ 5 │ Link
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.html:5:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 3 │ Link
+ 4 │
+ > 5 │ Link
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 6 │
+ 7 │ Link
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.html:7:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 5 │ Link
+ 6 │
+ > 7 │ Link
+ │ ^^^^^^^^^^^^^^^^^^^^^^
+ 8 │
+ 9 │ Link
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.html:9:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 7 │ Link
+ 8 │
+ > 9 │ Link
+ │ ^^^^^^^^^^^^^^^^^^^^^
+ 10 │
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.svelte b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.svelte
new file mode 100644
index 000000000000..2b1c9d105a77
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.svelte
@@ -0,0 +1,5 @@
+
+
+Void
+Confirm
+Uppercase
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.svelte.snap b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.svelte.snap
new file mode 100644
index 000000000000..c5f202eea123
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.svelte.snap
@@ -0,0 +1,69 @@
+---
+source: crates/biome_html_analyze/tests/spec_tests.rs
+expression: invalid.svelte
+---
+# Input
+```html
+
+
+Void
+Confirm
+Uppercase
+
+```
+
+# Diagnostics
+```
+invalid.svelte:3:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 1 │
+ 2 │
+ > 3 │ Void
+ │ ^^^^^^^^^^^^^^^^^^^^^
+ 4 │ Confirm
+ 5 │ Uppercase
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.svelte:4:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 3 │ Void
+ > 4 │ Confirm
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 5 │ Uppercase
+ 6 │
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.svelte:5:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 3 │ Void
+ 4 │ Confirm
+ > 5 │ Uppercase
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 6 │
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.vue b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.vue
new file mode 100644
index 000000000000..6c931f04f6fd
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.vue
@@ -0,0 +1,7 @@
+
+
+
+ Void
+ Alert
+ Uppercase
+
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.vue.snap b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.vue.snap
new file mode 100644
index 000000000000..b0f116e1b99e
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/invalid.vue.snap
@@ -0,0 +1,72 @@
+---
+source: crates/biome_html_analyze/tests/spec_tests.rs
+expression: invalid.vue
+---
+# Input
+```html
+
+
+
+ Void
+ Alert
+ Uppercase
+
+
+```
+
+# Diagnostics
+```
+invalid.vue:4:10 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 3 │
+ > 4 │ Void
+ │ ^^^^^^^^^^^^^^^^^^^^^
+ 5 │ Alert
+ 6 │ Uppercase
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.vue:5:10 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 3 │
+ 4 │ Void
+ > 5 │ Alert
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 6 │ Uppercase
+ 7 │
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.vue:6:10 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 4 │ Void
+ 5 │ Alert
+ > 6 │ Uppercase
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 7 │
+ 8 │
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.astro b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.astro
new file mode 100644
index 000000000000..24a0a10eb75e
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.astro
@@ -0,0 +1,9 @@
+---
+// Astro valid cases - should NOT trigger the rule
+---
+
+Docs
+Astro
+Footer
+
+Not a link
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.astro.snap b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.astro.snap
new file mode 100644
index 000000000000..2b88a44cb149
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.astro.snap
@@ -0,0 +1,17 @@
+---
+source: crates/biome_html_analyze/tests/spec_tests.rs
+expression: valid.astro
+---
+# Input
+```html
+---
+// Astro valid cases - should NOT trigger the rule
+---
+
+Docs
+Astro
+Footer
+
+Not a link
+
+```
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.html b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.html
new file mode 100644
index 000000000000..ef36b48dbb9a
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.html
@@ -0,0 +1,10 @@
+
+
+Link
+
+Link
+
+Link
+
+
+Not applicable
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.html.snap b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.html.snap
new file mode 100644
index 000000000000..030078d7a82a
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.html.snap
@@ -0,0 +1,19 @@
+---
+source: crates/biome_html_analyze/tests/spec_tests.rs
+assertion_line: 83
+expression: valid.html
+---
+# Input
+```html
+
+
+Link
+
+Link
+
+Link
+
+
+Not applicable
+
+```
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.svelte b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.svelte
new file mode 100644
index 000000000000..d34ab6ced697
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.svelte
@@ -0,0 +1,7 @@
+
+
+Home
+Biome
+Top
+
+Not a link
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.svelte.snap b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.svelte.snap
new file mode 100644
index 000000000000..4d50527f6b0c
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.svelte.snap
@@ -0,0 +1,15 @@
+---
+source: crates/biome_html_analyze/tests/spec_tests.rs
+expression: valid.svelte
+---
+# Input
+```html
+
+
+Home
+Biome
+Top
+
+Not a link
+
+```
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.vue b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.vue
new file mode 100644
index 000000000000..405a51c75426
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.vue
@@ -0,0 +1,9 @@
+
+
+
+ About
+ External
+ Hash
+
+ Not a link
+
diff --git a/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.vue.snap b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.vue.snap
new file mode 100644
index 000000000000..f7e692f99181
--- /dev/null
+++ b/crates/biome_html_analyze/tests/specs/nursery/noScriptUrl/valid.vue.snap
@@ -0,0 +1,17 @@
+---
+source: crates/biome_html_analyze/tests/spec_tests.rs
+expression: valid.vue
+---
+# Input
+```html
+
+
+
+ About
+ External
+ Hash
+
+ Not a link
+
+
+```
diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs
index 923891adad6d..7ec664d3d9cd 100644
--- a/crates/biome_js_analyze/src/lint/nursery.rs
+++ b/crates/biome_js_analyze/src/lint/nursery.rs
@@ -20,6 +20,7 @@ pub mod no_next_async_client_component;
pub mod no_parameters_only_used_in_recursion;
pub mod no_proto;
pub mod no_react_forward_ref;
+pub mod no_script_url;
pub mod no_shadow;
pub mod no_sync_scripts;
pub mod no_ternary;
@@ -49,4 +50,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_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , 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_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_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_script_url.rs b/crates/biome_js_analyze/src/lint/nursery/no_script_url.rs
new file mode 100644
index 000000000000..5016c9fd5e36
--- /dev/null
+++ b/crates/biome_js_analyze/src/lint/nursery/no_script_url.rs
@@ -0,0 +1,181 @@
+use crate::react::ReactCreateElementCall;
+use crate::services::semantic::Semantic;
+use biome_analyze::context::RuleContext;
+use biome_analyze::{Rule, RuleDiagnostic, RuleSource, declare_lint_rule};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_js_syntax::{AnyJsxAttributeName, JsCallExpression, JsxAttribute};
+use biome_rowan::{AstNode, TextRange, declare_node_union};
+use biome_rule_options::no_script_url::NoScriptUrlOptions;
+use biome_string_case::StrOnlyExtension;
+
+declare_lint_rule! {
+ /// Disallow `javascript:` URLs.
+ ///
+ /// Using `javascript:` URLs is considered a form of `eval` and can be a security risk.
+ /// These URLs can execute arbitrary JavaScript code, which can lead to cross-site scripting (XSS) vulnerabilities.
+ ///
+ /// ## Examples
+ ///
+ /// ### Invalid
+ ///
+ /// ```jsx,expect_diagnostic
+ /// Click me
+ /// ```
+ ///
+ /// ```jsx,expect_diagnostic
+ /// Click me
+ /// ```
+ ///
+ /// ```js,expect_diagnostic
+ /// React.createElement('a', { href: 'javascript:void(0)' });
+ /// ```
+ ///
+ /// ### Valid
+ ///
+ /// ```jsx
+ /// Click me
+ /// ```
+ ///
+ /// ```jsx
+ /// Click me
+ /// ```
+ ///
+ /// ```jsx
+ /// Click me
+ /// ```
+ ///
+ pub NoScriptUrl {
+ version: "next",
+ name: "noScriptUrl",
+ language: "js",
+ sources: &[
+ RuleSource::Eslint("no-script-url").same(),
+ // Framework-specific equivalents
+ RuleSource::EslintReact("jsx-no-script-url").same(),
+ RuleSource::EslintQwik("jsx-no-script-url").same(),
+ RuleSource::EslintSolid("jsx-no-script-url").same(),
+ RuleSource::EslintReactXyz("dom-no-script-url").same(),
+ ],
+ recommended: true,
+ severity: Severity::Error,
+ }
+}
+
+declare_node_union! {
+ pub AnyJsElementWithHref = JsxAttribute | JsCallExpression
+}
+
+pub enum NoScriptUrlState {
+ JsxAttribute(TextRange),
+ ReactProp(TextRange),
+}
+
+impl NoScriptUrlState {
+ fn range(&self) -> TextRange {
+ match self {
+ Self::JsxAttribute(range) | Self::ReactProp(range) => *range,
+ }
+ }
+}
+
+impl Rule for NoScriptUrl {
+ type Query = Semantic;
+ type State = NoScriptUrlState;
+ type Signals = Option;
+ type Options = NoScriptUrlOptions;
+
+ fn run(ctx: &RuleContext) -> Self::Signals {
+ let node = ctx.query();
+ let model = ctx.model();
+
+ match node {
+ AnyJsElementWithHref::JsxAttribute(jsx_attribute) => {
+ // Check if this is an href attribute
+ let name = jsx_attribute.name().ok()?;
+ if let AnyJsxAttributeName::JsxName(jsx_name) = name {
+ if jsx_name.syntax().text_trimmed() != "href" {
+ return None;
+ }
+ } else {
+ return None;
+ }
+
+ // Check if the value contains javascript:
+ let static_value = jsx_attribute.as_static_value()?;
+ if let Some(const_str) = static_value.as_string_constant()
+ && const_str
+ .trim()
+ .to_lowercase_cow()
+ .starts_with("javascript:")
+ {
+ return Some(NoScriptUrlState::JsxAttribute(
+ jsx_attribute.initializer()?.range(),
+ ));
+ }
+ }
+ AnyJsElementWithHref::JsCallExpression(call_expression) => {
+ // Check if this is a React.createElement call
+ if let Some(react_create_element) =
+ ReactCreateElementCall::from_call_expression(call_expression, model)
+ {
+ let ReactCreateElementCall { props, .. } = react_create_element;
+
+ // Look for href property in the props object
+ if let Some(props) = props {
+ let members = props.members();
+ for member in members {
+ let Ok(member) = member else { continue };
+ let Some(property_member) = member.as_js_property_object_member()
+ else {
+ continue;
+ };
+ let Ok(property_name) = property_member.name() else {
+ continue;
+ };
+ let Some(name) = property_name.as_js_literal_member_name() else {
+ continue;
+ };
+
+ if name.syntax().text_trimmed() == "href" {
+ let value = property_member.value().ok()?;
+
+ // Check if it's a string literal with javascript:
+ if let Some(string_literal) = value.as_any_js_literal_expression()
+ && let Some(string_value) =
+ string_literal.as_js_string_literal_expression()
+ {
+ let text = string_value.inner_string_text().ok()?;
+ if text.trim().to_lowercase_cow().starts_with("javascript:") {
+ return Some(NoScriptUrlState::ReactProp(value.range()));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ None
+ }
+
+ fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option {
+ let diagnostic = RuleDiagnostic::new(
+ rule_category!(),
+ state.range(),
+ markup! {
+ "Avoid using ""javascript:"" URLs, as they can be a security risk."
+ }
+ .to_owned(),
+ )
+ .note(markup! {
+ "Using ""javascript:"" URLs can lead to security vulnerabilities such as cross-site scripting (XSS)."
+ })
+ .note(markup! {
+ "Consider using regular URLs, or if you need to handle click events, use event handlers instead."
+ });
+
+ Some(diagnostic)
+ }
+}
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noScriptUrl/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noScriptUrl/invalid.jsx
new file mode 100644
index 000000000000..0d513ddf6a16
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noScriptUrl/invalid.jsx
@@ -0,0 +1,15 @@
+// Invalid cases - should trigger the rule
+
+Link;
+
+Link;
+
+Link;
+
+Link;
+
+Link;
+
+React.createElement('a', { href: 'javascript:void(0)' });
+
+React.createElement('a', { href: 'javascript:alert("XSS")' });
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noScriptUrl/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noScriptUrl/invalid.jsx.snap
new file mode 100644
index 000000000000..6bb1bca295bd
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noScriptUrl/invalid.jsx.snap
@@ -0,0 +1,157 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+assertion_line: 151
+expression: invalid.jsx
+---
+# Input
+```jsx
+// Invalid cases - should trigger the rule
+
+Link;
+
+Link;
+
+Link;
+
+Link;
+
+Link;
+
+React.createElement('a', { href: 'javascript:void(0)' });
+
+React.createElement('a', { href: 'javascript:alert("XSS")' });
+
+```
+
+# Diagnostics
+```
+invalid.jsx:3:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 1 │ // Invalid cases - should trigger the rule
+ 2 │
+ > 3 │ Link;
+ │ ^^^^^^^^^^^^^^^^^^^^^
+ 4 │
+ 5 │ Link;
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.jsx:5:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 3 │ Link;
+ 4 │
+ > 5 │ Link;
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ 6 │
+ 7 │ Link;
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.jsx:7:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 5 │ Link;
+ 6 │
+ > 7 │ Link;
+ │ ^^^^^^^^^^^^^^^^^^^^^^
+ 8 │
+ 9 │ Link;
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.jsx:9:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 7 │ Link;
+ 8 │
+ > 9 │ Link;
+ │ ^^^^^^^^^^^^^^^^^^^^^^
+ 10 │
+ 11 │ Link;
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.jsx:11:8 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 9 │ Link;
+ 10 │
+ > 11 │ Link;
+ │ ^^^^^^^^^^^^^^^^^^^^^
+ 12 │
+ 13 │ React.createElement('a', { href: 'javascript:void(0)' });
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.jsx:13:34 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 11 │ Link;
+ 12 │
+ > 13 │ React.createElement('a', { href: 'javascript:void(0)' });
+ │ ^^^^^^^^^^^^^^^^^^^^
+ 14 │
+ 15 │ React.createElement('a', { href: 'javascript:alert("XSS")' });
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
+
+```
+invalid.jsx:15:34 lint/nursery/noScriptUrl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Avoid using javascript: URLs, as they can be a security risk.
+
+ 13 │ React.createElement('a', { href: 'javascript:void(0)' });
+ 14 │
+ > 15 │ React.createElement('a', { href: 'javascript:alert("XSS")' });
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^
+ 16 │
+
+ i Using javascript: URLs can lead to security vulnerabilities such as cross-site scripting (XSS).
+
+ i Consider using regular URLs, or if you need to handle click events, use event handlers instead.
+
+
+```
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noScriptUrl/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noScriptUrl/valid.jsx
new file mode 100644
index 000000000000..be6cbed9ad8f
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noScriptUrl/valid.jsx
@@ -0,0 +1,23 @@
+/* should not generate diagnostics */
+
+// Valid cases - should not trigger the rule
+
+Link;
+
+Link;
+
+Link;
+
+Link;
+
+Link;
+
+Link;
+
+;
+
+React.createElement('a', { href: 'https://example.com' });
+
+React.createElement('a', { href: '/path' });
+
+React.createElement('a', { href: '#section' });
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noScriptUrl/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noScriptUrl/valid.jsx.snap
new file mode 100644
index 000000000000..59f043e65894
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noScriptUrl/valid.jsx.snap
@@ -0,0 +1,31 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: valid.jsx
+---
+# Input
+```jsx
+/* should not generate diagnostics */
+
+// Valid cases - should not trigger the rule
+
+Link;
+
+Link;
+
+Link;
+
+Link;
+
+Link;
+
+Link;
+
+;
+
+React.createElement('a', { href: 'https://example.com' });
+
+React.createElement('a', { href: '/path' });
+
+React.createElement('a', { href: '#section' });
+
+```
diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs
index b113636debf2..721f83798e2b 100644
--- a/crates/biome_rule_options/src/lib.rs
+++ b/crates/biome_rule_options/src/lib.rs
@@ -170,6 +170,7 @@ pub mod no_restricted_elements;
pub mod no_restricted_globals;
pub mod no_restricted_imports;
pub mod no_restricted_types;
+pub mod no_script_url;
pub mod no_secrets;
pub mod no_self_assign;
pub mod no_self_compare;
diff --git a/crates/biome_rule_options/src/no_script_url.rs b/crates/biome_rule_options/src/no_script_url.rs
new file mode 100644
index 000000000000..377ab9120dfb
--- /dev/null
+++ b/crates/biome_rule_options/src/no_script_url.rs
@@ -0,0 +1,6 @@
+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 NoScriptUrlOptions {}
diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts
index c0cbc55adf4d..b70818b696f9 100644
--- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts
+++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts
@@ -1947,6 +1947,11 @@ See
*/
noReactForwardRef?: NoReactForwardRefConfiguration;
/**
+ * Disallow javascript: URLs in HTML.
+See
+ */
+ noScriptUrl?: NoScriptUrlConfiguration;
+ /**
* Disallow variable declarations from shadowing variables declared in the outer scope.
See
*/
@@ -3638,6 +3643,9 @@ export type NoProtoConfiguration =
export type NoReactForwardRefConfiguration =
| RulePlainConfiguration
| RuleWithNoReactForwardRefOptions;
+export type NoScriptUrlConfiguration =
+ | RulePlainConfiguration
+ | RuleWithNoScriptUrlOptions;
export type NoShadowConfiguration =
| RulePlainConfiguration
| RuleWithNoShadowOptions;
@@ -5068,6 +5076,10 @@ export interface RuleWithNoReactForwardRefOptions {
level: RulePlainConfiguration;
options?: NoReactForwardRefOptions;
}
+export interface RuleWithNoScriptUrlOptions {
+ level: RulePlainConfiguration;
+ options?: NoScriptUrlOptions;
+}
export interface RuleWithNoShadowOptions {
level: RulePlainConfiguration;
options?: NoShadowOptions;
@@ -6389,6 +6401,7 @@ export type NoNextAsyncClientComponentOptions = {};
export type NoParametersOnlyUsedInRecursionOptions = {};
export type NoProtoOptions = {};
export type NoReactForwardRefOptions = {};
+export type NoScriptUrlOptions = {};
export type NoShadowOptions = {};
export type NoSyncScriptsOptions = {};
export type NoTernaryOptions = {};
@@ -7303,6 +7316,7 @@ export type Category =
| "lint/security/noDangerouslySetInnerHtml"
| "lint/security/noDangerouslySetInnerHtmlWithChildren"
| "lint/security/noGlobalEval"
+ | "lint/nursery/noScriptUrl"
| "lint/security/noSecrets"
| "lint/style/noCommonJs"
| "lint/style/noDefaultExport"
diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json
index 28e755222813..15ce072e8246 100644
--- a/packages/@biomejs/biome/configuration_schema.json
+++ b/packages/@biomejs/biome/configuration_schema.json
@@ -4201,6 +4201,13 @@
},
"additionalProperties": false
},
+ "NoScriptUrlConfiguration": {
+ "oneOf": [
+ { "$ref": "#/$defs/RulePlainConfiguration" },
+ { "$ref": "#/$defs/RuleWithNoScriptUrlOptions" }
+ ]
+ },
+ "NoScriptUrlOptions": { "type": "object", "additionalProperties": false },
"NoSecretsConfiguration": {
"oneOf": [
{ "$ref": "#/$defs/RulePlainConfiguration" },
@@ -5221,6 +5228,13 @@
{ "type": "null" }
]
},
+ "noScriptUrl": {
+ "description": "Disallow javascript: URLs in HTML.\nSee ",
+ "anyOf": [
+ { "$ref": "#/$defs/NoScriptUrlConfiguration" },
+ { "type": "null" }
+ ]
+ },
"noShadow": {
"description": "Disallow variable declarations from shadowing variables declared in the outer scope.\nSee ",
"anyOf": [
@@ -7610,6 +7624,15 @@
"additionalProperties": false,
"required": ["level"]
},
+ "RuleWithNoScriptUrlOptions": {
+ "type": "object",
+ "properties": {
+ "level": { "$ref": "#/$defs/RulePlainConfiguration" },
+ "options": { "$ref": "#/$defs/NoScriptUrlOptions" }
+ },
+ "additionalProperties": false,
+ "required": ["level"]
+ },
"RuleWithNoSecretsOptions": {
"type": "object",
"properties": {