Skip to content

Commit 0b047bb

Browse files
Copilotefargas
andcommitted
fix: apply review feedback — fence exclusion for inline backticks, relative path exclusion in frontmatter validator, fix deprecated test, update README
- markdown_parser.py: also skip inline-backtick path extraction inside fenced code blocks (previously only link patterns were skipped inside fences) - validate-frontmatter.py: use relative_to(docs_root).parts for directory exclusions so running the validator directly on .test-fixtures/ no longer skips everything - test_frontmatter_validator.py: fix test_deprecated_without_superseded_by_warns — deprecated-date was appended after the document body (outside YAML), now correctly placed inside the frontmatter block; add test_validate_fixtures_directly - test_markdown_parser.py: add test_inline_backtick_path_in_fenced_block_excluded - docs/.test-fixtures/README.md: replace misleading direct-invocation example with pytest commands Co-authored-by: efargas <9705611+efargas@users.noreply.github.com> Agent-Logs-Url: https://github.com/efargas/S7-Tools/sessions/aeb37029-8f66-4521-b16b-8f013b6abd02
1 parent d413523 commit 0b047bb

File tree

5 files changed

+79
-31
lines changed

5 files changed

+79
-31
lines changed

docs/.test-fixtures/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ This directory contains markdown files used to test and validate the
1616
## Usage
1717

1818
```bash
19-
# Validate frontmatter only
20-
python scripts/validate-frontmatter.py docs/.test-fixtures/
19+
# Run frontmatter-specific tests
20+
pytest scripts/tests -k frontmatter -q
2121

22-
# Run full validation suite (skip-compilation for speed)
23-
python scripts/validate-documentation.py --skip-compilation --verbose
22+
# Run the full validation test suite (covers all fixture scenarios)
23+
pytest scripts/tests -q
2424
```
2525

2626
> **Note:** This directory is excluded from the production validation runs
27-
> (`validate-frontmatter.py` and `validate-documentation.py` both skip `.test-fixtures/`).
28-
> Use the test suite in `scripts/tests/` to exercise these fixtures programmatically.
27+
> (`validate-frontmatter.py` and `validate-documentation.py` both skip `.test-fixtures/`
28+
> when scanning the main docs tree). Use the pytest suite above to exercise these
29+
> fixtures programmatically.

scripts/extractors/markdown_parser.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,12 @@ def extract_file_references(self, content: str, source_file: str) -> list[FilePa
151151
in_code_fence = not in_code_fence
152152
continue
153153

154-
# Always extract inline backtick patterns (safe – content is inside backticks)
154+
# Skip all extraction while inside a fenced code block –
155+
# paths and links there are illustrative/hypothetical, not real refs.
156+
if in_code_fence:
157+
continue
158+
159+
# Extract inline backtick patterns (prose references)
155160
for pattern in inline_patterns:
156161
for match in re.finditer(pattern, line):
157162
referenced_path = match.group(1)
@@ -166,23 +171,22 @@ def extract_file_references(self, content: str, source_file: str) -> list[FilePa
166171
exists=False
167172
))
168173

169-
# Only extract link patterns outside fenced code blocks
170-
if not in_code_fence:
171-
# Temporarily remove inline code spans to avoid matching inside them
172-
line_no_inline = re.sub(r'`[^`]+`', '', line)
173-
for pattern in link_patterns:
174-
for match in re.finditer(pattern, line_no_inline):
175-
referenced_path = match.group(1)
176-
path_type = "relative" if referenced_path.startswith('../') else (
177-
"absolute" if referenced_path.startswith('/') else "project_relative"
178-
)
179-
references.append(FilePathReference(
180-
source_file=source_file,
181-
line_number=line_num,
182-
referenced_path=referenced_path,
183-
path_type=path_type,
184-
exists=False
185-
))
174+
# Extract link patterns; strip inline code spans first to avoid
175+
# matching syntax written inside backticks.
176+
line_no_inline = re.sub(r'`[^`]+`', '', line)
177+
for pattern in link_patterns:
178+
for match in re.finditer(pattern, line_no_inline):
179+
referenced_path = match.group(1)
180+
path_type = "relative" if referenced_path.startswith('../') else (
181+
"absolute" if referenced_path.startswith('/') else "project_relative"
182+
)
183+
references.append(FilePathReference(
184+
source_file=source_file,
185+
line_number=line_num,
186+
referenced_path=referenced_path,
187+
path_type=path_type,
188+
exists=False
189+
))
186190

187191
return references
188192

