@@ -1,3 +1,4 @@
|
||||
import importlib.util
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -6,7 +7,22 @@ from pathlib import Path
|
||||
|
||||
|
||||
SCRIPT = Path(__file__).parents[1] / "scripts" / "lint.py"
|
||||
VALID_PAGE = "# 项目 / 示例\n\n## `demo()`\n\n示例函数\n\n返回:nil\n"
|
||||
API_MARKDOWN = Path(__file__).parents[1] / "scripts" / "api_markdown.py"
|
||||
VALID_PAGE = (
|
||||
"# 项目 / 示例\n\n"
|
||||
"## `demo()`\n\n"
|
||||
"声明:function\n\n"
|
||||
"示例函数\n\n"
|
||||
"返回:nil\n"
|
||||
)
|
||||
|
||||
|
||||
def load_api_markdown():
|
||||
spec = importlib.util.spec_from_file_location("tsl_api_markdown", API_MARKDOWN)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
class DocLintCliTest(unittest.TestCase):
|
||||
@@ -38,6 +54,781 @@ class DocLintCliTest(unittest.TestCase):
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("1 files", result.stderr)
|
||||
|
||||
def test_accepts_mixed_typed_h2_declarations(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 混合\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"组件。\n\n"
|
||||
"### `Open()`\n\n"
|
||||
"声明:function\n\n"
|
||||
"打开。\n\n"
|
||||
"可见性:`public`\n\n"
|
||||
"## `Parse()`\n\n"
|
||||
"声明:function\n\n"
|
||||
"解析。\n\n"
|
||||
"返回:Widget\n\n"
|
||||
"### 示例\n\n"
|
||||
"```tsl\nreturn Parse();\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",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page, "--strict")
|
||||
|
||||
self.assertEqual(0, result.returncode, result.stdout + result.stderr)
|
||||
|
||||
def test_declaration_must_use_exact_canonical_line(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 函数\n\n"
|
||||
"## `Open()`\n\n"
|
||||
"声明:Function\n\n"
|
||||
"打开。\n\n"
|
||||
"返回:nil\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[heading]", result.stdout)
|
||||
self.assertIn("声明:function", result.stdout)
|
||||
|
||||
def test_business_type_metadata_is_not_a_declaration_marker(self):
|
||||
module = load_api_markdown()
|
||||
lines = [
|
||||
"## `Account`",
|
||||
"",
|
||||
"声明:function",
|
||||
"",
|
||||
"远程服务器账户。",
|
||||
"",
|
||||
"类型:读写",
|
||||
"",
|
||||
"返回:nil",
|
||||
]
|
||||
|
||||
entries = list(module.iter_api_entries(lines))
|
||||
|
||||
self.assertEqual(1, len(entries))
|
||||
self.assertTrue(entries[0].heading.valid)
|
||||
self.assertEqual("function", entries[0].heading.root_kind)
|
||||
|
||||
self.page.write_text(
|
||||
"# 示例 / 遗留属性\n\n" + "\n".join(lines) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = self.run_cli("--file", self.page, "--strict")
|
||||
self.assertEqual(0, result.returncode, result.stdout + result.stderr)
|
||||
|
||||
def test_declaration_must_be_first_non_empty_line_after_heading(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 函数\n\n"
|
||||
"## `Open()`\n\n"
|
||||
"打开。\n\n"
|
||||
"声明:function\n\n"
|
||||
"返回:nil\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[heading]", result.stdout)
|
||||
self.assertIn("第一条非空正文", result.stdout)
|
||||
|
||||
def test_tags_must_follow_description(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 函数\n\n"
|
||||
"## `Open()`\n\n"
|
||||
"声明:function\n\n"
|
||||
"<!-- tags: 示例 -->\n\n"
|
||||
"打开。\n\n"
|
||||
"返回:nil\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[description]", result.stdout)
|
||||
|
||||
def test_property_type_is_optional_but_access_is_required(self):
|
||||
page_without_type = (
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"表示组件。\n\n"
|
||||
"### `Title`\n\n"
|
||||
"声明:property\n\n"
|
||||
"组件标题。\n\n"
|
||||
"可见性:`public`\n\n"
|
||||
"访问:read\n"
|
||||
)
|
||||
self.page.write_text(page_without_type, encoding="utf-8")
|
||||
|
||||
valid = self.run_cli("--file", self.page, "--strict")
|
||||
|
||||
self.assertEqual(0, valid.returncode, valid.stdout + valid.stderr)
|
||||
|
||||
self.page.write_text(
|
||||
page_without_type.replace("\n访问:read\n", "\n"),
|
||||
encoding="utf-8",
|
||||
)
|
||||
invalid = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, invalid.returncode)
|
||||
self.assertIn("[access]", invalid.stdout)
|
||||
|
||||
def test_accepts_class_page_with_optional_method_return(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"表示组件。\n\n"
|
||||
"### `Close()`\n\n"
|
||||
"声明:function\n\n"
|
||||
"关闭组件。\n\n"
|
||||
"可见性:`public`\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page, "--strict")
|
||||
|
||||
self.assertEqual(0, result.returncode, result.stdout + result.stderr)
|
||||
|
||||
def test_class_member_requires_visibility(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"表示组件。\n\n"
|
||||
"### `Count`\n\n"
|
||||
"声明:field\n\n"
|
||||
"组件数量。\n\n"
|
||||
"类型:integer\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[visibility]", result.stdout)
|
||||
|
||||
def test_parameterized_property_requires_the_fixed_parameter_table(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"表示组件。\n\n"
|
||||
"### `Items(index)`\n\n"
|
||||
"声明:property\n\n"
|
||||
"按索引读取组件。\n\n"
|
||||
"可见性:`public`\n\n"
|
||||
"类型:Widget\n\n"
|
||||
"访问:read\n\n"
|
||||
"| 名称 | 类型 | 说明 |\n"
|
||||
"| --- | --- | --- |\n"
|
||||
"| `index` | integer | 索引 |\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[param-header]", result.stdout)
|
||||
|
||||
def test_nullary_property_rejects_a_parameter_table(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"表示组件。\n\n"
|
||||
"### `Title`\n\n"
|
||||
"声明:property\n\n"
|
||||
"组件标题。\n\n"
|
||||
"可见性:`public`\n\n"
|
||||
"类型:string\n\n"
|
||||
"访问:read\n\n"
|
||||
"| 参数 | 类型 | 说明 |\n"
|
||||
"| --- | --- | --- |\n"
|
||||
"| `unused` | integer | 不应存在 |\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[param-table]", result.stdout)
|
||||
|
||||
def test_class_method_allows_h4_value_and_example_subheadings(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"表示组件。\n\n"
|
||||
"### `Open(mode)`\n\n"
|
||||
"声明:function\n\n"
|
||||
"打开组件。\n\n"
|
||||
"可见性:`public`\n\n"
|
||||
"| 参数 | 类型 | 说明 |\n"
|
||||
"| --- | --- | --- |\n"
|
||||
"| `mode` | integer | 打开模式 |\n\n"
|
||||
"#### `mode` 取值\n\n"
|
||||
"- `0` — 默认模式\n\n"
|
||||
"#### 示例\n\n"
|
||||
"```tsl\nreturn Open(0);\n```\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page, "--strict")
|
||||
|
||||
self.assertEqual(0, result.returncode, result.stdout + result.stderr)
|
||||
|
||||
def test_property_rejects_example_subheading(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"表示组件。\n\n"
|
||||
"### `Title`\n\n"
|
||||
"声明:property\n\n"
|
||||
"组件标题。\n\n"
|
||||
"可见性:`public`\n\n"
|
||||
"类型:string\n\n"
|
||||
"访问:read\n\n"
|
||||
"#### 示例\n\n"
|
||||
"```tsl\nreturn Widget.Title;\n```\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[subheading]", result.stdout)
|
||||
self.assertIn("property", result.stdout)
|
||||
|
||||
def test_accepts_unit_page_with_nested_class_member(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / Unit\n\n"
|
||||
"## `DemoUnit`\n\n"
|
||||
"声明:unit\n\n"
|
||||
"提供接口。\n\n"
|
||||
"### `DefaultSize`\n\n"
|
||||
"声明:const\n\n"
|
||||
"默认大小。\n\n"
|
||||
"值:`100`\n\n"
|
||||
"### `Open()`\n\n"
|
||||
"声明:function\n\n"
|
||||
"打开。\n\n"
|
||||
"返回:Document\n\n"
|
||||
"### `Document`\n\n"
|
||||
"声明:class\n\n"
|
||||
"文档对象。\n\n"
|
||||
"#### `Save()`\n\n"
|
||||
"声明:function\n\n"
|
||||
"保存。\n\n"
|
||||
"可见性:`protected`\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page, "--strict")
|
||||
|
||||
self.assertEqual(0, result.returncode, result.stdout + result.stderr)
|
||||
|
||||
def test_unit_class_method_allows_h5_value_and_example_subheadings(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / Unit\n\n"
|
||||
"## `DemoUnit`\n\n"
|
||||
"声明:unit\n\n"
|
||||
"提供接口。\n\n"
|
||||
"### `Document`\n\n"
|
||||
"声明:class\n\n"
|
||||
"文档对象。\n\n"
|
||||
"#### `Save(mode)`\n\n"
|
||||
"声明:function\n\n"
|
||||
"保存。\n\n"
|
||||
"可见性:`public`\n\n"
|
||||
"| 参数 | 类型 | 说明 |\n"
|
||||
"| --- | --- | --- |\n"
|
||||
"| `mode` | integer | 保存模式 |\n\n"
|
||||
"##### `mode` 取值\n\n"
|
||||
"- `0` — 默认模式\n\n"
|
||||
"##### 示例\n\n"
|
||||
"```tsl\nreturn Save(0);\n```\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page, "--strict")
|
||||
|
||||
self.assertEqual(0, result.returncode, result.stdout + result.stderr)
|
||||
|
||||
def test_unit_function_requires_return(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / Unit\n\n"
|
||||
"## `DemoUnit`\n\n"
|
||||
"声明:unit\n\n"
|
||||
"提供接口。\n\n"
|
||||
"### `Open()`\n\n"
|
||||
"声明:function\n\n"
|
||||
"打开。\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[return]", result.stdout)
|
||||
|
||||
def test_unit_function_rejects_an_empty_return_type(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / Unit\n\n"
|
||||
"## `DemoUnit`\n\n"
|
||||
"声明:unit\n\n"
|
||||
"提供接口。\n\n"
|
||||
"### `Open()`\n\n"
|
||||
"声明:function\n\n"
|
||||
"打开。\n\n"
|
||||
"返回:\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[return]", result.stdout)
|
||||
|
||||
def test_empty_type_and_value_lines_are_rejected(self):
|
||||
cases = {
|
||||
"property": (
|
||||
"# 示例 / 类\n\n## `Widget`\n\n声明:class\n\n组件。\n\n"
|
||||
"### `Title`\n\n声明:property\n\n标题。\n\n可见性:`public`\n\n"
|
||||
"类型:\n\n访问:read\n",
|
||||
"[type]",
|
||||
),
|
||||
"field": (
|
||||
"# 示例 / 类\n\n## `Widget`\n\n声明:class\n\n组件。\n\n"
|
||||
"### `Count`\n\n声明:field\n\n数量。\n\n可见性:`public`\n\n类型:\n",
|
||||
"[type]",
|
||||
),
|
||||
"variable": (
|
||||
"# 示例 / Unit\n\n## `DemoUnit`\n\n声明:unit\n\n接口。\n\n"
|
||||
"### `Current`\n\n声明:var\n\n当前值。\n\n类型:\n",
|
||||
"[type]",
|
||||
),
|
||||
"constant": (
|
||||
"# 示例 / Unit\n\n## `DemoUnit`\n\n声明:unit\n\n接口。\n\n"
|
||||
"### `Default`\n\n声明:const\n\n默认值。\n\n值:\n",
|
||||
"[value]",
|
||||
),
|
||||
}
|
||||
for name, (text, expected) in cases.items():
|
||||
with self.subTest(name=name):
|
||||
self.page.write_text(text, encoding="utf-8")
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn(expected, result.stdout)
|
||||
|
||||
def test_class_parent_metadata_does_not_count_as_description(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"父类:`BaseWidget`\n\n"
|
||||
"",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[description]", result.stdout)
|
||||
|
||||
def test_class_parameter_rows_must_match_signature(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"表示组件。\n\n"
|
||||
"### `Open(mode)`\n\n"
|
||||
"声明:function\n\n"
|
||||
"打开组件。\n\n"
|
||||
"可见性:`public`\n\n"
|
||||
"| 参数 | 类型 | 说明 |\n"
|
||||
"| --- | --- | --- |\n"
|
||||
"| `other` | integer | 错误参数 |\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[param-names]", result.stdout)
|
||||
|
||||
def test_class_parameter_rows_require_type_and_description(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"表示组件。\n\n"
|
||||
"### `Open(mode)`\n\n"
|
||||
"声明:function\n\n"
|
||||
"打开组件。\n\n"
|
||||
"可见性:`public`\n\n"
|
||||
"| 参数 | 类型 | 说明 |\n"
|
||||
"| --- | --- | --- |\n"
|
||||
"| `mode` | | |\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[param-row]", result.stdout)
|
||||
|
||||
def test_unit_class_member_requires_visibility(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / Unit\n\n"
|
||||
"## `DemoUnit`\n\n"
|
||||
"声明:unit\n\n"
|
||||
"提供接口。\n\n"
|
||||
"### `Document`\n\n"
|
||||
"声明:class\n\n"
|
||||
"文档对象。\n\n"
|
||||
"#### `Name`\n\n"
|
||||
"声明:field\n\n"
|
||||
"文档名称。\n\n"
|
||||
"类型:string\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[visibility]", result.stdout)
|
||||
|
||||
def test_class_and_unit_member_data_lines_are_required(self):
|
||||
cases = {
|
||||
"property": (
|
||||
"# 示例 / 类\n\n## `Widget`\n\n声明:class\n\n组件。\n\n"
|
||||
"### `Title`\n\n声明:property\n\n标题。\n\n可见性:`public`\n",
|
||||
"[access]",
|
||||
),
|
||||
"field": (
|
||||
"# 示例 / 类\n\n## `Widget`\n\n声明:class\n\n组件。\n\n"
|
||||
"### `Count`\n\n声明:field\n\n数量。\n\n可见性:`public`\n",
|
||||
"[type]",
|
||||
),
|
||||
"variable": (
|
||||
"# 示例 / Unit\n\n## `DemoUnit`\n\n声明:unit\n\n接口。\n\n"
|
||||
"### `Current`\n\n声明:var\n\n当前值。\n",
|
||||
"[type]",
|
||||
),
|
||||
"constant": (
|
||||
"# 示例 / Unit\n\n## `DemoUnit`\n\n声明:unit\n\n接口。\n\n"
|
||||
"### `Default`\n\n声明:const\n\n默认值。\n",
|
||||
"[value]",
|
||||
),
|
||||
}
|
||||
for name, (text, expected) in cases.items():
|
||||
with self.subTest(name=name):
|
||||
self.page.write_text(text, encoding="utf-8")
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn(expected, result.stdout)
|
||||
|
||||
def test_class_legacy_typed_api_heading_is_rejected(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"表示组件。\n\n"
|
||||
"### function `Open()`\n\n"
|
||||
"打开组件。\n\n"
|
||||
"可见性:`public`\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[heading]", result.stdout)
|
||||
self.assertIn("标题只写名称或调用签名", result.stdout)
|
||||
|
||||
def test_unit_legacy_typed_api_heading_is_rejected(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / Unit\n\n"
|
||||
"## `DemoUnit`\n\n"
|
||||
"声明:unit\n\n"
|
||||
"提供接口。\n\n"
|
||||
"### function `Open()`\n\n"
|
||||
"打开。\n\n"
|
||||
"返回:Document\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[heading]", result.stdout)
|
||||
self.assertIn("标题只写名称或调用签名", result.stdout)
|
||||
|
||||
def test_class_member_at_h4_is_rejected(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"表示组件。\n\n"
|
||||
"#### `Open()`\n\n"
|
||||
"声明:function\n\n"
|
||||
"打开组件。\n\n"
|
||||
"可见性:`public`\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[heading]", result.stdout)
|
||||
self.assertIn("H3", result.stdout)
|
||||
|
||||
def test_unit_class_member_at_h5_is_rejected(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / Unit\n\n"
|
||||
"## `DemoUnit`\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",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("[heading]", result.stdout)
|
||||
self.assertIn("H4", result.stdout)
|
||||
|
||||
def test_rejects_static_function_and_private_api(self):
|
||||
self.page.write_text(
|
||||
"# 示例 / 类\n\n"
|
||||
"## `Widget`\n\n"
|
||||
"声明:class\n\n"
|
||||
"表示组件。\n\n"
|
||||
"### `Create()`\n\n"
|
||||
"声明:static function\n\n"
|
||||
"创建。\n\n"
|
||||
"可见性:`private`\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = self.run_cli("--file", self.page)
|
||||
|
||||
self.assertEqual(1, result.returncode)
|
||||
self.assertIn("static function", result.stdout)
|
||||
self.assertIn("private", result.stdout)
|
||||
|
||||
def test_mixed_h2_declarations_reset_nested_context(self):
|
||||
module = load_api_markdown()
|
||||
lines = [
|
||||
"# Demo",
|
||||
"",
|
||||
"## `Widget`",
|
||||
"",
|
||||
"声明:class",
|
||||
"",
|
||||
"组件。",
|
||||
"",
|
||||
"### `Open()`",
|
||||
"",
|
||||
"声明:function",
|
||||
"",
|
||||
"打开。",
|
||||
"",
|
||||
"可见性:public",
|
||||
"",
|
||||
"## `Parse(text)`",
|
||||
"",
|
||||
"声明:function",
|
||||
"",
|
||||
"解析。",
|
||||
"",
|
||||
"返回:Widget",
|
||||
"",
|
||||
"## `DemoUnit`",
|
||||
"",
|
||||
"声明:unit",
|
||||
"",
|
||||
"接口。",
|
||||
"",
|
||||
"### `Document`",
|
||||
"",
|
||||
"声明:class",
|
||||
"",
|
||||
"文档。",
|
||||
"",
|
||||
"#### `Save()`",
|
||||
"",
|
||||
"声明:function",
|
||||
"",
|
||||
"保存。",
|
||||
"",
|
||||
"可见性:public",
|
||||
]
|
||||
|
||||
headings = [entry.heading for entry in module.iter_api_entries(lines)]
|
||||
|
||||
self.assertEqual(
|
||||
[
|
||||
(2, "class", "class", "Widget"),
|
||||
(3, "method", "class", "Open"),
|
||||
(2, "function", "function", "Parse"),
|
||||
(2, "unit", "unit", "DemoUnit"),
|
||||
(3, "class", "unit", "Document"),
|
||||
(4, "method", "unit", "Save"),
|
||||
],
|
||||
[
|
||||
(item.level, item.kind, item.root_kind, item.name)
|
||||
for item in headings
|
||||
],
|
||||
)
|
||||
|
||||
def test_missing_duplicate_and_unknown_declarations_are_errors(self):
|
||||
module = load_api_markdown()
|
||||
cases = {
|
||||
"missing": (
|
||||
["## `Open()`", "", "打开。", "", "返回:nil"],
|
||||
"第一条非空正文",
|
||||
),
|
||||
"duplicate": (
|
||||
[
|
||||
"## `Widget`",
|
||||
"",
|
||||
"声明:class",
|
||||
"",
|
||||
"组件。",
|
||||
"",
|
||||
"声明:unit",
|
||||
],
|
||||
"必须且只能包含一行",
|
||||
),
|
||||
"unknown": (
|
||||
["## `Runtime`", "", "声明:module", "", "接口。"],
|
||||
"必须是 function、class 或 unit",
|
||||
),
|
||||
}
|
||||
for name, (lines, expected) in cases.items():
|
||||
with self.subTest(name=name):
|
||||
headings = [
|
||||
entry.heading for entry in module.iter_api_entries(lines)
|
||||
]
|
||||
|
||||
self.assertEqual(1, len(headings))
|
||||
self.assertFalse(headings[0].valid)
|
||||
self.assertIn(expected, headings[0].error)
|
||||
|
||||
def test_plain_h2_ends_api_body_and_resets_nested_context(self):
|
||||
module = load_api_markdown()
|
||||
lines = [
|
||||
"# Demo",
|
||||
"",
|
||||
"## `Parse()`",
|
||||
"",
|
||||
"声明:function",
|
||||
"",
|
||||
"解析。",
|
||||
"",
|
||||
"返回:nil",
|
||||
"",
|
||||
"## 其他说明",
|
||||
"",
|
||||
"### function `NotAMember()`",
|
||||
]
|
||||
|
||||
entries = list(module.iter_api_entries(lines))
|
||||
|
||||
self.assertEqual(["Parse"], [entry.heading.name for entry in entries])
|
||||
self.assertEqual(10, entries[0].end)
|
||||
|
||||
def test_parameter_value_and_example_headings_are_not_api_entries(self):
|
||||
module = load_api_markdown()
|
||||
lines = [
|
||||
"# Demo",
|
||||
"",
|
||||
"## `Widget`",
|
||||
"",
|
||||
"声明:class",
|
||||
"",
|
||||
"表示组件。",
|
||||
"",
|
||||
"### `Open(mode)`",
|
||||
"",
|
||||
"声明:function",
|
||||
"",
|
||||
"打开。",
|
||||
"",
|
||||
"可见性:`public`",
|
||||
"",
|
||||
"#### `mode` 取值",
|
||||
"",
|
||||
"#### 示例",
|
||||
"",
|
||||
"## `Parse(mode)`",
|
||||
"",
|
||||
"声明:function",
|
||||
"",
|
||||
"解析。",
|
||||
"",
|
||||
"| 参数 | 类型 | 说明 |",
|
||||
"| --- | --- | --- |",
|
||||
"| `mode` | integer | 模式 |",
|
||||
"",
|
||||
"**mode 取值**",
|
||||
"",
|
||||
"### 示例",
|
||||
"",
|
||||
"## `DemoUnit`",
|
||||
"",
|
||||
"声明:unit",
|
||||
"",
|
||||
"接口。",
|
||||
"",
|
||||
"### `Load(mode)`",
|
||||
"",
|
||||
"声明:function",
|
||||
"",
|
||||
"加载。",
|
||||
"",
|
||||
"#### `mode` 取值",
|
||||
"",
|
||||
"#### 示例",
|
||||
]
|
||||
|
||||
entries = list(module.iter_api_entries(lines))
|
||||
|
||||
self.assertEqual(
|
||||
["Widget", "Open", "Parse", "DemoUnit", "Load"],
|
||||
[entry.heading.name for entry in entries],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user