Skip to content

Commit a8798ea

Browse files
authored
feat: useNamedLayer (#11377)
1 parent 85aac73 commit a8798ea

14 files changed

Lines changed: 298 additions & 2 deletions

File tree

.changeset/calm-bottles-jog.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Added a new nursery rule [`useNamedLayer`](https://biomejs.dev/linter/rules/use-named-layer) which disallows anonymous cascade layers.
6+
7+
```css
8+
@layer {
9+
a {
10+
color: red;
11+
}
12+
}
13+
```

crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_configuration/src/analyzer/linter/rules.rs

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_configuration/src/generated/linter_options_check.rs

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use biome_analyze::{
2+
Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
3+
};
4+
use biome_console::markup;
5+
use biome_css_syntax::{CssImportAnonymousLayer, CssLayerAtRule};
6+
use biome_diagnostics::Severity;
7+
use biome_rowan::{AstNode, AstSeparatedList, declare_node_union};
8+
use biome_rule_options::use_named_layer::UseNamedLayerOptions;
9+
10+
declare_lint_rule! {
11+
/// Disallow anonymous cascade layers.
12+
///
13+
/// A cascade layer created with `@layer { ... }` or imported with
14+
/// `@import "..." layer` has no name. Anonymous layers get their own place
15+
/// in the cascade order, but because they cannot be referred to by name,
16+
/// no later rule can add styles to them or reorder them. This makes the
17+
/// cascade harder to reason about and prevents reusing the layer.
18+
///
19+
/// Give every layer a name so it can be referenced, appended to, and
20+
/// ordered explicitly through a `@layer` statement.
21+
///
22+
/// ## Examples
23+
///
24+
/// ### Invalid
25+
///
26+
/// ```css,expect_diagnostic
27+
/// @layer {
28+
/// a {
29+
/// color: red;
30+
/// }
31+
/// }
32+
/// ```
33+
///
34+
/// ```css,expect_diagnostic
35+
/// @import "theme.css" layer;
36+
/// ```
37+
///
38+
/// ### Valid
39+
///
40+
/// ```css
41+
/// @layer base {
42+
/// a {
43+
/// color: red;
44+
/// }
45+
/// }
46+
/// ```
47+
///
48+
/// ```css
49+
/// @import "theme.css" layer(base);
50+
/// ```
51+
///
52+
pub UseNamedLayer {
53+
version: "next",
54+
name: "useNamedLayer",
55+
language: "css",
56+
recommended: false,
57+
severity: Severity::Warning,
58+
sources: &[RuleSource::EslintCss("use-layers").inspired()],
59+
}
60+
}
61+
62+
impl Rule for UseNamedLayer {
63+
type Query = Ast<AnyUseNamedLayerQuery>;
64+
type State = ();
65+
type Signals = Option<Self::State>;
66+
type Options = UseNamedLayerOptions;
67+
68+
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
69+
let node = ctx.query();
70+
71+
match node {
72+
AnyUseNamedLayerQuery::CssImportAnonymousLayer(_) => return Some(()),
73+
AnyUseNamedLayerQuery::CssLayerAtRule(at_rule) => {
74+
let layer = at_rule.layer().ok()?;
75+
let dec = layer.as_css_layer_declaration()?;
76+
if dec.references().is_empty() {
77+
return Some(());
78+
}
79+
}
80+
}
81+
82+
None
83+
}
84+
85+
fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> {
86+
let node = ctx.query();
87+
Some(
88+
RuleDiagnostic::new(
89+
rule_category!(),
90+
node.range(),
91+
markup! {
92+
"Missing cascade layer name."
93+
},
94+
)
95+
.note(markup! {
96+
"An anonymous layer cannot be referenced, so later rules cannot append to it or reorder it."
97+
})
98+
.note(markup! {
99+
"Give the layer a name, for example "<Emphasis>"@layer base { ... }"</Emphasis>"."
100+
}),
101+
)
102+
}
103+
}
104+
105+
declare_node_union! {
106+
pub AnyUseNamedLayerQuery = CssLayerAtRule | CssImportAnonymousLayer
107+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* should generate diagnostics */
2+
@import url(foo.css) layer;
3+
4+
@layer {
5+
.test {
6+
color: red;
7+
}
8+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
source: crates/biome_css_analyze/tests/spec_tests.rs
3+
expression: invalid.css
4+
---
5+
# Input
6+
```css
7+
/* should generate diagnostics */
8+
@import url(foo.css) layer;
9+
10+
@layer {
11+
.test {
12+
color: red;
13+
}
14+
}
15+
16+
```
17+
18+
# Diagnostics
19+
```
20+
invalid.css:2:22 lint/nursery/useNamedLayer ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
21+
22+
! Missing cascade layer name.
23+
24+
1 │ /* should generate diagnostics */
25+
> 2 │ @import url(foo.css) layer;
26+
│ ^^^^^
27+
3 │
28+
4 │ @layer {
29+
30+
i An anonymous layer cannot be referenced, so later rules cannot append to it or reorder it.
31+
32+
i Give the layer a name, for example @layer base { ... }.
33+
34+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
35+
36+
37+
```
38+
39+
```
40+
invalid.css:4:2 lint/nursery/useNamedLayer ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
41+
42+
! Missing cascade layer name.
43+
44+
2 │ @import url(foo.css) layer;
45+
3
46+
> 4 │ @layer {
47+
│ ^^^^^^^
48+
> 5 │ .test {
49+
> 6 │ color: red;
50+
> 7 │ }
51+
> 8}
52+
│ ^
53+
9 │
54+
55+
i An anonymous layer cannot be referenced, so later rules cannot append to it or reorder it.
56+
57+
i Give the layer a name, for example @layer base { ... }.
58+
59+
i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.
60+
61+
62+
```
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* should not generate diagnostics */
2+
@import url(foo.css) layer(reset);
3+
4+
@layer reset {
5+
.test {
6+
color: red;
7+
}
8+
}
9+
10+
@layer reset.text {
11+
.test {
12+
color: red;
13+
}
14+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: crates/biome_css_analyze/tests/spec_tests.rs
3+
expression: valid.css
4+
---
5+
# Input
6+
```css
7+
/* should not generate diagnostics */
8+
@import url(foo.css) layer(reset);
9+
10+
@layer reset {
11+
.test {
12+
color: red;
13+
}
14+
}
15+
16+
@layer reset.text {
17+
.test {
18+
color: red;
19+
}
20+
}
21+
22+
```

crates/biome_diagnostics_categories/src/categories.rs

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)