Skip to content

Commit

Permalink
feat(css_parser): recognize multiple semicolons after declaretion
Browse files Browse the repository at this point in the history
  • Loading branch information
fireairforce committed Oct 22, 2024
1 parent d621498 commit 78f7231
Show file tree
Hide file tree
Showing 22 changed files with 610 additions and 285 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b

Contributed by @fireairforce

- Fix [#3836](https://github.com/biomejs/biome/issues/3836), css parser allow multiple semicolons after a declaration, the following example will now parsed correctly:

```css
.foo {
color: red;;
}
```

Contributed by @fireairforce


## v1.9.4 (2024-10-17)

### Analyzer
Expand Down
6 changes: 6 additions & 0 deletions 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.

19 changes: 19 additions & 0 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 @@ -11,6 +11,7 @@ impl FormatRule<AnyCssDeclarationOrRule> for FormatAnyCssDeclarationOrRule {
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
10 changes: 10 additions & 0 deletions crates/biome_css_formatter/src/css/auxiliary/empty_declaration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use crate::prelude::*;
use biome_css_syntax::CssEmptyDeclaration;
use biome_rowan::AstNode;
#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssEmptyDeclaration;
impl FormatNodeRule<CssEmptyDeclaration> for FormatCssEmptyDeclaration {
fn fmt_fields(&self, node: &CssEmptyDeclaration, f: &mut CssFormatter) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
}
}
1 change: 1 addition & 0 deletions crates/biome_css_formatter/src/css/auxiliary/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub(crate) mod declaration_or_at_rule_block;
pub(crate) mod declaration_or_rule_block;
pub(crate) mod declaration_with_semicolon;
pub(crate) mod document_custom_matcher;
pub(crate) mod empty_declaration;
pub(crate) mod font_family_name;
pub(crate) mod font_feature_values_block;
pub(crate) mod font_feature_values_item;
Expand Down
40 changes: 40 additions & 0 deletions crates/biome_css_formatter/src/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,46 @@ impl IntoFormat<CssFormatContext> for biome_css_syntax::CssDocumentCustomMatcher
)
}
}
impl FormatRule<biome_css_syntax::CssEmptyDeclaration>
for crate::css::auxiliary::empty_declaration::FormatCssEmptyDeclaration
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::CssEmptyDeclaration,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::CssEmptyDeclaration>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::CssEmptyDeclaration {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::CssEmptyDeclaration,
crate::css::auxiliary::empty_declaration::FormatCssEmptyDeclaration,
>;
fn format(&self) -> Self::Format<'_> {
#![allow(clippy::default_constructed_unit_structs)]
FormatRefWithRule::new(
self,
crate::css::auxiliary::empty_declaration::FormatCssEmptyDeclaration::default(),
)
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::CssEmptyDeclaration {
type Format = FormatOwnedWithRule<
biome_css_syntax::CssEmptyDeclaration,
crate::css::auxiliary::empty_declaration::FormatCssEmptyDeclaration,
>;
fn into_format(self) -> Self::Format {
#![allow(clippy::default_constructed_unit_structs)]
FormatOwnedWithRule::new(
self,
crate::css::auxiliary::empty_declaration::FormatCssEmptyDeclaration::default(),
)
}
}
impl FormatRule<biome_css_syntax::CssFontFaceAtRule>
for crate::css::statements::font_face_at_rule::FormatCssFontFaceAtRule
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ font-size: 12px;
```diff
--- Prettier
+++ Biome
@@ -1,13 +1,14 @@
@@ -1,13 +1,15 @@
/* comment 1 */
:root {
/* comment 2 */
Expand All @@ -42,6 +42,7 @@ font-size: 12px;
- };
+ }
+ ;
+ /* comment 10 */
/* comment 10 */
}
/* comment 11 */
Expand All @@ -62,6 +63,7 @@ font-size: 12px;
}
;
/* comment 10 */
/* comment 10 */
}
/* comment 11 */
```
Expand Down Expand Up @@ -127,25 +129,5 @@ custom-properties.css:4:12 parse ━━━━━━━━━━━━━━━
- past
- future
custom-properties.css:10:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× Expected a declaration, or an at rule but instead found ';'.
8 │ font-size: 12px;
9 │ /* comment 9 */
> 10 │ };
│ ^
11 │ /* comment 10 */
12 │ }
i Expected a declaration, or an at rule here.
8 │ font-size: 12px;
9 │ /* comment 9 */
> 10 │ };
│ ^
11 │ /* comment 10 */
12 │ }
```
Original file line number Diff line number Diff line change
Expand Up @@ -854,26 +854,6 @@ selectors.css:152:75 parse ━━━━━━━━━━━━━━━━━
- past
- future
selectors.css:156:6 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× Expected a declaration, or an at rule but instead found ';'.
154 │ align-items: center;
155 │ justify-content: center;
> 156 │ };
│ ^
157 │ }
158 │
i Expected a declaration, or an at rule here.
154 │ align-items: center;
155 │ justify-content: center;
> 156 │ };
│ ^
157 │ }
158 │
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,26 +165,6 @@ url.css:3:33 parse ━━━━━━━━━━━━━━━━━━━━
- custom property
- function
url.css:30:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× Expected a declaration, or an at rule but instead found ';'.
28 │ no-repeat url(foo.woff2?foo=rgb\(255,255,0\))
29 │ no-repeat url(RobotoFlex-VariableFont_GRAD,XTRA,YOPQ,YTAS,YTDE,YTFI,YTLC,YTUC,opsz,slnt,wdth,wght.ttf);
> 30 │ ;
│ ^
31 │ }
32 │
i Expected a declaration, or an at rule here.
28 │ no-repeat url(foo.woff2?foo=rgb\(255,255,0\))
29 │ no-repeat url(RobotoFlex-VariableFont_GRAD,XTRA,YOPQ,YTAS,YTDE,YTFI,YTLC,YTUC,opsz,slnt,wdth,wght.ttf);
> 30 │ ;
│ ^
31 │ }
32 │
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,26 +268,6 @@ apply-rule.css:9:26 parse ━━━━━━━━━━━━━━━━━━
- past
- future
apply-rule.css:11:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× Expected a declaration, or an at rule but instead found ';'.
9 │ --toolbar-title-theme: {
10 │ color: green;
> 11 │ };
│ ^
12 │ }
13 │
i Expected a declaration, or an at rule here.
9 │ --toolbar-title-theme: {
10 │ color: green;
> 11 │ };
│ ^
12 │ }
13 │
apply-rule.css:15:19 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× Unexpected value or character.
Expand Down Expand Up @@ -521,25 +501,5 @@ apply-rule.css:27:36 parse ━━━━━━━━━━━━━━━━━
- past
- future
apply-rule.css:29:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× Expected a declaration, or an at rule but instead found ';'.
27 │ --another-one-like-a-apply-rule: {
28 │ color:red;
> 29 │ };
│ ^
30 │ }
31 │
i Expected a declaration, or an at rule here.
27 │ --another-one-like-a-apply-rule: {
28 │ color:red;
> 29 │ };
│ ^
30 │ }
31 │
```
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ use crate::syntax::at_rule::{is_at_at_rule, parse_at_rule};
use crate::syntax::block::ParseBlockBody;
use crate::syntax::parse_error::expected_any_declaration_or_at_rule;
use crate::syntax::{
is_at_declaration, is_at_metavariable, is_at_nested_qualified_rule,
parse_declaration_with_semicolon, parse_metavariable, parse_nested_qualified_rule, try_parse,
is_at_declaration, is_at_declaration_semion, is_at_metavariable, is_at_nested_qualified_rule,
parse_declaration_with_semicolon, parse_empty_declaration, parse_metavariable,
parse_nested_qualified_rule, try_parse,
};
use biome_css_syntax::CssSyntaxKind::*;
use biome_css_syntax::{CssSyntaxKind, T};
use biome_parser::parse_lists::ParseNodeList;
use biome_parser::parse_recovery::{ParseRecovery, RecoveryResult};
use biome_parser::parsed_syntax::ParsedSyntax;
use biome_parser::parsed_syntax::ParsedSyntax::Absent;
use biome_parser::prelude::ParsedSyntax;
use biome_parser::prelude::ParsedSyntax::Absent;
use biome_parser::{CompletedMarker, Parser};

