🐛 fix(tsl-syntax): index precise sections and enforce map integrity

This commit is contained in:
csh
2026-07-13 09:16:59 +08:00
parent 6728ed070e
commit 4a84dbae3a
2 changed files with 141 additions and 15 deletions
+77 -13
View File
@@ -125,8 +125,19 @@ def normalize(value: str) -> str:
return unicodedata.normalize("NFKC", value).casefold()
SYMBOL_SLUG_REPLACEMENTS = (
("**", " double-star "),
("[]", " index "),
("::", " double-colon "),
(":.", " colon-dot "),
("*", " star "),
)
def _slug(value: str) -> str:
normalized = normalize(value)
for symbol, replacement in SYMBOL_SLUG_REPLACEMENTS:
normalized = normalized.replace(symbol, replacement)
slug = re.sub(r"[^\w]+", "-", normalized, flags=re.UNICODE).strip("-_")
return slug or "section"
@@ -162,7 +173,7 @@ def _heading_records(lines: list[str]) -> tuple[str, list[tuple[int, int, str]]]
title = match.group(2)
if level == 1 and not page_title:
page_title = title
elif level in (2, 3):
elif level in (2, 3, 4):
records.append((index, level, title))
return page_title, records
@@ -206,18 +217,21 @@ def _identities(body: str) -> tuple[str, ...]:
def load_sections(references_dir: Path = DEFAULT_REFERENCES_DIR) -> list[Section]:
sections: list[Section] = []
id_counts: dict[str, int] = {}
for page in _reference_pages(Path(references_dir)):
text = page.read_text(encoding="utf-8")
lines = text.splitlines(keepends=True)
page_title, headings = _heading_records(lines)
h2_title = ""
heading_stack: dict[int, str] = {}
for position, (start, level, title) in enumerate(headings):
if level == 2:
h2_title = title
heading_path = (title,)
else:
heading_path = (h2_title, title) if h2_title else (title,)
for stacked_level in tuple(heading_stack):
if stacked_level >= level:
del heading_stack[stacked_level]
heading_stack[level] = title
heading_path = tuple(
heading_stack[item]
for item in range(2, level + 1)
if item in heading_stack
)
end = len(lines)
for next_start, next_level, _ in headings[position + 1 :]:
if next_level <= level:
@@ -225,16 +239,12 @@ def load_sections(references_dir: Path = DEFAULT_REFERENCES_DIR) -> list[Section
break
body = "".join(lines[start:end])
base_id = section_id(page.relative_to(references_dir), heading_path)
id_counts[base_id] = id_counts.get(base_id, 0) + 1
unique_id = base_id
if id_counts[base_id] > 1:
unique_id = f"{base_id}-{id_counts[base_id]}"
searchable_text = normalize(
"\n".join((page.stem, page_title, *heading_path, body))
)
sections.append(
Section(
id=unique_id,
id=base_id,
page=page,
page_title=page_title,
heading_path=heading_path,
@@ -325,6 +335,50 @@ def validate_references(
lines = text.splitlines()
problems.extend(_identity_problems(page, lines))
problems.extend(_local_link_problems(page, text, references_dir))
page_title, headings = _heading_records(text.splitlines(keepends=True))
h1_count = 0
in_fence = False
previous_level = 1
for index, line in enumerate(lines):
if FENCE_RE.match(line):
in_fence = not in_fence
continue
if in_fence:
continue
match = HEADING_RE.match(line)
if match is None:
continue
level = len(match.group(1))
if level == 1:
h1_count += 1
previous_level = 1
continue
if level not in (2, 3, 4):
continue
if level > previous_level + 1:
problems.append(
ValidationProblem(page, index + 1, "H2/H3/H4 标题层级跳跃")
)
previous_level = level
if h1_count != 1 or not page_title:
problems.append(ValidationProblem(page, 1, "每页必须有且仅有一个非空 H1"))
duty_sections = []
for position, (start, level, title) in enumerate(headings):
if level != 2 or title != DUTY_HEADING:
continue
end = len(lines)
for next_start, next_level, _ in headings[position + 1 :]:
if next_level <= level:
end = next_start
break
body = "\n".join(lines[start + 1 : end]).strip()
duty_sections.append(body)
if len(duty_sections) != 1 or not duty_sections[0]:
problems.append(
ValidationProblem(
page, 1, "每页必须有且仅有一个非空「本篇职责」"
)
)
for phrase in ROUTER_PHRASES:
for index, line in enumerate(lines, start=1):
if phrase in line:
@@ -356,6 +410,16 @@ def validate_references(
# 概念地图逐页从「本篇职责」段生成;有该段的页必须产出非空摘要,
# 否则某页职责段被清空/写坏时地图会静默缺页。
mapped_pages = {page_name for page_name, _, _ in build_concept_map(references_dir)}
reference_pages = {page.name for page in _reference_pages(references_dir)}
if mapped_pages != reference_pages:
missing = ", ".join(sorted(reference_pages - mapped_pages)) or "none"
problems.append(
ValidationProblem(
references_dir,
1,
f"概念地图页数与参考页不一致;缺失:{missing}",
)
)
for section in sections:
if section.heading_path != (DUTY_HEADING,):
continue