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