Skip to content

Commit

Permalink
feat(css_parser): introduce grit metavariable (#3340)
Browse files Browse the repository at this point in the history
  • Loading branch information
ah-yu committed Jul 9, 2024
1 parent 29280e3 commit a77c00b
Show file tree
Hide file tree
Showing 25 changed files with 675 additions and 23 deletions.
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::CssGritMetavariable(node) => node.format().fmt(f),
}
}
}
1 change: 1 addition & 0 deletions crates/biome_css_formatter/src/css/any/media_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ impl FormatRule<AnyCssMediaQuery> for FormatAnyCssMediaQuery {
match node {
AnyCssMediaQuery::AnyCssMediaTypeQuery(node) => node.format().fmt(f),
AnyCssMediaQuery::CssBogusMediaQuery(node) => node.format().fmt(f),
AnyCssMediaQuery::CssGritMetavariable(node) => node.format().fmt(f),
AnyCssMediaQuery::CssMediaConditionQuery(node) => node.format().fmt(f),
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/biome_css_formatter/src/css/any/selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ impl FormatRule<AnyCssSelector> for FormatAnyCssSelector {
AnyCssSelector::CssBogusSelector(node) => node.format().fmt(f),
AnyCssSelector::CssComplexSelector(node) => node.format().fmt(f),
AnyCssSelector::CssCompoundSelector(node) => node.format().fmt(f),
AnyCssSelector::CssGritMetavariable(node) => node.format().fmt(f),
}
}
}
1 change: 1 addition & 0 deletions crates/biome_css_formatter/src/css/any/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ impl FormatRule<AnyCssValue> for FormatAnyCssValue {
AnyCssValue::CssColor(node) => node.format().fmt(f),
AnyCssValue::CssCustomIdentifier(node) => node.format().fmt(f),
AnyCssValue::CssDashedIdentifier(node) => node.format().fmt(f),
AnyCssValue::CssGritMetavariable(node) => node.format().fmt(f),
AnyCssValue::CssIdentifier(node) => node.format().fmt(f),
AnyCssValue::CssNumber(node) => node.format().fmt(f),
AnyCssValue::CssRatio(node) => node.format().fmt(f),
Expand Down
12 changes: 12 additions & 0 deletions crates/biome_css_formatter/src/css/auxiliary/grit_metavariable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::prelude::*;
use biome_css_syntax::{CssGritMetavariable, CssGritMetavariableFields};
use biome_formatter::write;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssGritMetavariable;
impl FormatNodeRule<CssGritMetavariable> for FormatCssGritMetavariable {
fn fmt_fields(&self, node: &CssGritMetavariable, f: &mut CssFormatter) -> FormatResult<()> {
let CssGritMetavariableFields { value_token } = node.as_fields();
write!(f, [value_token.format()])
}
}
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 @@ -29,6 +29,7 @@ pub(crate) mod font_feature_values_block;
pub(crate) mod font_feature_values_item;
pub(crate) mod function;
pub(crate) mod generic_delimiter;
pub(crate) mod grit_metavariable;
pub(crate) mod import_anonymous_layer;
pub(crate) mod import_named_layer;
pub(crate) mod import_supports;
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 @@ -1747,6 +1747,46 @@ impl IntoFormat<CssFormatContext> for biome_css_syntax::CssGenericProperty {
)
}
}
impl FormatRule<biome_css_syntax::CssGritMetavariable>
for crate::css::auxiliary::grit_metavariable::FormatCssGritMetavariable
{
type Context = CssFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_css_syntax::CssGritMetavariable,
f: &mut CssFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_css_syntax::CssGritMetavariable>::fmt(self, node, f)
}
}
impl AsFormat<CssFormatContext> for biome_css_syntax::CssGritMetavariable {
type Format<'a> = FormatRefWithRule<
'a,
biome_css_syntax::CssGritMetavariable,
crate::css::auxiliary::grit_metavariable::FormatCssGritMetavariable,
>;
fn format(&self) -> Self::Format<'_> {
#![allow(clippy::default_constructed_unit_structs)]
FormatRefWithRule::new(
self,
crate::css::auxiliary::grit_metavariable::FormatCssGritMetavariable::default(),
)
}
}
impl IntoFormat<CssFormatContext> for biome_css_syntax::CssGritMetavariable {
type Format = FormatOwnedWithRule<
biome_css_syntax::CssGritMetavariable,
crate::css::auxiliary::grit_metavariable::FormatCssGritMetavariable,
>;
fn into_format(self) -> Self::Format {
#![allow(clippy::default_constructed_unit_structs)]
FormatOwnedWithRule::new(
self,
crate::css::auxiliary::grit_metavariable::FormatCssGritMetavariable::default(),
)
}
}
impl FormatRule<biome_css_syntax::CssIdSelector>
for crate::css::selectors::id_selector::FormatCssIdSelector
{
Expand Down
57 changes: 57 additions & 0 deletions crates/biome_css_parser/src/lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,11 @@ impl<'src> CssLexer<'src> {
self.advance(1);
self.consume_byte(T!["$="])
}
UNI if self.options.is_grit_metavariable_enabled()
&& self.is_grit_metavariable_start() =>
{
self.consume_grit_metavariable()
}
IDT | UNI | BSL if self.is_ident_start() => self.consume_identifier(),

MUL => self.consume_mul(),
Expand Down Expand Up @@ -1315,6 +1320,58 @@ impl<'src> CssLexer<'src> {
_ => false,
}
}

