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