Skip to main content

omena_query/
lib.rs

1use engine_input_producers::{
2    EngineInputV2, ExpressionDomainFlowAnalysisV0, ExpressionSemanticsCanonicalProducerSignalV0,
3    ExpressionSemanticsQueryFragmentsV0, SelectorUsageCanonicalProducerSignalV0,
4    SelectorUsageQueryFragmentsV0, SourceResolutionCanonicalProducerSignalV0,
5    SourceResolutionQueryFragmentsV0, summarize_expression_domain_flow_analysis_input,
6    summarize_expression_semantics_canonical_producer_signal_input,
7    summarize_expression_semantics_query_fragments_input,
8    summarize_selector_usage_canonical_producer_signal_input,
9    summarize_selector_usage_query_fragments_input,
10};
11use omena_abstract_value::{AbstractValueDomainSummaryV0, summarize_omena_abstract_value_domain};
12use omena_bridge::{
13    StyleSemanticGraphSummaryV0, summarize_omena_bridge_style_semantic_graph_from_source,
14};
15use omena_resolver::{
16    summarize_omena_resolver_canonical_producer_signal, summarize_omena_resolver_query_fragments,
17};
18use serde::Serialize;
19
20#[derive(Debug, Serialize)]
21#[serde(rename_all = "camelCase")]
22pub struct OmenaQueryBoundarySummaryV0 {
23    pub schema_version: &'static str,
24    pub product: &'static str,
25    pub query_engine_name: &'static str,
26    pub input_version: String,
27    pub abstract_value_domain: AbstractValueDomainSummaryV0,
28    pub selected_query_adapter_capabilities: SelectedQueryAdapterCapabilitiesV0,
29    pub delegated_fragment_products: Vec<&'static str>,
30    pub expression_semantics_query_count: usize,
31    pub source_resolution_query_count: usize,
32    pub selector_usage_query_count: usize,
33    pub total_query_count: usize,
34    pub ready_surfaces: Vec<&'static str>,
35    pub cme_coupled_surfaces: Vec<&'static str>,
36    pub next_decoupling_targets: Vec<&'static str>,
37}
38
39#[derive(Debug, Serialize)]
40#[serde(rename_all = "camelCase")]
41pub struct OmenaQueryFragmentBundleV0 {
42    pub schema_version: &'static str,
43    pub product: &'static str,
44    pub input_version: String,
45    pub expression_semantics: ExpressionSemanticsQueryFragmentsV0,
46    pub source_resolution: SourceResolutionQueryFragmentsV0,
47    pub selector_usage: SelectorUsageQueryFragmentsV0,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
51#[serde(rename_all = "camelCase")]
52pub struct SelectedQueryAdapterCapabilitiesV0 {
53    pub schema_version: &'static str,
54    pub product: &'static str,
55    pub default_candidate_backend: &'static str,
56    pub backend_kinds: Vec<SelectedQueryBackendCapabilityV0>,
57    pub runner_commands: Vec<SelectedQueryRunnerCommandV0>,
58    pub expression_semantics_payload_contracts: Vec<&'static str>,
59    pub required_input_contracts: Vec<&'static str>,
60    pub adapter_readiness: Vec<&'static str>,
61    pub routing_status: &'static str,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
65#[serde(rename_all = "camelCase")]
66pub struct SelectedQueryBackendCapabilityV0 {
67    pub backend_kind: &'static str,
68    pub source_resolution: bool,
69    pub expression_semantics: bool,
70    pub selector_usage: bool,
71    pub style_semantic_graph: bool,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
75#[serde(rename_all = "camelCase")]
76pub struct SelectedQueryRunnerCommandV0 {
77    pub surface: &'static str,
78    pub command: &'static str,
79    pub input_contract: &'static str,
80    pub output_product: &'static str,
81}
82
83#[derive(Debug, Serialize)]
84#[serde(rename_all = "camelCase")]
85pub struct OmenaQueryStyleSemanticGraphBatchOutputV0 {
86    pub schema_version: &'static str,
87    pub product: &'static str,
88    pub graphs: Vec<OmenaQueryStyleSemanticGraphBatchEntryV0>,
89}
90
91#[derive(Debug, Serialize)]
92#[serde(rename_all = "camelCase")]
93pub struct OmenaQueryStyleSemanticGraphBatchEntryV0 {
94    pub style_path: String,
95    pub graph: Option<StyleSemanticGraphSummaryV0>,
96}
97
98pub fn summarize_omena_query_boundary(input: &EngineInputV2) -> OmenaQueryBoundarySummaryV0 {
99    let fragment_bundle = summarize_omena_query_fragment_bundle(input);
100    let expression_semantics_query_count = fragment_bundle.expression_semantics.fragments.len();
101    let source_resolution_query_count = fragment_bundle.source_resolution.fragments.len();
102    let selector_usage_query_count = fragment_bundle.selector_usage.fragments.len();
103
104    OmenaQueryBoundarySummaryV0 {
105        schema_version: "0",
106        product: "omena-query.boundary",
107        query_engine_name: "omena-query",
108        input_version: input.version.clone(),
109        abstract_value_domain: summarize_omena_abstract_value_domain(),
110        selected_query_adapter_capabilities:
111            summarize_omena_query_selected_query_adapter_capabilities(),
112        delegated_fragment_products: vec![
113            "engine-input-producers.expression-semantics-query-fragments",
114            "engine-input-producers.source-resolution-query-fragments",
115            "omena-resolver.boundary",
116            "engine-input-producers.selector-usage-query-fragments",
117            "engine-input-producers.expression-domain-flow-analysis",
118        ],
119        expression_semantics_query_count,
120        source_resolution_query_count,
121        selector_usage_query_count,
122        total_query_count: expression_semantics_query_count
123            + source_resolution_query_count
124            + selector_usage_query_count,
125        ready_surfaces: vec![
126            "queryFragmentBundle",
127            "abstractValueProjectionContract",
128            "sourceResolutionResolverBoundary",
129            "expressionDomainFlowAnalysisBoundary",
130            "queryBoundarySummary",
131        ],
132        cme_coupled_surfaces: vec!["EngineInputV2", "producerQueryFragments"],
133        next_decoupling_targets: vec!["queryEvaluationRuntime", "selectedQueryBackendAdapter"],
134    }
135}
136
137pub fn summarize_omena_query_selected_query_adapter_capabilities()
138-> SelectedQueryAdapterCapabilitiesV0 {
139    SelectedQueryAdapterCapabilitiesV0 {
140        schema_version: "0",
141        product: "omena-query.selected-query-adapter-capabilities",
142        default_candidate_backend: "rust-selected-query",
143        backend_kinds: vec![
144            SelectedQueryBackendCapabilityV0 {
145                backend_kind: "typescript-current",
146                source_resolution: false,
147                expression_semantics: false,
148                selector_usage: false,
149                style_semantic_graph: false,
150            },
151            SelectedQueryBackendCapabilityV0 {
152                backend_kind: "rust-source-resolution",
153                source_resolution: true,
154                expression_semantics: false,
155                selector_usage: false,
156                style_semantic_graph: false,
157            },
158            SelectedQueryBackendCapabilityV0 {
159                backend_kind: "rust-expression-semantics",
160                source_resolution: false,
161                expression_semantics: true,
162                selector_usage: false,
163                style_semantic_graph: false,
164            },
165            SelectedQueryBackendCapabilityV0 {
166                backend_kind: "rust-selector-usage",
167                source_resolution: false,
168                expression_semantics: false,
169                selector_usage: true,
170                style_semantic_graph: false,
171            },
172            SelectedQueryBackendCapabilityV0 {
173                backend_kind: "rust-selected-query",
174                source_resolution: true,
175                expression_semantics: true,
176                selector_usage: true,
177                style_semantic_graph: true,
178            },
179        ],
180        runner_commands: vec![
181            SelectedQueryRunnerCommandV0 {
182                surface: "sourceResolution",
183                command: "input-source-resolution-canonical-producer",
184                input_contract: "EngineInputV2",
185                output_product: "engine-input-producers.source-resolution-canonical-producer",
186            },
187            SelectedQueryRunnerCommandV0 {
188                surface: "expressionSemantics",
189                command: "input-expression-semantics-canonical-producer",
190                input_contract: "EngineInputV2",
191                output_product: "engine-input-producers.expression-semantics-canonical-producer",
192            },
193            SelectedQueryRunnerCommandV0 {
194                surface: "expressionDomainFlowAnalysis",
195                command: "input-expression-domain-flow-analysis",
196                input_contract: "EngineInputV2",
197                output_product: "engine-input-producers.expression-domain-flow-analysis",
198            },
199            SelectedQueryRunnerCommandV0 {
200                surface: "selectorUsage",
201                command: "input-selector-usage-canonical-producer",
202                input_contract: "EngineInputV2",
203                output_product: "engine-input-producers.selector-usage-canonical-producer",
204            },
205            SelectedQueryRunnerCommandV0 {
206                surface: "styleSemanticGraph",
207                command: "style-semantic-graph",
208                input_contract: "StyleSemanticGraphInputV0",
209                output_product: "omena-semantic.style-semantic-graph",
210            },
211            SelectedQueryRunnerCommandV0 {
212                surface: "styleSemanticGraphBatch",
213                command: "style-semantic-graph-batch",
214                input_contract: "StyleSemanticGraphBatchInputV0",
215                output_product: "omena-semantic.style-semantic-graph-batch",
216            },
217        ],
218        expression_semantics_payload_contracts: vec!["valueDomainKind", "valueDomainDerivation"],
219        required_input_contracts: vec![
220            "EngineInputV2",
221            "StyleSemanticGraphInputV0",
222            "StyleSemanticGraphBatchInputV0",
223        ],
224        adapter_readiness: vec![
225            "backendCapabilityMatrix",
226            "canonicalProducerWrapperBoundary",
227            "styleSemanticGraphBridgeBoundary",
228            "runnerCommandContract",
229            "fragmentBundleBoundary",
230            "expressionSemanticsDerivationPayload",
231            "expressionDomainFlowAnalysisRunner",
232        ],
233        routing_status: "declaredOnly",
234    }
235}
236
237pub fn summarize_omena_query_fragment_bundle(input: &EngineInputV2) -> OmenaQueryFragmentBundleV0 {
238    OmenaQueryFragmentBundleV0 {
239        schema_version: "0",
240        product: "omena-query.fragment-bundle",
241        input_version: input.version.clone(),
242        expression_semantics: summarize_omena_query_expression_semantics_query_fragments(input),
243        source_resolution: summarize_omena_query_source_resolution_query_fragments(input),
244        selector_usage: summarize_omena_query_selector_usage_query_fragments(input),
245    }
246}
247
248pub fn summarize_omena_query_expression_semantics_query_fragments(
249    input: &EngineInputV2,
250) -> ExpressionSemanticsQueryFragmentsV0 {
251    summarize_expression_semantics_query_fragments_input(input)
252}
253
254pub fn summarize_omena_query_expression_domain_flow_analysis(
255    input: &EngineInputV2,
256) -> ExpressionDomainFlowAnalysisV0 {
257    summarize_expression_domain_flow_analysis_input(input)
258}
259
260pub fn summarize_omena_query_source_resolution_query_fragments(
261    input: &EngineInputV2,
262) -> SourceResolutionQueryFragmentsV0 {
263    summarize_omena_resolver_query_fragments(input)
264}
265
266pub fn summarize_omena_query_selector_usage_query_fragments(
267    input: &EngineInputV2,
268) -> SelectorUsageQueryFragmentsV0 {
269    summarize_selector_usage_query_fragments_input(input)
270}
271
272pub fn summarize_omena_query_source_resolution_canonical_producer_signal(
273    input: &EngineInputV2,
274) -> SourceResolutionCanonicalProducerSignalV0 {
275    summarize_omena_resolver_canonical_producer_signal(input)
276}
277
278pub fn summarize_omena_query_expression_semantics_canonical_producer_signal(
279    input: &EngineInputV2,
280) -> ExpressionSemanticsCanonicalProducerSignalV0 {
281    summarize_expression_semantics_canonical_producer_signal_input(input)
282}
283
284pub fn summarize_omena_query_selector_usage_canonical_producer_signal(
285    input: &EngineInputV2,
286) -> SelectorUsageCanonicalProducerSignalV0 {
287    summarize_selector_usage_canonical_producer_signal_input(input)
288}
289
290pub fn summarize_omena_query_style_semantic_graph_from_source(
291    style_path: &str,
292    style_source: &str,
293    input: &EngineInputV2,
294) -> Option<StyleSemanticGraphSummaryV0> {
295    summarize_omena_bridge_style_semantic_graph_from_source(style_path, style_source, input)
296}
297
298pub fn summarize_omena_query_style_semantic_graph_batch_from_sources<'a>(
299    styles: impl IntoIterator<Item = (&'a str, &'a str)>,
300    input: &EngineInputV2,
301) -> OmenaQueryStyleSemanticGraphBatchOutputV0 {
302    let graphs = styles
303        .into_iter()
304        .map(
305            |(style_path, style_source)| OmenaQueryStyleSemanticGraphBatchEntryV0 {
306                style_path: style_path.to_string(),
307                graph: summarize_omena_query_style_semantic_graph_from_source(
308                    style_path,
309                    style_source,
310                    input,
311                ),
312            },
313        )
314        .collect::<Vec<_>>();
315
316    OmenaQueryStyleSemanticGraphBatchOutputV0 {
317        schema_version: "0",
318        product: "omena-semantic.style-semantic-graph-batch",
319        graphs,
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use engine_input_producers::{
326        ClassExpressionInputV2, EngineInputV2, PositionV2, RangeV2, SourceAnalysisInputV2,
327        SourceDocumentV2, StringTypeFactsV2, StyleAnalysisInputV2, StyleDocumentV2,
328        StyleSelectorV2, TypeFactEntryV2,
329    };
330
331    use super::{
332        SelectedQueryAdapterCapabilitiesV0, summarize_omena_query_boundary,
333        summarize_omena_query_expression_domain_flow_analysis,
334        summarize_omena_query_expression_semantics_canonical_producer_signal,
335        summarize_omena_query_expression_semantics_query_fragments,
336        summarize_omena_query_fragment_bundle,
337        summarize_omena_query_selected_query_adapter_capabilities,
338        summarize_omena_query_selector_usage_canonical_producer_signal,
339        summarize_omena_query_selector_usage_query_fragments,
340        summarize_omena_query_source_resolution_canonical_producer_signal,
341        summarize_omena_query_source_resolution_query_fragments,
342        summarize_omena_query_style_semantic_graph_batch_from_sources,
343        summarize_omena_query_style_semantic_graph_from_source,
344    };
345
346    #[test]
347    fn summarizes_query_boundary_over_producer_fragments() {
348        let input = sample_input();
349        let summary = summarize_omena_query_boundary(&input);
350
351        assert_eq!(summary.schema_version, "0");
352        assert_eq!(summary.product, "omena-query.boundary");
353        assert_eq!(summary.query_engine_name, "omena-query");
354        assert_eq!(summary.input_version, "2");
355        assert_eq!(
356            summary.abstract_value_domain.product,
357            "omena-abstract-value.domain"
358        );
359        assert_eq!(
360            summary.selected_query_adapter_capabilities.product,
361            "omena-query.selected-query-adapter-capabilities"
362        );
363        assert_eq!(summary.expression_semantics_query_count, 2);
364        assert_eq!(summary.source_resolution_query_count, 2);
365        assert_eq!(summary.selector_usage_query_count, 2);
366        assert_eq!(summary.total_query_count, 6);
367        assert!(
368            summary
369                .ready_surfaces
370                .contains(&"abstractValueProjectionContract")
371        );
372        assert!(
373            summary
374                .ready_surfaces
375                .contains(&"sourceResolutionResolverBoundary")
376        );
377        assert!(
378            summary
379                .delegated_fragment_products
380                .contains(&"omena-resolver.boundary")
381        );
382        assert!(
383            summary
384                .delegated_fragment_products
385                .contains(&"engine-input-producers.expression-domain-flow-analysis")
386        );
387        assert!(
388            summary
389                .ready_surfaces
390                .contains(&"expressionDomainFlowAnalysisBoundary")
391        );
392        assert!(
393            summary
394                .cme_coupled_surfaces
395                .contains(&"producerQueryFragments")
396        );
397    }
398
399    #[test]
400    fn bundles_expression_source_and_selector_query_fragments() {
401        let input = sample_input();
402        let bundle = summarize_omena_query_fragment_bundle(&input);
403
404        assert_eq!(bundle.schema_version, "0");
405        assert_eq!(bundle.product, "omena-query.fragment-bundle");
406        assert_eq!(bundle.input_version, "2");
407        assert_eq!(bundle.expression_semantics.fragments.len(), 2);
408        assert_eq!(bundle.expression_semantics.fragments[0].query_id, "expr-1");
409        assert_eq!(bundle.source_resolution.fragments.len(), 2);
410        assert_eq!(bundle.source_resolution.fragments[1].query_id, "expr-2");
411        assert_eq!(bundle.selector_usage.fragments.len(), 2);
412        assert_eq!(bundle.selector_usage.fragments[0].query_id, "btn-active");
413
414        let expression = summarize_omena_query_expression_semantics_query_fragments(&input);
415        let source = summarize_omena_query_source_resolution_query_fragments(&input);
416        let selector = summarize_omena_query_selector_usage_query_fragments(&input);
417
418        assert_eq!(expression.schema_version, "0");
419        assert_eq!(source.schema_version, "0");
420        assert_eq!(selector.schema_version, "0");
421        assert_eq!(expression.input_version, "2");
422        assert_eq!(source.input_version, "2");
423        assert_eq!(selector.input_version, "2");
424        assert_eq!(
425            expression.fragments.len(),
426            bundle.expression_semantics.fragments.len()
427        );
428        assert_eq!(
429            source.fragments.len(),
430            bundle.source_resolution.fragments.len()
431        );
432        assert_eq!(
433            selector.fragments.len(),
434            bundle.selector_usage.fragments.len()
435        );
436    }
437
438    #[test]
439    fn declares_selected_query_adapter_capabilities_without_flipping_runtime_routing() {
440        let summary = summarize_omena_query_selected_query_adapter_capabilities();
441
442        assert_eq!(summary.schema_version, "0");
443        assert_eq!(
444            summary.product,
445            "omena-query.selected-query-adapter-capabilities"
446        );
447        assert_eq!(summary.default_candidate_backend, "rust-selected-query");
448        assert_eq!(summary.routing_status, "declaredOnly");
449
450        let unified = backend(&summary, "rust-selected-query");
451        assert!(unified.is_some());
452        let Some(unified) = unified else {
453            return;
454        };
455        assert!(unified.source_resolution);
456        assert!(unified.expression_semantics);
457        assert!(unified.selector_usage);
458        assert!(unified.style_semantic_graph);
459
460        let source_only = backend(&summary, "rust-source-resolution");
461        assert!(source_only.is_some());
462        let Some(source_only) = source_only else {
463            return;
464        };
465        assert!(source_only.source_resolution);
466        assert!(!source_only.expression_semantics);
467        assert!(!source_only.selector_usage);
468        assert!(!source_only.style_semantic_graph);
469
470        assert!(
471            summary
472                .runner_commands
473                .iter()
474                .any(|command| command.command == "input-expression-domain-flow-analysis")
475        );
476        assert!(
477            summary
478                .runner_commands
479                .iter()
480                .any(|command| command.command == "style-semantic-graph-batch")
481        );
482        assert!(
483            summary
484                .expression_semantics_payload_contracts
485                .contains(&"valueDomainDerivation")
486        );
487        assert!(summary.adapter_readiness.contains(&"runnerCommandContract"));
488        assert!(
489            summary
490                .adapter_readiness
491                .contains(&"canonicalProducerWrapperBoundary")
492        );
493        assert!(
494            summary
495                .adapter_readiness
496                .contains(&"styleSemanticGraphBridgeBoundary")
497        );
498        assert!(
499            summary
500                .adapter_readiness
501                .contains(&"expressionDomainFlowAnalysisRunner")
502        );
503    }
504
505    #[test]
506    fn owns_expression_domain_flow_analysis_wrapper_without_changing_product() {
507        let input = sample_input();
508        let summary = summarize_omena_query_expression_domain_flow_analysis(&input);
509
510        assert_eq!(summary.schema_version, "0");
511        assert_eq!(
512            summary.product,
513            "engine-input-producers.expression-domain-flow-analysis"
514        );
515        assert_eq!(summary.input_version, "2");
516        assert_eq!(summary.analyses.len(), 2);
517        assert!(
518            summary
519                .analyses
520                .iter()
521                .all(|entry| entry.analysis.product == "omena-abstract-value.flow-analysis")
522        );
523        assert!(
524            summary
525                .analyses
526                .iter()
527                .all(|entry| entry.analysis.converged)
528        );
529    }
530
531    #[test]
532    fn owns_selected_query_canonical_producer_wrappers_without_changing_products() {
533        let input = sample_input();
534
535        let source = summarize_omena_query_source_resolution_canonical_producer_signal(&input);
536        assert_eq!(source.schema_version, "0");
537        assert_eq!(source.input_version, "2");
538        assert_eq!(source.canonical_bundle.query_fragments.len(), 2);
539        assert_eq!(source.evaluator_candidates.results.len(), 2);
540
541        let expression =
542            summarize_omena_query_expression_semantics_canonical_producer_signal(&input);
543        assert_eq!(expression.schema_version, "0");
544        assert_eq!(expression.input_version, "2");
545        assert_eq!(expression.canonical_bundle.query_fragments.len(), 2);
546        assert_eq!(expression.evaluator_candidates.results.len(), 2);
547        assert_eq!(
548            expression.evaluator_candidates.results[0]
549                .payload
550                .value_domain_derivation
551                .product,
552            "omena-abstract-value.reduced-class-value-derivation"
553        );
554        assert_eq!(
555            expression.evaluator_candidates.results[0]
556                .payload
557                .value_domain_derivation
558                .reduced_kind,
559            "prefixSuffix"
560        );
561
562        let selector = summarize_omena_query_selector_usage_canonical_producer_signal(&input);
563        assert_eq!(selector.schema_version, "0");
564        assert_eq!(selector.input_version, "2");
565        assert_eq!(selector.canonical_bundle.query_fragments.len(), 2);
566        assert_eq!(selector.evaluator_candidates.results.len(), 2);
567    }
568
569    #[test]
570    fn owns_style_semantic_graph_adapter_boundary_without_changing_graph_product() {
571        let input = sample_input();
572        let graph = summarize_omena_query_style_semantic_graph_from_source(
573            "/tmp/App.module.scss",
574            ".btn-active { color: red; }",
575            &input,
576        );
577        assert!(graph.is_some());
578        let Some(graph) = graph else {
579            return;
580        };
581        assert_eq!(graph.schema_version, "0");
582        assert_eq!(graph.product, "omena-semantic.style-semantic-graph");
583        assert_eq!(graph.selector_identity_engine.canonical_ids.len(), 1);
584
585        let batch = summarize_omena_query_style_semantic_graph_batch_from_sources(
586            [
587                ("/tmp/App.module.scss", ".btn-active { color: red; }"),
588                ("/tmp/Card.module.scss", ".card-header { color: blue; }"),
589            ],
590            &input,
591        );
592        assert_eq!(batch.schema_version, "0");
593        assert_eq!(batch.product, "omena-semantic.style-semantic-graph-batch");
594        assert_eq!(batch.graphs.len(), 2);
595        assert_eq!(batch.graphs[0].style_path, "/tmp/App.module.scss");
596        assert!(batch.graphs[0].graph.is_some());
597        assert!(batch.graphs[1].graph.is_some());
598    }
599
600    fn backend<'a>(
601        summary: &'a SelectedQueryAdapterCapabilitiesV0,
602        backend_kind: &str,
603    ) -> Option<&'a super::SelectedQueryBackendCapabilityV0> {
604        summary
605            .backend_kinds
606            .iter()
607            .find(|backend| backend.backend_kind == backend_kind)
608    }
609
610    fn sample_input() -> EngineInputV2 {
611        EngineInputV2 {
612            version: "2".to_string(),
613            sources: vec![SourceAnalysisInputV2 {
614                document: SourceDocumentV2 {
615                    class_expressions: vec![
616                        ClassExpressionInputV2 {
617                            id: "expr-1".to_string(),
618                            kind: "symbolRef".to_string(),
619                            scss_module_path: "/tmp/App.module.scss".to_string(),
620                            range: range(4, 12, 4, 16),
621                            class_name: None,
622                            root_binding_decl_id: Some("decl-1".to_string()),
623                            access_path: None,
624                        },
625                        ClassExpressionInputV2 {
626                            id: "expr-2".to_string(),
627                            kind: "styleAccess".to_string(),
628                            scss_module_path: "/tmp/Card.module.scss".to_string(),
629                            range: range(6, 9, 6, 20),
630                            class_name: Some("card-header".to_string()),
631                            root_binding_decl_id: None,
632                            access_path: Some(vec!["card".to_string(), "header".to_string()]),
633                        },
634                    ],
635                },
636            }],
637            styles: vec![
638                StyleAnalysisInputV2 {
639                    file_path: "/tmp/App.module.scss".to_string(),
640                    document: StyleDocumentV2 {
641                        selectors: vec![StyleSelectorV2 {
642                            name: "btn-active".to_string(),
643                            view_kind: "canonical".to_string(),
644                            canonical_name: Some("btn-active".to_string()),
645                            range: range(1, 1, 1, 12),
646                            nested_safety: Some("safe".to_string()),
647                            composes: None,
648                            bem_suffix: None,
649                        }],
650                    },
651                },
652                StyleAnalysisInputV2 {
653                    file_path: "/tmp/Card.module.scss".to_string(),
654                    document: StyleDocumentV2 {
655                        selectors: vec![StyleSelectorV2 {
656                            name: "card-header".to_string(),
657                            view_kind: "canonical".to_string(),
658                            canonical_name: Some("card-header".to_string()),
659                            range: range(3, 1, 3, 13),
660                            nested_safety: Some("unsafe".to_string()),
661                            composes: None,
662                            bem_suffix: None,
663                        }],
664                    },
665                },
666            ],
667            type_facts: vec![
668                TypeFactEntryV2 {
669                    file_path: "/tmp/App.tsx".to_string(),
670                    expression_id: "expr-1".to_string(),
671                    facts: StringTypeFactsV2 {
672                        kind: "constrained".to_string(),
673                        constraint_kind: Some("prefixSuffix".to_string()),
674                        values: None,
675                        prefix: Some("btn-".to_string()),
676                        suffix: Some("-active".to_string()),
677                        min_len: Some(10),
678                        max_len: None,
679                        char_must: None,
680                        char_may: None,
681                        may_include_other_chars: None,
682                    },
683                },
684                TypeFactEntryV2 {
685                    file_path: "/tmp/Card.tsx".to_string(),
686                    expression_id: "expr-2".to_string(),
687                    facts: StringTypeFactsV2 {
688                        kind: "finiteSet".to_string(),
689                        constraint_kind: None,
690                        values: Some(vec!["card-header".to_string(), "card-body".to_string()]),
691                        prefix: None,
692                        suffix: None,
693                        min_len: None,
694                        max_len: None,
695                        char_must: None,
696                        char_may: None,
697                        may_include_other_chars: None,
698                    },
699                },
700            ],
701        }
702    }
703
704    fn range(
705        start_line: usize,
706        start_character: usize,
707        end_line: usize,
708        end_character: usize,
709    ) -> RangeV2 {
710        RangeV2 {
711            start: PositionV2 {
712                line: start_line,
713                character: start_character,
714            },
715            end: PositionV2 {
716                line: end_line,
717                character: end_character,
718            },
719        }
720    }
721}