Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/angry-women-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@biomejs/biome": minor
---

The Biome CSS parser is now able to parse Vue SFC syntax such as `:slotted` and `:deep`. These pseudo functions are only correctly parsed when the CSS is defined inside `.vue` components. Otherwise, Biome will a emit a parse error.

This capability is only available when `experimentalFullHtmlSupportedEnabled` is set to `true`.
17 changes: 17 additions & 0 deletions .changeset/odd-flies-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@biomejs/biome": minor
---

Improved the CSS parser for CSS modules. Biome now automatically enables CSS modules parsing for `*.module.css` files.

If your codebase has only `*.module.css` files, you can remove the parser feature as follows, because now Biome does it for you:

```diff
{
"css": {
"parser": {
- "cssModules": true
}
}
}
```
7 changes: 7 additions & 0 deletions .changeset/plenty-hornets-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@biomejs/biome": minor
---

Added support for parsing `:global` and `:local` inside `.astro`, `.svelte` and `.vue` files, in `<style>` portion of the file.

This capability is only available when `experimentalFullHtmlSupportedEnabled` is set to `true`.
7 changes: 0 additions & 7 deletions crates/biome_analyze/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ pub struct RuleContext<'a, R: Rule> {
jsx_runtime: Option<JsxRuntime>,
jsx_factory: Option<&'a str>,
jsx_fragment_factory: Option<&'a str>,
css_modules: bool,
}

impl<'a, R> RuleContext<'a, R>
Expand All @@ -43,7 +42,6 @@ where
jsx_runtime: Option<JsxRuntime>,
jsx_factory: Option<&'a str>,
jsx_fragment_factory: Option<&'a str>,
css_modules: bool,
) -> Result<Self, Error> {
let rule_key = RuleKey::rule::<R>();
Ok(Self {
Expand All @@ -60,7 +58,6 @@ where
jsx_runtime,
jsx_factory,
jsx_fragment_factory,
css_modules,
})
}

Expand Down Expand Up @@ -203,10 +200,6 @@ where
self.preferred_indentation
}

pub fn is_css_modules(&self) -> bool {
self.css_modules
}

/// Attempts to retrieve a service from the current context
///
/// ```no_test
Expand Down
12 changes: 0 additions & 12 deletions crates/biome_analyze/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@ pub struct AnalyzerConfiguration {
/// The JSX fragment factory function identifier (e.g., "Fragment")
/// Only applies when jsx_runtime is ReactClassic.
jsx_fragment_factory: Option<Box<str>>,

/// Whether the CSS files contain CSS Modules
css_modules: bool,
}

impl AnalyzerConfiguration {
Expand Down Expand Up @@ -132,11 +129,6 @@ impl AnalyzerConfiguration {
self.preferred_indentation = preferred_indentation;
self
}

pub fn with_css_modules(mut self, css_modules: bool) -> Self {
self.css_modules = css_modules;
self
}
}

/// A set of information useful to the analyzer infrastructure
Expand Down Expand Up @@ -230,10 +222,6 @@ impl AnalyzerOptions {
pub fn preferred_indentation(&self) -> PreferredIndentation {
self.configuration.preferred_indentation
}

pub fn css_modules(&self) -> bool {
self.configuration.css_modules
}
}

