1#![warn(missing_docs)]
2use perl_lsp_feature_ids::*;
8
9#[derive(Debug, Clone, Default)]
15pub struct AdvertisedFeatures {
16 pub completion: bool,
18 pub hover: bool,
20 pub definition: bool,
22 pub references: bool,
24 pub document_symbol: bool,
26 pub workspace_symbol: bool,
28 pub code_action: bool,
30 pub code_lens: bool,
32 pub formatting: bool,
34 pub range_formatting: bool,
36 pub rename: bool,
38 pub folding_range: bool,
40 pub selection_range: bool,
42 pub linked_editing: bool,
44 pub inlay_hints: bool,
46 pub semantic_tokens: bool,
48 pub call_hierarchy: bool,
50 pub type_hierarchy: bool,
52 pub diagnostic_provider: bool,
54 pub document_color: bool,
56 pub notebook_document_sync: bool,
58 pub notebook_cell_execution: bool,
60 pub signature_help: bool,
62 pub document_highlight: bool,
64 pub declaration: bool,
66}
67
68#[derive(Debug, Clone, Default, PartialEq, Eq)]
74pub struct BuildFlags {
75 pub completion: bool,
77 pub hover: bool,
79 pub definition: bool,
81 pub type_definition: bool,
83 pub implementation: bool,
85 pub references: bool,
87 pub document_symbol: bool,
89 pub workspace_symbol: bool,
91 pub inlay_hints: bool,
93 pub pull_diagnostics: bool,
95 pub workspace_symbol_resolve: bool,
97 pub semantic_tokens: bool,
99 pub code_actions: bool,
101 pub execute_command: bool,
103 pub rename: bool,
105 pub document_links: bool,
107 pub selection_ranges: bool,
109 pub on_type_formatting: bool,
111 pub code_lens: bool,
113 pub call_hierarchy: bool,
115 pub type_hierarchy: bool,
117 pub linked_editing: bool,
119 pub inline_completion: bool,
121 pub inline_values: bool,
123 pub notebook_document_sync: bool,
125 pub notebook_cell_execution: bool,
127 pub moniker: bool,
129 pub document_color: bool,
131 pub source_organize_imports: bool,
133 pub formatting: bool,
135 pub range_formatting: bool,
137 pub folding_range: bool,
139 pub signature_help: bool,
141 pub document_highlight: bool,
143 pub declaration: bool,
145}
146
147impl BuildFlags {
148 pub fn to_advertised_features(&self) -> AdvertisedFeatures {
150 AdvertisedFeatures {
151 completion: self.completion,
152 hover: self.hover,
153 definition: self.definition,
154 references: self.references,
155 document_symbol: self.document_symbol,
156 workspace_symbol: self.workspace_symbol,
157 code_action: self.code_actions,
158 code_lens: self.code_lens,
159 formatting: self.formatting,
160 range_formatting: self.range_formatting,
161 rename: self.rename,
162 folding_range: self.folding_range,
163 selection_range: self.selection_ranges,
164 linked_editing: self.linked_editing,
165 inlay_hints: self.inlay_hints,
166 semantic_tokens: self.semantic_tokens,
167 call_hierarchy: self.call_hierarchy,
168 type_hierarchy: self.type_hierarchy,
169 diagnostic_provider: self.pull_diagnostics,
170 document_color: self.document_color,
171 notebook_document_sync: self.notebook_document_sync,
172 notebook_cell_execution: self.notebook_cell_execution,
173 signature_help: self.signature_help,
174 document_highlight: self.document_highlight,
175 declaration: self.declaration,
176 }
177 }
178
179 pub fn to_feature_ids(&self) -> Vec<&'static str> {
181 let mut ids = Vec::new();
182
183 if self.completion {
184 ids.push(LSP_COMPLETION);
185 }
186 if self.hover {
187 ids.push(LSP_HOVER);
188 }
189 if self.definition {
190 ids.push(LSP_DEFINITION);
191 }
192 if self.type_definition {
193 ids.push(LSP_TYPE_DEFINITION);
194 }
195 if self.implementation {
196 ids.push(LSP_IMPLEMENTATION);
197 }
198 if self.references {
199 ids.push(LSP_REFERENCES);
200 }
201 if self.document_symbol {
202 ids.push(LSP_DOCUMENT_SYMBOL);
203 }
204 if self.workspace_symbol {
205 ids.push(LSP_WORKSPACE_SYMBOL);
206 }
207 if self.inlay_hints {
208 ids.push(LSP_INLAY_HINT);
209 }
210 if self.pull_diagnostics {
211 ids.push(LSP_PULL_DIAGNOSTICS);
212 }
213 if self.semantic_tokens {
214 ids.push(LSP_SEMANTIC_TOKENS);
215 }
216 if self.code_actions {
217 ids.push(LSP_CODE_ACTION);
218 }
219 if self.execute_command {
220 ids.push(LSP_EXECUTE_COMMAND);
221 }
222 if self.rename {
223 ids.push(LSP_RENAME);
224 }
225 if self.document_links {
226 ids.push(LSP_DOCUMENT_LINK);
227 }
228 if self.selection_ranges {
229 ids.push(LSP_SELECTION_RANGE);
230 }
231 if self.on_type_formatting {
232 ids.push(LSP_ON_TYPE_FORMATTING);
233 }
234 if self.code_lens {
235 ids.push(LSP_CODE_LENS);
236 }
237 if self.call_hierarchy {
238 ids.push(LSP_CALL_HIERARCHY);
239 }
240 if self.type_hierarchy {
241 ids.push(LSP_TYPE_HIERARCHY);
242 }
243 if self.linked_editing {
244 ids.push(LSP_LINKED_EDITING_RANGE);
245 }
246 if self.inline_completion {
247 ids.push(LSP_INLINE_COMPLETION);
248 }
249 if self.inline_values {
250 ids.push(LSP_INLINE_VALUE);
251 }
252 if self.notebook_document_sync {
253 ids.push(LSP_NOTEBOOK_DOCUMENT_SYNC);
254 }
255 if self.notebook_cell_execution {
256 ids.push(LSP_NOTEBOOK_CELL_EXECUTION);
257 }
258 if self.moniker {
259 ids.push(LSP_MONIKER);
260 }
261 if self.document_color {
262 ids.push(LSP_DOCUMENT_COLOR);
263 }
264 if self.formatting {
265 ids.push(LSP_FORMATTING);
266 }
267 if self.range_formatting {
268 ids.push(LSP_RANGE_FORMATTING);
269 ids.push(LSP_RANGES_FORMATTING);
272 }
273 if self.folding_range {
274 ids.push(LSP_FOLDING_RANGE);
275 }
276 if self.signature_help {
277 ids.push(LSP_SIGNATURE_HELP);
278 }
279 if self.document_highlight {
280 ids.push(LSP_DOCUMENT_HIGHLIGHT);
281 }
282 if self.declaration {
283 ids.push(LSP_DECLARATION);
284 }
285
286 ids.sort_unstable();
287 ids.dedup();
288 ids
289 }
290
291 pub fn production() -> Self {
293 Self {
294 completion: true,
295 hover: true,
296 definition: true,
297 type_definition: true,
298 implementation: true,
299 references: true,
300 document_symbol: true,
301 workspace_symbol: true,
302 inlay_hints: true,
303 pull_diagnostics: true,
304 workspace_symbol_resolve: true,
305 semantic_tokens: true,
306 code_actions: true,
307 execute_command: true,
308 rename: true,
309 document_links: true,
310 selection_ranges: true,
311 on_type_formatting: true,
312 code_lens: true,
313 call_hierarchy: true,
314 type_hierarchy: true,
315 linked_editing: true,
316 inline_completion: true,
317 inline_values: true,
318 notebook_document_sync: true,
319 notebook_cell_execution: true,
320 moniker: true,
321 document_color: true,
322 source_organize_imports: true,
323 formatting: false,
324 range_formatting: false,
325 folding_range: true,
326 signature_help: true,
327 document_highlight: true,
328 declaration: true,
329 }
330 }
331
332 pub fn all() -> Self {
334 Self {
335 completion: true,
336 hover: true,
337 definition: true,
338 type_definition: true,
339 implementation: true,
340 references: true,
341 document_symbol: true,
342 workspace_symbol: true,
343 inlay_hints: true,
344 pull_diagnostics: true,
345 workspace_symbol_resolve: true,
346 semantic_tokens: true,
347 code_actions: true,
348 execute_command: true,
349 rename: true,
350 document_links: true,
351 selection_ranges: true,
352 on_type_formatting: true,
353 code_lens: true,
354 call_hierarchy: true,
355 type_hierarchy: true,
356 linked_editing: true,
357 inline_completion: true,
358 inline_values: true,
359 notebook_document_sync: true,
360 notebook_cell_execution: true,
361 moniker: true,
362 document_color: true,
363 source_organize_imports: true,
364 formatting: true,
365 range_formatting: true,
366 folding_range: true,
367 signature_help: true,
368 document_highlight: true,
369 declaration: true,
370 }
371 }
372
373 pub fn ga_lock() -> Self {
375 Self {
376 completion: true,
377 hover: true,
378 definition: true,
379 type_definition: true,
380 implementation: true,
381 references: true,
382 document_symbol: true,
383 workspace_symbol: true,
384 inlay_hints: true,
385 pull_diagnostics: true,
386 workspace_symbol_resolve: true,
387 semantic_tokens: true,
388 code_actions: true,
389 execute_command: true,
390 rename: true,
391 document_links: true,
392 selection_ranges: true,
393 on_type_formatting: true,
394 code_lens: true,
395 call_hierarchy: true,
396 type_hierarchy: true,
397 linked_editing: true,
398 inline_completion: true,
399 inline_values: false,
400 notebook_document_sync: true,
401 notebook_cell_execution: true,
402 moniker: true,
403 document_color: true,
404 source_organize_imports: true,
405 formatting: true,
406 range_formatting: true,
407 folding_range: true,
408 signature_help: true,
409 document_highlight: true,
410 declaration: true,
411 }
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::{AdvertisedFeatures, BuildFlags};
418 use perl_lsp_feature_contracts::all_features;
419 use perl_lsp_feature_ids::LSP_DOCUMENT_COLOR;
420
421 #[test]
422 fn feature_ids_are_stable_and_sorted() {
423 let ga_lock = BuildFlags::ga_lock();
424 let ids = ga_lock.to_feature_ids();
425
426 let mut sorted = ids.clone();
427 sorted.sort_unstable();
428 sorted.dedup();
429
430 assert_eq!(ids, sorted);
431 assert!(
432 sorted.windows(2).all(|window| window[0] < window[1]),
433 "expected strictly increasing feature id ordering",
434 );
435 }
436
437 #[test]
438 fn feature_ids_are_valid_in_catalog() {
439 let ids = BuildFlags::all().to_feature_ids();
440 let known_ids: std::collections::HashSet<_> =
441 all_features().iter().map(|feature| feature.id).collect();
442 let unknown: Vec<_> = ids.iter().copied().filter(|id| !known_ids.contains(id)).collect();
443 assert!(unknown.is_empty(), "non-catalog feature IDs emitted: {:?}", unknown);
444 }
445
446 #[test]
447 fn document_color_uses_bdd_catalog_id() {
448 let flags = BuildFlags { document_color: true, ..Default::default() };
449 assert_eq!(flags.to_feature_ids(), vec![LSP_DOCUMENT_COLOR]);
450 }
451
452 #[test]
453 fn production_has_expected_profile_shape() {
454 let production = BuildFlags::production();
455 assert!(production.completion);
456 assert!(!production.formatting);
457 assert_eq!(
458 production,
459 BuildFlags {
460 completion: true,
461 hover: true,
462 definition: true,
463 type_definition: true,
464 implementation: true,
465 references: true,
466 document_symbol: true,
467 workspace_symbol: true,
468 inlay_hints: true,
469 pull_diagnostics: true,
470 workspace_symbol_resolve: true,
471 semantic_tokens: true,
472 code_actions: true,
473 execute_command: true,
474 rename: true,
475 document_links: true,
476 selection_ranges: true,
477 on_type_formatting: true,
478 code_lens: true,
479 call_hierarchy: true,
480 type_hierarchy: true,
481 linked_editing: true,
482 inline_completion: true,
483 inline_values: true,
484 notebook_document_sync: true,
485 notebook_cell_execution: true,
486 moniker: true,
487 document_color: true,
488 source_organize_imports: true,
489 formatting: false,
490 range_formatting: false,
491 folding_range: true,
492 signature_help: true,
493 document_highlight: true,
494 declaration: true
495 }
496 );
497 }
498
499 #[test]
502 fn ga_lock_has_expected_profile_shape() {
503 let ga = BuildFlags::ga_lock();
504 assert!(ga.completion);
505 assert!(ga.hover);
506 assert!(ga.definition);
507 assert!(ga.formatting, "ga-lock should include formatting");
508 assert!(ga.range_formatting, "ga-lock should include range_formatting");
509 assert!(!ga.inline_values, "ga-lock should exclude inline_values");
510 }
511
512 #[test]
515 fn all_profile_enables_every_flag() {
516 let all = BuildFlags::all();
517 assert!(all.completion);
518 assert!(all.hover);
519 assert!(all.definition);
520 assert!(all.type_definition);
521 assert!(all.implementation);
522 assert!(all.references);
523 assert!(all.document_symbol);
524 assert!(all.workspace_symbol);
525 assert!(all.inlay_hints);
526 assert!(all.pull_diagnostics);
527 assert!(all.workspace_symbol_resolve);
528 assert!(all.semantic_tokens);
529 assert!(all.code_actions);
530 assert!(all.execute_command);
531 assert!(all.rename);
532 assert!(all.document_links);
533 assert!(all.selection_ranges);
534 assert!(all.on_type_formatting);
535 assert!(all.code_lens);
536 assert!(all.call_hierarchy);
537 assert!(all.type_hierarchy);
538 assert!(all.linked_editing);
539 assert!(all.inline_completion);
540 assert!(all.inline_values);
541 assert!(all.notebook_document_sync);
542 assert!(all.notebook_cell_execution);
543 assert!(all.moniker);
544 assert!(all.document_color);
545 assert!(all.source_organize_imports);
546 assert!(all.formatting);
547 assert!(all.range_formatting);
548 assert!(all.folding_range);
549 assert!(all.signature_help);
550 assert!(all.document_highlight);
551 assert!(all.declaration);
552 }
553
554 #[test]
557 fn default_flags_are_all_false() {
558 let default = BuildFlags::default();
559 assert!(default.to_feature_ids().is_empty(), "default flags should yield no features");
560 }
561
562 #[test]
565 fn all_produces_superset_of_ga_lock_ids() {
566 let all_ids = BuildFlags::all().to_feature_ids();
567 let ga_ids = BuildFlags::ga_lock().to_feature_ids();
568 for id in &ga_ids {
569 assert!(all_ids.contains(id), "'all' profile should contain ga-lock feature '{id}'");
570 }
571 assert!(all_ids.len() >= ga_ids.len());
572 }
573
574 #[test]
577 fn to_advertised_features_maps_completion() {
578 let flags = BuildFlags { completion: true, ..Default::default() };
579 let adv = flags.to_advertised_features();
580 assert!(adv.completion);
581 assert!(!adv.hover);
582 }
583
584 #[test]
585 fn to_advertised_features_maps_code_actions_to_code_action() {
586 let flags = BuildFlags { code_actions: true, ..Default::default() };
587 let adv = flags.to_advertised_features();
588 assert!(
589 adv.code_action,
590 "BuildFlags.code_actions should map to AdvertisedFeatures.code_action"
591 );
592 }
593
594 #[test]
595 fn to_advertised_features_maps_pull_diagnostics_to_diagnostic_provider() {
596 let flags = BuildFlags { pull_diagnostics: true, ..Default::default() };
597 let adv = flags.to_advertised_features();
598 assert!(
599 adv.diagnostic_provider,
600 "BuildFlags.pull_diagnostics should map to AdvertisedFeatures.diagnostic_provider"
601 );
602 }
603
604 #[test]
605 fn to_advertised_features_maps_selection_ranges_to_selection_range() {
606 let flags = BuildFlags { selection_ranges: true, ..Default::default() };
607 let adv = flags.to_advertised_features();
608 assert!(
609 adv.selection_range,
610 "BuildFlags.selection_ranges should map to AdvertisedFeatures.selection_range"
611 );
612 }
613
614 #[test]
615 fn default_advertised_features_are_all_false() {
616 let adv = AdvertisedFeatures::default();
617 assert!(!adv.completion);
618 assert!(!adv.hover);
619 assert!(!adv.definition);
620 assert!(!adv.references);
621 assert!(!adv.document_symbol);
622 assert!(!adv.workspace_symbol);
623 assert!(!adv.code_action);
624 assert!(!adv.code_lens);
625 assert!(!adv.formatting);
626 assert!(!adv.rename);
627 }
628
629 #[test]
632 fn single_flag_produces_single_id() {
633 let cases: Vec<(&str, BuildFlags)> = vec![
634 ("completion", BuildFlags { completion: true, ..Default::default() }),
635 ("hover", BuildFlags { hover: true, ..Default::default() }),
636 ("definition", BuildFlags { definition: true, ..Default::default() }),
637 ("references", BuildFlags { references: true, ..Default::default() }),
638 ("rename", BuildFlags { rename: true, ..Default::default() }),
639 ("formatting", BuildFlags { formatting: true, ..Default::default() }),
640 ("signature_help", BuildFlags { signature_help: true, ..Default::default() }),
641 ("declaration", BuildFlags { declaration: true, ..Default::default() }),
642 ];
643 for (label, flags) in cases {
644 let ids = flags.to_feature_ids();
645 assert_eq!(
646 ids.len(),
647 1,
648 "flag '{label}' should produce exactly 1 feature id, got {ids:?}"
649 );
650 }
651 }
652
653 #[test]
656 fn production_and_all_differ_on_formatting() {
657 let prod = BuildFlags::production();
658 let all = BuildFlags::all();
659 assert!(!prod.formatting);
660 assert!(all.formatting);
661 assert!(!prod.range_formatting);
662 assert!(all.range_formatting);
663 }
664
665 #[test]
666 fn ga_lock_and_production_differ_on_inline_values() {
667 let ga = BuildFlags::ga_lock();
668 let prod = BuildFlags::production();
669 assert!(!ga.inline_values, "ga-lock excludes inline_values");
670 assert!(prod.inline_values, "production includes inline_values");
671 }
672}