-
-
Notifications
You must be signed in to change notification settings - Fork 500
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(linter): implement
noDuplicateProperties
(#4029)
- Loading branch information
1 parent
6e1170e
commit 5b7d158
Showing
12 changed files
with
723 additions
and
81 deletions.
There are no files selected for viewing
180 changes: 101 additions & 79 deletions
180
crates/biome_configuration/src/analyzer/linter/rules.rs
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
crates/biome_css_analyze/src/lint/nursery/no_duplicate_properties.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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." | ||
}), | ||
) | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
44 changes: 44 additions & 0 deletions
44
crates/biome_css_analyze/tests/specs/nursery/noDuplicateProperties/invalid.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } |
Oops, something went wrong.