Skip to content

Commit

Permalink
feat(biome_css_analyzer): noDuplicateCustomProperties (#2783)
Browse files Browse the repository at this point in the history
  • Loading branch information
chansuke authored Sep 8, 2024
1 parent 280165b commit 8179da2
Show file tree
Hide file tree
Showing 12 changed files with 448 additions and 132 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b
#### New features

- Implement [nursery/useConsistentMemberAccessibility](https://github.com/biomejs/biome/issues/3271). Contributed by @seitarof
- Implement [nursery/noDuplicateCustomProperties](https://github.com/biomejs/biome/issues/2784). Contributed by @chansuke

#### Enhancements

Expand Down
286 changes: 154 additions & 132 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 @@ -3,6 +3,7 @@
use biome_analyze::declare_lint_group;

pub mod no_duplicate_at_import_rules;
pub mod no_duplicate_custom_properties;
pub mod no_duplicate_font_names;
pub mod no_duplicate_selectors_keyframe_block;
pub mod no_empty_block;
Expand All @@ -27,6 +28,7 @@ declare_lint_group! {
name : "nursery" ,
rules : [
self :: no_duplicate_at_import_rules :: NoDuplicateAtImportRules ,
self :: no_duplicate_custom_properties :: NoDuplicateCustomProperties ,
self :: no_duplicate_font_names :: NoDuplicateFontNames ,
self :: no_duplicate_selectors_keyframe_block :: NoDuplicateSelectorsKeyframeBlock ,
self :: no_empty_block :: NoEmptyBlock ,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use biome_analyze::{context::RuleContext, declare_lint_rule, Rule, RuleDiagnostic, RuleSource};
use biome_console::markup;
use biome_css_semantic::model::CssProperty;
use biome_css_syntax::CssDeclarationOrRuleList;
use biome_rowan::{AstNode, TextRange};
use rustc_hash::FxHashSet;

use crate::services::semantic::Semantic;

declare_lint_rule! {
/// Disallow duplicate custom properties within declaration blocks.
///
/// This rule checks the declaration blocks for duplicate custom properties.
///
/// ## Examples
///
/// ### Invalid
///
/// ```css,expect_diagnostic
/// a { --custom-property: pink; --custom-property: orange; }
/// ```
///
/// ```css,expect_diagnostic
/// a { --custom-property: pink; background: orange; --custom-property: orange }
/// ```
///
/// ### Valid
///
/// ```css
/// a { --custom-property: pink; }
/// ```
///
/// ```css
/// a { --custom-property: pink; --cUstOm-prOpErtY: orange; }
/// ```
///
pub NoDuplicateCustomProperties {
version: "next",
name: "noDuplicateCustomProperties",
language: "css",
recommended: true,
sources: &[RuleSource::Stylelint("declaration-block-no-duplicate-custom-properties")],
}
}

impl Rule for NoDuplicateCustomProperties {
type Query = Semantic<CssDeclarationOrRuleList>;
type State = TextRange;
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 properties = rule
.declarations
.iter()
.map(|d| d.property.clone())
.collect::<Vec<_>>();

if let Some(range) = check_duplicate_custom_properties(properties) {
return Some(range);
}
None
}

fn diagnostic(_: &RuleContext<Self>, range: &Self::State) -> Option<RuleDiagnostic> {
Some(
RuleDiagnostic::new(
rule_category!(),
range,
markup! {
"Duplicate custom properties are not allowed."
},
)
.note(markup! {
"Consider removing the duplicate custom property."
}),
)
}
}

fn check_duplicate_custom_properties(properties: Vec<CssProperty>) -> Option<TextRange> {
let mut seen = FxHashSet::default();

for property in properties {
if !seen.insert(property.name) {
return Some(property.range);
}
}

None
}
1 change: 1 addition & 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,8 @@
a { --custom-property: 1; --custom-property: 2; }
a { --custom-property: 1; color: pink; --custom-property: 1; }
a { --custom-property: 1; --cUstOm-prOpErtY: 1; color: pink; --cUstOm-prOpErtY: 1; }
a { --custom-property: pink; { &:hover { --custom-property: orange; --custom-property: black; } } }
a { --custom-property: pink; @media { --custom-property: orange; --custom-property: black; } }
@media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } }
a { --custom-property: pink; @media { --custom-property: orange; &::before { --custom-property: black; --custom-property: white; } } }
a { --custom-property: pink; @media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } } }
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
---
source: crates/biome_css_analyze/tests/spec_tests.rs
expression: invalid.css
---
# Input
```css
a { --custom-property: 1; --custom-property: 2; }
a { --custom-property: 1; color: pink; --custom-property: 1; }
a { --custom-property: 1; --cUstOm-prOpErtY: 1; color: pink; --cUstOm-prOpErtY: 1; }
a { --custom-property: pink; { &:hover { --custom-property: orange; --custom-property: black; } } }
a { --custom-property: pink; @media { --custom-property: orange; --custom-property: black; } }
@media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } }
a { --custom-property: pink; @media { --custom-property: orange; &::before { --custom-property: black; --custom-property: white; } } }
a { --custom-property: pink; @media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } } }
```

