Skip to main content

omena_query/
source.rs

1use super::*;
2use omena_query_core::{
3    AbstractClassValueV0, ExternalStringTypeFactsV0, abstract_class_value_from_facts,
4    abstract_class_value_kind, join_abstract_class_values, top_class_value,
5};
6use serde::{Deserialize, Serialize};
7use std::collections::{BTreeMap, BTreeSet, VecDeque};
8
9pub type OmenaQueryTsconfigPathMappingV0 = omena_resolver::OmenaResolverTsconfigPathMappingV0;
10pub type OmenaQueryBundlerPathAliasMappingV0 =
11    omena_resolver::OmenaResolverBundlerPathAliasMappingV0;
12pub type OmenaQueryStyleModuleDiskCandidateIdentityV0 =
13    omena_resolver::OmenaResolverStyleModuleDiskCandidateIdentityV0;
14
15#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
16#[serde(default, rename_all = "camelCase")]
17pub struct OmenaQueryStyleResolutionInputsV0 {
18    pub package_manifests: Vec<OmenaQueryStylePackageManifestV0>,
19    pub tsconfig_path_mappings: Vec<OmenaQueryTsconfigPathMappingV0>,
20    pub bundler_path_mappings: Vec<OmenaQueryBundlerPathAliasMappingV0>,
21    #[serde(skip_serializing_if = "Vec::is_empty")]
22    pub disk_style_path_identities: Vec<OmenaQueryStyleModuleDiskCandidateIdentityV0>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub external_sif_cache_fingerprint: Option<String>,
25}
26
27pub fn summarize_omena_query_source_import_declarations(
28    source: &str,
29) -> OmenaQuerySourceImportDeclarationSummaryV0 {
30    omena_bridge::summarize_omena_bridge_source_import_declarations(source)
31}
32
33pub fn summarize_omena_query_source_import_declarations_for_source_language(
34    source_path: &str,
35    source: &str,
36    source_language: Option<&str>,
37) -> OmenaQuerySourceImportDeclarationSummaryV0 {
38    omena_bridge::summarize_omena_bridge_source_import_declarations_for_source_language(
39        source_path,
40        source,
41        source_language,
42    )
43}
44
45pub fn resolve_omena_query_style_uri_for_specifier(
46    base_document_uri: &str,
47    workspace_folder_uri: Option<&str>,
48    specifier: &str,
49) -> Option<String> {
50    omena_bridge::resolve_omena_bridge_style_uri_for_specifier(
51        base_document_uri,
52        workspace_folder_uri,
53        specifier,
54    )
55}
56
57pub fn resolve_omena_query_style_uri_for_specifier_with_package_manifests(
58    base_document_uri: &str,
59    workspace_folder_uri: Option<&str>,
60    specifier: &str,
61    package_manifests: &[OmenaQueryStylePackageManifestV0],
62) -> Option<String> {
63    let resolver_package_manifests = package_manifests
64        .iter()
65        .map(|manifest| OmenaResolverStylePackageManifestV0 {
66            package_json_path: manifest.package_json_path.clone(),
67            package_json_source: manifest.package_json_source.clone(),
68        })
69        .collect::<Vec<_>>();
70    omena_bridge::resolve_omena_bridge_style_uri_for_specifier_with_package_manifests(
71        base_document_uri,
72        workspace_folder_uri,
73        specifier,
74        resolver_package_manifests.as_slice(),
75    )
76}
77
78pub fn resolve_omena_query_style_uri_for_specifier_with_resolution_inputs(
79    base_document_uri: &str,
80    workspace_folder_uri: Option<&str>,
81    specifier: &str,
82    resolution_inputs: &OmenaQueryStyleResolutionInputsV0,
83) -> Option<String> {
84    let bridge_inputs = omena_bridge::OmenaBridgeStyleResolutionInputsV0 {
85        package_manifests: resolution_inputs
86            .package_manifests
87            .iter()
88            .map(|manifest| OmenaResolverStylePackageManifestV0 {
89                package_json_path: manifest.package_json_path.clone(),
90                package_json_source: manifest.package_json_source.clone(),
91            })
92            .collect(),
93        tsconfig_path_mappings: resolution_inputs.tsconfig_path_mappings.clone(),
94        bundler_path_mappings: resolution_inputs.bundler_path_mappings.clone(),
95        disk_style_path_identities: resolution_inputs.disk_style_path_identities.clone(),
96    };
97    omena_bridge::resolve_omena_bridge_style_uri_for_specifier_with_resolution_inputs(
98        base_document_uri,
99        workspace_folder_uri,
100        specifier,
101        &bridge_inputs,
102    )
103}
104
105pub fn load_omena_query_workspace_style_resolution_inputs(
106    workspace_folder_uri: Option<&str>,
107    configured_package_manifests: &[OmenaQueryStylePackageManifestV0],
108) -> OmenaQueryStyleResolutionInputsV0 {
109    let resolver_package_manifests = configured_package_manifests
110        .iter()
111        .map(|manifest| OmenaResolverStylePackageManifestV0 {
112            package_json_path: manifest.package_json_path.clone(),
113            package_json_source: manifest.package_json_source.clone(),
114        })
115        .collect::<Vec<_>>();
116    let bridge_inputs = omena_bridge::load_omena_bridge_workspace_style_resolution_inputs(
117        workspace_folder_uri,
118        resolver_package_manifests.as_slice(),
119    );
120    OmenaQueryStyleResolutionInputsV0 {
121        package_manifests: bridge_inputs
122            .package_manifests
123            .into_iter()
124            .map(|manifest| OmenaQueryStylePackageManifestV0 {
125                package_json_path: manifest.package_json_path,
126                package_json_source: manifest.package_json_source,
127            })
128            .collect(),
129        tsconfig_path_mappings: bridge_inputs.tsconfig_path_mappings,
130        bundler_path_mappings: bridge_inputs.bundler_path_mappings,
131        disk_style_path_identities: bridge_inputs.disk_style_path_identities,
132        external_sif_cache_fingerprint: None,
133    }
134}
135
136#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
137#[serde(rename_all = "camelCase")]
138pub struct OmenaQueryBridgeExternalSifResolutionV0 {
139    pub external_sifs: Vec<OmenaQueryExternalSifInputV0>,
140    pub bridge_urls: Vec<String>,
141    pub generation_count: usize,
142}
143
144pub fn resolve_omena_query_bridge_external_sifs_for_style_sources(
145    style_sources: &[OmenaQueryStyleSourceInputV0],
146    existing_external_sifs: &[OmenaQueryExternalSifInputV0],
147    resolution_inputs: &OmenaQueryStyleResolutionInputsV0,
148) -> OmenaQueryBridgeExternalSifResolutionV0 {
149    let seeds = style_sources
150        .iter()
151        .flat_map(|source| bridge_external_sif_seeds_for_style_source(source, resolution_inputs))
152        .collect::<BTreeSet<_>>();
153    resolve_omena_query_bridge_external_sifs_for_seed_pairs(
154        seeds.into_iter(),
155        existing_external_sifs,
156        resolution_inputs,
157    )
158}
159
160pub fn resolve_omena_query_bridge_external_sifs_for_seed_pairs(
161    seeds: impl Iterator<Item = (String, String)>,
162    existing_external_sifs: &[OmenaQueryExternalSifInputV0],
163    resolution_inputs: &OmenaQueryStyleResolutionInputsV0,
164) -> OmenaQueryBridgeExternalSifResolutionV0 {
165    let mut state =
166        BridgeExternalSifResolutionState::new(existing_external_sifs, resolution_inputs);
167
168    for (verbatim_source, resolved_url) in seeds {
169        state.enqueue_alias(verbatim_source, resolved_url);
170    }
171
172    while let Some(sif) = state.worklist.pop_front() {
173        let base_file_uri = sif.canonical_url.clone();
174        for forward in &sif.exports.forwards {
175            let specifier = forward.canonical_url.as_str();
176            if !bridge_external_sif_specifier_is_readable(specifier) {
177                continue;
178            }
179            let Some(child_url) =
180                resolve_omena_query_style_uri_for_specifier_with_resolution_inputs(
181                    base_file_uri.as_str(),
182                    None,
183                    specifier,
184                    state.resolution_inputs,
185                )
186                .filter(|uri| uri.starts_with("file://"))
187            else {
188                continue;
189            };
190            let alias_key = if specifier.starts_with('.') || specifier.starts_with("file://") {
191                child_url.clone()
192            } else {
193                specifier.to_string()
194            };
195            state.enqueue_alias(alias_key, child_url);
196        }
197    }
198
199    state.into_resolution()
200}
201
202struct BridgeExternalSifResolutionState<'a> {
203    resolution_inputs: &'a OmenaQueryStyleResolutionInputsV0,
204    emitted_keys: BTreeSet<String>,
205    generated_by_resolved_url: BTreeMap<String, omena_sif::OmenaSifV1>,
206    bridge_urls: BTreeSet<String>,
207    external_sifs: Vec<OmenaQueryExternalSifInputV0>,
208    worklist: VecDeque<omena_sif::OmenaSifV1>,
209    generation_count: usize,
210}
211
212impl<'a> BridgeExternalSifResolutionState<'a> {
213    fn new(
214        existing_external_sifs: &[OmenaQueryExternalSifInputV0],
215        resolution_inputs: &'a OmenaQueryStyleResolutionInputsV0,
216    ) -> Self {
217        Self {
218            resolution_inputs,
219            emitted_keys: existing_external_sifs
220                .iter()
221                .flat_map(|input| [input.canonical_url.clone(), input.sif.canonical_url.clone()])
222                .collect(),
223            generated_by_resolved_url: existing_external_sifs
224                .iter()
225                .map(|input| (input.sif.canonical_url.clone(), input.sif.clone()))
226                .collect(),
227            bridge_urls: BTreeSet::new(),
228            external_sifs: Vec::new(),
229            worklist: VecDeque::new(),
230            generation_count: 0,
231        }
232    }
233
234    fn into_resolution(self) -> OmenaQueryBridgeExternalSifResolutionV0 {
235        OmenaQueryBridgeExternalSifResolutionV0 {
236            external_sifs: self.external_sifs,
237            bridge_urls: self.bridge_urls.into_iter().collect(),
238            generation_count: self.generation_count,
239        }
240    }
241
242    fn enqueue_alias(&mut self, alias_key: String, resolved_url: String) {
243        if self.emitted_keys.contains(alias_key.as_str()) {
244            return;
245        }
246        self.bridge_urls.insert(alias_key.clone());
247        self.bridge_urls.insert(resolved_url.clone());
248        if let Some(sif) = self
249            .generated_by_resolved_url
250            .get(resolved_url.as_str())
251            .cloned()
252        {
253            self.emitted_keys.insert(alias_key.clone());
254            self.emitted_keys.insert(sif.canonical_url.clone());
255            self.external_sifs.push(OmenaQueryExternalSifInputV0 {
256                canonical_url: alias_key,
257                sif,
258            });
259            return;
260        }
261        let cache_context = omena_bridge::OmenaBridgeExternalSifCacheContextV0 {
262            freshness_fingerprint: self
263                .resolution_inputs
264                .external_sif_cache_fingerprint
265                .clone(),
266        };
267        let Ok(sif) = generate_omena_bridge_sif_for_resolved_style_path_with_cache_context(
268            resolved_url.as_str(),
269            &cache_context,
270        ) else {
271            return;
272        };
273        self.generation_count = self.generation_count.saturating_add(1);
274        self.generated_by_resolved_url
275            .insert(sif.canonical_url.clone(), sif.clone());
276        self.emitted_keys.insert(alias_key.clone());
277        self.emitted_keys.insert(sif.canonical_url.clone());
278        self.bridge_urls.insert(sif.canonical_url.clone());
279        self.worklist.push_back(sif.clone());
280        self.external_sifs.push(OmenaQueryExternalSifInputV0 {
281            canonical_url: alias_key,
282            sif,
283        });
284    }
285}
286
287fn bridge_external_sif_seeds_for_style_source(
288    source: &OmenaQueryStyleSourceInputV0,
289    resolution_inputs: &OmenaQueryStyleResolutionInputsV0,
290) -> Vec<(String, String)> {
291    let Some(module_sources) =
292        summarize_omena_query_sass_module_sources(&source.style_path, &source.style_source)
293    else {
294        return Vec::new();
295    };
296    let base_uri = style_source_path_as_file_uri(source.style_path.as_str());
297    module_sources
298        .module_use_edges
299        .iter()
300        .map(|edge| edge.source.as_str())
301        .chain(
302            module_sources
303                .module_forward_sources
304                .iter()
305                .map(String::as_str),
306        )
307        .filter_map(|specifier| {
308            if !bridge_external_sif_specifier_is_readable(specifier) {
309                return None;
310            }
311            let resolved_url = if specifier.starts_with("file://") {
312                specifier.to_string()
313            } else {
314                resolve_omena_query_style_uri_for_specifier_with_resolution_inputs(
315                    base_uri.as_str(),
316                    None,
317                    specifier,
318                    resolution_inputs,
319                )?
320            };
321            resolved_url
322                .starts_with("file://")
323                .then(|| (specifier.to_string(), resolved_url))
324        })
325        .collect()
326}
327
328fn bridge_external_sif_specifier_is_readable(specifier: &str) -> bool {
329    !specifier.starts_with("sass:")
330        && !specifier.starts_with("http://")
331        && !specifier.starts_with("https://")
332}
333
334fn style_source_path_as_file_uri(path: &str) -> String {
335    if path.starts_with("file://") {
336        path.to_string()
337    } else {
338        format!("file://{path}")
339    }
340}
341
342pub fn summarize_omena_query_source_syntax_index(
343    source: &str,
344    imported_style_bindings: Vec<OmenaQuerySourceImportedStyleBindingV0>,
345    classnames_bind_bindings: Vec<String>,
346) -> OmenaQuerySourceSyntaxIndexV0 {
347    omena_bridge::summarize_omena_bridge_source_syntax_index(
348        source,
349        imported_style_bindings,
350        classnames_bind_bindings,
351    )
352}
353
354pub fn summarize_omena_query_source_syntax_index_for_source_language(
355    source_path: &str,
356    source: &str,
357    source_language: Option<&str>,
358    imported_style_bindings: Vec<OmenaQuerySourceImportedStyleBindingV0>,
359    classnames_bind_bindings: Vec<String>,
360) -> OmenaQuerySourceSyntaxIndexV0 {
361    omena_bridge::summarize_omena_bridge_source_syntax_index_for_source_language(
362        source_path,
363        source,
364        source_language,
365        imported_style_bindings,
366        classnames_bind_bindings,
367    )
368}
369
370pub fn summarize_omena_query_source_binding_index(
371    source: &str,
372    imported_style_bindings: Vec<OmenaQuerySourceImportedStyleBindingV0>,
373    classnames_bind_bindings: Vec<String>,
374) -> OmenaQuerySourceBindingIndexV0 {
375    omena_bridge::summarize_omena_bridge_source_binding_index(
376        source,
377        imported_style_bindings,
378        classnames_bind_bindings,
379    )
380}
381
382pub fn summarize_omena_query_source_binding_index_for_source_language(
383    source_path: &str,
384    source: &str,
385    source_language: Option<&str>,
386    imported_style_bindings: Vec<OmenaQuerySourceImportedStyleBindingV0>,
387    classnames_bind_bindings: Vec<String>,
388) -> OmenaQuerySourceBindingIndexV0 {
389    omena_bridge::summarize_omena_bridge_source_binding_index_for_source_language(
390        source_path,
391        source,
392        source_language,
393        imported_style_bindings,
394        classnames_bind_bindings,
395    )
396}
397
398pub fn summarize_omena_query_source_control_flow_graph_for_source_language(
399    source_path: &str,
400    source: &str,
401    source_language: Option<&str>,
402    variable_name: &str,
403    reference_byte_offset: usize,
404) -> Option<crate::OmenaQuerySourceControlFlowGraphCaptureV0> {
405    omena_bridge::summarize_omena_bridge_source_control_flow_graph_for_source_language(
406        source_path,
407        source,
408        source_language,
409        variable_name,
410        reference_byte_offset,
411    )
412}
413
414#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
415#[serde(rename_all = "camelCase")]
416pub struct OmenaQuerySourcePrecisionReferenceV0 {
417    pub schema_version: &'static str,
418    pub product: &'static str,
419    pub source_path: String,
420    #[serde(skip_serializing_if = "Option::is_none")]
421    pub source_language: Option<String>,
422    pub variable_name: String,
423    pub reference_byte_offset: usize,
424    pub resolved_tier: &'static str,
425    pub resolved_value: AbstractClassValueV0,
426    pub precision: OmenaQueryAnalysisPrecisionV0,
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub top_cause: Option<&'static str>,
429}
430
431pub fn resolve_omena_query_source_precision_for_source(
432    source_path: &str,
433    source: &str,
434    source_language: Option<&str>,
435    variable_name: &str,
436    reference_byte_offset: usize,
437) -> OmenaQuerySourcePrecisionReferenceV0 {
438    let precision =
439        source_diagnostic_precision("classValueResolution", "sourceControlFlow", "sameFile");
440    let Some(capture) = summarize_omena_query_source_control_flow_graph_for_source_language(
441        source_path,
442        source,
443        source_language,
444        variable_name,
445        reference_byte_offset,
446    ) else {
447        return source_precision_reference(
448            source_path,
449            source_language,
450            variable_name,
451            reference_byte_offset,
452            top_class_value(),
453            precision,
454            Some("noFlowCapture"),
455        );
456    };
457
458    let resolved_flow = resolve_source_precision_flow_from_snapshot(
459        &capture.snapshot,
460        capture.binding.symbol_ordinal,
461    )
462    .unwrap_or(ResolvedSourcePrecisionFlowV0 {
463        value: top_class_value(),
464        top_cause: Some("ambiguousFlowSnapshot"),
465    });
466    let top_cause = if abstract_class_value_kind(&resolved_flow.value) == "top" {
467        resolved_flow.top_cause
468    } else {
469        None
470    };
471
472    source_precision_reference(
473        source_path,
474        source_language,
475        variable_name,
476        reference_byte_offset,
477        resolved_flow.value,
478        precision,
479        top_cause,
480    )
481}
482
483#[derive(Clone, PartialEq, Eq)]
484struct ResolvedSourcePrecisionFlowV0 {
485    value: AbstractClassValueV0,
486    top_cause: Option<&'static str>,
487}
488
489fn resolve_source_precision_flow_from_snapshot(
490    snapshot: &crate::OmenaQuerySourceFlowBlockGraphSnapshotV0,
491    symbol_ordinal: usize,
492) -> Option<ResolvedSourcePrecisionFlowV0> {
493    let predecessors = source_precision_predecessor_block_ids(&snapshot.blocks);
494    let mut states = snapshot
495        .blocks
496        .iter()
497        .map(|block| (block.id.clone(), None::<ResolvedSourcePrecisionFlowV0>))
498        .collect::<BTreeMap<_, _>>();
499
500    for _ in 0..std::cmp::max(snapshot.blocks.len() * 2, 1) {
501        let mut changed = false;
502        for block in &snapshot.blocks {
503            let incoming = source_precision_incoming_state(block, &predecessors, &states);
504            let next = apply_source_precision_block(block, symbol_ordinal, incoming);
505            if states.get(&block.id).and_then(Clone::clone) != next {
506                states.insert(block.id.clone(), next);
507                changed = true;
508            }
509        }
510        if !changed {
511            break;
512        }
513    }
514
515    let exit = snapshot
516        .blocks
517        .iter()
518        .find(|block| block.id == "exit")
519        .or_else(|| snapshot.blocks.last())?;
520    states.get(&exit.id).and_then(Clone::clone)
521}
522
523fn source_precision_predecessor_block_ids(
524    blocks: &[crate::OmenaQuerySourceFlowBlockSnapshotV0],
525) -> BTreeMap<String, Vec<String>> {
526    let mut predecessors = BTreeMap::<String, Vec<String>>::new();
527    for block in blocks {
528        for successor in &block.successor_block_ids {
529            predecessors
530                .entry(successor.clone())
531                .or_default()
532                .push(block.id.clone());
533        }
534    }
535    predecessors
536}
537
538fn source_precision_incoming_state(
539    block: &crate::OmenaQuerySourceFlowBlockSnapshotV0,
540    predecessors: &BTreeMap<String, Vec<String>>,
541    states: &BTreeMap<String, Option<ResolvedSourcePrecisionFlowV0>>,
542) -> Option<ResolvedSourcePrecisionFlowV0> {
543    predecessors
544        .get(&block.id)
545        .into_iter()
546        .flat_map(|ids| ids.iter())
547        .filter_map(|id| states.get(id).and_then(Clone::clone))
548        .reduce(join_source_precision_flows)
549}
550
551fn apply_source_precision_block(
552    block: &crate::OmenaQuerySourceFlowBlockSnapshotV0,
553    symbol_ordinal: usize,
554    incoming: Option<ResolvedSourcePrecisionFlowV0>,
555) -> Option<ResolvedSourcePrecisionFlowV0> {
556    if block.symbol_ordinal != Some(symbol_ordinal)
557        || !matches!(block.transfer_kind, "assignFacts" | "concatFacts")
558    {
559        return incoming;
560    }
561
562    let Some(facts) = block.facts.as_ref() else {
563        return Some(ResolvedSourcePrecisionFlowV0 {
564            value: top_class_value(),
565            top_cause: Some("missingValueFacts"),
566        });
567    };
568
569    let external_facts = ExternalStringTypeFactsV0 {
570        kind: facts.kind.clone(),
571        constraint_kind: facts.constraint_kind.clone(),
572        values: facts.values.clone(),
573        prefix: facts.prefix.clone(),
574        suffix: facts.suffix.clone(),
575        min_len: facts.min_len,
576        max_len: facts.max_len,
577        char_must: facts.char_must.clone(),
578        char_may: facts.char_may.clone(),
579        may_include_other_chars: facts.may_include_other_chars,
580    };
581
582    Some(ResolvedSourcePrecisionFlowV0 {
583        value: abstract_class_value_from_facts(&external_facts),
584        top_cause: None,
585    })
586}
587
588fn join_source_precision_flows(
589    left: ResolvedSourcePrecisionFlowV0,
590    right: ResolvedSourcePrecisionFlowV0,
591) -> ResolvedSourcePrecisionFlowV0 {
592    let value = join_abstract_class_values(&left.value, &right.value);
593    let top_cause = if abstract_class_value_kind(&value) == "top" {
594        left.top_cause.or(right.top_cause).or(Some("joinedTop"))
595    } else {
596        None
597    };
598    ResolvedSourcePrecisionFlowV0 { value, top_cause }
599}
600
601fn source_precision_reference(
602    source_path: &str,
603    source_language: Option<&str>,
604    variable_name: &str,
605    reference_byte_offset: usize,
606    resolved_value: AbstractClassValueV0,
607    precision: OmenaQueryAnalysisPrecisionV0,
608    top_cause: Option<&'static str>,
609) -> OmenaQuerySourcePrecisionReferenceV0 {
610    let resolved_tier = abstract_class_value_kind(&resolved_value);
611    OmenaQuerySourcePrecisionReferenceV0 {
612        schema_version: "0",
613        product: "omena-query.source-precision-reference",
614        source_path: source_path.to_string(),
615        source_language: source_language.map(str::to_string),
616        variable_name: variable_name.to_string(),
617        reference_byte_offset,
618        resolved_tier,
619        resolved_value,
620        precision,
621        top_cause,
622    }
623}
624
625pub fn summarize_omena_query_source_type_fact_control_flow_graph_for_source_language(
626    source_path: &str,
627    source: &str,
628    source_language: Option<&str>,
629    variable_name: &str,
630    reference_byte_offset: usize,
631) -> Option<crate::OmenaQuerySourceTypeFactControlFlowGraphV0> {
632    omena_bridge::summarize_omena_bridge_source_type_fact_control_flow_graph_for_source_language(
633        source_path,
634        source,
635        source_language,
636        variable_name,
637        reference_byte_offset,
638    )
639}
640
641pub fn collect_omena_query_vue_style_module_bindings(
642    source_path: &str,
643    source: &str,
644    source_language: Option<&str>,
645) -> Vec<String> {
646    omena_bridge::collect_omena_bridge_vue_style_module_bindings(
647        source_path,
648        source,
649        source_language,
650    )
651}
652
653pub fn canonicalize_omena_query_source_selector_references(
654    references: &mut Vec<OmenaQuerySourceSelectorReferenceFactV0>,
655) {
656    omena_bridge::canonicalize_source_selector_references(references);
657}