Skip to main content

css_module_lexer/
dependencies.rs

1use std::fmt::Display;
2
3use smallvec::smallvec;
4use smallvec::SmallVec;
5
6use crate::lexer::are_valid_escape;
7use crate::lexer::is_new_line;
8use crate::lexer::is_white_space;
9use crate::lexer::start_ident_sequence;
10use crate::lexer::Visitor;
11use crate::lexer::C_APOSTROPHE;
12use crate::lexer::C_ASTERISK;
13use crate::lexer::C_COLON;
14use crate::lexer::C_COMMA;
15use crate::lexer::C_HYPHEN_MINUS;
16use crate::lexer::C_LEFT_CURLY;
17use crate::lexer::C_LEFT_PARENTHESIS;
18use crate::lexer::C_QUOTATION_MARK;
19use crate::lexer::C_REVERSE_SOLIDUS;
20use crate::lexer::C_RIGHT_CURLY;
21use crate::lexer::C_RIGHT_PARENTHESIS;
22use crate::lexer::C_SEMICOLON;
23use crate::lexer::C_SOLIDUS;
24use crate::HandleDependency;
25use crate::HandleWarning;
26use crate::Lexer;
27use crate::Pos;
28
29#[derive(Debug)]
30enum Scope<'s> {
31    TopLevel,
32    InBlock,
33    InAtImport(ImportData<'s>),
34    AtImportInvalid,
35    AtNamespaceInvalid,
36}
37
38#[derive(Debug)]
39struct ImportData<'s> {
40    start: Pos,
41    url: Option<&'s str>,
42    url_range: Option<Range>,
43    supports: ImportDataSupports<'s>,
44    layer: ImportDataLayer<'s>,
45}
46
47impl ImportData<'_> {
48    pub fn new(start: Pos) -> Self {
49        Self {
50            start,
51            url: None,
52            url_range: None,
53            supports: ImportDataSupports::None,
54            layer: ImportDataLayer::None,
55        }
56    }
57
58    pub fn in_supports(&self) -> bool {
59        matches!(self.supports, ImportDataSupports::InSupports { .. })
60    }
61
62    pub fn layer_range(&self) -> Option<&Range> {
63        let ImportDataLayer::EndLayer { range, .. } = &self.layer else {
64            return None;
65        };
66        Some(range)
67    }
68
69    pub fn supports_range(&self) -> Option<&Range> {
70        let ImportDataSupports::EndSupports { range, .. } = &self.supports else {
71            return None;
72        };
73        Some(range)
74    }
75}
76
77#[derive(Debug)]
78enum ImportDataSupports<'s> {
79    None,
80    InSupports,
81    EndSupports { value: &'s str, range: Range },
82}
83
84#[derive(Debug)]
85enum ImportDataLayer<'s> {
86    None,
87    EndLayer { value: &'s str, range: Range },
88}
89
90#[derive(Debug, Default)]
91struct BalancedStack(SmallVec<[BalancedItem; 3]>);
92
93impl BalancedStack {
94    pub fn len(&self) -> usize {
95        self.0.len()
96    }
97
98    pub fn last(&self) -> Option<&BalancedItem> {
99        self.0.last()
100    }
101
102    pub fn is_empty(&self) -> bool {
103        self.0.is_empty()
104    }
105
106    pub fn push(&mut self, item: BalancedItem, mode_data: Option<&mut ModeData>) {
107        if let Some(mode_data) = mode_data {
108            if item.kind.is_mode_local() {
109                mode_data.set_current_mode(Mode::Local);
110            } else if item.kind.is_mode_global() {
111                mode_data.set_current_mode(Mode::Global);
112            }
113
114            if item.kind.is_mode_function() {
115                mode_data.inside_mode_function += 1;
116            } else if item.kind.is_mode_class() {
117                mode_data.inside_mode_class += 1;
118            }
119        }
120        self.0.push(item);
121    }
122
123    pub fn pop(&mut self, mode_data: Option<&mut ModeData>) -> Option<BalancedItem> {
124        let item = self.0.pop()?;
125        if let Some(mode_data) = mode_data {
126            if item.kind.is_mode_function() {
127                mode_data.inside_mode_function -= 1;
128            } else if item.kind.is_mode_class() {
129                mode_data.inside_mode_class -= 1;
130            }
131            self.update_current_mode(mode_data);
132        }
133        Some(item)
134    }
135
136    pub fn pop_without_moda_data(&mut self) -> Option<BalancedItem> {
137        self.0.pop()
138    }
139
140    pub fn pop_mode_pseudo_class(&mut self, mode_data: &mut ModeData) {
141        loop {
142            if let Some(last) = self.0.last() {
143                if matches!(
144                    last.kind,
145                    BalancedItemKind::LocalClass | BalancedItemKind::GlobalClass
146                ) {
147                    mode_data.inside_mode_class -= 1;
148                    self.0.pop();
149                    continue;
150                }
151            }
152            break;
153        }
154        self.update_current_mode(mode_data);
155    }
156
157    pub fn update_current_mode(&self, mode_data: &mut ModeData) {
158        mode_data.set_current_mode(self.topmost_mode(mode_data));
159    }
160
161    pub fn update_property_mode(&self, mode_data: &mut ModeData) {
162        mode_data.set_property_mode(self.topmost_mode(mode_data));
163    }
164
165    fn topmost_mode(&self, mode_data: &ModeData) -> Mode {
166        let mut iter = self.0.iter();
167        loop {
168            if let Some(last) = iter.next_back() {
169                if matches!(
170                    last.kind,
171                    BalancedItemKind::LocalFn | BalancedItemKind::LocalClass
172                ) {
173                    return Mode::Local;
174                } else if matches!(
175                    last.kind,
176                    BalancedItemKind::GlobalFn | BalancedItemKind::GlobalClass
177                ) {
178                    return Mode::Global;
179                }
180            } else {
181                return mode_data.default_mode();
182            }
183        }
184    }
185}
186
187#[derive(Debug)]
188struct BalancedItem {
189    kind: BalancedItemKind,
190    range: Range,
191}
192
193impl BalancedItem {
194    pub fn new(name: &str, start: Pos, end: Pos) -> Self {
195        Self {
196            kind: BalancedItemKind::new(name),
197            range: Range::new(start, end),
198        }
199    }
200
201    pub fn new_other(start: Pos, end: Pos) -> Self {
202        Self {
203            kind: BalancedItemKind::Other,
204            range: Range::new(start, end),
205        }
206    }
207}
208
209#[derive(Debug)]
210enum BalancedItemKind {
211    Url,
212    ImageSet,
213    Layer,
214    Supports,
215    PaletteMix,
216    LocalFn,
217    GlobalFn,
218    LocalClass,
219    GlobalClass,
220    Other,
221}
222
223impl BalancedItemKind {
224    pub fn new(name: &str) -> Self {
225        match name {
226            "url(" => Self::Url,
227            "image-set(" => Self::ImageSet,
228            _ if with_vendor_prefixed_eq(name, "image-set(", false) => Self::ImageSet,
229            "layer(" => Self::Layer,
230            "supports(" => Self::Supports,
231            "palette-mix(" => Self::PaletteMix,
232            ":local(" => Self::LocalFn,
233            ":global(" => Self::GlobalFn,
234            ":local" => Self::LocalClass,
235            ":global" => Self::GlobalClass,
236            _ => Self::Other,
237        }
238    }
239
240    pub fn is_mode_local(&self) -> bool {
241        matches!(self, Self::LocalFn | Self::LocalClass)
242    }
243
244    pub fn is_mode_global(&self) -> bool {
245        matches!(self, Self::GlobalFn | Self::GlobalClass)
246    }
247
248    pub fn is_mode_function(&self) -> bool {
249        matches!(self, Self::LocalFn | Self::GlobalFn)
250    }
251
252    pub fn is_mode_class(&self) -> bool {
253        matches!(self, Self::LocalClass | Self::GlobalClass)
254    }
255}
256
257#[derive(Debug)]
258enum ValueAtRule<'s> {
259    Import {
260        from: &'s str,
261        items: SmallVec<[ValueAtRuleImportItem<'s>; 2]>,
262    },
263    Value {
264        local_name: &'s str,
265        value: &'s str,
266    },
267}
268
269#[derive(Debug, Clone, Copy)]
270struct ValueAtRuleImportItem<'s> {
271    local_name: &'s str,
272    import_name: &'s str,
273}
274
275fn is_css_space_byte(byte: u8) -> bool {
276    matches!(byte, b' ' | b'\t' | b'\n' | b'\r' | 0x0C)
277}
278
279fn trim_slice(input: &str) -> &str {
280    let bytes = input.as_bytes();
281    let mut start = 0usize;
282    let mut end = bytes.len();
283    while start < end && is_css_space_byte(bytes[start]) {
284        start += 1;
285    }
286    while end > start && is_css_space_byte(bytes[end - 1]) {
287        end -= 1;
288    }
289    &input[start..end]
290}
291
292fn mask_css_comments(input: &str) -> String {
293    let bytes = input.as_bytes();
294    let mut result = String::with_capacity(input.len());
295    let mut i = 0usize;
296    while i < bytes.len() {
297        if i + 1 < bytes.len() && bytes[i] == b'/' && bytes[i + 1] == b'*' {
298            result.push(' ');
299            result.push(' ');
300            i += 2;
301            while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {
302                result.push(' ');
303                i += 1;
304            }
305            if i + 1 < bytes.len() {
306                result.push(' ');
307                result.push(' ');
308                i += 2;
309            }
310            continue;
311        }
312        result.push(bytes[i] as char);
313        i += 1;
314    }
315    result
316}
317
318fn split_value_at_rule_from<'s>(input: &'s str, masked: &str) -> Option<(&'s str, &'s str)> {
319    let input = trim_slice(input);
320    let masked = trim_slice(masked);
321    if input.is_empty() {
322        return None;
323    }
324
325    let bytes = masked.as_bytes();
326    let mut end = bytes.len();
327    while end > 0 && is_css_space_byte(bytes[end - 1]) {
328        end -= 1;
329    }
330    if end == 0 {
331        return None;
332    }
333
334    let token_start = if bytes[end - 1] == b'"' || bytes[end - 1] == b'\'' {
335        let quote = bytes[end - 1];
336        let mut start = end - 1;
337        while start > 0 {
338            start -= 1;
339            if bytes[start] == quote {
340                break;
341            }
342        }
343        start
344    } else {
345        let mut start = end;
346        while start > 0 && !is_css_space_byte(bytes[start - 1]) {
347            start -= 1;
348        }
349        start
350    };
351
352    let from = trim_slice(&input[token_start..end]);
353    let mut word_end = token_start;
354    while word_end > 0 && is_css_space_byte(bytes[word_end - 1]) {
355        word_end -= 1;
356    }
357    if word_end == 0 {
358        return None;
359    }
360    let mut word_start = word_end;
361    while word_start > 0 && !is_css_space_byte(bytes[word_start - 1]) {
362        word_start -= 1;
363    }
364    let word = &masked[word_start..word_end];
365    if !word.eq_ignore_ascii_case("from") {
366        return None;
367    }
368
369    Some((trim_slice(&input[..word_start]), from))
370}
371
372fn split_value_at_rule_items<'s>(input: &'s str, masked: &str) -> SmallVec<[&'s str; 2]> {
373    let input = trim_slice(input);
374    let masked = trim_slice(masked);
375    let (input, masked) = if input.starts_with('(') && input.ends_with(')') && masked.len() >= 2 {
376        (&input[1..input.len() - 1], &masked[1..masked.len() - 1])
377    } else {
378        (input, masked)
379    };
380
381    let mut result = SmallVec::new();
382    let bytes = masked.as_bytes();
383    let mut start = 0usize;
384    let mut depth = 0u32;
385    let mut quote = None;
386    let mut i = 0usize;
387    while i < bytes.len() {
388        let byte = bytes[i];
389        if let Some(current_quote) = quote {
390            if byte == current_quote {
391                quote = None;
392            }
393        } else if byte == b'"' || byte == b'\'' {
394            quote = Some(byte);
395        } else if byte == b'(' {
396            depth += 1;
397        } else if byte == b')' && depth > 0 {
398            depth -= 1;
399        } else if byte == b',' && depth == 0 {
400            let item = trim_slice(&input[start..i]);
401            if !item.is_empty() {
402                result.push(item);
403            }
404            start = i + 1;
405        }
406        i += 1;
407    }
408    let item = trim_slice(&input[start..]);
409    if !item.is_empty() {
410        result.push(item);
411    }
412    result
413}
414
415fn parse_value_at_rule_import_item(input: &str) -> ValueAtRuleImportItem<'_> {
416    let input = trim_slice(input);
417    if let Some(index) = input.find(':') {
418        return ValueAtRuleImportItem {
419            local_name: trim_slice(&input[..index]),
420            import_name: trim_slice(&input[index + 1..]),
421        };
422    }
423
424    let lower = input.to_ascii_lowercase();
425    if let Some(index) = lower.find(" as ") {
426        return ValueAtRuleImportItem {
427            local_name: trim_slice(&input[index + 4..]),
428            import_name: trim_slice(&input[..index]),
429        };
430    }
431
432    ValueAtRuleImportItem {
433        local_name: input,
434        import_name: input,
435    }
436}
437
438fn parse_value_at_rule(input: &str) -> ValueAtRule<'_> {
439    let masked = mask_css_comments(input);
440    if let Some((before_from, from)) = split_value_at_rule_from(input, &masked) {
441        let masked_before_from = &masked[..before_from.len()];
442        let items = split_value_at_rule_items(before_from, masked_before_from)
443            .into_iter()
444            .map(parse_value_at_rule_import_item)
445            .collect();
446        return ValueAtRule::Import { from, items };
447    }
448
449    let input = trim_slice(input);
450    let masked = trim_slice(&masked);
451    if let Some(index) = masked.find(':') {
452        return ValueAtRule::Value {
453            local_name: trim_slice(&input[..index]),
454            value: trim_slice(&input[index + 1..]),
455        };
456    }
457
458    let bytes = masked.as_bytes();
459    let mut split = bytes.len();
460    for (index, byte) in bytes.iter().enumerate() {
461        if is_css_space_byte(*byte) {
462            split = index;
463            break;
464        }
465    }
466    let mut value_start = split;
467    while value_start < bytes.len() && is_css_space_byte(bytes[value_start]) {
468        value_start += 1;
469    }
470    ValueAtRule::Value {
471        local_name: trim_slice(&input[..split]),
472        value: trim_slice(&input[value_start..]),
473    }
474}
475
476fn with_vendor_prefixed_eq(left: &str, right: &str, at_rule: bool) -> bool {
477    let left = if at_rule {
478        if let Some(left) = left.strip_prefix('@') {
479            left
480        } else {
481            return false;
482        }
483    } else {
484        left
485    };
486    matches!(left.strip_prefix("-webkit-"), Some(left) if left.eq_ignore_ascii_case(right))
487        || matches!(left.strip_prefix("-moz-"), Some(left) if left.eq_ignore_ascii_case(right))
488        || matches!(left.strip_prefix("-ms-"), Some(left) if left.eq_ignore_ascii_case(right))
489        || matches!(left.strip_prefix("-o-"), Some(left) if left.eq_ignore_ascii_case(right))
490}
491
492#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
493pub struct Range {
494    pub start: Pos,
495    pub end: Pos,
496}
497
498impl Range {
499    pub fn new(start: Pos, end: Pos) -> Self {
500        Self { start, end }
501    }
502}
503
504#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
505pub enum Mode {
506    Local,
507    Global,
508    Pure,
509    Css,
510}
511
512#[derive(Debug)]
513pub struct ModeData<'s> {
514    default: Mode,
515    current: Mode,
516    property: Mode,
517    resulting_global: Option<Pos>,
518    pure_global: Option<Pos>,
519    composes_local_classes: ComposesLocalClasses<'s>,
520    inside_mode_function: u32,
521    inside_mode_class: u32,
522}
523
524impl ModeData<'_> {
525    pub fn new(default: Mode) -> Self {
526        Self {
527            default,
528            current: default,
529            property: default,
530            resulting_global: None,
531            pure_global: Some(0),
532            composes_local_classes: ComposesLocalClasses::default(),
533            inside_mode_function: 0,
534            inside_mode_class: 0,
535        }
536    }
537
538    pub fn is_pure_mode(&self) -> bool {
539        matches!(self.default, Mode::Pure)
540    }
541
542    pub fn is_current_local_mode(&self) -> bool {
543        match self.current {
544            Mode::Local | Mode::Pure => true,
545            Mode::Global => false,
546            Mode::Css => unreachable!(),
547        }
548    }
549
550    pub fn is_property_local_mode(&self) -> bool {
551        match self.property {
552            Mode::Local | Mode::Pure => true,
553            Mode::Global => false,
554            Mode::Css => unreachable!(),
555        }
556    }
557
558    pub fn default_mode(&self) -> Mode {
559        self.default
560    }
561
562    pub fn set_current_mode(&mut self, mode: Mode) {
563        self.current = mode;
564    }
565
566    pub fn set_property_mode(&mut self, mode: Mode) {
567        self.property = mode;
568    }
569
570    pub fn is_inside_mode_function(&self) -> bool {
571        self.inside_mode_function > 0
572    }
573
574    pub fn is_inside_mode_class(&self) -> bool {
575        self.inside_mode_class > 0
576    }
577
578    pub fn is_mode_explicit(&self) -> bool {
579        self.is_inside_mode_function() || self.is_inside_mode_class()
580    }
581}
582
583#[derive(Debug, Default, Clone)]
584struct ComposesLocalClasses<'s> {
585    is_single: SingleLocalClass,
586    local_classes: SmallVec<[&'s str; 2]>,
587}
588
589impl<'s> ComposesLocalClasses<'s> {
590    pub fn get_valid_local_classes(&mut self, lexer: &Lexer<'s>) -> Option<SmallVec<[&'s str; 2]>> {
591        if let SingleLocalClass::Single(range) = &self.is_single {
592            let mut local_classes = self.local_classes.clone();
593            local_classes.push(lexer.slice(range.start, range.end)?);
594            Some(local_classes)
595        } else {
596            self.reset_to_initial();
597            None
598        }
599    }
600
601    pub fn invalidate(&mut self) {
602        if !matches!(self.is_single, SingleLocalClass::AtKeyword) {
603            self.is_single = SingleLocalClass::Invalid;
604            self.local_classes.clear();
605        }
606    }
607
608    pub fn find_local_class(&mut self, start: Pos, end: Pos) {
609        match self.is_single {
610            SingleLocalClass::Initial => {
611                self.is_single = SingleLocalClass::Single(Range::new(start, end))
612            }
613            SingleLocalClass::Single(_) => {
614                self.is_single = SingleLocalClass::Invalid;
615                self.local_classes.clear();
616            }
617            _ => {}
618        };
619    }
620
621    pub fn find_at_keyword(&mut self) {
622        self.is_single = SingleLocalClass::AtKeyword;
623        self.local_classes.clear();
624    }
625
626    pub fn reset_to_initial(&mut self) {
627        self.is_single = SingleLocalClass::Initial;
628        self.local_classes.clear();
629    }
630
631    pub fn find_comma(&mut self, lexer: &Lexer<'s>) -> Option<()> {
632        if let SingleLocalClass::Single(range) = &self.is_single {
633            self.local_classes
634                .push(lexer.slice(range.start, range.end)?);
635            self.is_single = SingleLocalClass::Initial
636        } else {
637            self.is_single = SingleLocalClass::Invalid;
638        }
639        Some(())
640    }
641}
642
643#[derive(Debug, Default, Clone)]
644enum SingleLocalClass {
645    #[default]
646    Initial,
647    Single(Range),
648    AtKeyword,
649    Invalid,
650}
651
652#[derive(Debug)]
653struct InProperty<T: ReservedValues> {
654    reserved: T,
655    rename: Option<Range>,
656    balanced_len: usize,
657}
658
659impl<T: ReservedValues> InProperty<T> {
660    pub fn new(reserved: T, balanced_len: usize) -> Self {
661        Self {
662            reserved,
663            rename: None,
664            balanced_len,
665        }
666    }
667
668    fn check_reserved(&mut self, ident: &str) -> bool {
669        self.reserved.check(ident)
670    }
671
672    pub fn reset_reserved(&mut self) {
673        self.reserved.reset();
674    }
675
676    pub fn set_rename(&mut self, ident: &str, range: Range) {
677        if self.check_reserved(ident) {
678            self.rename = Some(range);
679        }
680    }
681
682    pub fn take_rename(&mut self, balanced_len: usize) -> Option<Range> {
683        // Don't rename when we in functions
684        if balanced_len != self.balanced_len {
685            return None;
686        }
687        std::mem::take(&mut self.rename)
688    }
689}
690
691trait ReservedValues {
692    fn check(&mut self, ident: &str) -> bool;
693    fn reset(&mut self);
694}
695
696#[derive(Debug, Default)]
697struct AnimationReserved {
698    bits: u32,
699}
700
701impl ReservedValues for AnimationReserved {
702    fn check(&mut self, ident: &str) -> bool {
703        match ident {
704            "normal" => self.check_and_update(Self::NORMAL),
705            "reverse" => self.check_and_update(Self::REVERSE),
706            "alternate" => self.check_and_update(Self::ALTERNATE),
707            "alternate-reverse" => self.check_and_update(Self::ALTERNATE_REVERSE),
708            "forwards" => self.check_and_update(Self::FORWARDS),
709            "backwards" => self.check_and_update(Self::BACKWARDS),
710            "both" => self.check_and_update(Self::BOTH),
711            "infinite" => self.check_and_update(Self::INFINITE),
712            "paused" => self.check_and_update(Self::PAUSED),
713            "running" => self.check_and_update(Self::RUNNING),
714            "ease" => self.check_and_update(Self::EASE),
715            "ease-in" => self.check_and_update(Self::EASE_IN),
716            "ease-out" => self.check_and_update(Self::EASE_OUT),
717            "ease-in-out" => self.check_and_update(Self::EASE_IN_OUT),
718            "linear" => self.check_and_update(Self::LINEAR),
719            "step-end" => self.check_and_update(Self::STEP_END),
720            "step-start" => self.check_and_update(Self::STEP_START),
721            // keywords values
722            "none" |
723            // global values
724            "initial" | "inherit" | "unset" | "revert" | "revert-layer" => false,
725            _ => true,
726        }
727    }
728
729    fn reset(&mut self) {
730        self.bits = 0;
731    }
732}
733
734impl AnimationReserved {
735    const NORMAL: u32 = 1 << 0;
736    const REVERSE: u32 = 1 << 1;
737    const ALTERNATE: u32 = 1 << 2;
738    const ALTERNATE_REVERSE: u32 = 1 << 3;
739    const FORWARDS: u32 = 1 << 4;
740    const BACKWARDS: u32 = 1 << 5;
741    const BOTH: u32 = 1 << 6;
742    const INFINITE: u32 = 1 << 7;
743    const PAUSED: u32 = 1 << 8;
744    const RUNNING: u32 = 1 << 9;
745    const EASE: u32 = 1 << 10;
746    const EASE_IN: u32 = 1 << 11;
747    const EASE_OUT: u32 = 1 << 12;
748    const EASE_IN_OUT: u32 = 1 << 13;
749    const LINEAR: u32 = 1 << 14;
750    const STEP_END: u32 = 1 << 15;
751    const STEP_START: u32 = 1 << 16;
752
753    fn check_and_update(&mut self, bit: u32) -> bool {
754        if self.bits & bit == bit {
755            return true;
756        }
757        self.bits |= bit;
758        false
759    }
760}
761
762#[derive(Debug, Default)]
763struct ListStyleReserved;
764
765impl ReservedValues for ListStyleReserved {
766    fn check(&mut self, ident: &str) -> bool {
767        match ident {
768            // https://www.w3.org/TR/css-counter-styles-3/#simple-numeric
769            "decimal"
770            | "decimal-leading-zero"
771            | "arabic-indic"
772            | "armenian"
773            | "upper-armenian"
774            | "lower-armenian"
775            | "bengali"
776            | "cambodian"
777            | "khmer"
778            | "cjk-decimal"
779            | "devanagari"
780            | "georgian"
781            | "gujarati"
782            | "gurmukhi"
783            | "hebrew"
784            | "kannada"
785            | "lao"
786            | "malayalam"
787            | "mongolian"
788            | "myanmar"
789            | "oriya"
790            | "persian"
791            | "lower-roman"
792            | "upper-roman"
793            | "tamil"
794            | "telugu"
795            | "thai"
796            | "tibetan"
797            // https://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic
798            | "lower-alpha"
799            | "lower-latin"
800            | "upper-alpha"
801            | "upper-latin"
802            | "lower-greek"
803            | "hiragana"
804            | "hiragana-iroha"
805            | "katakana"
806            | "katakana-iroha"
807            // https://www.w3.org/TR/css-counter-styles-3/#simple-symbolic
808            | "disc"
809            | "circle"
810            | "square"
811            | "disclosure-open"
812            | "disclosure-closed"
813            // https://www.w3.org/TR/css-counter-styles-3/#simple-fixed
814            | "cjk-earthly-branch"
815            | "cjk-heavenly-stem"
816            // https://www.w3.org/TR/css-counter-styles-3/#complex-cjk
817            | "japanese-informal"
818            | "japanese-formal"
819            | "korean-hangul-formal"
820            | "korean-hanja-informal"
821            | "korean-hanja-formal"
822            | "simp-chinese-informal"
823            | "simp-chinese-formal"
824            | "trad-chinese-informal"
825            | "trad-chinese-formal"
826            | "ethiopic-numeric"
827            // keywords values
828            | "none"
829            // global values
830            | "initial"
831            | "inherit"
832            | "unset"
833            | "revert"
834            | "revert-layer" => false,
835            _ => true,
836        }
837    }
838
839    fn reset(&mut self) {}
840}
841
842#[derive(Debug, Default)]
843struct FontPaletteReserved;
844
845impl ReservedValues for FontPaletteReserved {
846    fn check(&mut self, ident: &str) -> bool {
847        ident.starts_with("--")
848    }
849
850    fn reset(&mut self) {}
851}
852
853#[derive(Debug, Default)]
854struct ContainerReserved;
855
856impl ReservedValues for ContainerReserved {
857    fn check(&mut self, ident: &str) -> bool {
858        !matches!(
859            ident,
860            "normal"
861                | "size"
862                | "inline-size"
863                | "scroll-state"
864                | "none"
865                | "initial"
866                | "inherit"
867                | "unset"
868                | "revert"
869                | "revert-layer"
870        )
871    }
872
873    fn reset(&mut self) {}
874}
875
876#[derive(Debug, Clone, Copy)]
877enum GridPropertyKind {
878    Generic,
879    TemplateLike,
880    TemplateAreas,
881}
882
883fn grid_property_kind(ident: &str) -> Option<GridPropertyKind> {
884    match ident {
885        "grid" | "grid-area" | "grid-column" | "grid-column-end" | "grid-column-start"
886        | "grid-row" | "grid-row-end" | "grid-row-start" => Some(GridPropertyKind::Generic),
887        "grid-template" | "grid-template-columns" | "grid-template-rows" => {
888            Some(GridPropertyKind::TemplateLike)
889        }
890        "grid-template-areas" => Some(GridPropertyKind::TemplateAreas),
891        _ => None,
892    }
893}
894
895fn is_reserved_grid_ident(ident: &str) -> bool {
896    matches!(
897        ident,
898        "auto"
899            | "span"
900            | "auto-flow"
901            | "dense"
902            | "row"
903            | "column"
904            | "none"
905            | "subgrid"
906            | "masonry"
907            | "max-content"
908            | "min-content"
909            | "initial"
910            | "inherit"
911            | "unset"
912            | "revert"
913            | "revert-layer"
914    )
915}
916
917fn is_reserved_container_query_ident(ident: &str) -> bool {
918    matches!(ident, "none" | "and" | "or" | "not")
919}
920
921fn parse_grid_template_area_ranges(input: &str, offset: Pos) -> SmallVec<[Range; 4]> {
922    let mut result = SmallVec::new();
923    let bytes = input.as_bytes();
924    let mut i = 0usize;
925
926    while i < bytes.len() {
927        while i < bytes.len() && bytes[i].is_ascii_whitespace() {
928            i += 1;
929        }
930        let start = i;
931        while i < bytes.len() && !bytes[i].is_ascii_whitespace() {
932            i += 1;
933        }
934        if start == i {
935            break;
936        }
937        let token = &input[start..i];
938        if !token.bytes().all(|c| c == b'.') {
939            result.push(Range::new(offset + start as Pos, offset + i as Pos));
940        }
941    }
942
943    result
944}
945
946#[derive(Debug, Clone, Hash, PartialEq, Eq)]
947pub enum Dependency<'s> {
948    Url {
949        request: &'s str,
950        range: Range,
951        kind: UrlRangeKind,
952    },
953    Import {
954        request: &'s str,
955        range: Range,
956        layer: Option<&'s str>,
957        supports: Option<&'s str>,
958        media: Option<&'s str>,
959    },
960    Replace {
961        content: &'s str,
962        range: Range,
963    },
964    Charset {
965        value: &'s str,
966        range: Range,
967    },
968    LocalClass {
969        name: &'s str,
970        range: Range,
971        explicit: bool,
972    },
973    LocalId {
974        name: &'s str,
975        range: Range,
976        explicit: bool,
977    },
978    LocalVar {
979        name: &'s str,
980        range: Range,
981        from: Option<&'s str>,
982    },
983    LocalVarDecl {
984        name: &'s str,
985        range: Range,
986    },
987    LocalPropertyDecl {
988        name: &'s str,
989        range: Range,
990    },
991    LocalKeyframes {
992        name: &'s str,
993        range: Range,
994    },
995    LocalKeyframesDecl {
996        name: &'s str,
997        range: Range,
998    },
999    LocalCounterStyle {
1000        name: &'s str,
1001        range: Range,
1002    },
1003    LocalCounterStyleDecl {
1004        name: &'s str,
1005        range: Range,
1006    },
1007    LocalFontPalette {
1008        name: &'s str,
1009        range: Range,
1010    },
1011    LocalFontPaletteDecl {
1012        name: &'s str,
1013        range: Range,
1014    },
1015    LocalContainer {
1016        name: &'s str,
1017        range: Range,
1018    },
1019    LocalContainerDecl {
1020        name: &'s str,
1021        range: Range,
1022    },
1023    LocalFunction {
1024        name: &'s str,
1025        range: Range,
1026    },
1027    LocalFunctionDecl {
1028        name: &'s str,
1029        range: Range,
1030    },
1031    LocalGrid {
1032        name: &'s str,
1033        range: Range,
1034    },
1035    LocalGridDecl {
1036        name: &'s str,
1037        range: Range,
1038    },
1039    Composes {
1040        local_classes: SmallVec<[&'s str; 2]>,
1041        names: SmallVec<[&'s str; 2]>,
1042        from: Option<&'s str>,
1043        range: Range,
1044    },
1045    ICSSImportFrom {
1046        path: &'s str,
1047    },
1048    ICSSImportValue {
1049        prop: &'s str,
1050        value: &'s str,
1051    },
1052    ICSSExportValue {
1053        prop: &'s str,
1054        value: &'s str,
1055    },
1056}
1057
1058#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
1059pub enum UrlRangeKind {
1060    Function,
1061    String,
1062}
1063
1064#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1065pub struct Warning<'s> {
1066    range: Range,
1067    kind: WarningKind<'s>,
1068}
1069
1070impl<'s> Warning<'s> {
1071    pub fn new(range: Range, kind: WarningKind<'s>) -> Self {
1072        Self { range, kind }
1073    }
1074
1075    pub fn range(&self) -> &Range {
1076        &self.range
1077    }
1078
1079    pub fn kind(&self) -> &WarningKind<'s> {
1080        &self.kind
1081    }
1082}
1083
1084#[derive(Debug, Clone, Hash, PartialEq, Eq)]
1085pub enum WarningKind<'s> {
1086    Unexpected { message: &'s str },
1087    DuplicateUrl { when: &'s str },
1088    NamespaceNotSupportedInBundledCss,
1089    NotPrecededAtImport,
1090    ExpectedUrl { when: &'s str },
1091    ExpectedUrlBefore { when: &'s str },
1092    ExpectedLayerBefore { when: &'s str },
1093    InconsistentModeResult,
1094    ExpectedNotInside { pseudo: &'s str },
1095    MissingWhitespace { surrounding: &'s str },
1096    NotPure { message: &'s str },
1097    UnexpectedComposition { message: &'s str },
1098}
1099
1100impl Display for Warning<'_> {
1101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1102        match self.kind {
1103            WarningKind::Unexpected { message, .. } => write!(f, "{message}"),
1104            WarningKind::DuplicateUrl { when, .. } => write!(
1105                f,
1106                "Duplicate of 'url(...)' in '{when}'"
1107            ),
1108            WarningKind::NamespaceNotSupportedInBundledCss { .. } => write!(
1109                f,
1110                "'@namespace' is not supported in bundled CSS"
1111            ),
1112            WarningKind::NotPrecededAtImport { .. } => {
1113                write!(f, "Any '@import' rules must precede all other rules")
1114            }
1115            WarningKind::ExpectedUrl { when, .. } => write!(f, "Expected URL in '{when}'"),
1116            WarningKind::ExpectedUrlBefore { when, .. } => write!(
1117                f,
1118                "An URL in '{when}' should be before 'layer(...)' or 'supports(...)'"
1119            ),
1120            WarningKind::ExpectedLayerBefore { when, .. } => write!(
1121                f,
1122                "The 'layer(...)' in '{when}' should be before 'supports(...)'"
1123            ),
1124            WarningKind::InconsistentModeResult { .. } => write!(
1125                f,
1126                "Inconsistent rule global/local (multiple selectors must result in the same mode for the rule)"
1127            ),
1128            WarningKind::ExpectedNotInside { pseudo, .. } => write!(
1129                f,
1130                "A '{pseudo}' is not allowed inside of a ':local()' or ':global()'"
1131            ),
1132            WarningKind::MissingWhitespace { surrounding, .. } => write!(
1133                f,
1134                "Missing {surrounding} whitespace"
1135            ),
1136            WarningKind::NotPure { message, .. } => write!(f, "Pure globals is not allowed in pure mode, {message}"),
1137            WarningKind::UnexpectedComposition {  message, .. } => write!(f, "Composition is {message}"),
1138        }
1139    }
1140}
1141
1142#[derive(Debug)]
1143pub struct LexDependencies<'s, D, W> {
1144    mode_data: Option<ModeData<'s>>,
1145    scope: Scope<'s>,
1146    block_nesting_level: u32,
1147    allow_import_at_rule: bool,
1148    balanced: BalancedStack,
1149    is_next_rule_prelude: bool,
1150    in_animation_property: Option<InProperty<AnimationReserved>>,
1151    in_list_style_property: Option<InProperty<ListStyleReserved>>,
1152    in_font_palette_property: Option<InProperty<FontPaletteReserved>>,
1153    in_container_property: Option<InProperty<ContainerReserved>>,
1154    in_grid_property: Option<GridPropertyKind>,
1155    handle_dependency: D,
1156    handle_warning: W,
1157}
1158
1159impl<'s, D: HandleDependency<'s>, W: HandleWarning<'s>> LexDependencies<'s, D, W> {
1160    pub fn new(handle_dependency: D, handle_warning: W, mode: Mode) -> Self {
1161        Self {
1162            mode_data: if mode == Mode::Css {
1163                None
1164            } else {
1165                Some(ModeData::new(mode))
1166            },
1167            scope: Scope::TopLevel,
1168            block_nesting_level: 0,
1169            allow_import_at_rule: true,
1170            balanced: Default::default(),
1171            is_next_rule_prelude: true,
1172            in_animation_property: None,
1173            in_list_style_property: None,
1174            in_font_palette_property: None,
1175            in_container_property: None,
1176            in_grid_property: None,
1177            handle_dependency,
1178            handle_warning,
1179        }
1180    }
1181
1182    fn is_next_nested_syntax(&self, lexer: &mut Lexer) -> Option<bool> {
1183        lexer.consume_white_space_and_comments()?;
1184        let c = lexer.cur()?;
1185        if c == C_RIGHT_CURLY {
1186            return Some(false);
1187        }
1188        // If what follows is a property, then it's not a nested selector
1189        // This is not strictly correct, but it's good enough for our purposes
1190        // since we only need 'is_selector()' when next char is '#', '.', or ':'
1191        Some(!start_ident_sequence(c, lexer.peek()?, lexer.peek2()?))
1192    }
1193
1194    fn get_media(&self, lexer: &Lexer<'s>, start: Pos, end: Pos) -> Option<&'s str> {
1195        let media = lexer.slice(start, end)?;
1196        let mut media_lexer = Lexer::new(media);
1197        media_lexer.consume();
1198        media_lexer.consume_white_space_and_comments()?;
1199        Some(media)
1200    }
1201
1202    fn lex_charset_at_rule(&mut self, lexer: &mut Lexer<'s>, start: Pos) -> Option<()> {
1203        lexer.consume_white_space_and_comments()?;
1204        let quote = lexer.cur()?;
1205        if quote != C_QUOTATION_MARK && quote != C_APOSTROPHE {
1206            return Some(());
1207        }
1208
1209        let value_start = lexer.cur_pos()? + 1;
1210        lexer.consume();
1211        loop {
1212            let c = lexer.cur()?;
1213            if c == quote {
1214                let value_end = lexer.cur_pos()?;
1215                lexer.consume();
1216                lexer.consume_white_space_and_comments()?;
1217                if lexer.cur()? == C_SEMICOLON {
1218                    lexer.consume();
1219                    self.handle_dependency
1220                        .handle_dependency(Dependency::Charset {
1221                            value: lexer.slice(value_start, value_end)?,
1222                            range: Range::new(start, lexer.cur_pos()?),
1223                        });
1224                }
1225                return Some(());
1226            } else if is_new_line(c) {
1227                return Some(());
1228            } else if c == C_REVERSE_SOLIDUS {
1229                lexer.consume();
1230                let c2 = lexer.cur()?;
1231                if is_new_line(c2) {
1232                    lexer.consume();
1233                } else if are_valid_escape(c, c2) {
1234                    lexer.consume_escaped()?;
1235                }
1236            } else {
1237                lexer.consume();
1238            }
1239        }
1240    }
1241
1242    fn enter_animation_property(&mut self) {
1243        self.in_animation_property = Some(InProperty::new(
1244            AnimationReserved::default(),
1245            self.balanced.len(),
1246        ));
1247    }
1248
1249    fn exit_animation_property(&mut self) {
1250        self.in_animation_property = None;
1251    }
1252
1253    fn enter_list_style_property(&mut self) {
1254        self.in_list_style_property = Some(InProperty::new(ListStyleReserved, self.balanced.len()));
1255    }
1256
1257    fn exit_list_style_property(&mut self) {
1258        self.in_list_style_property = None;
1259    }
1260
1261    fn enter_font_palette_property(&mut self) {
1262        self.in_font_palette_property =
1263            Some(InProperty::new(FontPaletteReserved, self.balanced.len()));
1264    }
1265
1266    fn exit_font_palette_property(&mut self) {
1267        self.in_font_palette_property = None;
1268    }
1269
1270    fn enter_container_property(&mut self) {
1271        self.in_container_property = Some(InProperty::new(ContainerReserved, self.balanced.len()));
1272    }
1273
1274    fn exit_container_property(&mut self) {
1275        self.in_container_property = None;
1276    }
1277
1278    fn enter_grid_property(&mut self, kind: GridPropertyKind) {
1279        self.in_grid_property = Some(kind);
1280    }
1281
1282    fn exit_grid_property(&mut self) {
1283        self.in_grid_property = None;
1284    }
1285
1286    fn back_white_space_and_comments_distance(&self, lexer: &Lexer<'s>, end: Pos) -> Option<Pos> {
1287        let mut lexer = lexer.clone().turn_back(end);
1288        lexer.consume();
1289        lexer.consume_white_space_and_comments()?;
1290        lexer.cur_pos()
1291    }
1292
1293    fn should_have_after_white_space(&self, lexer: &Lexer<'s>, end: Pos) -> bool {
1294        let mut lexer = lexer.clone().turn_back(end);
1295        let mut has_white_space = false;
1296        lexer.consume();
1297        loop {
1298            if lexer.consume_comments().is_none() {
1299                return true;
1300            }
1301            let Some(c) = lexer.cur() else {
1302                return true;
1303            };
1304            if is_white_space(c) {
1305                has_white_space = true;
1306                if lexer.consume_space().is_none() {
1307                    return true;
1308                }
1309            } else {
1310                break;
1311            }
1312        }
1313        let c = lexer.cur().unwrap();
1314        // start of a :global :local
1315        if c == C_LEFT_PARENTHESIS
1316            || c == C_COMMA
1317            || c == C_SEMICOLON
1318            || c == C_RIGHT_CURLY
1319            || c == C_LEFT_CURLY
1320        {
1321            return true;
1322        }
1323        has_white_space
1324    }
1325
1326    fn has_after_white_space(&self, lexer: &mut Lexer<'s>) -> Option<bool> {
1327        let mut has_white_space = false;
1328        loop {
1329            lexer.consume_comments()?;
1330            if is_white_space(lexer.cur()?) {
1331                has_white_space = true;
1332                lexer.consume_space()?;
1333            } else {
1334                break;
1335            }
1336        }
1337        Some(has_white_space)
1338    }
1339
1340    fn eat(&mut self, lexer: &mut Lexer<'s>, chars: &[char], message: &'s str) -> Option<bool> {
1341        if !chars.contains(&lexer.cur()?) {
1342            self.handle_warning.handle_warning(Warning {
1343                kind: WarningKind::Unexpected { message },
1344                range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
1345            });
1346            return Some(false);
1347        }
1348        lexer.consume();
1349        Some(true)
1350    }
1351
1352    fn lex_icss_import(&mut self, lexer: &mut Lexer<'s>) -> Option<()> {
1353        lexer.consume_white_space_and_comments()?;
1354        let start = lexer.cur_pos()?;
1355        let end = if lexer.cur()? == C_RIGHT_PARENTHESIS {
1356            start
1357        } else if lexer.cur()? == C_QUOTATION_MARK || lexer.cur()? == C_APOSTROPHE {
1358            let quote = lexer.cur()?;
1359            self.consume_raw_string(lexer, quote)?;
1360            lexer.cur_pos()?
1361        } else {
1362            let mut end = start;
1363            loop {
1364                let c = lexer.cur()?;
1365                if c == C_RIGHT_PARENTHESIS {
1366                    break;
1367                }
1368                if c == C_SOLIDUS && lexer.peek()? == C_ASTERISK {
1369                    lexer.consume_comments()?;
1370                    continue;
1371                }
1372                lexer.consume();
1373                if !is_white_space(c) {
1374                    end = lexer.cur_pos()?;
1375                }
1376            }
1377            end
1378        };
1379        lexer.consume_white_space_and_comments()?;
1380        if lexer.cur()? != C_RIGHT_PARENTHESIS {
1381            loop {
1382                let c = lexer.cur()?;
1383                if c == C_RIGHT_PARENTHESIS {
1384                    break;
1385                }
1386                lexer.consume();
1387            }
1388        }
1389        self.handle_dependency
1390            .handle_dependency(Dependency::ICSSImportFrom {
1391                path: lexer.slice(start, end)?,
1392            });
1393        lexer.consume();
1394        lexer.consume_white_space_and_comments()?;
1395        if !self.eat(
1396            lexer,
1397            &[C_LEFT_CURLY],
1398            "Expected '{' during parsing of ':import()'",
1399        )? {
1400            return Some(());
1401        }
1402        lexer.consume_white_space_and_comments()?;
1403        while lexer.cur()? != C_RIGHT_CURLY {
1404            lexer.consume_white_space_and_comments()?;
1405            let prop_start = lexer.cur_pos()?;
1406            self.consume_icss_export_prop(lexer)?;
1407            let prop_end = lexer.cur_pos()?;
1408            lexer.consume_white_space_and_comments()?;
1409            if !self.eat(
1410                lexer,
1411                &[C_COLON],
1412                "Expected ':' during parsing of ':import'",
1413            )? {
1414                return Some(());
1415            }
1416            lexer.consume_white_space_and_comments()?;
1417            let value_start = lexer.cur_pos()?;
1418            self.consume_icss_export_value(lexer)?;
1419            let value_end = lexer.cur_pos()?;
1420            if lexer.cur()? == C_SEMICOLON {
1421                lexer.consume();
1422                lexer.consume_white_space_and_comments()?;
1423            }
1424            self.handle_dependency
1425                .handle_dependency(Dependency::ICSSImportValue {
1426                    prop: lexer
1427                        .slice(prop_start, prop_end)?
1428                        .trim_end_matches(is_white_space),
1429                    value: lexer
1430                        .slice(value_start, value_end)?
1431                        .trim_end_matches(is_white_space),
1432                });
1433        }
1434        lexer.consume();
1435        Some(())
1436    }
1437
1438    fn consume_icss_export_prop(&self, lexer: &mut Lexer<'s>) -> Option<()> {
1439        loop {
1440            let c = lexer.cur()?;
1441            if c == C_COLON
1442                || c == C_RIGHT_CURLY
1443                || c == C_SEMICOLON
1444                || (c == C_SOLIDUS && lexer.peek()? == C_ASTERISK)
1445            {
1446                break;
1447            }
1448            lexer.consume();
1449        }
1450        Some(())
1451    }
1452
1453    fn consume_icss_export_value(&self, lexer: &mut Lexer<'s>) -> Option<()> {
1454        loop {
1455            let c = lexer.cur()?;
1456            if c == C_RIGHT_CURLY || c == C_SEMICOLON {
1457                break;
1458            }
1459            lexer.consume();
1460        }
1461        Some(())
1462    }
1463
1464    fn consume_raw_string(&self, lexer: &mut Lexer<'s>, end: char) -> Option<()> {
1465        lexer.consume();
1466        loop {
1467            let c = lexer.cur()?;
1468            if c == end {
1469                lexer.consume();
1470                break;
1471            } else if is_new_line(c) {
1472                break;
1473            } else if c == C_REVERSE_SOLIDUS {
1474                lexer.consume();
1475                let c2 = lexer.cur()?;
1476                if is_new_line(c2) {
1477                    lexer.consume();
1478                } else if are_valid_escape(c, c2) {
1479                    lexer.consume_escaped()?;
1480                }
1481            } else {
1482                lexer.consume();
1483            }
1484        }
1485        Some(())
1486    }
1487
1488    fn consume_value_at_rule_params(&self, lexer: &mut Lexer<'s>) -> Option<(Pos, Pos)> {
1489        let start = lexer.cur_pos()?;
1490        loop {
1491            let c = lexer.cur()?;
1492            if c == C_SEMICOLON {
1493                let end = lexer.cur_pos()?;
1494                lexer.consume();
1495                return Some((start, end));
1496            }
1497            if c == C_RIGHT_CURLY {
1498                return Some((start, lexer.cur_pos()?));
1499            }
1500            if c == C_QUOTATION_MARK || c == C_APOSTROPHE {
1501                self.consume_raw_string(lexer, c)?;
1502                continue;
1503            }
1504            if c == C_SOLIDUS && lexer.peek()? == C_ASTERISK {
1505                lexer.consume_comments()?;
1506                continue;
1507            }
1508            lexer.consume();
1509        }
1510    }
1511
1512    fn lex_value_at_rule(&mut self, lexer: &mut Lexer<'s>, start: Pos) -> Option<()> {
1513        let (params_start, params_end) = self.consume_value_at_rule_params(lexer)?;
1514        let at_rule_end = params_end.max(lexer.cur_pos().unwrap_or(params_end));
1515        let params = lexer.slice(params_start, params_end)?;
1516        match parse_value_at_rule(params) {
1517            ValueAtRule::Import { from, items } => {
1518                if from.is_empty() || items.is_empty() {
1519                    self.handle_warning.handle_warning(Warning {
1520                        range: Range::new(start, at_rule_end),
1521                        kind: WarningKind::Unexpected {
1522                            message: "Broken '@value' at-rule",
1523                        },
1524                    });
1525                } else {
1526                    self.handle_dependency
1527                        .handle_dependency(Dependency::ICSSImportFrom { path: from });
1528                    for item in items {
1529                        self.handle_dependency
1530                            .handle_dependency(Dependency::ICSSImportValue {
1531                                prop: item.local_name,
1532                                value: item.import_name,
1533                            });
1534                        self.handle_dependency
1535                            .handle_dependency(Dependency::ICSSExportValue {
1536                                prop: item.local_name,
1537                                value: item.local_name,
1538                            });
1539                    }
1540                }
1541            }
1542            ValueAtRule::Value { local_name, value } => {
1543                if local_name.is_empty() {
1544                    self.handle_warning.handle_warning(Warning {
1545                        range: Range::new(start, at_rule_end),
1546                        kind: WarningKind::Unexpected {
1547                            message: "Broken '@value' at-rule",
1548                        },
1549                    });
1550                } else {
1551                    self.handle_dependency
1552                        .handle_dependency(Dependency::ICSSExportValue {
1553                            prop: local_name,
1554                            value,
1555                        });
1556                }
1557            }
1558        }
1559        self.handle_dependency
1560            .handle_dependency(Dependency::Replace {
1561                content: "",
1562                range: Range::new(start, at_rule_end),
1563            });
1564        Some(())
1565    }
1566
1567    fn lex_icss_export(&mut self, lexer: &mut Lexer<'s>) -> Option<()> {
1568        lexer.consume_white_space_and_comments()?;
1569        if !self.eat(
1570            lexer,
1571            &[C_LEFT_CURLY],
1572            "Expected '{' during parsing of ':export'",
1573        )? {
1574            return Some(());
1575        }
1576        lexer.consume_white_space_and_comments()?;
1577        while lexer.cur()? != C_RIGHT_CURLY {
1578            lexer.consume_white_space_and_comments()?;
1579            let prop_start = lexer.cur_pos()?;
1580            self.consume_icss_export_prop(lexer)?;
1581            let prop_end = lexer.cur_pos()?;
1582            lexer.consume_white_space_and_comments()?;
1583            if !self.eat(
1584                lexer,
1585                &[C_COLON],
1586                "Expected ':' during parsing of ':export'",
1587            )? {
1588                return Some(());
1589            }
1590            lexer.consume_white_space_and_comments()?;
1591            let value_start = lexer.cur_pos()?;
1592            self.consume_icss_export_value(lexer)?;
1593            let value_end = lexer.cur_pos()?;
1594            if lexer.cur()? == C_SEMICOLON {
1595                lexer.consume();
1596                lexer.consume_white_space_and_comments()?;
1597            }
1598            self.handle_dependency
1599                .handle_dependency(Dependency::ICSSExportValue {
1600                    prop: lexer
1601                        .slice(prop_start, prop_end)?
1602                        .trim_end_matches(is_white_space),
1603                    value: lexer
1604                        .slice(value_start, value_end)?
1605                        .trim_end_matches(is_white_space),
1606                });
1607        }
1608        lexer.consume();
1609        Some(())
1610    }
1611
1612    fn lex_local_var(&mut self, lexer: &mut Lexer<'s>) -> Option<()> {
1613        lexer.consume_white_space_and_comments()?;
1614        let start = lexer.cur_pos()?;
1615        if lexer.cur()? != C_HYPHEN_MINUS || lexer.peek()? != C_HYPHEN_MINUS {
1616            self.handle_warning.handle_warning(Warning {
1617                kind: WarningKind::Unexpected {
1618                    message: "Expected starts with '--' during parsing of 'var()'",
1619                },
1620                range: Range::new(start, lexer.peek2_pos()?),
1621            });
1622            return Some(());
1623        }
1624        lexer.consume_ident_sequence()?;
1625        let name_start = start + 2;
1626        let end = lexer.cur_pos()?;
1627        lexer.consume_white_space_and_comments()?;
1628        let from_start = lexer.cur_pos()?;
1629        let from = if matches!(lexer.slice(from_start, from_start + 4), Some("from")) {
1630            lexer.consume();
1631            lexer.consume();
1632            lexer.consume();
1633            lexer.consume();
1634            lexer.consume_white_space_and_comments()?;
1635            let c = lexer.cur()?;
1636            let path_start = lexer.cur_pos()?;
1637            if c == '\'' || c == '"' {
1638                lexer.consume();
1639                lexer.consume_string(self, c)?;
1640            } else if start_ident_sequence(c, lexer.peek()?, lexer.peek2()?) {
1641                lexer.consume_ident_sequence()?;
1642            } else {
1643                self.handle_warning.handle_warning(Warning {
1644                    range: Range::new(path_start, lexer.peek_pos()?),
1645                    kind: WarningKind::Unexpected {
1646                        message: "Expected string or ident during parsing of 'composes'",
1647                    },
1648                });
1649                return Some(());
1650            }
1651            Some(lexer.slice(path_start, lexer.cur_pos()?)?)
1652        } else {
1653            None
1654        };
1655        self.handle_dependency
1656            .handle_dependency(Dependency::LocalVar {
1657                name: lexer.slice(name_start, end)?,
1658                range: Range::new(start, end),
1659                from,
1660            });
1661        Some(())
1662    }
1663
1664    fn lex_local_var_decl(
1665        &mut self,
1666        lexer: &mut Lexer<'s>,
1667        name: &'s str,
1668        start: Pos,
1669        end: Pos,
1670    ) -> Option<()> {
1671        lexer.consume_white_space_and_comments()?;
1672        if lexer.cur()? != C_COLON {
1673            return Some(());
1674        }
1675        lexer.consume();
1676        self.handle_dependency
1677            .handle_dependency(Dependency::LocalVarDecl {
1678                name,
1679                range: Range::new(start, end),
1680            });
1681        Some(())
1682    }
1683
1684    fn lex_local_dashed_ident_decl(
1685        &mut self,
1686        lexer: &mut Lexer<'s>,
1687        local_decl_dependency: impl FnOnce(&'s str, Range) -> Dependency<'s>,
1688        dashed_warning: impl FnOnce(Range) -> Warning<'s>,
1689        left_curly_warning: impl FnOnce(Range) -> Warning<'s>,
1690    ) -> Option<()> {
1691        lexer.consume_white_space_and_comments()?;
1692        let start = lexer.cur_pos()?;
1693        if lexer.cur()? != C_HYPHEN_MINUS || lexer.peek()? != C_HYPHEN_MINUS {
1694            self.handle_warning
1695                .handle_warning(dashed_warning(Range::new(start, lexer.peek2_pos()?)));
1696            return Some(());
1697        }
1698        lexer.consume_ident_sequence()?;
1699        let name_start = start + 2;
1700        let end = lexer.cur_pos()?;
1701        self.handle_dependency
1702            .handle_dependency(local_decl_dependency(
1703                lexer.slice(name_start, end)?,
1704                Range::new(start, end),
1705            ));
1706        lexer.consume_white_space_and_comments()?;
1707        if lexer.cur()? != C_LEFT_CURLY {
1708            self.handle_warning
1709                .handle_warning(left_curly_warning(Range::new(
1710                    lexer.cur_pos()?,
1711                    lexer.peek_pos()?,
1712                )));
1713            return Some(());
1714        }
1715        Some(())
1716    }
1717
1718    fn lex_local_keyframes_decl(&mut self, lexer: &mut Lexer<'s>) -> Option<()> {
1719        lexer.consume_white_space_and_comments()?;
1720        let mut is_function = false;
1721        if lexer.cur()? == C_COLON {
1722            let start = lexer.cur_pos()?;
1723            lexer.consume_potential_pseudo(self)?;
1724            let end = lexer.cur_pos()?;
1725            let pseudo = lexer.slice(start, end)?;
1726            let mode_data = self.mode_data.as_ref().unwrap();
1727            if mode_data.is_pure_mode() && pseudo.eq_ignore_ascii_case(":global(")
1728                || pseudo.eq_ignore_ascii_case(":global")
1729            {
1730                self.handle_warning.handle_warning(Warning {
1731                    range: Range::new(start, end),
1732                    kind: WarningKind::NotPure {
1733                        message: "'@keyframes :global' is not allowed in pure mode",
1734                    },
1735                });
1736            }
1737            is_function =
1738                pseudo.eq_ignore_ascii_case(":local(") || pseudo.eq_ignore_ascii_case(":global(");
1739            if !is_function
1740                && !pseudo.eq_ignore_ascii_case(":local")
1741                && !pseudo.eq_ignore_ascii_case(":global")
1742            {
1743                self.handle_warning.handle_warning(Warning {
1744                    range: Range::new(start, end),
1745                    kind: WarningKind::Unexpected {
1746                        message: "Expected ':local', ':local()', ':global', or ':global()' during parsing of '@keyframes' name",
1747                    }
1748                });
1749                return Some(());
1750            }
1751            lexer.consume_white_space_and_comments()?;
1752        }
1753        let start = lexer.cur_pos()?;
1754        if !start_ident_sequence(lexer.cur()?, lexer.peek()?, lexer.peek2()?) {
1755            self.handle_warning.handle_warning(Warning {
1756                range: Range::new(start, lexer.peek2_pos()?),
1757                kind: WarningKind::Unexpected {
1758                    message: "Expected ident during parsing of '@keyframes' name",
1759                },
1760            });
1761            return Some(());
1762        }
1763        lexer.consume_ident_sequence()?;
1764        let end = lexer.cur_pos()?;
1765        let mode_data = self.mode_data.as_mut().unwrap();
1766        if mode_data.is_current_local_mode() {
1767            self.handle_dependency
1768                .handle_dependency(Dependency::LocalKeyframesDecl {
1769                    name: lexer.slice(start, end)?,
1770                    range: Range::new(start, end),
1771                });
1772        }
1773        lexer.consume_white_space_and_comments()?;
1774        if is_function {
1775            if lexer.cur()? != C_RIGHT_PARENTHESIS {
1776                self.handle_warning.handle_warning(Warning {
1777                    range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
1778                    kind: WarningKind::Unexpected {
1779                        message: "Expected ')' during parsing of '@keyframes :local(' or '@keyframes :global('",
1780                    }
1781                });
1782                return Some(());
1783            }
1784            self.handle_dependency
1785                .handle_dependency(Dependency::Replace {
1786                    content: "",
1787                    range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
1788                });
1789            mode_data.inside_mode_function -= 1;
1790            self.balanced.pop_without_moda_data();
1791            lexer.consume();
1792            lexer.consume_white_space_and_comments()?;
1793        }
1794        if lexer.cur()? != C_LEFT_CURLY {
1795            self.handle_warning.handle_warning(Warning {
1796                range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
1797                kind: WarningKind::Unexpected {
1798                    message: "Expected '{' during parsing of '@keyframes'",
1799                },
1800            });
1801            return Some(());
1802        }
1803        Some(())
1804    }
1805
1806    fn handle_local_keyframes_dependency(&mut self, lexer: &Lexer<'s>) -> Option<()> {
1807        let animation = self.in_animation_property.as_mut().unwrap();
1808        if let Some(range) = animation.take_rename(self.balanced.len()) {
1809            self.handle_dependency
1810                .handle_dependency(Dependency::LocalKeyframes {
1811                    name: lexer.slice(range.start, range.end)?,
1812                    range,
1813                });
1814        }
1815        animation.reset_reserved();
1816        Some(())
1817    }
1818
1819    fn lex_local_counter_style_decl(&mut self, lexer: &mut Lexer<'s>) -> Option<()> {
1820        lexer.consume_white_space_and_comments()?;
1821        let start = lexer.cur_pos()?;
1822        if !start_ident_sequence(lexer.cur()?, lexer.peek()?, lexer.peek2()?) {
1823            self.handle_warning.handle_warning(Warning {
1824                range: Range::new(start, lexer.peek2_pos()?),
1825                kind: WarningKind::Unexpected {
1826                    message: "Expected ident during parsing of '@counter-style'",
1827                },
1828            });
1829            return Some(());
1830        }
1831        lexer.consume_ident_sequence()?;
1832        let end = lexer.cur_pos()?;
1833        self.handle_dependency
1834            .handle_dependency(Dependency::LocalCounterStyleDecl {
1835                name: lexer.slice(start, end)?,
1836                range: Range::new(start, end),
1837            });
1838        lexer.consume_white_space_and_comments()?;
1839        if lexer.cur()? != C_LEFT_CURLY {
1840            self.handle_warning.handle_warning(Warning {
1841                range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
1842                kind: WarningKind::Unexpected {
1843                    message: "Expected '{' during parsing of '@counter-style'",
1844                },
1845            });
1846            return Some(());
1847        }
1848        Some(())
1849    }
1850
1851    fn lex_local_container_at_rule(&mut self, lexer: &mut Lexer<'s>) -> Option<()> {
1852        lexer.consume_white_space_and_comments()?;
1853        if lexer.cur()? == C_LEFT_PARENTHESIS {
1854            return Some(());
1855        }
1856        if !start_ident_sequence(lexer.cur()?, lexer.peek()?, lexer.peek2()?) {
1857            return Some(());
1858        }
1859        let start = lexer.cur_pos()?;
1860        lexer.consume_ident_sequence()?;
1861        let end = lexer.cur_pos()?;
1862        let ident = lexer.slice(start, end)?;
1863        if is_reserved_container_query_ident(ident) {
1864            return Some(());
1865        }
1866        if self.mode_data.as_ref().unwrap().is_current_local_mode() {
1867            self.handle_dependency
1868                .handle_dependency(Dependency::LocalContainer {
1869                    name: ident,
1870                    range: Range::new(start, end),
1871                });
1872        }
1873        Some(())
1874    }
1875
1876    fn lex_local_function_decl(&mut self, lexer: &mut Lexer<'s>) -> Option<()> {
1877        lexer.consume_white_space_and_comments()?;
1878        let mut is_function = false;
1879        if lexer.cur()? == C_COLON {
1880            let start = lexer.cur_pos()?;
1881            lexer.consume_potential_pseudo(self)?;
1882            let end = lexer.cur_pos()?;
1883            let pseudo = lexer.slice(start, end)?;
1884            let mode_data = self.mode_data.as_ref().unwrap();
1885            if mode_data.is_pure_mode() && pseudo.eq_ignore_ascii_case(":global(")
1886                || pseudo.eq_ignore_ascii_case(":global")
1887            {
1888                self.handle_warning.handle_warning(Warning {
1889                    range: Range::new(start, end),
1890                    kind: WarningKind::NotPure {
1891                        message: "'@function :global' is not allowed in pure mode",
1892                    },
1893                });
1894            }
1895            is_function =
1896                pseudo.eq_ignore_ascii_case(":local(") || pseudo.eq_ignore_ascii_case(":global(");
1897            if !is_function
1898                && !pseudo.eq_ignore_ascii_case(":local")
1899                && !pseudo.eq_ignore_ascii_case(":global")
1900            {
1901                self.handle_warning.handle_warning(Warning {
1902                    range: Range::new(start, end),
1903                    kind: WarningKind::Unexpected {
1904                        message:
1905                            "Expected ':local', ':local()', ':global', or ':global()' during parsing of '@function' name",
1906                    },
1907                });
1908                return Some(());
1909            }
1910            lexer.consume_white_space_and_comments()?;
1911        }
1912        let start = lexer.cur_pos()?;
1913        if lexer.cur()? != C_HYPHEN_MINUS || lexer.peek()? != C_HYPHEN_MINUS {
1914            self.handle_warning.handle_warning(Warning {
1915                range: Range::new(start, lexer.peek2_pos()?),
1916                kind: WarningKind::Unexpected {
1917                    message: "Expected starts with '--' during parsing of '@function' name",
1918                },
1919            });
1920            return Some(());
1921        }
1922        lexer.consume_ident_sequence()?;
1923        let end = lexer.cur_pos()?;
1924        let mode_data = self.mode_data.as_mut().unwrap();
1925        if mode_data.is_current_local_mode() {
1926            self.handle_dependency
1927                .handle_dependency(Dependency::LocalFunctionDecl {
1928                    name: lexer.slice(start + 2, end)?,
1929                    range: Range::new(start, end),
1930                });
1931        }
1932        lexer.consume_white_space_and_comments()?;
1933        if is_function {
1934            if lexer.cur()? != C_RIGHT_PARENTHESIS {
1935                self.handle_warning.handle_warning(Warning {
1936                    range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
1937                    kind: WarningKind::Unexpected {
1938                        message:
1939                            "Expected ')' during parsing of '@function :local(' or '@function :global('",
1940                    },
1941                });
1942                return Some(());
1943            }
1944            self.handle_dependency
1945                .handle_dependency(Dependency::Replace {
1946                    content: "",
1947                    range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
1948                });
1949            mode_data.inside_mode_function -= 1;
1950            self.balanced.pop_without_moda_data();
1951            lexer.consume();
1952            lexer.consume_white_space_and_comments()?;
1953        }
1954        if lexer.cur()? != C_LEFT_PARENTHESIS {
1955            self.handle_warning.handle_warning(Warning {
1956                range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
1957                kind: WarningKind::Unexpected {
1958                    message: "Expected '(' during parsing of '@function'",
1959                },
1960            });
1961        }
1962        Some(())
1963    }
1964
1965    fn handle_local_counter_style_dependency(&mut self, lexer: &Lexer<'s>) -> Option<()> {
1966        let list_style = self.in_list_style_property.as_mut().unwrap();
1967        if let Some(range) = list_style.take_rename(self.balanced.len()) {
1968            self.handle_dependency
1969                .handle_dependency(Dependency::LocalCounterStyle {
1970                    name: lexer.slice(range.start, range.end)?,
1971                    range,
1972                });
1973        }
1974        Some(())
1975    }
1976
1977    fn handle_local_font_palette_dependency(&mut self, lexer: &Lexer<'s>) -> Option<()> {
1978        let font_palette = self.in_font_palette_property.as_mut().unwrap();
1979        if let Some(range) = font_palette.take_rename(self.balanced.len()) {
1980            self.handle_dependency
1981                .handle_dependency(Dependency::LocalFontPalette {
1982                    name: lexer.slice(range.start + 2, range.end)?,
1983                    range,
1984                });
1985        }
1986        Some(())
1987    }
1988
1989    fn lex_composes(
1990        &mut self,
1991        lexer: &mut Lexer<'s>,
1992        local_classes: SmallVec<[&'s str; 2]>,
1993        start: Pos,
1994    ) -> Option<()> {
1995        lexer.consume_white_space_and_comments()?;
1996        if lexer.cur()? != C_COLON {
1997            return Some(());
1998        }
1999        lexer.consume();
2000        let mut names: SmallVec<[&'s str; 2]> = SmallVec::new();
2001        let mut end;
2002        let mut has_from = false;
2003        loop {
2004            lexer.consume_white_space_and_comments()?;
2005            let start = lexer.cur_pos()?;
2006            end = start;
2007            loop {
2008                let c = lexer.cur()?;
2009                if c == C_COMMA || c == C_SEMICOLON || c == C_RIGHT_CURLY {
2010                    break;
2011                }
2012                let maybe_global_start = lexer.cur_pos()?;
2013                if matches!(
2014                    lexer.slice(maybe_global_start, maybe_global_start + 7),
2015                    Some("global(")
2016                ) {
2017                    for _ in 0..7 {
2018                        lexer.consume();
2019                    }
2020                    let name_start = lexer.cur_pos()?;
2021                    if !start_ident_sequence(lexer.cur()?, lexer.peek()?, lexer.peek2()?) {
2022                        self.handle_warning.handle_warning(Warning {
2023                            range: Range::new(name_start, lexer.peek2_pos()?),
2024                            kind: WarningKind::Unexpected {
2025                                message: "Expected ident during parsing of 'composes'",
2026                            },
2027                        });
2028                        return Some(());
2029                    }
2030                    lexer.consume_ident_sequence()?;
2031                    let name_end = lexer.cur_pos()?;
2032                    lexer.consume_white_space_and_comments()?;
2033                    self.eat(
2034                        lexer,
2035                        &[C_RIGHT_PARENTHESIS],
2036                        "Expected ')' during parsing of 'composes'",
2037                    );
2038                    end = lexer.cur_pos()?;
2039                    self.handle_dependency
2040                        .handle_dependency(Dependency::Composes {
2041                            local_classes: local_classes.clone(),
2042                            names: smallvec![lexer.slice(name_start, name_end)?],
2043                            from: Some("global"),
2044                            range: Range::new(maybe_global_start, lexer.cur_pos()?),
2045                        });
2046                } else {
2047                    let name_start = lexer.cur_pos()?;
2048                    if !start_ident_sequence(c, lexer.peek()?, lexer.peek2()?) {
2049                        self.handle_warning.handle_warning(Warning {
2050                            range: Range::new(name_start, lexer.peek2_pos()?),
2051                            kind: WarningKind::Unexpected {
2052                                message: "Expected ident during parsing of 'composes'",
2053                            },
2054                        });
2055                        return Some(());
2056                    }
2057                    lexer.consume_ident_sequence()?;
2058                    let name_end = lexer.cur_pos()?;
2059                    let ident = lexer.slice(name_start, name_end)?;
2060                    if !names.is_empty() && ident.eq_ignore_ascii_case("from") {
2061                        lexer.consume_white_space_and_comments()?;
2062                        let next_c = lexer.cur()?;
2063                        if next_c == C_QUOTATION_MARK
2064                            || next_c == C_APOSTROPHE
2065                            || start_ident_sequence(next_c, lexer.peek()?, lexer.peek2()?)
2066                        {
2067                            has_from = true;
2068                            break;
2069                        }
2070                    }
2071                    names.push(ident);
2072                    end = name_end;
2073                }
2074                lexer.consume_white_space_and_comments()?;
2075            }
2076            lexer.consume_white_space_and_comments()?;
2077            let c = lexer.cur()?;
2078            if !has_from {
2079                if !names.is_empty() {
2080                    self.handle_dependency
2081                        .handle_dependency(Dependency::Composes {
2082                            local_classes: local_classes.clone(),
2083                            names: std::mem::take(&mut names),
2084                            from: None,
2085                            range: Range::new(start, end),
2086                        });
2087                }
2088                if c == C_COMMA {
2089                    lexer.consume();
2090                    continue;
2091                }
2092                break;
2093            }
2094            let path_start = lexer.cur_pos()?;
2095            if c == '\'' || c == '"' {
2096                lexer.consume();
2097                lexer.consume_string(self, c)?;
2098            } else if start_ident_sequence(c, lexer.peek()?, lexer.peek2()?) {
2099                lexer.consume_ident_sequence()?;
2100            } else {
2101                self.handle_warning.handle_warning(Warning {
2102                    range: Range::new(path_start, lexer.peek_pos()?),
2103                    kind: WarningKind::Unexpected {
2104                        message: "Expected string or ident during parsing of 'composes'",
2105                    },
2106                });
2107                return Some(());
2108            }
2109            let path_end = lexer.cur_pos()?;
2110            end = path_end;
2111            let from = Some(lexer.slice(path_start, path_end)?);
2112            self.handle_dependency
2113                .handle_dependency(Dependency::Composes {
2114                    local_classes: local_classes.clone(),
2115                    names: std::mem::take(&mut names),
2116                    from,
2117                    range: Range::new(start, end),
2118                });
2119            lexer.consume_white_space_and_comments()?;
2120            if lexer.cur()? != C_COMMA {
2121                break;
2122            }
2123            lexer.consume();
2124        }
2125        if lexer.cur()? == C_SEMICOLON {
2126            lexer.consume();
2127            end = lexer.cur_pos()?;
2128        }
2129        self.handle_dependency
2130            .handle_dependency(Dependency::Replace {
2131                content: "",
2132                range: Range::new(start, end),
2133            });
2134        Some(())
2135    }
2136}
2137
2138impl<'s, D: HandleDependency<'s>, W: HandleWarning<'s>> Visitor<'s> for LexDependencies<'s, D, W> {
2139    fn is_selector(&mut self, _: &mut Lexer) -> Option<bool> {
2140        Some(self.is_next_rule_prelude)
2141    }
2142
2143    fn url(
2144        &mut self,
2145        lexer: &mut Lexer<'s>,
2146        start: Pos,
2147        end: Pos,
2148        content_start: Pos,
2149        content_end: Pos,
2150    ) -> Option<()> {
2151        let value = lexer.slice(content_start, content_end)?;
2152        match self.scope {
2153            Scope::InAtImport(ref mut import_data) => {
2154                if import_data.in_supports() {
2155                    return Some(());
2156                }
2157                if import_data.url.is_some() {
2158                    self.handle_warning.handle_warning(Warning {
2159                        range: Range::new(import_data.start, end),
2160                        kind: WarningKind::DuplicateUrl {
2161                            when: lexer.slice(import_data.start, end)?,
2162                        },
2163                    });
2164                    return Some(());
2165                }
2166                import_data.url = Some(value);
2167                import_data.url_range = Some(Range::new(start, end));
2168            }
2169            Scope::InBlock => self.handle_dependency.handle_dependency(Dependency::Url {
2170                request: value,
2171                range: Range::new(start, end),
2172                kind: UrlRangeKind::Function,
2173            }),
2174            _ => {}
2175        }
2176        Some(())
2177    }
2178
2179    fn string(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2180        match self.scope {
2181            Scope::InAtImport(ref mut import_data) => {
2182                let inside_url = matches!(
2183                    self.balanced.last(),
2184                    Some(last) if matches!(last.kind, BalancedItemKind::Url)
2185                );
2186
2187                // Do not parse URLs in `supports(...)` and other strings if we already have a URL
2188                if import_data.in_supports() || (!inside_url && import_data.url.is_some()) {
2189                    return Some(());
2190                }
2191
2192                if inside_url && import_data.url.is_some() {
2193                    self.handle_warning.handle_warning(Warning {
2194                        range: Range::new(import_data.start, end),
2195                        kind: WarningKind::DuplicateUrl {
2196                            when: lexer.slice(import_data.start, end)?,
2197                        },
2198                    });
2199                    return Some(());
2200                }
2201
2202                let value = lexer.slice(start + 1, end - 1)?;
2203                import_data.url = Some(value);
2204                // For url("inside_url") url_range will determined in right_parenthesis
2205                if !inside_url {
2206                    import_data.url_range = Some(Range::new(start, end));
2207                }
2208            }
2209            Scope::InBlock => {
2210                if let Some(mode_data) = &self.mode_data {
2211                    if mode_data.is_property_local_mode()
2212                        && matches!(
2213                            self.in_grid_property,
2214                            Some(GridPropertyKind::TemplateLike | GridPropertyKind::TemplateAreas)
2215                        )
2216                    {
2217                        for range in parse_grid_template_area_ranges(
2218                            lexer.slice(start + 1, end - 1)?,
2219                            start + 1,
2220                        ) {
2221                            self.handle_dependency
2222                                .handle_dependency(Dependency::LocalGridDecl {
2223                                    name: lexer.slice(range.start, range.end)?,
2224                                    range,
2225                                });
2226                        }
2227                    }
2228                }
2229                let Some(last) = self.balanced.last() else {
2230                    return Some(());
2231                };
2232                let kind = match last.kind {
2233                    BalancedItemKind::Url => UrlRangeKind::String,
2234                    BalancedItemKind::ImageSet => UrlRangeKind::Function,
2235                    _ => return Some(()),
2236                };
2237                let value = lexer.slice(start + 1, end - 1)?;
2238                self.handle_dependency.handle_dependency(Dependency::Url {
2239                    request: value,
2240                    range: Range::new(start, end),
2241                    kind,
2242                });
2243            }
2244            _ => {}
2245        }
2246        Some(())
2247    }
2248
2249    fn at_keyword(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2250        let name = lexer.slice(start, end)?;
2251        if name.eq_ignore_ascii_case("@namespace") {
2252            self.scope = Scope::AtNamespaceInvalid;
2253            self.handle_warning.handle_warning(Warning {
2254                range: Range::new(start, end),
2255                kind: WarningKind::NamespaceNotSupportedInBundledCss,
2256            });
2257        } else if name.eq_ignore_ascii_case("@import") {
2258            if !self.allow_import_at_rule {
2259                self.scope = Scope::AtImportInvalid;
2260                self.handle_warning.handle_warning(Warning {
2261                    range: Range::new(start, end),
2262                    kind: WarningKind::NotPrecededAtImport,
2263                });
2264                return Some(());
2265            }
2266            self.scope = Scope::InAtImport(ImportData::new(start));
2267        } else if name.eq_ignore_ascii_case("@charset") {
2268            self.lex_charset_at_rule(lexer, start)?;
2269        } else if self.mode_data.is_some() {
2270            let mut can_contain_rules = true;
2271            if name.eq_ignore_ascii_case("@value") {
2272                self.lex_value_at_rule(lexer, start)?;
2273                can_contain_rules = false;
2274            } else if name.eq_ignore_ascii_case("@keyframes")
2275                || with_vendor_prefixed_eq(name, "keyframes", true)
2276            {
2277                self.lex_local_keyframes_decl(lexer)?;
2278            } else if name.eq_ignore_ascii_case("@container") {
2279                self.lex_local_container_at_rule(lexer)?;
2280            } else if name.eq_ignore_ascii_case("@function") {
2281                self.lex_local_function_decl(lexer)?;
2282            } else if name.eq_ignore_ascii_case("@property") {
2283                self.lex_local_dashed_ident_decl(
2284                    lexer,
2285                    |name, range| Dependency::LocalPropertyDecl { name, range },
2286                    |range| Warning {
2287                        range,
2288                        kind: WarningKind::Unexpected {
2289                            message: "Expected starts with '--' during parsing of '@property'",
2290                        },
2291                    },
2292                    |range| Warning {
2293                        range,
2294                        kind: WarningKind::Unexpected {
2295                            message: "Expected '{' during parsing of '@property'",
2296                        },
2297                    },
2298                )?;
2299            } else if name.eq_ignore_ascii_case("@counter-style") {
2300                self.lex_local_counter_style_decl(lexer)?;
2301            } else if name.eq_ignore_ascii_case("@font-palette-values") {
2302                self.lex_local_dashed_ident_decl(
2303                    lexer,
2304                    |name, range| Dependency::LocalFontPaletteDecl { name, range },
2305                    |range| Warning {
2306                        range,
2307                        kind: WarningKind::Unexpected {
2308                            message: "Expected starts with '--' during parsing of '@font-palette-values'",
2309                        }
2310                    },
2311                    |range| Warning {
2312                        range,
2313                        kind: WarningKind::Unexpected {
2314                            message: "Expected '{' during parsing of '@font-palette-values'",
2315                        }
2316                    },
2317                )?;
2318            } else {
2319                self.is_next_rule_prelude = name.eq_ignore_ascii_case("@scope");
2320            }
2321
2322            let mode_data = self.mode_data.as_mut().unwrap();
2323            if can_contain_rules && self.block_nesting_level == 0 {
2324                mode_data.composes_local_classes.find_at_keyword();
2325            }
2326
2327            if mode_data.is_pure_mode() {
2328                mode_data.pure_global = None;
2329            }
2330        }
2331        Some(())
2332    }
2333
2334    fn semicolon(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2335        match self.scope {
2336            Scope::InAtImport(ref import_data) => {
2337                let Some(url) = import_data.url else {
2338                    self.handle_warning.handle_warning(Warning {
2339                        range: Range::new(import_data.start, end),
2340                        kind: WarningKind::ExpectedUrl {
2341                            when: lexer.slice(import_data.start, end)?,
2342                        },
2343                    });
2344                    self.scope = Scope::TopLevel;
2345                    return Some(());
2346                };
2347                let Some(url_range) = &import_data.url_range else {
2348                    self.handle_warning.handle_warning(Warning {
2349                        range: Range::new(start, end),
2350                        kind: WarningKind::Unexpected {
2351                            message: "Unexpected ';' during parsing of '@import url()'",
2352                        },
2353                    });
2354                    self.scope = Scope::TopLevel;
2355                    return Some(());
2356                };
2357                let layer = match &import_data.layer {
2358                    ImportDataLayer::None => None,
2359                    ImportDataLayer::EndLayer { value, range } => {
2360                        if url_range.start > range.start {
2361                            self.handle_warning.handle_warning(Warning {
2362                                range: url_range.clone(),
2363                                kind: WarningKind::ExpectedUrlBefore {
2364                                    when: lexer.slice(range.start, url_range.end)?,
2365                                },
2366                            });
2367                            self.scope = Scope::TopLevel;
2368                            return Some(());
2369                        }
2370                        Some(*value)
2371                    }
2372                };
2373                let supports = match &import_data.supports {
2374                    ImportDataSupports::None => None,
2375                    ImportDataSupports::InSupports => {
2376                        self.handle_warning.handle_warning(Warning {
2377                            range: Range::new(start, end),
2378                            kind: WarningKind::Unexpected {
2379                                message: "Unexpected ';' during parsing of 'supports()'",
2380                            },
2381                        });
2382                        None
2383                    }
2384                    ImportDataSupports::EndSupports { value, range } => {
2385                        if url_range.start > range.start {
2386                            self.handle_warning.handle_warning(Warning {
2387                                range: url_range.clone(),
2388                                kind: WarningKind::ExpectedUrlBefore {
2389                                    when: lexer.slice(range.start, url_range.end)?,
2390                                },
2391                            });
2392                            self.scope = Scope::TopLevel;
2393                            return Some(());
2394                        }
2395                        Some(*value)
2396                    }
2397                };
2398                if let Some(layer_range) = import_data.layer_range() {
2399                    if let Some(supports_range) = import_data.supports_range() {
2400                        if layer_range.start > supports_range.start {
2401                            self.handle_warning.handle_warning(Warning {
2402                                range: layer_range.clone(),
2403                                kind: WarningKind::ExpectedLayerBefore {
2404                                    when: lexer.slice(supports_range.start, layer_range.end)?,
2405                                },
2406                            });
2407                            self.scope = Scope::TopLevel;
2408                            return Some(());
2409                        }
2410                    }
2411                }
2412                let last_end = import_data
2413                    .supports_range()
2414                    .or_else(|| import_data.layer_range())
2415                    .unwrap_or(url_range)
2416                    .end;
2417                let media = self.get_media(lexer, last_end, start);
2418                self.handle_dependency
2419                    .handle_dependency(Dependency::Import {
2420                        request: url,
2421                        range: Range::new(import_data.start, end),
2422                        layer,
2423                        supports,
2424                        media,
2425                    });
2426                self.scope = Scope::TopLevel;
2427            }
2428            Scope::AtImportInvalid | Scope::AtNamespaceInvalid => {
2429                self.scope = Scope::TopLevel;
2430            }
2431            Scope::InBlock => {
2432                if let Some(mode_data) = &mut self.mode_data {
2433                    mode_data.pure_global = Some(end);
2434
2435                    if mode_data.is_property_local_mode() {
2436                        if self.in_animation_property.is_some() {
2437                            self.handle_local_keyframes_dependency(lexer)?;
2438                            self.exit_animation_property();
2439                        }
2440                        if self.in_list_style_property.is_some() {
2441                            self.handle_local_counter_style_dependency(lexer)?;
2442                            self.exit_list_style_property();
2443                        }
2444                        if self.in_font_palette_property.is_some() {
2445                            self.handle_local_font_palette_dependency(lexer)?;
2446                            self.exit_font_palette_property();
2447                        }
2448                        if self.in_container_property.is_some() {
2449                            self.exit_container_property();
2450                        }
2451                        if self.in_grid_property.is_some() {
2452                            self.exit_grid_property();
2453                        }
2454                    }
2455                }
2456                self.is_next_rule_prelude = self.is_next_nested_syntax(lexer)?;
2457            }
2458            Scope::TopLevel => {
2459                self.is_next_rule_prelude = self.is_next_nested_syntax(lexer)?;
2460            }
2461        }
2462        Some(())
2463    }
2464
2465    fn function(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2466        let name = lexer.slice(start, end)?;
2467        self.balanced
2468            .push(BalancedItem::new(name, start, end), self.mode_data.as_mut());
2469
2470        if let Scope::InAtImport(ref mut import_data) = self.scope {
2471            if name.eq_ignore_ascii_case("supports(") {
2472                import_data.supports = ImportDataSupports::InSupports;
2473            }
2474        }
2475
2476        let Some(mode_data) = &self.mode_data else {
2477            return Some(());
2478        };
2479        if mode_data.is_current_local_mode() && name.starts_with("--") && end > start + 1 {
2480            self.handle_dependency
2481                .handle_dependency(Dependency::LocalFunction {
2482                    name: lexer.slice(start + 2, end - 1)?,
2483                    range: Range::new(start, end - 1),
2484                });
2485        }
2486        if mode_data.is_current_local_mode() && name.eq_ignore_ascii_case("var(") {
2487            self.lex_local_var(lexer)?;
2488        }
2489        Some(())
2490    }
2491
2492    fn left_parenthesis(&mut self, _: &mut Lexer, start: Pos, end: Pos) -> Option<()> {
2493        self.balanced
2494            .push(BalancedItem::new_other(start, end), self.mode_data.as_mut());
2495        Some(())
2496    }
2497
2498    fn right_parenthesis(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2499        let Some(last) = self.balanced.pop(self.mode_data.as_mut()) else {
2500            return Some(());
2501        };
2502        if let Some(mode_data) = &mut self.mode_data {
2503            let mut is_function = last.kind.is_mode_function();
2504            if last.kind.is_mode_class() {
2505                self.balanced.pop_mode_pseudo_class(mode_data);
2506                let popped = self.balanced.pop_without_moda_data().unwrap();
2507                debug_assert!(!matches!(
2508                    popped.kind,
2509                    BalancedItemKind::GlobalClass | BalancedItemKind::LocalClass
2510                ));
2511                is_function = popped.kind.is_mode_function();
2512            }
2513            if is_function {
2514                let distance = self.back_white_space_and_comments_distance(lexer, start)?;
2515                let start = start - distance;
2516                let maybe_left_parenthesis_start = start - 1;
2517                if lexer.slice(start - 1, start)? == "(" {
2518                    self.handle_warning.handle_warning(Warning {
2519                        range: Range::new(maybe_left_parenthesis_start, end),
2520                        kind: WarningKind::Unexpected {
2521                            message: "':global()' or ':local()' can't be empty",
2522                        },
2523                    });
2524                }
2525                self.handle_dependency
2526                    .handle_dependency(Dependency::Replace {
2527                        content: "",
2528                        range: Range::new(start, end),
2529                    });
2530            }
2531        }
2532        if let Scope::InAtImport(ref mut import_data) = self.scope {
2533            let not_in_supports = !import_data.in_supports();
2534            if matches!(last.kind, BalancedItemKind::Url) && not_in_supports {
2535                import_data.url_range = Some(Range::new(last.range.start, end));
2536            } else if matches!(last.kind, BalancedItemKind::Layer) && not_in_supports {
2537                import_data.layer = ImportDataLayer::EndLayer {
2538                    value: lexer.slice(last.range.end, end - 1)?,
2539                    range: Range::new(last.range.start, end),
2540                };
2541            } else if matches!(last.kind, BalancedItemKind::Supports) {
2542                import_data.supports = ImportDataSupports::EndSupports {
2543                    value: lexer.slice(last.range.end, end - 1)?,
2544                    range: Range::new(last.range.start, end),
2545                }
2546            }
2547        }
2548        Some(())
2549    }
2550
2551    fn ident(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2552        match self.scope {
2553            Scope::InBlock => {
2554                let Some(mode_data) = &mut self.mode_data else {
2555                    return Some(());
2556                };
2557
2558                let ident = lexer.slice(start, end)?;
2559                if mode_data.is_current_local_mode()
2560                    && self
2561                        .balanced
2562                        .last()
2563                        .map(|last| last.kind.is_mode_function())
2564                        .unwrap_or(false)
2565                    && ident.starts_with("--")
2566                    && lexer.cur()? == C_RIGHT_PARENTHESIS
2567                {
2568                    self.handle_dependency
2569                        .handle_dependency(Dependency::LocalFunction {
2570                            name: lexer.slice(start + 2, end)?,
2571                            range: Range::new(start, end),
2572                        });
2573                    return Some(());
2574                }
2575                if mode_data.is_property_local_mode() {
2576                    if let Some(animation) = &mut self.in_animation_property {
2577                        // Not inside functions
2578                        if self.balanced.is_empty() {
2579                            animation.set_rename(lexer.slice(start, end)?, Range::new(start, end));
2580                        }
2581                        return Some(());
2582                    }
2583
2584                    if let Some(list_style) = &mut self.in_list_style_property {
2585                        // Not inside functions
2586                        if self.balanced.is_empty() {
2587                            list_style.set_rename(lexer.slice(start, end)?, Range::new(start, end));
2588                        }
2589                        return Some(());
2590                    }
2591
2592                    if let Some(font_palette) = &mut self.in_font_palette_property {
2593                        // Not inside functions or inside palette-mix()
2594                        if self.balanced.is_empty()
2595                            || matches!(self.balanced.last(), Some(last) if matches!(last.kind, BalancedItemKind::PaletteMix))
2596                        {
2597                            font_palette
2598                                .set_rename(lexer.slice(start, end)?, Range::new(start, end));
2599                        }
2600                        return Some(());
2601                    }
2602
2603                    if let Some(container) = &mut self.in_container_property {
2604                        if self.balanced.is_empty() {
2605                            container.set_rename(ident, Range::new(start, end));
2606                            if let Some(range) = container.take_rename(self.balanced.len()) {
2607                                self.handle_dependency.handle_dependency(
2608                                    Dependency::LocalContainerDecl {
2609                                        name: lexer.slice(range.start, range.end)?,
2610                                        range,
2611                                    },
2612                                );
2613                            }
2614                        }
2615                        return Some(());
2616                    }
2617
2618                    if self.in_grid_property.is_some() {
2619                        if self.balanced.is_empty() && !is_reserved_grid_ident(ident) {
2620                            self.handle_dependency
2621                                .handle_dependency(Dependency::LocalGrid {
2622                                    name: ident,
2623                                    range: Range::new(start, end),
2624                                });
2625                        }
2626                        return Some(());
2627                    }
2628
2629                    if let Some(name) = ident.strip_prefix("--") {
2630                        return self.lex_local_var_decl(lexer, name, start, end);
2631                    }
2632
2633                    if ident.eq_ignore_ascii_case("animation")
2634                        || ident.eq_ignore_ascii_case("animation-name")
2635                        || with_vendor_prefixed_eq(ident, "animation", false)
2636                        || with_vendor_prefixed_eq(ident, "animation-name", false)
2637                    {
2638                        self.enter_animation_property();
2639                        return Some(());
2640                    }
2641
2642                    if ident.eq_ignore_ascii_case("list-style")
2643                        || ident.eq_ignore_ascii_case("list-style-type")
2644                    {
2645                        self.enter_list_style_property();
2646                        return Some(());
2647                    }
2648
2649                    if ident.eq_ignore_ascii_case("font-palette") {
2650                        self.enter_font_palette_property();
2651                        return Some(());
2652                    }
2653
2654                    if ident.eq_ignore_ascii_case("container")
2655                        || ident.eq_ignore_ascii_case("container-name")
2656                    {
2657                        self.enter_container_property();
2658                        return Some(());
2659                    }
2660
2661                    if let Some(kind) = grid_property_kind(ident) {
2662                        self.enter_grid_property(kind);
2663                        return Some(());
2664                    }
2665                }
2666
2667                if ident.eq_ignore_ascii_case("composes")
2668                    || ident.eq_ignore_ascii_case("compose-with")
2669                {
2670                    if self.block_nesting_level != 1 {
2671                        self.handle_warning.handle_warning(Warning {
2672                            range: Range::new(start, end),
2673                            kind: WarningKind::UnexpectedComposition {
2674                                message: "not allowed in nested rule",
2675                            },
2676                        });
2677                        return Some(());
2678                    }
2679                    let Some(local_classes) = mode_data
2680                        .composes_local_classes
2681                        .get_valid_local_classes(lexer)
2682                    else {
2683                        self.handle_warning.handle_warning(Warning {
2684                            range: Range::new(start, end),
2685                            kind: WarningKind::UnexpectedComposition {
2686                                message: "only allowed when selector is single :local class",
2687                            },
2688                        });
2689                        return Some(());
2690                    };
2691                    return self.lex_composes(lexer, local_classes, start);
2692                }
2693            }
2694            Scope::InAtImport(ref mut import_data) => {
2695                if lexer.slice(start, end)?.eq_ignore_ascii_case("layer") {
2696                    import_data.layer = ImportDataLayer::EndLayer {
2697                        value: "",
2698                        range: Range::new(start, end),
2699                    }
2700                }
2701            }
2702            Scope::TopLevel => {
2703                let Some(mode_data) = &mut self.mode_data else {
2704                    return Some(());
2705                };
2706                mode_data.composes_local_classes.invalidate();
2707            }
2708            _ => {}
2709        }
2710        Some(())
2711    }
2712
2713    fn class(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2714        let Some(mode_data) = &mut self.mode_data else {
2715            return Some(());
2716        };
2717        let name = lexer.slice(start, end)?;
2718        if name == "." {
2719            self.handle_warning.handle_warning(Warning {
2720                range: Range::new(start, end),
2721                kind: WarningKind::Unexpected {
2722                    message: "Invalid class selector syntax",
2723                },
2724            });
2725            return Some(());
2726        }
2727        if mode_data.is_current_local_mode() {
2728            self.handle_dependency
2729                .handle_dependency(Dependency::LocalClass {
2730                    name,
2731                    range: Range::new(start, end),
2732                    explicit: mode_data.is_mode_explicit(),
2733                });
2734            if self.block_nesting_level == 0 {
2735                mode_data
2736                    .composes_local_classes
2737                    .find_local_class(start + 1, end);
2738            }
2739
2740            if mode_data.is_pure_mode() {
2741                mode_data.pure_global = None;
2742            }
2743        }
2744        Some(())
2745    }
2746
2747    fn id(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2748        let Some(mode_data) = &mut self.mode_data else {
2749            return Some(());
2750        };
2751        let name = lexer.slice(start, end)?;
2752        if name == "#" {
2753            self.handle_warning.handle_warning(Warning {
2754                range: Range::new(start, end),
2755                kind: WarningKind::Unexpected {
2756                    message: "Invalid id selector syntax",
2757                },
2758            });
2759            return Some(());
2760        }
2761        if mode_data.is_current_local_mode() {
2762            self.handle_dependency
2763                .handle_dependency(Dependency::LocalId {
2764                    name,
2765                    range: Range::new(start, end),
2766                    explicit: mode_data.is_mode_explicit(),
2767                });
2768
2769            if self.block_nesting_level == 0 {
2770                mode_data.composes_local_classes.invalidate();
2771            }
2772
2773            if mode_data.is_pure_mode() {
2774                mode_data.pure_global = None;
2775            }
2776        }
2777        Some(())
2778    }
2779
2780    fn left_curly_bracket(&mut self, lexer: &mut Lexer, start: Pos, _: Pos) -> Option<()> {
2781        match self.scope {
2782            Scope::TopLevel => {
2783                self.allow_import_at_rule = false;
2784                self.scope = Scope::InBlock;
2785                if self.mode_data.is_none()
2786                    || matches!(&self.mode_data, Some(mode_data) if !matches!(mode_data.composes_local_classes.is_single, SingleLocalClass::AtKeyword))
2787                {
2788                    self.block_nesting_level = 1;
2789                }
2790            }
2791            Scope::InBlock => {
2792                let is_at_rule_block = matches!(
2793                    &self.mode_data,
2794                    Some(mode_data)
2795                        if matches!(mode_data.composes_local_classes.is_single, SingleLocalClass::AtKeyword)
2796                );
2797                if !is_at_rule_block {
2798                    self.block_nesting_level += 1;
2799                }
2800            }
2801            _ => return Some(()),
2802        }
2803        self.is_next_rule_prelude = self.is_next_nested_syntax(lexer)?;
2804        if let Some(mode_data) = &mut self.mode_data {
2805            if mode_data.is_pure_mode() && mode_data.pure_global.is_some() {
2806                let pure_global_start = mode_data.pure_global.unwrap();
2807                self.handle_warning.handle_warning(Warning {
2808                    range: Range::new(pure_global_start, start),
2809                    kind: WarningKind::NotPure {
2810                        message: "Selector is not pure (pure selectors must contain at least one local class or id)",
2811                    }
2812                });
2813            }
2814
2815            if mode_data.resulting_global.is_some() && mode_data.is_current_local_mode() {
2816                let resulting_global_start = mode_data.resulting_global.unwrap();
2817                self.handle_warning.handle_warning(Warning {
2818                    range: Range::new(resulting_global_start, start),
2819                    kind: WarningKind::InconsistentModeResult,
2820                });
2821            }
2822            mode_data.resulting_global = None;
2823
2824            self.balanced.update_property_mode(mode_data);
2825            self.balanced.pop_mode_pseudo_class(mode_data);
2826            if self.is_next_rule_prelude && self.block_nesting_level == 0 {
2827                let mode_data = self.mode_data.as_mut().unwrap();
2828                mode_data.composes_local_classes.reset_to_initial();
2829            }
2830
2831            debug_assert!(
2832                self.balanced.is_empty(),
2833                "balanced should be empty when end of selector"
2834            );
2835        }
2836        self.exit_container_property();
2837        self.exit_grid_property();
2838        Some(())
2839    }
2840
2841    fn right_curly_bracket(&mut self, lexer: &mut Lexer<'s>, _: Pos, end: Pos) -> Option<()> {
2842        if matches!(self.scope, Scope::InBlock) {
2843            if let Some(mode_data) = &mut self.mode_data {
2844                mode_data.pure_global = Some(end);
2845
2846                if mode_data.is_property_local_mode() {
2847                    if self.in_animation_property.is_some() {
2848                        self.handle_local_keyframes_dependency(lexer)?;
2849                        self.exit_animation_property();
2850                    }
2851                    if self.in_list_style_property.is_some() {
2852                        self.handle_local_counter_style_dependency(lexer)?;
2853                        self.exit_list_style_property();
2854                    }
2855                    if self.in_font_palette_property.is_some() {
2856                        self.handle_local_font_palette_dependency(lexer)?;
2857                        self.exit_font_palette_property();
2858                    }
2859                    if self.in_container_property.is_some() {
2860                        self.exit_container_property();
2861                    }
2862                    if self.in_grid_property.is_some() {
2863                        self.exit_grid_property();
2864                    }
2865                }
2866            }
2867
2868            if self.block_nesting_level > 0 {
2869                self.block_nesting_level -= 1;
2870            }
2871            if self.block_nesting_level == 0 {
2872                self.scope = Scope::TopLevel;
2873                self.is_next_rule_prelude = true;
2874                if let Some(mode_data) = &mut self.mode_data {
2875                    mode_data.composes_local_classes.reset_to_initial();
2876                }
2877            } else {
2878                self.is_next_rule_prelude = self.is_next_nested_syntax(lexer)?;
2879            }
2880        }
2881        Some(())
2882    }
2883
2884    fn pseudo_function(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2885        let name = lexer.slice(start, end)?;
2886        if let Some(mode_data) = &mut self.mode_data {
2887            if name.eq_ignore_ascii_case(":import(") {
2888                self.lex_icss_import(lexer);
2889                self.handle_dependency
2890                    .handle_dependency(Dependency::Replace {
2891                        content: "",
2892                        range: Range::new(start, lexer.cur_pos()?),
2893                    });
2894                return Some(());
2895            }
2896            if name.eq_ignore_ascii_case(":global(") || name.eq_ignore_ascii_case(":local(") {
2897                if mode_data.is_inside_mode_function() {
2898                    self.handle_warning.handle_warning(Warning {
2899                        range: Range::new(start, end),
2900                        kind: WarningKind::ExpectedNotInside {
2901                            pseudo: lexer.slice(start, end)?,
2902                        },
2903                    });
2904                }
2905
2906                lexer.consume_white_space_and_comments()?;
2907                self.handle_dependency
2908                    .handle_dependency(Dependency::Replace {
2909                        content: "",
2910                        range: Range::new(start, lexer.cur_pos()?),
2911                    });
2912            } else if self.block_nesting_level == 0 {
2913                mode_data.composes_local_classes.invalidate();
2914            }
2915        }
2916        self.balanced
2917            .push(BalancedItem::new(name, start, end), self.mode_data.as_mut());
2918        Some(())
2919    }
2920
2921    fn pseudo_class(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2922        let Some(mode_data) = &mut self.mode_data else {
2923            return Some(());
2924        };
2925        let name = lexer.slice(start, end)?;
2926        if name.eq_ignore_ascii_case(":global") || name.eq_ignore_ascii_case(":local") {
2927            if mode_data.is_inside_mode_function() {
2928                self.handle_warning.handle_warning(Warning {
2929                    range: Range::new(start, end),
2930                    kind: WarningKind::ExpectedNotInside {
2931                        pseudo: lexer.slice(start, end)?,
2932                    },
2933                });
2934            }
2935
2936            let should_have_after_white_space = self.should_have_after_white_space(lexer, start);
2937            let has_after_white_space = self.has_after_white_space(lexer)?;
2938            let c = lexer.cur()?;
2939            if c != C_RIGHT_PARENTHESIS && c != C_LEFT_CURLY && c != C_COMMA {
2940                if should_have_after_white_space && !has_after_white_space {
2941                    self.handle_warning.handle_warning(Warning {
2942                        range: Range::new(start, end),
2943                        kind: WarningKind::MissingWhitespace {
2944                            surrounding: "trailing",
2945                        },
2946                    });
2947                }
2948                if !should_have_after_white_space && has_after_white_space {
2949                    self.handle_warning.handle_warning(Warning {
2950                        range: Range::new(start, end),
2951                        kind: WarningKind::MissingWhitespace {
2952                            surrounding: "leading",
2953                        },
2954                    });
2955                }
2956            }
2957
2958            self.balanced
2959                .push(BalancedItem::new(name, start, end), self.mode_data.as_mut());
2960            let end2 = lexer.cur_pos()?;
2961            self.handle_dependency
2962                .handle_dependency(Dependency::Replace {
2963                    content: "",
2964                    range: Range::new(start, end2),
2965                });
2966            return Some(());
2967        }
2968        if matches!(self.scope, Scope::TopLevel) && name.eq_ignore_ascii_case(":export") {
2969            self.lex_icss_export(lexer)?;
2970            self.handle_dependency
2971                .handle_dependency(Dependency::Replace {
2972                    content: "",
2973                    range: Range::new(start, lexer.cur_pos()?),
2974                });
2975            return Some(());
2976        }
2977
2978        if self.block_nesting_level == 0 {
2979            mode_data.composes_local_classes.invalidate();
2980        }
2981        Some(())
2982    }
2983
2984    fn comma(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2985        let Some(mode_data) = &mut self.mode_data else {
2986            return Some(());
2987        };
2988
2989        if mode_data.is_pure_mode() && mode_data.pure_global.is_some() {
2990            let pure_global_start = mode_data.pure_global.unwrap();
2991            self.handle_warning.handle_warning(Warning {
2992                range: Range::new(pure_global_start, start),
2993                kind: WarningKind::NotPure {
2994                    message: "Selector is not pure (pure selectors must contain at least one local class or id)",
2995                }
2996            });
2997        }
2998        mode_data.pure_global = Some(end);
2999
3000        if self.block_nesting_level == 0 {
3001            mode_data.composes_local_classes.find_comma(lexer)?;
3002        }
3003
3004        if mode_data.resulting_global.is_some() && mode_data.is_current_local_mode() {
3005            let resulting_global_start = mode_data.resulting_global.unwrap();
3006            self.handle_warning.handle_warning(Warning {
3007                range: Range::new(resulting_global_start, start),
3008                kind: WarningKind::InconsistentModeResult,
3009            });
3010        }
3011
3012        if self.balanced.len() == 1 {
3013            let last = self.balanced.last().unwrap();
3014            let is_local_class = matches!(last.kind, BalancedItemKind::LocalClass);
3015            let is_global_class = matches!(last.kind, BalancedItemKind::GlobalClass);
3016            if is_local_class || is_global_class {
3017                self.balanced.pop_mode_pseudo_class(mode_data);
3018                if mode_data.resulting_global.is_none() && is_global_class {
3019                    mode_data.resulting_global = Some(start);
3020                }
3021            }
3022        }
3023
3024        if matches!(self.scope, Scope::InBlock)
3025            && mode_data.is_property_local_mode()
3026            && self.in_animation_property.is_some()
3027        {
3028            self.handle_local_keyframes_dependency(lexer)?;
3029        }
3030
3031        Some(())
3032    }
3033}