Skip to content

Commit 889baac

Browse files
Feat: Add explicit option to allow for str in req ids
1 parent 6ad9d03 commit 889baac

5 files changed

Lines changed: 90 additions & 8 deletions

File tree

docs/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
project_url = "https://eclipse-score.github.io/docs-as-code/"
1717
version = "0.1"
1818

19+
required_in_id = ["blabasdfasdfsla"]
20+
1921
extensions = [
2022
"score_sphinx_bundle",
2123
]

docs/how-to/write_docs.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,47 @@ For further documentation on needextends please `look here <https://sphinx-needs
7070

7171
In the future we will enable a check that needextends will only modify needs in the current document.
7272
You can ensure this by adding `c.this_doc()` to the filter string of the need.
73+
74+
75+
Requirement ID Feature Part
76+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
77+
78+
Requirement IDs with 3 parts (defined by the Metamodel) follow the format ``<Type>__<feature>__<Title>``.
79+
The ``<feature>`` part must relate to where the requirement lives in the documentation,
80+
so that IDs stay meaningful and traceable as the project evolves.
81+
82+
The feature part is validated by checking that at least one of the following is true:
83+
84+
* A segment of the feature part (split on ``_`` and ``-``) appears in the document's directory path
85+
* The initials of the feature part's segments appear in the document's directory path
86+
* The feature part contains a string explicitly allowed via ``required_in_id`` in ``conf.py``
87+
88+
**Examples** — given a requirement in ``internals/safety/fmea/requirements.rst``:
89+
90+
.. list-table::
91+
:header-rows: 1
92+
:widths: 45 10 45
93+
94+
* - ID
95+
-
96+
- Reason
97+
* - ``feat_saf__fmea__late_message``
98+
- ✅
99+
- ``fmea`` is in the path
100+
* - ``feat_saf__safety_fmea__late_message``
101+
- ✅
102+
- ``safety`` and ``fmea`` are in the path
103+
* - ``feat_saf__sf__late_message``
104+
- ✅
105+
- ``sf`` are the initials of ``safety_fmea``, which is in the path
106+
* - ``feat_saf__blabla__late_message``
107+
- ❌
108+
- ``blabla`` has no relation to the path ``internals/safety/fmea``
109+
110+
To explicitly allow a feature part that intentionally doesn't match the path
111+
(e.g. in a single module repository), add a matching string to ``required_in_id`` in ``conf.py``:
112+
113+
.. code-block:: python
114+
115+
# conf.py
116+
required_in_id = ["persistenc"]

src/extensions/score_metamodel/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ def _clear_needs_defaults(app: Sphinx):
235235
def setup(app: Sphinx) -> dict[str, str | bool]:
236236
app.add_config_value("external_needs_source", "", rebuild="env")
237237
app.add_config_value("score_metamodel_yaml", "", rebuild="env")
238+
app.add_config_value("required_in_id", [], rebuild="env")
238239
config_setdefault(app.config, "needs_id_required", True)
239240
config_setdefault(app.config, "needs_id_regex", "^[A-Za-z0-9_-]{6,}")
240241

src/extensions/score_metamodel/checks/id_contains_feature.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,36 @@ def id_contains_feature(app: Sphinx, need: NeedItem, log: CheckLogger):
5858
for featurepart in featureparts
5959
if featureparts and featurepart and docname
6060
)
61+
allowed_parts_from_config = app.config.required_in_id
62+
found_part_from_config = any(
63+
part_from_config.lower() in need.get("id")
64+
for part_from_config in allowed_parts_from_config
65+
if allowed_parts_from_config
66+
)
6167

6268
# allow abbreviation of the feature
6369
initials = (
6470
"".join(fp[0].lower() for fp in featureparts) if len(featureparts) > 1 else ""
6571
)
6672
foundinitials = bool(initials) and docname and initials in docname.lower()
73+
if not (foundfeatpart or foundinitials or found_part_from_config):
74+
parts_display = ", ".join(f"'{p}'" for p in featureparts)
75+
config_display = (
76+
", ".join(f"'{p}'" for p in allowed_parts_from_config)
77+
if allowed_parts_from_config
78+
else "[]"
79+
)
80+
81+
fix_options = [f"rename the feature part to match a segment of '{docname}'"]
82+
if initials:
83+
fix_options.append(f"use correct abbreviation '{initials}'")
84+
fix_options.append(
85+
f"Add an allowed part to `required_in_id` in conf.py (currently: {config_display})"
86+
)
6787

68-
if not (foundfeatpart or foundinitials):
69-
log.warning_for_option(
70-
need,
71-
"id",
72-
(
73-
f"Featurepart '{featureparts}' not in path '{docname}' "
74-
f"or abbreviation not ok, expected: '{initials}'."
75-
),
88+
combined_msg = (
89+
f"Feature part {parts_display} not found in path '{docname}'. "
90+
"\nHow can you fix this:\n=>"
91+
f"{'\n=> '.join(fix_options)}.\n"
7692
)
93+
log.warning_for_option(need, "id", combined_msg)

src/extensions/score_metamodel/tests/rst/id_contains_feature/test_id_contains_feature.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
#
1212
# SPDX-License-Identifier: Apache-2.0
1313
# *******************************************************************************
14+
1415
#CHECK: id_contains_feature
1516

17+
1618
.. Feature is in the path of the RST file
1719
#EXPECT-NOT[+2]: Feature 'id_contains_feature' not in path
1820

@@ -34,3 +36,19 @@
3436

3537
.. stkh_req:: This is a test
3638
:id: stkh_req__test__abce
39+
40+
41+
42+
.. Check if feature is correctly found to not be in path
43+
#EXPECT[+2]: Feature part 'abcabc' not found in path 'id_contains_feature'.
44+
45+
.. feat_req:: Testing if warning correctly triggers
46+
:id: feat_req__abcabc__testing
47+
48+
49+
50+
.. Check if feature is correctly found to be in path
51+
#EXPECT-NOT[+2]: Feature part
52+
53+
.. feat_req:: Testing if warning correctly triggers
54+
:id: feat_req__id_contains__testing

0 commit comments

Comments
 (0)