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