1use rustc_hash::FxHashSet;
4use smallvec::SmallVec;
5
6use crate::HandleWarning;
7use crate::Lexer;
8use crate::Pos;
9use crate::dependency_types::{
10 Dependency, DependencyContext, Mode, Range, UrlRangeKind, ValueAtRuleImportItem, Warning,
11 WarningKind,
12};
13use crate::lexer::Token;
14use crate::lexer::TokenFlags;
15use crate::lexer::TokenKind;
16use crate::lexer::TokenStream;
17
18macro_rules! match_ignore_ascii_case {
19 (
20 $value:expr, {
21 $(
22 $($pattern:literal)|+ => $result:expr,
23 )*
24 _ => $fallback:expr $(,)?
25 }
26 ) => {{
27 let value = $value;
28 $(
29 if $(value.eq_ignore_ascii_case($pattern))||+ {
30 $result
31 } else
32 )*
33 {
34 $fallback
35 }
36 }};
37}
38
39#[derive(Debug)]
40enum Scope<'s> {
41 TopLevel,
42 InBlock,
43 InAtImport(ImportData<'s>),
44 AtImportInvalid,
45 AtNamespaceInvalid,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49enum ScanContext {
50 TopLevel,
51 BlockItem,
52 Selector,
53 DeclarationName,
54 GenericValue,
55 SpecialValue(PropertyKind),
56 AtRule,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub(crate) enum PropertyKind {
61 Generic,
62 Animation,
63 ListStyle,
64 FontPalette,
65 Container,
66 Grid,
67 Composes,
68 CustomProperty,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72enum AtRuleKind {
73 Namespace,
74 Import,
75 Charset,
76 Value,
77 Keyframes,
78 Container,
79 Function,
80 Property,
81 CounterStyle,
82 FontPaletteValues,
83 Scope,
84 Other,
85}
86
87impl ScanContext {
88 fn for_property(property: PropertyKind) -> Self {
89 if property == PropertyKind::Generic {
90 Self::GenericValue
91 } else {
92 Self::SpecialValue(property)
93 }
94 }
95}
96
97#[derive(Debug)]
98struct ImportData<'s> {
99 start: Pos,
100 prelude: ImportPrelude<'s>,
101 url: Option<&'s str>,
102 url_flags: TokenFlags,
103 url_range: Option<Range>,
104 supports: ImportDataSupports<'s>,
105 layer: ImportDataLayer<'s>,
106}
107
108impl ImportData<'_> {
109 pub fn new(start: Pos) -> Self {
110 Self {
111 start,
112 prelude: ImportPrelude::default(),
113 url: None,
114 url_flags: TokenFlags::ascii(),
115 url_range: None,
116 supports: ImportDataSupports::None,
117 layer: ImportDataLayer::None,
118 }
119 }
120
121 pub fn in_supports(&self) -> bool {
122 matches!(self.supports, ImportDataSupports::InSupports { .. })
123 }
124
125 pub fn layer_range(&self) -> Option<&Range> {
126 let ImportDataLayer::EndLayer { range, .. } = &self.layer else {
127 return None;
128 };
129 Some(range)
130 }
131
132 pub fn supports_range(&self) -> Option<&Range> {
133 let ImportDataSupports::EndSupports { range, .. } = &self.supports else {
134 return None;
135 };
136 Some(range)
137 }
138}
139
140#[derive(Debug, Default)]
141struct ImportPrelude<'s>(SmallVec<[ImportPreludeNode<'s>; 2]>);
142
143impl<'s> ImportPrelude<'s> {
144 pub fn push(&mut self, node: ImportPreludeNode<'s>) {
145 self.0.push(node);
146 }
147
148 pub fn is_empty(&self) -> bool {
149 self.0.is_empty()
150 }
151
152 pub fn icss_import_url(&self) -> Option<(&'s str, &Range)> {
153 let [ImportPreludeNode::IcssUrlCandidate { name, range }] = self.0.as_slice() else {
154 return None;
155 };
156 Some((name, range))
157 }
158
159 pub fn first_non_url_before(&self, url_range: &Range) -> Option<&Range> {
160 self.0.iter().find_map(|node| {
161 let range = node.range();
162 if range.start >= url_range.start || matches!(node, ImportPreludeNode::Url { .. }) {
163 None
164 } else {
165 Some(range)
166 }
167 })
168 }
169}
170
171#[derive(Debug)]
172enum ImportPreludeNode<'s> {
173 IcssUrlCandidate { name: &'s str, range: Range },
174 Url { range: Range },
175 Layer { range: Range },
176 Supports { range: Range },
177 Other { range: Range },
178}
179
180impl ImportPreludeNode<'_> {
181 fn range(&self) -> &Range {
182 match self {
183 Self::IcssUrlCandidate { range, .. }
184 | Self::Url { range }
185 | Self::Layer { range }
186 | Self::Supports { range }
187 | Self::Other { range } => range,
188 }
189 }
190}
191
192#[derive(Debug)]
193enum ImportDataSupports<'s> {
194 None,
195 InSupports,
196 EndSupports { value: &'s str, range: Range },
197}
198
199#[derive(Debug)]
200enum ImportDataLayer<'s> {
201 None,
202 EndLayer { value: &'s str, range: Range },
203}
204
205#[derive(Debug, Default)]
206struct BalancedStack(SmallVec<[BalancedItem; 3]>);
207
208impl BalancedStack {
209 pub fn len(&self) -> usize {
210 self.0.len()
211 }
212
213 pub fn last(&self) -> Option<&BalancedItem> {
214 self.0.last()
215 }
216
217 pub fn is_empty(&self) -> bool {
218 self.0.is_empty()
219 }
220
221 pub fn push(&mut self, item: BalancedItem, mode_data: Option<&mut ModeData>) {
222 if let Some(mode_data) = mode_data {
223 if item.kind.is_mode_local() {
224 mode_data.set_current_mode(Mode::Local);
225 } else if item.kind.is_mode_global() {
226 mode_data.set_current_mode(Mode::Global);
227 }
228
229 if item.kind.is_mode_function() {
230 mode_data.inside_mode_function += 1;
231 } else if item.kind.is_mode_class() {
232 mode_data.inside_mode_class += 1;
233 }
234 }
235 self.0.push(item);
236 }
237
238 pub fn pop(&mut self, mode_data: Option<&mut ModeData>) -> Option<BalancedItem> {
239 let item = self.0.pop()?;
240 if let Some(mode_data) = mode_data {
241 if item.kind.is_mode_function() {
242 mode_data.inside_mode_function -= 1;
243 } else if item.kind.is_mode_class() {
244 mode_data.inside_mode_class -= 1;
245 }
246 self.update_current_mode(mode_data);
247 }
248 Some(item)
249 }
250
251 pub fn pop_without_moda_data(&mut self) -> Option<BalancedItem> {
252 self.0.pop()
253 }
254
255 pub fn pop_mode_pseudo_class(&mut self, mode_data: &mut ModeData) {
256 loop {
257 if let Some(last) = self.0.last()
258 && matches!(
259 last.kind,
260 BalancedItemKind::LocalClass | BalancedItemKind::GlobalClass
261 )
262 {
263 mode_data.inside_mode_class -= 1;
264 self.0.pop();
265 continue;
266 }
267 break;
268 }
269 self.update_current_mode(mode_data);
270 }
271
272 pub fn update_current_mode(&self, mode_data: &mut ModeData) {
273 mode_data.set_current_mode(self.topmost_mode(mode_data));
274 }
275
276 pub fn update_property_mode(&self, mode_data: &mut ModeData) {
277 mode_data.set_property_mode(self.topmost_mode(mode_data));
278 }
279
280 fn topmost_mode(&self, mode_data: &ModeData) -> Mode {
281 let mut iter = self.0.iter();
282 loop {
283 if let Some(last) = iter.next_back() {
284 if matches!(
285 last.kind,
286 BalancedItemKind::LocalFn | BalancedItemKind::LocalClass
287 ) {
288 return Mode::Local;
289 } else if matches!(
290 last.kind,
291 BalancedItemKind::GlobalFn | BalancedItemKind::GlobalClass
292 ) {
293 return Mode::Global;
294 }
295 } else {
296 return mode_data.default_mode();
297 }
298 }
299 }
300}
301
302#[derive(Debug)]
303struct BalancedItem {
304 kind: BalancedItemKind,
305 range: Range,
306}
307
308impl BalancedItem {
309 pub fn new(name: &str, start: Pos, end: Pos) -> Self {
310 Self {
311 kind: BalancedItemKind::new(name),
312 range: Range::new(start, end),
313 }
314 }
315
316 pub fn new_other(start: Pos, end: Pos) -> Self {
317 Self {
318 kind: BalancedItemKind::Other,
319 range: Range::new(start, end),
320 }
321 }
322
323 pub fn new_curly(start: Pos, end: Pos) -> Self {
324 Self {
325 kind: BalancedItemKind::Curly,
326 range: Range::new(start, end),
327 }
328 }
329}
330
331#[derive(Debug)]
332enum BalancedItemKind {
333 Url,
334 ImageSet,
335 Layer,
336 Supports,
337 PaletteMix,
338 LocalFn,
339 GlobalFn,
340 LocalClass,
341 GlobalClass,
342 Curly,
343 Other,
344}
345
346impl BalancedItemKind {
347 pub fn new(name: &str) -> Self {
348 match name {
349 "url(" => Self::Url,
350 "image-set(" => Self::ImageSet,
351 _ if with_vendor_prefixed_eq(name, "image-set(", false) => Self::ImageSet,
352 _ if name.eq_ignore_ascii_case("layer(") => Self::Layer,
353 _ if name.eq_ignore_ascii_case("supports(") => Self::Supports,
354 "palette-mix(" => Self::PaletteMix,
355 _ if name.eq_ignore_ascii_case(":local(") => Self::LocalFn,
356 _ if name.eq_ignore_ascii_case(":global(") => Self::GlobalFn,
357 _ if name.eq_ignore_ascii_case(":local") => Self::LocalClass,
358 _ if name.eq_ignore_ascii_case(":global") => Self::GlobalClass,
359 _ => Self::Other,
360 }
361 }
362
363 pub fn is_mode_local(&self) -> bool {
364 matches!(self, Self::LocalFn | Self::LocalClass)
365 }
366
367 pub fn is_mode_global(&self) -> bool {
368 matches!(self, Self::GlobalFn | Self::GlobalClass)
369 }
370
371 pub fn is_mode_function(&self) -> bool {
372 matches!(self, Self::LocalFn | Self::GlobalFn)
373 }
374
375 pub fn is_mode_class(&self) -> bool {
376 matches!(self, Self::LocalClass | Self::GlobalClass)
377 }
378}
379
380fn is_css_space_byte(byte: u8) -> bool {
381 matches!(byte, b' ' | b'\t' | b'\n' | b'\r' | 0x0C)
382}
383
384fn is_css_white_space_char(c: char) -> bool {
385 matches!(c, ' ' | '\t' | '\n' | '\r' | '\u{c}')
386}
387
388fn trim_slice(input: &str) -> &str {
389 let bytes = input.as_bytes();
390 let mut start = 0usize;
391 let mut end = bytes.len();
392 while start < end && is_css_space_byte(bytes[start]) {
393 start += 1;
394 }
395 while end > start && is_css_space_byte(bytes[end - 1]) {
396 end -= 1;
397 }
398 &input[start..end]
399}
400
401fn is_css_modules_magic_comment(input: &str, keyword: &str) -> bool {
402 let input = trim_slice(input);
403 let Some(rest) = input.strip_prefix(keyword) else {
404 return false;
405 };
406 rest.is_empty()
407 || rest
408 .as_bytes()
409 .first()
410 .is_some_and(|byte| is_css_space_byte(*byte))
411}
412
413fn is_css_modules_pure_magic_comment(input: &str) -> bool {
414 let input = trim_slice(input);
415 let Some(keyword) = input.strip_prefix("cssmodules-pure-") else {
416 return false;
417 };
418 ["ignore", "no-check"].into_iter().any(|expected| {
419 keyword.strip_prefix(expected).is_some_and(|rest| {
420 rest.is_empty()
421 || rest
422 .as_bytes()
423 .first()
424 .is_some_and(|byte| is_css_space_byte(*byte))
425 })
426 })
427}
428
429fn trivia_only(input: &str) -> bool {
430 if input.is_empty() {
431 return false;
432 }
433 let bytes = input.as_bytes();
434 let mut position = 0;
435 while position < bytes.len() {
436 if is_css_space_byte(bytes[position]) {
437 position += 1;
438 continue;
439 }
440 if position + 1 < bytes.len() && bytes[position] == b'/' && bytes[position + 1] == b'*' {
441 position += 2;
442 while position + 1 < bytes.len()
443 && !(bytes[position] == b'*' && bytes[position + 1] == b'/')
444 {
445 position += 1;
446 }
447 if position + 1 >= bytes.len() {
448 return false;
449 }
450 position += 2;
451 continue;
452 }
453 return false;
454 }
455 true
456}
457
458fn token_text(input: &str, token: Token) -> &str {
459 Lexer::slice_range(input, &token.range).unwrap_or("")
460}
461
462fn is_open_token(kind: TokenKind) -> bool {
463 matches!(
464 kind,
465 TokenKind::Function
466 | TokenKind::LeftParenthesis
467 | TokenKind::LeftSquareBracket
468 | TokenKind::LeftCurlyBracket
469 )
470}
471
472fn is_close_token(kind: TokenKind) -> bool {
473 matches!(
474 kind,
475 TokenKind::RightParenthesis | TokenKind::RightSquareBracket | TokenKind::RightCurlyBracket
476 )
477}
478
479fn ident_like_range(token: Token) -> Option<Range> {
480 match token.kind {
481 TokenKind::Ident => Some(token.range),
482 TokenKind::Function => Some(token.value_range),
483 _ => None,
484 }
485}
486
487#[derive(Debug, Clone, Copy)]
491struct ValueAtRuleSplit {
492 split: Pos,
493 end: Pos,
494 prev_end: Pos,
495 next_start: Option<Pos>,
496}
497
498struct ValueAtRuleStream<'s> {
502 input: &'s str,
503 depth: u32,
504 params_end: Pos,
505 first_significant: Option<(Pos, Pos)>,
506 significant_count: u32,
507 first_colon: Option<ValueAtRuleSplit>,
508 first_colon_tokens_after: u32,
509 item_start: Option<Pos>,
510 item_end: Pos,
511 item_colon: Option<ValueAtRuleSplit>,
512 item_as: Option<ValueAtRuleSplit>,
513 last_significant: Option<Token>,
514 penultimate_significant: Option<Token>,
515 from_pos: Option<Pos>,
516 from_prev_end: Option<Pos>,
517}
518
519impl<'s> ValueAtRuleStream<'s> {
520 fn new(input: &'s str) -> Self {
521 Self {
522 input,
523 depth: 0,
524 params_end: 0,
525 first_significant: None,
526 significant_count: 0,
527 first_colon: None,
528 first_colon_tokens_after: 0,
529 item_start: None,
530 item_end: 0,
531 item_colon: None,
532 item_as: None,
533 last_significant: None,
534 penultimate_significant: None,
535 from_pos: None,
536 from_prev_end: None,
537 }
538 }
539
540 fn push(&mut self, context: &mut DependencyContext<'s>, token: Token) {
541 if matches!(token.kind, TokenKind::Comment | TokenKind::BadComment) {
542 self.params_end = token.range.end;
543 return;
544 }
545 self.params_end = token.range.end;
546 if self.first_significant.is_none() {
547 self.first_significant = Some((token.range.start, token.range.end));
548 }
549 self.significant_count += 1;
550 let had_first_colon = self.first_colon.is_some();
551
552 self.depth = (self.depth + u32::from(is_open_token(token.kind)))
553 .saturating_sub(u32::from(is_close_token(token.kind)));
554 let at_top = self.depth == 0;
555 let is_ident = token.kind == TokenKind::Ident;
556 let text = if is_ident {
557 self.input
558 .get(token.range.start as usize..token.range.end as usize)
559 .unwrap_or("")
560 } else {
561 ""
562 };
563 if at_top {
564 if token.kind == TokenKind::Colon {
565 let split = ValueAtRuleSplit {
566 split: token.range.start,
567 end: token.range.end,
568 prev_end: self
569 .last_significant
570 .map_or(token.range.start, |previous| previous.range.end),
571 next_start: None,
572 };
573 if self.first_colon.is_none() {
574 self.first_colon = Some(split);
575 }
576 if self.item_colon.is_none() {
577 self.item_colon = Some(split);
578 }
579 }
580 if is_ident && text.eq_ignore_ascii_case("as") {
581 self.item_as = Some(ValueAtRuleSplit {
582 split: token.range.start,
583 end: token.range.end,
584 prev_end: self
585 .last_significant
586 .map_or(token.range.start, |previous| previous.range.end),
587 next_start: None,
588 });
589 }
590 if is_ident
591 && text.eq_ignore_ascii_case("from")
592 && let Some(previous) = self.last_significant
593 {
594 let gap = self
595 .input
596 .get(previous.range.end as usize..token.range.start as usize)
597 .unwrap_or("");
598 if trivia_only(gap) {
599 self.from_pos = Some(token.range.start);
600 self.from_prev_end = Some(previous.range.end);
601 }
602 }
603 self.penultimate_significant = self.last_significant;
604 self.last_significant = Some(token);
605 }
606 if had_first_colon {
607 self.first_colon_tokens_after += 1;
608 }
609
610 if token.kind == TokenKind::Comma && at_top {
611 self.finish_item(context, self.item_end);
612 self.item_start = None;
613 self.item_colon = None;
614 self.item_as = None;
615 } else {
616 if self.item_start.is_none() {
617 self.item_start = Some(token.range.start);
618 }
619 self.item_end = token.range.end;
620 if let Some(split) = self.item_colon.as_mut()
621 && split.next_start.is_none()
622 && split.split != token.range.start
623 {
624 split.next_start = Some(token.range.start);
625 }
626 if let Some(split) = self.item_as.as_mut()
627 && split.next_start.is_none()
628 && split.split != token.range.start
629 {
630 split.next_start = Some(token.range.start);
631 }
632 }
633 }
634
635 fn finish_item(&mut self, context: &mut DependencyContext<'s>, end: Pos) {
636 let Some(item_start) = self.item_start else {
637 return;
638 };
639 if end.saturating_sub(item_start) >= 2
640 && self.input.as_bytes()[item_start as usize] == b'('
641 && self.input.as_bytes()[end as usize - 1] == b')'
642 {
643 self.parse_paren_items(context, item_start + 1, end - 1);
644 } else {
645 let item = self.build_item(item_start, end, self.item_colon, self.item_as);
646 if !item.local_name().is_empty() || !item.import_name().is_empty() {
647 context.push_value_at_rule_import_item(item);
648 }
649 }
650 }
651
652 fn build_item(
653 &self,
654 start: Pos,
655 end: Pos,
656 colon: Option<ValueAtRuleSplit>,
657 as_split: Option<ValueAtRuleSplit>,
658 ) -> ValueAtRuleImportItem<'s> {
659 let slice = |a: Pos, b: Pos| -> &'s str {
660 if a >= b {
661 ""
662 } else {
663 &self.input[a as usize..b as usize]
664 }
665 };
666 if let Some(split) = colon {
667 return ValueAtRuleImportItem::new(
668 slice(start, split.prev_end),
669 split
670 .next_start
671 .map_or("", |next_start| slice(next_start, end)),
672 );
673 }
674 if let Some(split) = as_split {
675 let import_name = slice(start, split.prev_end);
676 let local_name = split
677 .next_start
678 .map_or("", |next_start| slice(next_start, end));
679 if !import_name.is_empty() && !local_name.is_empty() {
680 return ValueAtRuleImportItem::new(local_name, import_name);
681 }
682 }
683 let value = slice(start, end);
684 ValueAtRuleImportItem::new(value, value)
685 }
686
687 fn parse_paren_items(&mut self, context: &mut DependencyContext<'s>, start: Pos, end: Pos) {
691 let slice = &self.input[start as usize..end as usize];
692 let mut lexer = Lexer::new(slice);
693 let mut tokens: SmallVec<[Token; 8]> = SmallVec::new();
694 loop {
695 let token = lexer.next_token();
696 if token.kind == TokenKind::Eof {
697 break;
698 }
699 if matches!(
700 token.kind,
701 TokenKind::Comment | TokenKind::BadComment | TokenKind::WhiteSpace
702 ) {
703 continue;
704 }
705 tokens.push(token);
706 }
707 let mut item_start = 0;
708 let mut item_depth = 0u32;
709 for (index, token) in tokens.iter().copied().enumerate() {
710 if is_close_token(token.kind) {
711 item_depth = item_depth.saturating_sub(1);
712 }
713 if token.kind == TokenKind::Comma && item_depth == 0 {
714 self.push_parsed_item(context, &tokens, item_start, index, slice);
715 item_start = index + 1;
716 }
717 if is_open_token(token.kind) {
718 item_depth += 1;
719 }
720 }
721 self.push_parsed_item(context, &tokens, item_start, tokens.len(), slice);
722 }
723
724 fn push_parsed_item(
725 &mut self,
726 context: &mut DependencyContext<'s>,
727 tokens: &[Token],
728 start: usize,
729 end: usize,
730 slice: &'s str,
731 ) {
732 let mut depth = 0u32;
733 let mut colon_index = None;
734 let mut as_index = None;
735 for (index, token) in tokens[start..end].iter().copied().enumerate() {
736 let index = start + index;
737 if is_close_token(token.kind) {
738 depth = depth.saturating_sub(1);
739 }
740 if depth == 0 {
741 if token.kind == TokenKind::Colon && colon_index.is_none() {
742 colon_index = Some(index);
743 }
744 if token.kind == TokenKind::Ident
745 && token_text(slice, token).eq_ignore_ascii_case("as")
746 {
747 as_index = Some(index);
748 }
749 }
750 if is_open_token(token.kind) {
751 depth += 1;
752 }
753 }
754 let span = |a: usize, b: usize| -> &'s str {
755 if a >= b {
756 ""
757 } else {
758 &slice[tokens[a].range.start as usize..tokens[b - 1].range.end as usize]
759 }
760 };
761 let item = if let Some(index) = colon_index {
762 ValueAtRuleImportItem::new(span(start, index), span(index + 1, end))
763 } else if let Some(index) = as_index {
764 let import_name = span(start, index);
765 let local_name = span(index + 1, end);
766 if !import_name.is_empty() && !local_name.is_empty() {
767 ValueAtRuleImportItem::new(local_name, import_name)
768 } else {
769 ValueAtRuleImportItem::new("", "")
770 }
771 } else {
772 let value = span(start, end);
773 ValueAtRuleImportItem::new(value, value)
774 };
775 if !item.local_name().is_empty() || !item.import_name().is_empty() {
776 context.push_value_at_rule_import_item(item);
777 }
778 }
779
780 fn last_two(&self) -> Option<(Token, Token)> {
783 Some((self.penultimate_significant?, self.last_significant?))
784 }
785}
786
787fn with_vendor_prefixed_eq(left: &str, right: &str, at_rule: bool) -> bool {
788 let left = if at_rule {
789 if let Some(left) = left.strip_prefix('@') {
790 left
791 } else {
792 return false;
793 }
794 } else {
795 left
796 };
797 matches!(left.strip_prefix("-webkit-"), Some(left) if left.eq_ignore_ascii_case(right))
798 || matches!(left.strip_prefix("-moz-"), Some(left) if left.eq_ignore_ascii_case(right))
799 || matches!(left.strip_prefix("-ms-"), Some(left) if left.eq_ignore_ascii_case(right))
800 || matches!(left.strip_prefix("-o-"), Some(left) if left.eq_ignore_ascii_case(right))
801}
802
803#[derive(Debug)]
804pub struct ModeData<'s> {
805 default: Mode,
806 current: Mode,
807 property: Mode,
808 resulting_global: Option<Pos>,
809 pure_global: Option<Pos>,
810 pure_no_check: bool,
811 pure_ignore_pending: bool,
812 pure_ignored_block_nesting_level: Option<u32>,
813 composes_local_classes: ComposesLocalClasses<'s>,
814 inside_mode_function: u32,
815 inside_mode_class: u32,
816}
817
818impl ModeData<'_> {
819 pub fn new(default: Mode) -> Self {
820 Self {
821 default,
822 current: default,
823 property: default,
824 resulting_global: None,
825 pure_global: Some(0),
826 pure_no_check: false,
827 pure_ignore_pending: false,
828 pure_ignored_block_nesting_level: None,
829 composes_local_classes: ComposesLocalClasses::default(),
830 inside_mode_function: 0,
831 inside_mode_class: 0,
832 }
833 }
834
835 pub fn is_pure_mode(&self) -> bool {
836 matches!(self.default, Mode::Pure)
837 }
838
839 pub fn mark_pure_ignore(&mut self) {
840 if self.is_pure_mode() {
841 self.pure_ignore_pending = true;
842 }
843 }
844
845 pub fn mark_pure_no_check(&mut self) {
846 if self.is_pure_mode() {
847 self.pure_no_check = true;
848 }
849 }
850
851 pub fn is_pure_check_disabled(&self) -> bool {
852 self.pure_no_check
853 || self.pure_ignore_pending
854 || self.pure_ignored_block_nesting_level.is_some()
855 }
856
857 pub fn enter_block(&mut self, block_nesting_level: u32) {
858 if self.pure_ignore_pending {
859 self.pure_ignore_pending = false;
860 if self.pure_ignored_block_nesting_level.is_none() {
861 self.pure_ignored_block_nesting_level = Some(block_nesting_level);
862 }
863 }
864 }
865
866 pub fn clear_pure_ignore_pending(&mut self) {
867 self.pure_ignore_pending = false;
868 }
869
870 pub fn exit_block(&mut self, block_nesting_level: u32) {
871 if self
872 .pure_ignored_block_nesting_level
873 .is_some_and(|level| block_nesting_level < level)
874 {
875 self.pure_ignored_block_nesting_level = None;
876 }
877 }
878
879 pub fn is_current_local_mode(&self) -> bool {
880 match self.current {
881 Mode::Local | Mode::Pure => true,
882 Mode::Global | Mode::Css => false,
883 }
884 }
885
886 pub fn is_property_local_mode(&self) -> bool {
887 match self.property {
888 Mode::Local | Mode::Pure => true,
889 Mode::Global | Mode::Css => false,
890 }
891 }
892
893 pub fn default_mode(&self) -> Mode {
894 self.default
895 }
896
897 pub fn set_current_mode(&mut self, mode: Mode) {
898 self.current = mode;
899 }
900
901 pub fn set_property_mode(&mut self, mode: Mode) {
902 self.property = mode;
903 }
904
905 pub fn is_inside_mode_function(&self) -> bool {
906 self.inside_mode_function > 0
907 }
908
909 pub fn is_inside_mode_class(&self) -> bool {
910 self.inside_mode_class > 0
911 }
912
913 pub fn is_mode_explicit(&self) -> bool {
914 self.is_inside_mode_function() || self.is_inside_mode_class()
915 }
916}
917
918#[derive(Debug, Default, Clone)]
919struct ComposesLocalClasses<'s> {
920 is_single: SingleLocalClass,
921 local_classes: SmallVec<[&'s str; 2]>,
922}
923
924impl<'s> ComposesLocalClasses<'s> {
925 pub fn get_valid_local_classes(&mut self, lexer: &Lexer<'s>) -> Option<SmallVec<[&'s str; 2]>> {
926 if let SingleLocalClass::Single(range) = &self.is_single {
927 let mut local_classes = self.local_classes.clone();
928 local_classes.push(lexer.slice(range.start, range.end)?);
929 Some(local_classes)
930 } else {
931 self.reset_to_initial();
932 None
933 }
934 }
935
936 pub fn invalidate(&mut self) {
937 if !matches!(self.is_single, SingleLocalClass::AtKeyword) {
938 self.is_single = SingleLocalClass::Invalid;
939 self.local_classes.clear();
940 }
941 }
942
943 pub fn find_local_class(&mut self, start: Pos, end: Pos) {
944 match self.is_single {
945 SingleLocalClass::Initial => {
946 self.is_single = SingleLocalClass::Single(Range::new(start, end))
947 }
948 SingleLocalClass::Single(_) => {
949 self.is_single = SingleLocalClass::Invalid;
950 self.local_classes.clear();
951 }
952 _ => {}
953 };
954 }
955
956 pub fn find_at_keyword(&mut self) {
957 self.is_single = SingleLocalClass::AtKeyword;
958 self.local_classes.clear();
959 }
960
961 pub fn reset_to_initial(&mut self) {
962 self.is_single = SingleLocalClass::Initial;
963 self.local_classes.clear();
964 }
965
966 pub fn find_comma(&mut self, lexer: &Lexer<'s>) -> Option<()> {
967 if let SingleLocalClass::Single(range) = &self.is_single {
968 self.local_classes
969 .push(lexer.slice(range.start, range.end)?);
970 self.is_single = SingleLocalClass::Initial
971 } else {
972 self.is_single = SingleLocalClass::Invalid;
973 }
974 Some(())
975 }
976}
977
978#[derive(Debug, Default, Clone)]
979enum SingleLocalClass {
980 #[default]
981 Initial,
982 Single(Range),
983 AtKeyword,
984 Invalid,
985}
986
987#[derive(Debug)]
988struct InProperty<T: ReservedValues> {
989 reserved: T,
990 rename: Option<Range>,
991 balanced_len: usize,
992}
993
994impl<T: ReservedValues> InProperty<T> {
995 pub fn new(reserved: T, balanced_len: usize) -> Self {
996 Self {
997 reserved,
998 rename: None,
999 balanced_len,
1000 }
1001 }
1002
1003 fn check_reserved(&mut self, ident: &str) -> bool {
1004 self.reserved.check(ident)
1005 }
1006
1007 pub fn reset_reserved(&mut self) {
1008 self.reserved.reset();
1009 }
1010
1011 pub fn set_rename(&mut self, ident: &str, range: Range) {
1012 if self.check_reserved(ident) {
1013 self.rename = Some(range);
1014 }
1015 }
1016
1017 pub fn take_rename(&mut self, balanced_len: usize) -> Option<Range> {
1018 if balanced_len != self.balanced_len {
1020 return None;
1021 }
1022 std::mem::take(&mut self.rename)
1023 }
1024}
1025
1026trait ReservedValues {
1027 fn check(&mut self, ident: &str) -> bool;
1028 fn reset(&mut self);
1029}
1030
1031#[derive(Debug, Default)]
1032struct AnimationReserved {
1033 bits: u32,
1034}
1035
1036impl ReservedValues for AnimationReserved {
1037 fn check(&mut self, ident: &str) -> bool {
1038 match_ignore_ascii_case!(ident, {
1039 "normal" => self.check_and_update(Self::NORMAL),
1040 "reverse" => self.check_and_update(Self::REVERSE),
1041 "alternate" => self.check_and_update(Self::ALTERNATE),
1042 "alternate-reverse" => self.check_and_update(Self::ALTERNATE_REVERSE),
1043 "forwards" => self.check_and_update(Self::FORWARDS),
1044 "backwards" => self.check_and_update(Self::BACKWARDS),
1045 "both" => self.check_and_update(Self::BOTH),
1046 "infinite" => self.check_and_update(Self::INFINITE),
1047 "paused" => self.check_and_update(Self::PAUSED),
1048 "running" => self.check_and_update(Self::RUNNING),
1049 "ease" => self.check_and_update(Self::EASE),
1050 "ease-in" => self.check_and_update(Self::EASE_IN),
1051 "ease-out" => self.check_and_update(Self::EASE_OUT),
1052 "ease-in-out" => self.check_and_update(Self::EASE_IN_OUT),
1053 "linear" => self.check_and_update(Self::LINEAR),
1054 "step-end" => self.check_and_update(Self::STEP_END),
1055 "step-start" => self.check_and_update(Self::STEP_START),
1056 "none" |
1058 "initial" | "inherit" | "unset" | "revert" | "revert-layer" => false,
1060 _ => true,
1061 })
1062 }
1063
1064 fn reset(&mut self) {
1065 self.bits = 0;
1066 }
1067}
1068
1069impl AnimationReserved {
1070 const NORMAL: u32 = 1 << 0;
1071 const REVERSE: u32 = 1 << 1;
1072 const ALTERNATE: u32 = 1 << 2;
1073 const ALTERNATE_REVERSE: u32 = 1 << 3;
1074 const FORWARDS: u32 = 1 << 4;
1075 const BACKWARDS: u32 = 1 << 5;
1076 const BOTH: u32 = 1 << 6;
1077 const INFINITE: u32 = 1 << 7;
1078 const PAUSED: u32 = 1 << 8;
1079 const RUNNING: u32 = 1 << 9;
1080 const EASE: u32 = 1 << 10;
1081 const EASE_IN: u32 = 1 << 11;
1082 const EASE_OUT: u32 = 1 << 12;
1083 const EASE_IN_OUT: u32 = 1 << 13;
1084 const LINEAR: u32 = 1 << 14;
1085 const STEP_END: u32 = 1 << 15;
1086 const STEP_START: u32 = 1 << 16;
1087
1088 fn check_and_update(&mut self, bit: u32) -> bool {
1089 if self.bits & bit == bit {
1090 return true;
1091 }
1092 self.bits |= bit;
1093 false
1094 }
1095}
1096
1097#[derive(Debug, Default)]
1098struct ListStyleReserved;
1099
1100impl ReservedValues for ListStyleReserved {
1101 fn check(&mut self, ident: &str) -> bool {
1102 match_ignore_ascii_case!(ident, {
1103 "decimal"
1105 | "decimal-leading-zero"
1106 | "arabic-indic"
1107 | "armenian"
1108 | "upper-armenian"
1109 | "lower-armenian"
1110 | "bengali"
1111 | "cambodian"
1112 | "khmer"
1113 | "cjk-decimal"
1114 | "devanagari"
1115 | "georgian"
1116 | "gujarati"
1117 | "gurmukhi"
1118 | "hebrew"
1119 | "kannada"
1120 | "lao"
1121 | "malayalam"
1122 | "mongolian"
1123 | "myanmar"
1124 | "oriya"
1125 | "persian"
1126 | "lower-roman"
1127 | "upper-roman"
1128 | "tamil"
1129 | "telugu"
1130 | "thai"
1131 | "tibetan"
1132 | "lower-alpha"
1134 | "lower-latin"
1135 | "upper-alpha"
1136 | "upper-latin"
1137 | "lower-greek"
1138 | "hiragana"
1139 | "hiragana-iroha"
1140 | "katakana"
1141 | "katakana-iroha"
1142 | "disc"
1144 | "circle"
1145 | "square"
1146 | "disclosure-open"
1147 | "disclosure-closed"
1148 | "cjk-earthly-branch"
1150 | "cjk-heavenly-stem"
1151 | "japanese-informal"
1153 | "japanese-formal"
1154 | "korean-hangul-formal"
1155 | "korean-hanja-informal"
1156 | "korean-hanja-formal"
1157 | "simp-chinese-informal"
1158 | "simp-chinese-formal"
1159 | "trad-chinese-informal"
1160 | "trad-chinese-formal"
1161 | "ethiopic-numeric"
1162 | "none"
1164 | "initial"
1166 | "inherit"
1167 | "unset"
1168 | "revert"
1169 | "revert-layer" => false,
1170 _ => true,
1171 })
1172 }
1173
1174 fn reset(&mut self) {}
1175}
1176
1177#[derive(Debug, Default)]
1178struct FontPaletteReserved;
1179
1180impl ReservedValues for FontPaletteReserved {
1181 fn check(&mut self, ident: &str) -> bool {
1182 ident.starts_with("--")
1183 }
1184
1185 fn reset(&mut self) {}
1186}
1187
1188#[derive(Debug, Default)]
1189struct ContainerReserved;
1190
1191impl ReservedValues for ContainerReserved {
1192 fn check(&mut self, ident: &str) -> bool {
1193 match_ignore_ascii_case!(ident, {
1194 "normal"
1195 | "size"
1196 | "inline-size"
1197 | "scroll-state"
1198 | "none"
1199 | "initial"
1200 | "inherit"
1201 | "unset"
1202 | "revert"
1203 | "revert-layer" => false,
1204 _ => true,
1205 })
1206 }
1207
1208 fn reset(&mut self) {}
1209}
1210
1211#[derive(Debug, Clone, Copy)]
1212enum GridPropertyKind {
1213 Generic,
1214 TemplateLike,
1215 TemplateAreas,
1216}
1217
1218fn grid_property_kind(ident: &str) -> Option<GridPropertyKind> {
1219 match_ignore_ascii_case!(ident, {
1220 "grid" | "grid-area" | "grid-column" | "grid-column-end" | "grid-column-start"
1221 | "grid-row" | "grid-row-end" | "grid-row-start" => Some(GridPropertyKind::Generic),
1222 "grid-template" | "grid-template-columns" | "grid-template-rows" => {
1223 Some(GridPropertyKind::TemplateLike)
1224 },
1225 "grid-template-areas" => Some(GridPropertyKind::TemplateAreas),
1226 _ => None,
1227 })
1228}
1229
1230fn is_reserved_grid_ident(ident: &str) -> bool {
1231 match_ignore_ascii_case!(ident, {
1232 "auto"
1233 | "span"
1234 | "auto-flow"
1235 | "dense"
1236 | "row"
1237 | "column"
1238 | "none"
1239 | "subgrid"
1240 | "masonry"
1241 | "max-content"
1242 | "min-content"
1243 | "initial"
1244 | "inherit"
1245 | "unset"
1246 | "revert"
1247 | "revert-layer" => true,
1248 _ => false,
1249 })
1250}
1251
1252pub(crate) fn special_value_is_candidate(
1260 property: PropertyKind,
1261 ident: &str,
1262 icss_symbols: Option<&FxHashSet<&str>>,
1263) -> bool {
1264 if icss_symbols.is_some_and(|symbols| symbols.contains(ident)) {
1265 return true;
1266 }
1267 match property {
1268 PropertyKind::ListStyle => ListStyleReserved.check(ident),
1269 PropertyKind::FontPalette => ident.starts_with("--"),
1270 PropertyKind::Container => ContainerReserved.check(ident),
1271 PropertyKind::Grid => !is_reserved_grid_ident(ident),
1272 PropertyKind::Animation
1273 | PropertyKind::Generic
1274 | PropertyKind::Composes
1275 | PropertyKind::CustomProperty => false,
1276 }
1277}
1278
1279fn is_reserved_container_query_ident(ident: &str) -> bool {
1280 match_ignore_ascii_case!(ident, {
1281 "none" | "and" | "or" | "not" => true,
1282 _ => false,
1283 })
1284}
1285
1286fn parse_grid_template_area_ranges(input: &str, offset: Pos) -> impl Iterator<Item = Range> + '_ {
1287 let bytes = input.as_bytes();
1288 let mut i = 0usize;
1289
1290 std::iter::from_fn(move || {
1291 loop {
1292 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
1293 i += 1;
1294 }
1295 let start = i;
1296 while i < bytes.len() && !bytes[i].is_ascii_whitespace() {
1297 i += 1;
1298 }
1299 if start == i {
1300 return None;
1301 }
1302 if !input[start..i].bytes().all(|c| c == b'.') {
1303 return Some(Range::new(offset + start as Pos, offset + i as Pos));
1304 }
1305 }
1306 })
1307}
1308
1309#[derive(Debug)]
1310pub struct LexDependencies<'s, W> {
1311 dependency_context: DependencyContext<'s>,
1312 mode_data: Option<ModeData<'s>>,
1313 scope: Scope<'s>,
1314 block_nesting_level: u32,
1315 allow_import_at_rule: bool,
1316 balanced: BalancedStack,
1317 is_next_rule_prelude: bool,
1318 scan_context: ScanContext,
1319 selector_square_depth: u32,
1320 selector_fast_forward_enabled: bool,
1321 property_kind: PropertyKind,
1322 in_animation_property: Option<InProperty<AnimationReserved>>,
1323 in_list_style_property: Option<InProperty<ListStyleReserved>>,
1324 in_font_palette_property: Option<InProperty<FontPaletteReserved>>,
1325 in_container_property: Option<InProperty<ContainerReserved>>,
1326 in_grid_property: Option<GridPropertyKind>,
1327 icss_symbols: FxHashSet<&'s str>,
1328 icss_symbol_filter: [u64; 16],
1329 icss_symbol_min_len: usize,
1330 icss_symbol_max_len: usize,
1331 pending_custom_property: Option<Range>,
1332 pending_grid_property: Option<GridPropertyKind>,
1333 handle_warning: W,
1334}
1335
1336impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
1337 pub fn new(handle_warning: W, mode: Mode) -> Self {
1338 Self::with_context(DependencyContext::new(), handle_warning, mode)
1339 }
1340
1341 pub(crate) fn with_context(
1342 dependency_context: DependencyContext<'s>,
1343 handle_warning: W,
1344 mode: Mode,
1345 ) -> Self {
1346 Self {
1347 dependency_context,
1348 mode_data: if mode == Mode::Css {
1349 None
1350 } else {
1351 Some(ModeData::new(mode))
1352 },
1353 scope: Scope::TopLevel,
1354 block_nesting_level: 0,
1355 allow_import_at_rule: true,
1356 balanced: Default::default(),
1357 is_next_rule_prelude: true,
1358 scan_context: ScanContext::TopLevel,
1359 selector_square_depth: 0,
1360 selector_fast_forward_enabled: false,
1361 property_kind: PropertyKind::Generic,
1362 in_animation_property: None,
1363 in_list_style_property: None,
1364 in_font_palette_property: None,
1365 in_container_property: None,
1366 in_grid_property: None,
1367 icss_symbols: Default::default(),
1368 icss_symbol_filter: [0; 16],
1369 icss_symbol_min_len: usize::MAX,
1370 icss_symbol_max_len: 0,
1371 pending_custom_property: None,
1372 pending_grid_property: None,
1373 handle_warning,
1374 }
1375 }
1376
1377 pub fn dependency_context(&self) -> &DependencyContext<'s> {
1378 &self.dependency_context
1379 }
1380
1381 pub fn into_dependency_context(self) -> DependencyContext<'s> {
1382 self.dependency_context
1383 }
1384
1385 #[inline]
1386 fn set_scan_context(&mut self, stream: &mut TokenStream<'_, 's>, scan_context: ScanContext) {
1387 if self.scan_context == scan_context {
1388 return;
1389 }
1390 match self.scan_context {
1391 ScanContext::Selector => self.selector_square_depth = 0,
1392 ScanContext::AtRule => stream.reset_at_rule_scan_state(),
1393 ScanContext::SpecialValue(_) => stream.reset_special_value_scan_state(),
1394 _ => {}
1395 }
1396 self.scan_context = scan_context;
1397 }
1398
1399 pub fn lex_streaming(&mut self, source: &mut Lexer<'s>) {
1401 self.selector_fast_forward_enabled = source.source_end() >= 256;
1402 let mode = self
1403 .mode_data
1404 .as_ref()
1405 .map_or(Mode::Css, ModeData::default_mode);
1406 self.dependency_context
1407 .reserve_estimated_capacity(source.source_end() as usize, mode);
1408 let mut stream = TokenStream::from_lexer(source);
1409 let keep_comments = self.mode_data.as_ref().is_some_and(ModeData::is_pure_mode);
1410 let has_mode = self.mode_data.is_some();
1411 match (keep_comments, has_mode) {
1412 (true, true) => self.lex_streaming_inner::<true, true>(&mut stream),
1413 (true, false) => self.lex_streaming_inner::<true, false>(&mut stream),
1414 (false, true) => self.lex_streaming_inner::<false, true>(&mut stream),
1415 (false, false) => self.lex_streaming_inner::<false, false>(&mut stream),
1416 }
1417 }
1418
1419 #[inline(always)]
1420 fn lex_streaming_inner<const KEEP_COMMENTS: bool, const HAS_MODE: bool>(
1421 &mut self,
1422 stream: &mut TokenStream<'_, 's>,
1423 ) {
1424 loop {
1425 let item = stream.next::<KEEP_COMMENTS>();
1426 let token = item.token;
1427 if token.kind == TokenKind::Eof {
1428 return;
1429 }
1430
1431 let is_trivia = matches!(token.kind, TokenKind::Comment | TokenKind::BadComment);
1432 if !is_trivia && self.scan_context == ScanContext::BlockItem {
1433 self.is_next_rule_prelude = !matches!(
1434 token.kind,
1435 TokenKind::Ident | TokenKind::Function | TokenKind::RightCurlyBracket
1436 );
1437 if self.is_next_rule_prelude
1438 && self.block_nesting_level == 0
1439 && let Some(mode_data) = &mut self.mode_data
1440 {
1441 mode_data.composes_local_classes.reset_to_initial();
1442 }
1443 let scan_context = if self.is_next_rule_prelude {
1444 ScanContext::Selector
1445 } else {
1446 ScanContext::DeclarationName
1447 };
1448 self.set_scan_context(stream, scan_context);
1449 }
1450
1451 if self.scan_context == ScanContext::TopLevel
1452 && self.is_next_rule_prelude
1453 && token.kind != TokenKind::AtKeyword
1454 {
1455 self.set_scan_context(stream, ScanContext::Selector);
1456 }
1457
1458 let mut colon_next = None;
1459 let mut dot_next = None;
1460 if HAS_MODE
1461 && self.scan_context == ScanContext::Selector
1462 && token.kind == TokenKind::Colon
1463 && stream.lexer().could_start_ident_at(token.range.end)
1464 {
1465 let first = stream.peek_significant_skipping_comments::<KEEP_COMMENTS>();
1466 if first.token.kind != TokenKind::Eof {
1467 colon_next = Some(first);
1468 }
1469 } else if HAS_MODE
1470 && self.scan_context == ScanContext::Selector
1471 && token.kind == TokenKind::Delim
1472 && stream.lexer().byte_at(token.range.start) == Some(b'.')
1473 && stream.lexer().could_start_ident_at(token.range.end)
1474 {
1475 let next = stream.peek_significant_skipping_comments::<KEEP_COMMENTS>();
1476 if next.token.kind != TokenKind::Eof {
1477 dot_next = Some(next);
1478 }
1479 }
1480
1481 let mut result = Some(());
1482 match token.kind {
1483 TokenKind::Comment | TokenKind::BadComment => {
1484 result =
1485 self.handle_comment(stream.lexer_mut(), token.range.start, token.range.end);
1486 }
1487 TokenKind::WhiteSpace => {}
1488 TokenKind::QuotedString | TokenKind::BadString => {
1489 result = self.handle_string(
1490 stream.lexer_mut(),
1491 token.range.start,
1492 token.range.end,
1493 token.flags,
1494 );
1495 }
1496 TokenKind::Url => {
1497 result = self.handle_url(
1498 stream.lexer_mut(),
1499 token.range.start,
1500 token.range.end,
1501 token.value_range.start,
1502 token.value_range.end,
1503 token.flags,
1504 );
1505 }
1506 TokenKind::Function => {
1507 result = self.handle_function(
1508 stream,
1509 token.range.start,
1510 token.range.end,
1511 token.flags,
1512 );
1513 }
1514 TokenKind::Ident => {
1515 result =
1516 self.handle_ident(stream, token.range.start, token.range.end, token.flags);
1517 }
1518 TokenKind::AtKeyword => {
1519 result = self.handle_at_keyword(
1520 stream,
1521 token.range.start,
1522 token.range.end,
1523 token.flags,
1524 );
1525 }
1526 TokenKind::IdHash | TokenKind::Hash
1527 if !HAS_MODE || self.scan_context != ScanContext::Selector => {}
1528 TokenKind::IdHash | TokenKind::Hash => {
1529 let id_end = if token.kind == TokenKind::Hash {
1530 token.range.start + 1
1531 } else {
1532 token.range.end
1533 };
1534 result =
1535 self.handle_id(stream.lexer_mut(), token.range.start, id_end, token.flags);
1536 }
1537 TokenKind::Delim
1538 if (!HAS_MODE || self.scan_context != ScanContext::Selector)
1539 && stream.byte_at(token.range.start) == Some(b'#') => {}
1540 TokenKind::Delim if stream.byte_at(token.range.start) == Some(b'#') => {
1541 result = self.handle_id(
1542 stream.lexer_mut(),
1543 token.range.start,
1544 token.range.end,
1545 token.flags,
1546 );
1547 }
1548 TokenKind::Delim
1549 if (!HAS_MODE || self.scan_context != ScanContext::Selector)
1550 && stream.byte_at(token.range.start) == Some(b'.') => {}
1551 TokenKind::Delim if stream.byte_at(token.range.start) == Some(b'.') => {
1552 let mut class_end = token.range.end;
1553 let mut class_flags = token.flags;
1554 let mut consumes_name = false;
1555 if let Some(next) = dot_next
1556 && next.token.kind == TokenKind::Ident
1557 && next.token.range.start == token.range.end
1558 {
1559 class_end = next.token.range.end;
1560 class_flags = next.token.flags;
1561 consumes_name = true;
1562 }
1563 result = self.handle_class(
1564 stream.lexer_mut(),
1565 token.range.start,
1566 class_end,
1567 class_flags,
1568 );
1569 if consumes_name {
1570 stream.next::<KEEP_COMMENTS>();
1571 }
1572 }
1573 TokenKind::Delim if self.scan_context == ScanContext::Selector => {
1574 if self.block_nesting_level == 0
1575 && let Some(mode_data) = &mut self.mode_data
1576 {
1577 mode_data.composes_local_classes.invalidate();
1578 }
1579 }
1580 TokenKind::Colon if self.scan_context == ScanContext::DeclarationName => {
1581 result = self.enter_property_value(stream);
1582 }
1583 TokenKind::Colon if !HAS_MODE => {}
1584 TokenKind::Colon => {
1585 if let Some(next) = colon_next.filter(|next| {
1586 matches!(next.token.kind, TokenKind::Ident | TokenKind::Function)
1587 && next.token.range.start == token.range.end
1588 }) {
1589 let (end, function) =
1590 (next.token.range.end, next.token.kind == TokenKind::Function);
1591 stream.next::<KEEP_COMMENTS>();
1592 result = if function {
1593 self.handle_pseudo_function(
1594 stream,
1595 token.range.start,
1596 end,
1597 next.token.flags,
1598 )
1599 } else {
1600 self.handle_pseudo_class(
1601 stream,
1602 token.range.start,
1603 end,
1604 next.token.flags,
1605 )
1606 };
1607 }
1608 }
1609 TokenKind::LeftSquareBracket if self.scan_context == ScanContext::Selector => {
1610 self.selector_square_depth += 1;
1611 if self.block_nesting_level == 0
1612 && let Some(mode_data) = &mut self.mode_data
1613 {
1614 mode_data.composes_local_classes.invalidate();
1615 }
1616 }
1617 TokenKind::RightSquareBracket if self.scan_context == ScanContext::Selector => {
1618 self.selector_square_depth = self.selector_square_depth.saturating_sub(1);
1619 }
1620 TokenKind::LeftParenthesis => {
1621 result = self.handle_left_parenthesis(
1622 stream.lexer_mut(),
1623 token.range.start,
1624 token.range.end,
1625 );
1626 }
1627 TokenKind::RightParenthesis => {
1628 result = self.handle_right_parenthesis(
1629 stream.lexer_mut(),
1630 item.leading.range.start,
1631 token.range.start,
1632 token.range.end,
1633 );
1634 }
1635 TokenKind::Comma => {
1636 result =
1637 self.handle_comma(stream.lexer_mut(), token.range.start, token.range.end);
1638 }
1639 TokenKind::Semicolon => {
1640 result = self.handle_semicolon(
1641 stream.lexer_mut(),
1642 token.range.start,
1643 token.range.end,
1644 );
1645 }
1646 TokenKind::LeftCurlyBracket => {
1647 result =
1648 self.handle_left_curly_bracket(stream, token.range.start, token.range.end);
1649 }
1650 TokenKind::RightCurlyBracket => {
1651 result =
1652 self.handle_right_curly_bracket(stream, token.range.start, token.range.end);
1653 }
1654 _ => {}
1655 }
1656
1657 if result.is_none() {
1658 return;
1659 }
1660 if token.kind == TokenKind::Semicolon {
1661 match self.scope {
1662 Scope::TopLevel => {
1663 self.set_scan_context(stream, ScanContext::TopLevel);
1664 self.is_next_rule_prelude = true;
1665 }
1666 Scope::InBlock => self.set_scan_context(stream, ScanContext::BlockItem),
1667 _ => {}
1668 }
1669 }
1670 match self.scan_context {
1671 ScanContext::Selector if self.selector_fast_forward_enabled => {
1672 self.fast_forward_selector::<KEEP_COMMENTS, HAS_MODE>(stream);
1673 }
1674 ScanContext::AtRule
1675 if !matches!(self.scope, Scope::InAtImport(_) | Scope::AtImportInvalid) =>
1676 {
1677 self.fast_forward_at_rule::<KEEP_COMMENTS>(stream);
1678 }
1679 ScanContext::SpecialValue(property) => {
1680 self.fast_forward_special_value::<KEEP_COMMENTS>(stream, property);
1681 }
1682 ScanContext::GenericValue if !is_trivia => {
1683 self.fast_forward_generic_value::<KEEP_COMMENTS>(stream);
1684 }
1685 _ => {}
1686 }
1687 }
1688 }
1689
1690 #[inline]
1691 fn fast_forward_selector<const KEEP_COMMENTS: bool, const HAS_MODE: bool>(
1692 &mut self,
1693 stream: &mut TokenStream<'_, 's>,
1694 ) {
1695 let invalidates_composes = stream.fast_forward_selector_if_buffer_empty(
1696 &mut self.selector_square_depth,
1697 KEEP_COMMENTS,
1698 HAS_MODE,
1699 is_css_modules_pure_magic_comment,
1700 );
1701 if invalidates_composes
1702 && self.block_nesting_level == 0
1703 && let Some(mode_data) = &mut self.mode_data
1704 {
1705 mode_data.composes_local_classes.invalidate();
1706 }
1707 }
1708
1709 #[inline]
1710 fn fast_forward_generic_value<const KEEP_COMMENTS: bool>(
1711 &self,
1712 stream: &mut TokenStream<'_, 's>,
1713 ) {
1714 let preserve_strings = matches!(
1715 self.balanced.last(),
1716 Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
1717 );
1718 let preserve_delimiters = !self.balanced.is_empty();
1719 if self.icss_symbols.is_empty() && !KEEP_COMMENTS {
1720 stream.fast_forward_generic_value_without_candidates_if_buffer_empty(
1721 preserve_strings,
1722 preserve_delimiters,
1723 );
1724 } else {
1725 stream.fast_forward_generic_value_if_buffer_empty(
1726 KEEP_COMMENTS,
1727 preserve_strings,
1728 preserve_delimiters,
1729 |ident| self.contains_icss_symbol(ident),
1730 is_css_modules_pure_magic_comment,
1731 );
1732 }
1733 }
1734
1735 #[inline]
1736 fn fast_forward_at_rule<const KEEP_COMMENTS: bool>(&self, stream: &mut TokenStream<'_, 's>) {
1737 let preserve_strings = matches!(
1738 self.balanced.last(),
1739 Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
1740 );
1741 let preserve_delimiters = !self.balanced.is_empty();
1742 stream.fast_forward_at_rule_if_buffer_empty(
1743 KEEP_COMMENTS,
1744 preserve_strings,
1745 preserve_delimiters,
1746 |ident| self.contains_icss_symbol(ident),
1747 is_css_modules_pure_magic_comment,
1748 );
1749 }
1750
1751 #[inline]
1752 fn fast_forward_special_value<const KEEP_COMMENTS: bool>(
1753 &self,
1754 stream: &mut TokenStream<'_, 's>,
1755 property: PropertyKind,
1756 ) {
1757 if property == PropertyKind::Animation {
1760 return;
1761 }
1762 let preserve_strings = property == PropertyKind::Grid
1763 || matches!(
1764 self.balanced.last(),
1765 Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
1766 );
1767 let preserve_delimiters = !self.balanced.is_empty();
1768 let icss_symbols = (!self.icss_symbols.is_empty()).then_some(&self.icss_symbols);
1769 stream.fast_forward_special_value_if_buffer_empty(
1770 KEEP_COMMENTS,
1771 preserve_strings,
1772 preserve_delimiters,
1773 property,
1774 icss_symbols,
1775 );
1776 }
1777
1778 #[inline]
1779 fn icss_symbol_filter_bit(value: &str) -> (usize, u64) {
1780 let bytes = value.as_bytes();
1781 let len = bytes.len();
1782 let first = bytes.first().copied().unwrap_or_default() as usize;
1783 let middle = bytes.get(len / 2).copied().unwrap_or_default() as usize;
1784 let last = bytes.last().copied().unwrap_or_default() as usize;
1785 let mut hash = len.wrapping_mul(0x9e37_79b1);
1786 hash ^= first.wrapping_mul(0x85eb_ca6b);
1787 hash ^= middle.wrapping_mul(0xc2b2_ae35);
1788 hash ^= last.wrapping_mul(0x27d4_eb2f);
1789 hash ^= hash >> 16;
1790 let bit = hash & 1023;
1791 (bit >> 6, 1u64 << (bit & 63))
1792 }
1793
1794 #[inline]
1795 fn contains_icss_symbol(&self, value: &str) -> bool {
1796 let len = value.len();
1797 if len < self.icss_symbol_min_len || len > self.icss_symbol_max_len {
1798 return false;
1799 }
1800 let (word, bit) = Self::icss_symbol_filter_bit(value);
1801 self.icss_symbol_filter[word] & bit != 0 && self.icss_symbols.contains(value)
1802 }
1803
1804 fn insert_icss_symbol(&mut self, value: &'s str) {
1805 let len = value.len();
1806 self.icss_symbol_min_len = self.icss_symbol_min_len.min(len);
1807 self.icss_symbol_max_len = self.icss_symbol_max_len.max(len);
1808 let (word, bit) = Self::icss_symbol_filter_bit(value);
1809 self.icss_symbol_filter[word] |= bit;
1810 self.icss_symbols.insert(value);
1811 }
1812
1813 fn enter_property_value(&mut self, stream: &mut TokenStream<'_, 's>) -> Option<()> {
1814 match self.property_kind {
1815 PropertyKind::Animation => self.enter_animation_property(),
1816 PropertyKind::ListStyle => self.enter_list_style_property(),
1817 PropertyKind::FontPalette => self.enter_font_palette_property(),
1818 PropertyKind::Container => self.enter_container_property(),
1819 PropertyKind::Grid => {
1820 if let Some(kind) = self.pending_grid_property.take() {
1821 self.enter_grid_property(kind);
1822 }
1823 }
1824 PropertyKind::CustomProperty => {
1825 if self
1826 .mode_data
1827 .as_ref()
1828 .is_some_and(ModeData::is_property_local_mode)
1829 && let Some(range) = self.pending_custom_property.take()
1830 {
1831 self.dependency_context
1832 .push_dependency(Dependency::LocalVarDecl {
1833 name: stream.lexer().slice(range.start + 2, range.end)?,
1834 range,
1835 });
1836 }
1837 }
1838 PropertyKind::Generic | PropertyKind::Composes => {}
1839 }
1840 self.pending_custom_property = None;
1841 self.pending_grid_property = None;
1842 self.set_scan_context(stream, ScanContext::for_property(self.property_kind));
1843 Some(())
1844 }
1845
1846 fn classify_property(
1847 name: &str,
1848 property_local_mode: bool,
1849 ) -> (PropertyKind, Option<GridPropertyKind>) {
1850 if name.eq_ignore_ascii_case("composes") || name.eq_ignore_ascii_case("compose-with") {
1851 return (PropertyKind::Composes, None);
1852 }
1853 if !property_local_mode {
1854 return (PropertyKind::Generic, None);
1855 }
1856 if name.starts_with("--") {
1857 return (PropertyKind::CustomProperty, None);
1858 }
1859 if name.eq_ignore_ascii_case("animation")
1860 || name.eq_ignore_ascii_case("animation-name")
1861 || with_vendor_prefixed_eq(name, "animation", false)
1862 || with_vendor_prefixed_eq(name, "animation-name", false)
1863 {
1864 return (PropertyKind::Animation, None);
1865 }
1866 if name.eq_ignore_ascii_case("list-style") || name.eq_ignore_ascii_case("list-style-type") {
1867 return (PropertyKind::ListStyle, None);
1868 }
1869 if name.eq_ignore_ascii_case("font-palette") {
1870 return (PropertyKind::FontPalette, None);
1871 }
1872 if name.eq_ignore_ascii_case("container") || name.eq_ignore_ascii_case("container-name") {
1873 return (PropertyKind::Container, None);
1874 }
1875 if let Some(grid) = grid_property_kind(name) {
1876 return (PropertyKind::Grid, Some(grid));
1877 }
1878 (PropertyKind::Generic, None)
1879 }
1880
1881 fn classify_at_rule(name: &str) -> AtRuleKind {
1882 match name.len() {
1883 6 if name.eq_ignore_ascii_case("@value") => AtRuleKind::Value,
1884 6 if name.eq_ignore_ascii_case("@scope") => AtRuleKind::Scope,
1885 7 if name.eq_ignore_ascii_case("@import") => AtRuleKind::Import,
1886 8 if name.eq_ignore_ascii_case("@charset") => AtRuleKind::Charset,
1887 9 if name.eq_ignore_ascii_case("@function") => AtRuleKind::Function,
1888 9 if name.eq_ignore_ascii_case("@property") => AtRuleKind::Property,
1889 10 if name.eq_ignore_ascii_case("@namespace") => AtRuleKind::Namespace,
1890 10 if name.eq_ignore_ascii_case("@keyframes") => AtRuleKind::Keyframes,
1891 10 if name.eq_ignore_ascii_case("@container") => AtRuleKind::Container,
1892 14 if name.eq_ignore_ascii_case("@counter-style") => AtRuleKind::CounterStyle,
1893 20 if name.eq_ignore_ascii_case("@font-palette-values") => {
1894 AtRuleKind::FontPaletteValues
1895 }
1896 _ if with_vendor_prefixed_eq(name, "keyframes", true) => AtRuleKind::Keyframes,
1897 _ => AtRuleKind::Other,
1898 }
1899 }
1900
1901 fn get_media(&self, lexer: &Lexer<'s>, start: Pos, end: Pos) -> Option<&'s str> {
1902 let media = lexer.slice(start, end)?;
1903 let bytes = media.as_bytes();
1904 let mut position = 0;
1905 loop {
1906 while position < bytes.len() && bytes[position].is_ascii_whitespace() {
1907 position += 1;
1908 }
1909 if position + 1 < bytes.len() && bytes[position] == b'/' && bytes[position + 1] == b'*'
1910 {
1911 let Some(relative_end) = media[position + 2..].find("*/") else {
1912 break;
1913 };
1914 position += relative_end + 4;
1915 continue;
1916 }
1917 break;
1918 }
1919 if position == bytes.len() {
1920 return None;
1921 }
1922 Some(media)
1923 }
1924
1925 fn lex_charset_at_rule(&mut self, stream: &mut TokenStream<'_, 's>, start: Pos) -> Option<()> {
1926 let string = stream.next_parser_token().token;
1927 if string.kind != TokenKind::QuotedString {
1928 return Some(());
1929 }
1930
1931 let next = stream.next_parser_token().token;
1932 if next.kind == TokenKind::Semicolon {
1933 self.dependency_context
1934 .push_dependency(Dependency::Charset {
1935 value: stream.slice(string.value_range.start, string.value_range.end)?,
1936 range: Range::new(start, next.range.end),
1937 });
1938 }
1939 Some(())
1940 }
1941
1942 fn enter_animation_property(&mut self) {
1943 self.in_animation_property = Some(InProperty::new(
1944 AnimationReserved::default(),
1945 self.balanced.len(),
1946 ));
1947 }
1948
1949 fn exit_animation_property(&mut self) {
1950 self.in_animation_property = None;
1951 }
1952
1953 fn enter_list_style_property(&mut self) {
1954 self.in_list_style_property = Some(InProperty::new(ListStyleReserved, self.balanced.len()));
1955 }
1956
1957 fn exit_list_style_property(&mut self) {
1958 self.in_list_style_property = None;
1959 }
1960
1961 fn enter_font_palette_property(&mut self) {
1962 self.in_font_palette_property =
1963 Some(InProperty::new(FontPaletteReserved, self.balanced.len()));
1964 }
1965
1966 fn exit_font_palette_property(&mut self) {
1967 self.in_font_palette_property = None;
1968 }
1969
1970 fn enter_container_property(&mut self) {
1971 self.in_container_property = Some(InProperty::new(ContainerReserved, self.balanced.len()));
1972 }
1973
1974 fn exit_container_property(&mut self) {
1975 self.in_container_property = None;
1976 }
1977
1978 fn enter_grid_property(&mut self, kind: GridPropertyKind) {
1979 self.in_grid_property = Some(kind);
1980 }
1981
1982 fn exit_grid_property(&mut self) {
1983 self.in_grid_property = None;
1984 }
1985
1986 fn lex_icss_import(&mut self, stream: &mut TokenStream<'_, 's>) -> Option<()> {
1987 let (start, end) = self.consume_icss_import_path(stream)?;
1988 let right_parenthesis = stream.next_parser_token().token;
1989 if right_parenthesis.kind != TokenKind::RightParenthesis {
1990 self.handle_warning.handle_warning(Warning {
1991 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
1992 kind: WarningKind::Unexpected {
1993 message: "Expected ')' during parsing of ':import()'",
1994 },
1995 });
1996 return Some(());
1997 }
1998 self.dependency_context
1999 .push_dependency(Dependency::ICSSImportFrom {
2000 path: stream.slice(start, end)?,
2001 });
2002 let left_curly = stream.next_parser_token().token;
2003 if left_curly.kind != TokenKind::LeftCurlyBracket {
2004 self.handle_warning.handle_warning(Warning {
2005 range: Range::new(left_curly.range.start, left_curly.range.end),
2006 kind: WarningKind::Unexpected {
2007 message: "Expected '{' during parsing of ':import()'",
2008 },
2009 });
2010 return Some(());
2011 }
2012 loop {
2013 let first = stream.next_parser_token().token;
2014 if first.kind == TokenKind::Eof {
2015 return None;
2016 }
2017 if first.kind == TokenKind::RightCurlyBracket {
2018 break;
2019 }
2020 let prop_start = first.range.start;
2021 let prop_end = self.consume_icss_export_prop(stream, first)?;
2022 let colon = stream.peek_significant_skipping_comments::<true>().token;
2023 if colon.kind != TokenKind::Colon {
2024 self.handle_warning.handle_warning(Warning {
2025 range: Range::new(colon.range.start, colon.range.end),
2026 kind: WarningKind::Unexpected {
2027 message: "Expected ':' during parsing of ':import'",
2028 },
2029 });
2030 return Some(());
2031 }
2032 stream.next_parser_token();
2033 let value_start_token = stream.next_parser_token().token;
2034 if value_start_token.kind == TokenKind::Eof {
2035 return None;
2036 }
2037 let value_start = value_start_token.range.start;
2038 let value_end = self.consume_icss_value(stream, value_start_token)?;
2039 let delimiter = stream.next_parser_token().token;
2040 self.dependency_context
2041 .push_dependency(Dependency::ICSSImportValue {
2042 prop: stream
2043 .slice(prop_start, prop_end)?
2044 .trim_end_matches(is_css_white_space_char),
2045 value: stream
2046 .slice(value_start, value_end)?
2047 .trim_end_matches(is_css_white_space_char),
2048 });
2049 self.insert_icss_symbol(
2050 stream
2051 .slice(prop_start, prop_end)?
2052 .trim_end_matches(is_css_white_space_char),
2053 );
2054 if delimiter.kind == TokenKind::RightCurlyBracket {
2055 break;
2056 }
2057 }
2058 Some(())
2059 }
2060
2061 fn consume_icss_import_path(&self, stream: &mut TokenStream<'_, 's>) -> Option<(Pos, Pos)> {
2062 let first = stream.next_parser_token().token;
2063 if first.kind == TokenKind::Eof {
2064 return None;
2065 }
2066 let start = first.range.start;
2067 let mut end = start;
2068 if first.kind == TokenKind::RightParenthesis {
2069 return Some((start, end));
2070 }
2071 end = first.range.end;
2072 loop {
2073 let token = stream.peek_significant_skipping_comments::<true>().token;
2074 if token.kind == TokenKind::Eof {
2075 return None;
2076 }
2077 if token.kind == TokenKind::RightParenthesis {
2078 return Some((start, end));
2079 }
2080 stream.next_parser_token();
2081 if first.kind != TokenKind::QuotedString {
2082 end = token.range.end;
2083 }
2084 }
2085 }
2086
2087 fn consume_icss_export_prop(
2088 &self,
2089 stream: &mut TokenStream<'_, 's>,
2090 first: Token,
2091 ) -> Option<Pos> {
2092 if matches!(
2093 first.kind,
2094 TokenKind::Colon | TokenKind::RightCurlyBracket | TokenKind::Semicolon
2095 ) {
2096 return Some(first.range.start);
2097 }
2098 loop {
2099 let token = stream.peek_parser_token();
2100 if token.token.kind == TokenKind::Eof {
2101 return None;
2102 }
2103 if let Some(first_comment_start) = token.leading.first_comment_start {
2104 return Some(first_comment_start);
2105 }
2106 if matches!(
2107 token.token.kind,
2108 TokenKind::Colon | TokenKind::RightCurlyBracket | TokenKind::Semicolon
2109 ) {
2110 return Some(token.token.range.start);
2111 }
2112 stream.next_parser_token();
2113 }
2114 }
2115
2116 fn consume_icss_value(&self, stream: &mut TokenStream<'_, 's>, first: Token) -> Option<Pos> {
2117 if matches!(
2118 first.kind,
2119 TokenKind::RightCurlyBracket | TokenKind::Semicolon
2120 ) {
2121 return Some(first.range.start);
2122 }
2123 loop {
2124 let token = stream.peek::<true>().token;
2125 if token.kind == TokenKind::Eof {
2126 return None;
2127 }
2128 if matches!(
2129 token.kind,
2130 TokenKind::RightCurlyBracket | TokenKind::Semicolon
2131 ) {
2132 return Some(token.range.start);
2133 }
2134 stream.next::<true>();
2135 }
2136 }
2137
2138 fn lex_value_at_rule(&mut self, stream: &mut TokenStream<'_, 's>, start: Pos) -> Option<()> {
2139 let input = stream.slice_trusted(0, stream.source_end());
2140 let checkpoint = self
2141 .dependency_context
2142 .value_at_rule_import_items_checkpoint();
2143 let mut parser = ValueAtRuleStream::new(input);
2144 loop {
2145 let token = stream.next_parser_token().token;
2146 match token.kind {
2147 TokenKind::Eof => return None,
2148 _ => {
2149 if token.kind == TokenKind::Semicolon && parser.depth == 0 {
2150 parser.params_end = token.range.start;
2151 break;
2152 }
2153 let depth_after = (parser.depth + u32::from(is_open_token(token.kind)))
2154 .saturating_sub(u32::from(is_close_token(token.kind)));
2155 if token.kind == TokenKind::RightCurlyBracket && depth_after == 0 {
2156 parser.params_end = token.range.start;
2157 break;
2158 }
2159 parser.push(&mut self.dependency_context, token);
2160 }
2161 }
2162 }
2163 let at_rule_end = parser.params_end.max(stream.consumed_pos());
2164 let import = parser.last_two().is_some_and(|(penultimate, last)| {
2165 penultimate.kind == TokenKind::Ident
2166 && parser
2167 .input
2168 .get(penultimate.range.start as usize..penultimate.range.end as usize)
2169 .is_some_and(|text| text.eq_ignore_ascii_case("from"))
2170 && parser.from_pos == Some(penultimate.range.start)
2171 && parser
2172 .input
2173 .get(penultimate.range.end as usize..last.range.start as usize)
2174 .is_some_and(trivia_only)
2175 });
2176 if import {
2177 let (_, last) = parser.last_two().unwrap();
2178 let from = &parser.input[last.range.start as usize..last.range.end as usize];
2179 let item_end = parser.from_prev_end.unwrap_or(parser.item_end);
2180 parser.finish_item(&mut self.dependency_context, item_end);
2181 let items = self
2182 .dependency_context
2183 .finish_value_at_rule_import_items(checkpoint);
2184 if items.is_empty() {
2185 self.handle_warning.handle_warning(Warning {
2186 range: Range::new(start, at_rule_end),
2187 kind: WarningKind::Unexpected {
2188 message: "Broken '@value' at-rule",
2189 },
2190 });
2191 } else {
2192 self.dependency_context
2193 .push_dependency(Dependency::ICSSImportFrom { path: from });
2194 for index in items.as_usize_range() {
2195 let item = self.dependency_context.value_at_rule_import_item(index);
2196 self.dependency_context
2197 .push_dependency(Dependency::ICSSImportValue {
2198 prop: item.local_name(),
2199 value: item.import_name(),
2200 });
2201 self.insert_icss_symbol(item.local_name());
2202 self.dependency_context
2203 .push_dependency(Dependency::ICSSExportValue {
2204 prop: item.local_name(),
2205 value: item.local_name(),
2206 });
2207 }
2208 }
2209 } else {
2210 self.dependency_context
2211 .truncate_value_at_rule_import_items(checkpoint);
2212 let local_name;
2213 let value;
2214 let has_colon;
2215 if let Some(colon) = parser.first_colon {
2216 local_name = &parser.input[parser
2217 .first_significant
2218 .unwrap_or((colon.split, colon.split))
2219 .0 as usize
2220 ..colon.prev_end as usize];
2221 let raw = &parser.input[colon.end as usize..parser.params_end as usize];
2222 value = if parser.first_colon_tokens_after > 0 {
2223 trim_slice(raw)
2224 } else {
2225 raw
2226 };
2227 has_colon = true;
2228 } else if let Some((first_start, first_end)) = parser.first_significant {
2229 local_name = &parser.input[first_start as usize..first_end as usize];
2230 let raw = &parser.input[first_end as usize..parser.params_end as usize];
2231 value = if parser.significant_count > 1 {
2232 trim_slice(raw)
2233 } else {
2234 raw
2235 };
2236 has_colon = false;
2237 } else {
2238 local_name = "";
2239 value = "";
2240 has_colon = false;
2241 }
2242 if local_name.is_empty() || (!has_colon && value.is_empty()) {
2243 self.handle_warning.handle_warning(Warning {
2244 range: Range::new(start, at_rule_end),
2245 kind: WarningKind::Unexpected {
2246 message: "Broken '@value' at-rule",
2247 },
2248 });
2249 }
2250 if !local_name.is_empty() {
2251 self.dependency_context
2252 .push_dependency(Dependency::ICSSExportValue {
2253 prop: local_name,
2254 value,
2255 });
2256 self.insert_icss_symbol(local_name);
2257 }
2258 }
2259 self.dependency_context
2260 .push_dependency(Dependency::Replace {
2261 content: "",
2262 range: Range::new(start, at_rule_end),
2263 });
2264 Some(())
2265 }
2266
2267 fn lex_icss_export(&mut self, stream: &mut TokenStream<'_, 's>) -> Option<()> {
2268 let left_curly = stream.next_parser_token().token;
2269 if left_curly.kind != TokenKind::LeftCurlyBracket {
2270 self.handle_warning.handle_warning(Warning {
2271 range: Range::new(left_curly.range.start, left_curly.range.end),
2272 kind: WarningKind::Unexpected {
2273 message: "Expected '{' during parsing of ':export'",
2274 },
2275 });
2276 return Some(());
2277 }
2278 loop {
2279 let first = stream.next_parser_token().token;
2280 if first.kind == TokenKind::Eof {
2281 return None;
2282 }
2283 if first.kind == TokenKind::RightCurlyBracket {
2284 break;
2285 }
2286 let prop_start = first.range.start;
2287 let prop_end = self.consume_icss_export_prop(stream, first)?;
2288 let colon = stream.peek_significant_skipping_comments::<true>().token;
2289 if colon.kind != TokenKind::Colon {
2290 self.handle_warning.handle_warning(Warning {
2291 range: Range::new(colon.range.start, colon.range.end),
2292 kind: WarningKind::Unexpected {
2293 message: "Expected ':' during parsing of ':export'",
2294 },
2295 });
2296 return Some(());
2297 }
2298 stream.next_parser_token();
2299 let value_start_token = stream.next_parser_token().token;
2300 if value_start_token.kind == TokenKind::Eof {
2301 return None;
2302 }
2303 let value_start = value_start_token.range.start;
2304 let value_end = self.consume_icss_value(stream, value_start_token)?;
2305 let delimiter = stream.next_parser_token().token;
2306 let value = stream
2307 .slice(value_start, value_end)?
2308 .trim_end_matches(is_css_white_space_char);
2309 self.dependency_context
2310 .push_dependency(Dependency::ICSSExportValue {
2311 prop: stream
2312 .slice(prop_start, prop_end)?
2313 .trim_end_matches(is_css_white_space_char),
2314 value,
2315 });
2316 self.insert_icss_symbol(
2317 stream
2318 .slice(prop_start, prop_end)?
2319 .trim_end_matches(is_css_white_space_char),
2320 );
2321 if delimiter.kind == TokenKind::RightCurlyBracket {
2322 break;
2323 }
2324 }
2325 Some(())
2326 }
2327
2328 fn lex_local_var(&mut self, stream: &mut TokenStream<'_, 's>) -> Option<()> {
2329 let name_token = stream.next_parser_token().token;
2330 let start = name_token.range.start;
2331 if name_token.kind != TokenKind::Ident
2332 || !stream.slice(start, name_token.range.end)?.starts_with("--")
2333 {
2334 self.handle_warning.handle_warning(Warning {
2335 kind: WarningKind::Unexpected {
2336 message: "Expected starts with '--' during parsing of 'var()'",
2337 },
2338 range: Range::new(start, (start + 2).min(name_token.range.end)),
2339 });
2340 return Some(());
2341 }
2342 let end = name_token.range.end;
2343 let next = stream.peek_significant_skipping_comments::<true>().token;
2344 let from = if next.kind == TokenKind::Ident
2345 && stream.slice(next.range.start, next.range.end)? == "from"
2346 {
2347 stream.next_parser_token();
2348 let path = stream.peek_significant_skipping_comments::<true>().token;
2349 let path_start = path.range.start;
2350 let path_end = path.range.end;
2351 if !matches!(path.kind, TokenKind::QuotedString | TokenKind::Ident) {
2352 self.handle_warning.handle_warning(Warning {
2353 range: Range::new(path_start, path_end),
2354 kind: WarningKind::Unexpected {
2355 message: "Expected string or ident during parsing of 'composes'",
2356 },
2357 });
2358 return Some(());
2359 }
2360 stream.next_parser_token();
2361 Some(stream.slice(path_start, path_end)?)
2362 } else {
2363 None
2364 };
2365 self.dependency_context
2366 .push_dependency(Dependency::LocalVar {
2367 name: stream.slice(start + 2, end)?,
2368 range: Range::new(start, end),
2369 from,
2370 });
2371 Some(())
2372 }
2373
2374 fn lex_local_dashed_ident_decl(
2375 &mut self,
2376 stream: &mut TokenStream<'_, 's>,
2377 local_decl_dependency: impl FnOnce(&'s str, Range) -> Dependency<'s>,
2378 dashed_warning: impl FnOnce(Range) -> Warning<'s>,
2379 left_curly_warning: impl FnOnce(Range) -> Warning<'s>,
2380 ) -> Option<()> {
2381 let name_token = stream.next_parser_token().token;
2382 let start = name_token.range.start;
2383 if name_token.kind != TokenKind::Ident
2384 || !stream.slice(start, name_token.range.end)?.starts_with("--")
2385 {
2386 self.handle_warning
2387 .handle_warning(dashed_warning(Range::new(
2388 start,
2389 (start + 2).min(name_token.range.end),
2390 )));
2391 return Some(());
2392 }
2393 let end = name_token.range.end;
2394 self.dependency_context
2395 .push_dependency(local_decl_dependency(
2396 stream.slice(start + 2, end)?,
2397 Range::new(start, end),
2398 ));
2399 let left_curly = stream.peek_significant_skipping_comments::<true>().token;
2400 if left_curly.kind != TokenKind::LeftCurlyBracket {
2401 self.handle_warning
2402 .handle_warning(left_curly_warning(Range::new(
2403 left_curly.range.start,
2404 left_curly.range.end,
2405 )));
2406 return Some(());
2407 }
2408 Some(())
2409 }
2410
2411 fn lex_local_keyframes_decl(&mut self, stream: &mut TokenStream<'_, 's>) -> Option<()> {
2412 let mut is_function = false;
2413 let first = stream.next_parser_token().token;
2414 let name_token = if first.kind == TokenKind::Colon {
2415 let pseudo_start = first.range.start;
2416 let pseudo_name = stream.next_parser_token().token;
2417 let pseudo_end = if matches!(pseudo_name.kind, TokenKind::Ident | TokenKind::Function) {
2418 pseudo_name.range.end
2419 } else {
2420 first.range.end
2421 };
2422 let pseudo = stream.slice(pseudo_start, pseudo_end)?;
2423 if pseudo_name.kind == TokenKind::Function {
2424 self.handle_pseudo_function(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2425 } else if pseudo_name.kind == TokenKind::Ident {
2426 self.handle_pseudo_class(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2427 }
2428 let mode_data = self.mode_data.as_ref().unwrap();
2429 if mode_data.is_pure_mode()
2430 && !mode_data.is_pure_check_disabled()
2431 && (pseudo.eq_ignore_ascii_case(":global(")
2432 || pseudo.eq_ignore_ascii_case(":global"))
2433 {
2434 self.handle_warning.handle_warning(Warning {
2435 range: Range::new(pseudo_start, pseudo_end),
2436 kind: WarningKind::NotPure {
2437 message: "'@keyframes :global' is not allowed in pure mode",
2438 },
2439 });
2440 }
2441 is_function =
2442 pseudo.eq_ignore_ascii_case(":local(") || pseudo.eq_ignore_ascii_case(":global(");
2443 if !is_function
2444 && !pseudo.eq_ignore_ascii_case(":local")
2445 && !pseudo.eq_ignore_ascii_case(":global")
2446 {
2447 self.handle_warning.handle_warning(Warning {
2448 range: Range::new(pseudo_start, pseudo_end),
2449 kind: WarningKind::Unexpected {
2450 message: "Expected ':local', ':local()', ':global', or ':global()' during parsing of '@keyframes' name",
2451 }
2452 });
2453 return Some(());
2454 }
2455 stream.next_parser_token().token
2456 } else {
2457 first
2458 };
2459
2460 let start = name_token.range.start;
2461 if name_token.kind != TokenKind::Ident {
2462 self.handle_warning.handle_warning(Warning {
2463 range: Range::new(start, start.saturating_add(2).min(stream.source_end())),
2464 kind: WarningKind::Unexpected {
2465 message: "Expected ident during parsing of '@keyframes' name",
2466 },
2467 });
2468 return Some(());
2469 }
2470 let end = name_token.range.end;
2471 if self.mode_data.as_ref().unwrap().is_current_local_mode() {
2472 let name = stream.slice(start, end)?;
2473 self.dependency_context
2474 .push_dependency(Dependency::LocalKeyframesDecl {
2475 name,
2476 range: Range::new(start, end),
2477 });
2478 }
2479 if is_function {
2480 let right_parenthesis = stream.peek_significant_skipping_comments::<true>().token;
2481 if right_parenthesis.kind != TokenKind::RightParenthesis {
2482 self.handle_warning.handle_warning(Warning {
2483 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2484 kind: WarningKind::Unexpected {
2485 message: "Expected ')' during parsing of '@keyframes :local(' or '@keyframes :global('",
2486 },
2487 });
2488 return Some(());
2489 }
2490 stream.next_parser_token();
2491 self.dependency_context
2492 .push_dependency(Dependency::Replace {
2493 content: "",
2494 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2495 });
2496 let mode_data = self.mode_data.as_mut().unwrap();
2497 mode_data.inside_mode_function -= 1;
2498 self.balanced.pop_without_moda_data();
2499 }
2500 let left_curly = stream.peek_significant_skipping_comments::<true>().token;
2501 if left_curly.kind != TokenKind::LeftCurlyBracket {
2502 self.handle_warning.handle_warning(Warning {
2503 range: Range::new(left_curly.range.start, left_curly.range.end),
2504 kind: WarningKind::Unexpected {
2505 message: "Expected '{' during parsing of '@keyframes'",
2506 },
2507 });
2508 return Some(());
2509 }
2510 Some(())
2511 }
2512
2513 fn handle_local_keyframes_dependency(&mut self, lexer: &Lexer<'s>) -> Option<()> {
2514 let animation = self.in_animation_property.as_mut().unwrap();
2515 if let Some(range) = animation.take_rename(self.balanced.len()) {
2516 self.dependency_context
2517 .push_dependency(Dependency::LocalKeyframes {
2518 name: lexer.slice(range.start, range.end)?,
2519 range,
2520 });
2521 }
2522 animation.reset_reserved();
2523 Some(())
2524 }
2525
2526 fn lex_local_counter_style_decl(&mut self, stream: &mut TokenStream<'_, 's>) -> Option<()> {
2527 let name_token = stream.next_parser_token().token;
2528 let start = name_token.range.start;
2529 if name_token.kind != TokenKind::Ident {
2530 self.handle_warning.handle_warning(Warning {
2531 range: Range::new(start, name_token.range.end),
2532 kind: WarningKind::Unexpected {
2533 message: "Expected ident during parsing of '@counter-style'",
2534 },
2535 });
2536 return Some(());
2537 }
2538 let end = name_token.range.end;
2539 self.dependency_context
2540 .push_dependency(Dependency::LocalCounterStyleDecl {
2541 name: stream.slice(start, end)?,
2542 range: Range::new(start, end),
2543 });
2544 let left_curly = stream.peek_significant_skipping_comments::<true>().token;
2545 if left_curly.kind != TokenKind::LeftCurlyBracket {
2546 self.handle_warning.handle_warning(Warning {
2547 range: Range::new(left_curly.range.start, left_curly.range.end),
2548 kind: WarningKind::Unexpected {
2549 message: "Expected '{' during parsing of '@counter-style'",
2550 },
2551 });
2552 return Some(());
2553 }
2554 Some(())
2555 }
2556
2557 fn lex_local_container_at_rule(&mut self, stream: &mut TokenStream<'_, 's>) -> Option<()> {
2558 let name_token = stream.next_parser_token().token;
2559 if name_token.kind == TokenKind::LeftParenthesis {
2560 return Some(());
2561 }
2562 if name_token.kind != TokenKind::Ident {
2563 return Some(());
2564 }
2565 let start = name_token.range.start;
2566 let end = name_token.range.end;
2567 let ident = stream.slice(start, end)?;
2568 if is_reserved_container_query_ident(ident) {
2569 return Some(());
2570 }
2571 if self.mode_data.as_ref().unwrap().is_current_local_mode() {
2572 self.dependency_context
2573 .push_dependency(Dependency::LocalContainer {
2574 name: ident,
2575 range: Range::new(start, end),
2576 });
2577 }
2578 Some(())
2579 }
2580
2581 fn lex_local_function_decl(&mut self, stream: &mut TokenStream<'_, 's>) -> Option<()> {
2582 let mut is_function = false;
2583 let first = stream.next_parser_token().token;
2584 let name_token = if first.kind == TokenKind::Colon {
2585 let pseudo_start = first.range.start;
2586 let pseudo_name = stream.next_parser_token().token;
2587 let pseudo_end = if matches!(pseudo_name.kind, TokenKind::Ident | TokenKind::Function) {
2588 pseudo_name.range.end
2589 } else {
2590 first.range.end
2591 };
2592 let pseudo = stream.slice(pseudo_start, pseudo_end)?;
2593 if pseudo_name.kind == TokenKind::Function {
2594 self.handle_pseudo_function(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2595 } else if pseudo_name.kind == TokenKind::Ident {
2596 self.handle_pseudo_class(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2597 }
2598 let mode_data = self.mode_data.as_ref().unwrap();
2599 if mode_data.is_pure_mode()
2600 && !mode_data.is_pure_check_disabled()
2601 && (pseudo.eq_ignore_ascii_case(":global(")
2602 || pseudo.eq_ignore_ascii_case(":global"))
2603 {
2604 self.handle_warning.handle_warning(Warning {
2605 range: Range::new(pseudo_start, pseudo_end),
2606 kind: WarningKind::NotPure {
2607 message: "'@function :global' is not allowed in pure mode",
2608 },
2609 });
2610 }
2611 is_function =
2612 pseudo.eq_ignore_ascii_case(":local(") || pseudo.eq_ignore_ascii_case(":global(");
2613 if !is_function
2614 && !pseudo.eq_ignore_ascii_case(":local")
2615 && !pseudo.eq_ignore_ascii_case(":global")
2616 {
2617 self.handle_warning.handle_warning(Warning {
2618 range: Range::new(pseudo_start, pseudo_end),
2619 kind: WarningKind::Unexpected {
2620 message:
2621 "Expected ':local', ':local()', ':global', or ':global()' during parsing of '@function' name",
2622 },
2623 });
2624 return Some(());
2625 }
2626 stream.next_parser_token().token
2627 } else {
2628 first
2629 };
2630
2631 let name_range = if name_token.kind == TokenKind::Function {
2632 name_token.value_range
2633 } else {
2634 name_token.range
2635 };
2636 let start = name_range.start;
2637 let end = name_range.end;
2638 let name = stream.slice_trusted(start, end);
2639 if name_token.kind != TokenKind::Ident && name_token.kind != TokenKind::Function
2640 || !name.starts_with("--")
2641 {
2642 self.handle_warning.handle_warning(Warning {
2643 range: Range::new(start, start.saturating_add(2).min(stream.source_end())),
2644 kind: WarningKind::Unexpected {
2645 message: "Expected starts with '--' during parsing of '@function' name",
2646 },
2647 });
2648 return Some(());
2649 }
2650 if self.mode_data.as_ref().unwrap().is_current_local_mode() {
2651 self.dependency_context
2652 .push_dependency(Dependency::LocalFunctionDecl {
2653 name: stream.slice(start + 2, end)?,
2654 range: Range::new(start, end),
2655 });
2656 }
2657
2658 if is_function {
2659 let right_parenthesis = stream.peek_significant_skipping_comments::<true>().token;
2660 if right_parenthesis.kind != TokenKind::RightParenthesis {
2661 self.handle_warning.handle_warning(Warning {
2662 range: Range::new(
2663 right_parenthesis.range.start,
2664 right_parenthesis.range.end,
2665 ),
2666 kind: WarningKind::Unexpected {
2667 message:
2668 "Expected ')' during parsing of '@function :local(' or '@function :global('",
2669 },
2670 });
2671 return Some(());
2672 }
2673 stream.next_parser_token();
2674 self.dependency_context
2675 .push_dependency(Dependency::Replace {
2676 content: "",
2677 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2678 });
2679 let mode_data = self.mode_data.as_mut().unwrap();
2680 mode_data.inside_mode_function -= 1;
2681 self.balanced.pop_without_moda_data();
2682 }
2683
2684 if name_token.kind == TokenKind::Function {
2685 return Some(());
2686 }
2687 let left_parenthesis = stream.peek_significant_skipping_comments::<true>().token;
2688 if left_parenthesis.kind != TokenKind::LeftParenthesis {
2689 self.handle_warning.handle_warning(Warning {
2690 range: Range::new(left_parenthesis.range.start, left_parenthesis.range.end),
2691 kind: WarningKind::Unexpected {
2692 message: "Expected '(' during parsing of '@function'",
2693 },
2694 });
2695 }
2696 Some(())
2697 }
2698
2699 fn handle_local_counter_style_dependency(&mut self, lexer: &Lexer<'s>) -> Option<()> {
2700 let list_style = self.in_list_style_property.as_mut().unwrap();
2701 if let Some(range) = list_style.take_rename(self.balanced.len()) {
2702 self.dependency_context
2703 .push_dependency(Dependency::LocalCounterStyle {
2704 name: lexer.slice(range.start, range.end)?,
2705 range,
2706 });
2707 }
2708 Some(())
2709 }
2710
2711 fn handle_local_font_palette_dependency(&mut self, lexer: &Lexer<'s>) -> Option<()> {
2712 let font_palette = self.in_font_palette_property.as_mut().unwrap();
2713 if let Some(range) = font_palette.take_rename(self.balanced.len()) {
2714 self.dependency_context
2715 .push_dependency(Dependency::LocalFontPalette {
2716 name: lexer.slice(range.start + 2, range.end)?,
2717 range,
2718 });
2719 }
2720 Some(())
2721 }
2722
2723 fn lex_composes(
2724 &mut self,
2725 stream: &mut TokenStream<'_, 's>,
2726 local_classes: SmallVec<[&'s str; 2]>,
2727 start: Pos,
2728 ) -> Option<()> {
2729 let colon = stream.peek_significant_skipping_comments::<true>().token;
2730 if colon.kind != TokenKind::Colon {
2731 return Some(());
2732 }
2733 stream.next_parser_token();
2734 let mut replacement_end = colon.range.end;
2735 loop {
2736 let first = stream.peek_significant_skipping_comments::<true>().token;
2737 if first.kind == TokenKind::Eof {
2738 break;
2739 }
2740 if first.kind == TokenKind::RightCurlyBracket {
2741 break;
2742 }
2743 if first.kind == TokenKind::Semicolon {
2744 stream.next_parser_token();
2745 replacement_end = first.range.end;
2746 break;
2747 }
2748
2749 let item_start = first.range.start;
2750 let mut item_end = item_start;
2751 let mut names: SmallVec<[&'s str; 2]> = SmallVec::new();
2752 let mut has_from = false;
2753 let mut delimiter = first;
2754 loop {
2755 if matches!(
2756 delimiter.kind,
2757 TokenKind::Comma | TokenKind::Semicolon | TokenKind::RightCurlyBracket
2758 ) {
2759 break;
2760 }
2761
2762 if delimiter.kind == TokenKind::Function
2763 && stream.slice(delimiter.range.start, delimiter.range.end)? == "global("
2764 {
2765 let global_start = delimiter.range.start;
2766 stream.next_parser_token();
2767 let name_token = stream.next_parser_token().token;
2768 let Some(name_range) = ident_like_range(name_token) else {
2769 self.handle_warning.handle_warning(Warning {
2770 range: Range::new(
2771 name_token.range.start,
2772 name_token
2773 .range
2774 .start
2775 .saturating_add(2)
2776 .min(stream.source_end()),
2777 ),
2778 kind: WarningKind::Unexpected {
2779 message: "Expected ident during parsing of 'composes'",
2780 },
2781 });
2782 return Some(());
2783 };
2784 let right_parenthesis =
2785 stream.peek_significant_skipping_comments::<true>().token;
2786 if right_parenthesis.kind != TokenKind::RightParenthesis {
2787 self.handle_warning.handle_warning(Warning {
2788 range: Range::new(
2789 right_parenthesis.range.start,
2790 right_parenthesis.range.end,
2791 ),
2792 kind: WarningKind::Unexpected {
2793 message: "Expected ')' during parsing of 'composes'",
2794 },
2795 });
2796 return Some(());
2797 }
2798 stream.next_parser_token();
2799 item_end = right_parenthesis.range.end;
2800 self.dependency_context.push_composes(
2801 local_classes.iter().copied(),
2802 std::iter::once(stream.slice(name_range.start, name_range.end)?),
2803 Some("global"),
2804 Range::new(global_start, item_end),
2805 );
2806 delimiter = stream.peek_significant_skipping_comments::<true>().token;
2807 continue;
2808 }
2809
2810 let Some(name_range) = ident_like_range(delimiter) else {
2811 let name_start = delimiter.range.start;
2812 self.handle_warning.handle_warning(Warning {
2813 range: Range::new(
2814 name_start,
2815 name_start.saturating_add(2).min(stream.source_end()),
2816 ),
2817 kind: WarningKind::Unexpected {
2818 message: "Expected ident during parsing of 'composes'",
2819 },
2820 });
2821 return Some(());
2822 };
2823 let ident = stream.slice(name_range.start, name_range.end)?;
2824 if !names.is_empty() && ident.eq_ignore_ascii_case("from") {
2825 stream.next_parser_token();
2826 let path = stream.peek_significant_skipping_comments::<true>().token;
2827 if matches!(
2828 path.kind,
2829 TokenKind::QuotedString | TokenKind::Ident | TokenKind::Function
2830 ) {
2831 let path_range = if path.kind == TokenKind::Function {
2832 path.value_range
2833 } else {
2834 path.range
2835 };
2836 item_end = path_range.end;
2837 self.dependency_context.push_composes(
2838 local_classes.iter().copied(),
2839 std::mem::take(&mut names),
2840 Some(stream.slice(path_range.start, path_range.end)?),
2841 Range::new(item_start, item_end),
2842 );
2843 has_from = true;
2844 stream.next_parser_token();
2845 delimiter = stream.peek_significant_skipping_comments::<true>().token;
2846 break;
2847 }
2848 names.push(ident);
2849 item_end = name_range.end;
2850 delimiter = path;
2851 continue;
2852 }
2853 names.push(ident);
2854 item_end = name_range.end;
2855 stream.next_parser_token();
2856 delimiter = stream.peek_significant_skipping_comments::<true>().token;
2857 }
2858
2859 if has_from {
2860 if delimiter.kind == TokenKind::Comma {
2861 stream.next_parser_token();
2862 replacement_end = delimiter.range.end;
2863 continue;
2864 }
2865 if delimiter.kind == TokenKind::Semicolon {
2866 stream.next_parser_token();
2867 replacement_end = delimiter.range.end;
2868 break;
2869 }
2870 if delimiter.kind == TokenKind::RightCurlyBracket {
2871 replacement_end = item_end;
2872 break;
2873 }
2874 replacement_end = item_end;
2875 break;
2876 }
2877
2878 if delimiter.kind == TokenKind::Comma {
2879 if !names.is_empty() {
2880 self.dependency_context.push_composes(
2881 local_classes.iter().copied(),
2882 names,
2883 None,
2884 Range::new(item_start, item_end),
2885 );
2886 }
2887 stream.next_parser_token();
2888 replacement_end = delimiter.range.end;
2889 continue;
2890 }
2891
2892 if delimiter.kind == TokenKind::Semicolon {
2893 if !names.is_empty() {
2894 self.dependency_context.push_composes(
2895 local_classes.iter().copied(),
2896 names,
2897 None,
2898 Range::new(item_start, item_end),
2899 );
2900 }
2901 stream.next_parser_token();
2902 replacement_end = delimiter.range.end;
2903 break;
2904 }
2905
2906 if delimiter.kind == TokenKind::RightCurlyBracket {
2907 if !names.is_empty() {
2908 self.dependency_context.push_composes(
2909 local_classes.iter().copied(),
2910 names,
2911 None,
2912 Range::new(item_start, item_end),
2913 );
2914 }
2915 replacement_end = item_end;
2916 break;
2917 }
2918
2919 self.handle_warning.handle_warning(Warning {
2923 range: Range::new(
2924 delimiter.range.start,
2925 delimiter
2926 .range
2927 .start
2928 .saturating_add(2)
2929 .min(stream.source_end()),
2930 ),
2931 kind: WarningKind::Unexpected {
2932 message: "Expected ident during parsing of 'composes'",
2933 },
2934 });
2935 return Some(());
2936 }
2937 self.dependency_context
2938 .push_dependency(Dependency::Replace {
2939 content: "",
2940 range: Range::new(start, replacement_end),
2941 });
2942 Some(())
2943 }
2944}
2945
2946impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
2947 fn handle_comment(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2948 let Some(mode_data) = &mut self.mode_data else {
2949 return Some(());
2950 };
2951 if !mode_data.is_pure_mode() || end < start + 4 {
2952 return Some(());
2953 }
2954
2955 let content = lexer.slice(start + 2, end - 2)?;
2956 if is_css_modules_magic_comment(content, "cssmodules-pure-ignore") {
2957 mode_data.mark_pure_ignore();
2958 } else if matches!(self.scope, Scope::TopLevel)
2959 && self.block_nesting_level == 0
2960 && is_css_modules_magic_comment(content, "cssmodules-pure-no-check")
2961 {
2962 mode_data.mark_pure_no_check();
2963 }
2964 Some(())
2965 }
2966
2967 fn handle_url(
2968 &mut self,
2969 lexer: &mut Lexer<'s>,
2970 start: Pos,
2971 end: Pos,
2972 content_start: Pos,
2973 content_end: Pos,
2974 flags: TokenFlags,
2975 ) -> Option<()> {
2976 let value = lexer.slice(content_start, content_end)?;
2977 match self.scope {
2978 Scope::InAtImport(ref mut import_data) => {
2979 if import_data.in_supports() {
2980 return Some(());
2981 }
2982 if import_data.url.is_some() {
2983 self.handle_warning.handle_warning(Warning {
2984 range: Range::new(import_data.start, end),
2985 kind: WarningKind::DuplicateUrl {
2986 when: lexer.slice(import_data.start, end)?,
2987 },
2988 });
2989 return Some(());
2990 }
2991 import_data.prelude.push(ImportPreludeNode::Url {
2992 range: Range::new(start, end),
2993 });
2994 import_data.url = Some(value);
2995 import_data.url_flags = flags;
2996 import_data.url_range = Some(Range::new(start, end));
2997 }
2998 Scope::InBlock => self.dependency_context.push_dependency(Dependency::Url {
2999 request: value,
3000 range: Range::new(start, end),
3001 kind: UrlRangeKind::Function,
3002 }),
3003 _ => {}
3004 }
3005 Some(())
3006 }
3007
3008 fn handle_string(
3009 &mut self,
3010 lexer: &mut Lexer<'s>,
3011 start: Pos,
3012 end: Pos,
3013 flags: TokenFlags,
3014 ) -> Option<()> {
3015 match self.scope {
3016 Scope::InAtImport(ref mut import_data) => {
3017 let inside_url = matches!(
3018 self.balanced.last(),
3019 Some(last) if matches!(last.kind, BalancedItemKind::Url)
3020 );
3021
3022 if import_data.in_supports() || (!inside_url && import_data.url.is_some()) {
3024 return Some(());
3025 }
3026
3027 if inside_url && import_data.url.is_some() {
3028 self.handle_warning.handle_warning(Warning {
3029 range: Range::new(import_data.start, end),
3030 kind: WarningKind::DuplicateUrl {
3031 when: lexer.slice(import_data.start, end)?,
3032 },
3033 });
3034 return Some(());
3035 }
3036
3037 let value = lexer.slice(start + 1, end - 1)?;
3038 import_data.url = Some(value);
3039 import_data.url_flags = flags;
3040 if !inside_url {
3042 import_data.prelude.push(ImportPreludeNode::Url {
3043 range: Range::new(start, end),
3044 });
3045 import_data.url_range = Some(Range::new(start, end));
3046 }
3047 }
3048 Scope::InBlock => {
3049 if let Some(mode_data) = &self.mode_data
3050 && mode_data.is_property_local_mode()
3051 && matches!(
3052 self.in_grid_property,
3053 Some(GridPropertyKind::TemplateLike | GridPropertyKind::TemplateAreas)
3054 )
3055 {
3056 for range in
3057 parse_grid_template_area_ranges(lexer.slice(start + 1, end - 1)?, start + 1)
3058 {
3059 self.dependency_context
3060 .push_dependency(Dependency::LocalGridDecl {
3061 name: lexer.slice(range.start, range.end)?,
3062 range,
3063 });
3064 }
3065 }
3066 let Some(last) = self.balanced.last() else {
3067 return Some(());
3068 };
3069 let kind = match last.kind {
3070 BalancedItemKind::Url => UrlRangeKind::String,
3071 BalancedItemKind::ImageSet => UrlRangeKind::Function,
3072 _ => return Some(()),
3073 };
3074 let value = lexer.slice(start + 1, end - 1)?;
3075 self.dependency_context.push_dependency(Dependency::Url {
3076 request: value,
3077 range: Range::new(start, end),
3078 kind,
3079 });
3080 }
3081 _ => {}
3082 }
3083 Some(())
3084 }
3085
3086 fn handle_at_keyword(
3087 &mut self,
3088 stream: &mut TokenStream<'_, 's>,
3089 start: Pos,
3090 end: Pos,
3091 _flags: TokenFlags,
3092 ) -> Option<()> {
3093 let name = stream.slice_trusted(start, end);
3094 let kind = Self::classify_at_rule(name);
3095 self.set_scan_context(stream, ScanContext::AtRule);
3096 if kind == AtRuleKind::Namespace {
3097 self.scope = Scope::AtNamespaceInvalid;
3098 self.handle_warning.handle_warning(Warning {
3099 range: Range::new(start, end),
3100 kind: WarningKind::NamespaceNotSupportedInBundledCss,
3101 });
3102 } else if kind == AtRuleKind::Import {
3103 if !self.allow_import_at_rule {
3104 self.scope = Scope::AtImportInvalid;
3105 self.handle_warning.handle_warning(Warning {
3106 range: Range::new(start, end),
3107 kind: WarningKind::NotPrecededAtImport,
3108 });
3109 return Some(());
3110 }
3111 self.scope = Scope::InAtImport(ImportData::new(start));
3112 } else if kind == AtRuleKind::Charset {
3113 self.lex_charset_at_rule(stream, start)?;
3114 self.set_scan_context(stream, ScanContext::TopLevel);
3115 self.is_next_rule_prelude = true;
3116 } else if self.mode_data.is_some() {
3117 let mut can_contain_rules = true;
3118 if kind == AtRuleKind::Value {
3119 self.lex_value_at_rule(stream, start)?;
3120 can_contain_rules = false;
3121 self.set_scan_context(stream, ScanContext::TopLevel);
3122 self.is_next_rule_prelude = true;
3123 } else if kind == AtRuleKind::Keyframes {
3124 self.lex_local_keyframes_decl(stream)?;
3125 } else if kind == AtRuleKind::Container {
3126 self.lex_local_container_at_rule(stream)?;
3127 } else if kind == AtRuleKind::Function {
3128 self.lex_local_function_decl(stream)?;
3129 } else if kind == AtRuleKind::Property {
3130 self.lex_local_dashed_ident_decl(
3131 stream,
3132 |name, range| Dependency::LocalPropertyDecl { name, range },
3133 |range| Warning {
3134 range,
3135 kind: WarningKind::Unexpected {
3136 message: "Expected starts with '--' during parsing of '@property'",
3137 },
3138 },
3139 |range| Warning {
3140 range,
3141 kind: WarningKind::Unexpected {
3142 message: "Expected '{' during parsing of '@property'",
3143 },
3144 },
3145 )?;
3146 } else if kind == AtRuleKind::CounterStyle {
3147 self.lex_local_counter_style_decl(stream)?;
3148 } else if kind == AtRuleKind::FontPaletteValues {
3149 self.lex_local_dashed_ident_decl(
3150 stream,
3151 |name, range| Dependency::LocalFontPaletteDecl { name, range },
3152 |range| Warning {
3153 range,
3154 kind: WarningKind::Unexpected {
3155 message: "Expected starts with '--' during parsing of '@font-palette-values'",
3156 }
3157 },
3158 |range| Warning {
3159 range,
3160 kind: WarningKind::Unexpected {
3161 message: "Expected '{' during parsing of '@font-palette-values'",
3162 }
3163 },
3164 )?;
3165 } else {
3166 self.is_next_rule_prelude = kind == AtRuleKind::Scope;
3167 if self.is_next_rule_prelude {
3168 self.set_scan_context(stream, ScanContext::Selector);
3169 }
3170 }
3171
3172 let mode_data = self.mode_data.as_mut().unwrap();
3173 if can_contain_rules && self.block_nesting_level == 0 {
3174 mode_data.composes_local_classes.find_at_keyword();
3175 }
3176
3177 if mode_data.is_pure_mode() {
3178 mode_data.pure_global = None;
3179 }
3180 }
3181 Some(())
3182 }
3183
3184 fn handle_semicolon(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
3185 match self.scope {
3186 Scope::InAtImport(ref import_data) => {
3187 let Some(url) = import_data.url else {
3188 if let Some((name, name_range)) = import_data.prelude.icss_import_url() {
3189 self.dependency_context
3190 .push_dependency(Dependency::ICSSImportUrl {
3191 name,
3192 range: Range::new(import_data.start, end),
3193 name_range: *name_range,
3194 });
3195 self.scope = Scope::TopLevel;
3196 return Some(());
3197 }
3198 self.handle_warning.handle_warning(Warning {
3199 range: Range::new(import_data.start, end),
3200 kind: WarningKind::ExpectedUrl {
3201 when: lexer.slice(import_data.start, end)?,
3202 },
3203 });
3204 self.scope = Scope::TopLevel;
3205 return Some(());
3206 };
3207 let Some(url_range) = &import_data.url_range else {
3208 self.handle_warning.handle_warning(Warning {
3209 range: Range::new(start, end),
3210 kind: WarningKind::Unexpected {
3211 message: "Unexpected ';' during parsing of '@import url()'",
3212 },
3213 });
3214 self.scope = Scope::TopLevel;
3215 return Some(());
3216 };
3217 if let Some(range) = import_data.prelude.first_non_url_before(url_range) {
3218 self.handle_warning.handle_warning(Warning {
3219 range: *url_range,
3220 kind: WarningKind::ExpectedUrlBefore {
3221 when: lexer.slice(range.start, url_range.end)?,
3222 },
3223 });
3224 self.scope = Scope::TopLevel;
3225 return Some(());
3226 }
3227 let layer = match &import_data.layer {
3228 ImportDataLayer::None => None,
3229 ImportDataLayer::EndLayer { value, range } => {
3230 if url_range.start > range.start {
3231 self.handle_warning.handle_warning(Warning {
3232 range: *url_range,
3233 kind: WarningKind::ExpectedUrlBefore {
3234 when: lexer.slice(range.start, url_range.end)?,
3235 },
3236 });
3237 self.scope = Scope::TopLevel;
3238 return Some(());
3239 }
3240 Some(*value)
3241 }
3242 };
3243 let supports = match &import_data.supports {
3244 ImportDataSupports::None => None,
3245 ImportDataSupports::InSupports => {
3246 self.handle_warning.handle_warning(Warning {
3247 range: Range::new(start, end),
3248 kind: WarningKind::Unexpected {
3249 message: "Unexpected ';' during parsing of 'supports()'",
3250 },
3251 });
3252 None
3253 }
3254 ImportDataSupports::EndSupports { value, range } => {
3255 if url_range.start > range.start {
3256 self.handle_warning.handle_warning(Warning {
3257 range: *url_range,
3258 kind: WarningKind::ExpectedUrlBefore {
3259 when: lexer.slice(range.start, url_range.end)?,
3260 },
3261 });
3262 self.scope = Scope::TopLevel;
3263 return Some(());
3264 }
3265 Some(*value)
3266 }
3267 };
3268 if let Some(layer_range) = import_data.layer_range()
3269 && let Some(supports_range) = import_data.supports_range()
3270 && layer_range.start > supports_range.start
3271 {
3272 self.handle_warning.handle_warning(Warning {
3273 range: *layer_range,
3274 kind: WarningKind::ExpectedLayerBefore {
3275 when: lexer.slice(supports_range.start, layer_range.end)?,
3276 },
3277 });
3278 self.scope = Scope::TopLevel;
3279 return Some(());
3280 }
3281 let last_end = import_data
3282 .supports_range()
3283 .or_else(|| import_data.layer_range())
3284 .unwrap_or(url_range)
3285 .end;
3286 let media = self.get_media(lexer, last_end, start);
3287 self.dependency_context.push_import(
3288 url,
3289 Range::new(import_data.start, end),
3290 layer,
3291 supports,
3292 media,
3293 );
3294 self.scope = Scope::TopLevel;
3295 }
3296 Scope::AtImportInvalid | Scope::AtNamespaceInvalid => {
3297 self.scope = Scope::TopLevel;
3298 }
3299 Scope::InBlock => {
3300 if let Some(mode_data) = &mut self.mode_data {
3301 mode_data.pure_global = Some(end);
3302
3303 if mode_data.is_property_local_mode() {
3304 if self.in_animation_property.is_some() {
3305 self.handle_local_keyframes_dependency(lexer)?;
3306 self.exit_animation_property();
3307 }
3308 if self.in_list_style_property.is_some() {
3309 self.handle_local_counter_style_dependency(lexer)?;
3310 self.exit_list_style_property();
3311 }
3312 if self.in_font_palette_property.is_some() {
3313 self.handle_local_font_palette_dependency(lexer)?;
3314 self.exit_font_palette_property();
3315 }
3316 if self.in_container_property.is_some() {
3317 self.exit_container_property();
3318 }
3319 if self.in_grid_property.is_some() {
3320 self.exit_grid_property();
3321 }
3322 }
3323 }
3324 self.pending_custom_property = None;
3325 self.pending_grid_property = None;
3326 self.property_kind = PropertyKind::Generic;
3327 }
3328 Scope::TopLevel => {
3329 self.is_next_rule_prelude = true;
3330 }
3331 }
3332 Some(())
3333 }
3334
3335 fn handle_function(
3336 &mut self,
3337 stream: &mut TokenStream<'_, 's>,
3338 start: Pos,
3339 end: Pos,
3340 _flags: TokenFlags,
3341 ) -> Option<()> {
3342 let name = stream.slice_trusted(start, end);
3343 let item = BalancedItem::new(name, start, end);
3344 let at_import_top_level =
3345 matches!(self.scope, Scope::InAtImport(_)) && self.balanced.is_empty();
3346 self.balanced.push(item, self.mode_data.as_mut());
3347
3348 if let Scope::InAtImport(ref mut import_data) = self.scope {
3349 if at_import_top_level && name.eq_ignore_ascii_case("url(") {
3350 import_data.prelude.push(ImportPreludeNode::Url {
3351 range: Range::new(start, end),
3352 });
3353 } else if at_import_top_level && name.eq_ignore_ascii_case("layer(") {
3354 import_data.prelude.push(ImportPreludeNode::Layer {
3355 range: Range::new(start, end),
3356 });
3357 } else if at_import_top_level && name.eq_ignore_ascii_case("supports(") {
3358 import_data.prelude.push(ImportPreludeNode::Supports {
3359 range: Range::new(start, end),
3360 });
3361 import_data.supports = ImportDataSupports::InSupports;
3362 } else if at_import_top_level {
3363 import_data.prelude.push(ImportPreludeNode::Other {
3364 range: Range::new(start, end),
3365 });
3366 } else if name.eq_ignore_ascii_case("supports(") {
3367 import_data.supports = ImportDataSupports::InSupports;
3368 }
3369 }
3370
3371 if let Scope::InAtImport(ref mut import_data) = self.scope {
3372 let layer_end = at_import_top_level && name.eq_ignore_ascii_case("layer(");
3373 let supports_end = name.eq_ignore_ascii_case("supports(");
3374 if layer_end || supports_end {
3375 let Some(close) = stream.fast_forward(TokenKind::RightParenthesis) else {
3376 return Some(());
3377 };
3378 self.balanced.pop(self.mode_data.as_mut());
3379 if layer_end {
3380 import_data.layer = ImportDataLayer::EndLayer {
3381 value: stream.slice(end, close.end.saturating_sub(1))?,
3382 range: Range::new(start, close.end),
3383 };
3384 } else {
3385 import_data.supports = ImportDataSupports::EndSupports {
3386 value: stream.slice(end, close.end.saturating_sub(1))?,
3387 range: Range::new(start, close.end),
3388 };
3389 }
3390 return Some(());
3391 }
3392 }
3393
3394 let Some(mode_data) = &self.mode_data else {
3395 return Some(());
3396 };
3397 if mode_data.is_current_local_mode() && name.starts_with("--") && end > start + 1 {
3398 self.dependency_context
3399 .push_dependency(Dependency::LocalFunction {
3400 name: stream.slice(start + 2, end - 1)?,
3401 range: Range::new(start, end - 1),
3402 });
3403 }
3404 if mode_data.is_current_local_mode() && name.eq_ignore_ascii_case("var(") {
3405 self.lex_local_var(stream)?;
3406 }
3407 Some(())
3408 }
3409
3410 fn handle_left_parenthesis(&mut self, _: &mut Lexer, start: Pos, end: Pos) -> Option<()> {
3411 self.balanced
3412 .push(BalancedItem::new_other(start, end), self.mode_data.as_mut());
3413 Some(())
3414 }
3415
3416 fn handle_right_parenthesis(
3417 &mut self,
3418 lexer: &mut Lexer<'s>,
3419 leading_start: Pos,
3420 start: Pos,
3421 end: Pos,
3422 ) -> Option<()> {
3423 let Some(last) = self.balanced.pop(self.mode_data.as_mut()) else {
3424 return Some(());
3425 };
3426 if let Some(mode_data) = &mut self.mode_data {
3427 let mut is_function = last.kind.is_mode_function();
3428 let mut function_end = last.range.end;
3429 if last.kind.is_mode_class() {
3430 self.balanced.pop_mode_pseudo_class(mode_data);
3431 let popped = self.balanced.pop_without_moda_data().unwrap();
3432 debug_assert!(!matches!(
3433 popped.kind,
3434 BalancedItemKind::GlobalClass | BalancedItemKind::LocalClass
3435 ));
3436 is_function = popped.kind.is_mode_function();
3437 function_end = popped.range.end;
3438 }
3439 if is_function {
3440 let is_empty =
3441 start == function_end || trivia_only(lexer.slice(function_end, start)?);
3442 let replacement_start = if is_empty {
3443 function_end
3444 } else {
3445 leading_start
3446 };
3447 if is_empty {
3448 let maybe_left_parenthesis_start = function_end.saturating_sub(1);
3449 self.handle_warning.handle_warning(Warning {
3450 range: Range::new(maybe_left_parenthesis_start, end),
3451 kind: WarningKind::Unexpected {
3452 message: "':global()' or ':local()' can't be empty",
3453 },
3454 });
3455 }
3456 self.dependency_context
3457 .push_dependency(Dependency::Replace {
3458 content: "",
3459 range: Range::new(replacement_start, end),
3460 });
3461 }
3462 }
3463 if let Scope::InAtImport(ref mut import_data) = self.scope {
3464 let not_in_supports = !import_data.in_supports();
3465 if matches!(last.kind, BalancedItemKind::Url) && not_in_supports {
3466 import_data.url_range = Some(Range::new(last.range.start, end));
3467 } else if matches!(last.kind, BalancedItemKind::Layer) && not_in_supports {
3468 import_data.layer = ImportDataLayer::EndLayer {
3469 value: lexer.slice(last.range.end, end - 1)?,
3470 range: Range::new(last.range.start, end),
3471 };
3472 } else if matches!(last.kind, BalancedItemKind::Supports) {
3473 import_data.supports = ImportDataSupports::EndSupports {
3474 value: lexer.slice(last.range.end, end - 1)?,
3475 range: Range::new(last.range.start, end),
3476 }
3477 }
3478 }
3479 Some(())
3480 }
3481
3482 fn handle_ident(
3483 &mut self,
3484 stream: &mut TokenStream<'_, 's>,
3485 start: Pos,
3486 end: Pos,
3487 _flags: TokenFlags,
3488 ) -> Option<()> {
3489 match self.scope {
3490 Scope::InBlock => {
3491 let ident = stream.slice_trusted(start, end);
3492 let is_declaration_name = self.scan_context == ScanContext::DeclarationName;
3493 if is_declaration_name {
3494 let property_local_mode = self
3495 .mode_data
3496 .as_ref()
3497 .is_some_and(ModeData::is_property_local_mode);
3498 (self.property_kind, self.pending_grid_property) =
3499 Self::classify_property(ident, property_local_mode);
3500 self.pending_custom_property = (self.property_kind
3501 == PropertyKind::CustomProperty)
3502 .then_some(Range::new(start, end));
3503 if self.property_kind != PropertyKind::Composes {
3504 return Some(());
3505 }
3506 }
3507 let can_reference_icss = matches!(
3508 self.scan_context,
3509 ScanContext::GenericValue | ScanContext::SpecialValue(_) | ScanContext::AtRule
3510 );
3511 if can_reference_icss
3512 && !self.icss_symbols.is_empty()
3513 && self.contains_icss_symbol(ident)
3514 {
3515 self.dependency_context
3516 .push_dependency(Dependency::ICSSSymbol {
3517 name: ident,
3518 range: Range::new(start, end),
3519 });
3520 return Some(());
3521 }
3522 let Some(mode_data) = &mut self.mode_data else {
3523 return Some(());
3524 };
3525 if mode_data.is_current_local_mode()
3526 && self
3527 .balanced
3528 .last()
3529 .map(|last| last.kind.is_mode_function())
3530 .unwrap_or(false)
3531 && ident.starts_with("--")
3532 && stream.peek_parser_token().token.kind == TokenKind::RightParenthesis
3533 {
3534 self.dependency_context
3535 .push_dependency(Dependency::LocalFunction {
3536 name: stream.slice(start + 2, end)?,
3537 range: Range::new(start, end),
3538 });
3539 return Some(());
3540 }
3541 if mode_data.is_property_local_mode()
3542 && matches!(self.scan_context, ScanContext::SpecialValue(_))
3543 {
3544 if let Some(animation) = &mut self.in_animation_property {
3545 if self.balanced.is_empty() {
3547 animation.set_rename(stream.slice(start, end)?, Range::new(start, end));
3548 }
3549 return Some(());
3550 }
3551
3552 if let Some(list_style) = &mut self.in_list_style_property {
3553 if self.balanced.is_empty() {
3555 list_style
3556 .set_rename(stream.slice(start, end)?, Range::new(start, end));
3557 }
3558 return Some(());
3559 }
3560
3561 if let Some(font_palette) = &mut self.in_font_palette_property {
3562 if self.balanced.is_empty()
3564 || matches!(self.balanced.last(), Some(last) if matches!(last.kind, BalancedItemKind::PaletteMix))
3565 {
3566 font_palette
3567 .set_rename(stream.slice(start, end)?, Range::new(start, end));
3568 }
3569 return Some(());
3570 }
3571
3572 if let Some(container) = &mut self.in_container_property {
3573 if self.balanced.is_empty() {
3574 container.set_rename(ident, Range::new(start, end));
3575 if let Some(range) = container.take_rename(self.balanced.len()) {
3576 self.dependency_context.push_dependency(
3577 Dependency::LocalContainerDecl {
3578 name: stream.slice(range.start, range.end)?,
3579 range,
3580 },
3581 );
3582 }
3583 }
3584 return Some(());
3585 }
3586
3587 if self.in_grid_property.is_some() {
3588 if self.balanced.is_empty() && !is_reserved_grid_ident(ident) {
3589 self.dependency_context
3590 .push_dependency(Dependency::LocalGrid {
3591 name: ident,
3592 range: Range::new(start, end),
3593 });
3594 }
3595 return Some(());
3596 }
3597 }
3598
3599 if is_declaration_name && self.property_kind == PropertyKind::Composes {
3600 if self.block_nesting_level != 1 {
3601 self.handle_warning.handle_warning(Warning {
3602 range: Range::new(start, end),
3603 kind: WarningKind::UnexpectedComposition {
3604 message: "not allowed in nested rule",
3605 },
3606 });
3607 return Some(());
3608 }
3609 let Some(local_classes) = mode_data
3610 .composes_local_classes
3611 .get_valid_local_classes(stream.lexer())
3612 else {
3613 self.handle_warning.handle_warning(Warning {
3614 range: Range::new(start, end),
3615 kind: WarningKind::UnexpectedComposition {
3616 message: "only allowed when selector is single :local class",
3617 },
3618 });
3619 return Some(());
3620 };
3621 return self.lex_composes(stream, local_classes, start);
3622 }
3623 }
3624 Scope::InAtImport(ref mut import_data) => {
3625 if !self.balanced.is_empty() || import_data.in_supports() {
3626 return Some(());
3627 }
3628
3629 let ident = stream.slice_trusted(start, end);
3630 if ident.eq_ignore_ascii_case("layer") {
3631 import_data.prelude.push(ImportPreludeNode::Layer {
3632 range: Range::new(start, end),
3633 });
3634 import_data.layer = ImportDataLayer::EndLayer {
3635 value: "",
3636 range: Range::new(start, end),
3637 }
3638 } else if import_data.url.is_none() && import_data.prelude.is_empty() {
3639 import_data
3640 .prelude
3641 .push(ImportPreludeNode::IcssUrlCandidate {
3642 name: ident,
3643 range: Range::new(start, end),
3644 });
3645 } else if import_data.url.is_none() {
3646 import_data.prelude.push(ImportPreludeNode::Other {
3647 range: Range::new(start, end),
3648 });
3649 }
3650 }
3651 Scope::TopLevel => {
3652 let Some(mode_data) = &mut self.mode_data else {
3653 return Some(());
3654 };
3655
3656 mode_data.composes_local_classes.invalidate();
3657 }
3658 _ => {}
3659 }
3660 Some(())
3661 }
3662
3663 fn handle_class(
3664 &mut self,
3665 lexer: &mut Lexer<'s>,
3666 start: Pos,
3667 end: Pos,
3668 _flags: TokenFlags,
3669 ) -> Option<()> {
3670 let Some(mode_data) = &mut self.mode_data else {
3671 return Some(());
3672 };
3673 let name = lexer.slice_trusted(start, end);
3674 if name == "." {
3675 self.handle_warning.handle_warning(Warning {
3676 range: Range::new(start, end),
3677 kind: WarningKind::Unexpected {
3678 message: "Invalid class selector syntax",
3679 },
3680 });
3681 return Some(());
3682 }
3683 if mode_data.is_current_local_mode() {
3684 self.dependency_context
3685 .push_dependency(Dependency::LocalClass {
3686 name,
3687 range: Range::new(start, end),
3688 explicit: mode_data.is_mode_explicit(),
3689 });
3690 if self.block_nesting_level == 0 {
3691 mode_data
3692 .composes_local_classes
3693 .find_local_class(start + 1, end);
3694 }
3695
3696 if mode_data.is_pure_mode() {
3697 mode_data.pure_global = None;
3698 }
3699 }
3700 Some(())
3701 }
3702
3703 fn handle_id(
3704 &mut self,
3705 lexer: &mut Lexer<'s>,
3706 start: Pos,
3707 end: Pos,
3708 _flags: TokenFlags,
3709 ) -> Option<()> {
3710 let Some(mode_data) = &mut self.mode_data else {
3711 return Some(());
3712 };
3713 let name = lexer.slice_trusted(start, end);
3714 if name == "#" {
3715 self.handle_warning.handle_warning(Warning {
3716 range: Range::new(start, end),
3717 kind: WarningKind::Unexpected {
3718 message: "Invalid id selector syntax",
3719 },
3720 });
3721 return Some(());
3722 }
3723 if mode_data.is_current_local_mode() {
3724 self.dependency_context
3725 .push_dependency(Dependency::LocalId {
3726 name,
3727 range: Range::new(start, end),
3728 explicit: mode_data.is_mode_explicit(),
3729 });
3730
3731 if self.block_nesting_level == 0 {
3732 mode_data.composes_local_classes.invalidate();
3733 }
3734
3735 if mode_data.is_pure_mode() {
3736 mode_data.pure_global = None;
3737 }
3738 }
3739 Some(())
3740 }
3741
3742 fn handle_left_curly_bracket(
3743 &mut self,
3744 stream: &mut TokenStream<'_, 's>,
3745 start: Pos,
3746 _: Pos,
3747 ) -> Option<()> {
3748 if matches!(self.scope, Scope::InBlock)
3749 && self.scan_context != ScanContext::Selector
3750 && !self.balanced.is_empty()
3751 {
3752 self.balanced.push(
3753 BalancedItem::new_curly(start, start + 1),
3754 self.mode_data.as_mut(),
3755 );
3756 return Some(());
3757 }
3758 match self.scope {
3759 Scope::TopLevel => {
3760 self.allow_import_at_rule = false;
3761 self.scope = Scope::InBlock;
3762 if self.mode_data.is_none()
3763 || matches!(&self.mode_data, Some(mode_data) if !matches!(mode_data.composes_local_classes.is_single, SingleLocalClass::AtKeyword))
3764 {
3765 self.block_nesting_level = 1;
3766 }
3767 }
3768 Scope::InBlock => {
3769 let is_at_rule_block = matches!(
3770 &self.mode_data,
3771 Some(mode_data)
3772 if matches!(mode_data.composes_local_classes.is_single, SingleLocalClass::AtKeyword)
3773 );
3774 if !is_at_rule_block {
3775 self.block_nesting_level += 1;
3776 }
3777 }
3778 _ => return Some(()),
3779 }
3780 let (pure_check_disabled_for_selector, enter_pure_ignored_block) =
3781 if let Some(mode_data) = &self.mode_data {
3782 (
3783 mode_data.is_pure_check_disabled(),
3784 mode_data.pure_ignore_pending,
3785 )
3786 } else {
3787 (false, false)
3788 };
3789 if self.mode_data.is_none() {
3790 self.set_scan_context(stream, ScanContext::BlockItem);
3791 return Some(());
3792 }
3793 if let Some(mode_data) = &mut self.mode_data {
3794 if let Some(pure_global_start) = mode_data
3795 .pure_global
3796 .filter(|_| mode_data.is_pure_mode() && !pure_check_disabled_for_selector)
3797 {
3798 self.handle_warning.handle_warning(Warning {
3799 range: Range::new(pure_global_start, start),
3800 kind: WarningKind::NotPure {
3801 message: "Selector is not pure (pure selectors must contain at least one local class or id)",
3802 }
3803 });
3804 }
3805
3806 if enter_pure_ignored_block {
3807 mode_data.enter_block(self.block_nesting_level);
3808 }
3809
3810 if let Some(resulting_global_start) = mode_data
3811 .resulting_global
3812 .filter(|_| mode_data.is_current_local_mode())
3813 {
3814 self.handle_warning.handle_warning(Warning {
3815 range: Range::new(resulting_global_start, start),
3816 kind: WarningKind::InconsistentModeResult,
3817 });
3818 }
3819 mode_data.resulting_global = None;
3820
3821 self.balanced.update_property_mode(mode_data);
3822 self.balanced.pop_mode_pseudo_class(mode_data);
3823 if self.is_next_rule_prelude && self.block_nesting_level == 0 {
3824 let mode_data = self.mode_data.as_mut().unwrap();
3825 mode_data.composes_local_classes.reset_to_initial();
3826 }
3827
3828 debug_assert!(
3829 self.balanced.is_empty(),
3830 "balanced should be empty when end of selector"
3831 );
3832 }
3833 self.exit_container_property();
3834 self.exit_grid_property();
3835 self.pending_custom_property = None;
3836 self.pending_grid_property = None;
3837 self.property_kind = PropertyKind::Generic;
3838 self.set_scan_context(stream, ScanContext::BlockItem);
3839 Some(())
3840 }
3841
3842 fn handle_right_curly_bracket(
3843 &mut self,
3844 stream: &mut TokenStream<'_, 's>,
3845 _: Pos,
3846 end: Pos,
3847 ) -> Option<()> {
3848 if matches!(self.scope, Scope::InBlock) {
3849 if matches!(
3850 self.balanced.last(),
3851 Some(last) if matches!(last.kind, BalancedItemKind::Curly)
3852 ) {
3853 self.balanced.pop(self.mode_data.as_mut());
3854 if self.block_nesting_level == 0 {
3855 self.scope = Scope::TopLevel;
3856 self.is_next_rule_prelude = true;
3857 self.set_scan_context(stream, ScanContext::TopLevel);
3858 if let Some(mode_data) = &mut self.mode_data {
3859 mode_data.composes_local_classes.reset_to_initial();
3860 }
3861 } else {
3862 self.set_scan_context(stream, ScanContext::BlockItem);
3863 }
3864 return Some(());
3865 }
3866
3867 if let Some(mode_data) = &mut self.mode_data {
3868 mode_data.pure_global = Some(end);
3869
3870 if mode_data.is_property_local_mode() {
3871 if self.in_animation_property.is_some() {
3872 self.handle_local_keyframes_dependency(stream.lexer_mut())?;
3873 self.exit_animation_property();
3874 }
3875 if self.in_list_style_property.is_some() {
3876 self.handle_local_counter_style_dependency(stream.lexer_mut())?;
3877 self.exit_list_style_property();
3878 }
3879 if self.in_font_palette_property.is_some() {
3880 self.handle_local_font_palette_dependency(stream.lexer_mut())?;
3881 self.exit_font_palette_property();
3882 }
3883 if self.in_container_property.is_some() {
3884 self.exit_container_property();
3885 }
3886 if self.in_grid_property.is_some() {
3887 self.exit_grid_property();
3888 }
3889 }
3890 }
3891 if self.block_nesting_level > 0 {
3892 self.block_nesting_level -= 1;
3893 }
3894 if let Some(mode_data) = &mut self.mode_data {
3895 mode_data.clear_pure_ignore_pending();
3896 mode_data.exit_block(self.block_nesting_level);
3897 }
3898 if self.block_nesting_level == 0 {
3899 self.scope = Scope::TopLevel;
3900 self.set_scan_context(stream, ScanContext::TopLevel);
3901 self.is_next_rule_prelude = true;
3902 if let Some(mode_data) = &mut self.mode_data {
3903 mode_data.composes_local_classes.reset_to_initial();
3904 }
3905 } else {
3906 self.set_scan_context(stream, ScanContext::BlockItem);
3907 }
3908 self.pending_custom_property = None;
3909 self.pending_grid_property = None;
3910 self.property_kind = PropertyKind::Generic;
3911 }
3912 Some(())
3913 }
3914
3915 fn handle_pseudo_function(
3916 &mut self,
3917 stream: &mut TokenStream<'_, 's>,
3918 start: Pos,
3919 end: Pos,
3920 _flags: TokenFlags,
3921 ) -> Option<()> {
3922 let name = stream.slice_trusted(start, end);
3923 if let Some(mode_data) = &mut self.mode_data {
3924 if name.eq_ignore_ascii_case(":import(") {
3925 self.lex_icss_import(stream);
3926 self.dependency_context
3927 .push_dependency(Dependency::Replace {
3928 content: "",
3929 range: Range::new(start, stream.consumed_pos()),
3930 });
3931 return Some(());
3932 }
3933 if name.eq_ignore_ascii_case(":global(") || name.eq_ignore_ascii_case(":local(") {
3934 if mode_data.is_inside_mode_function() {
3935 self.handle_warning.handle_warning(Warning {
3936 range: Range::new(start, end),
3937 kind: WarningKind::ExpectedNotInside {
3938 pseudo: stream.slice(start, end)?,
3939 },
3940 });
3941 }
3942
3943 let next = stream.peek_parser_token();
3944 if next.token.kind == TokenKind::Eof {
3945 return None;
3946 }
3947 self.dependency_context
3948 .push_dependency(Dependency::Replace {
3949 content: "",
3950 range: Range::new(start, next.token.range.start),
3951 });
3952 } else if self.block_nesting_level == 0 {
3953 mode_data.composes_local_classes.invalidate();
3954 }
3955 }
3956 self.balanced
3957 .push(BalancedItem::new(name, start, end), self.mode_data.as_mut());
3958 Some(())
3959 }
3960
3961 fn handle_pseudo_class(
3962 &mut self,
3963 stream: &mut TokenStream<'_, 's>,
3964 start: Pos,
3965 end: Pos,
3966 _flags: TokenFlags,
3967 ) -> Option<()> {
3968 let Some(mode_data) = &mut self.mode_data else {
3969 return Some(());
3970 };
3971 let name = stream.slice_trusted(start, end);
3972 if name.eq_ignore_ascii_case(":global") || name.eq_ignore_ascii_case(":local") {
3973 if mode_data.is_inside_mode_function() {
3974 self.handle_warning.handle_warning(Warning {
3975 range: Range::new(start, end),
3976 kind: WarningKind::ExpectedNotInside {
3977 pseudo: stream.slice(start, end)?,
3978 },
3979 });
3980 }
3981
3982 let next = stream.peek_parser_token();
3983 if next.token.kind == TokenKind::Eof {
3984 return None;
3985 }
3986 if !next.leading.has_whitespace() {
3987 let missing_whitespace = match stream.byte_at(next.token.range.start) {
3988 Some(b'.' | b'#') => true,
3989 Some(b'{') => next.leading.first_comment_start.is_some(),
3990 _ => false,
3991 };
3992 if missing_whitespace {
3993 self.handle_warning.handle_warning(Warning {
3994 range: Range::new(start, end),
3995 kind: WarningKind::Unexpected {
3996 message: "Missing trailing whitespace",
3997 },
3998 });
3999 }
4000 }
4001 self.balanced
4002 .push(BalancedItem::new(name, start, end), self.mode_data.as_mut());
4003 self.dependency_context
4004 .push_dependency(Dependency::Replace {
4005 content: "",
4006 range: Range::new(
4007 start,
4008 next.leading.first_comment_start.unwrap_or(next.leading.end),
4009 ),
4010 });
4011 return Some(());
4012 }
4013 if matches!(self.scope, Scope::TopLevel) && name.eq_ignore_ascii_case(":export") {
4014 self.lex_icss_export(stream)?;
4015 self.dependency_context
4016 .push_dependency(Dependency::Replace {
4017 content: "",
4018 range: Range::new(start, stream.consumed_pos()),
4019 });
4020 return Some(());
4021 }
4022
4023 if self.block_nesting_level == 0 {
4024 mode_data.composes_local_classes.invalidate();
4025 }
4026 Some(())
4027 }
4028
4029 fn handle_comma(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
4030 let Some(mode_data) = &mut self.mode_data else {
4031 return Some(());
4032 };
4033
4034 if let Some(pure_global_start) = mode_data
4035 .pure_global
4036 .filter(|_| mode_data.is_pure_mode() && !mode_data.is_pure_check_disabled())
4037 {
4038 self.handle_warning.handle_warning(Warning {
4039 range: Range::new(pure_global_start, start),
4040 kind: WarningKind::NotPure {
4041 message: "Selector is not pure (pure selectors must contain at least one local class or id)",
4042 }
4043 });
4044 }
4045 mode_data.pure_global = Some(end);
4046
4047 if self.block_nesting_level == 0 {
4048 mode_data.composes_local_classes.find_comma(lexer)?;
4049 }
4050
4051 if let Some(resulting_global_start) = mode_data
4052 .resulting_global
4053 .filter(|_| mode_data.is_current_local_mode())
4054 {
4055 self.handle_warning.handle_warning(Warning {
4056 range: Range::new(resulting_global_start, start),
4057 kind: WarningKind::InconsistentModeResult,
4058 });
4059 }
4060
4061 if self.balanced.len() == 1 {
4062 let last = self.balanced.last().unwrap();
4063 let is_local_class = matches!(last.kind, BalancedItemKind::LocalClass);
4064 let is_global_class = matches!(last.kind, BalancedItemKind::GlobalClass);
4065 if is_local_class || is_global_class {
4066 self.balanced.pop_mode_pseudo_class(mode_data);
4067 if mode_data.resulting_global.is_none() && is_global_class {
4068 mode_data.resulting_global = Some(start);
4069 }
4070 }
4071 }
4072
4073 if matches!(self.scope, Scope::InBlock)
4074 && mode_data.is_property_local_mode()
4075 && self.in_animation_property.is_some()
4076 {
4077 self.handle_local_keyframes_dependency(lexer)?;
4078 }
4079
4080 Some(())
4081 }
4082}