css_module_lexer/
dependencies.rs

1use std::fmt::Display;
2
3use smallvec::smallvec;
4use smallvec::SmallVec;
5
6use crate::lexer::is_white_space;
7use crate::lexer::start_ident_sequence;
8use crate::lexer::Visitor;
9use crate::lexer::C_ASTERISK;
10use crate::lexer::C_COLON;
11use crate::lexer::C_COMMA;
12use crate::lexer::C_HYPHEN_MINUS;
13use crate::lexer::C_LEFT_CURLY;
14use crate::lexer::C_LEFT_PARENTHESIS;
15use crate::lexer::C_RIGHT_CURLY;
16use crate::lexer::C_RIGHT_PARENTHESIS;
17use crate::lexer::C_SEMICOLON;
18use crate::lexer::C_SOLIDUS;
19use crate::HandleDependency;
20use crate::HandleWarning;
21use crate::Lexer;
22use crate::Pos;
23
24#[derive(Debug)]
25enum Scope<'s> {
26    TopLevel,
27    InBlock,
28    InAtImport(ImportData<'s>),
29    AtImportInvalid,
30    AtNamespaceInvalid,
31}
32
33#[derive(Debug)]
34struct ImportData<'s> {
35    start: Pos,
36    url: Option<&'s str>,
37    url_range: Option<Range>,
38    supports: ImportDataSupports<'s>,
39    layer: ImportDataLayer<'s>,
40}
41
42impl ImportData<'_> {
43    pub fn new(start: Pos) -> Self {
44        Self {
45            start,
46            url: None,
47            url_range: None,
48            supports: ImportDataSupports::None,
49            layer: ImportDataLayer::None,
50        }
51    }
52
53    pub fn in_supports(&self) -> bool {
54        matches!(self.supports, ImportDataSupports::InSupports { .. })
55    }
56
57    pub fn layer_range(&self) -> Option<&Range> {
58        let ImportDataLayer::EndLayer { range, .. } = &self.layer else {
59            return None;
60        };
61        Some(range)
62    }
63
64    pub fn supports_range(&self) -> Option<&Range> {
65        let ImportDataSupports::EndSupports { range, .. } = &self.supports else {
66            return None;
67        };
68        Some(range)
69    }
70}
71
72#[derive(Debug)]
73enum ImportDataSupports<'s> {
74    None,
75    InSupports,
76    EndSupports { value: &'s str, range: Range },
77}
78
79#[derive(Debug)]
80enum ImportDataLayer<'s> {
81    None,
82    EndLayer { value: &'s str, range: Range },
83}
84
85#[derive(Debug, Default)]
86struct BalancedStack(SmallVec<[BalancedItem; 3]>);
87
88impl BalancedStack {
89    pub fn len(&self) -> usize {
90        self.0.len()
91    }
92
93    pub fn last(&self) -> Option<&BalancedItem> {
94        self.0.last()
95    }
96
97    pub fn is_empty(&self) -> bool {
98        self.0.is_empty()
99    }
100
101    pub fn push(&mut self, item: BalancedItem, mode_data: Option<&mut ModeData>) {
102        if let Some(mode_data) = mode_data {
103            if item.kind.is_mode_local() {
104                mode_data.set_current_mode(Mode::Local);
105            } else if item.kind.is_mode_global() {
106                mode_data.set_current_mode(Mode::Global);
107            }
108
109            if item.kind.is_mode_function() {
110                mode_data.inside_mode_function += 1;
111            } else if item.kind.is_mode_class() {
112                mode_data.inside_mode_class += 1;
113            }
114        }
115        self.0.push(item);
116    }
117
118    pub fn pop(&mut self, mode_data: Option<&mut ModeData>) -> Option<BalancedItem> {
119        let item = self.0.pop()?;
120        if let Some(mode_data) = mode_data {
121            if item.kind.is_mode_function() {
122                mode_data.inside_mode_function -= 1;
123            } else if item.kind.is_mode_class() {
124                mode_data.inside_mode_class -= 1;
125            }
126            self.update_current_mode(mode_data);
127        }
128        Some(item)
129    }
130
131    pub fn pop_without_moda_data(&mut self) -> Option<BalancedItem> {
132        self.0.pop()
133    }
134
135    pub fn pop_mode_pseudo_class(&mut self, mode_data: &mut ModeData) {
136        loop {
137            if let Some(last) = self.0.last() {
138                if matches!(
139                    last.kind,
140                    BalancedItemKind::LocalClass | BalancedItemKind::GlobalClass
141                ) {
142                    mode_data.inside_mode_class -= 1;
143                    self.0.pop();
144                    continue;
145                }
146            }
147            break;
148        }
149        self.update_current_mode(mode_data);
150    }
151
152    pub fn update_current_mode(&self, mode_data: &mut ModeData) {
153        mode_data.set_current_mode(self.topmost_mode(mode_data));
154    }
155
156    pub fn update_property_mode(&self, mode_data: &mut ModeData) {
157        mode_data.set_property_mode(self.topmost_mode(mode_data));
158    }
159
160    fn topmost_mode(&self, mode_data: &ModeData) -> Mode {
161        let mut iter = self.0.iter();
162        loop {
163            if let Some(last) = iter.next_back() {
164                if matches!(
165                    last.kind,
166                    BalancedItemKind::LocalFn | BalancedItemKind::LocalClass
167                ) {
168                    return Mode::Local;
169                } else if matches!(
170                    last.kind,
171                    BalancedItemKind::GlobalFn | BalancedItemKind::GlobalClass
172                ) {
173                    return Mode::Global;
174                }
175            } else {
176                return mode_data.default_mode();
177            }
178        }
179    }
180}
181
182#[derive(Debug)]
183struct BalancedItem {
184    kind: BalancedItemKind,
185    range: Range,
186}
187
188impl BalancedItem {
189    pub fn new(name: &str, start: Pos, end: Pos) -> Self {
190        Self {
191            kind: BalancedItemKind::new(name),
192            range: Range::new(start, end),
193        }
194    }
195
196    pub fn new_other(start: Pos, end: Pos) -> Self {
197        Self {
198            kind: BalancedItemKind::Other,
199            range: Range::new(start, end),
200        }
201    }
202}
203
204#[derive(Debug)]
205enum BalancedItemKind {
206    Url,
207    ImageSet,
208    Layer,
209    Supports,
210    PaletteMix,
211    LocalFn,
212    GlobalFn,
213    LocalClass,
214    GlobalClass,
215    Other,
216}
217
218impl BalancedItemKind {
219    pub fn new(name: &str) -> Self {
220        match name {
221            "url(" => Self::Url,
222            "image-set(" => Self::ImageSet,
223            _ if with_vendor_prefixed_eq(name, "image-set(", false) => Self::ImageSet,
224            "layer(" => Self::Layer,
225            "supports(" => Self::Supports,
226            "palette-mix(" => Self::PaletteMix,
227            ":local(" => Self::LocalFn,
228            ":global(" => Self::GlobalFn,
229            ":local" => Self::LocalClass,
230            ":global" => Self::GlobalClass,
231            _ => Self::Other,
232        }
233    }
234
235    pub fn is_mode_local(&self) -> bool {
236        matches!(self, Self::LocalFn | Self::LocalClass)
237    }
238
239    pub fn is_mode_global(&self) -> bool {
240        matches!(self, Self::GlobalFn | Self::GlobalClass)
241    }
242
243    pub fn is_mode_function(&self) -> bool {
244        matches!(self, Self::LocalFn | Self::GlobalFn)
245    }
246
247    pub fn is_mode_class(&self) -> bool {
248        matches!(self, Self::LocalClass | Self::GlobalClass)
249    }
250}
251
252fn with_vendor_prefixed_eq(left: &str, right: &str, at_rule: bool) -> bool {
253    let left = if at_rule {
254        if let Some(left) = left.strip_prefix('@') {
255            left
256        } else {
257            return false;
258        }
259    } else {
260        left
261    };
262    matches!(left.strip_prefix("-webkit-"), Some(left) if left.eq_ignore_ascii_case(right))
263        || matches!(left.strip_prefix("-moz-"), Some(left) if left.eq_ignore_ascii_case(right))
264        || matches!(left.strip_prefix("-ms-"), Some(left) if left.eq_ignore_ascii_case(right))
265        || matches!(left.strip_prefix("-o-"), Some(left) if left.eq_ignore_ascii_case(right))
266}
267
268#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
269pub struct Range {
270    pub start: Pos,
271    pub end: Pos,
272}
273
274impl Range {
275    pub fn new(start: Pos, end: Pos) -> Self {
276        Self { start, end }
277    }
278}
279
280#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
281pub enum Mode {
282    Local,
283    Global,
284    Pure,
285    Css,
286}
287
288#[derive(Debug)]
289pub struct ModeData<'s> {
290    default: Mode,
291    current: Mode,
292    property: Mode,
293    resulting_global: Option<Pos>,
294    pure_global: Option<Pos>,
295    composes_local_classes: ComposesLocalClasses<'s>,
296    inside_mode_function: u32,
297    inside_mode_class: u32,
298}
299
300impl ModeData<'_> {
301    pub fn new(default: Mode) -> Self {
302        Self {
303            default,
304            current: default,
305            property: default,
306            resulting_global: None,
307            pure_global: Some(0),
308            composes_local_classes: ComposesLocalClasses::default(),
309            inside_mode_function: 0,
310            inside_mode_class: 0,
311        }
312    }
313
314    pub fn is_pure_mode(&self) -> bool {
315        matches!(self.default, Mode::Pure)
316    }
317
318    pub fn is_current_local_mode(&self) -> bool {
319        match self.current {
320            Mode::Local | Mode::Pure => true,
321            Mode::Global => false,
322            Mode::Css => unreachable!(),
323        }
324    }
325
326    pub fn is_property_local_mode(&self) -> bool {
327        match self.property {
328            Mode::Local | Mode::Pure => true,
329            Mode::Global => false,
330            Mode::Css => unreachable!(),
331        }
332    }
333
334    pub fn default_mode(&self) -> Mode {
335        self.default
336    }
337
338    pub fn set_current_mode(&mut self, mode: Mode) {
339        self.current = mode;
340    }
341
342    pub fn set_property_mode(&mut self, mode: Mode) {
343        self.property = mode;
344    }
345
346    pub fn is_inside_mode_function(&self) -> bool {
347        self.inside_mode_function > 0
348    }
349
350    pub fn is_inside_mode_class(&self) -> bool {
351        self.inside_mode_class > 0
352    }
353
354    pub fn is_mode_explicit(&self) -> bool {
355        self.is_inside_mode_function() || self.is_inside_mode_class()
356    }
357}
358
359#[derive(Debug, Default, Clone)]
360struct ComposesLocalClasses<'s> {
361    is_single: SingleLocalClass,
362    local_classes: SmallVec<[&'s str; 2]>,
363}
364
365impl<'s> ComposesLocalClasses<'s> {
366    pub fn get_valid_local_classes(&mut self, lexer: &Lexer<'s>) -> Option<SmallVec<[&'s str; 2]>> {
367        if let SingleLocalClass::Single(range) = &self.is_single {
368            let mut local_classes = self.local_classes.clone();
369            local_classes.push(lexer.slice(range.start, range.end)?);
370            Some(local_classes)
371        } else {
372            self.reset_to_initial();
373            None
374        }
375    }
376
377    pub fn invalidate(&mut self) {
378        if !matches!(self.is_single, SingleLocalClass::AtKeyword) {
379            self.is_single = SingleLocalClass::Invalid;
380            self.local_classes.clear();
381        }
382    }
383
384    pub fn find_local_class(&mut self, start: Pos, end: Pos) {
385        match self.is_single {
386            SingleLocalClass::Initial => {
387                self.is_single = SingleLocalClass::Single(Range::new(start, end))
388            }
389            SingleLocalClass::Single(_) => {
390                self.is_single = SingleLocalClass::Invalid;
391                self.local_classes.clear();
392            }
393            _ => {}
394        };
395    }
396
397    pub fn find_at_keyword(&mut self) {
398        self.is_single = SingleLocalClass::AtKeyword;
399        self.local_classes.clear();
400    }
401
402    pub fn reset_to_initial(&mut self) {
403        self.is_single = SingleLocalClass::Initial;
404        self.local_classes.clear();
405    }
406
407    pub fn find_comma(&mut self, lexer: &Lexer<'s>) -> Option<()> {
408        if let SingleLocalClass::Single(range) = &self.is_single {
409            self.local_classes
410                .push(lexer.slice(range.start, range.end)?);
411            self.is_single = SingleLocalClass::Initial
412        } else {
413            self.is_single = SingleLocalClass::Invalid;
414        }
415        Some(())
416    }
417}
418
419#[derive(Debug, Default, Clone)]
420enum SingleLocalClass {
421    #[default]
422    Initial,
423    Single(Range),
424    AtKeyword,
425    Invalid,
426}
427
428#[derive(Debug)]
429struct InProperty<T: ReservedValues> {
430    reserved: T,
431    rename: Option<Range>,
432    balanced_len: usize,
433}
434
435impl<T: ReservedValues> InProperty<T> {
436    pub fn new(reserved: T, balanced_len: usize) -> Self {
437        Self {
438            reserved,
439            rename: None,
440            balanced_len,
441        }
442    }
443
444    fn check_reserved(&mut self, ident: &str) -> bool {
445        self.reserved.check(ident)
446    }
447
448    pub fn reset_reserved(&mut self) {
449        self.reserved.reset();
450    }
451
452    pub fn set_rename(&mut self, ident: &str, range: Range) {
453        if self.check_reserved(ident) {
454            self.rename = Some(range);
455        }
456    }
457
458    pub fn take_rename(&mut self, balanced_len: usize) -> Option<Range> {
459        // Don't rename when we in functions
460        if balanced_len != self.balanced_len {
461            return None;
462        }
463        std::mem::take(&mut self.rename)
464    }
465}
466
467trait ReservedValues {
468    fn check(&mut self, ident: &str) -> bool;
469    fn reset(&mut self);
470}
471
472#[derive(Debug, Default)]
473struct AnimationReserved {
474    bits: u32,
475}
476
477impl ReservedValues for AnimationReserved {
478    fn check(&mut self, ident: &str) -> bool {
479        match ident {
480            "normal" => self.check_and_update(Self::NORMAL),
481            "reverse" => self.check_and_update(Self::REVERSE),
482            "alternate" => self.check_and_update(Self::ALTERNATE),
483            "alternate-reverse" => self.check_and_update(Self::ALTERNATE_REVERSE),
484            "forwards" => self.check_and_update(Self::FORWARDS),
485            "backwards" => self.check_and_update(Self::BACKWARDS),
486            "both" => self.check_and_update(Self::BOTH),
487            "infinite" => self.check_and_update(Self::INFINITE),
488            "paused" => self.check_and_update(Self::PAUSED),
489            "running" => self.check_and_update(Self::RUNNING),
490            "ease" => self.check_and_update(Self::EASE),
491            "ease-in" => self.check_and_update(Self::EASE_IN),
492            "ease-out" => self.check_and_update(Self::EASE_OUT),
493            "ease-in-out" => self.check_and_update(Self::EASE_IN_OUT),
494            "linear" => self.check_and_update(Self::LINEAR),
495            "step-end" => self.check_and_update(Self::STEP_END),
496            "step-start" => self.check_and_update(Self::STEP_START),
497            // keywords values
498            "none" | 
499            // global values
500            "initial" | "inherit" | "unset" | "revert" | "revert-layer" => false,
501            _ => true,
502        }
503    }
504
505    fn reset(&mut self) {
506        self.bits = 0;
507    }
508}
509
510impl AnimationReserved {
511    const NORMAL: u32 = 1 << 0;
512    const REVERSE: u32 = 1 << 1;
513    const ALTERNATE: u32 = 1 << 2;
514    const ALTERNATE_REVERSE: u32 = 1 << 3;
515    const FORWARDS: u32 = 1 << 4;
516    const BACKWARDS: u32 = 1 << 5;
517    const BOTH: u32 = 1 << 6;
518    const INFINITE: u32 = 1 << 7;
519    const PAUSED: u32 = 1 << 8;
520    const RUNNING: u32 = 1 << 9;
521    const EASE: u32 = 1 << 10;
522    const EASE_IN: u32 = 1 << 11;
523    const EASE_OUT: u32 = 1 << 12;
524    const EASE_IN_OUT: u32 = 1 << 13;
525    const LINEAR: u32 = 1 << 14;
526    const STEP_END: u32 = 1 << 15;
527    const STEP_START: u32 = 1 << 16;
528
529    fn check_and_update(&mut self, bit: u32) -> bool {
530        if self.bits & bit == bit {
531            return true;
532        }
533        self.bits |= bit;
534        false
535    }
536}
537
538#[derive(Debug, Default)]
539struct ListStyleReserved;
540
541impl ReservedValues for ListStyleReserved {
542    fn check(&mut self, ident: &str) -> bool {
543        match ident {
544            // https://www.w3.org/TR/css-counter-styles-3/#simple-numeric
545            "decimal"
546            | "decimal-leading-zero"
547            | "arabic-indic"
548            | "armenian"
549            | "upper-armenian"
550            | "lower-armenian"
551            | "bengali"
552            | "cambodian"
553            | "khmer"
554            | "cjk-decimal"
555            | "devanagari"
556            | "georgian"
557            | "gujarati"
558            | "gurmukhi"
559            | "hebrew"
560            | "kannada"
561            | "lao"
562            | "malayalam"
563            | "mongolian"
564            | "myanmar"
565            | "oriya"
566            | "persian"
567            | "lower-roman"
568            | "upper-roman"
569            | "tamil"
570            | "telugu"
571            | "thai"
572            | "tibetan"
573            // https://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic
574            | "lower-alpha"
575            | "lower-latin"
576            | "upper-alpha"
577            | "upper-latin"
578            | "lower-greek"
579            | "hiragana"
580            | "hiragana-iroha"
581            | "katakana"
582            | "katakana-iroha"
583            // https://www.w3.org/TR/css-counter-styles-3/#simple-symbolic
584            | "disc"
585            | "circle"
586            | "square"
587            | "disclosure-open"
588            | "disclosure-closed"
589            // https://www.w3.org/TR/css-counter-styles-3/#simple-fixed
590            | "cjk-earthly-branch"
591            | "cjk-heavenly-stem"
592            // https://www.w3.org/TR/css-counter-styles-3/#complex-cjk
593            | "japanese-informal"
594            | "japanese-formal"
595            | "korean-hangul-formal"
596            | "korean-hanja-informal"
597            | "korean-hanja-formal"
598            | "simp-chinese-informal"
599            | "simp-chinese-formal"
600            | "trad-chinese-informal"
601            | "trad-chinese-formal"
602            | "ethiopic-numeric"
603            // keywords values
604            | "none"
605            // global values
606            | "initial"
607            | "inherit"
608            | "unset"
609            | "revert"
610            | "revert-layer" => false,
611            _ => true,
612        }
613    }
614
615    fn reset(&mut self) {}
616}
617
618#[derive(Debug, Default)]
619struct FontPaletteReserved;
620
621impl ReservedValues for FontPaletteReserved {
622    fn check(&mut self, ident: &str) -> bool {
623        ident.starts_with("--")
624    }
625
626    fn reset(&mut self) {}
627}
628
629#[derive(Debug, Clone, Hash, PartialEq, Eq)]
630pub enum Dependency<'s> {
631    Url {
632        request: &'s str,
633        range: Range,
634        kind: UrlRangeKind,
635    },
636    Import {
637        request: &'s str,
638        range: Range,
639        layer: Option<&'s str>,
640        supports: Option<&'s str>,
641        media: Option<&'s str>,
642    },
643    Replace {
644        content: &'s str,
645        range: Range,
646    },
647    LocalClass {
648        name: &'s str,
649        range: Range,
650        explicit: bool,
651    },
652    LocalId {
653        name: &'s str,
654        range: Range,
655        explicit: bool,
656    },
657    LocalVar {
658        name: &'s str,
659        range: Range,
660        from: Option<&'s str>,
661    },
662    LocalVarDecl {
663        name: &'s str,
664        range: Range,
665    },
666    LocalPropertyDecl {
667        name: &'s str,
668        range: Range,
669    },
670    LocalKeyframes {
671        name: &'s str,
672        range: Range,
673    },
674    LocalKeyframesDecl {
675        name: &'s str,
676        range: Range,
677    },
678    LocalCounterStyle {
679        name: &'s str,
680        range: Range,
681    },
682    LocalCounterStyleDecl {
683        name: &'s str,
684        range: Range,
685    },
686    LocalFontPalette {
687        name: &'s str,
688        range: Range,
689    },
690    LocalFontPaletteDecl {
691        name: &'s str,
692        range: Range,
693    },
694    Composes {
695        local_classes: SmallVec<[&'s str; 2]>,
696        names: SmallVec<[&'s str; 2]>,
697        from: Option<&'s str>,
698        range: Range,
699    },
700    ICSSImportFrom {
701        path: &'s str,
702    },
703    ICSSImportValue {
704        prop: &'s str,
705        value: &'s str,
706    },
707    ICSSExportValue {
708        prop: &'s str,
709        value: &'s str,
710    },
711}
712
713#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
714pub enum UrlRangeKind {
715    Function,
716    String,
717}
718
719#[derive(Debug, Clone, Hash, PartialEq, Eq)]
720pub struct Warning<'s> {
721    range: Range,
722    kind: WarningKind<'s>,
723}
724
725impl<'s> Warning<'s> {
726    pub fn new(range: Range, kind: WarningKind<'s>) -> Self {
727        Self { range, kind }
728    }
729
730    pub fn range(&self) -> &Range {
731        &self.range
732    }
733
734    pub fn kind(&self) -> &WarningKind<'s> {
735        &self.kind
736    }
737}
738
739#[derive(Debug, Clone, Hash, PartialEq, Eq)]
740pub enum WarningKind<'s> {
741    Unexpected { message: &'s str },
742    DuplicateUrl { when: &'s str },
743    NamespaceNotSupportedInBundledCss,
744    NotPrecededAtImport,
745    ExpectedUrl { when: &'s str },
746    ExpectedUrlBefore { when: &'s str },
747    ExpectedLayerBefore { when: &'s str },
748    InconsistentModeResult,
749    ExpectedNotInside { pseudo: &'s str },
750    MissingWhitespace { surrounding: &'s str },
751    NotPure { message: &'s str },
752    UnexpectedComposition { message: &'s str },
753}
754
755impl Display for Warning<'_> {
756    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
757        match self.kind {
758            WarningKind::Unexpected { message, .. } => write!(f, "{message}"),
759            WarningKind::DuplicateUrl { when, .. } => write!(
760                f,
761                "Duplicate of 'url(...)' in '{when}'"
762            ),
763            WarningKind::NamespaceNotSupportedInBundledCss { .. } => write!(
764                f,
765                "'@namespace' is not supported in bundled CSS"
766            ),
767            WarningKind::NotPrecededAtImport { .. } => {
768                write!(f, "Any '@import' rules must precede all other rules")
769            }
770            WarningKind::ExpectedUrl { when, .. } => write!(f, "Expected URL in '{when}'"),
771            WarningKind::ExpectedUrlBefore { when, .. } => write!(
772                f,
773                "An URL in '{when}' should be before 'layer(...)' or 'supports(...)'"
774            ),
775            WarningKind::ExpectedLayerBefore { when, .. } => write!(
776                f,
777                "The 'layer(...)' in '{when}' should be before 'supports(...)'"
778            ),
779            WarningKind::InconsistentModeResult { .. } => write!(
780                f,
781                "Inconsistent rule global/local (multiple selectors must result in the same mode for the rule)"
782            ),
783            WarningKind::ExpectedNotInside { pseudo, .. } => write!(
784                f,
785                "A '{pseudo}' is not allowed inside of a ':local()' or ':global()'"
786            ),
787            WarningKind::MissingWhitespace { surrounding, .. } => write!(
788                f,
789                "Missing {surrounding} whitespace"
790            ),
791            WarningKind::NotPure { message, .. } => write!(f, "Pure globals is not allowed in pure mode, {message}"),
792            WarningKind::UnexpectedComposition {  message, .. } => write!(f, "Composition is {message}"),
793        }
794    }
795}
796
797#[derive(Debug)]
798pub struct LexDependencies<'s, D, W> {
799    mode_data: Option<ModeData<'s>>,
800    scope: Scope<'s>,
801    block_nesting_level: u32,
802    allow_import_at_rule: bool,
803    balanced: BalancedStack,
804    is_next_rule_prelude: bool,
805    in_animation_property: Option<InProperty<AnimationReserved>>,
806    in_list_style_property: Option<InProperty<ListStyleReserved>>,
807    in_font_palette_property: Option<InProperty<FontPaletteReserved>>,
808    handle_dependency: D,
809    handle_warning: W,
810}
811
812impl<'s, D: HandleDependency<'s>, W: HandleWarning<'s>> LexDependencies<'s, D, W> {
813    pub fn new(handle_dependency: D, handle_warning: W, mode: Mode) -> Self {
814        Self {
815            mode_data: if mode == Mode::Css {
816                None
817            } else {
818                Some(ModeData::new(mode))
819            },
820            scope: Scope::TopLevel,
821            block_nesting_level: 0,
822            allow_import_at_rule: true,
823            balanced: Default::default(),
824            is_next_rule_prelude: true,
825            in_animation_property: None,
826            in_list_style_property: None,
827            in_font_palette_property: None,
828            handle_dependency,
829            handle_warning,
830        }
831    }
832
833    fn is_next_nested_syntax(&self, lexer: &mut Lexer) -> Option<bool> {
834        lexer.consume_white_space_and_comments()?;
835        let c = lexer.cur()?;
836        if c == C_RIGHT_CURLY {
837            return Some(false);
838        }
839        // If what follows is a property, then it's not a nested selector
840        // This is not strictly correct, but it's good enough for our purposes
841        // since we only need 'is_selector()' when next char is '#', '.', or ':'
842        Some(!start_ident_sequence(c, lexer.peek()?, lexer.peek2()?))
843    }
844
845    fn get_media(&self, lexer: &Lexer<'s>, start: Pos, end: Pos) -> Option<&'s str> {
846        let media = lexer.slice(start, end)?;
847        let mut media_lexer = Lexer::new(media);
848        media_lexer.consume();
849        media_lexer.consume_white_space_and_comments()?;
850        Some(media)
851    }
852
853    fn enter_animation_property(&mut self) {
854        self.in_animation_property = Some(InProperty::new(
855            AnimationReserved::default(),
856            self.balanced.len(),
857        ));
858    }
859
860    fn exit_animation_property(&mut self) {
861        self.in_animation_property = None;
862    }
863
864    fn enter_list_style_property(&mut self) {
865        self.in_list_style_property = Some(InProperty::new(ListStyleReserved, self.balanced.len()));
866    }
867
868    fn exit_list_style_property(&mut self) {
869        self.in_list_style_property = None;
870    }
871
872    fn enter_font_palette_property(&mut self) {
873        self.in_font_palette_property =
874            Some(InProperty::new(FontPaletteReserved, self.balanced.len()));
875    }
876
877    fn exit_font_palette_property(&mut self) {
878        self.in_font_palette_property = None;
879    }
880
881    fn back_white_space_and_comments_distance(&self, lexer: &Lexer<'s>, end: Pos) -> Option<Pos> {
882        let mut lexer = lexer.clone().turn_back(end);
883        lexer.consume();
884        lexer.consume_white_space_and_comments()?;
885        lexer.cur_pos()
886    }
887
888    fn should_have_after_white_space(&self, lexer: &Lexer<'s>, end: Pos) -> bool {
889        let mut lexer = lexer.clone().turn_back(end);
890        let mut has_white_space = false;
891        lexer.consume();
892        loop {
893            if lexer.consume_comments().is_none() {
894                return true;
895            }
896            let Some(c) = lexer.cur() else {
897                return true;
898            };
899            if is_white_space(c) {
900                has_white_space = true;
901                if lexer.consume_space().is_none() {
902                    return true;
903                }
904            } else {
905                break;
906            }
907        }
908        let c = lexer.cur().unwrap();
909        // start of a :global :local
910        if c == C_LEFT_PARENTHESIS
911            || c == C_COMMA
912            || c == C_SEMICOLON
913            || c == C_RIGHT_CURLY
914            || c == C_LEFT_CURLY
915        {
916            return true;
917        }
918        has_white_space
919    }
920
921    fn has_after_white_space(&self, lexer: &mut Lexer<'s>) -> Option<bool> {
922        let mut has_white_space = false;
923        loop {
924            lexer.consume_comments()?;
925            if is_white_space(lexer.cur()?) {
926                has_white_space = true;
927                lexer.consume_space()?;
928            } else {
929                break;
930            }
931        }
932        Some(has_white_space)
933    }
934
935    fn eat(&mut self, lexer: &mut Lexer<'s>, chars: &[char], message: &'s str) -> Option<bool> {
936        if !chars.contains(&lexer.cur()?) {
937            self.handle_warning.handle_warning(Warning {
938                kind: WarningKind::Unexpected { message },
939                range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
940            });
941            return Some(false);
942        }
943        lexer.consume();
944        Some(true)
945    }
946
947    fn lex_icss_import(&mut self, lexer: &mut Lexer<'s>) -> Option<()> {
948        lexer.consume_white_space_and_comments()?;
949        let start = lexer.cur_pos()?;
950        loop {
951            let c = lexer.cur()?;
952            if c == C_RIGHT_PARENTHESIS {
953                break;
954            }
955            lexer.consume();
956        }
957        let end = lexer.cur_pos()?;
958        self.handle_dependency
959            .handle_dependency(Dependency::ICSSImportFrom {
960                path: lexer.slice(start, end)?,
961            });
962        lexer.consume();
963        lexer.consume_white_space_and_comments()?;
964        if !self.eat(
965            lexer,
966            &[C_LEFT_CURLY],
967            "Expected '{' during parsing of ':import()'",
968        )? {
969            return Some(());
970        }
971        lexer.consume_white_space_and_comments()?;
972        while lexer.cur()? != C_RIGHT_CURLY {
973            lexer.consume_white_space_and_comments()?;
974            let prop_start = lexer.cur_pos()?;
975            self.consume_icss_export_prop(lexer)?;
976            let prop_end = lexer.cur_pos()?;
977            lexer.consume_white_space_and_comments()?;
978            if !self.eat(
979                lexer,
980                &[C_COLON],
981                "Expected ':' during parsing of ':import'",
982            )? {
983                return Some(());
984            }
985            lexer.consume_white_space_and_comments()?;
986            let value_start = lexer.cur_pos()?;
987            self.consume_icss_export_value(lexer)?;
988            let value_end = lexer.cur_pos()?;
989            if lexer.cur()? == C_SEMICOLON {
990                lexer.consume();
991                lexer.consume_white_space_and_comments()?;
992            }
993            self.handle_dependency
994                .handle_dependency(Dependency::ICSSImportValue {
995                    prop: lexer
996                        .slice(prop_start, prop_end)?
997                        .trim_end_matches(is_white_space),
998                    value: lexer
999                        .slice(value_start, value_end)?
1000                        .trim_end_matches(is_white_space),
1001                });
1002        }
1003        lexer.consume();
1004        Some(())
1005    }
1006
1007    fn consume_icss_export_prop(&self, lexer: &mut Lexer<'s>) -> Option<()> {
1008        loop {
1009            let c = lexer.cur()?;
1010            if c == C_COLON
1011                || c == C_RIGHT_CURLY
1012                || c == C_SEMICOLON
1013                || (c == C_SOLIDUS && lexer.peek()? == C_ASTERISK)
1014            {
1015                break;
1016            }
1017            lexer.consume();
1018        }
1019        Some(())
1020    }
1021
1022    fn consume_icss_export_value(&self, lexer: &mut Lexer<'s>) -> Option<()> {
1023        loop {
1024            let c = lexer.cur()?;
1025            if c == C_RIGHT_CURLY || c == C_SEMICOLON {
1026                break;
1027            }
1028            lexer.consume();
1029        }
1030        Some(())
1031    }
1032
1033    fn lex_icss_export(&mut self, lexer: &mut Lexer<'s>) -> Option<()> {
1034        lexer.consume_white_space_and_comments()?;
1035        if !self.eat(
1036            lexer,
1037            &[C_LEFT_CURLY],
1038            "Expected '{' during parsing of ':export'",
1039        )? {
1040            return Some(());
1041        }
1042        lexer.consume_white_space_and_comments()?;
1043        while lexer.cur()? != C_RIGHT_CURLY {
1044            lexer.consume_white_space_and_comments()?;
1045            let prop_start = lexer.cur_pos()?;
1046            self.consume_icss_export_prop(lexer)?;
1047            let prop_end = lexer.cur_pos()?;
1048            lexer.consume_white_space_and_comments()?;
1049            if !self.eat(
1050                lexer,
1051                &[C_COLON],
1052                "Expected ':' during parsing of ':export'",
1053            )? {
1054                return Some(());
1055            }
1056            lexer.consume_white_space_and_comments()?;
1057            let value_start = lexer.cur_pos()?;
1058            self.consume_icss_export_value(lexer)?;
1059            let value_end = lexer.cur_pos()?;
1060            if lexer.cur()? == C_SEMICOLON {
1061                lexer.consume();
1062                lexer.consume_white_space_and_comments()?;
1063            }
1064            self.handle_dependency
1065                .handle_dependency(Dependency::ICSSExportValue {
1066                    prop: lexer
1067                        .slice(prop_start, prop_end)?
1068                        .trim_end_matches(is_white_space),
1069                    value: lexer
1070                        .slice(value_start, value_end)?
1071                        .trim_end_matches(is_white_space),
1072                });
1073        }
1074        lexer.consume();
1075        Some(())
1076    }
1077
1078    fn lex_local_var(&mut self, lexer: &mut Lexer<'s>) -> Option<()> {
1079        lexer.consume_white_space_and_comments()?;
1080        let start = lexer.cur_pos()?;
1081        if lexer.cur()? != C_HYPHEN_MINUS || lexer.peek()? != C_HYPHEN_MINUS {
1082            self.handle_warning.handle_warning(Warning {
1083                kind: WarningKind::Unexpected {
1084                    message: "Expected starts with '--' during parsing of 'var()'",
1085                },
1086                range: Range::new(start, lexer.peek2_pos()?),
1087            });
1088            return Some(());
1089        }
1090        lexer.consume_ident_sequence()?;
1091        let name_start = start + 2;
1092        let end = lexer.cur_pos()?;
1093        lexer.consume_white_space_and_comments()?;
1094        let from_start = lexer.cur_pos()?;
1095        let from = if matches!(lexer.slice(from_start, from_start + 4), Some("from")) {
1096            lexer.consume();
1097            lexer.consume();
1098            lexer.consume();
1099            lexer.consume();
1100            lexer.consume_white_space_and_comments()?;
1101            let c = lexer.cur()?;
1102            let path_start = lexer.cur_pos()?;
1103            if c == '\'' || c == '"' {
1104                lexer.consume();
1105                lexer.consume_string(self, c)?;
1106            } else if start_ident_sequence(c, lexer.peek()?, lexer.peek2()?) {
1107                lexer.consume_ident_sequence()?;
1108            } else {
1109                self.handle_warning.handle_warning(Warning {
1110                    range: Range::new(path_start, lexer.peek_pos()?),
1111                    kind: WarningKind::Unexpected {
1112                        message: "Expected string or ident during parsing of 'composes'",
1113                    },
1114                });
1115                return Some(());
1116            }
1117            Some(lexer.slice(path_start, lexer.cur_pos()?)?)
1118        } else {
1119            None
1120        };
1121        self.handle_dependency
1122            .handle_dependency(Dependency::LocalVar {
1123                name: lexer.slice(name_start, end)?,
1124                range: Range::new(start, end),
1125                from,
1126            });
1127        Some(())
1128    }
1129
1130    fn lex_local_var_decl(
1131        &mut self,
1132        lexer: &mut Lexer<'s>,
1133        name: &'s str,
1134        start: Pos,
1135        end: Pos,
1136    ) -> Option<()> {
1137        lexer.consume_white_space_and_comments()?;
1138        if lexer.cur()? != C_COLON {
1139            return Some(());
1140        }
1141        lexer.consume();
1142        self.handle_dependency
1143            .handle_dependency(Dependency::LocalVarDecl {
1144                name,
1145                range: Range::new(start, end),
1146            });
1147        Some(())
1148    }
1149
1150    fn lex_local_dashed_ident_decl(
1151        &mut self,
1152        lexer: &mut Lexer<'s>,
1153        local_decl_dependency: impl FnOnce(&'s str, Range) -> Dependency<'s>,
1154        dashed_warning: impl FnOnce(Range) -> Warning<'s>,
1155        left_curly_warning: impl FnOnce(Range) -> Warning<'s>,
1156    ) -> Option<()> {
1157        lexer.consume_white_space_and_comments()?;
1158        let start = lexer.cur_pos()?;
1159        if lexer.cur()? != C_HYPHEN_MINUS || lexer.peek()? != C_HYPHEN_MINUS {
1160            self.handle_warning
1161                .handle_warning(dashed_warning(Range::new(start, lexer.peek2_pos()?)));
1162            return Some(());
1163        }
1164        lexer.consume_ident_sequence()?;
1165        let name_start = start + 2;
1166        let end = lexer.cur_pos()?;
1167        self.handle_dependency
1168            .handle_dependency(local_decl_dependency(
1169                lexer.slice(name_start, end)?,
1170                Range::new(start, end),
1171            ));
1172        lexer.consume_white_space_and_comments()?;
1173        if lexer.cur()? != C_LEFT_CURLY {
1174            self.handle_warning
1175                .handle_warning(left_curly_warning(Range::new(
1176                    lexer.cur_pos()?,
1177                    lexer.peek_pos()?,
1178                )));
1179            return Some(());
1180        }
1181        Some(())
1182    }
1183
1184    fn lex_local_keyframes_decl(&mut self, lexer: &mut Lexer<'s>) -> Option<()> {
1185        lexer.consume_white_space_and_comments()?;
1186        let mut is_function = false;
1187        if lexer.cur()? == C_COLON {
1188            let start = lexer.cur_pos()?;
1189            lexer.consume_potential_pseudo(self)?;
1190            let end = lexer.cur_pos()?;
1191            let pseudo = lexer.slice(start, end)?;
1192            let mode_data = self.mode_data.as_ref().unwrap();
1193            if mode_data.is_pure_mode() && pseudo.eq_ignore_ascii_case(":global(")
1194                || pseudo.eq_ignore_ascii_case(":global")
1195            {
1196                self.handle_warning.handle_warning(Warning {
1197                    range: Range::new(start, end),
1198                    kind: WarningKind::NotPure {
1199                        message: "'@keyframes :global' is not allowed in pure mode",
1200                    },
1201                });
1202            }
1203            is_function =
1204                pseudo.eq_ignore_ascii_case(":local(") || pseudo.eq_ignore_ascii_case(":global(");
1205            if !is_function
1206                && !pseudo.eq_ignore_ascii_case(":local")
1207                && !pseudo.eq_ignore_ascii_case(":global")
1208            {
1209                self.handle_warning.handle_warning(Warning {
1210                    range: Range::new(start, end),
1211                    kind: WarningKind::Unexpected {
1212                        message: "Expected ':local', ':local()', ':global', or ':global()' during parsing of '@keyframes' name",
1213                    }
1214                });
1215                return Some(());
1216            }
1217            lexer.consume_white_space_and_comments()?;
1218        }
1219        let start = lexer.cur_pos()?;
1220        if !start_ident_sequence(lexer.cur()?, lexer.peek()?, lexer.peek2()?) {
1221            self.handle_warning.handle_warning(Warning {
1222                range: Range::new(start, lexer.peek2_pos()?),
1223                kind: WarningKind::Unexpected {
1224                    message: "Expected ident during parsing of '@keyframes' name",
1225                },
1226            });
1227            return Some(());
1228        }
1229        lexer.consume_ident_sequence()?;
1230        let end = lexer.cur_pos()?;
1231        let mode_data = self.mode_data.as_mut().unwrap();
1232        if mode_data.is_current_local_mode() {
1233            self.handle_dependency
1234                .handle_dependency(Dependency::LocalKeyframesDecl {
1235                    name: lexer.slice(start, end)?,
1236                    range: Range::new(start, end),
1237                });
1238        }
1239        lexer.consume_white_space_and_comments()?;
1240        if is_function {
1241            if lexer.cur()? != C_RIGHT_PARENTHESIS {
1242                self.handle_warning.handle_warning(Warning {
1243                    range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
1244                    kind: WarningKind::Unexpected {
1245                        message: "Expected ')' during parsing of '@keyframes :local(' or '@keyframes :global('",
1246                    }
1247                });
1248                return Some(());
1249            }
1250            self.handle_dependency
1251                .handle_dependency(Dependency::Replace {
1252                    content: "",
1253                    range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
1254                });
1255            mode_data.inside_mode_function -= 1;
1256            self.balanced.pop_without_moda_data();
1257            lexer.consume();
1258            lexer.consume_white_space_and_comments()?;
1259        }
1260        if lexer.cur()? != C_LEFT_CURLY {
1261            self.handle_warning.handle_warning(Warning {
1262                range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
1263                kind: WarningKind::Unexpected {
1264                    message: "Expected '{' during parsing of '@keyframes'",
1265                },
1266            });
1267            return Some(());
1268        }
1269        Some(())
1270    }
1271
1272    fn handle_local_keyframes_dependency(&mut self, lexer: &Lexer<'s>) -> Option<()> {
1273        let animation = self.in_animation_property.as_mut().unwrap();
1274        if let Some(range) = animation.take_rename(self.balanced.len()) {
1275            self.handle_dependency
1276                .handle_dependency(Dependency::LocalKeyframes {
1277                    name: lexer.slice(range.start, range.end)?,
1278                    range,
1279                });
1280        }
1281        animation.reset_reserved();
1282        Some(())
1283    }
1284
1285    fn lex_local_counter_style_decl(&mut self, lexer: &mut Lexer<'s>) -> Option<()> {
1286        lexer.consume_white_space_and_comments()?;
1287        let start = lexer.cur_pos()?;
1288        if !start_ident_sequence(lexer.cur()?, lexer.peek()?, lexer.peek2()?) {
1289            self.handle_warning.handle_warning(Warning {
1290                range: Range::new(start, lexer.peek2_pos()?),
1291                kind: WarningKind::Unexpected {
1292                    message: "Expected ident during parsing of '@counter-style'",
1293                },
1294            });
1295            return Some(());
1296        }
1297        lexer.consume_ident_sequence()?;
1298        let end = lexer.cur_pos()?;
1299        self.handle_dependency
1300            .handle_dependency(Dependency::LocalCounterStyleDecl {
1301                name: lexer.slice(start, end)?,
1302                range: Range::new(start, end),
1303            });
1304        lexer.consume_white_space_and_comments()?;
1305        if lexer.cur()? != C_LEFT_CURLY {
1306            self.handle_warning.handle_warning(Warning {
1307                range: Range::new(lexer.cur_pos()?, lexer.peek_pos()?),
1308                kind: WarningKind::Unexpected {
1309                    message: "Expected '{' during parsing of '@counter-style'",
1310                },
1311            });
1312            return Some(());
1313        }
1314        Some(())
1315    }
1316
1317    fn handle_local_counter_style_dependency(&mut self, lexer: &Lexer<'s>) -> Option<()> {
1318        let list_style = self.in_list_style_property.as_mut().unwrap();
1319        if let Some(range) = list_style.take_rename(self.balanced.len()) {
1320            self.handle_dependency
1321                .handle_dependency(Dependency::LocalCounterStyle {
1322                    name: lexer.slice(range.start, range.end)?,
1323                    range,
1324                });
1325        }
1326        Some(())
1327    }
1328
1329    fn handle_local_font_palette_dependency(&mut self, lexer: &Lexer<'s>) -> Option<()> {
1330        let font_palette = self.in_font_palette_property.as_mut().unwrap();
1331        if let Some(range) = font_palette.take_rename(self.balanced.len()) {
1332            self.handle_dependency
1333                .handle_dependency(Dependency::LocalFontPalette {
1334                    name: lexer.slice(range.start + 2, range.end)?,
1335                    range,
1336                });
1337        }
1338        Some(())
1339    }
1340
1341    fn lex_composes(
1342        &mut self,
1343        lexer: &mut Lexer<'s>,
1344        local_classes: SmallVec<[&'s str; 2]>,
1345        start: Pos,
1346    ) -> Option<()> {
1347        lexer.consume_white_space_and_comments()?;
1348        if lexer.cur()? != C_COLON {
1349            return Some(());
1350        }
1351        lexer.consume();
1352        let mut names: SmallVec<[&'s str; 2]> = SmallVec::new();
1353        let mut end;
1354        let mut has_from = false;
1355        loop {
1356            lexer.consume_white_space_and_comments()?;
1357            let start = lexer.cur_pos()?;
1358            end = start;
1359            loop {
1360                let c = lexer.cur()?;
1361                if c == C_COMMA || c == C_SEMICOLON || c == C_RIGHT_CURLY {
1362                    break;
1363                }
1364                let maybe_global_start = lexer.cur_pos()?;
1365                if matches!(
1366                    lexer.slice(maybe_global_start, maybe_global_start + 7),
1367                    Some("global(")
1368                ) {
1369                    for _ in 0..7 {
1370                        lexer.consume();
1371                    }
1372                    let name_start = lexer.cur_pos()?;
1373                    if !start_ident_sequence(lexer.cur()?, lexer.peek()?, lexer.peek2()?) {
1374                        self.handle_warning.handle_warning(Warning {
1375                            range: Range::new(name_start, lexer.peek2_pos()?),
1376                            kind: WarningKind::Unexpected {
1377                                message: "Expected ident during parsing of 'composes'",
1378                            },
1379                        });
1380                        return Some(());
1381                    }
1382                    lexer.consume_ident_sequence()?;
1383                    let name_end = lexer.cur_pos()?;
1384                    lexer.consume_white_space_and_comments()?;
1385                    self.eat(
1386                        lexer,
1387                        &[C_RIGHT_PARENTHESIS],
1388                        "Expected ')' during parsing of 'composes'",
1389                    );
1390                    end = lexer.cur_pos()?;
1391                    self.handle_dependency
1392                        .handle_dependency(Dependency::Composes {
1393                            local_classes: local_classes.clone(),
1394                            names: smallvec![lexer.slice(name_start, name_end)?],
1395                            from: Some("global"),
1396                            range: Range::new(maybe_global_start, lexer.cur_pos()?),
1397                        });
1398                } else {
1399                    let name_start = lexer.cur_pos()?;
1400                    if !start_ident_sequence(c, lexer.peek()?, lexer.peek2()?) {
1401                        self.handle_warning.handle_warning(Warning {
1402                            range: Range::new(name_start, lexer.peek2_pos()?),
1403                            kind: WarningKind::Unexpected {
1404                                message: "Expected ident during parsing of 'composes'",
1405                            },
1406                        });
1407                        return Some(());
1408                    }
1409                    lexer.consume_ident_sequence()?;
1410                    let name_end = lexer.cur_pos()?;
1411                    if lexer
1412                        .slice(name_start, name_end)?
1413                        .eq_ignore_ascii_case("from")
1414                    {
1415                        has_from = true;
1416                        break;
1417                    }
1418                    names.push(lexer.slice(name_start, name_end)?);
1419                    end = name_end;
1420                }
1421                lexer.consume_white_space_and_comments()?;
1422            }
1423            lexer.consume_white_space_and_comments()?;
1424            let c = lexer.cur()?;
1425            if !has_from {
1426                if !names.is_empty() {
1427                    self.handle_dependency
1428                        .handle_dependency(Dependency::Composes {
1429                            local_classes: local_classes.clone(),
1430                            names: std::mem::take(&mut names),
1431                            from: None,
1432                            range: Range::new(start, end),
1433                        });
1434                }
1435                if c == C_COMMA {
1436                    lexer.consume();
1437                    continue;
1438                }
1439                break;
1440            }
1441            let path_start = lexer.cur_pos()?;
1442            if c == '\'' || c == '"' {
1443                lexer.consume();
1444                lexer.consume_string(self, c)?;
1445            } else if start_ident_sequence(c, lexer.peek()?, lexer.peek2()?) {
1446                lexer.consume_ident_sequence()?;
1447            } else {
1448                self.handle_warning.handle_warning(Warning {
1449                    range: Range::new(path_start, lexer.peek_pos()?),
1450                    kind: WarningKind::Unexpected {
1451                        message: "Expected string or ident during parsing of 'composes'",
1452                    },
1453                });
1454                return Some(());
1455            }
1456            let path_end = lexer.cur_pos()?;
1457            end = path_end;
1458            let from = Some(lexer.slice(path_start, path_end)?);
1459            self.handle_dependency
1460                .handle_dependency(Dependency::Composes {
1461                    local_classes: local_classes.clone(),
1462                    names: std::mem::take(&mut names),
1463                    from,
1464                    range: Range::new(start, end),
1465                });
1466            lexer.consume_white_space_and_comments()?;
1467            if lexer.cur()? != C_COMMA {
1468                break;
1469            }
1470            lexer.consume();
1471        }
1472        if lexer.cur()? == C_SEMICOLON {
1473            lexer.consume();
1474            end = lexer.cur_pos()?;
1475        }
1476        self.handle_dependency
1477            .handle_dependency(Dependency::Replace {
1478                content: "",
1479                range: Range::new(start, end),
1480            });
1481        Some(())
1482    }
1483}
1484
1485impl<'s, D: HandleDependency<'s>, W: HandleWarning<'s>> Visitor<'s> for LexDependencies<'s, D, W> {
1486    fn is_selector(&mut self, _: &mut Lexer) -> Option<bool> {
1487        Some(self.is_next_rule_prelude)
1488    }
1489
1490    fn url(
1491        &mut self,
1492        lexer: &mut Lexer<'s>,
1493        start: Pos,
1494        end: Pos,
1495        content_start: Pos,
1496        content_end: Pos,
1497    ) -> Option<()> {
1498        let value = lexer.slice(content_start, content_end)?;
1499        match self.scope {
1500            Scope::InAtImport(ref mut import_data) => {
1501                if import_data.in_supports() {
1502                    return Some(());
1503                }
1504                if import_data.url.is_some() {
1505                    self.handle_warning.handle_warning(Warning {
1506                        range: Range::new(import_data.start, end),
1507                        kind: WarningKind::DuplicateUrl {
1508                            when: lexer.slice(import_data.start, end)?,
1509                        },
1510                    });
1511                    return Some(());
1512                }
1513                import_data.url = Some(value);
1514                import_data.url_range = Some(Range::new(start, end));
1515            }
1516            Scope::InBlock => self.handle_dependency.handle_dependency(Dependency::Url {
1517                request: value,
1518                range: Range::new(start, end),
1519                kind: UrlRangeKind::Function,
1520            }),
1521            _ => {}
1522        }
1523        Some(())
1524    }
1525
1526    fn string(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
1527        match self.scope {
1528            Scope::InAtImport(ref mut import_data) => {
1529                let inside_url = matches!(
1530                    self.balanced.last(),
1531                    Some(last) if matches!(last.kind, BalancedItemKind::Url)
1532                );
1533
1534                // Do not parse URLs in `supports(...)` and other strings if we already have a URL
1535                if import_data.in_supports() || (!inside_url && import_data.url.is_some()) {
1536                    return Some(());
1537                }
1538
1539                if inside_url && import_data.url.is_some() {
1540                    self.handle_warning.handle_warning(Warning {
1541                        range: Range::new(import_data.start, end),
1542                        kind: WarningKind::DuplicateUrl {
1543                            when: lexer.slice(import_data.start, end)?,
1544                        },
1545                    });
1546                    return Some(());
1547                }
1548
1549                let value = lexer.slice(start + 1, end - 1)?;
1550                import_data.url = Some(value);
1551                // For url("inside_url") url_range will determined in right_parenthesis
1552                if !inside_url {
1553                    import_data.url_range = Some(Range::new(start, end));
1554                }
1555            }
1556            Scope::InBlock => {
1557                let Some(last) = self.balanced.last() else {
1558                    return Some(());
1559                };
1560                let kind = match last.kind {
1561                    BalancedItemKind::Url => UrlRangeKind::String,
1562                    BalancedItemKind::ImageSet => UrlRangeKind::Function,
1563                    _ => return Some(()),
1564                };
1565                let value = lexer.slice(start + 1, end - 1)?;
1566                self.handle_dependency.handle_dependency(Dependency::Url {
1567                    request: value,
1568                    range: Range::new(start, end),
1569                    kind,
1570                });
1571            }
1572            _ => {}
1573        }
1574        Some(())
1575    }
1576
1577    fn at_keyword(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
1578        let name = lexer.slice(start, end)?;
1579        if name.eq_ignore_ascii_case("@namespace") {
1580            self.scope = Scope::AtNamespaceInvalid;
1581            self.handle_warning.handle_warning(Warning {
1582                range: Range::new(start, end),
1583                kind: WarningKind::NamespaceNotSupportedInBundledCss,
1584            });
1585        } else if name.eq_ignore_ascii_case("@import") {
1586            if !self.allow_import_at_rule {
1587                self.scope = Scope::AtImportInvalid;
1588                self.handle_warning.handle_warning(Warning {
1589                    range: Range::new(start, end),
1590                    kind: WarningKind::NotPrecededAtImport,
1591                });
1592                return Some(());
1593            }
1594            self.scope = Scope::InAtImport(ImportData::new(start));
1595        } else if self.mode_data.is_some() {
1596            if name.eq_ignore_ascii_case("@keyframes")
1597                || with_vendor_prefixed_eq(name, "keyframes", true)
1598            {
1599                self.lex_local_keyframes_decl(lexer)?;
1600            } else if name.eq_ignore_ascii_case("@property") {
1601                self.lex_local_dashed_ident_decl(
1602                    lexer,
1603                    |name, range| Dependency::LocalPropertyDecl { name, range },
1604                    |range| Warning {
1605                        range,
1606                        kind: WarningKind::Unexpected {
1607                            message: "Expected starts with '--' during parsing of '@property'",
1608                        },
1609                    },
1610                    |range| Warning {
1611                        range,
1612                        kind: WarningKind::Unexpected {
1613                            message: "Expected '{' during parsing of '@property'",
1614                        },
1615                    },
1616                )?;
1617            } else if name.eq_ignore_ascii_case("@counter-style") {
1618                self.lex_local_counter_style_decl(lexer)?;
1619            } else if name.eq_ignore_ascii_case("@font-palette-values") {
1620                self.lex_local_dashed_ident_decl(
1621                    lexer,
1622                    |name, range| Dependency::LocalFontPaletteDecl { name, range },
1623                    |range| Warning {
1624                        range,
1625                        kind: WarningKind::Unexpected {
1626                            message: "Expected starts with '--' during parsing of '@font-palette-values'",
1627                        }
1628                    },
1629                    |range| Warning {
1630                        range,
1631                        kind: WarningKind::Unexpected {
1632                            message: "Expected '{' during parsing of '@font-palette-values'",
1633                        }
1634                    },
1635                )?;
1636            } else {
1637                self.is_next_rule_prelude = name.eq_ignore_ascii_case("@scope");
1638            }
1639
1640            let mode_data = self.mode_data.as_mut().unwrap();
1641            if self.block_nesting_level == 0 {
1642                mode_data.composes_local_classes.find_at_keyword();
1643            }
1644
1645            if mode_data.is_pure_mode() {
1646                mode_data.pure_global = None;
1647            }
1648        }
1649        Some(())
1650    }
1651
1652    fn semicolon(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
1653        match self.scope {
1654            Scope::InAtImport(ref import_data) => {
1655                let Some(url) = import_data.url else {
1656                    self.handle_warning.handle_warning(Warning {
1657                        range: Range::new(import_data.start, end),
1658                        kind: WarningKind::ExpectedUrl {
1659                            when: lexer.slice(import_data.start, end)?,
1660                        },
1661                    });
1662                    self.scope = Scope::TopLevel;
1663                    return Some(());
1664                };
1665                let Some(url_range) = &import_data.url_range else {
1666                    self.handle_warning.handle_warning(Warning {
1667                        range: Range::new(start, end),
1668                        kind: WarningKind::Unexpected {
1669                            message: "Unexpected ';' during parsing of '@import url()'",
1670                        },
1671                    });
1672                    self.scope = Scope::TopLevel;
1673                    return Some(());
1674                };
1675                let layer = match &import_data.layer {
1676                    ImportDataLayer::None => None,
1677                    ImportDataLayer::EndLayer { value, range } => {
1678                        if url_range.start > range.start {
1679                            self.handle_warning.handle_warning(Warning {
1680                                range: url_range.clone(),
1681                                kind: WarningKind::ExpectedUrlBefore {
1682                                    when: lexer.slice(range.start, url_range.end)?,
1683                                },
1684                            });
1685                            self.scope = Scope::TopLevel;
1686                            return Some(());
1687                        }
1688                        Some(*value)
1689                    }
1690                };
1691                let supports = match &import_data.supports {
1692                    ImportDataSupports::None => None,
1693                    ImportDataSupports::InSupports => {
1694                        self.handle_warning.handle_warning(Warning {
1695                            range: Range::new(start, end),
1696                            kind: WarningKind::Unexpected {
1697                                message: "Unexpected ';' during parsing of 'supports()'",
1698                            },
1699                        });
1700                        None
1701                    }
1702                    ImportDataSupports::EndSupports { value, range } => {
1703                        if url_range.start > range.start {
1704                            self.handle_warning.handle_warning(Warning {
1705                                range: url_range.clone(),
1706                                kind: WarningKind::ExpectedUrlBefore {
1707                                    when: lexer.slice(range.start, url_range.end)?,
1708                                },
1709                            });
1710                            self.scope = Scope::TopLevel;
1711                            return Some(());
1712                        }
1713                        Some(*value)
1714                    }
1715                };
1716                if let Some(layer_range) = import_data.layer_range() {
1717                    if let Some(supports_range) = import_data.supports_range() {
1718                        if layer_range.start > supports_range.start {
1719                            self.handle_warning.handle_warning(Warning {
1720                                range: layer_range.clone(),
1721                                kind: WarningKind::ExpectedLayerBefore {
1722                                    when: lexer.slice(supports_range.start, layer_range.end)?,
1723                                },
1724                            });
1725                            self.scope = Scope::TopLevel;
1726                            return Some(());
1727                        }
1728                    }
1729                }
1730                let last_end = import_data
1731                    .supports_range()
1732                    .or_else(|| import_data.layer_range())
1733                    .unwrap_or(url_range)
1734                    .end;
1735                let media = self.get_media(lexer, last_end, start);
1736                self.handle_dependency
1737                    .handle_dependency(Dependency::Import {
1738                        request: url,
1739                        range: Range::new(import_data.start, end),
1740                        layer,
1741                        supports,
1742                        media,
1743                    });
1744                self.scope = Scope::TopLevel;
1745            }
1746            Scope::AtImportInvalid | Scope::AtNamespaceInvalid => {
1747                self.scope = Scope::TopLevel;
1748            }
1749            Scope::InBlock => {
1750                if let Some(mode_data) = &mut self.mode_data {
1751                    mode_data.pure_global = Some(end);
1752
1753                    if mode_data.is_property_local_mode() {
1754                        if self.in_animation_property.is_some() {
1755                            self.handle_local_keyframes_dependency(lexer)?;
1756                            self.exit_animation_property();
1757                        }
1758                        if self.in_list_style_property.is_some() {
1759                            self.handle_local_counter_style_dependency(lexer)?;
1760                            self.exit_list_style_property();
1761                        }
1762                        if self.in_font_palette_property.is_some() {
1763                            self.handle_local_font_palette_dependency(lexer)?;
1764                            self.exit_font_palette_property();
1765                        }
1766                    }
1767                }
1768                self.is_next_rule_prelude = self.is_next_nested_syntax(lexer)?;
1769            }
1770            Scope::TopLevel => {
1771                self.is_next_rule_prelude = self.is_next_nested_syntax(lexer)?;
1772            }
1773        }
1774        Some(())
1775    }
1776
1777    fn function(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
1778        let name = lexer.slice(start, end)?;
1779        self.balanced
1780            .push(BalancedItem::new(name, start, end), self.mode_data.as_mut());
1781
1782        if let Scope::InAtImport(ref mut import_data) = self.scope {
1783            if name.eq_ignore_ascii_case("supports(") {
1784                import_data.supports = ImportDataSupports::InSupports;
1785            }
1786        }
1787
1788        let Some(mode_data) = &self.mode_data else {
1789            return Some(());
1790        };
1791        if mode_data.is_current_local_mode() && name.eq_ignore_ascii_case("var(") {
1792            self.lex_local_var(lexer)?;
1793        }
1794        Some(())
1795    }
1796
1797    fn left_parenthesis(&mut self, _: &mut Lexer, start: Pos, end: Pos) -> Option<()> {
1798        self.balanced
1799            .push(BalancedItem::new_other(start, end), self.mode_data.as_mut());
1800        Some(())
1801    }
1802
1803    fn right_parenthesis(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
1804        let Some(last) = self.balanced.pop(self.mode_data.as_mut()) else {
1805            return Some(());
1806        };
1807        if let Some(mode_data) = &mut self.mode_data {
1808            let mut is_function = last.kind.is_mode_function();
1809            if last.kind.is_mode_class() {
1810                self.balanced.pop_mode_pseudo_class(mode_data);
1811                let popped = self.balanced.pop_without_moda_data().unwrap();
1812                debug_assert!(!matches!(
1813                    popped.kind,
1814                    BalancedItemKind::GlobalClass | BalancedItemKind::LocalClass
1815                ));
1816                is_function = popped.kind.is_mode_function();
1817            }
1818            if is_function {
1819                let distance = self.back_white_space_and_comments_distance(lexer, start)?;
1820                let start = start - distance;
1821                let maybe_left_parenthesis_start = start - 1;
1822                if lexer.slice(start - 1, start)? == "(" {
1823                    self.handle_warning.handle_warning(Warning {
1824                        range: Range::new(maybe_left_parenthesis_start, end),
1825                        kind: WarningKind::Unexpected {
1826                            message: "':global()' or ':local()' can't be empty",
1827                        },
1828                    });
1829                }
1830                self.handle_dependency
1831                    .handle_dependency(Dependency::Replace {
1832                        content: "",
1833                        range: Range::new(start, end),
1834                    });
1835            }
1836        }
1837        if let Scope::InAtImport(ref mut import_data) = self.scope {
1838            let not_in_supports = !import_data.in_supports();
1839            if matches!(last.kind, BalancedItemKind::Url) && not_in_supports {
1840                import_data.url_range = Some(Range::new(last.range.start, end));
1841            } else if matches!(last.kind, BalancedItemKind::Layer) && not_in_supports {
1842                import_data.layer = ImportDataLayer::EndLayer {
1843                    value: lexer.slice(last.range.end, end - 1)?,
1844                    range: Range::new(last.range.start, end),
1845                };
1846            } else if matches!(last.kind, BalancedItemKind::Supports) {
1847                import_data.supports = ImportDataSupports::EndSupports {
1848                    value: lexer.slice(last.range.end, end - 1)?,
1849                    range: Range::new(last.range.start, end),
1850                }
1851            }
1852        }
1853        Some(())
1854    }
1855
1856    fn ident(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
1857        match self.scope {
1858            Scope::InBlock => {
1859                let Some(mode_data) = &mut self.mode_data else {
1860                    return Some(());
1861                };
1862
1863                let ident = lexer.slice(start, end)?;
1864                if mode_data.is_property_local_mode() {
1865                    if let Some(animation) = &mut self.in_animation_property {
1866                        // Not inside functions
1867                        if self.balanced.is_empty() {
1868                            animation.set_rename(lexer.slice(start, end)?, Range::new(start, end));
1869                        }
1870                        return Some(());
1871                    }
1872
1873                    if let Some(list_style) = &mut self.in_list_style_property {
1874                        // Not inside functions
1875                        if self.balanced.is_empty() {
1876                            list_style.set_rename(lexer.slice(start, end)?, Range::new(start, end));
1877                        }
1878                        return Some(());
1879                    }
1880
1881                    if let Some(font_palette) = &mut self.in_font_palette_property {
1882                        // Not inside functions or inside palette-mix()
1883                        if self.balanced.is_empty()
1884                            || matches!(self.balanced.last(), Some(last) if matches!(last.kind, BalancedItemKind::PaletteMix))
1885                        {
1886                            font_palette
1887                                .set_rename(lexer.slice(start, end)?, Range::new(start, end));
1888                        }
1889                        return Some(());
1890                    }
1891
1892                    if let Some(name) = ident.strip_prefix("--") {
1893                        return self.lex_local_var_decl(lexer, name, start, end);
1894                    }
1895
1896                    if ident.eq_ignore_ascii_case("animation")
1897                        || ident.eq_ignore_ascii_case("animation-name")
1898                        || with_vendor_prefixed_eq(ident, "animation", false)
1899                        || with_vendor_prefixed_eq(ident, "animation-name", false)
1900                    {
1901                        self.enter_animation_property();
1902                        return Some(());
1903                    }
1904
1905                    if ident.eq_ignore_ascii_case("list-style")
1906                        || ident.eq_ignore_ascii_case("list-style-type")
1907                    {
1908                        self.enter_list_style_property();
1909                        return Some(());
1910                    }
1911
1912                    if ident.eq_ignore_ascii_case("font-palette") {
1913                        self.enter_font_palette_property();
1914                        return Some(());
1915                    }
1916                }
1917
1918                if ident.eq_ignore_ascii_case("composes")
1919                    || ident.eq_ignore_ascii_case("compose-with")
1920                {
1921                    if self.block_nesting_level != 1 {
1922                        self.handle_warning.handle_warning(Warning {
1923                            range: Range::new(start, end),
1924                            kind: WarningKind::UnexpectedComposition {
1925                                message: "not allowed in nested rule",
1926                            },
1927                        });
1928                        return Some(());
1929                    }
1930                    let Some(local_classes) = mode_data
1931                        .composes_local_classes
1932                        .get_valid_local_classes(lexer)
1933                    else {
1934                        self.handle_warning.handle_warning(Warning {
1935                            range: Range::new(start, end),
1936                            kind: WarningKind::UnexpectedComposition {
1937                                message: "only allowed when selector is single :local class",
1938                            },
1939                        });
1940                        return Some(());
1941                    };
1942                    return self.lex_composes(lexer, local_classes, start);
1943                }
1944            }
1945            Scope::InAtImport(ref mut import_data) => {
1946                if lexer.slice(start, end)?.eq_ignore_ascii_case("layer") {
1947                    import_data.layer = ImportDataLayer::EndLayer {
1948                        value: "",
1949                        range: Range::new(start, end),
1950                    }
1951                }
1952            }
1953            Scope::TopLevel => {
1954                let Some(mode_data) = &mut self.mode_data else {
1955                    return Some(());
1956                };
1957                mode_data.composes_local_classes.invalidate();
1958            }
1959            _ => {}
1960        }
1961        Some(())
1962    }
1963
1964    fn class(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
1965        let Some(mode_data) = &mut self.mode_data else {
1966            return Some(());
1967        };
1968        let name = lexer.slice(start, end)?;
1969        if name == "." {
1970            self.handle_warning.handle_warning(Warning {
1971                range: Range::new(start, end),
1972                kind: WarningKind::Unexpected {
1973                    message: "Invalid class selector syntax",
1974                },
1975            });
1976            return Some(());
1977        }
1978        if mode_data.is_current_local_mode() {
1979            self.handle_dependency
1980                .handle_dependency(Dependency::LocalClass {
1981                    name,
1982                    range: Range::new(start, end),
1983                    explicit: mode_data.is_mode_explicit(),
1984                });
1985            if self.block_nesting_level == 0 {
1986                mode_data
1987                    .composes_local_classes
1988                    .find_local_class(start + 1, end);
1989            }
1990
1991            if mode_data.is_pure_mode() {
1992                mode_data.pure_global = None;
1993            }
1994        }
1995        Some(())
1996    }
1997
1998    fn id(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
1999        let Some(mode_data) = &mut self.mode_data else {
2000            return Some(());
2001        };
2002        let name = lexer.slice(start, end)?;
2003        if name == "#" {
2004            self.handle_warning.handle_warning(Warning {
2005                range: Range::new(start, end),
2006                kind: WarningKind::Unexpected {
2007                    message: "Invalid id selector syntax",
2008                },
2009            });
2010            return Some(());
2011        }
2012        if mode_data.is_current_local_mode() {
2013            self.handle_dependency
2014                .handle_dependency(Dependency::LocalId {
2015                    name,
2016                    range: Range::new(start, end),
2017                    explicit: mode_data.is_mode_explicit(),
2018                });
2019
2020            if self.block_nesting_level == 0 {
2021                mode_data.composes_local_classes.invalidate();
2022            }
2023
2024            if mode_data.is_pure_mode() {
2025                mode_data.pure_global = None;
2026            }
2027        }
2028        Some(())
2029    }
2030
2031    fn left_curly_bracket(&mut self, lexer: &mut Lexer, start: Pos, _: Pos) -> Option<()> {
2032        match self.scope {
2033            Scope::TopLevel => {
2034                self.allow_import_at_rule = false;
2035                self.scope = Scope::InBlock;
2036                if self.mode_data.is_none()
2037                    || matches!(&self.mode_data, Some(mode_data) if !matches!(mode_data.composes_local_classes.is_single, SingleLocalClass::AtKeyword))
2038                {
2039                    self.block_nesting_level = 1;
2040                }
2041            }
2042            Scope::InBlock => {
2043                self.block_nesting_level += 1;
2044            }
2045            _ => return Some(()),
2046        }
2047        self.is_next_rule_prelude = self.is_next_nested_syntax(lexer)?;
2048        if let Some(mode_data) = &mut self.mode_data {
2049            if mode_data.is_pure_mode() && mode_data.pure_global.is_some() {
2050                let pure_global_start = mode_data.pure_global.unwrap();
2051                self.handle_warning.handle_warning(Warning {
2052                    range: Range::new(pure_global_start, start),
2053                    kind: WarningKind::NotPure {
2054                        message: "Selector is not pure (pure selectors must contain at least one local class or id)",
2055                    }
2056                });
2057            }
2058
2059            if mode_data.resulting_global.is_some() && mode_data.is_current_local_mode() {
2060                let resulting_global_start = mode_data.resulting_global.unwrap();
2061                self.handle_warning.handle_warning(Warning {
2062                    range: Range::new(resulting_global_start, start),
2063                    kind: WarningKind::InconsistentModeResult,
2064                });
2065            }
2066            mode_data.resulting_global = None;
2067
2068            self.balanced.update_property_mode(mode_data);
2069            self.balanced.pop_mode_pseudo_class(mode_data);
2070            if self.is_next_rule_prelude && self.block_nesting_level == 0 {
2071                let mode_data = self.mode_data.as_mut().unwrap();
2072                mode_data.composes_local_classes.reset_to_initial();
2073            }
2074
2075            debug_assert!(
2076                self.balanced.is_empty(),
2077                "balanced should be empty when end of selector"
2078            );
2079        }
2080        Some(())
2081    }
2082
2083    fn right_curly_bracket(&mut self, lexer: &mut Lexer<'s>, _: Pos, end: Pos) -> Option<()> {
2084        if matches!(self.scope, Scope::InBlock) {
2085            if let Some(mode_data) = &mut self.mode_data {
2086                mode_data.pure_global = Some(end);
2087
2088                if mode_data.is_property_local_mode() {
2089                    if self.in_animation_property.is_some() {
2090                        self.handle_local_keyframes_dependency(lexer)?;
2091                        self.exit_animation_property();
2092                    }
2093                    if self.in_list_style_property.is_some() {
2094                        self.handle_local_counter_style_dependency(lexer)?;
2095                        self.exit_list_style_property();
2096                    }
2097                    if self.in_font_palette_property.is_some() {
2098                        self.handle_local_font_palette_dependency(lexer)?;
2099                        self.exit_font_palette_property();
2100                    }
2101                }
2102            }
2103
2104            if self.block_nesting_level > 0 {
2105                self.block_nesting_level -= 1;
2106            }
2107            if self.block_nesting_level == 0 {
2108                self.scope = Scope::TopLevel;
2109                self.is_next_rule_prelude = true;
2110                if let Some(mode_data) = &mut self.mode_data {
2111                    mode_data.composes_local_classes.reset_to_initial();
2112                }
2113            } else {
2114                self.is_next_rule_prelude = self.is_next_nested_syntax(lexer)?;
2115            }
2116        }
2117        Some(())
2118    }
2119
2120    fn pseudo_function(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2121        let name = lexer.slice(start, end)?;
2122        if let Some(mode_data) = &mut self.mode_data {
2123            if name.eq_ignore_ascii_case(":import(") {
2124                self.lex_icss_import(lexer);
2125                self.handle_dependency
2126                    .handle_dependency(Dependency::Replace {
2127                        content: "",
2128                        range: Range::new(start, lexer.cur_pos()?),
2129                    });
2130                return Some(());
2131            }
2132            if name.eq_ignore_ascii_case(":global(") || name.eq_ignore_ascii_case(":local(") {
2133                if mode_data.is_inside_mode_function() {
2134                    self.handle_warning.handle_warning(Warning {
2135                        range: Range::new(start, end),
2136                        kind: WarningKind::ExpectedNotInside {
2137                            pseudo: lexer.slice(start, end)?,
2138                        },
2139                    });
2140                }
2141
2142                lexer.consume_white_space_and_comments()?;
2143                self.handle_dependency
2144                    .handle_dependency(Dependency::Replace {
2145                        content: "",
2146                        range: Range::new(start, lexer.cur_pos()?),
2147                    });
2148            } else if self.block_nesting_level == 0 {
2149                mode_data.composes_local_classes.invalidate();
2150            }
2151        }
2152        self.balanced
2153            .push(BalancedItem::new(name, start, end), self.mode_data.as_mut());
2154        Some(())
2155    }
2156
2157    fn pseudo_class(&mut self, lexer: &mut Lexer<'s>, start: Pos, end: Pos) -> Option<()> {
2158        let Some(mode_data) = &mut self.mode_data else {
2159            return Some(());
2160        };
2161        let name = lexer.slice(start, end)?;
2162        if name.eq_ignore_ascii_case(":global") || name.eq_ignore_ascii_case(":local") {
2163            if mode_data.is_inside_mode_function() {
2164                self.handle_warning.handle_warning(Warning {
2165                    range: Range::new(start, end),
2166                    kind: WarningKind::ExpectedNotInside {
2167                        pseudo: lexer.slice(start, end)?,
2168                    },
2169                });
2170            }
2171
2172            let should_have_after_white_space = self.should_have_after_white_space(lexer, start);
2173            let has_after_white_space = self.has_after_white_space(lexer)?;
2174            let c = lexer.cur()?;
2175            if c != C_RIGHT_PARENTHESIS && c != C_LEFT_CURLY && c != C_COMMA {
2176                if should_have_after_white_space && !has_after_white_space {
2177                    self.handle_warning.handle_warning(Warning {
2178                        range: Range::new(start, end),
2179                        kind: WarningKind::MissingWhitespace {
2180                            surrounding: "trailing",
2181                        },
2182                    });
2183                }
2184                if !should_have_after_white_space && has_after_white_space {
2185                    self.handle_warning.handle_warning(Warning {
2186                        range: Range::new(start, end),
2187                        kind: WarningKind::MissingWhitespace {
2188                            surrounding: "leading",
2189                        },
2190                    });
2191                }
2192            }
2193
2194            self.balanced
2195                .push(BalancedItem::new(name, start, end), self.mode_data.as_mut());
2196            let end2 = lexer.cur_pos()?;
2197            self.handle_dependency
2198                .handle_dependency(Dependency::Replace {
2199                    content: "",
2200                    range: Range::new(start, end2),
2201                });
2202            return Some(());
2203        }
2204        if matches!(self.scope, Scope::TopLevel) && name.eq_ignore_ascii_case(":export") {
2205            self.lex_icss_export(lexer)?;
2206            self.handle_dependency
2207                .handle_dependency(Dependency::Replace {
2208                    content: "",
2209                    range: Range::new(start, lexer.cur_pos()?),
2210                });
2211            return Some(());
2212        }
2213
2214        if self.block_nesting_level == 0 {
2215            mode_data.composes_local_classes.invalidate();
2216        }
2217        Some(())
2218    }
2219
2220    fn comma(&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
2225        if mode_data.is_pure_mode() && mode_data.pure_global.is_some() {
2226            let pure_global_start = mode_data.pure_global.unwrap();
2227            self.handle_warning.handle_warning(Warning {
2228                range: Range::new(pure_global_start, start),
2229                kind: WarningKind::NotPure {
2230                    message: "Selector is not pure (pure selectors must contain at least one local class or id)",
2231                }
2232            });
2233        }
2234        mode_data.pure_global = Some(end);
2235
2236        if self.block_nesting_level == 0 {
2237            mode_data.composes_local_classes.find_comma(lexer)?;
2238        }
2239
2240        if mode_data.resulting_global.is_some() && mode_data.is_current_local_mode() {
2241            let resulting_global_start = mode_data.resulting_global.unwrap();
2242            self.handle_warning.handle_warning(Warning {
2243                range: Range::new(resulting_global_start, start),
2244                kind: WarningKind::InconsistentModeResult,
2245            });
2246        }
2247
2248        if self.balanced.len() == 1 {
2249            let last = self.balanced.last().unwrap();
2250            let is_local_class = matches!(last.kind, BalancedItemKind::LocalClass);
2251            let is_global_class = matches!(last.kind, BalancedItemKind::GlobalClass);
2252            if is_local_class || is_global_class {
2253                self.balanced.pop_mode_pseudo_class(mode_data);
2254                if mode_data.resulting_global.is_none() && is_global_class {
2255                    mode_data.resulting_global = Some(start);
2256                }
2257            }
2258        }
2259
2260        if matches!(self.scope, Scope::InBlock)
2261            && mode_data.is_property_local_mode()
2262            && self.in_animation_property.is_some()
2263        {
2264            self.handle_local_keyframes_dependency(lexer)?;
2265        }
2266
2267        Some(())
2268    }
2269}