scripts/tests/test_frontmatter_validator.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def test_metadata_directory_excluded(self, tmp_path):
157157
assert v.summary.errors == 0
158158

159159
def test_test_fixtures_directory_excluded(self, tmp_path):
160-
"""Files under docs/.test-fixtures/ must be skipped."""
160+
"""Files under docs/.test-fixtures/ must be skipped when scanning the full docs tree."""
161161
docs = _make_docs(tmp_path, {
162162
".test-fixtures/fixture.md": "# Fixture\n\nNo frontmatter.\n",
163163
"guides/valid.md": VALID_FM,
@@ -167,6 +167,20 @@ def test_test_fixtures_directory_excluded(self, tmp_path):
167167
assert v.summary.files_checked == 1
168168
assert v.summary.errors == 0
169169

170+
def test_validate_fixtures_directly(self, tmp_path):
171+
"""Running the validator directly on .test-fixtures/ must check the files it contains,
172+
not skip them (regression test for absolute-path parts check).
173+
"""
174+
fixtures = tmp_path / ".test-fixtures"
175+
fixtures.mkdir()
176+
(fixtures / "fixture.md").write_text(VALID_FM, encoding="utf-8")
177+
178+
v = FrontmatterValidator(str(fixtures))
179+
v.validate_all()
180+
# The file should be checked (not skipped because '.test-fixtures' is in its absolute path)
181+
assert v.summary.files_checked == 1
182+
assert v.summary.errors == 0
183+
170184

171185
class TestFrontmatterDeprecatedDocuments:
172186
"""Tests for deprecated document validation rules."""
@@ -184,8 +198,8 @@ def test_deprecated_without_superseded_by_warns(self, tmp_path):
184198
"""Deprecated documents without superseded-by should produce META-008 warning."""
185199
content = VALID_FM.replace(
186200
'status: "current"',
187-
'status: "deprecated"'
188-
) + "deprecated-date: \"2026-01-01\"\n"
201+
'status: "deprecated"\ndeprecated-date: "2026-01-01"'
202+
)
189203
docs = _make_docs(tmp_path, {"guides/dep2.md": content})
190204
v = FrontmatterValidator(str(docs))
191205
v.validate_all()

scripts/tests/test_markdown_parser.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,5 +371,26 @@ def test_four_backtick_fence_handled(self):
371371
assert "src/Does/Not/Exist.cs" not in paths
372372

373373

374+
def test_inline_backtick_path_in_fenced_block_excluded(self):
375+
"""Inline-backtick paths that appear inside a fenced code block (e.g. a markdown
376+
example showing how to write a reference) must NOT be extracted as real file
377+
references.
378+
"""
379+
content = """\
380+
```markdown
381+
Here is how you reference a file: `src/S7Tools/Hypothetical/MyService.cs`
382+
```
383+
384+
Real reference: `src/S7Tools/Services/Profiles/StandardProfileManager.cs`
385+
"""
386+
references = extract_file_references(content, "test.md")
387+
paths = [r.referenced_path for r in references]
388+
389+
# Path inside the fenced block must NOT be captured
390+
assert "src/S7Tools/Hypothetical/MyService.cs" not in paths
391+
# Prose path after the block must be captured
392+
assert "src/S7Tools/Services/Profiles/StandardProfileManager.cs" in paths
393+
394+
374395
if __name__ == "__main__":
375396
pytest.main([__file__, "-v"])

scripts/validate-frontmatter.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,19 @@ def __init__(self, docs_root: str, strict: bool = False):
6868
def validate_all(self):
6969
"""Validate all markdown files in docs_root"""
7070
for md_file in self.docs_root.rglob('*.md'):
71-
# Skip metadata, test fixtures, and website/blog directories
71+
# Compute path relative to docs_root so that exclusions only apply
72+
# when the excluded directory is *under* docs_root, not when the
73+
# script is invoked directly on one of those subdirectories.
74+
try:
75+
relative_parts = md_file.relative_to(self.docs_root).parts
76+
except ValueError:
77+
# Fallback: file is not under docs_root (shouldn't happen with rglob)
78+
relative_parts = md_file.parts
79+
7280
if (
73-
'.metadata' in md_file.parts or
74-
'.test-fixtures' in md_file.parts or
75-
'website' in md_file.parts
81+
'.metadata' in relative_parts or
82+
'.test-fixtures' in relative_parts or
83+
'website' in relative_parts
7684
):
7785
continue
7886

0 commit comments

Comments
 (0)