Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 crates/biome_css_formatter/src/scss/any/expression_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ impl FormatRule<AnyScssExpressionItem> for FormatAnyScssExpressionItem {
fn fmt(&self, node: &AnyScssExpressionItem, f: &mut CssFormatter) -> FormatResult<()> {
match node {
AnyScssExpressionItem::AnyCssValue(node) => node.format().fmt(f),
AnyScssExpressionItem::CssDeclarationImportant(node) => node.format().fmt(f),
AnyScssExpressionItem::CssGenericDelimiter(node) => node.format().fmt(f),
AnyScssExpressionItem::ScssArbitraryArgument(node) => node.format().fmt(f),
AnyScssExpressionItem::ScssBinaryExpression(node) => node.format().fmt(f),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Trailing newline: true

```scss
$long-list: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11;
$empty-elements: 1,, 3;
$empty-elements: 1, , 3;
$nested-map: (
a: (
b: 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
source: crates/biome_formatter_test/src/snapshot_builder.rs
assertion_line: 212
info: css/scss/expression/formatting.scss
---

Expand Down
6 changes: 3 additions & 3 deletions crates/biome_css_parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use biome_css_factory::CssSyntaxFactory;
use biome_css_syntax::{AnyCssRoot, CssFileSource, CssLanguage, CssSyntaxNode};
pub use biome_parser::prelude::*;
use biome_parser::{AnyParse, EmbeddedNodeParse, NodeParse};
use biome_rowan::{AstNode, NodeCache, SyntaxNodeWithOffset};
use biome_rowan::{AstNode, NodeCache, SyntaxNodeWithOffset, TextSize};
pub use parser::{CssModulesKind, CssParserOptions};

mod lexer;
Expand All @@ -22,7 +22,7 @@ pub(crate) type CssLosslessTreeSink<'source> =
LosslessTreeSink<'source, CssLanguage, CssSyntaxFactory>;

pub(crate) type CssOffsetLosslessTreeSink<'source> =
biome_parser::tree_sink::OffsetLosslessTreeSink<'source, CssLanguage, CssSyntaxFactory>;
OffsetLosslessTreeSink<'source, CssLanguage, CssSyntaxFactory>;

pub fn parse_css(source: &str, source_type: CssFileSource, options: CssParserOptions) -> CssParse {
let mut cache = NodeCache::default();
Expand Down Expand Up @@ -166,7 +166,7 @@ impl CssOffsetParse {
}

/// Get the base offset applied to this parse result
pub fn base_offset(&self) -> biome_rowan::TextSize {
pub fn base_offset(&self) -> TextSize {
self.root.base_offset()
}

Expand Down
4 changes: 2 additions & 2 deletions crates/biome_css_parser/src/syntax/at_rule/tailwind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::syntax::{is_at_identifier, parse_identifier, parse_regular_identifier
use biome_css_syntax::CssSyntaxKind::{self, *};
use biome_css_syntax::T;
use biome_parser::parse_lists::{ParseNodeList, ParseSeparatedList};
use biome_parser::parse_recovery::ParseRecoveryTokenSet;
use biome_parser::parse_recovery::{ParseRecoveryTokenSet, RecoveryResult};
use biome_parser::parsed_syntax::ParsedSyntax;
use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present};
use biome_parser::prelude::*;
Expand Down Expand Up @@ -207,7 +207,7 @@ impl ParseNodeList for ApplyClassList {
&mut self,
p: &mut Self::Parser<'_>,
parsed_element: ParsedSyntax,
) -> biome_parser::parse_recovery::RecoveryResult {
) -> RecoveryResult {
parsed_element.or_recover_with_token_set(
p,
&ParseRecoveryTokenSet::new(CSS_BOGUS_CUSTOM_IDENTIFIER, TW_APPLY_CLASS_LIST_END)
Expand Down
14 changes: 0 additions & 14 deletions crates/biome_css_parser/src/syntax/parse_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,20 +229,6 @@ pub(crate) fn expected_component_value(p: &CssParser, range: TextRange) -> Parse
.into_diagnostic(p)
}

pub(crate) fn expected_scss_expression(p: &CssParser, range: TextRange) -> ParseDiagnostic {
expected_node("SCSS expression", range, p)
}

pub(crate) fn scss_ellipsis_not_allowed(p: &CssParser, range: TextRange) -> ParseDiagnostic {
p.err_builder(
"SCSS arbitrary arguments (`...`) are only allowed in function call arguments.",
range,
)
.with_hint(markup! {
"Use `...` only for function arguments, for example `fn($args...)`."
})
}

pub(crate) fn expected_declaration(p: &CssParser, range: TextRange) -> ParseDiagnostic {
expected_node("<declaration>", range, p)
}
Expand Down
1 change: 1 addition & 0 deletions crates/biome_css_parser/src/syntax/scss/declaration/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod nesting;
mod variable;
mod variable_modifier;

pub(crate) use nesting::{is_at_scss_nesting_declaration, parse_scss_nesting_declaration};
pub(crate) use variable::{is_at_scss_declaration, parse_scss_declaration};
11 changes: 5 additions & 6 deletions crates/biome_css_parser/src/syntax/scss/declaration/nesting.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
use crate::parser::CssParser;
use crate::syntax::block::parse_declaration_or_rule_list_block;
use crate::syntax::parse_error::expected_component_value;
use crate::syntax::scss::parse_scss_expression_allow_empty_value_until;
use crate::syntax::scss::{
SCSS_NESTING_VALUE_END_SET, parse_scss_expression_allow_empty_value_until,
};
use crate::syntax::{
CssSyntaxFeatures, is_at_dashed_identifier, is_at_identifier, parse_regular_identifier,
};
use biome_css_syntax::CssSyntaxKind::{
CSS_DECLARATION, CSS_DECLARATION_IMPORTANT, CSS_DECLARATION_WITH_SEMICOLON,
CSS_GENERIC_PROPERTY, EOF, SCSS_NESTING_DECLARATION,
};
use biome_css_syntax::{CssSyntaxKind, T};
use biome_css_syntax::T;
use biome_parser::prelude::ParsedSyntax;
use biome_parser::prelude::ParsedSyntax::{Absent, Present};
use biome_parser::{CompletedMarker, Parser, SyntaxFeature, TokenSet, token_set};

const SCSS_NESTING_VALUE_END_SET: TokenSet<CssSyntaxKind> =
token_set![T!['{'], T![;], T!['}'], T![!], EOF];
use biome_parser::{CompletedMarker, Parser, SyntaxFeature};

/// Detects nested property syntax (`prop: { ... }`) while excluding custom properties
/// and CSS Modules declarations that must remain regular properties.
Expand Down
148 changes: 10 additions & 138 deletions crates/biome_css_parser/src/syntax/scss/declaration/variable.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
use super::super::{is_at_scss_identifier, parse_scss_identifier};
use crate::parser::CssParser;
use crate::syntax::parse_error::expected_scss_expression;
use crate::syntax::scss::parse_scss_expression_until;
use crate::syntax::{is_nth_at_identifier, parse_regular_identifier};
use biome_css_syntax::CssSyntaxKind::{
EOF, ERROR_TOKEN, SCSS_DECLARATION, SCSS_NAMESPACED_IDENTIFIER, SCSS_VARIABLE_MODIFIER,
SCSS_VARIABLE_MODIFIER_LIST,
use super::super::{
is_at_scss_identifier, is_at_scss_namespaced_identifier, parse_scss_identifier,
parse_scss_namespaced_identifier,
};
use biome_css_syntax::{CssSyntaxKind, T, TextRange};
use biome_parser::diagnostic::{ParseDiagnostic, expected_token_any};
use biome_parser::parse_lists::ParseNodeList;
use biome_parser::parse_recovery::{RecoveryError, RecoveryResult};
use super::variable_modifier::parse_scss_variable_modifiers;
use crate::parser::CssParser;
use crate::syntax::scss::{expected_scss_expression, parse_scss_expression_until};
use biome_css_syntax::CssSyntaxKind::{EOF, SCSS_DECLARATION};
use biome_css_syntax::T;
use biome_parser::prelude::ParsedSyntax;
use biome_parser::prelude::ParsedSyntax::{Absent, Present};
use biome_parser::{Parser, TokenSet, token_set};
use biome_parser::{Parser, token_set};

/// Detects a SCSS variable declaration (including module-qualified variables).
///
Expand Down Expand Up @@ -52,7 +48,7 @@ pub(crate) fn parse_scss_declaration(p: &mut CssParser) -> ParsedSyntax {
let m = p.start();

parse_scss_declaration_name(p).ok();
p.expect(T![:]);
p.bump(T![:]);

parse_scss_expression_until(p, token_set![T![!], T![;], T!['}']])
.or_add_diagnostic(p, expected_scss_expression);
Expand All @@ -70,27 +66,6 @@ pub(crate) fn parse_scss_declaration(p: &mut CssParser) -> ParsedSyntax {
Present(m.complete(p, SCSS_DECLARATION))
}

#[inline]
fn is_at_scss_namespaced_identifier(p: &mut CssParser) -> bool {
is_nth_at_identifier(p, 0)
&& p.nth_at(1, T![.])
&& p.nth_at(2, T![$])
&& is_nth_at_identifier(p, 3)
}

#[inline]
fn parse_scss_namespaced_identifier(p: &mut CssParser) -> ParsedSyntax {
if !is_at_scss_namespaced_identifier(p) {
return Absent;
}

let m = p.start();
parse_regular_identifier(p).ok();
p.expect(T![.]);
parse_scss_identifier(p).ok();
Present(m.complete(p, SCSS_NAMESPACED_IDENTIFIER))
}

#[inline]
fn parse_scss_declaration_name(p: &mut CssParser) -> ParsedSyntax {
if is_at_scss_namespaced_identifier(p) {
Expand All @@ -99,106 +74,3 @@ fn parse_scss_declaration_name(p: &mut CssParser) -> ParsedSyntax {
parse_scss_identifier(p)
}
}

const SCSS_VARIABLE_MODIFIER_LIST_END_SET: TokenSet<CssSyntaxKind> =
token_set![T![;], T!['}'], EOF];
const SCSS_VARIABLE_MODIFIER_TOKEN_SET: TokenSet<CssSyntaxKind> =
token_set![T![default], T![global]];

#[inline]
fn parse_scss_variable_modifiers(p: &mut CssParser) {
ScssVariableModifierList.parse_list(p);
recover_scss_variable_modifier_tail(p);
}

#[inline]
fn recover_scss_variable_modifier_tail(p: &mut CssParser) {
loop {
if p.at_ts(SCSS_VARIABLE_MODIFIER_LIST_END_SET) {
return;
}

if p.at(T![!]) {
parse_scss_variable_modifier(p).ok();
continue;
}

if p.at(ERROR_TOKEN) {
p.bump_any();
continue;
}

// Recover malformed modifier separators only when they directly precede
// another modifier (`bar !global`). Otherwise leave the token for
// missing-semicolon recovery at declaration level.
if p.nth_at(1, T![!]) {
let range = p.cur_range();
p.error(
p.err_builder("Unexpected value or character.", range)
.with_hint("Expected a variable modifier or the end of the declaration."),
);
p.bump_any();
continue;
}

return;
}
}

#[inline]
fn parse_scss_variable_modifier(p: &mut CssParser) -> ParsedSyntax {
if !p.at(T![!]) {
return Absent;
}

let m = p.start();
p.bump(T![!]);

if p.at_ts(SCSS_VARIABLE_MODIFIER_TOKEN_SET) {
p.bump_ts(SCSS_VARIABLE_MODIFIER_TOKEN_SET);
} else if p.at(T![important]) {
p.error(important_modifier_not_allowed(p, p.cur_range()));
p.bump(T![important]);
} else {
p.error(expected_token_any(&[T![default], T![global]]));
if !p.at_ts(SCSS_VARIABLE_MODIFIER_LIST_END_SET) {
p.bump_any();
}
}

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

fn important_modifier_not_allowed(p: &CssParser, range: TextRange) -> ParseDiagnostic {
p.err_builder("`!important` is not valid here.", range)
.with_hint(
"SCSS variable declarations only support the `!default` and `!global` modifiers.",
)
}

struct ScssVariableModifierList;

impl ParseNodeList for ScssVariableModifierList {
type Kind = CssSyntaxKind;
type Parser<'source> = CssParser<'source>;
const LIST_KIND: Self::Kind = SCSS_VARIABLE_MODIFIER_LIST;

fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax {
parse_scss_variable_modifier(p)
}

fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool {
p.at_ts(SCSS_VARIABLE_MODIFIER_LIST_END_SET) || !p.at(T![!])
}

fn recover(
&mut self,
_p: &mut Self::Parser<'_>,
parsed_element: ParsedSyntax,
) -> RecoveryResult {
match parsed_element {
Absent => Err(RecoveryError::AlreadyRecovered),
Present(m) => Ok(m),
}
}
}
Loading
Loading