diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index cce300963082..e74d6c8a6843 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -3293,6 +3293,10 @@ pub struct Nursery { #[serde(skip_serializing_if = "Option::is_none")] pub no_duplicate_else_if: Option>, + #[doc = "Disallow duplicate properties within declaration blocks."] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_duplicate_properties: + Option>, #[doc = "No duplicated fields in GraphQL operations."] #[serde(skip_serializing_if = "Option::is_none")] pub no_duplicated_fields: @@ -3452,6 +3456,7 @@ impl Nursery { "noDocumentImportInPage", "noDuplicateCustomProperties", "noDuplicateElseIf", + "noDuplicateProperties", "noDuplicatedFields", "noDynamicNamespaceImportAccess", "noEnum", @@ -3494,6 +3499,7 @@ impl Nursery { "noDescendingSpecificity", "noDuplicateCustomProperties", "noDuplicateElseIf", + "noDuplicateProperties", "noDuplicatedFields", "noMissingVarFunction", "noUnknownPseudoClass", @@ -3510,15 +3516,16 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -3564,6 +3571,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -3610,191 +3618,196 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_duplicated_fields.as_ref() { + if let Some(rule) = self.no_duplicate_properties.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - 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[7])); } } - 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[8])); } } - 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[9])); } } - if let Some(rule) = self.no_head_element.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[10])); } } - if let Some(rule) = self.no_head_import_in_document.as_ref() { + if let Some(rule) = self.no_head_element.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_img_element.as_ref() { + if let Some(rule) = self.no_head_import_in_document.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_irregular_whitespace.as_ref() { + if let Some(rule) = self.no_img_element.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - 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[14])); } } - 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[15])); } } - 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[16])); } } - 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[17])); } } - 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[18])); } } - 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[19])); } } - 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[20])); } } - 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[21])); } } - 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[22])); } } - 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[23])); } } - 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[24])); } } - 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[25])); } } - if let Some(rule) = self.no_unknown_type_selector.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[26])); } } - if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { + if let Some(rule) = self.no_unknown_type_selector.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - 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[28])); } } - 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[29])); } } - 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[30])); } } - if let Some(rule) = self.use_at_index.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[31])); } } - if let Some(rule) = self.use_component_export_only_modules.as_ref() { + if let Some(rule) = self.use_at_index.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - 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[33])); } } - 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[34])); } } - 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[35])); } } - if let Some(rule) = self.use_explicit_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[36])); } } - if let Some(rule) = self.use_guard_for_in.as_ref() { + if let Some(rule) = self.use_explicit_type.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_guard_for_in.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - 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[39])); } } - 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[40])); } } - 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[41])); } } - 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[42])); } } + 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[43])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> FxHashSet> { @@ -3829,191 +3842,196 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_duplicated_fields.as_ref() { + if let Some(rule) = self.no_duplicate_properties.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - 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[7])); } } - 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[8])); } } - 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[9])); } } - if let Some(rule) = self.no_head_element.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[10])); } } - if let Some(rule) = self.no_head_import_in_document.as_ref() { + if let Some(rule) = self.no_head_element.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_img_element.as_ref() { + if let Some(rule) = self.no_head_import_in_document.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_irregular_whitespace.as_ref() { + if let Some(rule) = self.no_img_element.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - 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[14])); } } - 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[15])); } } - 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[16])); } } - 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[17])); } } - 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[18])); } } - 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[19])); } } - 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[20])); } } - 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[21])); } } - 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[22])); } } - 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[23])); } } - 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[24])); } } - 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[25])); } } - if let Some(rule) = self.no_unknown_type_selector.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[26])); } } - if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { + if let Some(rule) = self.no_unknown_type_selector.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - 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[28])); } } - 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[29])); } } - 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[30])); } } - if let Some(rule) = self.use_at_index.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[31])); } } - if let Some(rule) = self.use_component_export_only_modules.as_ref() { + if let Some(rule) = self.use_at_index.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - 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[33])); } } - 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[34])); } } - 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[35])); } } - if let Some(rule) = self.use_explicit_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[36])); } } - if let Some(rule) = self.use_guard_for_in.as_ref() { + if let Some(rule) = self.use_explicit_type.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_guard_for_in.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - 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[39])); } } - 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[40])); } } - 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[41])); } } - 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[42])); } } + 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[43])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -4074,6 +4092,10 @@ impl Nursery { .no_duplicate_else_if .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "noDuplicateProperties" => self + .no_duplicate_properties + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noDuplicatedFields" => self .no_duplicated_fields .as_ref() diff --git a/crates/biome_css_analyze/src/lint/nursery.rs b/crates/biome_css_analyze/src/lint/nursery.rs index 7e9c91dbf140..6d16b3056165 100644 --- a/crates/biome_css_analyze/src/lint/nursery.rs +++ b/crates/biome_css_analyze/src/lint/nursery.rs @@ -4,6 +4,7 @@ use biome_analyze::declare_lint_group; pub mod no_descending_specificity; pub mod no_duplicate_custom_properties; +pub mod no_duplicate_properties; pub mod no_irregular_whitespace; pub mod no_missing_var_function; pub mod no_unknown_pseudo_class; @@ -17,6 +18,7 @@ declare_lint_group! { rules : [ self :: no_descending_specificity :: NoDescendingSpecificity , self :: no_duplicate_custom_properties :: NoDuplicateCustomProperties , + self :: no_duplicate_properties :: NoDuplicateProperties , self :: no_irregular_whitespace :: NoIrregularWhitespace , self :: no_missing_var_function :: NoMissingVarFunction , self :: no_unknown_pseudo_class :: NoUnknownPseudoClass , diff --git a/crates/biome_css_analyze/src/lint/nursery/no_duplicate_properties.rs b/crates/biome_css_analyze/src/lint/nursery/no_duplicate_properties.rs new file mode 100644 index 000000000000..e0595b1fac44 --- /dev/null +++ b/crates/biome_css_analyze/src/lint/nursery/no_duplicate_properties.rs @@ -0,0 +1,102 @@ +use std::{borrow::Cow, collections::hash_map::Entry}; + +use biome_analyze::{context::RuleContext, declare_lint_rule, Rule, RuleDiagnostic, RuleSource}; +use biome_console::markup; +use biome_css_syntax::CssDeclarationOrRuleList; +use biome_rowan::{AstNode, TextRange}; +use biome_string_case::StrOnlyExtension; +use rustc_hash::FxHashMap; + +use crate::services::semantic::Semantic; + +declare_lint_rule! { + /// Disallow duplicate properties within declaration blocks. + /// + /// This rule checks the declaration blocks for duplicate properties. It ignores custom properties. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```css,expect_diagnostic + /// a { + /// color: pink; + /// color: orange; + /// } + /// ``` + /// + /// ### Valid + /// + /// ```css + /// a { + /// color: pink; + /// background: orange; + /// } + /// ``` + /// + pub NoDuplicateProperties { + version: "next", + name: "noDuplicateProperties", + language: "css", + recommended: true, + sources: &[RuleSource::Stylelint("declaration-block-no-duplicate-properties")], + } +} + +impl Rule for NoDuplicateProperties { + type Query = Semantic; + type State = (TextRange, (TextRange, String)); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Option { + let node = ctx.query(); + let model = ctx.model(); + + let rule = model.get_rule_by_range(node.range())?; + + let mut seen: FxHashMap, TextRange> = FxHashMap::default(); + + for declaration in rule.declarations.iter() { + let prop = &declaration.property; + let prop_name = prop.name.to_lowercase_cow(); + let prop_range = prop.range; + + let is_custom_property = prop_name.starts_with("--"); + + if is_custom_property { + continue; + } + + match seen.entry(prop_name.clone()) { + Entry::Occupied(entry) => { + return Some((*entry.get(), (prop_range, prop_name.to_string()))); + } + Entry::Vacant(_) => { + seen.insert(prop_name, prop_range); + } + } + } + + None + } + + fn diagnostic(_: &RuleContext, state: &Self::State) -> Option { + let (first_occurrence_range, (duplicate_range, duplicate_property_name)) = state; + Some( + RuleDiagnostic::new( + rule_category!(), + duplicate_range, + markup! { + "Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally." + }, + ) + .detail(first_occurrence_range, markup! { + {duplicate_property_name} " is already defined here." + }) + .note(markup! { + "Remove or rename the duplicate property to ensure consistent styling." + }), + ) + } +} diff --git a/crates/biome_css_analyze/src/options.rs b/crates/biome_css_analyze/src/options.rs index ae6fc3c26304..2edbb3a490ae 100644 --- a/crates/biome_css_analyze/src/options.rs +++ b/crates/biome_css_analyze/src/options.rs @@ -6,6 +6,8 @@ pub type NoDescendingSpecificity = < lint :: nursery :: no_descending_specificit 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 ; +pub type NoDuplicateProperties = + ::Options; pub type NoDuplicateSelectorsKeyframeBlock = < lint :: suspicious :: no_duplicate_selectors_keyframe_block :: NoDuplicateSelectorsKeyframeBlock as biome_analyze :: Rule > :: Options ; pub type NoEmptyBlock = ::Options; diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/invalid.css b/crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/invalid.css new file mode 100644 index 000000000000..107704483947 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/invalid.css @@ -0,0 +1,44 @@ +a { + color: pink; + color: orange; +} + +a { + color: pink; + color: pink; + color: pink; +} + +a { + color: pink; + color: pink; + color: orange; +} + +a { + color: pink; + background: orange; + color: orange; +} + +a { + color: pink; + background: orange; + background: pink; +} + +a { color: pink; { &:hover { color: orange; color: black; } } } + +a { color: pink; @media { color: orange; color: black; } } + +@media { color: orange; .foo { color: black; color: white; } } + +a { color: pink; @media { color: orange; &::before { color: black; color: white; } } } + +a { color: pink; @media { color: orange; .foo { color: black; color: white; } } } + +a { -webkit-border-radius: 12px; -webkit-border-radius: 10px; } + +a { color: red !important; color: blue; } + +a { color: red !important; color: blue !important; } diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/invalid.css.snap new file mode 100644 index 000000000000..3b56bf377764 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/invalid.css.snap @@ -0,0 +1,385 @@ +--- +source: crates/biome_css_analyze/tests/spec_tests.rs +expression: invalid.css +--- +# Input +```css +a { + color: pink; + color: orange; +} + +a { + color: pink; + color: pink; + color: pink; +} + +a { + color: pink; + color: pink; + color: orange; +} + +a { + color: pink; + background: orange; + color: orange; +} + +a { + color: pink; + background: orange; + background: pink; +} + +a { color: pink; { &:hover { color: orange; color: black; } } } + +a { color: pink; @media { color: orange; color: black; } } + +@media { color: orange; .foo { color: black; color: white; } } + +a { color: pink; @media { color: orange; &::before { color: black; color: white; } } } + +a { color: pink; @media { color: orange; .foo { color: black; color: white; } } } + +a { -webkit-border-radius: 12px; -webkit-border-radius: 10px; } + +a { color: red !important; color: blue; } + +a { color: red !important; color: blue !important; } + +``` + +# Diagnostics +``` +invalid.css:3:5 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 1 │ a { + 2 │ color: pink; + > 3 │ color: orange; + │ ^^^^^ + 4 │ } + 5 │ + + i color is already defined here. + + 1 │ a { + > 2 │ color: pink; + │ ^^^^^ + 3 │ color: orange; + 4 │ } + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` + +``` +invalid.css:8:5 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 6 │ a { + 7 │ color: pink; + > 8 │ color: pink; + │ ^^^^^ + 9 │ color: pink; + 10 │ } + + i color is already defined here. + + 6 │ a { + > 7 │ color: pink; + │ ^^^^^ + 8 │ color: pink; + 9 │ color: pink; + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` + +``` +invalid.css:14:5 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 12 │ a { + 13 │ color: pink; + > 14 │ color: pink; + │ ^^^^^ + 15 │ color: orange; + 16 │ } + + i color is already defined here. + + 12 │ a { + > 13 │ color: pink; + │ ^^^^^ + 14 │ color: pink; + 15 │ color: orange; + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` + +``` +invalid.css:21:5 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 19 │ color: pink; + 20 │ background: orange; + > 21 │ color: orange; + │ ^^^^^ + 22 │ } + 23 │ + + i color is already defined here. + + 18 │ a { + > 19 │ color: pink; + │ ^^^^^ + 20 │ background: orange; + 21 │ color: orange; + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` + +``` +invalid.css:27:5 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 25 │ color: pink; + 26 │ background: orange; + > 27 │ background: pink; + │ ^^^^^^^^^^ + 28 │ } + 29 │ + + i background is already defined here. + + 24 │ a { + 25 │ color: pink; + > 26 │ background: orange; + │ ^^^^^^^^^^ + 27 │ background: pink; + 28 │ } + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` + +``` +invalid.css:30:45 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 28 │ } + 29 │ + > 30 │ a { color: pink; { &:hover { color: orange; color: black; } } } + │ ^^^^^ + 31 │ + 32 │ a { color: pink; @media { color: orange; color: black; } } + + i color is already defined here. + + 28 │ } + 29 │ + > 30 │ a { color: pink; { &:hover { color: orange; color: black; } } } + │ ^^^^^ + 31 │ + 32 │ a { color: pink; @media { color: orange; color: black; } } + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` + +``` +invalid.css:32:42 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 30 │ a { color: pink; { &:hover { color: orange; color: black; } } } + 31 │ + > 32 │ a { color: pink; @media { color: orange; color: black; } } + │ ^^^^^ + 33 │ + 34 │ @media { color: orange; .foo { color: black; color: white; } } + + i color is already defined here. + + 30 │ a { color: pink; { &:hover { color: orange; color: black; } } } + 31 │ + > 32 │ a { color: pink; @media { color: orange; color: black; } } + │ ^^^^^ + 33 │ + 34 │ @media { color: orange; .foo { color: black; color: white; } } + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` + +``` +invalid.css:34:46 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 32 │ a { color: pink; @media { color: orange; color: black; } } + 33 │ + > 34 │ @media { color: orange; .foo { color: black; color: white; } } + │ ^^^^^ + 35 │ + 36 │ a { color: pink; @media { color: orange; &::before { color: black; color: white; } } } + + i color is already defined here. + + 32 │ a { color: pink; @media { color: orange; color: black; } } + 33 │ + > 34 │ @media { color: orange; .foo { color: black; color: white; } } + │ ^^^^^ + 35 │ + 36 │ a { color: pink; @media { color: orange; &::before { color: black; color: white; } } } + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` + +``` +invalid.css:36:68 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 34 │ @media { color: orange; .foo { color: black; color: white; } } + 35 │ + > 36 │ a { color: pink; @media { color: orange; &::before { color: black; color: white; } } } + │ ^^^^^ + 37 │ + 38 │ a { color: pink; @media { color: orange; .foo { color: black; color: white; } } } + + i color is already defined here. + + 34 │ @media { color: orange; .foo { color: black; color: white; } } + 35 │ + > 36 │ a { color: pink; @media { color: orange; &::before { color: black; color: white; } } } + │ ^^^^^ + 37 │ + 38 │ a { color: pink; @media { color: orange; .foo { color: black; color: white; } } } + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` + +``` +invalid.css:38:63 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 36 │ a { color: pink; @media { color: orange; &::before { color: black; color: white; } } } + 37 │ + > 38 │ a { color: pink; @media { color: orange; .foo { color: black; color: white; } } } + │ ^^^^^ + 39 │ + 40 │ a { -webkit-border-radius: 12px; -webkit-border-radius: 10px; } + + i color is already defined here. + + 36 │ a { color: pink; @media { color: orange; &::before { color: black; color: white; } } } + 37 │ + > 38 │ a { color: pink; @media { color: orange; .foo { color: black; color: white; } } } + │ ^^^^^ + 39 │ + 40 │ a { -webkit-border-radius: 12px; -webkit-border-radius: 10px; } + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` + +``` +invalid.css:40:34 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 38 │ a { color: pink; @media { color: orange; .foo { color: black; color: white; } } } + 39 │ + > 40 │ a { -webkit-border-radius: 12px; -webkit-border-radius: 10px; } + │ ^^^^^^^^^^^^^^^^^^^^^ + 41 │ + 42 │ a { color: red !important; color: blue; } + + i -webkit-border-radius is already defined here. + + 38 │ a { color: pink; @media { color: orange; .foo { color: black; color: white; } } } + 39 │ + > 40 │ a { -webkit-border-radius: 12px; -webkit-border-radius: 10px; } + │ ^^^^^^^^^^^^^^^^^^^^^ + 41 │ + 42 │ a { color: red !important; color: blue; } + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` + +``` +invalid.css:42:28 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 40 │ a { -webkit-border-radius: 12px; -webkit-border-radius: 10px; } + 41 │ + > 42 │ a { color: red !important; color: blue; } + │ ^^^^^ + 43 │ + 44 │ a { color: red !important; color: blue !important; } + + i color is already defined here. + + 40 │ a { -webkit-border-radius: 12px; -webkit-border-radius: 10px; } + 41 │ + > 42 │ a { color: red !important; color: blue; } + │ ^^^^^ + 43 │ + 44 │ a { color: red !important; color: blue !important; } + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` + +``` +invalid.css:44:28 lint/nursery/noDuplicateProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally. + + 42 │ a { color: red !important; color: blue; } + 43 │ + > 44 │ a { color: red !important; color: blue !important; } + │ ^^^^^ + 45 │ + + i color is already defined here. + + 42 │ a { color: red !important; color: blue; } + 43 │ + > 44 │ a { color: red !important; color: blue !important; } + │ ^^^^^ + 45 │ + + i Remove or rename the duplicate property to ensure consistent styling. + + +``` diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/valid.css b/crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/valid.css new file mode 100644 index 000000000000..9c5efe6a3a3c --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/valid.css @@ -0,0 +1,32 @@ +a { + color: pink; +} + +a { + color: pink; + background: orange; +} + +a { color: pink; { &:hover { color: orange; } } } + +a { color: pink; @media { color: orange; } } + +a { color: pink; @media { color: orange; &::before { color: black; } } } + +a { --custom-property: 0; --custom-property: 1; } + +/* TODO */ +/* @fontface { + src: url(font.eof); + src: url(font.woff); +} + +@fontface { + src: url(font.eof); + src: url(font.woff); +} + +@fontface { + src: url(font.eof); + src: url(font.woff); +} */ diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/valid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/valid.css.snap new file mode 100644 index 000000000000..3c8650efff49 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/valid.css.snap @@ -0,0 +1,40 @@ +--- +source: crates/biome_css_analyze/tests/spec_tests.rs +expression: valid.css +--- +# Input +```css +a { + color: pink; +} + +a { + color: pink; + background: orange; +} + +a { color: pink; { &:hover { color: orange; } } } + +a { color: pink; @media { color: orange; } } + +a { color: pink; @media { color: orange; &::before { color: black; } } } + +a { --custom-property: 0; --custom-property: 1; } + +/* TODO */ +/* @fontface { + src: url(font.eof); + src: url(font.woff); +} + +@fontface { + src: url(font.eof); + src: url(font.woff); +} + +@fontface { + src: url(font.eof); + src: url(font.woff); +} */ + +``` diff --git a/crates/biome_css_semantic/src/events.rs b/crates/biome_css_semantic/src/events.rs index b2e6bb40c246..d00fab3bfc36 100644 --- a/crates/biome_css_semantic/src/events.rs +++ b/crates/biome_css_semantic/src/events.rs @@ -100,11 +100,11 @@ impl SemanticEventExtractor { self.stash.push_back(SemanticEvent::PropertyDeclaration { property: CssProperty { name: property_name.text_trimmed().to_string(), - range: property_name.text_range(), + range: property_name.text_trimmed_range(), }, value: CssValue { text: value.text_trimmed().to_string(), - range: value.text_range(), + range: value.text_trimmed_range(), }, range: node.text_range(), }); diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 1af118be0cd5..f0b6584e3002 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -143,6 +143,7 @@ define_categories! { "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", "lint/nursery/noDuplicateElseIf": "https://biomejs.dev/linter/rules/no-duplicate-else-if", + "lint/nursery/noDuplicateProperties": "https://biomejs.dev/linter/rules/no-duplicate-properties", "lint/nursery/noDuplicatedFields": "https://biomejs.dev/linter/rules/no-duplicated-fields", "lint/nursery/noDynamicNamespaceImportAccess": "https://biomejs.dev/linter/rules/no-dynamic-namespace-import-access", "lint/nursery/noEnum": "https://biomejs.dev/linter/rules/no-enum", diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 2f51772896a2..aa177d187f0a 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1242,6 +1242,10 @@ export interface Nursery { * Disallow duplicate conditions in if-else-if chains */ noDuplicateElseIf?: RuleConfiguration_for_Null; + /** + * Disallow duplicate properties within declaration blocks. + */ + noDuplicateProperties?: RuleConfiguration_for_Null; /** * No duplicated fields in GraphQL operations. */ @@ -2903,6 +2907,7 @@ export type Category = | "lint/nursery/noDuplicateAtImportRules" | "lint/nursery/noDuplicateCustomProperties" | "lint/nursery/noDuplicateElseIf" + | "lint/nursery/noDuplicateProperties" | "lint/nursery/noDuplicatedFields" | "lint/nursery/noDynamicNamespaceImportAccess" | "lint/nursery/noEnum" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index f470c1cbe1e3..7df37a2bcf13 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -2127,6 +2127,13 @@ { "type": "null" } ] }, + "noDuplicateProperties": { + "description": "Disallow duplicate properties within declaration blocks.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noDuplicatedFields": { "description": "No duplicated fields in GraphQL operations.", "anyOf": [