1mod animations;
7mod at_rules;
8mod css_modules;
9mod emission_selectors;
10mod icss;
11mod sass;
12mod selectors;
13mod variables;
14
15#[cfg(test)]
16use cstree::syntax::SyntaxNode;
17use cstree::{
18 green::GreenNode,
19 text::{TextRange, TextSize},
20 util::NodeOrToken,
21};
22use omena_syntax::{StyleDialect, SyntaxKind};
23
24use crate::{DialectExtension, ParseResult, Parser, Token, tokenize};
25
26pub(crate) const STYLE_FACT_FAMILIES: &[&str] = &[
27 "selectors",
28 "variables",
29 "sass-symbols",
30 "sass-includes",
31 "sass-module-edges",
32 "sass-placeholder-definitions",
33 "extend-targets",
34 "animations",
35 "css-module-values",
36 "css-module-value-import-edges",
37 "css-module-value-definition-edges",
38 "css-module-composes",
39 "css-module-composes-edges",
40 "icss",
41 "icss-import-edges",
42 "icss-export-edges",
43 "at-rules",
44 "emission-selectors",
45];
46
47pub(crate) struct StyleFactSink<'text> {
53 text: &'text str,
54 dialect: StyleDialect,
55 error_count: usize,
56 tokens: Vec<Token<'text>>,
57 nodes: Vec<StyleFactNodeEvent>,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub(crate) struct StyleFactNodeEvent {
62 pub(crate) index: usize,
63 pub(crate) kind: SyntaxKind,
64 pub(crate) range: TextRange,
65 pub(crate) parent: Option<usize>,
66 pub(crate) is_top_level: bool,
67}
68
69impl<'text> StyleFactSink<'text> {
70 pub(crate) fn from_cst(text: &'text str, parsed: &ParseResult) -> Self {
71 crate::record_omena_parser_fact_collection_traversal("style-fact-sink");
72 crate::record_omena_parser_fact_collection_registrations(STYLE_FACT_FAMILIES);
73 let mut tokens = Vec::with_capacity(parsed.token_count());
74 let mut nodes = Vec::new();
75 collect_style_fact_events_from_green(
76 parsed.green(),
77 TextSize::from(0),
78 None,
79 text,
80 &mut tokens,
81 &mut nodes,
82 );
83 Self {
84 text,
85 dialect: parsed.dialect(),
86 error_count: parsed.errors().len(),
87 tokens,
88 nodes,
89 }
90 }
91
92 pub(crate) fn text(&self) -> &'text str {
93 self.text
94 }
95
96 pub(crate) fn dialect(&self) -> StyleDialect {
97 self.dialect
98 }
99
100 pub(crate) fn error_count(&self) -> usize {
101 self.error_count
102 }
103
104 pub(crate) fn tokens(&self) -> &[Token<'text>] {
105 self.tokens.as_slice()
106 }
107
108 pub(crate) fn nodes(&self) -> impl Iterator<Item = &StyleFactNodeEvent> {
109 self.nodes.iter()
110 }
111
112 pub(crate) fn node_tokens(&self, node: &StyleFactNodeEvent) -> &[Token<'text>] {
113 let node_range = node.range;
114 let start = self
115 .tokens
116 .partition_point(|token| token.range.start() < node_range.start());
117 let end = self.tokens[start..]
118 .partition_point(|token| token.range.start() < node_range.end())
119 + start;
120 &self.tokens[start..end]
121 }
122
123 pub(crate) fn has_token_kind(&self, kind: SyntaxKind) -> bool {
124 self.tokens.iter().any(|token| token.kind == kind)
125 }
126
127 pub(crate) fn ancestors_inclusive(
128 &self,
129 node: &StyleFactNodeEvent,
130 ) -> Vec<&StyleFactNodeEvent> {
131 let mut ancestors = Vec::new();
132 let mut current = Some(node.index);
133 while let Some(index) = current {
134 let ancestor = &self.nodes[index];
135 ancestors.push(ancestor);
136 current = ancestor.parent;
137 }
138 ancestors
139 }
140
141 pub(crate) fn has_intervening_ancestor_kind(
142 &self,
143 node: &StyleFactNodeEvent,
144 stop: &StyleFactNodeEvent,
145 kinds: &[SyntaxKind],
146 ) -> bool {
147 let mut current = node.parent;
148 while let Some(index) = current {
149 if index == stop.index {
150 return false;
151 }
152 let ancestor = &self.nodes[index];
153 if kinds.contains(&ancestor.kind) {
154 return true;
155 }
156 current = ancestor.parent;
157 }
158 false
159 }
160}
161
162fn collect_style_fact_events_from_green<'text>(
163 node: &GreenNode,
164 start: TextSize,
165 parent: Option<usize>,
166 text: &'text str,
167 tokens: &mut Vec<Token<'text>>,
168 nodes: &mut Vec<StyleFactNodeEvent>,
169) {
170 let mut offset = start;
171 for child in node.children() {
172 match child {
173 NodeOrToken::Node(child_node) => {
174 let kind =
175 SyntaxKind::from_raw_kind(child_node.kind().0).unwrap_or(SyntaxKind::Unknown);
176 let index = nodes.len();
177 let is_top_level = parent.is_some_and(|parent| {
178 matches!(
179 nodes[parent].kind,
180 SyntaxKind::Stylesheet
181 | SyntaxKind::ScssStylesheet
182 | SyntaxKind::LessStylesheet
183 )
184 });
185 nodes.push(StyleFactNodeEvent {
186 index,
187 kind,
188 range: TextRange::at(offset, child_node.text_len()),
189 parent,
190 is_top_level,
191 });
192 collect_style_fact_events_from_green(
193 child_node,
194 offset,
195 Some(index),
196 text,
197 tokens,
198 nodes,
199 );
200 offset += child_node.text_len();
201 }
202 NodeOrToken::Token(token) => {
203 let kind = SyntaxKind::from_raw_kind(token.kind().0).unwrap_or(SyntaxKind::Unknown);
204 let range = TextRange::at(offset, token.text_len());
205 let token_start = u32::from(range.start()) as usize;
206 let token_end = u32::from(range.end()) as usize;
207 tokens.push(Token {
208 kind,
209 text: text.get(token_start..token_end).unwrap_or_default(),
210 range,
211 });
212 offset += token.text_len();
213 }
214 }
215 }
216}
217
218pub(crate) use animations::collect_animation_facts_from_sink;
219pub use animations::{ParsedAnimationFact, ParsedAnimationFactKind};
220pub use at_rules::ParsedAtRuleFact;
221pub(crate) use at_rules::collect_at_rule_facts_from_sink;
222pub use css_modules::{
223 ParsedCssModuleComposesEdgeFact, ParsedCssModuleComposesEdgeKind, ParsedCssModuleComposesFact,
224 ParsedCssModuleComposesFactKind, ParsedCssModuleValueDefinitionEdgeFact,
225 ParsedCssModuleValueFact, ParsedCssModuleValueFactKind, ParsedCssModuleValueImportEdgeFact,
226};
227pub(crate) use css_modules::{
228 collect_css_module_composes_edge_facts_from_sink, collect_css_module_composes_facts_from_sink,
229 collect_css_module_value_definition_edge_facts_from_sink,
230 collect_css_module_value_definition_edge_names, collect_css_module_value_facts_from_sink,
231 collect_css_module_value_import_edge_facts_from_sink,
232 css_module_value_reference_token_can_be_name, css_module_value_source_name,
233 css_module_value_statement_end, declaration_colon_index,
234};
235pub(crate) use emission_selectors::collect_emission_selector_facts_from_sink;
236pub use emission_selectors::{
237 ParsedEmissionSelectorFactKindV0, ParsedEmissionSelectorFactV0, ParsedEmissionSelectorFactsV0,
238 collect_emission_selector_facts_from_cst,
239};
240pub use icss::{
241 ParsedIcssExportEdgeFact, ParsedIcssFact, ParsedIcssFactKind, ParsedIcssImportEdgeFact,
242 collect_icss_export_values_from_cst,
243};
244pub(crate) use icss::{
245 collect_icss_export_edge_facts_from_sink, collect_icss_facts_from_sink,
246 collect_icss_import_edge_facts_from_sink,
247};
248pub use sass::{
249 ParsedExtendTargetFact, ParsedExtendTargetFactKind, ParsedSassCallableParameterFact,
250 ParsedSassCallableSignatureFact, ParsedSassIncludeFact, ParsedSassModuleEdgeFact,
251 ParsedSassModuleEdgeFactKind, ParsedSassPlaceholderDefinitionFact, ParsedSassSymbolFact,
252 ParsedSassSymbolFactKind,
253};
254pub(crate) use sass::{
255 collect_extend_target_facts_from_sink, collect_sass_include_facts_from_sink,
256 collect_sass_module_edge_facts_from_sink, collect_sass_placeholder_definition_facts_from_sink,
257 collect_sass_symbol_facts_from_sink,
258};
259pub use selectors::{ParsedSelectorFact, ParsedSelectorFactKind};
260pub(crate) use selectors::{
261 SelectorBranch, collect_class_selector_names_from_header, collect_selector_facts_from_sink,
262 css_module_block_scope_marker_in_header, css_module_header_is_global_only,
263 resolve_selector_header, split_selector_groups,
264};
265pub use variables::{ParsedVariableFact, ParsedVariableFactKind, ParsedVariableFactNameV0};
266pub(crate) use variables::{collect_variable_facts_from_sink, scss_variable_token_is_declaration};
267
268#[derive(Debug, Clone, PartialEq, Eq)]
269pub struct ParsedStyleFacts {
270 pub product: &'static str,
271 pub dialect: StyleDialect,
272 pub selector_count: usize,
273 pub selectors: Vec<ParsedSelectorFact>,
274 pub variable_count: usize,
275 pub variables: Vec<ParsedVariableFact>,
276 pub sass_symbol_count: usize,
277 pub sass_symbols: Vec<ParsedSassSymbolFact>,
278 pub sass_include_count: usize,
279 pub sass_includes: Vec<ParsedSassIncludeFact>,
280 pub sass_module_edge_count: usize,
281 pub sass_module_edges: Vec<ParsedSassModuleEdgeFact>,
282 pub sass_placeholder_definition_count: usize,
283 pub sass_placeholder_definitions: Vec<ParsedSassPlaceholderDefinitionFact>,
284 pub extend_target_count: usize,
285 pub extend_targets: Vec<ParsedExtendTargetFact>,
286 pub animation_count: usize,
287 pub animations: Vec<ParsedAnimationFact>,
288 pub css_module_value_count: usize,
289 pub css_module_values: Vec<ParsedCssModuleValueFact>,
290 pub css_module_value_import_edge_count: usize,
291 pub css_module_value_import_edges: Vec<ParsedCssModuleValueImportEdgeFact>,
292 pub css_module_value_definition_edge_count: usize,
293 pub css_module_value_definition_edges: Vec<ParsedCssModuleValueDefinitionEdgeFact>,
294 pub css_module_composes_count: usize,
295 pub css_module_composes: Vec<ParsedCssModuleComposesFact>,
296 pub css_module_composes_edge_count: usize,
297 pub css_module_composes_edges: Vec<ParsedCssModuleComposesEdgeFact>,
298 pub icss_count: usize,
299 pub icss: Vec<ParsedIcssFact>,
300 pub icss_import_edge_count: usize,
301 pub icss_import_edges: Vec<ParsedIcssImportEdgeFact>,
302 pub icss_export_edge_count: usize,
303 pub icss_export_edges: Vec<ParsedIcssExportEdgeFact>,
304 pub at_rule_count: usize,
305 pub at_rules: Vec<ParsedAtRuleFact>,
306 pub error_count: usize,
307}
308
309#[derive(Debug, Clone, PartialEq, Eq)]
310#[non_exhaustive]
311pub struct ParsedStyleFactCollectionV0 {
312 pub facts: ParsedStyleFacts,
313 pub emission_selectors: ParsedEmissionSelectorFactsV0,
314}
315
316struct ProductFacts(ParsedStyleFacts);
317
318impl From<ParsedStyleFacts> for ProductFacts {
319 fn from(facts: ParsedStyleFacts) -> Self {
320 let ParsedStyleFacts {
321 product,
322 dialect,
323 selector_count,
324 selectors,
325 variable_count,
326 variables,
327 sass_symbol_count,
328 sass_symbols,
329 sass_include_count: _,
330 sass_includes: _,
331 sass_module_edge_count,
332 sass_module_edges,
333 sass_placeholder_definition_count,
334 sass_placeholder_definitions,
335 extend_target_count: _,
336 extend_targets: _,
337 animation_count,
338 animations,
339 css_module_value_count,
340 css_module_values,
341 css_module_value_import_edge_count,
342 css_module_value_import_edges,
343 css_module_value_definition_edge_count,
344 css_module_value_definition_edges,
345 css_module_composes_count,
346 css_module_composes,
347 css_module_composes_edge_count,
348 css_module_composes_edges,
349 icss_count: _,
350 icss: _,
351 icss_import_edge_count: _,
352 icss_import_edges: _,
353 icss_export_edge_count: _,
354 icss_export_edges: _,
355 at_rule_count: _,
356 at_rules: _,
357 error_count,
358 } = facts;
359 let include_sass_declarations = matches!(dialect, StyleDialect::Scss | StyleDialect::Sass);
360 let (
361 sass_symbol_count,
362 sass_symbols,
363 sass_module_edge_count,
364 sass_module_edges,
365 sass_placeholder_definition_count,
366 sass_placeholder_definitions,
367 ) = if include_sass_declarations {
368 (
369 sass_symbol_count,
370 sass_symbols,
371 sass_module_edge_count,
372 sass_module_edges,
373 sass_placeholder_definition_count,
374 sass_placeholder_definitions,
375 )
376 } else {
377 (0, Vec::new(), 0, Vec::new(), 0, Vec::new())
378 };
379
380 Self(ParsedStyleFacts {
381 product,
382 dialect,
383 selector_count,
384 selectors,
385 variable_count,
386 variables,
387 sass_symbol_count,
388 sass_symbols,
389 sass_include_count: 0,
390 sass_includes: Vec::new(),
391 sass_module_edge_count,
392 sass_module_edges,
393 sass_placeholder_definition_count,
394 sass_placeholder_definitions,
395 extend_target_count: 0,
396 extend_targets: Vec::new(),
397 animation_count,
398 animations,
399 css_module_value_count,
400 css_module_values,
401 css_module_value_import_edge_count,
402 css_module_value_import_edges,
403 css_module_value_definition_edge_count,
404 css_module_value_definition_edges,
405 css_module_composes_count,
406 css_module_composes,
407 css_module_composes_edge_count,
408 css_module_composes_edges,
409 icss_count: 0,
410 icss: Vec::new(),
411 icss_import_edge_count: 0,
412 icss_import_edges: Vec::new(),
413 icss_export_edge_count: 0,
414 icss_export_edges: Vec::new(),
415 at_rule_count: 0,
416 at_rules: Vec::new(),
417 error_count,
418 })
419 }
420}
421
422impl From<ProductFacts> for ParsedStyleFacts {
423 fn from(facts: ProductFacts) -> Self {
424 facts.0
425 }
426}
427
428pub fn collect_style_facts_with_extension(
429 text: &str,
430 extension: &impl DialectExtension,
431) -> ParsedStyleFacts {
432 let parsed = parse_style_fact_source(text, extension);
433 facts_from_cst(text, &parsed)
434}
435
436pub fn collect_style_fact_collection_with_extension(
437 text: &str,
438 extension: &impl DialectExtension,
439) -> ParsedStyleFactCollectionV0 {
440 let parsed = parse_style_fact_source(text, extension);
441 let sink = StyleFactSink::from_cst(text, &parsed);
442 ParsedStyleFactCollectionV0 {
443 facts: facts_from_sink(&sink),
444 emission_selectors: collect_emission_selector_facts_from_sink(&sink),
445 }
446}
447
448fn parse_style_fact_source(text: &str, extension: &impl DialectExtension) -> ParseResult {
449 let (tokens, lex_errors) = tokenize(text, extension);
450 let token_count = tokens.len();
451 let mut parser = Parser::new(tokens, lex_errors, extension.dialect());
452 crate::record_omena_parser_parse_materialization(token_count);
453 let (green, interner) = parser.parse();
454 let errors = parser.into_errors();
455 ParseResult::new(green, interner, errors, token_count, extension.dialect())
456}
457
458pub fn facts_from_cst(text: &str, parsed: &ParseResult) -> ParsedStyleFacts {
459 let sink = StyleFactSink::from_cst(text, parsed);
460 facts_from_sink(&sink)
461}
462
463fn facts_from_sink(sink: &StyleFactSink<'_>) -> ParsedStyleFacts {
464 let selectors = collect_selector_facts_from_sink(sink);
465 let variables = collect_variable_facts_from_sink(sink);
466 let sass_symbols = collect_sass_symbol_facts_from_sink(sink);
467 let sass_includes = collect_sass_include_facts_from_sink(sink);
468 let sass_module_edges = collect_sass_module_edge_facts_from_sink(sink);
469 let sass_placeholder_definitions = collect_sass_placeholder_definition_facts_from_sink(sink);
470 let extend_targets = collect_extend_target_facts_from_sink(sink);
471 let animations = collect_animation_facts_from_sink(sink);
472 let css_module_values = collect_css_module_value_facts_from_sink(sink);
473 let css_module_value_import_edges = collect_css_module_value_import_edge_facts_from_sink(sink);
474 let css_module_value_definition_edges =
475 collect_css_module_value_definition_edge_facts_from_sink(sink);
476 let css_module_composes = collect_css_module_composes_facts_from_sink(sink);
477 let css_module_composes_edges = collect_css_module_composes_edge_facts_from_sink(sink);
478 let icss = collect_icss_facts_from_sink(sink);
479 let icss_import_edges = collect_icss_import_edge_facts_from_sink(sink);
480 let icss_export_edges = collect_icss_export_edge_facts_from_sink(sink);
481 let at_rules = collect_at_rule_facts_from_sink(sink);
482
483 ParsedStyleFacts {
484 product: "omena-parser.style-facts",
485 dialect: sink.dialect(),
486 selector_count: selectors.len(),
487 selectors,
488 variable_count: variables.len(),
489 variables,
490 sass_symbol_count: sass_symbols.len(),
491 sass_symbols,
492 sass_include_count: sass_includes.len(),
493 sass_includes,
494 sass_module_edge_count: sass_module_edges.len(),
495 sass_module_edges,
496 sass_placeholder_definition_count: sass_placeholder_definitions.len(),
497 sass_placeholder_definitions,
498 extend_target_count: extend_targets.len(),
499 extend_targets,
500 animation_count: animations.len(),
501 animations,
502 css_module_value_count: css_module_values.len(),
503 css_module_values,
504 css_module_value_import_edge_count: css_module_value_import_edges.len(),
505 css_module_value_import_edges,
506 css_module_value_definition_edge_count: css_module_value_definition_edges.len(),
507 css_module_value_definition_edges,
508 css_module_composes_count: css_module_composes.len(),
509 css_module_composes,
510 css_module_composes_edge_count: css_module_composes_edges.len(),
511 css_module_composes_edges,
512 icss_count: icss.len(),
513 icss,
514 icss_import_edge_count: icss_import_edges.len(),
515 icss_import_edges,
516 icss_export_edge_count: icss_export_edges.len(),
517 icss_export_edges,
518 at_rule_count: at_rules.len(),
519 at_rules,
520 error_count: sink.error_count(),
521 }
522}
523
524pub(crate) fn product_facts_from_cst(text: &str, parsed: &ParseResult) -> ParsedStyleFacts {
525 ProductFacts::from(facts_from_cst(text, parsed)).into()
526}
527
528#[cfg(test)]
529mod product_facts_authority_tests;
530
531#[cfg(test)]
532pub(crate) fn tokens_from_syntax_node<'text>(
533 text: &'text str,
534 parsed: &ParseResult,
535 node: &SyntaxNode<SyntaxKind>,
536) -> Vec<Token<'text>> {
537 let node_range = node.text_range();
538 let tokens = parsed.syntax_token_views();
539 let start_index = tokens.partition_point(|token| token.range.start() < node_range.start());
540 let end_index = tokens[start_index..]
541 .partition_point(|token| token.range.start() < node_range.end())
542 + start_index;
543 tokens[start_index..end_index]
544 .iter()
545 .filter(|token| token.range.end() <= node_range.end())
546 .map(|token| {
547 let range = token.range;
548 let start = u32::from(range.start()) as usize;
549 let end = u32::from(range.end()) as usize;
550 Token {
551 kind: token.kind,
552 text: text.get(start..end).unwrap_or_default(),
553 range,
554 }
555 })
556 .collect()
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562 use crate::{StyleDialect, parse};
563
564 const FULL_FACT_IDENTITY_FIXTURE: &str = r#"@use "./tokens" as t;
565@forward "./theme";
566@import "./legacy";
567$brand: red !default;
568@mixin paint($tone: red) { @content; color: $tone; }
569@function scale($value) { @return $value; }
570@value spacing: 1rem;
571@value gap: spacing;
572@value remoteTone as tone from "./tokens.css";
573:import("./dep.css") { localName: remoteName; }
574:export { exported: localName; }
575%shared { color: $brand; }
576#app, div[data-state="open"]::before { --accent: blue; }
577.button {
578 @include paint($brand) { color: scale(1); }
579 @extend %shared !optional;
580 composes: base global(shell) from "./base.css";
581 animation: pulse 1s ease;
582 padding: gap;
583}
584@keyframes pulse { from { opacity: 0; } to { opacity: 1; } }
585"#;
586
587 fn tokens_from_syntax_node_linear<'text>(
588 text: &'text str,
589 parsed: &ParseResult,
590 node: &SyntaxNode<SyntaxKind>,
591 ) -> Vec<Token<'text>> {
592 let node_range = node.text_range();
593 parsed
594 .syntax_token_views()
595 .iter()
596 .filter(|token| token.range.start() >= node_range.start())
597 .filter(|token| token.range.end() <= node_range.end())
598 .map(|token| {
599 let range = token.range;
600 let start = u32::from(range.start()) as usize;
601 let end = u32::from(range.end()) as usize;
602 Token {
603 kind: token.kind,
604 text: text.get(start..end).unwrap_or_default(),
605 range,
606 }
607 })
608 .collect()
609 }
610
611 #[test]
612 fn tokens_from_syntax_node_matches_linear_scan_order() {
613 let text = r#"@use "./tokens" as t;
614:export { exported: local; }
615.button, :global(.card) {
616 --gap: 1rem;
617 color: var(--brand);
618 &__icon { composes: icon from "./icons.module.css"; }
619}
620@media (width >= 1px) {
621 .button--primary { color: t.$brand; }
622}"#;
623 let parsed = parse(text, StyleDialect::Scss);
624 let syntax = parsed.syntax();
625
626 for node in syntax.descendants() {
627 assert_eq!(
628 tokens_from_syntax_node(text, &parsed, node),
629 tokens_from_syntax_node_linear(text, &parsed, node),
630 "token slice drift for {:?} at {:?}",
631 node.kind(),
632 node.text_range()
633 );
634 }
635 }
636
637 #[test]
638 fn combined_fact_sink_preserves_the_full_pre_change_fact_bytes() {
639 let (collection, instrumentation) =
640 crate::instrumentation::with_omena_parser_fact_collection_instrumentation(|| {
641 crate::collect_style_fact_collection(FULL_FACT_IDENTITY_FIXTURE, StyleDialect::Scss)
642 });
643
644 assert_eq!(instrumentation.traversal_entry_count, 1);
645 assert_eq!(instrumentation.families, ["style-fact-sink"]);
646 assert_eq!(instrumentation.registered_family_count, 18);
647 assert_eq!(instrumentation.registered_families, STYLE_FACT_FAMILIES);
648
649 let facts = &collection.facts;
650 assert!(
651 facts.selector_count > 0,
652 "selectors handler produced no rows"
653 );
654 assert!(
655 facts.variable_count > 0,
656 "variables handler produced no rows"
657 );
658 assert!(
659 facts.sass_symbol_count > 0,
660 "sass-symbols handler produced no rows"
661 );
662 assert!(
663 facts.sass_include_count > 0,
664 "sass-includes handler produced no rows"
665 );
666 assert!(
667 facts.sass_module_edge_count > 0,
668 "sass-module-edges handler produced no rows"
669 );
670 assert!(
671 facts.sass_placeholder_definition_count > 0,
672 "sass-placeholder-definitions handler produced no rows"
673 );
674 assert!(
675 facts.extend_target_count > 0,
676 "extend-targets handler produced no rows"
677 );
678 assert!(
679 facts.animation_count > 0,
680 "animations handler produced no rows"
681 );
682 assert!(
683 facts.css_module_value_count > 0,
684 "css-module-values handler produced no rows"
685 );
686 assert!(
687 facts.css_module_value_import_edge_count > 0,
688 "css-module-value-import-edges handler produced no rows"
689 );
690 assert!(
691 facts.css_module_value_definition_edge_count > 0,
692 "css-module-value-definition-edges handler produced no rows"
693 );
694 assert!(
695 facts.css_module_composes_count > 0,
696 "css-module-composes handler produced no rows"
697 );
698 assert!(
699 facts.css_module_composes_edge_count > 0,
700 "css-module-composes-edges handler produced no rows"
701 );
702 assert!(facts.icss_count > 0, "icss handler produced no rows");
703 assert!(
704 facts.icss_import_edge_count > 0,
705 "icss-import-edges handler produced no rows"
706 );
707 assert!(
708 facts.icss_export_edge_count > 0,
709 "icss-export-edges handler produced no rows"
710 );
711 assert!(facts.at_rule_count > 0, "at-rules handler produced no rows");
712 assert!(
713 !collection.emission_selectors.selectors.is_empty(),
714 "emission-selectors handler produced no rows"
715 );
716
717 let bytes = format!("{collection:#?}");
718 let fingerprint = bytes.bytes().fold(0xcbf29ce484222325_u64, |hash, byte| {
719 (hash ^ u64::from(byte)).wrapping_mul(0x100000001b3)
720 });
721 assert_eq!(fingerprint, 0x60bac74d4ed97b4c);
722 }
723}