Skip to content

Commit

Permalink
feat(linter): implement noDuplicateProperties (#4029)
Browse files Browse the repository at this point in the history
  • Loading branch information
togami2864 authored Oct 14, 2024
1 parent 6e1170e commit 5b7d158
Show file tree
Hide file tree
Showing 12 changed files with 723 additions and 81 deletions.
180 changes: 101 additions & 79 deletions crates/biome_configuration/src/analyzer/linter/rules.rs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions crates/biome_css_analyze/src/lint/nursery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 ,
Expand Down
102 changes: 102 additions & 0 deletions crates/biome_css_analyze/src/lint/nursery/no_duplicate_properties.rs
Original file line number Diff line number Diff line change
@@ -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<CssDeclarationOrRuleList>;
type State = (TextRange, (TextRange, String));
type Signals = Option<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Option<Self::State> {
let node = ctx.query();
let model = ctx.model();

let rule = model.get_rule_by_range(node.range())?;

let mut seen: FxHashMap<Cow<'_, str>, 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<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
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! {
<Emphasis>{duplicate_property_name}</Emphasis> " is already defined here."
})
.note(markup! {
"Remove or rename the duplicate property to ensure consistent styling."
}),
)
}
}
2 changes: 2 additions & 0 deletions crates/biome_css_analyze/src/options.rs

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

Original file line number Diff line number Diff line change
@@ -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; }
Loading

0 comments on commit 5b7d158

Please sign in to comment.