🐛 fix(tsl-syntax-reference): route derived rules and bound section size

This commit is contained in:
csh
2026-08-20 15:49:41 +08:00
parent ca6830af9f
commit 5a8d95ebcf
7 changed files with 420 additions and 145 deletions
+144 -3
View File
@@ -1,4 +1,5 @@
import importlib.util
import re
import subprocess
import sys
import tempfile
@@ -275,6 +276,104 @@ class TslSyntaxReferenceTests(unittest.TestCase):
self.assertTrue(any("assignment" in message for message in messages))
def test_quickstart_owner_drift_fails_structure_check(self):
with tempfile.TemporaryDirectory() as temp_dir:
references = Path(temp_dir)
write_reference(
references,
"01_quickstart.md",
"""
# Quickstart
## 本篇职责
<!-- section-id: syntax-01-001 -->
派生摘要。
## 语言核心事实速查
<!-- section-id: syntax-01-002 -->
<!-- quickstart-rule: assignment -->
- 普通赋值使用 `:=`。
Owner Section`syntax-02-999`
""",
)
write_reference(
references,
"02_topic.md",
"""
# Topic
## 本篇职责
<!-- section-id: syntax-02-001 -->
完整事实源。
## 核心规则
<!-- section-id: syntax-02-002 -->
<!-- quickstart-rule: assignment -->
- 普通赋值使用 `:=`。
""",
)
messages = [item.message for item in lookup.validate_references(references)]
self.assertTrue(any("Owner Section 不存在" in message for message in messages))
def test_quickstart_routes_every_derived_rule_to_its_owner_section(self):
quickstart = run_lookup("--section", "syntax-01-002")
self.assertEqual(0, quickstart.returncode, quickstart.stderr)
owner_ids = set(
re.findall(r"Owner Section`(syntax-\d{2}-\d{3})`", quickstart.stdout)
)
self.assertEqual(
{
"syntax-02-002",
"syntax-03-002",
"syntax-03-004",
"syntax-05-002",
"syntax-06-004",
"syntax-08-002",
"syntax-09-002",
},
owner_ids,
)
for owner_id in owner_ids:
with self.subTest(owner_id=owner_id):
owner = run_lookup("--section", owner_id)
self.assertEqual(0, owner.returncode, owner.stderr)
def test_oversized_leaf_section_fails_structure_check(self):
with tempfile.TemporaryDirectory() as temp_dir:
references = Path(temp_dir)
long_body = "\n".join(f"事实行 {index}" for index in range(181))
write_reference(
references,
"99_fixture.md",
"# Fixture\n\n"
"## 本篇职责\n\n"
"<!-- section-id: syntax-99-001 -->\n\n"
"测试职责。\n\n"
"## 过长事实段\n\n"
"<!-- section-id: syntax-99-002 -->\n\n"
f"{long_body}\n",
)
result = run_lookup(
"--check",
"--references-dir",
str(references),
)
self.assertEqual(1, result.returncode)
self.assertIn("叶子 Section 正文超过 180 行", result.stderr)
def test_missing_references_are_installation_errors_for_every_action(self):
with tempfile.TemporaryDirectory() as temp_dir:
missing = Path(temp_dir) / "missing"
@@ -317,6 +416,25 @@ class TslSyntaxReferenceTests(unittest.TestCase):
self.assertEqual("syntax-05-008", write_result.matches[0].section.id)
self.assertEqual("syntax-02-006", diagnose_result.matches[0].section.id)
def test_object_and_class_queries_return_focused_sections(self):
cases = {
"成员访问可见性": "syntax-08-013",
"类外实现": "syntax-08-014",
"固定索引 property": "syntax-08-015",
"参数化 property": "syntax-08-016",
"方法隐藏 hide": "syntax-08-017",
"调用父类 inherited": "syntax-08-018",
"析构 destroy": "syntax-08-019",
}
for query, expected_id in cases.items():
with self.subTest(query=query):
result = lookup.query_sections(query, "explain", limit=1)
self.assertTrue(result.matches, query)
self.assertEqual(expected_id, result.matches[0].section.id)
section = run_lookup("--section", expected_id)
self.assertEqual(0, section.returncode, section.stderr)
self.assertLessEqual(len(section.stdout.splitlines()), 186)
def test_external_call_queries_retrieve_platform_and_abi_boundaries(self):
cases = {
"动态库常驻": "syntax-17-012",
@@ -440,17 +558,40 @@ class TslSyntaxReferenceTests(unittest.TestCase):
def test_skill_contract_uses_extracted_queries_and_deliverable_api_checks(self):
skill = SKILL_PATH.read_text(encoding="utf-8")
help_text = lookup._parser().format_help()
help_result = run_lookup("--help")
self.assertEqual(0, help_result.returncode, help_result.stderr)
help_text = help_result.stdout
self.assertIn("保留这些词在用户原话中", skill)
self.assertIn("不传完整句", skill)
self.assertNotIn("## 构造查询词", skill)
self.assertNotIn("用户怎么说就怎么传", skill)
self.assertIn("从用户原话提取", help_text)
self.assertIn("保留原写法但不传完整用户句", help_text)
self.assertIn("不传完整用户句", help_text)
self.assertIn("面向用户交付的 TSL/TSF 代码", skill)
self.assertIn("每个 builtin/API", skill)
self.assertIn("纯语法说明", skill)
self.assertIn("不得声称该占位调用的 API 行为或输出", skill)
def test_help_owns_cli_details_and_skill_defers_to_it(self):
skill = SKILL_PATH.read_text(encoding="utf-8")
result = run_lookup("--help")
self.assertEqual(0, result.returncode, result.stderr)
for text in (
"一次 --query 只覆盖一个语法要素",
"Required: yes",
"Owner Section",
"混合候选",
"180 行粒度上限",
"退出码",
):
with self.subTest(text=text):
self.assertIn(text, result.stdout)
self.assertIn("构造任一命令前先运行", skill)
self.assertIn("scripts/lookup.py --help", skill)
self.assertIn("不用于查询 API 签名", skill)
self.assertIn("不用于选择解释器或运行方式", skill)
def test_ci_runs_syntax_structure_and_format_gates(self):
workflow = CI_PATH.read_text(encoding="utf-8")
prepare = PREPARE_PATH.read_text(encoding="utf-8")