From 4e207392e675acc03aa51a2ec250c99b1d26b180 Mon Sep 17 00:00:00 2001 From: MaximilianSoerenPollak Date: Tue, 19 May 2026 14:35:28 +0200 Subject: [PATCH 1/5] Feat: Add check to ensure valid is linked to valid --- .../score_metamodel/checks/graph_checks.py | 28 +++++++++++++++++- .../tests/rst/graph/test_invalid_graph.rst | 29 +++++++++++++++++++ .../tests/rst/graph/test_metamodel_graph.rst | 2 ++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/extensions/score_metamodel/tests/rst/graph/test_invalid_graph.rst diff --git a/src/extensions/score_metamodel/checks/graph_checks.py b/src/extensions/score_metamodel/checks/graph_checks.py index 2335db228..376137249 100644 --- a/src/extensions/score_metamodel/checks/graph_checks.py +++ b/src/extensions/score_metamodel/checks/graph_checks.py @@ -11,6 +11,7 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* import operator +from itertools import chain from collections.abc import Callable from functools import reduce from typing import Any, cast @@ -22,7 +23,7 @@ from sphinx.application import Sphinx from sphinx_needs.config import NeedType from sphinx_needs.data import NeedsView -from sphinx_needs.need_item import NeedItem +from sphinx_needs.need_item import NeedItem, NeedLink def eval_need_check(need: NeedItem, check: str, log: CheckLogger) -> bool: @@ -198,3 +199,28 @@ def check_metamodel_graph( f" Explanation: {explanation}" ) log.warning_for_need(need, msg) + + +@graph_check +def check_valid_only_links_to_valid( + app: Sphinx, + all_needs: NeedsView, + log: CheckLogger, +): + # Get all possible link types + needs_dict_all = {need["id"]: need for need in all_needs.values()} + needs_dict_local = { + need["id"]: need for need in all_needs.filter_is_external(False).values() + } + # Pre-Filter for only valid & local needs + valid_needs_id_all = [x.id for x in all_needs.values() if x.get("status") == "valid"] + valid_needs_local = [ + x for x in all_needs.filter_is_external(False).values() if x.get("status") == "valid" + ] + + for need in valid_needs_local: + all_linked_needs: list[NeedLink] = list(chain(*need._links.values())) # type: ignore + for link in all_linked_needs: + if link.id not in valid_needs_id_all: + msg = f"Is valid but links to invalid need: {link.id}" + log.warning_for_need(need, msg, is_new_check=True) diff --git a/src/extensions/score_metamodel/tests/rst/graph/test_invalid_graph.rst b/src/extensions/score_metamodel/tests/rst/graph/test_invalid_graph.rst new file mode 100644 index 000000000..913f429bb --- /dev/null +++ b/src/extensions/score_metamodel/tests/rst/graph/test_invalid_graph.rst @@ -0,0 +1,29 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* + +.. #CHECK: check_valid_only_links_to_valid + +.. feat_req:: Parent requirement INVALID QM + :id: feat_req__parent__QM_invalid + :safety: QM + :status: invalid + +.. We can not yet enable this test. As the check is only an 'info' and not yet a true warning +.. #EXPECT: comp_saf_fmea__child__16: is valid but links to invalid need: + +.. comp_saf_fmea:: Child requirement + :id: comp_saf_fmea__child__1 + :safety: QM + :status: valid + :mitigated_by: feat_req__parent__QM_invalid diff --git a/src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst b/src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst index b93561e0e..e55d6b49e 100644 --- a/src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst +++ b/src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst @@ -186,3 +186,5 @@ :safety: ASIL_B :status: valid :mitigated_by: feat_req__parent__ASIL_B + + From 82adeb014e6da7907f954fa020a208969930538f Mon Sep 17 00:00:00 2001 From: MaximilianSoerenPollak Date: Tue, 19 May 2026 14:36:47 +0200 Subject: [PATCH 2/5] Chore: Formatting & Linting --- .../score_metamodel/checks/graph_checks.py | 14 +++++++------- .../tests/rst/graph/test_invalid_graph.rst | 2 +- .../tests/rst/graph/test_metamodel_graph.rst | 2 -- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/extensions/score_metamodel/checks/graph_checks.py b/src/extensions/score_metamodel/checks/graph_checks.py index 376137249..0bc541c18 100644 --- a/src/extensions/score_metamodel/checks/graph_checks.py +++ b/src/extensions/score_metamodel/checks/graph_checks.py @@ -11,9 +11,9 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* import operator -from itertools import chain from collections.abc import Callable from functools import reduce +from itertools import chain from typing import Any, cast from score_metamodel import ( @@ -208,14 +208,14 @@ def check_valid_only_links_to_valid( log: CheckLogger, ): # Get all possible link types - needs_dict_all = {need["id"]: need for need in all_needs.values()} - needs_dict_local = { - need["id"]: need for need in all_needs.filter_is_external(False).values() - } # Pre-Filter for only valid & local needs - valid_needs_id_all = [x.id for x in all_needs.values() if x.get("status") == "valid"] + valid_needs_id_all = [ + x.id for x in all_needs.values() if x.get("status") == "valid" + ] valid_needs_local = [ - x for x in all_needs.filter_is_external(False).values() if x.get("status") == "valid" + x + for x in all_needs.filter_is_external(False).values() + if x.get("status") == "valid" ] for need in valid_needs_local: diff --git a/src/extensions/score_metamodel/tests/rst/graph/test_invalid_graph.rst b/src/extensions/score_metamodel/tests/rst/graph/test_invalid_graph.rst index 913f429bb..3dc7dcd57 100644 --- a/src/extensions/score_metamodel/tests/rst/graph/test_invalid_graph.rst +++ b/src/extensions/score_metamodel/tests/rst/graph/test_invalid_graph.rst @@ -22,7 +22,7 @@ .. We can not yet enable this test. As the check is only an 'info' and not yet a true warning .. #EXPECT: comp_saf_fmea__child__16: is valid but links to invalid need: -.. comp_saf_fmea:: Child requirement +.. comp_saf_fmea:: Child requirement :id: comp_saf_fmea__child__1 :safety: QM :status: valid diff --git a/src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst b/src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst index e55d6b49e..b93561e0e 100644 --- a/src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst +++ b/src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst @@ -186,5 +186,3 @@ :safety: ASIL_B :status: valid :mitigated_by: feat_req__parent__ASIL_B - - From 7146d9d149aed76cb477f3aff0141e66857b935f Mon Sep 17 00:00:00 2001 From: MaximilianSoerenPollak Date: Tue, 19 May 2026 17:13:00 +0200 Subject: [PATCH 3/5] Fix: Address performance concerns and other PR comments --- .../score_metamodel/checks/graph_checks.py | 22 +++++++++++-------- .../tests/rst/graph/test_invalid_graph.rst | 7 ++++-- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/extensions/score_metamodel/checks/graph_checks.py b/src/extensions/score_metamodel/checks/graph_checks.py index 0bc541c18..2a875f54c 100644 --- a/src/extensions/score_metamodel/checks/graph_checks.py +++ b/src/extensions/score_metamodel/checks/graph_checks.py @@ -207,11 +207,11 @@ def check_valid_only_links_to_valid( all_needs: NeedsView, log: CheckLogger, ): - # Get all possible link types - # Pre-Filter for only valid & local needs - valid_needs_id_all = [ + # Pre-Gather all *valid* need id's (external, & local) + valid_needs_id_all = set( x.id for x in all_needs.values() if x.get("status") == "valid" - ] + ) + # Pre-Gather all LOCAL *valid* id's to iterate over and check valid_needs_local = [ x for x in all_needs.filter_is_external(False).values() @@ -219,8 +219,12 @@ def check_valid_only_links_to_valid( ] for need in valid_needs_local: - all_linked_needs: list[NeedLink] = list(chain(*need._links.values())) # type: ignore - for link in all_linked_needs: - if link.id not in valid_needs_id_all: - msg = f"Is valid but links to invalid need: {link.id}" - log.warning_for_need(need, msg, is_new_check=True) + # Using set comprehension here to enable faster computation for comparisons + all_linked_needs: set[str] = set( + x.id + for x in set(chain(*need._links.values())) # type: ignore + ) + invalid_needs = all_linked_needs.difference(valid_needs_id_all) + if invalid_needs: + msg = f"Is valid but links to invalid need(s): {invalid_needs}" + log.warning_for_need(need, msg, is_new_check=True) diff --git a/src/extensions/score_metamodel/tests/rst/graph/test_invalid_graph.rst b/src/extensions/score_metamodel/tests/rst/graph/test_invalid_graph.rst index 3dc7dcd57..581d3b8d1 100644 --- a/src/extensions/score_metamodel/tests/rst/graph/test_invalid_graph.rst +++ b/src/extensions/score_metamodel/tests/rst/graph/test_invalid_graph.rst @@ -12,7 +12,7 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -.. #CHECK: check_valid_only_links_to_valid +#CHECK: check_valid_only_links_to_valid .. feat_req:: Parent requirement INVALID QM :id: feat_req__parent__QM_invalid @@ -20,7 +20,10 @@ :status: invalid .. We can not yet enable this test. As the check is only an 'info' and not yet a true warning -.. #EXPECT: comp_saf_fmea__child__16: is valid but links to invalid need: +.. Therefore the test is the inverse of what we will test once it is enabled. + +.. #EXPECT: comp_saf_fmea__child__16: is valid but links to invalid need(s): +#EXPECT-NOT: invalid need(s): .. comp_saf_fmea:: Child requirement :id: comp_saf_fmea__child__1 From 2f44318faac6e6a2dcf2a9be6893a46cfe7113d1 Mon Sep 17 00:00:00 2001 From: MaximilianSoerenPollak Date: Tue, 19 May 2026 17:14:05 +0200 Subject: [PATCH 4/5] Chore: Formatting --- src/extensions/score_metamodel/checks/graph_checks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/score_metamodel/checks/graph_checks.py b/src/extensions/score_metamodel/checks/graph_checks.py index 2a875f54c..a6f825bad 100644 --- a/src/extensions/score_metamodel/checks/graph_checks.py +++ b/src/extensions/score_metamodel/checks/graph_checks.py @@ -23,7 +23,7 @@ from sphinx.application import Sphinx from sphinx_needs.config import NeedType from sphinx_needs.data import NeedsView -from sphinx_needs.need_item import NeedItem, NeedLink +from sphinx_needs.need_item import NeedItem def eval_need_check(need: NeedItem, check: str, log: CheckLogger) -> bool: From a98fce9fce2e2ebcd56e29594a13edbe4bcc9707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20S=C3=B6ren=20Pollak?= Date: Tue, 19 May 2026 17:21:15 +0200 Subject: [PATCH 5/5] Update warning message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andreas Zwinkau <95761648+a-zw@users.noreply.github.com> Signed-off-by: Maximilian Sören Pollak --- src/extensions/score_metamodel/checks/graph_checks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions/score_metamodel/checks/graph_checks.py b/src/extensions/score_metamodel/checks/graph_checks.py index a6f825bad..e056bf62f 100644 --- a/src/extensions/score_metamodel/checks/graph_checks.py +++ b/src/extensions/score_metamodel/checks/graph_checks.py @@ -226,5 +226,5 @@ def check_valid_only_links_to_valid( ) invalid_needs = all_linked_needs.difference(valid_needs_id_all) if invalid_needs: - msg = f"Is valid but links to invalid need(s): {invalid_needs}" + msg = f"is valid but links to invalid need(s): {invalid_needs}" log.warning_for_need(need, msg, is_new_check=True)