Skip to main content

css_module_lexer/
dependencies.rs

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