Skip to main content

css_module_lexer/
dependencies.rs

1//! Dependency parser and lexer visitor implementation.
2
3use 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/// Collects dashed identifiers while the dependency parser is in local mode.
21#[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/// Token-aligned split of an import item by a top-level colon or `as` ident.
474/// Names are delimited by the surrounding significant tokens, so comments and
475/// whitespace at the split points stay out of the names.
476#[derive(Debug, Clone, Copy)]
477struct ValueAtRuleSplit {
478  split: Pos,
479  end: Pos,
480  prev_end: Pos,
481  next_start: Option<Pos>,
482}
483
484/// Streaming state for a single `@value` at-rule. Tokens are consumed one at a
485/// time; completed import items are written into the [`DependencyContext`] side
486/// table immediately instead of collecting a token buffer first.
487struct 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  /// Re-parses a parenthesized item: the inner content was not streamed
675  /// token-by-token, so it is tokenized once more and split at depth-zero
676  /// commas, mirroring the legacy aligned-token behavior.
677  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  /// Returns the last two significant tokens, or `None` if there are fewer
766  /// than two.
767  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    // Don't rename when we in functions
992    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            // keywords values
1039            "none" |
1040            // global values
1041            "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            // https://www.w3.org/TR/css-counter-styles-3/#simple-numeric
1095            "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            // https://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic
1124            | "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            // https://www.w3.org/TR/css-counter-styles-3/#simple-symbolic
1134            | "disc"
1135            | "circle"
1136            | "square"
1137            | "disclosure-open"
1138            | "disclosure-closed"
1139            // https://www.w3.org/TR/css-counter-styles-3/#simple-fixed
1140            | "cjk-earthly-branch"
1141            | "cjk-heavenly-stem"
1142            // https://www.w3.org/TR/css-counter-styles-3/#complex-cjk
1143            | "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            // keywords values
1154            | "none"
1155            // global values
1156            | "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
1261/// Decide whether a raw-scanned ident inside a special property value must be
1262/// handed back to the regular tokenizer. The check applies to plain idents and
1263/// function names alike: `url`/`var`/`image-set` and `--`-prefixed functions
1264/// are stopped by the caller's generic dependency-function rule, while any
1265/// other non-reserved function name also stops the scan so the tokenizer can
1266/// push it onto the balanced stack. `icss_symbols` is only consulted for the
1267/// property kinds whose values can reference ICSS symbols.
1268pub(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  /// Drive dependency extraction from the forward token stream.
1419  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    // An animation name may be any top-level identifier, so the raw
1767    // scanner usually stops immediately. Continue tokenizing directly.
1768    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      // An invalid token was encountered after a name.  The next loop
3031      // iteration would produce the same warning, but keeping the
3032      // cursor at that token preserves the legacy recovery point.
3033      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        // Do not parse URLs in `supports(...)` and other strings if we already have a URL
3140        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        // For url("inside_url") url_range will determined in right_parenthesis
3158        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            // Not inside functions
3688            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            // Not inside functions
3696            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            // Not inside functions or inside palette-mix()
3704            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}