1. extension.ts
| # |
问题 |
严重度 |
说明与建议 |
| 1 |
let watcher = null; 后续没有明确赋值就使用,虽然逻辑上一定会赋值,但 TypeScript 会抱怨可能为 null |
★★☆ |
改用 let watcher: vscode.FileSystemWatcher; 并确保两个分支都赋值 |
| 2 |
import { existsSync, fstat } from "fs"; 却没用到 fstat,而且下面又 import "fs";(无效写法) |
★★☆ |
移除无用 import,统一使用 import * as fs from "fs"; 或只 import 需要的 |
| 3 |
格式化 provider 硬编码检查 firstLine.text !== "42" 并插入 "42\n",这明显是测试/占位代码 |
★★★ |
应移除或注解掉,否则会破坏正常 js 档案格式化 |
| 4 |
context.subscriptions.push(metaProvideHover()); 只在「没有指定 target」时注册,指定 target 后就不注册 hover → 不一致体验 |
★★☆ |
建议无论是否指定 target 都注册 hover(hover 是语言层级功能,与同步目标无关) |
| 5 |
deactivate() 完全空实作,没有清理任何资源 |
★★☆ |
至少把主要的 watcher、同步器等 dispose(虽然有 push subscriptions,但不够明确) |
| 6 |
mSync.changeTargetScript() 传入全新 watcher,但旧 watcher 没有 dispose |
★★★ |
旧 watcher 会持续监听,造成泄漏 |
建议修正片段(重点部分)
// extension.ts 开头
import * as vscode from "vscode";
import * as fs from "fs"; // 统一
import { metaProvideHover } from "./provide";
// ... 其他 import
export function activate(context: vscode.ExtensionContext) {
const signatureFileCommand = "scriptcat.target";
const autoTargetCommand = "scriptcat.autoTarget";
let watcher: vscode.FileSystemWatcher;
const target = context.workspaceState.get<string>("target");
const registerCommonProviders = () => {
context.subscriptions.push(metaProvideHover());
context.subscriptions.push(registerUserScriptHighlighter());
context.subscriptions.push(...registerUserScriptDiagnostics());
};
if (target && fs.existsSync(target)) {
vscode.window.showInformationMessage(`工作区已选定脚本:${target}`);
watcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(target, "*"),
false, false, false
);
} else {
watcher = vscode.workspace.createFileSystemWatcher("**/*.user.js", false, false, false);
registerCommonProviders(); // 统一注册
}
const mSync = new Synchronizer(watcher, context);
context.subscriptions.push(mSync); // 如果 Synchronizer 实作 Disposable 更好
// 无论如何都注册这些(比较合理)
registerCommonProviders();
checkAndPromptSemanticHighlighting();
// 格式化 provider 应移除或改成真的 formatter
// vscode.languages.registerDocumentFormattingEditProvider(...) ← 建议注解或移除
// command 里的 changeTargetScript 前应先 dispose 旧 watcher
// ...
}
2. sync.ts
| # |
问题 |
严重度 |
说明与建议 |
| 1 |
onDidChange 与 onDidCreate 都呼叫 onChange,但没处理 onDidDelete(脚本删除时不通知) |
★★☆ |
补上 watcher.onDidDelete 发送 delete 事件 |
| 2 |
快速连续保存时会多次读档、多次发送相同内容,没有 debounce |
★★☆ |
加 debounce (例如 300ms) |
| 3 |
sendMessageViaFile 使用 Date.now() + Math.random() 档名,极低机率仍可能 collision |
★☆☆ |
可改用 uuid 或递增序号 |
| 4 |
档案清理使用 setTimeout(5000),但如果 VSCode 这段时间关闭,垃圾档案会残留 |
★★☆ |
启动时扫描并清理旧 message-*.json |
| 5 |
messageHandler 完全没实作,注册了却什么都不做 |
★★☆ |
如果暂时不需要,应移除;若未来要用,应明确定义讯息类型 |
| 6 |
Synchronizer 本身没有实作 Disposable,但被 push 进去 dispose: () => mSync.close() |
★★☆ |
建议让 Synchronizer 实作 vscode.Disposable |
3. provide.ts (目前最干净,但功能极简)
| # |
问题 |
严重度 |
说明与建议 |
| 1 |
正则 `^//\s*@(\w+?)(\s+(.*?) |
)$无法处理@grant GM_xmlhttpRequest` 后面的多余空格 |
★★☆ |
| 2 |
只支援 // @key 单行,不支援 /* @key ... */ 多行注解 |
★★☆ |
目前多数现代油猴脚本仍用 //,但应考虑支援 |
| 3 |
hover 内容只是中文名称,资讯量极低 |
★☆☆ |
可加入官方文件连结或更详细说明 |
| 4 |
没有处理 @ 后面不是标准 key 的情况(会显示 undefined) |
★☆☆ |
加 fallback 提示「未知的元数据」 |
4. globalWebSocketManager.ts (最复杂、最多问题)
这是目前最容易出问题的部分。
| # |
问题 |
严重度 |
说明与建议 |
| 1 |
没有真正的心跳 + pong 处理,val.ping() 发出但没监听 pong,无法侦测真正断线 |
★★★ |
应实作 ping/pong 机制,并在超时时主动 close |
| 2 |
setupFileWatcher 只在主窗口启动,但从窗口也需要监听 sharedDir 来转发消息?目前设计是只有主窗口监听 |
★★★ |
目前从窗口发文件 → 主窗口监听并广播,设计合理;但应在 start() 失败时也让从窗口监听一次(防主窗口意外关闭) |
| 3 |
checkExistingServer 读 portFile 后只检查端口是否占用,没验证是不是真的 scriptcat 服务 |
★★☆ |
低风险,但可接受 |
| 4 |
stop() 没被任何人呼叫,导致多开关闭后 portFile 残留,下次启动误判 |
★★★ |
在 deactivate() 或最后一个窗口关闭时呼叫 GlobalWebSocketManager.getInstance().stop() |
| 5 |
broadcast 没检查 client.readyState,虽然有但写法不够严谨 |
★☆☆ |
已写,但可改成 if (client.readyState === WebSocket.OPEN) |
| 6 |
完全没有处理 WebSocket upgrade 错误、连线数限制等 |
★★☆ |
小型工具可接受,但生产级应加限制 |
整体架构层级问题建议
-
多窗口竞争与接管问题
目前靠 port + 档案锁,基本可行,但极不稳定(例如主窗口 crash 没清理 portFile)。
→ 考虑改用更稳定的方案:
- 固定端口 + 选举机制(第一个启动的为主)
- 或使用本地 socket 文件(unix domain socket)
- 或干脆改用 VS Code 内建的 Named Pipe / IPC(但较复杂)
-
缺少日志与错误回馈
大量使用 console.log,建议引入简单的 output channel。
-
缺少测试
几乎不可能单元测试目前架构,建议至少把 parser / hover 逻辑抽出来测。
目前最优先修复的项目:
- extension.ts 格式化 provider 的测试代码移除
- globalWebSocketManager 的 stop() 时机与 portFile 清理
- sync.ts 的 debounce 与 onDidDelete 处理
- Synchronizer 实作 Disposable 并正确 dispose watcher
1. extension.ts
let watcher = null;后续没有明确赋值就使用,虽然逻辑上一定会赋值,但 TypeScript 会抱怨可能为 nulllet watcher: vscode.FileSystemWatcher;并确保两个分支都赋值import { existsSync, fstat } from "fs";却没用到fstat,而且下面又import "fs";(无效写法)import * as fs from "fs";或只 import 需要的firstLine.text !== "42"并插入 "42\n",这明显是测试/占位代码context.subscriptions.push(metaProvideHover());只在「没有指定 target」时注册,指定 target 后就不注册 hover → 不一致体验deactivate()完全空实作,没有清理任何资源mSync.changeTargetScript()传入全新 watcher,但旧 watcher 没有 dispose建议修正片段(重点部分)
2. sync.ts
onDidChange与onDidCreate都呼叫onChange,但没处理onDidDelete(脚本删除时不通知)watcher.onDidDelete发送 delete 事件sendMessageViaFile使用Date.now() + Math.random()档名,极低机率仍可能 collisionsetTimeout(5000),但如果 VSCode 这段时间关闭,垃圾档案会残留messageHandler完全没实作,注册了却什么都不做Synchronizer本身没有实作Disposable,但被 push 进去dispose: () => mSync.close()Synchronizer实作vscode.Disposable3. provide.ts (目前最干净,但功能极简)
无法处理@grant GM_xmlhttpRequest` 后面的多余空格// @key单行,不支援/* @key ... */多行注解@后面不是标准 key 的情况(会显示 undefined)4. globalWebSocketManager.ts (最复杂、最多问题)
这是目前最容易出问题的部分。
val.ping()发出但没监听 pong,无法侦测真正断线setupFileWatcher只在主窗口启动,但从窗口也需要监听 sharedDir 来转发消息?目前设计是只有主窗口监听start()失败时也让从窗口监听一次(防主窗口意外关闭)checkExistingServer读 portFile 后只检查端口是否占用,没验证是不是真的 scriptcat 服务stop()没被任何人呼叫,导致多开关闭后 portFile 残留,下次启动误判deactivate()或最后一个窗口关闭时呼叫GlobalWebSocketManager.getInstance().stop()broadcast没检查client.readyState,虽然有但写法不够严谨if (client.readyState === WebSocket.OPEN)整体架构层级问题建议
多窗口竞争与接管问题
目前靠 port + 档案锁,基本可行,但极不稳定(例如主窗口 crash 没清理 portFile)。
→ 考虑改用更稳定的方案:
缺少日志与错误回馈
大量使用
console.log,建议引入简单的 output channel。缺少测试
几乎不可能单元测试目前架构,建议至少把 parser / hover 逻辑抽出来测。
目前最优先修复的项目: