* 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 进行安全可靠的代码转换 - 移除所有可能导致回溯的正则表达式
274 lines
8.2 KiB
TypeScript
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;
|
|
}
|