Skip to main content

omena_query/
lib.rs

1use engine_input_producers::{
2    EngineInputV2, ExpressionDomainControlFlowAnalysisV0, ExpressionDomainFlowAnalysisV0,
3    ExpressionSemanticsCanonicalProducerSignalV0, ExpressionSemanticsQueryFragmentsV0,
4    SelectorUsageCanonicalProducerSignalV0, SelectorUsageQueryFragmentsV0,
5    SourceResolutionCanonicalProducerSignalV0, SourceResolutionQueryFragmentsV0,
6    collect_expression_domain_flow_graphs, summarize_expression_domain_control_flow_analysis_input,
7    summarize_expression_domain_flow_analysis_input,
8    summarize_expression_semantics_canonical_producer_signal_input,
9    summarize_expression_semantics_query_fragments_input,
10    summarize_selector_usage_canonical_producer_signal_input,
11    summarize_selector_usage_query_fragments_input,
12};
13use std::collections::{BTreeMap, BTreeSet, VecDeque};
14use std::path::{Component, Path, PathBuf};
15
16use engine_style_parser::{
17    AtRuleKind, Stylesheet, SyntaxNodePayload, parse_style_module,
18    summarize_css_modules_intermediate,
19};
20pub use engine_style_parser::{ParserByteSpanV0, ParserPositionV0, ParserRangeV0, StyleLanguage};
21use omena_abstract_value::{
22    AbstractValueDomainSummaryV0, ClassValueFlowAnalysisV0, ClassValueFlowIncrementalAnalysisV0,
23    SelectorProjectionCertaintyV0, analyze_class_value_flow_incremental_with_database,
24    project_abstract_value_selectors, summarize_omena_abstract_value_domain,
25};
26use omena_bridge::{
27    DesignTokenExternalDeclarationCandidateScopeV0, DesignTokenWorkspaceDeclarationFactV0,
28    StyleSemanticGraphSummaryV0, collect_omena_bridge_design_token_workspace_declarations,
29    summarize_omena_bridge_style_semantic_graph_for_path_with_scoped_workspace_declarations,
30    summarize_omena_bridge_style_semantic_graph_from_source,
31};
32pub use omena_bridge::{
33    SourceImportDeclarationSummaryV0 as OmenaQuerySourceImportDeclarationSummaryV0,
34    SourceImportDeclarationV0 as OmenaQuerySourceImportDeclarationV0,
35    SourceImportedStyleBindingV0 as OmenaQuerySourceImportedStyleBindingV0,
36    SourceSelectorReferenceFactV0 as OmenaQuerySourceSelectorReferenceFactV0,
37    SourceSelectorReferenceMatchKindV0 as OmenaQuerySourceSelectorReferenceMatchKindV0,
38    SourceSyntaxIndexV0 as OmenaQuerySourceSyntaxIndexV0,
39    SourceTypeFactTargetV0 as OmenaQuerySourceTypeFactTargetV0,
40};
41use omena_incremental::OmenaIncrementalDatabaseV0;
42use omena_resolver::{
43    OmenaResolverSourceResolutionRuntimeIndexV0,
44    summarize_omena_resolver_canonical_producer_signal, summarize_omena_resolver_query_fragments,
45    summarize_omena_resolver_source_resolution_runtime,
46};
47use serde::Serialize;
48
49pub fn summarize_omena_query_source_import_declarations(
50    source: &str,
51) -> OmenaQuerySourceImportDeclarationSummaryV0 {
52    omena_bridge::summarize_omena_bridge_source_import_declarations(source)
53}
54
55pub fn resolve_omena_query_style_uri_for_specifier(
56    base_document_uri: &str,
57    workspace_folder_uri: Option<&str>,
58    specifier: &str,
59) -> Option<String> {
60    omena_bridge::resolve_omena_bridge_style_uri_for_specifier(
61        base_document_uri,
62        workspace_folder_uri,
63        specifier,
64    )
65}
66
67pub fn summarize_omena_query_source_syntax_index(
68    source: &str,
69    imported_style_bindings: Vec<OmenaQuerySourceImportedStyleBindingV0>,
70    classnames_bind_bindings: Vec<String>,
71) -> OmenaQuerySourceSyntaxIndexV0 {
72    omena_bridge::summarize_omena_bridge_source_syntax_index(
73        source,
74        imported_style_bindings,
75        classnames_bind_bindings,
76    )
77}
78
79pub fn canonicalize_omena_query_source_selector_references(
80    references: &mut Vec<OmenaQuerySourceSelectorReferenceFactV0>,
81) {
82    omena_bridge::canonicalize_source_selector_references(references);
83}
84
85#[derive(Debug, Serialize)]
86#[serde(rename_all = "camelCase")]
87pub struct OmenaQueryBoundarySummaryV0 {
88    pub schema_version: &'static str,
89    pub product: &'static str,
90    pub query_engine_name: &'static str,
91    pub input_version: String,
92    pub abstract_value_domain: AbstractValueDomainSummaryV0,
93    pub selected_query_adapter_capabilities: SelectedQueryAdapterCapabilitiesV0,
94    pub delegated_fragment_products: Vec<&'static str>,
95    pub expression_semantics_query_count: usize,
96    pub source_resolution_query_count: usize,
97    pub selector_usage_query_count: usize,
98    pub total_query_count: usize,
99    pub ready_surfaces: Vec<&'static str>,
100    pub cme_coupled_surfaces: Vec<&'static str>,
101    pub next_decoupling_targets: Vec<&'static str>,
102}
103
104#[derive(Debug, Serialize)]
105#[serde(rename_all = "camelCase")]
106pub struct OmenaQueryFragmentBundleV0 {
107    pub schema_version: &'static str,
108    pub product: &'static str,
109    pub input_version: String,
110    pub expression_semantics: ExpressionSemanticsQueryFragmentsV0,
111    pub source_resolution: SourceResolutionQueryFragmentsV0,
112    pub selector_usage: SelectorUsageQueryFragmentsV0,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
116#[serde(rename_all = "camelCase")]
117pub struct SelectedQueryAdapterCapabilitiesV0 {
118    pub schema_version: &'static str,
119    pub product: &'static str,
120    pub default_candidate_backend: &'static str,
121    pub backend_kinds: Vec<SelectedQueryBackendCapabilityV0>,
122    pub runner_commands: Vec<SelectedQueryRunnerCommandV0>,
123    pub expression_semantics_payload_contracts: Vec<&'static str>,
124    pub required_input_contracts: Vec<&'static str>,
125    pub adapter_readiness: Vec<&'static str>,
126    pub routing_status: &'static str,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
130#[serde(rename_all = "camelCase")]
131pub struct SelectedQueryBackendCapabilityV0 {
132    pub backend_kind: &'static str,
133    pub source_resolution: bool,
134    pub expression_semantics: bool,
135    pub selector_usage: bool,
136    pub style_semantic_graph: bool,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
140#[serde(rename_all = "camelCase")]
141pub struct SelectedQueryRunnerCommandV0 {
142    pub surface: &'static str,
143    pub command: &'static str,
144    pub input_contract: &'static str,
145    pub output_product: &'static str,
146}
147
148#[derive(Debug, Serialize)]
149#[serde(rename_all = "camelCase")]
150pub struct OmenaQueryStyleSemanticGraphBatchOutputV0 {
151    pub schema_version: &'static str,
152    pub product: &'static str,
153    pub graphs: Vec<OmenaQueryStyleSemanticGraphBatchEntryV0>,
154}
155
156#[derive(Debug, Serialize)]
157#[serde(rename_all = "camelCase")]
158pub struct OmenaQueryStyleSemanticGraphBatchEntryV0 {
159    pub style_path: String,
160    pub graph: Option<StyleSemanticGraphSummaryV0>,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
164#[serde(rename_all = "camelCase")]
165pub struct OmenaQueryStyleDocumentSummaryV0 {
166    pub schema_version: &'static str,
167    pub product: &'static str,
168    pub language: &'static str,
169    pub selector_names: Vec<String>,
170    pub custom_property_decl_names: Vec<String>,
171    pub custom_property_ref_names: Vec<String>,
172    pub sass_module_use_sources: Vec<String>,
173    pub sass_module_forward_sources: Vec<String>,
174    pub diagnostic_count: usize,
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
178#[serde(rename_all = "camelCase")]
179pub struct OmenaQueryStyleHoverCandidateV0 {
180    pub kind: &'static str,
181    pub name: String,
182    pub range: ParserRangeV0,
183    pub source: &'static str,
184    pub namespace: Option<String>,
185}
186
187#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
188#[serde(rename_all = "camelCase")]
189pub struct OmenaQueryStyleHoverCandidatesV0 {
190    pub schema_version: &'static str,
191    pub product: &'static str,
192    pub language: &'static str,
193    pub candidates: Vec<OmenaQueryStyleHoverCandidateV0>,
194}
195
196#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
197#[serde(rename_all = "camelCase")]
198pub struct OmenaQueryStyleHoverRenderPartsV0 {
199    pub schema_version: &'static str,
200    pub product: &'static str,
201    pub snippet: String,
202    pub value: Option<String>,
203    pub signature: Option<String>,
204    pub render_source: &'static str,
205}
206
207#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
208#[serde(rename_all = "camelCase")]
209pub struct OmenaQueryStyleDiagnosticV0 {
210    pub code: &'static str,
211    pub range: ParserRangeV0,
212    pub message: String,
213    pub create_custom_property: Option<OmenaQueryCreateCustomPropertyActionV0>,
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
217#[serde(rename_all = "camelCase")]
218pub struct OmenaQueryCreateCustomPropertyActionV0 {
219    pub uri: String,
220    pub range: ParserRangeV0,
221    pub new_text: String,
222    pub property_name: String,
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
226#[serde(rename_all = "camelCase")]
227pub struct OmenaQuerySourceDiagnosticV0 {
228    pub code: &'static str,
229    pub range: ParserRangeV0,
230    pub message: String,
231    pub create_selector: Option<OmenaQueryCreateSelectorActionV0>,
232}
233
234#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
235#[serde(rename_all = "camelCase")]
236pub struct OmenaQueryCreateSelectorActionV0 {
237    pub uri: String,
238    pub range: ParserRangeV0,
239    pub new_text: String,
240    pub selector_name: String,
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
244#[serde(rename_all = "camelCase")]
245pub struct OmenaQuerySourceSelectorCandidateV0 {
246    pub kind: &'static str,
247    pub name: String,
248    pub range: ParserRangeV0,
249    pub source: &'static str,
250    pub target_style_uri: Option<String>,
251}
252
253#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
254#[serde(rename_all = "camelCase")]
255pub struct OmenaQueryStyleSelectorDefinitionV0 {
256    pub uri: String,
257    pub name: String,
258    pub range: ParserRangeV0,
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
262#[serde(rename_all = "camelCase")]
263pub struct OmenaQuerySourceProviderCandidateResolutionV0 {
264    pub schema_version: &'static str,
265    pub product: &'static str,
266    pub matched: Vec<OmenaQuerySourceSelectorCandidateV0>,
267    pub unresolved: Vec<OmenaQuerySourceSelectorCandidateV0>,
268}
269
270#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
271#[serde(rename_all = "camelCase")]
272pub struct OmenaQuerySourceSelectorReferenceEditTargetV0 {
273    pub uri: String,
274    pub name: String,
275    pub range: ParserRangeV0,
276    pub target_style_uri: Option<String>,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
280#[serde(rename_all = "camelCase")]
281pub struct OmenaQueryWorkspaceTextEditV0 {
282    pub uri: String,
283    pub range: ParserRangeV0,
284    pub new_text: String,
285}
286
287#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
288#[serde(rename_all = "camelCase")]
289pub struct OmenaQuerySassModuleUseEdgeV0 {
290    pub source: String,
291    pub namespace_kind: &'static str,
292    pub namespace: Option<String>,
293}
294
295#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
296#[serde(rename_all = "camelCase")]
297pub struct OmenaQuerySassModuleSourcesV0 {
298    pub schema_version: &'static str,
299    pub product: &'static str,
300    pub module_use_edges: Vec<OmenaQuerySassModuleUseEdgeV0>,
301    pub module_forward_sources: Vec<String>,
302}
303
304#[derive(Debug, Clone, PartialEq, Eq)]
305pub struct OmenaQueryStylePackageManifestV0 {
306    pub package_json_path: String,
307    pub package_json_source: String,
308}
309
310#[derive(Debug, Serialize)]
311#[serde(rename_all = "camelCase")]
312pub struct OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
313    pub schema_version: &'static str,
314    pub product: &'static str,
315    pub input_version: String,
316    pub revision: u64,
317    pub graph_count: usize,
318    pub dirty_graph_count: usize,
319    pub reused_graph_count: usize,
320    pub analyses: Vec<OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0>,
321}
322
323#[derive(Debug, Serialize)]
324#[serde(rename_all = "camelCase")]
325pub struct OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0 {
326    pub graph_id: String,
327    pub file_path: String,
328    pub analysis: ClassValueFlowIncrementalAnalysisV0,
329}
330
331#[derive(Debug, Serialize)]
332#[serde(rename_all = "camelCase")]
333pub struct OmenaQueryExpressionDomainSelectorProjectionV0 {
334    pub schema_version: &'static str,
335    pub product: &'static str,
336    pub input_version: String,
337    pub projection_count: usize,
338    pub projections: Vec<OmenaQueryExpressionDomainSelectorProjectionEntryV0>,
339}
340
341#[derive(Debug, Serialize)]
342#[serde(rename_all = "camelCase")]
343pub struct OmenaQueryExpressionDomainSelectorProjectionEntryV0 {
344    pub graph_id: String,
345    pub file_path: String,
346    pub node_id: String,
347    pub target_style_paths: Vec<String>,
348    pub value_kind: &'static str,
349    pub selector_names: Vec<String>,
350    pub certainty: SelectorProjectionCertaintyV0,
351}
352
353#[derive(Default)]
354pub struct OmenaQueryExpressionDomainFlowRuntimeV0 {
355    revision: u64,
356    databases_by_graph_id: BTreeMap<String, OmenaIncrementalDatabaseV0>,
357    previous_analyses_by_graph_id: BTreeMap<String, ClassValueFlowAnalysisV0>,
358}
359
360pub fn summarize_omena_query_boundary(input: &EngineInputV2) -> OmenaQueryBoundarySummaryV0 {
361    let fragment_bundle = summarize_omena_query_fragment_bundle(input);
362    let expression_semantics_query_count = fragment_bundle.expression_semantics.fragments.len();
363    let source_resolution_query_count = fragment_bundle.source_resolution.fragments.len();
364    let selector_usage_query_count = fragment_bundle.selector_usage.fragments.len();
365
366    OmenaQueryBoundarySummaryV0 {
367        schema_version: "0",
368        product: "omena-query.boundary",
369        query_engine_name: "omena-query",
370        input_version: input.version.clone(),
371        abstract_value_domain: summarize_omena_abstract_value_domain(),
372        selected_query_adapter_capabilities:
373            summarize_omena_query_selected_query_adapter_capabilities(),
374        delegated_fragment_products: vec![
375            "engine-input-producers.expression-semantics-query-fragments",
376            "engine-input-producers.source-resolution-query-fragments",
377            "omena-resolver.boundary",
378            "omena-resolver.source-resolution-runtime-index",
379            "engine-input-producers.selector-usage-query-fragments",
380            "engine-input-producers.expression-domain-flow-analysis",
381            "engine-input-producers.expression-domain-control-flow-analysis",
382            "omena-query.expression-domain-incremental-flow-analysis",
383            "omena-query.expression-domain-selector-projection",
384        ],
385        expression_semantics_query_count,
386        source_resolution_query_count,
387        selector_usage_query_count,
388        total_query_count: expression_semantics_query_count
389            + source_resolution_query_count
390            + selector_usage_query_count,
391        ready_surfaces: vec![
392            "queryFragmentBundle",
393            "abstractValueProjectionContract",
394            "sourceResolutionResolverBoundary",
395            "sourceResolutionRuntimeIndex",
396            "expressionDomainFlowAnalysisBoundary",
397            "expressionDomainControlFlowAnalysisBoundary",
398            "expressionDomainSalsaRuntime",
399            "expressionDomainSelectorProjection",
400            "styleHoverRenderParts",
401            "styleMissingCustomPropertyDiagnostics",
402            "sourceMissingSelectorDiagnostics",
403            "sourceProviderCandidateResolution",
404            "selectorRenameEditPlanning",
405            "sassSymbolResolutionPrimitives",
406            "sassModuleSourceSelection",
407            "queryBoundarySummary",
408        ],
409        cme_coupled_surfaces: vec!["EngineInputV2", "producerQueryFragments"],
410        next_decoupling_targets: vec!["queryEvaluationRuntime", "selectedQueryBackendAdapter"],
411    }
412}
413
414pub fn summarize_omena_query_selected_query_adapter_capabilities()
415-> SelectedQueryAdapterCapabilitiesV0 {
416    SelectedQueryAdapterCapabilitiesV0 {
417        schema_version: "0",
418        product: "omena-query.selected-query-adapter-capabilities",
419        default_candidate_backend: "rust-selected-query",
420        backend_kinds: vec![
421            SelectedQueryBackendCapabilityV0 {
422                backend_kind: "typescript-current",
423                source_resolution: false,
424                expression_semantics: false,
425                selector_usage: false,
426                style_semantic_graph: false,
427            },
428            SelectedQueryBackendCapabilityV0 {
429                backend_kind: "rust-source-resolution",
430                source_resolution: true,
431                expression_semantics: false,
432                selector_usage: false,
433                style_semantic_graph: false,
434            },
435            SelectedQueryBackendCapabilityV0 {
436                backend_kind: "rust-expression-semantics",
437                source_resolution: false,
438                expression_semantics: true,
439                selector_usage: false,
440                style_semantic_graph: false,
441            },
442            SelectedQueryBackendCapabilityV0 {
443                backend_kind: "rust-selector-usage",
444                source_resolution: false,
445                expression_semantics: false,
446                selector_usage: true,
447                style_semantic_graph: false,
448            },
449            SelectedQueryBackendCapabilityV0 {
450                backend_kind: "rust-selected-query",
451                source_resolution: true,
452                expression_semantics: true,
453                selector_usage: true,
454                style_semantic_graph: true,
455            },
456        ],
457        runner_commands: vec![
458            SelectedQueryRunnerCommandV0 {
459                surface: "sourceResolution",
460                command: "input-source-resolution-canonical-producer",
461                input_contract: "EngineInputV2",
462                output_product: "engine-input-producers.source-resolution-canonical-producer",
463            },
464            SelectedQueryRunnerCommandV0 {
465                surface: "sourceResolutionRuntime",
466                command: "input-omena-resolver-source-resolution-runtime",
467                input_contract: "EngineInputV2",
468                output_product: "omena-resolver.source-resolution-runtime-index",
469            },
470            SelectedQueryRunnerCommandV0 {
471                surface: "expressionSemantics",
472                command: "input-expression-semantics-canonical-producer",
473                input_contract: "EngineInputV2",
474                output_product: "engine-input-producers.expression-semantics-canonical-producer",
475            },
476            SelectedQueryRunnerCommandV0 {
477                surface: "expressionDomainFlowAnalysis",
478                command: "input-expression-domain-flow-analysis",
479                input_contract: "EngineInputV2",
480                output_product: "engine-input-producers.expression-domain-flow-analysis",
481            },
482            SelectedQueryRunnerCommandV0 {
483                surface: "expressionDomainControlFlowAnalysis",
484                command: "input-expression-domain-control-flow-analysis",
485                input_contract: "EngineInputV2",
486                output_product: "engine-input-producers.expression-domain-control-flow-analysis",
487            },
488            SelectedQueryRunnerCommandV0 {
489                surface: "expressionDomainIncrementalFlowAnalysis",
490                command: "input-expression-domain-incremental-flow-analysis",
491                input_contract: "EngineInputV2 + OmenaQueryExpressionDomainFlowRuntimeV0",
492                output_product: "omena-query.expression-domain-incremental-flow-analysis",
493            },
494            SelectedQueryRunnerCommandV0 {
495                surface: "expressionDomainSelectorProjection",
496                command: "input-expression-domain-selector-projection",
497                input_contract: "EngineInputV2",
498                output_product: "omena-query.expression-domain-selector-projection",
499            },
500            SelectedQueryRunnerCommandV0 {
501                surface: "selectorUsage",
502                command: "input-selector-usage-canonical-producer",
503                input_contract: "EngineInputV2",
504                output_product: "engine-input-producers.selector-usage-canonical-producer",
505            },
506            SelectedQueryRunnerCommandV0 {
507                surface: "styleSemanticGraph",
508                command: "style-semantic-graph",
509                input_contract: "StyleSemanticGraphInputV0",
510                output_product: "omena-semantic.style-semantic-graph",
511            },
512            SelectedQueryRunnerCommandV0 {
513                surface: "styleSemanticGraphBatch",
514                command: "style-semantic-graph-batch",
515                input_contract: "StyleSemanticGraphBatchInputV0",
516                output_product: "omena-semantic.style-semantic-graph-batch",
517            },
518        ],
519        expression_semantics_payload_contracts: vec!["valueDomainKind", "valueDomainDerivation"],
520        required_input_contracts: vec![
521            "EngineInputV2",
522            "StyleSemanticGraphInputV0",
523            "StyleSemanticGraphBatchInputV0",
524        ],
525        adapter_readiness: vec![
526            "backendCapabilityMatrix",
527            "canonicalProducerWrapperBoundary",
528            "styleSemanticGraphBridgeBoundary",
529            "runnerCommandContract",
530            "fragmentBundleBoundary",
531            "sourceResolutionRuntimeIndex",
532            "expressionSemanticsDerivationPayload",
533            "expressionDomainFlowAnalysisRunner",
534            "expressionDomainControlFlowAnalysisRunner",
535            "expressionDomainSalsaRuntime",
536            "expressionDomainSelectorProjection",
537        ],
538        routing_status: "declaredOnly",
539    }
540}
541
542pub fn summarize_omena_query_fragment_bundle(input: &EngineInputV2) -> OmenaQueryFragmentBundleV0 {
543    OmenaQueryFragmentBundleV0 {
544        schema_version: "0",
545        product: "omena-query.fragment-bundle",
546        input_version: input.version.clone(),
547        expression_semantics: summarize_omena_query_expression_semantics_query_fragments(input),
548        source_resolution: summarize_omena_query_source_resolution_query_fragments(input),
549        selector_usage: summarize_omena_query_selector_usage_query_fragments(input),
550    }
551}
552
553pub fn summarize_omena_query_expression_semantics_query_fragments(
554    input: &EngineInputV2,
555) -> ExpressionSemanticsQueryFragmentsV0 {
556    summarize_expression_semantics_query_fragments_input(input)
557}
558
559pub fn summarize_omena_query_expression_domain_flow_analysis(
560    input: &EngineInputV2,
561) -> ExpressionDomainFlowAnalysisV0 {
562    summarize_expression_domain_flow_analysis_input(input)
563}
564
565pub fn summarize_omena_query_expression_domain_control_flow_analysis(
566    input: &EngineInputV2,
567) -> ExpressionDomainControlFlowAnalysisV0 {
568    summarize_expression_domain_control_flow_analysis_input(input)
569}
570
571pub fn summarize_omena_query_expression_domain_incremental_flow_analysis(
572    input: &EngineInputV2,
573    runtime: &mut OmenaQueryExpressionDomainFlowRuntimeV0,
574) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
575    runtime.analyze_input(input)
576}
577
578pub fn summarize_omena_query_expression_domain_selector_projection(
579    input: &EngineInputV2,
580) -> OmenaQueryExpressionDomainSelectorProjectionV0 {
581    let style_selectors_by_path = style_selector_universe_by_path(input);
582    let expression_targets = expression_target_style_paths(input);
583    let flow_analysis = summarize_omena_query_expression_domain_flow_analysis(input);
584    let mut projections = Vec::new();
585
586    for graph in flow_analysis.analyses {
587        for node in graph.analysis.nodes {
588            let target_style_paths = target_style_paths_for_flow_node(
589                node.id.as_str(),
590                node.predecessor_ids.as_slice(),
591                &expression_targets,
592            );
593            let selector_universe = selector_universe_for_targets(
594                target_style_paths.as_slice(),
595                &style_selectors_by_path,
596            );
597            let projection = project_abstract_value_selectors(&node.value, &selector_universe);
598            projections.push(OmenaQueryExpressionDomainSelectorProjectionEntryV0 {
599                graph_id: graph.graph_id.clone(),
600                file_path: graph.file_path.clone(),
601                node_id: node.id,
602                target_style_paths,
603                value_kind: node.value_kind,
604                selector_names: projection.selector_names,
605                certainty: projection.certainty,
606            });
607        }
608    }
609
610    OmenaQueryExpressionDomainSelectorProjectionV0 {
611        schema_version: "0",
612        product: "omena-query.expression-domain-selector-projection",
613        input_version: input.version.clone(),
614        projection_count: projections.len(),
615        projections,
616    }
617}
618
619impl OmenaQueryExpressionDomainFlowRuntimeV0 {
620    pub fn revision(&self) -> u64 {
621        self.revision
622    }
623
624    pub fn graph_count(&self) -> usize {
625        self.databases_by_graph_id.len()
626    }
627
628    pub fn analyze_input(
629        &mut self,
630        input: &EngineInputV2,
631    ) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
632        self.revision += 1;
633        let revision = self.revision;
634        let flow_graphs = collect_expression_domain_flow_graphs(input);
635        let live_graph_ids = flow_graphs
636            .iter()
637            .map(|entry| entry.graph_id.clone())
638            .collect::<BTreeSet<_>>();
639
640        self.databases_by_graph_id
641            .retain(|graph_id, _| live_graph_ids.contains(graph_id));
642        self.previous_analyses_by_graph_id
643            .retain(|graph_id, _| live_graph_ids.contains(graph_id));
644
645        let analyses = flow_graphs
646            .into_iter()
647            .map(|entry| {
648                let database = self
649                    .databases_by_graph_id
650                    .entry(entry.graph_id.clone())
651                    .or_default();
652                let previous_analysis = self.previous_analyses_by_graph_id.get(&entry.graph_id);
653                let analysis = analyze_class_value_flow_incremental_with_database(
654                    &entry.graph,
655                    database,
656                    previous_analysis,
657                    revision,
658                );
659                self.previous_analyses_by_graph_id
660                    .insert(entry.graph_id.clone(), analysis.analysis.clone());
661
662                OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0 {
663                    graph_id: entry.graph_id,
664                    file_path: entry.file_path,
665                    analysis,
666                }
667            })
668            .collect::<Vec<_>>();
669
670        let dirty_graph_count = analyses
671            .iter()
672            .filter(|entry| entry.analysis.incremental_plan.dirty_node_count > 0)
673            .count();
674        let reused_graph_count = analyses
675            .iter()
676            .filter(|entry| entry.analysis.reused_previous_analysis)
677            .count();
678
679        OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
680            schema_version: "0",
681            product: "omena-query.expression-domain-incremental-flow-analysis",
682            input_version: input.version.clone(),
683            revision,
684            graph_count: analyses.len(),
685            dirty_graph_count,
686            reused_graph_count,
687            analyses,
688        }
689    }
690}
691
692fn expression_target_style_paths(input: &EngineInputV2) -> BTreeMap<String, String> {
693    input
694        .sources
695        .iter()
696        .flat_map(|source| source.document.class_expressions.iter())
697        .map(|expression| (expression.id.clone(), expression.scss_module_path.clone()))
698        .collect()
699}
700
701fn style_selector_universe_by_path(input: &EngineInputV2) -> BTreeMap<String, Vec<String>> {
702    input
703        .styles
704        .iter()
705        .map(|style| {
706            let selector_names = style
707                .document
708                .selectors
709                .iter()
710                .map(|selector| {
711                    selector
712                        .canonical_name
713                        .clone()
714                        .unwrap_or_else(|| selector.name.clone())
715                })
716                .collect::<BTreeSet<_>>()
717                .into_iter()
718                .collect::<Vec<_>>();
719            (style.file_path.clone(), selector_names)
720        })
721        .collect()
722}
723
724fn target_style_paths_for_flow_node(
725    node_id: &str,
726    predecessor_ids: &[String],
727    expression_targets: &BTreeMap<String, String>,
728) -> Vec<String> {
729    let mut targets = BTreeSet::new();
730    if let Some(target) = expression_targets.get(node_id) {
731        targets.insert(target.clone());
732    }
733    for predecessor_id in predecessor_ids {
734        if let Some(target) = expression_targets.get(predecessor_id) {
735            targets.insert(target.clone());
736        }
737    }
738    targets.into_iter().collect()
739}
740
741fn selector_universe_for_targets(
742    target_style_paths: &[String],
743    style_selectors_by_path: &BTreeMap<String, Vec<String>>,
744) -> Vec<String> {
745    let mut selectors = BTreeSet::new();
746    if target_style_paths.is_empty() {
747        for selector_names in style_selectors_by_path.values() {
748            selectors.extend(selector_names.iter().cloned());
749        }
750    } else {
751        for target_style_path in target_style_paths {
752            if let Some(selector_names) = style_selectors_by_path.get(target_style_path) {
753                selectors.extend(selector_names.iter().cloned());
754            }
755        }
756    }
757    selectors.into_iter().collect()
758}
759
760pub fn summarize_omena_query_source_resolution_query_fragments(
761    input: &EngineInputV2,
762) -> SourceResolutionQueryFragmentsV0 {
763    summarize_omena_resolver_query_fragments(input)
764}
765
766pub fn summarize_omena_query_selector_usage_query_fragments(
767    input: &EngineInputV2,
768) -> SelectorUsageQueryFragmentsV0 {
769    summarize_selector_usage_query_fragments_input(input)
770}
771
772pub fn summarize_omena_query_source_resolution_canonical_producer_signal(
773    input: &EngineInputV2,
774) -> SourceResolutionCanonicalProducerSignalV0 {
775    summarize_omena_resolver_canonical_producer_signal(input)
776}
777
778pub fn summarize_omena_query_source_resolution_runtime(
779    input: &EngineInputV2,
780) -> OmenaResolverSourceResolutionRuntimeIndexV0 {
781    summarize_omena_resolver_source_resolution_runtime(input)
782}
783
784pub fn summarize_omena_query_expression_semantics_canonical_producer_signal(
785    input: &EngineInputV2,
786) -> ExpressionSemanticsCanonicalProducerSignalV0 {
787    summarize_expression_semantics_canonical_producer_signal_input(input)
788}
789
790pub fn summarize_omena_query_selector_usage_canonical_producer_signal(
791    input: &EngineInputV2,
792) -> SelectorUsageCanonicalProducerSignalV0 {
793    summarize_selector_usage_canonical_producer_signal_input(input)
794}
795
796pub fn summarize_omena_query_style_semantic_graph_from_source(
797    style_path: &str,
798    style_source: &str,
799    input: &EngineInputV2,
800) -> Option<StyleSemanticGraphSummaryV0> {
801    summarize_omena_bridge_style_semantic_graph_from_source(style_path, style_source, input)
802}
803
804pub fn summarize_omena_query_style_document(
805    style_path: &str,
806    style_source: &str,
807) -> Option<OmenaQueryStyleDocumentSummaryV0> {
808    let sheet = parse_style_module(style_path, style_source)?;
809    let index = summarize_css_modules_intermediate(&sheet);
810    Some(OmenaQueryStyleDocumentSummaryV0 {
811        schema_version: "0",
812        product: "omena-query.style-document-summary",
813        language: style_language_label(sheet.language),
814        selector_names: index.selectors.names,
815        custom_property_decl_names: index.custom_properties.decl_names,
816        custom_property_ref_names: index.custom_properties.ref_names,
817        sass_module_use_sources: index.sass.module_use_sources,
818        sass_module_forward_sources: index.sass.module_forward_sources,
819        diagnostic_count: sheet.diagnostics.len(),
820    })
821}
822
823pub fn summarize_omena_query_style_hover_candidates(
824    style_path: &str,
825    style_source: &str,
826) -> Option<OmenaQueryStyleHoverCandidatesV0> {
827    let sheet = parse_style_module(style_path, style_source)?;
828    let index = summarize_css_modules_intermediate(&sheet);
829    let mut seen = BTreeSet::new();
830    let mut candidates = Vec::new();
831    collect_style_selector_hover_candidates_from_parser_facts(
832        index.selectors.definition_facts.as_slice(),
833        &mut seen,
834        &mut candidates,
835    );
836    collect_custom_property_hover_candidates(
837        sheet.source.as_str(),
838        index.custom_properties.decl_facts.as_slice(),
839        index.custom_properties.ref_names.as_slice(),
840        &mut seen,
841        &mut candidates,
842    );
843    collect_sass_symbol_hover_candidates(
844        index.sass.symbol_decl_facts.as_slice(),
845        index.sass.selector_symbol_facts.as_slice(),
846        &mut seen,
847        &mut candidates,
848    );
849    collect_sass_partial_evaluator_selector_candidates(
850        sheet.source.as_str(),
851        sheet.nodes.as_slice(),
852        &mut seen,
853        &mut candidates,
854    );
855    candidates.sort();
856    Some(OmenaQueryStyleHoverCandidatesV0 {
857        schema_version: "0",
858        product: "omena-query.style-hover-candidates",
859        language: style_language_label(sheet.language),
860        candidates,
861    })
862}
863
864pub fn summarize_omena_query_style_hover_render_parts(
865    source: &str,
866    kind: &str,
867    name: &str,
868    position: ParserPositionV0,
869) -> OmenaQueryStyleHoverRenderPartsV0 {
870    let mut parts = OmenaQueryStyleHoverRenderPartsV0 {
871        schema_version: "0",
872        product: "omena-query.style-hover-render-parts",
873        snippet: String::new(),
874        value: None,
875        signature: None,
876        render_source: "lineSnippet",
877    };
878
879    match kind {
880        "selector" => {
881            parts.snippet = rule_snippet_around_position(source, position).unwrap_or_else(|| {
882                parts.render_source = "selectorFallback";
883                format!(".{name} {{ ... }}")
884            });
885            if parts.render_source != "selectorFallback" {
886                parts.render_source = "ruleSnippet";
887            }
888        }
889        "customPropertyReference" | "customPropertyDeclaration" => {
890            parts.snippet = line_snippet_at_position(source, position).unwrap_or_default();
891        }
892        kind if is_sass_symbol_candidate_kind(kind) => {
893            parts.snippet = line_snippet_at_position(source, position).unwrap_or_default();
894            if sass_symbol_kind_from_candidate_kind(kind) == Some("variable")
895                && is_sass_symbol_declaration_kind(kind)
896            {
897                parts.value = sass_variable_value_from_declaration_line(parts.snippet.as_str());
898            } else if matches!(
899                sass_symbol_kind_from_candidate_kind(kind),
900                Some("mixin" | "function")
901            ) && is_sass_symbol_declaration_kind(kind)
902                && let Some((signature, snippet)) =
903                    sass_callable_definition_render_parts(source, position)
904            {
905                parts.signature = Some(signature);
906                parts.snippet = snippet;
907                parts.render_source = "callableBlockSnippet";
908            }
909        }
910        _ => {
911            parts.snippet = name.to_string();
912            parts.render_source = "candidateNameFallback";
913        }
914    }
915
916    parts
917}
918
919pub fn summarize_omena_query_missing_custom_property_diagnostics(
920    style_uri: &str,
921    source: &str,
922    candidates: &[OmenaQueryStyleHoverCandidateV0],
923) -> Vec<OmenaQueryStyleDiagnosticV0> {
924    let declaration_names = candidates
925        .iter()
926        .filter(|candidate| candidate.kind == "customPropertyDeclaration")
927        .map(|candidate| candidate.name.as_str())
928        .collect::<BTreeSet<_>>();
929    if declaration_names.is_empty() {
930        return Vec::new();
931    }
932
933    let insertion_range = end_of_source_range(source);
934    candidates
935        .iter()
936        .filter(|candidate| {
937            candidate.kind == "customPropertyReference"
938                && !declaration_names.contains(candidate.name.as_str())
939        })
940        .map(|candidate| OmenaQueryStyleDiagnosticV0 {
941            code: "missingCustomProperty",
942            range: candidate.range,
943            message: format!(
944                "CSS custom property '{}' not found in indexed style tokens.",
945                candidate.name
946            ),
947            create_custom_property: Some(OmenaQueryCreateCustomPropertyActionV0 {
948                uri: style_uri.to_string(),
949                range: insertion_range,
950                new_text: format!("\n\n:root {{\n  {}: ;\n}}\n", candidate.name),
951                property_name: candidate.name.clone(),
952            }),
953        })
954        .collect()
955}
956
957pub fn summarize_omena_query_missing_selector_diagnostic(
958    target_style_uri: &str,
959    target_style_source: &str,
960    selector_name: &str,
961    source_reference_range: ParserRangeV0,
962) -> OmenaQuerySourceDiagnosticV0 {
963    let insertion_range = end_of_source_range(target_style_source);
964    let has_existing_style_content = !target_style_source.trim().is_empty();
965    OmenaQuerySourceDiagnosticV0 {
966        code: "missingSelector",
967        range: source_reference_range,
968        message: format!(
969            "CSS Module selector '.{selector_name}' not found in indexed style tokens."
970        ),
971        create_selector: Some(OmenaQueryCreateSelectorActionV0 {
972            uri: target_style_uri.to_string(),
973            range: insertion_range,
974            new_text: if has_existing_style_content {
975                format!("\n\n.{selector_name} {{\n}}\n")
976            } else {
977                format!(".{selector_name} {{\n}}\n")
978            },
979            selector_name: selector_name.to_string(),
980        }),
981    }
982}
983
984pub fn resolve_omena_query_source_provider_candidates(
985    source_candidates: Vec<OmenaQuerySourceSelectorCandidateV0>,
986    definitions: &[OmenaQueryStyleSelectorDefinitionV0],
987) -> OmenaQuerySourceProviderCandidateResolutionV0 {
988    if definitions.is_empty() {
989        return OmenaQuerySourceProviderCandidateResolutionV0 {
990            schema_version: "0",
991            product: "omena-query.source-provider-candidate-resolution",
992            matched: Vec::new(),
993            unresolved: Vec::new(),
994        };
995    }
996
997    let (mut matched, mut unresolved): (Vec<_>, Vec<_>) =
998        source_candidates.into_iter().partition(|candidate| {
999            definitions.iter().any(|definition| {
1000                source_selector_candidate_matches_definition(candidate, definition)
1001            })
1002        });
1003    matched.sort();
1004    unresolved.sort();
1005    OmenaQuerySourceProviderCandidateResolutionV0 {
1006        schema_version: "0",
1007        product: "omena-query.source-provider-candidate-resolution",
1008        matched,
1009        unresolved,
1010    }
1011}
1012
1013pub fn resolve_omena_query_style_selector_definitions_for_source_candidate(
1014    candidate: &OmenaQuerySourceSelectorCandidateV0,
1015    definitions: &[OmenaQueryStyleSelectorDefinitionV0],
1016) -> Vec<OmenaQueryStyleSelectorDefinitionV0> {
1017    let mut matched = definitions
1018        .iter()
1019        .filter(|definition| source_selector_candidate_matches_definition(candidate, definition))
1020        .cloned()
1021        .collect::<Vec<_>>();
1022    matched.sort_by_key(|definition| {
1023        (
1024            definition.uri.clone(),
1025            definition.range.start.line,
1026            definition.range.start.character,
1027            definition.name.clone(),
1028        )
1029    });
1030    matched.dedup();
1031    matched
1032}
1033
1034pub fn resolve_omena_query_source_candidate_selector_names(
1035    candidate: &OmenaQuerySourceSelectorCandidateV0,
1036    definitions: &[OmenaQueryStyleSelectorDefinitionV0],
1037    target_style_uri: Option<&str>,
1038) -> Vec<String> {
1039    if candidate.kind != "sourceSelectorPrefixReference" {
1040        return vec![candidate.name.clone()];
1041    }
1042
1043    let mut names = definitions
1044        .iter()
1045        .filter(|definition| source_selector_candidate_matches_definition(candidate, definition))
1046        .filter(|definition| {
1047            candidate
1048                .target_style_uri
1049                .as_deref()
1050                .or(target_style_uri)
1051                .is_none_or(|target_uri| target_uri == definition.uri)
1052        })
1053        .map(|definition| definition.name.clone())
1054        .collect::<Vec<_>>();
1055    names.sort();
1056    names.dedup();
1057    names
1058}
1059
1060pub fn resolve_omena_query_selector_rename_edits(
1061    selector_name: &str,
1062    new_name: &str,
1063    target_style_uri: Option<&str>,
1064    definitions: &[OmenaQueryStyleSelectorDefinitionV0],
1065    references: &[OmenaQuerySourceSelectorReferenceEditTargetV0],
1066) -> Vec<OmenaQueryWorkspaceTextEditV0> {
1067    let replacement = new_name.trim_start_matches('.');
1068    if replacement.is_empty() {
1069        return Vec::new();
1070    }
1071
1072    let mut edits = definitions
1073        .iter()
1074        .filter(|definition| definition.name == selector_name)
1075        .filter(|definition| target_style_uri.is_none_or(|target_uri| target_uri == definition.uri))
1076        .map(|definition| OmenaQueryWorkspaceTextEditV0 {
1077            uri: definition.uri.clone(),
1078            range: definition.range,
1079            new_text: replacement.to_string(),
1080        })
1081        .chain(
1082            references
1083                .iter()
1084                .filter(|reference| reference.name == selector_name)
1085                .filter(|reference| {
1086                    source_reference_matches_target_style(reference, target_style_uri)
1087                })
1088                .map(|reference| OmenaQueryWorkspaceTextEditV0 {
1089                    uri: reference.uri.clone(),
1090                    range: reference.range,
1091                    new_text: replacement.to_string(),
1092                }),
1093        )
1094        .collect::<Vec<_>>();
1095    edits.sort_by_key(|edit| {
1096        (
1097            edit.uri.clone(),
1098            edit.range.start.line,
1099            edit.range.start.character,
1100            edit.range.end.line,
1101            edit.range.end.character,
1102        )
1103    });
1104    edits
1105}
1106
1107pub fn is_omena_query_sass_symbol_candidate_kind(kind: &str) -> bool {
1108    omena_query_sass_symbol_kind_from_candidate_kind(kind).is_some()
1109}
1110
1111pub fn is_omena_query_sass_symbol_reference_kind(kind: &str) -> bool {
1112    matches!(
1113        kind,
1114        "sassVariableReference"
1115            | "sassMixinInclude"
1116            | "sassFunctionCall"
1117            | "sassMixinReference"
1118            | "sassFunctionReference"
1119            | "sassSymbolReference"
1120    )
1121}
1122
1123pub fn is_omena_query_sass_symbol_declaration_kind(kind: &str) -> bool {
1124    matches!(
1125        kind,
1126        "sassVariableDeclaration"
1127            | "sassMixinDeclaration"
1128            | "sassFunctionDeclaration"
1129            | "sassSymbolDeclaration"
1130    )
1131}
1132
1133pub fn omena_query_sass_symbol_kind_from_candidate_kind(kind: &str) -> Option<&'static str> {
1134    match kind {
1135        "sassVariableDeclaration" | "sassVariableReference" => Some("variable"),
1136        "sassMixinDeclaration" | "sassMixinInclude" | "sassMixinReference" => Some("mixin"),
1137        "sassFunctionDeclaration" | "sassFunctionCall" | "sassFunctionReference" => {
1138            Some("function")
1139        }
1140        "sassSymbolDeclaration" | "sassSymbolReference" => Some("symbol"),
1141        _ => None,
1142    }
1143}
1144
1145pub fn omena_query_sass_symbol_target_matches(
1146    candidate_kind: &str,
1147    candidate_name: &str,
1148    candidate_namespace: Option<&str>,
1149    target_kind: &str,
1150    target_name: &str,
1151    target_namespace: Option<&str>,
1152) -> bool {
1153    candidate_name == target_name
1154        && candidate_namespace == target_namespace
1155        && omena_query_sass_symbol_kind_from_candidate_kind(candidate_kind)
1156            == omena_query_sass_symbol_kind_from_candidate_kind(target_kind)
1157}
1158
1159pub fn resolve_omena_query_sass_symbol_declarations(
1160    candidates: &[OmenaQueryStyleHoverCandidateV0],
1161    symbol_kind: &str,
1162    name: &str,
1163) -> Vec<OmenaQueryStyleHoverCandidateV0> {
1164    candidates
1165        .iter()
1166        .filter(|target| {
1167            is_omena_query_sass_symbol_declaration_kind(target.kind)
1168                && omena_query_sass_symbol_kind_from_candidate_kind(target.kind)
1169                    == Some(symbol_kind)
1170                && target.name == name
1171        })
1172        .cloned()
1173        .collect()
1174}
1175
1176pub fn resolve_omena_query_sass_module_use_sources_for_candidate(
1177    sources: &OmenaQuerySassModuleSourcesV0,
1178    namespace: Option<&str>,
1179) -> Vec<String> {
1180    let mut selected = sources
1181        .module_use_edges
1182        .iter()
1183        .filter(|edge| {
1184            if let Some(namespace) = namespace {
1185                edge.namespace.as_deref() == Some(namespace)
1186            } else {
1187                edge.namespace_kind == "wildcard"
1188            }
1189        })
1190        .filter(|edge| !is_sass_builtin_module_source(edge.source.as_str()))
1191        .map(|edge| edge.source.clone())
1192        .collect::<Vec<_>>();
1193    selected.sort();
1194    selected.dedup();
1195    selected
1196}
1197
1198pub fn resolve_omena_query_sass_forward_sources(
1199    sources: &OmenaQuerySassModuleSourcesV0,
1200) -> Vec<String> {
1201    let mut selected = sources
1202        .module_forward_sources
1203        .iter()
1204        .filter(|source| !is_sass_builtin_module_source(source.as_str()))
1205        .cloned()
1206        .collect::<Vec<_>>();
1207    selected.sort();
1208    selected.dedup();
1209    selected
1210}
1211
1212pub fn summarize_omena_query_sass_module_sources(
1213    style_path: &str,
1214    style_source: &str,
1215) -> Option<OmenaQuerySassModuleSourcesV0> {
1216    let sheet = parse_style_module(style_path, style_source)?;
1217    let index = summarize_css_modules_intermediate(&sheet);
1218    Some(OmenaQuerySassModuleSourcesV0 {
1219        schema_version: "0",
1220        product: "omena-query.sass-module-sources",
1221        module_use_edges: index
1222            .sass
1223            .module_use_edges
1224            .into_iter()
1225            .map(|edge| OmenaQuerySassModuleUseEdgeV0 {
1226                source: edge.source,
1227                namespace_kind: edge.namespace_kind,
1228                namespace: edge.namespace,
1229            })
1230            .collect(),
1231        module_forward_sources: index.sass.module_forward_sources,
1232    })
1233}
1234
1235pub fn summarize_omena_query_style_semantic_graph_batch_from_sources<'a>(
1236    styles: impl IntoIterator<Item = (&'a str, &'a str)>,
1237    input: &EngineInputV2,
1238) -> OmenaQueryStyleSemanticGraphBatchOutputV0 {
1239    summarize_omena_query_style_semantic_graph_batch_from_sources_with_package_manifests(
1240        styles,
1241        input,
1242        &[],
1243    )
1244}
1245
1246pub fn summarize_omena_query_style_semantic_graph_batch_from_sources_with_package_manifests<'a>(
1247    styles: impl IntoIterator<Item = (&'a str, &'a str)>,
1248    input: &EngineInputV2,
1249    package_manifests: &[OmenaQueryStylePackageManifestV0],
1250) -> OmenaQueryStyleSemanticGraphBatchOutputV0 {
1251    let style_sources = styles.into_iter().collect::<Vec<_>>();
1252    let parsed_styles = style_sources
1253        .iter()
1254        .filter_map(|(style_path, style_source)| {
1255            parse_style_module(style_path, style_source)
1256                .map(|sheet| ((*style_path).to_string(), sheet))
1257        })
1258        .collect::<Vec<_>>();
1259    let workspace_declarations = parsed_styles
1260        .iter()
1261        .flat_map(|(style_path, sheet)| {
1262            collect_omena_bridge_design_token_workspace_declarations(style_path, sheet)
1263        })
1264        .collect::<Vec<_>>();
1265    let graphs = style_sources
1266        .into_iter()
1267        .map(
1268            |(style_path, _style_source)| OmenaQueryStyleSemanticGraphBatchEntryV0 {
1269                style_path: style_path.to_string(),
1270                graph: parsed_style_by_path(&parsed_styles, style_path).map(|sheet| {
1271                    let import_reachable_declarations =
1272                        filter_import_reachable_design_token_workspace_declarations(
1273                            style_path,
1274                            &parsed_styles,
1275                            &workspace_declarations,
1276                            package_manifests,
1277                        );
1278                    summarize_omena_bridge_style_semantic_graph_for_path_with_scoped_workspace_declarations(
1279                        sheet,
1280                        input,
1281                        Some(style_path),
1282                        &import_reachable_declarations,
1283                        DesignTokenExternalDeclarationCandidateScopeV0::CrossFileImportGraph,
1284                    )
1285                }),
1286            },
1287        )
1288        .collect::<Vec<_>>();
1289
1290    OmenaQueryStyleSemanticGraphBatchOutputV0 {
1291        schema_version: "0",
1292        product: "omena-semantic.style-semantic-graph-batch",
1293        graphs,
1294    }
1295}
1296
1297fn parsed_style_by_path<'a>(
1298    parsed_styles: &'a [(String, Stylesheet)],
1299    style_path: &str,
1300) -> Option<&'a Stylesheet> {
1301    parsed_styles
1302        .iter()
1303        .find(|(parsed_style_path, _sheet)| parsed_style_path == style_path)
1304        .map(|(_style_path, sheet)| sheet)
1305}
1306
1307fn filter_import_reachable_design_token_workspace_declarations(
1308    target_style_path: &str,
1309    parsed_styles: &[(String, Stylesheet)],
1310    workspace_declarations: &[DesignTokenWorkspaceDeclarationFactV0],
1311    package_manifests: &[OmenaQueryStylePackageManifestV0],
1312) -> Vec<DesignTokenWorkspaceDeclarationFactV0> {
1313    let reachable_style_paths = collect_import_reachable_style_path_metadata(
1314        target_style_path,
1315        parsed_styles,
1316        package_manifests,
1317    );
1318    workspace_declarations
1319        .iter()
1320        .filter_map(|declaration| {
1321            if declaration.file_path == target_style_path {
1322                return Some(declaration.clone());
1323            }
1324            let reachability = reachable_style_paths.get(declaration.file_path.as_str())?;
1325            let mut declaration = declaration.clone();
1326            declaration.import_graph_distance = Some(reachability.distance);
1327            declaration.import_graph_order = Some(reachability.order);
1328            Some(declaration)
1329        })
1330        .collect()
1331}
1332
1333#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1334struct ImportReachability {
1335    distance: usize,
1336    order: usize,
1337}
1338
1339fn collect_import_reachable_style_path_metadata(
1340    target_style_path: &str,
1341    parsed_styles: &[(String, Stylesheet)],
1342    package_manifests: &[OmenaQueryStylePackageManifestV0],
1343) -> BTreeMap<String, ImportReachability> {
1344    let mut reachable_style_paths = BTreeMap::new();
1345    let available_style_paths = parsed_styles
1346        .iter()
1347        .map(|(style_path, _sheet)| style_path.as_str())
1348        .collect::<BTreeSet<_>>();
1349    let mut pending_style_paths = collect_import_reachable_direct_style_paths(
1350        target_style_path,
1351        parsed_styles,
1352        &available_style_paths,
1353        package_manifests,
1354    )
1355    .into_iter()
1356    .map(|style_path| (style_path, 1usize))
1357    .collect::<VecDeque<_>>();
1358    let style_by_path = parsed_styles
1359        .iter()
1360        .map(|(style_path, sheet)| (style_path.as_str(), sheet))
1361        .collect::<BTreeMap<_, _>>();
1362    let mut visit_order = 0usize;
1363
1364    while let Some((style_path, distance)) = pending_style_paths.pop_front() {
1365        if style_path == target_style_path || reachable_style_paths.contains_key(&style_path) {
1366            continue;
1367        }
1368        reachable_style_paths.insert(
1369            style_path.clone(),
1370            ImportReachability {
1371                distance,
1372                order: visit_order,
1373            },
1374        );
1375        visit_order += 1;
1376
1377        let Some(sheet) = style_by_path.get(style_path.as_str()) else {
1378            continue;
1379        };
1380        for source in collect_sass_module_sources(sheet) {
1381            if let Some(next_style_path) = resolve_style_module_source(
1382                &style_path,
1383                &source,
1384                &available_style_paths,
1385                package_manifests,
1386            ) {
1387                pending_style_paths.push_back((next_style_path, distance + 1));
1388            }
1389        }
1390    }
1391
1392    reachable_style_paths
1393}
1394
1395fn collect_import_reachable_direct_style_paths(
1396    target_style_path: &str,
1397    parsed_styles: &[(String, Stylesheet)],
1398    available_style_paths: &BTreeSet<&str>,
1399    package_manifests: &[OmenaQueryStylePackageManifestV0],
1400) -> Vec<String> {
1401    let Some(target_sheet) = parsed_style_by_path(parsed_styles, target_style_path) else {
1402        return Vec::new();
1403    };
1404    collect_sass_module_sources(target_sheet)
1405        .into_iter()
1406        .filter_map(|source| {
1407            resolve_style_module_source(
1408                target_style_path,
1409                &source,
1410                available_style_paths,
1411                package_manifests,
1412            )
1413        })
1414        .collect()
1415}
1416
1417fn collect_sass_module_sources(sheet: &Stylesheet) -> Vec<String> {
1418    let summary = summarize_css_modules_intermediate(sheet);
1419    let mut sources = Vec::new();
1420    for edge in summary.sass.module_use_edges {
1421        push_unique_string(&mut sources, edge.source);
1422    }
1423    for source in summary.sass.module_forward_sources {
1424        push_unique_string(&mut sources, source);
1425    }
1426    for source in summary.sass.module_import_sources {
1427        push_unique_string(&mut sources, source);
1428    }
1429    sources
1430}
1431
1432fn resolve_style_module_source(
1433    from_style_path: &str,
1434    source: &str,
1435    available_style_paths: &BTreeSet<&str>,
1436    package_manifests: &[OmenaQueryStylePackageManifestV0],
1437) -> Option<String> {
1438    if source.starts_with("sass:")
1439        || source.starts_with("http://")
1440        || source.starts_with("https://")
1441    {
1442        return None;
1443    }
1444
1445    style_module_source_candidates(from_style_path, source, package_manifests)
1446        .into_iter()
1447        .find(|candidate| available_style_paths.contains(candidate.as_str()))
1448}
1449
1450fn style_module_source_candidates(
1451    from_style_path: &str,
1452    source: &str,
1453    package_manifests: &[OmenaQueryStylePackageManifestV0],
1454) -> Vec<String> {
1455    let source_path = Path::new(source);
1456    let base_path = if source_path.is_absolute() {
1457        PathBuf::from(source)
1458    } else {
1459        Path::new(from_style_path)
1460            .parent()
1461            .map(|parent| parent.join(source))
1462            .unwrap_or_else(|| PathBuf::from(source))
1463    };
1464    let mut candidates = Vec::new();
1465    push_style_module_path_candidates(
1466        &mut candidates,
1467        base_path,
1468        source_path.extension().is_none(),
1469    );
1470    for package_manifest_base_path in
1471        package_manifest_style_module_base_candidates(from_style_path, source, package_manifests)
1472    {
1473        push_style_module_path_candidates(&mut candidates, package_manifest_base_path, true);
1474    }
1475    for package_base_path in package_style_module_base_candidates(from_style_path, source) {
1476        push_style_module_path_candidates(&mut candidates, package_base_path, true);
1477    }
1478
1479    candidates
1480}
1481
1482fn push_style_module_path_candidates(
1483    candidates: &mut Vec<String>,
1484    base_path: PathBuf,
1485    include_extension_variants: bool,
1486) {
1487    push_style_path_candidate(candidates, base_path.clone());
1488    push_partial_style_path_candidate(candidates, &base_path);
1489
1490    if !include_extension_variants {
1491        return;
1492    }
1493
1494    for extension in [
1495        ".module.scss",
1496        ".module.css",
1497        ".module.less",
1498        ".scss",
1499        ".css",
1500        ".less",
1501    ] {
1502        let candidate = PathBuf::from(format!("{}{}", base_path.display(), extension));
1503        push_style_path_candidate(candidates, candidate.clone());
1504        push_partial_style_path_candidate(candidates, &candidate);
1505    }
1506}
1507
1508fn package_style_module_base_candidates(from_style_path: &str, source: &str) -> Vec<PathBuf> {
1509    let Some(package_source) = parse_package_style_source(source) else {
1510        return Vec::new();
1511    };
1512    let Some(from_dir) = Path::new(from_style_path).parent() else {
1513        return Vec::new();
1514    };
1515    let mut candidates = Vec::new();
1516    let mut current_dir = Some(from_dir);
1517    while let Some(dir) = current_dir {
1518        let package_root = dir.join("node_modules").join(package_source.package_name);
1519        let package_entry = match package_source.subpath {
1520            Some(subpath) => package_root.join(subpath),
1521            None => package_root.clone(),
1522        };
1523        push_unique_pathbuf(&mut candidates, package_entry.clone());
1524        if let Some(subpath) = package_source.subpath {
1525            push_unique_pathbuf(&mut candidates, package_root.join("src").join(subpath));
1526        } else {
1527            push_unique_pathbuf(&mut candidates, package_root.join("index"));
1528            push_unique_pathbuf(&mut candidates, package_root.join("src").join("index"));
1529        }
1530        current_dir = dir.parent();
1531    }
1532    candidates
1533}
1534
1535fn package_manifest_style_module_base_candidates(
1536    from_style_path: &str,
1537    source: &str,
1538    package_manifests: &[OmenaQueryStylePackageManifestV0],
1539) -> Vec<PathBuf> {
1540    let Some(package_source) = parse_package_style_source(source) else {
1541        return Vec::new();
1542    };
1543    let Some(from_dir) = Path::new(from_style_path).parent() else {
1544        return Vec::new();
1545    };
1546    let manifest_by_package_dir = package_manifests
1547        .iter()
1548        .map(|manifest| {
1549            (
1550                package_dir_from_package_json_path(&manifest.package_json_path),
1551                manifest.package_json_source.as_str(),
1552            )
1553        })
1554        .collect::<BTreeMap<_, _>>();
1555
1556    let mut candidates = Vec::new();
1557    let mut current_dir = Some(from_dir);
1558    while let Some(dir) = current_dir {
1559        let package_root = dir.join("node_modules").join(package_source.package_name);
1560        let package_root_key = normalize_style_path(package_root.clone());
1561        if let Some(package_json_source) = manifest_by_package_dir.get(&package_root_key)
1562            && let Some(entry) =
1563                read_package_manifest_style_entry(package_json_source, package_source.subpath)
1564        {
1565            push_unique_pathbuf(&mut candidates, package_root.join(entry));
1566        }
1567        current_dir = dir.parent();
1568    }
1569    candidates
1570}
1571
1572fn package_dir_from_package_json_path(package_json_path: &str) -> String {
1573    Path::new(package_json_path)
1574        .parent()
1575        .map(|path| normalize_style_path(path.to_path_buf()))
1576        .unwrap_or_default()
1577}
1578
1579fn read_package_manifest_style_entry(
1580    package_json_source: &str,
1581    subpath: Option<&str>,
1582) -> Option<PathBuf> {
1583    let package_json = serde_json::from_str::<serde_json::Value>(package_json_source).ok()?;
1584    let package_object = package_json.as_object()?;
1585    let entry = if let Some(subpath) = subpath {
1586        read_package_export_subpath_entry(package_object.get("exports"), subpath)
1587    } else {
1588        read_package_json_string_field(package_object, "sass")
1589            .or_else(|| read_package_json_string_field(package_object, "scss"))
1590            .or_else(|| read_package_json_string_field(package_object, "style"))
1591            .or_else(|| read_package_export_entry(package_object.get("exports")))
1592    }?;
1593    Some(PathBuf::from(normalize_package_json_entry(&entry)))
1594}
1595
1596fn read_package_export_subpath_entry(
1597    exports_value: Option<&serde_json::Value>,
1598    subpath: &str,
1599) -> Option<String> {
1600    let exports_object = exports_value?.as_object()?;
1601    for key in package_export_subpath_keys(subpath) {
1602        if let Some(entry) = read_package_export_entry(exports_object.get(&key)) {
1603            return Some(entry);
1604        }
1605    }
1606    for (key, export_value) in exports_object {
1607        let Some(pattern_match) = match_package_export_subpath_pattern(key, subpath) else {
1608            continue;
1609        };
1610        let Some(entry) = read_package_export_entry(Some(export_value)) else {
1611            continue;
1612        };
1613        return Some(substitute_package_export_pattern(&entry, &pattern_match));
1614    }
1615    None
1616}
1617
1618fn package_export_subpath_keys(subpath: &str) -> Vec<String> {
1619    let normalized = subpath
1620        .trim_start_matches("./")
1621        .trim_start_matches('/')
1622        .to_string();
1623    vec![
1624        format!("./{normalized}"),
1625        format!("./{normalized}.scss"),
1626        format!("./{normalized}.sass"),
1627        format!("./{normalized}.css"),
1628    ]
1629}
1630
1631fn match_package_export_subpath_pattern(pattern_key: &str, subpath: &str) -> Option<String> {
1632    let normalized_pattern = pattern_key.trim_start_matches("./").trim_start_matches('/');
1633    let (prefix, suffix) = normalized_pattern.split_once('*')?;
1634    if suffix.contains('*') {
1635        return None;
1636    }
1637
1638    for candidate_key in package_export_subpath_keys(subpath) {
1639        let normalized_candidate = candidate_key
1640            .trim_start_matches("./")
1641            .trim_start_matches('/')
1642            .to_string();
1643        if !normalized_candidate.starts_with(prefix) || !normalized_candidate.ends_with(suffix) {
1644            continue;
1645        }
1646        return Some(
1647            normalized_candidate[prefix.len()..normalized_candidate.len() - suffix.len()]
1648                .to_string(),
1649        );
1650    }
1651    None
1652}
1653
1654fn substitute_package_export_pattern(entry: &str, pattern_match: &str) -> String {
1655    if entry.contains('*') {
1656        entry.replace('*', pattern_match)
1657    } else {
1658        entry.to_string()
1659    }
1660}
1661
1662fn read_package_export_entry(exports_value: Option<&serde_json::Value>) -> Option<String> {
1663    let exports_value = exports_value?;
1664    if let Some(entry) = exports_value.as_str() {
1665        return Some(entry.to_string());
1666    }
1667    if let Some(entries) = exports_value.as_array() {
1668        for entry_value in entries {
1669            if let Some(entry) = read_package_export_entry(Some(entry_value)) {
1670                return Some(entry);
1671            }
1672        }
1673        return None;
1674    }
1675    let exports_object = exports_value.as_object()?;
1676    if let Some(root_entry) = read_package_export_entry(exports_object.get(".")) {
1677        return Some(root_entry);
1678    }
1679    for key in ["sass", "scss", "style", "default", "import", "require"] {
1680        if let Some(entry) = read_package_export_entry(exports_object.get(key)) {
1681            return Some(entry);
1682        }
1683    }
1684    None
1685}
1686
1687fn read_package_json_string_field(
1688    package_object: &serde_json::Map<String, serde_json::Value>,
1689    key: &str,
1690) -> Option<String> {
1691    package_object
1692        .get(key)
1693        .and_then(|value| value.as_str())
1694        .map(ToString::to_string)
1695}
1696
1697fn normalize_package_json_entry(entry: &str) -> String {
1698    entry
1699        .trim_start_matches("./")
1700        .trim_start_matches('/')
1701        .to_string()
1702}
1703
1704#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1705struct PackageStyleSource<'a> {
1706    package_name: &'a str,
1707    subpath: Option<&'a str>,
1708}
1709
1710fn parse_package_style_source(source: &str) -> Option<PackageStyleSource<'_>> {
1711    if source.starts_with('.')
1712        || source.starts_with('/')
1713        || source.starts_with("sass:")
1714        || source.starts_with("http://")
1715        || source.starts_with("https://")
1716    {
1717        return None;
1718    }
1719
1720    if source.starts_with('@') {
1721        let mut segments = source.splitn(3, '/');
1722        let scope = segments.next()?;
1723        let package = segments.next()?;
1724        if scope.len() <= 1 || package.is_empty() {
1725            return None;
1726        }
1727        let package_name_end = scope.len() + 1 + package.len();
1728        let package_name = &source[..package_name_end];
1729        let subpath = segments.next().filter(|subpath| !subpath.is_empty());
1730        return Some(PackageStyleSource {
1731            package_name,
1732            subpath,
1733        });
1734    }
1735
1736    let mut segments = source.splitn(2, '/');
1737    let package_name = segments.next()?;
1738    if package_name.is_empty() {
1739        return None;
1740    }
1741    let subpath = segments.next().filter(|subpath| !subpath.is_empty());
1742    Some(PackageStyleSource {
1743        package_name,
1744        subpath,
1745    })
1746}
1747
1748fn push_unique_pathbuf(candidates: &mut Vec<PathBuf>, value: PathBuf) {
1749    if !candidates.contains(&value) {
1750        candidates.push(value);
1751    }
1752}
1753
1754fn push_partial_style_path_candidate(candidates: &mut Vec<String>, path: &Path) {
1755    let Some(file_name) = path.file_name().and_then(|file_name| file_name.to_str()) else {
1756        return;
1757    };
1758    if file_name.starts_with('_') {
1759        return;
1760    }
1761    let mut partial_path = path.to_path_buf();
1762    partial_path.set_file_name(format!("_{file_name}"));
1763    push_style_path_candidate(candidates, partial_path);
1764}
1765
1766fn push_style_path_candidate(candidates: &mut Vec<String>, path: PathBuf) {
1767    let candidate = normalize_style_path(path);
1768    if !candidates.contains(&candidate) {
1769        candidates.push(candidate);
1770    }
1771}
1772
1773fn normalize_style_path(path: PathBuf) -> String {
1774    let mut normalized = PathBuf::new();
1775    for component in path.components() {
1776        match component {
1777            Component::CurDir => {}
1778            Component::ParentDir => {
1779                normalized.pop();
1780            }
1781            Component::Normal(part) => normalized.push(part),
1782            Component::RootDir | Component::Prefix(_) => normalized.push(component.as_os_str()),
1783        }
1784    }
1785    normalized.to_string_lossy().replace('\\', "/")
1786}
1787
1788fn collect_style_selector_hover_candidates_from_parser_facts(
1789    definition_facts: &[engine_style_parser::ParserIndexSelectorDefinitionFactV0],
1790    seen: &mut BTreeSet<(usize, usize, String)>,
1791    candidates: &mut Vec<OmenaQueryStyleHoverCandidateV0>,
1792) {
1793    for fact in definition_facts {
1794        if seen.insert((fact.byte_span.start, fact.byte_span.end, fact.name.clone())) {
1795            candidates.push(OmenaQueryStyleHoverCandidateV0 {
1796                kind: "selector",
1797                name: fact.name.clone(),
1798                range: fact.range,
1799                source: "engineStyleParserSelectorDefinitionFacts",
1800                namespace: None,
1801            });
1802        }
1803    }
1804}
1805
1806fn collect_custom_property_hover_candidates(
1807    source: &str,
1808    decl_facts: &[engine_style_parser::ParserIndexCustomPropertyDeclFactV0],
1809    ref_names: &[String],
1810    seen: &mut BTreeSet<(usize, usize, String)>,
1811    candidates: &mut Vec<OmenaQueryStyleHoverCandidateV0>,
1812) {
1813    for fact in decl_facts {
1814        if seen.insert((fact.byte_span.start, fact.byte_span.end, fact.name.clone())) {
1815            candidates.push(OmenaQueryStyleHoverCandidateV0 {
1816                kind: "customPropertyDeclaration",
1817                name: fact.name.clone(),
1818                range: fact.range,
1819                source: "openedStyleDocumentIndex",
1820                namespace: None,
1821            });
1822        }
1823    }
1824
1825    for name in ref_names {
1826        for byte_span in custom_property_ref_byte_spans(source, name) {
1827            if seen.insert((byte_span.start, byte_span.end, name.clone())) {
1828                candidates.push(OmenaQueryStyleHoverCandidateV0 {
1829                    kind: "customPropertyReference",
1830                    name: name.clone(),
1831                    range: parser_range_for_byte_span(source, byte_span),
1832                    source: "openedStyleDocumentIndex",
1833                    namespace: None,
1834                });
1835            }
1836        }
1837    }
1838}
1839
1840fn collect_sass_symbol_hover_candidates(
1841    decl_facts: &[engine_style_parser::ParserIndexSassSymbolDeclFactV0],
1842    ref_facts: &[engine_style_parser::ParserIndexSassSelectorSymbolFactV0],
1843    seen: &mut BTreeSet<(usize, usize, String)>,
1844    candidates: &mut Vec<OmenaQueryStyleHoverCandidateV0>,
1845) {
1846    for fact in decl_facts {
1847        if seen.insert((
1848            fact.byte_span.start,
1849            fact.byte_span.end,
1850            format!("{}:{}", fact.symbol_kind, fact.name),
1851        )) {
1852            candidates.push(OmenaQueryStyleHoverCandidateV0 {
1853                kind: sass_symbol_declaration_candidate_kind(fact.symbol_kind),
1854                name: fact.name.clone(),
1855                range: fact.range,
1856                source: "engineStyleParserSassSymbolFacts",
1857                namespace: None,
1858            });
1859        }
1860    }
1861
1862    for fact in ref_facts {
1863        if seen.insert((
1864            fact.byte_span.start,
1865            fact.byte_span.end,
1866            format!(
1867                "{}:{}:{}",
1868                fact.symbol_kind,
1869                fact.namespace.as_deref().unwrap_or_default(),
1870                fact.name
1871            ),
1872        )) {
1873            candidates.push(OmenaQueryStyleHoverCandidateV0 {
1874                kind: sass_symbol_reference_candidate_kind(fact.symbol_kind, fact.role),
1875                name: fact.name.clone(),
1876                range: fact.range,
1877                source: "engineStyleParserSassSymbolFacts",
1878                namespace: fact.namespace.clone(),
1879            });
1880        }
1881    }
1882}
1883
1884fn collect_sass_partial_evaluator_selector_candidates(
1885    source: &str,
1886    nodes: &[engine_style_parser::SyntaxNode],
1887    seen: &mut BTreeSet<(usize, usize, String)>,
1888    candidates: &mut Vec<OmenaQueryStyleHoverCandidateV0>,
1889) {
1890    for node in nodes {
1891        if let Some(SyntaxNodePayload::AtRule(at_rule)) = &node.payload
1892            && at_rule.kind == AtRuleKind::Include
1893        {
1894            let range_span = ParserByteSpanV0 {
1895                start: node.header_span.unwrap_or(node.span).start,
1896                end: node.header_span.unwrap_or(node.span).end,
1897            };
1898            for selector_name in infer_sass_include_generated_selector_names(&at_rule.params) {
1899                if seen.insert((range_span.start, range_span.end, selector_name.clone())) {
1900                    candidates.push(OmenaQueryStyleHoverCandidateV0 {
1901                        kind: "selector",
1902                        name: selector_name,
1903                        range: parser_range_for_byte_span(source, range_span),
1904                        source: "sassPartialEvaluatorGeneratedSelectors",
1905                        namespace: None,
1906                    });
1907                }
1908            }
1909        }
1910        collect_sass_partial_evaluator_selector_candidates(
1911            source,
1912            &node.children,
1913            seen,
1914            candidates,
1915        );
1916    }
1917}
1918
1919fn infer_sass_include_generated_selector_names(params: &str) -> Vec<String> {
1920    let Some(prefix) = sass_named_argument_string_value(params, "prefix") else {
1921        return Vec::new();
1922    };
1923    if prefix.is_empty() || !prefix.chars().all(is_css_identifier_continue) {
1924        return Vec::new();
1925    }
1926    let mut selectors = sass_first_map_string_keys(params)
1927        .into_iter()
1928        .filter(|key| !key.is_empty() && key.chars().all(is_css_identifier_continue))
1929        .map(|key| format!("{prefix}-{key}"))
1930        .collect::<Vec<_>>();
1931    selectors.sort();
1932    selectors.dedup();
1933    selectors
1934}
1935
1936fn sass_named_argument_string_value(params: &str, name: &str) -> Option<String> {
1937    let needle = format!("${name}");
1938    let mut cursor = 0usize;
1939    while let Some(relative_match) = params[cursor..].find(needle.as_str()) {
1940        let name_start = cursor + relative_match;
1941        let name_end = name_start + needle.len();
1942        if !sass_identifier_boundary(params, name_start, name_end) {
1943            cursor = name_end;
1944            continue;
1945        }
1946        let colon_offset = skip_ascii_whitespace(params, name_end);
1947        if params.as_bytes().get(colon_offset) != Some(&b':') {
1948            cursor = name_end;
1949            continue;
1950        }
1951        let value_start = skip_ascii_whitespace(params, colon_offset + 1);
1952        return sass_string_literal_value(params, value_start).map(|(value, _)| value);
1953    }
1954    None
1955}
1956
1957fn sass_first_map_string_keys(params: &str) -> Vec<String> {
1958    let mut cursor = 0usize;
1959    while cursor < params.len() {
1960        let Some(open_relative) = params[cursor..].find('(') else {
1961            break;
1962        };
1963        let open = cursor + open_relative;
1964        let Some(close) = matching_style_block_end(params, open, b'(', b')') else {
1965            break;
1966        };
1967        let keys = sass_map_string_keys(params, open + 1, close);
1968        if !keys.is_empty() {
1969            return keys;
1970        }
1971        cursor = open + 1;
1972    }
1973    Vec::new()
1974}
1975
1976fn sass_map_string_keys(params: &str, start: usize, end: usize) -> Vec<String> {
1977    split_top_level_style_segments(params, start, end, b',')
1978        .into_iter()
1979        .filter_map(|(entry_start, entry_end)| {
1980            let key_start = skip_ascii_whitespace(params, entry_start);
1981            let (key, key_end) = sass_string_literal_value(params, key_start)?;
1982            let colon_offset = skip_ascii_whitespace(params, key_end);
1983            (colon_offset < entry_end && params.as_bytes().get(colon_offset) == Some(&b':'))
1984                .then_some(key)
1985        })
1986        .collect()
1987}
1988
1989fn sass_string_literal_value(source: &str, quote_offset: usize) -> Option<(String, usize)> {
1990    let quote = source.as_bytes().get(quote_offset).copied()?;
1991    if !matches!(quote, b'\'' | b'"') {
1992        return None;
1993    }
1994    let literal_end = skip_style_string_literal(source, quote_offset, source.len())?;
1995    let value_end = literal_end.saturating_sub(1);
1996    source
1997        .get(quote_offset + 1..value_end)
1998        .map(|value| (value.to_string(), literal_end))
1999}
2000
2001fn sass_identifier_boundary(source: &str, start: usize, end: usize) -> bool {
2002    let before = source
2003        .get(..start)
2004        .and_then(|prefix| prefix.chars().next_back())
2005        .is_none_or(|ch| !is_css_identifier_continue(ch) && ch != '$');
2006    let after = source
2007        .get(end..)
2008        .and_then(|suffix| suffix.chars().next())
2009        .is_none_or(|ch| !is_css_identifier_continue(ch));
2010    before && after
2011}
2012
2013fn sass_symbol_declaration_candidate_kind(symbol_kind: &str) -> &'static str {
2014    match symbol_kind {
2015        "variable" => "sassVariableDeclaration",
2016        "mixin" => "sassMixinDeclaration",
2017        "function" => "sassFunctionDeclaration",
2018        _ => "sassSymbolDeclaration",
2019    }
2020}
2021
2022fn is_sass_symbol_candidate_kind(kind: &str) -> bool {
2023    sass_symbol_kind_from_candidate_kind(kind).is_some()
2024}
2025
2026fn is_sass_symbol_declaration_kind(kind: &str) -> bool {
2027    matches!(
2028        kind,
2029        "sassVariableDeclaration"
2030            | "sassMixinDeclaration"
2031            | "sassFunctionDeclaration"
2032            | "sassSymbolDeclaration"
2033    )
2034}
2035
2036fn sass_symbol_kind_from_candidate_kind(kind: &str) -> Option<&'static str> {
2037    match kind {
2038        "sassVariableDeclaration" | "sassVariableReference" => Some("variable"),
2039        "sassMixinDeclaration" | "sassMixinInclude" | "sassMixinReference" => Some("mixin"),
2040        "sassFunctionDeclaration" | "sassFunctionCall" | "sassFunctionReference" => {
2041            Some("function")
2042        }
2043        "sassSymbolDeclaration" | "sassSymbolReference" => Some("symbol"),
2044        _ => None,
2045    }
2046}
2047
2048fn sass_symbol_reference_candidate_kind(symbol_kind: &str, role: &str) -> &'static str {
2049    match (symbol_kind, role) {
2050        ("variable", _) => "sassVariableReference",
2051        ("mixin", "include") => "sassMixinInclude",
2052        ("function", "call") => "sassFunctionCall",
2053        ("mixin", _) => "sassMixinReference",
2054        ("function", _) => "sassFunctionReference",
2055        _ => "sassSymbolReference",
2056    }
2057}
2058
2059fn sass_variable_value_from_declaration_line(line: &str) -> Option<String> {
2060    let (_, value) = line.split_once(':')?;
2061    let value = value
2062        .trim()
2063        .trim_end_matches(';')
2064        .trim()
2065        .trim_end_matches("!default")
2066        .trim();
2067    (!value.is_empty()).then(|| value.to_string())
2068}
2069
2070fn sass_callable_definition_render_parts(
2071    source: &str,
2072    position: ParserPositionV0,
2073) -> Option<(String, String)> {
2074    let line_start = byte_offset_for_parser_position(
2075        source,
2076        ParserPositionV0 {
2077            line: position.line,
2078            character: 0,
2079        },
2080    )?;
2081    let open_brace = source[line_start..].find('{')? + line_start;
2082    let close_brace = matching_style_block_end(source, open_brace, b'{', b'}')?;
2083    let signature = source[line_start..open_brace].trim().to_string();
2084    let body = source[open_brace + 1..close_brace].trim();
2085    if signature.is_empty() || body.is_empty() {
2086        return None;
2087    }
2088    Some((signature, trim_hover_snippet(body)))
2089}
2090
2091fn rule_snippet_around_position(source: &str, position: ParserPositionV0) -> Option<String> {
2092    let line_start = byte_offset_for_parser_position(
2093        source,
2094        ParserPositionV0 {
2095            line: position.line,
2096            character: 0,
2097        },
2098    )?;
2099    let open_brace = source[line_start..].find('{')? + line_start;
2100    let mut depth = 0usize;
2101    let mut cursor = open_brace;
2102    while cursor < source.len() {
2103        match source.as_bytes().get(cursor).copied()? {
2104            b'{' => depth += 1,
2105            b'}' => {
2106                depth = depth.saturating_sub(1);
2107                if depth == 0 {
2108                    let snippet = source[line_start..=cursor].trim();
2109                    return Some(trim_hover_snippet(snippet));
2110                }
2111            }
2112            _ => {}
2113        }
2114        cursor = advance_style_scan_cursor(source, cursor, source.len());
2115    }
2116    None
2117}
2118
2119fn line_snippet_at_position(source: &str, position: ParserPositionV0) -> Option<String> {
2120    let line_start = byte_offset_for_parser_position(
2121        source,
2122        ParserPositionV0 {
2123            line: position.line,
2124            character: 0,
2125        },
2126    )?;
2127    let line_end = source[line_start..]
2128        .find('\n')
2129        .map(|offset| line_start + offset)
2130        .unwrap_or(source.len());
2131    Some(source[line_start..line_end].trim().to_string())
2132}
2133
2134fn trim_hover_snippet(snippet: &str) -> String {
2135    const MAX_SNIPPET_LEN: usize = 1200;
2136    if snippet.len() <= MAX_SNIPPET_LEN {
2137        return snippet.to_string();
2138    }
2139    let end = char_boundary_floor(snippet, MAX_SNIPPET_LEN);
2140    format!("{}...", snippet[..end].trim_end())
2141}
2142
2143fn custom_property_ref_byte_spans(source: &str, name: &str) -> Vec<ParserByteSpanV0> {
2144    let mut spans = Vec::new();
2145    let mut search_offset = 0usize;
2146
2147    while let Some(relative_match) = source[search_offset..].find(name) {
2148        let name_start = search_offset + relative_match;
2149        let name_end = name_start + name.len();
2150        if source[..name_start].trim_end().ends_with("var(")
2151            && is_selector_name_boundary(source, name_end)
2152        {
2153            spans.push(ParserByteSpanV0 {
2154                start: name_start,
2155                end: name_end,
2156            });
2157        }
2158        search_offset += relative_match + name.len();
2159    }
2160
2161    spans
2162}
2163
2164fn is_selector_name_boundary(source: &str, byte_offset: usize) -> bool {
2165    source[byte_offset..]
2166        .chars()
2167        .next()
2168        .is_none_or(|ch| !is_css_identifier_continue(ch))
2169}
2170
2171fn is_css_identifier_continue(ch: char) -> bool {
2172    ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_')
2173}
2174
2175fn parser_range_for_byte_span(source: &str, span: ParserByteSpanV0) -> ParserRangeV0 {
2176    ParserRangeV0 {
2177        start: parser_position_for_byte_offset(source, span.start),
2178        end: parser_position_for_byte_offset(source, span.end),
2179    }
2180}
2181
2182fn end_of_source_range(source: &str) -> ParserRangeV0 {
2183    let position = parser_position_for_byte_offset(source, source.len());
2184    ParserRangeV0 {
2185        start: position,
2186        end: position,
2187    }
2188}
2189
2190fn parser_position_for_byte_offset(source: &str, offset: usize) -> ParserPositionV0 {
2191    let clamped_offset = offset.min(source.len());
2192    let mut line = 0usize;
2193    let mut character = 0usize;
2194
2195    for (byte_index, ch) in source.char_indices() {
2196        if byte_index >= clamped_offset {
2197            break;
2198        }
2199        if ch == '\n' {
2200            line += 1;
2201            character = 0;
2202        } else {
2203            character += ch.len_utf16();
2204        }
2205    }
2206
2207    ParserPositionV0 { line, character }
2208}
2209
2210fn byte_offset_for_parser_position(source: &str, position: ParserPositionV0) -> Option<usize> {
2211    let mut current_line = 0usize;
2212    let mut current_character = 0usize;
2213
2214    if position.line == 0 && position.character == 0 {
2215        return Some(0);
2216    }
2217
2218    for (byte_index, ch) in source.char_indices() {
2219        if current_line == position.line && current_character == position.character {
2220            return Some(byte_index);
2221        }
2222        if ch == '\n' {
2223            current_line += 1;
2224            current_character = 0;
2225            if current_line == position.line && position.character == 0 {
2226                return Some(byte_index + ch.len_utf8());
2227            }
2228        } else if current_line == position.line {
2229            current_character += ch.len_utf16();
2230        }
2231    }
2232
2233    (current_line == position.line && current_character == position.character)
2234        .then_some(source.len())
2235}
2236
2237fn skip_ascii_whitespace(source: &str, mut offset: usize) -> usize {
2238    while source
2239        .as_bytes()
2240        .get(offset)
2241        .is_some_and(u8::is_ascii_whitespace)
2242    {
2243        offset += 1;
2244    }
2245    offset
2246}
2247
2248fn matching_style_block_end(
2249    source: &str,
2250    open_offset: usize,
2251    open: u8,
2252    close: u8,
2253) -> Option<usize> {
2254    if source.as_bytes().get(open_offset) != Some(&open) {
2255        return None;
2256    }
2257    let mut cursor = advance_style_scan_cursor(source, open_offset, source.len());
2258    let mut depth = 1usize;
2259    while cursor < source.len() {
2260        match source.as_bytes().get(cursor).copied()? {
2261            b'\'' | b'"' | b'`' => {
2262                cursor = skip_style_string_literal(source, cursor, source.len())?;
2263            }
2264            byte if byte == open => {
2265                depth += 1;
2266                cursor = advance_style_scan_cursor(source, cursor, source.len());
2267            }
2268            byte if byte == close => {
2269                depth -= 1;
2270                if depth == 0 {
2271                    return Some(cursor);
2272                }
2273                cursor = advance_style_scan_cursor(source, cursor, source.len());
2274            }
2275            _ => cursor = advance_style_scan_cursor(source, cursor, source.len()),
2276        }
2277    }
2278    None
2279}
2280
2281fn split_top_level_style_segments(
2282    source: &str,
2283    start: usize,
2284    end: usize,
2285    delimiter: u8,
2286) -> Vec<(usize, usize)> {
2287    let mut segments = Vec::new();
2288    let end = char_boundary_floor(source, end);
2289    let mut segment_start = char_boundary_ceil(source, start).min(end);
2290    let mut cursor = segment_start;
2291    let mut depth = 0usize;
2292    while cursor < end {
2293        match source.as_bytes().get(cursor).copied() {
2294            Some(b'\'' | b'"' | b'`') => {
2295                cursor = skip_style_string_literal(source, cursor, end).unwrap_or(end);
2296            }
2297            Some(b'(' | b'[' | b'{') => {
2298                depth += 1;
2299                cursor = advance_style_scan_cursor(source, cursor, end);
2300            }
2301            Some(b')' | b']' | b'}') => {
2302                depth = depth.saturating_sub(1);
2303                cursor = advance_style_scan_cursor(source, cursor, end);
2304            }
2305            Some(byte) if byte == delimiter && depth == 0 => {
2306                segments.push((segment_start, cursor));
2307                cursor = advance_style_scan_cursor(source, cursor, end);
2308                segment_start = cursor;
2309            }
2310            Some(_) => cursor = advance_style_scan_cursor(source, cursor, end),
2311            None => break,
2312        }
2313    }
2314    if segment_start <= end {
2315        segments.push((segment_start, end));
2316    }
2317    segments
2318}
2319
2320fn skip_style_string_literal(source: &str, quote_offset: usize, limit: usize) -> Option<usize> {
2321    let quote = source.as_bytes().get(quote_offset).copied()?;
2322    let limit = char_boundary_floor(source, limit);
2323    let mut cursor = quote_offset + 1;
2324    while cursor < limit {
2325        let byte = source.as_bytes().get(cursor).copied()?;
2326        if byte == b'\\' {
2327            cursor = advance_style_escaped_char(source, cursor, limit);
2328            continue;
2329        }
2330        if byte == quote {
2331            return Some(cursor + 1);
2332        }
2333        cursor = advance_style_scan_cursor(source, cursor, limit);
2334    }
2335    None
2336}
2337
2338fn advance_style_escaped_char(source: &str, slash_offset: usize, limit: usize) -> usize {
2339    let after_slash = advance_style_scan_cursor(source, slash_offset, limit);
2340    advance_style_scan_cursor(source, after_slash, limit)
2341}
2342
2343fn advance_style_scan_cursor(source: &str, cursor: usize, limit: usize) -> usize {
2344    let cursor = char_boundary_ceil(source, cursor);
2345    let limit = char_boundary_floor(source, limit);
2346    if cursor >= limit {
2347        return limit;
2348    }
2349    char_boundary_ceil(source, cursor + 1).min(limit)
2350}
2351
2352fn char_boundary_floor(source: &str, index: usize) -> usize {
2353    let mut index = index.min(source.len());
2354    while index > 0 && !source.is_char_boundary(index) {
2355        index -= 1;
2356    }
2357    index
2358}
2359
2360fn char_boundary_ceil(source: &str, index: usize) -> usize {
2361    let mut index = index.min(source.len());
2362    while index < source.len() && !source.is_char_boundary(index) {
2363        index += 1;
2364    }
2365    index
2366}
2367
2368fn source_selector_candidate_matches_definition(
2369    candidate: &OmenaQuerySourceSelectorCandidateV0,
2370    definition: &OmenaQueryStyleSelectorDefinitionV0,
2371) -> bool {
2372    let selector_matches = if candidate.kind == "sourceSelectorPrefixReference" {
2373        definition.name.starts_with(candidate.name.as_str())
2374    } else {
2375        definition.name == candidate.name
2376    };
2377    selector_matches
2378        && candidate
2379            .target_style_uri
2380            .as_deref()
2381            .is_none_or(|target_uri| target_uri == definition.uri)
2382}
2383
2384fn source_reference_matches_target_style(
2385    reference: &OmenaQuerySourceSelectorReferenceEditTargetV0,
2386    target_style_uri: Option<&str>,
2387) -> bool {
2388    target_style_uri.is_none_or(|target_uri| {
2389        reference
2390            .target_style_uri
2391            .as_deref()
2392            .is_none_or(|candidate_target_uri| candidate_target_uri == target_uri)
2393    })
2394}
2395
2396fn is_sass_builtin_module_source(source: &str) -> bool {
2397    source.starts_with("sass:")
2398}
2399
2400fn style_language_label(language: StyleLanguage) -> &'static str {
2401    match language {
2402        StyleLanguage::Css => "css",
2403        StyleLanguage::Scss => "scss",
2404        StyleLanguage::Less => "less",
2405    }
2406}
2407
2408fn push_unique_string(values: &mut Vec<String>, value: String) {
2409    if !values.contains(&value) {
2410        values.push(value);
2411    }
2412}
2413
2414#[cfg(test)]
2415mod tests {
2416    use engine_input_producers::{
2417        ClassExpressionInputV2, EngineInputV2, PositionV2, RangeV2, SourceAnalysisInputV2,
2418        SourceDocumentV2, StringTypeFactsV2, StyleAnalysisInputV2, StyleDocumentV2,
2419        StyleSelectorV2, TypeFactEntryV2,
2420    };
2421    use omena_abstract_value::SelectorProjectionCertaintyV0;
2422
2423    use super::{
2424        OmenaQueryExpressionDomainFlowRuntimeV0, OmenaQueryStylePackageManifestV0,
2425        SelectedQueryAdapterCapabilitiesV0, summarize_omena_query_boundary,
2426        summarize_omena_query_expression_domain_control_flow_analysis,
2427        summarize_omena_query_expression_domain_flow_analysis,
2428        summarize_omena_query_expression_domain_incremental_flow_analysis,
2429        summarize_omena_query_expression_domain_selector_projection,
2430        summarize_omena_query_expression_semantics_canonical_producer_signal,
2431        summarize_omena_query_expression_semantics_query_fragments,
2432        summarize_omena_query_fragment_bundle,
2433        summarize_omena_query_selected_query_adapter_capabilities,
2434        summarize_omena_query_selector_usage_canonical_producer_signal,
2435        summarize_omena_query_selector_usage_query_fragments,
2436        summarize_omena_query_source_resolution_canonical_producer_signal,
2437        summarize_omena_query_source_resolution_query_fragments,
2438        summarize_omena_query_source_resolution_runtime,
2439        summarize_omena_query_style_semantic_graph_batch_from_sources,
2440        summarize_omena_query_style_semantic_graph_batch_from_sources_with_package_manifests,
2441        summarize_omena_query_style_semantic_graph_from_source,
2442    };
2443
2444    #[test]
2445    fn summarizes_query_boundary_over_producer_fragments() {
2446        let input = sample_input();
2447        let summary = summarize_omena_query_boundary(&input);
2448
2449        assert_eq!(summary.schema_version, "0");
2450        assert_eq!(summary.product, "omena-query.boundary");
2451        assert_eq!(summary.query_engine_name, "omena-query");
2452        assert_eq!(summary.input_version, "2");
2453        assert_eq!(
2454            summary.abstract_value_domain.product,
2455            "omena-abstract-value.domain"
2456        );
2457        assert_eq!(
2458            summary.selected_query_adapter_capabilities.product,
2459            "omena-query.selected-query-adapter-capabilities"
2460        );
2461        assert_eq!(summary.expression_semantics_query_count, 2);
2462        assert_eq!(summary.source_resolution_query_count, 2);
2463        assert_eq!(summary.selector_usage_query_count, 2);
2464        assert_eq!(summary.total_query_count, 6);
2465        assert!(
2466            summary
2467                .ready_surfaces
2468                .contains(&"abstractValueProjectionContract")
2469        );
2470        assert!(
2471            summary
2472                .ready_surfaces
2473                .contains(&"sourceResolutionResolverBoundary")
2474        );
2475        assert!(
2476            summary
2477                .delegated_fragment_products
2478                .contains(&"omena-resolver.boundary")
2479        );
2480        assert!(
2481            summary
2482                .delegated_fragment_products
2483                .contains(&"omena-resolver.source-resolution-runtime-index")
2484        );
2485        assert!(
2486            summary
2487                .delegated_fragment_products
2488                .contains(&"engine-input-producers.expression-domain-flow-analysis")
2489        );
2490        assert!(
2491            summary
2492                .delegated_fragment_products
2493                .contains(&"engine-input-producers.expression-domain-control-flow-analysis")
2494        );
2495        assert!(
2496            summary
2497                .delegated_fragment_products
2498                .contains(&"omena-query.expression-domain-incremental-flow-analysis")
2499        );
2500        assert!(
2501            summary
2502                .delegated_fragment_products
2503                .contains(&"omena-query.expression-domain-selector-projection")
2504        );
2505        assert!(
2506            summary
2507                .ready_surfaces
2508                .contains(&"expressionDomainFlowAnalysisBoundary")
2509        );
2510        assert!(
2511            summary
2512                .ready_surfaces
2513                .contains(&"expressionDomainControlFlowAnalysisBoundary")
2514        );
2515        assert!(
2516            summary
2517                .ready_surfaces
2518                .contains(&"expressionDomainSalsaRuntime")
2519        );
2520        assert!(
2521            summary
2522                .ready_surfaces
2523                .contains(&"expressionDomainSelectorProjection")
2524        );
2525        assert!(
2526            summary
2527                .ready_surfaces
2528                .contains(&"sourceResolutionRuntimeIndex")
2529        );
2530        assert!(
2531            summary
2532                .cme_coupled_surfaces
2533                .contains(&"producerQueryFragments")
2534        );
2535    }
2536
2537    #[test]
2538    fn bundles_expression_source_and_selector_query_fragments() {
2539        let input = sample_input();
2540        let bundle = summarize_omena_query_fragment_bundle(&input);
2541
2542        assert_eq!(bundle.schema_version, "0");
2543        assert_eq!(bundle.product, "omena-query.fragment-bundle");
2544        assert_eq!(bundle.input_version, "2");
2545        assert_eq!(bundle.expression_semantics.fragments.len(), 2);
2546        assert_eq!(bundle.expression_semantics.fragments[0].query_id, "expr-1");
2547        assert_eq!(bundle.source_resolution.fragments.len(), 2);
2548        assert_eq!(bundle.source_resolution.fragments[1].query_id, "expr-2");
2549        assert_eq!(bundle.selector_usage.fragments.len(), 2);
2550        assert_eq!(bundle.selector_usage.fragments[0].query_id, "btn-active");
2551
2552        let expression = summarize_omena_query_expression_semantics_query_fragments(&input);
2553        let source = summarize_omena_query_source_resolution_query_fragments(&input);
2554        let selector = summarize_omena_query_selector_usage_query_fragments(&input);
2555
2556        assert_eq!(expression.schema_version, "0");
2557        assert_eq!(source.schema_version, "0");
2558        assert_eq!(selector.schema_version, "0");
2559        assert_eq!(expression.input_version, "2");
2560        assert_eq!(source.input_version, "2");
2561        assert_eq!(selector.input_version, "2");
2562        assert_eq!(
2563            expression.fragments.len(),
2564            bundle.expression_semantics.fragments.len()
2565        );
2566        assert_eq!(
2567            source.fragments.len(),
2568            bundle.source_resolution.fragments.len()
2569        );
2570        assert_eq!(
2571            selector.fragments.len(),
2572            bundle.selector_usage.fragments.len()
2573        );
2574    }
2575
2576    #[test]
2577    fn declares_selected_query_adapter_capabilities_without_flipping_runtime_routing() {
2578        let summary = summarize_omena_query_selected_query_adapter_capabilities();
2579
2580        assert_eq!(summary.schema_version, "0");
2581        assert_eq!(
2582            summary.product,
2583            "omena-query.selected-query-adapter-capabilities"
2584        );
2585        assert_eq!(summary.default_candidate_backend, "rust-selected-query");
2586        assert_eq!(summary.routing_status, "declaredOnly");
2587
2588        let unified = backend(&summary, "rust-selected-query");
2589        assert!(unified.is_some());
2590        let Some(unified) = unified else {
2591            return;
2592        };
2593        assert!(unified.source_resolution);
2594        assert!(unified.expression_semantics);
2595        assert!(unified.selector_usage);
2596        assert!(unified.style_semantic_graph);
2597
2598        let source_only = backend(&summary, "rust-source-resolution");
2599        assert!(source_only.is_some());
2600        let Some(source_only) = source_only else {
2601            return;
2602        };
2603        assert!(source_only.source_resolution);
2604        assert!(!source_only.expression_semantics);
2605        assert!(!source_only.selector_usage);
2606        assert!(!source_only.style_semantic_graph);
2607
2608        assert!(
2609            summary
2610                .runner_commands
2611                .iter()
2612                .any(|command| command.command == "input-omena-resolver-source-resolution-runtime")
2613        );
2614        assert!(
2615            summary
2616                .runner_commands
2617                .iter()
2618                .any(|command| command.command == "input-expression-domain-flow-analysis")
2619        );
2620        assert!(
2621            summary.runner_commands.iter().any(|command| {
2622                command.command == "input-expression-domain-control-flow-analysis"
2623            })
2624        );
2625        assert!(summary.runner_commands.iter().any(|command| {
2626            command.command == "input-expression-domain-incremental-flow-analysis"
2627        }));
2628        assert!(
2629            summary
2630                .runner_commands
2631                .iter()
2632                .any(|command| command.command == "input-expression-domain-selector-projection")
2633        );
2634        assert!(
2635            summary
2636                .runner_commands
2637                .iter()
2638                .any(|command| command.command == "style-semantic-graph-batch")
2639        );
2640        assert!(
2641            summary
2642                .expression_semantics_payload_contracts
2643                .contains(&"valueDomainDerivation")
2644        );
2645        assert!(summary.adapter_readiness.contains(&"runnerCommandContract"));
2646        assert!(
2647            summary
2648                .adapter_readiness
2649                .contains(&"canonicalProducerWrapperBoundary")
2650        );
2651        assert!(
2652            summary
2653                .adapter_readiness
2654                .contains(&"styleSemanticGraphBridgeBoundary")
2655        );
2656        assert!(
2657            summary
2658                .adapter_readiness
2659                .contains(&"expressionDomainFlowAnalysisRunner")
2660        );
2661        assert!(
2662            summary
2663                .adapter_readiness
2664                .contains(&"expressionDomainControlFlowAnalysisRunner")
2665        );
2666        assert!(
2667            summary
2668                .adapter_readiness
2669                .contains(&"expressionDomainSalsaRuntime")
2670        );
2671        assert!(
2672            summary
2673                .adapter_readiness
2674                .contains(&"expressionDomainSelectorProjection")
2675        );
2676        assert!(
2677            summary
2678                .adapter_readiness
2679                .contains(&"sourceResolutionRuntimeIndex")
2680        );
2681    }
2682
2683    #[test]
2684    fn owns_expression_domain_flow_analysis_wrapper_without_changing_product() {
2685        let input = sample_input();
2686        let summary = summarize_omena_query_expression_domain_flow_analysis(&input);
2687
2688        assert_eq!(summary.schema_version, "0");
2689        assert_eq!(
2690            summary.product,
2691            "engine-input-producers.expression-domain-flow-analysis"
2692        );
2693        assert_eq!(summary.input_version, "2");
2694        assert_eq!(summary.analyses.len(), 2);
2695        assert!(
2696            summary
2697                .analyses
2698                .iter()
2699                .all(|entry| entry.analysis.product == "omena-abstract-value.flow-analysis")
2700        );
2701        assert!(
2702            summary
2703                .analyses
2704                .iter()
2705                .all(|entry| entry.analysis.converged)
2706        );
2707    }
2708
2709    #[test]
2710    fn owns_expression_domain_control_flow_analysis_wrapper_without_changing_product() {
2711        let input = sample_input();
2712        let summary = summarize_omena_query_expression_domain_control_flow_analysis(&input);
2713
2714        assert_eq!(summary.schema_version, "0");
2715        assert_eq!(
2716            summary.product,
2717            "engine-input-producers.expression-domain-control-flow-analysis"
2718        );
2719        assert_eq!(summary.input_version, "2");
2720        assert_eq!(summary.analyses.len(), 2);
2721        assert!(
2722            summary
2723                .analyses
2724                .iter()
2725                .all(|entry| entry.analysis.product
2726                    == "omena-abstract-value.control-flow-analysis")
2727        );
2728    }
2729
2730    #[test]
2731    fn reuses_expression_domain_flow_analysis_through_salsa_runtime() {
2732        let input = sample_input();
2733        let mut runtime = OmenaQueryExpressionDomainFlowRuntimeV0::default();
2734
2735        let first =
2736            summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
2737        assert_eq!(
2738            first.product,
2739            "omena-query.expression-domain-incremental-flow-analysis"
2740        );
2741        assert_eq!(first.revision, 1);
2742        assert_eq!(first.graph_count, 2);
2743        assert_eq!(first.dirty_graph_count, 2);
2744        assert_eq!(first.reused_graph_count, 0);
2745        assert_eq!(runtime.graph_count(), 2);
2746
2747        let second =
2748            summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
2749        assert_eq!(second.revision, 2);
2750        assert_eq!(second.graph_count, 2);
2751        assert_eq!(second.dirty_graph_count, 0);
2752        assert_eq!(second.reused_graph_count, 2);
2753        assert!(
2754            second
2755                .analyses
2756                .iter()
2757                .all(|entry| entry.analysis.reused_previous_analysis)
2758        );
2759    }
2760
2761    #[test]
2762    fn projects_reduced_product_flow_to_target_style_selectors() {
2763        let input = reduced_product_projection_input();
2764        let summary = summarize_omena_query_expression_domain_selector_projection(&input);
2765
2766        assert_eq!(summary.schema_version, "0");
2767        assert_eq!(
2768            summary.product,
2769            "omena-query.expression-domain-selector-projection"
2770        );
2771        assert_eq!(summary.input_version, "2");
2772        assert_eq!(summary.projection_count, 3);
2773
2774        let merge = summary
2775            .projections
2776            .iter()
2777            .find(|projection| projection.node_id == "file-merge");
2778        assert!(merge.is_some());
2779        let Some(merge) = merge else {
2780            return;
2781        };
2782        assert_eq!(merge.graph_id, "/tmp/App.tsx:expression-domain-flow");
2783        assert_eq!(merge.file_path, "/tmp/App.tsx");
2784        assert_eq!(
2785            merge.target_style_paths,
2786            vec!["/tmp/App.module.scss".to_string()]
2787        );
2788        assert_eq!(merge.value_kind, "composite");
2789        assert_eq!(
2790            merge.selector_names,
2791            vec![
2792                "btn-primary--active".to_string(),
2793                "btn-secondary--active".to_string()
2794            ]
2795        );
2796        assert_eq!(merge.certainty, SelectorProjectionCertaintyV0::Inferred);
2797    }
2798
2799    #[test]
2800    fn owns_selected_query_canonical_producer_wrappers_without_changing_products() {
2801        let input = sample_input();
2802
2803        let source = summarize_omena_query_source_resolution_canonical_producer_signal(&input);
2804        assert_eq!(source.schema_version, "0");
2805        assert_eq!(source.input_version, "2");
2806        assert_eq!(source.canonical_bundle.query_fragments.len(), 2);
2807        assert_eq!(source.evaluator_candidates.results.len(), 2);
2808
2809        let expression =
2810            summarize_omena_query_expression_semantics_canonical_producer_signal(&input);
2811        assert_eq!(expression.schema_version, "0");
2812        assert_eq!(expression.input_version, "2");
2813        assert_eq!(expression.canonical_bundle.query_fragments.len(), 2);
2814        assert_eq!(expression.evaluator_candidates.results.len(), 2);
2815        assert_eq!(
2816            expression.evaluator_candidates.results[0]
2817                .payload
2818                .value_domain_derivation
2819                .product,
2820            "omena-abstract-value.reduced-class-value-derivation"
2821        );
2822        assert_eq!(
2823            expression.evaluator_candidates.results[0]
2824                .payload
2825                .value_domain_derivation
2826                .reduced_kind,
2827            "prefixSuffix"
2828        );
2829
2830        let selector = summarize_omena_query_selector_usage_canonical_producer_signal(&input);
2831        assert_eq!(selector.schema_version, "0");
2832        assert_eq!(selector.input_version, "2");
2833        assert_eq!(selector.canonical_bundle.query_fragments.len(), 2);
2834        assert_eq!(selector.evaluator_candidates.results.len(), 2);
2835    }
2836
2837    #[test]
2838    fn owns_source_resolution_runtime_index_wrapper() {
2839        let input = sample_input();
2840        let runtime_index = summarize_omena_query_source_resolution_runtime(&input);
2841
2842        assert_eq!(
2843            runtime_index.product,
2844            "omena-resolver.source-resolution-runtime-index"
2845        );
2846        assert_eq!(runtime_index.expression_count, 2);
2847        assert_eq!(runtime_index.resolved_expression_count, 2);
2848        assert_eq!(runtime_index.unresolved_expression_count, 0);
2849        assert!(
2850            runtime_index
2851                .entries
2852                .iter()
2853                .any(|entry| entry.expression_id == "expr-1"
2854                    && entry.selector_names == ["btn-active"])
2855        );
2856    }
2857
2858    #[test]
2859    fn owns_style_semantic_graph_adapter_boundary_without_changing_graph_product() {
2860        let input = sample_input();
2861        let graph = summarize_omena_query_style_semantic_graph_from_source(
2862            "/tmp/App.module.scss",
2863            ".btn-active { color: red; }",
2864            &input,
2865        );
2866        assert!(graph.is_some());
2867        let Some(graph) = graph else {
2868            return;
2869        };
2870        assert_eq!(graph.schema_version, "0");
2871        assert_eq!(graph.product, "omena-semantic.style-semantic-graph");
2872        assert_eq!(graph.selector_identity_engine.canonical_ids.len(), 1);
2873
2874        let batch = summarize_omena_query_style_semantic_graph_batch_from_sources(
2875            [
2876                ("/tmp/App.module.scss", ".btn-active { color: red; }"),
2877                ("/tmp/Card.module.scss", ".card-header { color: blue; }"),
2878            ],
2879            &input,
2880        );
2881        assert_eq!(batch.schema_version, "0");
2882        assert_eq!(batch.product, "omena-semantic.style-semantic-graph-batch");
2883        assert_eq!(batch.graphs.len(), 2);
2884        assert_eq!(batch.graphs[0].style_path, "/tmp/App.module.scss");
2885        assert!(batch.graphs[0].graph.is_some());
2886        assert!(batch.graphs[1].graph.is_some());
2887    }
2888
2889    #[test]
2890    fn style_semantic_graph_batch_feeds_workspace_design_token_candidates() {
2891        let input = sample_input();
2892        let batch = summarize_omena_query_style_semantic_graph_batch_from_sources(
2893            [
2894                ("/tmp/tokens.module.scss", ":root { --brand: red; }"),
2895                ("/tmp/theme.module.scss", "@forward \"./tokens\";"),
2896                ("/tmp/unrelated.module.scss", ":root { --brand: blue; }"),
2897                (
2898                    "/tmp/App.module.scss",
2899                    "@use \"./theme\";\n.button { color: var(--brand); }",
2900                ),
2901            ],
2902            &input,
2903        );
2904
2905        let app_graph = batch
2906            .graphs
2907            .iter()
2908            .find(|entry| entry.style_path == "/tmp/App.module.scss")
2909            .and_then(|entry| entry.graph.as_ref());
2910        assert!(app_graph.is_some());
2911        let Some(app_graph) = app_graph else {
2912            return;
2913        };
2914        let design_tokens = &app_graph.design_token_semantics;
2915
2916        assert_eq!(
2917            design_tokens.status,
2918            "cross-file-import-cascade-ranking-seed"
2919        );
2920        assert_eq!(
2921            design_tokens.resolution_scope,
2922            "cross-file-import-candidate"
2923        );
2924        assert!(
2925            design_tokens
2926                .capabilities
2927                .workspace_cascade_candidate_signal_ready
2928        );
2929        assert!(design_tokens.capabilities.cross_file_import_graph_ready);
2930        assert_eq!(
2931            design_tokens
2932                .resolution_signal
2933                .cross_file_declaration_fact_count,
2934            1
2935        );
2936        assert_eq!(
2937            design_tokens
2938                .resolution_signal
2939                .workspace_occurrence_resolved_reference_count,
2940            1
2941        );
2942        assert_eq!(
2943            design_tokens
2944                .cascade_ranking_signal
2945                .cross_file_candidate_declaration_count,
2946            1
2947        );
2948        assert_eq!(
2949            design_tokens
2950                .cascade_ranking_signal
2951                .cross_file_winner_declaration_count,
2952            1
2953        );
2954        assert_eq!(
2955            design_tokens.cascade_ranking_signal.ranked_references[0]
2956                .winner_declaration_file_path
2957                .as_deref(),
2958            Some("/tmp/tokens.module.scss")
2959        );
2960        let winner_range =
2961            design_tokens.cascade_ranking_signal.ranked_references[0].winner_declaration_range;
2962        assert_eq!(winner_range.map(|range| range.start.line), Some(0));
2963        assert_eq!(winner_range.map(|range| range.start.character), Some(8));
2964        assert_eq!(design_tokens.declaration_candidates.len(), 1);
2965        let declaration_candidate = &design_tokens.declaration_candidates[0];
2966        assert_eq!(declaration_candidate.name, "--brand");
2967        assert_eq!(declaration_candidate.file_path, "/tmp/tokens.module.scss");
2968        assert_eq!(
2969            declaration_candidate.candidate_scope,
2970            "cross-file-import-candidate"
2971        );
2972        assert!(declaration_candidate.import_graph_distance.is_some());
2973        assert_eq!(
2974            design_tokens.cascade_ranking_signal.ranked_references[0]
2975                .cross_file_candidate_declaration_count,
2976            1
2977        );
2978    }
2979
2980    #[test]
2981    fn style_semantic_graph_batch_prefers_nearer_import_graph_token_candidates() {
2982        let input = sample_input();
2983        let batch = summarize_omena_query_style_semantic_graph_batch_from_sources(
2984            [
2985                ("/tmp/a-direct.module.scss", ":root { --brand: direct; }"),
2986                ("/tmp/mid.module.scss", "@forward \"./z-transitive\";"),
2987                (
2988                    "/tmp/z-transitive.module.scss",
2989                    ":root { --brand: transitive; }",
2990                ),
2991                (
2992                    "/tmp/App.module.scss",
2993                    "@use \"./a-direct\";\n@use \"./mid\";\n.button { color: var(--brand); }",
2994                ),
2995            ],
2996            &input,
2997        );
2998
2999        let app_graph = batch
3000            .graphs
3001            .iter()
3002            .find(|entry| entry.style_path == "/tmp/App.module.scss")
3003            .and_then(|entry| entry.graph.as_ref());
3004        assert!(app_graph.is_some());
3005        let Some(app_graph) = app_graph else {
3006            return;
3007        };
3008        let ranked_reference = &app_graph
3009            .design_token_semantics
3010            .cascade_ranking_signal
3011            .ranked_references[0];
3012
3013        assert_eq!(
3014            ranked_reference.winner_declaration_file_path.as_deref(),
3015            Some("/tmp/a-direct.module.scss")
3016        );
3017        assert_eq!(ranked_reference.winner_import_graph_distance, Some(1));
3018        assert_eq!(ranked_reference.winner_import_graph_order, Some(0));
3019        assert_eq!(ranked_reference.cross_file_candidate_declaration_count, 2);
3020        assert_eq!(ranked_reference.cross_file_shadowed_declaration_count, 1);
3021    }
3022
3023    #[test]
3024    fn style_semantic_graph_batch_resolves_package_root_forward_chain_token_candidates() {
3025        let input = sample_input();
3026        let batch = summarize_omena_query_style_semantic_graph_batch_from_sources(
3027            [
3028                (
3029                    "/fake/workspace/node_modules/@design/tokens/src/index.scss",
3030                    "@forward \"./colors\";",
3031                ),
3032                (
3033                    "/fake/workspace/node_modules/@design/tokens/src/_colors.scss",
3034                    ":root { --brand: package; }",
3035                ),
3036                (
3037                    "/fake/workspace/src/_utils.scss",
3038                    "@forward \"@design/tokens\" as ds_*;",
3039                ),
3040                (
3041                    "/fake/workspace/src/App.module.scss",
3042                    "@use \"./utils\";\n.button { color: var(--brand); }",
3043                ),
3044            ],
3045            &input,
3046        );
3047
3048        let app_graph = batch
3049            .graphs
3050            .iter()
3051            .find(|entry| entry.style_path == "/fake/workspace/src/App.module.scss")
3052            .and_then(|entry| entry.graph.as_ref());
3053        assert!(app_graph.is_some());
3054        let Some(app_graph) = app_graph else {
3055            return;
3056        };
3057        let ranked_reference = &app_graph
3058            .design_token_semantics
3059            .cascade_ranking_signal
3060            .ranked_references[0];
3061
3062        assert_eq!(
3063            ranked_reference.winner_declaration_file_path.as_deref(),
3064            Some("/fake/workspace/node_modules/@design/tokens/src/_colors.scss")
3065        );
3066        assert_eq!(ranked_reference.winner_import_graph_distance, Some(3));
3067        assert_eq!(ranked_reference.cross_file_candidate_declaration_count, 1);
3068    }
3069
3070    #[test]
3071    fn style_semantic_graph_batch_resolves_package_manifest_style_exports() {
3072        let input = sample_input();
3073        let batch =
3074            summarize_omena_query_style_semantic_graph_batch_from_sources_with_package_manifests(
3075                [
3076                    (
3077                        "/fake/workspace/node_modules/@design/tokens/dist/theme.css",
3078                        ":root { --brand: package; }",
3079                    ),
3080                    (
3081                        "/fake/workspace/src/App.module.scss",
3082                        "@use \"@design/tokens/theme\";\n.button { color: var(--brand); }",
3083                    ),
3084                ],
3085                &input,
3086                &[OmenaQueryStylePackageManifestV0 {
3087                    package_json_path: "/fake/workspace/node_modules/@design/tokens/package.json"
3088                        .to_string(),
3089                    package_json_source: r#"{"exports":{"./theme":{"style":"./dist/theme.css"}}}"#
3090                        .to_string(),
3091                }],
3092            );
3093
3094        let app_graph = batch
3095            .graphs
3096            .iter()
3097            .find(|entry| entry.style_path == "/fake/workspace/src/App.module.scss")
3098            .and_then(|entry| entry.graph.as_ref());
3099        assert!(app_graph.is_some());
3100        let Some(app_graph) = app_graph else {
3101            return;
3102        };
3103        let ranked_reference = &app_graph
3104            .design_token_semantics
3105            .cascade_ranking_signal
3106            .ranked_references[0];
3107
3108        assert_eq!(
3109            ranked_reference.winner_declaration_file_path.as_deref(),
3110            Some("/fake/workspace/node_modules/@design/tokens/dist/theme.css")
3111        );
3112        assert_eq!(ranked_reference.winner_import_graph_distance, Some(1));
3113        assert_eq!(ranked_reference.cross_file_candidate_declaration_count, 1);
3114        let declaration_candidate = &app_graph.design_token_semantics.declaration_candidates[0];
3115        assert_eq!(declaration_candidate.name, "--brand");
3116        assert_eq!(
3117            declaration_candidate.file_path,
3118            "/fake/workspace/node_modules/@design/tokens/dist/theme.css"
3119        );
3120        assert_eq!(
3121            declaration_candidate.candidate_scope,
3122            "cross-file-import-candidate"
3123        );
3124        assert_eq!(declaration_candidate.import_graph_distance, Some(1));
3125    }
3126
3127    #[test]
3128    fn style_hover_candidates_are_query_owned() {
3129        let candidates = super::summarize_omena_query_style_hover_candidates(
3130            "Component.module.scss",
3131            r#"
3132@mixin variants($prefix, $map) {
3133  @each $name, $value in $map {
3134    .#{$prefix}-#{$name} { color: $value; }
3135  }
3136}
3137
3138$accent: red;
3139.button { color: var(--brand); }
3140:root { --brand: blue; }
3141@include variants($prefix: "tone", $map: ("warm": red));
3142"#,
3143        );
3144        assert!(candidates.is_some());
3145        let Some(candidates) = candidates else {
3146            return;
3147        };
3148
3149        assert_eq!(candidates.product, "omena-query.style-hover-candidates");
3150        assert!(
3151            candidates
3152                .candidates
3153                .iter()
3154                .any(|candidate| candidate.kind == "selector" && candidate.name == "button")
3155        );
3156        assert!(candidates.candidates.iter().any(|candidate| {
3157            candidate.kind == "customPropertyReference" && candidate.name == "--brand"
3158        }));
3159        assert!(candidates.candidates.iter().any(|candidate| {
3160            candidate.kind == "sassVariableDeclaration" && candidate.name == "accent"
3161        }));
3162        assert!(
3163            candidates
3164                .candidates
3165                .iter()
3166                .any(
3167                    |candidate| candidate.source == "sassPartialEvaluatorGeneratedSelectors"
3168                        && candidate.name == "tone-warm"
3169                )
3170        );
3171    }
3172
3173    #[test]
3174    fn style_hover_render_parts_are_query_owned() {
3175        let source = r#"$accent: red !default;
3176@mixin tone($color) {
3177  color: $color;
3178}
3179.button { color: var(--brand); }
3180"#;
3181
3182        let variable = super::summarize_omena_query_style_hover_render_parts(
3183            source,
3184            "sassVariableDeclaration",
3185            "accent",
3186            engine_style_parser::ParserPositionV0 {
3187                line: 0,
3188                character: 1,
3189            },
3190        );
3191        assert_eq!(variable.product, "omena-query.style-hover-render-parts");
3192        assert_eq!(variable.value.as_deref(), Some("red"));
3193        assert_eq!(variable.snippet, "$accent: red !default;");
3194
3195        let mixin = super::summarize_omena_query_style_hover_render_parts(
3196            source,
3197            "sassMixinDeclaration",
3198            "tone",
3199            engine_style_parser::ParserPositionV0 {
3200                line: 1,
3201                character: 7,
3202            },
3203        );
3204        assert_eq!(mixin.signature.as_deref(), Some("@mixin tone($color)"));
3205        assert_eq!(mixin.snippet, "color: $color;");
3206        assert_eq!(mixin.render_source, "callableBlockSnippet");
3207
3208        let selector = super::summarize_omena_query_style_hover_render_parts(
3209            source,
3210            "selector",
3211            "button",
3212            engine_style_parser::ParserPositionV0 {
3213                line: 4,
3214                character: 1,
3215            },
3216        );
3217        assert_eq!(selector.snippet, ".button { color: var(--brand); }");
3218        assert_eq!(selector.render_source, "ruleSnippet");
3219    }
3220
3221    #[test]
3222    fn missing_custom_property_diagnostics_are_query_owned() {
3223        let source = ":root { --brand: red; }\n.alert { color: var(--missing); }";
3224        let candidates =
3225            super::summarize_omena_query_style_hover_candidates("Component.module.scss", source);
3226        assert!(candidates.is_some());
3227        let Some(candidates) = candidates else {
3228            return;
3229        };
3230
3231        let diagnostics = super::summarize_omena_query_missing_custom_property_diagnostics(
3232            "file:///workspace/src/Component.module.scss",
3233            source,
3234            candidates.candidates.as_slice(),
3235        );
3236
3237        assert_eq!(diagnostics.len(), 1);
3238        let diagnostic = &diagnostics[0];
3239        assert_eq!(diagnostic.code, "missingCustomProperty");
3240        assert_eq!(
3241            diagnostic.message,
3242            "CSS custom property '--missing' not found in indexed style tokens."
3243        );
3244        assert_eq!(
3245            diagnostic.range,
3246            engine_style_parser::ParserRangeV0 {
3247                start: engine_style_parser::ParserPositionV0 {
3248                    line: 1,
3249                    character: 20,
3250                },
3251                end: engine_style_parser::ParserPositionV0 {
3252                    line: 1,
3253                    character: 29,
3254                },
3255            }
3256        );
3257        assert_eq!(
3258            diagnostic
3259                .create_custom_property
3260                .as_ref()
3261                .map(|action| action.new_text.as_str()),
3262            Some("\n\n:root {\n  --missing: ;\n}\n")
3263        );
3264        assert_eq!(
3265            diagnostic
3266                .create_custom_property
3267                .as_ref()
3268                .map(|action| action.range),
3269            Some(engine_style_parser::ParserRangeV0 {
3270                start: engine_style_parser::ParserPositionV0 {
3271                    line: 1,
3272                    character: 33,
3273                },
3274                end: engine_style_parser::ParserPositionV0 {
3275                    line: 1,
3276                    character: 33,
3277                },
3278            })
3279        );
3280    }
3281
3282    #[test]
3283    fn missing_selector_diagnostics_are_query_owned() {
3284        let diagnostic = super::summarize_omena_query_missing_selector_diagnostic(
3285            "file:///workspace/src/App.module.scss",
3286            ".root {\n}\n",
3287            "missing",
3288            engine_style_parser::ParserRangeV0 {
3289                start: engine_style_parser::ParserPositionV0 {
3290                    line: 2,
3291                    character: 18,
3292                },
3293                end: engine_style_parser::ParserPositionV0 {
3294                    line: 2,
3295                    character: 25,
3296                },
3297            },
3298        );
3299
3300        assert_eq!(diagnostic.code, "missingSelector");
3301        assert_eq!(
3302            diagnostic.message,
3303            "CSS Module selector '.missing' not found in indexed style tokens."
3304        );
3305        assert_eq!(
3306            diagnostic
3307                .create_selector
3308                .as_ref()
3309                .map(|action| action.new_text.as_str()),
3310            Some("\n\n.missing {\n}\n")
3311        );
3312        assert_eq!(
3313            diagnostic
3314                .create_selector
3315                .as_ref()
3316                .map(|action| action.range),
3317            Some(engine_style_parser::ParserRangeV0 {
3318                start: engine_style_parser::ParserPositionV0 {
3319                    line: 2,
3320                    character: 0,
3321                },
3322                end: engine_style_parser::ParserPositionV0 {
3323                    line: 2,
3324                    character: 0,
3325                },
3326            })
3327        );
3328    }
3329
3330    #[test]
3331    fn source_provider_candidate_resolution_is_query_owned() {
3332        let source_range = engine_style_parser::ParserRangeV0 {
3333            start: engine_style_parser::ParserPositionV0 {
3334                line: 0,
3335                character: 0,
3336            },
3337            end: engine_style_parser::ParserPositionV0 {
3338                line: 0,
3339                character: 4,
3340            },
3341        };
3342        let definition_range = engine_style_parser::ParserRangeV0 {
3343            start: engine_style_parser::ParserPositionV0 {
3344                line: 1,
3345                character: 1,
3346            },
3347            end: engine_style_parser::ParserPositionV0 {
3348                line: 1,
3349                character: 5,
3350            },
3351        };
3352
3353        let resolution = super::resolve_omena_query_source_provider_candidates(
3354            vec![
3355                super::OmenaQuerySourceSelectorCandidateV0 {
3356                    kind: "sourceSelectorReference",
3357                    name: "root".to_string(),
3358                    range: source_range,
3359                    source: "omenaQuerySourceSyntaxIndex",
3360                    target_style_uri: Some("file:///workspace/src/App.module.scss".to_string()),
3361                },
3362                super::OmenaQuerySourceSelectorCandidateV0 {
3363                    kind: "sourceSelectorPrefixReference",
3364                    name: "btn-".to_string(),
3365                    range: source_range,
3366                    source: "omenaQuerySourceSyntaxIndex",
3367                    target_style_uri: Some("file:///workspace/src/App.module.scss".to_string()),
3368                },
3369                super::OmenaQuerySourceSelectorCandidateV0 {
3370                    kind: "sourceSelectorReference",
3371                    name: "ghost".to_string(),
3372                    range: source_range,
3373                    source: "omenaQuerySourceSyntaxIndex",
3374                    target_style_uri: Some("file:///workspace/src/Other.module.scss".to_string()),
3375                },
3376            ],
3377            &[
3378                super::OmenaQueryStyleSelectorDefinitionV0 {
3379                    uri: "file:///workspace/src/App.module.scss".to_string(),
3380                    name: "root".to_string(),
3381                    range: definition_range,
3382                },
3383                super::OmenaQueryStyleSelectorDefinitionV0 {
3384                    uri: "file:///workspace/src/App.module.scss".to_string(),
3385                    name: "btn-primary".to_string(),
3386                    range: definition_range,
3387                },
3388            ],
3389        );
3390
3391        assert_eq!(
3392            resolution
3393                .matched
3394                .iter()
3395                .map(|candidate| candidate.name.as_str())
3396                .collect::<Vec<_>>(),
3397            vec!["btn-", "root"]
3398        );
3399        assert_eq!(
3400            resolution
3401                .unresolved
3402                .iter()
3403                .map(|candidate| candidate.name.as_str())
3404                .collect::<Vec<_>>(),
3405            vec!["ghost"]
3406        );
3407
3408        let prefix_candidate = &resolution.matched[0];
3409        let definitions = vec![
3410            super::OmenaQueryStyleSelectorDefinitionV0 {
3411                uri: "file:///workspace/src/App.module.scss".to_string(),
3412                name: "root".to_string(),
3413                range: definition_range,
3414            },
3415            super::OmenaQueryStyleSelectorDefinitionV0 {
3416                uri: "file:///workspace/src/App.module.scss".to_string(),
3417                name: "btn-primary".to_string(),
3418                range: definition_range,
3419            },
3420        ];
3421        assert_eq!(
3422            super::resolve_omena_query_source_candidate_selector_names(
3423                prefix_candidate,
3424                definitions.as_slice(),
3425                None,
3426            ),
3427            vec!["btn-primary".to_string()]
3428        );
3429        assert_eq!(
3430            super::resolve_omena_query_style_selector_definitions_for_source_candidate(
3431                prefix_candidate,
3432                definitions.as_slice(),
3433            )
3434            .into_iter()
3435            .map(|definition| definition.name)
3436            .collect::<Vec<_>>(),
3437            vec!["btn-primary".to_string()]
3438        );
3439    }
3440
3441    #[test]
3442    fn source_syntax_index_adapter_is_query_owned_without_changing_product() {
3443        let style_uri = super::resolve_omena_query_style_uri_for_specifier(
3444            "file:///workspace/src/Button.tsx",
3445            Some("file:///workspace"),
3446            "./Button.module.scss",
3447        );
3448        assert_eq!(
3449            style_uri.as_deref(),
3450            Some("file:///workspace/src/Button.module.scss")
3451        );
3452        let style_uri = style_uri.unwrap_or_default();
3453        assert_eq!(style_uri, "file:///workspace/src/Button.module.scss");
3454
3455        let import_summary = super::summarize_omena_query_source_import_declarations(
3456            "import styles from './Button.module.scss';",
3457        );
3458        assert_eq!(import_summary.import_count, 1);
3459        assert_eq!(import_summary.imports[0].binding, "styles");
3460
3461        let source = "import styles from './Button.module.scss';\nconst el = styles.root;\n";
3462        let mut index = super::summarize_omena_query_source_syntax_index(
3463            source,
3464            vec![super::OmenaQuerySourceImportedStyleBindingV0 {
3465                binding: "styles".to_string(),
3466                style_uri,
3467            }],
3468            Vec::new(),
3469        );
3470        assert_eq!(index.product, "omena-bridge.source-syntax-index");
3471        assert_eq!(index.selector_references.len(), 1);
3472        let reference = &index.selector_references[0];
3473        assert_eq!(
3474            &source[reference.byte_span.start..reference.byte_span.end],
3475            "root"
3476        );
3477
3478        super::canonicalize_omena_query_source_selector_references(&mut index.selector_references);
3479        assert_eq!(index.selector_references.len(), 1);
3480    }
3481
3482    #[test]
3483    fn selector_rename_edit_planning_is_query_owned() {
3484        let source_range = engine_style_parser::ParserRangeV0 {
3485            start: engine_style_parser::ParserPositionV0 {
3486                line: 3,
3487                character: 16,
3488            },
3489            end: engine_style_parser::ParserPositionV0 {
3490                line: 3,
3491                character: 20,
3492            },
3493        };
3494        let definition_range = engine_style_parser::ParserRangeV0 {
3495            start: engine_style_parser::ParserPositionV0 {
3496                line: 0,
3497                character: 1,
3498            },
3499            end: engine_style_parser::ParserPositionV0 {
3500                line: 0,
3501                character: 5,
3502            },
3503        };
3504
3505        let edits = super::resolve_omena_query_selector_rename_edits(
3506            "root",
3507            ".shell",
3508            Some("file:///workspace/src/App.module.scss"),
3509            &[super::OmenaQueryStyleSelectorDefinitionV0 {
3510                uri: "file:///workspace/src/App.module.scss".to_string(),
3511                name: "root".to_string(),
3512                range: definition_range,
3513            }],
3514            &[super::OmenaQuerySourceSelectorReferenceEditTargetV0 {
3515                uri: "file:///workspace/src/App.tsx".to_string(),
3516                name: "root".to_string(),
3517                range: source_range,
3518                target_style_uri: Some("file:///workspace/src/App.module.scss".to_string()),
3519            }],
3520        );
3521
3522        assert_eq!(
3523            edits
3524                .iter()
3525                .map(|edit| (edit.uri.as_str(), edit.new_text.as_str()))
3526                .collect::<Vec<_>>(),
3527            vec![
3528                ("file:///workspace/src/App.module.scss", "shell"),
3529                ("file:///workspace/src/App.tsx", "shell"),
3530            ]
3531        );
3532    }
3533
3534    #[test]
3535    fn sass_symbol_matching_is_query_owned() {
3536        let source = "$accent: red;\n.button { color: $accent; }\n";
3537        let Some(candidates) =
3538            super::summarize_omena_query_style_hover_candidates("Component.module.scss", source)
3539        else {
3540            return;
3541        };
3542
3543        assert!(super::is_omena_query_sass_symbol_candidate_kind(
3544            "sassVariableDeclaration"
3545        ));
3546        assert!(super::is_omena_query_sass_symbol_reference_kind(
3547            "sassVariableReference"
3548        ));
3549        assert_eq!(
3550            super::omena_query_sass_symbol_kind_from_candidate_kind("sassVariableReference"),
3551            Some("variable")
3552        );
3553        assert!(super::omena_query_sass_symbol_target_matches(
3554            "sassVariableReference",
3555            "accent",
3556            None,
3557            "sassVariableDeclaration",
3558            "accent",
3559            None,
3560        ));
3561
3562        let declarations = super::resolve_omena_query_sass_symbol_declarations(
3563            candidates.candidates.as_slice(),
3564            "variable",
3565            "accent",
3566        );
3567        assert_eq!(declarations.len(), 1);
3568        assert_eq!(declarations[0].kind, "sassVariableDeclaration");
3569    }
3570
3571    #[test]
3572    fn sass_module_sources_are_query_owned() {
3573        let sources = super::summarize_omena_query_sass_module_sources(
3574            "Component.module.scss",
3575            r#"
3576@use "./tokens" as tokens;
3577@use "./reset" as *;
3578@use "sass:map";
3579@forward "./theme";
3580@forward "sass:color";
3581"#,
3582        );
3583        assert!(sources.is_some());
3584        let Some(sources) = sources else {
3585            return;
3586        };
3587
3588        assert_eq!(sources.product, "omena-query.sass-module-sources");
3589        assert!(sources.module_use_edges.iter().any(|edge| {
3590            edge.source == "./tokens"
3591                && edge.namespace.as_deref() == Some("tokens")
3592                && edge.namespace_kind == "alias"
3593        }));
3594        assert!(sources.module_use_edges.iter().any(|edge| {
3595            edge.source == "./reset"
3596                && edge.namespace.is_none()
3597                && edge.namespace_kind == "wildcard"
3598        }));
3599        assert_eq!(
3600            super::resolve_omena_query_sass_module_use_sources_for_candidate(&sources, None),
3601            vec!["./reset".to_string()]
3602        );
3603        assert_eq!(
3604            super::resolve_omena_query_sass_module_use_sources_for_candidate(
3605                &sources,
3606                Some("tokens"),
3607            ),
3608            vec!["./tokens".to_string()]
3609        );
3610        assert_eq!(
3611            super::resolve_omena_query_sass_forward_sources(&sources),
3612            vec!["./theme".to_string()]
3613        );
3614    }
3615
3616    fn backend<'a>(
3617        summary: &'a SelectedQueryAdapterCapabilitiesV0,
3618        backend_kind: &str,
3619    ) -> Option<&'a super::SelectedQueryBackendCapabilityV0> {
3620        summary
3621            .backend_kinds
3622            .iter()
3623            .find(|backend| backend.backend_kind == backend_kind)
3624    }
3625
3626    fn sample_input() -> EngineInputV2 {
3627        EngineInputV2 {
3628            version: "2".to_string(),
3629            sources: vec![SourceAnalysisInputV2 {
3630                document: SourceDocumentV2 {
3631                    class_expressions: vec![
3632                        ClassExpressionInputV2 {
3633                            id: "expr-1".to_string(),
3634                            kind: "symbolRef".to_string(),
3635                            scss_module_path: "/tmp/App.module.scss".to_string(),
3636                            range: range(4, 12, 4, 16),
3637                            class_name: None,
3638                            root_binding_decl_id: Some("decl-1".to_string()),
3639                            access_path: None,
3640                        },
3641                        ClassExpressionInputV2 {
3642                            id: "expr-2".to_string(),
3643                            kind: "styleAccess".to_string(),
3644                            scss_module_path: "/tmp/Card.module.scss".to_string(),
3645                            range: range(6, 9, 6, 20),
3646                            class_name: Some("card-header".to_string()),
3647                            root_binding_decl_id: None,
3648                            access_path: Some(vec!["card".to_string(), "header".to_string()]),
3649                        },
3650                    ],
3651                },
3652            }],
3653            styles: vec![
3654                StyleAnalysisInputV2 {
3655                    file_path: "/tmp/App.module.scss".to_string(),
3656                    document: StyleDocumentV2 {
3657                        selectors: vec![StyleSelectorV2 {
3658                            name: "btn-active".to_string(),
3659                            view_kind: "canonical".to_string(),
3660                            canonical_name: Some("btn-active".to_string()),
3661                            range: range(1, 1, 1, 12),
3662                            nested_safety: Some("safe".to_string()),
3663                            composes: None,
3664                            bem_suffix: None,
3665                        }],
3666                    },
3667                },
3668                StyleAnalysisInputV2 {
3669                    file_path: "/tmp/Card.module.scss".to_string(),
3670                    document: StyleDocumentV2 {
3671                        selectors: vec![StyleSelectorV2 {
3672                            name: "card-header".to_string(),
3673                            view_kind: "canonical".to_string(),
3674                            canonical_name: Some("card-header".to_string()),
3675                            range: range(3, 1, 3, 13),
3676                            nested_safety: Some("unsafe".to_string()),
3677                            composes: None,
3678                            bem_suffix: None,
3679                        }],
3680                    },
3681                },
3682            ],
3683            type_facts: vec![
3684                TypeFactEntryV2 {
3685                    file_path: "/tmp/App.tsx".to_string(),
3686                    expression_id: "expr-1".to_string(),
3687                    facts: StringTypeFactsV2 {
3688                        kind: "constrained".to_string(),
3689                        constraint_kind: Some("prefixSuffix".to_string()),
3690                        values: None,
3691                        prefix: Some("btn-".to_string()),
3692                        suffix: Some("-active".to_string()),
3693                        min_len: Some(10),
3694                        max_len: None,
3695                        char_must: None,
3696                        char_may: None,
3697                        may_include_other_chars: None,
3698                    },
3699                },
3700                TypeFactEntryV2 {
3701                    file_path: "/tmp/Card.tsx".to_string(),
3702                    expression_id: "expr-2".to_string(),
3703                    facts: StringTypeFactsV2 {
3704                        kind: "finiteSet".to_string(),
3705                        constraint_kind: None,
3706                        values: Some(vec!["card-header".to_string(), "card-body".to_string()]),
3707                        prefix: None,
3708                        suffix: None,
3709                        min_len: None,
3710                        max_len: None,
3711                        char_must: None,
3712                        char_may: None,
3713                        may_include_other_chars: None,
3714                    },
3715                },
3716            ],
3717        }
3718    }
3719
3720    fn reduced_product_projection_input() -> EngineInputV2 {
3721        EngineInputV2 {
3722            version: "2".to_string(),
3723            sources: vec![SourceAnalysisInputV2 {
3724                document: SourceDocumentV2 {
3725                    class_expressions: vec![
3726                        ClassExpressionInputV2 {
3727                            id: "expr-primary".to_string(),
3728                            kind: "symbolRef".to_string(),
3729                            scss_module_path: "/tmp/App.module.scss".to_string(),
3730                            range: range(4, 12, 4, 16),
3731                            class_name: None,
3732                            root_binding_decl_id: Some("decl-primary".to_string()),
3733                            access_path: None,
3734                        },
3735                        ClassExpressionInputV2 {
3736                            id: "expr-secondary".to_string(),
3737                            kind: "symbolRef".to_string(),
3738                            scss_module_path: "/tmp/App.module.scss".to_string(),
3739                            range: range(5, 12, 5, 16),
3740                            class_name: None,
3741                            root_binding_decl_id: Some("decl-secondary".to_string()),
3742                            access_path: None,
3743                        },
3744                    ],
3745                },
3746            }],
3747            styles: vec![StyleAnalysisInputV2 {
3748                file_path: "/tmp/App.module.scss".to_string(),
3749                document: StyleDocumentV2 {
3750                    selectors: vec![
3751                        style_selector("btn--active"),
3752                        style_selector("btn-primary--active"),
3753                        style_selector("btn-secondary--active"),
3754                        style_selector("card-active"),
3755                    ],
3756                },
3757            }],
3758            type_facts: vec![
3759                TypeFactEntryV2 {
3760                    file_path: "/tmp/App.tsx".to_string(),
3761                    expression_id: "expr-primary".to_string(),
3762                    facts: StringTypeFactsV2 {
3763                        kind: "constrained".to_string(),
3764                        constraint_kind: Some("prefixSuffix".to_string()),
3765                        values: None,
3766                        prefix: Some("btn-primary-".to_string()),
3767                        suffix: Some("-active".to_string()),
3768                        min_len: Some("btn-primary--active".len()),
3769                        max_len: None,
3770                        char_must: None,
3771                        char_may: None,
3772                        may_include_other_chars: None,
3773                    },
3774                },
3775                TypeFactEntryV2 {
3776                    file_path: "/tmp/App.tsx".to_string(),
3777                    expression_id: "expr-secondary".to_string(),
3778                    facts: StringTypeFactsV2 {
3779                        kind: "constrained".to_string(),
3780                        constraint_kind: Some("prefixSuffix".to_string()),
3781                        values: None,
3782                        prefix: Some("btn-secondary-".to_string()),
3783                        suffix: Some("-active".to_string()),
3784                        min_len: Some("btn-secondary--active".len()),
3785                        max_len: None,
3786                        char_must: None,
3787                        char_may: None,
3788                        may_include_other_chars: None,
3789                    },
3790                },
3791            ],
3792        }
3793    }
3794
3795    fn style_selector(name: &str) -> StyleSelectorV2 {
3796        StyleSelectorV2 {
3797            name: name.to_string(),
3798            view_kind: "canonical".to_string(),
3799            canonical_name: Some(name.to_string()),
3800            range: range(1, 1, 1, 1 + name.len()),
3801            nested_safety: Some("safe".to_string()),
3802            composes: None,
3803            bem_suffix: None,
3804        }
3805    }
3806
3807    fn range(
3808        start_line: usize,
3809        start_character: usize,
3810        end_line: usize,
3811        end_character: usize,
3812    ) -> RangeV2 {
3813        RangeV2 {
3814            start: PositionV2 {
3815                line: start_line,
3816                character: start_character,
3817            },
3818            end: PositionV2 {
3819                line: end_line,
3820                character: end_character,
3821            },
3822        }
3823    }
3824}