#[derive(Clone, Copy, Debug, Default)]
Expand Down
2 changes: 0 additions & 2 deletions crates/biome_analyze/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,6 @@ impl<L: Language + Default> RegistryRule<L> {
let jsx_runtime = params.options.jsx_runtime();
let jsx_factory = params.options.jsx_factory();
let jsx_fragment_factory = params.options.jsx_fragment_factory();
let css_modules = params.options.css_modules();
let options = params.options.rule_options::<R>().unwrap_or_default();
let ctx = RuleContext::new(
&query_result,
Expand All @@ -423,7 +422,6 @@ impl<L: Language + Default> RegistryRule<L> {
jsx_runtime,
jsx_factory,
jsx_fragment_factory,
css_modules,
)?;

for result in R::run(&ctx) {
Expand Down
3 changes: 0 additions & 3 deletions crates/biome_analyze/src/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,6 @@ where
self.options.jsx_runtime(),
self.options.jsx_factory(),
self.options.jsx_fragment_factory(),
self.options.css_modules(),
)
.ok()?;

Expand Down Expand Up @@ -411,7 +410,6 @@ where
self.options.jsx_runtime(),
self.options.jsx_factory(),
self.options.jsx_fragment_factory(),
self.options.css_modules(),
)
.ok();
let mut actions = Vec::new();
Expand Down Expand Up @@ -478,7 +476,6 @@ where
self.options.jsx_runtime(),
self.options.jsx_factory(),
self.options.jsx_fragment_factory(),
self.options.css_modules(),
)
.ok();
if let Some(ctx) = ctx {
Expand Down
3 changes: 3 additions & 0 deletions crates/biome_cli/tests/cases/handle_astro_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,9 @@ schema + sure()

<style>
#id { font-family: comic-sans } .class { background: red}
:global(div) {
color: red;
}
</style>
"#
.as_bytes(),
Expand Down
3 changes: 3 additions & 0 deletions crates/biome_cli/tests/cases/handle_svelte_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ schema + sure()

<style>
#id { font-family: comic-sans } .class { background: red}
:global(div) {
color: red;
}
</style>
"#
.as_bytes(),
Expand Down
6 changes: 6 additions & 0 deletions crates/biome_cli/tests/cases/handle_vue_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,12 @@ schema + sure()

<style>
#id { font-family: comic-sans } .class { background: red}
:slotted(div) {
color: red;
}
:global(div) {
color: red;
}
</style>
"#
.as_bytes(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ schema + sure();
.class {
background: red;
}
:global(div) {
color: red;
}
</style>

```
Expand Down Expand Up @@ -79,8 +82,8 @@ file.astro:16:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━
15 │ <style>
> 16 │ #id { font-family: comic-sans } .class { background: red}
│ ^^^^^^^^^^
17 │ </style>
18 │
17 │ :global(div) {
18 │ color: red;

i Consider adding a generic font family as a fallback.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ schema + sure();
.class {
background: red;
}
:global(div) {
color: red;
}
</style>

```
Expand Down Expand Up @@ -81,8 +84,8 @@ file.svelte:12:20 lint/a11y/useGenericFontNames ━━━━━━━━━━
11 │ <style>
> 12 │ #id { font-family: comic-sans } .class { background: red}
│ ^^^^^^^^^^
13 │ </style>
14 │
13 │ :global(div) {
14 │ color: red;

i Consider adding a generic font family as a fallback.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ schema + sure();
.class {
background: red;
}
:slotted(div) {
color: red;
}
:global(div) {
color: red;
}
</style>
```
Expand Down Expand Up @@ -81,8 +87,8 @@ file.vue:12:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━
11 │ <style>
> 12 │ #id { font-family: comic-sans } .class { background: red}
│ ^^^^^^^^^^
13 │ </style>
14 │
13 │ :slotted(div) {
14 color: red;
i Consider adding a generic font family as a fallback.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ The configuration that is contained inside the file `biome.json`
--json-assist-enabled=<true|false> Control the assist for JSON (and its super languages)
files.
--css-parse-css-modules=<true|false> Enables parsing of CSS Modules specific features.
Enable this feature only when your files don't end in `.module.css`.
--css-parse-tailwind-directives=<true|false> Enables parsing of Tailwind CSS 4.0 directives
and functions.
--css-formatter-enabled=<true|false> Control the formatter for CSS (and its super
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ The configuration that is contained inside the file `biome.json`
--json-assist-enabled=<true|false> Control the assist for JSON (and its super languages)
files.
--css-parse-css-modules=<true|false> Enables parsing of CSS Modules specific features.
Enable this feature only when your files don't end in `.module.css`.
--css-parse-tailwind-directives=<true|false> Enables parsing of Tailwind CSS 4.0 directives
and functions.
--css-formatter-enabled=<true|false> Control the formatter for CSS (and its super
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_configuration/src/analyzer/linter/rules.rs

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion crates/biome_configuration/src/css.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ pub struct CssParserConfiguration {
#[serde(skip_serializing_if = "Option::is_none")]
pub allow_wrong_line_comments: Option<CssAllowWrongLineCommentsEnabled>,

/// Enables parsing of CSS Modules specific features.
/// Enables parsing of CSS Modules specific features. Enable this feature only
/// when your files don't end in `.module.css`.
#[serde(skip_serializing_if = "Option::is_none")]
#[bpaf(long("css-parse-css-modules"), argument("true|false"))]
pub css_modules: Option<CssModulesEnabled>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ use biome_analyze::{
};
use biome_console::markup;
use biome_css_syntax::{
CssBogusPseudoClass, CssPageSelectorPseudo, CssPseudoClassFunctionCompoundSelector,
CssPseudoClassFunctionCompoundSelectorList, CssPseudoClassFunctionIdentifier,
CssPseudoClassFunctionNth, CssPseudoClassFunctionRelativeSelectorList,
CssPseudoClassFunctionSelector, CssPseudoClassFunctionSelectorList,
CssPseudoClassFunctionValueList, CssPseudoClassIdentifier, CssPseudoElementSelector,
CssSyntaxToken,
CssBogusPseudoClass, CssFileSource, CssPageSelectorPseudo,
CssPseudoClassFunctionCompoundSelector, CssPseudoClassFunctionCompoundSelectorList,
CssPseudoClassFunctionIdentifier, CssPseudoClassFunctionNth,
CssPseudoClassFunctionRelativeSelectorList, CssPseudoClassFunctionSelector,
CssPseudoClassFunctionSelectorList, CssPseudoClassFunctionValueList, CssPseudoClassIdentifier,
CssPseudoElementSelector, CssSyntaxToken,
};
use biome_diagnostics::Severity;
use biome_rowan::{AstNode, TextRange, declare_node_union};
Expand Down Expand Up @@ -146,9 +146,9 @@ impl Rule for NoUnknownPseudoClass {

fn run(ctx: &RuleContext<Self>) -> Option<Self::State> {
let pseudo_class = ctx.query();
let is_css_modules = ctx.is_css_modules();
let span = pseudo_class.name_range()?;
let name = pseudo_class.name()?;
let file_source = ctx.source_type::<CssFileSource>();

let pseudo_type = match &pseudo_class {
AnyPseudoLike::CssPageSelectorPseudo(_) => PseudoClassType::PagePseudoClass,
Expand Down Expand Up @@ -177,7 +177,8 @@ impl Rule for NoUnknownPseudoClass {
}
};

if is_valid_class || is_css_modules && is_css_module_pseudo_class(lower_name) {
if is_valid_class || file_source.is_css_modules() && is_css_module_pseudo_class(lower_name)
{
None
} else {
Some(NoUnknownPseudoClassSelectorState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use biome_analyze::{
Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
};
use biome_console::markup;
use biome_css_syntax::{AnyCssPseudoElement, CssPseudoElementSelector};
use biome_css_syntax::{AnyCssPseudoElement, CssFileSource, CssPseudoElementSelector};
use biome_diagnostics::Severity;
use biome_rowan::AstNode;
use biome_rule_options::no_unknown_pseudo_element::NoUnknownPseudoElementOptions;
Expand Down Expand Up @@ -70,22 +70,23 @@ impl Rule for NoUnknownPseudoElement {
fn run(ctx: &RuleContext<Self>) -> Option<Self::State> {
let node: &CssPseudoElementSelector = ctx.query();
let pseudo_element = node.element().ok()?;
let file_source = ctx.source_type::<CssFileSource>();

let should_not_trigger = match &pseudo_element {
AnyCssPseudoElement::CssBogusPseudoElement(element) => {
should_not_trigger(element.to_trimmed_text().text())
should_not_trigger(element.to_trimmed_text().text(), file_source)
}
AnyCssPseudoElement::CssPseudoElementFunctionCustomIdentifier(ident) => {
should_not_trigger(ident.name().ok()?.to_trimmed_text().text())
should_not_trigger(ident.name().ok()?.to_trimmed_text().text(), file_source)
}
AnyCssPseudoElement::CssPseudoElementFunctionSelector(selector) => {
should_not_trigger(selector.name().ok()?.to_trimmed_text().text())
should_not_trigger(selector.name().ok()?.to_trimmed_text().text(), file_source)
}
AnyCssPseudoElement::CssPseudoElementIdentifier(ident) => {
should_not_trigger(ident.name().ok()?.to_trimmed_text().text())
should_not_trigger(ident.name().ok()?.to_trimmed_text().text(), file_source)
}
AnyCssPseudoElement::CssPseudoElementFunction(ident) => {
should_not_trigger(ident.name().ok()?.to_trimmed_text().text())
should_not_trigger(ident.name().ok()?.to_trimmed_text().text(), file_source)
}
};

Expand Down Expand Up @@ -120,7 +121,12 @@ impl Rule for NoUnknownPseudoElement {
}

/// It doesn't trigger the rule if the pseudo-element name isn't a vendor prefix or is a pseudo-element
fn should_not_trigger(pseudo_element_name: &str) -> bool {
fn should_not_trigger(pseudo_element_name: &str, file_source: &CssFileSource) -> bool {
if file_source.is_css_modules() {
return ["global", "local"]
.contains(&pseudo_element_name.to_ascii_lowercase_cow().as_ref());
}

!vender_prefix(pseudo_element_name).is_empty()
|| is_pseudo_elements(pseudo_element_name.to_ascii_lowercase_cow().as_ref())
}
Loading
Loading