Skip to main content

omena_query/style/
parser_facade.rs

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