Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
project_url = "https://eclipse-score.github.io/docs-as-code/"
version = "0.1"

required_in_id = ["blabasdfasdfsla"]

extensions = [
"score_sphinx_bundle",
]
Expand Down
44 changes: 44 additions & 0 deletions docs/how-to/write_docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,47 @@ For further documentation on needextends please `look here <https://sphinx-needs

In the future we will enable a check that needextends will only modify needs in the current document.
You can ensure this by adding `c.this_doc()` to the filter string of the need.


Requirement ID Feature Part
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Requirement IDs with 3 parts (defined by the Metamodel) follow the format ``<Type>__<feature>__<Title>``.
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"]
1 change: 1 addition & 0 deletions src/extensions/score_metamodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,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,}")

Expand Down
33 changes: 25 additions & 8 deletions src/extensions/score_metamodel/checks/id_contains_feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 need.get("id")
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)
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -34,3 +36,19 @@

.. 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
Loading