use crate::lexer::{CssLexContext, CssReLexContext};
use crate::parser::CssParser;
use biome_css_syntax::CssSyntaxKind::{
self, CSS_BOGUS_UNICODE_RANGE_VALUE, CSS_DIMENSION_VALUE, CSS_NUMBER_LITERAL,
CSS_UNICODE_CODEPOINT, CSS_UNICODE_CODEPOINT_LITERAL, CSS_UNICODE_RANGE,
CSS_UNICODE_RANGE_INTERVAL, CSS_UNICODE_RANGE_WILDCARD, CSS_UNICODE_RANGE_WILDCARD_LITERAL,
};
use biome_css_syntax::{TextRange, T};
use biome_parser::diagnostic::{expected_any, expected_node, ParseDiagnostic};
use biome_parser::parsed_syntax::ParsedSyntax;
use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present};
use biome_parser::{token_set, Parser, TokenSet};
const UNICODE: TokenSet<CssSyntaxKind> = token_set![
T![+],
CSS_NUMBER_LITERAL,
CSS_DIMENSION_VALUE,
];
pub(crate) fn is_at_unicode_range(p: &mut CssParser) -> bool {
matches!(p.cur_text(), "U" | "u") && p.nth_at_ts(1, UNICODE)
}
pub(crate) fn parse_unicode_range(p: &mut CssParser) -> ParsedSyntax {
if !is_at_unicode_range(p) {
return Absent;
}
let kind = p.re_lex(CssReLexContext::UnicodeRange);
if kind != T![U+] {
return Absent;
}
let m = p.start();
p.bump_with_context(T![U+], CssLexContext::UnicodeRange);
if is_at_unicode_range_wildcard(p) {
parse_unicode_range_wildcard(p).ok();
return Present(m.complete(p, CSS_UNICODE_RANGE));
}
let codepoint = parse_unicode_codepoint(p).or_add_diagnostic(p, expected_codepoint_value);
let Some(codepoint) = codepoint else {
return Present(m.complete(p, CSS_BOGUS_UNICODE_RANGE_VALUE));
};
if p.at(T![-]) {
let range = codepoint.precede(p);
p.bump_with_context(T![-], CssLexContext::UnicodeRange);
if parse_unicode_codepoint(p).is_absent() {
if parse_unicode_range_wildcard(p)
.add_diagnostic_if_present(p, wildcard_not_allowed)
.is_none()
{
p.error(expected_codepoint(p, p.cur_range()));
}
range.abandon(p);
return Present(m.complete(p, CSS_BOGUS_UNICODE_RANGE_VALUE));
}
range.complete(p, CSS_UNICODE_RANGE_INTERVAL);
}
Present(m.complete(p, CSS_UNICODE_RANGE))
}
fn is_at_unicode_codepoint(p: &mut CssParser) -> bool {
p.at(CSS_UNICODE_CODEPOINT_LITERAL)
}
fn parse_unicode_codepoint(p: &mut CssParser) -> ParsedSyntax {
if !is_at_unicode_codepoint(p) {
return Absent;
}
let m = p.start();
p.bump_with_context(CSS_UNICODE_CODEPOINT_LITERAL, CssLexContext::UnicodeRange);
Present(m.complete(p, CSS_UNICODE_CODEPOINT))
}
fn is_at_unicode_range_wildcard(p: &mut CssParser) -> bool {
p.at(CSS_UNICODE_RANGE_WILDCARD_LITERAL)
}
fn parse_unicode_range_wildcard(p: &mut CssParser) -> ParsedSyntax {
if !is_at_unicode_range_wildcard(p) {
return Absent;
}
let m = p.start();
p.bump_with_context(
CSS_UNICODE_RANGE_WILDCARD_LITERAL,
CssLexContext::UnicodeRange,
);
Present(m.complete(p, CSS_UNICODE_RANGE_WILDCARD))
}
pub(crate) fn wildcard_not_allowed(p: &CssParser, range: TextRange) -> ParseDiagnostic {
p.err_builder("Codepoint range wildcard is not valid here.", range)
.with_hint(
"Wildcards (`U+????`) are only allowed at the beginning of a Unicode range descriptor. \
When specifying a range interval (`U+XXXX-YYYY`), wildcards cannot be used in the second position."
)
}
pub(crate) fn expected_codepoint(p: &CssParser, range: TextRange) -> ParseDiagnostic {
expected_node("codepoint", range, p)
.with_hint("Expected a valid Unicode codepoint (e.g., U+1234).")
}
pub(crate) fn expected_codepoint_value(p: &CssParser, range: TextRange) -> ParseDiagnostic {
expected_any(&["codepoint", "codepoint range wildcard"], range, p)
.with_hint("Expected a valid Unicode codepoint (e.g., U+1234) or a codepoint range wildcard (e.g., U+????).")
}