📦 deps(tsl): sync tsl-playbook from aa8a3e73

Source-Commit: aa8a3e73a8
This commit is contained in:
ci[bot]
2026-07-30 01:23:00 +00:00
parent 80357f4bc5
commit 7c9a23746f
3 changed files with 1608 additions and 297 deletions
+581 -130
View File
@@ -237,7 +237,7 @@ class ConvertTsfCliTest(unittest.TestCase):
self.read_declaration()["params"],
)
def test_parameter_type_mismatch_reports_param_line_and_preserves_output(self):
def test_parameter_type_mismatch_creates_structural_draft(self):
source = self.write_tsf(
"ParamTypeMismatch",
"""
@@ -249,32 +249,24 @@ class ConvertTsfCliTest(unittest.TestCase):
end;
""",
)
self.output.write_text("原内容\n", encoding="utf-8")
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:4:", result.stderr)
self.assertIn("参数 value 的类型与函数声明不一致", result.stderr)
self.assertIn("注释为 string,声明为 integer", result.stderr)
self.assertEqual("原内容\n", self.output.read_text(encoding="utf-8"))
self.assertEqual(0, result.returncode, result.stderr)
function = self.read_declaration()
self.assertEqual("", function["desc"])
self.assertEqual(
[{"name": "value", "type": "integer", "desc": ""}],
function["params"],
)
self.assertEqual("integer", function["returns"])
def test_parameter_type_braces_must_be_complete_and_nonempty(self):
def test_malformed_parameter_docs_create_structural_draft(self):
cases = {
"EmptyParamType": (
"/// @param: value {} 输入值",
"@param: 参数类型不能为空",
),
"UnclosedParamType": (
"/// @param: value {integer 输入值",
"@param: 参数类型缺少右花括号",
),
"MissingParamDescription": (
"/// @param: value {integer}",
"@param: 参数说明不能为空",
),
"EmptyParamType": "/// @param: value {} 输入值",
"UnclosedParamType": "/// @param: value {integer 输入值",
"MissingParamDescription": "/// @param: value {integer}",
}
for name, (param_line, expected) in cases.items():
for name, param_line in cases.items():
with self.subTest(name=name):
source = self.write_tsf(
name,
@@ -290,10 +282,14 @@ class ConvertTsfCliTest(unittest.TestCase):
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:4:", result.stderr)
self.assertIn(expected, result.stderr)
self.assertFalse(self.output.exists())
self.assertEqual(0, result.returncode, result.stderr)
function = self.read_declaration()
self.assertEqual("", function["desc"])
self.assertEqual(
[{"name": "value", "type": "", "desc": ""}],
function["params"],
)
self.assertEqual("integer", function["returns"])
def test_multiple_tsf_files_are_merged_in_input_order(self):
first = self.write_tsf(
@@ -514,6 +510,45 @@ class ConvertTsfCliTest(unittest.TestCase):
)
self.assertEqual("", function["returns"])
def test_legacy_document_block_creates_structural_draft(self):
source = self.write_tsf(
"LegacyDraft",
"""
function LegacyDraft(required: integer; optional = 1): array;
begin
{**
@param(required)(integer) 必填值
@param(optional)(integer) 可选值
@return(array) 结果
**}
return array();
end;
""",
)
result = self.run_cli(source)
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual(
{
"kind": "function",
"name": "LegacyDraft",
"signature": "LegacyDraft(required, optional)",
"desc": "",
"params": [
{"name": "required", "type": "integer", "desc": ""},
{
"name": "optional",
"type": "",
"optional": True,
"desc": "",
},
],
"returns": "array",
},
self.read_declaration(),
)
def test_json_draft_completes_fixed_fields_for_every_declaration_kind(self):
function_source = self.write_tsf(
"DraftFunction",
@@ -649,7 +684,7 @@ class ConvertTsfCliTest(unittest.TestCase):
self.assertNotIn("positional arguments:", result.stdout)
self.assertNotIn("options:", result.stdout)
def test_return_type_mismatch_reports_directive_line_and_preserves_output(self):
def test_return_type_mismatch_creates_structural_draft(self):
source = self.write_tsf(
"Mismatch",
"""
@@ -661,17 +696,14 @@ class ConvertTsfCliTest(unittest.TestCase):
end;
""",
)
self.output.write_text("原内容\n", encoding="utf-8")
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:4:", result.stderr)
self.assertIn("返回类型与函数声明不一致", result.stderr)
self.assertIn("注释为 string,声明为 integer", result.stderr)
self.assertEqual("原内容\n", self.output.read_text(encoding="utf-8"))
self.assertEqual(0, result.returncode, result.stderr)
function = self.read_declaration()
self.assertEqual("", function["desc"])
self.assertEqual("integer", function["returns"])
def test_document_block_must_be_first_content_after_begin(self):
def test_late_document_block_creates_structural_draft(self):
source = self.write_tsf(
"LateDoc",
"""
@@ -686,12 +718,30 @@ class ConvertTsfCliTest(unittest.TestCase):
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:3:", result.stderr)
self.assertIn("begin 后第一段内容必须是 /// 文档块", result.stderr)
self.assertFalse(self.output.exists())
self.assertEqual(0, result.returncode, result.stderr)
function = self.read_declaration()
self.assertEqual("", function["desc"])
self.assertEqual("integer", function["returns"])
def test_duplicate_enum_value_is_rejected(self):
def test_missing_body_does_not_borrow_later_function_document(self):
source = self.write_tsf(
"MissingOwnBody",
"function MissingOwnBody(): integer;\n"
"function Helper(): integer;\n"
"begin\n"
" /// 辅助函数文档。\n"
" return 1;\n"
"end;\n",
)
result = self.run_cli(source)
self.assertEqual(0, result.returncode, result.stderr)
function = self.read_declaration()
self.assertEqual("MissingOwnBody", function["name"])
self.assertEqual("", function["desc"])
def test_duplicate_enum_value_creates_structural_draft(self):
source = self.write_tsf(
"DuplicateValue",
"""
@@ -709,11 +759,16 @@ class ConvertTsfCliTest(unittest.TestCase):
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:7:", result.stderr)
self.assertIn("枚举值重复", result.stderr)
self.assertEqual(0, result.returncode, result.stderr)
function = self.read_declaration()
self.assertEqual("", function["desc"])
self.assertEqual(
[{"name": "mode", "type": "integer", "desc": ""}],
function["params"],
)
self.assertEqual("integer", function["returns"])
def test_directives_after_examples_are_rejected(self):
def test_directives_after_examples_create_structural_draft(self):
source = self.write_tsf(
"BadOrder",
"""
@@ -730,9 +785,14 @@ class ConvertTsfCliTest(unittest.TestCase):
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:6:", result.stderr)
self.assertIn("示例组之后不能再写函数级指令", result.stderr)
self.assertEqual(0, result.returncode, result.stderr)
function = self.read_declaration()
self.assertEqual("", function["desc"])
self.assertEqual(
[{"name": "value", "type": "integer", "desc": ""}],
function["params"],
)
self.assertEqual("integer", function["returns"])
def test_class_converts_only_first_matching_public_class(self):
source = self.write_tsf(
@@ -982,7 +1042,7 @@ class ConvertTsfCliTest(unittest.TestCase):
members = self.read_declaration()["members"]
self.assertEqual(["Value"], [member["name"] for member in members])
def test_class_rejects_unsupported_public_declaration_at_exact_line(self):
def test_class_skips_unsupported_public_declaration(self):
source = self.write_tsf(
"UnsupportedClass",
"""
@@ -992,14 +1052,10 @@ class ConvertTsfCliTest(unittest.TestCase):
end;
""",
)
self.output.write_text("原内容\n", encoding="utf-8")
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:3:", result.stderr)
self.assertIn("class 不支持成员声明:type", result.stderr)
self.assertEqual("原内容\n", self.output.read_text(encoding="utf-8"))
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual([], self.read_declaration()["members"])
def test_missing_class_and_unit_member_types_remain_explicit_drafts(self):
cls_source = self.write_tsf(
@@ -1126,7 +1182,33 @@ class ConvertTsfCliTest(unittest.TestCase):
self.assertNotIn("HiddenValue", serialized)
self.assertNotIn("生命周期文档", serialized)
def test_unit_filename_must_match_name_at_declaration_line(self):
def test_incomplete_interface_class_does_not_scan_implementation(self):
source = self.write_tsf(
"IncompleteInterfaceClass",
"unit IncompleteInterfaceClass;\n"
"interface\n"
"type PublicType = class\n"
"public\n"
"function Keep(): integer;\n"
"implementation\n"
"function Hidden(): integer;\n"
"begin\n"
" return 1;\n"
"end;\n"
"end.\n",
)
result = self.run_cli(source)
self.assertEqual(0, result.returncode, result.stderr)
unit = self.read_declaration()
self.assertEqual(["PublicType"], [member["name"] for member in unit["members"]])
self.assertEqual(
["Keep"],
[member["name"] for member in unit["members"][0]["members"]],
)
def test_unit_filename_mismatch_keeps_declared_name(self):
source = self.write_tsf(
"Wrong",
"unit Right;\ninterface\nimplementation\nend.\n",
@@ -1134,9 +1216,8 @@ class ConvertTsfCliTest(unittest.TestCase):
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:1:", result.stderr)
self.assertIn("unit 名称必须与文件名一致", result.stderr)
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual("Right", self.read_declaration()["name"])
def test_unit_preserves_multiple_interface_classes_in_source_order(self):
source = self.write_tsf(
@@ -1162,7 +1243,7 @@ class ConvertTsfCliTest(unittest.TestCase):
members = self.read_declaration()["members"]
self.assertEqual(["First", "Second"], [member["name"] for member in members])
def test_unit_unbound_document_reports_exact_line(self):
def test_unit_unbound_document_is_ignored(self):
source = self.write_tsf(
"UnboundUnit",
"""
@@ -1178,11 +1259,12 @@ class ConvertTsfCliTest(unittest.TestCase):
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:3:", result.stderr)
self.assertIn("unit interface 文档块无法绑定", result.stderr)
self.assertEqual(0, result.returncode, result.stderr)
current = self.read_declaration()["members"][0]
self.assertEqual("Current", current["name"])
self.assertEqual("", current["desc"])
def test_unit_unbound_header_document_reports_exact_line(self):
def test_unit_unbound_header_document_is_ignored(self):
source = self.write_tsf(
"UnboundHeaderUnit",
"""
@@ -1197,11 +1279,10 @@ class ConvertTsfCliTest(unittest.TestCase):
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:2:", result.stderr)
self.assertIn("unit interface 文档块无法绑定", result.stderr)
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual("", self.read_declaration()["desc"])
def test_unit_end_dot_must_be_the_terminal_tokens(self):
def test_unit_ignores_tokens_after_terminal_end_dot(self):
source = self.write_tsf(
"TrailingUnit",
"""
@@ -1215,9 +1296,8 @@ class ConvertTsfCliTest(unittest.TestCase):
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:5:", result.stderr)
self.assertIn("end. 必须结束整个文件", result.stderr)
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual("TrailingUnit", self.read_declaration()["name"])
def test_class_missing_docs_default_visibility_and_private_filter_create_draft(self):
source = self.write_tsf(
@@ -1269,35 +1349,37 @@ class ConvertTsfCliTest(unittest.TestCase):
members = self.read_declaration()["members"]
self.assertEqual(["value_"], [member["name"] for member in members])
def test_class_structure_errors_preserve_existing_output(self):
def test_class_structure_errors_create_best_effort_drafts(self):
cases = {
"WrongName": (
"type Actual = class\nend;\n",
1,
"对外 class 名称必须与文件名一致",
"Actual",
[],
),
"StaticMethod": (
"type StaticMethod = class\npublic\nstatic function Bad();\nend;\n",
3,
"不存在 static function",
"StaticMethod",
["Bad"],
),
"ManyFields": (
"type ManyFields = class\npublic\nleft_, right_: integer;\nend;\n",
3,
"一项一条声明",
"ManyFields",
["left_", "right_"],
),
}
for name, (text, line, expected) in cases.items():
for name, (text, expected_name, expected_members) in cases.items():
with self.subTest(name=name):
source = self.write_tsf(name, text)
self.output.write_text("原内容\n", encoding="utf-8")
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:{line}:", result.stderr)
self.assertIn(expected, result.stderr)
self.assertEqual("原内容\n", self.output.read_text(encoding="utf-8"))
self.assertEqual(0, result.returncode, result.stderr)
declaration = self.read_declaration()
self.assertEqual(expected_name, declaration["name"])
self.assertEqual(
expected_members,
[member["name"] for member in declaration["members"]],
)
def test_class_and_unit_constants_reject_multiple_names_at_declaration_line(self):
def test_class_and_unit_constants_split_multiple_names_into_drafts(self):
cases = {
"ManyConstants": (
"type ManyConstants = class\npublic\nconst Left, Right = 1;\nend;\n",
@@ -1312,19 +1394,19 @@ class ConvertTsfCliTest(unittest.TestCase):
3,
),
}
for name, (text, line) in cases.items():
for name, (text, _line) in cases.items():
with self.subTest(name=name):
source = self.write_tsf(name, text)
self.output.write_text("原内容\n", encoding="utf-8")
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:{line}:", result.stderr)
self.assertIn("对外常量必须一项一条声明", result.stderr)
self.assertEqual("原内容\n", self.output.read_text(encoding="utf-8"))
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual(
["Left", "Right"],
[member["name"] for member in self.read_declaration()["members"]],
)
def test_class_rejects_procedure_at_declaration_line(self):
def test_class_converts_procedure_to_method_draft(self):
source = self.write_tsf(
"ProcedureClass",
"""
@@ -1334,16 +1416,15 @@ class ConvertTsfCliTest(unittest.TestCase):
end;
""",
)
self.output.write_text("原内容\n", encoding="utf-8")
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:3:", result.stderr)
self.assertIn("class 暂不支持 procedure", result.stderr)
self.assertEqual("原内容\n", self.output.read_text(encoding="utf-8"))
self.assertEqual(0, result.returncode, result.stderr)
method = self.read_declaration()["members"][0]
self.assertEqual("Open", method["name"])
self.assertEqual("method", method["kind"])
self.assertEqual("", method["returns"])
def test_class_unbound_document_reports_its_line(self):
def test_class_unbound_document_is_ignored(self):
source = self.write_tsf(
"UnboundDoc",
"""
@@ -1359,35 +1440,323 @@ class ConvertTsfCliTest(unittest.TestCase):
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:4:", result.stderr)
self.assertIn("文档块无法绑定", result.stderr)
self.assertEqual(0, result.returncode, result.stderr)
field = self.read_declaration()["members"][0]
self.assertEqual("value_", field["name"])
self.assertEqual("", field["desc"])
def test_unit_rejects_shorthand_procedure_and_non_class_type(self):
def test_unit_converts_shorthand_and_skips_unsupported_types(self):
cases = {
"ShortUnit": (
"unit ShortUnit;\nfunction Open(): integer;\nbegin return 1; end;\nend.\n",
1,
"显式 interface",
["Open"],
),
"ProcedureUnit": (
"unit ProcedureUnit;\ninterface\nprocedure Open();\nimplementation\nend.\n",
3,
"暂不支持 procedure",
["Open"],
),
"AliasUnit": (
"unit AliasUnit;\ninterface\ntype Size = integer;\nimplementation\nend.\n",
3,
"只支持 class type",
[],
),
}
for name, (text, line, expected) in cases.items():
for name, (text, expected_members) in cases.items():
with self.subTest(name=name):
source = self.write_tsf(name, text)
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn(f"{source}:{line}:", result.stderr)
self.assertIn(expected, result.stderr)
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual(
expected_members,
[member["name"] for member in self.read_declaration()["members"]],
)
def test_malformed_class_and_unit_documents_create_structural_drafts(self):
cls = self.write_tsf(
"BadClassDocs",
"""
type BadClassDocs = class
public
/// 打开值
/// @param: value {string} 输入值
function Open(value: integer): integer;
end;
""",
)
unit = self.write_tsf(
"BadUnitDocs",
"""
unit BadUnitDocs;
interface
/// 打开值
/// @returns: string
function Open(): integer;
implementation
end.
""",
)
result = self.run_cli(cls, unit)
self.assertEqual(0, result.returncode, result.stderr)
class_method = self.read_declarations()[0]["members"][0]
unit_function = self.read_declarations()[1]["members"][0]
self.assertEqual("", class_method["desc"])
self.assertEqual("integer", class_method["params"][0]["type"])
self.assertEqual("", unit_function["desc"])
self.assertEqual("integer", unit_function["returns"])
def test_incomplete_class_and_unit_keep_recognizable_members(self):
cases = {
"IncompleteClass": (
"type IncompleteClass = class\n"
"public\n"
"function Keep(value: integer): integer;\n",
"Keep",
),
"IncompleteUnit": (
"unit IncompleteUnit;\n"
"function Keep(value: integer): integer;\n",
"Keep",
),
}
for name, (text, expected_member) in cases.items():
with self.subTest(name=name):
source = self.write_tsf(name, text)
result = self.run_cli(source)
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual(
[expected_member],
[member["name"] for member in self.read_declaration()["members"]],
)
def test_broken_member_does_not_hide_following_function(self):
cases = {
"RecoverClass": (
"type RecoverClass = class\n"
"public\n"
"property Broken\n"
"function Keep(): integer;\n"
"end;\n",
"class",
),
"RecoverUnit": (
"unit RecoverUnit;\n"
"interface\n"
"const Broken\n"
"function Keep(): integer;\n"
"implementation\n"
"end.\n",
"unit",
),
}
for name, (text, expected_kind) in cases.items():
with self.subTest(name=name):
source = self.write_tsf(name, text)
result = self.run_cli(source)
self.assertEqual(0, result.returncode, result.stderr)
declaration = self.read_declaration()
self.assertEqual(expected_kind, declaration["kind"])
self.assertIn(
"Keep",
[member["name"] for member in declaration["members"]],
)
def test_unnamed_class_and_unit_functions_are_skipped(self):
cases = {
"UnnamedClassFunction": (
"type UnnamedClassFunction = class\n"
"public\n"
"function\n"
"function Keep(): integer;\n"
"end;\n",
"class",
),
"UnnamedUnitFunction": (
"unit UnnamedUnitFunction;\n"
"interface\n"
"function\n"
"function Keep(): integer;\n"
"implementation\n"
"end.\n",
"unit",
),
}
for name, (text, expected_kind) in cases.items():
with self.subTest(name=name):
source = self.write_tsf(name, text)
result = self.run_cli(source)
self.assertEqual(0, result.returncode, result.stderr)
declaration = self.read_declaration()
self.assertEqual(expected_kind, declaration["kind"])
self.assertEqual(
["Keep"],
[member["name"] for member in declaration["members"]],
)
def test_unnamed_members_do_not_hide_following_functions(self):
cases = {
"UnnamedProperty": (
"type UnnamedProperty = class\n"
"public\n"
"property\n"
"function Keep(): integer;\n"
"end;\n",
"class",
),
"UnnamedConstant": (
"unit UnnamedConstant;\n"
"interface\n"
"const\n"
"function Keep(): integer;\n"
"implementation\n"
"end.\n",
"unit",
),
}
for name, (text, expected_kind) in cases.items():
with self.subTest(name=name):
source = self.write_tsf(name, text)
result = self.run_cli(source)
self.assertEqual(0, result.returncode, result.stderr)
declaration = self.read_declaration()
self.assertEqual(expected_kind, declaration["kind"])
self.assertEqual(
["Keep"],
[member["name"] for member in declaration["members"]],
)
def test_method_missing_signature_semicolon_does_not_hide_next_member(self):
source = self.write_tsf(
"BrokenInlineMethod",
"type BrokenInlineMethod = class\n"
"public\n"
"function Broken()\n"
"begin\n"
" return 1;\n"
"end;\n"
"function Keep(): integer;\n"
"end;\n",
)
result = self.run_cli(source)
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual(
["Broken", "Keep"],
[member["name"] for member in self.read_declaration()["members"]],
)
def test_broken_headers_do_not_hide_first_function(self):
cases = {
"BrokenBase": (
"type BrokenBase = class(Base\n"
"function Keep(): integer;\n",
"class",
),
"MissingClassName": (
"type\n"
"class\n"
"function Keep(): integer;\n",
"class",
),
"BrokenUnitHeader": (
"unit BrokenUnitHeader\n"
"function Keep(): integer;\n",
"unit",
),
"MissingUnitName": (
"unit\n"
"function Keep(): integer;\n",
"unit",
),
}
for name, (text, expected_kind) in cases.items():
with self.subTest(name=name):
source = self.write_tsf(name, text)
result = self.run_cli(source)
self.assertEqual(0, result.returncode, result.stderr)
declaration = self.read_declaration()
self.assertEqual(expected_kind, declaration["kind"])
self.assertEqual(
["Keep"],
[member["name"] for member in declaration["members"]],
)
def test_declaration_truncation_after_name_always_creates_a_draft(self):
module = load_script()
samples = {
"function": (
"TruncateFunction.tsf",
"function TruncateFunction(required: integer; optional = 1): array;\n"
"begin\n"
" return array();\n"
"end;\n",
),
"class": (
"TruncateClass.tsf",
"type TruncateClass = class(Base)\n"
"public\n"
"function Open(value: integer): string;\n"
"property Value: integer read value_ write value_;\n"
"const Limit = 2;\n"
"value_: integer;\n"
"end;\n",
),
"unit": (
"TruncateUnit.tsf",
"unit TruncateUnit;\n"
"interface\n"
"function Open(value: integer): string;\n"
"const Limit = 2;\n"
"var Current: integer;\n"
"implementation\n"
"end.\n",
),
}
for expected_kind, (filename, source) in samples.items():
name_end = module.tokenize(source)[1].end
for cut in range(name_end, len(source) + 1):
with self.subTest(kind=expected_kind, cut=cut):
declaration = module.complete_draft_fields(
module.convert_source(source[:cut], Path(filename))
)
self.assertEqual(expected_kind, declaration["kind"])
self.assertTrue(declaration["name"])
def test_unit_fallback_keeps_bare_const_and_var_sections(self):
source = self.write_tsf(
"RecoverSections",
"""
unit RecoverSections;
interface
/// 无法绑定
// 切断文档绑定
const First = 1;
Second = 2;
var Current: integer;
Previous: integer;
implementation
end.
""",
)
result = self.run_cli(source)
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual(
["First", "Second", "Current", "Previous"],
[member["name"] for member in self.read_declaration()["members"]],
)
def test_unit_var_and_const_sections_keep_each_declaration(self):
source = self.write_tsf(
@@ -1458,7 +1827,7 @@ class ConvertTsfCliTest(unittest.TestCase):
[item["signature"] for item in self.read_declarations()],
)
def test_duplicate_function_signature_preserves_existing_output(self):
def test_duplicate_function_signature_is_preserved_in_input_order(self):
first = self.write_nested_tsf(
"first",
"Parse",
@@ -1483,15 +1852,15 @@ class ConvertTsfCliTest(unittest.TestCase):
end;
""",
)
self.output.write_text("原内容\n", encoding="utf-8")
result = self.run_cli(first, second)
self.assertEqual(1, result.returncode)
self.assertIn("重复 function signature", result.stderr)
self.assertEqual("原内容\n", self.output.read_text(encoding="utf-8"))
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual(
["Parse(value)", "parse(value)"],
[item["signature"] for item in self.read_declarations()],
)
def test_duplicate_class_and_unit_names_are_rejected_case_insensitively(self):
def test_duplicate_class_and_unit_names_are_preserved_in_input_order(self):
cases = (
(
self.write_nested_tsf(
@@ -1516,16 +1885,14 @@ class ConvertTsfCliTest(unittest.TestCase):
"重复 unit",
),
)
for first, second, expected in cases:
with self.subTest(expected=expected):
self.output.write_text("原内容\n", encoding="utf-8")
for first, second, kind in cases:
with self.subTest(kind=kind):
result = self.run_cli(first, second)
self.assertEqual(1, result.returncode)
self.assertIn(expected, result.stderr)
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual(
"原内容\n", self.output.read_text(encoding="utf-8")
2,
len(self.read_declarations()),
)
def test_same_name_across_kinds_is_allowed(self):
@@ -1574,16 +1941,100 @@ class ConvertTsfCliTest(unittest.TestCase):
[item["name"] for item in self.read_declarations()],
)
def test_top_level_procedure_remains_unsupported(self):
def test_top_level_procedure_creates_function_draft(self):
source = self.write_tsf(
"ProcedureDemo",
"procedure ProcedureDemo();\nbegin\nend;\n",
"procedure ProcedureDemo(value: integer);\nbegin\nend;\n",
)
result = self.run_cli(source)
self.assertEqual(1, result.returncode)
self.assertIn("目前支持独立顶层 function、class 和完整 unit", result.stderr)
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual(
{
"kind": "function",
"name": "ProcedureDemo",
"signature": "ProcedureDemo(value)",
"desc": "",
"params": [
{"name": "value", "type": "integer", "desc": ""}
],
"returns": "",
},
self.read_declaration(),
)
def test_malformed_top_level_function_keeps_recognizable_signature(self):
cases = {
"MissingBody": (
"function MissingBody(value: integer): array;\n",
"MissingBody",
[{"name": "value", "type": "integer", "desc": ""}],
"array",
),
"BrokenParams": (
"function BrokenParams(required: integer; optional = 1\n",
"BrokenParams",
[
{"name": "required", "type": "integer", "desc": ""},
{
"name": "optional",
"type": "",
"optional": True,
"desc": "",
},
],
"",
),
"MissingName": (
"function\n",
"MissingName",
[],
"",
),
}
for filename, (text, name, params, returns) in cases.items():
with self.subTest(filename=filename):
source = self.write_tsf(filename, text)
result = self.run_cli(source)
self.assertEqual(0, result.returncode, result.stderr)
declaration = self.read_declaration()
self.assertEqual(name, declaration["name"])
parameter_names = ", ".join(
parameter["name"] for parameter in params
)
self.assertEqual(
f"{name}({parameter_names})",
declaration["signature"],
)
self.assertEqual(params, declaration["params"])
self.assertEqual(returns, declaration["returns"])
def test_unrecognized_tsf_header_still_creates_function_draft(self):
cases = {
"LooseCall": (
"LegacyName(first, second)\n",
"LegacyName",
["first", "second"],
),
"EmptySource": ("", "EmptySource", []),
}
for filename, (text, name, params) in cases.items():
with self.subTest(filename=filename):
source = self.write_tsf(filename, text)
result = self.run_cli(source)
self.assertEqual(0, result.returncode, result.stderr)
declaration = self.read_declaration()
self.assertEqual("function", declaration["kind"])
self.assertEqual(name, declaration["name"])
self.assertEqual(
params,
[parameter["name"] for parameter in declaration["params"]],
)
if __name__ == "__main__":