fix(amodel-gen): API 静态门方法名提取支持泛型声明(治 save.get<T> 被误判不存在)

#5 九门 gen 暴露的正交真 bug:check 的 API-存在静态门用正则
/(?:^|\n)\s*(\w+)\??\s*\(/ 抽 api.d.ts 方法名,遇泛型声明 `get<T = unknown>(`
时 \w+ 后是 `<` 非 `(`、抽不到 `get`,把真实存在(impl + api.d.ts:79 都有)的
save.get 误判"方法名幻觉不存在"→ 误杀一整类带存档持久化的游戏(高分榜等)。
修:两处方法名正则加可选泛型段 (?:<[^>]*>)?(apiMethodSet + apiVoidMethodSet)。

验证:形状门单测 11/11;针对性 check 一段同用 save.get('best',0)+sessionScore.add+
drawButton 的 game-logic → [check] PASS(三者均不再误判)。over-permissive 取向不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-29 09:37:03 -07:00
parent dc49f5f866
commit 13126a08ca

View File

@ -151,14 +151,15 @@ const PLUGIN_KEY_DIR = {
/** PluginContext(boot.ctx)真方法集 + 运行时附加 log;time/random 是属性对象(ctx.time.nowMs()/ctx.random.next() 内层 time/random≠ctx,经"非键跳过"放行)。 */
const CTX_METHODS = new Set(['getContext2d', 'onFrame', 'getInput', 'getAudioContext', 'getEngine', 'log']);
const _apiCache = new Map();
/** 读某插件 api.d.ts 抽其声明的方法名集(宽松:取所有「名(」形态,over-permissive=少误报)。返回 Set|null。 */
/** 读某插件 api.d.ts 抽其声明的方法名集(宽松:取所有「名(」形态,含泛型方法 名<T>(,over-permissive=少误报)。返回 Set|null。 */
function apiMethodSet(dir) {
if (_apiCache.has(dir)) return _apiCache.get(dir);
let set = null;
try {
const txt = readFileSync(join(GAME_RUNTIME, 'src', 'plugins', dir, 'api.d.ts'), 'utf8');
set = new Set();
for (const m of txt.matchAll(/(?:^|\n)\s*(\w+)\??\s*\(/g)) set.add(m[1]);
// 名后可带可选 ? 与泛型形参 <…>(如 save.get<T = unknown>(…)):漏认泛型会把真实方法误判"不存在"、误杀带存档的游戏。
for (const m of txt.matchAll(/(?:^|\n)\s*(\w+)\??\s*(?:<[^>]*>)?\s*\(/g)) set.add(m[1]);
} catch { set = null; }
_apiCache.set(dir, set);
return set;
@ -177,7 +178,7 @@ function apiVoidMethodSet(dir) {
const txt = readFileSync(join(GAME_RUNTIME, 'src', 'plugins', dir, 'api.d.ts'), 'utf8');
set = new Set();
// 逐行锚定:行首(可带 export declare function / readonly 修饰)的「名(…): void」;参数里的 `=> void` 不以「): void」收尾、不误收。
for (const m of txt.matchAll(/^\s*(?:export\s+declare\s+function\s+|readonly\s+)?(\w+)\??\s*\([^\n]*\)\s*:\s*void\b/gm)) set.add(m[1]);
for (const m of txt.matchAll(/^\s*(?:export\s+declare\s+function\s+|readonly\s+)?(\w+)\??\s*(?:<[^>]*>)?\s*\([^\n]*\)\s*:\s*void\b/gm)) set.add(m[1]);
} catch { set = null; }
_voidCache.set(dir, set);
return set;