Repository: biomejs/biome (language: rust)

Issue:
📎  Implement `stylelint/no-descending-specificity`
## Description

Implement [no-descending-specificity](https://stylelint.io/user-guide/rules/no-descending-specificity)

> [!IMPORTANT]
> - Please skip implementing options for now since we will evaluate users actually want them later.
> - Please ignore handling extended CSS language cases such as `sass` and `less`.
> - Please skip custom function since we haven't syntax model yet.

**Want to contribute?** Lets you know you are interested! We will assign you to the issue to prevent several people to work on the same issue. Don't worry, we can unassign you later if you are no longer interested in the issue! Read our [contributing guide](https://github.com/biomejs/biome/blob/main/CONTRIBUTING.md) and [analyzer contributing guide](https://github.com/biomejs/biome/blob/main/crates/biome_analyze/CONTRIBUTING.md).


Discussion / hints:
Hi, Can I work on this?
Absolutely, assigned.
I have a question.

In the process of implementing this feature, I started by creating a function to calculate the specificity of CSS selectors. During this process, I noticed that there is something called CssMetavariable within AnyCssSelector.
https://github.com/biomejs/biome/blob/ab0f96ea51a576456a6aa7c439bc292d61a0ec42/crates/biome_css_syntax/src/generated/nodes.rs#L8285-L8291

Since I'm not very familiar with CSS, I'm unsure how to handle the specificity calculation for CssMetavariable. Could you please advise me on how to process the specificity of CssMetavariable?

I found where it is used, but I don't fully understand what it represents.
https://github.com/biomejs/biome/blob/ab0f96ea51a576456a6aa7c439bc292d61a0ec42/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css.snap#L55-L61
The specificity of selectors should be already available in the semantic model

https://github.com/biomejs/biome/blob/d03c6cd029dd273ba6a3a270ab82e7f02b24b8d5/crates/biome_css_semantic/src/semantic_model/model.rs#L113-L128
I completely missed that... Thank you for letting me know!

Fix patch:
diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs
index 7f9d335f686c..b7cac6a2ab55 100644
--- a/crates/biome_configuration/src/analyzer/linter/rules.rs
+++ b/crates/biome_configuration/src/analyzer/linter/rules.rs
@@ -3274,6 +3274,10 @@ pub struct Nursery {
     #[doc = "Disallow use of CommonJs module system in favor of ESM style imports."]
     #[serde(skip_serializing_if = "Option::is_none")]
     pub no_common_js: Option<RuleConfiguration<biome_js_analyze::options::NoCommonJs>>,
+    #[doc = "Disallow a lower specificity selector from coming after a higher specificity selector."]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub no_descending_specificity:
+        Option<RuleConfiguration<biome_css_analyze::options::NoDescendingSpecificity>>,
     #[doc = "Disallow duplicate custom properties within declaration blocks."]
     #[serde(skip_serializing_if = "Option::is_none")]
     pub no_duplicate_custom_properties:
@@ -3417,6 +3421,7 @@ impl Nursery {
     const GROUP_NAME: &'static str = "nursery";
     pub(crate) const GROUP_RULES: &'static [&'static str] = &[
         "noCommonJs",
+        "noDescendingSpecificity",
         "noDuplicateCustomProperties",
         "noDuplicateElseIf",
         "noDuplicatedFields",
@@ -3452,6 +3457,7 @@ impl Nursery {
         "useValidAutocomplete",
     ];
     const RECOMMENDED_RULES: &'static [&'static str] = &[
+        "noDescendingSpecificity",
         "noDuplicateCustomProperties",
         "noDuplicateElseIf",
         "noDuplicatedFields",
@@ -3468,14 +3474,15 @@ impl Nursery {
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3]),
-        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]),
-        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]),
+        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]),
+        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]),
-        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]),
-        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]),
+        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]),
+        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]),
-        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]),
+        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]),
+        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]),
     ];
     const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]),
@@ -3512,6 +3519,7 @@ impl Nursery {
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]),
         RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]),
+        RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]),
     ];
     #[doc = r" Retrieves the recommended rules"]
     pub(crate) fn is_recommended_true(&self) -> bool {
@@ -3533,171 +3541,176 @@ impl Nursery {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]));
             }
         }
-        if let Some(rule) = self.no_duplicate_custom_properties.as_ref() {
+        if let Some(rule) = self.no_descending_specificity.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]));
             }
         }
-        if let Some(rule) = self.no_duplicate_else_if.as_ref() {
+        if let Some(rule) = self.no_duplicate_custom_properties.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]));
             }
         }
-        if let Some(rule) = self.no_duplicated_fields.as_ref() {
+        if let Some(rule) = self.no_duplicate_else_if.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3]));
             }
         }
-        if let Some(rule) = self.no_dynamic_namespace_import_access.as_ref() {
+        if let Some(rule) = self.no_duplicated_fields.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]));
             }
         }
-        if let Some(rule) = self.no_enum.as_ref() {
+        if let Some(rule) = self.no_dynamic_namespace_import_access.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5]));
             }
         }
-        if let Some(rule) = self.no_exported_imports.as_ref() {
+        if let Some(rule) = self.no_enum.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6]));
             }
         }
-        if let Some(rule) = self.no_irregular_whitespace.as_ref() {
+        if let Some(rule) = self.no_exported_imports.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7]));
             }
         }
-        if let Some(rule) = self.no_missing_var_function.as_ref() {
+        if let Some(rule) = self.no_irregular_whitespace.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]));
             }
         }
-        if let Some(rule) = self.no_nested_ternary.as_ref() {
+        if let Some(rule) = self.no_missing_var_function.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]));
             }
         }
-        if let Some(rule) = self.no_octal_escape.as_ref() {
+        if let Some(rule) = self.no_nested_ternary.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]));
             }
         }
-        if let Some(rule) = self.no_process_env.as_ref() {
+        if let Some(rule) = self.no_octal_escape.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]));
             }
         }
-        if let Some(rule) = self.no_restricted_imports.as_ref() {
+        if let Some(rule) = self.no_process_env.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]));
             }
         }
-        if let Some(rule) = self.no_restricted_types.as_ref() {
+        if let Some(rule) = self.no_restricted_imports.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]));
             }
         }
-        if let Some(rule) = self.no_secrets.as_ref() {
+        if let Some(rule) = self.no_restricted_types.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]));
             }
         }
-        if let Some(rule) = self.no_static_element_interactions.as_ref() {
+        if let Some(rule) = self.no_secrets.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15]));
             }
         }
-        if let Some(rule) = self.no_substr.as_ref() {
+        if let Some(rule) = self.no_static_element_interactions.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]));
             }
         }
