-
Notifications
You must be signed in to change notification settings - Fork 25
Feat: Forbid certain uses of need extends directive #544
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
058a104
d9f441b
a33b81f
27929c2
a1c1ec6
31d5653
e83e5a1
376a8d2
792f9a8
80ddb3a
a4f1996
ffa4a0e
0306ba6
2d88464
e110892
b9b8f08
7ae3337
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| # ******************************************************************************* | ||
| # 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 | ||
| # ******************************************************************************* | ||
| from __future__ import annotations | ||
|
|
||
| import sphinx_needs.directives.need | ||
| from sphinx_needs.config import NeedsSphinxConfig | ||
| from sphinx_needs.data import ExtendType, NeedsExtendType, NeedsMutable | ||
| from sphinx_needs.directives.needextend import extend_needs_data as original_function | ||
| from sphinx_needs.exceptions import NeedsInvalidFilter | ||
| from sphinx_needs.filter_common import filter_needs_mutable | ||
| from sphinx_needs.logging import get_logger, log_warning | ||
| from sphinx_needs.needs_schema import ( | ||
| FieldFunctionArray, | ||
| FieldLiteralValue, | ||
| LinksFunctionArray, | ||
| LinksLiteralValue, | ||
| ) | ||
|
|
||
| logger = get_logger(__name__) | ||
|
|
||
|
|
||
| def score_extend_needs_data_func( # noqa: C901 | ||
| all_needs: NeedsMutable, | ||
| extends: dict[str, NeedsExtendType], | ||
| needs_config: NeedsSphinxConfig, | ||
| ): | ||
| """Use data gathered from needextend directives to modify fields of existing needs.""" | ||
| # regardless of parallel build worker completion order. | ||
| sorted_extends = sorted(extends.values(), key=lambda x: (x["docname"], x["lineno"])) | ||
|
|
||
| current_needextend: NeedsExtendType | ||
| for current_needextend in sorted_extends: | ||
| need_filter = current_needextend["filter"] | ||
| location = (current_needextend["docname"], current_needextend["lineno"]) | ||
|
|
||
| # β β | ||
| # β This is currently as a grace period still allowed, but β | ||
| # β will be forbiden in future releases β | ||
| # β β | ||
| # if "c.this_doc()" not in need_filter: | ||
| # error_msg = "Potentially altering needs outside of the document is not allowed. Please add 'c.this_doc()' to the needextend to limit it to only needs in the same document" | ||
| # log_warning(logger, error_msg, "needextend", location=location) | ||
|
|
||
| if current_needextend["filter_is_id"]: | ||
| try: | ||
| found_needs = [all_needs[need_filter]] | ||
| except KeyError: | ||
| error = f"Provided id {need_filter!r} for needextend does not exist." | ||
| if current_needextend["strict"]: | ||
| raise NeedsInvalidFilter(error) from KeyError | ||
| log_warning(logger, error, "needextend", location=location) | ||
| continue | ||
| else: | ||
| try: | ||
| found_needs = filter_needs_mutable( | ||
| all_needs, | ||
| needs_config, | ||
| need_filter, | ||
| location=location, | ||
| origin_docname=current_needextend["docname"], | ||
| ) | ||
| except Exception as e: | ||
| log_warning( | ||
| logger, | ||
| f"Invalid filter {need_filter!r}: {e}", | ||
| "needextend", | ||
| location=location, | ||
| ) | ||
| continue | ||
| for found_need in found_needs: | ||
| if found_need["is_external"]: | ||
| log_warning( | ||
| logger, | ||
| f"Error when extending need: {found_need['id']}. " | ||
| + "It is not allowed to modify external needs via needextend", | ||
| "needextend", | ||
| location, | ||
| ) | ||
| # Work in the stored needs, not on the search result | ||
| need = all_needs[found_need["id"]] | ||
|
|
||
| location = ( | ||
| current_needextend["docname"], | ||
| current_needextend["lineno"], | ||
| ) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AI-assisted review (Claude): Suggestion:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is taken stragith from the function. I'm not changing that, it is how the original function works. |
||
|
|
||
| for _, etype, link_value in current_needextend["list_modifications"]: | ||
| match (etype, link_value): | ||
| case ( | ||
| ExtendType.REPLACE | ExtendType.DELETE, | ||
| LinksLiteralValue() | LinksFunctionArray(), | ||
| ): | ||
| # Replacing / Deleting links is not allowed | ||
| error_msg = ( | ||
| f"Error when extending need: {need['id']}. " | ||
| "Replace or Delete action is not allowed via needextends." | ||
| ) | ||
| # logger.warning_for_need(current_needextend["id"], error_msg) | ||
| log_warning(logger, error_msg, "needextend", location=location) | ||
|
|
||
| for option_name, etype, field_value in current_needextend["modifications"]: | ||
| if etype == ExtendType.DELETE: | ||
| error_msg = ( | ||
| f"Error when extending need: {need['id']}. " | ||
| "Delete action is not allowed via needextends." | ||
| ) | ||
| log_warning(logger, error_msg, "needextend", location=location) | ||
| match (etype, field_value): | ||
| case (ExtendType.APPEND, FieldLiteralValue()): | ||
| if isinstance(field_value.value, str): | ||
| error_msg = ( | ||
| f"Error when extending need: {need['id']}. " | ||
| "Append action is not allowed via needextends on 'string type options'." | ||
| ) | ||
| log_warning( | ||
| logger, error_msg, "needextend", location=location | ||
| ) | ||
|
|
||
| case ( | ||
| ExtendType.REPLACE, | ||
| None | FieldLiteralValue() | FieldFunctionArray(), | ||
| ): | ||
| if need[option_name]: | ||
| error_msg = f"Error when extending need: {need['id']}. Replacing of options that are already set is not allowed via needextends." | ||
|
|
||
| log_warning( | ||
| logger, error_msg, "needextend", location=location | ||
| ) | ||
| return original_function(all_needs, extends, needs_config) | ||
|
|
||
|
|
||
| sphinx_needs.directives.need.extend_needs_data = score_extend_needs_data_func | ||
|
MaximilianSoerenPollak marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,75 @@ | ||||||||||||||
| .. | ||||||||||||||
| # ******************************************************************************* | ||||||||||||||
| # 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 | ||||||||||||||
| # ******************************************************************************* | ||||||||||||||
|
|
||||||||||||||
| .. stkh_req:: Test Req Extends 1 | ||||||||||||||
| :id: stkh_req__test__need_extends_1 | ||||||||||||||
| :status: invalid | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| .. stkh_req:: Test Req Extends 2 | ||||||||||||||
| :id: stkh_req__test__need_extends_abc | ||||||||||||||
| :status: valid | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| .. feat_req:: Test Linkage Override | ||||||||||||||
| :id: feat_req__test__linkage_override | ||||||||||||||
| :satisfies: stkh_req__test__need_extends_1 | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| .. Replacing of options that are already set is not allowed. | ||||||||||||||
|
|
||||||||||||||
| #EXPECT: Error when extending need: stkh_req__test__need_extends_1. Replacing of options that are already set is not allowed via needextends. | ||||||||||||||
|
|
||||||||||||||
| .. needextend:: c.this_doc() and id == 'stkh_req__test__need_extends_1' | ||||||||||||||
| :status: valid | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| .. We explicitly allow the replacing of options on needs that are NOT set and | ||||||||||||||
| .. where the need is in the current document | ||||||||||||||
|
|
||||||||||||||
| #EXPECT-NOT: Replacing of options | ||||||||||||||
|
|
||||||||||||||
| .. needextend:: c.this_doc() and id == 'stkh_req__test__need_extends_1' | ||||||||||||||
| :safety: NO | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| #EXPECT-NOT: Replacing of options | ||||||||||||||
|
|
||||||||||||||
| .. needextend:: c.this_doc() and id == 'stkh_req__test__need_extends_1' | ||||||||||||||
| :safety: NO | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| #EXPECT: Error when extending need: feat_req__test__linkage_override. Replace or Delete action is not allowed via needextends. | ||||||||||||||
|
|
||||||||||||||
| .. needextend:: feat_req__test__linkage_override | ||||||||||||||
| :satisfies: stkh_req__test__need_extends_abc | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| #EXPECT: Error when extending need: stkh_req__test__need_extends_1. Delete action is not allowed via needextends. | ||||||||||||||
|
|
||||||||||||||
| .. needextend:: id == 'stkh_req__test__need_extends_1' | ||||||||||||||
| :-safety: | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| #EXPECT: Error when extending need: stkh_req__test__need_extends_1. Append action is not allowed via needextends on 'string type options' | ||||||||||||||
|
|
||||||||||||||
| .. needextend:: id == 'stkh_req__test__need_extends_1' | ||||||||||||||
| :+safety: YES | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| .. This will be activated once we have activated the c.this_doc() check aswell | ||||||||||||||
| .. #EXPECT: Potentially altering needs outside of the document is not allowed. Please add 'c.this_doc()' to the needextend to limit it to only needs in the same document | ||||||||||||||
|
|
||||||||||||||
| .. .. needextend:: id == 'stkh_req__test__need_extends_1' | ||||||||||||||
| .. :security: QM | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AI-assisted review (Claude): Important: Three validation branches have no test coverage The checker has five distinct warning paths. Only two are exercised here:
The three uncovered branches represent ~60% of the warning logic. Adding RST fixtures for at least |
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AI-assisted review (Claude):
Suggestion: external-need branch warns but continues processing
After logging the warning for an external need, the loop falls through and still executes the modification checks below (lines 89β130) for that same need. The original
extend_needs_datawill then apply the actual change. This means the user gets a warning and the modification still happens.If modifying external needs is genuinely disallowed, this should
continueafter the warning (or the original function should skip it too). If it's just advisory for now, the message "It is not allowed" is misleading β consider "Warning: modifying external needs is discouraged" until it's enforced.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ye that's not an issue, you get the warning and the build fails.
Though we could do early exits to speed it up, that mightn ot be a bad idea.