🐛 fix(tsl-syntax): close lookup review gaps
This commit is contained in:
@@ -72,6 +72,61 @@ class TslSyntaxLookupTests(unittest.TestCase):
|
||||
result = lookup.query_sections("数据库左连接", "write")
|
||||
|
||||
self.assertEqual(result.matches[0].section.page.name, "14_ts_sql.md")
|
||||
self.assertTrue(
|
||||
any(reason.startswith("synonym=") for reason in result.matches[0].reasons)
|
||||
)
|
||||
|
||||
def test_synonym_reason_only_appears_when_expansion_matches_candidate(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
references = Path(tmp_dir)
|
||||
(references / "sample.md").write_text(
|
||||
"# Sample\n\n"
|
||||
"## 本篇职责\n\n"
|
||||
"Synthetic synonym provenance.\n\n"
|
||||
"## Exact\n\n"
|
||||
"needle appears here without the expanded collection term.\n",
|
||||
encoding="utf-8",
|
||||
newline="\n",
|
||||
)
|
||||
|
||||
result = lookup.query_sections(
|
||||
"列表 needle", "explain", references_dir=references
|
||||
)
|
||||
|
||||
exact = next(
|
||||
match for match in result.matches if match.section.heading_path == ("Exact",)
|
||||
)
|
||||
self.assertFalse(
|
||||
any(reason.startswith("synonym=") for reason in exact.reasons),
|
||||
exact.reasons,
|
||||
)
|
||||
|
||||
def test_ascii_synonym_and_intent_triggers_require_token_boundaries(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
references = Path(tmp_dir)
|
||||
(references / "15_debug_and_profiler.md").write_text(
|
||||
"# Debug Probe\n\n"
|
||||
"## 本篇职责\n\n"
|
||||
"Synthetic boundary probe.\n\n"
|
||||
"## Probe\n\n"
|
||||
"probe body.\n",
|
||||
encoding="utf-8",
|
||||
newline="\n",
|
||||
)
|
||||
|
||||
result = lookup.query_sections(
|
||||
"debugger programmer probe",
|
||||
"explain",
|
||||
references_dir=references,
|
||||
)
|
||||
|
||||
probe = next(
|
||||
match for match in result.matches if match.section.heading_path == ("Probe",)
|
||||
)
|
||||
self.assertFalse(
|
||||
any(reason.startswith(("synonym=", "intent=")) for reason in probe.reasons),
|
||||
probe.reasons,
|
||||
)
|
||||
|
||||
def test_query_renders_compact_candidates_without_bodies_or_absolute_paths(self):
|
||||
result = lookup.query_sections("函数 默认参数", "write", limit=5)
|
||||
@@ -81,6 +136,8 @@ class TslSyntaxLookupTests(unittest.TestCase):
|
||||
self.assertIn("## Candidate 1", rendered)
|
||||
self.assertNotIn("```tsl", rendered)
|
||||
self.assertNotIn(str(lookup.DEFAULT_REFERENCES_DIR.resolve()), rendered)
|
||||
self.assertNotIn("Why: `rank=", rendered)
|
||||
self.assertRegex(rendered, r"Why: `[^`]*(intent|heading|identifier|body)=\d+")
|
||||
self.assertLess(len(rendered.encode("utf-8")), 8192)
|
||||
|
||||
def test_query_echo_is_json_encoded_and_cannot_inject_markdown(self):
|
||||
@@ -96,6 +153,31 @@ class TslSyntaxLookupTests(unittest.TestCase):
|
||||
self.assertEqual(rendered.count("## Match 999"), 1)
|
||||
self.assertNotIn("\n## Match 999\n", rendered)
|
||||
|
||||
def test_query_echo_escapes_all_unicode_line_separators(self):
|
||||
for separator in ("\u0085", "\u2028", "\u2029"):
|
||||
with self.subTest(separator=hex(ord(separator))):
|
||||
query = f"数组{separator}## Candidate 999"
|
||||
rendered = lookup.render_candidates(
|
||||
lookup.query_sections(query, "explain")
|
||||
)
|
||||
|
||||
self.assertNotIn(separator, rendered)
|
||||
self.assertIn(f"\\u{ord(separator):04x}", rendered)
|
||||
self.assertNotIn("\n## Candidate 999\n", rendered)
|
||||
|
||||
def test_mixed_language_paraphrases_ignore_unknown_ascii_fillers(self):
|
||||
cases = (
|
||||
("please 帮我写个最简单能跑的天软脚本", "write", "01_quickstart.md"),
|
||||
("TSL 中两个表如何做左外连接并聚合排序", "write", "14_ts_sql.md"),
|
||||
("debug 一下程序的性能问题", "diagnose", "15_debug_and_profiler.md"),
|
||||
("请写一个可以运行的最小 Tinysoft program", "write", "01_quickstart.md"),
|
||||
)
|
||||
for query, mode, expected_page in cases:
|
||||
with self.subTest(query=query):
|
||||
result = lookup.query_sections(query, mode)
|
||||
self.assertTrue(result.matches, query)
|
||||
self.assertEqual(result.matches[0].section.page.name, expected_page)
|
||||
|
||||
def test_section_is_only_cli_path_that_returns_body(self):
|
||||
result = lookup.query_sections("基础函数", "write", limit=5)
|
||||
section = result.matches[0].section
|
||||
@@ -124,6 +206,61 @@ class TslSyntaxLookupTests(unittest.TestCase):
|
||||
self.assertNotIn(section.body.rstrip(), query_completed.stdout)
|
||||
self.assertIn(section.body.rstrip(), section_completed.stdout)
|
||||
|
||||
def test_body_only_match_prefers_deepest_section(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
references = Path(tmp_dir)
|
||||
(references / "sample.md").write_text(
|
||||
"# Sample\n\n"
|
||||
"## 本篇职责\n\n"
|
||||
"Synthetic hierarchy.\n\n"
|
||||
"## Parent\n\n"
|
||||
"Parent introduction.\n\n"
|
||||
"### Child\n\n"
|
||||
"Child introduction.\n\n"
|
||||
"#### Exact Leaf\n\n"
|
||||
"needle appears only in the leaf body.\n",
|
||||
encoding="utf-8",
|
||||
newline="\n",
|
||||
)
|
||||
|
||||
result = lookup.query_sections(
|
||||
"needle", "explain", references_dir=references
|
||||
)
|
||||
|
||||
self.assertEqual(result.matches[0].section.heading_path[-1], "Exact Leaf")
|
||||
|
||||
def test_limit_applies_to_final_rendered_candidate_list(self):
|
||||
for limit in (1, 2, 5):
|
||||
with self.subTest(limit=limit):
|
||||
result = lookup.query_sections("函数 默认参数", "write", limit=limit)
|
||||
rendered = lookup.render_candidates(result)
|
||||
self.assertEqual(rendered.count("## Candidate "), limit)
|
||||
|
||||
def test_required_context_stays_required_when_query_also_matches_it(self):
|
||||
result = lookup.query_sections("语言核心事实速查", "write", limit=5)
|
||||
rendered = lookup.render_candidates(result)
|
||||
required_id = "01_quickstart--语言核心事实速查"
|
||||
|
||||
self.assertIn(
|
||||
f"Required: yes\nSection ID: `{required_id}`",
|
||||
rendered,
|
||||
)
|
||||
self.assertEqual(rendered.count(f"Section ID: `{required_id}`"), 1)
|
||||
|
||||
def test_displayed_scores_are_sorted_descending(self):
|
||||
for query, mode in (
|
||||
("函数 默认参数", "write"),
|
||||
("变量怎么改", "write"),
|
||||
("数组", "diagnose"),
|
||||
("invalid statement 声明区", "diagnose"),
|
||||
("程序慢怎么计时找瓶颈", "diagnose"),
|
||||
):
|
||||
with self.subTest(query=query, mode=mode):
|
||||
result = lookup.query_sections(query, mode, limit=5)
|
||||
scores = [match.score for match in result.matches]
|
||||
|
||||
self.assertEqual(scores, sorted(scores, reverse=True))
|
||||
|
||||
def test_parser_creates_unique_h2_h3_h4_section_ids(self):
|
||||
sections = lookup.load_sections(lookup.DEFAULT_REFERENCES_DIR)
|
||||
ids = [section.id for section in sections]
|
||||
@@ -143,6 +280,14 @@ class TslSyntaxLookupTests(unittest.TestCase):
|
||||
self.assertNotEqual(star, double_star)
|
||||
self.assertNotRegex(double_star, r"-2$")
|
||||
|
||||
def test_all_promised_symbolic_heading_slugs_are_distinct(self):
|
||||
ids = {
|
||||
lookup.section_id("sample.md", ("Examples", heading))
|
||||
for heading in ("with *", "with **", "operator[]", "walk ::", "walk :.")
|
||||
}
|
||||
|
||||
self.assertEqual(len(ids), 5)
|
||||
|
||||
def test_check_requires_one_nonempty_duty_section_per_page(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
references = Path(tmp_dir)
|
||||
@@ -206,6 +351,7 @@ class TslSyntaxLookupTests(unittest.TestCase):
|
||||
self.assertEqual(result.prelude, [])
|
||||
|
||||
def test_no_match_returns_exit_code_two_in_every_mode(self):
|
||||
query = "夔魍魉xyzqv"
|
||||
for mode in ("write", "diagnose", "explain"):
|
||||
with self.subTest(mode=mode):
|
||||
completed = subprocess.run(
|
||||
@@ -213,7 +359,7 @@ class TslSyntaxLookupTests(unittest.TestCase):
|
||||
sys.executable,
|
||||
str(SCRIPT),
|
||||
"--query",
|
||||
"不存在的孤立语法词xyz",
|
||||
query,
|
||||
"--mode",
|
||||
mode,
|
||||
],
|
||||
@@ -222,6 +368,9 @@ class TslSyntaxLookupTests(unittest.TestCase):
|
||||
encoding="utf-8",
|
||||
)
|
||||
self.assertEqual(completed.returncode, 2)
|
||||
self.assertIn("# TSL Syntax Candidates", completed.stdout)
|
||||
self.assertIn(f'Query: "{query}"', completed.stdout)
|
||||
self.assertIn("no matching sections", completed.stderr)
|
||||
|
||||
def test_missing_section_returns_nearest_section_ids(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
|
||||
Reference in New Issue
Block a user