use biome_css_syntax::CssSyntaxKind::*;
use biome_css_syntax::{CssSyntaxKind, TextRange, T};
use biome_parser::diagnostic::{expect_one_of, ParseDiagnostic};
use biome_parser::parse_lists::ParseSeparatedList;
use biome_parser::parse_recovery::{ParseRecovery, RecoveryResult};
use biome_parser::parsed_syntax::ParsedSyntax::Present;
use biome_parser::prelude::ParsedSyntax::Absent;
use biome_parser::prelude::ToDiagnostic;
use biome_parser::{parsed_syntax::ParsedSyntax, token_set, Parser};
use crate::parser::CssParser;
use crate::syntax::parse_error::{expected_component_value, expected_identifier};
use crate::syntax::{
is_at_identifier, is_at_string, is_nth_at_identifier, parse_regular_identifier, parse_string,
};
#[inline]
pub(crate) fn is_at_value_at_rule(p: &mut CssParser) -> bool {
p.at(T![value])
}
#[inline]
pub(crate) fn parse_value_at_rule(p: &mut CssParser) -> ParsedSyntax {
if !is_at_value_at_rule(p) {
return Absent;
}
if p.options().is_css_modules_disabled() {
p.error(value_at_rule_not_allowed(p, p.cur_range()));
while !p.eat(T![;]) {
p.bump_any();
}
return Absent;
}
let m = p.start();
p.bump(T![value]);
if is_at_value_at_rule_declaration_clause(p) {
parse_value_at_rule_declaration_clause(p).ok();
} else if is_at_value_at_rule_import_clause(p) {
parse_value_at_rule_import_clause(p).ok();
} else {
p.error(expected_at_rule_declaration_clause(p, p.cur_range()));
}
p.expect(T![;]);
Present(m.complete(p, CSS_VALUE_AT_RULE))
}
fn is_at_value_at_rule_import_clause(p: &mut CssParser) -> bool {
is_at_identifier(p)
}
fn parse_value_at_rule_import_clause(p: &mut CssParser) -> ParsedSyntax {
if !is_at_value_at_rule_import_clause(p) {
return Absent;
}
let m = p.start();
ValueAtRuleImportSpecifierList.parse_list(p);
p.expect(T![from]);
parse_value_at_rule_import_source(p).or_add_diagnostic(p, expected_import_source);
Present(m.complete(p, CSS_VALUE_AT_RULE_IMPORT_CLAUSE))
}
struct ValueAtRuleImportSpecifierList;
impl ParseSeparatedList for ValueAtRuleImportSpecifierList {
type Kind = CssSyntaxKind;
type Parser<'source> = CssParser<'source>;
const LIST_KIND: Self::Kind = CSS_VALUE_AT_RULE_IMPORT_SPECIFIER_LIST;
fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax {
parse_value_at_rule_import_specifier(p)
}
fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool {
p.at(T![from])
}
fn recover(
&mut self,
p: &mut Self::Parser<'_>,
parsed_element: ParsedSyntax,
) -> RecoveryResult {
parsed_element.or_recover(
p,
&ValueAtRuleImportSpecifierListParseRecovery,
expected_import_specifier,
)
}
fn separating_element_kind(&mut self) -> Self::Kind {
T![,]
}
}
struct ValueAtRuleImportSpecifierListParseRecovery;
impl ParseRecovery for ValueAtRuleImportSpecifierListParseRecovery {
type Kind = CssSyntaxKind;
type Parser<'source> = CssParser<'source>;
const RECOVERED_KIND: Self::Kind = CSS_BOGUS;
fn is_at_recovered(&self, p: &mut Self::Parser<'_>) -> bool {
p.at_ts(token_set![T![,], T![from]])
}
}
fn parse_value_at_rule_import_specifier(p: &mut CssParser) -> ParsedSyntax {
if !is_at_identifier(p) {
return Absent;
}
let m = p.start();
parse_regular_identifier(p).ok();
let kind = if p.eat(T![as]) {
parse_regular_identifier(p).or_add_diagnostic(p, expected_identifier);
CSS_VALUE_AT_RULE_NAMED_IMPORT_SPECIFIER
} else {
CSS_VALUE_AT_RULE_IMPORT_SPECIFIER
};
Present(m.complete(p, kind))
}
fn is_at_value_at_rule_import_source(p: &mut CssParser) -> bool {
is_at_identifier(p) || is_at_string(p)
}
fn parse_value_at_rule_import_source(p: &mut CssParser) -> ParsedSyntax {
if !is_at_value_at_rule_import_source(p) {
return Absent;
}
if is_at_identifier(p) {
parse_regular_identifier(p)
} else {
parse_string(p)
}
}
fn is_at_value_at_rule_declaration_clause(p: &mut CssParser) -> bool {
is_nth_at_value_at_rule_generic_property(p, 0)
}
fn parse_value_at_rule_declaration_clause(p: &mut CssParser) -> ParsedSyntax {
if !is_at_value_at_rule_declaration_clause(p) {
return Absent;
}
let m = p.start();
ValueAtRulePropertyList.parse_list(p);
Present(m.complete(p, CSS_VALUE_AT_RULE_DECLARATION_CLAUSE))
}
pub(crate) struct ValueAtRulePropertyList;
impl ParseSeparatedList for ValueAtRulePropertyList {
type Kind = CssSyntaxKind;
type Parser<'source> = CssParser<'source>;
const LIST_KIND: Self::Kind = CSS_VALUE_AT_RULE_PROPERTY_LIST;
fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax {
parse_value_at_rule_generic_property(p)
}
fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool {
p.at(T![;])
}
fn recover(
&mut self,
p: &mut Self::Parser<'_>,
parsed_element: ParsedSyntax,
) -> RecoveryResult {
parsed_element.or_recover(
p,
&ValueAtRulePropertyListParseRecovery,
expected_component_value,
)
}
fn separating_element_kind(&mut self) -> Self::Kind {
T![,]
}
}
fn is_nth_at_value_at_rule_generic_property(p: &mut CssParser, n: usize) -> bool {
is_nth_at_identifier(p, n) && p.nth_at(n + 1, T![:])
}
fn parse_value_at_rule_generic_property(p: &mut CssParser) -> ParsedSyntax {
if !is_nth_at_value_at_rule_generic_property(p, 0) {
return Absent;
}
let m = p.start();
parse_regular_identifier(p).ok();
p.expect(T![:]);
{
let m = p.start();
while !(p.at(EOF)
|| ValueAtRulePropertyList.is_at_list_end(p)
|| p.at(T![,]) && is_nth_at_value_at_rule_generic_property(p, 1))
{
p.bump_any();
}
m.complete(p, CSS_VALUE_AT_RULE_GENERIC_VALUE);
}
Present(m.complete(p, CSS_VALUE_AT_RULE_GENERIC_PROPERTY))
}
struct ValueAtRulePropertyListParseRecovery;
impl ParseRecovery for ValueAtRulePropertyListParseRecovery {
type Kind = CssSyntaxKind;
type Parser<'source> = CssParser<'source>;
const RECOVERED_KIND: Self::Kind = CSS_BOGUS_PROPERTY;
fn is_at_recovered(&self, p: &mut Self::Parser<'_>) -> bool {
p.at(T![;]) || is_at_identifier(p) || p.has_nth_preceding_line_break(0)
}
}
fn expected_import_source(p: &CssParser, range: TextRange) -> ParseDiagnostic {
expect_one_of(&["identifier", "string"], range).into_diagnostic(p)
}
fn expected_import_specifier(p: &CssParser, range: TextRange) -> ParseDiagnostic {
expect_one_of(&["identifier", "<identifier> as <identifier>"], range).into_diagnostic(p)
}
fn expected_at_rule_declaration_clause(p: &CssParser, range: TextRange) -> ParseDiagnostic {
expect_one_of(
&["declaration at rule clause", "import at rule clause"],
range,
)
.into_diagnostic(p)
}
pub(crate) fn value_at_rule_not_allowed(p: &CssParser, range: TextRange) -> ParseDiagnostic {
p.err_builder(
"@value at-rule is not a standard CSS feature.",
range,
)
.with_hint(
"You can enable @value at-rule parsing by setting the `css.parser.cssModules` option to `true` in your configuration file.",
)
}