Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: basic class selector parsing #307

Merged
merged 2 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions Cargo.lock

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

40 changes: 21 additions & 19 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.

28 changes: 4 additions & 24 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.

1 change: 1 addition & 0 deletions crates/biome_css_parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ insta = { workspace = true }
quickcheck = { workspace = true }
quickcheck_macros = { workspace = true }
tests_macros = { workspace = true }
biome_test_utils = { workspace = true}

# cargo-workspaces metadata
[package.metadata.workspaces]
Expand Down
13 changes: 0 additions & 13 deletions crates/biome_css_parser/src/syntax.rs

This file was deleted.

138 changes: 138 additions & 0 deletions crates/biome_css_parser/src/syntax/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use crate::parser::CssParser;
use biome_css_syntax::CssSyntaxKind::*;
use biome_css_syntax::{CssSyntaxKind, T};
use biome_parser::diagnostic::expected_any;
use biome_parser::parse_recovery::ParseRecovery;
use biome_parser::prelude::ParsedSyntax::{Absent, Present};
use biome_parser::prelude::{ParseDiagnostic, ParsedSyntax, ToDiagnostic};
use biome_parser::{token_set, Parser, ParserProgress, TokenSet};
use biome_rowan::TextRange;

const SELECTOR_RECOVERY_SET: TokenSet<CssSyntaxKind> = token_set![T!['{'], T!['}'],];
const BODY_RECOVERY_SET: TokenSet<CssSyntaxKind> =
SELECTOR_RECOVERY_SET.union(token_set![T![.], T![*],]);

pub(crate) fn parse_root(p: &mut CssParser) {
let m = p.start();

parse_rules_list(p).expect("Parse rule list, handle this case");

m.complete(p, CSS_ROOT);
}

pub(crate) fn parse_rules_list(p: &mut CssParser) -> ParsedSyntax {
let rules = p.start();
while !p.at(EOF) {
match p.cur() {
T![.] => {
parse_rule(p).expect("Parse rule, handle this case properly");
}
_ => return Absent,
}
}

let completed = rules.complete(p, CSS_RULE_LIST);

Present(completed)
}

pub(crate) fn parse_rule(p: &mut CssParser) -> ParsedSyntax {
let m = p.start();
if parse_selector_list(p)
.or_recover(
p,
&ParseRecovery::new(CSS_BOGUS_PATTERN, SELECTOR_RECOVERY_SET),
expect_pattern,
)
.is_err()
{
m.abandon(p);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please share your strategy for situations where we might need to abandon and return as absent, rather than completing and returning bogus kind?

Copy link
Member Author

@ematipico ematipico Sep 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a clear strategy in mind yet, but I've had some ideas while starting this work, still it's best if we discuss them.

For rules, I thought we could bogus+recover in the prelude and in the block.

This would allow us to parse cases like:

.foo }
.bar {}
.686 {}
.bar {}

Although, I'm not sure about the prelude of the rule.

Or maybe we should have a big CssBogusRule? What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you, let's try to use a recovery strategy.

return Absent;
}

if parse_css_block(p)
.or_recover(
p,
&ParseRecovery::new(CSS_BOGUS_BODY, BODY_RECOVERY_SET),
expect_block,
)
.is_err()
{
m.abandon(p);
return Absent;
}

let completed = m.complete(p, CSS_RULE);
Present(completed)
}

pub(crate) fn parse_selector_list(p: &mut CssParser) -> ParsedSyntax {
let m = p.start();
let mut progress = ParserProgress::default();

while !p.at(EOF) && !p.at(T!['{']) {
progress.assert_progressing(p);

match p.cur() {
T![.] => {
parse_css_selector_pattern(p).expect("Handle this case");
}

_ => {
return Absent;
}
}
}
if p.at(EOF) {
m.abandon(p);
return Absent;
}

Present(m.complete(p, CSS_SELECTOR_LIST))
}

#[inline]
pub(crate) fn parse_css_selector_pattern(p: &mut CssParser) -> ParsedSyntax {
if !p.at(T![.]) {
return Absent;
}
let m = p.start();

p.bump(T![.]);

match p.cur() {
IDENT => {
let m = p.start();
p.bump(IDENT);
m.complete(p, CSS_IDENTIFIER);
}

_ => {
m.abandon(p);
return Absent;
}
}

Present(m.complete(p, CSS_CLASS_SELECTOR_PATTERN))
}

pub(crate) fn parse_css_block(p: &mut CssParser) -> ParsedSyntax {
if !p.at(T!['{']) {
return Absent;
}
let m = p.start();
p.expect(T!['{']);
let list = p.start();
list.complete(p, CSS_DECLARATION_LIST);
p.expect(T!['}']);

Present(m.complete(p, CSS_BLOCK))
}

fn expect_pattern(p: &CssParser, range: TextRange) -> ParseDiagnostic {
expected_any(&["selector pattern"], range).into_diagnostic(p)
}

fn expect_block(p: &CssParser, range: TextRange) -> ParseDiagnostic {
expected_any(&["body"], range).into_diagnostic(p)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.action {
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
source: crates/biome_css_parser/tests/spec_test.rs
expression: snapshot
---

## Input

```css
.action {

```


## AST

```
CssRoot {
rules: CssRuleList [
CssRule {
prelude: CssSelectorList [
CssClassSelectorPattern {
dot_token: [email protected] "." [] [],
name: CssIdentifier {
value_token: [email protected] "action" [] [Whitespace(" ")],
},
},
],
block: CssBlock {
l_curly_token: [email protected] "{" [] [],
declaration_list: CssDeclarationList [],
r_curly_token: missing (required),
},
},
],
eof_token: [email protected] "" [Newline("\n")] [],
}
```

## CST

```
0: [email protected]
0: [email protected]
0: [email protected]
0: [email protected]
0: [email protected]
0: [email protected] "." [] []
1: [email protected]
0: [email protected] "action" [] [Whitespace(" ")]
1: [email protected]
0: [email protected] "{" [] []
1: [email protected]
2: (empty)
1: [email protected] "" [Newline("\n")] []

```

## Diagnostics

```
css_unfinished_block.css:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

× expected `}` but instead the file ends

1 │ .action {
> 2 │

i the file ends here

1 │ .action {
> 2 │

```


Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.action {}
Loading
Loading