diff --git a/MODULE.bazel b/MODULE.bazel index 6cda922dd..683123aa7 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -70,5 +70,6 @@ http_file( ) bazel_dep(name = "score_process", version = "1.6.0") + # Provide the tools from the devcontainer to Bazel bazel_dep(name = "score_devcontainer", version = "1.7.0") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index c4c9ec9a5..40a5a6330 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -286,6 +286,8 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_shell/0.4.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_devcontainer/1.7.0/MODULE.bazel": "f9a5971fbd05f0ed14e7a373dbf58af72a5c58d081537a75c314daaf61c92ae9", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_devcontainer/1.7.0/source.json": "a3f55522fd9f63fae7a92f3cb5f91c25ae7474a39e9f9c633f0cf797fc0ca8e5", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.6.0/MODULE.bazel": "2496bc24311f69f49449ee85d8bb38e3b970cbfcf10d0a7f19b2d5262ce80e8d", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.6.0/source.json": "093424aa8bfed8705a3d142b21fe1d053258f2dd5eb1944941f6439b2c7157e9", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.5.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.5.3/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/stardoc/0.5.6/MODULE.bazel": "not found", diff --git a/docs/how-to/write_docs.rst b/docs/how-to/write_docs.rst index 68f43676d..3d0f9f0fa 100644 --- a/docs/how-to/write_docs.rst +++ b/docs/how-to/write_docs.rst @@ -70,3 +70,47 @@ For further documentation on needextends please `look here ____``. +The ``<feature>`` part must relate to where the requirement lives in the documentation, +so that IDs stay meaningful and traceable as the project evolves. + +The feature part is validated by checking that at least one of the following is true: + +* A segment of the feature part (split on ``_`` and ``-``) appears in the document's directory path +* The initials of the feature part's segments appear in the document's directory path +* The feature part contains a string explicitly allowed via ``required_in_id`` in ``conf.py`` + +**Examples** — given a requirement in ``internals/safety/fmea/requirements.rst``: + +.. list-table:: + :header-rows: 1 + :widths: 45 10 45 + + * - ID + - + - Reason + * - ``feat_saf__fmea__late_message`` + - ✅ + - ``fmea`` is in the path + * - ``feat_saf__safety_fmea__late_message`` + - ✅ + - ``safety`` and ``fmea`` are in the path + * - ``feat_saf__sf__late_message`` + - ✅ + - ``sf`` are the initials of ``safety_fmea``, which is in the path + * - ``feat_saf__blabla__late_message`` + - ❌ + - ``blabla`` has no relation to the path ``internals/safety/fmea`` + +To explicitly allow a feature part that intentionally doesn't match the path +(e.g. in a single module repository), add a matching string to ``required_in_id`` in ``conf.py``: + +.. code-block:: python + + # conf.py + required_in_id = ["persistenc"] diff --git a/src/extensions/score_metamodel/__init__.py b/src/extensions/score_metamodel/__init__.py index 2ad8eca5f..641025dc7 100644 --- a/src/extensions/score_metamodel/__init__.py +++ b/src/extensions/score_metamodel/__init__.py @@ -236,6 +236,7 @@ def _clear_needs_defaults(app: Sphinx): def setup(app: Sphinx) -> dict[str, str | bool]: app.add_config_value("external_needs_source", "", rebuild="env") app.add_config_value("score_metamodel_yaml", "", rebuild="env") + app.add_config_value("required_in_id", [], rebuild="env") config_setdefault(app.config, "needs_id_required", True) config_setdefault(app.config, "needs_id_regex", "^[A-Za-z0-9_-]{6,}") diff --git a/src/extensions/score_metamodel/checks/id_contains_feature.py b/src/extensions/score_metamodel/checks/id_contains_feature.py index 035cf18a9..38eeee19e 100644 --- a/src/extensions/score_metamodel/checks/id_contains_feature.py +++ b/src/extensions/score_metamodel/checks/id_contains_feature.py @@ -58,19 +58,36 @@ def id_contains_feature(app: Sphinx, need: NeedItem, log: CheckLogger): for featurepart in featureparts if featureparts and featurepart and docname ) + allowed_parts_from_config = app.config.required_in_id + found_part_from_config = any( + part_from_config.lower() in feature.lower() + for part_from_config in allowed_parts_from_config + if allowed_parts_from_config + ) # allow abbreviation of the feature initials = ( "".join(fp[0].lower() for fp in featureparts) if len(featureparts) > 1 else "" ) foundinitials = bool(initials) and docname and initials in docname.lower() + if not (foundfeatpart or foundinitials or found_part_from_config): + parts_display = ", ".join(f"'{p}'" for p in featureparts) + config_display = ( + ", ".join(f"'{p}'" for p in allowed_parts_from_config) + if allowed_parts_from_config + else "[]" + ) + + fix_options = [f"rename the feature part to match a segment of '{docname}'"] + if initials: + fix_options.append(f"use correct abbreviation '{initials}'") + fix_options.append( + f"Add an allowed part to `required_in_id` in conf.py (currently: {config_display})" + ) - if not (foundfeatpart or foundinitials): - log.warning_for_option( - need, - "id", - ( - f"Featurepart '{featureparts}' not in path '{docname}' " - f"or abbreviation not ok, expected: '{initials}'." - ), + combined_msg = ( + f"Feature part {parts_display} not found in path '{docname}'. " + "\nHow can you fix this:\n=>" + f"{'\n=> '.join(fix_options)}.\n" ) + log.warning_for_option(need, "id", combined_msg) diff --git a/src/extensions/score_metamodel/tests/rst/conf.py b/src/extensions/score_metamodel/tests/rst/conf.py index 5995ab809..27a9713a4 100644 --- a/src/extensions/score_metamodel/tests/rst/conf.py +++ b/src/extensions/score_metamodel/tests/rst/conf.py @@ -20,7 +20,7 @@ "sphinx_needs", "score_metamodel", ] - +required_in_id = ["blabla"] needs_external_needs = [ { "base_url": "https://eclipse-score.github.io/process_description/main/", diff --git a/src/extensions/score_metamodel/tests/rst/id_contains_feature/test_id_contains_feature.rst b/src/extensions/score_metamodel/tests/rst/id_contains_feature/test_id_contains_feature.rst index 50b07c966..cdd83ed74 100644 --- a/src/extensions/score_metamodel/tests/rst/id_contains_feature/test_id_contains_feature.rst +++ b/src/extensions/score_metamodel/tests/rst/id_contains_feature/test_id_contains_feature.rst @@ -11,8 +11,10 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* + #CHECK: id_contains_feature + .. Feature is in the path of the RST file #EXPECT-NOT[+2]: Feature 'id_contains_feature' not in path @@ -34,3 +36,33 @@ .. stkh_req:: This is a test :id: stkh_req__test__abce + + + +.. Check if feature is correctly found to not be in path +#EXPECT[+2]: Feature part 'abcabc' not found in path 'id_contains_feature'. + +.. feat_req:: Testing if warning correctly triggers + :id: feat_req__abcabc__testing + + + +.. Check if feature is correctly found to be in path +#EXPECT-NOT[+2]: Feature part + +.. feat_req:: Testing if warning correctly triggers + :id: feat_req__id_contains__testing + + +.. Testing if additional allowed param in conf.py is valid +#EXPECT-NOT[+2]: Feature part + +.. feat_req:: Testing conf.py parameter + :id: feat_req__blabla__testing + + +.. Testing if additional allowed param in conf.py is valid +#EXPECT[+2]: Feature part 'abcabcabc' not found in path 'id_contains_feature'. + +.. feat_req:: Testing conf.py parameter + :id: feat_req__abcabcabc__blabla_testing