-        if let Some(rule) = self.no_template_curly_in_string.as_ref() {
+        if let Some(rule) = self.no_substr.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]));
             }
         }
-        if let Some(rule) = self.no_unknown_pseudo_class.as_ref() {
+        if let Some(rule) = self.no_template_curly_in_string.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]));
             }
         }
-        if let Some(rule) = self.no_unknown_pseudo_element.as_ref() {
+        if let Some(rule) = self.no_unknown_pseudo_class.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]));
             }
         }
-        if let Some(rule) = self.no_useless_escape_in_regex.as_ref() {
+        if let Some(rule) = self.no_unknown_pseudo_element.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]));
             }
         }
-        if let Some(rule) = self.no_value_at_rule.as_ref() {
+        if let Some(rule) = self.no_useless_escape_in_regex.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]));
             }
         }
-        if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() {
+        if let Some(rule) = self.no_value_at_rule.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]));
             }
         }
-        if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() {
+        if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]));
             }
         }
-        if let Some(rule) = self.use_component_export_only_modules.as_ref() {
+        if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]));
             }
         }
-        if let Some(rule) = self.use_consistent_curly_braces.as_ref() {
+        if let Some(rule) = self.use_component_export_only_modules.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]));
             }
         }
-        if let Some(rule) = self.use_consistent_member_accessibility.as_ref() {
+        if let Some(rule) = self.use_consistent_curly_braces.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]));
             }
         }
-        if let Some(rule) = self.use_deprecated_reason.as_ref() {
+        if let Some(rule) = self.use_consistent_member_accessibility.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]));
             }
         }
-        if let Some(rule) = self.use_explicit_function_return_type.as_ref() {
+        if let Some(rule) = self.use_deprecated_reason.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]));
             }
         }
-        if let Some(rule) = self.use_import_restrictions.as_ref() {
+        if let Some(rule) = self.use_explicit_function_return_type.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]));
             }
         }
-        if let Some(rule) = self.use_sorted_classes.as_ref() {
+        if let Some(rule) = self.use_import_restrictions.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]));
             }
         }
-        if let Some(rule) = self.use_strict_mode.as_ref() {
+        if let Some(rule) = self.use_sorted_classes.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]));
             }
         }
-        if let Some(rule) = self.use_trim_start_end.as_ref() {
+        if let Some(rule) = self.use_strict_mode.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]));
             }
         }
-        if let Some(rule) = self.use_valid_autocomplete.as_ref() {
+        if let Some(rule) = self.use_trim_start_end.as_ref() {
             if rule.is_enabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]));
             }
         }
+        if let Some(rule) = self.use_valid_autocomplete.as_ref() {
+            if rule.is_enabled() {
+                index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]));
+            }
+        }
         index_set
     }
     pub(crate) fn get_disabled_rules(&self) -> FxHashSet<RuleFilter<'static>> {
@@ -3707,171 +3720,176 @@ impl Nursery {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]));
             }
         }
-        if let Some(rule) = self.no_duplicate_custom_properties.as_ref() {
+        if let Some(rule) = self.no_descending_specificity.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]));
             }
         }
-        if let Some(rule) = self.no_duplicate_else_if.as_ref() {
+        if let Some(rule) = self.no_duplicate_custom_properties.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]));
             }
         }
-        if let Some(rule) = self.no_duplicated_fields.as_ref() {
+        if let Some(rule) = self.no_duplicate_else_if.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3]));
             }
         }
-        if let Some(rule) = self.no_dynamic_namespace_import_access.as_ref() {
+        if let Some(rule) = self.no_duplicated_fields.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]));
             }
         }
-        if let Some(rule) = self.no_enum.as_ref() {
+        if let Some(rule) = self.no_dynamic_namespace_import_access.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5]));
             }
         }
-        if let Some(rule) = self.no_exported_imports.as_ref() {
+        if let Some(rule) = self.no_enum.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6]));
             }
         }
-        if let Some(rule) = self.no_irregular_whitespace.as_ref() {
+        if let Some(rule) = self.no_exported_imports.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7]));
             }
         }
-        if let Some(rule) = self.no_missing_var_function.as_ref() {
+        if let Some(rule) = self.no_irregular_whitespace.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]));
             }
         }
-        if let Some(rule) = self.no_nested_ternary.as_ref() {
+        if let Some(rule) = self.no_missing_var_function.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]));
             }
         }
-        if let Some(rule) = self.no_octal_escape.as_ref() {
+        if let Some(rule) = self.no_nested_ternary.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]));
             }
         }
-        if let Some(rule) = self.no_process_env.as_ref() {
+        if let Some(rule) = self.no_octal_escape.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]));
             }
         }
-        if let Some(rule) = self.no_restricted_imports.as_ref() {
+        if let Some(rule) = self.no_process_env.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]));
             }
         }
-        if let Some(rule) = self.no_restricted_types.as_ref() {
+        if let Some(rule) = self.no_restricted_imports.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]));
             }
         }
-        if let Some(rule) = self.no_secrets.as_ref() {
+        if let Some(rule) = self.no_restricted_types.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]));
             }
         }
-        if let Some(rule) = self.no_static_element_interactions.as_ref() {
+        if let Some(rule) = self.no_secrets.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15]));
             }
         }
-        if let Some(rule) = self.no_substr.as_ref() {
+        if let Some(rule) = self.no_static_element_interactions.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]));
             }
         }
-        if let Some(rule) = self.no_template_curly_in_string.as_ref() {
+        if let Some(rule) = self.no_substr.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]));
             }
         }
-        if let Some(rule) = self.no_unknown_pseudo_class.as_ref() {
+        if let Some(rule) = self.no_template_curly_in_string.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]));
             }
         }
-        if let Some(rule) = self.no_unknown_pseudo_element.as_ref() {
+        if let Some(rule) = self.no_unknown_pseudo_class.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]));
             }
         }
-        if let Some(rule) = self.no_useless_escape_in_regex.as_ref() {
+        if let Some(rule) = self.no_unknown_pseudo_element.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]));
             }
         }
-        if let Some(rule) = self.no_value_at_rule.as_ref() {
+        if let Some(rule) = self.no_useless_escape_in_regex.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]));
             }
         }
-        if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() {
+        if let Some(rule) = self.no_value_at_rule.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]));
             }
         }
-        if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() {
+        if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]));
             }
         }
-        if let Some(rule) = self.use_component_export_only_modules.as_ref() {
+        if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]));
             }
         }
-        if let Some(rule) = self.use_consistent_curly_braces.as_ref() {
+        if let Some(rule) = self.use_component_export_only_modules.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]));
             }
         }
