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 mut square_depth = self.selector_square_depth;
1711 let invalidates_composes = stream.fast_forward_selector_if_buffer_empty(
1712 &mut square_depth,
1713 keep_comments,
1714 has_mode,
1715 |ident| self.contains_icss_symbol(ident),
1716 is_css_modules_pure_magic_comment,
1717 );
1718 self.selector_square_depth = square_depth;
1719 if invalidates_composes
1720 && self.block_nesting_level == 0
1721 && let Some(mode_data) = &mut self.mode_data
1722 {
1723 mode_data.composes_local_classes.invalidate();
1724 }
1725 }
1726
1727 #[inline]
1728 fn fast_forward_generic_value(
1729 &self,
1730 stream: &mut DependencyTokenStream<'_, 's>,
1731 keep_comments: bool,
1732 ) {
1733 let preserve_strings = matches!(
1734 self.balanced.last(),
1735 Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
1736 );
1737 let preserve_delimiters = !self.balanced.is_empty();
1738 if self.icss_symbols.is_empty() && !keep_comments {
1739 stream.fast_forward_generic_value_without_candidates_if_buffer_empty(
1740 preserve_strings,
1741 preserve_delimiters,
1742 );
1743 } else {
1744 stream.fast_forward_generic_value_if_buffer_empty(
1745 keep_comments,
1746 preserve_strings,
1747 preserve_delimiters,
1748 |ident| self.contains_icss_symbol(ident),
1749 is_css_modules_pure_magic_comment,
1750 );
1751 }
1752 }
1753
1754 #[inline]
1755 fn fast_forward_at_rule(&self, stream: &mut DependencyTokenStream<'_, 's>, keep_comments: bool) {
1756 let preserve_strings = matches!(
1757 self.balanced.last(),
1758 Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
1759 );
1760 let preserve_delimiters = !self.balanced.is_empty();
1761 stream.fast_forward_at_rule_if_buffer_empty(
1762 keep_comments,
1763 preserve_strings,
1764 preserve_delimiters,
1765 |ident| self.contains_icss_symbol(ident),
1766 is_css_modules_pure_magic_comment,
1767 );
1768 }
1769
1770 #[inline]
1771 fn fast_forward_special_value(
1772 &self,
1773 stream: &mut DependencyTokenStream<'_, 's>,
1774 property: PropertyKind,
1775 keep_comments: bool,
1776 ) {
1777 if property == PropertyKind::Animation {
1780 return;
1781 }
1782 let preserve_strings = property == PropertyKind::Grid
1783 || matches!(
1784 self.balanced.last(),
1785 Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
1786 );
1787 let preserve_delimiters = !self.balanced.is_empty();
1788 let icss_symbols = (!self.icss_symbols.is_empty()).then_some(&self.icss_symbols);
1789 stream.fast_forward_special_value_if_buffer_empty(
1790 keep_comments,
1791 preserve_strings,
1792 preserve_delimiters,
1793 property,
1794 icss_symbols,
1795 );
1796 }
1797
1798 #[inline]
1799 fn icss_symbol_filter_bit(value: &str) -> (usize, u64) {
1800 let bytes = value.as_bytes();
1801 let len = bytes.len();
1802 let first = bytes.first().copied().unwrap_or_default() as usize;
1803 let middle = bytes.get(len / 2).copied().unwrap_or_default() as usize;
1804 let last = bytes.last().copied().unwrap_or_default() as usize;
1805 let mut hash = len.wrapping_mul(0x9e37_79b1);
1806 hash ^= first.wrapping_mul(0x85eb_ca6b);
1807 hash ^= middle.wrapping_mul(0xc2b2_ae35);
1808 hash ^= last.wrapping_mul(0x27d4_eb2f);
1809 hash ^= hash >> 16;
1810 let bit = hash & 1023;
1811 (bit >> 6, 1u64 << (bit & 63))
1812 }
1813
1814 #[inline]
1815 fn contains_icss_symbol(&self, value: &str) -> bool {
1816 let len = value.len();
1817 if len < self.icss_symbol_min_len || len > self.icss_symbol_max_len {
1818 return false;
1819 }
1820 let (word, bit) = Self::icss_symbol_filter_bit(value);
1821 self.icss_symbol_filter[word] & bit != 0 && self.icss_symbols.contains(value)
1822 }
1823
1824 fn insert_icss_symbol(&mut self, value: &'s str) {
1825 let len = value.len();
1826 self.icss_symbol_min_len = self.icss_symbol_min_len.min(len);
1827 self.icss_symbol_max_len = self.icss_symbol_max_len.max(len);
1828 let (word, bit) = Self::icss_symbol_filter_bit(value);
1829 self.icss_symbol_filter[word] |= bit;
1830 self.icss_symbols.insert(value);
1831 }
1832
1833 fn enter_property_value(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
1834 match self.property_kind {
1835 PropertyKind::Animation => self.enter_animation_property(),
1836 PropertyKind::ListStyle => self.enter_list_style_property(),
1837 PropertyKind::FontPalette => self.enter_font_palette_property(),
1838 PropertyKind::Container => self.enter_container_property(),
1839 PropertyKind::Grid => {
1840 if let Some(kind) = self.pending_grid_property.take() {
1841 self.enter_grid_property(kind);
1842 }
1843 }
1844 PropertyKind::CustomProperty => {
1845 if self
1846 .mode_data
1847 .as_ref()
1848 .is_some_and(ModeData::is_property_local_mode)
1849 && let Some(range) = self.pending_custom_property.take()
1850 {
1851 self
1852 .dependency_context
1853 .push_dependency(Dependency::LocalVarDecl {
1854 name: dashed_ident_name(stream.lexer().slice(range.start, range.end)?)?,
1855 range,
1856 });
1857 }
1858 }
1859 PropertyKind::Generic | PropertyKind::Composes => {}
1860 }
1861 self.pending_custom_property = None;
1862 self.pending_grid_property = None;
1863 self.set_scan_context(stream, ScanContext::for_property(self.property_kind));
1864 Some(())
1865 }
1866
1867 fn classify_property(
1868 name: &str,
1869 flags: TokenFlags,
1870 property_local_mode: bool,
1871 ) -> (PropertyKind, Option<GridPropertyKind>) {
1872 if name.starts_with("--") {
1873 return if property_local_mode {
1874 (PropertyKind::CustomProperty, None)
1875 } else {
1876 (PropertyKind::Generic, None)
1877 };
1878 }
1879 let mut normalized = [0; MAX_CSS_KEYWORD_LEN];
1880 let name = if flags.has_escape() {
1881 let Some(name) = decode_css_keyword(name, &mut normalized) else {
1882 return (PropertyKind::Generic, None);
1883 };
1884 name
1885 } else {
1886 let Some(name) = lowercase_ascii_keyword(name, &mut normalized) else {
1887 return (PropertyKind::Generic, None);
1888 };
1889 name
1890 };
1891 if matches!(name, "composes" | "compose-with") {
1892 return (PropertyKind::Composes, None);
1893 }
1894 if !property_local_mode {
1895 return (PropertyKind::Generic, None);
1896 }
1897 let unprefixed = strip_vendor_prefix(name).unwrap_or(name);
1898 if matches!(unprefixed, "animation" | "animation-name") {
1899 return (PropertyKind::Animation, None);
1900 }
1901 if matches!(name, "list-style" | "list-style-type") {
1902 return (PropertyKind::ListStyle, None);
1903 }
1904 if name == "font-palette" {
1905 return (PropertyKind::FontPalette, None);
1906 }
1907 if matches!(name, "container" | "container-name") {
1908 return (PropertyKind::Container, None);
1909 }
1910 if let Some(grid) = grid_property_kind(name) {
1911 return (PropertyKind::Grid, Some(grid));
1912 }
1913 (PropertyKind::Generic, None)
1914 }
1915
1916 fn classify_at_rule(name: &str, flags: TokenFlags) -> AtRuleKind {
1917 let mut normalized = [0; MAX_CSS_KEYWORD_LEN];
1918 let name = if flags.has_escape() {
1919 let Some(name) = decode_css_keyword(name, &mut normalized) else {
1920 return AtRuleKind::Other;
1921 };
1922 name
1923 } else {
1924 let Some(name) = lowercase_ascii_keyword(name, &mut normalized) else {
1925 return AtRuleKind::Other;
1926 };
1927 name
1928 };
1929 match name {
1930 "@value" => AtRuleKind::Value,
1931 "@scope" => AtRuleKind::Scope,
1932 "@import" => AtRuleKind::Import,
1933 "@charset" => AtRuleKind::Charset,
1934 "@function" => AtRuleKind::Function,
1935 "@property" => AtRuleKind::Property,
1936 "@namespace" => AtRuleKind::Namespace,
1937 "@keyframes" => AtRuleKind::Keyframes,
1938 "@container" => AtRuleKind::Container,
1939 "@counter-style" => AtRuleKind::CounterStyle,
1940 "@font-palette-values" => AtRuleKind::FontPaletteValues,
1941 _ if name.strip_prefix('@').and_then(strip_vendor_prefix) == Some("keyframes") => {
1942 AtRuleKind::Keyframes
1943 }
1944 _ => AtRuleKind::Other,
1945 }
1946 }
1947
1948 fn get_media(&self, lexer: &DependencyLexer<'s>, start: Pos, end: Pos) -> Option<&'s str> {
1949 let media = lexer.slice(start, end)?;
1950 let bytes = media.as_bytes();
1951 let mut position = 0;
1952 loop {
1953 while position < bytes.len() && bytes[position].is_ascii_whitespace() {
1954 position += 1;
1955 }
1956 if position + 1 < bytes.len() && bytes[position] == b'/' && bytes[position + 1] == b'*' {
1957 let Some(relative_end) = media[position + 2..].find("*/") else {
1958 break;
1959 };
1960 position += relative_end + 4;
1961 continue;
1962 }
1963 break;
1964 }
1965 if position == bytes.len() {
1966 return None;
1967 }
1968 Some(media)
1969 }
1970
1971 fn lex_charset_at_rule(
1972 &mut self,
1973 stream: &mut DependencyTokenStream<'_, 's>,
1974 start: Pos,
1975 ) -> Option<()> {
1976 let string = stream.next_parser_token().token;
1977 if string.kind != TokenKind::QuotedString {
1978 return Some(());
1979 }
1980
1981 let next = stream.next_parser_token().token;
1982 if next.kind == TokenKind::Semicolon {
1983 self
1984 .dependency_context
1985 .push_dependency(Dependency::Charset {
1986 value: stream.slice(string.value_range.start, string.value_range.end)?,
1987 range: Range::new(start, next.range.end),
1988 });
1989 }
1990 Some(())
1991 }
1992
1993 fn enter_animation_property(&mut self) {
1994 self.in_animation_property = Some(InProperty::new(
1995 AnimationReserved::default(),
1996 self.balanced.len(),
1997 ));
1998 }
1999
2000 fn exit_animation_property(&mut self) {
2001 self.in_animation_property = None;
2002 }
2003
2004 fn enter_list_style_property(&mut self) {
2005 self.in_list_style_property = Some(InProperty::new(ListStyleReserved, self.balanced.len()));
2006 }
2007
2008 fn exit_list_style_property(&mut self) {
2009 self.in_list_style_property = None;
2010 }
2011
2012 fn enter_font_palette_property(&mut self) {
2013 self.in_font_palette_property = Some(InProperty::new(FontPaletteReserved, self.balanced.len()));
2014 }
2015
2016 fn exit_font_palette_property(&mut self) {
2017 self.in_font_palette_property = None;
2018 }
2019
2020 fn enter_container_property(&mut self) {
2021 self.in_container_property = Some(InProperty::new(ContainerReserved, self.balanced.len()));
2022 }
2023
2024 fn exit_container_property(&mut self) {
2025 self.in_container_property = None;
2026 }
2027
2028 fn enter_grid_property(&mut self, kind: GridPropertyKind) {
2029 self.in_grid_property = Some(kind);
2030 }
2031
2032 fn exit_grid_property(&mut self) {
2033 self.in_grid_property = None;
2034 }
2035
2036 fn lex_icss_import(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2037 let (start, end) = self.consume_icss_import_path(stream)?;
2038 let right_parenthesis = stream.next_parser_token().token;
2039 if right_parenthesis.kind != TokenKind::RightParenthesis {
2040 self.handle_warning.handle_warning(Warning {
2041 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2042 kind: WarningKind::Unexpected {
2043 message: "Expected ')' during parsing of ':import()'",
2044 },
2045 });
2046 return Some(());
2047 }
2048 self
2049 .dependency_context
2050 .push_dependency(Dependency::ICSSImportFrom {
2051 path: stream.slice(start, end)?,
2052 });
2053 let left_curly = stream.next_parser_token().token;
2054 if left_curly.kind != TokenKind::LeftCurlyBracket {
2055 self.handle_warning.handle_warning(Warning {
2056 range: Range::new(left_curly.range.start, left_curly.range.end),
2057 kind: WarningKind::Unexpected {
2058 message: "Expected '{' during parsing of ':import()'",
2059 },
2060 });
2061 return Some(());
2062 }
2063 loop {
2064 let first = stream.next_parser_token().token;
2065 if first.kind == TokenKind::Eof {
2066 return None;
2067 }
2068 if first.kind == TokenKind::RightCurlyBracket {
2069 break;
2070 }
2071 let prop_start = first.range.start;
2072 let prop_end = self.consume_icss_export_prop(stream, first)?;
2073 let colon = stream.peek_significant_skipping_comments(true).token;
2074 if colon.kind != TokenKind::Colon {
2075 self.handle_warning.handle_warning(Warning {
2076 range: Range::new(colon.range.start, colon.range.end),
2077 kind: WarningKind::Unexpected {
2078 message: "Expected ':' during parsing of ':import'",
2079 },
2080 });
2081 return Some(());
2082 }
2083 stream.next_parser_token();
2084 let value_start_token = stream.next_parser_token().token;
2085 if value_start_token.kind == TokenKind::Eof {
2086 return None;
2087 }
2088 let value_start = value_start_token.range.start;
2089 let value_end = self.consume_icss_value(stream, value_start_token)?;
2090 let delimiter = stream.next_parser_token().token;
2091 self
2092 .dependency_context
2093 .push_dependency(Dependency::ICSSImportValue {
2094 prop: stream
2095 .slice(prop_start, prop_end)?
2096 .trim_end_matches(is_css_white_space_char),
2097 value: stream
2098 .slice(value_start, value_end)?
2099 .trim_end_matches(is_css_white_space_char),
2100 });
2101 self.insert_icss_symbol(
2102 stream
2103 .slice(prop_start, prop_end)?
2104 .trim_end_matches(is_css_white_space_char),
2105 );
2106 if delimiter.kind == TokenKind::RightCurlyBracket {
2107 break;
2108 }
2109 }
2110 Some(())
2111 }
2112
2113 fn consume_icss_import_path(
2114 &self,
2115 stream: &mut DependencyTokenStream<'_, 's>,
2116 ) -> Option<(Pos, Pos)> {
2117 let first = stream.next_parser_token().token;
2118 if first.kind == TokenKind::Eof {
2119 return None;
2120 }
2121 let start = first.range.start;
2122 let mut end = start;
2123 if first.kind == TokenKind::RightParenthesis {
2124 return Some((start, end));
2125 }
2126 end = first.range.end;
2127 loop {
2128 let token = stream.peek_significant_skipping_comments(true).token;
2129 if token.kind == TokenKind::Eof {
2130 return None;
2131 }
2132 if token.kind == TokenKind::RightParenthesis {
2133 return Some((start, end));
2134 }
2135 stream.next_parser_token();
2136 if first.kind != TokenKind::QuotedString {
2137 end = token.range.end;
2138 }
2139 }
2140 }
2141
2142 fn consume_icss_export_prop(
2143 &self,
2144 stream: &mut DependencyTokenStream<'_, 's>,
2145 first: Token,
2146 ) -> Option<Pos> {
2147 if matches!(
2148 first.kind,
2149 TokenKind::Colon | TokenKind::RightCurlyBracket | TokenKind::Semicolon
2150 ) {
2151 return Some(first.range.start);
2152 }
2153 loop {
2154 let token = stream.peek_parser_token();
2155 if token.token.kind == TokenKind::Eof {
2156 return None;
2157 }
2158 if let Some(first_comment_start) = token.leading.first_comment_start {
2159 return Some(first_comment_start);
2160 }
2161 if matches!(
2162 token.token.kind,
2163 TokenKind::Colon | TokenKind::RightCurlyBracket | TokenKind::Semicolon
2164 ) {
2165 return Some(token.token.range.start);
2166 }
2167 stream.next_parser_token();
2168 }
2169 }
2170
2171 fn consume_icss_value(
2172 &self,
2173 stream: &mut DependencyTokenStream<'_, 's>,
2174 first: Token,
2175 ) -> Option<Pos> {
2176 if matches!(
2177 first.kind,
2178 TokenKind::RightCurlyBracket | TokenKind::Semicolon
2179 ) {
2180 return Some(first.range.start);
2181 }
2182 loop {
2183 let token = stream.peek(true).token;
2184 if token.kind == TokenKind::Eof {
2185 return None;
2186 }
2187 if matches!(
2188 token.kind,
2189 TokenKind::RightCurlyBracket | TokenKind::Semicolon
2190 ) {
2191 return Some(token.range.start);
2192 }
2193 stream.next(true);
2194 }
2195 }
2196
2197 fn lex_value_at_rule(
2198 &mut self,
2199 stream: &mut DependencyTokenStream<'_, 's>,
2200 start: Pos,
2201 ) -> Option<()> {
2202 let input = stream.slice_trusted(0, stream.source_end());
2203 let checkpoint = self
2204 .dependency_context
2205 .value_at_rule_import_items_checkpoint();
2206 let mut parser = ValueAtRuleStream::new(input);
2207 loop {
2208 let token = stream.next_parser_token().token;
2209 match token.kind {
2210 TokenKind::Eof => return None,
2211 _ => {
2212 if token.kind == TokenKind::Semicolon && parser.depth == 0 {
2213 parser.params_end = token.range.start;
2214 break;
2215 }
2216 let depth_after = (parser.depth + u32::from(is_open_token(token.kind)))
2217 .saturating_sub(u32::from(is_close_token(token.kind)));
2218 if token.kind == TokenKind::RightCurlyBracket && depth_after == 0 {
2219 parser.params_end = token.range.start;
2220 break;
2221 }
2222 parser.push(&mut self.dependency_context, token);
2223 }
2224 }
2225 }
2226 let at_rule_end = parser.params_end.max(stream.consumed_pos());
2227 let import = parser.last_two().is_some_and(|(penultimate, last)| {
2228 penultimate.kind == TokenKind::Ident
2229 && parser
2230 .input
2231 .get(penultimate.range.start as usize..penultimate.range.end as usize)
2232 .is_some_and(|text| text.eq_ignore_ascii_case("from"))
2233 && parser.from_pos == Some(penultimate.range.start)
2234 && parser
2235 .input
2236 .get(penultimate.range.end as usize..last.range.start as usize)
2237 .is_some_and(trivia_only)
2238 });
2239 if import {
2240 let (_, last) = parser
2241 .last_two()
2242 .expect("an import value must end with at least two tokens");
2243 let from = &parser.input[last.range.start as usize..last.range.end as usize];
2244 let item_end = parser.from_prev_end.unwrap_or(parser.item_end);
2245 parser.finish_item(&mut self.dependency_context, item_end);
2246 let items = self
2247 .dependency_context
2248 .finish_value_at_rule_import_items(checkpoint);
2249 if items.is_empty() {
2250 self.handle_warning.handle_warning(Warning {
2251 range: Range::new(start, at_rule_end),
2252 kind: WarningKind::Unexpected {
2253 message: "Broken '@value' at-rule",
2254 },
2255 });
2256 } else {
2257 self
2258 .dependency_context
2259 .push_dependency(Dependency::ICSSImportFrom { path: from });
2260 for index in items.as_usize_range() {
2261 let item = self.dependency_context.value_at_rule_import_item(index);
2262 self
2263 .dependency_context
2264 .push_dependency(Dependency::ICSSImportValue {
2265 prop: item.local_name(),
2266 value: item.import_name(),
2267 });
2268 self.insert_icss_symbol(item.local_name());
2269 self
2270 .dependency_context
2271 .push_dependency(Dependency::ICSSExportValue {
2272 prop: item.local_name(),
2273 value: item.local_name(),
2274 });
2275 }
2276 }
2277 } else {
2278 self
2279 .dependency_context
2280 .truncate_value_at_rule_import_items(checkpoint);
2281 let local_name;
2282 let value;
2283 let has_colon;
2284 if let Some(colon) = parser.first_colon {
2285 local_name = &parser.input[parser
2286 .first_significant
2287 .unwrap_or((colon.split, colon.split))
2288 .0 as usize..colon.prev_end as usize];
2289 let raw = &parser.input[colon.end as usize..parser.params_end as usize];
2290 value = if parser.first_colon_tokens_after > 0 {
2291 trim_css_whitespace(raw)
2292 } else {
2293 raw
2294 };
2295 has_colon = true;
2296 } else if let Some((first_start, first_end)) = parser.first_significant {
2297 local_name = &parser.input[first_start as usize..first_end as usize];
2298 let raw = &parser.input[first_end as usize..parser.params_end as usize];
2299 value = if parser.significant_count > 1 {
2300 trim_css_whitespace(raw)
2301 } else {
2302 raw
2303 };
2304 has_colon = false;
2305 } else {
2306 local_name = "";
2307 value = "";
2308 has_colon = false;
2309 }
2310 if local_name.is_empty() || (!has_colon && value.is_empty()) {
2311 self.handle_warning.handle_warning(Warning {
2312 range: Range::new(start, at_rule_end),
2313 kind: WarningKind::Unexpected {
2314 message: "Broken '@value' at-rule",
2315 },
2316 });
2317 }
2318 if !local_name.is_empty() {
2319 self
2320 .dependency_context
2321 .push_dependency(Dependency::ICSSExportValue {
2322 prop: local_name,
2323 value,
2324 });
2325 self.insert_icss_symbol(local_name);
2326 }
2327 }
2328 self
2329 .dependency_context
2330 .push_dependency(Dependency::Replace {
2331 content: "",
2332 range: Range::new(start, at_rule_end),
2333 });
2334 Some(())
2335 }
2336
2337 fn lex_icss_export(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2338 let left_curly = stream.next_parser_token().token;
2339 if left_curly.kind != TokenKind::LeftCurlyBracket {
2340 self.handle_warning.handle_warning(Warning {
2341 range: Range::new(left_curly.range.start, left_curly.range.end),
2342 kind: WarningKind::Unexpected {
2343 message: "Expected '{' during parsing of ':export'",
2344 },
2345 });
2346 return Some(());
2347 }
2348 loop {
2349 let first = stream.next_parser_token().token;
2350 if first.kind == TokenKind::Eof {
2351 return None;
2352 }
2353 if first.kind == TokenKind::RightCurlyBracket {
2354 break;
2355 }
2356 let prop_start = first.range.start;
2357 let prop_end = self.consume_icss_export_prop(stream, first)?;
2358 let colon = stream.peek_significant_skipping_comments(true).token;
2359 if colon.kind != TokenKind::Colon {
2360 self.handle_warning.handle_warning(Warning {
2361 range: Range::new(colon.range.start, colon.range.end),
2362 kind: WarningKind::Unexpected {
2363 message: "Expected ':' during parsing of ':export'",
2364 },
2365 });
2366 return Some(());
2367 }
2368 stream.next_parser_token();
2369 let value_start_token = stream.next_parser_token().token;
2370 if value_start_token.kind == TokenKind::Eof {
2371 return None;
2372 }
2373 let value_start = value_start_token.range.start;
2374 let value_end = self.consume_icss_value(stream, value_start_token)?;
2375 let delimiter = stream.next_parser_token().token;
2376 let value = stream
2377 .slice(value_start, value_end)?
2378 .trim_end_matches(is_css_white_space_char);
2379 self
2380 .dependency_context
2381 .push_dependency(Dependency::ICSSExportValue {
2382 prop: stream
2383 .slice(prop_start, prop_end)?
2384 .trim_end_matches(is_css_white_space_char),
2385 value,
2386 });
2387 self.insert_icss_symbol(
2388 stream
2389 .slice(prop_start, prop_end)?
2390 .trim_end_matches(is_css_white_space_char),
2391 );
2392 if delimiter.kind == TokenKind::RightCurlyBracket {
2393 break;
2394 }
2395 }
2396 Some(())
2397 }
2398
2399 fn lex_local_var(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2400 let name_token = stream.next_parser_token().token;
2401 let start = name_token.range.start;
2402 let raw_name = stream.slice(start, name_token.range.end)?;
2403 let name = if name_token.kind == TokenKind::Ident {
2404 dashed_ident_name(raw_name)
2405 } else {
2406 None
2407 };
2408 let Some(name) = name else {
2409 self.handle_warning.handle_warning(Warning {
2410 kind: WarningKind::Unexpected {
2411 message: "Expected starts with '--' during parsing of 'var()'",
2412 },
2413 range: Range::new(start, (start + 2).min(name_token.range.end)),
2414 });
2415 return Some(());
2416 };
2417 let end = name_token.range.end;
2418 let next = stream.peek_significant_skipping_comments(true).token;
2419 let (from, from_is_global) = if next.kind == TokenKind::Ident
2420 && is_ascii_keyword(stream.slice(next.range.start, next.range.end)?, "from")
2421 {
2422 stream.next_parser_token();
2423 let path_token = stream.peek_significant_skipping_comments(true).token;
2424 let path_start = path_token.range.start;
2425 let path_end = path_token.range.end;
2426 if !matches!(path_token.kind, TokenKind::QuotedString | TokenKind::Ident) {
2427 self.handle_warning.handle_warning(Warning {
2428 range: Range::new(path_start, path_end),
2429 kind: WarningKind::Unexpected {
2430 message: "Expected string or ident during parsing of 'var()'",
2431 },
2432 });
2433 return Some(());
2434 }
2435 stream.next_parser_token();
2436 let path = stream.slice(path_start, path_end)?;
2437 (
2438 Some(path),
2439 path_token.kind == TokenKind::Ident && path == "global",
2440 )
2441 } else {
2442 (None, false)
2443 };
2444 if from_is_global {
2445 let name_start = dashed_ident_name_start(raw_name)?;
2446 stream
2447 .lexer_mut()
2448 .visitor_mut()
2449 .discard_last(Range::new(start + name_start as Pos, end));
2450 }
2451 self
2452 .dependency_context
2453 .push_dependency(Dependency::LocalVar {
2454 name,
2455 range: Range::new(start, end),
2456 from,
2457 from_is_global,
2458 });
2459 Some(())
2460 }
2461
2462 fn lex_local_dashed_ident_decl(
2463 &mut self,
2464 stream: &mut DependencyTokenStream<'_, 's>,
2465 local_decl_dependency: impl FnOnce(&'s str, Range) -> Dependency<'s>,
2466 dashed_warning: impl FnOnce(Range) -> Warning<'s>,
2467 left_curly_warning: impl FnOnce(Range) -> Warning<'s>,
2468 ) -> Option<()> {
2469 let name_token = stream.next_parser_token().token;
2470 let start = name_token.range.start;
2471 if name_token.kind != TokenKind::Ident
2472 || !stream.slice(start, name_token.range.end)?.starts_with("--")
2473 {
2474 self
2475 .handle_warning
2476 .handle_warning(dashed_warning(Range::new(
2477 start,
2478 (start + 2).min(name_token.range.end),
2479 )));
2480 return Some(());
2481 }
2482 let end = name_token.range.end;
2483 self
2484 .dependency_context
2485 .push_dependency(local_decl_dependency(
2486 dashed_ident_name(stream.slice(start, end)?)?,
2487 Range::new(start, end),
2488 ));
2489 let left_curly = stream.peek_significant_skipping_comments(true).token;
2490 if left_curly.kind != TokenKind::LeftCurlyBracket {
2491 self
2492 .handle_warning
2493 .handle_warning(left_curly_warning(Range::new(
2494 left_curly.range.start,
2495 left_curly.range.end,
2496 )));
2497 return Some(());
2498 }
2499 Some(())
2500 }
2501
2502 fn lex_local_keyframes_decl(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2503 let mut is_function = false;
2504 let first = stream.next_parser_token().token;
2505 let name_token = if first.kind == TokenKind::Colon {
2506 let pseudo_start = first.range.start;
2507 let pseudo_name = stream.next_parser_token().token;
2508 let pseudo_end = if matches!(pseudo_name.kind, TokenKind::Ident | TokenKind::Function) {
2509 pseudo_name.range.end
2510 } else {
2511 first.range.end
2512 };
2513 let pseudo = stream.slice(pseudo_start, pseudo_end)?;
2514 if pseudo_name.kind == TokenKind::Function {
2515 self.handle_pseudo_function(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2516 } else if pseudo_name.kind == TokenKind::Ident {
2517 self.handle_pseudo_class(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2518 }
2519 let mode_data = self
2520 .mode_data
2521 .as_ref()
2522 .expect("CSS Modules mode data must exist while parsing keyframes");
2523 if mode_data.is_pure_mode()
2524 && !mode_data.is_pure_check_disabled()
2525 && (pseudo.eq_ignore_ascii_case(":global(") || pseudo.eq_ignore_ascii_case(":global"))
2526 {
2527 self.handle_warning.handle_warning(Warning {
2528 range: Range::new(pseudo_start, pseudo_end),
2529 kind: WarningKind::NotPure {
2530 message: "'@keyframes :global' is not allowed in pure mode",
2531 },
2532 });
2533 }
2534 is_function =
2535 pseudo.eq_ignore_ascii_case(":local(") || pseudo.eq_ignore_ascii_case(":global(");
2536 if !is_function
2537 && !pseudo.eq_ignore_ascii_case(":local")
2538 && !pseudo.eq_ignore_ascii_case(":global")
2539 {
2540 self.handle_warning.handle_warning(Warning {
2541 range: Range::new(pseudo_start, pseudo_end),
2542 kind: WarningKind::Unexpected {
2543 message: "Expected ':local', ':local()', ':global', or ':global()' during parsing of '@keyframes' name",
2544 }
2545 });
2546 return Some(());
2547 }
2548 stream.next_parser_token().token
2549 } else {
2550 first
2551 };
2552
2553 let start = name_token.range.start;
2554 if name_token.kind != TokenKind::Ident {
2555 self.handle_warning.handle_warning(Warning {
2556 range: Range::new(start, start.saturating_add(2).min(stream.source_end())),
2557 kind: WarningKind::Unexpected {
2558 message: "Expected ident during parsing of '@keyframes' name",
2559 },
2560 });
2561 return Some(());
2562 }
2563 let end = name_token.range.end;
2564 if self
2565 .mode_data
2566 .as_ref()
2567 .expect("CSS Modules mode data must exist while parsing keyframes")
2568 .is_current_local_mode()
2569 {
2570 let name = stream.slice(start, end)?;
2571 self
2572 .dependency_context
2573 .push_dependency(Dependency::LocalKeyframesDecl {
2574 name,
2575 range: Range::new(start, end),
2576 });
2577 }
2578 if is_function {
2579 let right_parenthesis = stream.peek_significant_skipping_comments(true).token;
2580 if right_parenthesis.kind != TokenKind::RightParenthesis {
2581 self.handle_warning.handle_warning(Warning {
2582 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2583 kind: WarningKind::Unexpected {
2584 message: "Expected ')' during parsing of '@keyframes :local(' or '@keyframes :global('",
2585 },
2586 });
2587 return Some(());
2588 }
2589 stream.next_parser_token();
2590 self
2591 .dependency_context
2592 .push_dependency(Dependency::Replace {
2593 content: "",
2594 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2595 });
2596 let mode_data = self
2597 .mode_data
2598 .as_mut()
2599 .expect("CSS Modules mode data must exist while leaving a keyframes mode function");
2600 mode_data.inside_mode_function -= 1;
2601 self.balanced.pop_without_moda_data();
2602 }
2603 let left_curly = stream.peek_significant_skipping_comments(true).token;
2604 if left_curly.kind != TokenKind::LeftCurlyBracket {
2605 self.handle_warning.handle_warning(Warning {
2606 range: Range::new(left_curly.range.start, left_curly.range.end),
2607 kind: WarningKind::Unexpected {
2608 message: "Expected '{' during parsing of '@keyframes'",
2609 },
2610 });
2611 return Some(());
2612 }
2613 Some(())
2614 }
2615
2616 fn handle_local_keyframes_dependency(&mut self, lexer: &DependencyLexer<'s>) -> Option<()> {
2617 let animation = self
2618 .in_animation_property
2619 .as_mut()
2620 .expect("animation state must exist while handling an animation dependency");
2621 if let Some(range) = animation.take_rename(self.balanced.len()) {
2622 self
2623 .dependency_context
2624 .push_dependency(Dependency::LocalKeyframes {
2625 name: lexer.slice(range.start, range.end)?,
2626 range,
2627 });
2628 }
2629 animation.reset_reserved();
2630 Some(())
2631 }
2632
2633 fn lex_local_counter_style_decl(
2634 &mut self,
2635 stream: &mut DependencyTokenStream<'_, 's>,
2636 ) -> Option<()> {
2637 let name_token = stream.next_parser_token().token;
2638 let start = name_token.range.start;
2639 if name_token.kind != TokenKind::Ident {
2640 self.handle_warning.handle_warning(Warning {
2641 range: Range::new(start, name_token.range.end),
2642 kind: WarningKind::Unexpected {
2643 message: "Expected ident during parsing of '@counter-style'",
2644 },
2645 });
2646 return Some(());
2647 }
2648 let end = name_token.range.end;
2649 self
2650 .dependency_context
2651 .push_dependency(Dependency::LocalCounterStyleDecl {
2652 name: stream.slice(start, end)?,
2653 range: Range::new(start, end),
2654 });
2655 let left_curly = stream.peek_significant_skipping_comments(true).token;
2656 if left_curly.kind != TokenKind::LeftCurlyBracket {
2657 self.handle_warning.handle_warning(Warning {
2658 range: Range::new(left_curly.range.start, left_curly.range.end),
2659 kind: WarningKind::Unexpected {
2660 message: "Expected '{' during parsing of '@counter-style'",
2661 },
2662 });
2663 return Some(());
2664 }
2665 Some(())
2666 }
2667
2668 fn lex_local_container_at_rule(
2669 &mut self,
2670 stream: &mut DependencyTokenStream<'_, 's>,
2671 ) -> Option<()> {
2672 let name_token = stream.next_parser_token().token;
2673 if name_token.kind == TokenKind::LeftParenthesis {
2674 return Some(());
2675 }
2676 if name_token.kind != TokenKind::Ident {
2677 return Some(());
2678 }
2679 let start = name_token.range.start;
2680 let end = name_token.range.end;
2681 let ident = stream.slice(start, end)?;
2682 if is_reserved_container_query_ident(ident, name_token.flags) {
2683 return Some(());
2684 }
2685 if self
2686 .mode_data
2687 .as_ref()
2688 .expect("CSS Modules mode data must exist while parsing a container")
2689 .is_current_local_mode()
2690 {
2691 self
2692 .dependency_context
2693 .push_dependency(Dependency::LocalContainer {
2694 name: ident,
2695 range: Range::new(start, end),
2696 });
2697 }
2698 Some(())
2699 }
2700
2701 fn lex_local_function_decl(&mut self, stream: &mut DependencyTokenStream<'_, 's>) -> Option<()> {
2702 let mut is_function = false;
2703 let first = stream.next_parser_token().token;
2704 let name_token = if first.kind == TokenKind::Colon {
2705 let pseudo_start = first.range.start;
2706 let pseudo_name = stream.next_parser_token().token;
2707 let pseudo_end = if matches!(pseudo_name.kind, TokenKind::Ident | TokenKind::Function) {
2708 pseudo_name.range.end
2709 } else {
2710 first.range.end
2711 };
2712 let pseudo = stream.slice(pseudo_start, pseudo_end)?;
2713 if pseudo_name.kind == TokenKind::Function {
2714 self.handle_pseudo_function(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2715 } else if pseudo_name.kind == TokenKind::Ident {
2716 self.handle_pseudo_class(stream, pseudo_start, pseudo_end, pseudo_name.flags)?;
2717 }
2718 let mode_data = self
2719 .mode_data
2720 .as_ref()
2721 .expect("CSS Modules mode data must exist while parsing a function");
2722 if mode_data.is_pure_mode()
2723 && !mode_data.is_pure_check_disabled()
2724 && (pseudo.eq_ignore_ascii_case(":global(") || pseudo.eq_ignore_ascii_case(":global"))
2725 {
2726 self.handle_warning.handle_warning(Warning {
2727 range: Range::new(pseudo_start, pseudo_end),
2728 kind: WarningKind::NotPure {
2729 message: "'@function :global' is not allowed in pure mode",
2730 },
2731 });
2732 }
2733 is_function =
2734 pseudo.eq_ignore_ascii_case(":local(") || pseudo.eq_ignore_ascii_case(":global(");
2735 if !is_function
2736 && !pseudo.eq_ignore_ascii_case(":local")
2737 && !pseudo.eq_ignore_ascii_case(":global")
2738 {
2739 self.handle_warning.handle_warning(Warning {
2740 range: Range::new(pseudo_start, pseudo_end),
2741 kind: WarningKind::Unexpected {
2742 message:
2743 "Expected ':local', ':local()', ':global', or ':global()' during parsing of '@function' name",
2744 },
2745 });
2746 return Some(());
2747 }
2748 stream.next_parser_token().token
2749 } else {
2750 first
2751 };
2752
2753 let name_range = if name_token.kind == TokenKind::Function {
2754 name_token.value_range
2755 } else {
2756 name_token.range
2757 };
2758 let start = name_range.start;
2759 let end = name_range.end;
2760 let name = stream.slice_trusted(start, end);
2761 if name_token.kind != TokenKind::Ident && name_token.kind != TokenKind::Function
2762 || !name.starts_with("--")
2763 {
2764 self.handle_warning.handle_warning(Warning {
2765 range: Range::new(start, start.saturating_add(2).min(stream.source_end())),
2766 kind: WarningKind::Unexpected {
2767 message: "Expected starts with '--' during parsing of '@function' name",
2768 },
2769 });
2770 return Some(());
2771 }
2772 if self
2773 .mode_data
2774 .as_ref()
2775 .expect("CSS Modules mode data must exist while parsing a function")
2776 .is_current_local_mode()
2777 {
2778 self
2779 .dependency_context
2780 .push_dependency(Dependency::LocalFunctionDecl {
2781 name: dashed_ident_name(stream.slice(start, end)?)?,
2782 range: Range::new(start, end),
2783 });
2784 }
2785
2786 if is_function {
2787 let right_parenthesis = stream.peek_significant_skipping_comments(true).token;
2788 if right_parenthesis.kind != TokenKind::RightParenthesis {
2789 self.handle_warning.handle_warning(Warning {
2790 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2791 kind: WarningKind::Unexpected {
2792 message: "Expected ')' during parsing of '@function :local(' or '@function :global('",
2793 },
2794 });
2795 return Some(());
2796 }
2797 stream.next_parser_token();
2798 self
2799 .dependency_context
2800 .push_dependency(Dependency::Replace {
2801 content: "",
2802 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2803 });
2804 let mode_data = self
2805 .mode_data
2806 .as_mut()
2807 .expect("CSS Modules mode data must exist while leaving a function mode function");
2808 mode_data.inside_mode_function -= 1;
2809 self.balanced.pop_without_moda_data();
2810 }
2811
2812 if name_token.kind == TokenKind::Function {
2813 return Some(());
2814 }
2815 let left_parenthesis = stream.peek_significant_skipping_comments(true).token;
2816 if left_parenthesis.kind != TokenKind::LeftParenthesis {
2817 self.handle_warning.handle_warning(Warning {
2818 range: Range::new(left_parenthesis.range.start, left_parenthesis.range.end),
2819 kind: WarningKind::Unexpected {
2820 message: "Expected '(' during parsing of '@function'",
2821 },
2822 });
2823 }
2824 Some(())
2825 }
2826
2827 fn handle_local_counter_style_dependency(&mut self, lexer: &DependencyLexer<'s>) -> Option<()> {
2828 let list_style = self
2829 .in_list_style_property
2830 .as_mut()
2831 .expect("list-style state must exist while handling a counter-style dependency");
2832 if let Some(range) = list_style.take_rename(self.balanced.len()) {
2833 self
2834 .dependency_context
2835 .push_dependency(Dependency::LocalCounterStyle {
2836 name: lexer.slice(range.start, range.end)?,
2837 range,
2838 });
2839 }
2840 Some(())
2841 }
2842
2843 fn handle_local_font_palette_dependency(&mut self, lexer: &DependencyLexer<'s>) -> Option<()> {
2844 let font_palette = self
2845 .in_font_palette_property
2846 .as_mut()
2847 .expect("font-palette state must exist while handling a font-palette dependency");
2848 if let Some(range) = font_palette.take_rename(self.balanced.len()) {
2849 self
2850 .dependency_context
2851 .push_dependency(Dependency::LocalFontPalette {
2852 name: dashed_ident_name(lexer.slice(range.start, range.end)?)?,
2853 range,
2854 });
2855 }
2856 Some(())
2857 }
2858
2859 fn lex_composes(
2860 &mut self,
2861 stream: &mut DependencyTokenStream<'_, 's>,
2862 local_classes: SmallVec<[&'s str; 2]>,
2863 start: Pos,
2864 ) -> Option<()> {
2865 let colon = stream.peek_significant_skipping_comments(true).token;
2866 if colon.kind != TokenKind::Colon {
2867 return Some(());
2868 }
2869 stream.next_parser_token();
2870 let mut replacement_end = colon.range.end;
2871 loop {
2872 let first = stream.peek_significant_skipping_comments(true).token;
2873 if first.kind == TokenKind::Eof {
2874 break;
2875 }
2876 if first.kind == TokenKind::RightCurlyBracket {
2877 break;
2878 }
2879 if first.kind == TokenKind::Semicolon {
2880 stream.next_parser_token();
2881 replacement_end = first.range.end;
2882 break;
2883 }
2884
2885 let item_start = first.range.start;
2886 let mut item_end = item_start;
2887 let mut names: SmallVec<[&'s str; 2]> = SmallVec::new();
2888 let mut has_from = false;
2889 let mut delimiter = first;
2890 loop {
2891 if matches!(
2892 delimiter.kind,
2893 TokenKind::Comma | TokenKind::Semicolon | TokenKind::RightCurlyBracket
2894 ) {
2895 break;
2896 }
2897
2898 if delimiter.kind == TokenKind::Function
2899 && stream.slice(delimiter.range.start, delimiter.range.end)? == "global("
2900 {
2901 let global_start = delimiter.range.start;
2902 stream.next_parser_token();
2903 let name_token = stream.next_parser_token().token;
2904 let Some(name_range) = ident_like_range(name_token) else {
2905 self.handle_warning.handle_warning(Warning {
2906 range: Range::new(
2907 name_token.range.start,
2908 name_token
2909 .range
2910 .start
2911 .saturating_add(2)
2912 .min(stream.source_end()),
2913 ),
2914 kind: WarningKind::Unexpected {
2915 message: "Expected ident during parsing of 'composes'",
2916 },
2917 });
2918 return Some(());
2919 };
2920 let right_parenthesis = stream.peek_significant_skipping_comments(true).token;
2921 if right_parenthesis.kind != TokenKind::RightParenthesis {
2922 self.handle_warning.handle_warning(Warning {
2923 range: Range::new(right_parenthesis.range.start, right_parenthesis.range.end),
2924 kind: WarningKind::Unexpected {
2925 message: "Expected ')' during parsing of 'composes'",
2926 },
2927 });
2928 return Some(());
2929 }
2930 stream.next_parser_token();
2931 item_end = right_parenthesis.range.end;
2932 self.dependency_context.push_composes(
2933 local_classes.iter().copied(),
2934 std::iter::once(stream.slice(name_range.start, name_range.end)?),
2935 Some("global"),
2936 true,
2937 Range::new(global_start, item_end),
2938 );
2939 delimiter = stream.peek_significant_skipping_comments(true).token;
2940 continue;
2941 }
2942
2943 let Some(name_range) = ident_like_range(delimiter) else {
2944 let name_start = delimiter.range.start;
2945 self.handle_warning.handle_warning(Warning {
2946 range: Range::new(
2947 name_start,
2948 name_start.saturating_add(2).min(stream.source_end()),
2949 ),
2950 kind: WarningKind::Unexpected {
2951 message: "Expected ident during parsing of 'composes'",
2952 },
2953 });
2954 return Some(());
2955 };
2956 let ident = stream.slice(name_range.start, name_range.end)?;
2957 if !names.is_empty() && ident.eq_ignore_ascii_case("from") {
2958 stream.next_parser_token();
2959 let path = stream.peek_significant_skipping_comments(true).token;
2960 if matches!(
2961 path.kind,
2962 TokenKind::QuotedString | TokenKind::Ident | TokenKind::Function
2963 ) {
2964 let path_range = if path.kind == TokenKind::Function {
2965 path.value_range
2966 } else {
2967 path.range
2968 };
2969 item_end = path_range.end;
2970 let from_is_global = path.kind == TokenKind::Ident
2971 && is_ascii_keyword(stream.slice(path_range.start, path_range.end)?, "global");
2972 self.dependency_context.push_composes(
2973 local_classes.iter().copied(),
2974 std::mem::take(&mut names),
2975 Some(stream.slice(path_range.start, path_range.end)?),
2976 from_is_global,
2977 Range::new(item_start, item_end),
2978 );
2979 has_from = true;
2980 stream.next_parser_token();
2981 delimiter = stream.peek_significant_skipping_comments(true).token;
2982 break;
2983 }
2984 names.push(ident);
2985 item_end = name_range.end;
2986 delimiter = path;
2987 continue;
2988 }
2989 names.push(ident);
2990 item_end = name_range.end;
2991 stream.next_parser_token();
2992 delimiter = stream.peek_significant_skipping_comments(true).token;
2993 }
2994
2995 if has_from {
2996 if delimiter.kind == TokenKind::Comma {
2997 stream.next_parser_token();
2998 replacement_end = delimiter.range.end;
2999 continue;
3000 }
3001 if delimiter.kind == TokenKind::Semicolon {
3002 stream.next_parser_token();
3003 replacement_end = delimiter.range.end;
3004 break;
3005 }
3006 if delimiter.kind == TokenKind::RightCurlyBracket {
3007 replacement_end = item_end;
3008 break;
3009 }
3010 replacement_end = item_end;
3011 break;
3012 }
3013
3014 if delimiter.kind == TokenKind::Comma {
3015 if !names.is_empty() {
3016 self.dependency_context.push_composes(
3017 local_classes.iter().copied(),
3018 names,
3019 None,
3020 false,
3021 Range::new(item_start, item_end),
3022 );
3023 }
3024 stream.next_parser_token();
3025 replacement_end = delimiter.range.end;
3026 continue;
3027 }
3028
3029 if delimiter.kind == TokenKind::Semicolon {
3030 if !names.is_empty() {
3031 self.dependency_context.push_composes(
3032 local_classes.iter().copied(),
3033 names,
3034 None,
3035 false,
3036 Range::new(item_start, item_end),
3037 );
3038 }
3039 stream.next_parser_token();
3040 replacement_end = delimiter.range.end;
3041 break;
3042 }
3043
3044 if delimiter.kind == TokenKind::RightCurlyBracket {
3045 if !names.is_empty() {
3046 self.dependency_context.push_composes(
3047 local_classes.iter().copied(),
3048 names,
3049 None,
3050 false,
3051 Range::new(item_start, item_end),
3052 );
3053 }
3054 replacement_end = item_end;
3055 break;
3056 }
3057
3058 self.handle_warning.handle_warning(Warning {
3062 range: Range::new(
3063 delimiter.range.start,
3064 delimiter
3065 .range
3066 .start
3067 .saturating_add(2)
3068 .min(stream.source_end()),
3069 ),
3070 kind: WarningKind::Unexpected {
3071 message: "Expected ident during parsing of 'composes'",
3072 },
3073 });
3074 return Some(());
3075 }
3076 self
3077 .dependency_context
3078 .push_dependency(Dependency::Replace {
3079 content: "",
3080 range: Range::new(start, replacement_end),
3081 });
3082 Some(())
3083 }
3084}
3085
3086impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
3087 fn handle_comment(
3088 &mut self,
3089 lexer: &mut DependencyLexer<'s>,
3090 start: Pos,
3091 end: Pos,
3092 ) -> Option<()> {
3093 let Some(mode_data) = &mut self.mode_data else {
3094 return Some(());
3095 };
3096 if !mode_data.is_pure_mode() || end < start + 4 {
3097 return Some(());
3098 }
3099
3100 let content = lexer.slice(start + 2, end - 2)?;
3101 if is_css_modules_magic_comment(content, "cssmodules-pure-ignore") {
3102 mode_data.mark_pure_ignore();
3103 } else if matches!(self.scope, Scope::TopLevel)
3104 && self.block_nesting_level == 0
3105 && is_css_modules_magic_comment(content, "cssmodules-pure-no-check")
3106 {
3107 mode_data.mark_pure_no_check();
3108 }
3109 Some(())
3110 }
3111
3112 fn handle_url(
3113 &mut self,
3114 lexer: &mut DependencyLexer<'s>,
3115 start: Pos,
3116 end: Pos,
3117 content_start: Pos,
3118 content_end: Pos,
3119 flags: TokenFlags,
3120 ) -> Option<()> {
3121 let value = lexer.slice(content_start, content_end)?;
3122 match self.scope {
3123 Scope::InAtImport(ref mut import_data) => {
3124 if import_data.in_supports() {
3125 return Some(());
3126 }
3127 if import_data.url.is_some() {
3128 self.handle_warning.handle_warning(Warning {
3129 range: Range::new(import_data.start, end),
3130 kind: WarningKind::DuplicateUrl {
3131 when: lexer.slice(import_data.start, end)?,
3132 },
3133 });
3134 return Some(());
3135 }
3136 import_data.prelude.push(ImportPreludeNode::Url {
3137 range: Range::new(start, end),
3138 });
3139 import_data.url = Some(value);
3140 import_data.url_flags = flags;
3141 import_data.url_range = Some(Range::new(start, end));
3142 }
3143 Scope::InBlock => self.dependency_context.push_dependency(Dependency::Url {
3144 request: value,
3145 range: Range::new(start, end),
3146 kind: UrlRangeKind::Function,
3147 }),
3148 _ => {}
3149 }
3150 Some(())
3151 }
3152
3153 fn handle_string(
3154 &mut self,
3155 lexer: &mut DependencyLexer<'s>,
3156 start: Pos,
3157 end: Pos,
3158 flags: TokenFlags,
3159 ) -> Option<()> {
3160 match self.scope {
3161 Scope::InAtImport(ref mut import_data) => {
3162 let inside_url = matches!(
3163 self.balanced.last(),
3164 Some(last) if matches!(last.kind, BalancedItemKind::Url)
3165 );
3166
3167 if import_data.in_supports() || (!inside_url && import_data.url.is_some()) {
3169 return Some(());
3170 }
3171
3172 if inside_url && import_data.url.is_some() {
3173 self.handle_warning.handle_warning(Warning {
3174 range: Range::new(import_data.start, end),
3175 kind: WarningKind::DuplicateUrl {
3176 when: lexer.slice(import_data.start, end)?,
3177 },
3178 });
3179 return Some(());
3180 }
3181
3182 let value = lexer.slice(start + 1, end - 1)?;
3183 import_data.url = Some(value);
3184 import_data.url_flags = flags;
3185 if !inside_url {
3187 import_data.prelude.push(ImportPreludeNode::Url {
3188 range: Range::new(start, end),
3189 });
3190 import_data.url_range = Some(Range::new(start, end));
3191 }
3192 }
3193 Scope::InBlock => {
3194 if let Some(mode_data) = &self.mode_data
3195 && mode_data.is_property_local_mode()
3196 && matches!(
3197 self.in_grid_property,
3198 Some(GridPropertyKind::TemplateLike | GridPropertyKind::TemplateAreas)
3199 )
3200 {
3201 for range in parse_grid_template_area_ranges(lexer.slice(start + 1, end - 1)?, start + 1)
3202 {
3203 self
3204 .dependency_context
3205 .push_dependency(Dependency::LocalGridDecl {
3206 name: lexer.slice(range.start, range.end)?,
3207 range,
3208 });
3209 }
3210 }
3211 let Some(last) = self.balanced.last() else {
3212 return Some(());
3213 };
3214 let kind = match last.kind {
3215 BalancedItemKind::Url => UrlRangeKind::String,
3216 BalancedItemKind::ImageSet => UrlRangeKind::Function,
3217 _ => return Some(()),
3218 };
3219 let value = lexer.slice(start + 1, end - 1)?;
3220 self.dependency_context.push_dependency(Dependency::Url {
3221 request: value,
3222 range: Range::new(start, end),
3223 kind,
3224 });
3225 }
3226 _ => {}
3227 }
3228 Some(())
3229 }
3230
3231 fn handle_at_keyword(
3232 &mut self,
3233 stream: &mut DependencyTokenStream<'_, 's>,
3234 start: Pos,
3235 end: Pos,
3236 flags: TokenFlags,
3237 ) -> Option<()> {
3238 let name = stream.slice_trusted(start, end);
3239 let kind = Self::classify_at_rule(name, flags);
3240 self.set_scan_context(stream, ScanContext::AtRule);
3241 if kind == AtRuleKind::Namespace {
3242 self.scope = Scope::AtNamespaceInvalid;
3243 self.handle_warning.handle_warning(Warning {
3244 range: Range::new(start, end),
3245 kind: WarningKind::NamespaceNotSupportedInBundledCss,
3246 });
3247 } else if kind == AtRuleKind::Import {
3248 if !self.allow_import_at_rule {
3249 self.scope = Scope::AtImportInvalid;
3250 self.handle_warning.handle_warning(Warning {
3251 range: Range::new(start, end),
3252 kind: WarningKind::NotPrecededAtImport,
3253 });
3254 return Some(());
3255 }
3256 self.scope = Scope::InAtImport(ImportData::new(start));
3257 } else if kind == AtRuleKind::Charset {
3258 self.lex_charset_at_rule(stream, start)?;
3259 self.set_scan_context(stream, ScanContext::TopLevel);
3260 self.is_next_rule_prelude = true;
3261 } else if self.mode_data.is_some() {
3262 let mut can_contain_rules = true;
3263 if kind == AtRuleKind::Value {
3264 self.lex_value_at_rule(stream, start)?;
3265 can_contain_rules = false;
3266 self.set_scan_context(stream, ScanContext::TopLevel);
3267 self.is_next_rule_prelude = true;
3268 } else if kind == AtRuleKind::Keyframes {
3269 self.lex_local_keyframes_decl(stream)?;
3270 } else if kind == AtRuleKind::Container {
3271 self.lex_local_container_at_rule(stream)?;
3272 } else if kind == AtRuleKind::Function {
3273 self.lex_local_function_decl(stream)?;
3274 } else if kind == AtRuleKind::Property {
3275 self.lex_local_dashed_ident_decl(
3276 stream,
3277 |name, range| Dependency::LocalPropertyDecl { name, range },
3278 |range| Warning {
3279 range,
3280 kind: WarningKind::Unexpected {
3281 message: "Expected starts with '--' during parsing of '@property'",
3282 },
3283 },
3284 |range| Warning {
3285 range,
3286 kind: WarningKind::Unexpected {
3287 message: "Expected '{' during parsing of '@property'",
3288 },
3289 },
3290 )?;
3291 } else if kind == AtRuleKind::CounterStyle {
3292 self.lex_local_counter_style_decl(stream)?;
3293 } else if kind == AtRuleKind::FontPaletteValues {
3294 self.lex_local_dashed_ident_decl(
3295 stream,
3296 |name, range| Dependency::LocalFontPaletteDecl { name, range },
3297 |range| Warning {
3298 range,
3299 kind: WarningKind::Unexpected {
3300 message: "Expected starts with '--' during parsing of '@font-palette-values'",
3301 },
3302 },
3303 |range| Warning {
3304 range,
3305 kind: WarningKind::Unexpected {
3306 message: "Expected '{' during parsing of '@font-palette-values'",
3307 },
3308 },
3309 )?;
3310 } else {
3311 self.is_next_rule_prelude = kind == AtRuleKind::Scope;
3312 if self.is_next_rule_prelude {
3313 self.set_scan_context(stream, ScanContext::Selector);
3314 }
3315 }
3316
3317 let mode_data = self
3318 .mode_data
3319 .as_mut()
3320 .expect("CSS Modules mode data must exist while handling an at-rule");
3321 if can_contain_rules && self.block_nesting_level == 0 {
3322 mode_data.composes_local_classes.find_at_keyword();
3323 }
3324
3325 if mode_data.is_pure_mode() {
3326 mode_data.pure_global = None;
3327 }
3328 }
3329 Some(())
3330 }
3331
3332 fn handle_semicolon(
3333 &mut self,
3334 lexer: &mut DependencyLexer<'s>,
3335 start: Pos,
3336 end: Pos,
3337 ) -> Option<()> {
3338 match self.scope {
3339 Scope::InAtImport(ref import_data) => {
3340 let Some(url) = import_data.url else {
3341 if let Some((name, name_range)) = import_data.prelude.icss_import_url() {
3342 self
3343 .dependency_context
3344 .push_dependency(Dependency::ICSSImportUrl {
3345 name,
3346 range: Range::new(import_data.start, end),
3347 name_range: *name_range,
3348 });
3349 self.scope = Scope::TopLevel;
3350 return Some(());
3351 }
3352 self.handle_warning.handle_warning(Warning {
3353 range: Range::new(import_data.start, end),
3354 kind: WarningKind::ExpectedUrl {
3355 when: lexer.slice(import_data.start, end)?,
3356 },
3357 });
3358 self.scope = Scope::TopLevel;
3359 return Some(());
3360 };
3361 let Some(url_range) = &import_data.url_range else {
3362 self.handle_warning.handle_warning(Warning {
3363 range: Range::new(start, end),
3364 kind: WarningKind::Unexpected {
3365 message: "Unexpected ';' during parsing of '@import url()'",
3366 },
3367 });
3368 self.scope = Scope::TopLevel;
3369 return Some(());
3370 };
3371 if let Some(range) = import_data.prelude.first_non_url_before(url_range) {
3372 self.handle_warning.handle_warning(Warning {
3373 range: *url_range,
3374 kind: WarningKind::ExpectedUrlBefore {
3375 when: lexer.slice(range.start, url_range.end)?,
3376 },
3377 });
3378 self.scope = Scope::TopLevel;
3379 return Some(());
3380 }
3381 let layer = match &import_data.layer {
3382 ImportDataLayer::None => None,
3383 ImportDataLayer::EndLayer { value, range } => {
3384 if url_range.start > range.start {
3385 self.handle_warning.handle_warning(Warning {
3386 range: *url_range,
3387 kind: WarningKind::ExpectedUrlBefore {
3388 when: lexer.slice(range.start, url_range.end)?,
3389 },
3390 });
3391 self.scope = Scope::TopLevel;
3392 return Some(());
3393 }
3394 Some(*value)
3395 }
3396 };
3397 let supports = match &import_data.supports {
3398 ImportDataSupports::None => None,
3399 ImportDataSupports::InSupports => {
3400 self.handle_warning.handle_warning(Warning {
3401 range: Range::new(start, end),
3402 kind: WarningKind::Unexpected {
3403 message: "Unexpected ';' during parsing of 'supports()'",
3404 },
3405 });
3406 None
3407 }
3408 ImportDataSupports::EndSupports { value, range } => {
3409 if url_range.start > range.start {
3410 self.handle_warning.handle_warning(Warning {
3411 range: *url_range,
3412 kind: WarningKind::ExpectedUrlBefore {
3413 when: lexer.slice(range.start, url_range.end)?,
3414 },
3415 });
3416 self.scope = Scope::TopLevel;
3417 return Some(());
3418 }
3419 Some(*value)
3420 }
3421 };
3422 if let Some(layer_range) = import_data.layer_range()
3423 && let Some(supports_range) = import_data.supports_range()
3424 && layer_range.start > supports_range.start
3425 {
3426 self.handle_warning.handle_warning(Warning {
3427 range: *layer_range,
3428 kind: WarningKind::ExpectedLayerBefore {
3429 when: lexer.slice(supports_range.start, layer_range.end)?,
3430 },
3431 });
3432 self.scope = Scope::TopLevel;
3433 return Some(());
3434 }
3435 let last_end = import_data
3436 .supports_range()
3437 .or_else(|| import_data.layer_range())
3438 .unwrap_or(url_range)
3439 .end;
3440 let media = self.get_media(lexer, last_end, start);
3441 self.dependency_context.push_import(
3442 url,
3443 Range::new(import_data.start, end),
3444 layer,
3445 supports,
3446 media,
3447 );
3448 self.scope = Scope::TopLevel;
3449 }
3450 Scope::AtImportInvalid | Scope::AtNamespaceInvalid => {
3451 self.scope = Scope::TopLevel;
3452 }
3453 Scope::InBlock => {
3454 if let Some(mode_data) = &mut self.mode_data {
3455 mode_data.pure_global = Some(end);
3456
3457 if mode_data.is_property_local_mode() {
3458 if self.in_animation_property.is_some() {
3459 self.handle_local_keyframes_dependency(lexer)?;
3460 self.exit_animation_property();
3461 }
3462 if self.in_list_style_property.is_some() {
3463 self.handle_local_counter_style_dependency(lexer)?;
3464 self.exit_list_style_property();
3465 }
3466 if self.in_font_palette_property.is_some() {
3467 self.handle_local_font_palette_dependency(lexer)?;
3468 self.exit_font_palette_property();
3469 }
3470 if self.in_container_property.is_some() {
3471 self.exit_container_property();
3472 }
3473 if self.in_grid_property.is_some() {
3474 self.exit_grid_property();
3475 }
3476 }
3477 }
3478 self.pending_custom_property = None;
3479 self.pending_grid_property = None;
3480 self.property_kind = PropertyKind::Generic;
3481 }
3482 Scope::TopLevel => {
3483 self.is_next_rule_prelude = true;
3484 }
3485 }
3486 Some(())
3487 }
3488
3489 fn handle_function(
3490 &mut self,
3491 stream: &mut DependencyTokenStream<'_, 's>,
3492 start: Pos,
3493 end: Pos,
3494 flags: TokenFlags,
3495 ) -> Option<()> {
3496 let name = stream.slice_trusted(start, end);
3497 let mut normalized = [0; MAX_CSS_KEYWORD_LEN];
3498 let normalized_name = if flags.has_escape() {
3499 decode_css_keyword(name, &mut normalized)
3500 } else {
3501 lowercase_ascii_keyword(name, &mut normalized)
3502 };
3503 let item = normalized_name.map_or_else(
3504 || BalancedItem::new_other(start, end),
3505 |name| BalancedItem::new_normalized(name, start, end),
3506 );
3507 let at_import_top_level =
3508 matches!(self.scope, Scope::InAtImport(_)) && self.balanced.is_empty();
3509 self.balanced.push(item, self.mode_data.as_mut());
3510
3511 if let Scope::InAtImport(ref mut import_data) = self.scope {
3512 if at_import_top_level && normalized_name == Some("url(") {
3513 import_data.prelude.push(ImportPreludeNode::Url {
3514 range: Range::new(start, end),
3515 });
3516 } else if at_import_top_level && normalized_name == Some("layer(") {
3517 import_data.prelude.push(ImportPreludeNode::Layer {
3518 range: Range::new(start, end),
3519 });
3520 } else if at_import_top_level && normalized_name == Some("supports(") {
3521 import_data.prelude.push(ImportPreludeNode::Supports {
3522 range: Range::new(start, end),
3523 });
3524 import_data.supports = ImportDataSupports::InSupports;
3525 } else if at_import_top_level {
3526 import_data.prelude.push(ImportPreludeNode::Other {
3527 range: Range::new(start, end),
3528 });
3529 } else if normalized_name == Some("supports(") {
3530 import_data.supports = ImportDataSupports::InSupports;
3531 }
3532 }
3533
3534 if let Scope::InAtImport(ref mut import_data) = self.scope {
3535 let layer_end = at_import_top_level && normalized_name == Some("layer(");
3536 let supports_end = normalized_name == Some("supports(");
3537 if layer_end || supports_end {
3538 let Some(close) = stream.fast_forward(TokenKind::RightParenthesis) else {
3539 return Some(());
3540 };
3541 self.balanced.pop(self.mode_data.as_mut());
3542 if layer_end {
3543 import_data.layer = ImportDataLayer::EndLayer {
3544 value: stream.slice(end, close.end.saturating_sub(1))?,
3545 range: Range::new(start, close.end),
3546 };
3547 } else {
3548 import_data.supports = ImportDataSupports::EndSupports {
3549 value: stream.slice(end, close.end.saturating_sub(1))?,
3550 range: Range::new(start, close.end),
3551 };
3552 }
3553 return Some(());
3554 }
3555 }
3556
3557 let Some(mode_data) = &self.mode_data else {
3558 return Some(());
3559 };
3560 if mode_data.is_current_local_mode() && name.starts_with("--") && end > start + 1 {
3561 self
3562 .dependency_context
3563 .push_dependency(Dependency::LocalFunction {
3564 name: dashed_ident_name(stream.slice(start, end - 1)?)?,
3565 range: Range::new(start, end - 1),
3566 });
3567 }
3568 if mode_data.is_current_local_mode() && normalized_name == Some("var(") {
3569 self.lex_local_var(stream)?;
3570 }
3571 Some(())
3572 }
3573
3574 fn handle_left_parenthesis(
3575 &mut self,
3576 _: &mut DependencyLexer<'s>,
3577 start: Pos,
3578 end: Pos,
3579 ) -> Option<()> {
3580 self
3581 .balanced
3582 .push(BalancedItem::new_other(start, end), self.mode_data.as_mut());
3583 Some(())
3584 }
3585
3586 fn handle_right_parenthesis(
3587 &mut self,
3588 lexer: &mut DependencyLexer<'s>,
3589 leading_start: Pos,
3590 start: Pos,
3591 end: Pos,
3592 ) -> Option<()> {
3593 let Some(last) = self.balanced.pop(self.mode_data.as_mut()) else {
3594 return Some(());
3595 };
3596 if let Some(mode_data) = &mut self.mode_data {
3597 let mut is_function = last.kind.is_mode_function();
3598 let mut function_end = last.range.end;
3599 if last.kind.is_mode_class() {
3600 self.balanced.pop_mode_pseudo_class(mode_data);
3601 let popped = self
3602 .balanced
3603 .pop_without_moda_data()
3604 .expect("a mode pseudo-class must have a preceding balanced item");
3605 debug_assert!(!matches!(
3606 popped.kind,
3607 BalancedItemKind::GlobalClass | BalancedItemKind::LocalClass
3608 ));
3609 is_function = popped.kind.is_mode_function();
3610 function_end = popped.range.end;
3611 }
3612 if is_function {
3613 let is_empty = start == function_end || trivia_only(lexer.slice(function_end, start)?);
3614 let replacement_start = if is_empty {
3615 function_end
3616 } else {
3617 leading_start
3618 };
3619 if is_empty {
3620 let maybe_left_parenthesis_start = function_end.saturating_sub(1);
3621 self.handle_warning.handle_warning(Warning {
3622 range: Range::new(maybe_left_parenthesis_start, end),
3623 kind: WarningKind::Unexpected {
3624 message: "':global()' or ':local()' can't be empty",
3625 },
3626 });
3627 }
3628 self
3629 .dependency_context
3630 .push_dependency(Dependency::Replace {
3631 content: "",
3632 range: Range::new(replacement_start, end),
3633 });
3634 }
3635 }
3636 if let Scope::InAtImport(ref mut import_data) = self.scope {
3637 let not_in_supports = !import_data.in_supports();
3638 if matches!(last.kind, BalancedItemKind::Url) && not_in_supports {
3639 import_data.url_range = Some(Range::new(last.range.start, end));
3640 } else if matches!(last.kind, BalancedItemKind::Layer) && not_in_supports {
3641 import_data.layer = ImportDataLayer::EndLayer {
3642 value: lexer.slice(last.range.end, end - 1)?,
3643 range: Range::new(last.range.start, end),
3644 };
3645 } else if matches!(last.kind, BalancedItemKind::Supports) {
3646 import_data.supports = ImportDataSupports::EndSupports {
3647 value: lexer.slice(last.range.end, end - 1)?,
3648 range: Range::new(last.range.start, end),
3649 }
3650 }
3651 }
3652 Some(())
3653 }
3654
3655 fn handle_ident(
3656 &mut self,
3657 stream: &mut DependencyTokenStream<'_, 's>,
3658 start: Pos,
3659 end: Pos,
3660 flags: TokenFlags,
3661 ) -> Option<()> {
3662 let ident = stream.slice_trusted(start, end);
3663 if matches!(self.scope, Scope::TopLevel | Scope::InBlock)
3666 && matches!(
3667 self.scan_context,
3668 ScanContext::Selector
3669 | ScanContext::GenericValue
3670 | ScanContext::SpecialValue(_)
3671 | ScanContext::AtRule
3672 )
3673 && (self.scan_context != ScanContext::Selector || self.selector_square_depth == 0)
3674 && self.contains_icss_symbol(ident)
3675 {
3676 self
3677 .dependency_context
3678 .push_dependency(Dependency::ICSSSymbol {
3679 name: ident,
3680 range: Range::new(start, end),
3681 });
3682 if matches!(self.scope, Scope::TopLevel)
3683 && let Some(mode_data) = &mut self.mode_data
3684 {
3685 mode_data.composes_local_classes.invalidate();
3686 }
3687 return Some(());
3688 }
3689 match self.scope {
3690 Scope::InBlock => {
3691 let is_declaration_name = self.scan_context == ScanContext::DeclarationName;
3692 if is_declaration_name {
3693 let property_local_mode = self
3694 .mode_data
3695 .as_ref()
3696 .is_some_and(ModeData::is_property_local_mode);
3697 (self.property_kind, self.pending_grid_property) =
3698 Self::classify_property(ident, flags, property_local_mode);
3699 self.pending_custom_property =
3700 (self.property_kind == PropertyKind::CustomProperty).then_some(Range::new(start, end));
3701 if self.property_kind != PropertyKind::Composes {
3702 return Some(());
3703 }
3704 }
3705 let Some(mode_data) = &mut self.mode_data else {
3706 return Some(());
3707 };
3708 if mode_data.is_current_local_mode()
3709 && self
3710 .balanced
3711 .last()
3712 .is_some_and(|last| last.kind.is_mode_function())
3713 && ident.starts_with("--")
3714 && stream.peek_parser_token().token.kind == TokenKind::RightParenthesis
3715 {
3716 self
3717 .dependency_context
3718 .push_dependency(Dependency::LocalFunction {
3719 name: dashed_ident_name(stream.slice(start, end)?)?,
3720 range: Range::new(start, end),
3721 });
3722 return Some(());
3723 }
3724 if mode_data.is_property_local_mode()
3725 && matches!(self.scan_context, ScanContext::SpecialValue(_))
3726 {
3727 if let Some(animation) = &mut self.in_animation_property {
3728 if self.balanced.is_empty() {
3730 animation.set_rename(stream.slice(start, end)?, flags, Range::new(start, end));
3731 }
3732 return Some(());
3733 }
3734
3735 if let Some(list_style) = &mut self.in_list_style_property {
3736 if self.balanced.is_empty() {
3738 list_style.set_rename(stream.slice(start, end)?, flags, Range::new(start, end));
3739 }
3740 return Some(());
3741 }
3742
3743 if let Some(font_palette) = &mut self.in_font_palette_property {
3744 if self.balanced.is_empty()
3746 || matches!(self.balanced.last(), Some(last) if matches!(last.kind, BalancedItemKind::PaletteMix))
3747 {
3748 font_palette.set_rename(stream.slice(start, end)?, flags, Range::new(start, end));
3749 }
3750 return Some(());
3751 }
3752
3753 if let Some(container) = &mut self.in_container_property {
3754 if self.balanced.is_empty() {
3755 container.set_rename(ident, flags, Range::new(start, end));
3756 if let Some(range) = container.take_rename(self.balanced.len()) {
3757 self
3758 .dependency_context
3759 .push_dependency(Dependency::LocalContainerDecl {
3760 name: stream.slice(range.start, range.end)?,
3761 range,
3762 });
3763 }
3764 }
3765 return Some(());
3766 }
3767
3768 if self.in_grid_property.is_some() {
3769 if self.balanced.is_empty() && !is_reserved_grid_ident(ident, flags) {
3770 self
3771 .dependency_context
3772 .push_dependency(Dependency::LocalGrid {
3773 name: ident,
3774 range: Range::new(start, end),
3775 });
3776 }
3777 return Some(());
3778 }
3779 }
3780
3781 if is_declaration_name && self.property_kind == PropertyKind::Composes {
3782 if self.block_nesting_level != 1 {
3783 self.handle_warning.handle_warning(Warning {
3784 range: Range::new(start, end),
3785 kind: WarningKind::UnexpectedComposition {
3786 message: "not allowed in nested rule",
3787 },
3788 });
3789 return Some(());
3790 }
3791 let Some(local_classes) = mode_data
3792 .composes_local_classes
3793 .get_valid_local_classes(stream.lexer())
3794 else {
3795 self.handle_warning.handle_warning(Warning {
3796 range: Range::new(start, end),
3797 kind: WarningKind::UnexpectedComposition {
3798 message: "only allowed when selector is single :local class",
3799 },
3800 });
3801 return Some(());
3802 };
3803 return self.lex_composes(stream, local_classes, start);
3804 }
3805 }
3806 Scope::InAtImport(ref mut import_data) => {
3807 if !self.balanced.is_empty() || import_data.in_supports() {
3808 return Some(());
3809 }
3810
3811 let ident = stream.slice_trusted(start, end);
3812 if ident.eq_ignore_ascii_case("layer") {
3813 import_data.prelude.push(ImportPreludeNode::Layer {
3814 range: Range::new(start, end),
3815 });
3816 import_data.layer = ImportDataLayer::EndLayer {
3817 value: "",
3818 range: Range::new(start, end),
3819 }
3820 } else if import_data.url.is_none() && import_data.prelude.is_empty() {
3821 import_data
3822 .prelude
3823 .push(ImportPreludeNode::IcssUrlCandidate {
3824 name: ident,
3825 range: Range::new(start, end),
3826 });
3827 } else if import_data.url.is_none() {
3828 import_data.prelude.push(ImportPreludeNode::Other {
3829 range: Range::new(start, end),
3830 });
3831 }
3832 }
3833 Scope::TopLevel => {
3834 let Some(mode_data) = &mut self.mode_data else {
3835 return Some(());
3836 };
3837
3838 mode_data.composes_local_classes.invalidate();
3839 }
3840 _ => {}
3841 }
3842 Some(())
3843 }
3844
3845 fn handle_class(
3846 &mut self,
3847 lexer: &mut DependencyLexer<'s>,
3848 start: Pos,
3849 end: Pos,
3850 _flags: TokenFlags,
3851 ) -> Option<()> {
3852 let Some(mode_data) = &mut self.mode_data else {
3853 return Some(());
3854 };
3855 let name = lexer.slice_trusted(start, end);
3856 if name == "." {
3857 self.handle_warning.handle_warning(Warning {
3858 range: Range::new(start, end),
3859 kind: WarningKind::Unexpected {
3860 message: "Invalid class selector syntax",
3861 },
3862 });
3863 return Some(());
3864 }
3865 if mode_data.is_current_local_mode() {
3866 self
3867 .dependency_context
3868 .push_dependency(Dependency::LocalClass {
3869 name,
3870 range: Range::new(start, end),
3871 explicit: mode_data.is_mode_explicit(),
3872 });
3873 if self.block_nesting_level == 0 {
3874 mode_data
3875 .composes_local_classes
3876 .find_local_class(start + 1, end);
3877 }
3878
3879 if mode_data.is_pure_mode() {
3880 mode_data.pure_global = None;
3881 }
3882 }
3883 Some(())
3884 }
3885
3886 fn handle_id(
3887 &mut self,
3888 lexer: &mut DependencyLexer<'s>,
3889 start: Pos,
3890 end: Pos,
3891 _flags: TokenFlags,
3892 ) -> Option<()> {
3893 let Some(mode_data) = &mut self.mode_data else {
3894 return Some(());
3895 };
3896 let name = lexer.slice_trusted(start, end);
3897 if name == "#" {
3898 self.handle_warning.handle_warning(Warning {
3899 range: Range::new(start, end),
3900 kind: WarningKind::Unexpected {
3901 message: "Invalid id selector syntax",
3902 },
3903 });
3904 return Some(());
3905 }
3906 if mode_data.is_current_local_mode() {
3907 self
3908 .dependency_context
3909 .push_dependency(Dependency::LocalId {
3910 name,
3911 range: Range::new(start, end),
3912 explicit: mode_data.is_mode_explicit(),
3913 });
3914
3915 if self.block_nesting_level == 0 {
3916 mode_data.composes_local_classes.invalidate();
3917 }
3918
3919 if mode_data.is_pure_mode() {
3920 mode_data.pure_global = None;
3921 }
3922 }
3923 Some(())
3924 }
3925
3926 fn handle_left_curly_bracket(
3927 &mut self,
3928 stream: &mut DependencyTokenStream<'_, 's>,
3929 start: Pos,
3930 _: Pos,
3931 ) -> Option<()> {
3932 if matches!(self.scope, Scope::InBlock)
3933 && self.scan_context != ScanContext::Selector
3934 && !self.balanced.is_empty()
3935 {
3936 self.balanced.push(
3937 BalancedItem::new_curly(start, start + 1),
3938 self.mode_data.as_mut(),
3939 );
3940 return Some(());
3941 }
3942 match self.scope {
3943 Scope::TopLevel => {
3944 self.allow_import_at_rule = false;
3945 self.scope = Scope::InBlock;
3946 if self.mode_data.is_none()
3947 || matches!(&self.mode_data, Some(mode_data) if !matches!(mode_data.composes_local_classes.is_single, SingleLocalClass::AtKeyword))
3948 {
3949 self.block_nesting_level = 1;
3950 }
3951 }
3952 Scope::InBlock => {
3953 let is_at_rule_block = matches!(
3954 &self.mode_data,
3955 Some(mode_data)
3956 if matches!(mode_data.composes_local_classes.is_single, SingleLocalClass::AtKeyword)
3957 );
3958 if !is_at_rule_block {
3959 self.block_nesting_level += 1;
3960 }
3961 }
3962 _ => return Some(()),
3963 }
3964 let (pure_check_disabled_for_selector, enter_pure_ignored_block) =
3965 if let Some(mode_data) = &self.mode_data {
3966 (
3967 mode_data.is_pure_check_disabled(),
3968 mode_data.pure_ignore_pending,
3969 )
3970 } else {
3971 (false, false)
3972 };
3973 if self.mode_data.is_none() {
3974 self.set_scan_context(stream, ScanContext::BlockItem);
3975 return Some(());
3976 }
3977 if let Some(mode_data) = &mut self.mode_data {
3978 if let Some(pure_global_start) = mode_data
3979 .pure_global
3980 .filter(|_| mode_data.is_pure_mode() && !pure_check_disabled_for_selector)
3981 {
3982 self.handle_warning.handle_warning(Warning {
3983 range: Range::new(pure_global_start, start),
3984 kind: WarningKind::NotPure {
3985 message: "Selector is not pure (pure selectors must contain at least one local class or id)",
3986 }
3987 });
3988 }
3989
3990 if enter_pure_ignored_block {
3991 mode_data.enter_block(self.block_nesting_level);
3992 }
3993
3994 if let Some(resulting_global_start) = mode_data
3995 .resulting_global
3996 .filter(|_| mode_data.is_current_local_mode())
3997 {
3998 self.handle_warning.handle_warning(Warning {
3999 range: Range::new(resulting_global_start, start),
4000 kind: WarningKind::InconsistentModeResult,
4001 });
4002 }
4003 mode_data.resulting_global = None;
4004
4005 self.balanced.update_property_mode(mode_data);
4006 self.balanced.pop_mode_pseudo_class(mode_data);
4007 if self.is_next_rule_prelude && self.block_nesting_level == 0 {
4008 let mode_data = self
4009 .mode_data
4010 .as_mut()
4011 .expect("CSS Modules mode data must exist while finishing a selector");
4012 mode_data.composes_local_classes.reset_to_initial();
4013 }
4014
4015 debug_assert!(
4016 self.balanced.is_empty(),
4017 "balanced should be empty when end of selector"
4018 );
4019 }
4020 self.exit_container_property();
4021 self.exit_grid_property();
4022 self.pending_custom_property = None;
4023 self.pending_grid_property = None;
4024 self.property_kind = PropertyKind::Generic;
4025 self.set_scan_context(stream, ScanContext::BlockItem);
4026 Some(())
4027 }
4028
4029 fn handle_right_curly_bracket(
4030 &mut self,
4031 stream: &mut DependencyTokenStream<'_, 's>,
4032 _: Pos,
4033 end: Pos,
4034 ) -> Option<()> {
4035 if matches!(self.scope, Scope::InBlock) {
4036 if matches!(
4037 self.balanced.last(),
4038 Some(last) if matches!(last.kind, BalancedItemKind::Curly)
4039 ) {
4040 self.balanced.pop(self.mode_data.as_mut());
4041 if self.block_nesting_level == 0 {
4042 self.scope = Scope::TopLevel;
4043 self.is_next_rule_prelude = true;
4044 self.set_scan_context(stream, ScanContext::TopLevel);
4045 if let Some(mode_data) = &mut self.mode_data {
4046 mode_data.composes_local_classes.reset_to_initial();
4047 }
4048 } else {
4049 self.set_scan_context(stream, ScanContext::BlockItem);
4050 }
4051 return Some(());
4052 }
4053
4054 if let Some(mode_data) = &mut self.mode_data {
4055 mode_data.pure_global = Some(end);
4056
4057 if mode_data.is_property_local_mode() {
4058 if self.in_animation_property.is_some() {
4059 self.handle_local_keyframes_dependency(stream.lexer_mut())?;
4060 self.exit_animation_property();
4061 }
4062 if self.in_list_style_property.is_some() {
4063 self.handle_local_counter_style_dependency(stream.lexer_mut())?;
4064 self.exit_list_style_property();
4065 }
4066 if self.in_font_palette_property.is_some() {
4067 self.handle_local_font_palette_dependency(stream.lexer_mut())?;
4068 self.exit_font_palette_property();
4069 }
4070 if self.in_container_property.is_some() {
4071 self.exit_container_property();
4072 }
4073 if self.in_grid_property.is_some() {
4074 self.exit_grid_property();
4075 }
4076 }
4077 }
4078 if self.block_nesting_level > 0 {
4079 self.block_nesting_level -= 1;
4080 }
4081 if let Some(mode_data) = &mut self.mode_data {
4082 mode_data.clear_pure_ignore_pending();
4083 mode_data.exit_block(self.block_nesting_level);
4084 }
4085 if self.block_nesting_level == 0 {
4086 self.scope = Scope::TopLevel;
4087 self.set_scan_context(stream, ScanContext::TopLevel);
4088 self.is_next_rule_prelude = true;
4089 if let Some(mode_data) = &mut self.mode_data {
4090 mode_data.composes_local_classes.reset_to_initial();
4091 }
4092 } else {
4093 self.set_scan_context(stream, ScanContext::BlockItem);
4094 }
4095 self.pending_custom_property = None;
4096 self.pending_grid_property = None;
4097 self.property_kind = PropertyKind::Generic;
4098 }
4099 Some(())
4100 }
4101
4102 fn handle_pseudo_function(
4103 &mut self,
4104 stream: &mut DependencyTokenStream<'_, 's>,
4105 start: Pos,
4106 end: Pos,
4107 flags: TokenFlags,
4108 ) -> Option<()> {
4109 let name = stream.slice_trusted(start, end);
4110 if let Some(mode_data) = &mut self.mode_data {
4111 if name.eq_ignore_ascii_case(":import(") {
4112 self.lex_icss_import(stream);
4113 self
4114 .dependency_context
4115 .push_dependency(Dependency::Replace {
4116 content: "",
4117 range: Range::new(start, stream.consumed_pos()),
4118 });
4119 return Some(());
4120 }
4121 if name.eq_ignore_ascii_case(":global(") || name.eq_ignore_ascii_case(":local(") {
4122 if mode_data.is_inside_mode_function() {
4123 self.handle_warning.handle_warning(Warning {
4124 range: Range::new(start, end),
4125 kind: WarningKind::ExpectedNotInside {
4126 pseudo: stream.slice(start, end)?,
4127 },
4128 });
4129 }
4130
4131 let next = stream.peek_parser_token();
4132 if next.token.kind == TokenKind::Eof {
4133 return None;
4134 }
4135 self
4136 .dependency_context
4137 .push_dependency(Dependency::Replace {
4138 content: "",
4139 range: Range::new(start, next.token.range.start),
4140 });
4141 } else if self.block_nesting_level == 0 {
4142 mode_data.composes_local_classes.invalidate();
4143 }
4144 }
4145 self.balanced.push(
4146 BalancedItem::new(name, flags, start, end),
4147 self.mode_data.as_mut(),
4148 );
4149 Some(())
4150 }
4151
4152 fn handle_pseudo_class(
4153 &mut self,
4154 stream: &mut DependencyTokenStream<'_, 's>,
4155 start: Pos,
4156 end: Pos,
4157 flags: TokenFlags,
4158 ) -> Option<()> {
4159 let Some(mode_data) = &mut self.mode_data else {
4160 return Some(());
4161 };
4162 let name = stream.slice_trusted(start, end);
4163 if name.eq_ignore_ascii_case(":global") || name.eq_ignore_ascii_case(":local") {
4164 if mode_data.is_inside_mode_function() {
4165 self.handle_warning.handle_warning(Warning {
4166 range: Range::new(start, end),
4167 kind: WarningKind::ExpectedNotInside {
4168 pseudo: stream.slice(start, end)?,
4169 },
4170 });
4171 }
4172
4173 let next = stream.peek_parser_token();
4174 if next.token.kind == TokenKind::Eof {
4175 return None;
4176 }
4177 if !next.leading.has_whitespace() {
4178 let missing_whitespace = match stream.byte_at(next.token.range.start) {
4179 Some(b'.' | b'#') => true,
4180 Some(b'{') => next.leading.first_comment_start.is_some(),
4181 _ => false,
4182 };
4183 if missing_whitespace {
4184 self.handle_warning.handle_warning(Warning {
4185 range: Range::new(start, end),
4186 kind: WarningKind::Unexpected {
4187 message: "Missing trailing whitespace",
4188 },
4189 });
4190 }
4191 }
4192 self.balanced.push(
4193 BalancedItem::new(name, flags, start, end),
4194 self.mode_data.as_mut(),
4195 );
4196 self
4197 .dependency_context
4198 .push_dependency(Dependency::Replace {
4199 content: "",
4200 range: Range::new(
4201 start,
4202 next.leading.first_comment_start.unwrap_or(next.leading.end),
4203 ),
4204 });
4205 return Some(());
4206 }
4207 if matches!(self.scope, Scope::TopLevel) && name.eq_ignore_ascii_case(":export") {
4208 self.lex_icss_export(stream)?;
4209 self
4210 .dependency_context
4211 .push_dependency(Dependency::Replace {
4212 content: "",
4213 range: Range::new(start, stream.consumed_pos()),
4214 });
4215 return Some(());
4216 }
4217
4218 if self.block_nesting_level == 0 {
4219 mode_data.composes_local_classes.invalidate();
4220 }
4221 Some(())
4222 }
4223
4224 fn handle_comma(&mut self, lexer: &mut DependencyLexer<'s>, start: Pos, end: Pos) -> Option<()> {
4225 let Some(mode_data) = &mut self.mode_data else {
4226 return Some(());
4227 };
4228
4229 if let Some(pure_global_start) = mode_data
4230 .pure_global
4231 .filter(|_| mode_data.is_pure_mode() && !mode_data.is_pure_check_disabled())
4232 {
4233 self.handle_warning.handle_warning(Warning {
4234 range: Range::new(pure_global_start, start),
4235 kind: WarningKind::NotPure {
4236 message: "Selector is not pure (pure selectors must contain at least one local class or id)",
4237 }
4238 });
4239 }
4240 mode_data.pure_global = Some(end);
4241
4242 if self.block_nesting_level == 0 {
4243 mode_data.composes_local_classes.find_comma(lexer)?;
4244 }
4245
4246 if let Some(resulting_global_start) = mode_data
4247 .resulting_global
4248 .filter(|_| mode_data.is_current_local_mode())
4249 {
4250 self.handle_warning.handle_warning(Warning {
4251 range: Range::new(resulting_global_start, start),
4252 kind: WarningKind::InconsistentModeResult,
4253 });
4254 }
4255
4256 if self.balanced.len() == 1 {
4257 let last = self
4258 .balanced
4259 .last()
4260 .expect("a balanced item must exist when the stack length is one");
4261 let is_local_class = matches!(last.kind, BalancedItemKind::LocalClass);
4262 let is_global_class = matches!(last.kind, BalancedItemKind::GlobalClass);
4263 if is_local_class || is_global_class {
4264 self.balanced.pop_mode_pseudo_class(mode_data);
4265 if mode_data.resulting_global.is_none() && is_global_class {
4266 mode_data.resulting_global = Some(start);
4267 }
4268 }
4269 }
4270
4271 if matches!(self.scope, Scope::InBlock)
4272 && mode_data.is_property_local_mode()
4273 && self.in_animation_property.is_some()
4274 {
4275 self.handle_local_keyframes_dependency(lexer)?;
4276 }
4277
4278 Some(())
4279 }
4280}