/// Check if the lexer starts a grit metavariable
fn is_grit_metavariable_start(&mut self) -> bool {
let current_char = self.current_char_unchecked();
if current_char == 'μ' {
let current_char_length = current_char.len_utf8();
// μ[a-zA-Z_][a-zA-Z0-9_]*
if matches!(
self.byte_at(current_char_length),
Some(b'a'..=b'z' | b'A'..=b'Z' | b'_')
) {
return true;
}

// μ...
if self.byte_at(current_char_length) == Some(b'.')
&& self.byte_at(current_char_length + 1) == Some(b'.')
&& self.byte_at(current_char_length + 2) == Some(b'.')
{
return true;
}
}
false
}

/// Consume a grit metavariable(μ[a-zA-Z_][a-zA-Z0-9_]*|μ...)
/// https://github.com/getgrit/gritql/blob/8f3f077d078ccaf0618510bba904a06309c2435e/resources/language-metavariables/tree-sitter-css/grammar.js#L388
fn consume_grit_metavariable(&mut self) -> CssSyntaxKind {
debug_assert!(self.is_grit_metavariable_start());

// SAFETY: We know the current character is μ.
let current_char = self.current_char_unchecked();
self.advance(current_char.len_utf8());

if self.current_byte() == Some(b'.') {
// SAFETY: We know that the current token is μ...
self.advance(3);
} else {
// μ[a-zA-Z_][a-zA-Z0-9_]*
self.advance(1);
while let Some(chr) = self.current_byte() {
match chr {
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' => {
self.advance(1);
}
_ => break,
}
}
}

GRIT_METAVARIABLE
}
}

impl<'src> ReLexer<'src> for CssLexer<'src> {
Expand Down
15 changes: 15 additions & 0 deletions crates/biome_css_parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ pub struct CssParserOptions {
/// Enables parsing of CSS Modules specific features.
/// Defaults to `false`.
pub css_modules: bool,

/// Enables parsing of Grit metavariables.
/// Defaults to `false`.
pub grit_metavariable: bool,
}

impl CssParserOptions {
Expand All @@ -44,10 +48,21 @@ impl CssParserOptions {
self
}

/// Enables parsing of Grit metavariables.
pub fn allow_grit_metavariables(mut self) -> Self {
self.grit_metavariable = true;
self
}

/// Checks if parsing of CSS Modules features is disabled.
pub fn is_css_modules_disabled(&self) -> bool {
!self.css_modules
}

/// Checks if parsing of Grit metavariables is enabled.
pub fn is_grit_metavariable_enabled(&self) -> bool {
self.grit_metavariable
}
}

