✨ feat(tsl-syntax): add section lookup core
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import importlib.util
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPT = ROOT / "skills" / "tsl-syntax-reference" / "scripts" / "lookup.py"
|
||||
|
||||
spec = importlib.util.spec_from_file_location("tsl_syntax_lookup", SCRIPT)
|
||||
lookup = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(lookup)
|
||||
|
||||
|
||||
class TslSyntaxLookupTests(unittest.TestCase):
|
||||
def test_parser_creates_unique_h2_h3_section_ids(self):
|
||||
sections = lookup.load_sections(lookup.DEFAULT_REFERENCES_DIR)
|
||||
ids = [section.id for section in sections]
|
||||
self.assertEqual(len(ids), len(set(ids)))
|
||||
self.assertTrue(any("05_functions_and_calls" in value for value in ids))
|
||||
|
||||
def test_write_mode_includes_file_model_and_direct_example(self):
|
||||
result = lookup.query_sections("写函数 命名参数", "write", limit=4)
|
||||
rendered = lookup.render_result(result)
|
||||
self.assertIn("文件模型", rendered)
|
||||
self.assertIn("代码块身份:可直接照写示例", rendered)
|
||||
|
||||
def test_diagnose_mode_prioritizes_invalid_statement_pitfall(self):
|
||||
result = lookup.query_sections("invalid statement 声明区", "diagnose", limit=4)
|
||||
self.assertIn("11_pitfalls.md", result.matches[0].section.page.as_posix())
|
||||
|
||||
def test_explain_mode_does_not_force_write_prelude(self):
|
||||
result = lookup.query_sections("数组下标", "explain", limit=3)
|
||||
self.assertEqual(result.prelude, [])
|
||||
|
||||
def test_no_match_returns_exit_code_two(self):
|
||||
completed = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(SCRIPT),
|
||||
"--query",
|
||||
"不存在的孤立语法词xyz",
|
||||
"--mode",
|
||||
"explain",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual(completed.returncode, 2)
|
||||
|
||||
def test_check_rejects_unknown_code_block_identity(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
references = Path(tmp_dir)
|
||||
(references / "sample.md").write_text(
|
||||
"# Sample\n\n"
|
||||
"## Example\n\n"
|
||||
"代码块身份:未知身份\n\n"
|
||||
"```tsl\n"
|
||||
"return 1;\n"
|
||||
"```\n",
|
||||
encoding="utf-8",
|
||||
newline="\n",
|
||||
)
|
||||
|
||||
problems = lookup.validate_references(references)
|
||||
|
||||
self.assertTrue(
|
||||
any("未知身份" in problem.message for problem in problems), problems
|
||||
)
|
||||
|
||||
def test_check_accumulates_fence_identity_and_link_problems(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
references = Path(tmp_dir)
|
||||
(references / "sample.md").write_text(
|
||||
"# Sample\n\n"
|
||||
"## Example\n\n"
|
||||
"[missing](missing.md)\n\n"
|
||||
"代码块身份:输出片段\n"
|
||||
"代码块身份:可直接照写示例\n\n"
|
||||
"```tsl\n"
|
||||
"return 1;\n",
|
||||
encoding="utf-8",
|
||||
newline="\n",
|
||||
)
|
||||
|
||||
problems = lookup.validate_references(references)
|
||||
|
||||
messages = "\n".join(problem.message for problem in problems)
|
||||
self.assertIn("本地链接不存在", messages)
|
||||
self.assertIn("代码围栏未闭合", messages)
|
||||
self.assertIn("恰好一个代码块身份", messages)
|
||||
|
||||
def test_check_ignores_markdown_link_shapes_inside_inline_code(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
references = Path(tmp_dir)
|
||||
(references / "sample.md").write_text(
|
||||
"# Sample\n\n"
|
||||
"## Operators\n\n"
|
||||
"Use `function operator[](index);`.\n",
|
||||
encoding="utf-8",
|
||||
newline="\n",
|
||||
)
|
||||
|
||||
problems = lookup.validate_references(references)
|
||||
|
||||
self.assertEqual(problems, [])
|
||||
|
||||
def test_natural_chinese_query_finds_object_creation(self):
|
||||
result = lookup.query_sections("写一个类并创建对象", "write", limit=5)
|
||||
sources = "\n".join(match.section.id for match in result.matches)
|
||||
self.assertIn("08_objects_and_classes", sources)
|
||||
|
||||
def test_tsl_identifier_is_preserved(self):
|
||||
result = lookup.query_sections("varByRef 命名参数", "explain", limit=5)
|
||||
rendered = lookup.render_result(result)
|
||||
self.assertRegex(rendered, r"(?i)varByRef")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -36,7 +36,6 @@ TOPIC_REFERENCES = {
|
||||
"23_fmarray.md",
|
||||
"24_object_overloads_and_iteration.md",
|
||||
}
|
||||
EXPECTED_REFERENCES = {"index.md", *TOPIC_REFERENCES}
|
||||
MARKDOWN_LINK = re.compile(r"!?\[[^\]]*\]\(([^)]+)\)")
|
||||
|
||||
|
||||
@@ -83,9 +82,20 @@ class TslSyntaxReferenceSkillStructureTest(unittest.TestCase):
|
||||
self.assertRegex(description, r"(?i)\bTSF\b")
|
||||
self.assertRegex(description, r"写|编写|修改|审查|解释|语法错误")
|
||||
|
||||
def test_reference_inventory_is_exact(self) -> None:
|
||||
actual = {path.name for path in REFERENCES_DIR.iterdir()}
|
||||
self.assertEqual(actual, EXPECTED_REFERENCES)
|
||||
def test_reference_inventory_contains_topics_without_router(self) -> None:
|
||||
actual = {path.name for path in REFERENCES_DIR.iterdir() if path.is_file()}
|
||||
self.assertEqual(actual, TOPIC_REFERENCES)
|
||||
self.assertFalse((REFERENCES_DIR / "index.md").exists())
|
||||
|
||||
def test_lookup_script_is_bundled(self) -> None:
|
||||
self.assertTrue((SKILL_DIR / "scripts" / "lookup.py").is_file())
|
||||
|
||||
def test_manual_router_protocol_is_absent(self) -> None:
|
||||
corpus = read_text(SKILL_FILE) + "\n" + "\n".join(
|
||||
read_text(path) for path in REFERENCES_DIR.glob("*.md")
|
||||
)
|
||||
for forbidden in ("路由中心", "选择一个主专题", "候选页继续判断"):
|
||||
self.assertNotIn(forbidden, corpus)
|
||||
|
||||
def test_json_router_is_absent(self) -> None:
|
||||
self.assertFalse((REFERENCES_DIR / "00_agent_index.json").exists())
|
||||
|
||||
Reference in New Issue
Block a user