Skip to content

Commit

Permalink
feat: basic class selector parsing (#307)
Browse files Browse the repository at this point in the history
  • Loading branch information
ematipico committed Sep 18, 2023
1 parent 8cd9474 commit ef41e50
Show file tree
Hide file tree
Showing 21 changed files with 446 additions and 219 deletions.
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 @@ -21,6 +21,7 @@ biome_rowan = { workspace = true }
tracing = { workspace = true }

[dev-dependencies]
biome_test_utils = { workspace = true }
insta = { workspace = true }
quickcheck = { workspace = true }
quickcheck_macros = { workspace = true }
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);
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: DOT@0..1 "." [] [],
name: CssIdentifier {
value_token: IDENT@1..8 "action" [] [Whitespace(" ")],
},
},
],
block: CssBlock {
l_curly_token: L_CURLY@8..9 "{" [] [],
declaration_list: CssDeclarationList [],
r_curly_token: missing (required),
},
},
],
eof_token: EOF@9..10 "" [Newline("\n")] [],
}
```
## CST
```
0: CSS_ROOT@0..10
0: CSS_RULE_LIST@0..9
0: CSS_RULE@0..9
0: CSS_SELECTOR_LIST@0..8
0: CSS_CLASS_SELECTOR_PATTERN@0..8
0: DOT@0..1 "." [] []
1: CSS_IDENTIFIER@1..8
0: IDENT@1..8 "action" [] [Whitespace(" ")]
1: CSS_BLOCK@8..9
0: L_CURLY@8..9 "{" [] []
1: CSS_DECLARATION_LIST@9..9
2: (empty)
1: EOF@9..10 "" [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

0 comments on commit ef41e50

Please sign in to comment.