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