-        if let Some(rule) = self.use_consistent_member_accessibility.as_ref() {
+        if let Some(rule) = self.use_consistent_curly_braces.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]));
             }
         }
-        if let Some(rule) = self.use_deprecated_reason.as_ref() {
+        if let Some(rule) = self.use_consistent_member_accessibility.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]));
             }
         }
-        if let Some(rule) = self.use_explicit_function_return_type.as_ref() {
+        if let Some(rule) = self.use_deprecated_reason.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]));
             }
         }
-        if let Some(rule) = self.use_import_restrictions.as_ref() {
+        if let Some(rule) = self.use_explicit_function_return_type.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]));
             }
         }
-        if let Some(rule) = self.use_sorted_classes.as_ref() {
+        if let Some(rule) = self.use_import_restrictions.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]));
             }
         }
-        if let Some(rule) = self.use_strict_mode.as_ref() {
+        if let Some(rule) = self.use_sorted_classes.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]));
             }
         }
-        if let Some(rule) = self.use_trim_start_end.as_ref() {
+        if let Some(rule) = self.use_strict_mode.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]));
             }
         }
-        if let Some(rule) = self.use_valid_autocomplete.as_ref() {
+        if let Some(rule) = self.use_trim_start_end.as_ref() {
             if rule.is_disabled() {
                 index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]));
             }
         }
+        if let Some(rule) = self.use_valid_autocomplete.as_ref() {
+            if rule.is_disabled() {
+                index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]));
+            }
+        }
         index_set
     }
     #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"]
