+
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
12 changes: 12 additions & 0 deletions .changeset/breezy-zebras-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@biomejs/biome": patch
---

Fix [#6485](https://github.com/biomejs/biome/issues/6485): Handle multiple semicolons correctly in blocks (#6485)

```css
div {
box-sizing: border-box;;
color: red;
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,6 @@ impl RecessOrderMember {
/// Returns the kind of node for ordering purposes. The nodes are sorted in the order they're declared in [NodeKindOrder].
pub fn kind(&self) -> NodeKindOrder {
match &self.0 {
AnyCssDeclarationOrRule::CssEmptyDeclaration(_) => NodeKindOrder::UnknownKind,
AnyCssDeclarationOrRule::CssBogus(_) => NodeKindOrder::UnknownKind,
AnyCssDeclarationOrRule::CssMetavariable(_) => NodeKindOrder::UnknownKind,
AnyCssDeclarationOrRule::AnyCssRule(rule) => match rule {
Expand All @@ -249,7 +248,12 @@ impl RecessOrderMember {
AnyCssRule::CssNestedQualifiedRule(_) => NodeKindOrder::NestedRuleOrAtRule,
AnyCssRule::CssQualifiedRule(_) => NodeKindOrder::UnknownKind,
},
AnyCssDeclarationOrRule::CssDeclarationWithSemicolon(decl_with_semicolon) => {
AnyCssDeclarationOrRule::AnyCssDeclarationWithSemicolon(any_decl_with_semicolon) => {
let Some(decl_with_semicolon) =
any_decl_with_semicolon.as_css_declaration_with_semicolon()
else {
return NodeKindOrder::UnknownKind;
};
let Some(decl) = decl_with_semicolon.declaration().ok() else {
return NodeKindOrder::UnknownKind;
};
Expand Down Expand Up @@ -280,7 +284,8 @@ impl RecessOrderMember {
pub fn property_index(&self) -> usize {
let Some(prop_text) = &self
.0
.as_css_declaration_with_semicolon()
.as_any_css_declaration_with_semicolon()
.and_then(|decl| decl.as_css_declaration_with_semicolon())
.and_then(css_declaration_to_prop_text)
else {
return usize::MAX;
Expand All @@ -295,7 +300,8 @@ impl RecessOrderMember {
pub fn vendor_prefix_index(&self) -> usize {
let Some(prop_text) = &self
.0
.as_css_declaration_with_semicolon()
.as_any_css_declaration_with_semicolon()
.and_then(|decl| decl.as_css_declaration_with_semicolon())
.and_then(css_declaration_to_prop_text)
else {
return usize::MAX;
Expand Down Expand Up @@ -402,7 +408,8 @@ fn contains_shorthand_after_longhand(nodes: &[AnyCssDeclarationOrRule]) -> bool
// Starting from the bottom, when we see a shorthand property, record the set of longhand properties that are no longer allowed to appear above it.
for node in nodes.iter().rev() {
let Some(prop_text) = &node
.as_css_declaration_with_semicolon()
.as_any_css_declaration_with_semicolon()
.and_then(|decl| decl.as_css_declaration_with_semicolon())
.and_then(css_declaration_to_prop_text)
else {
continue;
Expand Down Expand Up @@ -435,7 +442,8 @@ fn contains_shorthand_after_longhand(nodes: &[AnyCssDeclarationOrRule]) -> bool
fn contains_unknown_property(nodes: &[AnyCssDeclarationOrRule]) -> bool {
for node in nodes.iter() {
let Some(prop_text) = &node
.as_css_declaration_with_semicolon()
.as_any_css_declaration_with_semicolon()
.and_then(|decl| decl.as_css_declaration_with_semicolon())
.and_then(css_declaration_to_prop_text)
else {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ impl Rule for NoInvalidGridAreas {
.into_iter()
.filter_map(|item| {
let binding = item
.as_any_css_declaration_with_semicolon()?
.as_css_declaration_with_semicolon()?
.declaration()
.ok()?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,12 @@ impl Rule for NoImportantInKeyframe {
else {
return None;
};
for colon_declaration in block_declaration.declarations() {
if let Some(important) = colon_declaration.declaration().ok()?.important() {

for any_colon_declaration in block_declaration.declarations() {
if let Some(important) = any_colon_declaration
.as_css_declaration_with_semicolon()
.and_then(|decl| decl.declaration().ok()?.important())
{
return Some(important);
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_css_factory/src/generated/node_factory.rs

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

8 changes: 5 additions & 3 deletions crates/biome_css_factory/src/generated/syntax_factory.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
Expand Up @@ -8,8 +8,8 @@ impl FormatRule<AnyCssDeclarationOrAtRule> for FormatAnyCssDeclarationOrAtRule {
type Context = CssFormatContext;
fn fmt(&self, node: &AnyCssDeclarationOrAtRule, f: &mut CssFormatter) -> FormatResult<()> {
match node {
AnyCssDeclarationOrAtRule::AnyCssDeclarationWithSemicolon(node) => node.format().fmt(f),
AnyCssDeclarationOrAtRule::CssAtRule(node) => node.format().fmt(f),
AnyCssDeclarationOrAtRule::CssDeclarationWithSemicolon(node) => node.format().fmt(f),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ impl FormatRule<AnyCssDeclarationOrRule> for FormatAnyCssDeclarationOrRule {
type Context = CssFormatContext;
fn fmt(&self, node: &AnyCssDeclarationOrRule, f: &mut CssFormatter) -> FormatResult<()> {
match node {
AnyCssDeclarationOrRule::AnyCssDeclarationWithSemicolon(node) => node.format().fmt(f),
AnyCssDeclarationOrRule::AnyCssRule(node) => node.format().fmt(f),
AnyCssDeclarationOrRule::CssBogus(node) => node.format().fmt(f),
AnyCssDeclarationOrRule::CssDeclarationWithSemicolon(node) => node.format().fmt(f),
AnyCssDeclarationOrRule::CssEmptyDeclaration(node) => node.format().fmt(f),
AnyCssDeclarationOrRule::CssMetavariable(node) => node.format().fmt(f),
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file.

use crate::prelude::*;
use biome_css_syntax::AnyCssDeclarationWithSemicolon;
#[derive(Debug, Clone, Default)]
pub(crate) struct FormatAnyCssDeclarationWithSemicolon;
impl FormatRule<AnyCssDeclarationWithSemicolon> for FormatAnyCssDeclarationWithSemicolon {
type Context = CssFormatContext;
fn fmt(&self, node: &AnyCssDeclarationWithSemicolon, f: &mut CssFormatter) -> FormatResult<()> {
match node {
AnyCssDeclarationWithSemicolon::CssDeclarationWithSemicolon(node) => {
node.format().fmt(f)
}
AnyCssDeclarationWithSemicolon::CssEmptyDeclaration(node) => node.format().fmt(f),
}
}
}
1 change: 1 addition & 0 deletions crates/biome_css_formatter/src/css/any/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub(crate) mod declaration_or_at_rule;
pub(crate) mod declaration_or_at_rule_block;
pub(crate) mod declaration_or_rule;
pub(crate) mod declaration_or_rule_block;
pub(crate) mod declaration_with_semicolon;
pub(crate) mod dimension;
pub(crate) mod document_matcher;
pub(crate) mod expression;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ impl FormatRule<AnyCssPageAtRuleItem> for FormatAnyCssPageAtRuleItem {
type Context = CssFormatContext;
fn fmt(&self, node: &AnyCssPageAtRuleItem, f: &mut CssFormatter) -> FormatResult<()> {
match node {
AnyCssPageAtRuleItem::AnyCssDeclarationWithSemicolon(node) => node.format().fmt(f),
AnyCssPageAtRuleItem::CssAtRule(node) => node.format().fmt(f),
AnyCssPageAtRuleItem::CssDeclarationWithSemicolon(node) => node.format().fmt(f),
AnyCssPageAtRuleItem::CssMarginAtRule(node) => node.format().fmt(f),
}
}
Expand Down
14 changes: 2 additions & 12 deletions crates/biome_css_formatter/src/css/auxiliary/empty_declaration.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
use crate::prelude::*;
use biome_css_syntax::{CssEmptyDeclaration, CssEmptyDeclarationFields, CssSyntaxKind};
use biome_css_syntax::{CssEmptyDeclaration, CssEmptyDeclarationFields};
use biome_formatter::write;
use biome_rowan::{AstNode, SyntaxNodeOptionExt};
#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssEmptyDeclaration;
impl FormatNodeRule<CssEmptyDeclaration> for FormatCssEmptyDeclaration {
fn fmt_fields(&self, node: &CssEmptyDeclaration, f: &mut CssFormatter) -> FormatResult<()> {
let CssEmptyDeclarationFields { semicolon_token } = node.as_fields();
let parent_kind = node.syntax().parent().kind();

if matches!(
parent_kind,
Some(CssSyntaxKind::CSS_DECLARATION_WITH_SEMICOLON,)
) {
write!(f, [semicolon_token.format()])
} else {
write!(f, [format_removed(&semicolon_token?)])
}
write!(f, [format_removed(&semicolon_token?)])
}
}
19 changes: 19 additions & 0 deletions crates/biome_css_formatter/src/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7648,6 +7648,25 @@ impl IntoFormat<CssFormatContext> for biome_css_syntax::AnyCssDeclarationOrRuleB
)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::AnyCssDeclarationWithSemicolon {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::AnyCssDeclarationWithSemicolon,
crate::css::any::declaration_with_semicolon::FormatAnyCssDeclarationWithSemicolon,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule :: new (self , crate :: css :: any :: declaration_with_semicolon :: FormatAnyCssDeclarationWithSemicolon :: default ())
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::AnyCssDeclarationWithSemicolon {
type Format = FormatOwnedWithRule<
biome_css_syntax::AnyCssDeclarationWithSemicolon,
crate::css::any::declaration_with_semicolon::FormatAnyCssDeclarationWithSemicolon,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule :: new (self , crate :: css :: any :: declaration_with_semicolon :: FormatAnyCssDeclarationWithSemicolon :: default ())
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::AnyCssDimension {
type Format<'a> = FormatRefWithRule<
'a,
Expand Down
8 changes: 4 additions & 4 deletions crates/biome_css_formatter/src/utils/block_like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,9 @@ impl Format<CssFormatContext> for FormatCssBlockLike<'_> {
fn fmt(&self, f: &mut Formatter<CssFormatContext>) -> FormatResult<()> {
write!(f, [self.block.l_curly_token().format()])?;

let r_curly_token = self.block.r_curly_token()?;

// When the list is empty, we still print a hard line to put the
// closing curly on the next line.
if self.block.is_empty() {
if self.block.is_empty() || self.block.has_only_empty_declarations() {
let comments = f.context().comments();

let has_dangling_comments = comments.has_dangling_comments(self.block.syntax());
Expand All @@ -61,6 +59,8 @@ impl Format<CssFormatContext> for FormatCssBlockLike<'_> {
[format_dangling_comments(self.block.syntax()).with_block_indent()]
)?;
} else {
// we still need to write items because the block may have empty declarations
self.write_items(f)?;
write!(f, [soft_line_break()])?;
}
} else {
Expand All @@ -69,6 +69,6 @@ impl Format<CssFormatContext> for FormatCssBlockLike<'_> {
[soft_block_indent(&format_with(|f| self.write_items(f)))]
)?;
}
write!(f, [r_curly_token.format()])
write!(f, [self.block.r_curly_token().format()])
}
}
10 changes: 5 additions & 5 deletions crates/biome_css_formatter/tests/quick_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ mod language {
#[test]
// use this test check if your snippet prints as you wish, without using a snapshot
fn quick_test() {
let src = r#".font-heading {
font-feature-settings:
var(--heading-salt), var(--heading-ss06), var(--heading-ss11), var(--heading-cv09),
var(--heading-liga), var(--heading-calt);
}
let src = r#"
.container {& [lang="ru"] {
color: blue;
} }


"#;
let parse = parse_css(src, CssParserOptions::default());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
source: crates/biome_formatter_test/src/snapshot_builder.rs
info: css/atrule/supports.css
---

# Input

```css
Expand Down Expand Up @@ -403,5 +402,3 @@ Quote style: Double Quotes
}
}
```


47 changes: 47 additions & 0 deletions crates/biome_css_formatter/tests/specs/css/declaration_emty.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
a {
prop1: 1px;
prop1: 1px;;
prop2: 2px;;;
}

a {
prop1: 1px;;
}

a {
;
}

b {
prop1: 1px;;
rule {
;
prop2: 2px;;
;
}
;;;;;
span {

}
;
}

@page :first{
;;
prop2: 2px;
;
margin: 2cm;;;
}

@page :first {
@top-left {
;;;
}
@top-left {
;
}
@top-left {
;
content: "foo";;;
}
}
Loading
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载