#[inline]
Expand Down Expand Up @@ -89,8 +90,6 @@ impl ParseNodeList for DeclarationOrRuleList {
if matches!(p.last(), Some(T![;])) || p.at(T!['}']) {
Ok(declaration)
} else {
// If neither condition is met, return an error to indicate parsing failure.
// And rewind the parser to the start of the declaration.
Err(())
}
});
Expand Down Expand Up @@ -129,6 +128,8 @@ impl ParseNodeList for DeclarationOrRuleList {
parse_nested_qualified_rule(p)
} else if is_at_metavariable(p) {
parse_metavariable(p)
} else if is_at_declaration_semion(p) {
parse_empty_declaration(p)
} else {
Absent
}
Expand Down
16 changes: 16 additions & 0 deletions crates/biome_css_parser/src/syntax/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,27 @@ pub(crate) fn parse_declaration_with_semicolon(p: &mut CssParser) -> ParsedSynta
Present(m.complete(p, CSS_DECLARATION_WITH_SEMICOLON))
}

#[inline]
pub(crate) fn parse_empty_declaration(p: &mut CssParser) -> ParsedSyntax {
if p.at(T![;]) {
let m = p.start();
p.bump_any(); // bump ;
m.complete(p, CSS_EMPTY_DECLARATION).into()
} else {
Absent
}
}

#[inline]
fn is_at_declaration_important(p: &mut CssParser) -> bool {
p.at(T![!]) && p.nth_at(1, T![important])
}

#[inline]
pub(crate) fn is_at_declaration_semion(p: &mut CssParser) -> bool {
p.at(T![;]) && (p.nth_at(1, T![;]) || p.nth_at(1, T!['}']))
}

#[inline]
fn parse_declaration_important(p: &mut CssParser) -> ParsedSyntax {
if !is_at_declaration_important(p) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ a {
prop1: --custom;
}

a {
prop1: 1px;
prop2: 2px;;;
}

a {
prop1: 1px;;;
}

div {
flex: 1 1 auto !important;
}
Expand Down
Loading

0 comments on commit 78f7231

Please sign in to comment.