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