# Diagnostics
```
invalid.css:1:27 lint/nursery/noDuplicateCustomProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! Duplicate custom properties are not allowed.
> 1 │ a { --custom-property: 1; --custom-property: 2; }
│ ^^^^^^^^^^^^^^^^^
2 │ a { --custom-property: 1; color: pink; --custom-property: 1; }
3 │ a { --custom-property: 1; --cUstOm-prOpErtY: 1; color: pink; --cUstOm-prOpErtY: 1; }
i Consider removing the duplicate custom property.
```

```
invalid.css:2:40 lint/nursery/noDuplicateCustomProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! Duplicate custom properties are not allowed.
1 │ a { --custom-property: 1; --custom-property: 2; }
> 2 │ a { --custom-property: 1; color: pink; --custom-property: 1; }
│ ^^^^^^^^^^^^^^^^^
3 │ a { --custom-property: 1; --cUstOm-prOpErtY: 1; color: pink; --cUstOm-prOpErtY: 1; }
4 │ a { --custom-property: pink; { &:hover { --custom-property: orange; --custom-property: black; } } }
i Consider removing the duplicate custom property.
```

```
invalid.css:3:62 lint/nursery/noDuplicateCustomProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! Duplicate custom properties are not allowed.
1 │ a { --custom-property: 1; --custom-property: 2; }
2 │ a { --custom-property: 1; color: pink; --custom-property: 1; }
> 3 │ a { --custom-property: 1; --cUstOm-prOpErtY: 1; color: pink; --cUstOm-prOpErtY: 1; }
│ ^^^^^^^^^^^^^^^^^
4 │ a { --custom-property: pink; { &:hover { --custom-property: orange; --custom-property: black; } } }
5 │ a { --custom-property: pink; @media { --custom-property: orange; --custom-property: black; } }
i Consider removing the duplicate custom property.
```

```
invalid.css:4:69 lint/nursery/noDuplicateCustomProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! Duplicate custom properties are not allowed.
2 │ a { --custom-property: 1; color: pink; --custom-property: 1; }
3 │ a { --custom-property: 1; --cUstOm-prOpErtY: 1; color: pink; --cUstOm-prOpErtY: 1; }
> 4 │ a { --custom-property: pink; { &:hover { --custom-property: orange; --custom-property: black; } } }
│ ^^^^^^^^^^^^^^^^^
5 │ a { --custom-property: pink; @media { --custom-property: orange; --custom-property: black; } }
6 │ @media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } }
i Consider removing the duplicate custom property.
```

```
invalid.css:5:66 lint/nursery/noDuplicateCustomProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! Duplicate custom properties are not allowed.
3 │ a { --custom-property: 1; --cUstOm-prOpErtY: 1; color: pink; --cUstOm-prOpErtY: 1; }
4 │ a { --custom-property: pink; { &:hover { --custom-property: orange; --custom-property: black; } } }
> 5 │ a { --custom-property: pink; @media { --custom-property: orange; --custom-property: black; } }
│ ^^^^^^^^^^^^^^^^^
6 │ @media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } }
7 │ a { --custom-property: pink; @media { --custom-property: orange; &::before { --custom-property: black; --custom-property: white; } } }
i Consider removing the duplicate custom property.
```

