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(css_parser): introduce grit metavariable #3340

Merged
merged 18 commits into from
Jul 9, 2024
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
Loading