Files
suixinkan_ios_uikit/Scripts/add_swift_doc_comments.py
汉秋 d99a5b1bf8 Advance UIKit rewrite with AMap integration and core UI modules.
Integrate高德 SDK with simulator-safe build flags, add map views for operating area and punch points, refactor main tabs and key feature screens to UIKit with Diffable lists, and document Swift concurrency defaults in AGENTS.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 15:16:12 +08:00

281 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""为 Swift 源码中缺少 /// 的类型与方法补充中文文档注释。"""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1] / "suixinkan_ios"
SKIP_DIRS = {"Pods", ".build", "DerivedData"}
TYPE_DECL = re.compile(
r"^(?P<indent>\s*)"
r"(?:(?:@\w+(?:\([^)]*\))?\s+|@MainActor\s+)*)"
r"(?:(?P<modifiers>(?:final|private|fileprivate|public|internal|open)\s+)*)"
r"(?P<kind>class|struct|enum|protocol|actor)\s+(?P<name>\w+)"
)
FUNC_DECL = re.compile(
r"^(?P<indent>\s*)"
r"(?:(?:@\w+(?:\([^)]*\))?\s+|@MainActor\s+|@objc\s+)*)"
r"(?:(?P<modifiers>(?:override|private|fileprivate|public|internal|static|class|mutating|nonisolated)\s+)*)"
r"func\s+(?P<name>\w+)"
)
INIT_DECL = re.compile(
r"^(?P<indent>\s*)"
r"(?:(?:@\w+(?:\([^)]*\))?\s+|@available\([^)]+\)\s+)*)"
r"(?:(?:override|required|convenience|private|fileprivate|public|internal)\s+)*"
r"init(\?|\()"
)
METHOD_DOCS: dict[str, str] = {
"viewDidLoad": "视图加载完成后的 UI 初始化与数据绑定。",
"viewWillAppear": "视图即将展示,刷新可见状态。",
"viewDidAppear": "视图已展示,执行需等待布局完成的逻辑。",
"viewWillDisappear": "视图即将消失,保存或清理临时状态。",
"viewDidDisappear": "视图已消失。",
"viewDidLayoutSubviews": "子视图布局完成后调整依赖 frame 的 UI。",
"deinit": "释放资源并解除绑定。",
"numberOfSections": "返回列表 section 数量。",
"numberOfRowsInSection": "返回指定 section 的行数。",
"cellForRowAt": "配置并返回指定 indexPath 的 Cell。",
"didSelectRowAt": "处理行选中事件。",
"heightForRowAt": "返回指定行高度。",
"titleForHeaderInSection": "返回 section 标题。",
"numberOfItemsInSection": "返回指定 section 的 item 数量。",
"cellForItemAt": "配置并返回指定 indexPath 的 Collection Cell。",
"didSelectItemAt": "处理 item 选中事件。",
"sizeForItemAt": "返回 item 尺寸。",
"makeUIViewController": "创建并返回对应路由的 ViewController。",
"updateUIViewController": "更新 ViewController 状态。",
"encode": "编码为可持久化或传输的数据。",
"decode": "从外部数据解码为模型实例。",
"hash": "计算哈希值,用于集合与 Diffable 标识。",
"==": "判断两个实例是否相等。",
}
TYPE_SUFFIX_DOCS: list[tuple[str, str]] = [
("ViewController", "页面控制器,负责 UI 展示与用户交互。"),
("ViewModel", "视图模型,负责业务逻辑与状态管理。"),
("TableViewCell", "列表 Cell负责单行内容展示。"),
("CollectionViewCell", "集合视图 Cell负责单项内容展示。"),
("Cell", "列表或网格 Cell负责单项内容展示。"),
("Serving", "服务协议,定义模块对外能力。"),
("API", "网络接口封装。"),
("Store", "本地持久化或内存存储。"),
("Provider", "能力提供者,封装外部依赖。"),
("Coordinator", "流程协调器,串联多步业务。"),
("Router", "路由跳转封装。"),
("Manager", "管理器,负责模块级生命周期与调度。"),
("Service", "服务层,封装可复用业务能力。"),
("Error", "错误类型定义。"),
("State", "状态实体。"),
("Context", "上下文,持有跨页面共享状态。"),
("Delegate", "代理协议或实现。"),
("DataSource", "数据源协议或实现。"),
("Loader", "加载器,负责异步拉取与组装数据。"),
("Runtime", "运行时调度器,负责后台任务与状态同步。"),
("Client", "客户端封装,负责与外部服务通信。"),
("Factory", "工厂,负责按路由或参数创建 ViewController。"),
("Driver", "驱动器,供 UI 测试或自动化场景使用。"),
]
def preceding_doc_index(lines: list[str], index: int) -> int | None:
"""向上查找最近的 /// 或阻断性声明,返回 /// 行号。"""
j = index - 1
while j >= 0:
stripped = lines[j].strip()
if stripped == "":
j -= 1
continue
if stripped.startswith("///"):
return j
if stripped.startswith("@") or stripped.startswith("// MARK:"):
j -= 1
continue
break
return None
def has_doc_comment(lines: list[str], index: int) -> bool:
return preceding_doc_index(lines, index) is not None
def camel_to_chinese_hint(name: str) -> str:
if name in METHOD_DOCS:
return METHOD_DOCS[name]
if name.startswith("handle"):
return f"处理{name[6:]}相关事件。"
if name.startswith("load"):
return f"加载{name[4:]}数据。"
if name.startswith("fetch"):
return f"请求{name[5:]}数据。"
if name.startswith("reload"):
return f"刷新{name[6:]}"
if name.startswith("update"):
return f"更新{name[6:]}状态。"
if name.startswith("configure"):
return f"配置{name[9:]}展示内容。"
if name.startswith("setup"):
return f"初始化{name[5:]}相关 UI 或状态。"
if name.startswith("bind"):
return f"绑定{name[4:]}回调或数据。"
if name.startswith("make"):
return f"创建{name[4:]}实例。"
if name.startswith("wire"):
return f"连接{name[4:]}与 UI 刷新逻辑。"
if name.startswith("sync"):
return f"同步{name[4:]}状态。"
if name.startswith("register"):
return f"注册{name[8:]}"
if name.startswith("did"):
return f"{name} 回调处理。"
if name.startswith("will"):
return f"{name} 回调处理。"
if name.startswith("on"):
return f"响应{name[2:]}事件。"
if name.startswith("is") or name.startswith("has"):
return f"判断{name}条件。"
if name.startswith("validate"):
return f"校验{name[8:]}输入或状态。"
if name.startswith("submit"):
return f"提交{name[6:]}"
if name.startswith("show"):
return f"展示{name[4:]}"
if name.startswith("hide"):
return f"隐藏{name[4:]}"
if name.startswith("present"):
return f"弹出{name[7:]}页面。"
if name.startswith("push"):
return f"Push {name[4:]}页面。"
if name.startswith("pop"):
return f"Pop {name[3:]}页面。"
if name.startswith("start"):
return f"启动{name[5:]}流程。"
if name.startswith("stop"):
return f"停止{name[4:]}流程。"
if name.startswith("reset"):
return f"重置{name[5:]}状态。"
if name.startswith("parse"):
return f"解析{name[5:]}数据。"
if name.startswith("normalize"):
return f"规范化{name[9:]}格式。"
if name.startswith("refresh"):
return f"刷新{name[7:]}展示。"
if name.startswith("attach"):
return f"挂载{name[6:]}到目标视图层级。"
if name.startswith("mount"):
return f"挂载{name[5:]}子控制器。"
if name.startswith("run"):
return f"执行{name[3:]}循环或任务。"
if name.startswith("poll"):
return f"轮询{name[4:]}数据。"
if name.startswith("resume"):
return f"恢复{name[6:]}流程。"
if name.startswith("enqueue"):
return f"入队等待{name[7:]}处理。"
if name.startswith("route"):
return f"根据{name[5:]}执行路由跳转。"
if name.startswith("navigate"):
return f"导航至{name[8:]}页面。"
if name == "init":
return "初始化实例。"
return f"{name} 方法实现。"
def type_doc(name: str, kind: str) -> str:
for suffix, desc in TYPE_SUFFIX_DOCS:
if name.endswith(suffix):
prefix = name[: -len(suffix)]
if prefix:
return f"{prefix}{desc}"
return desc
kind_map = {
"enum": "枚举,定义相关常量或状态。",
"struct": "结构体,封装数据实体。",
"protocol": "协议,定义能力约束。",
"actor": "Actor保证并发安全的状态访问。",
"class": "类,封装业务逻辑或 UI 组件。",
}
return kind_map.get(kind, f"{name} 类型定义。")
def should_skip_init(line: str) -> bool:
return "unavailable" in line or "init?(coder" in line
def should_skip_func(name: str) -> bool:
return name in {"body", "callAsFunction", "previewLayout"}
def process_file(path: Path) -> bool:
text = path.read_text(encoding="utf-8")
lines = text.splitlines(keepends=True)
output: list[str] = []
changed = False
i = 0
while i < len(lines):
line = lines[i]
stripped = line.rstrip("\n")
type_match = TYPE_DECL.match(stripped)
func_match = FUNC_DECL.match(stripped)
init_match = INIT_DECL.match(stripped)
if type_match and not has_doc_comment(lines, i):
indent = type_match.group("indent")
name = type_match.group("name")
kind = type_match.group("kind")
output.append(f"{indent}/// {type_doc(name, kind)}\n")
changed = True
elif func_match and not has_doc_comment(lines, i):
name = func_match.group("name")
if not should_skip_func(name):
indent = func_match.group("indent")
output.append(f"{indent}/// {camel_to_chinese_hint(name)}\n")
changed = True
elif init_match and not has_doc_comment(lines, i) and not should_skip_init(stripped):
indent = init_match.group("indent")
output.append(f"{indent}/// 初始化实例。\n")
changed = True
output.append(line)
i += 1
if changed:
path.write_text("".join(output), encoding="utf-8")
return changed
def main() -> int:
target = Path(sys.argv[1]) if len(sys.argv) > 1 else ROOT
changed_files: list[str] = []
for _ in range(3):
round_changed: list[str] = []
for path in sorted(target.rglob("*.swift")):
if any(part in SKIP_DIRS for part in path.parts):
continue
if process_file(path):
round_changed.append(str(path.relative_to(ROOT.parent)))
changed_files = round_changed
if not round_changed:
break
print(f"Updated {len(changed_files)} files in last pass")
for file in changed_files:
print(f" - {file}")
return 0
if __name__ == "__main__":
raise SystemExit(main())