Files
esengine/packages/worker-generator/src/parser.ts
YHH dfd0dfc7f9 feat(worker): 添加微信小游戏 Worker 支持和 Worker Generator CLI (#297)
* feat(worker): 添加微信小游戏 Worker 支持和 Worker Generator CLI

- 新增 @esengine/worker-generator 包,用于从 WorkerEntitySystem 生成 Worker 文件
- WorkerEntitySystem 添加 workerScriptPath 配置项,支持预编译 Worker 脚本
- CLI 工具支持 --wechat 模式,自动转换 ES6+ 为 ES5 语法
- 修复微信小游戏 Worker 消息格式差异(res 直接是数据,无需 .data)
- 更新中英文文档,添加微信小游戏支持章节

* docs: 更新 changelog,添加 v2.3.1 说明并标注 v2.3.0 为废弃

* fix: 修复 CI 检查问题

- 移除 cli.ts 中未使用的 toKebabCase 函数
- 修复 generator.ts 中正则表达式的 ReDoS 风险(使用 [ \t] 替代 \s*)
- 更新 changelog 版本号(2.3.1 -> 2.3.2)

* docs: 移除未发布版本的 changelog 条目

* fix(worker-generator): 使用 TypeScript 编译器替代手写正则进行 ES5 转换

- 修复 CodeQL 检测的 ReDoS 安全问题
- 使用 ts.transpileModule 进行安全可靠的代码转换
- 移除所有可能导致回溯的正则表达式
2025-12-08 17:02:11 +08:00

274 lines
8.2 KiB
TypeScript

/**
* AST 解析器 - 提取 WorkerEntitySystem 子类信息
* AST Parser - Extract WorkerEntitySystem subclass information
*/
import { Project, SyntaxKind, ClassDeclaration, MethodDeclaration, Node } from 'ts-morph';
import * as path from 'path';
import type { WorkerSystemInfo, GeneratorConfig } from './types';
/**
* 解析项目中的 WorkerEntitySystem 子类
* Parse WorkerEntitySystem subclasses in the project
*/
export function parseWorkerSystems(config: GeneratorConfig): WorkerSystemInfo[] {
const project = new Project({
tsConfigFilePath: config.tsConfigPath,
skipAddingFilesFromTsConfig: true,
});
// 添加源文件
// Add source files
const globPattern = path.join(config.srcDir, '**/*.ts').replace(/\\/g, '/');
project.addSourceFilesAtPaths(globPattern);
const results: WorkerSystemInfo[] = [];
for (const sourceFile of project.getSourceFiles()) {
const filePath = sourceFile.getFilePath();
// 跳过 node_modules 和 .d.ts 文件
// Skip node_modules and .d.ts files
if (filePath.includes('node_modules') || filePath.endsWith('.d.ts')) {
continue;
}
for (const classDecl of sourceFile.getClasses()) {
const info = extractWorkerSystemInfo(classDecl, filePath, config.verbose);
if (info) {
results.push(info);
}
}
}
return results;
}
/**
* 检查类是否继承自 WorkerEntitySystem
* Check if class extends WorkerEntitySystem
*/
function isWorkerEntitySystemSubclass(classDecl: ClassDeclaration): boolean {
const extendsClause = classDecl.getExtends();
if (!extendsClause) {
return false;
}
const extendsText = extendsClause.getText();
// 直接检查是否继承 WorkerEntitySystem
// Directly check if extends WorkerEntitySystem
if (extendsText.startsWith('WorkerEntitySystem')) {
return true;
}
// 递归检查基类(如果需要)
// Recursively check base class (if needed)
// 这里简化处理,只检查直接继承
// Simplified: only check direct inheritance
return false;
}
/**
* 提取 WorkerEntitySystem 子类信息
* Extract WorkerEntitySystem subclass information
*/
function extractWorkerSystemInfo(
classDecl: ClassDeclaration,
filePath: string,
verbose?: boolean
): WorkerSystemInfo | null {
if (!isWorkerEntitySystemSubclass(classDecl)) {
return null;
}
const className = classDecl.getName();
if (!className) {
return null;
}
if (verbose) {
console.log(` Found WorkerEntitySystem: ${className} in ${filePath}`);
}
// 查找 workerProcess 方法
// Find workerProcess method
const workerProcessMethod = classDecl.getMethod('workerProcess');
if (!workerProcessMethod) {
if (verbose) {
console.log(` Warning: No workerProcess method found in ${className}`);
}
return null;
}
// 提取方法体
// Extract method body
const workerProcessBody = extractMethodBody(workerProcessMethod);
if (!workerProcessBody) {
if (verbose) {
console.log(` Warning: Could not extract workerProcess body from ${className}`);
}
return null;
}
// 提取参数名
// Extract parameter names
const params = workerProcessMethod.getParameters();
const workerProcessParams = {
entities: params[0]?.getName() || 'entities',
deltaTime: params[1]?.getName() || 'deltaTime',
config: params[2]?.getName() || 'config',
};
// 尝试提取 getSharedArrayBufferProcessFunction
// Try to extract getSharedArrayBufferProcessFunction
let sharedBufferProcessBody: string | undefined;
const sharedBufferMethod = classDecl.getMethod('getSharedArrayBufferProcessFunction');
if (sharedBufferMethod) {
sharedBufferProcessBody = extractSharedBufferFunctionBody(sharedBufferMethod);
}
// 尝试提取 entityDataSize
// Try to extract entityDataSize
let entityDataSize: number | undefined;
const getDefaultEntityDataSizeMethod = classDecl.getMethod('getDefaultEntityDataSize');
if (getDefaultEntityDataSizeMethod) {
entityDataSize = extractEntityDataSize(getDefaultEntityDataSizeMethod);
}
// 尝试从构造函数中提取 workerScriptPath 配置
// Try to extract workerScriptPath from constructor
const workerScriptPath = extractWorkerScriptPath(classDecl, verbose);
return {
className,
filePath,
workerProcessBody,
workerProcessParams,
sharedBufferProcessBody,
entityDataSize,
workerScriptPath,
};
}
/**
* 提取方法体(去掉方法签名,保留函数体内容)
* Extract method body (remove method signature, keep function body content)
*/
function extractMethodBody(method: MethodDeclaration): string | null {
const body = method.getBody();
if (!body) {
return null;
}
// 获取方法体的文本
// Get method body text
let bodyText = body.getText();
// 去掉外层的花括号
// Remove outer braces
if (bodyText.startsWith('{') && bodyText.endsWith('}')) {
bodyText = bodyText.slice(1, -1).trim();
}
return bodyText;
}
/**
* 提取 getSharedArrayBufferProcessFunction 返回的函数体
* Extract function body returned by getSharedArrayBufferProcessFunction
*/
function extractSharedBufferFunctionBody(method: MethodDeclaration): string | undefined {
const body = method.getBody();
if (!body) {
return undefined;
}
// 查找 return 语句中的函数表达式
// Find function expression in return statement
const returnStatements = body.getDescendantsOfKind(SyntaxKind.ReturnStatement);
for (const returnStmt of returnStatements) {
const expression = returnStmt.getExpression();
if (expression) {
// 检查是否是函数表达式或箭头函数
// Check if it's a function expression or arrow function
if (Node.isFunctionExpression(expression) || Node.isArrowFunction(expression)) {
const funcBody = expression.getBody();
if (funcBody) {
let bodyText = funcBody.getText();
if (bodyText.startsWith('{') && bodyText.endsWith('}')) {
bodyText = bodyText.slice(1, -1).trim();
}
return bodyText;
}
}
}
}
return undefined;
}
/**
* 提取 entityDataSize 值
* Extract entityDataSize value
*/
function extractEntityDataSize(method: MethodDeclaration): number | undefined {
const body = method.getBody();
if (!body) {
return undefined;
}
// 查找 return 语句
// Find return statement
const returnStatements = body.getDescendantsOfKind(SyntaxKind.ReturnStatement);
for (const returnStmt of returnStatements) {
const expression = returnStmt.getExpression();
if (expression && Node.isNumericLiteral(expression)) {
return parseInt(expression.getText(), 10);
}
}
return undefined;
}
/**
* 从构造函数中提取 workerScriptPath 配置
* Extract workerScriptPath from constructor
*/
function extractWorkerScriptPath(classDecl: ClassDeclaration, verbose?: boolean): string | undefined {
// 查找构造函数
// Find constructor
const constructors = classDecl.getConstructors();
if (constructors.length === 0) {
return undefined;
}
const constructor = constructors[0]!;
const body = constructor.getBody();
if (!body) {
return undefined;
}
const bodyText = body.getText();
// 使用正则表达式查找 workerScriptPath: 'xxx' 或 workerScriptPath: "xxx"
// Use regex to find workerScriptPath: 'xxx' or workerScriptPath: "xxx"
const patterns = [
/workerScriptPath\s*:\s*['"]([^'"]+)['"]/,
/workerScriptPath\s*:\s*`([^`]+)`/,
];
for (const pattern of patterns) {
const match = bodyText.match(pattern);
if (match && match[1]) {
if (verbose) {
console.log(` Found workerScriptPath: ${match[1]}`);
}
return match[1];
}
}
return undefined;
}