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