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