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 magic_comments: Option<&'s str>,
189 },
190 Import {
191 request: &'s str,
192 range: Range,
193 attributes: DependencyIndex<ImportAttributes<'s>>,
194 magic_comments: Option<&'s str>,
195 },
196 ICSSImportUrl {
197 name: &'s str,
198 range: Range,
199 name_range: Range,
200 },
201 Replace {
202 content: &'s str,
203 range: Range,
204 },
205 Charset {
206 value: &'s str,
207 range: Range,
208 },
209 LocalClass {
210 name: &'s str,
211 range: Range,
212 explicit: bool,
213 },
214 LocalId {
215 name: &'s str,
216 range: Range,
217 explicit: bool,
218 },
219 LocalVar {
220 name: &'s str,
221 range: Range,
222 from: Option<&'s str>,
223 from_is_global: bool,
225 },
226 LocalVarDecl {
227 name: &'s str,
228 range: Range,
229 },
230 LocalPropertyDecl {
231 name: &'s str,
232 range: Range,
233 },
234 LocalKeyframes {
235 name: &'s str,
236 range: Range,
237 },
238 LocalKeyframesDecl {
239 name: &'s str,
240 range: Range,
241 },
242 LocalCounterStyle {
243 name: &'s str,
244 range: Range,
245 },
246 LocalCounterStyleDecl {
247 name: &'s str,
248 range: Range,
249 },
250 LocalFontPalette {
251 name: &'s str,
252 range: Range,
253 },
254 LocalFontPaletteDecl {
255 name: &'s str,
256 range: Range,
257 },
258 LocalContainer {
259 name: &'s str,
260 range: Range,
261 },
262 LocalContainerDecl {
263 name: &'s str,
264 range: Range,
265 },
266 LocalFunction {
267 name: &'s str,
268 range: Range,
269 },
270 LocalFunctionDecl {
271 name: &'s str,
272 range: Range,
273 },
274 LocalGrid {
275 name: &'s str,
276 range: Range,
277 },
278 LocalGridDecl {
279 name: &'s str,
280 range: Range,
281 },
282 Composes {
283 local_classes: DependencyListRange<&'s str>,
284 names: DependencyListRange<&'s str>,
285 from: Option<&'s str>,
286 from_is_global: bool,
288 range: Range,
289 },
290 ICSSImportFrom {
291 path: &'s str,
292 },
293 ICSSImportValue {
294 prop: &'s str,
295 value: &'s str,
296 },
297 ICSSExportValue {
298 prop: &'s str,
299 value: &'s str,
300 },
301 ICSSSymbol {
302 name: &'s str,
303 range: Range,
304 },
305}
306
307#[derive(Debug, Clone, Default)]
314pub struct DependencyContext<'s> {
315 dependencies: Vec<Dependency<'s>>,
316 dashed_ident_occurrences: Vec<Range>,
317 import_attributes: Vec<ImportAttributes<'s>>,
318 composes_local_classes: Vec<&'s str>,
319 composes_names: Vec<&'s str>,
320 value_at_rule_import_items: Vec<ValueAtRuleImportItem<'s>>,
321}
322
323impl<'s> DependencyContext<'s> {
324 pub fn new() -> Self {
325 Self::default()
326 }
327
328 pub(crate) fn reserve_estimated_capacity(&mut self, input_len: usize, mode: Mode) {
329 let estimate = DependencyContextCapacity::estimate(input_len, mode);
330 reserve_estimated_capacity(&mut self.dependencies, estimate.dependencies);
331 reserve_estimated_capacity(
332 &mut self.import_attributes,
333 estimate.import_attributes as usize,
334 );
335 reserve_estimated_capacity(
336 &mut self.composes_local_classes,
337 estimate.composes_local_classes as usize,
338 );
339 reserve_estimated_capacity(&mut self.composes_names, estimate.composes_names as usize);
340 reserve_estimated_capacity(
341 &mut self.value_at_rule_import_items,
342 estimate.value_at_rule_import_items as usize,
343 );
344 }
345
346 pub fn len(&self) -> usize {
347 self.dependencies.len()
348 }
349
350 pub fn is_empty(&self) -> bool {
351 self.dependencies.is_empty()
352 }
353
354 pub fn get(&self, index: usize) -> Option<&Dependency<'s>> {
355 self.dependencies.get(index)
356 }
357
358 pub fn iter(&self) -> std::slice::Iter<'_, Dependency<'s>> {
359 self.dependencies.iter()
360 }
361
362 pub fn dependencies(&self) -> &[Dependency<'s>] {
363 &self.dependencies
364 }
365
366 pub fn dashed_ident_name_ranges(&self) -> &[Range] {
367 &self.dashed_ident_occurrences
368 }
369
370 pub(crate) fn set_dashed_ident_occurrences(&mut self, occurrences: Vec<Range>) {
371 self.dashed_ident_occurrences = occurrences;
372 }
373
374 pub(crate) fn estimated_dashed_ident_capacity(input_len: usize, mode: Mode) -> usize {
375 DependencyContextCapacity::estimate(input_len, mode).dashed_ident_occurrences
376 }
377
378 pub fn import_attributes(
379 &self,
380 index: DependencyIndex<ImportAttributes<'s>>,
381 ) -> &ImportAttributes<'s> {
382 &self.import_attributes[index.as_usize()]
383 }
384
385 pub fn composes_local_classes(&self, range: DependencyListRange<&'s str>) -> &[&'s str] {
386 &self.composes_local_classes[range.as_usize_range()]
387 }
388
389 pub fn composes_names(&self, range: DependencyListRange<&'s str>) -> &[&'s str] {
390 &self.composes_names[range.as_usize_range()]
391 }
392
393 pub fn value_at_rule_import_items(&self) -> &[ValueAtRuleImportItem<'s>] {
394 &self.value_at_rule_import_items
395 }
396
397 pub(crate) fn push_dependency(&mut self, dependency: Dependency<'s>) {
398 self.dependencies.push(dependency);
399 }
400
401 pub(crate) fn push_import(
402 &mut self,
403 request: &'s str,
404 range: Range,
405 layer: Option<&'s str>,
406 supports: Option<&'s str>,
407 media: Option<&'s str>,
408 magic_comments: Option<&'s str>,
409 ) {
410 let attributes = DependencyIndex::from_index(self.import_attributes.len());
411 self
412 .import_attributes
413 .push(ImportAttributes::new(layer, supports, media));
414 self.dependencies.push(Dependency::Import {
415 request,
416 range,
417 attributes,
418 magic_comments,
419 });
420 }
421
422 pub(crate) fn push_value_at_rule_import_item(&mut self, item: ValueAtRuleImportItem<'s>) {
423 self.value_at_rule_import_items.push(item);
424 }
425
426 pub(crate) fn value_at_rule_import_item(&self, index: usize) -> ValueAtRuleImportItem<'s> {
427 self.value_at_rule_import_items[index]
428 }
429
430 pub(crate) fn value_at_rule_import_items_checkpoint(&self) -> usize {
431 self.value_at_rule_import_items.len()
432 }
433
434 pub(crate) fn truncate_value_at_rule_import_items(&mut self, checkpoint: usize) {
435 self.value_at_rule_import_items.truncate(checkpoint);
436 }
437
438 pub(crate) fn finish_value_at_rule_import_items(
439 &self,
440 checkpoint: usize,
441 ) -> DependencyListRange<ValueAtRuleImportItem<'s>> {
442 debug_assert!(checkpoint <= self.value_at_rule_import_items.len());
443 DependencyListRange::from_bounds(checkpoint, self.value_at_rule_import_items.len())
444 }
445
446 pub(crate) fn push_composes(
447 &mut self,
448 local_classes: impl IntoIterator<Item = &'s str>,
449 names: impl IntoIterator<Item = &'s str>,
450 from: Option<&'s str>,
451 from_is_global: bool,
452 range: Range,
453 ) {
454 let local_classes_start = self.composes_local_classes.len();
455 self.composes_local_classes.extend(local_classes);
456 let local_classes = DependencyListRange::<&'s str>::from_bounds(
457 local_classes_start,
458 self.composes_local_classes.len(),
459 );
460
461 let names_start = self.composes_names.len();
462 self.composes_names.extend(names);
463 let names = DependencyListRange::<&'s str>::from_bounds(names_start, self.composes_names.len());
464
465 self.dependencies.push(Dependency::Composes {
466 local_classes,
467 names,
468 from,
469 from_is_global,
470 range,
471 });
472 }
473}
474
475impl PartialEq for DependencyContext<'_> {
476 fn eq(&self, other: &Self) -> bool {
477 self.dependencies == other.dependencies
478 && self.dashed_ident_occurrences == other.dashed_ident_occurrences
479 && self.import_attributes == other.import_attributes
480 && self.composes_local_classes == other.composes_local_classes
481 && self.composes_names == other.composes_names
482 && self.value_at_rule_import_items == other.value_at_rule_import_items
483 }
484}
485
486impl Eq for DependencyContext<'_> {}
487
488impl Hash for DependencyContext<'_> {
489 fn hash<H: Hasher>(&self, state: &mut H) {
490 self.dependencies.hash(state);
491 self.dashed_ident_occurrences.hash(state);
492 self.import_attributes.hash(state);
493 self.composes_local_classes.hash(state);
494 self.composes_names.hash(state);
495 self.value_at_rule_import_items.hash(state);
496 }
497}
498
499#[derive(Debug, Clone, Copy, Default)]
500struct DependencyContextCapacity {
501 dependencies: usize,
502 dashed_ident_occurrences: usize,
503 import_attributes: u16,
504 composes_local_classes: u16,
505 composes_names: u16,
506 value_at_rule_import_items: u16,
507}
508
509impl DependencyContextCapacity {
510 fn estimate(input_len: usize, mode: Mode) -> Self {
511 let dependency_denominator = match mode {
512 Mode::Local | Mode::Pure => 32,
513 Mode::Global => 80,
514 Mode::Css => 4096,
515 };
516 let dependencies = estimate_capacity(input_len, dependency_denominator, 4, 8192);
517 let dashed_ident_occurrences = if mode != Mode::Css {
518 estimate_capacity(input_len, 64, 2, 8192)
519 } else {
520 0
521 };
522 let import_attributes = estimate_capacity(input_len, 8192, 2, 1024);
523 let (composes_local_classes, composes_names, value_at_rule_import_items) = match mode {
524 Mode::Local | Mode::Pure => (
525 estimate_capacity(input_len, 640, 2, 4096),
526 estimate_capacity(input_len, 576, 2, 4096),
527 estimate_capacity(input_len, 1792, 2, 4096),
528 ),
529 Mode::Global => (
530 estimate_capacity(input_len, 3880, 2, 4096),
531 estimate_capacity(input_len, 3880, 2, 4096),
532 estimate_capacity(input_len, 1774, 2, 4096),
533 ),
534 Mode::Css => (0, 0, 0),
535 };
536 Self {
537 dependencies,
538 dashed_ident_occurrences,
539 import_attributes: import_attributes as u16,
540 composes_local_classes: composes_local_classes as u16,
541 composes_names: composes_names as u16,
542 value_at_rule_import_items: value_at_rule_import_items as u16,
543 }
544 }
545}
546
547fn estimate_capacity(
548 input_len: usize,
549 denominator: usize,
550 minimum: usize,
551 maximum: usize,
552) -> usize {
553 input_len
554 .checked_div(denominator)
555 .unwrap_or(0)
556 .clamp(minimum, maximum)
557}
558
559fn reserve_estimated_capacity<T>(values: &mut Vec<T>, estimated_capacity: usize) {
560 if values.capacity() < estimated_capacity {
561 values.reserve(estimated_capacity.saturating_sub(values.len()));
562 }
563}
564
565impl<'s> Index<usize> for DependencyContext<'s> {
566 type Output = Dependency<'s>;
567
568 fn index(&self, index: usize) -> &Self::Output {
569 &self.dependencies[index]
570 }
571}
572
573impl<'context, 's> IntoIterator for &'context DependencyContext<'s> {
574 type Item = &'context Dependency<'s>;
575 type IntoIter = std::slice::Iter<'context, Dependency<'s>>;
576
577 fn into_iter(self) -> Self::IntoIter {
578 self.dependencies.iter()
579 }
580}
581
582#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
583pub enum UrlRangeKind {
584 Function,
585 String,
586}
587
588#[derive(Debug, Clone, Hash, PartialEq, Eq)]
589pub struct Warning<'s> {
590 pub(crate) range: Range,
591 pub(crate) kind: WarningKind<'s>,
592}
593
594impl<'s> Warning<'s> {
595 pub fn new(range: Range, kind: WarningKind<'s>) -> Self {
596 Self { range, kind }
597 }
598
599 pub fn range(&self) -> &Range {
600 &self.range
601 }
602
603 pub fn kind(&self) -> &WarningKind<'s> {
604 &self.kind
605 }
606}
607
608#[derive(Debug, Clone, Hash, PartialEq, Eq)]
609pub enum WarningKind<'s> {
610 Unexpected { message: &'s str },
611 DuplicateUrl { when: &'s str },
612 NamespaceNotSupportedInBundledCss,
613 NotPrecededAtImport,
614 ExpectedUrl { when: &'s str },
615 ExpectedUrlBefore { when: &'s str },
616 ExpectedLayerBefore { when: &'s str },
617 InconsistentModeResult,
618 ExpectedNotInside { pseudo: &'s str },
619 NotPure { message: &'s str },
620 UnexpectedComposition { message: &'s str },
621}
622
623impl Display for Warning<'_> {
624 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
625 match self.kind {
626 WarningKind::Unexpected { message, .. } => write!(f, "{message}"),
627 WarningKind::DuplicateUrl { when, .. } => {
628 write!(f, "Duplicate of 'url(...)' in '{when}'")
629 }
630 WarningKind::NamespaceNotSupportedInBundledCss { .. } => {
631 write!(f, "'@namespace' is not supported in bundled CSS")
632 }
633 WarningKind::NotPrecededAtImport { .. } => {
634 write!(f, "Any '@import' rules must precede all other rules")
635 }
636 WarningKind::ExpectedUrl { when, .. } => write!(f, "Expected URL in '{when}'"),
637 WarningKind::ExpectedUrlBefore { when, .. } => {
638 write!(
639 f,
640 "An URL in '{when}' should be before 'layer(...)' or 'supports(...)'"
641 )
642 }
643 WarningKind::ExpectedLayerBefore { when, .. } => {
644 write!(
645 f,
646 "The 'layer(...)' in '{when}' should be before 'supports(...)'"
647 )
648 }
649 WarningKind::InconsistentModeResult { .. } => write!(
650 f,
651 "Inconsistent rule global/local (multiple selectors must result in the same mode for the rule)"
652 ),
653 WarningKind::ExpectedNotInside { pseudo, .. } => write!(
654 f,
655 "A '{pseudo}' is not allowed inside of a ':local()' or ':global()'"
656 ),
657 WarningKind::NotPure { message, .. } => {
658 write!(f, "Pure globals is not allowed in pure mode, {message}")
659 }
660 WarningKind::UnexpectedComposition { message, .. } => {
661 write!(f, "Composition is {message}")
662 }
663 }
664 }
665}
666
667#[cfg(test)]
668#[path = "../tests/dependency_types_tests.rs"]
669mod tests;