|
17 | 17 | from strictdoc.backend.sdoc.document_reference import DocumentReference |
18 | 18 | from strictdoc.backend.sdoc.error_handling import StrictDocSemanticError |
19 | 19 | from strictdoc.backend.sdoc.models.document import SDocDocument |
| 20 | +from strictdoc.backend.sdoc.models.document_grammar import ( |
| 21 | + DocumentGrammar, |
| 22 | +) |
20 | 23 | from strictdoc.backend.sdoc.models.model import SDocDocumentIF, SDocNodeIF |
21 | 24 | from strictdoc.backend.sdoc.models.node import SDocNode |
22 | 25 | from strictdoc.backend.sdoc.models.reference import FileEntry, FileReference |
|
37 | 40 | RelationMarkerType, |
38 | 41 | SourceFileTraceabilityInfo, |
39 | 42 | ) |
| 43 | +from strictdoc.backend.sdoc_source_code.models.source_node import SourceNode |
40 | 44 | from strictdoc.core.constants import GraphLinkType |
41 | 45 | from strictdoc.core.document_iterator import SDocDocumentIterator |
42 | 46 | from strictdoc.core.project_config import ProjectConfig |
@@ -638,95 +642,40 @@ def create_folder_section( |
638 | 642 | current_top_node = section_cache[path_component_] |
639 | 643 |
|
640 | 644 | for source_node_ in traceability_info_.source_nodes: |
641 | | - source_sdoc_node = SDocNode( |
642 | | - parent=document, |
643 | | - node_type=relevant_source_node_entry["node_type"], |
644 | | - fields=[], |
645 | | - relations=[], |
646 | | - # It is important that this autogenerated node is marked as such. |
647 | | - autogen=True, |
648 | | - ) |
649 | 645 | assert source_node_.entity_name is not None |
650 | 646 | source_sdoc_node_uid = f"{document_uid}/{path_to_source_file_}/{source_node_.entity_name}" |
651 | | - source_sdoc_node.ng_document_reference = DocumentReference() |
652 | | - source_sdoc_node.ng_document_reference.set_document(document) |
653 | | - source_sdoc_node.ng_including_document_reference = ( |
654 | | - DocumentReference() |
655 | | - ) |
656 | | - source_sdoc_node.set_field_value( |
657 | | - field_name="UID", |
658 | | - form_field_index=0, |
659 | | - value=source_sdoc_node_uid, |
660 | | - ) |
661 | | - source_sdoc_node.set_field_value( |
662 | | - field_name="TITLE", |
663 | | - form_field_index=0, |
664 | | - value=source_node_.entity_name, |
665 | | - ) |
666 | 647 |
|
667 | | - for node_name_, node_value_ in source_node_.fields.items(): |
668 | | - source_sdoc_node.set_field_value( |
669 | | - field_name=node_name_, |
670 | | - form_field_index=0, |
671 | | - value=node_value_, |
| 648 | + source_sdoc_node = traceability_index.get_node_by_uid_weak( |
| 649 | + source_sdoc_node_uid |
| 650 | + ) |
| 651 | + if source_sdoc_node is not None: |
| 652 | + source_sdoc_node = assert_cast(source_sdoc_node, SDocNode) |
| 653 | + self.merge_sdoc_node_with_source_node( |
| 654 | + source_sdoc_node, source_node_, document, config_entry_ |
| 655 | + ) |
| 656 | + else: |
| 657 | + source_sdoc_node = self.create_sdoc_node_from_source_node( |
| 658 | + source_node_, |
| 659 | + source_sdoc_node_uid, |
| 660 | + document, |
| 661 | + relevant_source_node_entry, |
| 662 | + ) |
| 663 | + current_top_node.section_contents.append(source_sdoc_node) |
| 664 | + traceability_index.graph_database.create_link( |
| 665 | + link_type=GraphLinkType.UID_TO_NODE, |
| 666 | + lhs_node=source_sdoc_node_uid, |
| 667 | + rhs_node=source_sdoc_node, |
672 | 668 | ) |
673 | | - current_top_node.section_contents.append(source_sdoc_node) |
674 | 669 |
|
675 | | - traceability_index.graph_database.create_link( |
676 | | - link_type=GraphLinkType.UID_TO_NODE, |
677 | | - lhs_node=source_sdoc_node_uid, |
678 | | - rhs_node=source_sdoc_node, |
| 670 | + self.connect_source_node_function( |
| 671 | + source_node_, source_sdoc_node_uid, traceability_info_ |
679 | 672 | ) |
680 | | - |
681 | | - source_node_function = source_node_.function |
682 | | - assert source_node_function is not None |
683 | | - |
684 | | - function_marker = self.forward_function_marker_from_function( |
685 | | - function=source_node_function, |
686 | | - marker_type=RangeMarkerType.FUNCTION, |
687 | | - reqs=[Req(None, source_sdoc_node_uid)], |
688 | | - role=None, |
689 | | - description=f"function {source_node_function.display_name}()", |
| 673 | + self.connect_sdoc_node_with_file_path( |
| 674 | + source_sdoc_node, path_to_source_file_ |
| 675 | + ) |
| 676 | + self.connect_source_node_requirements( |
| 677 | + source_node_, source_sdoc_node, traceability_index |
690 | 678 | ) |
691 | | - |
692 | | - traceability_info_.ng_map_reqs_to_markers.setdefault( |
693 | | - source_sdoc_node_uid, [] |
694 | | - ).append(function_marker) |
695 | | - |
696 | | - self.map_reqs_uids_to_paths.setdefault( |
697 | | - source_sdoc_node_uid, OrderedSet() |
698 | | - ).add(path_to_source_file_) |
699 | | - |
700 | | - self.map_paths_to_reqs.setdefault( |
701 | | - path_to_source_file_, OrderedSet() |
702 | | - ).add(source_sdoc_node) |
703 | | - |
704 | | - function_marker_copy = function_marker.create_end_marker() |
705 | | - |
706 | | - traceability_info_.markers.append(function_marker) |
707 | | - traceability_info_.markers.append(function_marker_copy) |
708 | | - |
709 | | - # |
710 | | - # This connects: |
711 | | - # - Source nodes and auto-generated requirements. |
712 | | - # - Source nodes-related requirements and auto-generated requirements. |
713 | | - # |
714 | | - for marker_ in source_node_.markers: |
715 | | - if not isinstance(marker_, FunctionRangeMarker): |
716 | | - continue |
717 | | - |
718 | | - for req_ in marker_.reqs: |
719 | | - node = traceability_index.get_node_by_uid_weak2(req_) |
720 | | - traceability_index.graph_database.create_link( |
721 | | - link_type=GraphLinkType.NODE_TO_PARENT_NODES, |
722 | | - lhs_node=source_sdoc_node, |
723 | | - rhs_node=node, |
724 | | - ) |
725 | | - traceability_index.graph_database.create_link( |
726 | | - link_type=GraphLinkType.NODE_TO_CHILD_NODES, |
727 | | - lhs_node=node, |
728 | | - rhs_node=source_sdoc_node, |
729 | | - ) |
730 | 679 |
|
731 | 680 | # Warn if source_node was not matched by any include_source_paths, it indicates misconfiguration |
732 | 681 | for unused_source_node_path in unused_source_node_paths: |
@@ -990,3 +939,164 @@ def compare_sdocnode_by_uid(node_: SDocNode) -> str: |
990 | 939 | return assert_cast(node_.reserved_uid, str) |
991 | 940 |
|
992 | 941 | path_requirements_.sort(key=compare_sdocnode_by_uid) |
| 942 | + |
| 943 | + def connect_source_node_function( |
| 944 | + self, |
| 945 | + source_node: SourceNode, |
| 946 | + source_sdoc_node_uid: str, |
| 947 | + traceability_info: SourceFileTraceabilityInfo, |
| 948 | + ) -> None: |
| 949 | + source_node_function = source_node.function |
| 950 | + assert source_node_function is not None |
| 951 | + |
| 952 | + function_marker = self.forward_function_marker_from_function( |
| 953 | + function=source_node_function, |
| 954 | + marker_type=RangeMarkerType.FUNCTION, |
| 955 | + reqs=[Req(None, source_sdoc_node_uid)], |
| 956 | + role=None, |
| 957 | + description=f"function {source_node_function.display_name}()", |
| 958 | + ) |
| 959 | + |
| 960 | + traceability_info.ng_map_reqs_to_markers.setdefault( |
| 961 | + source_sdoc_node_uid, [] |
| 962 | + ).append(function_marker) |
| 963 | + function_marker_copy = function_marker.create_end_marker() |
| 964 | + traceability_info.markers.append(function_marker) |
| 965 | + traceability_info.markers.append(function_marker_copy) |
| 966 | + |
| 967 | + @staticmethod |
| 968 | + def create_sdoc_node_from_source_node( |
| 969 | + source_node: SourceNode, |
| 970 | + uid: str, |
| 971 | + parent_document: SDocDocumentIF, |
| 972 | + relevant_source_node_entry: dict[str, str], |
| 973 | + ) -> SDocNode: |
| 974 | + source_sdoc_node = SDocNode( |
| 975 | + parent=parent_document, |
| 976 | + node_type=relevant_source_node_entry["node_type"], |
| 977 | + fields=[], |
| 978 | + relations=[], |
| 979 | + # It is important that this autogenerated node is marked as such. |
| 980 | + autogen=True, |
| 981 | + ) |
| 982 | + source_sdoc_node.ng_document_reference = DocumentReference() |
| 983 | + source_sdoc_node.ng_document_reference.set_document(parent_document) |
| 984 | + source_sdoc_node.ng_including_document_reference = DocumentReference() |
| 985 | + source_sdoc_node.set_field_value( |
| 986 | + field_name="UID", |
| 987 | + form_field_index=0, |
| 988 | + value=uid, |
| 989 | + ) |
| 990 | + source_sdoc_node.set_field_value( |
| 991 | + field_name="TITLE", |
| 992 | + form_field_index=0, |
| 993 | + value=source_node.entity_name, |
| 994 | + ) |
| 995 | + for node_name_, node_value_ in source_node.fields.items(): |
| 996 | + source_sdoc_node.set_field_value( |
| 997 | + field_name=node_name_, |
| 998 | + form_field_index=0, |
| 999 | + value=node_value_, |
| 1000 | + ) |
| 1001 | + return source_sdoc_node |
| 1002 | + |
| 1003 | + @staticmethod |
| 1004 | + def merge_sdoc_node_with_source_node( |
| 1005 | + sdoc_node: SDocNode, |
| 1006 | + source_node: SourceNode, |
| 1007 | + parent_document: SDocDocumentIF, |
| 1008 | + source_node_config_entry: dict[str, str], |
| 1009 | + ) -> None: |
| 1010 | + # First check if grammar element definitions are compatible. |
| 1011 | + source_node_type = source_node_config_entry["node_type"] |
| 1012 | + source_node_grammar = assert_cast( |
| 1013 | + parent_document.grammar, DocumentGrammar |
| 1014 | + ) |
| 1015 | + source_node_grammar_element = source_node_grammar.elements_by_type[ |
| 1016 | + source_node_type |
| 1017 | + ] |
| 1018 | + sdoc_node_document = assert_cast( |
| 1019 | + sdoc_node.get_document(), SDocDocumentIF |
| 1020 | + ) |
| 1021 | + sdoc_node_grammar = assert_cast( |
| 1022 | + sdoc_node_document.grammar, DocumentGrammar |
| 1023 | + ) |
| 1024 | + sdoc_node_grammar_element = sdoc_node_grammar.elements_by_type[ |
| 1025 | + source_node_type |
| 1026 | + ] |
| 1027 | + if source_node_grammar_element != sdoc_node_grammar_element: |
| 1028 | + raise StrictDocException( |
| 1029 | + f"Can't merge node {sdoc_node.reserved_uid} with source portion: " |
| 1030 | + f"Grammar element {sdoc_node_document.reserved_uid}::{source_node_type} " |
| 1031 | + f"incompatible with {parent_document.reserved_uid}::{source_node_type}" |
| 1032 | + ) |
| 1033 | + # Merge strategy: overwrite title if there's a TITLE from custom tags. |
| 1034 | + if "TITLE" in source_node.fields: |
| 1035 | + sdoc_node.set_field_value( |
| 1036 | + field_name="TITLE", |
| 1037 | + form_field_index=0, |
| 1038 | + value=source_node.fields["TITLE"], |
| 1039 | + ) |
| 1040 | + # Merge strategy: overwrite any field if there's a field with same name from custom tags. |
| 1041 | + for node_name_, node_value_ in source_node.fields.items(): |
| 1042 | + sdoc_node.set_field_value( |
| 1043 | + field_name=node_name_, |
| 1044 | + form_field_index=0, |
| 1045 | + value=node_value_, |
| 1046 | + ) |
| 1047 | + |
| 1048 | + def connect_sdoc_node_with_file_path( |
| 1049 | + self, sdoc_node: SDocNode, path_to_source_file_: str |
| 1050 | + ) -> None: |
| 1051 | + uid = sdoc_node.reserved_uid |
| 1052 | + assert uid is not None |
| 1053 | + self.map_reqs_uids_to_paths.setdefault(uid, OrderedSet()).add( |
| 1054 | + path_to_source_file_ |
| 1055 | + ) |
| 1056 | + self.map_paths_to_reqs.setdefault( |
| 1057 | + path_to_source_file_, OrderedSet() |
| 1058 | + ).add(sdoc_node) |
| 1059 | + |
| 1060 | + @staticmethod |
| 1061 | + def connect_source_node_requirements( |
| 1062 | + source_node: SourceNode, |
| 1063 | + sdoc_node: SDocNode, |
| 1064 | + traceability_index: "TraceabilityIndex", |
| 1065 | + ) -> None: |
| 1066 | + """ |
| 1067 | + Connect auto-generated requirement with function marker and with marker target requirement. |
| 1068 | +
|
| 1069 | + If function comment has @relation(REQ, scope=function), connections shall become |
| 1070 | + [REQ] <-parent- [auto-generated/merged sdoc_node] -file-> [function marker] |
| 1071 | +
|
| 1072 | + Here we link REQ and sdoc_node bidirectional. |
| 1073 | + """ |
| 1074 | + for marker_ in source_node.markers: |
| 1075 | + if not isinstance(marker_, FunctionRangeMarker): |
| 1076 | + continue |
| 1077 | + for req_ in marker_.reqs: |
| 1078 | + node = traceability_index.get_node_by_uid_weak2(req_) |
| 1079 | + if ( |
| 1080 | + node |
| 1081 | + not in traceability_index.graph_database.get_link_values( |
| 1082 | + link_type=GraphLinkType.NODE_TO_PARENT_NODES, |
| 1083 | + lhs_node=sdoc_node, |
| 1084 | + ) |
| 1085 | + ): |
| 1086 | + traceability_index.graph_database.create_link( |
| 1087 | + link_type=GraphLinkType.NODE_TO_PARENT_NODES, |
| 1088 | + lhs_node=sdoc_node, |
| 1089 | + rhs_node=node, |
| 1090 | + ) |
| 1091 | + if ( |
| 1092 | + sdoc_node |
| 1093 | + not in traceability_index.graph_database.get_link_values( |
| 1094 | + link_type=GraphLinkType.NODE_TO_CHILD_NODES, |
| 1095 | + lhs_node=node, |
| 1096 | + ) |
| 1097 | + ): |
| 1098 | + traceability_index.graph_database.create_link( |
| 1099 | + link_type=GraphLinkType.NODE_TO_CHILD_NODES, |
| 1100 | + lhs_node=node, |
| 1101 | + rhs_node=sdoc_node, |
| 1102 | + ) |
0 commit comments