From ac925c3b214bd984c91ac2b37cf8d967d5374624 Mon Sep 17 00:00:00 2001 From: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:40:21 +0300 Subject: [PATCH 1/6] feat(css_parser): Implement CSS unicode range --- .../src/generated/node_factory.rs | 45 + .../src/generated/syntax_factory.rs | 98 ++ crates/biome_css_formatter/src/css/any/mod.rs | 1 + .../src/css/any/unicode_value.rs | 17 + .../biome_css_formatter/src/css/any/value.rs | 1 + .../src/css/auxiliary/mod.rs | 4 + .../src/css/auxiliary/unicode_codepoint.rs | 13 + .../src/css/auxiliary/unicode_range.rs | 16 + .../css/auxiliary/unicode_range_interval.rs | 16 + .../css/auxiliary/unicode_range_wildcard.rs | 15 + .../css/bogus/bogus_unicode_range_value.rs | 5 + .../biome_css_formatter/src/css/bogus/mod.rs | 1 + crates/biome_css_formatter/src/generated.rs | 229 +++ .../specs/css/properties/unicode_range.css | 59 + .../css/properties/unicode_range.css.snap | 118 ++ .../specs/prettier/css/parens/parens.css.snap | 63 +- crates/biome_css_parser/src/lexer/mod.rs | 79 +- crates/biome_css_parser/src/parser.rs | 1 - crates/biome_css_parser/src/syntax/mod.rs | 3 + .../src/syntax/property/mod.rs | 2 + .../src/syntax/property/unicode_range.rs | 158 ++ .../ok/property/unicode_range.css | 31 + .../ok/property/unicode_range.css.snap | 1301 +++++++++++++++++ crates/biome_css_parser/tests/spec_test.rs | 3 +- crates/biome_css_syntax/src/generated/kind.rs | 17 +- .../biome_css_syntax/src/generated/macros.rs | 21 + .../biome_css_syntax/src/generated/nodes.rs | 561 ++++++- .../src/generated/nodes_mut.rs | 50 + crates/biome_css_syntax/src/lib.rs | 2 + xtask/codegen/css.ungram | 31 + xtask/codegen/src/css_kinds_src.rs | 8 + 31 files changed, 2899 insertions(+), 70 deletions(-) create mode 100644 crates/biome_css_formatter/src/css/any/unicode_value.rs create mode 100644 crates/biome_css_formatter/src/css/auxiliary/unicode_codepoint.rs create mode 100644 crates/biome_css_formatter/src/css/auxiliary/unicode_range.rs create mode 100644 crates/biome_css_formatter/src/css/auxiliary/unicode_range_interval.rs create mode 100644 crates/biome_css_formatter/src/css/auxiliary/unicode_range_wildcard.rs create mode 100644 crates/biome_css_formatter/src/css/bogus/bogus_unicode_range_value.rs create mode 100644 crates/biome_css_formatter/tests/specs/css/properties/unicode_range.css create mode 100644 crates/biome_css_formatter/tests/specs/css/properties/unicode_range.css.snap create mode 100644 crates/biome_css_parser/src/syntax/property/unicode_range.rs create mode 100644 crates/biome_css_parser/tests/css_test_suite/ok/property/unicode_range.css create mode 100644 crates/biome_css_parser/tests/css_test_suite/ok/property/unicode_range.css.snap diff --git a/crates/biome_css_factory/src/generated/node_factory.rs b/crates/biome_css_factory/src/generated/node_factory.rs index 0a421cb9592f..f221cb2b3ed5 100644 --- a/crates/biome_css_factory/src/generated/node_factory.rs +++ b/crates/biome_css_factory/src/generated/node_factory.rs @@ -2048,6 +2048,41 @@ impl CssTypeSelectorBuilder { )) } } +pub fn css_unicode_codepoint(value_token: SyntaxToken) -> CssUnicodeCodepoint { + CssUnicodeCodepoint::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_UNICODE_CODEPOINT, + [Some(SyntaxElement::Token(value_token))], + )) +} +pub fn css_unicode_range(prefix_token: SyntaxToken, value: AnyCssUnicodeValue) -> CssUnicodeRange { + CssUnicodeRange::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_UNICODE_RANGE, + [ + Some(SyntaxElement::Token(prefix_token)), + Some(SyntaxElement::Node(value.into_syntax())), + ], + )) +} +pub fn css_unicode_range_interval( + start: CssUnicodeCodepoint, + minus_token: SyntaxToken, + end: CssUnicodeCodepoint, +) -> CssUnicodeRangeInterval { + CssUnicodeRangeInterval::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_UNICODE_RANGE_INTERVAL, + [ + Some(SyntaxElement::Node(start.into_syntax())), + Some(SyntaxElement::Token(minus_token)), + Some(SyntaxElement::Node(end.into_syntax())), + ], + )) +} +pub fn css_unicode_range_wildcard(value_token: SyntaxToken) -> CssUnicodeRangeWildcard { + CssUnicodeRangeWildcard::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_UNICODE_RANGE_WILDCARD, + [Some(SyntaxElement::Token(value_token))], + )) +} pub fn css_universal_namespace_prefix(star_token: SyntaxToken) -> CssUniversalNamespacePrefix { CssUniversalNamespacePrefix::unwrap_cast(SyntaxNode::new_detached( CssSyntaxKind::CSS_UNIVERSAL_NAMESPACE_PREFIX, @@ -2930,6 +2965,16 @@ where slots, )) } +pub fn css_bogus_unicode_range_value(slots: I) -> CssBogusUnicodeRangeValue +where + I: IntoIterator>, + I::IntoIter: ExactSizeIterator, +{ + CssBogusUnicodeRangeValue::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_BOGUS_UNICODE_RANGE_VALUE, + slots, + )) +} pub fn css_bogus_url_modifier(slots: I) -> CssBogusUrlModifier where I: IntoIterator>, diff --git a/crates/biome_css_factory/src/generated/syntax_factory.rs b/crates/biome_css_factory/src/generated/syntax_factory.rs index 4f7c7ed5879a..dac7f48f0441 100644 --- a/crates/biome_css_factory/src/generated/syntax_factory.rs +++ b/crates/biome_css_factory/src/generated/syntax_factory.rs @@ -36,6 +36,7 @@ impl SyntaxFactory for CssSyntaxFactory { | CSS_BOGUS_SCOPE_RANGE | CSS_BOGUS_SELECTOR | CSS_BOGUS_SUB_SELECTOR + | CSS_BOGUS_UNICODE_RANGE_VALUE | CSS_BOGUS_URL_MODIFIER | CSS_UNKNOWN_AT_RULE_COMPONENT_LIST | CSS_VALUE_AT_RULE_GENERIC_VALUE => { @@ -4177,6 +4178,103 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(CSS_TYPE_SELECTOR, children) } + CSS_UNICODE_CODEPOINT => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<1usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == CSS_UNICODE_CODEPOINT_LITERAL { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + CSS_UNICODE_CODEPOINT.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(CSS_UNICODE_CODEPOINT, children) + } + CSS_UNICODE_RANGE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == T![U+] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if AnyCssUnicodeValue::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + CSS_UNICODE_RANGE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(CSS_UNICODE_RANGE, children) + } + CSS_UNICODE_RANGE_INTERVAL => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if CssUnicodeCodepoint::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [-] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if CssUnicodeCodepoint::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + CSS_UNICODE_RANGE_INTERVAL.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(CSS_UNICODE_RANGE_INTERVAL, children) + } + CSS_UNICODE_RANGE_WILDCARD => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<1usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == CSS_UNICODE_RANGE_WILDCARD_LITERAL { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + CSS_UNICODE_RANGE_WILDCARD.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(CSS_UNICODE_RANGE_WILDCARD, children) + } CSS_UNIVERSAL_NAMESPACE_PREFIX => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<1usize> = RawNodeSlots::default(); diff --git a/crates/biome_css_formatter/src/css/any/mod.rs b/crates/biome_css_formatter/src/css/any/mod.rs index bb8adf8963c6..70ce67e84bf6 100644 --- a/crates/biome_css_formatter/src/css/any/mod.rs +++ b/crates/biome_css_formatter/src/css/any/mod.rs @@ -71,6 +71,7 @@ pub(crate) mod supports_and_combinable_condition; pub(crate) mod supports_condition; pub(crate) mod supports_in_parens; pub(crate) mod supports_or_combinable_condition; +pub(crate) mod unicode_value; pub(crate) mod url_modifier; pub(crate) mod url_value; pub(crate) mod value; diff --git a/crates/biome_css_formatter/src/css/any/unicode_value.rs b/crates/biome_css_formatter/src/css/any/unicode_value.rs new file mode 100644 index 000000000000..6807cf532516 --- /dev/null +++ b/crates/biome_css_formatter/src/css/any/unicode_value.rs @@ -0,0 +1,17 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_css_syntax::AnyCssUnicodeValue; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyCssUnicodeValue; +impl FormatRule for FormatAnyCssUnicodeValue { + type Context = CssFormatContext; + fn fmt(&self, node: &AnyCssUnicodeValue, f: &mut CssFormatter) -> FormatResult<()> { + match node { + AnyCssUnicodeValue::CssBogusUnicodeRangeValue(node) => node.format().fmt(f), + AnyCssUnicodeValue::CssUnicodeCodepoint(node) => node.format().fmt(f), + AnyCssUnicodeValue::CssUnicodeRangeInterval(node) => node.format().fmt(f), + AnyCssUnicodeValue::CssUnicodeRangeWildcard(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_css_formatter/src/css/any/value.rs b/crates/biome_css_formatter/src/css/any/value.rs index af0ad47fa6c6..ad9316306fd0 100644 --- a/crates/biome_css_formatter/src/css/any/value.rs +++ b/crates/biome_css_formatter/src/css/any/value.rs @@ -18,6 +18,7 @@ impl FormatRule for FormatAnyCssValue { AnyCssValue::CssNumber(node) => node.format().fmt(f), AnyCssValue::CssRatio(node) => node.format().fmt(f), AnyCssValue::CssString(node) => node.format().fmt(f), + AnyCssValue::CssUnicodeRange(node) => node.format().fmt(f), } } } diff --git a/crates/biome_css_formatter/src/css/auxiliary/mod.rs b/crates/biome_css_formatter/src/css/auxiliary/mod.rs index efa42ca644f9..b49df5ae5c91 100644 --- a/crates/biome_css_formatter/src/css/auxiliary/mod.rs +++ b/crates/biome_css_formatter/src/css/auxiliary/mod.rs @@ -74,6 +74,10 @@ pub(crate) mod supports_condition_in_parens; pub(crate) mod supports_feature_declaration; pub(crate) mod supports_not_condition; pub(crate) mod supports_or_condition; +pub(crate) mod unicode_codepoint; +pub(crate) mod unicode_range; +pub(crate) mod unicode_range_interval; +pub(crate) mod unicode_range_wildcard; pub(crate) mod universal_namespace_prefix; pub(crate) mod url_function; pub(crate) mod value_at_rule_declaration_clause; diff --git a/crates/biome_css_formatter/src/css/auxiliary/unicode_codepoint.rs b/crates/biome_css_formatter/src/css/auxiliary/unicode_codepoint.rs new file mode 100644 index 000000000000..d7de22962844 --- /dev/null +++ b/crates/biome_css_formatter/src/css/auxiliary/unicode_codepoint.rs @@ -0,0 +1,13 @@ +use crate::prelude::*; +use biome_css_syntax::{CssUnicodeCodepoint, CssUnicodeCodepointFields}; +use biome_formatter::write; + +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatCssUnicodeCodepoint; +impl FormatNodeRule for FormatCssUnicodeCodepoint { + fn fmt_fields(&self, node: &CssUnicodeCodepoint, f: &mut CssFormatter) -> FormatResult<()> { + let CssUnicodeCodepointFields { value_token } = node.as_fields(); + + write!(f, [value_token.format(),]) + } +} diff --git a/crates/biome_css_formatter/src/css/auxiliary/unicode_range.rs b/crates/biome_css_formatter/src/css/auxiliary/unicode_range.rs new file mode 100644 index 000000000000..975d3236b486 --- /dev/null +++ b/crates/biome_css_formatter/src/css/auxiliary/unicode_range.rs @@ -0,0 +1,16 @@ +use crate::prelude::*; +use biome_css_syntax::{CssUnicodeRange, CssUnicodeRangeFields}; +use biome_formatter::write; + +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatCssUnicodeRange; +impl FormatNodeRule for FormatCssUnicodeRange { + fn fmt_fields(&self, node: &CssUnicodeRange, f: &mut CssFormatter) -> FormatResult<()> { + let CssUnicodeRangeFields { + prefix_token, + value, + } = node.as_fields(); + + write!(f, [prefix_token.format(), value.format()]) + } +} diff --git a/crates/biome_css_formatter/src/css/auxiliary/unicode_range_interval.rs b/crates/biome_css_formatter/src/css/auxiliary/unicode_range_interval.rs new file mode 100644 index 000000000000..d01abb8cde0d --- /dev/null +++ b/crates/biome_css_formatter/src/css/auxiliary/unicode_range_interval.rs @@ -0,0 +1,16 @@ +use crate::prelude::*; +use biome_css_syntax::{CssUnicodeRangeInterval, CssUnicodeRangeIntervalFields}; +use biome_formatter::write; + +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatCssUnicodeRangeInterval; +impl FormatNodeRule for FormatCssUnicodeRangeInterval { + fn fmt_fields(&self, node: &CssUnicodeRangeInterval, f: &mut CssFormatter) -> FormatResult<()> { + let CssUnicodeRangeIntervalFields { + start, + minus_token, + end, + } = node.as_fields(); + write!(f, [start.format(), minus_token.format(), end.format()]) + } +} diff --git a/crates/biome_css_formatter/src/css/auxiliary/unicode_range_wildcard.rs b/crates/biome_css_formatter/src/css/auxiliary/unicode_range_wildcard.rs new file mode 100644 index 000000000000..974e4144a6cc --- /dev/null +++ b/crates/biome_css_formatter/src/css/auxiliary/unicode_range_wildcard.rs @@ -0,0 +1,15 @@ +use crate::prelude::*; +use biome_css_syntax::{CssUnicodeRangeWildcard, CssUnicodeRangeWildcardFields}; +use biome_formatter::write; + +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatCssUnicodeRangeWildcard; +impl FormatNodeRule for FormatCssUnicodeRangeWildcard { + fn fmt_fields(&self, node: &CssUnicodeRangeWildcard, f: &mut CssFormatter) -> FormatResult<()> { + let CssUnicodeRangeWildcardFields { + value_token + } = node.as_fields(); + + write!(f, [value_token.format(),]) + } +} diff --git a/crates/biome_css_formatter/src/css/bogus/bogus_unicode_range_value.rs b/crates/biome_css_formatter/src/css/bogus/bogus_unicode_range_value.rs new file mode 100644 index 000000000000..46a9dff98fda --- /dev/null +++ b/crates/biome_css_formatter/src/css/bogus/bogus_unicode_range_value.rs @@ -0,0 +1,5 @@ +use crate::FormatBogusNodeRule; +use biome_css_syntax::CssBogusUnicodeRangeValue; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatCssBogusUnicodeRangeValue; +impl FormatBogusNodeRule for FormatCssBogusUnicodeRangeValue {} diff --git a/crates/biome_css_formatter/src/css/bogus/mod.rs b/crates/biome_css_formatter/src/css/bogus/mod.rs index cea2ccd9355f..b0a20e6421cc 100644 --- a/crates/biome_css_formatter/src/css/bogus/mod.rs +++ b/crates/biome_css_formatter/src/css/bogus/mod.rs @@ -23,6 +23,7 @@ pub(crate) mod bogus_rule; pub(crate) mod bogus_scope_range; pub(crate) mod bogus_selector; pub(crate) mod bogus_sub_selector; +pub(crate) mod bogus_unicode_range_value; pub(crate) mod bogus_url_modifier; pub(crate) mod unknown_at_rule_component_list; pub(crate) mod value_at_rule_generic_value; diff --git a/crates/biome_css_formatter/src/generated.rs b/crates/biome_css_formatter/src/generated.rs index 93acca60128c..7fb67bd98113 100644 --- a/crates/biome_css_formatter/src/generated.rs +++ b/crates/biome_css_formatter/src/generated.rs @@ -4944,6 +4944,166 @@ impl IntoFormat for biome_css_syntax::CssTypeSelector { ) } } +impl FormatRule + for crate::css::auxiliary::unicode_codepoint::FormatCssUnicodeCodepoint +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::CssUnicodeCodepoint, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::CssUnicodeCodepoint { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::CssUnicodeCodepoint, + crate::css::auxiliary::unicode_codepoint::FormatCssUnicodeCodepoint, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::auxiliary::unicode_codepoint::FormatCssUnicodeCodepoint::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::CssUnicodeCodepoint { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssUnicodeCodepoint, + crate::css::auxiliary::unicode_codepoint::FormatCssUnicodeCodepoint, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::auxiliary::unicode_codepoint::FormatCssUnicodeCodepoint::default(), + ) + } +} +impl FormatRule + for crate::css::auxiliary::unicode_range::FormatCssUnicodeRange +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::CssUnicodeRange, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::CssUnicodeRange { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::CssUnicodeRange, + crate::css::auxiliary::unicode_range::FormatCssUnicodeRange, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::auxiliary::unicode_range::FormatCssUnicodeRange::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::CssUnicodeRange { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssUnicodeRange, + crate::css::auxiliary::unicode_range::FormatCssUnicodeRange, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::auxiliary::unicode_range::FormatCssUnicodeRange::default(), + ) + } +} +impl FormatRule + for crate::css::auxiliary::unicode_range_interval::FormatCssUnicodeRangeInterval +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::CssUnicodeRangeInterval, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::CssUnicodeRangeInterval { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::CssUnicodeRangeInterval, + crate::css::auxiliary::unicode_range_interval::FormatCssUnicodeRangeInterval, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::auxiliary::unicode_range_interval::FormatCssUnicodeRangeInterval::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::CssUnicodeRangeInterval { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssUnicodeRangeInterval, + crate::css::auxiliary::unicode_range_interval::FormatCssUnicodeRangeInterval, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::auxiliary::unicode_range_interval::FormatCssUnicodeRangeInterval::default(), + ) + } +} +impl FormatRule + for crate::css::auxiliary::unicode_range_wildcard::FormatCssUnicodeRangeWildcard +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::CssUnicodeRangeWildcard, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::CssUnicodeRangeWildcard { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::CssUnicodeRangeWildcard, + crate::css::auxiliary::unicode_range_wildcard::FormatCssUnicodeRangeWildcard, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::auxiliary::unicode_range_wildcard::FormatCssUnicodeRangeWildcard::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::CssUnicodeRangeWildcard { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssUnicodeRangeWildcard, + crate::css::auxiliary::unicode_range_wildcard::FormatCssUnicodeRangeWildcard, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::auxiliary::unicode_range_wildcard::FormatCssUnicodeRangeWildcard::default(), + ) + } +} impl FormatRule for crate::css::auxiliary::universal_namespace_prefix::FormatCssUniversalNamespacePrefix { @@ -6985,6 +7145,48 @@ impl IntoFormat for biome_css_syntax::CssBogusSubSelector { ) } } +impl FormatRule + for crate::css::bogus::bogus_unicode_range_value::FormatCssBogusUnicodeRangeValue +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::CssBogusUnicodeRangeValue, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatBogusNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::CssBogusUnicodeRangeValue { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::CssBogusUnicodeRangeValue, + crate::css::bogus::bogus_unicode_range_value::FormatCssBogusUnicodeRangeValue, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::bogus::bogus_unicode_range_value::FormatCssBogusUnicodeRangeValue::default( + ), + ) + } +} +impl IntoFormat for biome_css_syntax::CssBogusUnicodeRangeValue { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssBogusUnicodeRangeValue, + crate::css::bogus::bogus_unicode_range_value::FormatCssBogusUnicodeRangeValue, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::bogus::bogus_unicode_range_value::FormatCssBogusUnicodeRangeValue::default( + ), + ) + } +} impl FormatRule for crate::css::bogus::bogus_url_modifier::FormatCssBogusUrlModifier { @@ -8918,6 +9120,33 @@ impl IntoFormat for biome_css_syntax::AnyCssSupportsOrCombinab FormatOwnedWithRule :: new (self , crate :: css :: any :: supports_or_combinable_condition :: FormatAnyCssSupportsOrCombinableCondition :: default ()) } } +impl AsFormat for biome_css_syntax::AnyCssUnicodeValue { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::AnyCssUnicodeValue, + crate::css::any::unicode_value::FormatAnyCssUnicodeValue, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::any::unicode_value::FormatAnyCssUnicodeValue::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::AnyCssUnicodeValue { + type Format = FormatOwnedWithRule< + biome_css_syntax::AnyCssUnicodeValue, + crate::css::any::unicode_value::FormatAnyCssUnicodeValue, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::any::unicode_value::FormatAnyCssUnicodeValue::default(), + ) + } +} impl AsFormat for biome_css_syntax::AnyCssUrlModifier { type Format<'a> = FormatRefWithRule< 'a, diff --git a/crates/biome_css_formatter/tests/specs/css/properties/unicode_range.css b/crates/biome_css_formatter/tests/specs/css/properties/unicode_range.css new file mode 100644 index 000000000000..a089330f2486 --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/properties/unicode_range.css @@ -0,0 +1,59 @@ +@font-face { + unicode-range: + U+000-49F, + U+2000-27FF, U+2900-2BFF, + U+1D400-1D7FF, + U+ff??; +} + +@font-face { + unicode-range: U+0000-00FF, + U+0131, U+0152-0153, U+02C6, + U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC + , U+2212, U+2215; +} + +@font-face { + unicode-range: + U+0400-045F, + + U+0490-0491, + U+04B0-04B1, + + U+2116; +} + +@font-face { + font-family: 'Ampersand'; + src: local('Times New Roman'); + unicode-range: + U+26; /* single codepoint */ + unicode-range: u+26; + unicode-range: + U+0-7F; + unicode-range: + U+0025-00FF; /* codepoint range */ + unicode-range: U+4??; /* wildcard range */ + unicode-range: + + U+0025-00FF, U+4??; /* multiple values */ + unicode-range: + + U+A5, U+4E00-9FFF, + U+30??, U+FF00-FF9F; /* multiple values */ + unicode-range: + U+????; + unicode-range: + U+??????; + unicode-range: U+12; + unicode-range: + U+12e112; + unicode-range: + U+1e1ee1; + unicode-range: + U+1e1ee1-FFFFFF; + unicode-range: + + U+1e1ee?; + unicode-range: U+12-13; +} diff --git a/crates/biome_css_formatter/tests/specs/css/properties/unicode_range.css.snap b/crates/biome_css_formatter/tests/specs/css/properties/unicode_range.css.snap new file mode 100644 index 000000000000..6168574594f3 --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/properties/unicode_range.css.snap @@ -0,0 +1,118 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: css/properties/unicode_range.css +--- +# Input + +```css +@font-face { + unicode-range: + U+000-49F, + U+2000-27FF, U+2900-2BFF, + U+1D400-1D7FF, + U+ff??; +} + +@font-face { + unicode-range: U+0000-00FF, + U+0131, U+0152-0153, U+02C6, + U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC + , U+2212, U+2215; +} + +@font-face { + unicode-range: + U+0400-045F, + + U+0490-0491, + U+04B0-04B1, + + U+2116; +} + +@font-face { + font-family: 'Ampersand'; + src: local('Times New Roman'); + unicode-range: + U+26; /* single codepoint */ + unicode-range: u+26; + unicode-range: + U+0-7F; + unicode-range: + U+0025-00FF; /* codepoint range */ + unicode-range: U+4??; /* wildcard range */ + unicode-range: + + U+0025-00FF, U+4??; /* multiple values */ + unicode-range: + + U+A5, U+4E00-9FFF, + U+30??, U+FF00-FF9F; /* multiple values */ + unicode-range: + U+????; + unicode-range: + U+??????; + unicode-range: U+12; + unicode-range: + U+12e112; + unicode-range: + U+1e1ee1; + unicode-range: + U+1e1ee1-FFFFFF; + unicode-range: + + U+1e1ee?; + unicode-range: U+12-13; +} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Quote style: Double Quotes +----- + +```css +@font-face { + unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF, U+ff??; +} + +@font-face { + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, + U+2000-206F, U+2074, U+20AC, U+2212, U+2215; +} + +@font-face { + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +@font-face { + font-family: "Ampersand"; + src: local("Times New Roman"); + unicode-range: U+26; /* single codepoint */ + unicode-range: u+26; + unicode-range: U+0-7F; + unicode-range: U+0025-00FF; /* codepoint range */ + unicode-range: U+4??; /* wildcard range */ + unicode-range: U+0025-00FF, U+4??; /* multiple values */ + unicode-range: U+A5, U+4E00-9FFF, U+30??, U+FF00-FF9F; /* multiple values */ + unicode-range: U+????; + unicode-range: U+??????; + unicode-range: U+12; + unicode-range: U+12e112; + unicode-range: U+1e1ee1; + unicode-range: U+1e1ee1-FFFFFF; + unicode-range: U+1e1ee?; + unicode-range: U+12-13; +} +``` diff --git a/crates/biome_css_formatter/tests/specs/prettier/css/parens/parens.css.snap b/crates/biome_css_formatter/tests/specs/prettier/css/parens/parens.css.snap index c4dcfa331042..3d76c3f74f2e 100644 --- a/crates/biome_css_formatter/tests/specs/prettier/css/parens/parens.css.snap +++ b/crates/biome_css_formatter/tests/specs/prettier/css/parens/parens.css.snap @@ -347,19 +347,6 @@ a { prop77: calc(-5px); prop78: calc(+5px); prop79: calc(-100px + 100px); -@@ -152,9 +152,9 @@ - - .unicode-ranges { - /* values */ -- unicode-range: U+26; /* single codepoint */ -- unicode-range: U+0-7F; -- unicode-range: U+0025-00FF; /* codepoint range */ -+ unicode-range: u +26; /* single codepoint */ -+ unicode-range: u +0 -7f; -+ unicode-range: u +0025 -00ff; /* codepoint range */ - unicode-range: U+4??; /* wildcard range */ - unicode-range: U+0025-00FF, U+4??; /* multiple values */ - } ``` # Output @@ -519,9 +506,9 @@ a { .unicode-ranges { /* values */ - unicode-range: u +26; /* single codepoint */ - unicode-range: u +0 -7f; - unicode-range: u +0025 -00ff; /* codepoint range */ + unicode-range: U+26; /* single codepoint */ + unicode-range: U+0-7F; + unicode-range: U+0025-00FF; /* codepoint range */ unicode-range: U+4??; /* wildcard range */ unicode-range: U+0025-00FF, U+4??; /* multiple values */ } @@ -1244,50 +1231,6 @@ parens.css:241:19 parse ━━━━━━━━━━━━━━━━━━ - custom property - function -parens.css:251:23 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × unexpected character `?` - - 249 │ unicode-range: U+0-7F; - 250 │ unicode-range: U+0025-00FF; /* codepoint range */ - > 251 │ unicode-range: U+4??; /* wildcard range */ - │ ^ - 252 │ unicode-range: U+0025-00FF, U+4??; /* multiple values */ - 253 │ } - -parens.css:251:24 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × unexpected character `?` - - 249 │ unicode-range: U+0-7F; - 250 │ unicode-range: U+0025-00FF; /* codepoint range */ - > 251 │ unicode-range: U+4??; /* wildcard range */ - │ ^ - 252 │ unicode-range: U+0025-00FF, U+4??; /* multiple values */ - 253 │ } - -parens.css:252:36 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × unexpected character `?` - - 250 │ unicode-range: U+0025-00FF; /* codepoint range */ - 251 │ unicode-range: U+4??; /* wildcard range */ - > 252 │ unicode-range: U+0025-00FF, U+4??; /* multiple values */ - │ ^ - 253 │ } - 254 │ - -parens.css:252:37 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × unexpected character `?` - - 250 │ unicode-range: U+0025-00FF; /* codepoint range */ - 251 │ unicode-range: U+4??; /* wildcard range */ - > 252 │ unicode-range: U+0025-00FF, U+4??; /* multiple values */ - │ ^ - 253 │ } - 254 │ - ``` diff --git a/crates/biome_css_parser/src/lexer/mod.rs b/crates/biome_css_parser/src/lexer/mod.rs index 7d4e06f918ee..f17c5c3e15ce 100644 --- a/crates/biome_css_parser/src/lexer/mod.rs +++ b/crates/biome_css_parser/src/lexer/mod.rs @@ -36,6 +36,12 @@ pub enum CssLexContext { /// support #000 #000f #ffffff #ffffffff /// https://drafts.csswg.org/css-color/#typedef-hex-color Color, + + /// Applied when lexing CSS unicode range. + /// Starting from U+ or u+ + /// support U+0-9A-F? U+0-9A-F{1,6} U+0-9A-F{1,6}? + /// https://drafts.csswg.org/css-fonts/#unicode-range-desc + UnicodeRange, } impl LexContext for CssLexContext { @@ -47,7 +53,12 @@ impl LexContext for CssLexContext { /// Context in which the [CssLexContext]'s current should be re-lexed. #[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum CssReLexContext {} +pub enum CssReLexContext { + #[allow(dead_code)] + Regular, + /// See [CssLexContext::UnicodeRange] + UnicodeRange, +} /// An extremely fast, lookup table based, lossless CSS lexer #[derive(Debug)] @@ -119,6 +130,7 @@ impl<'src> Lexer<'src> for CssLexer<'src> { CssLexContext::PseudoNthSelector => self.consume_pseudo_nth_selector_token(current), CssLexContext::UrlRawValue => self.consume_url_raw_value_token(current), CssLexContext::Color => self.consume_color_token(current), + CssLexContext::UnicodeRange => self.consume_unicode_range_token(current), }, None => EOF, }; @@ -375,6 +387,62 @@ impl<'src> CssLexer<'src> { CSS_COLOR_LITERAL } + /// Consumes a Unicode range token and returns its corresponding syntax kind. + fn consume_unicode_range_token(&mut self, current: u8) -> CssSyntaxKind { + match current { + b'u' | b'U' if matches!(self.peek_byte(), Some(b'+')) => { + self.advance(1); + self.consume_byte(T![U+]) + } + b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F' | b'?' => self.consume_unicode_range(), + b'-' => self.consume_byte(T![-]), + _ => self.consume_token(current), + } + } + + /// Consumes a Unicode range and determines its syntax kind. + /// + /// This method reads consecutive bytes representing valid hexadecimal characters ('0'-'9', 'a'-'f', + /// 'A'-'F') or the wildcard character '?'. + /// It tracks the length of the range and detects if it contains a wildcard. + /// If the length is invalid (either zero or greater than six), it generates + /// a `ParseDiagnostic` indicating an invalid Unicode range. + fn consume_unicode_range(&mut self) -> CssSyntaxKind { + let start = self.text_position(); + let mut length = 0; + let mut is_wildcard = false; + + while matches!( + self.current_byte(), + Some(b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F' | b'?') + ) { + // If the current byte is a wildcard character, set the wildcard flag to true. + if self.current_byte() == Some(b'?') { + is_wildcard = true; + } + + self.advance(1); + length += 1; + } + + if length == 0 || length > 6 { + let diagnostic = ParseDiagnostic::new( + "Invalid unicode range", + start..self.text_position(), + ) + .with_hint( + "Valid length (minimum 1 or maximum 6 hex digits) in the start of unicode range.", + ); + self.diagnostics.push(diagnostic); + } + + if is_wildcard { + CSS_UNICODE_RANGE_WILDCARD_LITERAL + } else { + CSS_UNICODE_CODEPOINT_LITERAL + } + } + fn consume_selector_token(&mut self, current: u8) -> CssSyntaxKind { let dispatched = lookup_byte(current); @@ -383,6 +451,7 @@ impl<'src> CssLexer<'src> { _ => self.consume_token(current), } } + fn consume_url_raw_value_token(&mut self, current: u8) -> CssSyntaxKind { if let Some(chr) = self.current_byte() { let dispatch = lookup_byte(chr); @@ -395,6 +464,7 @@ impl<'src> CssLexer<'src> { } self.consume_token(current) } + fn consume_url_raw_value(&mut self) -> CssSyntaxKind { let start = self.text_position(); while let Some(chr) = self.current_byte() { @@ -1248,12 +1318,15 @@ impl<'src> CssLexer<'src> { } impl<'src> ReLexer<'src> for CssLexer<'src> { - fn re_lex(&mut self, _context: Self::ReLexContext) -> Self::Kind { + fn re_lex(&mut self, context: Self::ReLexContext) -> Self::Kind { let old_position = self.position; self.position = u32::from(self.current_start) as usize; let re_lexed_kind = match self.current_byte() { - Some(current) => self.consume_selector_token(current), + Some(current) => match context { + CssReLexContext::Regular => self.consume_token(current), + CssReLexContext::UnicodeRange => self.consume_unicode_range_token(current), + }, None => EOF, }; diff --git a/crates/biome_css_parser/src/parser.rs b/crates/biome_css_parser/src/parser.rs index de43e8824cd1..8ac7c9d81e61 100644 --- a/crates/biome_css_parser/src/parser.rs +++ b/crates/biome_css_parser/src/parser.rs @@ -66,7 +66,6 @@ impl<'source> CssParser<'source> { /// Re-lexes the current token in the specified context. Returns the kind /// of the re-lexed token (can be the same as before if the context doesn't make a difference for the current token) - #[allow(dead_code)] //TODO remote this once we actually don't use it pub fn re_lex(&mut self, context: CssReLexContext) -> CssSyntaxKind { self.source_mut().re_lex(context) } diff --git a/crates/biome_css_parser/src/syntax/mod.rs b/crates/biome_css_parser/src/syntax/mod.rs index ff6ae39dfe5d..31950ed63b53 100644 --- a/crates/biome_css_parser/src/syntax/mod.rs +++ b/crates/biome_css_parser/src/syntax/mod.rs @@ -11,6 +11,7 @@ use crate::parser::CssParser; use crate::syntax::at_rule::{is_at_at_rule, parse_at_rule}; use crate::syntax::block::parse_declaration_or_rule_list_block; use crate::syntax::parse_error::{expected_any_rule, expected_non_css_wide_keyword_identifier}; +use crate::syntax::property::unicode_range::{is_at_unicode_range, parse_unicode_range}; use crate::syntax::property::{is_at_any_property, parse_any_property}; use crate::syntax::selector::is_nth_at_selector; use crate::syntax::selector::relative_selector::{is_at_relative_selector, RelativeSelectorList}; @@ -265,6 +266,8 @@ pub(crate) fn parse_any_value(p: &mut CssParser) -> ParsedSyntax { parse_any_function(p) } else if is_at_dashed_identifier(p) { parse_dashed_identifier(p) + } else if is_at_unicode_range(p) { + parse_unicode_range(p) } else if is_at_identifier(p) { parse_regular_identifier(p) } else if p.at(CSS_STRING_LITERAL) { diff --git a/crates/biome_css_parser/src/syntax/property/mod.rs b/crates/biome_css_parser/src/syntax/property/mod.rs index 4999d8824393..b94377bd4399 100644 --- a/crates/biome_css_parser/src/syntax/property/mod.rs +++ b/crates/biome_css_parser/src/syntax/property/mod.rs @@ -1,3 +1,5 @@ +pub(crate) mod unicode_range; + use crate::lexer::CssLexContext; use crate::parser::CssParser; use crate::syntax::css_modules::{ diff --git a/crates/biome_css_parser/src/syntax/property/unicode_range.rs b/crates/biome_css_parser/src/syntax/property/unicode_range.rs new file mode 100644 index 000000000000..c822725bc97a --- /dev/null +++ b/crates/biome_css_parser/src/syntax/property/unicode_range.rs @@ -0,0 +1,158 @@ +use crate::lexer::{CssLexContext, CssReLexContext}; +use crate::parser::CssParser; +use biome_css_syntax::CssSyntaxKind::{ + CSS_BOGUS_UNICODE_RANGE_VALUE, 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::Parser; + +/// Checks if the parser is positioned to potentially start parsing a Unicode range in CSS, identified by "U" or "u". +pub(crate) fn is_at_unicode_range(p: &mut CssParser) -> bool { + matches!(p.cur_text(), "U" | "u") +} + +/// Parses a Unicode range in CSS starting from "U+" or "u+". +/// +/// This function first performs lexical analysis to confirm the presence of a Unicode range indicator +/// before proceeding to parse the range or fallback to a bogus value. +/// +/// Specification: +/// - [Unicode Range Descriptor](https://drafts.csswg.org/css-fonts/#unicode-range-desc) +/// - [CSS Syntax Level 3](https://www.w3.org/TR/css-syntax-3/#typedef-urange) +/// +/// # Examples +/// +/// Basic usage in CSS: +/// +/// ```css +/// .class { +/// unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF, U+ff??; +/// } +/// ``` +/// +/// This function is integral in parsing and interpreting Unicode range descriptors in CSS. +pub(crate) fn parse_unicode_range(p: &mut CssParser) -> ParsedSyntax { + if !is_at_unicode_range(p) { + return Absent; + } + + // Perform lexical analysis to confirm the presence of a Unicode range indicator. + // This is necessary to ensure that the parser is positioned to parse a Unicode range. + let kind = p.re_lex(CssReLexContext::UnicodeRange); + + // If the parser is not positioned to parse a Unicode range, return an absent value. + if kind != T![U+] { + return Absent; + } + + let m = p.start(); + + p.bump_with_context(T![U+], CssLexContext::UnicodeRange); + + // Checks if the parser is positioned to parse a Unicode range wildcard. + // A wildcard cannot be combined with a range interval. For example, `U+????-U+????` is invalid. + 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)); + }; + + // Checks if the parser is positioned to parse a Unicode range interval. + // A range interval is identified by a hyphen (`-`) followed by another Unicode codepoint. + if p.at(T![-]) { + let range = codepoint.precede(p); + p.bump_with_context(T![-], CssLexContext::UnicodeRange); + + if parse_unicode_codepoint(p) + .or_add_diagnostic(p, expected_codepoint) + .is_none() + { + // If the parser is not positioned to parse a Unicode codepoint, abandon the range and return a bogus value. + 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)) +} + +/// Checks if the parser is positioned at a Unicode codepoint. +fn is_at_unicode_codepoint(p: &mut CssParser) -> bool { + p.at(CSS_UNICODE_CODEPOINT_LITERAL) +} + +/// Parses a Unicode codepoint from the current position in the CSS parser. +/// # Examples +/// +/// Basic usage in CSS: +/// +/// ```css +/// .class { +/// unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF; +/// } +/// ``` +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)) +} + +/// Checks if the parser is positioned at a Unicode range wildcard in CSS. +fn is_at_unicode_range_wildcard(p: &mut CssParser) -> bool { + p.at(CSS_UNICODE_RANGE_WILDCARD_LITERAL) +} + +/// Parses a Unicode range wildcard from the current position in the CSS parser. +/// # Examples +/// +/// Basic usage in CSS: +/// +/// ```css +/// .class { +/// unicode-range: U+ff??; +/// } +/// ``` +/// +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)) +} + +/// Generates a parse diagnostic for an expected "codepoint" error message at the given range. +pub(crate) fn expected_codepoint(p: &CssParser, range: TextRange) -> ParseDiagnostic { + expected_node("codepoint", range, p) +} + +/// Generates a parse diagnostic for an expected "codepoint or wildcard" error message at the given range. +pub(crate) fn expected_codepoint_value(p: &CssParser, range: TextRange) -> ParseDiagnostic { + expected_any(&["codepoint", "codepoint range wildcard"], range, p) +} diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/property/unicode_range.css b/crates/biome_css_parser/tests/css_test_suite/ok/property/unicode_range.css new file mode 100644 index 000000000000..dfa6d0ac8c11 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/property/unicode_range.css @@ -0,0 +1,31 @@ +@font-face { + unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF, U+ff??; +} + +@font-face { + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; +} + +@font-face { + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +@font-face { + font-family: 'Ampersand'; + src: local('Times New Roman'); + unicode-range: U+26; /* single codepoint */ + unicode-range: u+26; + unicode-range: U+0-7F; + unicode-range: U+0025-00FF; /* codepoint range */ + unicode-range: U+4??; /* wildcard range */ + unicode-range: U+0025-00FF, U+4??; /* multiple values */ + unicode-range: U+A5, U+4E00-9FFF, U+30??, U+FF00-FF9F; /* multiple values */ + unicode-range: U+????; + unicode-range: U+??????; + unicode-range: U+12; + unicode-range: U+12e112; + unicode-range: U+1e1ee1; + unicode-range: U+1e1ee1-FFFFFF; + unicode-range: U+1e1ee?; + unicode-range: U+12-13; +} diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/property/unicode_range.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/property/unicode_range.css.snap new file mode 100644 index 000000000000..9f392bef627d --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/property/unicode_range.css.snap @@ -0,0 +1,1301 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```css +@font-face { + unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF, U+ff??; +} + +@font-face { + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; +} + +@font-face { + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +@font-face { + font-family: 'Ampersand'; + src: local('Times New Roman'); + unicode-range: U+26; /* single codepoint */ + unicode-range: u+26; + unicode-range: U+0-7F; + unicode-range: U+0025-00FF; /* codepoint range */ + unicode-range: U+4??; /* wildcard range */ + unicode-range: U+0025-00FF, U+4??; /* multiple values */ + unicode-range: U+A5, U+4E00-9FFF, U+30??, U+FF00-FF9F; /* multiple values */ + unicode-range: U+????; + unicode-range: U+??????; + unicode-range: U+12; + unicode-range: U+12e112; + unicode-range: U+1e1ee1; + unicode-range: U+1e1ee1-FFFFFF; + unicode-range: U+1e1ee?; + unicode-range: U+12-13; +} + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssFontFaceAtRule { + font_face_token: FONT_FACE_KW@1..11 "font-face" [] [Whitespace(" ")], + block: CssDeclarationBlock { + l_curly_token: L_CURLY@11..12 "{" [] [], + declarations: CssDeclarationList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@12..27 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@27..29 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@29..31 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@31..34 "000" [] [], + }, + minus_token: MINUS@34..35 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@35..38 "49F" [] [], + }, + }, + }, + CssGenericDelimiter { + value: COMMA@38..40 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@40..42 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@42..46 "2000" [] [], + }, + minus_token: MINUS@46..47 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@47..51 "27FF" [] [], + }, + }, + }, + CssGenericDelimiter { + value: COMMA@51..53 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@53..55 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@55..59 "2900" [] [], + }, + minus_token: MINUS@59..60 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@60..64 "2BFF" [] [], + }, + }, + }, + CssGenericDelimiter { + value: COMMA@64..66 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@66..68 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@68..73 "1D400" [] [], + }, + minus_token: MINUS@73..74 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@74..79 "1D7FF" [] [], + }, + }, + }, + CssGenericDelimiter { + value: COMMA@79..81 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@81..83 "U+" [] [], + value: CssUnicodeRangeWildcard { + value_token: CSS_UNICODE_RANGE_WILDCARD_LITERAL@83..87 "ff??" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@87..88 ";" [] [], + }, + ], + r_curly_token: R_CURLY@88..90 "}" [Newline("\n")] [], + }, + }, + }, + CssAtRule { + at_token: AT@90..93 "@" [Newline("\n"), Newline("\n")] [], + rule: CssFontFaceAtRule { + font_face_token: FONT_FACE_KW@93..103 "font-face" [] [Whitespace(" ")], + block: CssDeclarationBlock { + l_curly_token: L_CURLY@103..104 "{" [] [], + declarations: CssDeclarationList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@104..119 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@119..121 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@121..123 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@123..127 "0000" [] [], + }, + minus_token: MINUS@127..128 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@128..132 "00FF" [] [], + }, + }, + }, + CssGenericDelimiter { + value: COMMA@132..134 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@134..136 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@136..140 "0131" [] [], + }, + }, + CssGenericDelimiter { + value: COMMA@140..142 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@142..144 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@144..148 "0152" [] [], + }, + minus_token: MINUS@148..149 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@149..153 "0153" [] [], + }, + }, + }, + CssGenericDelimiter { + value: COMMA@153..155 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@155..157 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@157..161 "02C6" [] [], + }, + }, + CssGenericDelimiter { + value: COMMA@161..163 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@163..165 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@165..169 "02DA" [] [], + }, + }, + CssGenericDelimiter { + value: COMMA@169..171 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@171..173 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@173..177 "02DC" [] [], + }, + }, + CssGenericDelimiter { + value: COMMA@177..179 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@179..181 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@181..185 "2000" [] [], + }, + minus_token: MINUS@185..186 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@186..190 "206F" [] [], + }, + }, + }, + CssGenericDelimiter { + value: COMMA@190..192 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@192..194 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@194..198 "2074" [] [], + }, + }, + CssGenericDelimiter { + value: COMMA@198..200 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@200..202 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@202..206 "20AC" [] [], + }, + }, + CssGenericDelimiter { + value: COMMA@206..208 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@208..210 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@210..214 "2212" [] [], + }, + }, + CssGenericDelimiter { + value: COMMA@214..216 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@216..218 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@218..222 "2215" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@222..223 ";" [] [], + }, + ], + r_curly_token: R_CURLY@223..225 "}" [Newline("\n")] [], + }, + }, + }, + CssAtRule { + at_token: AT@225..228 "@" [Newline("\n"), Newline("\n")] [], + rule: CssFontFaceAtRule { + font_face_token: FONT_FACE_KW@228..238 "font-face" [] [Whitespace(" ")], + block: CssDeclarationBlock { + l_curly_token: L_CURLY@238..239 "{" [] [], + declarations: CssDeclarationList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@239..254 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@254..256 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@256..258 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@258..262 "0400" [] [], + }, + minus_token: MINUS@262..263 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@263..267 "045F" [] [], + }, + }, + }, + CssGenericDelimiter { + value: COMMA@267..269 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@269..271 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@271..275 "0490" [] [], + }, + minus_token: MINUS@275..276 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@276..280 "0491" [] [], + }, + }, + }, + CssGenericDelimiter { + value: COMMA@280..282 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@282..284 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@284..288 "04B0" [] [], + }, + minus_token: MINUS@288..289 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@289..293 "04B1" [] [], + }, + }, + }, + CssGenericDelimiter { + value: COMMA@293..295 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@295..297 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@297..301 "2116" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@301..302 ";" [] [], + }, + ], + r_curly_token: R_CURLY@302..304 "}" [Newline("\n")] [], + }, + }, + }, + CssAtRule { + at_token: AT@304..307 "@" [Newline("\n"), Newline("\n")] [], + rule: CssFontFaceAtRule { + font_face_token: FONT_FACE_KW@307..317 "font-face" [] [Whitespace(" ")], + block: CssDeclarationBlock { + l_curly_token: L_CURLY@317..318 "{" [] [], + declarations: CssDeclarationList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@318..331 "font-family" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@331..333 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssString { + value_token: CSS_STRING_LITERAL@333..344 "'Ampersand'" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@344..345 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@345..350 "src" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@350..352 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssFunction { + name: CssIdentifier { + value_token: IDENT@352..357 "local" [] [], + }, + l_paren_token: L_PAREN@357..358 "(" [] [], + items: CssParameterList [ + CssParameter { + any_css_expression: CssListOfComponentValuesExpression { + css_component_value_list: CssComponentValueList [ + CssString { + value_token: CSS_STRING_LITERAL@358..375 "'Times New Roman'" [] [], + }, + ], + }, + }, + ], + r_paren_token: R_PAREN@375..376 ")" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@376..377 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@377..392 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@392..394 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@394..396 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@396..398 "26" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@398..436 ";" [] [Whitespace(" "), Comments("/* single codepoint */")], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@436..451 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@451..453 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@453..455 "u+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@455..457 "26" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@457..458 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@458..473 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@473..475 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@475..477 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@477..478 "0" [] [], + }, + minus_token: MINUS@478..479 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@479..481 "7F" [] [], + }, + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@481..482 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@482..497 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@497..499 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@499..501 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@501..505 "0025" [] [], + }, + minus_token: MINUS@505..506 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@506..510 "00FF" [] [], + }, + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@510..540 ";" [] [Whitespace(" "), Comments("/* codepoint range */")], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@540..555 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@555..557 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@557..559 "U+" [] [], + value: CssUnicodeRangeWildcard { + value_token: CSS_UNICODE_RANGE_WILDCARD_LITERAL@559..562 "4??" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@562..597 ";" [] [Whitespace(" "), Comments("/* wildcard range */")], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@597..612 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@612..614 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@614..616 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@616..620 "0025" [] [], + }, + minus_token: MINUS@620..621 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@621..625 "00FF" [] [], + }, + }, + }, + CssGenericDelimiter { + value: COMMA@625..627 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@627..629 "U+" [] [], + value: CssUnicodeRangeWildcard { + value_token: CSS_UNICODE_RANGE_WILDCARD_LITERAL@629..632 "4??" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@632..655 ";" [] [Whitespace(" "), Comments("/* multiple values */")], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@655..670 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@670..672 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@672..674 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@674..676 "A5" [] [], + }, + }, + CssGenericDelimiter { + value: COMMA@676..678 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@678..680 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@680..684 "4E00" [] [], + }, + minus_token: MINUS@684..685 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@685..689 "9FFF" [] [], + }, + }, + }, + CssGenericDelimiter { + value: COMMA@689..691 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@691..693 "U+" [] [], + value: CssUnicodeRangeWildcard { + value_token: CSS_UNICODE_RANGE_WILDCARD_LITERAL@693..697 "30??" [] [], + }, + }, + CssGenericDelimiter { + value: COMMA@697..699 "," [] [Whitespace(" ")], + }, + CssUnicodeRange { + prefix_token: UNICODE@699..701 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@701..705 "FF00" [] [], + }, + minus_token: MINUS@705..706 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@706..710 "FF9F" [] [], + }, + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@710..733 ";" [] [Whitespace(" "), Comments("/* multiple values */")], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@733..748 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@748..750 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@750..752 "U+" [] [], + value: CssUnicodeRangeWildcard { + value_token: CSS_UNICODE_RANGE_WILDCARD_LITERAL@752..756 "????" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@756..757 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@757..772 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@772..774 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@774..776 "U+" [] [], + value: CssUnicodeRangeWildcard { + value_token: CSS_UNICODE_RANGE_WILDCARD_LITERAL@776..782 "??????" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@782..783 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@783..798 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@798..800 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@800..802 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@802..804 "12" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@804..805 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@805..820 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@820..822 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@822..824 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@824..830 "12e112" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@830..831 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@831..846 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@846..848 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@848..850 "U+" [] [], + value: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@850..856 "1e1ee1" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@856..857 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@857..872 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@872..874 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@874..876 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@876..882 "1e1ee1" [] [], + }, + minus_token: MINUS@882..883 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@883..889 "FFFFFF" [] [], + }, + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@889..890 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@890..905 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@905..907 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@907..909 "U+" [] [], + value: CssUnicodeRangeWildcard { + value_token: CSS_UNICODE_RANGE_WILDCARD_LITERAL@909..915 "1e1ee?" [] [], + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@915..916 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@916..931 "unicode-range" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@931..933 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssUnicodeRange { + prefix_token: UNICODE@933..935 "U+" [] [], + value: CssUnicodeRangeInterval { + start: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@935..937 "12" [] [], + }, + minus_token: MINUS@937..938 "-" [] [], + end: CssUnicodeCodepoint { + value_token: CSS_UNICODE_CODEPOINT_LITERAL@938..940 "13" [] [], + }, + }, + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@940..941 ";" [] [], + }, + ], + r_curly_token: R_CURLY@941..943 "}" [Newline("\n")] [], + }, + }, + }, + ], + eof_token: EOF@943..944 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..944 + 0: (empty) + 1: CSS_RULE_LIST@0..943 + 0: CSS_AT_RULE@0..90 + 0: AT@0..1 "@" [] [] + 1: CSS_FONT_FACE_AT_RULE@1..90 + 0: FONT_FACE_KW@1..11 "font-face" [] [Whitespace(" ")] + 1: CSS_DECLARATION_BLOCK@11..90 + 0: L_CURLY@11..12 "{" [] [] + 1: CSS_DECLARATION_LIST@12..88 + 0: CSS_DECLARATION_WITH_SEMICOLON@12..88 + 0: CSS_DECLARATION@12..87 + 0: CSS_GENERIC_PROPERTY@12..87 + 0: CSS_IDENTIFIER@12..27 + 0: IDENT@12..27 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@27..29 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@29..87 + 0: CSS_UNICODE_RANGE@29..38 + 0: UNICODE@29..31 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@31..38 + 0: CSS_UNICODE_CODEPOINT@31..34 + 0: CSS_UNICODE_CODEPOINT_LITERAL@31..34 "000" [] [] + 1: MINUS@34..35 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@35..38 + 0: CSS_UNICODE_CODEPOINT_LITERAL@35..38 "49F" [] [] + 1: CSS_GENERIC_DELIMITER@38..40 + 0: COMMA@38..40 "," [] [Whitespace(" ")] + 2: CSS_UNICODE_RANGE@40..51 + 0: UNICODE@40..42 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@42..51 + 0: CSS_UNICODE_CODEPOINT@42..46 + 0: CSS_UNICODE_CODEPOINT_LITERAL@42..46 "2000" [] [] + 1: MINUS@46..47 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@47..51 + 0: CSS_UNICODE_CODEPOINT_LITERAL@47..51 "27FF" [] [] + 3: CSS_GENERIC_DELIMITER@51..53 + 0: COMMA@51..53 "," [] [Whitespace(" ")] + 4: CSS_UNICODE_RANGE@53..64 + 0: UNICODE@53..55 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@55..64 + 0: CSS_UNICODE_CODEPOINT@55..59 + 0: CSS_UNICODE_CODEPOINT_LITERAL@55..59 "2900" [] [] + 1: MINUS@59..60 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@60..64 + 0: CSS_UNICODE_CODEPOINT_LITERAL@60..64 "2BFF" [] [] + 5: CSS_GENERIC_DELIMITER@64..66 + 0: COMMA@64..66 "," [] [Whitespace(" ")] + 6: CSS_UNICODE_RANGE@66..79 + 0: UNICODE@66..68 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@68..79 + 0: CSS_UNICODE_CODEPOINT@68..73 + 0: CSS_UNICODE_CODEPOINT_LITERAL@68..73 "1D400" [] [] + 1: MINUS@73..74 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@74..79 + 0: CSS_UNICODE_CODEPOINT_LITERAL@74..79 "1D7FF" [] [] + 7: CSS_GENERIC_DELIMITER@79..81 + 0: COMMA@79..81 "," [] [Whitespace(" ")] + 8: CSS_UNICODE_RANGE@81..87 + 0: UNICODE@81..83 "U+" [] [] + 1: CSS_UNICODE_RANGE_WILDCARD@83..87 + 0: CSS_UNICODE_RANGE_WILDCARD_LITERAL@83..87 "ff??" [] [] + 1: (empty) + 1: SEMICOLON@87..88 ";" [] [] + 2: R_CURLY@88..90 "}" [Newline("\n")] [] + 1: CSS_AT_RULE@90..225 + 0: AT@90..93 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_FONT_FACE_AT_RULE@93..225 + 0: FONT_FACE_KW@93..103 "font-face" [] [Whitespace(" ")] + 1: CSS_DECLARATION_BLOCK@103..225 + 0: L_CURLY@103..104 "{" [] [] + 1: CSS_DECLARATION_LIST@104..223 + 0: CSS_DECLARATION_WITH_SEMICOLON@104..223 + 0: CSS_DECLARATION@104..222 + 0: CSS_GENERIC_PROPERTY@104..222 + 0: CSS_IDENTIFIER@104..119 + 0: IDENT@104..119 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@119..121 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@121..222 + 0: CSS_UNICODE_RANGE@121..132 + 0: UNICODE@121..123 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@123..132 + 0: CSS_UNICODE_CODEPOINT@123..127 + 0: CSS_UNICODE_CODEPOINT_LITERAL@123..127 "0000" [] [] + 1: MINUS@127..128 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@128..132 + 0: CSS_UNICODE_CODEPOINT_LITERAL@128..132 "00FF" [] [] + 1: CSS_GENERIC_DELIMITER@132..134 + 0: COMMA@132..134 "," [] [Whitespace(" ")] + 2: CSS_UNICODE_RANGE@134..140 + 0: UNICODE@134..136 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@136..140 + 0: CSS_UNICODE_CODEPOINT_LITERAL@136..140 "0131" [] [] + 3: CSS_GENERIC_DELIMITER@140..142 + 0: COMMA@140..142 "," [] [Whitespace(" ")] + 4: CSS_UNICODE_RANGE@142..153 + 0: UNICODE@142..144 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@144..153 + 0: CSS_UNICODE_CODEPOINT@144..148 + 0: CSS_UNICODE_CODEPOINT_LITERAL@144..148 "0152" [] [] + 1: MINUS@148..149 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@149..153 + 0: CSS_UNICODE_CODEPOINT_LITERAL@149..153 "0153" [] [] + 5: CSS_GENERIC_DELIMITER@153..155 + 0: COMMA@153..155 "," [] [Whitespace(" ")] + 6: CSS_UNICODE_RANGE@155..161 + 0: UNICODE@155..157 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@157..161 + 0: CSS_UNICODE_CODEPOINT_LITERAL@157..161 "02C6" [] [] + 7: CSS_GENERIC_DELIMITER@161..163 + 0: COMMA@161..163 "," [] [Whitespace(" ")] + 8: CSS_UNICODE_RANGE@163..169 + 0: UNICODE@163..165 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@165..169 + 0: CSS_UNICODE_CODEPOINT_LITERAL@165..169 "02DA" [] [] + 9: CSS_GENERIC_DELIMITER@169..171 + 0: COMMA@169..171 "," [] [Whitespace(" ")] + 10: CSS_UNICODE_RANGE@171..177 + 0: UNICODE@171..173 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@173..177 + 0: CSS_UNICODE_CODEPOINT_LITERAL@173..177 "02DC" [] [] + 11: CSS_GENERIC_DELIMITER@177..179 + 0: COMMA@177..179 "," [] [Whitespace(" ")] + 12: CSS_UNICODE_RANGE@179..190 + 0: UNICODE@179..181 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@181..190 + 0: CSS_UNICODE_CODEPOINT@181..185 + 0: CSS_UNICODE_CODEPOINT_LITERAL@181..185 "2000" [] [] + 1: MINUS@185..186 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@186..190 + 0: CSS_UNICODE_CODEPOINT_LITERAL@186..190 "206F" [] [] + 13: CSS_GENERIC_DELIMITER@190..192 + 0: COMMA@190..192 "," [] [Whitespace(" ")] + 14: CSS_UNICODE_RANGE@192..198 + 0: UNICODE@192..194 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@194..198 + 0: CSS_UNICODE_CODEPOINT_LITERAL@194..198 "2074" [] [] + 15: CSS_GENERIC_DELIMITER@198..200 + 0: COMMA@198..200 "," [] [Whitespace(" ")] + 16: CSS_UNICODE_RANGE@200..206 + 0: UNICODE@200..202 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@202..206 + 0: CSS_UNICODE_CODEPOINT_LITERAL@202..206 "20AC" [] [] + 17: CSS_GENERIC_DELIMITER@206..208 + 0: COMMA@206..208 "," [] [Whitespace(" ")] + 18: CSS_UNICODE_RANGE@208..214 + 0: UNICODE@208..210 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@210..214 + 0: CSS_UNICODE_CODEPOINT_LITERAL@210..214 "2212" [] [] + 19: CSS_GENERIC_DELIMITER@214..216 + 0: COMMA@214..216 "," [] [Whitespace(" ")] + 20: CSS_UNICODE_RANGE@216..222 + 0: UNICODE@216..218 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@218..222 + 0: CSS_UNICODE_CODEPOINT_LITERAL@218..222 "2215" [] [] + 1: (empty) + 1: SEMICOLON@222..223 ";" [] [] + 2: R_CURLY@223..225 "}" [Newline("\n")] [] + 2: CSS_AT_RULE@225..304 + 0: AT@225..228 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_FONT_FACE_AT_RULE@228..304 + 0: FONT_FACE_KW@228..238 "font-face" [] [Whitespace(" ")] + 1: CSS_DECLARATION_BLOCK@238..304 + 0: L_CURLY@238..239 "{" [] [] + 1: CSS_DECLARATION_LIST@239..302 + 0: CSS_DECLARATION_WITH_SEMICOLON@239..302 + 0: CSS_DECLARATION@239..301 + 0: CSS_GENERIC_PROPERTY@239..301 + 0: CSS_IDENTIFIER@239..254 + 0: IDENT@239..254 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@254..256 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@256..301 + 0: CSS_UNICODE_RANGE@256..267 + 0: UNICODE@256..258 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@258..267 + 0: CSS_UNICODE_CODEPOINT@258..262 + 0: CSS_UNICODE_CODEPOINT_LITERAL@258..262 "0400" [] [] + 1: MINUS@262..263 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@263..267 + 0: CSS_UNICODE_CODEPOINT_LITERAL@263..267 "045F" [] [] + 1: CSS_GENERIC_DELIMITER@267..269 + 0: COMMA@267..269 "," [] [Whitespace(" ")] + 2: CSS_UNICODE_RANGE@269..280 + 0: UNICODE@269..271 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@271..280 + 0: CSS_UNICODE_CODEPOINT@271..275 + 0: CSS_UNICODE_CODEPOINT_LITERAL@271..275 "0490" [] [] + 1: MINUS@275..276 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@276..280 + 0: CSS_UNICODE_CODEPOINT_LITERAL@276..280 "0491" [] [] + 3: CSS_GENERIC_DELIMITER@280..282 + 0: COMMA@280..282 "," [] [Whitespace(" ")] + 4: CSS_UNICODE_RANGE@282..293 + 0: UNICODE@282..284 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@284..293 + 0: CSS_UNICODE_CODEPOINT@284..288 + 0: CSS_UNICODE_CODEPOINT_LITERAL@284..288 "04B0" [] [] + 1: MINUS@288..289 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@289..293 + 0: CSS_UNICODE_CODEPOINT_LITERAL@289..293 "04B1" [] [] + 5: CSS_GENERIC_DELIMITER@293..295 + 0: COMMA@293..295 "," [] [Whitespace(" ")] + 6: CSS_UNICODE_RANGE@295..301 + 0: UNICODE@295..297 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@297..301 + 0: CSS_UNICODE_CODEPOINT_LITERAL@297..301 "2116" [] [] + 1: (empty) + 1: SEMICOLON@301..302 ";" [] [] + 2: R_CURLY@302..304 "}" [Newline("\n")] [] + 3: CSS_AT_RULE@304..943 + 0: AT@304..307 "@" [Newline("\n"), Newline("\n")] [] + 1: CSS_FONT_FACE_AT_RULE@307..943 + 0: FONT_FACE_KW@307..317 "font-face" [] [Whitespace(" ")] + 1: CSS_DECLARATION_BLOCK@317..943 + 0: L_CURLY@317..318 "{" [] [] + 1: CSS_DECLARATION_LIST@318..941 + 0: CSS_DECLARATION_WITH_SEMICOLON@318..345 + 0: CSS_DECLARATION@318..344 + 0: CSS_GENERIC_PROPERTY@318..344 + 0: CSS_IDENTIFIER@318..331 + 0: IDENT@318..331 "font-family" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@331..333 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@333..344 + 0: CSS_STRING@333..344 + 0: CSS_STRING_LITERAL@333..344 "'Ampersand'" [] [] + 1: (empty) + 1: SEMICOLON@344..345 ";" [] [] + 1: CSS_DECLARATION_WITH_SEMICOLON@345..377 + 0: CSS_DECLARATION@345..376 + 0: CSS_GENERIC_PROPERTY@345..376 + 0: CSS_IDENTIFIER@345..350 + 0: IDENT@345..350 "src" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@350..352 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@352..376 + 0: CSS_FUNCTION@352..376 + 0: CSS_IDENTIFIER@352..357 + 0: IDENT@352..357 "local" [] [] + 1: L_PAREN@357..358 "(" [] [] + 2: CSS_PARAMETER_LIST@358..375 + 0: CSS_PARAMETER@358..375 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@358..375 + 0: CSS_COMPONENT_VALUE_LIST@358..375 + 0: CSS_STRING@358..375 + 0: CSS_STRING_LITERAL@358..375 "'Times New Roman'" [] [] + 3: R_PAREN@375..376 ")" [] [] + 1: (empty) + 1: SEMICOLON@376..377 ";" [] [] + 2: CSS_DECLARATION_WITH_SEMICOLON@377..436 + 0: CSS_DECLARATION@377..398 + 0: CSS_GENERIC_PROPERTY@377..398 + 0: CSS_IDENTIFIER@377..392 + 0: IDENT@377..392 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@392..394 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@394..398 + 0: CSS_UNICODE_RANGE@394..398 + 0: UNICODE@394..396 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@396..398 + 0: CSS_UNICODE_CODEPOINT_LITERAL@396..398 "26" [] [] + 1: (empty) + 1: SEMICOLON@398..436 ";" [] [Whitespace(" "), Comments("/* single codepoint */")] + 3: CSS_DECLARATION_WITH_SEMICOLON@436..458 + 0: CSS_DECLARATION@436..457 + 0: CSS_GENERIC_PROPERTY@436..457 + 0: CSS_IDENTIFIER@436..451 + 0: IDENT@436..451 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@451..453 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@453..457 + 0: CSS_UNICODE_RANGE@453..457 + 0: UNICODE@453..455 "u+" [] [] + 1: CSS_UNICODE_CODEPOINT@455..457 + 0: CSS_UNICODE_CODEPOINT_LITERAL@455..457 "26" [] [] + 1: (empty) + 1: SEMICOLON@457..458 ";" [] [] + 4: CSS_DECLARATION_WITH_SEMICOLON@458..482 + 0: CSS_DECLARATION@458..481 + 0: CSS_GENERIC_PROPERTY@458..481 + 0: CSS_IDENTIFIER@458..473 + 0: IDENT@458..473 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@473..475 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@475..481 + 0: CSS_UNICODE_RANGE@475..481 + 0: UNICODE@475..477 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@477..481 + 0: CSS_UNICODE_CODEPOINT@477..478 + 0: CSS_UNICODE_CODEPOINT_LITERAL@477..478 "0" [] [] + 1: MINUS@478..479 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@479..481 + 0: CSS_UNICODE_CODEPOINT_LITERAL@479..481 "7F" [] [] + 1: (empty) + 1: SEMICOLON@481..482 ";" [] [] + 5: CSS_DECLARATION_WITH_SEMICOLON@482..540 + 0: CSS_DECLARATION@482..510 + 0: CSS_GENERIC_PROPERTY@482..510 + 0: CSS_IDENTIFIER@482..497 + 0: IDENT@482..497 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@497..499 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@499..510 + 0: CSS_UNICODE_RANGE@499..510 + 0: UNICODE@499..501 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@501..510 + 0: CSS_UNICODE_CODEPOINT@501..505 + 0: CSS_UNICODE_CODEPOINT_LITERAL@501..505 "0025" [] [] + 1: MINUS@505..506 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@506..510 + 0: CSS_UNICODE_CODEPOINT_LITERAL@506..510 "00FF" [] [] + 1: (empty) + 1: SEMICOLON@510..540 ";" [] [Whitespace(" "), Comments("/* codepoint range */")] + 6: CSS_DECLARATION_WITH_SEMICOLON@540..597 + 0: CSS_DECLARATION@540..562 + 0: CSS_GENERIC_PROPERTY@540..562 + 0: CSS_IDENTIFIER@540..555 + 0: IDENT@540..555 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@555..557 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@557..562 + 0: CSS_UNICODE_RANGE@557..562 + 0: UNICODE@557..559 "U+" [] [] + 1: CSS_UNICODE_RANGE_WILDCARD@559..562 + 0: CSS_UNICODE_RANGE_WILDCARD_LITERAL@559..562 "4??" [] [] + 1: (empty) + 1: SEMICOLON@562..597 ";" [] [Whitespace(" "), Comments("/* wildcard range */")] + 7: CSS_DECLARATION_WITH_SEMICOLON@597..655 + 0: CSS_DECLARATION@597..632 + 0: CSS_GENERIC_PROPERTY@597..632 + 0: CSS_IDENTIFIER@597..612 + 0: IDENT@597..612 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@612..614 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@614..632 + 0: CSS_UNICODE_RANGE@614..625 + 0: UNICODE@614..616 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@616..625 + 0: CSS_UNICODE_CODEPOINT@616..620 + 0: CSS_UNICODE_CODEPOINT_LITERAL@616..620 "0025" [] [] + 1: MINUS@620..621 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@621..625 + 0: CSS_UNICODE_CODEPOINT_LITERAL@621..625 "00FF" [] [] + 1: CSS_GENERIC_DELIMITER@625..627 + 0: COMMA@625..627 "," [] [Whitespace(" ")] + 2: CSS_UNICODE_RANGE@627..632 + 0: UNICODE@627..629 "U+" [] [] + 1: CSS_UNICODE_RANGE_WILDCARD@629..632 + 0: CSS_UNICODE_RANGE_WILDCARD_LITERAL@629..632 "4??" [] [] + 1: (empty) + 1: SEMICOLON@632..655 ";" [] [Whitespace(" "), Comments("/* multiple values */")] + 8: CSS_DECLARATION_WITH_SEMICOLON@655..733 + 0: CSS_DECLARATION@655..710 + 0: CSS_GENERIC_PROPERTY@655..710 + 0: CSS_IDENTIFIER@655..670 + 0: IDENT@655..670 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@670..672 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@672..710 + 0: CSS_UNICODE_RANGE@672..676 + 0: UNICODE@672..674 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@674..676 + 0: CSS_UNICODE_CODEPOINT_LITERAL@674..676 "A5" [] [] + 1: CSS_GENERIC_DELIMITER@676..678 + 0: COMMA@676..678 "," [] [Whitespace(" ")] + 2: CSS_UNICODE_RANGE@678..689 + 0: UNICODE@678..680 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@680..689 + 0: CSS_UNICODE_CODEPOINT@680..684 + 0: CSS_UNICODE_CODEPOINT_LITERAL@680..684 "4E00" [] [] + 1: MINUS@684..685 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@685..689 + 0: CSS_UNICODE_CODEPOINT_LITERAL@685..689 "9FFF" [] [] + 3: CSS_GENERIC_DELIMITER@689..691 + 0: COMMA@689..691 "," [] [Whitespace(" ")] + 4: CSS_UNICODE_RANGE@691..697 + 0: UNICODE@691..693 "U+" [] [] + 1: CSS_UNICODE_RANGE_WILDCARD@693..697 + 0: CSS_UNICODE_RANGE_WILDCARD_LITERAL@693..697 "30??" [] [] + 5: CSS_GENERIC_DELIMITER@697..699 + 0: COMMA@697..699 "," [] [Whitespace(" ")] + 6: CSS_UNICODE_RANGE@699..710 + 0: UNICODE@699..701 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@701..710 + 0: CSS_UNICODE_CODEPOINT@701..705 + 0: CSS_UNICODE_CODEPOINT_LITERAL@701..705 "FF00" [] [] + 1: MINUS@705..706 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@706..710 + 0: CSS_UNICODE_CODEPOINT_LITERAL@706..710 "FF9F" [] [] + 1: (empty) + 1: SEMICOLON@710..733 ";" [] [Whitespace(" "), Comments("/* multiple values */")] + 9: CSS_DECLARATION_WITH_SEMICOLON@733..757 + 0: CSS_DECLARATION@733..756 + 0: CSS_GENERIC_PROPERTY@733..756 + 0: CSS_IDENTIFIER@733..748 + 0: IDENT@733..748 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@748..750 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@750..756 + 0: CSS_UNICODE_RANGE@750..756 + 0: UNICODE@750..752 "U+" [] [] + 1: CSS_UNICODE_RANGE_WILDCARD@752..756 + 0: CSS_UNICODE_RANGE_WILDCARD_LITERAL@752..756 "????" [] [] + 1: (empty) + 1: SEMICOLON@756..757 ";" [] [] + 10: CSS_DECLARATION_WITH_SEMICOLON@757..783 + 0: CSS_DECLARATION@757..782 + 0: CSS_GENERIC_PROPERTY@757..782 + 0: CSS_IDENTIFIER@757..772 + 0: IDENT@757..772 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@772..774 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@774..782 + 0: CSS_UNICODE_RANGE@774..782 + 0: UNICODE@774..776 "U+" [] [] + 1: CSS_UNICODE_RANGE_WILDCARD@776..782 + 0: CSS_UNICODE_RANGE_WILDCARD_LITERAL@776..782 "??????" [] [] + 1: (empty) + 1: SEMICOLON@782..783 ";" [] [] + 11: CSS_DECLARATION_WITH_SEMICOLON@783..805 + 0: CSS_DECLARATION@783..804 + 0: CSS_GENERIC_PROPERTY@783..804 + 0: CSS_IDENTIFIER@783..798 + 0: IDENT@783..798 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@798..800 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@800..804 + 0: CSS_UNICODE_RANGE@800..804 + 0: UNICODE@800..802 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@802..804 + 0: CSS_UNICODE_CODEPOINT_LITERAL@802..804 "12" [] [] + 1: (empty) + 1: SEMICOLON@804..805 ";" [] [] + 12: CSS_DECLARATION_WITH_SEMICOLON@805..831 + 0: CSS_DECLARATION@805..830 + 0: CSS_GENERIC_PROPERTY@805..830 + 0: CSS_IDENTIFIER@805..820 + 0: IDENT@805..820 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@820..822 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@822..830 + 0: CSS_UNICODE_RANGE@822..830 + 0: UNICODE@822..824 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@824..830 + 0: CSS_UNICODE_CODEPOINT_LITERAL@824..830 "12e112" [] [] + 1: (empty) + 1: SEMICOLON@830..831 ";" [] [] + 13: CSS_DECLARATION_WITH_SEMICOLON@831..857 + 0: CSS_DECLARATION@831..856 + 0: CSS_GENERIC_PROPERTY@831..856 + 0: CSS_IDENTIFIER@831..846 + 0: IDENT@831..846 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@846..848 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@848..856 + 0: CSS_UNICODE_RANGE@848..856 + 0: UNICODE@848..850 "U+" [] [] + 1: CSS_UNICODE_CODEPOINT@850..856 + 0: CSS_UNICODE_CODEPOINT_LITERAL@850..856 "1e1ee1" [] [] + 1: (empty) + 1: SEMICOLON@856..857 ";" [] [] + 14: CSS_DECLARATION_WITH_SEMICOLON@857..890 + 0: CSS_DECLARATION@857..889 + 0: CSS_GENERIC_PROPERTY@857..889 + 0: CSS_IDENTIFIER@857..872 + 0: IDENT@857..872 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@872..874 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@874..889 + 0: CSS_UNICODE_RANGE@874..889 + 0: UNICODE@874..876 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@876..889 + 0: CSS_UNICODE_CODEPOINT@876..882 + 0: CSS_UNICODE_CODEPOINT_LITERAL@876..882 "1e1ee1" [] [] + 1: MINUS@882..883 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@883..889 + 0: CSS_UNICODE_CODEPOINT_LITERAL@883..889 "FFFFFF" [] [] + 1: (empty) + 1: SEMICOLON@889..890 ";" [] [] + 15: CSS_DECLARATION_WITH_SEMICOLON@890..916 + 0: CSS_DECLARATION@890..915 + 0: CSS_GENERIC_PROPERTY@890..915 + 0: CSS_IDENTIFIER@890..905 + 0: IDENT@890..905 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@905..907 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@907..915 + 0: CSS_UNICODE_RANGE@907..915 + 0: UNICODE@907..909 "U+" [] [] + 1: CSS_UNICODE_RANGE_WILDCARD@909..915 + 0: CSS_UNICODE_RANGE_WILDCARD_LITERAL@909..915 "1e1ee?" [] [] + 1: (empty) + 1: SEMICOLON@915..916 ";" [] [] + 16: CSS_DECLARATION_WITH_SEMICOLON@916..941 + 0: CSS_DECLARATION@916..940 + 0: CSS_GENERIC_PROPERTY@916..940 + 0: CSS_IDENTIFIER@916..931 + 0: IDENT@916..931 "unicode-range" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@931..933 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@933..940 + 0: CSS_UNICODE_RANGE@933..940 + 0: UNICODE@933..935 "U+" [] [] + 1: CSS_UNICODE_RANGE_INTERVAL@935..940 + 0: CSS_UNICODE_CODEPOINT@935..937 + 0: CSS_UNICODE_CODEPOINT_LITERAL@935..937 "12" [] [] + 1: MINUS@937..938 "-" [] [] + 2: CSS_UNICODE_CODEPOINT@938..940 + 0: CSS_UNICODE_CODEPOINT_LITERAL@938..940 "13" [] [] + 1: (empty) + 1: SEMICOLON@940..941 ";" [] [] + 2: R_CURLY@941..943 "}" [Newline("\n")] [] + 2: EOF@943..944 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_css_parser/tests/spec_test.rs b/crates/biome_css_parser/tests/spec_test.rs index 3a801db17809..b46f4847524c 100644 --- a/crates/biome_css_parser/tests/spec_test.rs +++ b/crates/biome_css_parser/tests/spec_test.rs @@ -175,7 +175,8 @@ pub fn run(test_case: &str, _snapshot_name: &str, test_directory: &str, outcome_ pub fn quick_test() { let code = r#" div { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + unicode: U+000; + unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF, U+ff??; } diff --git a/crates/biome_css_syntax/src/generated/kind.rs b/crates/biome_css_syntax/src/generated/kind.rs index 4823672506a0..8bf417db4341 100644 --- a/crates/biome_css_syntax/src/generated/kind.rs +++ b/crates/biome_css_syntax/src/generated/kind.rs @@ -54,6 +54,7 @@ pub enum CssSyntaxKind { TILDE_EQ, CDC, CDO, + UNICODE, MEDIA_KW, KEYFRAMES_KW, NOT_KW, @@ -240,6 +241,8 @@ pub enum CssSyntaxKind { CSS_COLOR_LITERAL, CSS_DIMENSION_VALUE, CSS_PERCENTAGE_VALUE, + CSS_UNICODE_CODEPOINT_LITERAL, + CSS_UNICODE_RANGE_WILDCARD_LITERAL, ERROR_TOKEN, IDENT, NEWLINE, @@ -335,6 +338,10 @@ pub enum CssSyntaxKind { CSS_BORDER, CSS_BRACKETED_VALUE, CSS_BRACKETED_VALUE_LIST, + CSS_UNICODE_RANGE, + CSS_UNICODE_CODEPOINT, + CSS_UNICODE_RANGE_WILDCARD, + CSS_UNICODE_RANGE_INTERVAL, CSS_AT_RULE, CSS_CHARSET_AT_RULE, CSS_COLOR_PROFILE_AT_RULE, @@ -466,6 +473,7 @@ pub enum CssSyntaxKind { CSS_BOGUS_FONT_FAMILY_NAME, CSS_BOGUS_CUSTOM_IDENTIFIER, CSS_BOGUS_KEYFRAMES_NAME, + CSS_BOGUS_UNICODE_RANGE_VALUE, #[doc(hidden)] __LAST, } @@ -477,7 +485,7 @@ impl CssSyntaxKind { | L_ANGLE | R_ANGLE | TILDE | HASH | AMP | PIPE | PIPE2 | PLUS | STAR | SLASH | CARET | PERCENT | DOT | COLON | COLON2 | EQ | BANG | NEQ | MINUS | LTEQ | GTEQ | PLUSEQ | PIPEEQ | AMPEQ | CARETEQ | SLASHEQ | STAREQ | PERCENTEQ | AT | DOLLAR_EQ - | TILDE_EQ | CDC | CDO => true, + | TILDE_EQ | CDC | CDO | UNICODE => true, _ => false, } } @@ -491,7 +499,9 @@ impl CssSyntaxKind { | CSS_URL_VALUE_RAW_LITERAL | CSS_COLOR_LITERAL | CSS_DIMENSION_VALUE - | CSS_PERCENTAGE_VALUE => true, + | CSS_PERCENTAGE_VALUE + | CSS_UNICODE_CODEPOINT_LITERAL + | CSS_UNICODE_RANGE_WILDCARD_LITERAL => true, _ => false, } } @@ -762,6 +772,7 @@ impl CssSyntaxKind { TILDE_EQ => "~=", CDC => "-->", CDO => "] => { $ crate :: CssSyntaxKind :: CDC } ; [] => { $ crate :: CssSyntaxKind :: CDC } ; [", "CDC"), ("