Coverage for src/debputy/lsp/lsp_debian_upstream_metadata.py: 30%
233 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-03-24 16:38 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-03-24 16:38 +0000
1import textwrap
2from functools import lru_cache
3from typing import (
4 Optional,
5 Any,
6 Union,
7 Sequence,
8 TYPE_CHECKING,
9)
11from debputy.highlevel_manifest import MANIFEST_YAML
12from debputy.linting.lint_util import LintState, te_range_to_lsp
13from debputy.lsp.lsp_features import (
14 lint_diagnostics,
15 lsp_standard_handler,
16 lsp_hover,
17 lsp_completer,
18 LanguageDispatchRule,
19 SecondaryLanguage,
20)
21from debputy.lsp.lsp_generic_yaml import (
22 error_range_at_position,
23 insert_complete_marker_snippet,
24 YAML_COMPLETION_HINT_KEY,
25 yaml_flag_unknown_key,
26 _trace_cursor,
27 DEBPUTY_PLUGIN_METADATA,
28 resolve_keyword,
29 generic_yaml_hover,
30 completion_from_attr,
31)
32from debputy.manifest_parser.base_types import (
33 DebputyParsedContent,
34)
35from debputy.manifest_parser.declarative_parser import (
36 AttributeDescription,
37 ParserGenerator,
38 DeclarativeNonMappingInputParser,
39)
40from debputy.manifest_parser.declarative_parser import DeclarativeMappingInputParser
41from debputy.manifest_parser.parser_data import ParserContextData
42from debputy.manifest_parser.tagging_types import DebputyDispatchableType
43from debputy.manifest_parser.util import AttributePath
44from debputy.plugin.api.impl import plugin_metadata_for_debputys_own_plugin
45from debputy.plugin.api.impl_types import (
46 DeclarativeInputParser,
47 DispatchingParserBase,
48 DebputyPluginMetadata,
49 ListWrappedDeclarativeInputParser,
50 InPackageContextParser,
51 DeclarativeValuelessKeywordInputParser,
52 DispatchingObjectParser,
53)
54from debputy.plugin.api.spec import ParserDocumentation, reference_documentation
55from debputy.util import _info
56from debputy.yaml.compat import (
57 CommentedMap,
58 CommentedSeq,
59 MarkedYAMLError,
60 YAMLError,
61)
63try:
64 from debputy.lsp.debputy_ls import DebputyLanguageServer
65 from debputy.lsp.vendoring._deb822_repro.locatable import (
66 Position as TEPosition,
67 Range as TERange,
68 )
69except ImportError:
70 pass
72if TYPE_CHECKING:
73 import lsprotocol.types as types
74else:
75 import debputy.lsprotocol.types as types
78_DISPATCH_RULE = LanguageDispatchRule.new_rule(
79 "debian/upstream/metadata",
80 "debian/upstream/metadata",
81 [SecondaryLanguage("yaml", filename_based_lookup=True)],
82)
85lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_CODE_ACTION)
86lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL)
89class StrDebputyParsedContent(DebputyParsedContent):
90 content: str
93def _parser_handler(
94 _key: str,
95 value: Any,
96 _attr_path: AttributePath,
97 _context: Optional["ParserContextData"],
98) -> Any:
99 return value
102def add_keyword(
103 pg: ParserGenerator,
104 root_parser: DispatchingParserBase[Any],
105 plugin_metadata: DebputyPluginMetadata,
106 keyword: str,
107 *,
108 inline_reference_documentation: Optional[ParserDocumentation] = None,
109) -> None:
110 parser = pg.generate_parser(
111 StrDebputyParsedContent,
112 source_content=str,
113 inline_reference_documentation=inline_reference_documentation,
114 )
115 root_parser.register_parser(
116 keyword,
117 parser,
118 _parser_handler,
119 plugin_metadata,
120 )
123@lru_cache
124def root_object_parser() -> DispatchingObjectParser:
125 plugin_metadata = plugin_metadata_for_debputys_own_plugin()
126 pg = ParserGenerator()
127 pg.add_object_parser(
128 "<ROOT>",
129 unknown_keys_diagnostic_severity="warning",
130 )
131 root_parser = pg.dispatchable_object_parsers["<ROOT>"]
132 add_keyword(
133 pg,
134 root_parser,
135 plugin_metadata,
136 "Archive",
137 inline_reference_documentation=reference_documentation(
138 title="Archive (`Archive`)",
139 description=textwrap.dedent(
140 """\
141 The name of the large archive that the upstream work is part of, like CPAN.
142 """
143 ),
144 ),
145 )
146 add_keyword(
147 pg,
148 root_parser,
149 plugin_metadata,
150 "ASCL-Id",
151 inline_reference_documentation=reference_documentation(
152 title="ASCL Identifier (`ASCL-Id`)",
153 description=textwrap.dedent(
154 """\
155 Identification code in the http://ascl.net
156 """
157 ),
158 ),
159 )
160 add_keyword(
161 pg,
162 root_parser,
163 plugin_metadata,
164 "Bug-Database",
165 inline_reference_documentation=reference_documentation(
166 title="Bug database or tracker for the project (`Bug-Database`)",
167 description=textwrap.dedent(
168 """\
169 A URL to the list of known bugs for the project.
170 """
171 ),
172 ),
173 )
174 add_keyword(
175 pg,
176 root_parser,
177 plugin_metadata,
178 "Bug-Submit",
179 inline_reference_documentation=reference_documentation(
180 title="Bug submission URL for the project (`Bug-Submit`)",
181 description=textwrap.dedent(
182 """\
183 A URL that is the place where new bug reports should be sent.
184 """
185 ),
186 ),
187 )
188 add_keyword(
189 pg,
190 root_parser,
191 plugin_metadata,
192 "Cite-As",
193 inline_reference_documentation=reference_documentation(
194 title="Cite-As (`Cite-As`)",
195 description=textwrap.dedent(
196 """\
197 The way the authors want their software be cited in publications.
199 The value is a string which might contain a link in valid HTML syntax.
200 """
201 ),
202 ),
203 )
204 add_keyword(
205 pg,
206 root_parser,
207 plugin_metadata,
208 "Changelog",
209 inline_reference_documentation=reference_documentation(
210 title="Changelog (`Changelog`)",
211 description=textwrap.dedent(
212 """\
213 URL to the upstream changelog.
214 """
215 ),
216 ),
217 )
218 add_keyword(
219 pg,
220 root_parser,
221 plugin_metadata,
222 "CPE",
223 inline_reference_documentation=reference_documentation(
224 title="CPE (`CPE`)",
225 description=textwrap.dedent(
226 """\
227 One or more space separated http://cpe.mitre.org/ values useful to look up relevant CVEs
228 in the https://nvd.nist.gov/home.cfm and other CVE sources.
230 See `CPEtagPackagesDep` for information on how this information can be used.
231 **Example**: `cpe:/a:ethereal_group:ethereal`
232 """
233 ),
234 ),
235 )
236 add_keyword(
237 pg,
238 root_parser,
239 plugin_metadata,
240 "Documentation",
241 inline_reference_documentation=reference_documentation(
242 title="Documentation (`Documentation`)",
243 description=textwrap.dedent(
244 """\
245 A URL to online documentation.
246 """
247 ),
248 ),
249 )
250 add_keyword(
251 pg,
252 root_parser,
253 plugin_metadata,
254 "Donation",
255 inline_reference_documentation=reference_documentation(
256 title="Donation (`Donation`)",
257 description=textwrap.dedent(
258 """\
259 A URL to a donation form (or instructions).
260 """
261 ),
262 ),
263 )
264 add_keyword(
265 pg,
266 root_parser,
267 plugin_metadata,
268 "FAQ",
269 inline_reference_documentation=reference_documentation(
270 title="FAQ (`FAQ`)",
271 description=textwrap.dedent(
272 """\
273 A URL to the online FAQ.
274 """
275 ),
276 ),
277 )
278 add_keyword(
279 pg,
280 root_parser,
281 plugin_metadata,
282 "Funding",
283 inline_reference_documentation=reference_documentation(
284 title="Funding (`Funding`)",
285 description=textwrap.dedent(
286 """\
287 One or more sources of funding which have supported this project (e.g. NSF OCI-12345).
288 """
289 ),
290 ),
291 )
292 add_keyword(
293 pg,
294 root_parser,
295 plugin_metadata,
296 "Gallery",
297 inline_reference_documentation=reference_documentation(
298 title="Gallery (`Gallery`)",
299 description=textwrap.dedent(
300 """\
301 A URL to a gallery of pictures made with the program (not screenshots).
302 """
303 ),
304 ),
305 )
306 add_keyword(
307 pg,
308 root_parser,
309 plugin_metadata,
310 "Other-References",
311 inline_reference_documentation=reference_documentation(
312 title="Other-References (`Other-References`)",
313 description=textwrap.dedent(
314 """\
315 A URL to a upstream page containing more references.
316 """
317 ),
318 ),
319 )
320 add_keyword(
321 pg,
322 root_parser,
323 plugin_metadata,
324 "Reference",
325 inline_reference_documentation=reference_documentation(
326 title="Reference (`Reference`)",
327 # FIXME: Add the fields below as a nested subobject or list of such objects
328 description=textwrap.dedent(
329 """\
330 One or more bibliographic references, represented as a mapping or sequence of mappings containing
331 the one or more of the following keys.
333 The values for the keys are always scalars, and the keys that correspond to standard BibTeX
334 entries must provide the same content.
335 """
336 ),
337 ),
338 )
340 # Reference:: One or more bibliographic references, represented as a mapping or sequence of mappings containing the one or more of the following keys. The values for the keys are always scalars, and the keys that correspond to standard BibTeX entries must provide the same content.
341 #
342 # Author:: Author list in BibTeX friendly syntax (separating multiple authors by the keyword "and" and using as few as possible abbreviations in the names, as proposed in http://nwalsh.com/tex/texhelp/bibtx-23.html).
343 #
344 # Booktitle:: Title of the book the article is published in
345 #
346 # DOI:: This is the digital object identifier of the academic publication describing the packaged work.
347 #
348 # Editor:: Editor of the book the article is published in
349 #
350 # Eprint:: Hyperlink to the PDF file of the article.
351 #
352 # ISBN:: International Standard Book Number of the book if the article is part of the book or the reference is a book
353 #
354 # ISSN:: International Standard Serial Number of the periodical publication if the article is part of a series
355 #
356 # Journal:: Abbreviated journal name [To be discussed: which standard to recommend ?].
357 #
358 # Number:: Issue number.
359 #
360 # Pages:: Article page number(s). [To be discussed] Page number separator must be a single ASCII hyphen. What do we do with condensed notations like 401-10 ?
361 #
362 # PMID:: ID number in the https://www.ncbi.nlm.nih.gov/pubmed/ database.
363 #
364 # Publisher:: Publisher of the book containing the article
365 #
366 # Title:: Article title.
367 #
368 # Type:: A http://www.bibtex.org/Format indicating what is cited. Typical values are {{{article}}}, {{{book}}}, or {{{inproceedings}}}. [To be discussed]. In case this field is not present, {{{article}}} is assumed.
369 #
370 # URL:: Hyperlink to the abstract of the article. This should not point to the full version because this is specified by Eprint. Please also do not drop links to pubmed here because this would be redundant to PMID.
371 #
372 # Volume:: Journal volume.
373 #
374 # Year:: Year of publication
375 #
377 add_keyword(
378 pg,
379 root_parser,
380 plugin_metadata,
381 "Registration",
382 inline_reference_documentation=reference_documentation(
383 title="Registration (`Registration`)",
384 description=textwrap.dedent(
385 """\
386 A URL to a registration form (or instructions). This could be registration of bug reporting
387 accounts, registration for counting/contacting users etc.
388 """
389 ),
390 ),
391 )
392 add_keyword(
393 pg,
394 root_parser,
395 plugin_metadata,
396 "Registry",
397 # FIXME: Add List of `Name`, `Entry` objects
398 inline_reference_documentation=reference_documentation(
399 title="Registry (`Registry`)",
400 description=textwrap.dedent(
401 """\
402 This field shall point to external catalogs/registries of software.
404 The field features an array of "Name (of registry) - Entry (ID of software in that catalog)" pairs.
405 The names and entries shall only be names, not complete URIs, to avoid any bias on mirrors etc.
406 Example:
407 ```yaml
408 Registry:
409 - Name: bio.tools
410 Entry: clustalw
411 - Name: OMICtools
412 Entry: OMICS_02562
413 - Name: SciCrunch
414 Entry: SCR_002909
415 ```
416 """
417 ),
418 ),
419 )
421 add_keyword(
422 pg,
423 root_parser,
424 plugin_metadata,
425 "Repository",
426 inline_reference_documentation=reference_documentation(
427 title="Repository (`Repository`)",
428 description=textwrap.dedent(
429 """\
430 URL to a repository containing the upstream sources.
431 """
432 ),
433 ),
434 )
436 add_keyword(
437 pg,
438 root_parser,
439 plugin_metadata,
440 "Repository-Browse",
441 inline_reference_documentation=reference_documentation(
442 title="Repository-Browse (`Repository-Browse`)",
443 description=textwrap.dedent(
444 """\
445 A URL to browse the repository containing the upstream sources.
446 """
447 ),
448 ),
449 )
450 add_keyword(
451 pg,
452 root_parser,
453 plugin_metadata,
454 "Screenshots",
455 inline_reference_documentation=reference_documentation(
456 title="Screenshots (`Screenshots`)",
457 description=textwrap.dedent(
458 """\
459 One or more URLs to upstream pages containing screenshots (not <https://screenshots.debian.net>),
460 represented by a scalar or a sequence of scalars.
461 """
462 ),
463 ),
464 )
465 add_keyword(
466 pg,
467 root_parser,
468 plugin_metadata,
469 "Security-Contact",
470 inline_reference_documentation=reference_documentation(
471 title="Security-Contact (`Security-Contact`)",
472 description=textwrap.dedent(
473 """\
474 Which person, mailing list, forum, etc. to send security-related messages in the first place.
475 """
476 ),
477 ),
478 )
479 add_keyword(
480 pg,
481 root_parser,
482 plugin_metadata,
483 "Webservice",
484 inline_reference_documentation=reference_documentation(
485 title="Webservice (`Webservice`)",
486 description=textwrap.dedent(
487 """\
488 URL to a web page where the packaged program can also be used.
489 """
490 ),
491 ),
492 )
493 return root_parser
496@lint_diagnostics(_DISPATCH_RULE)
497async def _lint_debian_upstream_metadata(
498 lint_state: LintState,
499) -> None:
500 lines = lint_state.lines
502 try:
503 content = MANIFEST_YAML.load("".join(lines))
504 except MarkedYAMLError as e:
505 if e.context_mark:
506 line = e.context_mark.line
507 column = e.context_mark.column
508 else:
509 line = e.problem_mark.line
510 column = e.problem_mark.column
511 error_range = error_range_at_position(
512 lines,
513 line,
514 column,
515 )
516 lint_state.emit_diagnostic(
517 error_range,
518 f"YAML parse error: {e}",
519 "error",
520 "debputy",
521 )
522 except YAMLError as e:
523 error_range = TERange(
524 TEPosition(0, 0),
525 TEPosition(0, len(lines[0])),
526 )
527 lint_state.emit_diagnostic(
528 error_range,
529 f"Unknown YAML parse error: {e} [{e!r}]",
530 "error",
531 "debputy",
532 )
533 else:
534 feature_set = lint_state.plugin_feature_set
535 pg = feature_set.manifest_parser_generator
536 root_parser = root_object_parser()
537 _lint_content(
538 lint_state,
539 pg,
540 root_parser,
541 content,
542 )
545def _conflicting_key(
546 lint_state: LintState,
547 key_a: str,
548 key_b: str,
549 key_a_line: int,
550 key_a_col: int,
551 key_b_line: int,
552 key_b_col: int,
553) -> None:
554 key_a_range = TERange(
555 TEPosition(
556 key_a_line,
557 key_a_col,
558 ),
559 TEPosition(
560 key_a_line,
561 key_a_col + len(key_a),
562 ),
563 )
564 key_b_range = TERange(
565 TEPosition(
566 key_b_line,
567 key_b_col,
568 ),
569 TEPosition(
570 key_b_line,
571 key_b_col + len(key_b),
572 ),
573 )
574 lint_state.emit_diagnostic(
575 key_a_range,
576 f'The "{key_a}" cannot be used with "{key_b}".',
577 "error",
578 "debputy",
579 related_information=[
580 lint_state.related_diagnostic_information(
581 key_b_range, f'The attribute "{key_b}" is used here.'
582 ),
583 ],
584 )
586 lint_state.emit_diagnostic(
587 key_b_range,
588 f'The "{key_b}" cannot be used with "{key_a}".',
589 "error",
590 "debputy",
591 related_information=[
592 lint_state.related_diagnostic_information(
593 key_a_range,
594 f'The attribute "{key_a}" is used here.',
595 ),
596 ],
597 )
600def _lint_attr_value(
601 lint_state: LintState,
602 attr: AttributeDescription,
603 pg: ParserGenerator,
604 value: Any,
605) -> None:
606 attr_type = attr.attribute_type
607 if isinstance(attr_type, type) and issubclass(attr_type, DebputyDispatchableType):
608 parser = pg.dispatch_parser_table_for(attr_type)
609 _lint_content(
610 lint_state,
611 pg,
612 parser,
613 value,
614 )
617def _lint_declarative_mapping_input_parser(
618 lint_state: LintState,
619 pg: ParserGenerator,
620 parser: DeclarativeMappingInputParser,
621 content: Any,
622) -> None:
623 if not isinstance(content, CommentedMap):
624 return
625 lc = content.lc
626 for key, value in content.items():
627 attr = parser.manifest_attributes.get(key)
628 line, col = lc.key(key)
629 if attr is None:
630 corrected_key = yaml_flag_unknown_key(
631 lint_state,
632 key,
633 parser.manifest_attributes,
634 line,
635 col,
636 )
637 if corrected_key:
638 key = corrected_key
639 attr = parser.manifest_attributes.get(corrected_key)
640 if attr is None:
641 continue
643 _lint_attr_value(
644 lint_state,
645 attr,
646 pg,
647 value,
648 )
650 for forbidden_key in attr.conflicting_attributes:
651 if forbidden_key in content:
652 con_line, con_col = lc.key(forbidden_key)
653 _conflicting_key(
654 lint_state,
655 key,
656 forbidden_key,
657 line,
658 col,
659 con_line,
660 con_col,
661 )
662 for mx in parser.mutually_exclusive_attributes:
663 matches = content.keys() & mx
664 if len(matches) < 2:
665 continue
666 key, *others = list(matches)
667 line, col = lc.key(key)
668 for other in others:
669 con_line, con_col = lc.key(other)
670 _conflicting_key(
671 lint_state,
672 key,
673 other,
674 line,
675 col,
676 con_line,
677 con_col,
678 )
681def _lint_content(
682 lint_state: LintState,
683 pg: ParserGenerator,
684 parser: DeclarativeInputParser[Any],
685 content: Any,
686) -> None:
687 if isinstance(parser, DispatchingParserBase): 687 ↛ 716line 687 didn't jump to line 716 because the condition on line 687 was always true
688 if not isinstance(content, CommentedMap): 688 ↛ 689line 688 didn't jump to line 689 because the condition on line 688 was never true
689 return
690 lc = content.lc
691 for key, value in content.items():
692 is_known = parser.is_known_keyword(key)
693 if not is_known: 693 ↛ 707line 693 didn't jump to line 707 because the condition on line 693 was always true
694 line, col = lc.key(key)
695 corrected_key = yaml_flag_unknown_key(
696 lint_state,
697 key,
698 parser.registered_keywords(),
699 line,
700 col,
701 unknown_keys_diagnostic_severity=parser.unknown_keys_diagnostic_severity,
702 )
703 if corrected_key is not None: 703 ↛ 704line 703 didn't jump to line 704 because the condition on line 703 was never true
704 key = corrected_key
705 is_known = True
707 if is_known: 707 ↛ 708line 707 didn't jump to line 708 because the condition on line 707 was never true
708 subparser = parser.parser_for(key)
709 assert subparser is not None
710 _lint_content(
711 lint_state,
712 pg,
713 subparser.parser,
714 value,
715 )
716 elif isinstance(parser, ListWrappedDeclarativeInputParser):
717 if not isinstance(content, CommentedSeq):
718 return
719 subparser = parser.delegate
720 for value in content:
721 _lint_content(lint_state, pg, subparser, value)
722 elif isinstance(parser, InPackageContextParser):
723 if not isinstance(content, CommentedMap):
724 return
725 known_packages = lint_state.binary_packages
726 lc = content.lc
727 for k, v in content.items():
728 if "{{" not in k and known_packages is not None and k not in known_packages:
729 line, col = lc.key(k)
730 yaml_flag_unknown_key(
731 lint_state,
732 k,
733 known_packages,
734 line,
735 col,
736 message_format='Unknown package "{key}".',
737 )
738 _lint_content(lint_state, pg, parser.delegate, v)
739 elif isinstance(parser, DeclarativeMappingInputParser):
740 _lint_declarative_mapping_input_parser(
741 lint_state,
742 pg,
743 parser,
744 content,
745 )
748@lsp_completer(_DISPATCH_RULE)
749def debian_upstream_metadata_completer(
750 ls: "DebputyLanguageServer",
751 params: types.CompletionParams,
752) -> Optional[Union[types.CompletionList, Sequence[types.CompletionItem]]]:
753 doc = ls.workspace.get_text_document(params.text_document.uri)
754 lines = doc.lines
755 server_position = doc.position_codec.position_from_client_units(
756 lines, params.position
757 )
758 added_key = insert_complete_marker_snippet(lines, server_position)
759 attempts = 1 if added_key else 2
760 content = None
762 while attempts > 0:
763 attempts -= 1
764 try:
765 content = MANIFEST_YAML.load("".join(lines))
766 break
767 except MarkedYAMLError as e:
768 context_line = (
769 e.context_mark.line if e.context_mark else e.problem_mark.line
770 )
771 if (
772 e.problem_mark.line != server_position.line
773 and context_line != server_position.line
774 ):
775 l_data = (
776 lines[e.problem_mark.line].rstrip()
777 if e.problem_mark.line < len(lines)
778 else "N/A (OOB)"
779 )
781 _info(f"Parse error on line: {e.problem_mark.line}: {l_data}")
782 return None
784 if attempts > 0:
785 # Try to make it a key and see if that fixes the problem
786 new_line = (
787 lines[server_position.line].rstrip() + YAML_COMPLETION_HINT_KEY
788 )
789 lines[server_position.line] = new_line
790 except YAMLError:
791 break
792 if content is None:
793 context = lines[server_position.line].replace("\n", "\\n")
794 _info(f"Completion failed: parse error: Line in question: {context}")
795 return None
796 attribute_root_path = AttributePath.root_path(content)
797 m = _trace_cursor(content, attribute_root_path, server_position)
799 if m is None:
800 _info("No match")
801 return None
802 matched_key, attr_path, matched, parent = m
803 _info(f"Matched path: {matched} (path: {attr_path.path}) [{matched_key=}]")
804 feature_set = ls.plugin_feature_set
805 root_parser = root_object_parser()
806 segments = list(attr_path.path_segments())
807 km = resolve_keyword(
808 root_parser,
809 DEBPUTY_PLUGIN_METADATA,
810 segments,
811 0,
812 feature_set.manifest_parser_generator,
813 is_completion_attempt=True,
814 )
815 if km is None:
816 return None
817 parser, _, at_depth_idx = km
818 _info(f"Match leaf parser {at_depth_idx} -- {parser.__class__}")
819 items = []
820 if at_depth_idx + 1 >= len(segments):
821 if isinstance(parser, DispatchingParserBase):
822 if matched_key:
823 items = [
824 types.CompletionItem(f"{k}:")
825 for k in parser.registered_keywords()
826 if k not in parent
827 and not isinstance(
828 parser.parser_for(k).parser,
829 DeclarativeValuelessKeywordInputParser,
830 )
831 ]
832 else:
833 items = [
834 types.CompletionItem(k)
835 for k in parser.registered_keywords()
836 if k not in parent
837 and isinstance(
838 parser.parser_for(k).parser,
839 DeclarativeValuelessKeywordInputParser,
840 )
841 ]
842 elif isinstance(parser, InPackageContextParser):
843 binary_packages = ls.lint_state(doc).binary_packages
844 if binary_packages is not None:
845 items = [
846 types.CompletionItem(f"{p}:")
847 for p in binary_packages
848 if p not in parent
849 ]
850 elif isinstance(parser, DeclarativeMappingInputParser):
851 if matched_key:
852 _info("Match attributes")
853 locked = set(parent)
854 for mx in parser.mutually_exclusive_attributes:
855 if not mx.isdisjoint(parent.keys()):
856 locked.update(mx)
857 for attr_name, attr in parser.manifest_attributes.items():
858 if not attr.conflicting_attributes.isdisjoint(parent.keys()):
859 locked.add(attr_name)
860 break
861 items = [
862 types.CompletionItem(f"{k}:")
863 for k in parser.manifest_attributes
864 if k not in locked
865 ]
866 else:
867 # Value
868 key = segments[at_depth_idx] if len(segments) > at_depth_idx else None
869 attr = parser.manifest_attributes.get(key)
870 if attr is not None:
871 _info(f"Expand value / key: {key} -- {attr.attribute_type}")
872 items = completion_from_attr(
873 attr,
874 feature_set.manifest_parser_generator,
875 matched,
876 )
877 else:
878 _info(
879 f"Expand value / key: {key} -- !! {list(parser.manifest_attributes)}"
880 )
881 elif isinstance(parser, DeclarativeNonMappingInputParser):
882 attr = parser.alt_form_parser
883 items = completion_from_attr(
884 attr,
885 feature_set.manifest_parser_generator,
886 matched,
887 )
888 return items
891@lsp_hover(_DISPATCH_RULE)
892def debputy_manifest_hover(
893 ls: "DebputyLanguageServer",
894 params: types.HoverParams,
895) -> Optional[types.Hover]:
896 return generic_yaml_hover(ls, params, lambda _: root_object_parser())