Flatten builtin pages, add third-party and platform scopes, and import financial and data warehouse references. Keep the existing generated index without rebuilding after signature normalization.
805 lines
25 KiB
Python
805 lines
25 KiB
Python
import io
|
|
import importlib.util
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from contextlib import redirect_stdout
|
|
from pathlib import Path
|
|
|
|
|
|
SCRIPT = (
|
|
Path(__file__).resolve().parents[3]
|
|
/ "skills"
|
|
/ "tsl-api-reference"
|
|
/ "scripts"
|
|
/ "lookup.py"
|
|
)
|
|
SKILL_MD = SCRIPT.parents[1] / "SKILL.md"
|
|
|
|
|
|
def load_script():
|
|
spec = importlib.util.spec_from_file_location("tsl_lookup", SCRIPT)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
class LookupTest(unittest.TestCase):
|
|
def write_unified_api_fixture(self, skill_root):
|
|
data_dir = skill_root / "data"
|
|
codegen_dir = skill_root / "references" / "codegen"
|
|
data_dir.mkdir()
|
|
(codegen_dir / "project").mkdir(parents=True)
|
|
|
|
header = [
|
|
"name",
|
|
"scope",
|
|
"module",
|
|
"signature",
|
|
"page",
|
|
"anchor",
|
|
"tags",
|
|
"summary",
|
|
"kind",
|
|
"binding",
|
|
"visibility",
|
|
"owner",
|
|
"qualified_name",
|
|
]
|
|
|
|
def row(
|
|
name,
|
|
signature,
|
|
page,
|
|
anchor,
|
|
summary,
|
|
kind,
|
|
binding="",
|
|
visibility="",
|
|
owner="",
|
|
qualified_name="",
|
|
):
|
|
return [
|
|
name,
|
|
"project",
|
|
"demo",
|
|
signature,
|
|
page,
|
|
anchor,
|
|
"",
|
|
summary,
|
|
kind,
|
|
binding,
|
|
visibility,
|
|
owner,
|
|
qualified_name or name,
|
|
]
|
|
|
|
rows = [
|
|
row(
|
|
"DemoUnit",
|
|
"DemoUnit",
|
|
"project/demo_unit.md",
|
|
"demounit",
|
|
"接口。",
|
|
"unit",
|
|
),
|
|
row(
|
|
"Open",
|
|
"Open()",
|
|
"project/demo_unit.md",
|
|
"open",
|
|
"打开。",
|
|
"function",
|
|
"unit",
|
|
"public",
|
|
"DemoUnit",
|
|
"DemoUnit.Open",
|
|
),
|
|
row(
|
|
"Document",
|
|
"Document",
|
|
"project/demo_unit.md",
|
|
"document",
|
|
"文档。",
|
|
"class",
|
|
"unit",
|
|
"public",
|
|
"DemoUnit",
|
|
"DemoUnit.Document",
|
|
),
|
|
row(
|
|
"Save",
|
|
"Save(path)",
|
|
"project/demo_unit.md",
|
|
"savepath",
|
|
"按路径保存。",
|
|
"method",
|
|
"instance",
|
|
"public",
|
|
"DemoUnit.Document",
|
|
"DemoUnit.Document.Save",
|
|
),
|
|
row(
|
|
"Save",
|
|
"Save(mode)",
|
|
"project/demo_unit.md",
|
|
"savemode",
|
|
"按模式保存。",
|
|
"method",
|
|
"instance",
|
|
"protected",
|
|
"DemoUnit.Document",
|
|
"DemoUnit.Document.Save",
|
|
),
|
|
row(
|
|
"Name",
|
|
"Name",
|
|
"project/demo_unit.md",
|
|
"name",
|
|
"文档名称。",
|
|
"field",
|
|
"instance",
|
|
"public",
|
|
"DemoUnit.Document",
|
|
"DemoUnit.Document.Name",
|
|
),
|
|
row(
|
|
"Other",
|
|
"Other",
|
|
"project/demo_unit.md",
|
|
"other",
|
|
"其他对象。",
|
|
"class",
|
|
"unit",
|
|
"public",
|
|
"DemoUnit",
|
|
"DemoUnit.Other",
|
|
),
|
|
row(
|
|
"Save",
|
|
"Save()",
|
|
"project/demo_unit.md",
|
|
"save",
|
|
"保存其他对象。",
|
|
"method",
|
|
"instance",
|
|
"public",
|
|
"DemoUnit.Other",
|
|
"DemoUnit.Other.Save",
|
|
),
|
|
row(
|
|
"Widget",
|
|
"Widget",
|
|
"project/widget.md",
|
|
"widget",
|
|
"组件。",
|
|
"class",
|
|
),
|
|
row(
|
|
"Create",
|
|
"Create()",
|
|
"project/widget.md",
|
|
"create",
|
|
"创建组件。",
|
|
"method",
|
|
"class",
|
|
"public",
|
|
"Widget",
|
|
"Widget.Create",
|
|
),
|
|
row(
|
|
"Count",
|
|
"Count",
|
|
"project/widget.md",
|
|
"count",
|
|
"组件数量。",
|
|
"field",
|
|
"static",
|
|
"protected",
|
|
"Widget",
|
|
"Widget.Count",
|
|
),
|
|
]
|
|
index_text = "\n".join(
|
|
["\t".join(header), *("\t".join(values) for values in rows)]
|
|
)
|
|
tsv_path = data_dir / "function_index.tsv"
|
|
tsv_path.write_text(index_text + "\n", encoding="utf-8")
|
|
|
|
(codegen_dir / "project" / "demo_unit.md").write_text(
|
|
"# Demo / Unit\n\n"
|
|
"## `DemoUnit`\n\n"
|
|
"声明:unit\n\n"
|
|
"接口。\n\n"
|
|
"### `Open()`\n\n"
|
|
"声明:function\n\n"
|
|
"打开。\n\n"
|
|
"返回:Document\n\n"
|
|
"### `Document`\n\n"
|
|
"声明:class\n\n"
|
|
"文档。\n\n"
|
|
"#### `Save(path)`\n\n"
|
|
"声明:function\n\n"
|
|
"按路径保存。\n\n"
|
|
"可见性:`public`\n\n"
|
|
"#### `Save(mode)`\n\n"
|
|
"声明:function\n\n"
|
|
"按模式保存。\n\n"
|
|
"可见性:`protected`\n\n"
|
|
"#### `Name`\n\n"
|
|
"声明:field\n\n"
|
|
"文档名称。\n\n"
|
|
"可见性:`public`\n\n"
|
|
"类型:string\n\n"
|
|
"### `Other`\n\n"
|
|
"声明:class\n\n"
|
|
"其他对象。\n\n"
|
|
"#### `Save()`\n\n"
|
|
"声明:function\n\n"
|
|
"保存其他对象。\n\n"
|
|
"可见性:`public`\n",
|
|
encoding="utf-8",
|
|
)
|
|
(codegen_dir / "project" / "widget.md").write_text(
|
|
"# Demo / Class\n\n"
|
|
"## `Widget`\n\n"
|
|
"声明:class\n\n"
|
|
"组件。\n\n"
|
|
"### `Create()`\n\n"
|
|
"声明:class function\n\n"
|
|
"创建组件。\n\n"
|
|
"可见性:`public`\n\n"
|
|
"### `Count`\n\n"
|
|
"声明:static field\n\n"
|
|
"组件数量。\n\n"
|
|
"可见性:`protected`\n\n"
|
|
"类型:integer\n",
|
|
encoding="utf-8",
|
|
)
|
|
return tsv_path
|
|
|
|
def write_mixed_api_fixture(self, skill_root):
|
|
data_dir = skill_root / "data"
|
|
codegen_dir = skill_root / "references" / "codegen" / "project"
|
|
data_dir.mkdir()
|
|
codegen_dir.mkdir(parents=True)
|
|
header = [
|
|
"name",
|
|
"scope",
|
|
"module",
|
|
"signature",
|
|
"page",
|
|
"anchor",
|
|
"tags",
|
|
"summary",
|
|
"kind",
|
|
"binding",
|
|
"visibility",
|
|
"owner",
|
|
"qualified_name",
|
|
]
|
|
page = "project/mixed.md"
|
|
rows = [
|
|
[
|
|
"Widget",
|
|
"project",
|
|
"mixed",
|
|
"Widget",
|
|
page,
|
|
"widget",
|
|
"",
|
|
"组件。",
|
|
"class",
|
|
"",
|
|
"",
|
|
"",
|
|
"Widget",
|
|
],
|
|
[
|
|
"Open",
|
|
"project",
|
|
"mixed",
|
|
"Open()",
|
|
page,
|
|
"open",
|
|
"",
|
|
"打开。",
|
|
"method",
|
|
"instance",
|
|
"public",
|
|
"Widget",
|
|
"Widget.Open",
|
|
],
|
|
[
|
|
"Parse",
|
|
"project",
|
|
"mixed",
|
|
"Parse(text)",
|
|
page,
|
|
"parse",
|
|
"",
|
|
"解析。",
|
|
"function",
|
|
"",
|
|
"",
|
|
"",
|
|
"Parse",
|
|
],
|
|
[
|
|
"Runtime",
|
|
"project",
|
|
"mixed",
|
|
"Runtime",
|
|
page,
|
|
"runtime",
|
|
"",
|
|
"接口。",
|
|
"unit",
|
|
"",
|
|
"",
|
|
"",
|
|
"Runtime",
|
|
],
|
|
[
|
|
"Document",
|
|
"project",
|
|
"mixed",
|
|
"Document",
|
|
page,
|
|
"document",
|
|
"",
|
|
"文档。",
|
|
"class",
|
|
"unit",
|
|
"public",
|
|
"Runtime",
|
|
"Runtime.Document",
|
|
],
|
|
[
|
|
"Save",
|
|
"project",
|
|
"mixed",
|
|
"Save()",
|
|
page,
|
|
"save",
|
|
"",
|
|
"保存。",
|
|
"method",
|
|
"instance",
|
|
"public",
|
|
"Runtime.Document",
|
|
"Runtime.Document.Save",
|
|
],
|
|
[
|
|
"Other",
|
|
"project",
|
|
"mixed",
|
|
"Other",
|
|
page,
|
|
"other",
|
|
"",
|
|
"其他组件。",
|
|
"class",
|
|
"",
|
|
"",
|
|
"",
|
|
"Other",
|
|
],
|
|
]
|
|
tsv_path = data_dir / "function_index.tsv"
|
|
tsv_path.write_text(
|
|
"\n".join(
|
|
["\t".join(header), *("\t".join(row) for row in rows)]
|
|
)
|
|
+ "\n",
|
|
encoding="utf-8",
|
|
)
|
|
(codegen_dir / "mixed.md").write_text(
|
|
"# Mixed\n\n"
|
|
"## `Widget`\n\n声明:class\n\n组件。\n\n"
|
|
"### `Open()`\n\n声明:function\n\n打开。\n\n可见性:public\n\n"
|
|
"## `Parse(text)`\n\n声明:function\n\n解析。\n\n"
|
|
"| 参数 | 类型 | 说明 |\n"
|
|
"| --- | --- | --- |\n"
|
|
"| `text` | string | 文本 |\n\n"
|
|
"返回:Widget\n\n"
|
|
"### 示例\n\n"
|
|
"```markdown\n## `NotBoundary`\n```\n\n"
|
|
"## `Runtime`\n\n声明:unit\n\n接口。\n\n"
|
|
"### `Document`\n\n声明:class\n\n文档。\n\n"
|
|
"#### `Save()`\n\n声明:function\n\n保存。\n\n可见性:public\n\n"
|
|
"## `Other`\n\n声明:class\n\n其他组件。\n",
|
|
encoding="utf-8",
|
|
)
|
|
return tsv_path
|
|
|
|
def run_lookup(self, tsv_path, *args):
|
|
return subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
*args,
|
|
"--tsv",
|
|
str(tsv_path),
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
check=False,
|
|
)
|
|
|
|
def test_tsv_without_tags_is_rejected(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
tsv_path = Path(temp_dir) / "function_index.tsv"
|
|
tsv_path.write_text(
|
|
"name\tscope\tmodule\tsignature\tpage\tanchor\tsummary\n"
|
|
"foo\tbuiltin\tmisc\tfoo()\tmisc.md\tfoo\tLegacy entry\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
"--kw",
|
|
"Legacy",
|
|
"--tsv",
|
|
str(tsv_path),
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
self.assertEqual(1, result.returncode)
|
|
self.assertEqual("", result.stdout)
|
|
self.assertIn("missing required columns: tags", result.stderr)
|
|
self.assertNotIn("Traceback", result.stderr)
|
|
|
|
def test_missing_required_tsv_columns_are_rejected(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
tsv_path = Path(temp_dir) / "function_index.tsv"
|
|
tsv_path.write_text("name\nfoo\n", encoding="utf-8")
|
|
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
"--name",
|
|
"foo",
|
|
"--tsv",
|
|
str(tsv_path),
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
self.assertEqual(1, result.returncode)
|
|
self.assertIn("missing required columns", result.stderr)
|
|
self.assertNotIn("Traceback", result.stderr)
|
|
|
|
def test_tsv_row_width_is_validated(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
tsv_path = Path(temp_dir) / "function_index.tsv"
|
|
tsv_path.write_text(
|
|
"name\tscope\tmodule\tsignature\tpage\tanchor\ttags\tsummary\n"
|
|
"foo\tbuiltin\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
"--name",
|
|
"foo",
|
|
"--tsv",
|
|
str(tsv_path),
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
self.assertEqual(1, result.returncode)
|
|
self.assertIn("line 2 has 2 columns; expected 8", result.stderr)
|
|
self.assertNotIn("Traceback", result.stderr)
|
|
|
|
def test_missing_entry_body_is_rejected(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
skill_root = Path(temp_dir)
|
|
data_dir = skill_root / "data"
|
|
codegen_dir = skill_root / "references" / "codegen"
|
|
data_dir.mkdir()
|
|
codegen_dir.mkdir(parents=True)
|
|
(data_dir / "function_index.tsv").write_text(
|
|
"name\tscope\tmodule\tsignature\tpage\tanchor\ttags\tsummary\n"
|
|
"foo\tbuiltin\tmisc\tfoo()\tmissing.md\tfoo\t\tFallback only\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
"--name",
|
|
"foo",
|
|
"--tsv",
|
|
str(data_dir / "function_index.tsv"),
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
self.assertEqual(1, result.returncode)
|
|
self.assertEqual("", result.stdout)
|
|
self.assertIn("entry body not found", result.stderr)
|
|
self.assertIn("missing.md#foo", result.stderr)
|
|
|
|
def test_empty_queries_are_rejected(self):
|
|
for option in ("--name", "--kw"):
|
|
with self.subTest(option=option):
|
|
result = subprocess.run(
|
|
[sys.executable, str(SCRIPT), option, ""],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
self.assertEqual(2, result.returncode)
|
|
self.assertIn("must not be empty", result.stderr)
|
|
|
|
def test_non_positive_limit_is_rejected(self):
|
|
for limit in ("0", "-1"):
|
|
with self.subTest(limit=limit):
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
"--kw",
|
|
"数组",
|
|
"--limit",
|
|
limit,
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
self.assertEqual(2, result.returncode)
|
|
self.assertIn("--limit must be >= 1", result.stderr)
|
|
|
|
def test_help_lists_all_bundled_scopes(self):
|
|
result = subprocess.run(
|
|
[sys.executable, str(SCRIPT), "--help"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertIn("builtin、dotnet、third 与 deprecated", result.stdout)
|
|
|
|
def test_exact_search_uses_anchor_for_duplicate_signatures(self):
|
|
module = load_script()
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
skill_root = Path(temp_dir)
|
|
data_dir = skill_root / "data"
|
|
codegen_dir = skill_root / "references" / "codegen"
|
|
data_dir.mkdir()
|
|
codegen_dir.mkdir(parents=True)
|
|
|
|
header = [
|
|
"name",
|
|
"scope",
|
|
"module",
|
|
"signature",
|
|
"page",
|
|
"anchor",
|
|
"tags",
|
|
"summary",
|
|
]
|
|
rows = [
|
|
[
|
|
"CAPA",
|
|
"builtin",
|
|
"language",
|
|
"CAPA()",
|
|
"objects.md",
|
|
"capa",
|
|
"",
|
|
"IMAP summary",
|
|
],
|
|
[
|
|
"CAPA",
|
|
"builtin",
|
|
"language",
|
|
"CAPA()",
|
|
"objects.md",
|
|
"capa-1",
|
|
"",
|
|
"Pop3 summary",
|
|
],
|
|
]
|
|
index_text = "\n".join(
|
|
["\t".join(header), *("\t".join(row) for row in rows)]
|
|
)
|
|
(data_dir / "function_index.tsv").write_text(
|
|
index_text + "\n", encoding="utf-8"
|
|
)
|
|
(codegen_dir / "objects.md").write_text(
|
|
"# Objects\n\n"
|
|
"## `CAPA()`\n\n"
|
|
"声明:function\n\n"
|
|
"对象:IMAP\n\n"
|
|
"## `CAPA()`\n\n"
|
|
"声明:function\n\n"
|
|
"对象:Pop3\n\n"
|
|
"",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
stdout = io.StringIO()
|
|
with redirect_stdout(stdout):
|
|
status = module.main(
|
|
[
|
|
"--name",
|
|
"CAPA",
|
|
"--tsv",
|
|
str(data_dir / "function_index.tsv"),
|
|
]
|
|
)
|
|
|
|
output = stdout.getvalue()
|
|
self.assertEqual(0, status)
|
|
self.assertEqual(1, output.count("对象:IMAP"))
|
|
self.assertEqual(1, output.count("对象:Pop3"))
|
|
self.assertIn("objects.md#capa -->", output)
|
|
self.assertIn("objects.md#capa-1 -->", output)
|
|
|
|
def test_exact_qualified_member_query_returns_only_member_overloads(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
tsv_path = self.write_unified_api_fixture(Path(temp_dir))
|
|
|
|
result = self.run_lookup(
|
|
tsv_path, "--name", "demounit.document.save"
|
|
)
|
|
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertEqual(2, result.stdout.count("#### `Save("))
|
|
self.assertNotIn("#### `Name`", result.stdout)
|
|
self.assertNotIn("### `Other`", result.stdout)
|
|
|
|
def test_exact_unit_class_query_returns_its_h4_subtree_only(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
tsv_path = self.write_unified_api_fixture(Path(temp_dir))
|
|
|
|
result = self.run_lookup(
|
|
tsv_path, "--name", "DemoUnit.Document"
|
|
)
|
|
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertIn("### `Document`", result.stdout)
|
|
self.assertIn("#### `Save(path)`", result.stdout)
|
|
self.assertIn("#### `Name`", result.stdout)
|
|
self.assertNotIn("### `Other`", result.stdout)
|
|
|
|
def test_exact_unit_query_returns_the_complete_unit_subtree(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
tsv_path = self.write_unified_api_fixture(Path(temp_dir))
|
|
|
|
result = self.run_lookup(tsv_path, "--name", "DemoUnit")
|
|
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertIn("## `DemoUnit`", result.stdout)
|
|
self.assertIn("### `Open()`", result.stdout)
|
|
self.assertIn("### `Document`", result.stdout)
|
|
self.assertIn("#### `Save(path)`", result.stdout)
|
|
self.assertIn("### `Other`", result.stdout)
|
|
self.assertNotIn("## `Widget`", result.stdout)
|
|
|
|
def test_exact_class_query_returns_all_class_members(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
tsv_path = self.write_unified_api_fixture(Path(temp_dir))
|
|
|
|
result = self.run_lookup(tsv_path, "--name", "widget")
|
|
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertIn("## `Widget`", result.stdout)
|
|
self.assertIn("### `Create()`\n\n声明:class function", result.stdout)
|
|
self.assertIn("### `Count`\n\n声明:static field", result.stdout)
|
|
self.assertNotIn("## `DemoUnit`", result.stdout)
|
|
|
|
def test_exact_class_in_mixed_page_stops_at_next_h2(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
tsv_path = self.write_mixed_api_fixture(Path(temp_dir))
|
|
|
|
result = self.run_lookup(tsv_path, "--name", "Widget")
|
|
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertIn("### `Open()`", result.stdout)
|
|
self.assertNotIn("## `Parse(text)`", result.stdout)
|
|
|
|
def test_exact_function_in_mixed_page_keeps_h3_example_only(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
tsv_path = self.write_mixed_api_fixture(Path(temp_dir))
|
|
|
|
result = self.run_lookup(tsv_path, "--name", "Parse")
|
|
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertIn("### 示例", result.stdout)
|
|
self.assertIn("## `NotBoundary`", result.stdout)
|
|
self.assertNotIn("## `Runtime`", result.stdout)
|
|
|
|
def test_exact_unit_in_mixed_page_stops_before_following_class(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
tsv_path = self.write_mixed_api_fixture(Path(temp_dir))
|
|
|
|
result = self.run_lookup(tsv_path, "--name", "Runtime")
|
|
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertIn("#### `Save()`", result.stdout)
|
|
self.assertNotIn("## `Other`", result.stdout)
|
|
|
|
def test_unqualified_member_query_returns_all_owners(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
tsv_path = self.write_unified_api_fixture(Path(temp_dir))
|
|
|
|
result = self.run_lookup(tsv_path, "--name", "Save")
|
|
|
|
self.assertEqual(0, result.returncode, result.stderr)
|
|
self.assertEqual(3, result.stdout.count("#### `Save("))
|
|
self.assertIn("按路径保存。", result.stdout)
|
|
self.assertIn("按模式保存。", result.stdout)
|
|
self.assertIn("保存其他对象。", result.stdout)
|
|
|
|
def test_keyword_search_includes_unified_fields_and_declaration_term(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
tsv_path = self.write_unified_api_fixture(Path(temp_dir))
|
|
|
|
qualified = self.run_lookup(
|
|
tsv_path, "--kw", "Widget.Create"
|
|
)
|
|
visibility = self.run_lookup(
|
|
tsv_path, "--kw", "protected", "method"
|
|
)
|
|
|
|
self.assertEqual(0, qualified.returncode, qualified.stderr)
|
|
self.assertIn("Widget.Create\tclass function\tCreate()", qualified.stdout)
|
|
self.assertNotIn("static function", qualified.stdout)
|
|
self.assertEqual(0, visibility.returncode, visibility.stderr)
|
|
self.assertIn("DemoUnit.Document.Save", visibility.stdout)
|
|
|
|
def test_skill_documents_qualified_queries_and_legacy_index_compatibility(self):
|
|
text = SKILL_MD.read_text(encoding="utf-8")
|
|
|
|
self.assertIn("完全限定名称", text)
|
|
self.assertIn("--scope third", text)
|
|
self.assertIn("qualified_name", text)
|
|
self.assertIn("owner、kind、binding、visibility", text)
|
|
self.assertIn("class function", text)
|
|
self.assertIn("旧 8 列", text)
|
|
|
|
def test_keyword_search_includes_tags_and_summary(self):
|
|
module = load_script()
|
|
rows = [
|
|
{
|
|
"name": "demo",
|
|
"signature": "demo()",
|
|
"module": "base",
|
|
"tags": "数组 列表",
|
|
"summary": "返回示例值",
|
|
}
|
|
]
|
|
|
|
self.assertEqual(rows, module.search_keyword(rows, ["数组"]))
|
|
self.assertEqual(rows, module.search_keyword(rows, ["返回示例"]))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|