Skip to content
Merged
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
28 changes: 27 additions & 1 deletion strictdoc/backend/sdoc_source_code/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,36 @@ def line_marker_processor(
# This marker is within a "nosdoc" block, so we ignore it.
return

# Semantic constraint validation
has_previous_markers = len(parse_context.markers) > 0
is_consecutive = (
has_previous_markers
and parse_context.markers[-1].ng_range_line_end == line
)
is_at_eof = line == parse_context.file_stats.lines_total

if is_consecutive:
raise StrictDocSemanticError(
"Consecutive LineMarkers are not allowed",
hint=None,
example=None,
line=line,
filename=location["filename"],
)

if is_at_eof:
raise StrictDocSemanticError(
"LineMarker cannot be followed by EOF",
hint=None,
example=None,
line=line,
filename=location["filename"],
)

parse_context.markers.append(line_marker)
line_marker.ng_source_line_begin = line
line_marker.ng_range_line_begin = line
line_marker.ng_range_line_end = line
line_marker.ng_range_line_end = line + 1

for req in line_marker.reqs:
markers = parse_context.map_reqs_to_markers.setdefault(req, [])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ def is_end(self) -> bool:
def is_line_marker(self) -> bool:
return any(map(lambda m_: isinstance(m_, LineMarker), self.markers))

def is_range_marker(self) -> bool:
return any(map(lambda m_: isinstance(m_, RangeMarker), self.markers))


SourceLineEntry = Union[
Markup,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@

{%- if view_object.pygmented_source_file_lines|length > 0 -%}
<div id="source" class="source">
{%- set ns = namespace(prev_line = none) -%}
{%- for line in view_object.pygmented_source_file_lines -%}
{%- if line.__class__.__name__ == "SourceMarkerTuple" and not line.is_end() -%}
{%- set is_marker = (line.__class__.__name__ == "SourceMarkerTuple") -%}
{%- set is_markup = (line.__class__.__name__ == "Markup") -%}

{%- if is_marker and not line.is_end() -%}
{%- set current_marker_link = view_object.render_marker_range_link(line) -%}
{%- set current_range_begin = line.ng_range_line_begin -%}
{%- set current_range_end = line.ng_range_line_end -%}
Expand Down Expand Up @@ -70,17 +74,36 @@
{%- endif -%}
</div>
</div>
{%- if line.__class__.__name__ == "SourceMarkerTuple" and (line.is_end() or line.is_line_marker()) -%}

{#-- keep begin marker to close on the next non-marker line --#}
{%- if is_marker and line.is_line_marker() -%}
{%- set ns.prev_line = line -%}
{%- endif -%}

{#-- decide closer candidate: explicit end vs implicit close --#}
{%- set marker_is_end = (is_marker and line.is_range_marker() and line.is_end()) -%}
{%- set implicit_close = (is_markup and ns.prev_line != none) -%}

{%- if marker_is_end -%}
{%- set range_closer_line = line -%}
{%- elif implicit_close -%}
{%- set range_closer_line = ns.prev_line -%}
{%- set ns.prev_line = none -%}
{%- else -%}
{%- set range_closer_line = none -%}
{%- endif -%}

{% if range_closer_line != None %}
<div
class="source__range-closer"
data-end="{{ line.markers[0].ng_range_line_end }}"
data-end="{{ range_closer_line.markers[0].ng_range_line_end }}"
>
<div class="source__range-closer-label">
{%- with
begin=line.markers[0].ng_range_line_begin,
end=line.markers[0].ng_range_line_end,
href=view_object.render_marker_range_link(line),
scope=line.markers[0].get_description()
begin=range_closer_line.markers[0].ng_range_line_begin,
end=range_closer_line.markers[0].ng_range_line_end,
href=view_object.render_marker_range_link(range_closer_line),
scope=range_closer_line.markers[0].get_description()
-%}
{% include "screens/source_file_view/range_button.jinja" %}
{%- endwith -%}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
A generic line @relation(REQ-001, scope=line, role=Implementation)
@relation(REQ-001, scope=line, role=Implementation)
A generic line

@relation(REQ-001, scope=range_start, role=Implementation)
A generic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-001#24#24">
CHECK-HTML: file.py, <i>lines: 24-24</i>, line
CHECK-HTML: <span{{.*}}">(Implementation)</span>

CHECK-HTML: <a{{.*}}href="../_source_files/file.txt.html#REQ-001#1#1">
CHECK-HTML: file.txt, <i>lines: 1-1</i>, line
CHECK-HTML: <a{{.*}}href="../_source_files/file.txt.html#REQ-001#1#2">
CHECK-HTML: file.txt, <i>lines: 1-2</i>, line
CHECK-HTML: <span{{.*}}">(Implementation)</span>

CHECK-HTML: <a{{.*}}href="../_source_files/file.txt.html#REQ-001#3#6">
CHECK-HTML: file.txt, <i>lines: 3-6</i>, range
CHECK-HTML: <a{{.*}}href="../_source_files/file.txt.html#REQ-001#4#7">
CHECK-HTML: file.txt, <i>lines: 4-7</i>, range
CHECK-HTML: <span{{.*}}">(Implementation)</span>
Original file line number Diff line number Diff line change
Expand Up @@ -355,15 +355,15 @@ def test_050_line_marker():
assert markers[0].reqs == ["REQ-001"]
assert markers[0].ng_source_line_begin == 1
assert markers[0].ng_range_line_begin == 1
assert markers[0].ng_range_line_end == 1
assert markers[0].ng_range_line_end == 2
assert markers[1].reqs == ["REQ-002"]
assert markers[1].ng_source_line_begin == 3
assert markers[1].ng_range_line_begin == 3
assert markers[1].ng_range_line_end == 3
assert markers[1].ng_range_line_end == 4
assert markers[2].reqs == ["REQ-003"]
assert markers[2].ng_source_line_begin == 5
assert markers[2].ng_range_line_begin == 5
assert markers[2].ng_range_line_end == 5
assert markers[2].ng_range_line_end == 6


def test_060_file_level_marker():
Expand Down Expand Up @@ -443,3 +443,29 @@ def test_validation_03_range_start_without_range_end():
exc_info.value.args[0]
== "Unmatched @sdoc keyword found in source file."
)


def test_validation_04_consecutive_line_markers():
source_input = """
# @sdoc(REQ-001)
# @sdoc(REQ-002)
"""
reader = SourceFileTraceabilityReader()

with pytest.raises(Exception) as exc_info:
_ = reader.read(source_input)

assert exc_info.type is StrictDocSemanticError
assert exc_info.value.args[0] == "Consecutive LineMarkers are not allowed"


def test_validation_05_line_marker_followed_by_eof():
source_input = "# @sdoc(REQ-001)"

reader = SourceFileTraceabilityReader()

with pytest.raises(Exception) as exc_info:
_ = reader.read(source_input)

assert exc_info.type is StrictDocSemanticError
assert exc_info.value.args[0] == "LineMarker cannot be followed by EOF"
Loading