Skip to main content

omena_query/
lib.rs

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