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 mut square_depth = self.selector_square_depth;
1711    let invalidates_composes = stream.fast_forward_selector_if_buffer_empty(
1712      &mut square_depth,
1713      keep_comments,
1714      has_mode,
1715      |ident| self.contains_icss_symbol(ident),
1716      is_css_modules_pure_magic_comment,
1717    );
1718    self.selector_square_depth = square_depth;
1719    if invalidates_composes
1720      && self.block_nesting_level == 0
1721      && let Some(mode_data) = &mut self.mode_data
1722    {
1723      mode_data.composes_local_classes.invalidate();
1724    }
1725  }
1726
1727  #[inline]
1728  fn fast_forward_generic_value(
1729    &self,
1730    stream: &mut DependencyTokenStream<'_, 's>,
1731    keep_comments: bool,
1732  ) {
1733    let preserve_strings = matches!(
1734        self.balanced.last(),
1735        Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
1736    );
1737    let preserve_delimiters = !self.balanced.is_empty();
1738    if self.icss_symbols.is_empty() && !keep_comments {
1739      stream.fast_forward_generic_value_without_candidates_if_buffer_empty(
1740        preserve_strings,
1741        preserve_delimiters,
1742      );
1743    } else {
1744      stream.fast_forward_generic_value_if_buffer_empty(
1745        keep_comments,
1746        preserve_strings,
1747        preserve_delimiters,
1748        |ident| self.contains_icss_symbol(ident),
1749        is_css_modules_pure_magic_comment,
1750      );
1751    }
1752  }
1753
1754  #[inline]
1755  fn fast_forward_at_rule(&self, stream: &mut DependencyTokenStream<'_, 's>, keep_comments: bool) {
1756    let preserve_strings = matches!(
1757        self.balanced.last(),
1758        Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
1759    );
1760    let preserve_delimiters = !self.balanced.is_empty();
1761    stream.fast_forward_at_rule_if_buffer_empty(
1762      keep_comments,
1763      preserve_strings,
1764      preserve_delimiters,
1765      |ident| self.contains_icss_symbol(ident),
1766      is_css_modules_pure_magic_comment,
1767    );
1768  }
1769
1770  #[inline]
1771  fn fast_forward_special_value(
1772    &self,
1773    stream: &mut DependencyTokenStream<'_, 's>,
1774    property: PropertyKind,
1775    keep_comments: bool,
1776  ) {
1777    // An animation name may be any top-level identifier, so the raw
1778    // scanner usually stops immediately. Continue tokenizing directly.
1779    if property == PropertyKind::Animation {
1780      return;
1781    }
1782    let preserve_strings = property == PropertyKind::Grid
1783      || matches!(
1784          self.balanced.last(),
1785          Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
1786      );
1787    let preserve_delimiters = !self.balanced.is_empty();
1788    let icss_symbols = (!self.icss_symbols.is_empty()).then_some(&self.icss_symbols);
1789    stream.fast_forward_special_value_if_buffer_empty(
1790      keep_comments,
1791      preserve_strings,
1792      preserve_delimiters,
1793      property,
1794      icss_symbols,
1795    );
1796  }
1797
1798  #[inline]
1799  fn icss_symbol_filter_bit(value: &str) -> (usize, u64) {
1800    let bytes = value.as_bytes();
1801    let len = bytes.len();
1802    let first = bytes.first().copied().unwrap_or_default() as usize;
1803    let middle = bytes.get(len / 2).copied().unwrap_or_default() as usize;
1804    let last = bytes.last().copied().unwrap_or_default() as usize;
1805    let mut hash = len.wrapping_mul(0x9e37_79b1);
1806    hash ^= first.wrapping_mul(0x85eb_ca6b);
1807    hash ^= middle.wrapping_mul(0xc2b2_ae35);
1808    hash ^= last.wrapping_mul(0x27d4_eb2f);
1809    hash ^= hash >> 16;
1810    let bit = hash & 1023;
1811    (bit >> 6, 1u64 << (bit & 63))
1812  }
1813
1814  #[inline]
1815  fn contains_icss_symbol(&self, value: &str) -> bool {
1816    let len = value.len();
1817    if len < self.icss_symbol_min_len || len > self.icss_symbol_max_len {
1818      return false;
1819    }
1820    let (word, bit) = Self::icss_symbol_filter_bit(value);
1821    self.icss_symbol_filter[word] & bit != 0 && self.icss_symbols.contains(value)
1822  }
1823
1824  fn insert_icss_symbol(&mut self, value: &'s str) {
1825    let len = value.len();
1826    self.icss_symbol_min_len = self.icss_symbol_min_len.min(len);
1827    self.icss_symbol_max_len = self.icss_symbol_max_len.max(len);
1828    let (word, bit) = Self::icss_symbol_filter_bit(value);
1829    self.icss_symbol_filter[word] |= bit;
1830    self.icss_symbols.insert(value);
1831  }
1832
1833  fn enter_property_value(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
1834    match self.property_kind {
1835      PropertyKind::Animation => self.enter_animation_property(),
1836      PropertyKind::ListStyle => self.enter_list_style_property(),
1837      PropertyKind::FontPalette => self.enter_font_palette_property(),
1838      PropertyKind::Container => self.enter_container_property(),
1839      PropertyKind::Grid => {
1840        if let Some(kind) = self.pending_grid_property.take() {
1841          self.enter_grid_property(kind);
1842        }
1843      }
1844      PropertyKind::CustomProperty => {
1845        if self
1846          .mode_data
1847          .as_ref()
1848          .is_some_and(ModeData::is_property_local_mode)
1849          && let Some(range) = self.pending_custom_property.take()
1850        {
1851          self
1852            .dependency_context
1853            .push_dependency(Dependency::LocalVarDecl {
1854              name: dashed_ident_name(stream.lexer().slice(range.start, range.end)?)?,
1855              range,
1856            });
1857        }
1858      }
1859      PropertyKind::Generic | PropertyKind::Composes => {}
1860    }
1861    self.pending_custom_property = None;
1862    self.pending_grid_property = None;
1863    self.set_scan_context(stream, ScanContext::for_property(self.property_kind));
1864    Some(())
1865  }
1866
1867  fn classify_property(
1868    name: &str,
1869    flags: TokenFlags,
1870    property_local_mode: bool,
1871  ) -> (PropertyKind, Option<GridPropertyKind>) {
1872    if name.starts_with("--") {
1873      return if property_local_mode {
1874        (PropertyKind::CustomProperty, None)
1875      } else {
1876        (PropertyKind::Generic, None)
1877      };
1878    }
1879    let mut normalized = [0; MAX_CSS_KEYWORD_LEN];
1880    let name = if flags.has_escape() {
1881      let Some(name) = decode_css_keyword(name, &mut normalized) else {
1882        return (PropertyKind::Generic, None);
1883      };
1884      name
1885    } else {
1886      let Some(name) = lowercase_ascii_keyword(name, &mut normalized) else {
1887        return (PropertyKind::Generic, None);
1888      };
1889      name
1890    };
1891    if matches!(name, "composes" | "compose-with") {
1892      return (PropertyKind::Composes, None);
1893    }
1894    if !property_local_mode {
1895      return (PropertyKind::Generic, None);
1896    }
1897    let unprefixed = strip_vendor_prefix(name).unwrap_or(name);
1898    if matches!(unprefixed, "animation" | "animation-name") {
1899      return (PropertyKind::Animation, None);
1900    }
1901    if matches!(name, "list-style" | "list-style-type") {
1902      return (PropertyKind::ListStyle, None);
1903    }
1904    if name == "font-palette" {
1905      return (PropertyKind::FontPalette, None);
1906    }
1907    if matches!(name, "container" | "container-name") {
1908      return (PropertyKind::Container, None);
1909    }
1910    if let Some(grid) = grid_property_kind(name) {
1911      return (PropertyKind::Grid, Some(grid));
1912    }
1913    (PropertyKind::Generic, None)
1914  }
1915
1916  fn classify_at_rule(name: &str, flags: TokenFlags) -> AtRuleKind {
1917    let mut normalized = [0; MAX_CSS_KEYWORD_LEN];
1918    let name = if flags.has_escape() {
1919      let Some(name) = decode_css_keyword(name, &mut normalized) else {
1920        return AtRuleKind::Other;
1921      };
1922      name
1923    } else {
1924      let Some(name) = lowercase_ascii_keyword(name, &mut normalized) else {
1925        return AtRuleKind::Other;
1926      };
1927      name
1928    };
1929    match name {
1930      "@value" => AtRuleKind::Value,
1931      "@scope" => AtRuleKind::Scope,
1932      "@import" => AtRuleKind::Import,
1933      "@charset" => AtRuleKind::Charset,
1934      "@function" => AtRuleKind::Function,
1935      "@property" => AtRuleKind::Property,
1936      "@namespace" => AtRuleKind::Namespace,
1937      "@keyframes" => AtRuleKind::Keyframes,
1938      "@container" => AtRuleKind::Container,
1939      "@counter-style" => AtRuleKind::CounterStyle,
1940      "@font-palette-values" => AtRuleKind::FontPaletteValues,
1941      _ if name.strip_prefix('@').and_then(strip_vendor_prefix) == Some("keyframes") => {
1942        AtRuleKind::Keyframes
1943      }
1944      _ => AtRuleKind::Other,
1945    }
1946  }
1947
1948  fn get_media(&self, lexer: &DependencyLexer<'s>, start: Pos, end: Pos) -> Option<&'s str> {
1949    let media = lexer.slice(start, end)?;
1950    let bytes = media.as_bytes();
1951    let mut position = 0;
1952    loop {
1953      while position < bytes.len() && bytes[position].is_ascii_whitespace() {
1954        position += 1;
1955      }
1956      if position + 1 < bytes.len() && bytes[position] == b'/' && bytes[position + 1] == b'*' {
1957        let Some(relative_end) = media[position + 2..].find("*/") else {
1958          break;
1959        };
1960        position += relative_end + 4;
1961        continue;
1962      }
1963      break;
1964    }
1965    if position == bytes.len() {
1966      return None;
1967    }
1968    Some(media)
1969  }
1970
1971  fn lex_charset_at_rule(
1972    &mut self,
1973    stream: &mut DependencyTokenStream<'_, 's>,
1974    start: Pos,
1975  ) -> Option<()> {
1976    let string = stream.next_parser_token().token;
1977    if string.kind != TokenKind::QuotedString {
1978      return Some(());
1979    }
1980
1981    let next = stream.next_parser_token().token;
1982    if next.kind == TokenKind::Semicolon {
1983      self
1984        .dependency_context
1985        .push_dependency(Dependency::Charset {
1986          value: stream.slice(string.value_range.start, string.value_range.end)?,
1987          range: Range::new(start, next.range.end),
1988        });
1989    }
1990    Some(())
1991  }
1992
1993  fn enter_animation_property(&mut self) {
1994    self.in_animation_property = Some(InProperty::new(
1995      AnimationReserved::default(),
1996      self.balanced.len(),
1997    ));
1998  }
1999
2000  fn exit_animation_property(&mut self) {
2001    self.in_animation_property = None;
2002  }
2003
2004  fn enter_list_style_property(&mut self) {
2005    self.in_list_style_property = Some(InProperty::new(ListStyleReserved, self.balanced.len()));
2006  }
2007
2008  fn exit_list_style_property(&mut self) {
2009    self.in_list_style_property = None;
2010  }
2011
2012  fn enter_font_palette_property(&mut self) {
2013    self.in_font_palette_property = Some(InProperty::new(FontPaletteReserved, self.balanced.len()));
2014  }
2015
2016  fn exit_font_palette_property(&mut self) {
2017    self.in_font_palette_property = None;
2018  }
2019
2020  fn enter_container_property(&mut self) {
2021    self.in_container_property = Some(InProperty::new(ContainerReserved, self.balanced.len()));
2022  }
2023
2024  fn exit_container_property(&mut self) {
2025    self.in_container_property = None;
2026  }
2027
2028  fn enter_grid_property(&mut self, kind: GridPropertyKind) {
2029    self.in_grid_property = Some(kind);
2030  }
2031
2032  fn exit_grid_property(&mut self) {
2033    self.in_grid_property = None;
2034  }
2035
2036  fn lex_icss_import(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2037    let (start, end) = self.consume_icss_import_path(stream)?;
2038    let right_parenthesis = stream.next_parser_token().token;
2039    if right_parenthesis.kind != TokenKind::RightParenthesis {
2040      self.handle_warning.handle_warning(Warning {
2041        range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2042        kind: WarningKind::Unexpected {
2043          message: "Expected ')' during parsing of ':import()'",
2044        },
2045      });
2046      return Some(());
2047    }
2048    self
2049      .dependency_context
2050      .push_dependency(Dependency::ICSSImportFrom {
2051        path: stream.slice(start, end)?,
2052      });
2053    let left_curly = stream.next_parser_token().token;
2054    if left_curly.kind != TokenKind::LeftCurlyBracket {
2055      self.handle_warning.handle_warning(Warning {
2056        range: Range::new(left_curly.range.start, left_curly.range.end),
2057        kind: WarningKind::Unexpected {
2058          message: "Expected '{' during parsing of ':import()'",
2059        },
2060      });
2061      return Some(());
2062    }
2063    loop {
2064      let first = stream.next_parser_token().token;
2065      if first.kind == TokenKind::Eof {
2066        return None;
2067      }
2068      if first.kind == TokenKind::RightCurlyBracket {
2069        break;
2070      }
2071      let prop_start = first.range.start;
2072      let prop_end = self.consume_icss_export_prop(stream, first)?;
2073      let colon = stream.peek_significant_skipping_comments(true).token;
2074      if colon.kind != TokenKind::Colon {
2075        self.handle_warning.handle_warning(Warning {
2076          range: Range::new(colon.range.start, colon.range.end),
2077          kind: WarningKind::Unexpected {
2078            message: "Expected ':' during parsing of ':import'",
2079          },
2080        });
2081        return Some(());
2082      }
2083      stream.next_parser_token();
2084      let value_start_token = stream.next_parser_token().token;
2085      if value_start_token.kind == TokenKind::Eof {
2086        return None;
2087      }
2088      let value_start = value_start_token.range.start;
2089      let value_end = self.consume_icss_value(stream, value_start_token)?;
2090      let delimiter = stream.next_parser_token().token;
2091      self
2092        .dependency_context
2093        .push_dependency(Dependency::ICSSImportValue {
2094          prop: stream
2095            .slice(prop_start, prop_end)?
2096            .trim_end_matches(is_css_white_space_char),
2097          value: stream
2098            .slice(value_start, value_end)?
2099            .trim_end_matches(is_css_white_space_char),
2100        });
2101      self.insert_icss_symbol(
2102        stream
2103          .slice(prop_start, prop_end)?
2104          .trim_end_matches(is_css_white_space_char),
2105      );
2106      if delimiter.kind == TokenKind::RightCurlyBracket {
2107        break;
2108      }
2109    }
2110    Some(())
2111  }
2112
2113  fn consume_icss_import_path(
2114    &self,
2115    stream: &mut DependencyTokenStream<'_, 's>,
2116  ) -> Option<(Pos, Pos)> {
2117    let first = stream.next_parser_token().token;
2118    if first.kind == TokenKind::Eof {
2119      return None;
2120    }
2121    let start = first.range.start;
2122    let mut end = start;
2123    if first.kind == TokenKind::RightParenthesis {
2124      return Some((start, end));
2125    }
2126    end = first.range.end;
2127    loop {
2128      let token = stream.peek_significant_skipping_comments(true).token;
2129      if token.kind == TokenKind::Eof {
2130        return None;
2131      }
2132      if token.kind == TokenKind::RightParenthesis {
2133        return Some((start, end));
2134      }
2135      stream.next_parser_token();
2136      if first.kind != TokenKind::QuotedString {
2137        end = token.range.end;
2138      }
2139    }
2140  }
2141
2142  fn consume_icss_export_prop(
2143    &self,
2144    stream: &mut DependencyTokenStream<'_, 's>,
2145    first: Token,
2146  ) -> Option<Pos> {
2147    if matches!(
2148      first.kind,
2149      TokenKind::Colon | TokenKind::RightCurlyBracket | TokenKind::Semicolon
2150    ) {
2151      return Some(first.range.start);
2152    }
2153    loop {
2154      let token = stream.peek_parser_token();
2155      if token.token.kind == TokenKind::Eof {
2156        return None;
2157      }
2158      if let Some(first_comment_start) = token.leading.first_comment_start {
2159        return Some(first_comment_start);
2160      }
2161      if matches!(
2162        token.token.kind,
2163        TokenKind::Colon | TokenKind::RightCurlyBracket | TokenKind::Semicolon
2164      ) {
2165        return Some(token.token.range.start);
2166      }
2167      stream.next_parser_token();
2168    }
2169  }
2170
2171  fn consume_icss_value(
2172    &self,
2173    stream: &mut DependencyTokenStream<'_, 's>,
2174    first: Token,
2175  ) -> Option<Pos> {
2176    if matches!(
2177      first.kind,
2178      TokenKind::RightCurlyBracket | TokenKind::Semicolon
2179    ) {
2180      return Some(first.range.start);
2181    }
2182    loop {
2183      let token = stream.peek(true).token;
2184      if token.kind == TokenKind::Eof {
2185        return None;
2186      }
2187      if matches!(
2188        token.kind,
2189        TokenKind::RightCurlyBracket | TokenKind::Semicolon
2190      ) {
2191        return Some(token.range.start);
2192      }
2193      stream.next(true);
2194    }
2195  }
2196
2197  fn lex_value_at_rule(
2198    &mut self,
2199    stream: &mut DependencyTokenStream<'_, 's>,
2200    start: Pos,
2201  ) -> Option<()> {
2202    let input = stream.slice_trusted(0, stream.source_end());
2203    let checkpoint = self
2204      .dependency_context
2205      .value_at_rule_import_items_checkpoint();
2206    let mut parser = ValueAtRuleStream::new(input);
2207    loop {
2208      let token = stream.next_parser_token().token;
2209      match token.kind {
2210        TokenKind::Eof => return None,
2211        _ => {
2212          if token.kind == TokenKind::Semicolon && parser.depth == 0 {
2213            parser.params_end = token.range.start;
2214            break;
2215          }
2216          let depth_after = (parser.depth + u32::from(is_open_token(token.kind)))
2217            .saturating_sub(u32::from(is_close_token(token.kind)));
2218          if token.kind == TokenKind::RightCurlyBracket && depth_after == 0 {
2219            parser.params_end = token.range.start;
2220            break;
2221          }
2222          parser.push(&mut self.dependency_context, token);
2223        }
2224      }
2225    }
2226    let at_rule_end = parser.params_end.max(stream.consumed_pos());
2227    let import = parser.last_two().is_some_and(|(penultimate, last)| {
2228      penultimate.kind == TokenKind::Ident
2229        && parser
2230          .input
2231          .get(penultimate.range.start as usize..penultimate.range.end as usize)
2232          .is_some_and(|text| text.eq_ignore_ascii_case("from"))
2233        && parser.from_pos == Some(penultimate.range.start)
2234        && parser
2235          .input
2236          .get(penultimate.range.end as usize..last.range.start as usize)
2237          .is_some_and(trivia_only)
2238    });
2239    if import {
2240      let (_, last) = parser
2241        .last_two()
2242        .expect("an import value must end with at least two tokens");
2243      let from = &parser.input[last.range.start as usize..last.range.end as usize];
2244      let item_end = parser.from_prev_end.unwrap_or(parser.item_end);
2245      parser.finish_item(&mut self.dependency_context, item_end);
2246      let items = self
2247        .dependency_context
2248        .finish_value_at_rule_import_items(checkpoint);
2249      if items.is_empty() {
2250        self.handle_warning.handle_warning(Warning {
2251          range: Range::new(start, at_rule_end),
2252          kind: WarningKind::Unexpected {
2253            message: "Broken '@value' at-rule",
2254          },
2255        });
2256      } else {
2257        self
2258          .dependency_context
2259          .push_dependency(Dependency::ICSSImportFrom { path: from });
2260        for index in items.as_usize_range() {
2261          let item = self.dependency_context.value_at_rule_import_item(index);
2262          self
2263            .dependency_context
2264            .push_dependency(Dependency::ICSSImportValue {
2265              prop: item.local_name(),
2266              value: item.import_name(),
2267            });
2268          self.insert_icss_symbol(item.local_name());
2269          self
2270            .dependency_context
2271            .push_dependency(Dependency::ICSSExportValue {
2272              prop: item.local_name(),
2273              value: item.local_name(),
2274            });
2275        }
2276      }
2277    } else {
2278      self
2279        .dependency_context
2280        .truncate_value_at_rule_import_items(checkpoint);
2281      let local_name;
2282      let value;
2283      let has_colon;
2284      if let Some(colon) = parser.first_colon {
2285        local_name = &parser.input[parser
2286          .first_significant
2287          .unwrap_or((colon.split, colon.split))
2288          .0 as usize..colon.prev_end as usize];
2289        let raw = &parser.input[colon.end as usize..parser.params_end as usize];
2290        value = if parser.first_colon_tokens_after > 0 {
2291          trim_css_whitespace(raw)
2292        } else {
2293          raw
2294        };
2295        has_colon = true;
2296      } else if let Some((first_start, first_end)) = parser.first_significant {
2297        local_name = &parser.input[first_start as usize..first_end as usize];
2298        let raw = &parser.input[first_end as usize..parser.params_end as usize];
2299        value = if parser.significant_count > 1 {
2300          trim_css_whitespace(raw)
2301        } else {
2302          raw
2303        };
2304        has_colon = false;
2305      } else {
2306        local_name = "";
2307        value = "";
2308        has_colon = false;
2309      }
2310      if local_name.is_empty() || (!has_colon && value.is_empty()) {
2311        self.handle_warning.handle_warning(Warning {
2312          range: Range::new(start, at_rule_end),
2313          kind: WarningKind::Unexpected {
2314            message: "Broken '@value' at-rule",
2315          },
2316        });
2317      }
2318      if !local_name.is_empty() {
2319        self
2320          .dependency_context
2321          .push_dependency(Dependency::ICSSExportValue {
2322            prop: local_name,
2323            value,
2324          });
2325        self.insert_icss_symbol(local_name);
2326      }
2327    }
2328    self
2329      .dependency_context
2330      .push_dependency(Dependency::Replace {
2331        content: "",
2332        range: Range::new(start, at_rule_end),
2333      });
2334    Some(())
2335  }
2336
2337  fn lex_icss_export(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2338    let left_curly = stream.next_parser_token().token;
2339    if left_curly.kind != TokenKind::LeftCurlyBracket {
2340      self.handle_warning.handle_warning(Warning {
2341        range: Range::new(left_curly.range.start, left_curly.range.end),
2342        kind: WarningKind::Unexpected {
2343          message: "Expected '{' during parsing of ':export'",
2344        },
2345      });
2346      return Some(());
2347    }
2348    loop {
2349      let first = stream.next_parser_token().token;
2350      if first.kind == TokenKind::Eof {
2351        return None;
2352      }
2353      if first.kind == TokenKind::RightCurlyBracket {
2354        break;
2355      }
2356      let prop_start = first.range.start;
2357      let prop_end = self.consume_icss_export_prop(stream, first)?;
2358      let colon = stream.peek_significant_skipping_comments(true).token;
2359      if colon.kind != TokenKind::Colon {
2360        self.handle_warning.handle_warning(Warning {
2361          range: Range::new(colon.range.start, colon.range.end),
2362          kind: WarningKind::Unexpected {
2363            message: "Expected ':' during parsing of ':export'",
2364          },
2365        });
2366        return Some(());
2367      }
2368      stream.next_parser_token();
2369      let value_start_token = stream.next_parser_token().token;
2370      if value_start_token.kind == TokenKind::Eof {
2371        return None;
2372      }
2373      let value_start = value_start_token.range.start;
2374      let value_end = self.consume_icss_value(stream, value_start_token)?;
2375      let delimiter = stream.next_parser_token().token;
2376      let value = stream
2377        .slice(value_start, value_end)?
2378        .trim_end_matches(is_css_white_space_char);
2379      self
2380        .dependency_context
2381        .push_dependency(Dependency::ICSSExportValue {
2382          prop: stream
2383            .slice(prop_start, prop_end)?
2384            .trim_end_matches(is_css_white_space_char),
2385          value,
2386        });
2387      self.insert_icss_symbol(
2388        stream
2389          .slice(prop_start, prop_end)?
2390          .trim_end_matches(is_css_white_space_char),
2391      );
2392      if delimiter.kind == TokenKind::RightCurlyBracket {
2393        break;
2394      }
2395    }
2396    Some(())
2397  }
2398
2399  fn lex_local_var(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2400    let name_token = stream.next_parser_token().token;
2401    let start = name_token.range.start;
2402    let raw_name = stream.slice(start, name_token.range.end)?;
2403    let name = if name_token.kind == TokenKind::Ident {
2404      dashed_ident_name(raw_name)
2405    } else {
2406      None
2407    };
2408    let Some(name) = name else {
2409      self.handle_warning.handle_warning(Warning {
2410        kind: WarningKind::Unexpected {
2411          message: "Expected starts with '--' during parsing of 'var()'",
2412        },
2413        range: Range::new(start, (start + 2).min(name_token.range.end)),
2414      });
2415      return Some(());
2416    };
2417    let end = name_token.range.end;
2418    let next = stream.peek_significant_skipping_comments(true).token;
2419    let (from, from_is_global) = if next.kind == TokenKind::Ident
2420      && is_ascii_keyword(stream.slice(next.range.start, next.range.end)?, "from")
2421    {
2422      stream.next_parser_token();
2423      let path_token = stream.peek_significant_skipping_comments(true).token;
2424      let path_start = path_token.range.start;
2425      let path_end = path_token.range.end;
2426      if !matches!(path_token.kind, TokenKind::QuotedString | TokenKind::Ident) {
2427        self.handle_warning.handle_warning(Warning {
2428          range: Range::new(path_start, path_end),
2429          kind: WarningKind::Unexpected {
2430            message: "Expected string or ident during parsing of 'var()'",
2431          },
2432        });
2433        return Some(());
2434      }
2435      stream.next_parser_token();
2436      let path = stream.slice(path_start, path_end)?;
2437      (
2438        Some(path),
2439        path_token.kind == TokenKind::Ident && path == "global",
2440      )
2441    } else {
2442      (None, false)
2443    };
2444    if from_is_global {
2445      let name_start = dashed_ident_name_start(raw_name)?;
2446      stream
2447        .lexer_mut()
2448        .visitor_mut()
2449        .discard_last(Range::new(start + name_start as Pos, end));
2450    }
2451    self
2452      .dependency_context
2453      .push_dependency(Dependency::LocalVar {
2454        name,
2455        range: Range::new(start, end),
2456        from,
2457        from_is_global,
2458      });
2459    Some(())
2460  }
2461
2462  fn lex_local_dashed_ident_decl(
2463    &mut self,
2464    stream: &mut DependencyTokenStream<'_, 's>,
2465    local_decl_dependency: impl FnOnce(&'s str, Range) -> Dependency<'s>,
2466    dashed_warning: impl FnOnce(Range) -> Warning<'s>,
2467    left_curly_warning: impl FnOnce(Range) -> Warning<'s>,
2468  ) -> Option<()> {
2469    let name_token = stream.next_parser_token().token;
2470    let start = name_token.range.start;
2471    if name_token.kind != TokenKind::Ident
2472      || !stream.slice(start, name_token.range.end)?.starts_with("--")
2473    {
2474      self
2475        .handle_warning
2476        .handle_warning(dashed_warning(Range::new(
2477          start,
2478          (start + 2).min(name_token.range.end),
2479        )));
2480      return Some(());
2481    }
2482    let end = name_token.range.end;
2483    self
2484      .dependency_context
2485      .push_dependency(local_decl_dependency(
2486        dashed_ident_name(stream.slice(start, end)?)?,
2487        Range::new(start, end),
2488      ));
2489    let left_curly = stream.peek_significant_skipping_comments(true).token;
2490    if left_curly.kind != TokenKind::LeftCurlyBracket {
2491      self
2492        .handle_warning
2493        .handle_warning(left_curly_warning(Range::new(
2494          left_curly.range.start,
2495          left_curly.range.end,
2496        )));
2497      return Some(());
2498    }
2499    Some(())
2500  }
2501
2502  fn lex_local_keyframes_decl(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2503    let mut is_function = false;
2504    let first = stream.next_parser_token().token;
2505    let name_token = if first.kind == TokenKind::Colon {
2506      let pseudo_start = first.range.start;
2507      let pseudo_name = stream.next_parser_token().token;
2508      let pseudo_end = if matches!(pseudo_name.kind, TokenKind::Ident | TokenKind::Function) {
2509        pseudo_name.range.end
2510      } else {
2511        first.range.end
2512      };
2513      let pseudo = stream.slice(pseudo_start, pseudo_end)?;
2514      if pseudo_name.kind == TokenKind::Function {
2515        self.handle_pseudo_function(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2516      } else if pseudo_name.kind == TokenKind::Ident {
2517        self.handle_pseudo_class(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2518      }
2519      let mode_data = self
2520        .mode_data
2521        .as_ref()
2522        .expect("CSS Modules mode data must exist while parsing keyframes");
2523      if mode_data.is_pure_mode()
2524        && !mode_data.is_pure_check_disabled()
2525        && (pseudo.eq_ignore_ascii_case(":global(") || pseudo.eq_ignore_ascii_case(":global"))
2526      {
2527        self.handle_warning.handle_warning(Warning {
2528          range: Range::new(pseudo_start, pseudo_end),
2529          kind: WarningKind::NotPure {
2530            message: "'@keyframes :global' is not allowed in pure mode",
2531          },
2532        });
2533      }
2534      is_function =
2535        pseudo.eq_ignore_ascii_case(":local(") || pseudo.eq_ignore_ascii_case(":global(");
2536      if !is_function
2537        && !pseudo.eq_ignore_ascii_case(":local")
2538        && !pseudo.eq_ignore_ascii_case(":global")
2539      {
2540        self.handle_warning.handle_warning(Warning {
2541                    range: Range::new(pseudo_start, pseudo_end),
2542                    kind: WarningKind::Unexpected {
2543                        message: "Expected ':local', ':local()', ':global', or ':global()' during parsing of '@keyframes' name",
2544                    }
2545                });
2546        return Some(());
2547      }
2548      stream.next_parser_token().token
2549    } else {
2550      first
2551    };
2552
2553    let start = name_token.range.start;
2554    if name_token.kind != TokenKind::Ident {
2555      self.handle_warning.handle_warning(Warning {
2556        range: Range::new(start, start.saturating_add(2).min(stream.source_end())),
2557        kind: WarningKind::Unexpected {
2558          message: "Expected ident during parsing of '@keyframes' name",
2559        },
2560      });
2561      return Some(());
2562    }
2563    let end = name_token.range.end;
2564    if self
2565      .mode_data
2566      .as_ref()
2567      .expect("CSS Modules mode data must exist while parsing keyframes")
2568      .is_current_local_mode()
2569    {
2570      let name = stream.slice(start, end)?;
2571      self
2572        .dependency_context
2573        .push_dependency(Dependency::LocalKeyframesDecl {
2574          name,
2575          range: Range::new(start, end),
2576        });
2577    }
2578    if is_function {
2579      let right_parenthesis = stream.peek_significant_skipping_comments(true).token;
2580      if right_parenthesis.kind != TokenKind::RightParenthesis {
2581        self.handle_warning.handle_warning(Warning {
2582          range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2583          kind: WarningKind::Unexpected {
2584            message: "Expected ')' during parsing of '@keyframes :local(' or '@keyframes :global('",
2585          },
2586        });
2587        return Some(());
2588      }
2589      stream.next_parser_token();
2590      self
2591        .dependency_context
2592        .push_dependency(Dependency::Replace {
2593          content: "",
2594          range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2595        });
2596      let mode_data = self
2597        .mode_data
2598        .as_mut()
2599        .expect("CSS Modules mode data must exist while leaving a keyframes mode function");
2600      mode_data.inside_mode_function -= 1;
2601      self.balanced.pop_without_moda_data();
2602    }
2603    let left_curly = stream.peek_significant_skipping_comments(true).token;
2604    if left_curly.kind != TokenKind::LeftCurlyBracket {
2605      self.handle_warning.handle_warning(Warning {
2606        range: Range::new(left_curly.range.start, left_curly.range.end),
2607        kind: WarningKind::Unexpected {
2608          message: "Expected '{' during parsing of '@keyframes'",
2609        },
2610      });
2611      return Some(());
2612    }
2613    Some(())
2614  }
2615
2616  fn handle_local_keyframes_dependency(&mut self, lexer: &DependencyLexer<'s>) -> Option<()> {
2617    let animation = self
2618      .in_animation_property
2619      .as_mut()
2620      .expect("animation state must exist while handling an animation dependency");
2621    if let Some(range) = animation.take_rename(self.balanced.len()) {
2622      self
2623        .dependency_context
2624        .push_dependency(Dependency::LocalKeyframes {
2625          name: lexer.slice(range.start, range.end)?,
2626          range,
2627        });
2628    }
2629    animation.reset_reserved();
2630    Some(())
2631  }
2632
2633  fn lex_local_counter_style_decl(
2634    &mut self,
2635    stream: &mut DependencyTokenStream<'_, 's>,
2636  ) -> Option<()> {
2637    let name_token = stream.next_parser_token().token;
2638    let start = name_token.range.start;
2639    if name_token.kind != TokenKind::Ident {
2640      self.handle_warning.handle_warning(Warning {
2641        range: Range::new(start, name_token.range.end),
2642        kind: WarningKind::Unexpected {
2643          message: "Expected ident during parsing of '@counter-style'",
2644        },
2645      });
2646      return Some(());
2647    }
2648    let end = name_token.range.end;
2649    self
2650      .dependency_context
2651      .push_dependency(Dependency::LocalCounterStyleDecl {
2652        name: stream.slice(start, end)?,
2653        range: Range::new(start, end),
2654      });
2655    let left_curly = stream.peek_significant_skipping_comments(true).token;
2656    if left_curly.kind != TokenKind::LeftCurlyBracket {
2657      self.handle_warning.handle_warning(Warning {
2658        range: Range::new(left_curly.range.start, left_curly.range.end),
2659        kind: WarningKind::Unexpected {
2660          message: "Expected '{' during parsing of '@counter-style'",
2661        },
2662      });
2663      return Some(());
2664    }
2665    Some(())
2666  }
2667
2668  fn lex_local_container_at_rule(
2669    &mut self,
2670    stream: &mut DependencyTokenStream<'_, 's>,
2671  ) -> Option<()> {
2672    let name_token = stream.next_parser_token().token;
2673    if name_token.kind == TokenKind::LeftParenthesis {
2674      return Some(());
2675    }
2676    if name_token.kind != TokenKind::Ident {
2677      return Some(());
2678    }
2679    let start = name_token.range.start;
2680    let end = name_token.range.end;
2681    let ident = stream.slice(start, end)?;
2682    if is_reserved_container_query_ident(ident, name_token.flags) {
2683      return Some(());
2684    }
2685    if self
2686      .mode_data
2687      .as_ref()
2688      .expect("CSS Modules mode data must exist while parsing a container")
2689      .is_current_local_mode()
2690    {
2691      self
2692        .dependency_context
2693        .push_dependency(Dependency::LocalContainer {
2694          name: ident,
2695          range: Range::new(start, end),
2696        });
2697    }
2698    Some(())
2699  }
2700
2701  fn lex_local_function_decl(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2702    let mut is_function = false;
2703    let first = stream.next_parser_token().token;
2704    let name_token = if first.kind == TokenKind::Colon {
2705      let pseudo_start = first.range.start;
2706      let pseudo_name = stream.next_parser_token().token;
2707      let pseudo_end = if matches!(pseudo_name.kind, TokenKind::Ident | TokenKind::Function) {
2708        pseudo_name.range.end
2709      } else {
2710        first.range.end
2711      };
2712      let pseudo = stream.slice(pseudo_start, pseudo_end)?;
2713      if pseudo_name.kind == TokenKind::Function {
2714        self.handle_pseudo_function(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2715      } else if pseudo_name.kind == TokenKind::Ident {
2716        self.handle_pseudo_class(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2717      }
2718      let mode_data = self
2719        .mode_data
2720        .as_ref()
2721        .expect("CSS Modules mode data must exist while parsing a function");
2722      if mode_data.is_pure_mode()
2723        && !mode_data.is_pure_check_disabled()
2724        && (pseudo.eq_ignore_ascii_case(":global(") || pseudo.eq_ignore_ascii_case(":global"))
2725      {
2726        self.handle_warning.handle_warning(Warning {
2727          range: Range::new(pseudo_start, pseudo_end),
2728          kind: WarningKind::NotPure {
2729            message: "'@function :global' is not allowed in pure mode",
2730          },
2731        });
2732      }
2733      is_function =
2734        pseudo.eq_ignore_ascii_case(":local(") || pseudo.eq_ignore_ascii_case(":global(");
2735      if !is_function
2736        && !pseudo.eq_ignore_ascii_case(":local")
2737        && !pseudo.eq_ignore_ascii_case(":global")
2738      {
2739        self.handle_warning.handle_warning(Warning {
2740                    range: Range::new(pseudo_start, pseudo_end),
2741                    kind: WarningKind::Unexpected {
2742                        message:
2743                            "Expected ':local', ':local()', ':global', or ':global()' during parsing of '@function' name",
2744                    },
2745                });
2746        return Some(());
2747      }
2748      stream.next_parser_token().token
2749    } else {
2750      first
2751    };
2752
2753    let name_range = if name_token.kind == TokenKind::Function {
2754      name_token.value_range
2755    } else {
2756      name_token.range
2757    };
2758    let start = name_range.start;
2759    let end = name_range.end;
2760    let name = stream.slice_trusted(start, end);
2761    if name_token.kind != TokenKind::Ident && name_token.kind != TokenKind::Function
2762      || !name.starts_with("--")
2763    {
2764      self.handle_warning.handle_warning(Warning {
2765        range: Range::new(start, start.saturating_add(2).min(stream.source_end())),
2766        kind: WarningKind::Unexpected {
2767          message: "Expected starts with '--' during parsing of '@function' name",
2768        },
2769      });
2770      return Some(());
2771    }
2772    if self
2773      .mode_data
2774      .as_ref()
2775      .expect("CSS Modules mode data must exist while parsing a function")
2776      .is_current_local_mode()
2777    {
2778      self
2779        .dependency_context
2780        .push_dependency(Dependency::LocalFunctionDecl {
2781          name: dashed_ident_name(stream.slice(start, end)?)?,
2782          range: Range::new(start, end),
2783        });
2784    }
2785
2786    if is_function {
2787      let right_parenthesis = stream.peek_significant_skipping_comments(true).token;
2788      if right_parenthesis.kind != TokenKind::RightParenthesis {
2789        self.handle_warning.handle_warning(Warning {
2790          range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2791          kind: WarningKind::Unexpected {
2792            message: "Expected ')' during parsing of '@function :local(' or '@function :global('",
2793          },
2794        });
2795        return Some(());
2796      }
2797      stream.next_parser_token();
2798      self
2799        .dependency_context
2800        .push_dependency(Dependency::Replace {
2801          content: "",
2802          range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2803        });
2804      let mode_data = self
2805        .mode_data
2806        .as_mut()
2807        .expect("CSS Modules mode data must exist while leaving a function mode function");
2808      mode_data.inside_mode_function -= 1;
2809      self.balanced.pop_without_moda_data();
2810    }
2811
2812    if name_token.kind == TokenKind::Function {
2813      return Some(());
2814    }
2815    let left_parenthesis = stream.peek_significant_skipping_comments(true).token;
2816    if left_parenthesis.kind != TokenKind::LeftParenthesis {
2817      self.handle_warning.handle_warning(Warning {
2818        range: Range::new(left_parenthesis.range.start, left_parenthesis.range.end),
2819        kind: WarningKind::Unexpected {
2820          message: "Expected '(' during parsing of '@function'",
2821        },
2822      });
2823    }
2824    Some(())
2825  }
2826
2827  fn handle_local_counter_style_dependency(&mut self, lexer: &DependencyLexer<'s>) -> Option<()> {
2828    let list_style = self
2829      .in_list_style_property
2830      .as_mut()
2831      .expect("list-style state must exist while handling a counter-style dependency");
2832    if let Some(range) = list_style.take_rename(self.balanced.len()) {
2833      self
2834        .dependency_context
2835        .push_dependency(Dependency::LocalCounterStyle {
2836          name: lexer.slice(range.start, range.end)?,
2837          range,
2838        });
2839    }
2840    Some(())
2841  }
2842
2843  fn handle_local_font_palette_dependency(&mut self, lexer: &DependencyLexer<'s>) -> Option<()> {
2844    let font_palette = self
2845      .in_font_palette_property
2846      .as_mut()
2847      .expect("font-palette state must exist while handling a font-palette dependency");
2848    if let Some(range) = font_palette.take_rename(self.balanced.len()) {
2849      self
2850        .dependency_context
2851        .push_dependency(Dependency::LocalFontPalette {
2852          name: dashed_ident_name(lexer.slice(range.start, range.end)?)?,
2853          range,
2854        });
2855    }
2856    Some(())
2857  }
2858
2859  fn lex_composes(
2860    &mut self,
2861    stream: &mut DependencyTokenStream<'_, 's>,
2862    local_classes: SmallVec<[&'s str; 2]>,
2863    start: Pos,
2864  ) -> Option<()> {
2865    let colon = stream.peek_significant_skipping_comments(true).token;
2866    if colon.kind != TokenKind::Colon {
2867      return Some(());
2868    }
2869    stream.next_parser_token();
2870    let mut replacement_end = colon.range.end;
2871    loop {
2872      let first = stream.peek_significant_skipping_comments(true).token;
2873      if first.kind == TokenKind::Eof {
2874        break;
2875      }
2876      if first.kind == TokenKind::RightCurlyBracket {
2877        break;
2878      }
2879      if first.kind == TokenKind::Semicolon {
2880        stream.next_parser_token();
2881        replacement_end = first.range.end;
2882        break;
2883      }
2884
2885      let item_start = first.range.start;
2886      let mut item_end = item_start;
2887      let mut names: SmallVec<[&'s str; 2]> = SmallVec::new();
2888      let mut has_from = false;
2889      let mut delimiter = first;
2890      loop {
2891        if matches!(
2892          delimiter.kind,
2893          TokenKind::Comma | TokenKind::Semicolon | TokenKind::RightCurlyBracket
2894        ) {
2895          break;
2896        }
2897
2898        if delimiter.kind == TokenKind::Function
2899          && stream.slice(delimiter.range.start, delimiter.range.end)? == "global("
2900        {
2901          let global_start = delimiter.range.start;
2902          stream.next_parser_token();
2903          let name_token = stream.next_parser_token().token;
2904          let Some(name_range) = ident_like_range(name_token) else {
2905            self.handle_warning.handle_warning(Warning {
2906              range: Range::new(
2907                name_token.range.start,
2908                name_token
2909                  .range
2910                  .start
2911                  .saturating_add(2)
2912                  .min(stream.source_end()),
2913              ),
2914              kind: WarningKind::Unexpected {
2915                message: "Expected ident during parsing of 'composes'",
2916              },
2917            });
2918            return Some(());
2919          };
2920          let right_parenthesis = stream.peek_significant_skipping_comments(true).token;
2921          if right_parenthesis.kind != TokenKind::RightParenthesis {
2922            self.handle_warning.handle_warning(Warning {
2923              range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2924              kind: WarningKind::Unexpected {
2925                message: "Expected ')' during parsing of 'composes'",
2926              },
2927            });
2928            return Some(());
2929          }
2930          stream.next_parser_token();
2931          item_end = right_parenthesis.range.end;
2932          self.dependency_context.push_composes(
2933            local_classes.iter().copied(),
2934            std::iter::once(stream.slice(name_range.start, name_range.end)?),
2935            Some("global"),
2936            true,
2937            Range::new(global_start, item_end),
2938          );
2939          delimiter = stream.peek_significant_skipping_comments(true).token;
2940          continue;
2941        }
2942
2943        let Some(name_range) = ident_like_range(delimiter) else {
2944          let name_start = delimiter.range.start;
2945          self.handle_warning.handle_warning(Warning {
2946            range: Range::new(
2947              name_start,
2948              name_start.saturating_add(2).min(stream.source_end()),
2949            ),
2950            kind: WarningKind::Unexpected {
2951              message: "Expected ident during parsing of 'composes'",
2952            },
2953          });
2954          return Some(());
2955        };
2956        let ident = stream.slice(name_range.start, name_range.end)?;
2957        if !names.is_empty() && ident.eq_ignore_ascii_case("from") {
2958          stream.next_parser_token();
2959          let path = stream.peek_significant_skipping_comments(true).token;
2960          if matches!(
2961            path.kind,
2962            TokenKind::QuotedString | TokenKind::Ident | TokenKind::Function
2963          ) {
2964            let path_range = if path.kind == TokenKind::Function {
2965              path.value_range
2966            } else {
2967              path.range
2968            };
2969            item_end = path_range.end;
2970            let from_is_global = path.kind == TokenKind::Ident
2971              && is_ascii_keyword(stream.slice(path_range.start, path_range.end)?, "global");
2972            self.dependency_context.push_composes(
2973              local_classes.iter().copied(),
2974              std::mem::take(&mut names),
2975              Some(stream.slice(path_range.start, path_range.end)?),
2976              from_is_global,
2977              Range::new(item_start, item_end),
2978            );
2979            has_from = true;
2980            stream.next_parser_token();
2981            delimiter = stream.peek_significant_skipping_comments(true).token;
2982            break;
2983          }
2984          names.push(ident);
2985          item_end = name_range.end;
2986          delimiter = path;
2987          continue;
2988        }
2989        names.push(ident);
2990        item_end = name_range.end;
2991        stream.next_parser_token();
2992        delimiter = stream.peek_significant_skipping_comments(true).token;
2993      }
2994
2995      if has_from {
2996        if delimiter.kind == TokenKind::Comma {
2997          stream.next_parser_token();
2998          replacement_end = delimiter.range.end;
2999          continue;
3000        }
3001        if delimiter.kind == TokenKind::Semicolon {
3002          stream.next_parser_token();
3003          replacement_end = delimiter.range.end;
3004          break;
3005        }
3006        if delimiter.kind == TokenKind::RightCurlyBracket {
3007          replacement_end = item_end;
3008          break;
3009        }
3010        replacement_end = item_end;
3011        break;
3012      }
3013
3014      if delimiter.kind == TokenKind::Comma {
3015        if !names.is_empty() {
3016          self.dependency_context.push_composes(
3017            local_classes.iter().copied(),
3018            names,
3019            None,
3020            false,
3021            Range::new(item_start, item_end),
3022          );
3023        }
3024        stream.next_parser_token();
3025        replacement_end = delimiter.range.end;
3026        continue;
3027      }
3028
3029      if delimiter.kind == TokenKind::Semicolon {
3030        if !names.is_empty() {
3031          self.dependency_context.push_composes(
3032            local_classes.iter().copied(),
3033            names,
3034            None,
3035            false,
3036            Range::new(item_start, item_end),
3037          );
3038        }
3039        stream.next_parser_token();
3040        replacement_end = delimiter.range.end;
3041        break;
3042      }
3043
3044      if delimiter.kind == TokenKind::RightCurlyBracket {
3045        if !names.is_empty() {
3046          self.dependency_context.push_composes(
3047            local_classes.iter().copied(),
3048            names,
3049            None,
3050            false,
3051            Range::new(item_start, item_end),
3052          );
3053        }
3054        replacement_end = item_end;
3055        break;
3056      }
3057
3058      // An invalid token was encountered after a name.  The next loop
3059      // iteration would produce the same warning, but keeping the
3060      // cursor at that token preserves the legacy recovery point.
3061      self.handle_warning.handle_warning(Warning {
3062        range: Range::new(
3063          delimiter.range.start,
3064          delimiter
3065            .range
3066            .start
3067            .saturating_add(2)
3068            .min(stream.source_end()),
3069        ),
3070        kind: WarningKind::Unexpected {
3071          message: "Expected ident during parsing of 'composes'",
3072        },
3073      });
3074      return Some(());
3075    }
3076    self
3077      .dependency_context
3078      .push_dependency(Dependency::Replace {
3079        content: "",
3080        range: Range::new(start, replacement_end),
3081      });
3082    Some(())
3083  }
3084}
3085
3086impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
3087  fn handle_comment(
3088    &mut self,
3089    lexer: &mut DependencyLexer<'s>,
3090    start: Pos,
3091    end: Pos,
3092  ) -> Option<()> {
3093    let Some(mode_data) = &mut self.mode_data else {
3094      return Some(());
3095    };
3096    if !mode_data.is_pure_mode() || end < start + 4 {
3097      return Some(());
3098    }
3099
3100    let content = lexer.slice(start + 2, end - 2)?;
3101    if is_css_modules_magic_comment(content, "cssmodules-pure-ignore") {
3102      mode_data.mark_pure_ignore();
3103    } else if matches!(self.scope, Scope::TopLevel)
3104      && self.block_nesting_level == 0
3105      && is_css_modules_magic_comment(content, "cssmodules-pure-no-check")
3106    {
3107      mode_data.mark_pure_no_check();
3108    }
3109    Some(())
3110  }
3111
3112  fn handle_url(
3113    &mut self,
3114    lexer: &mut DependencyLexer<'s>,
3115    start: Pos,
3116    end: Pos,
3117    content_start: Pos,
3118    content_end: Pos,
3119    flags: TokenFlags,
3120  ) -> Option<()> {
3121    let value = lexer.slice(content_start, content_end)?;
3122    match self.scope {
3123      Scope::InAtImport(ref mut import_data) => {
3124        if import_data.in_supports() {
3125          return Some(());
3126        }
3127        if import_data.url.is_some() {
3128          self.handle_warning.handle_warning(Warning {
3129            range: Range::new(import_data.start, end),
3130            kind: WarningKind::DuplicateUrl {
3131              when: lexer.slice(import_data.start, end)?,
3132            },
3133          });
3134          return Some(());
3135        }
3136        import_data.prelude.push(ImportPreludeNode::Url {
3137          range: Range::new(start, end),
3138        });
3139        import_data.url = Some(value);
3140        import_data.url_flags = flags;
3141        import_data.url_range = Some(Range::new(start, end));
3142      }
3143      Scope::InBlock => self.dependency_context.push_dependency(Dependency::Url {
3144        request: value,
3145        range: Range::new(start, end),
3146        kind: UrlRangeKind::Function,
3147      }),
3148      _ => {}
3149    }
3150    Some(())
3151  }
3152
3153  fn handle_string(
3154    &mut self,
3155    lexer: &mut DependencyLexer<'s>,
3156    start: Pos,
3157    end: Pos,
3158    flags: TokenFlags,
3159  ) -> Option<()> {
3160    match self.scope {
3161      Scope::InAtImport(ref mut import_data) => {
3162        let inside_url = matches!(
3163            self.balanced.last(),
3164            Some(last) if matches!(last.kind, BalancedItemKind::Url)
3165        );
3166
3167        // Do not parse URLs in `supports(...)` and other strings if we already have a URL
3168        if import_data.in_supports() || (!inside_url && import_data.url.is_some()) {
3169          return Some(());
3170        }
3171
3172        if inside_url && import_data.url.is_some() {
3173          self.handle_warning.handle_warning(Warning {
3174            range: Range::new(import_data.start, end),
3175            kind: WarningKind::DuplicateUrl {
3176              when: lexer.slice(import_data.start, end)?,
3177            },
3178          });
3179          return Some(());
3180        }
3181
3182        let value = lexer.slice(start + 1, end - 1)?;
3183        import_data.url = Some(value);
3184        import_data.url_flags = flags;
3185        // For url("inside_url") url_range will determined in right_parenthesis
3186        if !inside_url {
3187          import_data.prelude.push(ImportPreludeNode::Url {
3188            range: Range::new(start, end),
3189          });
3190          import_data.url_range = Some(Range::new(start, end));
3191        }
3192      }
3193      Scope::InBlock => {
3194        if let Some(mode_data) = &self.mode_data
3195          && mode_data.is_property_local_mode()
3196          && matches!(
3197            self.in_grid_property,
3198            Some(GridPropertyKind::TemplateLike | GridPropertyKind::TemplateAreas)
3199          )
3200        {
3201          for range in parse_grid_template_area_ranges(lexer.slice(start + 1, end - 1)?, start + 1)
3202          {
3203            self
3204              .dependency_context
3205              .push_dependency(Dependency::LocalGridDecl {
3206                name: lexer.slice(range.start, range.end)?,
3207                range,
3208              });
3209          }
3210        }
3211        let Some(last) = self.balanced.last() else {
3212          return Some(());
3213        };
3214        let kind = match last.kind {
3215          BalancedItemKind::Url => UrlRangeKind::String,
3216          BalancedItemKind::ImageSet => UrlRangeKind::Function,
3217          _ => return Some(()),
3218        };
3219        let value = lexer.slice(start + 1, end - 1)?;
3220        self.dependency_context.push_dependency(Dependency::Url {
3221          request: value,
3222          range: Range::new(start, end),
3223          kind,
3224        });
3225      }
3226      _ => {}
3227    }
3228    Some(())
3229  }
3230
3231  fn handle_at_keyword(
3232    &mut self,
3233    stream: &mut DependencyTokenStream<'_, 's>,
3234    start: Pos,
3235    end: Pos,
3236    flags: TokenFlags,
3237  ) -> Option<()> {
3238    let name = stream.slice_trusted(start, end);
3239    let kind = Self::classify_at_rule(name, flags);
3240    self.set_scan_context(stream, ScanContext::AtRule);
3241    if kind == AtRuleKind::Namespace {
3242      self.scope = Scope::AtNamespaceInvalid;
3243      self.handle_warning.handle_warning(Warning {
3244        range: Range::new(start, end),
3245        kind: WarningKind::NamespaceNotSupportedInBundledCss,
3246      });
3247    } else if kind == AtRuleKind::Import {
3248      if !self.allow_import_at_rule {
3249        self.scope = Scope::AtImportInvalid;
3250        self.handle_warning.handle_warning(Warning {
3251          range: Range::new(start, end),
3252          kind: WarningKind::NotPrecededAtImport,
3253        });
3254        return Some(());
3255      }
3256      self.scope = Scope::InAtImport(ImportData::new(start));
3257    } else if kind == AtRuleKind::Charset {
3258      self.lex_charset_at_rule(stream, start)?;
3259      self.set_scan_context(stream, ScanContext::TopLevel);
3260      self.is_next_rule_prelude = true;
3261    } else if self.mode_data.is_some() {
3262      let mut can_contain_rules = true;
3263      if kind == AtRuleKind::Value {
3264        self.lex_value_at_rule(stream, start)?;
3265        can_contain_rules = false;
3266        self.set_scan_context(stream, ScanContext::TopLevel);
3267        self.is_next_rule_prelude = true;
3268      } else if kind == AtRuleKind::Keyframes {
3269        self.lex_local_keyframes_decl(stream)?;
3270      } else if kind == AtRuleKind::Container {
3271        self.lex_local_container_at_rule(stream)?;
3272      } else if kind == AtRuleKind::Function {
3273        self.lex_local_function_decl(stream)?;
3274      } else if kind == AtRuleKind::Property {
3275        self.lex_local_dashed_ident_decl(
3276          stream,
3277          |name, range| Dependency::LocalPropertyDecl { name, range },
3278          |range| Warning {
3279            range,
3280            kind: WarningKind::Unexpected {
3281              message: "Expected starts with '--' during parsing of '@property'",
3282            },
3283          },
3284          |range| Warning {
3285            range,
3286            kind: WarningKind::Unexpected {
3287              message: "Expected '{' during parsing of '@property'",
3288            },
3289          },
3290        )?;
3291      } else if kind == AtRuleKind::CounterStyle {
3292        self.lex_local_counter_style_decl(stream)?;
3293      } else if kind == AtRuleKind::FontPaletteValues {
3294        self.lex_local_dashed_ident_decl(
3295          stream,
3296          |name, range| Dependency::LocalFontPaletteDecl { name, range },
3297          |range| Warning {
3298            range,
3299            kind: WarningKind::Unexpected {
3300              message: "Expected starts with '--' during parsing of '@font-palette-values'",
3301            },
3302          },
3303          |range| Warning {
3304            range,
3305            kind: WarningKind::Unexpected {
3306              message: "Expected '{' during parsing of '@font-palette-values'",
3307            },
3308          },
3309        )?;
3310      } else {
3311        self.is_next_rule_prelude = kind == AtRuleKind::Scope;
3312        if self.is_next_rule_prelude {
3313          self.set_scan_context(stream, ScanContext::Selector);
3314        }
3315      }
3316
3317      let mode_data = self
3318        .mode_data
3319        .as_mut()
3320        .expect("CSS Modules mode data must exist while handling an at-rule");
3321      if can_contain_rules && self.block_nesting_level == 0 {
3322        mode_data.composes_local_classes.find_at_keyword();
3323      }
3324
3325      if mode_data.is_pure_mode() {
3326        mode_data.pure_global = None;
3327      }
3328    }
3329    Some(())
3330  }
3331
3332  fn handle_semicolon(
3333    &mut self,
3334    lexer: &mut DependencyLexer<'s>,
3335    start: Pos,
3336    end: Pos,
3337  ) -> Option<()> {
3338    match self.scope {
3339      Scope::InAtImport(ref import_data) => {
3340        let Some(url) = import_data.url else {
3341          if let Some((name, name_range)) = import_data.prelude.icss_import_url() {
3342            self
3343              .dependency_context
3344              .push_dependency(Dependency::ICSSImportUrl {
3345                name,
3346                range: Range::new(import_data.start, end),
3347                name_range: *name_range,
3348              });
3349            self.scope = Scope::TopLevel;
3350            return Some(());
3351          }
3352          self.handle_warning.handle_warning(Warning {
3353            range: Range::new(import_data.start, end),
3354            kind: WarningKind::ExpectedUrl {
3355              when: lexer.slice(import_data.start, end)?,
3356            },
3357          });
3358          self.scope = Scope::TopLevel;
3359          return Some(());
3360        };
3361        let Some(url_range) = &import_data.url_range else {
3362          self.handle_warning.handle_warning(Warning {
3363            range: Range::new(start, end),
3364            kind: WarningKind::Unexpected {
3365              message: "Unexpected ';' during parsing of '@import url()'",
3366            },
3367          });
3368          self.scope = Scope::TopLevel;
3369          return Some(());
3370        };
3371        if let Some(range) = import_data.prelude.first_non_url_before(url_range) {
3372          self.handle_warning.handle_warning(Warning {
3373            range: *url_range,
3374            kind: WarningKind::ExpectedUrlBefore {
3375              when: lexer.slice(range.start, url_range.end)?,
3376            },
3377          });
3378          self.scope = Scope::TopLevel;
3379          return Some(());
3380        }
3381        let layer = match &import_data.layer {
3382          ImportDataLayer::None => None,
3383          ImportDataLayer::EndLayer { value, range } => {
3384            if url_range.start > range.start {
3385              self.handle_warning.handle_warning(Warning {
3386                range: *url_range,
3387                kind: WarningKind::ExpectedUrlBefore {
3388                  when: lexer.slice(range.start, url_range.end)?,
3389                },
3390              });
3391              self.scope = Scope::TopLevel;
3392              return Some(());
3393            }
3394            Some(*value)
3395          }
3396        };
3397        let supports = match &import_data.supports {
3398          ImportDataSupports::None => None,
3399          ImportDataSupports::InSupports => {
3400            self.handle_warning.handle_warning(Warning {
3401              range: Range::new(start, end),
3402              kind: WarningKind::Unexpected {
3403                message: "Unexpected ';' during parsing of 'supports()'",
3404              },
3405            });
3406            None
3407          }
3408          ImportDataSupports::EndSupports { value, range } => {
3409            if url_range.start > range.start {
3410              self.handle_warning.handle_warning(Warning {
3411                range: *url_range,
3412                kind: WarningKind::ExpectedUrlBefore {
3413                  when: lexer.slice(range.start, url_range.end)?,
3414                },
3415              });
3416              self.scope = Scope::TopLevel;
3417              return Some(());
3418            }
3419            Some(*value)
3420          }
3421        };
3422        if let Some(layer_range) = import_data.layer_range()
3423          && let Some(supports_range) = import_data.supports_range()
3424          && layer_range.start > supports_range.start
3425        {
3426          self.handle_warning.handle_warning(Warning {
3427            range: *layer_range,
3428            kind: WarningKind::ExpectedLayerBefore {
3429              when: lexer.slice(supports_range.start, layer_range.end)?,
3430            },
3431          });
3432          self.scope = Scope::TopLevel;
3433          return Some(());
3434        }
3435        let last_end = import_data
3436          .supports_range()
3437          .or_else(|| import_data.layer_range())
3438          .unwrap_or(url_range)
3439          .end;
3440        let media = self.get_media(lexer, last_end, start);
3441        self.dependency_context.push_import(
3442          url,
3443          Range::new(import_data.start, end),
3444          layer,
3445          supports,
3446          media,
3447        );
3448        self.scope = Scope::TopLevel;
3449      }
3450      Scope::AtImportInvalid | Scope::AtNamespaceInvalid => {
3451        self.scope = Scope::TopLevel;
3452      }
3453      Scope::InBlock => {
3454        if let Some(mode_data) = &mut self.mode_data {
3455          mode_data.pure_global = Some(end);
3456
3457          if mode_data.is_property_local_mode() {
3458            if self.in_animation_property.is_some() {
3459              self.handle_local_keyframes_dependency(lexer)?;
3460              self.exit_animation_property();
3461            }
3462            if self.in_list_style_property.is_some() {
3463              self.handle_local_counter_style_dependency(lexer)?;
3464              self.exit_list_style_property();
3465            }
3466            if self.in_font_palette_property.is_some() {
3467              self.handle_local_font_palette_dependency(lexer)?;
3468              self.exit_font_palette_property();
3469            }
3470            if self.in_container_property.is_some() {
3471              self.exit_container_property();
3472            }
3473            if self.in_grid_property.is_some() {
3474              self.exit_grid_property();
3475            }
3476          }
3477        }
3478        self.pending_custom_property = None;
3479        self.pending_grid_property = None;
3480        self.property_kind = PropertyKind::Generic;
3481      }
3482      Scope::TopLevel => {
3483        self.is_next_rule_prelude = true;
3484      }
3485    }
3486    Some(())
3487  }
3488
3489  fn handle_function(
3490    &mut self,
3491    stream: &mut DependencyTokenStream<'_, 's>,
3492    start: Pos,
3493    end: Pos,
3494    flags: TokenFlags,
3495  ) -> Option<()> {
3496    let name = stream.slice_trusted(start, end);
3497    let mut normalized = [0; MAX_CSS_KEYWORD_LEN];
3498    let normalized_name = if flags.has_escape() {
3499      decode_css_keyword(name, &mut normalized)
3500    } else {
3501      lowercase_ascii_keyword(name, &mut normalized)
3502    };
3503    let item = normalized_name.map_or_else(
3504      || BalancedItem::new_other(start, end),
3505      |name| BalancedItem::new_normalized(name, start, end),
3506    );
3507    let at_import_top_level =
3508      matches!(self.scope, Scope::InAtImport(_)) && self.balanced.is_empty();
3509    self.balanced.push(item, self.mode_data.as_mut());
3510
3511    if let Scope::InAtImport(ref mut import_data) = self.scope {
3512      if at_import_top_level && normalized_name == Some("url(") {
3513        import_data.prelude.push(ImportPreludeNode::Url {
3514          range: Range::new(start, end),
3515        });
3516      } else if at_import_top_level && normalized_name == Some("layer(") {
3517        import_data.prelude.push(ImportPreludeNode::Layer {
3518          range: Range::new(start, end),
3519        });
3520      } else if at_import_top_level && normalized_name == Some("supports(") {
3521        import_data.prelude.push(ImportPreludeNode::Supports {
3522          range: Range::new(start, end),
3523        });
3524        import_data.supports = ImportDataSupports::InSupports;
3525      } else if at_import_top_level {
3526        import_data.prelude.push(ImportPreludeNode::Other {
3527          range: Range::new(start, end),
3528        });
3529      } else if normalized_name == Some("supports(") {
3530        import_data.supports = ImportDataSupports::InSupports;
3531      }
3532    }
3533
3534    if let Scope::InAtImport(ref mut import_data) = self.scope {
3535      let layer_end = at_import_top_level && normalized_name == Some("layer(");
3536      let supports_end = normalized_name == Some("supports(");
3537      if layer_end || supports_end {
3538        let Some(close) = stream.fast_forward(TokenKind::RightParenthesis) else {
3539          return Some(());
3540        };
3541        self.balanced.pop(self.mode_data.as_mut());
3542        if layer_end {
3543          import_data.layer = ImportDataLayer::EndLayer {
3544            value: stream.slice(end, close.end.saturating_sub(1))?,
3545            range: Range::new(start, close.end),
3546          };
3547        } else {
3548          import_data.supports = ImportDataSupports::EndSupports {
3549            value: stream.slice(end, close.end.saturating_sub(1))?,
3550            range: Range::new(start, close.end),
3551          };
3552        }
3553        return Some(());
3554      }
3555    }
3556
3557    let Some(mode_data) = &self.mode_data else {
3558      return Some(());
3559    };
3560    if mode_data.is_current_local_mode() && name.starts_with("--") && end > start + 1 {
3561      self
3562        .dependency_context
3563        .push_dependency(Dependency::LocalFunction {
3564          name: dashed_ident_name(stream.slice(start, end - 1)?)?,
3565          range: Range::new(start, end - 1),
3566        });
3567    }
3568    if mode_data.is_current_local_mode() && normalized_name == Some("var(") {
3569      self.lex_local_var(stream)?;
3570    }
3571    Some(())
3572  }
3573
3574  fn handle_left_parenthesis(
3575    &mut self,
3576    _: &mut DependencyLexer<'s>,
3577    start: Pos,
3578    end: Pos,
3579  ) -> Option<()> {
3580    self
3581      .balanced
3582      .push(BalancedItem::new_other(start, end), self.mode_data.as_mut());
3583    Some(())
3584  }
3585
3586  fn handle_right_parenthesis(
3587    &mut self,
3588    lexer: &mut DependencyLexer<'s>,
3589    leading_start: Pos,
3590    start: Pos,
3591    end: Pos,
3592  ) -> Option<()> {
3593    let Some(last) = self.balanced.pop(self.mode_data.as_mut()) else {
3594      return Some(());
3595    };
3596    if let Some(mode_data) = &mut self.mode_data {
3597      let mut is_function = last.kind.is_mode_function();
3598      let mut function_end = last.range.end;
3599      if last.kind.is_mode_class() {
3600        self.balanced.pop_mode_pseudo_class(mode_data);
3601        let popped = self
3602          .balanced
3603          .pop_without_moda_data()
3604          .expect("a mode pseudo-class must have a preceding balanced item");
3605        debug_assert!(!matches!(
3606          popped.kind,
3607          BalancedItemKind::GlobalClass | BalancedItemKind::LocalClass
3608        ));
3609        is_function = popped.kind.is_mode_function();
3610        function_end = popped.range.end;
3611      }
3612      if is_function {
3613        let is_empty = start == function_end || trivia_only(lexer.slice(function_end, start)?);
3614        let replacement_start = if is_empty {
3615          function_end
3616        } else {
3617          leading_start
3618        };
3619        if is_empty {
3620          let maybe_left_parenthesis_start = function_end.saturating_sub(1);
3621          self.handle_warning.handle_warning(Warning {
3622            range: Range::new(maybe_left_parenthesis_start, end),
3623            kind: WarningKind::Unexpected {
3624              message: "':global()' or ':local()' can't be empty",
3625            },
3626          });
3627        }
3628        self
3629          .dependency_context
3630          .push_dependency(Dependency::Replace {
3631            content: "",
3632            range: Range::new(replacement_start, end),
3633          });
3634      }
3635    }
3636    if let Scope::InAtImport(ref mut import_data) = self.scope {
3637      let not_in_supports = !import_data.in_supports();
3638      if matches!(last.kind, BalancedItemKind::Url) && not_in_supports {
3639        import_data.url_range = Some(Range::new(last.range.start, end));
3640      } else if matches!(last.kind, BalancedItemKind::Layer) && not_in_supports {
3641        import_data.layer = ImportDataLayer::EndLayer {
3642          value: lexer.slice(last.range.end, end - 1)?,
3643          range: Range::new(last.range.start, end),
3644        };
3645      } else if matches!(last.kind, BalancedItemKind::Supports) {
3646        import_data.supports = ImportDataSupports::EndSupports {
3647          value: lexer.slice(last.range.end, end - 1)?,
3648          range: Range::new(last.range.start, end),
3649        }
3650      }
3651    }
3652    Some(())
3653  }
3654
3655  fn handle_ident(
3656    &mut self,
3657    stream: &mut DependencyTokenStream<'_, 's>,
3658    start: Pos,
3659    end: Pos,
3660    flags: TokenFlags,
3661  ) -> Option<()> {
3662    let ident = stream.slice_trusted(start, end);
3663    // ICSS references can also occur in selectors and at-rule preludes,
3664    // where the scope is not necessarily a declaration block.
3665    if matches!(self.scope, Scope::TopLevel | Scope::InBlock)
3666      && matches!(
3667        self.scan_context,
3668        ScanContext::Selector
3669          | ScanContext::GenericValue
3670          | ScanContext::SpecialValue(_)
3671          | ScanContext::AtRule
3672      )
3673      && (self.scan_context != ScanContext::Selector || self.selector_square_depth == 0)
3674      && self.contains_icss_symbol(ident)
3675    {
3676      self
3677        .dependency_context
3678        .push_dependency(Dependency::ICSSSymbol {
3679          name: ident,
3680          range: Range::new(start, end),
3681        });
3682      if matches!(self.scope, Scope::TopLevel)
3683        && let Some(mode_data) = &mut self.mode_data
3684      {
3685        mode_data.composes_local_classes.invalidate();
3686      }
3687      return Some(());
3688    }
3689    match self.scope {
3690      Scope::InBlock => {
3691        let is_declaration_name = self.scan_context == ScanContext::DeclarationName;
3692        if is_declaration_name {
3693          let property_local_mode = self
3694            .mode_data
3695            .as_ref()
3696            .is_some_and(ModeData::is_property_local_mode);
3697          (self.property_kind, self.pending_grid_property) =
3698            Self::classify_property(ident, flags, property_local_mode);
3699          self.pending_custom_property =
3700            (self.property_kind == PropertyKind::CustomProperty).then_some(Range::new(start, end));
3701          if self.property_kind != PropertyKind::Composes {
3702            return Some(());
3703          }
3704        }
3705        let Some(mode_data) = &mut self.mode_data else {
3706          return Some(());
3707        };
3708        if mode_data.is_current_local_mode()
3709          && self
3710            .balanced
3711            .last()
3712            .is_some_and(|last| last.kind.is_mode_function())
3713          && ident.starts_with("--")
3714          && stream.peek_parser_token().token.kind == TokenKind::RightParenthesis
3715        {
3716          self
3717            .dependency_context
3718            .push_dependency(Dependency::LocalFunction {
3719              name: dashed_ident_name(stream.slice(start, end)?)?,
3720              range: Range::new(start, end),
3721            });
3722          return Some(());
3723        }
3724        if mode_data.is_property_local_mode()
3725          && matches!(self.scan_context, ScanContext::SpecialValue(_))
3726        {
3727          if let Some(animation) = &mut self.in_animation_property {
3728            // Not inside functions
3729            if self.balanced.is_empty() {
3730              animation.set_rename(stream.slice(start, end)?, flags, Range::new(start, end));
3731            }
3732            return Some(());
3733          }
3734
3735          if let Some(list_style) = &mut self.in_list_style_property {
3736            // Not inside functions
3737            if self.balanced.is_empty() {
3738              list_style.set_rename(stream.slice(start, end)?, flags, Range::new(start, end));
3739            }
3740            return Some(());
3741          }
3742
3743          if let Some(font_palette) = &mut self.in_font_palette_property {
3744            // Not inside functions or inside palette-mix()
3745            if self.balanced.is_empty()
3746              || matches!(self.balanced.last(), Some(last) if matches!(last.kind, BalancedItemKind::PaletteMix))
3747            {
3748              font_palette.set_rename(stream.slice(start, end)?, flags, Range::new(start, end));
3749            }
3750            return Some(());
3751          }
3752
3753          if let Some(container) = &mut self.in_container_property {
3754            if self.balanced.is_empty() {
3755              container.set_rename(ident, flags, Range::new(start, end));
3756              if let Some(range) = container.take_rename(self.balanced.len()) {
3757                self
3758                  .dependency_context
3759                  .push_dependency(Dependency::LocalContainerDecl {
3760                    name: stream.slice(range.start, range.end)?,
3761                    range,
3762                  });
3763              }
3764            }
3765            return Some(());
3766          }
3767
3768          if self.in_grid_property.is_some() {
3769            if self.balanced.is_empty() && !is_reserved_grid_ident(ident, flags) {
3770              self
3771                .dependency_context
3772                .push_dependency(Dependency::LocalGrid {
3773                  name: ident,
3774                  range: Range::new(start, end),
3775                });
3776            }
3777            return Some(());
3778          }
3779        }
3780
3781        if is_declaration_name && self.property_kind == PropertyKind::Composes {
3782          if self.block_nesting_level != 1 {
3783            self.handle_warning.handle_warning(Warning {
3784              range: Range::new(start, end),
3785              kind: WarningKind::UnexpectedComposition {
3786                message: "not allowed in nested rule",
3787              },
3788            });
3789            return Some(());
3790          }
3791          let Some(local_classes) = mode_data
3792            .composes_local_classes
3793            .get_valid_local_classes(stream.lexer())
3794          else {
3795            self.handle_warning.handle_warning(Warning {
3796              range: Range::new(start, end),
3797              kind: WarningKind::UnexpectedComposition {
3798                message: "only allowed when selector is single :local class",
3799              },
3800            });
3801            return Some(());
3802          };
3803          return self.lex_composes(stream, local_classes, start);
3804        }
3805      }
3806      Scope::InAtImport(ref mut import_data) => {
3807        if !self.balanced.is_empty() || import_data.in_supports() {
3808          return Some(());
3809        }
3810
3811        let ident = stream.slice_trusted(start, end);
3812        if ident.eq_ignore_ascii_case("layer") {
3813          import_data.prelude.push(ImportPreludeNode::Layer {
3814            range: Range::new(start, end),
3815          });
3816          import_data.layer = ImportDataLayer::EndLayer {
3817            value: "",
3818            range: Range::new(start, end),
3819          }
3820        } else if import_data.url.is_none() && import_data.prelude.is_empty() {
3821          import_data
3822            .prelude
3823            .push(ImportPreludeNode::IcssUrlCandidate {
3824              name: ident,
3825              range: Range::new(start, end),
3826            });
3827        } else if import_data.url.is_none() {
3828          import_data.prelude.push(ImportPreludeNode::Other {
3829            range: Range::new(start, end),
3830          });
3831        }
3832      }
3833      Scope::TopLevel => {
3834        let Some(mode_data) = &mut self.mode_data else {
3835          return Some(());
3836        };
3837
3838        mode_data.composes_local_classes.invalidate();
3839      }
3840      _ => {}
3841    }
3842    Some(())
3843  }
3844
3845  fn handle_class(
3846    &mut self,
3847    lexer: &mut DependencyLexer<'s>,
3848    start: Pos,
3849    end: Pos,
3850    _flags: TokenFlags,
3851  ) -> Option<()> {
3852    let Some(mode_data) = &mut self.mode_data else {
3853      return Some(());
3854    };
3855    let name = lexer.slice_trusted(start, end);
3856    if name == "." {
3857      self.handle_warning.handle_warning(Warning {
3858        range: Range::new(start, end),
3859        kind: WarningKind::Unexpected {
3860          message: "Invalid class selector syntax",
3861        },
3862      });
3863      return Some(());
3864    }
3865    if mode_data.is_current_local_mode() {
3866      self
3867        .dependency_context
3868        .push_dependency(Dependency::LocalClass {
3869          name,
3870          range: Range::new(start, end),
3871          explicit: mode_data.is_mode_explicit(),
3872        });
3873      if self.block_nesting_level == 0 {
3874        mode_data
3875          .composes_local_classes
3876          .find_local_class(start + 1, end);
3877      }
3878
3879      if mode_data.is_pure_mode() {
3880        mode_data.pure_global = None;
3881      }
3882    }
3883    Some(())
3884  }
3885
3886  fn handle_id(
3887    &mut self,
3888    lexer: &mut DependencyLexer<'s>,
3889    start: Pos,
3890    end: Pos,
3891    _flags: TokenFlags,
3892  ) -> Option<()> {
3893    let Some(mode_data) = &mut self.mode_data else {
3894      return Some(());
3895    };
3896    let name = lexer.slice_trusted(start, end);
3897    if name == "#" {
3898      self.handle_warning.handle_warning(Warning {
3899        range: Range::new(start, end),
3900        kind: WarningKind::Unexpected {
3901          message: "Invalid id selector syntax",
3902        },
3903      });
3904      return Some(());
3905    }
3906    if mode_data.is_current_local_mode() {
3907      self
3908        .dependency_context
3909        .push_dependency(Dependency::LocalId {
3910          name,
3911          range: Range::new(start, end),
3912          explicit: mode_data.is_mode_explicit(),
3913        });
3914
3915      if self.block_nesting_level == 0 {
3916        mode_data.composes_local_classes.invalidate();
3917      }
3918
3919      if mode_data.is_pure_mode() {
3920        mode_data.pure_global = None;
3921      }
3922    }
3923    Some(())
3924  }
3925
3926  fn handle_left_curly_bracket(
3927    &mut self,
3928    stream: &mut DependencyTokenStream<'_, 's>,
3929    start: Pos,
3930    _: Pos,
3931  ) -> Option<()> {
3932    if matches!(self.scope, Scope::InBlock)
3933      && self.scan_context != ScanContext::Selector
3934      && !self.balanced.is_empty()
3935    {
3936      self.balanced.push(
3937        BalancedItem::new_curly(start, start + 1),
3938        self.mode_data.as_mut(),
3939      );
3940      return Some(());
3941    }
3942    match self.scope {
3943      Scope::TopLevel => {
3944        self.allow_import_at_rule = false;
3945        self.scope = Scope::InBlock;
3946        if self.mode_data.is_none()
3947          || matches!(&self.mode_data, Some(mode_data) if !matches!(mode_data.composes_local_classes.is_single, SingleLocalClass::AtKeyword))
3948        {
3949          self.block_nesting_level = 1;
3950        }
3951      }
3952      Scope::InBlock => {
3953        let is_at_rule_block = matches!(
3954            &self.mode_data,
3955            Some(mode_data)
3956                if matches!(mode_data.composes_local_classes.is_single, SingleLocalClass::AtKeyword)
3957        );
3958        if !is_at_rule_block {
3959          self.block_nesting_level += 1;
3960        }
3961      }
3962      _ => return Some(()),
3963    }
3964    let (pure_check_disabled_for_selector, enter_pure_ignored_block) =
3965      if let Some(mode_data) = &self.mode_data {
3966        (
3967          mode_data.is_pure_check_disabled(),
3968          mode_data.pure_ignore_pending,
3969        )
3970      } else {
3971        (false, false)
3972      };
3973    if self.mode_data.is_none() {
3974      self.set_scan_context(stream, ScanContext::BlockItem);
3975      return Some(());
3976    }
3977    if let Some(mode_data) = &mut self.mode_data {
3978      if let Some(pure_global_start) = mode_data
3979        .pure_global
3980        .filter(|_| mode_data.is_pure_mode() && !pure_check_disabled_for_selector)
3981      {
3982        self.handle_warning.handle_warning(Warning {
3983                    range: Range::new(pure_global_start, start),
3984                    kind: WarningKind::NotPure {
3985                        message: "Selector is not pure (pure selectors must contain at least one local class or id)",
3986                    }
3987                });
3988      }
3989
3990      if enter_pure_ignored_block {
3991        mode_data.enter_block(self.block_nesting_level);
3992      }
3993
3994      if let Some(resulting_global_start) = mode_data
3995        .resulting_global
3996        .filter(|_| mode_data.is_current_local_mode())
3997      {
3998        self.handle_warning.handle_warning(Warning {
3999          range: Range::new(resulting_global_start, start),
4000          kind: WarningKind::InconsistentModeResult,
4001        });
4002      }
4003      mode_data.resulting_global = None;
4004
4005      self.balanced.update_property_mode(mode_data);
4006      self.balanced.pop_mode_pseudo_class(mode_data);
4007      if self.is_next_rule_prelude && self.block_nesting_level == 0 {
4008        let mode_data = self
4009          .mode_data
4010          .as_mut()
4011          .expect("CSS Modules mode data must exist while finishing a selector");
4012        mode_data.composes_local_classes.reset_to_initial();
4013      }
4014
4015      debug_assert!(
4016        self.balanced.is_empty(),
4017        "balanced should be empty when end of selector"
4018      );
4019    }
4020    self.exit_container_property();
4021    self.exit_grid_property();
4022    self.pending_custom_property = None;
4023    self.pending_grid_property = None;
4024    self.property_kind = PropertyKind::Generic;
4025    self.set_scan_context(stream, ScanContext::BlockItem);
4026    Some(())
4027  }
4028
4029  fn handle_right_curly_bracket(
4030    &mut self,
4031    stream: &mut DependencyTokenStream<'_, 's>,
4032    _: Pos,
4033    end: Pos,
4034  ) -> Option<()> {
4035    if matches!(self.scope, Scope::InBlock) {
4036      if matches!(
4037          self.balanced.last(),
4038          Some(last) if matches!(last.kind, BalancedItemKind::Curly)
4039      ) {
4040        self.balanced.pop(self.mode_data.as_mut());
4041        if self.block_nesting_level == 0 {
4042          self.scope = Scope::TopLevel;
4043          self.is_next_rule_prelude = true;
4044          self.set_scan_context(stream, ScanContext::TopLevel);
4045          if let Some(mode_data) = &mut self.mode_data {
4046            mode_data.composes_local_classes.reset_to_initial();
4047          }
4048        } else {
4049          self.set_scan_context(stream, ScanContext::BlockItem);
4050        }
4051        return Some(());
4052      }
4053
4054      if let Some(mode_data) = &mut self.mode_data {
4055        mode_data.pure_global = Some(end);
4056
4057        if mode_data.is_property_local_mode() {
4058          if self.in_animation_property.is_some() {
4059            self.handle_local_keyframes_dependency(stream.lexer_mut())?;
4060            self.exit_animation_property();
4061          }
4062          if self.in_list_style_property.is_some() {
4063            self.handle_local_counter_style_dependency(stream.lexer_mut())?;
4064            self.exit_list_style_property();
4065          }
4066          if self.in_font_palette_property.is_some() {
4067            self.handle_local_font_palette_dependency(stream.lexer_mut())?;
4068            self.exit_font_palette_property();
4069          }
4070          if self.in_container_property.is_some() {
4071            self.exit_container_property();
4072          }
4073          if self.in_grid_property.is_some() {
4074            self.exit_grid_property();
4075          }
4076        }
4077      }
4078      if self.block_nesting_level > 0 {
4079        self.block_nesting_level -= 1;
4080      }
4081      if let Some(mode_data) = &mut self.mode_data {
4082        mode_data.clear_pure_ignore_pending();
4083        mode_data.exit_block(self.block_nesting_level);
4084      }
4085      if self.block_nesting_level == 0 {
4086        self.scope = Scope::TopLevel;
4087        self.set_scan_context(stream, ScanContext::TopLevel);
4088        self.is_next_rule_prelude = true;
4089        if let Some(mode_data) = &mut self.mode_data {
4090          mode_data.composes_local_classes.reset_to_initial();
4091        }
4092      } else {
4093        self.set_scan_context(stream, ScanContext::BlockItem);
4094      }
4095      self.pending_custom_property = None;
4096      self.pending_grid_property = None;
4097      self.property_kind = PropertyKind::Generic;
4098    }
4099    Some(())
4100  }
4101
4102  fn handle_pseudo_function(
4103    &mut self,
4104    stream: &mut DependencyTokenStream<'_, 's>,
4105    start: Pos,
4106    end: Pos,
4107    flags: TokenFlags,
4108  ) -> Option<()> {
4109    let name = stream.slice_trusted(start, end);
4110    if let Some(mode_data) = &mut self.mode_data {
4111      if name.eq_ignore_ascii_case(":import(") {
4112        self.lex_icss_import(stream);
4113        self
4114          .dependency_context
4115          .push_dependency(Dependency::Replace {
4116            content: "",
4117            range: Range::new(start, stream.consumed_pos()),
4118          });
4119        return Some(());
4120      }
4121      if name.eq_ignore_ascii_case(":global(") || name.eq_ignore_ascii_case(":local(") {
4122        if mode_data.is_inside_mode_function() {
4123          self.handle_warning.handle_warning(Warning {
4124            range: Range::new(start, end),
4125            kind: WarningKind::ExpectedNotInside {
4126              pseudo: stream.slice(start, end)?,
4127            },
4128          });
4129        }
4130
4131        let next = stream.peek_parser_token();
4132        if next.token.kind == TokenKind::Eof {
4133          return None;
4134        }
4135        self
4136          .dependency_context
4137          .push_dependency(Dependency::Replace {
4138            content: "",
4139            range: Range::new(start, next.token.range.start),
4140          });
4141      } else if self.block_nesting_level == 0 {
4142        mode_data.composes_local_classes.invalidate();
4143      }
4144    }
4145    self.balanced.push(
4146      BalancedItem::new(name, flags, start, end),
4147      self.mode_data.as_mut(),
4148    );
4149    Some(())
4150  }
4151
4152  fn handle_pseudo_class(
4153    &mut self,
4154    stream: &mut DependencyTokenStream<'_, 's>,
4155    start: Pos,
4156    end: Pos,
4157    flags: TokenFlags,
4158  ) -> Option<()> {
4159    let Some(mode_data) = &mut self.mode_data else {
4160      return Some(());
4161    };
4162    let name = stream.slice_trusted(start, end);
4163    if name.eq_ignore_ascii_case(":global") || name.eq_ignore_ascii_case(":local") {
4164      if mode_data.is_inside_mode_function() {
4165        self.handle_warning.handle_warning(Warning {
4166          range: Range::new(start, end),
4167          kind: WarningKind::ExpectedNotInside {
4168            pseudo: stream.slice(start, end)?,
4169          },
4170        });
4171      }
4172
4173      let next = stream.peek_parser_token();
4174      if next.token.kind == TokenKind::Eof {
4175        return None;
4176      }
4177      if !next.leading.has_whitespace() {
4178        let missing_whitespace = match stream.byte_at(next.token.range.start) {
4179          Some(b'.' | b'#') => true,
4180          Some(b'{') => next.leading.first_comment_start.is_some(),
4181          _ => false,
4182        };
4183        if missing_whitespace {
4184          self.handle_warning.handle_warning(Warning {
4185            range: Range::new(start, end),
4186            kind: WarningKind::Unexpected {
4187              message: "Missing trailing whitespace",
4188            },
4189          });
4190        }
4191      }
4192      self.balanced.push(
4193        BalancedItem::new(name, flags, start, end),
4194        self.mode_data.as_mut(),
4195      );
4196      self
4197        .dependency_context
4198        .push_dependency(Dependency::Replace {
4199          content: "",
4200          range: Range::new(
4201            start,
4202            next.leading.first_comment_start.unwrap_or(next.leading.end),
4203          ),
4204        });
4205      return Some(());
4206    }
4207    if matches!(self.scope, Scope::TopLevel) && name.eq_ignore_ascii_case(":export") {
4208      self.lex_icss_export(stream)?;
4209      self
4210        .dependency_context
4211        .push_dependency(Dependency::Replace {
4212          content: "",
4213          range: Range::new(start, stream.consumed_pos()),
4214        });
4215      return Some(());
4216    }
4217
4218    if self.block_nesting_level == 0 {
4219      mode_data.composes_local_classes.invalidate();
4220    }
4221    Some(())
4222  }
4223
4224  fn handle_comma(&mut self, lexer: &mut DependencyLexer<'s>, start: Pos, end: Pos) -> Option<()> {
4225    let Some(mode_data) = &mut self.mode_data else {
4226      return Some(());
4227    };
4228
4229    if let Some(pure_global_start) = mode_data
4230      .pure_global
4231      .filter(|_| mode_data.is_pure_mode() && !mode_data.is_pure_check_disabled())
4232    {
4233      self.handle_warning.handle_warning(Warning {
4234                range: Range::new(pure_global_start, start),
4235                kind: WarningKind::NotPure {
4236                    message: "Selector is not pure (pure selectors must contain at least one local class or id)",
4237                }
4238            });
4239    }
4240    mode_data.pure_global = Some(end);
4241
4242    if self.block_nesting_level == 0 {
4243      mode_data.composes_local_classes.find_comma(lexer)?;
4244    }
4245
4246    if let Some(resulting_global_start) = mode_data
4247      .resulting_global
4248      .filter(|_| mode_data.is_current_local_mode())
4249    {
4250      self.handle_warning.handle_warning(Warning {
4251        range: Range::new(resulting_global_start, start),
4252        kind: WarningKind::InconsistentModeResult,
4253      });
4254    }
4255
4256    if self.balanced.len() == 1 {
4257      let last = self
4258        .balanced
4259        .last()
4260        .expect("a balanced item must exist when the stack length is one");
4261      let is_local_class = matches!(last.kind, BalancedItemKind::LocalClass);
4262      let is_global_class = matches!(last.kind, BalancedItemKind::GlobalClass);
4263      if is_local_class || is_global_class {
4264        self.balanced.pop_mode_pseudo_class(mode_data);
4265        if mode_data.resulting_global.is_none() && is_global_class {
4266          mode_data.resulting_global = Some(start);
4267        }
4268      }
4269    }
4270
4271    if matches!(self.scope, Scope::InBlock)
4272      && mode_data.is_property_local_mode()
4273      && self.in_animation_property.is_some()
4274    {
4275      self.handle_local_keyframes_dependency(lexer)?;
4276    }
4277
4278    Some(())
4279  }
4280}