impl<'source> CssParser<'source> {
Expand Down
7 changes: 6 additions & 1 deletion crates/biome_css_parser/src/syntax/at_rule/media.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use super::parse_error::expected_media_query;
use crate::parser::CssParser;
use crate::syntax::at_rule::feature::parse_any_query_feature;
use crate::syntax::block::parse_conditional_block;
use crate::syntax::{is_at_identifier, is_nth_at_identifier, parse_regular_identifier};
use crate::syntax::{
is_at_grit_metavariable, is_at_identifier, is_nth_at_identifier, parse_grit_metavariable,
parse_regular_identifier,
};
use biome_css_syntax::CssSyntaxKind::*;
use biome_css_syntax::{CssSyntaxKind, T};
use biome_parser::parse_lists::ParseSeparatedList;
Expand Down Expand Up @@ -77,6 +80,8 @@ impl ParseSeparatedList for MediaQueryList {
fn parse_any_media_query(p: &mut CssParser) -> ParsedSyntax {
if is_at_media_type_query(p) {
parse_any_media_type_query(p)
} else if is_at_grit_metavariable(p) {
parse_grit_metavariable(p)
} else {
let m = p.start();
parse_any_media_condition(p).ok(); // TODO handle error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ 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_nested_qualified_rule, parse_declaration_with_semicolon,
parse_nested_qualified_rule, try_parse,
is_at_declaration, is_at_grit_metavariable, is_at_nested_qualified_rule,
parse_declaration_with_semicolon, parse_grit_metavariable, parse_nested_qualified_rule,
try_parse,
};
use biome_css_syntax::CssSyntaxKind::*;
use biome_css_syntax::{CssSyntaxKind, T};
Expand Down Expand Up @@ -35,7 +36,10 @@ impl ParseBlockBody for DeclarationOrRuleListBlock {

#[inline]
fn is_at_declaration_or_rule_item(p: &mut CssParser) -> bool {
is_at_at_rule(p) || is_at_nested_qualified_rule(p) || is_at_declaration(p)
is_at_at_rule(p)
|| is_at_nested_qualified_rule(p)
|| is_at_declaration(p)
|| is_at_grit_metavariable(p)
}

struct DeclarationOrRuleListParseRecovery;
Expand Down Expand Up @@ -124,6 +128,8 @@ impl ParseNodeList for DeclarationOrRuleList {
parse_declaration_with_semicolon(p)
} else if is_at_nested_qualified_rule(p) {
parse_nested_qualified_rule(p)
} else if is_at_grit_metavariable(p) {
parse_grit_metavariable(p)
} else {
Absent
}
Expand Down
23 changes: 23 additions & 0 deletions crates/biome_css_parser/src/syntax/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,26 @@ fn parse_declaration_important(p: &mut CssParser) -> ParsedSyntax {
Present(m.complete(p, CSS_DECLARATION_IMPORTANT))
}

#[inline]
fn is_at_grit_metavariable(p: &mut CssParser) -> bool {
p.at(GRIT_METAVARIABLE)
}

#[inline]
fn is_nth_at_grit_metavariable(p: &mut CssParser, n: usize) -> bool {
p.nth_at(n, GRIT_METAVARIABLE)
}

#[inline]
fn parse_grit_metavariable(p: &mut CssParser) -> ParsedSyntax {
if !is_at_grit_metavariable(p) {
return Absent;
}
let m = p.start();
p.bump(GRIT_METAVARIABLE);
Present(m.complete(p, CSS_GRIT_METAVARIABLE))
}

#[inline]
pub(crate) fn is_at_any_value(p: &mut CssParser) -> bool {
is_at_any_function(p)
Expand All @@ -259,6 +279,7 @@ pub(crate) fn is_at_any_value(p: &mut CssParser) -> bool {
|| is_at_ratio(p)
|| is_at_color(p)
|| is_at_bracketed_value(p)
|| is_at_grit_metavariable(p)
}

#[inline]
Expand All @@ -283,6 +304,8 @@ pub(crate) fn parse_any_value(p: &mut CssParser) -> ParsedSyntax {
parse_color(p)
} else if is_at_bracketed_value(p) {
parse_bracketed_value(p)
} else if is_at_grit_metavariable(p) {
parse_grit_metavariable(p)
} else {
Absent
}
Expand Down
19 changes: 12 additions & 7 deletions crates/biome_css_parser/src/syntax/selector/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ use biome_parser::prelude::ParsedSyntax;
use biome_parser::prelude::ParsedSyntax::{Absent, Present};
use biome_parser::{token_set, CompletedMarker, Parser, ParserProgress, TokenSet};

use super::{is_nth_at_grit_metavariable, parse_grit_metavariable};

/// Determines the lexical context for parsing CSS selectors.
///
/// This function is applied when lexing CSS selectors. It decides whether the
Expand Down Expand Up @@ -197,7 +199,7 @@ impl ParseRecovery for SelectorListParseRecovery {
/// the elements to which a set of CSS rules apply.
#[inline]
pub(crate) fn is_nth_at_selector(p: &mut CssParser, n: usize) -> bool {
is_nth_at_compound_selector(p, n)
is_nth_at_compound_selector(p, n) || is_nth_at_grit_metavariable(p, n)
}

/// Parses a CSS selector.
Expand All @@ -213,12 +215,15 @@ pub(crate) fn parse_selector(p: &mut CssParser) -> ParsedSyntax {
if !is_nth_at_selector(p, 0) {
return Absent;
}

// In CSS, we have compound selectors and complex selectors.
// Compound selectors are simple, unseparated chains of selectors,
// while complex selectors are compound selectors separated by combinators.
// After parsing the compound selector, it then checks if this compound selector is a part of a complex selector.
parse_compound_selector(p).and_then(|selector| parse_complex_selector(p, selector))
if is_nth_at_grit_metavariable(p, 0) {
parse_grit_metavariable(p)
} else {
// In CSS, we have compound selectors and complex selectors.
// Compound selectors are simple, unseparated chains of selectors,
// while complex selectors are compound selectors separated by combinators.
// After parsing the compound selector, it then checks if this compound selector is a part of a complex selector.
parse_compound_selector(p).and_then(|selector| parse_complex_selector(p, selector))
}
}

const COMPLEX_SELECTOR_COMBINATOR_SET: TokenSet<CssSyntaxKind> =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.foo {
color: μcolor;
}

.foo {
μbar
}

.foo {
@media μbaz {}
}

μqux {}

μ... {}
Loading

0 comments on commit a77c00b

Please sign in to comment.