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