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 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 "none" |
499 "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 "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 | "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 | "disc"
585 | "circle"
586 | "square"
587 | "disclosure-open"
588 | "disclosure-closed"
589 | "cjk-earthly-branch"
591 | "cjk-heavenly-stem"
592 | "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 | "none"
605 | "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 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 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 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 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 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 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 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}