Skip to content

Commit 516e04a

Browse files
committed
Forward functions: Search all parent classes
1 parent 5ac32b9 commit 516e04a

File tree

4 files changed

+49
-30
lines changed

4 files changed

+49
-30
lines changed

strictdoc/backend/sdoc_source_code/reader_python.py

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# mypy: disable-error-code="no-redef,no-untyped-call,no-untyped-def,type-arg,var-annotated"
22
import sys
33
import traceback
4-
from typing import List, Optional, Union
4+
from typing import List, Optional, Sequence, Union
55

66
import tree_sitter_python
77
from tree_sitter import Language, Node, Parser
@@ -119,25 +119,9 @@ def read(self, input_buffer: bytes, file_path=None):
119119

120120
assert function_name is not None, "Function name"
121121

122-
parent_class_name: Optional[str] = None
123-
if (function_parent_node := node_.parent) is not None and (
124-
class_node_or_node := function_parent_node.parent
125-
) is not None:
126-
if (
127-
class_node_or_node.type == "class_definition"
128-
and len(class_node_or_node.children) > 1
129-
):
130-
second_node_or_none = class_node_or_node.children[1]
131-
if (
132-
second_node_or_none.type == "identifier"
133-
and second_node_or_none.text is not None
134-
):
135-
parent_class_name = second_node_or_none.text.decode(
136-
"utf8"
137-
)
138-
139-
if parent_class_name is not None:
140-
function_name = parent_class_name + "." + function_name
122+
parent_names = self.get_node_ns(node_)
123+
if parent_names:
124+
function_name = f"{'.'.join(parent_names)}.{function_name}"
141125

142126
block_comment = None
143127
if (
@@ -271,3 +255,31 @@ def read_from_file(self, file_path):
271255
# TODO: when --debug is provided
272256
# traceback.print_exc() # noqa: ERA001
273257
sys.exit(1)
258+
259+
@staticmethod
260+
def get_node_ns(node: Node) -> Sequence[str]:
261+
"""Walk up the tree and find parent classes"""
262+
parent_scopes = []
263+
cursor = node
264+
while cursor:
265+
if (block_node := cursor.parent) is not None and (
266+
class_node_or_node := block_node.parent
267+
) is not None:
268+
cursor = class_node_or_node
269+
if (
270+
class_node_or_node.type == "class_definition"
271+
and len(class_node_or_node.children) > 1
272+
):
273+
second_node_or_none = class_node_or_node.children[1]
274+
if (
275+
second_node_or_none.type == "identifier"
276+
and second_node_or_none.text is not None
277+
):
278+
parent_class_name = second_node_or_none.text.decode(
279+
"utf8"
280+
)
281+
parent_scopes.append(parent_class_name)
282+
else:
283+
cursor = None
284+
parent_scopes.reverse()
285+
return parent_scopes

tests/integration/features/file_traceability/_language_parsers/python/04_python_forward_function_in_class/file.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
class Foo:
2+
class Bar:
3+
class Baz:
4+
def hello_world(self):
5+
print("hello world") # noqa: T201
6+
27
def hello_world(self):
38
"""
49
@relation(REQ-1, scope=function)

tests/integration/features/file_traceability/_language_parsers/python/04_python_forward_function_in_class/input.sdoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ STATEMENT: Requirement Statement
1717
RELATIONS:
1818
- TYPE: File
1919
VALUE: file.py
20-
FUNCTION: Foo.hello_world
20+
FUNCTION: Foo.Bar.Baz.hello_world

tests/integration/features/file_traceability/_language_parsers/python/04_python_forward_function_in_class/test.itest

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@ CHECK: Published: Hello world doc
66
RUN: %check_exists --file "%S/Output/html/_source_files/file.py.html"
77

88
RUN: %cat %S/Output/html/%THIS_TEST_FOLDER/input.html | filecheck %s --dump-input=fail --check-prefix CHECK-HTML
9-
CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-1#2#11">
10-
CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-1#6#11">
11-
CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-1#8#8">
12-
CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-1#2#11">
9+
CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-1#7#16">
10+
CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-1#11#16">
11+
CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-1#13#13">
12+
CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-1#7#16">
13+
CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-2#4#5">
1314

1415
RUN: %cat %S/Output/html/_source_files/file.py.html | filecheck %s --dump-input=fail --check-prefix CHECK-SOURCE-FILE
15-
CHECK-SOURCE-FILE: def</span> <span class="nf">hello_world</span>
16-
CHECK-SOURCE-FILE: href="../_source_files/file.py.html#REQ-1#2#11"
17-
CHECK-SOURCE-FILE: href="../_source_files/file.py.html#REQ-2#2#11"
1816
CHECK-SOURCE-FILE: <div data-line=3 class="source__line-content">
19-
CHECK-SOURCE-FILE: href="../_source_files/file.py.html#REQ-1#2#11"
17+
CHECK-SOURCE-FILE: def</span> <span class="nf">hello_world</span>
18+
CHECK-SOURCE-FILE: href="../_source_files/file.py.html#REQ-2#4#5"
19+
CHECK-SOURCE-FILE: href="../_source_files/file.py.html#REQ-1#7#16"
20+
CHECK-SOURCE-FILE: href="../_source_files/file.py.html#REQ-1#11#16"
21+
CHECK-SOURCE-FILE: href="../_source_files/file.py.html#REQ-1#13#13"
2022

2123
RUN: %cat %S/Output/html/source_coverage.html | filecheck %s --dump-input=fail --check-prefix CHECK-SOURCE-COVERAGE
22-
CHECK-SOURCE-COVERAGE: 71.4%
24+
CHECK-SOURCE-COVERAGE: 63.2%

0 commit comments

Comments
 (0)