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