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