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