#!/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" METHOD_OVERRIDES: dict[str, str] = { "startIfNeeded": "条件满足时启动轮询任务。", "runLoop": "按间隔持续轮询队列数据。", "pollOnce": "执行一次队列数据拉取与播报判断。", "pollIntervalSeconds": "根据前后台状态返回轮询间隔秒数。", "selectedSpotId": "读取用户当前选中的打卡点 ID。", "resetSnapshot": "重置播报状态与上次轮询快照。", "beginBackgroundTask": "申请后台任务,保证进入后台时仍可短时轮询。", "endBackgroundTask": "结束后台任务。", "resumePending": "恢复所有等待播报结束的 continuation。", "enqueue": "将语音文本加入播报队列,可选择替换待播内容。", "registerNavigationBridge": "向 UIKit 导航桥注册当前 Tab 的 NavigationController。", "syncFromRouterPath": "根据 Router 路径深度补齐或回退导航栈。", "syncRouterPathFromStack": "导航栈变化后裁剪 Router 路径,保持双向同步。", "observeContextChanges": "监听权限与账号上下文变化并刷新菜单。", "rebuildMenus": "按当前角色权限重建首页菜单与常用应用。", "menuItem": "按 URI 解析并返回可用菜单项。", "startCountdownTimerIfNeeded": "在线状态下启动位置上报倒计时。", "scenicTapped": "点击景区名称,跳转景区选择页。", "reminderTapped": "选择位置上报提前提醒时间。", "locationReportTapped": "跳转位置上报页面。", "paymentTapped": "跳转立即收款页面。", "taskCreateTapped": "跳转提交任务页面。", "onlineTapped": "切换在线/离线状态并更新倒计时。", "setupTopBar": "搭建首页顶部景区选择栏。", "setupTableView": "配置首页分组列表布局与注册 Cell。", "makeStatusCard": "构建在线状态与倒计时卡片。", "makeLocationCard": "构建位置上报提醒卡片。", "makeStoreCard": "构建门店信息卡片。", "makeQuickActionsRow": "构建快捷操作按钮行。", "configureViews": "初始化子视图与约束。", "refreshPresentation": "根据当前状态刷新展示或隐藏动画。", "showBanner": "展示 Toast 横幅动画。", "hideBanner": "隐藏 Toast 横幅动画。", "loadAnimation": "加载 Lottie 动画资源。", "route": "解析推送载荷并执行路由跳转。", "navigateHomeRoute": "切换至首页 Tab 并 Push 目标路由。", "hexString": "将二进制数据转为十六进制字符串。", "receiveLoop": "持续接收 WebSocket 消息并分发处理。", "stringValue": "安全地将任意值转为字符串。", "homeRouteURI": "将首页路由编码为 URI 字符串。", "make": "按路由参数创建 ViewController。", "fail": "记录校验失败并返回 false。", "normalizedError": "将后端错误信息归一化为用户可读中文。", "amountText": "格式化金额展示文本。", "summaryCard": "创建统计摘要卡片视图。", "selectPeriod": "切换统计周期并重新加载数据。", "reload": "加载或刷新页面数据。", "loadMore": "加载下一页列表数据。", "updateFilterTitle": "更新筛选条件标题展示。", "updateSummary": "刷新钱包摘要区域。", "setupHeader": "搭建页面头部区域。", "updateTitle": "刷新导航或页面标题。", "setupFormHeader": "搭建表单页头部说明区域。", "wireViewModel": "绑定 ViewModel 变更回调并触发列表刷新。", "bindViewModel": "绑定 ViewModel 数据变更并刷新 UI。", "handleRefresh": "下拉刷新触发,重新加载页面数据。", "mountChild": "按登录阶段挂载对应子控制器。", "updateScenicTitle": "刷新顶部景区名称展示。", } LINE_REPLACEMENTS: dict[str, str] = { "/// speechSynthesizer 方法实现。": "/// 语音播报生命周期回调。", "/// 处理相关事件。": "/// 更新轮询结果并根据队列变化触发语音播报。", "/// tableView 方法实现。": "/// UITableView 数据源或代理回调。", "/// 启动IfNeeded流程。": "/// 条件满足时启动轮询任务。", "/// 执行Loop循环或任务。": "/// 按间隔持续轮询队列数据。", "/// 轮询Once数据。": "/// 执行一次队列数据拉取与播报判断。", "/// 轮询IntervalSeconds数据。": "/// 根据前后台状态返回轮询间隔秒数。", "/// 重置Snapshot状态。": "/// 重置播报状态与上次轮询快照。", "/// 恢复Pending流程。": "/// 恢复所有等待播报结束的 continuation。", "/// 更新ScenicTitle状态。": "/// 刷新顶部景区名称展示。", "/// 绑定ViewModel回调或数据。": "/// 绑定 ViewModel 数据变更并刷新 UI。", "/// 响应lineTapped事件。": "/// 切换在线/离线状态并更新倒计时。", "/// observeContextChanges 方法实现。": "/// 监听权限与账号上下文变化并刷新菜单。", "/// rebuildMenus 方法实现。": "/// 按当前角色权限重建首页菜单与常用应用。", "/// menuItem 方法实现。": "/// 按 URI 解析并返回可用菜单项。", "/// 启动CountdownTimerIfNeeded流程。": "/// 在线状态下启动位置上报倒计时。", "/// scenicTapped 方法实现。": "/// 点击景区名称,跳转景区选择页。", "/// reminderTapped 方法实现。": "/// 选择位置上报提前提醒时间。", "/// locationReportTapped 方法实现。": "/// 跳转位置上报页面。", "/// paymentTapped 方法实现。": "/// 跳转立即收款页面。", "/// taskCreateTapped 方法实现。": "/// 跳转提交任务页面。", "/// selectedSpotId 方法实现。": "/// 读取用户当前选中的打卡点 ID。", "/// beginBackgroundTask 方法实现。": "/// 申请后台任务,保证进入后台时仍可短时轮询。", "/// endBackgroundTask 方法实现。": "/// 结束后台任务。", } METHOD_LINE = re.compile(r"^(?P\s*)/// (?P\w+) 方法实现。\s*$") FUNC_LINE = re.compile(r"^\s*(?:@\w+(?:\([^)]*\))?\s+)*(?:override\s+|private\s+|static\s+)*func\s+(?P\w+)") WORD_HINTS: dict[str, str] = { "Tapped": "点击", "Setup": "初始化", "Configure": "配置", "Update": "更新", "Reload": "刷新", "Load": "加载", "Fetch": "请求", "Create": "创建", "Delete": "删除", "Clear": "清空", "Copy": "复制", "Show": "展示", "Hide": "隐藏", "Start": "启动", "Stop": "停止", "Cancel": "取消", "Confirm": "确认", "Apply": "提交", "Parse": "解析", "Format": "格式化", "Decode": "解码", "Encode": "编码", "Validate": "校验", "Submit": "提交", "Toggle": "切换", "Refresh": "刷新", "Build": "构建", "Make": "创建", "Present": "弹出", "Dismiss": "关闭", "Select": "选择", "Filter": "筛选", "Handle": "处理", "Check": "检查", "Request": "请求", "Register": "注册", "Bind": "绑定", "Sync": "同步", "Mount": "挂载", "Embed": "嵌入", "Generate": "生成", "Edit": "编辑", "Add": "添加", "Reset": "重置", "Save": "保存", "Open": "打开", "Close": "关闭", "Login": "登录", "Logout": "登出", "Complete": "完成", "Continue": "继续", "Consume": "消费", "Dedupe": "去重", "Scanner": "扫码", "Camera": "相机", "Permission": "权限", "Avatar": "头像", "Header": "头部", "Card": "卡片", "Row": "行", "Field": "字段", "Timer": "计时器", "Countdown": "倒计时", "Queue": "排队", "Order": "订单", "Task": "任务", "Project": "项目", "Scenic": "景区", "Store": "门店", "Wallet": "钱包", "Payment": "支付", "Album": "相册", "Schedule": "排班", "Invite": "邀请", "Message": "消息", "Location": "位置", "Live": "直播", "Flyer": "飞手", "Punch": "打卡", "Detail": "详情", "List": "列表", "Summary": "摘要", "Amount": "金额", "Code": "验证码", "QR": "二维码", "URL": "链接", "Text": "文本", "Title": "标题", "Form": "表单", "Keyboard": "键盘", "Business": "营业", "Time": "时间", "Date": "日期", "Folder": "文件夹", "File": "文件", "Broadcast": "播报", "Voice": "语音", "Background": "后台", "Foreground": "前台", "Remote": "远程", "Scan": "扫码", "Verify": "验证", "Collection": "集合", "Table": "列表", "View": "视图", "Model": "模型", "Data": "数据", "Payload": "载荷", "Snapshot": "快照", "Runtime": "运行时", "Settings": "设置", "Menu": "菜单", "Route": "路由", "Navigation": "导航", "Tab": "Tab", "Badge": "角标", "Filter": "筛选", "Search": "搜索", "Upload": "上传", "Download": "下载", "Image": "图片", "Video": "视频", "Audio": "音频", "Map": "地图", "Spot": "打卡点", "Ticket": "票号", "Stats": "统计", "Report": "上报", "Audit": "审核", "Settlement": "结算", "Withdrawal": "提现", "Deposit": "押金", "Refund": "退款", "WriteOff": "核销", "Pilot": "飞手", "Certification": "认证", "RealName": "实名", "Account": "账号", "Switch": "切换", "Profile": "个人中心", "Auth": "认证", "Session": "会话", "Token": "令牌", "Push": "推送", "Notification": "通知", "Socket": "WebSocket", "Client": "客户端", "Provider": "提供者", "Factory": "工厂", "Helper": "工具", "Extension": "扩展", "IfNeeded": "(按需)", "And": "并", "Or": "或", "From": "从", "To": "至", "For": "用于", "With": "附带", "Without": "不带", "All": "全部", "Current": "当前", "Selected": "选中", "Pending": "待处理", "Active": "活跃", "Empty": "空", "Default": "默认", "Custom": "自定义", "Local": "本地", "Remote": "远程", "Legacy": "兼容旧版", "Normalized": "规范化", "Coerced": "强制转换", "Lossy": "宽松", "Deduplicated": "去重", "Sorted": "排序", "Changed": "变更", "Interval": "间隔", "Threshold": "阈值", "Positive": "正数", "Decimal": "小数", "Int": "整数", "String": "字符串", "Bool": "布尔", "Value": "值", "Object": "对象", "JSON": "JSON", "LatLng": "经纬度", "LngLat": "经纬度", "Pair": "坐标对", "LooksLike": "判断是否符合", "Fill": "填充", "Cycle": "循环切换", "ApplyViewModel": "应用 ViewModel 状态", "Wire": "连接", "Dispose": "释放订阅", "Embed": "嵌入", "Labeled": "带标签", "LongValid": "长期有效", "ClearListAndDetail": "清空列表与详情", "ClearQueueData": "清空排队数据", "ClearMessages": "清空消息", "ClearFolders": "清空文件夹", "CheckPermissionAndStart": "检查权限并启动", "ConsumePendingRemoteCalledTickets": "消费待处理的远程叫号", "ConsumePendingScanCodeIfNeeded": "按需消费待处理扫码结果", "FillFormIfNeeded": "按需回填表单", "CopyOrderNumber": "复制订单号", "CopyDownloadLink": "复制下载链接", "CopyCode": "复制验证码", "CopyURL": "复制链接", "ConfirmVerify": "确认验证码", "CreatePunchPoint": "创建打卡点", "CreateTask": "创建任务", "CreateProject": "创建项目", "AddSchedule": "添加排班", "ApplyAmount": "提交金额", "BusinessTime": "营业时间", "BusinessTimePayload": "构建营业时间请求体", "AvatarPlaceholder": "生成头像占位图", "FormatQueueTime": "格式化排队时间", "FromJSONObject": "从 JSON 对象解析", "GenerateQRCode": "生成二维码", "Int64Value": "解析 Int64 值", "IntValue": "解析 Int 值", "DecimalValue": "解析小数值", "EmptyToZero": "空值转零", "FileType": "解析文件类型", "DecodeLossyBool": "宽松解码布尔值", "DecodeLossyDouble": "宽松解码浮点数", "DecodeLossyInt": "宽松解码整数", "DecodeLossyString": "宽松解码字符串", "LiveDecodeLossyInt": "直播模块宽松解码整数", "LiveDecodeLossyString": "直播模块宽松解码字符串", "CoercedBroadcastInterval": "规范化播报间隔", "CoercedCountdownThreshold": "规范化倒计时阈值", "LegacyPositiveInt": "兼容旧版正整数解析", "DeduplicatedAndSorted": "去重并排序", "CompleteLogin": "完成登录流程", "LoginTapped": "点击登录按钮", "LogoutTapped": "点击登出", "CancelTapped": "点击取消", "ConfirmTapped": "点击确认", "CloseTapped": "点击关闭", "ContinueTapped": "点击继续", "EditTapped": "点击编辑", "DetailTapped": "点击详情", } def split_camel(name: str) -> list[str]: parts = re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", name).split() return parts def describe_method(name: str) -> str: if name in METHOD_OVERRIDES: return METHOD_OVERRIDES[name] if name in WORD_HINTS: return f"{WORD_HINTS[name]}。" # 尝试从最长前缀/后缀匹配组合词 if name.endswith("Tapped"): base = name[:-6] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"点击{hint}的处理逻辑。" if name.endswith("IfNeeded"): base = name[:-8] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"按需{hint}。" if name.startswith("make"): base = name[4:] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"创建{hint}。" if name.startswith("setup"): base = name[5:] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"初始化{hint}。" if name.startswith("update"): base = name[6:] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"更新{hint}。" if name.startswith("load"): base = name[4:] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"加载{hint}。" if name.startswith("clear"): base = name[5:] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"清空{hint}。" if name.startswith("copy"): base = name[4:] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"复制{hint}。" if name.startswith("create"): base = name[6:] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"创建{hint}。" if name.startswith("decode"): base = name[6:] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"解码{hint}。" if name.startswith("format"): base = name[6:] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"格式化{hint}。" if name.startswith("live"): base = name[4:] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"直播{hint}相关逻辑。" if name.startswith("flyer"): base = name[5:] hint = "".join(WORD_HINTS.get(p, p) for p in split_camel(base)) or base return f"飞手{hint}相关逻辑。" parts = split_camel(name) if len(parts) == 1: return f"{name} 业务逻辑。" hint = "".join(WORD_HINTS.get(p, p) for p in parts) return f"{hint}相关逻辑。" def process_file(path: Path) -> bool: lines = path.read_text(encoding="utf-8").splitlines(keepends=True) output: list[str] = [] changed = False for i, line in enumerate(lines): stripped = line.rstrip("\n") if stripped in LINE_REPLACEMENTS: indent = re.match(r"^(\s*)", line).group(1) output.append(f"{indent}{LINE_REPLACEMENTS[stripped]}\n") changed = True continue match = METHOD_LINE.match(stripped) if match: indent = match.group("indent") name = match.group("name") output.append(f"{indent}/// {describe_method(name)}\n") changed = True continue if stripped.startswith("/// ") and i + 1 < len(lines): func_match = FUNC_LINE.match(lines[i + 1]) if func_match: name = func_match.group("name") if name in METHOD_OVERRIDES and ("方法实现" in stripped or name in stripped): indent = re.match(r"^(\s*)", line).group(1) output.append(f"{indent}/// {METHOD_OVERRIDES[name]}\n") changed = True continue output.append(line) 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 count = sum(1 for path in sorted(target.rglob("*.swift")) if process_file(path)) print(f"Polished {count} files") return 0 if __name__ == "__main__": raise SystemExit(main())