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}