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