Skip to content

Commit 54e8239

Browse files
authored
fix(linter): correctly track is:global and current styles (#10746)
1 parent 0efe244 commit 54e8239

12 files changed

Lines changed: 181 additions & 23 deletions

File tree

.changeset/silver-parents-show.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed an issue where [`noUndeclaredClasses`](https://biomejs.dev/linter/rules/no-undeclared-classes) didn't correctly detect styles defined inside the Astro directive `is:global`.

.changeset/tired-grapes-know.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed an issue where [`noUndeclaredClasses`](https://biomejs.dev/linter/rules/no-undeclared-classes) didn't detect styles declared inside HTML documents.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!-- should not generate diagnostics -->
2+
<div class="app">
3+
<label class="visually-hidden" for="filename">Component filename</label>
4+
</div>
5+
6+
<style>
7+
.app {
8+
display: flex;
9+
flex-direction: column;
10+
height: 100%;
11+
background: var(--bg);
12+
color: var(--fg);
13+
}
14+
</style>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: crates/biome_html_analyze/tests/spec_tests.rs
3+
expression: file.svelte
4+
---
5+
# Input
6+
```svelte
7+
<!-- should not generate diagnostics -->
8+
<div class="app">
9+
<label class="visually-hidden" for="filename">Component filename</label>
10+
</div>
11+
12+
<style>
13+
.app {
14+
display: flex;
15+
flex-direction: column;
16+
height: 100%;
17+
background: var(--bg);
18+
color: var(--fg);
19+
}
20+
</style>
21+
22+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!-- should not generate diagnostics -->
2+
---
3+
export const prerender = true;
4+
5+
import Playground from './file.svelte';
6+
---
7+
8+
<!doctype html>
9+
<html lang="en">
10+
<Playground client:only="svelte" />
11+
</html>
12+
13+
<style is:global>
14+
.visually-hidden {
15+
position: absolute;
16+
width: 1px;
17+
height: 1px;
18+
padding: 0;
19+
margin: -1px;
20+
overflow: hidden;
21+
clip: rect(0, 0, 0, 0);
22+
white-space: nowrap;
23+
border: 0;
24+
}
25+
</style>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
source: crates/biome_html_analyze/tests/spec_tests.rs
3+
expression: index.astro
4+
---
5+
# Input
6+
```astro
7+
<!-- should not generate diagnostics -->
8+
---
9+
export const prerender = true;
10+
11+
import Playground from './file.svelte';
12+
---
13+
14+
<!doctype html>
15+
<html lang="en">
16+
<Playground client:only="svelte" />
17+
</html>
18+
19+
<style is:global>
20+
.visually-hidden {
21+
position: absolute;
22+
width: 1px;
23+
height: 1px;
24+
padding: 0;
25+
margin: -1px;
26+
overflow: hidden;
27+
clip: rect(0, 0, 0, 0);
28+
white-space: nowrap;
29+
border: 0;
30+
}
31+
</style>
32+
33+
```

crates/biome_languages/src/css.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ pub enum EmbeddingStyleApplicability {
5555
Unknown,
5656
}
5757

58+
impl EmbeddingStyleApplicability {
59+
pub const fn is_local(&self) -> bool {
60+
matches!(self, EmbeddingStyleApplicability::Local)
61+
}
62+
63+
pub const fn is_global(&self) -> bool {
64+
matches!(self, EmbeddingStyleApplicability::Global)
65+
}
66+
}
67+
5868
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
5969
#[derive(
6070
Debug, Clone, Default, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize,

crates/biome_module_graph/src/css_module_info/traverse.rs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -120,21 +120,30 @@ impl<'a> Iterator for ImportTreeTraversal<'a> {
120120
})
121121
})
122122
.collect::<Vec<_>>(),
123-
ModuleInfoKind::Html(html_info) => html_info
124-
.imported_stylesheets
125-
.iter()
126-
.chain(html_info.static_import_paths.values())
127-
.chain(html_info.dynamic_import_paths.values())
128-
.filter_map(|stylesheet_path| {
129-
let path = stylesheet_path.as_path()?;
130-
let css_info = self.module_database.css_module_info_for_path(path)?;
131-
132-
Some(CssClassStep {
133-
css_path: path.to_path_buf(),
134-
css_classes: css_info.classes.clone(),
123+
ModuleInfoKind::Html(html_info) => {
124+
let mut steps = html_info
125+
.imported_stylesheets
126+
.iter()
127+
.chain(html_info.static_import_paths.values())
128+
.chain(html_info.dynamic_import_paths.values())
129+
.filter_map(|stylesheet_path| {
130+
let path = stylesheet_path.as_path()?;
131+
let css_info =
132+
self.module_database.css_module_info_for_path(path)?;
133+
134+
Some(CssClassStep {
135+
css_path: path.to_path_buf(),
136+
css_classes: css_info.classes.clone(),
137+
})
135138
})
136-
})
137-
.collect::<Vec<_>>(),
139+
.collect::<Vec<_>>();
140+
141+
steps.push(CssClassStep {
142+
css_path: file_path.to_path_buf(),
143+
css_classes: html_info.get_global_styles(),
144+
});
145+
steps
146+
}
138147
ModuleInfoKind::Css(_) => Vec::new(),
139148
});
140149
}

crates/biome_module_graph/src/html_module_info/mod.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
mod visitor;
22

33
use crate::css_module_info::{CssClassDefinition, CssClassReference};
4-
use biome_css_syntax::AnyCssRoot;
4+
use biome_css_syntax::{AnyCssRoot, TextRange};
55
use biome_js_syntax::AnyJsRoot;
66
use biome_languages::CssFileSource;
77
use biome_resolver::ResolvedPath;
8-
use biome_rowan::{Text, TextSize};
8+
use biome_rowan::{Text, TextSize, TokenText};
99
use indexmap::IndexMap;
1010
use indexmap::IndexSet;
1111
use std::collections::BTreeSet;
@@ -126,6 +126,17 @@ pub struct HtmlModuleInfoInner {
126126
pub dynamic_import_paths: IndexMap<Text, ResolvedPath>,
127127
}
128128

129+
impl HtmlModuleInfoInner {
130+
/// Returns CSS classes defined in `global` definitions
131+
pub(crate) fn get_global_styles(&self) -> IndexMap<TextRange, TokenText> {
132+
self.style_classes
133+
.iter()
134+
.filter(|declaration| declaration.applicability.is_global())
135+
.map(|c| (c.range, c.name.clone()))
136+
.collect()
137+
}
138+
}
139+
129140
#[derive(Debug)]
130141
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
131142
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]

crates/biome_module_graph/src/html_module_info/visitor.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,7 @@ impl<'a> HtmlModuleVisitor<'a> {
7171
continue;
7272
};
7373
if let Some(element) = HtmlElement::cast(node.clone()) {
74-
self.visit_html_element(
75-
element,
76-
&mut referenced_classes,
77-
&mut imported_stylesheets,
78-
);
74+
self.visit_html_element(element, &mut referenced_classes);
7975
} else if let Some(element) = HtmlSelfClosingElement::cast(node) {
8076
self.visit_self_closing_element(
8177
element,
@@ -172,7 +168,6 @@ impl<'a> HtmlModuleVisitor<'a> {
172168
&self,
173169
element: HtmlElement,
174170
referenced_classes: &mut Vec<CssClassReference>,
175-
_imported_stylesheets: &mut Vec<ResolvedPath>,
176171
) {
177172
let Ok(opening) = element.opening_element() else {
178173
return;

0 commit comments

Comments
 (0)