1use std::collections::{BTreeMap, BTreeSet};
2
3use omena_parser::{
4 LexResult, ParseResult, ParsedAnimationFactKind, ParsedCssModuleComposesEdgeKind,
5 ParsedCssModuleComposesFactKind, ParsedCssModuleValueFactKind, ParsedIcssFactKind,
6 ParsedSassModuleEdgeFactKind, ParsedSassSymbolFactKind, ParsedSelectorFactKind,
7 ParsedStyleFacts, ParsedVariableFactKind, collect_icss_export_values_from_cst,
8 collect_style_facts, facts_from_cst, lex, parse, parse_with_reuse_cache,
9};
10
11use crate::*;
12
13pub(super) fn collect_omena_query_omena_parser_style_facts_raw(
14 style_source: &str,
15 dialect: OmenaParserStyleDialect,
16) -> ParsedStyleFacts {
17 #[cfg(test)]
18 style_facts_collect_probe::record();
19 collect_style_facts(style_source, dialect)
20}
21
22pub fn summarize_omena_query_sass_module_source_edges(
23 style_source: &str,
24 dialect: OmenaParserStyleDialect,
25) -> Vec<OmenaQuerySassModuleSourceEdgeV0> {
26 collect_omena_query_omena_parser_style_facts_raw(style_source, dialect)
27 .sass_module_edges
28 .into_iter()
29 .map(|edge| OmenaQuerySassModuleSourceEdgeV0 {
30 kind: omena_query_sass_module_edge_fact_kind_label(edge.kind),
31 source: edge.source,
32 byte_span: ParserByteSpanV0 {
33 start: u32::from(edge.range.start()) as usize,
34 end: u32::from(edge.range.end()) as usize,
35 },
36 namespace_kind: edge.namespace_kind,
37 namespace: edge.namespace,
38 forward_prefix: edge.forward_prefix,
39 visibility_filter_kind: edge.visibility_filter_kind,
40 visibility_filter_names: edge.visibility_filter_names,
41 media_qualified: edge.media_qualified,
42 })
43 .collect()
44}
45
46pub(super) fn collect_omena_query_style_facts_with_icss_values_raw(
47 style_source: &str,
48 dialect: OmenaParserStyleDialect,
49) -> (ParsedStyleFacts, BTreeMap<String, String>) {
50 let parsed = parse(style_source, dialect);
51 collect_omena_query_style_facts_with_icss_values_from_parse(style_source, &parsed)
52}
53
54pub(super) fn collect_omena_query_style_facts_with_icss_values_from_parse(
55 style_source: &str,
56 parsed: &ParseResult,
57) -> (ParsedStyleFacts, BTreeMap<String, String>) {
58 #[cfg(test)]
59 style_facts_collect_probe::record();
60 let values = collect_icss_export_values_from_cst(style_source, parsed)
61 .into_iter()
62 .collect();
63 (facts_from_cst(style_source, parsed), values)
64}
65
66pub(super) fn parse_omena_query_omena_parser_style_source(
67 style_source: &str,
68 dialect: OmenaParserStyleDialect,
69) -> ParseResult {
70 parse(style_source, dialect)
71}
72
73pub(super) fn lex_omena_query_omena_parser_style_source(
74 style_source: &str,
75 dialect: OmenaParserStyleDialect,
76) -> LexResult {
77 lex(style_source, dialect)
78}
79
80#[derive(Default)]
81pub struct OmenaQueryStyleFrameRefreshParseCacheV0 {
82 reuse_cache: omena_parser::ParseReuseCache,
83}
84
85pub struct OmenaQueryStyleFrameRefreshFactsV0 {
86 pub token_count: usize,
87 pub error_count: usize,
88 pub dependency_ids: Vec<String>,
89}
90
91pub fn summarize_omena_query_style_frame_refresh_facts_with_reuse(
92 style_source: &str,
93 dialect: OmenaParserStyleDialect,
94 cache: &mut OmenaQueryStyleFrameRefreshParseCacheV0,
95) -> OmenaQueryStyleFrameRefreshFactsV0 {
96 let parsed = parse_with_reuse_cache(style_source, dialect, &mut cache.reuse_cache);
97 let facts = facts_from_cst(style_source, &parsed);
98 OmenaQueryStyleFrameRefreshFactsV0 {
99 token_count: parsed.token_count(),
100 error_count: parsed.errors().len(),
101 dependency_ids: frame_refresh_dependency_ids(&facts),
102 }
103}
104
105fn frame_refresh_dependency_ids(facts: &ParsedStyleFacts) -> Vec<String> {
106 let mut dependency_ids = BTreeSet::new();
107 for edge in &facts.sass_module_edges {
108 dependency_ids.insert(edge.source.clone());
109 }
110 for edge in &facts.css_module_value_import_edges {
111 dependency_ids.insert(edge.import_source.clone());
112 }
113 for edge in &facts.css_module_composes_edges {
114 if let Some(import_source) = &edge.import_source {
115 dependency_ids.insert(import_source.clone());
116 }
117 }
118 for edge in &facts.icss_import_edges {
119 dependency_ids.insert(edge.import_source.clone());
120 }
121 dependency_ids.into_iter().collect()
122}
123
124pub fn summarize_omena_query_style_document(
125 style_path: &str,
126 style_source: &str,
127) -> Option<OmenaQueryStyleDocumentSummaryV0> {
128 let dialect = omena_parser_dialect_for_style_path(style_path);
129 let facts = collect_omena_query_omena_parser_style_facts_raw(style_source, dialect);
130 let mut selector_names = Vec::new();
131 let mut custom_property_decl_names = Vec::new();
132 let mut custom_property_ref_names = Vec::new();
133 let mut sass_module_use_sources = BTreeSet::new();
134 let mut sass_module_forward_sources = BTreeSet::new();
135
136 for selector in facts.selectors {
137 if selector.kind == ParsedSelectorFactKind::Class {
138 selector_names.push(selector.name);
139 }
140 }
141
142 for variable in facts.variables {
143 match variable.kind {
144 ParsedVariableFactKind::CustomPropertyDeclaration => {
145 if let Some(name) = variable.name.as_custom_property() {
146 custom_property_decl_names.push(name.clone());
147 }
148 }
149 ParsedVariableFactKind::CustomPropertyReference => {
150 if let Some(name) = variable.name.as_custom_property() {
151 custom_property_ref_names.push(name.clone());
152 }
153 }
154 _ => {}
155 }
156 }
157
158 for edge in facts.sass_module_edges {
159 match edge.kind {
160 ParsedSassModuleEdgeFactKind::Use => {
161 sass_module_use_sources.insert(edge.source);
162 }
163 ParsedSassModuleEdgeFactKind::Forward => {
164 sass_module_forward_sources.insert(edge.source);
165 }
166 ParsedSassModuleEdgeFactKind::Import => {
167 sass_module_use_sources.insert(edge.source);
168 }
169 }
170 }
171
172 Some(OmenaQueryStyleDocumentSummaryV0 {
173 schema_version: "0",
174 product: "omena-query.style-document-summary",
175 language: omena_parser_style_dialect_label(dialect),
176 selector_names,
177 custom_property_decl_names,
178 custom_property_ref_names,
179 sass_module_use_sources: sass_module_use_sources.into_iter().collect(),
180 sass_module_forward_sources: sass_module_forward_sources.into_iter().collect(),
181 diagnostic_count: facts.error_count,
182 })
183}
184
185pub fn summarize_omena_query_omena_parser_style_facts(
186 style_source: &str,
187 dialect: OmenaParserStyleDialect,
188) -> OmenaQueryOmenaParserStyleFactsV0 {
189 let facts = collect_omena_query_omena_parser_style_facts_raw(style_source, dialect);
190 summarize_omena_query_omena_parser_style_facts_from_facts(facts, dialect)
191}
192
193pub(super) fn summarize_omena_query_omena_parser_style_facts_from_facts(
194 facts: ParsedStyleFacts,
195 dialect: OmenaParserStyleDialect,
196) -> OmenaQueryOmenaParserStyleFactsV0 {
197 let sass_symbol_resolution =
198 summarize_omena_query_sass_symbol_resolution(facts.sass_symbols.as_slice());
199 let mut class_selector_names = Vec::new();
200 let mut id_selector_names = Vec::new();
201 let mut placeholder_selector_names = Vec::new();
202 let mut keyframe_names = Vec::new();
203 let mut animation_reference_names = Vec::new();
204 let mut css_module_value_definition_names = BTreeSet::new();
205 let mut css_module_value_reference_names = BTreeSet::new();
206 let mut css_module_value_import_sources = BTreeSet::new();
207 let mut css_module_composes_target_names = BTreeSet::new();
208 let mut css_module_composes_import_sources = BTreeSet::new();
209 let mut icss_export_names = BTreeSet::new();
210 let mut icss_import_local_names = BTreeSet::new();
211 let mut icss_import_remote_names = BTreeSet::new();
212 let mut icss_import_sources = BTreeSet::new();
213 let mut variable_names = BTreeSet::new();
214 let mut sass_symbol_declaration_names = BTreeSet::new();
215 let mut sass_symbol_reference_names = BTreeSet::new();
216 let mut sass_module_use_sources = BTreeSet::new();
217 let mut sass_module_forward_sources = BTreeSet::new();
218 let mut sass_module_import_sources = BTreeSet::new();
219 let mut custom_property_names = BTreeMap::new();
220 let mut custom_property_decl_names = BTreeMap::new();
221 let mut custom_property_ref_names = BTreeMap::new();
222
223 for selector in facts.selectors {
224 match selector.kind {
225 ParsedSelectorFactKind::Class => class_selector_names.push(selector.name),
226 ParsedSelectorFactKind::Id => id_selector_names.push(selector.name),
227 ParsedSelectorFactKind::Placeholder => placeholder_selector_names.push(selector.name),
228 }
229 }
230
231 for variable in facts.variables {
232 match variable.kind {
233 ParsedVariableFactKind::ScssDeclaration
234 | ParsedVariableFactKind::ScssReference
235 | ParsedVariableFactKind::LessDeclaration
236 | ParsedVariableFactKind::LessReference => {
237 if let Some(name) = variable.name.as_non_property() {
238 variable_names.insert(name.to_string());
239 }
240 }
241 ParsedVariableFactKind::CustomPropertyDeclaration
242 | ParsedVariableFactKind::CustomPropertyReference => {
243 let Some(property_key) = variable.property_key else {
244 continue;
245 };
246 let Some(property_name) = variable.name.as_custom_property().cloned() else {
247 continue;
248 };
249 custom_property_names
250 .entry(property_key.clone())
251 .or_insert_with(|| property_name.clone());
252 match variable.kind {
253 ParsedVariableFactKind::CustomPropertyDeclaration => {
254 custom_property_decl_names
255 .entry(property_key)
256 .or_insert(property_name);
257 }
258 ParsedVariableFactKind::CustomPropertyReference => {
259 custom_property_ref_names
260 .entry(property_key)
261 .or_insert(property_name);
262 }
263 _ => {}
264 }
265 }
266 }
267 }
268
269 for symbol in &facts.sass_symbols {
270 match symbol.role {
271 "declaration" => {
272 sass_symbol_declaration_names.insert(symbol.name.clone());
273 }
274 _ => {
275 sass_symbol_reference_names.insert(symbol.name.clone());
276 }
277 }
278 }
279
280 for edge in &facts.sass_module_edges {
281 match edge.kind {
282 ParsedSassModuleEdgeFactKind::Use => {
283 sass_module_use_sources.insert(edge.source.clone());
284 }
285 ParsedSassModuleEdgeFactKind::Forward => {
286 sass_module_forward_sources.insert(edge.source.clone());
287 }
288 ParsedSassModuleEdgeFactKind::Import => {
289 sass_module_import_sources.insert(edge.source.clone());
290 }
291 }
292 }
293
294 for animation in facts.animations {
295 match animation.kind {
296 ParsedAnimationFactKind::KeyframesDeclaration => keyframe_names.push(animation.name),
297 ParsedAnimationFactKind::AnimationNameReference => {
298 animation_reference_names.push(animation.name);
299 }
300 }
301 }
302
303 for value in facts.css_module_values {
304 match value.kind {
305 ParsedCssModuleValueFactKind::Definition => {
306 css_module_value_definition_names.insert(value.name);
307 }
308 ParsedCssModuleValueFactKind::Reference => {
309 css_module_value_reference_names.insert(value.name);
310 }
311 ParsedCssModuleValueFactKind::ImportSource => {
312 css_module_value_import_sources.insert(value.name);
313 }
314 }
315 }
316
317 for composes in facts.css_module_composes {
318 match composes.kind {
319 ParsedCssModuleComposesFactKind::Target => {
320 css_module_composes_target_names.insert(composes.name);
321 }
322 ParsedCssModuleComposesFactKind::ImportSource => {
323 css_module_composes_import_sources.insert(composes.name);
324 }
325 }
326 }
327
328 for icss in facts.icss {
329 match icss.kind {
330 ParsedIcssFactKind::ExportName => {
331 icss_export_names.insert(icss.name);
332 }
333 ParsedIcssFactKind::ImportLocalName => {
334 icss_import_local_names.insert(icss.name);
335 }
336 ParsedIcssFactKind::ImportRemoteName => {
337 icss_import_remote_names.insert(icss.name);
338 }
339 ParsedIcssFactKind::ImportSource => {
340 icss_import_sources.insert(icss.name);
341 }
342 }
343 }
344
345 OmenaQueryOmenaParserStyleFactsV0 {
346 schema_version: "0",
347 product: "omena-query.omena-parser-style-facts",
348 dialect: omena_parser_style_dialect_label(dialect),
349 class_selector_names,
350 id_selector_names,
351 placeholder_selector_names,
352 keyframe_names,
353 animation_reference_names,
354 css_module_value_definition_names: css_module_value_definition_names.into_iter().collect(),
355 css_module_value_reference_names: css_module_value_reference_names.into_iter().collect(),
356 css_module_value_import_sources: css_module_value_import_sources.into_iter().collect(),
357 css_module_value_import_edges: facts
358 .css_module_value_import_edges
359 .into_iter()
360 .map(|edge| OmenaQueryCssModuleValueImportEdgeFactV0 {
361 remote_name: edge.remote_name,
362 local_name: edge.local_name,
363 import_source: edge.import_source,
364 })
365 .collect(),
366 css_module_value_definition_edges: facts
367 .css_module_value_definition_edges
368 .into_iter()
369 .map(|edge| OmenaQueryCssModuleValueDefinitionEdgeFactV0 {
370 definition_name: edge.definition_name,
371 reference_names: edge.reference_names,
372 })
373 .collect(),
374 css_module_composes_target_names: css_module_composes_target_names.into_iter().collect(),
375 css_module_composes_import_sources: css_module_composes_import_sources
376 .into_iter()
377 .collect(),
378 css_module_composes_edges: facts
379 .css_module_composes_edges
380 .into_iter()
381 .map(|edge| OmenaQueryCssModuleComposesEdgeFactV0 {
382 kind: omena_query_css_module_composes_edge_kind_label(edge.kind),
383 owner_selector_names: edge.owner_selector_names,
384 target_names: edge.target_names,
385 import_source: edge.import_source,
386 })
387 .collect(),
388 icss_export_names: icss_export_names.into_iter().collect(),
389 icss_import_local_names: icss_import_local_names.into_iter().collect(),
390 icss_import_remote_names: icss_import_remote_names.into_iter().collect(),
391 icss_import_sources: icss_import_sources.into_iter().collect(),
392 icss_import_edges: facts
393 .icss_import_edges
394 .into_iter()
395 .map(|edge| OmenaQueryIcssImportEdgeFactV0 {
396 local_name: edge.local_name,
397 remote_name: edge.remote_name,
398 import_source: edge.import_source,
399 })
400 .collect(),
401 icss_export_edges: facts
402 .icss_export_edges
403 .into_iter()
404 .map(|edge| OmenaQueryIcssExportEdgeFactV0 {
405 export_name: edge.export_name,
406 reference_names: edge.reference_names,
407 })
408 .collect(),
409 variable_names: variable_names.into_iter().collect(),
410 sass_symbol_declaration_names: sass_symbol_declaration_names.into_iter().collect(),
411 sass_symbol_reference_names: sass_symbol_reference_names.into_iter().collect(),
412 sass_symbol_facts: facts
413 .sass_symbols
414 .into_iter()
415 .map(|symbol| OmenaQuerySassSymbolFactV0 {
416 kind: omena_query_sass_symbol_fact_kind_label(symbol.kind),
417 symbol_kind: symbol.symbol_kind,
418 name: symbol.name,
419 role: symbol.role,
420 namespace: symbol.namespace,
421 })
422 .collect(),
423 sass_symbol_resolution,
424 sass_module_use_sources: sass_module_use_sources.into_iter().collect(),
425 sass_module_forward_sources: sass_module_forward_sources.into_iter().collect(),
426 sass_module_import_sources: sass_module_import_sources.into_iter().collect(),
427 sass_module_edges: facts
428 .sass_module_edges
429 .into_iter()
430 .map(|edge| OmenaQuerySassModuleEdgeFactV0 {
431 kind: omena_query_sass_module_edge_fact_kind_label(edge.kind),
432 source: edge.source,
433 namespace_kind: edge.namespace_kind,
434 namespace: edge.namespace,
435 forward_prefix: edge.forward_prefix,
436 visibility_filter_kind: edge.visibility_filter_kind,
437 visibility_filter_names: edge.visibility_filter_names,
438 })
439 .collect(),
440 custom_property_names: custom_property_names.into_values().collect(),
441 custom_property_decl_names: custom_property_decl_names.into_values().collect(),
442 custom_property_ref_names: custom_property_ref_names.into_values().collect(),
443 at_rule_names: facts
444 .at_rules
445 .into_iter()
446 .map(|at_rule| at_rule.name)
447 .collect(),
448 parser_error_count: facts.error_count,
449 }
450}
451
452pub fn summarize_omena_query_omena_parser_css_modules_intermediate(
453 style_source: &str,
454 dialect: OmenaParserStyleDialect,
455) -> omena_parser::ParserIndexSummaryV0 {
456 omena_parser::summarize_css_modules_intermediate(style_source, dialect)
457}
458
459pub fn summarize_omena_query_omena_parser_lex(
460 style_source: &str,
461 dialect: OmenaParserStyleDialect,
462) -> omena_parser::OmenaParserLexSummaryV0 {
463 omena_parser::summarize_omena_parser_lex(style_source, dialect)
464}
465
466pub(super) fn omena_parser_dialect_for_style_path(style_path: &str) -> OmenaParserStyleDialect {
467 if style_path.ends_with(".sass") {
468 OmenaParserStyleDialect::Sass
469 } else if style_path.ends_with(".scss") {
470 OmenaParserStyleDialect::Scss
471 } else if style_path.ends_with(".less") {
472 OmenaParserStyleDialect::Less
473 } else {
474 OmenaParserStyleDialect::Css
475 }
476}
477
478pub(super) fn omena_parser_style_dialect_label(dialect: OmenaParserStyleDialect) -> &'static str {
479 match dialect {
480 OmenaParserStyleDialect::Css => "css",
481 OmenaParserStyleDialect::Scss => "scss",
482 OmenaParserStyleDialect::Sass => "sass",
483 OmenaParserStyleDialect::Less => "less",
484 }
485}
486
487pub(super) fn omena_query_css_module_composes_edge_kind_label(
488 kind: ParsedCssModuleComposesEdgeKind,
489) -> &'static str {
490 match kind {
491 ParsedCssModuleComposesEdgeKind::Local => "local",
492 ParsedCssModuleComposesEdgeKind::Global => "global",
493 ParsedCssModuleComposesEdgeKind::External => "external",
494 }
495}
496
497pub(super) fn omena_query_sass_symbol_fact_kind_label(
498 kind: ParsedSassSymbolFactKind,
499) -> &'static str {
500 match kind {
501 ParsedSassSymbolFactKind::VariableDeclaration => "sassVariableDeclaration",
502 ParsedSassSymbolFactKind::VariableReference => "sassVariableReference",
503 ParsedSassSymbolFactKind::MixinDeclaration => "sassMixinDeclaration",
504 ParsedSassSymbolFactKind::MixinInclude => "sassMixinInclude",
505 ParsedSassSymbolFactKind::FunctionDeclaration => "sassFunctionDeclaration",
506 ParsedSassSymbolFactKind::FunctionCall => "sassFunctionCall",
507 }
508}
509
510pub(super) fn omena_query_sass_module_edge_fact_kind_label(
511 kind: ParsedSassModuleEdgeFactKind,
512) -> &'static str {
513 match kind {
514 ParsedSassModuleEdgeFactKind::Use => "sassUse",
515 ParsedSassModuleEdgeFactKind::Forward => "sassForward",
516 ParsedSassModuleEdgeFactKind::Import => "sassImport",
517 }
518}
519
520fn summarize_omena_query_sass_symbol_resolution(
521 symbols: &[omena_parser::ParsedSassSymbolFact],
522) -> OmenaQuerySassSymbolResolutionV0 {
523 let mut declaration_by_symbol: BTreeMap<
524 (&'static str, Option<String>, String),
525 (usize, &'static str),
526 > = BTreeMap::new();
527 let mut declaration_count = 0usize;
528 let mut reference_count = 0usize;
529 let mut edges = Vec::new();
530
531 for (source_order, symbol) in symbols.iter().enumerate() {
532 let kind = omena_query_sass_symbol_fact_kind_label(symbol.kind);
533 if omena_query_sass_symbol_fact_kind_is_declaration(symbol.kind) {
534 declaration_count += 1;
535 declaration_by_symbol.insert(
536 (
537 symbol.symbol_kind,
538 symbol.namespace.clone(),
539 symbol.name.clone(),
540 ),
541 (source_order, kind),
542 );
543 continue;
544 }
545 if !omena_query_sass_symbol_fact_kind_is_reference(symbol.kind) {
546 continue;
547 }
548
549 reference_count += 1;
550 let declaration = declaration_by_symbol.get(&(
551 symbol.symbol_kind,
552 symbol.namespace.clone(),
553 symbol.name.clone(),
554 ));
555 edges.push(OmenaQuerySassSymbolResolutionEdgeV0 {
556 symbol_kind: symbol.symbol_kind,
557 name: symbol.name.clone(),
558 namespace: symbol.namespace.clone(),
559 reference_kind: kind,
560 reference_role: symbol.role,
561 reference_source_order: source_order,
562 declaration_kind: declaration.map(|(_, declaration_kind)| *declaration_kind),
563 declaration_source_order: declaration.map(|(declaration_order, _)| *declaration_order),
564 status: if declaration.is_some() {
565 "resolved"
566 } else {
567 "unresolved"
568 },
569 });
570 }
571
572 let resolved_reference_count = edges
573 .iter()
574 .filter(|edge| edge.status == "resolved")
575 .count();
576 OmenaQuerySassSymbolResolutionV0 {
577 schema_version: "0",
578 product: "omena-query.sass-symbol-same-file-resolution",
579 resolution_scope: "same-file",
580 declaration_count,
581 reference_count,
582 resolved_reference_count,
583 unresolved_reference_count: reference_count.saturating_sub(resolved_reference_count),
584 edges,
585 capabilities: OmenaQuerySassSymbolResolutionCapabilitiesV0 {
586 same_file_lexical_resolution_ready: true,
587 declaration_before_reference_ready: true,
588 unresolved_reference_reporting_ready: true,
589 cross_file_module_resolution_ready: false,
590 },
591 }
592}
593
594pub(super) fn omena_query_sass_symbol_fact_kind_is_declaration(
595 kind: ParsedSassSymbolFactKind,
596) -> bool {
597 matches!(
598 kind,
599 ParsedSassSymbolFactKind::VariableDeclaration
600 | ParsedSassSymbolFactKind::MixinDeclaration
601 | ParsedSassSymbolFactKind::FunctionDeclaration
602 )
603}
604
605pub(super) fn omena_query_sass_symbol_fact_kind_is_reference(
606 kind: ParsedSassSymbolFactKind,
607) -> bool {
608 matches!(
609 kind,
610 ParsedSassSymbolFactKind::VariableReference
611 | ParsedSassSymbolFactKind::MixinInclude
612 | ParsedSassSymbolFactKind::FunctionCall
613 )
614}
615
616#[cfg(test)]
619pub(crate) mod style_facts_collect_probe {
620 use std::cell::Cell;
621
622 thread_local! {
623 static COLLECT_CALLS: Cell<usize> = const { Cell::new(0) };
624 }
625
626 pub(crate) fn reset() {
627 COLLECT_CALLS.with(|calls| calls.set(0));
628 }
629
630 pub(crate) fn count() -> usize {
631 COLLECT_CALLS.with(Cell::get)
632 }
633
634 pub(super) fn record() {
635 COLLECT_CALLS.with(|calls| calls.set(calls.get() + 1));
636 }
637}