Skip to main content

omena_parser/
summaries.rs

1//! Parser boundary and evidence summaries.
2//!
3//! These V0 records are consumed by check gates, CLIs, and higher-level crates
4//! to prove parser coverage without coupling to private parser internals.
5
6use cstree::Syntax;
7use omena_interner::{
8    NameKind, intern_class_name, intern_css_ident, intern_custom_property_name, intern_file_path,
9    intern_keyframes_name, intern_mixin_name, intern_property_name, intern_selector_key,
10};
11use omena_syntax::{StyleDialect, SyntaxKind, ident::AuthoredPropertyTextV0};
12use serde::Serialize;
13use std::collections::{BTreeMap, BTreeSet};
14
15use crate::value_names::{CSS_COLOR_FUNCTION_NAMES, VALUES_L4_MATH_FUNCTION_NAMES};
16use crate::{
17    BuiltinDialectExtension, ParsedAnimationFactKind, ParsedCssModuleComposesEdgeKind,
18    ParsedCssModuleComposesFactKind, ParsedCssModuleValueFactKind, ParsedIcssFactKind,
19    ParsedSassModuleEdgeFactKind, ParsedSassSymbolFact, ParsedSassSymbolFactKind,
20    ParsedSelectorFactKind, ParsedStyleFacts, ParsedVariableFactKind, ParsedVariableFactNameV0,
21    SelectorBranch, Token, collect_class_selector_names_from_header, collect_style_facts,
22    css_module_block_scope_marker_in_header, css_module_value_statement_end,
23    declaration_colon_index, find_block_after_header, find_selector_block_after_header, lex,
24    matches_ignore_ascii_case, matching_right_brace, next_non_trivia_token_index_until, parse,
25    previous_non_trivia_token_index, resolve_selector_header, skip_statement_or_unmatched_boundary,
26    skip_trivia_tokens, split_selector_groups, style_wrapper_at_rule, tokenize,
27};
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct ParserBoundarySummary {
31    pub product: &'static str,
32    pub tree_model: &'static str,
33    pub parser_track: &'static str,
34    pub dialect_count: usize,
35    pub shared_name_kind_count: usize,
36    pub ready_surfaces: Vec<&'static str>,
37    pub not_ready_surfaces: Vec<&'static str>,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct ParserSemanticNameConsumptionSummaryV0 {
42    pub product: &'static str,
43    pub dialect: StyleDialect,
44    pub semantic_name_count: usize,
45    pub interned_name_count: usize,
46    pub invalid_name_count: usize,
47    pub class_name_count: usize,
48    pub css_ident_count: usize,
49    pub property_name_count: usize,
50    pub selector_key_count: usize,
51    pub custom_property_name_count: usize,
52    pub keyframes_name_count: usize,
53    pub mixin_name_count: usize,
54    pub file_path_count: usize,
55    pub ready_surfaces: Vec<&'static str>,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct ParserCstEquivalenceSummaryV0 {
60    pub product: &'static str,
61    pub dialect: StyleDialect,
62    pub root_kind: SyntaxKind,
63    pub parser_node_count: usize,
64    pub parser_token_count: usize,
65    pub typed_wrapper_count: usize,
66    pub source_text_round_trip_ready: bool,
67    pub syntax_kind_round_trip_ready: bool,
68    pub zero_unknown_kind_ready: bool,
69    pub typed_cst_wrapper_ready: bool,
70    pub ready_surfaces: Vec<&'static str>,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct ParserPrattValueCoverageSummaryV0 {
75    pub product: &'static str,
76    pub infix_operator_kinds: Vec<SyntaxKind>,
77    pub prefix_operator_kinds: Vec<SyntaxKind>,
78    pub value_expression_node_kinds: Vec<SyntaxKind>,
79    pub specialized_function_family_count: usize,
80    pub css_values_l4_math_function_count: usize,
81    pub css_color_function_count: usize,
82    pub ready_surfaces: Vec<&'static str>,
83    pub next_surfaces: Vec<&'static str>,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct ParserRecursiveDescentCoverageSummaryV0 {
88    pub product: &'static str,
89    pub dialect_count: usize,
90    pub entry_point_count: usize,
91    pub selector_surface_count: usize,
92    pub at_rule_surface_count: usize,
93    pub dialect_extension_surface_count: usize,
94    pub recovery_surface_count: usize,
95    pub ready_surfaces: Vec<&'static str>,
96    pub next_surfaces: Vec<&'static str>,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub(crate) struct ParserSemanticNameCandidateV0 {
101    pub(crate) kind: NameKind,
102    pub(crate) text: String,
103}
104
105#[derive(Debug, Clone, Serialize)]
106#[serde(rename_all = "camelCase")]
107pub struct OmenaParserStyleFactsSummaryV0 {
108    pub schema_version: &'static str,
109    pub product: &'static str,
110    pub dialect: &'static str,
111    pub class_selector_names: Vec<String>,
112    pub id_selector_names: Vec<String>,
113    pub placeholder_selector_names: Vec<String>,
114    pub keyframe_names: Vec<String>,
115    pub animation_reference_names: Vec<String>,
116    pub css_module_value_definition_names: Vec<String>,
117    pub css_module_value_reference_names: Vec<String>,
118    pub css_module_value_import_sources: Vec<String>,
119    pub css_module_value_import_edges: Vec<OmenaParserCssModuleValueImportEdgeFactV0>,
120    pub css_module_value_definition_edges: Vec<OmenaParserCssModuleValueDefinitionEdgeFactV0>,
121    pub css_module_composes_target_names: Vec<String>,
122    pub css_module_composes_import_sources: Vec<String>,
123    pub css_module_composes_edges: Vec<OmenaParserCssModuleComposesEdgeFactV0>,
124    pub icss_export_names: Vec<String>,
125    pub icss_import_local_names: Vec<String>,
126    pub icss_import_remote_names: Vec<String>,
127    pub icss_import_sources: Vec<String>,
128    pub icss_import_edges: Vec<OmenaParserIcssImportEdgeFactV0>,
129    pub icss_export_edges: Vec<OmenaParserIcssExportEdgeFactV0>,
130    pub variable_names: Vec<String>,
131    pub sass_symbol_declaration_names: Vec<String>,
132    pub sass_symbol_reference_names: Vec<String>,
133    pub sass_symbol_facts: Vec<OmenaParserSassSymbolFactV0>,
134    pub sass_symbol_resolution: OmenaParserSassSymbolResolutionV0,
135    pub sass_module_use_sources: Vec<String>,
136    pub sass_module_forward_sources: Vec<String>,
137    pub sass_module_import_sources: Vec<String>,
138    pub sass_module_edges: Vec<OmenaParserSassModuleEdgeFactV0>,
139    pub custom_property_names: Vec<AuthoredPropertyTextV0>,
140    pub custom_property_decl_names: Vec<AuthoredPropertyTextV0>,
141    pub custom_property_ref_names: Vec<AuthoredPropertyTextV0>,
142    pub at_rule_names: Vec<String>,
143    pub parser_error_count: usize,
144}
145
146fn authored_custom_property_sequences_same(
147    left: &[AuthoredPropertyTextV0],
148    right: &[AuthoredPropertyTextV0],
149) -> bool {
150    left.len() == right.len()
151        && left
152            .iter()
153            .zip(right)
154            .all(|(left, right)| left.to_custom_key() == right.to_custom_key())
155}
156
157impl PartialEq for OmenaParserStyleFactsSummaryV0 {
158    fn eq(&self, other: &Self) -> bool {
159        self.schema_version == other.schema_version
160            && self.product == other.product
161            && self.dialect == other.dialect
162            && self.class_selector_names == other.class_selector_names
163            && self.id_selector_names == other.id_selector_names
164            && self.placeholder_selector_names == other.placeholder_selector_names
165            && self.keyframe_names == other.keyframe_names
166            && self.animation_reference_names == other.animation_reference_names
167            && self.css_module_value_definition_names == other.css_module_value_definition_names
168            && self.css_module_value_reference_names == other.css_module_value_reference_names
169            && self.css_module_value_import_sources == other.css_module_value_import_sources
170            && self.css_module_value_import_edges == other.css_module_value_import_edges
171            && self.css_module_value_definition_edges == other.css_module_value_definition_edges
172            && self.css_module_composes_target_names == other.css_module_composes_target_names
173            && self.css_module_composes_import_sources == other.css_module_composes_import_sources
174            && self.css_module_composes_edges == other.css_module_composes_edges
175            && self.icss_export_names == other.icss_export_names
176            && self.icss_import_local_names == other.icss_import_local_names
177            && self.icss_import_remote_names == other.icss_import_remote_names
178            && self.icss_import_sources == other.icss_import_sources
179            && self.icss_import_edges == other.icss_import_edges
180            && self.icss_export_edges == other.icss_export_edges
181            && self.variable_names == other.variable_names
182            && self.sass_symbol_declaration_names == other.sass_symbol_declaration_names
183            && self.sass_symbol_reference_names == other.sass_symbol_reference_names
184            && self.sass_symbol_facts == other.sass_symbol_facts
185            && self.sass_symbol_resolution == other.sass_symbol_resolution
186            && self.sass_module_use_sources == other.sass_module_use_sources
187            && self.sass_module_forward_sources == other.sass_module_forward_sources
188            && self.sass_module_import_sources == other.sass_module_import_sources
189            && self.sass_module_edges == other.sass_module_edges
190            && authored_custom_property_sequences_same(
191                &self.custom_property_names,
192                &other.custom_property_names,
193            )
194            && authored_custom_property_sequences_same(
195                &self.custom_property_decl_names,
196                &other.custom_property_decl_names,
197            )
198            && authored_custom_property_sequences_same(
199                &self.custom_property_ref_names,
200                &other.custom_property_ref_names,
201            )
202            && self.at_rule_names == other.at_rule_names
203            && self.parser_error_count == other.parser_error_count
204    }
205}
206
207impl Eq for OmenaParserStyleFactsSummaryV0 {}
208
209#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
210#[serde(rename_all = "camelCase")]
211pub struct OmenaParserCssModuleValueImportEdgeFactV0 {
212    pub remote_name: String,
213    pub local_name: String,
214    pub import_source: String,
215}
216
217#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
218#[serde(rename_all = "camelCase")]
219pub struct OmenaParserCssModuleValueDefinitionEdgeFactV0 {
220    pub definition_name: String,
221    pub reference_names: Vec<String>,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
225#[serde(rename_all = "camelCase")]
226pub struct OmenaParserCssModuleComposesEdgeFactV0 {
227    pub kind: &'static str,
228    pub owner_selector_names: Vec<String>,
229    pub target_names: Vec<String>,
230    pub import_source: Option<String>,
231}
232
233#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
234#[serde(rename_all = "camelCase")]
235pub struct OmenaParserIcssImportEdgeFactV0 {
236    pub local_name: String,
237    pub remote_name: String,
238    pub import_source: String,
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
242#[serde(rename_all = "camelCase")]
243pub struct OmenaParserIcssExportEdgeFactV0 {
244    pub export_name: String,
245    pub reference_names: Vec<String>,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
249#[serde(rename_all = "camelCase")]
250pub struct OmenaParserSassSymbolFactV0 {
251    pub kind: &'static str,
252    pub symbol_kind: &'static str,
253    pub name: String,
254    pub role: &'static str,
255    pub namespace: Option<String>,
256}
257
258#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
259#[serde(rename_all = "camelCase")]
260pub struct OmenaParserSassModuleEdgeFactV0 {
261    pub kind: &'static str,
262    pub source: String,
263    pub namespace_kind: Option<&'static str>,
264    pub namespace: Option<String>,
265    pub visibility_filter_kind: Option<&'static str>,
266    pub visibility_filter_names: Vec<String>,
267}
268
269#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
270#[serde(rename_all = "camelCase")]
271pub struct OmenaParserSassSymbolResolutionV0 {
272    pub schema_version: &'static str,
273    pub product: &'static str,
274    pub resolution_scope: &'static str,
275    pub declaration_count: usize,
276    pub reference_count: usize,
277    pub resolved_reference_count: usize,
278    pub unresolved_reference_count: usize,
279    pub edges: Vec<OmenaParserSassSymbolResolutionEdgeV0>,
280    pub capabilities: OmenaParserSassSymbolResolutionCapabilitiesV0,
281}
282
283#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
284#[serde(rename_all = "camelCase")]
285pub struct OmenaParserSassSymbolResolutionEdgeV0 {
286    pub symbol_kind: &'static str,
287    pub name: String,
288    pub namespace: Option<String>,
289    pub reference_kind: &'static str,
290    pub reference_role: &'static str,
291    pub reference_source_order: usize,
292    pub declaration_kind: Option<&'static str>,
293    pub declaration_source_order: Option<usize>,
294    pub status: &'static str,
295}
296
297#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
298#[serde(rename_all = "camelCase")]
299pub struct OmenaParserSassSymbolResolutionCapabilitiesV0 {
300    pub same_file_lexical_resolution_ready: bool,
301    pub declaration_before_reference_ready: bool,
302    pub unresolved_reference_reporting_ready: bool,
303    pub cross_file_module_resolution_ready: bool,
304}
305
306#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
307#[serde(rename_all = "camelCase")]
308pub struct OmenaParserLexSummaryV0 {
309    pub schema_version: &'static str,
310    pub product: &'static str,
311    pub dialect: &'static str,
312    pub tokens: Vec<OmenaParserLexTokenV0>,
313    pub parser_error_count: usize,
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
317#[serde(rename_all = "camelCase")]
318pub struct OmenaParserLexTokenV0 {
319    pub kind: String,
320    pub text: String,
321    pub start: usize,
322    pub end: usize,
323}
324
325#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
326#[serde(rename_all = "camelCase")]
327pub struct OmenaParserParityLiteSummaryV0 {
328    pub schema_version: &'static str,
329    pub language: &'static str,
330    pub selector_names: Vec<String>,
331    pub keyframes_names: Vec<String>,
332    pub value_decl_names: Vec<String>,
333    pub diagnostic_count: usize,
334    pub rule_count: usize,
335    pub declaration_count: usize,
336    pub grouped_selector_count: usize,
337    pub max_nesting_depth: usize,
338    pub at_rule_kind_counts: OmenaParserAtRuleKindCountsV0,
339    pub declaration_kind_counts: OmenaParserDeclarationKindCountsV0,
340}
341
342#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
343#[serde(rename_all = "camelCase")]
344pub struct OmenaParserAtRuleKindCountsV0 {
345    pub media: usize,
346    pub supports: usize,
347    pub layer: usize,
348    pub keyframes: usize,
349    pub value: usize,
350    pub at_root: usize,
351    pub generic: usize,
352}
353
354#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
355#[serde(rename_all = "camelCase")]
356pub struct OmenaParserDeclarationKindCountsV0 {
357    pub composes: usize,
358    pub animation: usize,
359    pub animation_name: usize,
360    pub generic: usize,
361}
362
363pub fn summarize_pratt_value_parser_coverage() -> ParserPrattValueCoverageSummaryV0 {
364    ParserPrattValueCoverageSummaryV0 {
365        product: "omena-parser.pratt-value-coverage",
366        infix_operator_kinds: vec![
367            SyntaxKind::Plus,
368            SyntaxKind::Minus,
369            SyntaxKind::Star,
370            SyntaxKind::Slash,
371            SyntaxKind::Percent,
372        ],
373        prefix_operator_kinds: vec![SyntaxKind::Plus, SyntaxKind::Minus],
374        value_expression_node_kinds: vec![
375            SyntaxKind::UnaryExpression,
376            SyntaxKind::BinaryExpression,
377            SyntaxKind::ParenthesizedExpression,
378            SyntaxKind::FunctionCall,
379            SyntaxKind::FunctionArguments,
380            SyntaxKind::ValueList,
381            SyntaxKind::ComponentValueList,
382            SyntaxKind::SimpleBlock,
383            SyntaxKind::BogusValue,
384        ],
385        specialized_function_family_count: 10,
386        css_values_l4_math_function_count: VALUES_L4_MATH_FUNCTION_NAMES.len(),
387        css_color_function_count: CSS_COLOR_FUNCTION_NAMES.len(),
388        ready_surfaces: vec![
389            "prattValueParserCore",
390            "prefixUnaryExpressions",
391            "additiveMultiplicativePrecedence",
392            "parenthesizedValueExpressions",
393            "functionArgumentValueLists",
394            "specializedCssValueFunctionFamilies",
395            "valuesL4MathFunctionArityChecks",
396            "varEnvAttrFunctionHeadChecks",
397            "dynamicInterpolationEscapeHatches",
398            "valueBogusRecovery",
399        ],
400        next_surfaces: vec!["fullPropertyValueGrammarRegistry"],
401    }
402}
403
404pub fn summarize_recursive_descent_parser_coverage() -> ParserRecursiveDescentCoverageSummaryV0 {
405    ParserRecursiveDescentCoverageSummaryV0 {
406        product: "omena-parser.recursive-descent-coverage",
407        dialect_count: 4,
408        entry_point_count: 10,
409        selector_surface_count: 12,
410        at_rule_surface_count: 19,
411        dialect_extension_surface_count: 17,
412        recovery_surface_count: 8,
413        ready_surfaces: vec![
414            "recursiveDescentParserCore",
415            "stylesheetRuleDeclarationEntryPoints",
416            "selectorsLevelFourCstNodes",
417            "registeredAtRulePreludeParsers",
418            "cssNestingRuleItems",
419            "scssDialectStatements",
420            "sassIndentedBlocks",
421            "lessDialectStatements",
422            "bogusRecoverySkeleton",
423            "styleFactExtractionSurface",
424        ],
425        next_surfaces: vec!["completeExternalSpecMirror"],
426    }
427}
428
429pub fn summarize_parser_boundary() -> ParserBoundarySummary {
430    ParserBoundarySummary {
431        product: "omena-parser.boundary",
432        tree_model: "cstree-green-root",
433        parser_track: "greenFieldNextToEngineStyleParser",
434        dialect_count: 4,
435        shared_name_kind_count: NameKind::ALL.len(),
436        ready_surfaces: vec![
437            "lexResult",
438            "lexedTokenTextSurface",
439            "parseResult",
440            "panicFreeTokenizer",
441            "cstreeGreenBuilder",
442            "tokenSetRecoveryScaffold",
443            "dialectExtensionScaffold",
444            "recursiveDescentParserCore",
445            "recursiveDescentCoverageSummary",
446            "selectorCstSkeleton",
447            "atRuleRegistrySkeleton",
448            "prattValueExpressionSkeleton",
449            "prattValueParserCore",
450            "prattValueCoverageSummary",
451            "attributeMatcherTokenization",
452            "attributeMatcherCstNodes",
453            "attributeNameValueModifierCstNodes",
454            "specializedValueFunctionCstNodes",
455            "caseInsensitiveFunctionRegistry",
456            "caseInsensitiveAtRuleRegistry",
457            "valueAtomCstNodes",
458            "identifierValueCstNodes",
459            "stringValueCstNodes",
460            "unicodeRangeValueCstNodes",
461            "functionArgumentValueLists",
462            "cssModuleScopeFunctionCstNodes",
463            "cssModuleGlobalSelectorFactFiltering",
464            "cssModuleLocalIdSelectorFacts",
465            "cssModuleValueStyleFacts",
466            "cssModuleValueDeclarationReferenceFacts",
467            "cssModuleComposesStyleFacts",
468            "icssStyleFacts",
469            "animationNameStyleFacts",
470            "animationShorthandStyleFacts",
471            "scssStructuredBlockAtRules",
472            "scssControlPreludeValidation",
473            "scssControlStyleFactExtraction",
474            "scssIncludeContentBlockStyleFacts",
475            "scssSassModuleEdgeStyleFacts",
476            "scssSassSymbolStyleFacts",
477            "scssUtilityAtRules",
478            "scssVariableFlagCstNodes",
479            "scssNestedPropertyCstNodes",
480            "scssModulePreludeSourceValidation",
481            "scssModulePreludeClauseValidation",
482            "scssModuleConfigCstNodes",
483            "scssModuleConfigBogusRecovery",
484            "scssPlaceholderSelectorCstNodes",
485            "lessMixinDeclarationCstNodes",
486            "lessMixinCallCstNodes",
487            "lessMixinGuardCstNodes",
488            "lessExtendPseudoCstNodes",
489            "lessDetachedRulesetCstNodes",
490            "lessNamespaceAccessCstNodes",
491            "lessPropertyVariableTokenization",
492            "lessPropertyVariableCstNodes",
493            "lessEscapedStringTokenization",
494            "lessEscapedStringValueCstNodes",
495            "importantAnnotationTokenization",
496            "urlTokenization",
497            "urlValueCstNodes",
498            "quotedUrlFunctionValueCstNodes",
499            "conditionalAtRulePreludeCstNodes",
500            "supportsAtRulePreludeValidation",
501            "conditionalLevel5AtRuleCstNodes",
502            "mediaQueryCstNodes",
503            "mediaQueryListValidation",
504            "importPreludeCstNodes",
505            "importSourcePreludeValidation",
506            "importTailPreludeValidation",
507            "customMediaPreludeValidation",
508            "propertyAtRuleNameValidation",
509            "namedAtRulePreludeValidation",
510            "containerAtRulePreludeValidation",
511            "charsetNamespaceAtRulePreludeValidation",
512            "keyframesAtRuleNameValidation",
513            "emptyBlockAtRulePreludeValidation",
514            "layerScopePreludeCstNodes",
515            "layerAtRulePreludeValidation",
516            "scopeAtRulePreludeValidation",
517            "pageAtRulePreludeValidation",
518            "pageMarginAtRuleCstNodes",
519            "modernDeclarationAtRuleCstNodes",
520            "fontFeatureValuesAtRuleCstNodes",
521            "fontFeatureValuesPreludeValidation",
522            "keyframeSelectorListValidation",
523            "viewTransitionAtRuleCstNodes",
524            "genericAtRulePreludeCstNodes",
525            "bogusAtRulePreludeCstNodes",
526            "nestingAtRuleCstNodes",
527            "customMediaAtRuleCstNodes",
528            "cssColorFunctionCstNodes",
529            "colorFunctionArgumentChecks",
530            "gradientFunctionCstNodes",
531            "transformFunctionCstNodes",
532            "filterFunctionCstNodes",
533            "imageFunctionCstNodes",
534            "shapeFunctionCstNodes",
535            "envAttrFunctionCstNodes",
536            "mathFunctionCstNodes",
537            "mathFunctionArityChecks",
538            "mathFunctionEmptyArgumentChecks",
539            "varEnvAttrFunctionHeadChecks",
540            "scssInterpolationTokenization",
541            "scssInterpolationCstNodes",
542            "lessInterpolationTokenization",
543            "lessInterpolationCstNodes",
544            "interpolationBogusRecovery",
545            "unicodeRangeTokenization",
546            "badStringTokenRecovery",
547            "badStringValueBogusNodes",
548            "emptyDeclarationValueRecovery",
549            "emptyVariableValueRecovery",
550            "missingSemicolonDeclarationRecovery",
551            "coreBogusPopulationSlice",
552            "dialectBogusPopulationSlice",
553            "cssModuleValueCstNodes",
554            "cssModuleComposesCstNodes",
555            "icssModuleBlockCstNodes",
556            "icssImportSourceValidation",
557            "cssModuleFromClauseSourceValidation",
558            "cssModuleComposesMultipleFromValidation",
559            "cssModuleGlobalComposesValidation",
560            "cssModuleBogusRecovery",
561            "valueListCstNodes",
562            "valueListBogusRecovery",
563            "genericRecoveryBogusNodes",
564            "sassIndentedTokenization",
565            "sassIndentedBlockCstNodes",
566            "sassIndentedStyleFacts",
567            "differentialCorpusSeed",
568            "differentialCorpus",
569            "lightningCssDifferentialCorpusSlice",
570            "lightningCssSelectorIdAndAtRuleDifferentialSlice",
571            "midTypingNoPanicPropertySlice",
572            "deterministicPanicFreeCorpus",
573            "losslessCstTextRoundTripSmoke",
574            "parseResultSourceTextSurface",
575            "parseSourceParseRoundTripSmoke",
576            "typedNumericValueAtomCstNodes",
577            "bracketedValueCstNodes",
578            "importantAnnotationCstNodes",
579            "splitImportantAnnotationCstNodes",
580            "unexpectedValueTokenBogusNodes",
581            "cdoCdcTokenization",
582            "cssIdentifierEscapeTokenization",
583            "nullAndBomInputPreprocessingSlice",
584            "hashDelimiterTokenization",
585            "cssDashIdentTokenization",
586            "signedNumericTokenization",
587            "exponentNumericTokenization",
588            "badUrlWhitespaceRecovery",
589            "parserEntryPointApiSlice",
590            "ruleListEntryPointApiSlice",
591            "componentValueEntryPointApiSlice",
592            "componentValueListEntryPointApiSlice",
593            "commaSeparatedComponentValueListEntryPointApiSlice",
594            "simpleBlockEntryPointApiSlice",
595            "typedCstWrapperSlice",
596            "parserCstEquivalence",
597            "typedBogusCstWrapperSlice",
598            "componentValueCstNodes",
599            "simpleBlockCstNodes",
600            "fullBogusPopulation",
601            "componentValueListCstNodes",
602            "commaSeparatedComponentValueListCstNodes",
603            "customPropertyAnyValueComponentList",
604            "customPropertyValueCstNodes",
605            "functionalPseudoSelectorListCstNodes",
606            "strictNotPseudoSelectorListCstNodes",
607            "nthSelectorOfSelectorListCstNodes",
608            "nthSelectorFormulaCstNodes",
609            "hasRelativeSelectorListCstNodes",
610            "langDirSelectorArgumentCstNodes",
611            "namespaceQualifiedSelectorCstNodes",
612            "selectorFunctionArgumentFactExclusion",
613            "missingBlockCloseBogusTrivia",
614            "initialDialectStatementNodes",
615            "recoveryBogusSkeleton",
616            "styleFactExtractionSurface",
617            "parserSemanticNameConsumption",
618            "productCutoverGate",
619        ],
620        not_ready_surfaces: vec![
621            "completeExternalSpecMirror",
622            "fullPropertyValueGrammarRegistry",
623        ],
624    }
625}
626
627pub fn summarize_omena_parser_style_facts(
628    style_source: &str,
629    dialect: StyleDialect,
630) -> OmenaParserStyleFactsSummaryV0 {
631    let facts = collect_style_facts(style_source, dialect);
632    let sass_symbol_resolution = summarize_omena_parser_sass_symbol_resolution(&facts.sass_symbols);
633    let mut class_selector_names = Vec::new();
634    let mut id_selector_names = Vec::new();
635    let mut placeholder_selector_names = Vec::new();
636    let mut keyframe_names = Vec::new();
637    let mut animation_reference_names = Vec::new();
638    let mut css_module_value_definition_names = BTreeSet::new();
639    let mut css_module_value_reference_names = BTreeSet::new();
640    let mut css_module_value_import_sources = BTreeSet::new();
641    let mut css_module_composes_target_names = BTreeSet::new();
642    let mut css_module_composes_import_sources = BTreeSet::new();
643    let mut icss_export_names = BTreeSet::new();
644    let mut icss_import_local_names = BTreeSet::new();
645    let mut icss_import_remote_names = BTreeSet::new();
646    let mut icss_import_sources = BTreeSet::new();
647    let mut variable_names = BTreeSet::new();
648    let mut sass_symbol_declaration_names = BTreeSet::new();
649    let mut sass_symbol_reference_names = BTreeSet::new();
650    let mut sass_module_use_sources = BTreeSet::new();
651    let mut sass_module_forward_sources = BTreeSet::new();
652    let mut sass_module_import_sources = BTreeSet::new();
653    let mut custom_property_names = BTreeMap::new();
654    let mut custom_property_decl_names = BTreeMap::new();
655    let mut custom_property_ref_names = BTreeMap::new();
656
657    for selector in facts.selectors {
658        match selector.kind {
659            ParsedSelectorFactKind::Class => class_selector_names.push(selector.name),
660            ParsedSelectorFactKind::Id => id_selector_names.push(selector.name),
661            ParsedSelectorFactKind::Placeholder => placeholder_selector_names.push(selector.name),
662        }
663    }
664
665    for variable in facts.variables {
666        match variable.kind {
667            ParsedVariableFactKind::ScssDeclaration
668            | ParsedVariableFactKind::ScssReference
669            | ParsedVariableFactKind::LessDeclaration
670            | ParsedVariableFactKind::LessReference => {
671                let ParsedVariableFactNameV0::NonProperty(name) = variable.name else {
672                    continue;
673                };
674                variable_names.insert(name);
675            }
676            ParsedVariableFactKind::CustomPropertyDeclaration
677            | ParsedVariableFactKind::CustomPropertyReference => {
678                let Some(property_key) = variable.property_key else {
679                    continue;
680                };
681                let ParsedVariableFactNameV0::CustomProperty(name) = variable.name else {
682                    continue;
683                };
684                custom_property_names
685                    .entry(property_key.clone())
686                    .or_insert_with(|| name.clone());
687                match variable.kind {
688                    ParsedVariableFactKind::CustomPropertyDeclaration => {
689                        custom_property_decl_names
690                            .entry(property_key)
691                            .or_insert_with(|| name.clone());
692                    }
693                    ParsedVariableFactKind::CustomPropertyReference => {
694                        custom_property_ref_names
695                            .entry(property_key)
696                            .or_insert(name);
697                    }
698                    _ => {}
699                }
700            }
701        }
702    }
703
704    for symbol in &facts.sass_symbols {
705        match symbol.role {
706            "declaration" => {
707                sass_symbol_declaration_names.insert(symbol.name.clone());
708            }
709            _ => {
710                sass_symbol_reference_names.insert(symbol.name.clone());
711            }
712        }
713    }
714
715    for edge in &facts.sass_module_edges {
716        match edge.kind {
717            ParsedSassModuleEdgeFactKind::Use => {
718                sass_module_use_sources.insert(edge.source.clone());
719            }
720            ParsedSassModuleEdgeFactKind::Forward => {
721                sass_module_forward_sources.insert(edge.source.clone());
722            }
723            ParsedSassModuleEdgeFactKind::Import => {
724                sass_module_import_sources.insert(edge.source.clone());
725            }
726        }
727    }
728
729    for animation in facts.animations {
730        match animation.kind {
731            ParsedAnimationFactKind::KeyframesDeclaration => keyframe_names.push(animation.name),
732            ParsedAnimationFactKind::AnimationNameReference => {
733                animation_reference_names.push(animation.name);
734            }
735        }
736    }
737
738    for value in facts.css_module_values {
739        match value.kind {
740            ParsedCssModuleValueFactKind::Definition => {
741                css_module_value_definition_names.insert(value.name);
742            }
743            ParsedCssModuleValueFactKind::Reference => {
744                css_module_value_reference_names.insert(value.name);
745            }
746            ParsedCssModuleValueFactKind::ImportSource => {
747                css_module_value_import_sources.insert(value.name);
748            }
749        }
750    }
751
752    for composes in facts.css_module_composes {
753        match composes.kind {
754            ParsedCssModuleComposesFactKind::Target => {
755                css_module_composes_target_names.insert(composes.name);
756            }
757            ParsedCssModuleComposesFactKind::ImportSource => {
758                css_module_composes_import_sources.insert(composes.name);
759            }
760        }
761    }
762
763    for icss in facts.icss {
764        match icss.kind {
765            ParsedIcssFactKind::ExportName => {
766                icss_export_names.insert(icss.name);
767            }
768            ParsedIcssFactKind::ImportLocalName => {
769                icss_import_local_names.insert(icss.name);
770            }
771            ParsedIcssFactKind::ImportRemoteName => {
772                icss_import_remote_names.insert(icss.name);
773            }
774            ParsedIcssFactKind::ImportSource => {
775                icss_import_sources.insert(icss.name);
776            }
777        }
778    }
779
780    OmenaParserStyleFactsSummaryV0 {
781        schema_version: "0",
782        product: "omena-parser.style-facts",
783        dialect: style_dialect_label(dialect),
784        class_selector_names,
785        id_selector_names,
786        placeholder_selector_names,
787        keyframe_names,
788        animation_reference_names,
789        css_module_value_definition_names: css_module_value_definition_names.into_iter().collect(),
790        css_module_value_reference_names: css_module_value_reference_names.into_iter().collect(),
791        css_module_value_import_sources: css_module_value_import_sources.into_iter().collect(),
792        css_module_value_import_edges: facts
793            .css_module_value_import_edges
794            .into_iter()
795            .map(|edge| OmenaParserCssModuleValueImportEdgeFactV0 {
796                remote_name: edge.remote_name,
797                local_name: edge.local_name,
798                import_source: edge.import_source,
799            })
800            .collect(),
801        css_module_value_definition_edges: facts
802            .css_module_value_definition_edges
803            .into_iter()
804            .map(|edge| OmenaParserCssModuleValueDefinitionEdgeFactV0 {
805                definition_name: edge.definition_name,
806                reference_names: edge.reference_names,
807            })
808            .collect(),
809        css_module_composes_target_names: css_module_composes_target_names.into_iter().collect(),
810        css_module_composes_import_sources: css_module_composes_import_sources
811            .into_iter()
812            .collect(),
813        css_module_composes_edges: facts
814            .css_module_composes_edges
815            .into_iter()
816            .map(|edge| OmenaParserCssModuleComposesEdgeFactV0 {
817                kind: css_module_composes_edge_kind_label(edge.kind),
818                owner_selector_names: edge.owner_selector_names,
819                target_names: edge.target_names,
820                import_source: edge.import_source,
821            })
822            .collect(),
823        icss_export_names: icss_export_names.into_iter().collect(),
824        icss_import_local_names: icss_import_local_names.into_iter().collect(),
825        icss_import_remote_names: icss_import_remote_names.into_iter().collect(),
826        icss_import_sources: icss_import_sources.into_iter().collect(),
827        icss_import_edges: facts
828            .icss_import_edges
829            .into_iter()
830            .map(|edge| OmenaParserIcssImportEdgeFactV0 {
831                local_name: edge.local_name,
832                remote_name: edge.remote_name,
833                import_source: edge.import_source,
834            })
835            .collect(),
836        icss_export_edges: facts
837            .icss_export_edges
838            .into_iter()
839            .map(|edge| OmenaParserIcssExportEdgeFactV0 {
840                export_name: edge.export_name,
841                reference_names: edge.reference_names,
842            })
843            .collect(),
844        variable_names: variable_names.into_iter().collect(),
845        sass_symbol_declaration_names: sass_symbol_declaration_names.into_iter().collect(),
846        sass_symbol_reference_names: sass_symbol_reference_names.into_iter().collect(),
847        sass_symbol_facts: facts
848            .sass_symbols
849            .into_iter()
850            .map(|symbol| OmenaParserSassSymbolFactV0 {
851                kind: sass_symbol_fact_kind_label(symbol.kind),
852                symbol_kind: symbol.symbol_kind,
853                name: symbol.name,
854                role: symbol.role,
855                namespace: symbol.namespace,
856            })
857            .collect(),
858        sass_symbol_resolution,
859        sass_module_use_sources: sass_module_use_sources.into_iter().collect(),
860        sass_module_forward_sources: sass_module_forward_sources.into_iter().collect(),
861        sass_module_import_sources: sass_module_import_sources.into_iter().collect(),
862        sass_module_edges: facts
863            .sass_module_edges
864            .into_iter()
865            .map(|edge| OmenaParserSassModuleEdgeFactV0 {
866                kind: sass_module_edge_fact_kind_label(edge.kind),
867                source: edge.source,
868                namespace_kind: edge.namespace_kind,
869                namespace: edge.namespace,
870                visibility_filter_kind: edge.visibility_filter_kind,
871                visibility_filter_names: edge.visibility_filter_names,
872            })
873            .collect(),
874        custom_property_names: custom_property_names.into_values().collect(),
875        custom_property_decl_names: custom_property_decl_names.into_values().collect(),
876        custom_property_ref_names: custom_property_ref_names.into_values().collect(),
877        at_rule_names: facts
878            .at_rules
879            .into_iter()
880            .map(|at_rule| at_rule.name)
881            .collect(),
882        parser_error_count: facts.error_count,
883    }
884}
885
886pub fn summarize_omena_parser_lex(source: &str, dialect: StyleDialect) -> OmenaParserLexSummaryV0 {
887    let result = lex(source, dialect);
888    OmenaParserLexSummaryV0 {
889        schema_version: "0",
890        product: "omena-parser.lex-result",
891        dialect: style_dialect_label(result.dialect()),
892        tokens: result
893            .tokens()
894            .iter()
895            .map(|token| OmenaParserLexTokenV0 {
896                kind: format!("{:?}", token.kind),
897                text: token.text.clone(),
898                start: token.range.start().into(),
899                end: token.range.end().into(),
900            })
901            .collect(),
902        parser_error_count: result.errors().len(),
903    }
904}
905
906pub fn summarize_omena_parser_parity_lite(
907    source: &str,
908    dialect: StyleDialect,
909) -> OmenaParserParityLiteSummaryV0 {
910    let facts = collect_style_facts(source, dialect);
911    let result = parse(source, dialect);
912    let (tokens, _) = tokenize(source, &BuiltinDialectExtension::new(dialect));
913    let mut structural = ParserStructuralSummary::default();
914    summarize_parser_structural_range(&tokens, 0, tokens.len(), 0, &mut structural);
915    let mut selector_names = collect_parity_lite_selector_names_from_tokens(&tokens);
916    selector_names.sort();
917
918    OmenaParserParityLiteSummaryV0 {
919        schema_version: "0",
920        language: style_dialect_label(dialect),
921        selector_names,
922        keyframes_names: sorted_unique(
923            facts
924                .animations
925                .iter()
926                .filter(|animation| animation.kind == ParsedAnimationFactKind::KeyframesDeclaration)
927                .map(|animation| animation.name.clone()),
928        ),
929        value_decl_names: sorted_unique(
930            facts
931                .css_module_values
932                .iter()
933                .filter(|value| value.kind == ParsedCssModuleValueFactKind::Definition)
934                .map(|value| value.name.clone()),
935        ),
936        diagnostic_count: result.errors().len(),
937        rule_count: structural.rule_count,
938        declaration_count: structural.declaration_count,
939        grouped_selector_count: structural.grouped_selector_count,
940        max_nesting_depth: structural.max_nesting_depth,
941        at_rule_kind_counts: structural.at_rule_kind_counts,
942        declaration_kind_counts: structural.declaration_kind_counts,
943    }
944}
945
946fn style_dialect_label(dialect: StyleDialect) -> &'static str {
947    match dialect {
948        StyleDialect::Css => "css",
949        StyleDialect::Scss => "scss",
950        StyleDialect::Sass => "sass",
951        StyleDialect::Less => "less",
952    }
953}
954
955#[derive(Default)]
956struct ParserStructuralSummary {
957    rule_count: usize,
958    declaration_count: usize,
959    grouped_selector_count: usize,
960    max_nesting_depth: usize,
961    at_rule_kind_counts: OmenaParserAtRuleKindCountsV0,
962    declaration_kind_counts: OmenaParserDeclarationKindCountsV0,
963}
964
965fn summarize_parser_structural_range(
966    tokens: &[Token<'_>],
967    start: usize,
968    end: usize,
969    depth: usize,
970    summary: &mut ParserStructuralSummary,
971) {
972    let mut index = start;
973    while index < end {
974        index = skip_trivia_tokens(tokens, index, end);
975        if index >= end {
976            break;
977        }
978
979        if tokens[index].kind == SyntaxKind::AtKeyword {
980            increment_omena_parser_at_rule_kind_count(
981                &mut summary.at_rule_kind_counts,
982                classify_omena_parser_at_rule_kind(tokens[index].text),
983            );
984            let next_depth = depth + 1;
985            summary.max_nesting_depth = summary.max_nesting_depth.max(next_depth);
986            if let Some((open, close)) = find_block_after_header(tokens, index, end) {
987                summarize_parser_structural_range(tokens, open + 1, close, next_depth, summary);
988                index = close + 1;
989            } else {
990                index = skip_statement_or_unmatched_boundary(tokens, index, end);
991            }
992            continue;
993        }
994
995        let statement_end = css_module_value_statement_end(tokens, index);
996        if is_root_less_variable_statement(tokens, index, statement_end.min(end), depth) {
997            increment_omena_parser_at_rule_kind_count(
998                &mut summary.at_rule_kind_counts,
999                keyof_omena_parser_at_rule_kind_counts::Kind::Generic,
1000            );
1001            if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
1002                break;
1003            }
1004            index = statement_end + 1;
1005            continue;
1006        }
1007
1008        if statement_end < end && tokens[statement_end].kind == SyntaxKind::LeftBrace {
1009            summary.rule_count += 1;
1010            let next_depth = depth + 1;
1011            summary.max_nesting_depth = summary.max_nesting_depth.max(next_depth);
1012            let group_count = count_omena_parser_selector_groups(tokens, index, statement_end);
1013            if group_count > 1 {
1014                summary.grouped_selector_count += group_count;
1015            }
1016            if let Some(close) = matching_right_brace(tokens, statement_end, end) {
1017                summarize_parser_structural_range(
1018                    tokens,
1019                    statement_end + 1,
1020                    close,
1021                    next_depth,
1022                    summary,
1023                );
1024                index = close + 1;
1025            } else {
1026                index = statement_end + 1;
1027            }
1028            continue;
1029        }
1030
1031        if let Some(colon_index) = declaration_colon_index(tokens, index, statement_end.min(end)) {
1032            summary.declaration_count += 1;
1033            let property = previous_non_trivia_token_index(tokens, colon_index, index)
1034                .map(|property| tokens[property].text)
1035                .unwrap_or_default();
1036            increment_omena_parser_declaration_kind_count(
1037                &mut summary.declaration_kind_counts,
1038                classify_omena_parser_declaration_kind(property),
1039            );
1040        }
1041
1042        if statement_end >= end || tokens[statement_end].kind == SyntaxKind::RightBrace {
1043            break;
1044        }
1045        index = statement_end + 1;
1046    }
1047}
1048
1049fn is_root_less_variable_statement(
1050    tokens: &[Token<'_>],
1051    start: usize,
1052    end: usize,
1053    depth: usize,
1054) -> bool {
1055    if depth != 0 {
1056        return false;
1057    }
1058    let Some(first) = next_non_trivia_token_index_until(tokens, start, end) else {
1059        return false;
1060    };
1061    tokens[first].kind == SyntaxKind::LessVariable
1062        && declaration_colon_index(tokens, first, end).is_some()
1063}
1064
1065fn count_omena_parser_selector_groups(tokens: &[Token<'_>], start: usize, end: usize) -> usize {
1066    split_selector_groups(tokens, start, end)
1067        .into_iter()
1068        .filter(|(group_start, group_end)| {
1069            *group_start < *group_end
1070                && next_non_trivia_token_index_until(tokens, *group_start, *group_end).is_some()
1071        })
1072        .count()
1073}
1074
1075fn collect_parity_lite_selector_names_from_tokens(tokens: &[Token<'_>]) -> Vec<String> {
1076    let mut names = Vec::new();
1077    collect_parity_lite_selector_names_in_range(tokens, 0, tokens.len(), &[], None, &mut names);
1078    names
1079}
1080
1081fn collect_parity_lite_selector_names_in_range(
1082    tokens: &[Token<'_>],
1083    start: usize,
1084    end: usize,
1085    parent_branches: &[SelectorBranch],
1086    css_module_scope: Option<&'static str>,
1087    names: &mut Vec<String>,
1088) {
1089    let mut index = start;
1090    while index < end {
1091        index = skip_trivia_tokens(tokens, index, end);
1092        if index >= end {
1093            break;
1094        }
1095
1096        if tokens[index].kind == SyntaxKind::AtKeyword {
1097            let block = find_selector_block_after_header(tokens, index, end);
1098            if let Some((open, close)) = block {
1099                if tokens[index].text == "@nest" {
1100                    if css_module_scope == Some("global") {
1101                        collect_parity_lite_selector_names_in_range(
1102                            tokens,
1103                            open + 1,
1104                            close,
1105                            &[],
1106                            css_module_scope,
1107                            names,
1108                        );
1109                    } else {
1110                        let branches =
1111                            resolve_selector_header(tokens, index + 1, open, parent_branches);
1112                        names.extend(branches.iter().map(|branch| branch.name.clone()));
1113                        collect_grouped_ampersand_compound_selector_duplicates(
1114                            tokens,
1115                            index + 1,
1116                            open,
1117                            parent_branches.len(),
1118                            names,
1119                        );
1120                        collect_parity_lite_selector_names_in_range(
1121                            tokens,
1122                            open + 1,
1123                            close,
1124                            &branches,
1125                            css_module_scope,
1126                            names,
1127                        );
1128                    }
1129                } else if style_wrapper_at_rule(tokens[index].text) {
1130                    collect_parity_lite_selector_names_in_range(
1131                        tokens,
1132                        open + 1,
1133                        close,
1134                        parent_branches,
1135                        css_module_scope,
1136                        names,
1137                    );
1138                }
1139                index = close + 1;
1140            } else {
1141                index = skip_statement_or_unmatched_boundary(tokens, index, end);
1142            }
1143            continue;
1144        }
1145
1146        let Some((open, close)) = find_selector_block_after_header(tokens, index, end) else {
1147            index = skip_statement_or_unmatched_boundary(tokens, index, end);
1148            continue;
1149        };
1150
1151        let effective_scope = css_module_scope
1152            .or_else(|| css_module_block_scope_marker_in_header(tokens, index, open));
1153        if effective_scope == Some("global") {
1154            collect_parity_lite_selector_names_in_range(
1155                tokens,
1156                open + 1,
1157                close,
1158                &[],
1159                effective_scope,
1160                names,
1161            );
1162        } else {
1163            let branches = resolve_selector_header(tokens, index, open, parent_branches);
1164            names.extend(branches.iter().map(|branch| branch.name.clone()));
1165            collect_grouped_ampersand_compound_selector_duplicates(
1166                tokens,
1167                index,
1168                open,
1169                parent_branches.len(),
1170                names,
1171            );
1172            collect_parity_lite_selector_names_in_range(
1173                tokens,
1174                open + 1,
1175                close,
1176                &branches,
1177                effective_scope,
1178                names,
1179            );
1180        }
1181        index = close + 1;
1182    }
1183}
1184
1185fn collect_grouped_ampersand_compound_selector_duplicates(
1186    tokens: &[Token<'_>],
1187    start: usize,
1188    end: usize,
1189    parent_branch_count: usize,
1190    names: &mut Vec<String>,
1191) {
1192    if parent_branch_count <= 1 || !header_contains_ampersand(tokens, start, end) {
1193        return;
1194    }
1195    for (name, _) in collect_class_selector_names_from_header(tokens, start, end) {
1196        names.extend(std::iter::repeat_n(name, parent_branch_count - 1));
1197    }
1198}
1199
1200fn header_contains_ampersand(tokens: &[Token<'_>], start: usize, end: usize) -> bool {
1201    tokens[start..end]
1202        .iter()
1203        .any(|token| token.kind == SyntaxKind::Ampersand)
1204}
1205
1206fn classify_omena_parser_at_rule_kind(text: &str) -> keyof_omena_parser_at_rule_kind_counts::Kind {
1207    let name = text.trim_start_matches('@');
1208    if matches_ignore_ascii_case(name, &["media"]) {
1209        keyof_omena_parser_at_rule_kind_counts::Kind::Media
1210    } else if matches_ignore_ascii_case(name, &["supports"]) {
1211        keyof_omena_parser_at_rule_kind_counts::Kind::Supports
1212    } else if matches_ignore_ascii_case(name, &["layer"]) {
1213        keyof_omena_parser_at_rule_kind_counts::Kind::Layer
1214    } else if matches_ignore_ascii_case(name, &["keyframes", "-webkit-keyframes"]) {
1215        keyof_omena_parser_at_rule_kind_counts::Kind::Keyframes
1216    } else if matches_ignore_ascii_case(name, &["value"]) {
1217        keyof_omena_parser_at_rule_kind_counts::Kind::Value
1218    } else if matches_ignore_ascii_case(name, &["at-root"]) {
1219        keyof_omena_parser_at_rule_kind_counts::Kind::AtRoot
1220    } else {
1221        keyof_omena_parser_at_rule_kind_counts::Kind::Generic
1222    }
1223}
1224
1225fn increment_omena_parser_at_rule_kind_count(
1226    counts: &mut OmenaParserAtRuleKindCountsV0,
1227    kind: keyof_omena_parser_at_rule_kind_counts::Kind,
1228) {
1229    match kind {
1230        keyof_omena_parser_at_rule_kind_counts::Kind::Media => counts.media += 1,
1231        keyof_omena_parser_at_rule_kind_counts::Kind::Supports => counts.supports += 1,
1232        keyof_omena_parser_at_rule_kind_counts::Kind::Layer => counts.layer += 1,
1233        keyof_omena_parser_at_rule_kind_counts::Kind::Keyframes => counts.keyframes += 1,
1234        keyof_omena_parser_at_rule_kind_counts::Kind::Value => counts.value += 1,
1235        keyof_omena_parser_at_rule_kind_counts::Kind::AtRoot => counts.at_root += 1,
1236        keyof_omena_parser_at_rule_kind_counts::Kind::Generic => counts.generic += 1,
1237    }
1238}
1239
1240fn classify_omena_parser_declaration_kind(
1241    property: &str,
1242) -> keyof_omena_parser_declaration_kind_counts::Kind {
1243    let property = property.trim();
1244    if matches_ignore_ascii_case(property, &["composes"]) {
1245        keyof_omena_parser_declaration_kind_counts::Kind::Composes
1246    } else if matches_ignore_ascii_case(property, &["animation"]) {
1247        keyof_omena_parser_declaration_kind_counts::Kind::Animation
1248    } else if matches_ignore_ascii_case(property, &["animation-name"]) {
1249        keyof_omena_parser_declaration_kind_counts::Kind::AnimationName
1250    } else {
1251        keyof_omena_parser_declaration_kind_counts::Kind::Generic
1252    }
1253}
1254
1255fn increment_omena_parser_declaration_kind_count(
1256    counts: &mut OmenaParserDeclarationKindCountsV0,
1257    kind: keyof_omena_parser_declaration_kind_counts::Kind,
1258) {
1259    match kind {
1260        keyof_omena_parser_declaration_kind_counts::Kind::Composes => counts.composes += 1,
1261        keyof_omena_parser_declaration_kind_counts::Kind::Animation => counts.animation += 1,
1262        keyof_omena_parser_declaration_kind_counts::Kind::AnimationName => {
1263            counts.animation_name += 1
1264        }
1265        keyof_omena_parser_declaration_kind_counts::Kind::Generic => counts.generic += 1,
1266    }
1267}
1268
1269mod keyof_omena_parser_at_rule_kind_counts {
1270    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1271    pub enum Kind {
1272        Media,
1273        Supports,
1274        Layer,
1275        Keyframes,
1276        Value,
1277        AtRoot,
1278        Generic,
1279    }
1280}
1281
1282mod keyof_omena_parser_declaration_kind_counts {
1283    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1284    pub enum Kind {
1285        Composes,
1286        Animation,
1287        AnimationName,
1288        Generic,
1289    }
1290}
1291
1292fn sorted_unique(values: impl IntoIterator<Item = String>) -> Vec<String> {
1293    values
1294        .into_iter()
1295        .collect::<BTreeSet<_>>()
1296        .into_iter()
1297        .collect()
1298}
1299
1300fn css_module_composes_edge_kind_label(kind: ParsedCssModuleComposesEdgeKind) -> &'static str {
1301    match kind {
1302        ParsedCssModuleComposesEdgeKind::Local => "local",
1303        ParsedCssModuleComposesEdgeKind::Global => "global",
1304        ParsedCssModuleComposesEdgeKind::External => "external",
1305    }
1306}
1307
1308fn sass_symbol_fact_kind_label(kind: ParsedSassSymbolFactKind) -> &'static str {
1309    match kind {
1310        ParsedSassSymbolFactKind::VariableDeclaration => "sassVariableDeclaration",
1311        ParsedSassSymbolFactKind::VariableReference => "sassVariableReference",
1312        ParsedSassSymbolFactKind::MixinDeclaration => "sassMixinDeclaration",
1313        ParsedSassSymbolFactKind::MixinInclude => "sassMixinInclude",
1314        ParsedSassSymbolFactKind::FunctionDeclaration => "sassFunctionDeclaration",
1315        ParsedSassSymbolFactKind::FunctionCall => "sassFunctionCall",
1316    }
1317}
1318
1319fn sass_module_edge_fact_kind_label(kind: ParsedSassModuleEdgeFactKind) -> &'static str {
1320    match kind {
1321        ParsedSassModuleEdgeFactKind::Use => "sassUse",
1322        ParsedSassModuleEdgeFactKind::Forward => "sassForward",
1323        ParsedSassModuleEdgeFactKind::Import => "sassImport",
1324    }
1325}
1326
1327fn summarize_omena_parser_sass_symbol_resolution(
1328    symbols: &[ParsedSassSymbolFact],
1329) -> OmenaParserSassSymbolResolutionV0 {
1330    let mut declaration_by_symbol: BTreeMap<
1331        (&'static str, Option<String>, String),
1332        (usize, &'static str),
1333    > = BTreeMap::new();
1334    let mut declaration_count = 0usize;
1335    let mut reference_count = 0usize;
1336    let mut edges = Vec::new();
1337
1338    for (source_order, symbol) in symbols.iter().enumerate() {
1339        let kind = sass_symbol_fact_kind_label(symbol.kind);
1340        if sass_symbol_fact_kind_is_declaration(symbol.kind) {
1341            declaration_count += 1;
1342            declaration_by_symbol.insert(
1343                (
1344                    symbol.symbol_kind,
1345                    symbol.namespace.clone(),
1346                    symbol.name.clone(),
1347                ),
1348                (source_order, kind),
1349            );
1350            continue;
1351        }
1352        if !sass_symbol_fact_kind_is_reference(symbol.kind) {
1353            continue;
1354        }
1355
1356        reference_count += 1;
1357        let declaration = declaration_by_symbol.get(&(
1358            symbol.symbol_kind,
1359            symbol.namespace.clone(),
1360            symbol.name.clone(),
1361        ));
1362        edges.push(OmenaParserSassSymbolResolutionEdgeV0 {
1363            symbol_kind: symbol.symbol_kind,
1364            name: symbol.name.clone(),
1365            namespace: symbol.namespace.clone(),
1366            reference_kind: kind,
1367            reference_role: symbol.role,
1368            reference_source_order: source_order,
1369            declaration_kind: declaration.map(|(_, declaration_kind)| *declaration_kind),
1370            declaration_source_order: declaration.map(|(declaration_order, _)| *declaration_order),
1371            status: if declaration.is_some() {
1372                "resolved"
1373            } else {
1374                "unresolved"
1375            },
1376        });
1377    }
1378
1379    let resolved_reference_count = edges
1380        .iter()
1381        .filter(|edge| edge.status == "resolved")
1382        .count();
1383
1384    OmenaParserSassSymbolResolutionV0 {
1385        schema_version: "0",
1386        product: "omena-parser.sass-symbol-same-file-resolution",
1387        resolution_scope: "same-file",
1388        declaration_count,
1389        reference_count,
1390        resolved_reference_count,
1391        unresolved_reference_count: reference_count.saturating_sub(resolved_reference_count),
1392        edges,
1393        capabilities: OmenaParserSassSymbolResolutionCapabilitiesV0 {
1394            same_file_lexical_resolution_ready: true,
1395            declaration_before_reference_ready: true,
1396            unresolved_reference_reporting_ready: true,
1397            cross_file_module_resolution_ready: false,
1398        },
1399    }
1400}
1401
1402fn sass_symbol_fact_kind_is_declaration(kind: ParsedSassSymbolFactKind) -> bool {
1403    matches!(
1404        kind,
1405        ParsedSassSymbolFactKind::VariableDeclaration
1406            | ParsedSassSymbolFactKind::MixinDeclaration
1407            | ParsedSassSymbolFactKind::FunctionDeclaration
1408    )
1409}
1410
1411fn sass_symbol_fact_kind_is_reference(kind: ParsedSassSymbolFactKind) -> bool {
1412    matches!(
1413        kind,
1414        ParsedSassSymbolFactKind::VariableReference
1415            | ParsedSassSymbolFactKind::MixinInclude
1416            | ParsedSassSymbolFactKind::FunctionCall
1417    )
1418}
1419
1420pub fn summarize_parser_cst_equivalence(
1421    text: &str,
1422    dialect: StyleDialect,
1423) -> ParserCstEquivalenceSummaryV0 {
1424    let result = parse(text, dialect);
1425    let syntax = result.syntax();
1426    let cst = result.cst();
1427
1428    let mut node_count = 0;
1429    let mut token_count = 0;
1430    let mut syntax_kind_round_trip_ready = true;
1431    let mut zero_unknown_kind_ready = true;
1432
1433    for node in syntax.descendants() {
1434        node_count += 1;
1435        let kind = node.kind();
1436        syntax_kind_round_trip_ready &= SyntaxKind::from_raw(kind.into_raw()) == kind;
1437        zero_unknown_kind_ready &= SyntaxKind::ALL.contains(&kind);
1438    }
1439
1440    for token in syntax
1441        .descendants_with_tokens()
1442        .filter_map(|element| element.into_token())
1443    {
1444        token_count += 1;
1445        let kind = token.kind();
1446        syntax_kind_round_trip_ready &= SyntaxKind::from_raw(kind.into_raw()) == kind;
1447        zero_unknown_kind_ready &= SyntaxKind::ALL.contains(&kind);
1448    }
1449
1450    let typed_wrapper_count = usize::from(cst.stylesheet().is_some())
1451        + cst.rules().len()
1452        + cst.selectors().len()
1453        + cst.declarations().len()
1454        + cst.declaration_lists().len()
1455        + cst.values().len()
1456        + cst.component_values().len()
1457        + cst.simple_blocks().len()
1458        + cst.component_value_lists().len()
1459        + cst.comma_separated_component_value_lists().len()
1460        + cst.custom_property_values().len()
1461        + cst.at_rules().len()
1462        + cst.bogus_nodes().len();
1463
1464    ParserCstEquivalenceSummaryV0 {
1465        product: "omena-parser.cst-equivalence",
1466        dialect,
1467        root_kind: syntax.kind(),
1468        parser_node_count: node_count,
1469        parser_token_count: token_count,
1470        typed_wrapper_count,
1471        source_text_round_trip_ready: result.source_text().as_deref() == Some(text),
1472        syntax_kind_round_trip_ready,
1473        zero_unknown_kind_ready,
1474        typed_cst_wrapper_ready: cst.stylesheet().is_some() && typed_wrapper_count > 1,
1475        ready_surfaces: vec![
1476            "parserCstEquivalence",
1477            "parserUsesOmenaSyntaxKind",
1478            "parserCstSourceTextRoundTrip",
1479            "typedCstWrapperEquivalence",
1480        ],
1481    }
1482}
1483
1484pub fn summarize_parser_semantic_name_consumption(
1485    text: &str,
1486    dialect: StyleDialect,
1487    db: &dyn salsa::Database,
1488) -> ParserSemanticNameConsumptionSummaryV0 {
1489    let facts = collect_style_facts(text, dialect);
1490    let candidates = parser_semantic_name_candidates(&facts);
1491    let interned_name_count = candidates
1492        .iter()
1493        .filter(|candidate| intern_parser_semantic_name(db, candidate.kind, &candidate.text))
1494        .count();
1495    let invalid_name_count = candidates.len().saturating_sub(interned_name_count);
1496
1497    ParserSemanticNameConsumptionSummaryV0 {
1498        product: "omena-parser.semantic-name-consumption",
1499        dialect,
1500        semantic_name_count: candidates.len(),
1501        interned_name_count,
1502        invalid_name_count,
1503        class_name_count: count_parser_semantic_name_kind(&candidates, NameKind::ClassName),
1504        css_ident_count: count_parser_semantic_name_kind(&candidates, NameKind::CssIdent),
1505        property_name_count: count_parser_semantic_name_kind(&candidates, NameKind::PropertyName),
1506        selector_key_count: count_parser_semantic_name_kind(&candidates, NameKind::SelectorKey),
1507        custom_property_name_count: count_parser_semantic_name_kind(
1508            &candidates,
1509            NameKind::CustomPropertyName,
1510        ),
1511        keyframes_name_count: count_parser_semantic_name_kind(&candidates, NameKind::KeyframesName),
1512        mixin_name_count: count_parser_semantic_name_kind(&candidates, NameKind::MixinName),
1513        file_path_count: count_parser_semantic_name_kind(&candidates, NameKind::FilePath),
1514        ready_surfaces: vec![
1515            "parserSemanticNameConsumption",
1516            "typedInternerValidation",
1517            "styleFactNameKindProjection",
1518        ],
1519    }
1520}
1521
1522fn parser_semantic_name_candidates(facts: &ParsedStyleFacts) -> Vec<ParserSemanticNameCandidateV0> {
1523    let mut candidates = Vec::new();
1524
1525    for selector in &facts.selectors {
1526        let kind = match selector.kind {
1527            ParsedSelectorFactKind::Class => NameKind::ClassName,
1528            ParsedSelectorFactKind::Id | ParsedSelectorFactKind::Placeholder => {
1529                NameKind::SelectorKey
1530            }
1531        };
1532        push_parser_semantic_name_candidate(&mut candidates, kind, &selector.name);
1533    }
1534
1535    for variable in &facts.variables {
1536        let candidate = match variable.kind {
1537            ParsedVariableFactKind::CustomPropertyDeclaration
1538            | ParsedVariableFactKind::CustomPropertyReference => variable
1539                .property_key
1540                .as_ref()
1541                .map(|property_key| (NameKind::CustomPropertyName, property_key.as_str())),
1542            ParsedVariableFactKind::ScssDeclaration
1543            | ParsedVariableFactKind::ScssReference
1544            | ParsedVariableFactKind::LessDeclaration
1545            | ParsedVariableFactKind::LessReference => variable
1546                .name
1547                .as_non_property()
1548                .map(|name| (NameKind::CssIdent, name)),
1549        };
1550        let Some((kind, text)) = candidate else {
1551            continue;
1552        };
1553        push_parser_semantic_name_candidate(&mut candidates, kind, text);
1554    }
1555
1556    for symbol in &facts.sass_symbols {
1557        let kind = match symbol.kind {
1558            ParsedSassSymbolFactKind::MixinDeclaration | ParsedSassSymbolFactKind::MixinInclude => {
1559                NameKind::MixinName
1560            }
1561            ParsedSassSymbolFactKind::VariableDeclaration
1562            | ParsedSassSymbolFactKind::VariableReference
1563            | ParsedSassSymbolFactKind::FunctionDeclaration
1564            | ParsedSassSymbolFactKind::FunctionCall => NameKind::CssIdent,
1565        };
1566        push_parser_semantic_name_candidate(&mut candidates, kind, &symbol.name);
1567        if let Some(namespace) = &symbol.namespace {
1568            push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, namespace);
1569        }
1570    }
1571
1572    for include in &facts.sass_includes {
1573        push_parser_semantic_name_candidate(&mut candidates, NameKind::MixinName, &include.name);
1574        if let Some(namespace) = &include.namespace {
1575            push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, namespace);
1576        }
1577    }
1578
1579    for edge in &facts.sass_module_edges {
1580        push_parser_semantic_name_candidate(&mut candidates, NameKind::FilePath, &edge.source);
1581        if let Some(namespace) = &edge.namespace {
1582            push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, namespace);
1583        }
1584    }
1585
1586    for animation in &facts.animations {
1587        push_parser_semantic_name_candidate(
1588            &mut candidates,
1589            NameKind::KeyframesName,
1590            &animation.name,
1591        );
1592    }
1593
1594    for value in &facts.css_module_values {
1595        let kind = match value.kind {
1596            ParsedCssModuleValueFactKind::Definition | ParsedCssModuleValueFactKind::Reference => {
1597                NameKind::CssIdent
1598            }
1599            ParsedCssModuleValueFactKind::ImportSource => NameKind::FilePath,
1600        };
1601        push_parser_semantic_name_candidate(&mut candidates, kind, &value.name);
1602    }
1603
1604    for edge in &facts.css_module_value_import_edges {
1605        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.local_name);
1606        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.remote_name);
1607        push_parser_semantic_name_candidate(
1608            &mut candidates,
1609            NameKind::FilePath,
1610            &edge.import_source,
1611        );
1612    }
1613
1614    for edge in &facts.css_module_value_definition_edges {
1615        push_parser_semantic_name_candidate(
1616            &mut candidates,
1617            NameKind::CssIdent,
1618            &edge.definition_name,
1619        );
1620        for reference_name in &edge.reference_names {
1621            push_parser_semantic_name_candidate(
1622                &mut candidates,
1623                NameKind::CssIdent,
1624                reference_name,
1625            );
1626        }
1627    }
1628
1629    for composes in &facts.css_module_composes {
1630        let kind = match composes.kind {
1631            ParsedCssModuleComposesFactKind::Target => NameKind::ClassName,
1632            ParsedCssModuleComposesFactKind::ImportSource => NameKind::FilePath,
1633        };
1634        push_parser_semantic_name_candidate(&mut candidates, kind, &composes.name);
1635    }
1636
1637    for edge in &facts.css_module_composes_edges {
1638        for owner_selector_name in &edge.owner_selector_names {
1639            push_parser_semantic_name_candidate(
1640                &mut candidates,
1641                NameKind::ClassName,
1642                owner_selector_name,
1643            );
1644        }
1645        for target_name in &edge.target_names {
1646            push_parser_semantic_name_candidate(&mut candidates, NameKind::ClassName, target_name);
1647        }
1648        if let Some(import_source) = &edge.import_source {
1649            push_parser_semantic_name_candidate(&mut candidates, NameKind::FilePath, import_source);
1650        }
1651    }
1652
1653    for icss in &facts.icss {
1654        let kind = match icss.kind {
1655            ParsedIcssFactKind::ImportSource => NameKind::FilePath,
1656            ParsedIcssFactKind::ExportName
1657            | ParsedIcssFactKind::ImportLocalName
1658            | ParsedIcssFactKind::ImportRemoteName => NameKind::CssIdent,
1659        };
1660        push_parser_semantic_name_candidate(&mut candidates, kind, &icss.name);
1661    }
1662
1663    for edge in &facts.icss_import_edges {
1664        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.local_name);
1665        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.remote_name);
1666        push_parser_semantic_name_candidate(
1667            &mut candidates,
1668            NameKind::FilePath,
1669            &edge.import_source,
1670        );
1671    }
1672
1673    for edge in &facts.icss_export_edges {
1674        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &edge.export_name);
1675        for reference_name in &edge.reference_names {
1676            push_parser_semantic_name_candidate(
1677                &mut candidates,
1678                NameKind::CssIdent,
1679                reference_name,
1680            );
1681        }
1682    }
1683
1684    for at_rule in &facts.at_rules {
1685        push_parser_semantic_name_candidate(&mut candidates, NameKind::CssIdent, &at_rule.name);
1686    }
1687
1688    candidates
1689}
1690
1691fn push_parser_semantic_name_candidate(
1692    candidates: &mut Vec<ParserSemanticNameCandidateV0>,
1693    kind: NameKind,
1694    text: &str,
1695) {
1696    candidates.push(ParserSemanticNameCandidateV0 {
1697        kind,
1698        text: text.to_string(),
1699    });
1700}
1701
1702fn count_parser_semantic_name_kind(
1703    candidates: &[ParserSemanticNameCandidateV0],
1704    kind: NameKind,
1705) -> usize {
1706    candidates
1707        .iter()
1708        .filter(|candidate| candidate.kind == kind)
1709        .count()
1710}
1711
1712fn intern_parser_semantic_name(db: &dyn salsa::Database, kind: NameKind, text: &str) -> bool {
1713    match kind {
1714        NameKind::ClassName => intern_class_name(db, text).is_ok(),
1715        NameKind::CssIdent => intern_css_ident(db, text).is_ok(),
1716        NameKind::PropertyName => intern_property_name(db, text).is_ok(),
1717        NameKind::SelectorKey => intern_selector_key(db, text).is_ok(),
1718        NameKind::CustomPropertyName => intern_custom_property_name(db, text).is_ok(),
1719        NameKind::KeyframesName => intern_keyframes_name(db, text).is_ok(),
1720        NameKind::MixinName => intern_mixin_name(db, text).is_ok(),
1721        NameKind::FilePath => intern_file_path(db, text).is_ok(),
1722    }
1723}