```
invalid.css:6:70 lint/nursery/noDuplicateCustomProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! Duplicate custom properties are not allowed.
4 │ a { --custom-property: pink; { &:hover { --custom-property: orange; --custom-property: black; } } }
5 │ a { --custom-property: pink; @media { --custom-property: orange; --custom-property: black; } }
> 6 │ @media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } }
│ ^^^^^^^^^^^^^^^^^
7 │ a { --custom-property: pink; @media { --custom-property: orange; &::before { --custom-property: black; --custom-property: white; } } }
8 │ a { --custom-property: pink; @media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } } }
i Consider removing the duplicate custom property.
```

```
invalid.css:7:104 lint/nursery/noDuplicateCustomProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! Duplicate custom properties are not allowed.
5 │ a { --custom-property: pink; @media { --custom-property: orange; --custom-property: black; } }
6 │ @media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } }
> 7 │ a { --custom-property: pink; @media { --custom-property: orange; &::before { --custom-property: black; --custom-property: white; } } }
│ ^^^^^^^^^^^^^^^^^
8 │ a { --custom-property: pink; @media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } } }
9 │
i Consider removing the duplicate custom property.
```

```
invalid.css:8:99 lint/nursery/noDuplicateCustomProperties ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! Duplicate custom properties are not allowed.
6 │ @media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } }
7 │ a { --custom-property: pink; @media { --custom-property: orange; &::before { --custom-property: black; --custom-property: white; } } }
> 8 │ a { --custom-property: pink; @media { --custom-property: orange; .foo { --custom-property: black; --custom-property: white; } } }
│ ^^^^^^^^^^^^^^^^^
9 │
i Consider removing the duplicate custom property.
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
a { --custom-property: 1; }
a { --custom-property: 1; --cUstOm-prOpErtY: 1; }
a { --custom-property: 1; color: pink; --cUstOm-prOpErtY: 1 }
a { color: var(--custom-property, --custom-property) }
a { --custom-property: pink; @media { --custom-property: orange; } }
a { --custom-property: pink; @media { --custom-property: orange; &::before { --custom-property: black; } } }
a { --custom-property: pink; { &:hover { --custom-property: orange; --cUstOm-prOpErtY: black; } } }
a { --cUstOm-prOpErtY: pink; { &:hover { --custom-property: orange; --cUstOm-prOpErtY: black; } } }
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
source: crates/biome_css_analyze/tests/spec_tests.rs
expression: valid.css
---
# Input
```css
a { --custom-property: 1; }
a { --custom-property: 1; --cUstOm-prOpErtY: 1; }
a { --custom-property: 1; color: pink; --cUstOm-prOpErtY: 1 }
a { color: var(--custom-property, --custom-property) }
a { --custom-property: pink; @media { --custom-property: orange; } }
a { --custom-property: pink; @media { --custom-property: orange; &::before { --custom-property: black; } } }
a { --custom-property: pink; { &:hover { --custom-property: orange; --cUstOm-prOpErtY: black; } } }
a { --cUstOm-prOpErtY: pink; { &:hover { --custom-property: orange; --cUstOm-prOpErtY: black; } } }
```
1 change: 1 addition & 0 deletions crates/biome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ define_categories! {
"lint/nursery/noConsole": "https://biomejs.dev/linter/rules/no-console",
"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",
"lint/nursery/noDuplicateElseIf": "https://biomejs.dev/linter/rules/no-duplicate-else-if",
"lint/nursery/noDuplicateFontNames": "https://biomejs.dev/linter/rules/no-font-family-duplicate-names",
"lint/nursery/noDuplicateSelectorsKeyframeBlock": "https://biomejs.dev/linter/rules/no-duplicate-selectors-keyframe-block",
Expand Down
5 changes: 5 additions & 0 deletions packages/@biomejs/backend-jsonrpc/src/workspace.ts

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

7 changes: 7 additions & 0 deletions packages/@biomejs/biome/configuration_schema.json

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

0 comments on commit 8179da2

Please sign in to comment.