1use std::{
4 fmt::Display,
5 hash::{Hash, Hasher},
6 marker::PhantomData,
7 ops::Index,
8};
9
10use crate::lexer::Pos;
11
12#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
13pub struct Range {
14 pub start: Pos,
15 pub end: Pos,
16}
17
18impl Range {
19 pub fn new(start: Pos, end: Pos) -> Self {
20 Self { start, end }
21 }
22}
23
24#[derive(Debug, Hash, PartialEq, Eq)]
29pub struct DependencyListRange<T> {
30 start: u32,
31 end: u32,
32 marker: PhantomData<fn() -> T>,
33}
34
35impl<T> Copy for DependencyListRange<T> {}
36
37impl<T> Clone for DependencyListRange<T> {
38 fn clone(&self) -> Self {
39 *self
40 }
41}
42
43impl<T> DependencyListRange<T> {
44 pub(crate) fn from_bounds(start: usize, end: usize) -> Self {
45 assert!(start <= end, "dependency list range is reversed");
46 assert!(
47 u32::try_from(end).is_ok(),
48 "dependency list storage is too large"
49 );
50 Self {
51 start: start as u32,
52 end: end as u32,
53 marker: PhantomData,
54 }
55 }
56
57 pub(crate) fn as_usize_range(self) -> std::ops::Range<usize> {
58 self.start as usize..self.end as usize
59 }
60
61 pub fn start(self) -> u32 {
62 self.start
63 }
64
65 pub fn end(self) -> u32 {
66 self.end
67 }
68
69 pub fn len(self) -> usize {
70 (self.end - self.start) as usize
71 }
72
73 pub fn is_empty(self) -> bool {
74 self.start == self.end
75 }
76}
77
78#[derive(Debug, Hash, PartialEq, Eq)]
84pub struct DependencyIndex<T> {
85 index: u32,
86 marker: PhantomData<fn() -> T>,
87}
88
89impl<T> Copy for DependencyIndex<T> {}
90
91impl<T> Clone for DependencyIndex<T> {
92 fn clone(&self) -> Self {
93 *self
94 }
95}
96
97impl<T> DependencyIndex<T> {
98 pub(crate) fn from_index(index: usize) -> Self {
99 assert!(
100 u32::try_from(index).is_ok(),
101 "dependency side table is too large"
102 );
103 Self {
104 index: index as u32,
105 marker: PhantomData,
106 }
107 }
108
109 pub(crate) fn as_usize(self) -> usize {
110 self.index as usize
111 }
112
113 pub fn index(self) -> u32 {
114 self.index
115 }
116}
117
118#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
119pub enum Mode {
120 Local,
121 Global,
122 Pure,
123 Css,
124}
125
126#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
127pub struct ValueAtRuleImportItem<'s> {
128 local_name: &'s str,
129 import_name: &'s str,
130}
131
132impl<'s> ValueAtRuleImportItem<'s> {
133 pub(crate) fn new(local_name: &'s str, import_name: &'s str) -> Self {
134 Self {
135 local_name,
136 import_name,
137 }
138 }
139
140 pub fn local_name(&self) -> &'s str {
141 self.local_name
142 }
143
144 pub fn import_name(&self) -> &'s str {
145 self.import_name
146 }
147}
148
149#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
150pub struct ImportAttributes<'s> {
151 layer: Option<&'s str>,
152 supports: Option<&'s str>,
153 media: Option<&'s str>,
154}
155
156impl<'s> ImportAttributes<'s> {
157 pub(crate) fn new(
158 layer: Option<&'s str>,
159 supports: Option<&'s str>,
160 media: Option<&'s str>,
161 ) -> Self {
162 Self {
163 layer,
164 supports,
165 media,
166 }
167 }
168
169 pub fn layer(&self) -> Option<&'s str> {
170 self.layer
171 }
172
173 pub fn supports(&self) -> Option<&'s str> {
174 self.supports
175 }
176
177 pub fn media(&self) -> Option<&'s str> {
178 self.media
179 }
180}
181
182#[derive(Debug, Clone, Hash, PartialEq, Eq)]
183pub enum Dependency<'s> {
184 Url {
185 request: &'s str,
186 range: Range,
187 kind: UrlRangeKind,
188 },
189 Import {
190 request: &'s str,
191 range: Range,
192 attributes: DependencyIndex<ImportAttributes<'s>>,
193 },
194 ICSSImportUrl {
195 name: &'s str,
196 range: Range,
197 name_range: Range,
198 },
199 Replace {
200 content: &'s str,
201 range: Range,
202 },
203 Charset {
204 value: &'s str,
205 range: Range,
206 },
207 LocalClass {
208 name: &'s str,
209 range: Range,
210 explicit: bool,
211 },
212 LocalId {
213 name: &'s str,
214 range: Range,
215 explicit: bool,
216 },
217 LocalVar {
218 name: &'s str,
219 range: Range,
220 from: Option<&'s str>,
221 from_is_global: bool,
223 },
224 LocalVarDecl {
225 name: &'s str,
226 range: Range,
227 },
228 LocalPropertyDecl {
229 name: &'s str,
230 range: Range,
231 },
232 LocalKeyframes {
233 name: &'s str,
234 range: Range,
235 },
236 LocalKeyframesDecl {
237 name: &'s str,
238 range: Range,
239 },
240 LocalCounterStyle {
241 name: &'s str,
242 range: Range,
243 },
244 LocalCounterStyleDecl {
245 name: &'s str,
246 range: Range,
247 },
248 LocalFontPalette {
249 name: &'s str,
250 range: Range,
251 },
252 LocalFontPaletteDecl {
253 name: &'s str,
254 range: Range,
255 },
256 LocalContainer {
257 name: &'s str,
258 range: Range,
259 },
260 LocalContainerDecl {
261 name: &'s str,
262 range: Range,
263 },
264 LocalFunction {
265 name: &'s str,
266 range: Range,
267 },
268 LocalFunctionDecl {
269 name: &'s str,
270 range: Range,
271 },
272 LocalGrid {
273 name: &'s str,
274 range: Range,
275 },
276 LocalGridDecl {
277 name: &'s str,
278 range: Range,
279 },
280 Composes {
281 local_classes: DependencyListRange<&'s str>,
282 names: DependencyListRange<&'s str>,
283 from: Option<&'s str>,
284 from_is_global: bool,
286 range: Range,
287 },
288 ICSSImportFrom {
289 path: &'s str,
290 },
291 ICSSImportValue {
292 prop: &'s str,
293 value: &'s str,
294 },
295 ICSSExportValue {
296 prop: &'s str,
297 value: &'s str,
298 },
299 ICSSSymbol {
300 name: &'s str,
301 range: Range,
302 },
303}
304
305#[derive(Debug, Clone, Default)]
312pub struct DependencyContext<'s> {
313 dependencies: Vec<Dependency<'s>>,
314 dashed_ident_occurrences: Vec<Range>,
315 import_attributes: Vec<ImportAttributes<'s>>,
316 composes_local_classes: Vec<&'s str>,
317 composes_names: Vec<&'s str>,
318 value_at_rule_import_items: Vec<ValueAtRuleImportItem<'s>>,
319}
320
321impl<'s> DependencyContext<'s> {
322 pub fn new() -> Self {
323 Self::default()
324 }
325
326 pub(crate) fn reserve_estimated_capacity(&mut self, input_len: usize, mode: Mode) {
327 let estimate = DependencyContextCapacity::estimate(input_len, mode);
328 reserve_estimated_capacity(&mut self.dependencies, estimate.dependencies);
329 reserve_estimated_capacity(
330 &mut self.import_attributes,
331 estimate.import_attributes as usize,
332 );
333 reserve_estimated_capacity(
334 &mut self.composes_local_classes,
335 estimate.composes_local_classes as usize,
336 );
337 reserve_estimated_capacity(&mut self.composes_names, estimate.composes_names as usize);
338 reserve_estimated_capacity(
339 &mut self.value_at_rule_import_items,
340 estimate.value_at_rule_import_items as usize,
341 );
342 }
343
344 pub fn len(&self) -> usize {
345 self.dependencies.len()
346 }
347
348 pub fn is_empty(&self) -> bool {
349 self.dependencies.is_empty()
350 }
351
352 pub fn get(&self, index: usize) -> Option<&Dependency<'s>> {
353 self.dependencies.get(index)
354 }
355
356 pub fn iter(&self) -> std::slice::Iter<'_, Dependency<'s>> {
357 self.dependencies.iter()
358 }
359
360 pub fn dependencies(&self) -> &[Dependency<'s>] {
361 &self.dependencies
362 }
363
364 pub fn dashed_ident_name_ranges(&self) -> &[Range] {
365 &self.dashed_ident_occurrences
366 }
367
368 pub(crate) fn set_dashed_ident_occurrences(&mut self, occurrences: Vec<Range>) {
369 self.dashed_ident_occurrences = occurrences;
370 }
371
372 pub(crate) fn estimated_dashed_ident_capacity(input_len: usize, mode: Mode) -> usize {
373 DependencyContextCapacity::estimate(input_len, mode).dashed_ident_occurrences
374 }
375
376 pub fn import_attributes(
377 &self,
378 index: DependencyIndex<ImportAttributes<'s>>,
379 ) -> &ImportAttributes<'s> {
380 &self.import_attributes[index.as_usize()]
381 }
382
383 pub fn composes_local_classes(&self, range: DependencyListRange<&'s str>) -> &[&'s str] {
384 &self.composes_local_classes[range.as_usize_range()]
385 }
386
387 pub fn composes_names(&self, range: DependencyListRange<&'s str>) -> &[&'s str] {
388 &self.composes_names[range.as_usize_range()]
389 }
390
391 pub fn value_at_rule_import_items(&self) -> &[ValueAtRuleImportItem<'s>] {
392 &self.value_at_rule_import_items
393 }
394
395 pub(crate) fn push_dependency(&mut self, dependency: Dependency<'s>) {
396 self.dependencies.push(dependency);
397 }
398
399 pub(crate) fn push_import(
400 &mut self,
401 request: &'s str,
402 range: Range,
403 layer: Option<&'s str>,
404 supports: Option<&'s str>,
405 media: Option<&'s str>,
406 ) {
407 let attributes = DependencyIndex::from_index(self.import_attributes.len());
408 self
409 .import_attributes
410 .push(ImportAttributes::new(layer, supports, media));
411 self.dependencies.push(Dependency::Import {
412 request,
413 range,
414 attributes,
415 });
416 }
417
418 pub(crate) fn push_value_at_rule_import_item(&mut self, item: ValueAtRuleImportItem<'s>) {
419 self.value_at_rule_import_items.push(item);
420 }
421
422 pub(crate) fn value_at_rule_import_item(&self, index: usize) -> ValueAtRuleImportItem<'s> {
423 self.value_at_rule_import_items[index]
424 }
425
426 pub(crate) fn value_at_rule_import_items_checkpoint(&self) -> usize {
427 self.value_at_rule_import_items.len()
428 }
429
430 pub(crate) fn truncate_value_at_rule_import_items(&mut self, checkpoint: usize) {
431 self.value_at_rule_import_items.truncate(checkpoint);
432 }
433
434 pub(crate) fn finish_value_at_rule_import_items(
435 &self,
436 checkpoint: usize,
437 ) -> DependencyListRange<ValueAtRuleImportItem<'s>> {
438 debug_assert!(checkpoint <= self.value_at_rule_import_items.len());
439 DependencyListRange::from_bounds(checkpoint, self.value_at_rule_import_items.len())
440 }
441
442 pub(crate) fn push_composes(
443 &mut self,
444 local_classes: impl IntoIterator<Item = &'s str>,
445 names: impl IntoIterator<Item = &'s str>,
446 from: Option<&'s str>,
447 from_is_global: bool,
448 range: Range,
449 ) {
450 let local_classes_start = self.composes_local_classes.len();
451 self.composes_local_classes.extend(local_classes);
452 let local_classes = DependencyListRange::<&'s str>::from_bounds(
453 local_classes_start,
454 self.composes_local_classes.len(),
455 );
456
457 let names_start = self.composes_names.len();
458 self.composes_names.extend(names);
459 let names = DependencyListRange::<&'s str>::from_bounds(names_start, self.composes_names.len());
460
461 self.dependencies.push(Dependency::Composes {
462 local_classes,
463 names,
464 from,
465 from_is_global,
466 range,
467 });
468 }
469}
470
471impl PartialEq for DependencyContext<'_> {
472 fn eq(&self, other: &Self) -> bool {
473 self.dependencies == other.dependencies
474 && self.dashed_ident_occurrences == other.dashed_ident_occurrences
475 && self.import_attributes == other.import_attributes
476 && self.composes_local_classes == other.composes_local_classes
477 && self.composes_names == other.composes_names
478 && self.value_at_rule_import_items == other.value_at_rule_import_items
479 }
480}
481
482impl Eq for DependencyContext<'_> {}
483
484impl Hash for DependencyContext<'_> {
485 fn hash<H: Hasher>(&self, state: &mut H) {
486 self.dependencies.hash(state);
487 self.dashed_ident_occurrences.hash(state);
488 self.import_attributes.hash(state);
489 self.composes_local_classes.hash(state);
490 self.composes_names.hash(state);
491 self.value_at_rule_import_items.hash(state);
492 }
493}
494
495#[derive(Debug, Clone, Copy, Default)]
496struct DependencyContextCapacity {
497 dependencies: usize,
498 dashed_ident_occurrences: usize,
499 import_attributes: u16,
500 composes_local_classes: u16,
501 composes_names: u16,
502 value_at_rule_import_items: u16,
503}
504
505impl DependencyContextCapacity {
506 fn estimate(input_len: usize, mode: Mode) -> Self {
507 let dependency_denominator = match mode {
508 Mode::Local | Mode::Pure => 32,
509 Mode::Global => 80,
510 Mode::Css => 4096,
511 };
512 let dependencies = estimate_capacity(input_len, dependency_denominator, 4, 8192);
513 let dashed_ident_occurrences = if mode != Mode::Css {
514 estimate_capacity(input_len, 64, 2, 8192)
515 } else {
516 0
517 };
518 let import_attributes = estimate_capacity(input_len, 8192, 2, 1024);
519 let (composes_local_classes, composes_names, value_at_rule_import_items) = match mode {
520 Mode::Local | Mode::Pure => (
521 estimate_capacity(input_len, 640, 2, 4096),
522 estimate_capacity(input_len, 576, 2, 4096),
523 estimate_capacity(input_len, 1792, 2, 4096),
524 ),
525 Mode::Global => (
526 estimate_capacity(input_len, 3880, 2, 4096),
527 estimate_capacity(input_len, 3880, 2, 4096),
528 estimate_capacity(input_len, 1774, 2, 4096),
529 ),
530 Mode::Css => (0, 0, 0),
531 };
532 Self {
533 dependencies,
534 dashed_ident_occurrences,
535 import_attributes: import_attributes as u16,
536 composes_local_classes: composes_local_classes as u16,
537 composes_names: composes_names as u16,
538 value_at_rule_import_items: value_at_rule_import_items as u16,
539 }
540 }
541}
542
543fn estimate_capacity(
544 input_len: usize,
545 denominator: usize,
546 minimum: usize,
547 maximum: usize,
548) -> usize {
549 input_len
550 .checked_div(denominator)
551 .unwrap_or(0)
552 .clamp(minimum, maximum)
553}
554
555fn reserve_estimated_capacity<T>(values: &mut Vec<T>, estimated_capacity: usize) {
556 if values.capacity() < estimated_capacity {
557 values.reserve(estimated_capacity.saturating_sub(values.len()));
558 }
559}
560
561impl<'s> Index<usize> for DependencyContext<'s> {
562 type Output = Dependency<'s>;
563
564 fn index(&self, index: usize) -> &Self::Output {
565 &self.dependencies[index]
566 }
567}
568
569impl<'context, 's> IntoIterator for &'context DependencyContext<'s> {
570 type Item = &'context Dependency<'s>;
571 type IntoIter = std::slice::Iter<'context, Dependency<'s>>;
572
573 fn into_iter(self) -> Self::IntoIter {
574 self.dependencies.iter()
575 }
576}
577
578#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
579pub enum UrlRangeKind {
580 Function,
581 String,
582}
583
584#[derive(Debug, Clone, Hash, PartialEq, Eq)]
585pub struct Warning<'s> {
586 pub(crate) range: Range,
587 pub(crate) kind: WarningKind<'s>,
588}
589
590impl<'s> Warning<'s> {
591 pub fn new(range: Range, kind: WarningKind<'s>) -> Self {
592 Self { range, kind }
593 }
594
595 pub fn range(&self) -> &Range {
596 &self.range
597 }
598
599 pub fn kind(&self) -> &WarningKind<'s> {
600 &self.kind
601 }
602}
603
604#[derive(Debug, Clone, Hash, PartialEq, Eq)]
605pub enum WarningKind<'s> {
606 Unexpected { message: &'s str },
607 DuplicateUrl { when: &'s str },
608 NamespaceNotSupportedInBundledCss,
609 NotPrecededAtImport,
610 ExpectedUrl { when: &'s str },
611 ExpectedUrlBefore { when: &'s str },
612 ExpectedLayerBefore { when: &'s str },
613 InconsistentModeResult,
614 ExpectedNotInside { pseudo: &'s str },
615 NotPure { message: &'s str },
616 UnexpectedComposition { message: &'s str },
617}
618
619impl Display for Warning<'_> {
620 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
621 match self.kind {
622 WarningKind::Unexpected { message, .. } => write!(f, "{message}"),
623 WarningKind::DuplicateUrl { when, .. } => {
624 write!(f, "Duplicate of 'url(...)' in '{when}'")
625 }
626 WarningKind::NamespaceNotSupportedInBundledCss { .. } => {
627 write!(f, "'@namespace' is not supported in bundled CSS")
628 }
629 WarningKind::NotPrecededAtImport { .. } => {
630 write!(f, "Any '@import' rules must precede all other rules")
631 }
632 WarningKind::ExpectedUrl { when, .. } => write!(f, "Expected URL in '{when}'"),
633 WarningKind::ExpectedUrlBefore { when, .. } => {
634 write!(
635 f,
636 "An URL in '{when}' should be before 'layer(...)' or 'supports(...)'"
637 )
638 }
639 WarningKind::ExpectedLayerBefore { when, .. } => {
640 write!(
641 f,
642 "The 'layer(...)' in '{when}' should be before 'supports(...)'"
643 )
644 }
645 WarningKind::InconsistentModeResult { .. } => write!(
646 f,
647 "Inconsistent rule global/local (multiple selectors must result in the same mode for the rule)"
648 ),
649 WarningKind::ExpectedNotInside { pseudo, .. } => write!(
650 f,
651 "A '{pseudo}' is not allowed inside of a ':local()' or ':global()'"
652 ),
653 WarningKind::NotPure { message, .. } => {
654 write!(f, "Pure globals is not allowed in pure mode, {message}")
655 }
656 WarningKind::UnexpectedComposition { message, .. } => {
657 write!(f, "Composition is {message}")
658 }
659 }
660 }
661}
662
663#[cfg(test)]
664#[path = "../tests/dependency_types_tests.rs"]
665mod tests;