mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-26 18:35:05 +00:00
fix(governance): preserve inline link title context
This commit is contained in:
1 parent
9b21b2ccd5
commit
f5a4d33797
3 files changed
+96
-20
No files matched your search
@@ -9,7 +9,7 @@ INLINE_LINK_START_RE = re.compile(r"\[[^\]\n]*\]\(")
|
||||
REFERENCE_DEFINITION_RE = re.compile(
|
||||
r"""(?mx)
|
||||
^\ {0,3}\[(?P<label>[^\]\n]+)\]:[\t\ ]*
|
||||
(?:\r?\n[\t\ ]+)?
|
||||
(?:\r?\n[\t\ ]*)?
|
||||
(?P<target><[^>\n]+>|[^\t\ \n]+)
|
||||
(?:[\t\ ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\([^()]*\)))?
|
||||
[\t\ ]*$
|
||||
@@ -77,20 +77,46 @@ def without_inline_code(text: str) -> str:
|
||||
def inline_link_targets(text: str) -> list[str]:
|
||||
targets: list[str] = []
|
||||
matches = list(INLINE_LINK_START_RE.finditer(text))
|
||||
for index, match in enumerate(matches):
|
||||
index = 0
|
||||
cursor = 0
|
||||
while index < len(matches):
|
||||
match = matches[index]
|
||||
if match.start() < cursor:
|
||||
index += 1
|
||||
continue
|
||||
start = match.end()
|
||||
next_start = (
|
||||
matches[index + 1].start() if index + 1 < len(matches) else len(text)
|
||||
)
|
||||
candidate_index = index + 1
|
||||
if start < len(text) and text[start] == "<":
|
||||
end = text.find(">", start + 1, next_start)
|
||||
if end != -1:
|
||||
targets.append(text[start : end + 1])
|
||||
end = start + 1
|
||||
while end < len(text):
|
||||
if (
|
||||
candidate_index < len(matches)
|
||||
and end == matches[candidate_index].start()
|
||||
):
|
||||
index = candidate_index
|
||||
break
|
||||
if text[end] == ">":
|
||||
targets.append(text[start : end + 1])
|
||||
cursor = end + 1
|
||||
index = candidate_index
|
||||
break
|
||||
end += 1
|
||||
else:
|
||||
break
|
||||
continue
|
||||
depth = 0
|
||||
quote: str | None = None
|
||||
escaped = False
|
||||
for end in range(start, next_start):
|
||||
end = start
|
||||
while end < len(text):
|
||||
if (
|
||||
candidate_index < len(matches)
|
||||
and end == matches[candidate_index].start()
|
||||
):
|
||||
if quote is None:
|
||||
index = candidate_index
|
||||
break
|
||||
candidate_index += 1
|
||||
character = text[end]
|
||||
if quote is not None:
|
||||
if escaped:
|
||||
@@ -99,17 +125,24 @@ def inline_link_targets(text: str) -> list[str]:
|
||||
escaped = True
|
||||
elif character == quote:
|
||||
quote = None
|
||||
end += 1
|
||||
continue
|
||||
if character in {"'", '"'}:
|
||||
quote = character
|
||||
end += 1
|
||||
continue
|
||||
if character == "(":
|
||||
depth += 1
|
||||
elif character == ")":
|
||||
if depth == 0:
|
||||
targets.append(text[start:end])
|
||||
cursor = end + 1
|
||||
index = candidate_index
|
||||
break
|
||||
depth -= 1
|
||||
end += 1
|
||||
else:
|
||||
break
|
||||
return targets
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ INLINE_LINK_START_RE = re.compile(r"\[[^\]\n]*\]\(")
|
||||
REFERENCE_DEFINITION_RE = re.compile(
|
||||
r"""(?mx)
|
||||
^\ {0,3}\[(?P<label>[^\]\n]+)\]:[\t\ ]*
|
||||
(?:\r?\n[\t\ ]+)?
|
||||
(?:\r?\n[\t\ ]*)?
|
||||
(?P<target><[^>\n]+>|[^\t\ \n]+)
|
||||
(?:[\t\ ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\([^()]*\)))?
|
||||
[\t\ ]*$
|
||||
@@ -77,20 +77,46 @@ def without_inline_code(text: str) -> str:
|
||||
def inline_link_targets(text: str) -> list[str]:
|
||||
targets: list[str] = []
|
||||
matches = list(INLINE_LINK_START_RE.finditer(text))
|
||||
for index, match in enumerate(matches):
|
||||
index = 0
|
||||
cursor = 0
|
||||
while index < len(matches):
|
||||
match = matches[index]
|
||||
if match.start() < cursor:
|
||||
index += 1
|
||||
continue
|
||||
start = match.end()
|
||||
next_start = (
|
||||
matches[index + 1].start() if index + 1 < len(matches) else len(text)
|
||||
)
|
||||
candidate_index = index + 1
|
||||
if start < len(text) and text[start] == "<":
|
||||
end = text.find(">", start + 1, next_start)
|
||||
if end != -1:
|
||||
targets.append(text[start : end + 1])
|
||||
end = start + 1
|
||||
while end < len(text):
|
||||
if (
|
||||
candidate_index < len(matches)
|
||||
and end == matches[candidate_index].start()
|
||||
):
|
||||
index = candidate_index
|
||||
break
|
||||
if text[end] == ">":
|
||||
targets.append(text[start : end + 1])
|
||||
cursor = end + 1
|
||||
index = candidate_index
|
||||
break
|
||||
end += 1
|
||||
else:
|
||||
break
|
||||
continue
|
||||
depth = 0
|
||||
quote: str | None = None
|
||||
escaped = False
|
||||
for end in range(start, next_start):
|
||||
end = start
|
||||
while end < len(text):
|
||||
if (
|
||||
candidate_index < len(matches)
|
||||
and end == matches[candidate_index].start()
|
||||
):
|
||||
if quote is None:
|
||||
index = candidate_index
|
||||
break
|
||||
candidate_index += 1
|
||||
character = text[end]
|
||||
if quote is not None:
|
||||
if escaped:
|
||||
@@ -99,17 +125,24 @@ def inline_link_targets(text: str) -> list[str]:
|
||||
escaped = True
|
||||
elif character == quote:
|
||||
quote = None
|
||||
end += 1
|
||||
continue
|
||||
if character in {"'", '"'}:
|
||||
quote = character
|
||||
end += 1
|
||||
continue
|
||||
if character == "(":
|
||||
depth += 1
|
||||
elif character == ")":
|
||||
if depth == 0:
|
||||
targets.append(text[start:end])
|
||||
cursor = end + 1
|
||||
index = candidate_index
|
||||
break
|
||||
depth -= 1
|
||||
end += 1
|
||||
else:
|
||||
break
|
||||
return targets
|
||||
|
||||
|
||||
|
||||
@@ -262,6 +262,15 @@ def test_artifact_scope_ignores_title_with_closing_parenthesis(project: Path) ->
|
||||
assert result.returncode == 0, result.stdout
|
||||
|
||||
|
||||
def test_artifact_scope_ignores_link_like_text_in_title(project: Path) -> None:
|
||||
(project / "guide.md").write_text("# Guide\n", encoding="utf-8")
|
||||
(project / "index.md").write_text(
|
||||
'[guide](guide.md "See [example](missing.md)")\n', encoding="utf-8"
|
||||
)
|
||||
result = run_audit(project, "artifacts")
|
||||
assert result.returncode == 0, result.stdout
|
||||
|
||||
|
||||
def test_artifact_scope_checks_angle_wrapped_destination_with_title(
|
||||
project: Path,
|
||||
) -> None:
|
||||
@@ -289,11 +298,12 @@ def test_artifact_scope_checks_reference_style_markdown_links(
|
||||
assert "missing-guide.md" in result.stdout
|
||||
|
||||
|
||||
@pytest.mark.parametrize("target_line", (" missing-guide.md", "missing-guide.md"))
|
||||
def test_artifact_scope_checks_continuation_line_reference_target(
|
||||
project: Path,
|
||||
project: Path, target_line: str
|
||||
) -> None:
|
||||
(project / "index.md").write_text(
|
||||
"[docs]\n\n[docs]:\n missing-guide.md\n", encoding="utf-8"
|
||||
f"[docs]\n\n[docs]:\n{target_line}\n", encoding="utf-8"
|
||||
)
|
||||
result = run_audit(project, "artifacts")
|
||||
assert result.returncode == 1
|
||||
|
||||
Reference in new issue
Block a user