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