1use rustc_hash::FxHashSet;
4use smallvec::SmallVec;
5
6use crate::HandleWarning;
7use crate::Lexer;
8use crate::Pos;
9use crate::css_syntax::{
10 MAX_CSS_KEYWORD_LEN, decode_css_keyword, is_css_modules_magic_comment,
11 is_css_modules_pure_magic_comment, is_css_space_byte, is_css_white_space_char, is_dashed_ident,
12 lowercase_ascii_keyword, strip_vendor_prefix, trim_css_whitespace,
13};
14use crate::dependency_types::{
15 Dependency, DependencyContext, Mode, Range, UrlRangeKind, ValueAtRuleImportItem, Warning,
16 WarningKind,
17};
18use crate::lexer::LexerVisitor;
19use crate::lexer::Token;
20use crate::lexer::TokenFlags;
21use crate::lexer::TokenKind;
22use crate::lexer::TokenStream;
23
24#[derive(Debug, Default)]
26pub struct DashedIdentCollector {
27 occurrences: Vec<Range>,
28 enabled: bool,
29}
30
31impl DashedIdentCollector {
32 #[inline(always)]
33 fn set_enabled(&mut self, enabled: bool) {
34 self.enabled = enabled;
35 }
36
37 fn reserve(&mut self, additional: usize) {
38 self.occurrences.reserve(additional);
39 }
40
41 fn take(&mut self) -> Vec<Range> {
42 std::mem::take(&mut self.occurrences)
43 }
44
45 fn discard_last(&mut self, range: Range) {
46 if self.occurrences.last() == Some(&range) {
47 self.occurrences.pop();
48 }
49 }
50}
51
52impl LexerVisitor for DashedIdentCollector {
53 #[inline(always)]
54 fn visit_ident(&mut self, name: &str, range: Range) {
55 if self.enabled && is_dashed_ident(name) {
56 self.occurrences.push(range);
57 }
58 }
59}
60
61type DependencyLexer<'s> = Lexer<'s, DashedIdentCollector>;
62type DependencyTokenStream<'a, 's> = TokenStream<'a, 's, DashedIdentCollector>;
63
64#[derive(Debug)]
65enum Scope<'s> {
66 TopLevel,
67 InBlock,
68 InAtImport(ImportData<'s>),
69 AtImportInvalid,
70 AtNamespaceInvalid,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74enum ScanContext {
75 TopLevel,
76 BlockItem,
77 Selector,
78 DeclarationName,
79 GenericValue,
80 SpecialValue(PropertyKind),
81 AtRule,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub(crate) enum PropertyKind {
86 Generic,
87 Animation,
88 ListStyle,
89 FontPalette,
90 Container,
91 Grid,
92 Composes,
93 CustomProperty,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97enum AtRuleKind {
98 Namespace,
99 Import,
100 Charset,
101 Value,
102 Keyframes,
103 Container,
104 Function,
105 Property,
106 CounterStyle,
107 FontPaletteValues,
108 Scope,
109 Other,
110}
111
112impl ScanContext {
113 fn for_property(property: PropertyKind) -> Self {
114 if property == PropertyKind::Generic {
115 Self::GenericValue
116 } else {
117 Self::SpecialValue(property)
118 }
119 }
120}
121
122#[derive(Debug)]
123struct ImportData<'s> {
124 start: Pos,
125 prelude: ImportPrelude<'s>,
126 url: Option<&'s str>,
127 url_flags: TokenFlags,
128 url_range: Option<Range>,
129 supports: ImportDataSupports<'s>,
130 layer: ImportDataLayer<'s>,
131}
132
133impl ImportData<'_> {
134 pub fn new(start: Pos) -> Self {
135 Self {
136 start,
137 prelude: ImportPrelude::default(),
138 url: None,
139 url_flags: TokenFlags::ascii(),
140 url_range: None,
141 supports: ImportDataSupports::None,
142 layer: ImportDataLayer::None,
143 }
144 }
145
146 pub fn in_supports(&self) -> bool {
147 matches!(self.supports, ImportDataSupports::InSupports { .. })
148 }
149
150 pub fn layer_range(&self) -> Option<&Range> {
151 let ImportDataLayer::EndLayer { range, .. } = &self.layer else {
152 return None;
153 };
154 Some(range)
155 }
156
157 pub fn supports_range(&self) -> Option<&Range> {
158 let ImportDataSupports::EndSupports { range, .. } = &self.supports else {
159 return None;
160 };
161 Some(range)
162 }
163}
164
165#[derive(Debug, Default)]
166struct ImportPrelude<'s>(SmallVec<[ImportPreludeNode<'s>; 2]>);
167
168impl<'s> ImportPrelude<'s> {
169 pub fn push(&mut self, node: ImportPreludeNode<'s>) {
170 self.0.push(node);
171 }
172
173 pub fn is_empty(&self) -> bool {
174 self.0.is_empty()
175 }
176
177 pub fn icss_import_url(&self) -> Option<(&'s str, &Range)> {
178 let [ImportPreludeNode::IcssUrlCandidate { name, range }] = self.0.as_slice() else {
179 return None;
180 };
181 Some((name, range))
182 }
183
184 pub fn first_non_url_before(&self, url_range: &Range) -> Option<&Range> {
185 self.0.iter().find_map(|node| {
186 let range = node.range();
187 if range.start >= url_range.start || matches!(node, ImportPreludeNode::Url { .. }) {
188 None
189 } else {
190 Some(range)
191 }
192 })
193 }
194}
195
196#[derive(Debug)]
197enum ImportPreludeNode<'s> {
198 IcssUrlCandidate { name: &'s str, range: Range },
199 Url { range: Range },
200 Layer { range: Range },
201 Supports { range: Range },
202 Other { range: Range },
203}
204
205impl ImportPreludeNode<'_> {
206 fn range(&self) -> &Range {
207 match self {
208 Self::IcssUrlCandidate { range, .. }
209 | Self::Url { range }
210 | Self::Layer { range }
211 | Self::Supports { range }
212 | Self::Other { range } => range,
213 }
214 }
215}
216
217#[derive(Debug)]
218enum ImportDataSupports<'s> {
219 None,
220 InSupports,
221 EndSupports { value: &'s str, range: Range },
222}
223
224#[derive(Debug)]
225enum ImportDataLayer<'s> {
226 None,
227 EndLayer { value: &'s str, range: Range },
228}
229
230#[derive(Debug, Default)]
231struct BalancedStack(SmallVec<[BalancedItem; 3]>);
232
233impl BalancedStack {
234 pub fn len(&self) -> usize {
235 self.0.len()
236 }
237
238 pub fn last(&self) -> Option<&BalancedItem> {
239 self.0.last()
240 }
241
242 pub fn is_empty(&self) -> bool {
243 self.0.is_empty()
244 }
245
246 pub fn push(&mut self, item: BalancedItem, mode_data: Option<&mut ModeData>) {
247 if let Some(mode_data) = mode_data {
248 if item.kind.is_mode_local() {
249 mode_data.set_current_mode(Mode::Local);
250 } else if item.kind.is_mode_global() {
251 mode_data.set_current_mode(Mode::Global);
252 }
253
254 if item.kind.is_mode_function() {
255 mode_data.inside_mode_function += 1;
256 } else if item.kind.is_mode_class() {
257 mode_data.inside_mode_class += 1;
258 }
259 }
260 self.0.push(item);
261 }
262
263 pub fn pop(&mut self, mode_data: Option<&mut ModeData>) -> Option<BalancedItem> {
264 let item = self.0.pop()?;
265 if let Some(mode_data) = mode_data {
266 if item.kind.is_mode_function() {
267 mode_data.inside_mode_function -= 1;
268 } else if item.kind.is_mode_class() {
269 mode_data.inside_mode_class -= 1;
270 }
271 self.update_current_mode(mode_data);
272 }
273 Some(item)
274 }
275
276 pub fn pop_without_moda_data(&mut self) -> Option<BalancedItem> {
277 self.0.pop()
278 }
279
280 pub fn pop_mode_pseudo_class(&mut self, mode_data: &mut ModeData) {
281 loop {
282 if let Some(last) = self.0.last()
283 && matches!(
284 last.kind,
285 BalancedItemKind::LocalClass | BalancedItemKind::GlobalClass
286 )
287 {
288 mode_data.inside_mode_class -= 1;
289 self.0.pop();
290 continue;
291 }
292 break;
293 }
294 self.update_current_mode(mode_data);
295 }
296
297 pub fn update_current_mode(&self, mode_data: &mut ModeData) {
298 mode_data.set_current_mode(self.topmost_mode(mode_data));
299 }
300
301 pub fn update_property_mode(&self, mode_data: &mut ModeData) {
302 mode_data.set_property_mode(self.topmost_mode(mode_data));
303 }
304
305 fn topmost_mode(&self, mode_data: &ModeData) -> Mode {
306 let mut iter = self.0.iter();
307 loop {
308 if let Some(last) = iter.next_back() {
309 if matches!(
310 last.kind,
311 BalancedItemKind::LocalFn | BalancedItemKind::LocalClass
312 ) {
313 return Mode::Local;
314 } else if matches!(
315 last.kind,
316 BalancedItemKind::GlobalFn | BalancedItemKind::GlobalClass
317 ) {
318 return Mode::Global;
319 }
320 } else {
321 return mode_data.default_mode();
322 }
323 }
324 }
325}
326
327#[derive(Debug)]
328struct BalancedItem {
329 kind: BalancedItemKind,
330 range: Range,
331}
332
333impl BalancedItem {
334 pub fn new(name: &str, flags: TokenFlags, start: Pos, end: Pos) -> Self {
335 let mut normalized = [0; MAX_CSS_KEYWORD_LEN];
336 let kind = if flags.has_escape() {
337 decode_css_keyword(name, &mut normalized)
338 .map(BalancedItemKind::new)
339 .unwrap_or(BalancedItemKind::Other)
340 } else {
341 lowercase_ascii_keyword(name, &mut normalized)
342 .map(BalancedItemKind::new)
343 .unwrap_or(BalancedItemKind::Other)
344 };
345 Self {
346 kind,
347 range: Range::new(start, end),
348 }
349 }
350
351 pub fn new_normalized(name: &str, start: Pos, end: Pos) -> Self {
352 Self {
353 kind: BalancedItemKind::new(name),
354 range: Range::new(start, end),
355 }
356 }
357
358 pub fn new_other(start: Pos, end: Pos) -> Self {
359 Self {
360 kind: BalancedItemKind::Other,
361 range: Range::new(start, end),
362 }
363 }
364
365 pub fn new_curly(start: Pos, end: Pos) -> Self {
366 Self {
367 kind: BalancedItemKind::Curly,
368 range: Range::new(start, end),
369 }
370 }
371}
372
373#[derive(Debug)]
374enum BalancedItemKind {
375 Url,
376 ImageSet,
377 Layer,
378 Supports,
379 PaletteMix,
380 LocalFn,
381 GlobalFn,
382 LocalClass,
383 GlobalClass,
384 Curly,
385 Other,
386}
387
388impl BalancedItemKind {
389 pub fn new(name: &str) -> Self {
390 match name {
391 "url(" => Self::Url,
392 "image-set(" => Self::ImageSet,
393 _ if strip_vendor_prefix(name) == Some("image-set(") => Self::ImageSet,
394 "layer(" => Self::Layer,
395 "supports(" => Self::Supports,
396 "palette-mix(" => Self::PaletteMix,
397 ":local(" => Self::LocalFn,
398 ":global(" => Self::GlobalFn,
399 ":local" => Self::LocalClass,
400 ":global" => Self::GlobalClass,
401 _ => Self::Other,
402 }
403 }
404
405 pub fn is_mode_local(&self) -> bool {
406 matches!(self, Self::LocalFn | Self::LocalClass)
407 }
408
409 pub fn is_mode_global(&self) -> bool {
410 matches!(self, Self::GlobalFn | Self::GlobalClass)
411 }
412
413 pub fn is_mode_function(&self) -> bool {
414 matches!(self, Self::LocalFn | Self::GlobalFn)
415 }
416
417 pub fn is_mode_class(&self) -> bool {
418 matches!(self, Self::LocalClass | Self::GlobalClass)
419 }
420}
421
422fn trivia_only(input: &str) -> bool {
423 if input.is_empty() {
424 return false;
425 }
426 let bytes = input.as_bytes();
427 let mut position = 0;
428 while position < bytes.len() {
429 if is_css_space_byte(bytes[position]) {
430 position += 1;
431 continue;
432 }
433 if position + 1 < bytes.len() && bytes[position] == b'/' && bytes[position + 1] == b'*' {
434 position += 2;
435 while position + 1 < bytes.len()
436 && !(bytes[position] == b'*' && bytes[position + 1] == b'/')
437 {
438 position += 1;
439 }
440 if position + 1 >= bytes.len() {
441 return false;
442 }
443 position += 2;
444 continue;
445 }
446 return false;
447 }
448 true
449}
450
451fn token_text(input: &str, token: Token) -> &str {
452 Lexer::slice_range(input, &token.range).unwrap_or("")
453}
454
455fn is_open_token(kind: TokenKind) -> bool {
456 matches!(
457 kind,
458 TokenKind::Function
459 | TokenKind::LeftParenthesis
460 | TokenKind::LeftSquareBracket
461 | TokenKind::LeftCurlyBracket
462 )
463}
464
465fn is_close_token(kind: TokenKind) -> bool {
466 matches!(
467 kind,
468 TokenKind::RightParenthesis | TokenKind::RightSquareBracket | TokenKind::RightCurlyBracket
469 )
470}
471
472fn ident_like_range(token: Token) -> Option<Range> {
473 match token.kind {
474 TokenKind::Ident => Some(token.range),
475 TokenKind::Function => Some(token.value_range),
476 _ => None,
477 }
478}
479
480#[derive(Debug, Clone, Copy)]
484struct ValueAtRuleSplit {
485 split: Pos,
486 end: Pos,
487 prev_end: Pos,
488 next_start: Option<Pos>,
489}
490
491struct ValueAtRuleStream<'s> {
495 input: &'s str,
496 depth: u32,
497 params_end: Pos,
498 first_significant: Option<(Pos, Pos)>,
499 significant_count: u32,
500 first_colon: Option<ValueAtRuleSplit>,
501 first_colon_tokens_after: u32,
502 item_start: Option<Pos>,
503 item_end: Pos,
504 item_colon: Option<ValueAtRuleSplit>,
505 item_as: Option<ValueAtRuleSplit>,
506 last_significant: Option<Token>,
507 penultimate_significant: Option<Token>,
508 from_pos: Option<Pos>,
509 from_prev_end: Option<Pos>,
510}
511
512impl<'s> ValueAtRuleStream<'s> {
513 fn new(input: &'s str) -> Self {
514 Self {
515 input,
516 depth: 0,
517 params_end: 0,
518 first_significant: None,
519 significant_count: 0,
520 first_colon: None,
521 first_colon_tokens_after: 0,
522 item_start: None,
523 item_end: 0,
524 item_colon: None,
525 item_as: None,
526 last_significant: None,
527 penultimate_significant: None,
528 from_pos: None,
529 from_prev_end: None,
530 }
531 }
532
533 fn push(&mut self, context: &mut DependencyContext<'s>, token: Token) {
534 if matches!(token.kind, TokenKind::Comment | TokenKind::BadComment) {
535 self.params_end = token.range.end;
536 return;
537 }
538 self.params_end = token.range.end;
539 if self.first_significant.is_none() {
540 self.first_significant = Some((token.range.start, token.range.end));
541 }
542 self.significant_count += 1;
543 let had_first_colon = self.first_colon.is_some();
544
545 self.depth = (self.depth + u32::from(is_open_token(token.kind)))
546 .saturating_sub(u32::from(is_close_token(token.kind)));
547 let at_top = self.depth == 0;
548 let is_ident = token.kind == TokenKind::Ident;
549 let text = if is_ident {
550 self.input
551 .get(token.range.start as usize..token.range.end as usize)
552 .unwrap_or("")
553 } else {
554 ""
555 };
556 if at_top {
557 if token.kind == TokenKind::Colon {
558 let split = ValueAtRuleSplit {
559 split: token.range.start,
560 end: token.range.end,
561 prev_end: self
562 .last_significant
563 .map_or(token.range.start, |previous| previous.range.end),
564 next_start: None,
565 };
566 if self.first_colon.is_none() {
567 self.first_colon = Some(split);
568 }
569 if self.item_colon.is_none() {
570 self.item_colon = Some(split);
571 }
572 }
573 if is_ident && text.eq_ignore_ascii_case("as") {
574 self.item_as = Some(ValueAtRuleSplit {
575 split: token.range.start,
576 end: token.range.end,
577 prev_end: self
578 .last_significant
579 .map_or(token.range.start, |previous| previous.range.end),
580 next_start: None,
581 });
582 }
583 if is_ident
584 && text.eq_ignore_ascii_case("from")
585 && let Some(previous) = self.last_significant
586 {
587 let gap = self
588 .input
589 .get(previous.range.end as usize..token.range.start as usize)
590 .unwrap_or("");
591 if trivia_only(gap) {
592 self.from_pos = Some(token.range.start);
593 self.from_prev_end = Some(previous.range.end);
594 }
595 }
596 self.penultimate_significant = self.last_significant;
597 self.last_significant = Some(token);
598 }
599 if had_first_colon {
600 self.first_colon_tokens_after += 1;
601 }
602
603 if token.kind == TokenKind::Comma && at_top {
604 self.finish_item(context, self.item_end);
605 self.item_start = None;
606 self.item_colon = None;
607 self.item_as = None;
608 } else {
609 if self.item_start.is_none() {
610 self.item_start = Some(token.range.start);
611 }
612 self.item_end = token.range.end;
613 if let Some(split) = self.item_colon.as_mut()
614 && split.next_start.is_none()
615 && split.split != token.range.start
616 {
617 split.next_start = Some(token.range.start);
618 }
619 if let Some(split) = self.item_as.as_mut()
620 && split.next_start.is_none()
621 && split.split != token.range.start
622 {
623 split.next_start = Some(token.range.start);
624 }
625 }
626 }
627
628 fn finish_item(&mut self, context: &mut DependencyContext<'s>, end: Pos) {
629 let Some(item_start) = self.item_start else {
630 return;
631 };
632 if end.saturating_sub(item_start) >= 2
633 && self.input.as_bytes()[item_start as usize] == b'('
634 && self.input.as_bytes()[end as usize - 1] == b')'
635 {
636 self.parse_paren_items(context, item_start + 1, end - 1);
637 } else {
638 let item = self.build_item(item_start, end, self.item_colon, self.item_as);
639 if !item.local_name().is_empty() || !item.import_name().is_empty() {
640 context.push_value_at_rule_import_item(item);
641 }
642 }
643 }
644
645 fn build_item(
646 &self,
647 start: Pos,
648 end: Pos,
649 colon: Option<ValueAtRuleSplit>,
650 as_split: Option<ValueAtRuleSplit>,
651 ) -> ValueAtRuleImportItem<'s> {
652 let slice = |a: Pos, b: Pos| -> &'s str {
653 if a >= b {
654 ""
655 } else {
656 &self.input[a as usize..b as usize]
657 }
658 };
659 if let Some(split) = colon {
660 return ValueAtRuleImportItem::new(
661 slice(start, split.prev_end),
662 split
663 .next_start
664 .map_or("", |next_start| slice(next_start, end)),
665 );
666 }
667 if let Some(split) = as_split {
668 let import_name = slice(start, split.prev_end);
669 let local_name = split
670 .next_start
671 .map_or("", |next_start| slice(next_start, end));
672 if !import_name.is_empty() && !local_name.is_empty() {
673 return ValueAtRuleImportItem::new(local_name, import_name);
674 }
675 }
676 let value = slice(start, end);
677 ValueAtRuleImportItem::new(value, value)
678 }
679
680 fn parse_paren_items(&mut self, context: &mut DependencyContext<'s>, start: Pos, end: Pos) {
684 let slice = &self.input[start as usize..end as usize];
685 let mut lexer = Lexer::new(slice, ());
686 let mut tokens: SmallVec<[Token; 8]> = SmallVec::new();
687 loop {
688 let token = lexer.next_token();
689 if token.kind == TokenKind::Eof {
690 break;
691 }
692 if matches!(
693 token.kind,
694 TokenKind::Comment | TokenKind::BadComment | TokenKind::WhiteSpace
695 ) {
696 continue;
697 }
698 tokens.push(token);
699 }
700 let mut item_start = 0;
701 let mut item_depth = 0u32;
702 for (index, token) in tokens.iter().copied().enumerate() {
703 if is_close_token(token.kind) {
704 item_depth = item_depth.saturating_sub(1);
705 }
706 if token.kind == TokenKind::Comma && item_depth == 0 {
707 self.push_parsed_item(context, &tokens, item_start, index, slice);
708 item_start = index + 1;
709 }
710 if is_open_token(token.kind) {
711 item_depth += 1;
712 }
713 }
714 self.push_parsed_item(context, &tokens, item_start, tokens.len(), slice);
715 }
716
717 fn push_parsed_item(
718 &mut self,
719 context: &mut DependencyContext<'s>,
720 tokens: &[Token],
721 start: usize,
722 end: usize,
723 slice: &'s str,
724 ) {
725 let mut depth = 0u32;
726 let mut colon_index = None;
727 let mut as_index = None;
728 for (index, token) in tokens[start..end].iter().copied().enumerate() {
729 let index = start + index;
730 if is_close_token(token.kind) {
731 depth = depth.saturating_sub(1);
732 }
733 if depth == 0 {
734 if token.kind == TokenKind::Colon && colon_index.is_none() {
735 colon_index = Some(index);
736 }
737 if token.kind == TokenKind::Ident
738 && token_text(slice, token).eq_ignore_ascii_case("as")
739 {
740 as_index = Some(index);
741 }
742 }
743 if is_open_token(token.kind) {
744 depth += 1;
745 }
746 }
747 let span = |a: usize, b: usize| -> &'s str {
748 if a >= b {
749 ""
750 } else {
751 &slice[tokens[a].range.start as usize..tokens[b - 1].range.end as usize]
752 }
753 };
754 let item = if let Some(index) = colon_index {
755 ValueAtRuleImportItem::new(span(start, index), span(index + 1, end))
756 } else if let Some(index) = as_index {
757 let import_name = span(start, index);
758 let local_name = span(index + 1, end);
759 if !import_name.is_empty() && !local_name.is_empty() {
760 ValueAtRuleImportItem::new(local_name, import_name)
761 } else {
762 ValueAtRuleImportItem::new("", "")
763 }
764 } else {
765 let value = span(start, end);
766 ValueAtRuleImportItem::new(value, value)
767 };
768 if !item.local_name().is_empty() || !item.import_name().is_empty() {
769 context.push_value_at_rule_import_item(item);
770 }
771 }
772
773 fn last_two(&self) -> Option<(Token, Token)> {
776 Some((self.penultimate_significant?, self.last_significant?))
777 }
778}
779
780#[derive(Debug)]
781pub struct ModeData<'s> {
782 default: Mode,
783 current: Mode,
784 property: Mode,
785 resulting_global: Option<Pos>,
786 pure_global: Option<Pos>,
787 pure_no_check: bool,
788 pure_ignore_pending: bool,
789 pure_ignored_block_nesting_level: Option<u32>,
790 composes_local_classes: ComposesLocalClasses<'s>,
791 inside_mode_function: u32,
792 inside_mode_class: u32,
793}
794
795impl ModeData<'_> {
796 pub fn new(default: Mode) -> Self {
797 Self {
798 default,
799 current: default,
800 property: default,
801 resulting_global: None,
802 pure_global: Some(0),
803 pure_no_check: false,
804 pure_ignore_pending: false,
805 pure_ignored_block_nesting_level: None,
806 composes_local_classes: ComposesLocalClasses::default(),
807 inside_mode_function: 0,
808 inside_mode_class: 0,
809 }
810 }
811
812 pub fn is_pure_mode(&self) -> bool {
813 matches!(self.default, Mode::Pure)
814 }
815
816 pub fn mark_pure_ignore(&mut self) {
817 if self.is_pure_mode() {
818 self.pure_ignore_pending = true;
819 }
820 }
821
822 pub fn mark_pure_no_check(&mut self) {
823 if self.is_pure_mode() {
824 self.pure_no_check = true;
825 }
826 }
827
828 pub fn is_pure_check_disabled(&self) -> bool {
829 self.pure_no_check
830 || self.pure_ignore_pending
831 || self.pure_ignored_block_nesting_level.is_some()
832 }
833
834 pub fn enter_block(&mut self, block_nesting_level: u32) {
835 if self.pure_ignore_pending {
836 self.pure_ignore_pending = false;
837 if self.pure_ignored_block_nesting_level.is_none() {
838 self.pure_ignored_block_nesting_level = Some(block_nesting_level);
839 }
840 }
841 }
842
843 pub fn clear_pure_ignore_pending(&mut self) {
844 self.pure_ignore_pending = false;
845 }
846
847 pub fn exit_block(&mut self, block_nesting_level: u32) {
848 if self
849 .pure_ignored_block_nesting_level
850 .is_some_and(|level| block_nesting_level < level)
851 {
852 self.pure_ignored_block_nesting_level = None;
853 }
854 }
855
856 pub fn is_current_local_mode(&self) -> bool {
857 match self.current {
858 Mode::Local | Mode::Pure => true,
859 Mode::Global | Mode::Css => false,
860 }
861 }
862
863 pub fn is_property_local_mode(&self) -> bool {
864 match self.property {
865 Mode::Local | Mode::Pure => true,
866 Mode::Global | Mode::Css => false,
867 }
868 }
869
870 pub fn default_mode(&self) -> Mode {
871 self.default
872 }
873
874 pub fn set_current_mode(&mut self, mode: Mode) {
875 self.current = mode;
876 }
877
878 pub fn set_property_mode(&mut self, mode: Mode) {
879 self.property = mode;
880 }
881
882 pub fn is_inside_mode_function(&self) -> bool {
883 self.inside_mode_function > 0
884 }
885
886 pub fn is_inside_mode_class(&self) -> bool {
887 self.inside_mode_class > 0
888 }
889
890 pub fn is_mode_explicit(&self) -> bool {
891 self.is_inside_mode_function() || self.is_inside_mode_class()
892 }
893}
894
895#[derive(Debug, Default, Clone)]
896struct ComposesLocalClasses<'s> {
897 is_single: SingleLocalClass,
898 local_classes: SmallVec<[&'s str; 2]>,
899}
900
901impl<'s> ComposesLocalClasses<'s> {
902 pub fn get_valid_local_classes(
903 &mut self,
904 lexer: &DependencyLexer<'s>,
905 ) -> Option<SmallVec<[&'s str; 2]>> {
906 if let SingleLocalClass::Single(range) = &self.is_single {
907 let mut local_classes = self.local_classes.clone();
908 local_classes.push(lexer.slice(range.start, range.end)?);
909 Some(local_classes)
910 } else {
911 self.reset_to_initial();
912 None
913 }
914 }
915
916 pub fn invalidate(&mut self) {
917 if !matches!(self.is_single, SingleLocalClass::AtKeyword) {
918 self.is_single = SingleLocalClass::Invalid;
919 self.local_classes.clear();
920 }
921 }
922
923 pub fn find_local_class(&mut self, start: Pos, end: Pos) {
924 match self.is_single {
925 SingleLocalClass::Initial => {
926 self.is_single = SingleLocalClass::Single(Range::new(start, end))
927 }
928 SingleLocalClass::Single(_) => {
929 self.is_single = SingleLocalClass::Invalid;
930 self.local_classes.clear();
931 }
932 _ => {}
933 };
934 }
935
936 pub fn find_at_keyword(&mut self) {
937 self.is_single = SingleLocalClass::AtKeyword;
938 self.local_classes.clear();
939 }
940
941 pub fn reset_to_initial(&mut self) {
942 self.is_single = SingleLocalClass::Initial;
943 self.local_classes.clear();
944 }
945
946 pub fn find_comma(&mut self, lexer: &DependencyLexer<'s>) -> Option<()> {
947 if let SingleLocalClass::Single(range) = &self.is_single {
948 self.local_classes
949 .push(lexer.slice(range.start, range.end)?);
950 self.is_single = SingleLocalClass::Initial
951 } else {
952 self.is_single = SingleLocalClass::Invalid;
953 }
954 Some(())
955 }
956}
957
958#[derive(Debug, Default, Clone)]
959enum SingleLocalClass {
960 #[default]
961 Initial,
962 Single(Range),
963 AtKeyword,
964 Invalid,
965}
966
967#[derive(Debug)]
968struct InProperty<T: ReservedValues> {
969 reserved: T,
970 rename: Option<Range>,
971 balanced_len: usize,
972}
973
974impl<T: ReservedValues> InProperty<T> {
975 pub fn new(reserved: T, balanced_len: usize) -> Self {
976 Self {
977 reserved,
978 rename: None,
979 balanced_len,
980 }
981 }
982
983 fn check_reserved(&mut self, ident: &str, flags: TokenFlags) -> bool {
984 self.reserved.check(ident, flags)
985 }
986
987 pub fn reset_reserved(&mut self) {
988 self.reserved.reset();
989 }
990
991 pub fn set_rename(&mut self, ident: &str, flags: TokenFlags, range: Range) {
992 if self.check_reserved(ident, flags) {
993 self.rename = Some(range);
994 }
995 }
996
997 pub fn take_rename(&mut self, balanced_len: usize) -> Option<Range> {
998 if balanced_len != self.balanced_len {
1000 return None;
1001 }
1002 std::mem::take(&mut self.rename)
1003 }
1004}
1005
1006trait ReservedValues {
1007 fn check(&mut self, ident: &str, flags: TokenFlags) -> bool;
1008 fn reset(&mut self);
1009}
1010
1011#[derive(Debug, Default)]
1012struct AnimationReserved {
1013 bits: u32,
1014}
1015
1016impl ReservedValues for AnimationReserved {
1017 fn check(&mut self, ident: &str, flags: TokenFlags) -> bool {
1018 let mut lowercase = [0; MAX_CSS_KEYWORD_LEN];
1019 let ident = if flags.has_escape() {
1020 decode_css_keyword(ident, &mut lowercase)
1021 } else {
1022 lowercase_ascii_keyword(ident, &mut lowercase)
1023 };
1024 let Some(ident) = ident else {
1025 return true;
1026 };
1027 match ident {
1028 "normal" => self.check_and_update(Self::NORMAL),
1029 "reverse" => self.check_and_update(Self::REVERSE),
1030 "alternate" => self.check_and_update(Self::ALTERNATE),
1031 "alternate-reverse" => self.check_and_update(Self::ALTERNATE_REVERSE),
1032 "forwards" => self.check_and_update(Self::FORWARDS),
1033 "backwards" => self.check_and_update(Self::BACKWARDS),
1034 "both" => self.check_and_update(Self::BOTH),
1035 "infinite" => self.check_and_update(Self::INFINITE),
1036 "paused" => self.check_and_update(Self::PAUSED),
1037 "running" => self.check_and_update(Self::RUNNING),
1038 "ease" => self.check_and_update(Self::EASE),
1039 "ease-in" => self.check_and_update(Self::EASE_IN),
1040 "ease-out" => self.check_and_update(Self::EASE_OUT),
1041 "ease-in-out" => self.check_and_update(Self::EASE_IN_OUT),
1042 "linear" => self.check_and_update(Self::LINEAR),
1043 "step-end" => self.check_and_update(Self::STEP_END),
1044 "step-start" => self.check_and_update(Self::STEP_START),
1045 "none" |
1047 "initial" | "inherit" | "unset" | "revert" | "revert-layer" => false,
1049 _ => true,
1050 }
1051 }
1052
1053 fn reset(&mut self) {
1054 self.bits = 0;
1055 }
1056}
1057
1058impl AnimationReserved {
1059 const NORMAL: u32 = 1 << 0;
1060 const REVERSE: u32 = 1 << 1;
1061 const ALTERNATE: u32 = 1 << 2;
1062 const ALTERNATE_REVERSE: u32 = 1 << 3;
1063 const FORWARDS: u32 = 1 << 4;
1064 const BACKWARDS: u32 = 1 << 5;
1065 const BOTH: u32 = 1 << 6;
1066 const INFINITE: u32 = 1 << 7;
1067 const PAUSED: u32 = 1 << 8;
1068 const RUNNING: u32 = 1 << 9;
1069 const EASE: u32 = 1 << 10;
1070 const EASE_IN: u32 = 1 << 11;
1071 const EASE_OUT: u32 = 1 << 12;
1072 const EASE_IN_OUT: u32 = 1 << 13;
1073 const LINEAR: u32 = 1 << 14;
1074 const STEP_END: u32 = 1 << 15;
1075 const STEP_START: u32 = 1 << 16;
1076
1077 fn check_and_update(&mut self, bit: u32) -> bool {
1078 if self.bits & bit == bit {
1079 return true;
1080 }
1081 self.bits |= bit;
1082 false
1083 }
1084}
1085
1086#[derive(Debug, Default)]
1087struct ListStyleReserved;
1088
1089impl ReservedValues for ListStyleReserved {
1090 fn check(&mut self, ident: &str, flags: TokenFlags) -> bool {
1091 let mut lowercase = [0; MAX_CSS_KEYWORD_LEN];
1092 let ident = if flags.has_escape() {
1093 decode_css_keyword(ident, &mut lowercase)
1094 } else {
1095 lowercase_ascii_keyword(ident, &mut lowercase)
1096 };
1097 let Some(ident) = ident else {
1098 return true;
1099 };
1100 match ident {
1101 "decimal"
1103 | "decimal-leading-zero"
1104 | "arabic-indic"
1105 | "armenian"
1106 | "upper-armenian"
1107 | "lower-armenian"
1108 | "bengali"
1109 | "cambodian"
1110 | "khmer"
1111 | "cjk-decimal"
1112 | "devanagari"
1113 | "georgian"
1114 | "gujarati"
1115 | "gurmukhi"
1116 | "hebrew"
1117 | "kannada"
1118 | "lao"
1119 | "malayalam"
1120 | "mongolian"
1121 | "myanmar"
1122 | "oriya"
1123 | "persian"
1124 | "lower-roman"
1125 | "upper-roman"
1126 | "tamil"
1127 | "telugu"
1128 | "thai"
1129 | "tibetan"
1130 | "lower-alpha"
1132 | "lower-latin"
1133 | "upper-alpha"
1134 | "upper-latin"
1135 | "lower-greek"
1136 | "hiragana"
1137 | "hiragana-iroha"
1138 | "katakana"
1139 | "katakana-iroha"
1140 | "disc"
1142 | "circle"
1143 | "square"
1144 | "disclosure-open"
1145 | "disclosure-closed"
1146 | "cjk-earthly-branch"
1148 | "cjk-heavenly-stem"
1149 | "japanese-informal"
1151 | "japanese-formal"
1152 | "korean-hangul-formal"
1153 | "korean-hanja-informal"
1154 | "korean-hanja-formal"
1155 | "simp-chinese-informal"
1156 | "simp-chinese-formal"
1157 | "trad-chinese-informal"
1158 | "trad-chinese-formal"
1159 | "ethiopic-numeric"
1160 | "none"
1162 | "initial"
1164 | "inherit"
1165 | "unset"
1166 | "revert"
1167 | "revert-layer" => false,
1168 _ => true,
1169 }
1170 }
1171
1172 fn reset(&mut self) {}
1173}
1174
1175#[derive(Debug, Default)]
1176struct FontPaletteReserved;
1177
1178impl ReservedValues for FontPaletteReserved {
1179 fn check(&mut self, ident: &str, _flags: TokenFlags) -> bool {
1180 ident.starts_with("--")
1181 }
1182
1183 fn reset(&mut self) {}
1184}
1185
1186#[derive(Debug, Default)]
1187struct ContainerReserved;
1188
1189impl ReservedValues for ContainerReserved {
1190 fn check(&mut self, ident: &str, flags: TokenFlags) -> bool {
1191 let mut lowercase = [0; MAX_CSS_KEYWORD_LEN];
1192 let ident = if flags.has_escape() {
1193 decode_css_keyword(ident, &mut lowercase)
1194 } else {
1195 lowercase_ascii_keyword(ident, &mut lowercase)
1196 };
1197 let Some(ident) = ident else {
1198 return true;
1199 };
1200 !matches!(
1201 ident,
1202 "normal"
1203 | "size"
1204 | "inline-size"
1205 | "scroll-state"
1206 | "none"
1207 | "initial"
1208 | "inherit"
1209 | "unset"
1210 | "revert"
1211 | "revert-layer"
1212 )
1213 }
1214
1215 fn reset(&mut self) {}
1216}
1217
1218#[derive(Debug, Clone, Copy)]
1219enum GridPropertyKind {
1220 Generic,
1221 TemplateLike,
1222 TemplateAreas,
1223}
1224
1225fn grid_property_kind(ident: &str) -> Option<GridPropertyKind> {
1226 match ident {
1227 "grid" | "grid-area" | "grid-column" | "grid-column-end" | "grid-column-start"
1228 | "grid-row" | "grid-row-end" | "grid-row-start" => Some(GridPropertyKind::Generic),
1229 "grid-template" | "grid-template-columns" | "grid-template-rows" => {
1230 Some(GridPropertyKind::TemplateLike)
1231 }
1232 "grid-template-areas" => Some(GridPropertyKind::TemplateAreas),
1233 _ => None,
1234 }
1235}
1236
1237fn is_reserved_grid_ident(ident: &str, flags: TokenFlags) -> bool {
1238 let mut lowercase = [0; MAX_CSS_KEYWORD_LEN];
1239 let ident = if flags.has_escape() {
1240 decode_css_keyword(ident, &mut lowercase)
1241 } else {
1242 lowercase_ascii_keyword(ident, &mut lowercase)
1243 };
1244 let Some(ident) = ident else {
1245 return false;
1246 };
1247 matches!(
1248 ident,
1249 "auto"
1250 | "span"
1251 | "auto-flow"
1252 | "dense"
1253 | "row"
1254 | "column"
1255 | "none"
1256 | "subgrid"
1257 | "masonry"
1258 | "max-content"
1259 | "min-content"
1260 | "initial"
1261 | "inherit"
1262 | "unset"
1263 | "revert"
1264 | "revert-layer"
1265 )
1266}
1267
1268pub(crate) fn special_value_is_candidate(
1276 property: PropertyKind,
1277 ident: &str,
1278 icss_symbols: Option<&FxHashSet<&str>>,
1279) -> bool {
1280 if icss_symbols.is_some_and(|symbols| symbols.contains(ident)) {
1281 return true;
1282 }
1283 match property {
1284 PropertyKind::ListStyle => ListStyleReserved.check(ident, TokenFlags::ascii()),
1285 PropertyKind::FontPalette => ident.starts_with("--"),
1286 PropertyKind::Container => ContainerReserved.check(ident, TokenFlags::ascii()),
1287 PropertyKind::Grid => !is_reserved_grid_ident(ident, TokenFlags::ascii()),
1288 PropertyKind::Animation
1289 | PropertyKind::Generic
1290 | PropertyKind::Composes
1291 | PropertyKind::CustomProperty => false,
1292 }
1293}
1294
1295fn is_reserved_container_query_ident(ident: &str, flags: TokenFlags) -> bool {
1296 let mut lowercase = [0; MAX_CSS_KEYWORD_LEN];
1297 let ident = if flags.has_escape() {
1298 decode_css_keyword(ident, &mut lowercase)
1299 } else {
1300 lowercase_ascii_keyword(ident, &mut lowercase)
1301 };
1302 let Some(ident) = ident else {
1303 return false;
1304 };
1305 matches!(ident, "none" | "and" | "or" | "not")
1306}
1307
1308fn parse_grid_template_area_ranges(input: &str, offset: Pos) -> impl Iterator<Item = Range> + '_ {
1309 let bytes = input.as_bytes();
1310 let mut i = 0usize;
1311
1312 std::iter::from_fn(move || {
1313 loop {
1314 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
1315 i += 1;
1316 }
1317 let start = i;
1318 while i < bytes.len() && !bytes[i].is_ascii_whitespace() {
1319 i += 1;
1320 }
1321 if start == i {
1322 return None;
1323 }
1324 if !input[start..i].bytes().all(|c| c == b'.') {
1325 return Some(Range::new(offset + start as Pos, offset + i as Pos));
1326 }
1327 }
1328 })
1329}
1330
1331#[derive(Debug)]
1332pub struct LexDependencies<'s, W> {
1333 dependency_context: DependencyContext<'s>,
1334 mode_data: Option<ModeData<'s>>,
1335 scope: Scope<'s>,
1336 block_nesting_level: u32,
1337 allow_import_at_rule: bool,
1338 balanced: BalancedStack,
1339 is_next_rule_prelude: bool,
1340 scan_context: ScanContext,
1341 selector_square_depth: u32,
1342 selector_fast_forward_enabled: bool,
1343 property_kind: PropertyKind,
1344 in_animation_property: Option<InProperty<AnimationReserved>>,
1345 in_list_style_property: Option<InProperty<ListStyleReserved>>,
1346 in_font_palette_property: Option<InProperty<FontPaletteReserved>>,
1347 in_container_property: Option<InProperty<ContainerReserved>>,
1348 in_grid_property: Option<GridPropertyKind>,
1349 icss_symbols: FxHashSet<&'s str>,
1350 icss_symbol_filter: [u64; 16],
1351 icss_symbol_min_len: usize,
1352 icss_symbol_max_len: usize,
1353 pending_custom_property: Option<Range>,
1354 pending_grid_property: Option<GridPropertyKind>,
1355 handle_warning: W,
1356}
1357
1358impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
1359 pub fn new(handle_warning: W, mode: Mode) -> Self {
1360 Self::with_context(DependencyContext::new(), handle_warning, mode)
1361 }
1362
1363 pub(crate) fn with_context(
1364 dependency_context: DependencyContext<'s>,
1365 handle_warning: W,
1366 mode: Mode,
1367 ) -> Self {
1368 Self {
1369 dependency_context,
1370 mode_data: if mode == Mode::Css {
1371 None
1372 } else {
1373 Some(ModeData::new(mode))
1374 },
1375 scope: Scope::TopLevel,
1376 block_nesting_level: 0,
1377 allow_import_at_rule: true,
1378 balanced: Default::default(),
1379 is_next_rule_prelude: true,
1380 scan_context: ScanContext::TopLevel,
1381 selector_square_depth: 0,
1382 selector_fast_forward_enabled: false,
1383 property_kind: PropertyKind::Generic,
1384 in_animation_property: None,
1385 in_list_style_property: None,
1386 in_font_palette_property: None,
1387 in_container_property: None,
1388 in_grid_property: None,
1389 icss_symbols: Default::default(),
1390 icss_symbol_filter: [0; 16],
1391 icss_symbol_min_len: usize::MAX,
1392 icss_symbol_max_len: 0,
1393 pending_custom_property: None,
1394 pending_grid_property: None,
1395 handle_warning,
1396 }
1397 }
1398
1399 pub fn dependency_context(&self) -> &DependencyContext<'s> {
1400 &self.dependency_context
1401 }
1402
1403 pub fn into_dependency_context(self) -> DependencyContext<'s> {
1404 self.dependency_context
1405 }
1406
1407 #[inline]
1408 fn set_scan_context(
1409 &mut self,
1410 stream: &mut DependencyTokenStream<'_, 's>,
1411 scan_context: ScanContext,
1412 ) {
1413 if self.scan_context == scan_context {
1414 return;
1415 }
1416 match self.scan_context {
1417 ScanContext::Selector => self.selector_square_depth = 0,
1418 ScanContext::AtRule => stream.reset_at_rule_scan_state(),
1419 ScanContext::SpecialValue(_) => stream.reset_special_value_scan_state(),
1420 _ => {}
1421 }
1422 self.scan_context = scan_context;
1423 }
1424
1425 pub fn lex_streaming(&mut self, source: &mut DependencyLexer<'s>) {
1427 self.selector_fast_forward_enabled = source.source_end() >= 256;
1428 let mode = self
1429 .mode_data
1430 .as_ref()
1431 .map_or(Mode::Css, ModeData::default_mode);
1432 let source_len = source.source_end() as usize;
1433 source
1434 .visitor_mut()
1435 .reserve(DependencyContext::estimated_dashed_ident_capacity(
1436 source_len, mode,
1437 ));
1438 self.dependency_context
1439 .reserve_estimated_capacity(source.source_end() as usize, mode);
1440 {
1441 let mut stream = TokenStream::from_lexer(source);
1442 let keep_comments = self.mode_data.as_ref().is_some_and(ModeData::is_pure_mode);
1443 let has_mode = self.mode_data.is_some();
1444 self.lex_streaming_inner(&mut stream, keep_comments, has_mode);
1445 }
1446 self.dependency_context
1447 .set_dashed_ident_occurrences(source.visitor_mut().take());
1448 }
1449
1450 #[inline]
1451 fn update_dashed_ident_collection(&self, stream: &mut DependencyTokenStream<'_, 's>) {
1452 let enabled = self
1453 .mode_data
1454 .as_ref()
1455 .is_some_and(ModeData::is_current_local_mode);
1456 stream.lexer_mut().visitor_mut().set_enabled(enabled);
1457 }
1458
1459 #[inline(always)]
1460 fn lex_streaming_inner(
1461 &mut self,
1462 stream: &mut DependencyTokenStream<'_, 's>,
1463 keep_comments: bool,
1464 has_mode: bool,
1465 ) {
1466 loop {
1467 self.update_dashed_ident_collection(stream);
1468 let item = stream.next(keep_comments);
1469 let token = item.token;
1470 if token.kind == TokenKind::Eof {
1471 return;
1472 }
1473
1474 let is_trivia = matches!(token.kind, TokenKind::Comment | TokenKind::BadComment);
1475 if !is_trivia && self.scan_context == ScanContext::BlockItem {
1476 self.is_next_rule_prelude = !matches!(
1477 token.kind,
1478 TokenKind::Ident | TokenKind::Function | TokenKind::RightCurlyBracket
1479 );
1480 if self.is_next_rule_prelude
1481 && self.block_nesting_level == 0
1482 && let Some(mode_data) = &mut self.mode_data
1483 {
1484 mode_data.composes_local_classes.reset_to_initial();
1485 }
1486 let scan_context = if self.is_next_rule_prelude {
1487 ScanContext::Selector
1488 } else {
1489 ScanContext::DeclarationName
1490 };
1491 self.set_scan_context(stream, scan_context);
1492 }
1493
1494 if self.scan_context == ScanContext::TopLevel
1495 && self.is_next_rule_prelude
1496 && token.kind != TokenKind::AtKeyword
1497 {
1498 self.set_scan_context(stream, ScanContext::Selector);
1499 }
1500
1501 let mut colon_next = None;
1502 let mut dot_next = None;
1503 if has_mode
1504 && self.scan_context == ScanContext::Selector
1505 && token.kind == TokenKind::Colon
1506 && stream.lexer().could_start_ident_at(token.range.end)
1507 {
1508 let first = stream.peek_significant_skipping_comments(keep_comments);
1509 if first.token.kind != TokenKind::Eof {
1510 colon_next = Some(first);
1511 }
1512 } else if has_mode
1513 && self.scan_context == ScanContext::Selector
1514 && token.kind == TokenKind::Delim
1515 && stream.lexer().byte_at(token.range.start) == Some(b'.')
1516 && stream.lexer().could_start_ident_at(token.range.end)
1517 {
1518 let next = stream.peek_significant_skipping_comments(keep_comments);
1519 if next.token.kind != TokenKind::Eof {
1520 dot_next = Some(next);
1521 }
1522 }
1523
1524 let mut result = Some(());
1525 match token.kind {
1526 TokenKind::Comment | TokenKind::BadComment => {
1527 result =
1528 self.handle_comment(stream.lexer_mut(), token.range.start, token.range.end);
1529 }
1530 TokenKind::WhiteSpace => {}
1531 TokenKind::QuotedString | TokenKind::BadString => {
1532 result = self.handle_string(
1533 stream.lexer_mut(),
1534 token.range.start,
1535 token.range.end,
1536 token.flags,
1537 );
1538 }
1539 TokenKind::Url => {
1540 result = self.handle_url(
1541 stream.lexer_mut(),
1542 token.range.start,
1543 token.range.end,
1544 token.value_range.start,
1545 token.value_range.end,
1546 token.flags,
1547 );
1548 }
1549 TokenKind::Function => {
1550 result = self.handle_function(
1551 stream,
1552 token.range.start,
1553 token.range.end,
1554 token.flags,
1555 );
1556 }
1557 TokenKind::Ident => {
1558 result =
1559 self.handle_ident(stream, token.range.start, token.range.end, token.flags);
1560 }
1561 TokenKind::AtKeyword => {
1562 result = self.handle_at_keyword(
1563 stream,
1564 token.range.start,
1565 token.range.end,
1566 token.flags,
1567 );
1568 }
1569 TokenKind::IdHash | TokenKind::Hash
1570 if !has_mode || self.scan_context != ScanContext::Selector => {}
1571 TokenKind::IdHash | TokenKind::Hash => {
1572 let id_end = if token.kind == TokenKind::Hash {
1573 token.range.start + 1
1574 } else {
1575 token.range.end
1576 };
1577 result =
1578 self.handle_id(stream.lexer_mut(), token.range.start, id_end, token.flags);
1579 }
1580 TokenKind::Delim
1581 if (!has_mode || self.scan_context != ScanContext::Selector)
1582 && stream.byte_at(token.range.start) == Some(b'#') => {}
1583 TokenKind::Delim if stream.byte_at(token.range.start) == Some(b'#') => {
1584 result = self.handle_id(
1585 stream.lexer_mut(),
1586 token.range.start,
1587 token.range.end,
1588 token.flags,
1589 );
1590 }
1591 TokenKind::Delim
1592 if (!has_mode || self.scan_context != ScanContext::Selector)
1593 && stream.byte_at(token.range.start) == Some(b'.') => {}
1594 TokenKind::Delim if stream.byte_at(token.range.start) == Some(b'.') => {
1595 let mut class_end = token.range.end;
1596 let mut class_flags = token.flags;
1597 let mut consumes_name = false;
1598 if let Some(next) = dot_next
1599 && next.token.kind == TokenKind::Ident
1600 && next.token.range.start == token.range.end
1601 {
1602 class_end = next.token.range.end;
1603 class_flags = next.token.flags;
1604 consumes_name = true;
1605 }
1606 result = self.handle_class(
1607 stream.lexer_mut(),
1608 token.range.start,
1609 class_end,
1610 class_flags,
1611 );
1612 if consumes_name {
1613 stream.next(keep_comments);
1614 }
1615 }
1616 TokenKind::Delim if self.scan_context == ScanContext::Selector => {
1617 if self.block_nesting_level == 0
1618 && let Some(mode_data) = &mut self.mode_data
1619 {
1620 mode_data.composes_local_classes.invalidate();
1621 }
1622 }
1623 TokenKind::Colon if self.scan_context == ScanContext::DeclarationName => {
1624 result = self.enter_property_value(stream);
1625 }
1626 TokenKind::Colon if !has_mode => {}
1627 TokenKind::Colon => {
1628 if let Some(next) = colon_next.filter(|next| {
1629 matches!(next.token.kind, TokenKind::Ident | TokenKind::Function)
1630 && next.token.range.start == token.range.end
1631 }) {
1632 let (end, function) =
1633 (next.token.range.end, next.token.kind == TokenKind::Function);
1634 stream.next(keep_comments);
1635 result = if function {
1636 self.handle_pseudo_function(
1637 stream,
1638 token.range.start,
1639 end,
1640 next.token.flags,
1641 )
1642 } else {
1643 self.handle_pseudo_class(
1644 stream,
1645 token.range.start,
1646 end,
1647 next.token.flags,
1648 )
1649 };
1650 }
1651 }
1652 TokenKind::LeftSquareBracket if self.scan_context == ScanContext::Selector => {
1653 self.selector_square_depth += 1;
1654 if self.block_nesting_level == 0
1655 && let Some(mode_data) = &mut self.mode_data
1656 {
1657 mode_data.composes_local_classes.invalidate();
1658 }
1659 }
1660 TokenKind::RightSquareBracket if self.scan_context == ScanContext::Selector => {
1661 self.selector_square_depth = self.selector_square_depth.saturating_sub(1);
1662 }
1663 TokenKind::LeftParenthesis => {
1664 result = self.handle_left_parenthesis(
1665 stream.lexer_mut(),
1666 token.range.start,
1667 token.range.end,
1668 );
1669 }
1670 TokenKind::RightParenthesis => {
1671 result = self.handle_right_parenthesis(
1672 stream.lexer_mut(),
1673 item.leading.range.start,
1674 token.range.start,
1675 token.range.end,
1676 );
1677 }
1678 TokenKind::Comma => {
1679 result =
1680 self.handle_comma(stream.lexer_mut(), token.range.start, token.range.end);
1681 }
1682 TokenKind::Semicolon => {
1683 result = self.handle_semicolon(
1684 stream.lexer_mut(),
1685 token.range.start,
1686 token.range.end,
1687 );
1688 }
1689 TokenKind::LeftCurlyBracket => {
1690 result =
1691 self.handle_left_curly_bracket(stream, token.range.start, token.range.end);
1692 }
1693 TokenKind::RightCurlyBracket => {
1694 result =
1695 self.handle_right_curly_bracket(stream, token.range.start, token.range.end);
1696 }
1697 _ => {}
1698 }
1699
1700 if result.is_none() {
1701 return;
1702 }
1703 if token.kind == TokenKind::Semicolon {
1704 match self.scope {
1705 Scope::TopLevel => {
1706 self.set_scan_context(stream, ScanContext::TopLevel);
1707 self.is_next_rule_prelude = true;
1708 }
1709 Scope::InBlock => self.set_scan_context(stream, ScanContext::BlockItem),
1710 _ => {}
1711 }
1712 }
1713 self.update_dashed_ident_collection(stream);
1714 match self.scan_context {
1715 ScanContext::Selector if self.selector_fast_forward_enabled => {
1716 self.fast_forward_selector(stream, keep_comments, has_mode);
1717 }
1718 ScanContext::AtRule
1719 if !matches!(self.scope, Scope::InAtImport(_) | Scope::AtImportInvalid) =>
1720 {
1721 self.fast_forward_at_rule(stream, keep_comments);
1722 }
1723 ScanContext::SpecialValue(property) => {
1724 self.fast_forward_special_value(stream, property, keep_comments);
1725 }
1726 ScanContext::GenericValue if !is_trivia => {
1727 self.fast_forward_generic_value(stream, keep_comments);
1728 }
1729 _ => {}
1730 }
1731 }
1732 }
1733
1734 #[inline]
1735 fn fast_forward_selector(
1736 &mut self,
1737 stream: &mut DependencyTokenStream<'_, 's>,
1738 keep_comments: bool,
1739 has_mode: bool,
1740 ) {
1741 let invalidates_composes = stream.fast_forward_selector_if_buffer_empty(
1742 &mut self.selector_square_depth,
1743 keep_comments,
1744 has_mode,
1745 is_css_modules_pure_magic_comment,
1746 );
1747 if invalidates_composes
1748 && self.block_nesting_level == 0
1749 && let Some(mode_data) = &mut self.mode_data
1750 {
1751 mode_data.composes_local_classes.invalidate();
1752 }
1753 }
1754
1755 #[inline]
1756 fn fast_forward_generic_value(
1757 &self,
1758 stream: &mut DependencyTokenStream<'_, 's>,
1759 keep_comments: bool,
1760 ) {
1761 let preserve_strings = matches!(
1762 self.balanced.last(),
1763 Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
1764 );
1765 let preserve_delimiters = !self.balanced.is_empty();
1766 if self.icss_symbols.is_empty() && !keep_comments {
1767 stream.fast_forward_generic_value_without_candidates_if_buffer_empty(
1768 preserve_strings,
1769 preserve_delimiters,
1770 );
1771 } else {
1772 stream.fast_forward_generic_value_if_buffer_empty(
1773 keep_comments,
1774 preserve_strings,
1775 preserve_delimiters,
1776 |ident| self.contains_icss_symbol(ident),
1777 is_css_modules_pure_magic_comment,
1778 );
1779 }
1780 }
1781
1782 #[inline]
1783 fn fast_forward_at_rule(
1784 &self,
1785 stream: &mut DependencyTokenStream<'_, 's>,
1786 keep_comments: bool,
1787 ) {
1788 let preserve_strings = matches!(
1789 self.balanced.last(),
1790 Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
1791 );
1792 let preserve_delimiters = !self.balanced.is_empty();
1793 stream.fast_forward_at_rule_if_buffer_empty(
1794 keep_comments,
1795 preserve_strings,
1796 preserve_delimiters,
1797 |ident| self.contains_icss_symbol(ident),
1798 is_css_modules_pure_magic_comment,
1799 );
1800 }
1801
1802 #[inline]
1803 fn fast_forward_special_value(
1804 &self,
1805 stream: &mut DependencyTokenStream<'_, 's>,
1806 property: PropertyKind,
1807 keep_comments: bool,
1808 ) {
1809 if property == PropertyKind::Animation {
1812 return;
1813 }
1814 let preserve_strings = property == PropertyKind::Grid
1815 || matches!(
1816 self.balanced.last(),
1817 Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
1818 );
1819 let preserve_delimiters = !self.balanced.is_empty();
1820 let icss_symbols = (!self.icss_symbols.is_empty()).then_some(&self.icss_symbols);
1821 stream.fast_forward_special_value_if_buffer_empty(
1822 keep_comments,
1823 preserve_strings,
1824 preserve_delimiters,
1825 property,
1826 icss_symbols,
1827 );
1828 }
1829
1830 #[inline]
1831 fn icss_symbol_filter_bit(value: &str) -> (usize, u64) {
1832 let bytes = value.as_bytes();
1833 let len = bytes.len();
1834 let first = bytes.first().copied().unwrap_or_default() as usize;
1835 let middle = bytes.get(len / 2).copied().unwrap_or_default() as usize;
1836 let last = bytes.last().copied().unwrap_or_default() as usize;
1837 let mut hash = len.wrapping_mul(0x9e37_79b1);
1838 hash ^= first.wrapping_mul(0x85eb_ca6b);
1839 hash ^= middle.wrapping_mul(0xc2b2_ae35);
1840 hash ^= last.wrapping_mul(0x27d4_eb2f);
1841 hash ^= hash >> 16;
1842 let bit = hash & 1023;
1843 (bit >> 6, 1u64 << (bit & 63))
1844 }
1845
1846 #[inline]
1847 fn contains_icss_symbol(&self, value: &str) -> bool {
1848 let len = value.len();
1849 if len < self.icss_symbol_min_len || len > self.icss_symbol_max_len {
1850 return false;
1851 }
1852 let (word, bit) = Self::icss_symbol_filter_bit(value);
1853 self.icss_symbol_filter[word] & bit != 0 && self.icss_symbols.contains(value)
1854 }
1855
1856 fn insert_icss_symbol(&mut self, value: &'s str) {
1857 let len = value.len();
1858 self.icss_symbol_min_len = self.icss_symbol_min_len.min(len);
1859 self.icss_symbol_max_len = self.icss_symbol_max_len.max(len);
1860 let (word, bit) = Self::icss_symbol_filter_bit(value);
1861 self.icss_symbol_filter[word] |= bit;
1862 self.icss_symbols.insert(value);
1863 }
1864
1865 fn enter_property_value(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
1866 match self.property_kind {
1867 PropertyKind::Animation => self.enter_animation_property(),
1868 PropertyKind::ListStyle => self.enter_list_style_property(),
1869 PropertyKind::FontPalette => self.enter_font_palette_property(),
1870 PropertyKind::Container => self.enter_container_property(),
1871 PropertyKind::Grid => {
1872 if let Some(kind) = self.pending_grid_property.take() {
1873 self.enter_grid_property(kind);
1874 }
1875 }
1876 PropertyKind::CustomProperty => {
1877 if self
1878 .mode_data
1879 .as_ref()
1880 .is_some_and(ModeData::is_property_local_mode)
1881 && let Some(range) = self.pending_custom_property.take()
1882 {
1883 self.dependency_context
1884 .push_dependency(Dependency::LocalVarDecl {
1885 name: stream.lexer().slice(range.start + 2, range.end)?,
1886 range,
1887 });
1888 }
1889 }
1890 PropertyKind::Generic | PropertyKind::Composes => {}
1891 }
1892 self.pending_custom_property = None;
1893 self.pending_grid_property = None;
1894 self.set_scan_context(stream, ScanContext::for_property(self.property_kind));
1895 Some(())
1896 }
1897
1898 fn classify_property(
1899 name: &str,
1900 flags: TokenFlags,
1901 property_local_mode: bool,
1902 ) -> (PropertyKind, Option<GridPropertyKind>) {
1903 if name.starts_with("--") {
1904 return if property_local_mode {
1905 (PropertyKind::CustomProperty, None)
1906 } else {
1907 (PropertyKind::Generic, None)
1908 };
1909 }
1910 let mut normalized = [0; MAX_CSS_KEYWORD_LEN];
1911 let name = if flags.has_escape() {
1912 let Some(name) = decode_css_keyword(name, &mut normalized) else {
1913 return (PropertyKind::Generic, None);
1914 };
1915 name
1916 } else {
1917 let Some(name) = lowercase_ascii_keyword(name, &mut normalized) else {
1918 return (PropertyKind::Generic, None);
1919 };
1920 name
1921 };
1922 if matches!(name, "composes" | "compose-with") {
1923 return (PropertyKind::Composes, None);
1924 }
1925 if !property_local_mode {
1926 return (PropertyKind::Generic, None);
1927 }
1928 let unprefixed = strip_vendor_prefix(name).unwrap_or(name);
1929 if matches!(unprefixed, "animation" | "animation-name") {
1930 return (PropertyKind::Animation, None);
1931 }
1932 if matches!(name, "list-style" | "list-style-type") {
1933 return (PropertyKind::ListStyle, None);
1934 }
1935 if name == "font-palette" {
1936 return (PropertyKind::FontPalette, None);
1937 }
1938 if matches!(name, "container" | "container-name") {
1939 return (PropertyKind::Container, None);
1940 }
1941 if let Some(grid) = grid_property_kind(name) {
1942 return (PropertyKind::Grid, Some(grid));
1943 }
1944 (PropertyKind::Generic, None)
1945 }
1946
1947 fn classify_at_rule(name: &str, flags: TokenFlags) -> AtRuleKind {
1948 let mut normalized = [0; MAX_CSS_KEYWORD_LEN];
1949 let name = if flags.has_escape() {
1950 let Some(name) = decode_css_keyword(name, &mut normalized) else {
1951 return AtRuleKind::Other;
1952 };
1953 name
1954 } else {
1955 let Some(name) = lowercase_ascii_keyword(name, &mut normalized) else {
1956 return AtRuleKind::Other;
1957 };
1958 name
1959 };
1960 match name {
1961 "@value" => AtRuleKind::Value,
1962 "@scope" => AtRuleKind::Scope,
1963 "@import" => AtRuleKind::Import,
1964 "@charset" => AtRuleKind::Charset,
1965 "@function" => AtRuleKind::Function,
1966 "@property" => AtRuleKind::Property,
1967 "@namespace" => AtRuleKind::Namespace,
1968 "@keyframes" => AtRuleKind::Keyframes,
1969 "@container" => AtRuleKind::Container,
1970 "@counter-style" => AtRuleKind::CounterStyle,
1971 "@font-palette-values" => AtRuleKind::FontPaletteValues,
1972 _ if name.strip_prefix('@').and_then(strip_vendor_prefix) == Some("keyframes") => {
1973 AtRuleKind::Keyframes
1974 }
1975 _ => AtRuleKind::Other,
1976 }
1977 }
1978
1979 fn get_media(&self, lexer: &DependencyLexer<'s>, start: Pos, end: Pos) -> Option<&'s str> {
1980 let media = lexer.slice(start, end)?;
1981 let bytes = media.as_bytes();
1982 let mut position = 0;
1983 loop {
1984 while position < bytes.len() && bytes[position].is_ascii_whitespace() {
1985 position += 1;
1986 }
1987 if position + 1 < bytes.len() && bytes[position] == b'/' && bytes[position + 1] == b'*'
1988 {
1989 let Some(relative_end) = media[position + 2..].find("*/") else {
1990 break;
1991 };
1992 position += relative_end + 4;
1993 continue;
1994 }
1995 break;
1996 }
1997 if position == bytes.len() {
1998 return None;
1999 }
2000 Some(media)
2001 }
2002
2003 fn lex_charset_at_rule(
2004 &mut self,
2005 stream: &mut DependencyTokenStream<'_, 's>,
2006 start: Pos,
2007 ) -> Option<()> {
2008 let string = stream.next_parser_token().token;
2009 if string.kind != TokenKind::QuotedString {
2010 return Some(());
2011 }
2012
2013 let next = stream.next_parser_token().token;
2014 if next.kind == TokenKind::Semicolon {
2015 self.dependency_context
2016 .push_dependency(Dependency::Charset {
2017 value: stream.slice(string.value_range.start, string.value_range.end)?,
2018 range: Range::new(start, next.range.end),
2019 });
2020 }
2021 Some(())
2022 }
2023
2024 fn enter_animation_property(&mut self) {
2025 self.in_animation_property = Some(InProperty::new(
2026 AnimationReserved::default(),
2027 self.balanced.len(),
2028 ));
2029 }
2030
2031 fn exit_animation_property(&mut self) {
2032 self.in_animation_property = None;
2033 }
2034
2035 fn enter_list_style_property(&mut self) {
2036 self.in_list_style_property = Some(InProperty::new(ListStyleReserved, self.balanced.len()));
2037 }
2038
2039 fn exit_list_style_property(&mut self) {
2040 self.in_list_style_property = None;
2041 }
2042
2043 fn enter_font_palette_property(&mut self) {
2044 self.in_font_palette_property =
2045 Some(InProperty::new(FontPaletteReserved, self.balanced.len()));
2046 }
2047
2048 fn exit_font_palette_property(&mut self) {
2049 self.in_font_palette_property = None;
2050 }
2051
2052 fn enter_container_property(&mut self) {
2053 self.in_container_property = Some(InProperty::new(ContainerReserved, self.balanced.len()));
2054 }
2055
2056 fn exit_container_property(&mut self) {
2057 self.in_container_property = None;
2058 }
2059
2060 fn enter_grid_property(&mut self, kind: GridPropertyKind) {
2061 self.in_grid_property = Some(kind);
2062 }
2063
2064 fn exit_grid_property(&mut self) {
2065 self.in_grid_property = None;
2066 }
2067
2068 fn lex_icss_import(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2069 let (start, end) = self.consume_icss_import_path(stream)?;
2070 let right_parenthesis = stream.next_parser_token().token;
2071 if right_parenthesis.kind != TokenKind::RightParenthesis {
2072 self.handle_warning.handle_warning(Warning {
2073 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2074 kind: WarningKind::Unexpected {
2075 message: "Expected ')' during parsing of ':import()'",
2076 },
2077 });
2078 return Some(());
2079 }
2080 self.dependency_context
2081 .push_dependency(Dependency::ICSSImportFrom {
2082 path: stream.slice(start, end)?,
2083 });
2084 let left_curly = stream.next_parser_token().token;
2085 if left_curly.kind != TokenKind::LeftCurlyBracket {
2086 self.handle_warning.handle_warning(Warning {
2087 range: Range::new(left_curly.range.start, left_curly.range.end),
2088 kind: WarningKind::Unexpected {
2089 message: "Expected '{' during parsing of ':import()'",
2090 },
2091 });
2092 return Some(());
2093 }
2094 loop {
2095 let first = stream.next_parser_token().token;
2096 if first.kind == TokenKind::Eof {
2097 return None;
2098 }
2099 if first.kind == TokenKind::RightCurlyBracket {
2100 break;
2101 }
2102 let prop_start = first.range.start;
2103 let prop_end = self.consume_icss_export_prop(stream, first)?;
2104 let colon = stream.peek_significant_skipping_comments(true).token;
2105 if colon.kind != TokenKind::Colon {
2106 self.handle_warning.handle_warning(Warning {
2107 range: Range::new(colon.range.start, colon.range.end),
2108 kind: WarningKind::Unexpected {
2109 message: "Expected ':' during parsing of ':import'",
2110 },
2111 });
2112 return Some(());
2113 }
2114 stream.next_parser_token();
2115 let value_start_token = stream.next_parser_token().token;
2116 if value_start_token.kind == TokenKind::Eof {
2117 return None;
2118 }
2119 let value_start = value_start_token.range.start;
2120 let value_end = self.consume_icss_value(stream, value_start_token)?;
2121 let delimiter = stream.next_parser_token().token;
2122 self.dependency_context
2123 .push_dependency(Dependency::ICSSImportValue {
2124 prop: stream
2125 .slice(prop_start, prop_end)?
2126 .trim_end_matches(is_css_white_space_char),
2127 value: stream
2128 .slice(value_start, value_end)?
2129 .trim_end_matches(is_css_white_space_char),
2130 });
2131 self.insert_icss_symbol(
2132 stream
2133 .slice(prop_start, prop_end)?
2134 .trim_end_matches(is_css_white_space_char),
2135 );
2136 if delimiter.kind == TokenKind::RightCurlyBracket {
2137 break;
2138 }
2139 }
2140 Some(())
2141 }
2142
2143 fn consume_icss_import_path(
2144 &self,
2145 stream: &mut DependencyTokenStream<'_, 's>,
2146 ) -> Option<(Pos, Pos)> {
2147 let first = stream.next_parser_token().token;
2148 if first.kind == TokenKind::Eof {
2149 return None;
2150 }
2151 let start = first.range.start;
2152 let mut end = start;
2153 if first.kind == TokenKind::RightParenthesis {
2154 return Some((start, end));
2155 }
2156 end = first.range.end;
2157 loop {
2158 let token = stream.peek_significant_skipping_comments(true).token;
2159 if token.kind == TokenKind::Eof {
2160 return None;
2161 }
2162 if token.kind == TokenKind::RightParenthesis {
2163 return Some((start, end));
2164 }
2165 stream.next_parser_token();
2166 if first.kind != TokenKind::QuotedString {
2167 end = token.range.end;
2168 }
2169 }
2170 }
2171
2172 fn consume_icss_export_prop(
2173 &self,
2174 stream: &mut DependencyTokenStream<'_, 's>,
2175 first: Token,
2176 ) -> Option<Pos> {
2177 if matches!(
2178 first.kind,
2179 TokenKind::Colon | TokenKind::RightCurlyBracket | TokenKind::Semicolon
2180 ) {
2181 return Some(first.range.start);
2182 }
2183 loop {
2184 let token = stream.peek_parser_token();
2185 if token.token.kind == TokenKind::Eof {
2186 return None;
2187 }
2188 if let Some(first_comment_start) = token.leading.first_comment_start {
2189 return Some(first_comment_start);
2190 }
2191 if matches!(
2192 token.token.kind,
2193 TokenKind::Colon | TokenKind::RightCurlyBracket | TokenKind::Semicolon
2194 ) {
2195 return Some(token.token.range.start);
2196 }
2197 stream.next_parser_token();
2198 }
2199 }
2200
2201 fn consume_icss_value(
2202 &self,
2203 stream: &mut DependencyTokenStream<'_, 's>,
2204 first: Token,
2205 ) -> Option<Pos> {
2206 if matches!(
2207 first.kind,
2208 TokenKind::RightCurlyBracket | TokenKind::Semicolon
2209 ) {
2210 return Some(first.range.start);
2211 }
2212 loop {
2213 let token = stream.peek(true).token;
2214 if token.kind == TokenKind::Eof {
2215 return None;
2216 }
2217 if matches!(
2218 token.kind,
2219 TokenKind::RightCurlyBracket | TokenKind::Semicolon
2220 ) {
2221 return Some(token.range.start);
2222 }
2223 stream.next(true);
2224 }
2225 }
2226
2227 fn lex_value_at_rule(
2228 &mut self,
2229 stream: &mut DependencyTokenStream<'_, 's>,
2230 start: Pos,
2231 ) -> Option<()> {
2232 let input = stream.slice_trusted(0, stream.source_end());
2233 let checkpoint = self
2234 .dependency_context
2235 .value_at_rule_import_items_checkpoint();
2236 let mut parser = ValueAtRuleStream::new(input);
2237 loop {
2238 let token = stream.next_parser_token().token;
2239 match token.kind {
2240 TokenKind::Eof => return None,
2241 _ => {
2242 if token.kind == TokenKind::Semicolon && parser.depth == 0 {
2243 parser.params_end = token.range.start;
2244 break;
2245 }
2246 let depth_after = (parser.depth + u32::from(is_open_token(token.kind)))
2247 .saturating_sub(u32::from(is_close_token(token.kind)));
2248 if token.kind == TokenKind::RightCurlyBracket && depth_after == 0 {
2249 parser.params_end = token.range.start;
2250 break;
2251 }
2252 parser.push(&mut self.dependency_context, token);
2253 }
2254 }
2255 }
2256 let at_rule_end = parser.params_end.max(stream.consumed_pos());
2257 let import = parser.last_two().is_some_and(|(penultimate, last)| {
2258 penultimate.kind == TokenKind::Ident
2259 && parser
2260 .input
2261 .get(penultimate.range.start as usize..penultimate.range.end as usize)
2262 .is_some_and(|text| text.eq_ignore_ascii_case("from"))
2263 && parser.from_pos == Some(penultimate.range.start)
2264 && parser
2265 .input
2266 .get(penultimate.range.end as usize..last.range.start as usize)
2267 .is_some_and(trivia_only)
2268 });
2269 if import {
2270 let (_, last) = parser.last_two().unwrap();
2271 let from = &parser.input[last.range.start as usize..last.range.end as usize];
2272 let item_end = parser.from_prev_end.unwrap_or(parser.item_end);
2273 parser.finish_item(&mut self.dependency_context, item_end);
2274 let items = self
2275 .dependency_context
2276 .finish_value_at_rule_import_items(checkpoint);
2277 if items.is_empty() {
2278 self.handle_warning.handle_warning(Warning {
2279 range: Range::new(start, at_rule_end),
2280 kind: WarningKind::Unexpected {
2281 message: "Broken '@value' at-rule",
2282 },
2283 });
2284 } else {
2285 self.dependency_context
2286 .push_dependency(Dependency::ICSSImportFrom { path: from });
2287 for index in items.as_usize_range() {
2288 let item = self.dependency_context.value_at_rule_import_item(index);
2289 self.dependency_context
2290 .push_dependency(Dependency::ICSSImportValue {
2291 prop: item.local_name(),
2292 value: item.import_name(),
2293 });
2294 self.insert_icss_symbol(item.local_name());
2295 self.dependency_context
2296 .push_dependency(Dependency::ICSSExportValue {
2297 prop: item.local_name(),
2298 value: item.local_name(),
2299 });
2300 }
2301 }
2302 } else {
2303 self.dependency_context
2304 .truncate_value_at_rule_import_items(checkpoint);
2305 let local_name;
2306 let value;
2307 let has_colon;
2308 if let Some(colon) = parser.first_colon {
2309 local_name = &parser.input[parser
2310 .first_significant
2311 .unwrap_or((colon.split, colon.split))
2312 .0 as usize
2313 ..colon.prev_end as usize];
2314 let raw = &parser.input[colon.end as usize..parser.params_end as usize];
2315 value = if parser.first_colon_tokens_after > 0 {
2316 trim_css_whitespace(raw)
2317 } else {
2318 raw
2319 };
2320 has_colon = true;
2321 } else if let Some((first_start, first_end)) = parser.first_significant {
2322 local_name = &parser.input[first_start as usize..first_end as usize];
2323 let raw = &parser.input[first_end as usize..parser.params_end as usize];
2324 value = if parser.significant_count > 1 {
2325 trim_css_whitespace(raw)
2326 } else {
2327 raw
2328 };
2329 has_colon = false;
2330 } else {
2331 local_name = "";
2332 value = "";
2333 has_colon = false;
2334 }
2335 if local_name.is_empty() || (!has_colon && value.is_empty()) {
2336 self.handle_warning.handle_warning(Warning {
2337 range: Range::new(start, at_rule_end),
2338 kind: WarningKind::Unexpected {
2339 message: "Broken '@value' at-rule",
2340 },
2341 });
2342 }
2343 if !local_name.is_empty() {
2344 self.dependency_context
2345 .push_dependency(Dependency::ICSSExportValue {
2346 prop: local_name,
2347 value,
2348 });
2349 self.insert_icss_symbol(local_name);
2350 }
2351 }
2352 self.dependency_context
2353 .push_dependency(Dependency::Replace {
2354 content: "",
2355 range: Range::new(start, at_rule_end),
2356 });
2357 Some(())
2358 }
2359
2360 fn lex_icss_export(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2361 let left_curly = stream.next_parser_token().token;
2362 if left_curly.kind != TokenKind::LeftCurlyBracket {
2363 self.handle_warning.handle_warning(Warning {
2364 range: Range::new(left_curly.range.start, left_curly.range.end),
2365 kind: WarningKind::Unexpected {
2366 message: "Expected '{' during parsing of ':export'",
2367 },
2368 });
2369 return Some(());
2370 }
2371 loop {
2372 let first = stream.next_parser_token().token;
2373 if first.kind == TokenKind::Eof {
2374 return None;
2375 }
2376 if first.kind == TokenKind::RightCurlyBracket {
2377 break;
2378 }
2379 let prop_start = first.range.start;
2380 let prop_end = self.consume_icss_export_prop(stream, first)?;
2381 let colon = stream.peek_significant_skipping_comments(true).token;
2382 if colon.kind != TokenKind::Colon {
2383 self.handle_warning.handle_warning(Warning {
2384 range: Range::new(colon.range.start, colon.range.end),
2385 kind: WarningKind::Unexpected {
2386 message: "Expected ':' during parsing of ':export'",
2387 },
2388 });
2389 return Some(());
2390 }
2391 stream.next_parser_token();
2392 let value_start_token = stream.next_parser_token().token;
2393 if value_start_token.kind == TokenKind::Eof {
2394 return None;
2395 }
2396 let value_start = value_start_token.range.start;
2397 let value_end = self.consume_icss_value(stream, value_start_token)?;
2398 let delimiter = stream.next_parser_token().token;
2399 let value = stream
2400 .slice(value_start, value_end)?
2401 .trim_end_matches(is_css_white_space_char);
2402 self.dependency_context
2403 .push_dependency(Dependency::ICSSExportValue {
2404 prop: stream
2405 .slice(prop_start, prop_end)?
2406 .trim_end_matches(is_css_white_space_char),
2407 value,
2408 });
2409 self.insert_icss_symbol(
2410 stream
2411 .slice(prop_start, prop_end)?
2412 .trim_end_matches(is_css_white_space_char),
2413 );
2414 if delimiter.kind == TokenKind::RightCurlyBracket {
2415 break;
2416 }
2417 }
2418 Some(())
2419 }
2420
2421 fn lex_local_var(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2422 let name_token = stream.next_parser_token().token;
2423 let start = name_token.range.start;
2424 if name_token.kind != TokenKind::Ident
2425 || !stream.slice(start, name_token.range.end)?.starts_with("--")
2426 {
2427 self.handle_warning.handle_warning(Warning {
2428 kind: WarningKind::Unexpected {
2429 message: "Expected starts with '--' during parsing of 'var()'",
2430 },
2431 range: Range::new(start, (start + 2).min(name_token.range.end)),
2432 });
2433 return Some(());
2434 }
2435 let end = name_token.range.end;
2436 let next = stream.peek_significant_skipping_comments(true).token;
2437 let from = if next.kind == TokenKind::Ident
2438 && stream.slice(next.range.start, next.range.end)? == "from"
2439 {
2440 stream.next_parser_token();
2441 let path = stream.peek_significant_skipping_comments(true).token;
2442 let path_start = path.range.start;
2443 let path_end = path.range.end;
2444 if !matches!(path.kind, TokenKind::QuotedString | TokenKind::Ident) {
2445 self.handle_warning.handle_warning(Warning {
2446 range: Range::new(path_start, path_end),
2447 kind: WarningKind::Unexpected {
2448 message: "Expected string or ident during parsing of 'composes'",
2449 },
2450 });
2451 return Some(());
2452 }
2453 stream.next_parser_token();
2454 Some(stream.slice(path_start, path_end)?)
2455 } else {
2456 None
2457 };
2458 if from.is_some_and(|from| from.trim_matches(['\'', '"']) == "global") {
2459 stream
2460 .lexer_mut()
2461 .visitor_mut()
2462 .discard_last(Range::new(start, end));
2463 }
2464 self.dependency_context
2465 .push_dependency(Dependency::LocalVar {
2466 name: stream.slice(start + 2, end)?,
2467 range: Range::new(start, end),
2468 from,
2469 });
2470 Some(())
2471 }
2472
2473 fn lex_local_dashed_ident_decl(
2474 &mut self,
2475 stream: &mut DependencyTokenStream<'_, 's>,
2476 local_decl_dependency: impl FnOnce(&'s str, Range) -> Dependency<'s>,
2477 dashed_warning: impl FnOnce(Range) -> Warning<'s>,
2478 left_curly_warning: impl FnOnce(Range) -> Warning<'s>,
2479 ) -> Option<()> {
2480 let name_token = stream.next_parser_token().token;
2481 let start = name_token.range.start;
2482 if name_token.kind != TokenKind::Ident
2483 || !stream.slice(start, name_token.range.end)?.starts_with("--")
2484 {
2485 self.handle_warning
2486 .handle_warning(dashed_warning(Range::new(
2487 start,
2488 (start + 2).min(name_token.range.end),
2489 )));
2490 return Some(());
2491 }
2492 let end = name_token.range.end;
2493 self.dependency_context
2494 .push_dependency(local_decl_dependency(
2495 stream.slice(start + 2, end)?,
2496 Range::new(start, end),
2497 ));
2498 let left_curly = stream.peek_significant_skipping_comments(true).token;
2499 if left_curly.kind != TokenKind::LeftCurlyBracket {
2500 self.handle_warning
2501 .handle_warning(left_curly_warning(Range::new(
2502 left_curly.range.start,
2503 left_curly.range.end,
2504 )));
2505 return Some(());
2506 }
2507 Some(())
2508 }
2509
2510 fn lex_local_keyframes_decl(
2511 &mut self,
2512 stream: &mut DependencyTokenStream<'_, 's>,
2513 ) -> Option<()> {
2514 let mut is_function = false;
2515 let first = stream.next_parser_token().token;
2516 let name_token = if first.kind == TokenKind::Colon {
2517 let pseudo_start = first.range.start;
2518 let pseudo_name = stream.next_parser_token().token;
2519 let pseudo_end = if matches!(pseudo_name.kind, TokenKind::Ident | TokenKind::Function) {
2520 pseudo_name.range.end
2521 } else {
2522 first.range.end
2523 };
2524 let pseudo = stream.slice(pseudo_start, pseudo_end)?;
2525 if pseudo_name.kind == TokenKind::Function {
2526 self.handle_pseudo_function(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2527 } else if pseudo_name.kind == TokenKind::Ident {
2528 self.handle_pseudo_class(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2529 }
2530 let mode_data = self.mode_data.as_ref().unwrap();
2531 if mode_data.is_pure_mode()
2532 && !mode_data.is_pure_check_disabled()
2533 && (pseudo.eq_ignore_ascii_case(":global(")
2534 || pseudo.eq_ignore_ascii_case(":global"))
2535 {
2536 self.handle_warning.handle_warning(Warning {
2537 range: Range::new(pseudo_start, pseudo_end),
2538 kind: WarningKind::NotPure {
2539 message: "'@keyframes :global' is not allowed in pure mode",
2540 },
2541 });
2542 }
2543 is_function =
2544 pseudo.eq_ignore_ascii_case(":local(") || pseudo.eq_ignore_ascii_case(":global(");
2545 if !is_function
2546 && !pseudo.eq_ignore_ascii_case(":local")
2547 && !pseudo.eq_ignore_ascii_case(":global")
2548 {
2549 self.handle_warning.handle_warning(Warning {
2550 range: Range::new(pseudo_start, pseudo_end),
2551 kind: WarningKind::Unexpected {
2552 message: "Expected ':local', ':local()', ':global', or ':global()' during parsing of '@keyframes' name",
2553 }
2554 });
2555 return Some(());
2556 }
2557 stream.next_parser_token().token
2558 } else {
2559 first
2560 };
2561
2562 let start = name_token.range.start;
2563 if name_token.kind != TokenKind::Ident {
2564 self.handle_warning.handle_warning(Warning {
2565 range: Range::new(start, start.saturating_add(2).min(stream.source_end())),
2566 kind: WarningKind::Unexpected {
2567 message: "Expected ident during parsing of '@keyframes' name",
2568 },
2569 });
2570 return Some(());
2571 }
2572 let end = name_token.range.end;
2573 if self.mode_data.as_ref().unwrap().is_current_local_mode() {
2574 let name = stream.slice(start, end)?;
2575 self.dependency_context
2576 .push_dependency(Dependency::LocalKeyframesDecl {
2577 name,
2578 range: Range::new(start, end),
2579 });
2580 }
2581 if is_function {
2582 let right_parenthesis = stream.peek_significant_skipping_comments(true).token;
2583 if right_parenthesis.kind != TokenKind::RightParenthesis {
2584 self.handle_warning.handle_warning(Warning {
2585 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2586 kind: WarningKind::Unexpected {
2587 message: "Expected ')' during parsing of '@keyframes :local(' or '@keyframes :global('",
2588 },
2589 });
2590 return Some(());
2591 }
2592 stream.next_parser_token();
2593 self.dependency_context
2594 .push_dependency(Dependency::Replace {
2595 content: "",
2596 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2597 });
2598 let mode_data = self.mode_data.as_mut().unwrap();
2599 mode_data.inside_mode_function -= 1;
2600 self.balanced.pop_without_moda_data();
2601 }
2602 let left_curly = stream.peek_significant_skipping_comments(true).token;
2603 if left_curly.kind != TokenKind::LeftCurlyBracket {
2604 self.handle_warning.handle_warning(Warning {
2605 range: Range::new(left_curly.range.start, left_curly.range.end),
2606 kind: WarningKind::Unexpected {
2607 message: "Expected '{' during parsing of '@keyframes'",
2608 },
2609 });
2610 return Some(());
2611 }
2612 Some(())
2613 }
2614
2615 fn handle_local_keyframes_dependency(&mut self, lexer: &DependencyLexer<'s>) -> Option<()> {
2616 let animation = self.in_animation_property.as_mut().unwrap();
2617 if let Some(range) = animation.take_rename(self.balanced.len()) {
2618 self.dependency_context
2619 .push_dependency(Dependency::LocalKeyframes {
2620 name: lexer.slice(range.start, range.end)?,
2621 range,
2622 });
2623 }
2624 animation.reset_reserved();
2625 Some(())
2626 }
2627
2628 fn lex_local_counter_style_decl(
2629 &mut self,
2630 stream: &mut DependencyTokenStream<'_, 's>,
2631 ) -> Option<()> {
2632 let name_token = stream.next_parser_token().token;
2633 let start = name_token.range.start;
2634 if name_token.kind != TokenKind::Ident {
2635 self.handle_warning.handle_warning(Warning {
2636 range: Range::new(start, name_token.range.end),
2637 kind: WarningKind::Unexpected {
2638 message: "Expected ident during parsing of '@counter-style'",
2639 },
2640 });
2641 return Some(());
2642 }
2643 let end = name_token.range.end;
2644 self.dependency_context
2645 .push_dependency(Dependency::LocalCounterStyleDecl {
2646 name: stream.slice(start, end)?,
2647 range: Range::new(start, end),
2648 });
2649 let left_curly = stream.peek_significant_skipping_comments(true).token;
2650 if left_curly.kind != TokenKind::LeftCurlyBracket {
2651 self.handle_warning.handle_warning(Warning {
2652 range: Range::new(left_curly.range.start, left_curly.range.end),
2653 kind: WarningKind::Unexpected {
2654 message: "Expected '{' during parsing of '@counter-style'",
2655 },
2656 });
2657 return Some(());
2658 }
2659 Some(())
2660 }
2661
2662 fn lex_local_container_at_rule(
2663 &mut self,
2664 stream: &mut DependencyTokenStream<'_, 's>,
2665 ) -> Option<()> {
2666 let name_token = stream.next_parser_token().token;
2667 if name_token.kind == TokenKind::LeftParenthesis {
2668 return Some(());
2669 }
2670 if name_token.kind != TokenKind::Ident {
2671 return Some(());
2672 }
2673 let start = name_token.range.start;
2674 let end = name_token.range.end;
2675 let ident = stream.slice(start, end)?;
2676 if is_reserved_container_query_ident(ident, name_token.flags) {
2677 return Some(());
2678 }
2679 if self.mode_data.as_ref().unwrap().is_current_local_mode() {
2680 self.dependency_context
2681 .push_dependency(Dependency::LocalContainer {
2682 name: ident,
2683 range: Range::new(start, end),
2684 });
2685 }
2686 Some(())
2687 }
2688
2689 fn lex_local_function_decl(
2690 &mut self,
2691 stream: &mut DependencyTokenStream<'_, 's>,
2692 ) -> Option<()> {
2693 let mut is_function = false;
2694 let first = stream.next_parser_token().token;
2695 let name_token = if first.kind == TokenKind::Colon {
2696 let pseudo_start = first.range.start;
2697 let pseudo_name = stream.next_parser_token().token;
2698 let pseudo_end = if matches!(pseudo_name.kind, TokenKind::Ident | TokenKind::Function) {
2699 pseudo_name.range.end
2700 } else {
2701 first.range.end
2702 };
2703 let pseudo = stream.slice(pseudo_start, pseudo_end)?;
2704 if pseudo_name.kind == TokenKind::Function {
2705 self.handle_pseudo_function(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2706 } else if pseudo_name.kind == TokenKind::Ident {
2707 self.handle_pseudo_class(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2708 }
2709 let mode_data = self.mode_data.as_ref().unwrap();
2710 if mode_data.is_pure_mode()
2711 && !mode_data.is_pure_check_disabled()
2712 && (pseudo.eq_ignore_ascii_case(":global(")
2713 || pseudo.eq_ignore_ascii_case(":global"))
2714 {
2715 self.handle_warning.handle_warning(Warning {
2716 range: Range::new(pseudo_start, pseudo_end),
2717 kind: WarningKind::NotPure {
2718 message: "'@function :global' is not allowed in pure mode",
2719 },
2720 });
2721 }
2722 is_function =
2723 pseudo.eq_ignore_ascii_case(":local(") || pseudo.eq_ignore_ascii_case(":global(");
2724 if !is_function
2725 && !pseudo.eq_ignore_ascii_case(":local")
2726 && !pseudo.eq_ignore_ascii_case(":global")
2727 {
2728 self.handle_warning.handle_warning(Warning {
2729 range: Range::new(pseudo_start, pseudo_end),
2730 kind: WarningKind::Unexpected {
2731 message:
2732 "Expected ':local', ':local()', ':global', or ':global()' during parsing of '@function' name",
2733 },
2734 });
2735 return Some(());
2736 }
2737 stream.next_parser_token().token
2738 } else {
2739 first
2740 };
2741
2742 let name_range = if name_token.kind == TokenKind::Function {
2743 name_token.value_range
2744 } else {
2745 name_token.range
2746 };
2747 let start = name_range.start;
2748 let end = name_range.end;
2749 let name = stream.slice_trusted(start, end);
2750 if name_token.kind != TokenKind::Ident && name_token.kind != TokenKind::Function
2751 || !name.starts_with("--")
2752 {
2753 self.handle_warning.handle_warning(Warning {
2754 range: Range::new(start, start.saturating_add(2).min(stream.source_end())),
2755 kind: WarningKind::Unexpected {
2756 message: "Expected starts with '--' during parsing of '@function' name",
2757 },
2758 });
2759 return Some(());
2760 }
2761 if self.mode_data.as_ref().unwrap().is_current_local_mode() {
2762 self.dependency_context
2763 .push_dependency(Dependency::LocalFunctionDecl {
2764 name: stream.slice(start + 2, end)?,
2765 range: Range::new(start, end),
2766 });
2767 }
2768
2769 if is_function {
2770 let right_parenthesis = stream.peek_significant_skipping_comments(true).token;
2771 if right_parenthesis.kind != TokenKind::RightParenthesis {
2772 self.handle_warning.handle_warning(Warning {
2773 range: Range::new(
2774 right_parenthesis.range.start,
2775 right_parenthesis.range.end,
2776 ),
2777 kind: WarningKind::Unexpected {
2778 message:
2779 "Expected ')' during parsing of '@function :local(' or '@function :global('",
2780 },
2781 });
2782 return Some(());
2783 }
2784 stream.next_parser_token();
2785 self.dependency_context
2786 .push_dependency(Dependency::Replace {
2787 content: "",
2788 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2789 });
2790 let mode_data = self.mode_data.as_mut().unwrap();
2791 mode_data.inside_mode_function -= 1;
2792 self.balanced.pop_without_moda_data();
2793 }
2794
2795 if name_token.kind == TokenKind::Function {
2796 return Some(());
2797 }
2798 let left_parenthesis = stream.peek_significant_skipping_comments(true).token;
2799 if left_parenthesis.kind != TokenKind::LeftParenthesis {
2800 self.handle_warning.handle_warning(Warning {
2801 range: Range::new(left_parenthesis.range.start, left_parenthesis.range.end),
2802 kind: WarningKind::Unexpected {
2803 message: "Expected '(' during parsing of '@function'",
2804 },
2805 });
2806 }
2807 Some(())
2808 }
2809
2810 fn handle_local_counter_style_dependency(&mut self, lexer: &DependencyLexer<'s>) -> Option<()> {
2811 let list_style = self.in_list_style_property.as_mut().unwrap();
2812 if let Some(range) = list_style.take_rename(self.balanced.len()) {
2813 self.dependency_context
2814 .push_dependency(Dependency::LocalCounterStyle {
2815 name: lexer.slice(range.start, range.end)?,
2816 range,
2817 });
2818 }
2819 Some(())
2820 }
2821
2822 fn handle_local_font_palette_dependency(&mut self, lexer: &DependencyLexer<'s>) -> Option<()> {
2823 let font_palette = self.in_font_palette_property.as_mut().unwrap();
2824 if let Some(range) = font_palette.take_rename(self.balanced.len()) {
2825 self.dependency_context
2826 .push_dependency(Dependency::LocalFontPalette {
2827 name: lexer.slice(range.start + 2, range.end)?,
2828 range,
2829 });
2830 }
2831 Some(())
2832 }
2833
2834 fn lex_composes(
2835 &mut self,
2836 stream: &mut DependencyTokenStream<'_, 's>,
2837 local_classes: SmallVec<[&'s str; 2]>,
2838 start: Pos,
2839 ) -> Option<()> {
2840 let colon = stream.peek_significant_skipping_comments(true).token;
2841 if colon.kind != TokenKind::Colon {
2842 return Some(());
2843 }
2844 stream.next_parser_token();
2845 let mut replacement_end = colon.range.end;
2846 loop {
2847 let first = stream.peek_significant_skipping_comments(true).token;
2848 if first.kind == TokenKind::Eof {
2849 break;
2850 }
2851 if first.kind == TokenKind::RightCurlyBracket {
2852 break;
2853 }
2854 if first.kind == TokenKind::Semicolon {
2855 stream.next_parser_token();
2856 replacement_end = first.range.end;
2857 break;
2858 }
2859
2860 let item_start = first.range.start;
2861 let mut item_end = item_start;
2862 let mut names: SmallVec<[&'s str; 2]> = SmallVec::new();
2863 let mut has_from = false;
2864 let mut delimiter = first;
2865 loop {
2866 if matches!(
2867 delimiter.kind,
2868 TokenKind::Comma | TokenKind::Semicolon | TokenKind::RightCurlyBracket
2869 ) {
2870 break;
2871 }
2872
2873 if delimiter.kind == TokenKind::Function
2874 && stream.slice(delimiter.range.start, delimiter.range.end)? == "global("
2875 {
2876 let global_start = delimiter.range.start;
2877 stream.next_parser_token();
2878 let name_token = stream.next_parser_token().token;
2879 let Some(name_range) = ident_like_range(name_token) else {
2880 self.handle_warning.handle_warning(Warning {
2881 range: Range::new(
2882 name_token.range.start,
2883 name_token
2884 .range
2885 .start
2886 .saturating_add(2)
2887 .min(stream.source_end()),
2888 ),
2889 kind: WarningKind::Unexpected {
2890 message: "Expected ident during parsing of 'composes'",
2891 },
2892 });
2893 return Some(());
2894 };
2895 let right_parenthesis = stream.peek_significant_skipping_comments(true).token;
2896 if right_parenthesis.kind != TokenKind::RightParenthesis {
2897 self.handle_warning.handle_warning(Warning {
2898 range: Range::new(
2899 right_parenthesis.range.start,
2900 right_parenthesis.range.end,
2901 ),
2902 kind: WarningKind::Unexpected {
2903 message: "Expected ')' during parsing of 'composes'",
2904 },
2905 });
2906 return Some(());
2907 }
2908 stream.next_parser_token();
2909 item_end = right_parenthesis.range.end;
2910 self.dependency_context.push_composes(
2911 local_classes.iter().copied(),
2912 std::iter::once(stream.slice(name_range.start, name_range.end)?),
2913 Some("global"),
2914 Range::new(global_start, item_end),
2915 );
2916 delimiter = stream.peek_significant_skipping_comments(true).token;
2917 continue;
2918 }
2919
2920 let Some(name_range) = ident_like_range(delimiter) else {
2921 let name_start = delimiter.range.start;
2922 self.handle_warning.handle_warning(Warning {
2923 range: Range::new(
2924 name_start,
2925 name_start.saturating_add(2).min(stream.source_end()),
2926 ),
2927 kind: WarningKind::Unexpected {
2928 message: "Expected ident during parsing of 'composes'",
2929 },
2930 });
2931 return Some(());
2932 };
2933 let ident = stream.slice(name_range.start, name_range.end)?;
2934 if !names.is_empty() && ident.eq_ignore_ascii_case("from") {
2935 stream.next_parser_token();
2936 let path = stream.peek_significant_skipping_comments(true).token;
2937 if matches!(
2938 path.kind,
2939 TokenKind::QuotedString | TokenKind::Ident | TokenKind::Function
2940 ) {
2941 let path_range = if path.kind == TokenKind::Function {
2942 path.value_range
2943 } else {
2944 path.range
2945 };
2946 item_end = path_range.end;
2947 self.dependency_context.push_composes(
2948 local_classes.iter().copied(),
2949 std::mem::take(&mut names),
2950 Some(stream.slice(path_range.start, path_range.end)?),
2951 Range::new(item_start, item_end),
2952 );
2953 has_from = true;
2954 stream.next_parser_token();
2955 delimiter = stream.peek_significant_skipping_comments(true).token;
2956 break;
2957 }
2958 names.push(ident);
2959 item_end = name_range.end;
2960 delimiter = path;
2961 continue;
2962 }
2963 names.push(ident);
2964 item_end = name_range.end;
2965 stream.next_parser_token();
2966 delimiter = stream.peek_significant_skipping_comments(true).token;
2967 }
2968
2969 if has_from {
2970 if delimiter.kind == TokenKind::Comma {
2971 stream.next_parser_token();
2972 replacement_end = delimiter.range.end;
2973 continue;
2974 }
2975 if delimiter.kind == TokenKind::Semicolon {
2976 stream.next_parser_token();
2977 replacement_end = delimiter.range.end;
2978 break;
2979 }
2980 if delimiter.kind == TokenKind::RightCurlyBracket {
2981 replacement_end = item_end;
2982 break;
2983 }
2984 replacement_end = item_end;
2985 break;
2986 }
2987
2988 if delimiter.kind == TokenKind::Comma {
2989 if !names.is_empty() {
2990 self.dependency_context.push_composes(
2991 local_classes.iter().copied(),
2992 names,
2993 None,
2994 Range::new(item_start, item_end),
2995 );
2996 }
2997 stream.next_parser_token();
2998 replacement_end = delimiter.range.end;
2999 continue;
3000 }
3001
3002 if delimiter.kind == TokenKind::Semicolon {
3003 if !names.is_empty() {
3004 self.dependency_context.push_composes(
3005 local_classes.iter().copied(),
3006 names,
3007 None,
3008 Range::new(item_start, item_end),
3009 );
3010 }
3011 stream.next_parser_token();
3012 replacement_end = delimiter.range.end;
3013 break;
3014 }
3015
3016 if delimiter.kind == TokenKind::RightCurlyBracket {
3017 if !names.is_empty() {
3018 self.dependency_context.push_composes(
3019 local_classes.iter().copied(),
3020 names,
3021 None,
3022 Range::new(item_start, item_end),
3023 );
3024 }
3025 replacement_end = item_end;
3026 break;
3027 }
3028
3029 self.handle_warning.handle_warning(Warning {
3033 range: Range::new(
3034 delimiter.range.start,
3035 delimiter
3036 .range
3037 .start
3038 .saturating_add(2)
3039 .min(stream.source_end()),
3040 ),
3041 kind: WarningKind::Unexpected {
3042 message: "Expected ident during parsing of 'composes'",
3043 },
3044 });
3045 return Some(());
3046 }
3047 self.dependency_context
3048 .push_dependency(Dependency::Replace {
3049 content: "",
3050 range: Range::new(start, replacement_end),
3051 });
3052 Some(())
3053 }
3054}
3055
3056impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
3057 fn handle_comment(
3058 &mut self,
3059 lexer: &mut DependencyLexer<'s>,
3060 start: Pos,
3061 end: Pos,
3062 ) -> Option<()> {
3063 let Some(mode_data) = &mut self.mode_data else {
3064 return Some(());
3065 };
3066 if !mode_data.is_pure_mode() || end < start + 4 {
3067 return Some(());
3068 }
3069
3070 let content = lexer.slice(start + 2, end - 2)?;
3071 if is_css_modules_magic_comment(content, "cssmodules-pure-ignore") {
3072 mode_data.mark_pure_ignore();
3073 } else if matches!(self.scope, Scope::TopLevel)
3074 && self.block_nesting_level == 0
3075 && is_css_modules_magic_comment(content, "cssmodules-pure-no-check")
3076 {
3077 mode_data.mark_pure_no_check();
3078 }
3079 Some(())
3080 }
3081
3082 fn handle_url(
3083 &mut self,
3084 lexer: &mut DependencyLexer<'s>,
3085 start: Pos,
3086 end: Pos,
3087 content_start: Pos,
3088 content_end: Pos,
3089 flags: TokenFlags,
3090 ) -> Option<()> {
3091 let value = lexer.slice(content_start, content_end)?;
3092 match self.scope {
3093 Scope::InAtImport(ref mut import_data) => {
3094 if import_data.in_supports() {
3095 return Some(());
3096 }
3097 if import_data.url.is_some() {
3098 self.handle_warning.handle_warning(Warning {
3099 range: Range::new(import_data.start, end),
3100 kind: WarningKind::DuplicateUrl {
3101 when: lexer.slice(import_data.start, end)?,
3102 },
3103 });
3104 return Some(());
3105 }
3106 import_data.prelude.push(ImportPreludeNode::Url {
3107 range: Range::new(start, end),
3108 });
3109 import_data.url = Some(value);
3110 import_data.url_flags = flags;
3111 import_data.url_range = Some(Range::new(start, end));
3112 }
3113 Scope::InBlock => self.dependency_context.push_dependency(Dependency::Url {
3114 request: value,
3115 range: Range::new(start, end),
3116 kind: UrlRangeKind::Function,
3117 }),
3118 _ => {}
3119 }
3120 Some(())
3121 }
3122
3123 fn handle_string(
3124 &mut self,
3125 lexer: &mut DependencyLexer<'s>,
3126 start: Pos,
3127 end: Pos,
3128 flags: TokenFlags,
3129 ) -> Option<()> {
3130 match self.scope {
3131 Scope::InAtImport(ref mut import_data) => {
3132 let inside_url = matches!(
3133 self.balanced.last(),
3134 Some(last) if matches!(last.kind, BalancedItemKind::Url)
3135 );
3136
3137 if import_data.in_supports() || (!inside_url && import_data.url.is_some()) {
3139 return Some(());
3140 }
3141
3142 if inside_url && import_data.url.is_some() {
3143 self.handle_warning.handle_warning(Warning {
3144 range: Range::new(import_data.start, end),
3145 kind: WarningKind::DuplicateUrl {
3146 when: lexer.slice(import_data.start, end)?,
3147 },
3148 });
3149 return Some(());
3150 }
3151
3152 let value = lexer.slice(start + 1, end - 1)?;
3153 import_data.url = Some(value);
3154 import_data.url_flags = flags;
3155 if !inside_url {
3157 import_data.prelude.push(ImportPreludeNode::Url {
3158 range: Range::new(start, end),
3159 });
3160 import_data.url_range = Some(Range::new(start, end));
3161 }
3162 }
3163 Scope::InBlock => {
3164 if let Some(mode_data) = &self.mode_data
3165 && mode_data.is_property_local_mode()
3166 && matches!(
3167 self.in_grid_property,
3168 Some(GridPropertyKind::TemplateLike | GridPropertyKind::TemplateAreas)
3169 )
3170 {
3171 for range in
3172 parse_grid_template_area_ranges(lexer.slice(start + 1, end - 1)?, start + 1)
3173 {
3174 self.dependency_context
3175 .push_dependency(Dependency::LocalGridDecl {
3176 name: lexer.slice(range.start, range.end)?,
3177 range,
3178 });
3179 }
3180 }
3181 let Some(last) = self.balanced.last() else {
3182 return Some(());
3183 };
3184 let kind = match last.kind {
3185 BalancedItemKind::Url => UrlRangeKind::String,
3186 BalancedItemKind::ImageSet => UrlRangeKind::Function,
3187 _ => return Some(()),
3188 };
3189 let value = lexer.slice(start + 1, end - 1)?;
3190 self.dependency_context.push_dependency(Dependency::Url {
3191 request: value,
3192 range: Range::new(start, end),
3193 kind,
3194 });
3195 }
3196 _ => {}
3197 }
3198 Some(())
3199 }
3200
3201 fn handle_at_keyword(
3202 &mut self,
3203 stream: &mut DependencyTokenStream<'_, 's>,
3204 start: Pos,
3205 end: Pos,
3206 flags: TokenFlags,
3207 ) -> Option<()> {
3208 let name = stream.slice_trusted(start, end);
3209 let kind = Self::classify_at_rule(name, flags);
3210 self.set_scan_context(stream, ScanContext::AtRule);
3211 if kind == AtRuleKind::Namespace {
3212 self.scope = Scope::AtNamespaceInvalid;
3213 self.handle_warning.handle_warning(Warning {
3214 range: Range::new(start, end),
3215 kind: WarningKind::NamespaceNotSupportedInBundledCss,
3216 });
3217 } else if kind == AtRuleKind::Import {
3218 if !self.allow_import_at_rule {
3219 self.scope = Scope::AtImportInvalid;
3220 self.handle_warning.handle_warning(Warning {
3221 range: Range::new(start, end),
3222 kind: WarningKind::NotPrecededAtImport,
3223 });
3224 return Some(());
3225 }
3226 self.scope = Scope::InAtImport(ImportData::new(start));
3227 } else if kind == AtRuleKind::Charset {
3228 self.lex_charset_at_rule(stream, start)?;
3229 self.set_scan_context(stream, ScanContext::TopLevel);
3230 self.is_next_rule_prelude = true;
3231 } else if self.mode_data.is_some() {
3232 let mut can_contain_rules = true;
3233 if kind == AtRuleKind::Value {
3234 self.lex_value_at_rule(stream, start)?;
3235 can_contain_rules = false;
3236 self.set_scan_context(stream, ScanContext::TopLevel);
3237 self.is_next_rule_prelude = true;
3238 } else if kind == AtRuleKind::Keyframes {
3239 self.lex_local_keyframes_decl(stream)?;
3240 } else if kind == AtRuleKind::Container {
3241 self.lex_local_container_at_rule(stream)?;
3242 } else if kind == AtRuleKind::Function {
3243 self.lex_local_function_decl(stream)?;
3244 } else if kind == AtRuleKind::Property {
3245 self.lex_local_dashed_ident_decl(
3246 stream,
3247 |name, range| Dependency::LocalPropertyDecl { name, range },
3248 |range| Warning {
3249 range,
3250 kind: WarningKind::Unexpected {
3251 message: "Expected starts with '--' during parsing of '@property'",
3252 },
3253 },
3254 |range| Warning {
3255 range,
3256 kind: WarningKind::Unexpected {
3257 message: "Expected '{' during parsing of '@property'",
3258 },
3259 },
3260 )?;
3261 } else if kind == AtRuleKind::CounterStyle {
3262 self.lex_local_counter_style_decl(stream)?;
3263 } else if kind == AtRuleKind::FontPaletteValues {
3264 self.lex_local_dashed_ident_decl(
3265 stream,
3266 |name, range| Dependency::LocalFontPaletteDecl { name, range },
3267 |range| Warning {
3268 range,
3269 kind: WarningKind::Unexpected {
3270 message: "Expected starts with '--' during parsing of '@font-palette-values'",
3271 }
3272 },
3273 |range| Warning {
3274 range,
3275 kind: WarningKind::Unexpected {
3276 message: "Expected '{' during parsing of '@font-palette-values'",
3277 }
3278 },
3279 )?;
3280 } else {
3281 self.is_next_rule_prelude = kind == AtRuleKind::Scope;
3282 if self.is_next_rule_prelude {
3283 self.set_scan_context(stream, ScanContext::Selector);
3284 }
3285 }
3286
3287 let mode_data = self.mode_data.as_mut().unwrap();
3288 if can_contain_rules && self.block_nesting_level == 0 {
3289 mode_data.composes_local_classes.find_at_keyword();
3290 }
3291
3292 if mode_data.is_pure_mode() {
3293 mode_data.pure_global = None;
3294 }
3295 }
3296 Some(())
3297 }
3298
3299 fn handle_semicolon(
3300 &mut self,
3301 lexer: &mut DependencyLexer<'s>,
3302 start: Pos,
3303 end: Pos,
3304 ) -> Option<()> {
3305 match self.scope {
3306 Scope::InAtImport(ref import_data) => {
3307 let Some(url) = import_data.url else {
3308 if let Some((name, name_range)) = import_data.prelude.icss_import_url() {
3309 self.dependency_context
3310 .push_dependency(Dependency::ICSSImportUrl {
3311 name,
3312 range: Range::new(import_data.start, end),
3313 name_range: *name_range,
3314 });
3315 self.scope = Scope::TopLevel;
3316 return Some(());
3317 }
3318 self.handle_warning.handle_warning(Warning {
3319 range: Range::new(import_data.start, end),
3320 kind: WarningKind::ExpectedUrl {
3321 when: lexer.slice(import_data.start, end)?,
3322 },
3323 });
3324 self.scope = Scope::TopLevel;
3325 return Some(());
3326 };
3327 let Some(url_range) = &import_data.url_range else {
3328 self.handle_warning.handle_warning(Warning {
3329 range: Range::new(start, end),
3330 kind: WarningKind::Unexpected {
3331 message: "Unexpected ';' during parsing of '@import url()'",
3332 },
3333 });
3334 self.scope = Scope::TopLevel;
3335 return Some(());
3336 };
3337 if let Some(range) = import_data.prelude.first_non_url_before(url_range) {
3338 self.handle_warning.handle_warning(Warning {
3339 range: *url_range,
3340 kind: WarningKind::ExpectedUrlBefore {
3341 when: lexer.slice(range.start, url_range.end)?,
3342 },
3343 });
3344 self.scope = Scope::TopLevel;
3345 return Some(());
3346 }
3347 let layer = match &import_data.layer {
3348 ImportDataLayer::None => None,
3349 ImportDataLayer::EndLayer { value, range } => {
3350 if url_range.start > range.start {
3351 self.handle_warning.handle_warning(Warning {
3352 range: *url_range,
3353 kind: WarningKind::ExpectedUrlBefore {
3354 when: lexer.slice(range.start, url_range.end)?,
3355 },
3356 });
3357 self.scope = Scope::TopLevel;
3358 return Some(());
3359 }
3360 Some(*value)
3361 }
3362 };
3363 let supports = match &import_data.supports {
3364 ImportDataSupports::None => None,
3365 ImportDataSupports::InSupports => {
3366 self.handle_warning.handle_warning(Warning {
3367 range: Range::new(start, end),
3368 kind: WarningKind::Unexpected {
3369 message: "Unexpected ';' during parsing of 'supports()'",
3370 },
3371 });
3372 None
3373 }
3374 ImportDataSupports::EndSupports { value, range } => {
3375 if url_range.start > range.start {
3376 self.handle_warning.handle_warning(Warning {
3377 range: *url_range,
3378 kind: WarningKind::ExpectedUrlBefore {
3379 when: lexer.slice(range.start, url_range.end)?,
3380 },
3381 });
3382 self.scope = Scope::TopLevel;
3383 return Some(());
3384 }
3385 Some(*value)
3386 }
3387 };
3388 if let Some(layer_range) = import_data.layer_range()
3389 && let Some(supports_range) = import_data.supports_range()
3390 && layer_range.start > supports_range.start
3391 {
3392 self.handle_warning.handle_warning(Warning {
3393 range: *layer_range,
3394 kind: WarningKind::ExpectedLayerBefore {
3395 when: lexer.slice(supports_range.start, layer_range.end)?,
3396 },
3397 });
3398 self.scope = Scope::TopLevel;
3399 return Some(());
3400 }
3401 let last_end = import_data
3402 .supports_range()
3403 .or_else(|| import_data.layer_range())
3404 .unwrap_or(url_range)
3405 .end;
3406 let media = self.get_media(lexer, last_end, start);
3407 self.dependency_context.push_import(
3408 url,
3409 Range::new(import_data.start, end),
3410 layer,
3411 supports,
3412 media,
3413 );
3414 self.scope = Scope::TopLevel;
3415 }
3416 Scope::AtImportInvalid | Scope::AtNamespaceInvalid => {
3417 self.scope = Scope::TopLevel;
3418 }
3419 Scope::InBlock => {
3420 if let Some(mode_data) = &mut self.mode_data {
3421 mode_data.pure_global = Some(end);
3422
3423 if mode_data.is_property_local_mode() {
3424 if self.in_animation_property.is_some() {
3425 self.handle_local_keyframes_dependency(lexer)?;
3426 self.exit_animation_property();
3427 }
3428 if self.in_list_style_property.is_some() {
3429 self.handle_local_counter_style_dependency(lexer)?;
3430 self.exit_list_style_property();
3431 }
3432 if self.in_font_palette_property.is_some() {
3433 self.handle_local_font_palette_dependency(lexer)?;
3434 self.exit_font_palette_property();
3435 }
3436 if self.in_container_property.is_some() {
3437 self.exit_container_property();
3438 }
3439 if self.in_grid_property.is_some() {
3440 self.exit_grid_property();
3441 }
3442 }
3443 }
3444 self.pending_custom_property = None;
3445 self.pending_grid_property = None;
3446 self.property_kind = PropertyKind::Generic;
3447 }
3448 Scope::TopLevel => {
3449 self.is_next_rule_prelude = true;
3450 }
3451 }
3452 Some(())
3453 }
3454
3455 fn handle_function(
3456 &mut self,
3457 stream: &mut DependencyTokenStream<'_, 's>,
3458 start: Pos,
3459 end: Pos,
3460 flags: TokenFlags,
3461 ) -> Option<()> {
3462 let name = stream.slice_trusted(start, end);
3463 let mut normalized = [0; MAX_CSS_KEYWORD_LEN];
3464 let normalized_name = if flags.has_escape() {
3465 decode_css_keyword(name, &mut normalized)
3466 } else {
3467 lowercase_ascii_keyword(name, &mut normalized)
3468 };
3469 let item = normalized_name.map_or_else(
3470 || BalancedItem::new_other(start, end),
3471 |name| BalancedItem::new_normalized(name, start, end),
3472 );
3473 let at_import_top_level =
3474 matches!(self.scope, Scope::InAtImport(_)) && self.balanced.is_empty();
3475 self.balanced.push(item, self.mode_data.as_mut());
3476
3477 if let Scope::InAtImport(ref mut import_data) = self.scope {
3478 if at_import_top_level && normalized_name == Some("url(") {
3479 import_data.prelude.push(ImportPreludeNode::Url {
3480 range: Range::new(start, end),
3481 });
3482 } else if at_import_top_level && normalized_name == Some("layer(") {
3483 import_data.prelude.push(ImportPreludeNode::Layer {
3484 range: Range::new(start, end),
3485 });
3486 } else if at_import_top_level && normalized_name == Some("supports(") {
3487 import_data.prelude.push(ImportPreludeNode::Supports {
3488 range: Range::new(start, end),
3489 });
3490 import_data.supports = ImportDataSupports::InSupports;
3491 } else if at_import_top_level {
3492 import_data.prelude.push(ImportPreludeNode::Other {
3493 range: Range::new(start, end),
3494 });
3495 } else if normalized_name == Some("supports(") {
3496 import_data.supports = ImportDataSupports::InSupports;
3497 }
3498 }
3499
3500 if let Scope::InAtImport(ref mut import_data) = self.scope {
3501 let layer_end = at_import_top_level && normalized_name == Some("layer(");
3502 let supports_end = normalized_name == Some("supports(");
3503 if layer_end || supports_end {
3504 let Some(close) = stream.fast_forward(TokenKind::RightParenthesis) else {
3505 return Some(());
3506 };
3507 self.balanced.pop(self.mode_data.as_mut());
3508 if layer_end {
3509 import_data.layer = ImportDataLayer::EndLayer {
3510 value: stream.slice(end, close.end.saturating_sub(1))?,
3511 range: Range::new(start, close.end),
3512 };
3513 } else {
3514 import_data.supports = ImportDataSupports::EndSupports {
3515 value: stream.slice(end, close.end.saturating_sub(1))?,
3516 range: Range::new(start, close.end),
3517 };
3518 }
3519 return Some(());
3520 }
3521 }
3522
3523 let Some(mode_data) = &self.mode_data else {
3524 return Some(());
3525 };
3526 if mode_data.is_current_local_mode() && name.starts_with("--") && end > start + 1 {
3527 self.dependency_context
3528 .push_dependency(Dependency::LocalFunction {
3529 name: stream.slice(start + 2, end - 1)?,
3530 range: Range::new(start, end - 1),
3531 });
3532 }
3533 if mode_data.is_current_local_mode() && normalized_name == Some("var(") {
3534 self.lex_local_var(stream)?;
3535 }
3536 Some(())
3537 }
3538
3539 fn handle_left_parenthesis(
3540 &mut self,
3541 _: &mut DependencyLexer<'s>,
3542 start: Pos,
3543 end: Pos,
3544 ) -> Option<()> {
3545 self.balanced
3546 .push(BalancedItem::new_other(start, end), self.mode_data.as_mut());
3547 Some(())
3548 }
3549
3550 fn handle_right_parenthesis(
3551 &mut self,
3552 lexer: &mut DependencyLexer<'s>,
3553 leading_start: Pos,
3554 start: Pos,
3555 end: Pos,
3556 ) -> Option<()> {
3557 let Some(last) = self.balanced.pop(self.mode_data.as_mut()) else {
3558 return Some(());
3559 };
3560 if let Some(mode_data) = &mut self.mode_data {
3561 let mut is_function = last.kind.is_mode_function();
3562 let mut function_end = last.range.end;
3563 if last.kind.is_mode_class() {
3564 self.balanced.pop_mode_pseudo_class(mode_data);
3565 let popped = self.balanced.pop_without_moda_data().unwrap();
3566 debug_assert!(!matches!(
3567 popped.kind,
3568 BalancedItemKind::GlobalClass | BalancedItemKind::LocalClass
3569 ));
3570 is_function = popped.kind.is_mode_function();
3571 function_end = popped.range.end;
3572 }
3573 if is_function {
3574 let is_empty =
3575 start == function_end || trivia_only(lexer.slice(function_end, start)?);
3576 let replacement_start = if is_empty {
3577 function_end
3578 } else {
3579 leading_start
3580 };
3581 if is_empty {
3582 let maybe_left_parenthesis_start = function_end.saturating_sub(1);
3583 self.handle_warning.handle_warning(Warning {
3584 range: Range::new(maybe_left_parenthesis_start, end),
3585 kind: WarningKind::Unexpected {
3586 message: "':global()' or ':local()' can't be empty",
3587 },
3588 });
3589 }
3590 self.dependency_context
3591 .push_dependency(Dependency::Replace {
3592 content: "",
3593 range: Range::new(replacement_start, end),
3594 });
3595 }
3596 }
3597 if let Scope::InAtImport(ref mut import_data) = self.scope {
3598 let not_in_supports = !import_data.in_supports();
3599 if matches!(last.kind, BalancedItemKind::Url) && not_in_supports {
3600 import_data.url_range = Some(Range::new(last.range.start, end));
3601 } else if matches!(last.kind, BalancedItemKind::Layer) && not_in_supports {
3602 import_data.layer = ImportDataLayer::EndLayer {
3603 value: lexer.slice(last.range.end, end - 1)?,
3604 range: Range::new(last.range.start, end),
3605 };
3606 } else if matches!(last.kind, BalancedItemKind::Supports) {
3607 import_data.supports = ImportDataSupports::EndSupports {
3608 value: lexer.slice(last.range.end, end - 1)?,
3609 range: Range::new(last.range.start, end),
3610 }
3611 }
3612 }
3613 Some(())
3614 }
3615
3616 fn handle_ident(
3617 &mut self,
3618 stream: &mut DependencyTokenStream<'_, 's>,
3619 start: Pos,
3620 end: Pos,
3621 flags: TokenFlags,
3622 ) -> Option<()> {
3623 match self.scope {
3624 Scope::InBlock => {
3625 let ident = stream.slice_trusted(start, end);
3626 let is_declaration_name = self.scan_context == ScanContext::DeclarationName;
3627 if is_declaration_name {
3628 let property_local_mode = self
3629 .mode_data
3630 .as_ref()
3631 .is_some_and(ModeData::is_property_local_mode);
3632 (self.property_kind, self.pending_grid_property) =
3633 Self::classify_property(ident, flags, property_local_mode);
3634 self.pending_custom_property = (self.property_kind
3635 == PropertyKind::CustomProperty)
3636 .then_some(Range::new(start, end));
3637 if self.property_kind != PropertyKind::Composes {
3638 return Some(());
3639 }
3640 }
3641 let can_reference_icss = matches!(
3642 self.scan_context,
3643 ScanContext::GenericValue | ScanContext::SpecialValue(_) | ScanContext::AtRule
3644 );
3645 if can_reference_icss
3646 && !self.icss_symbols.is_empty()
3647 && self.contains_icss_symbol(ident)
3648 {
3649 self.dependency_context
3650 .push_dependency(Dependency::ICSSSymbol {
3651 name: ident,
3652 range: Range::new(start, end),
3653 });
3654 return Some(());
3655 }
3656 let Some(mode_data) = &mut self.mode_data else {
3657 return Some(());
3658 };
3659 if mode_data.is_current_local_mode()
3660 && self
3661 .balanced
3662 .last()
3663 .map(|last| last.kind.is_mode_function())
3664 .unwrap_or(false)
3665 && ident.starts_with("--")
3666 && stream.peek_parser_token().token.kind == TokenKind::RightParenthesis
3667 {
3668 self.dependency_context
3669 .push_dependency(Dependency::LocalFunction {
3670 name: stream.slice(start + 2, end)?,
3671 range: Range::new(start, end),
3672 });
3673 return Some(());
3674 }
3675 if mode_data.is_property_local_mode()
3676 && matches!(self.scan_context, ScanContext::SpecialValue(_))
3677 {
3678 if let Some(animation) = &mut self.in_animation_property {
3679 if self.balanced.is_empty() {
3681 animation.set_rename(
3682 stream.slice(start, end)?,
3683 flags,
3684 Range::new(start, end),
3685 );
3686 }
3687 return Some(());
3688 }
3689
3690 if let Some(list_style) = &mut self.in_list_style_property {
3691 if self.balanced.is_empty() {
3693 list_style.set_rename(
3694 stream.slice(start, end)?,
3695 flags,
3696 Range::new(start, end),
3697 );
3698 }
3699 return Some(());
3700 }
3701
3702 if let Some(font_palette) = &mut self.in_font_palette_property {
3703 if self.balanced.is_empty()
3705 || matches!(self.balanced.last(), Some(last) if matches!(last.kind, BalancedItemKind::PaletteMix))
3706 {
3707 font_palette.set_rename(
3708 stream.slice(start, end)?,
3709 flags,
3710 Range::new(start, end),
3711 );
3712 }
3713 return Some(());
3714 }
3715
3716 if let Some(container) = &mut self.in_container_property {
3717 if self.balanced.is_empty() {
3718 container.set_rename(ident, flags, Range::new(start, end));
3719 if let Some(range) = container.take_rename(self.balanced.len()) {
3720 self.dependency_context.push_dependency(
3721 Dependency::LocalContainerDecl {
3722 name: stream.slice(range.start, range.end)?,
3723 range,
3724 },
3725 );
3726 }
3727 }
3728 return Some(());
3729 }
3730
3731 if self.in_grid_property.is_some() {
3732 if self.balanced.is_empty() && !is_reserved_grid_ident(ident, flags) {
3733 self.dependency_context
3734 .push_dependency(Dependency::LocalGrid {
3735 name: ident,
3736 range: Range::new(start, end),
3737 });
3738 }
3739 return Some(());
3740 }
3741 }
3742
3743 if is_declaration_name && self.property_kind == PropertyKind::Composes {
3744 if self.block_nesting_level != 1 {
3745 self.handle_warning.handle_warning(Warning {
3746 range: Range::new(start, end),
3747 kind: WarningKind::UnexpectedComposition {
3748 message: "not allowed in nested rule",
3749 },
3750 });
3751 return Some(());
3752 }
3753 let Some(local_classes) = mode_data
3754 .composes_local_classes
3755 .get_valid_local_classes(stream.lexer())
3756 else {
3757 self.handle_warning.handle_warning(Warning {
3758 range: Range::new(start, end),
3759 kind: WarningKind::UnexpectedComposition {
3760 message: "only allowed when selector is single :local class",
3761 },
3762 });
3763 return Some(());
3764 };
3765 return self.lex_composes(stream, local_classes, start);
3766 }
3767 }
3768 Scope::InAtImport(ref mut import_data) => {
3769 if !self.balanced.is_empty() || import_data.in_supports() {
3770 return Some(());
3771 }
3772
3773 let ident = stream.slice_trusted(start, end);
3774 if ident.eq_ignore_ascii_case("layer") {
3775 import_data.prelude.push(ImportPreludeNode::Layer {
3776 range: Range::new(start, end),
3777 });
3778 import_data.layer = ImportDataLayer::EndLayer {
3779 value: "",
3780 range: Range::new(start, end),
3781 }
3782 } else if import_data.url.is_none() && import_data.prelude.is_empty() {
3783 import_data
3784 .prelude
3785 .push(ImportPreludeNode::IcssUrlCandidate {
3786 name: ident,
3787 range: Range::new(start, end),
3788 });
3789 } else if import_data.url.is_none() {
3790 import_data.prelude.push(ImportPreludeNode::Other {
3791 range: Range::new(start, end),
3792 });
3793 }
3794 }
3795 Scope::TopLevel => {
3796 let Some(mode_data) = &mut self.mode_data else {
3797 return Some(());
3798 };
3799
3800 mode_data.composes_local_classes.invalidate();
3801 }
3802 _ => {}
3803 }
3804 Some(())
3805 }
3806
3807 fn handle_class(
3808 &mut self,
3809 lexer: &mut DependencyLexer<'s>,
3810 start: Pos,
3811 end: Pos,
3812 _flags: TokenFlags,
3813 ) -> Option<()> {
3814 let Some(mode_data) = &mut self.mode_data else {
3815 return Some(());
3816 };
3817 let name = lexer.slice_trusted(start, end);
3818 if name == "." {
3819 self.handle_warning.handle_warning(Warning {
3820 range: Range::new(start, end),
3821 kind: WarningKind::Unexpected {
3822 message: "Invalid class selector syntax",
3823 },
3824 });
3825 return Some(());
3826 }
3827 if mode_data.is_current_local_mode() {
3828 self.dependency_context
3829 .push_dependency(Dependency::LocalClass {
3830 name,
3831 range: Range::new(start, end),
3832 explicit: mode_data.is_mode_explicit(),
3833 });
3834 if self.block_nesting_level == 0 {
3835 mode_data
3836 .composes_local_classes
3837 .find_local_class(start + 1, end);
3838 }
3839
3840 if mode_data.is_pure_mode() {
3841 mode_data.pure_global = None;
3842 }
3843 }
3844 Some(())
3845 }
3846
3847 fn handle_id(
3848 &mut self,
3849 lexer: &mut DependencyLexer<'s>,
3850 start: Pos,
3851 end: Pos,
3852 _flags: TokenFlags,
3853 ) -> Option<()> {
3854 let Some(mode_data) = &mut self.mode_data else {
3855 return Some(());
3856 };
3857 let name = lexer.slice_trusted(start, end);
3858 if name == "#" {
3859 self.handle_warning.handle_warning(Warning {
3860 range: Range::new(start, end),
3861 kind: WarningKind::Unexpected {
3862 message: "Invalid id selector syntax",
3863 },
3864 });
3865 return Some(());
3866 }
3867 if mode_data.is_current_local_mode() {
3868 self.dependency_context
3869 .push_dependency(Dependency::LocalId {
3870 name,
3871 range: Range::new(start, end),
3872 explicit: mode_data.is_mode_explicit(),
3873 });
3874
3875 if self.block_nesting_level == 0 {
3876 mode_data.composes_local_classes.invalidate();
3877 }
3878
3879 if mode_data.is_pure_mode() {
3880 mode_data.pure_global = None;
3881 }
3882 }
3883 Some(())
3884 }
3885
3886 fn handle_left_curly_bracket(
3887 &mut self,
3888 stream: &mut DependencyTokenStream<'_, 's>,
3889 start: Pos,
3890 _: Pos,
3891 ) -> Option<()> {
3892 if matches!(self.scope, Scope::InBlock)
3893 && self.scan_context != ScanContext::Selector
3894 && !self.balanced.is_empty()
3895 {
3896 self.balanced.push(
3897 BalancedItem::new_curly(start, start + 1),
3898 self.mode_data.as_mut(),
3899 );
3900 return Some(());
3901 }
3902 match self.scope {
3903 Scope::TopLevel => {
3904 self.allow_import_at_rule = false;
3905 self.scope = Scope::InBlock;
3906 if self.mode_data.is_none()
3907 || matches!(&self.mode_data, Some(mode_data) if !matches!(mode_data.composes_local_classes.is_single, SingleLocalClass::AtKeyword))
3908 {
3909 self.block_nesting_level = 1;
3910 }
3911 }
3912 Scope::InBlock => {
3913 let is_at_rule_block = matches!(
3914 &self.mode_data,
3915 Some(mode_data)
3916 if matches!(mode_data.composes_local_classes.is_single, SingleLocalClass::AtKeyword)
3917 );
3918 if !is_at_rule_block {
3919 self.block_nesting_level += 1;
3920 }
3921 }
3922 _ => return Some(()),
3923 }
3924 let (pure_check_disabled_for_selector, enter_pure_ignored_block) =
3925 if let Some(mode_data) = &self.mode_data {
3926 (
3927 mode_data.is_pure_check_disabled(),
3928 mode_data.pure_ignore_pending,
3929 )
3930 } else {
3931 (false, false)
3932 };
3933 if self.mode_data.is_none() {
3934 self.set_scan_context(stream, ScanContext::BlockItem);
3935 return Some(());
3936 }
3937 if let Some(mode_data) = &mut self.mode_data {
3938 if let Some(pure_global_start) = mode_data
3939 .pure_global
3940 .filter(|_| mode_data.is_pure_mode() && !pure_check_disabled_for_selector)
3941 {
3942 self.handle_warning.handle_warning(Warning {
3943 range: Range::new(pure_global_start, start),
3944 kind: WarningKind::NotPure {
3945 message: "Selector is not pure (pure selectors must contain at least one local class or id)",
3946 }
3947 });
3948 }
3949
3950 if enter_pure_ignored_block {
3951 mode_data.enter_block(self.block_nesting_level);
3952 }
3953
3954 if let Some(resulting_global_start) = mode_data
3955 .resulting_global
3956 .filter(|_| mode_data.is_current_local_mode())
3957 {
3958 self.handle_warning.handle_warning(Warning {
3959 range: Range::new(resulting_global_start, start),
3960 kind: WarningKind::InconsistentModeResult,
3961 });
3962 }
3963 mode_data.resulting_global = None;
3964
3965 self.balanced.update_property_mode(mode_data);
3966 self.balanced.pop_mode_pseudo_class(mode_data);
3967 if self.is_next_rule_prelude && self.block_nesting_level == 0 {
3968 let mode_data = self.mode_data.as_mut().unwrap();
3969 mode_data.composes_local_classes.reset_to_initial();
3970 }
3971
3972 debug_assert!(
3973 self.balanced.is_empty(),
3974 "balanced should be empty when end of selector"
3975 );
3976 }
3977 self.exit_container_property();
3978 self.exit_grid_property();
3979 self.pending_custom_property = None;
3980 self.pending_grid_property = None;
3981 self.property_kind = PropertyKind::Generic;
3982 self.set_scan_context(stream, ScanContext::BlockItem);
3983 Some(())
3984 }
3985
3986 fn handle_right_curly_bracket(
3987 &mut self,
3988 stream: &mut DependencyTokenStream<'_, 's>,
3989 _: Pos,
3990 end: Pos,
3991 ) -> Option<()> {
3992 if matches!(self.scope, Scope::InBlock) {
3993 if matches!(
3994 self.balanced.last(),
3995 Some(last) if matches!(last.kind, BalancedItemKind::Curly)
3996 ) {
3997 self.balanced.pop(self.mode_data.as_mut());
3998 if self.block_nesting_level == 0 {
3999 self.scope = Scope::TopLevel;
4000 self.is_next_rule_prelude = true;
4001 self.set_scan_context(stream, ScanContext::TopLevel);
4002 if let Some(mode_data) = &mut self.mode_data {
4003 mode_data.composes_local_classes.reset_to_initial();
4004 }
4005 } else {
4006 self.set_scan_context(stream, ScanContext::BlockItem);
4007 }
4008 return Some(());
4009 }
4010
4011 if let Some(mode_data) = &mut self.mode_data {
4012 mode_data.pure_global = Some(end);
4013
4014 if mode_data.is_property_local_mode() {
4015 if self.in_animation_property.is_some() {
4016 self.handle_local_keyframes_dependency(stream.lexer_mut())?;
4017 self.exit_animation_property();
4018 }
4019 if self.in_list_style_property.is_some() {
4020 self.handle_local_counter_style_dependency(stream.lexer_mut())?;
4021 self.exit_list_style_property();
4022 }
4023 if self.in_font_palette_property.is_some() {
4024 self.handle_local_font_palette_dependency(stream.lexer_mut())?;
4025 self.exit_font_palette_property();
4026 }
4027 if self.in_container_property.is_some() {
4028 self.exit_container_property();
4029 }
4030 if self.in_grid_property.is_some() {
4031 self.exit_grid_property();
4032 }
4033 }
4034 }
4035 if self.block_nesting_level > 0 {
4036 self.block_nesting_level -= 1;
4037 }
4038 if let Some(mode_data) = &mut self.mode_data {
4039 mode_data.clear_pure_ignore_pending();
4040 mode_data.exit_block(self.block_nesting_level);
4041 }
4042 if self.block_nesting_level == 0 {
4043 self.scope = Scope::TopLevel;
4044 self.set_scan_context(stream, ScanContext::TopLevel);
4045 self.is_next_rule_prelude = true;
4046 if let Some(mode_data) = &mut self.mode_data {
4047 mode_data.composes_local_classes.reset_to_initial();
4048 }
4049 } else {
4050 self.set_scan_context(stream, ScanContext::BlockItem);
4051 }
4052 self.pending_custom_property = None;
4053 self.pending_grid_property = None;
4054 self.property_kind = PropertyKind::Generic;
4055 }
4056 Some(())
4057 }
4058
4059 fn handle_pseudo_function(
4060 &mut self,
4061 stream: &mut DependencyTokenStream<'_, 's>,
4062 start: Pos,
4063 end: Pos,
4064 flags: TokenFlags,
4065 ) -> Option<()> {
4066 let name = stream.slice_trusted(start, end);
4067 if let Some(mode_data) = &mut self.mode_data {
4068 if name.eq_ignore_ascii_case(":import(") {
4069 self.lex_icss_import(stream);
4070 self.dependency_context
4071 .push_dependency(Dependency::Replace {
4072 content: "",
4073 range: Range::new(start, stream.consumed_pos()),
4074 });
4075 return Some(());
4076 }
4077 if name.eq_ignore_ascii_case(":global(") || name.eq_ignore_ascii_case(":local(") {
4078 if mode_data.is_inside_mode_function() {
4079 self.handle_warning.handle_warning(Warning {
4080 range: Range::new(start, end),
4081 kind: WarningKind::ExpectedNotInside {
4082 pseudo: stream.slice(start, end)?,
4083 },
4084 });
4085 }
4086
4087 let next = stream.peek_parser_token();
4088 if next.token.kind == TokenKind::Eof {
4089 return None;
4090 }
4091 self.dependency_context
4092 .push_dependency(Dependency::Replace {
4093 content: "",
4094 range: Range::new(start, next.token.range.start),
4095 });
4096 } else if self.block_nesting_level == 0 {
4097 mode_data.composes_local_classes.invalidate();
4098 }
4099 }
4100 self.balanced.push(
4101 BalancedItem::new(name, flags, start, end),
4102 self.mode_data.as_mut(),
4103 );
4104 Some(())
4105 }
4106
4107 fn handle_pseudo_class(
4108 &mut self,
4109 stream: &mut DependencyTokenStream<'_, 's>,
4110 start: Pos,
4111 end: Pos,
4112 flags: TokenFlags,
4113 ) -> Option<()> {
4114 let Some(mode_data) = &mut self.mode_data else {
4115 return Some(());
4116 };
4117 let name = stream.slice_trusted(start, end);
4118 if name.eq_ignore_ascii_case(":global") || name.eq_ignore_ascii_case(":local") {
4119 if mode_data.is_inside_mode_function() {
4120 self.handle_warning.handle_warning(Warning {
4121 range: Range::new(start, end),
4122 kind: WarningKind::ExpectedNotInside {
4123 pseudo: stream.slice(start, end)?,
4124 },
4125 });
4126 }
4127
4128 let next = stream.peek_parser_token();
4129 if next.token.kind == TokenKind::Eof {
4130 return None;
4131 }
4132 if !next.leading.has_whitespace() {
4133 let missing_whitespace = match stream.byte_at(next.token.range.start) {
4134 Some(b'.' | b'#') => true,
4135 Some(b'{') => next.leading.first_comment_start.is_some(),
4136 _ => false,
4137 };
4138 if missing_whitespace {
4139 self.handle_warning.handle_warning(Warning {
4140 range: Range::new(start, end),
4141 kind: WarningKind::Unexpected {
4142 message: "Missing trailing whitespace",
4143 },
4144 });
4145 }
4146 }
4147 self.balanced.push(
4148 BalancedItem::new(name, flags, start, end),
4149 self.mode_data.as_mut(),
4150 );
4151 self.dependency_context
4152 .push_dependency(Dependency::Replace {
4153 content: "",
4154 range: Range::new(
4155 start,
4156 next.leading.first_comment_start.unwrap_or(next.leading.end),
4157 ),
4158 });
4159 return Some(());
4160 }
4161 if matches!(self.scope, Scope::TopLevel) && name.eq_ignore_ascii_case(":export") {
4162 self.lex_icss_export(stream)?;
4163 self.dependency_context
4164 .push_dependency(Dependency::Replace {
4165 content: "",
4166 range: Range::new(start, stream.consumed_pos()),
4167 });
4168 return Some(());
4169 }
4170
4171 if self.block_nesting_level == 0 {
4172 mode_data.composes_local_classes.invalidate();
4173 }
4174 Some(())
4175 }
4176
4177 fn handle_comma(
4178 &mut self,
4179 lexer: &mut DependencyLexer<'s>,
4180 start: Pos,
4181 end: Pos,
4182 ) -> Option<()> {
4183 let Some(mode_data) = &mut self.mode_data else {
4184 return Some(());
4185 };
4186
4187 if let Some(pure_global_start) = mode_data
4188 .pure_global
4189 .filter(|_| mode_data.is_pure_mode() && !mode_data.is_pure_check_disabled())
4190 {
4191 self.handle_warning.handle_warning(Warning {
4192 range: Range::new(pure_global_start, start),
4193 kind: WarningKind::NotPure {
4194 message: "Selector is not pure (pure selectors must contain at least one local class or id)",
4195 }
4196 });
4197 }
4198 mode_data.pure_global = Some(end);
4199
4200 if self.block_nesting_level == 0 {
4201 mode_data.composes_local_classes.find_comma(lexer)?;
4202 }
4203
4204 if let Some(resulting_global_start) = mode_data
4205 .resulting_global
4206 .filter(|_| mode_data.is_current_local_mode())
4207 {
4208 self.handle_warning.handle_warning(Warning {
4209 range: Range::new(resulting_global_start, start),
4210 kind: WarningKind::InconsistentModeResult,
4211 });
4212 }
4213
4214 if self.balanced.len() == 1 {
4215 let last = self.balanced.last().unwrap();
4216 let is_local_class = matches!(last.kind, BalancedItemKind::LocalClass);
4217 let is_global_class = matches!(last.kind, BalancedItemKind::GlobalClass);
4218 if is_local_class || is_global_class {
4219 self.balanced.pop_mode_pseudo_class(mode_data);
4220 if mode_data.resulting_global.is_none() && is_global_class {
4221 mode_data.resulting_global = Some(start);
4222 }
4223 }
4224 }
4225
4226 if matches!(self.scope, Scope::InBlock)
4227 && mode_data.is_property_local_mode()
4228 && self.in_animation_property.is_some()
4229 {
4230 self.handle_local_keyframes_dependency(lexer)?;
4231 }
4232
4233 Some(())
4234 }
4235}