1use 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}