@@ -3912,6 +3930,10 @@ impl Nursery {
                 .no_common_js
                 .as_ref()
                 .map(|conf| (conf.level(), conf.get_options())),
+            "noDescendingSpecificity" => self
+                .no_descending_specificity
+                .as_ref()
+                .map(|conf| (conf.level(), conf.get_options())),
             "noDuplicateCustomProperties" => self
                 .no_duplicate_custom_properties
                 .as_ref()
diff --git a/crates/biome_css_analyze/src/lint/nursery.rs b/crates/biome_css_analyze/src/lint/nursery.rs
index da0d81727c08..7910f2e76559 100644
--- a/crates/biome_css_analyze/src/lint/nursery.rs
+++ b/crates/biome_css_analyze/src/lint/nursery.rs
@@ -2,6 +2,7 @@
 
 use biome_analyze::declare_lint_group;
 
+pub mod no_descending_specificity;
 pub mod no_duplicate_custom_properties;
 pub mod no_irregular_whitespace;
 pub mod no_missing_var_function;
@@ -13,6 +14,7 @@ declare_lint_group! {
     pub Nursery {
         name : "nursery" ,
         rules : [
+            self :: no_descending_specificity :: NoDescendingSpecificity ,
             self :: no_duplicate_custom_properties :: NoDuplicateCustomProperties ,
             self :: no_irregular_whitespace :: NoIrregularWhitespace ,
             self :: no_missing_var_function :: NoMissingVarFunction ,
diff --git a/crates/biome_css_analyze/src/lint/nursery/no_descending_specificity.rs b/crates/biome_css_analyze/src/lint/nursery/no_descending_specificity.rs
new file mode 100644
index 000000000000..94da4204046e
--- /dev/null
+++ b/crates/biome_css_analyze/src/lint/nursery/no_descending_specificity.rs
@@ -0,0 +1,203 @@
+use rustc_hash::{FxHashMap, FxHashSet};
+
+use biome_analyze::{context::RuleContext, declare_lint_rule, Rule, RuleDiagnostic, RuleSource};
+use biome_console::markup;
+use biome_css_semantic::model::{Rule as CssSemanticRule, RuleId, SemanticModel, Specificity};
+use biome_css_syntax::{AnyCssSelector, CssRoot};
+use biome_rowan::TextRange;
+
+use biome_rowan::AstNode;
+
+use crate::services::semantic::Semantic;
+
+declare_lint_rule! {
+    /// Disallow a lower specificity selector from coming after a higher specificity selector.
+    ///
+    /// This rule prohibits placing selectors with lower specificity after selectors with higher specificity.
+    /// By maintaining the order of the source and specificity as consistently as possible, it enhances readability.
+    ///
+    /// ## Examples
+    ///
+    /// ### Invalid
+    ///
+    /// ```css,expect_diagnostic
+    /// b a { color: red; }
+    /// a { color: red; }
+    /// ```
+    ///
+    /// ```css,expect_diagnostic
+    /// a {
+    ///   & > b { color: red; }
+    /// }
+    /// b { color: red; }
+    /// ```
+    ///
+    /// ```css,expect_diagnostic
+    /// :root input {
+    ///     color: red;
+    /// }
+    /// html input {
+    ///     color: red;
+    /// }
+    /// ```
+    ///
+    ///
+    /// ### Valid
+    ///
+    /// ```css
+    /// a { color: red; }
+    /// b a { color: red; }
+    /// ```
+    ///
+    /// ```css
+    /// b { color: red; }
+    /// a {
+    ///   & > b { color: red; }
+    /// }
+    /// ```
+    ///
+    /// ```css
+    /// a:hover { color: red; }
+    /// a { color: red; }
+    /// ```
+    ///
+    /// ```css
+    /// a b {
+    ///     color: red;
+    /// }
+    /// /* This selector is overwritten by the one above it, but this is not an error because the rule only evaluates it as a compound selector */
+    /// :where(a) :is(b) {
+    ///     color: blue;
+    /// }
+    /// ```
+    ///
+    pub NoDescendingSpecificity {
+        version: "next",
+        name: "noDescendingSpecificity",
+        language: "css",
+        recommended: true,
+        sources: &[RuleSource::Stylelint("no-descending-specificity")],
+    }
+}
+
+#[derive(Debug)]
+pub struct DescendingSelector {
+    high: (TextRange, Specificity),
+    low: (TextRange, Specificity),
+}
+/// find tail selector
+/// ```css
+/// a b:hover {
+///   ^^^^^^^
+/// }
+/// ```
+fn find_tail_selector(selector: &AnyCssSelector) -> Option<String> {
+    match selector {
+        AnyCssSelector::CssCompoundSelector(s) => {
+            let simple = s
+                .simple_selector()
+                .map_or(String::new(), |s| s.syntax().text_trimmed().to_string());
+            let sub = s.sub_selectors().syntax().text_trimmed().to_string();
+
+            let last_selector = [simple, sub].join("");
+            Some(last_selector)
+        }
+        AnyCssSelector::CssComplexSelector(s) => {
+            s.right().as_ref().ok().and_then(find_tail_selector)
+        }
+        _ => None,
+    }
+}
+
+/// This function traverses the CSS rules starting from the given rule and checks for selectors that have the same tail selector.
+/// For each selector, it compares its specificity with the previously encountered specificity of the same tail selector.
+/// If a lower specificity selector is found after a higher specificity selector with the same tail selector, it records this as a descending selector.
+fn find_descending_selector(
+    rule: &CssSemanticRule,
+    model: &SemanticModel,
+    visited_rules: &mut FxHashSet<RuleId>,
+    visited_selectors: &mut FxHashMap<String, (TextRange, Specificity)>,
+    descending_selectors: &mut Vec<DescendingSelector>,
+) {
+    if visited_rules.contains(&rule.id) {
+        return;
+    } else {
+        visited_rules.insert(rule.id);
+    };
+
+    for selector in &rule.selectors {
+        let tail_selector = if let Some(s) = find_tail_selector(&selector.original) {
+            s
+        } else {
+            continue;
+        };
+
+        if let Some((last_textrange, last_specificity)) = visited_selectors.get(&tail_selector) {
+            if last_specificity > &selector.specificity {
+                descending_selectors.push(DescendingSelector {
+                    high: (*last_textrange, last_specificity.clone()),
+                    low: (selector.range, selector.specificity.clone()),
+                });
+            }
+        } else {
+            visited_selectors.insert(
+                tail_selector,
+                (selector.range, selector.specificity.clone()),
+            );
+        }
+    }
+
+    for child_id in &rule.child_ids {
+        if let Some(child_rule) = model.get_rule_by_id(*child_id) {
+            find_descending_selector(
+                child_rule,
+                model,
+                visited_rules,
+                visited_selectors,
+                descending_selectors,
+            );
+        }
+    }
+}
+
+impl Rule for NoDescendingSpecificity {
+    type Query = Semantic<CssRoot>;
+    type State = DescendingSelector;
+    type Signals = Vec<Self::State>;
+    type Options = ();
+
+    fn run(ctx: &RuleContext<Self>) -> Self::Signals {
+        let model = ctx.model();
+        let mut visited_rules = FxHashSet::default();
+        let mut visited_selectors = FxHashMap::default();
+        let mut descending_selectors = Vec::new();
+        for rule in model.rules() {
+            find_descending_selector(
+                rule,
+                model,
+                &mut visited_rules,
+                &mut visited_selectors,
+                &mut descending_selectors,
+            );
+        }
+
+        descending_selectors
+    }
+
+    fn diagnostic(_: &RuleContext<Self>, node: &Self::State) -> Option<RuleDiagnostic> {
+        Some(
+            RuleDiagnostic::new(
+                rule_category!(),
+                node.low.0,
+                markup! {
+                    "Descending specificity selector found. This selector specificity is "{node.low.1.to_string()}
+                },
+            ).detail(node.high.0, markup!(
+                "This selector specificity is "{node.high.1.to_string()}
+            ))
+            .note(markup! {
+                    "Descending specificity selector may not applied. Consider rearranging the order of the selectors. See "<Hyperlink href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity">"MDN web docs"</Hyperlink>" for more details."
+            }),
+        )
+    }
+}
diff --git a/crates/biome_css_analyze/src/options.rs b/crates/biome_css_analyze/src/options.rs
index 8f09248e25d6..c91337050f9b 100644
--- a/crates/biome_css_analyze/src/options.rs
+++ b/crates/biome_css_analyze/src/options.rs
@@ -2,6 +2,7 @@
 
 use crate::lint;
 
+pub type NoDescendingSpecificity = < lint :: nursery :: no_descending_specificity :: NoDescendingSpecificity as biome_analyze :: Rule > :: Options ;
 pub type NoDuplicateAtImportRules = < lint :: suspicious :: no_duplicate_at_import_rules :: NoDuplicateAtImportRules as biome_analyze :: Rule > :: Options ;
 pub type NoDuplicateCustomProperties = < lint :: nursery :: no_duplicate_custom_properties :: NoDuplicateCustomProperties as biome_analyze :: Rule > :: Options ;
 pub type NoDuplicateFontNames = < lint :: suspicious :: no_duplicate_font_names :: NoDuplicateFontNames as biome_analyze :: Rule > :: Options ;
diff --git a/crates/biome_css_semantic/src/events.rs b/crates/biome_css_semantic/src/events.rs
index 64b5dee7fedd..b2e6bb40c246 100644
--- a/crates/biome_css_semantic/src/events.rs
+++ b/crates/biome_css_semantic/src/events.rs
@@ -3,11 +3,12 @@ use std::{borrow::Cow, collections::VecDeque};
 use biome_css_syntax::{
     AnyCssSelector, CssDeclarationBlock, CssRelativeSelector, CssSyntaxKind::*,
 };
-use biome_rowan::{AstNode, SyntaxNodeCast, TextRange};
+use biome_rowan::{AstNode, SyntaxNodeCast, SyntaxNodeOptionExt, TextRange};
 
 use crate::{
     model::{CssProperty, CssValue},
     semantic_model::model::Specificity,
+    specificity::{evaluate_complex_selector, evaluate_compound_selector},
 };
 
 const ROOT_SELECTOR: &str = ":root";
@@ -19,6 +20,7 @@ pub enum SemanticEvent {
     SelectorDeclaration {
         name: String,
         range: TextRange,
+        original: AnyCssSelector,
         specificity: Specificity,
     },
     PropertyDeclaration {
@@ -70,11 +72,23 @@ impl SemanticEventExtractor {
                 self.current_rule_stack.push(range);
             }
             CSS_SELECTOR_LIST => {
+                if !matches!(
+                    node.parent().kind(),
+                    Some(CSS_QUALIFIED_RULE | CSS_NESTED_QUALIFIED_RULE)
+                ) {
+                    return;
+                };
                 node.children()
                     .filter_map(AnyCssSelector::cast)
                     .for_each(|s| self.process_selector(s));
             }
             CSS_RELATIVE_SELECTOR_LIST => {
+                if !matches!(
+                    node.parent().kind(),
+                    Some(CSS_QUALIFIED_RULE | CSS_NESTED_QUALIFIED_RULE)
+                ) {
+                    return;
+                };
                 node.children()
                     .filter_map(CssRelativeSelector::cast)
                     .filter_map(|s| s.selector().ok())
@@ -108,20 +122,28 @@ impl SemanticEventExtractor {
     fn process_selector(&mut self, selector: AnyCssSelector) {
         match selector {
             AnyCssSelector::CssComplexSelector(s) => {
-                if let Ok(l) = s.left() {
-                    self.add_selector_event(Cow::Borrowed(&l.text()), l.range());
-                }
-                if let Ok(r) = s.right() {
-                    self.add_selector_event(Cow::Borrowed(&r.text()), r.range());
-                }
+                let specificity = evaluate_complex_selector(&s);
+                self.add_selector_event(
+                    Cow::Borrowed(&s.text()),
+                    s.range(),
+                    AnyCssSelector::CssComplexSelector(s),
+                    specificity,
+                );
             }
+
             AnyCssSelector::CssCompoundSelector(selector) => {
                 let selector_text = selector.text();
                 if selector_text == ROOT_SELECTOR {
                     self.stash.push_back(SemanticEvent::RootSelectorStart);
                     self.is_in_root_selector = true;
                 }
-                self.add_selector_event(Cow::Borrowed(&selector_text), selector.range())
+                let specificity = evaluate_compound_selector(&selector);
+                self.add_selector_event(
+                    Cow::Borrowed(&selector_text),
+                    selector.range(),
+                    AnyCssSelector::CssCompoundSelector(selector),
+                    specificity,
+                )
             }
             _ => {}
         }
@@ -198,11 +220,18 @@ impl SemanticEventExtractor {
         });
     }
 
-    fn add_selector_event(&mut self, name: Cow<str>, range: TextRange) {
+    fn add_selector_event(
+        &mut self,
+        name: Cow<str>,
+        range: TextRange,
+        original: AnyCssSelector,
+        specificity: Specificity,
+    ) {
         self.stash.push_back(SemanticEvent::SelectorDeclaration {
             name: name.into_owned(),
             range,
-            specificity: Specificity(0, 0, 0), // TODO: Implement this
+            original,
+            specificity,
         });
     }
 
diff --git a/crates/biome_css_semantic/src/semantic_model/builder.rs b/crates/biome_css_semantic/src/semantic_model/builder.rs
index ec263240e54f..864c169707aa 100644
--- a/crates/biome_css_semantic/src/semantic_model/builder.rs
+++ b/crates/biome_css_semantic/src/semantic_model/builder.rs
@@ -6,7 +6,7 @@ use rustc_hash::FxHashMap;
 
 use super::model::{
     CssDeclaration, CssGlobalCustomVariable, Rule, RuleId, Selector, SemanticModel,
-    SemanticModelData,
+    SemanticModelData, Specificity,
 };
 use crate::events::SemanticEvent;
 
@@ -66,6 +66,7 @@ impl SemanticModelBuilder {
                     range,
                     parent_id,
                     child_ids: Vec::new(),
+                    specificity: Specificity::default(),
                 };
 
                 if let Some(&parent_id) = self.current_rule_stack.last() {
@@ -95,15 +96,28 @@ impl SemanticModelBuilder {
             SemanticEvent::SelectorDeclaration {
                 name,
                 range,
+                original,
                 specificity,
             } => {
-                if let Some(current_rule) = self.current_rule_stack.last_mut() {
+                let parent_specificity = self
+                    .current_rule_stack
+                    .last()
+                    .and_then(|rule_id| self.rules_by_id.get(rule_id))
+                    .and_then(|rule| rule.parent_id)
+                    .and_then(|parent_id| self.rules_by_id.get(&parent_id))
+                    .map(|parent| parent.specificity.clone())
+                    .unwrap_or_default();
+
+                if let Some(current_rule) = self.current_rule_stack.last() {
                     let current_rule = self.rules_by_id.get_mut(current_rule).unwrap();
                     current_rule.selectors.push(Selector {
                         name,
                         range,
-                        specificity,
+                        original,
+                        specificity: parent_specificity + specificity.clone(),
                     });
+
+                    current_rule.specificity += specificity;
                 }
             }
             SemanticEvent::PropertyDeclaration {
diff --git a/crates/biome_css_semantic/src/semantic_model/mod.rs b/crates/biome_css_semantic/src/semantic_model/mod.rs
index 4372a8fdfae3..2038b0177447 100644
--- a/crates/biome_css_semantic/src/semantic_model/mod.rs
+++ b/crates/biome_css_semantic/src/semantic_model/mod.rs
@@ -1,5 +1,6 @@
 pub mod builder;
 pub mod model;
+pub mod specificity;
 
 use biome_css_syntax::CssRoot;
 use biome_rowan::AstNode;
diff --git a/crates/biome_css_semantic/src/semantic_model/model.rs b/crates/biome_css_semantic/src/semantic_model/model.rs
index 8dc2a2e3a562..f65e9e75f125 100644
--- a/crates/biome_css_semantic/src/semantic_model/model.rs
+++ b/crates/biome_css_semantic/src/semantic_model/model.rs
@@ -1,6 +1,6 @@
 use std::{collections::BTreeMap, rc::Rc};
 
-use biome_css_syntax::CssRoot;
+use biome_css_syntax::{AnyCssSelector, CssRoot};
 use biome_rowan::{TextRange, TextSize};
 use rustc_hash::FxHashMap;
 
@@ -108,6 +108,9 @@ pub struct Rule {
     pub child_ids: Vec<RuleId>,
     /// The text range of this rule in the source document.
     pub range: TextRange,
+    /// Specificity context of this rule  
+    /// See https://drafts.csswg.org/selectors-4/#specificity-rules
+    pub specificity: Specificity,
 }
 
 /// Represents a CSS selector.
@@ -123,6 +126,7 @@ pub struct Selector {
     pub name: String,
     /// The text range of the selector in the source document.
     pub range: TextRange,
+    pub original: AnyCssSelector,
     /// The specificity of the selector.
     pub specificity: Specificity,
 }
@@ -135,6 +139,46 @@ pub struct Selector {
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
 pub struct Specificity(pub u32, pub u32, pub u32);
 
+/// In CSS, when selectors are combined (e.g., in a compound selector), their specificities are summed.
+/// This implementation mirrors that behavior by adding the ID, class, and type selector counts separately.
+///
+/// Consider the following selector.
+/// ```css
+/// #id .class {}
+/// ```
+///
+/// The specificity of each component is as follows:
+/// - `#id` has a specificity of `Specificity(1, 0, 0)`
+/// - `.class` has a specificity of `Specificity(0, 1, 0)`
+///
+/// Therefore, the combined selector `#id .class` has a specificity of:
+/// - `Specificity(1 + 0, 0 + 1, 0 + 0) = Specificity(1, 1, 0)`
+///
+/// More details https://drafts.csswg.org/selectors/#example-d97bd125
+impl std::ops::Add for Specificity {
+    type Output = Specificity;
+    fn add(self, rhs: Self) -> Self::Output {
+        Self(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2)
+    }
+}
+
+impl std::ops::AddAssign for Specificity {
+    fn add_assign(&mut self, rhs: Self) {
+        self.0 = rhs.0;
+        self.1 = rhs.1;
+        self.2 = rhs.2;
+    }
+}
+
+/// Formats the `Specificity` instance to match the notation used in the official CSS specification.
+///
+/// More details https://www.w3.org/TR/selectors-4/#specificity-rules
+impl std::fmt::Display for Specificity {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "({}, {}, {})", self.0, self.1, self.2)
+    }
+}
+
 /// Represents a CSS declaration (property-value pair).
 /// ```css
 /// a {
diff --git a/crates/biome_css_semantic/src/semantic_model/specificity.rs b/crates/biome_css_semantic/src/semantic_model/specificity.rs
new file mode 100644
index 000000000000..041713d67e42
--- /dev/null
+++ b/crates/biome_css_semantic/src/semantic_model/specificity.rs
@@ -0,0 +1,183 @@
+use crate::semantic_model::model::Specificity;
+
+use biome_css_syntax::{
+    AnyCssCompoundSelector, AnyCssPseudoClass, AnyCssRelativeSelector, AnyCssSelector,
+    AnyCssSimpleSelector, AnyCssSubSelector, CssComplexSelector, CssCompoundSelector,
+    CssPseudoClassSelector,
+};
+
+use biome_rowan::{AstNodeList, AstSeparatedList};
+
+const ID_SPECIFICITY: Specificity = Specificity(1, 0, 0);
+const CLASS_SPECIFICITY: Specificity = Specificity(0, 1, 0);
+const TYPE_SPECIFICITY: Specificity = Specificity(0, 0, 1);
+const ZERO_SPECIFICITY: Specificity = Specificity(0, 0, 0);
+
+fn evaluate_any_simple_selector(selector: &AnyCssSimpleSelector) -> Specificity {
+    match selector {
+        AnyCssSimpleSelector::CssTypeSelector(_) => TYPE_SPECIFICITY,
+        AnyCssSimpleSelector::CssUniversalSelector(_) => ZERO_SPECIFICITY,
+    }
+}
+
+/// See https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity#the_is_not_has_and_css_nesting_exceptions
+const fn evaluate_pseudo_function_selector(name: &str) -> Option<Specificity> {
+    match name.as_bytes() {
+        b"where" => None,
+        b"is" | b"not" | b"has" | b"matches" => Some(ZERO_SPECIFICITY),
+        _ => Some(CLASS_SPECIFICITY),
+    }
+}
+
+fn evaluate_any_pseudo_class(class: &AnyCssPseudoClass) -> Specificity {
+    // https://www.w3.org/TR/selectors-4/#specificity-rules
+    match class {
+        AnyCssPseudoClass::CssBogusPseudoClass(_) => ZERO_SPECIFICITY,
+        AnyCssPseudoClass::CssPseudoClassFunctionCompoundSelector(selector) => {
+            CLASS_SPECIFICITY
+                + selector
+                    .selector()
+                    .map_or(ZERO_SPECIFICITY, |s| evaluate_any_compound_selector(&s))
+        }
+        AnyCssPseudoClass::CssPseudoClassFunctionCompoundSelectorList(selector_list) => {
+            let list_max = selector_list
+                .compound_selectors()
+                .iter()
+                .map(|s| s.map_or(ZERO_SPECIFICITY, |s| evaluate_any_compound_selector(&s)))
+                .reduce(|acc, e| acc.max(e))
+                .unwrap_or(ZERO_SPECIFICITY);
+
+            CLASS_SPECIFICITY + list_max
+        }
+        AnyCssPseudoClass::CssPseudoClassFunctionIdentifier(_) => CLASS_SPECIFICITY,
+        AnyCssPseudoClass::CssPseudoClassFunctionNth(_) => CLASS_SPECIFICITY,
+        AnyCssPseudoClass::CssPseudoClassFunctionRelativeSelectorList(selector_list) => {
+            if let Some(base) = selector_list
+                .name_token()
+                .ok()
+                .and_then(|name| evaluate_pseudo_function_selector(name.text()))
+            {
+                let list_max = selector_list
+                    .relative_selectors()
+                    .iter()
+                    .map(|relative_selector| {
+                        relative_selector
+                            .map_or(ZERO_SPECIFICITY, |s| evaluate_any_relative_selector(&s))
+                    })
+                    .reduce(|acc, e| acc.max(e))
+                    .unwrap_or(ZERO_SPECIFICITY);
+                base + list_max
+            } else {
+                ZERO_SPECIFICITY
+            }
+        }
+        AnyCssPseudoClass::CssPseudoClassFunctionSelector(s) => {
+            if let Some(base) = s
+                .name()
+                .ok()
+                .and_then(|name| evaluate_pseudo_function_selector(name.text()))
+            {
+                base + s.selector().map_or(ZERO_SPECIFICITY, |selector| {
+                    evaluate_any_selector(&selector)
+                })
+            } else {
+                ZERO_SPECIFICITY
+            }
+        }
+        AnyCssPseudoClass::CssPseudoClassFunctionSelectorList(selector_list) => {
+            if let Some(base) = selector_list
+                .name()
+                .ok()
+                .and_then(|name| evaluate_pseudo_function_selector(name.text()))
+            {
+                let list_max = selector_list
+                    .selectors()
+                    .iter()
+                    .map(|selector| {
+                        selector.map_or(ZERO_SPECIFICITY, |s| evaluate_any_selector(&s))
+                    })
+                    .reduce(|acc, e| acc.max(e))
+                    .unwrap_or(ZERO_SPECIFICITY);
+                base + list_max
+            } else {
+                ZERO_SPECIFICITY
+            }
+        }
+        AnyCssPseudoClass::CssPseudoClassFunctionValueList(_) => CLASS_SPECIFICITY,
+        AnyCssPseudoClass::CssPseudoClassIdentifier(_) => CLASS_SPECIFICITY,
+    }
+}
+
+fn evaluate_pseudo_selector(selector: &CssPseudoClassSelector) -> Specificity {
+    match selector.class() {
+        Ok(any_pseudo_class) => evaluate_any_pseudo_class(&any_pseudo_class),
+        Err(_) => ZERO_SPECIFICITY,
+    }
+}
+
+fn evaluate_any_subselector(selector: &AnyCssSubSelector) -> Specificity {
+    // https://www.w3.org/TR/selectors-4/#typedef-subclass-selector
+    match selector {
+        AnyCssSubSelector::CssIdSelector(_) => ID_SPECIFICITY,
+        AnyCssSubSelector::CssClassSelector(_) => CLASS_SPECIFICITY,
+        AnyCssSubSelector::CssAttributeSelector(_) => CLASS_SPECIFICITY,
+        AnyCssSubSelector::CssPseudoClassSelector(s) => evaluate_pseudo_selector(s),
+        AnyCssSubSelector::CssPseudoElementSelector(_) => TYPE_SPECIFICITY,
+        AnyCssSubSelector::CssBogusSubSelector(_) => ZERO_SPECIFICITY,
+    }
+}
+
+pub fn evaluate_compound_selector(selector: &CssCompoundSelector) -> Specificity {
+    let nested_specificity = ZERO_SPECIFICITY; // TODO: Implement this
+
+    let simple_specificity = selector
+        .simple_selector()
+        .map_or(ZERO_SPECIFICITY, |s| evaluate_any_simple_selector(&s));
+    let subselector_specificity = selector
+        .sub_selectors()
+        .iter()
+        .map(|s| evaluate_any_subselector(&s))
+        .reduce(|acc, e| acc + e)
+        .unwrap_or(ZERO_SPECIFICITY);
+
+    nested_specificity + simple_specificity + subselector_specificity
+}
+
+fn evaluate_any_compound_selector(selector: &AnyCssCompoundSelector) -> Specificity {
+    match selector {
+        AnyCssCompoundSelector::CssBogusSelector(_) => ZERO_SPECIFICITY,
+        AnyCssCompoundSelector::CssCompoundSelector(s) => evaluate_compound_selector(s),
+    }
+}
+
+pub fn evaluate_complex_selector(selector: &CssComplexSelector) -> Specificity {
+    let left_specificity = selector
+        .left()
+        .map_or(ZERO_SPECIFICITY, |s| evaluate_any_selector(&s));
+    let right_specificity = selector
+        .right()
+        .map_or(ZERO_SPECIFICITY, |s| evaluate_any_selector(&s));
+
+    left_specificity + right_specificity
+}
+
+pub fn evaluate_any_selector(selector: &AnyCssSelector) -> Specificity {
+    match selector {
+        AnyCssSelector::CssCompoundSelector(s) => evaluate_compound_selector(s),
+        AnyCssSelector::CssComplexSelector(s) => evaluate_complex_selector(s),
+        AnyCssSelector::CssBogusSelector(_) => ZERO_SPECIFICITY,
+        AnyCssSelector::CssMetavariable(_) => {
+            // TODO: Implement this
+            ZERO_SPECIFICITY
+        }
+    }
+}
+
+fn evaluate_any_relative_selector(selector: &AnyCssRelativeSelector) -> Specificity {
+    match selector {
+        AnyCssRelativeSelector::CssBogusSelector(_) => ZERO_SPECIFICITY,
+        AnyCssRelativeSelector::CssRelativeSelector(s) => s
+            .selector()
+            .map_or(ZERO_SPECIFICITY, |s| evaluate_any_selector(&s)),
+    }
+}
diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs
index d39031092aa9..c8321d685ad8 100644
--- a/crates/biome_diagnostics_categories/src/categories.rs
+++ b/crates/biome_diagnostics_categories/src/categories.rs
@@ -136,6 +136,7 @@ define_categories! {
     "lint/nursery/noColorInvalidHex": "https://biomejs.dev/linter/rules/no-color-invalid-hex",
     "lint/nursery/noCommonJs": "https://biomejs.dev/linter/rules/no-common-js",
     "lint/nursery/noConsole": "https://biomejs.dev/linter/rules/no-console",
+    "lint/nursery/noDescendingSpecificity": "https://biomejs.dev/linter/rules/no-descending-specificity",
     "lint/nursery/noDoneCallback": "https://biomejs.dev/linter/rules/no-done-callback",
     "lint/nursery/noDuplicateAtImportRules": "https://biomejs.dev/linter/rules/no-duplicate-at-import-rules",
     "lint/nursery/noDuplicateCustomProperties": "https://biomejs.dev/linter/rules/no-duplicate-custom-properties",
diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts
index 81fa5a018fc5..d10410948d90 100644
--- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts
+++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts
@@ -1222,6 +1222,10 @@ export interface Nursery {
 	 * Disallow use of CommonJs module system in favor of ESM style imports.
 	 */
 	noCommonJs?: RuleConfiguration_for_Null;
+	/**
+	 * Disallow a lower specificity selector from coming after a higher specificity selector.
+	 */
+	noDescendingSpecificity?: RuleConfiguration_for_Null;
 	/**
 	 * Disallow duplicate custom properties within declaration blocks.
 	 */
@@ -2833,6 +2837,7 @@ export type Category =
 	| "lint/nursery/noColorInvalidHex"
 	| "lint/nursery/noCommonJs"
 	| "lint/nursery/noConsole"
+	| "lint/nursery/noDescendingSpecificity"
 	| "lint/nursery/noDoneCallback"
 	| "lint/nursery/noDuplicateAtImportRules"
 	| "lint/nursery/noDuplicateCustomProperties"
diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json
index b45098e9db28..9f725c5c0a2e 100644
--- a/packages/@biomejs/biome/configuration_schema.json
+++ b/packages/@biomejs/biome/configuration_schema.json
@@ -2083,6 +2083,13 @@
 						{ "type": "null" }
 					]
 				},
+				"noDescendingSpecificity": {
+					"description": "Disallow a lower specificity selector from coming after a higher specificity selector.",
+					"anyOf": [
+						{ "$ref": "#/definitions/RuleConfiguration" },
+						{ "type": "null" }
+					]
+				},
 				"noDuplicateCustomProperties": {
 					"description": "Disallow duplicate custom properties within declaration blocks.",
 					"anyOf": [


Test patch:
diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css
new file mode 100644
index 000000000000..dabe1edd13c1
--- /dev/null
+++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css
@@ -0,0 +1,7 @@
+b a {
+    color: red;
+}
+
+a {
+    color: red;
+}
diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css.snap
new file mode 100644
index 000000000000..e860dea23937
--- /dev/null
+++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css.snap
@@ -0,0 +1,40 @@
+---
+source: crates/biome_css_analyze/tests/spec_tests.rs
+expression: complex_selector.invalid.css
+---
+# Input
+```css
+b a {
+    color: red;
+}
+
+a {
+    color: red;
+}
+
+```
+
+# Diagnostics
+```
+complex_selector.invalid.css:5:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+  ! Descending specificity selector found. This selector specificity is (0, 0, 1)
+  
+    3 │ }
+    4 │ 
+  > 5 │ a {
+      │ ^
+    6 │     color: red;
+    7 │ }
+  
+  i This selector specificity is (0, 0, 2)
+  
+  > 1 │ b a {
+      │ ^^^
+    2 │     color: red;
+    3 │ }
+  
+  i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details.
+  
+
+```
diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css
new file mode 100644
index 000000000000..f4b9ff63a876
--- /dev/null
+++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css
@@ -0,0 +1,15 @@
+:is(#a, a) f {
+    color: red;
+}
+
+:is(a, b, c, d) f {
+    color: red;
+}
+
+:is(#fake#fake#fake#fake#fake#fake, *) g {
+    color: red;
+}
+
+:where(*) g {
+    color: red;
+}
\ No newline at end of file
diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css.snap
new file mode 100644
index 000000000000..03b6d35bb478
--- /dev/null
+++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css.snap
@@ -0,0 +1,73 @@
+---
+source: crates/biome_css_analyze/tests/spec_tests.rs
+expression: function_pseudo_selector.invalid.css
+---
+# Input
+```css
+:is(#a, a) f {
+    color: red;
+}
+
+:is(a, b, c, d) f {
+    color: red;
+}
+
+:is(#fake#fake#fake#fake#fake#fake, *) g {
+    color: red;
+}
+
+:where(*) g {
+    color: red;
+}
+```
+
+# Diagnostics
+```
+function_pseudo_selector.invalid.css:5:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━━
+
+  ! Descending specificity selector found. This selector specificity is (0, 0, 2)
+  
+    3 │ }
+    4 │ 
+  > 5 │ :is(a, b, c, d) f {
+      │ ^^^^^^^^^^^^^^^^^
+    6 │     color: red;
+    7 │ }
+  
+  i This selector specificity is (1, 0, 1)
+  
+  > 1 │ :is(#a, a) f {
+      │ ^^^^^^^^^^^^
+    2 │     color: red;
+    3 │ }
+  
+  i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details.
+  
+
+```
+
+```
+function_pseudo_selector.invalid.css:13:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━
+
+  ! Descending specificity selector found. This selector specificity is (0, 0, 1)
+  
+    11 │ }
+    12 │ 
+  > 13 │ :where(*) g {
+       │ ^^^^^^^^^^^
+    14 │     color: red;
+    15 │ }
+  
+  i This selector specificity is (6, 0, 1)
+  
+     7 │ }
+     8 │ 
+   > 9 │ :is(#fake#fake#fake#fake#fake#fake, *) g {
+       │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+    10 │     color: red;
+    11 │ }
+  
+  i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details.
+  
+
+```
diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css
new file mode 100644
index 000000000000..f44130b1de57
--- /dev/null
+++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css
@@ -0,0 +1,9 @@
+a {
+    & > b {
+        color: red;
+    }
+}
+
+b {
+    color: red;
+}
\ No newline at end of file
diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css.snap
new file mode 100644
index 000000000000..8cadc52ec585
--- /dev/null
+++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css.snap
@@ -0,0 +1,42 @@
+---
+source: crates/biome_css_analyze/tests/spec_tests.rs
+expression: nested.invalid.css
+---
+# Input
+```css
+a {
+    & > b {
+        color: red;
+    }
+}
+
+b {
+    color: red;
+}
+```
+
+# Diagnostics
+```
+nested.invalid.css:7:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+  ! Descending specificity selector found. This selector specificity is (0, 0, 1)
+  
+    5 │ }
+    6 │ 
+  > 7 │ b {
+      │ ^
+    8 │     color: red;
+    9 │ }
+  
+  i This selector specificity is (0, 0, 2)
+  
+    1 │ a {
+  > 2 │     & > b {
+      │     ^^^^^
+    3 │         color: red;
+    4 │     }
+  
+  i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details.
+  
+
+```
diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css
new file mode 100644
index 000000000000..05a78fadd6eb
--- /dev/null
+++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css
@@ -0,0 +1,7 @@
+a:hover #b {
+    color: red;
+}
+
+a #b {
+    color: red;
+}
diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css.snap
new file mode 100644
index 000000000000..d33be5459b12
--- /dev/null
+++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css.snap
@@ -0,0 +1,40 @@
+---
+source: crates/biome_css_analyze/tests/spec_tests.rs
+expression: simple_pseudo_selector.invalid.css
+---
+# Input
+```css
+a:hover #b {
+    color: red;
+}
+
+a #b {
+    color: red;
+}
+
+```
+
+# Diagnostics
+```
+simple_pseudo_selector.invalid.css:5:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━━━━
+
+  ! Descending specificity selector found. This selector specificity is (1, 0, 1)
+  
+    3 │ }
+    4 │ 
+  > 5 │ a #b {
+      │ ^^^^
+    6 │     color: red;
+    7 │ }
+  
+  i This selector specificity is (1, 1, 1)
+  
+  > 1 │ a:hover #b {
+      │ ^^^^^^^^^^
+    2 │     color: red;
+    3 │ }
+  
+  i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details.
+  
+
+```
diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css
new file mode 100644
index 000000000000..730a7a0b314e
--- /dev/null
+++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css
@@ -0,0 +1,51 @@
+a {
+    color: red;
+}
+
+b a {
+    color: red;
+}
+
+d {
+    color: red;
+}
+
+c {
+    &>d {
+        color: red;
+    }
+}
+
+e:hover {
+    color: red;
+}
+
+e {
+    color: red;
+}
+
+:is(a, b, c, d) f {
+    color: red;
+}
+
+:is(#a, a) f {
+    color: red;
+}
+
+:where(#fake#fake#fake#fake#fake#fake, *) g {
+    color: red;
+}
+
+:where(*) g {
+    color: red;
+}
+
+
+#h h {
+    color: red;
+}
+
+/* This selector is overwritten by the one above it, but this is not an error because the rule only evaluates it as a compound selector */
+:where(#h) :is(h) {
+    color: red;
+}
\ No newline at end of file
diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css.snap
new file mode 100644
index 000000000000..78ddffc7cafb
--- /dev/null
+++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css.snap
@@ -0,0 +1,58 @@
+---
+source: crates/biome_css_analyze/tests/spec_tests.rs
+expression: valid.css
+---
+# Input
+```css
+a {
+    color: red;
+}
+
+b a {
+    color: red;
+}
+
+d {
+    color: red;
+}
+
+c {
+    &>d {
+        color: red;
+    }
+}
+
+e:hover {
+    color: red;
+}
+
+e {
+    color: red;
+}
+
+:is(a, b, c, d) f {
+    color: red;
+}
+
+:is(#a, a) f {
+    color: red;
+}
+
+:where(#fake#fake#fake#fake#fake#fake, *) g {
+    color: red;
+}
+
+:where(*) g {
+    color: red;
+}
+
+
+#h h {
+    color: red;
+}
+
+/* This selector is overwritten by the one above it, but this is not an error because the rule only evaluates it as a compound selector */
+:where(#h) :is(h) {
+    color: red;
+}
+```