fix: 处理外部dts和js引入的actionDef问题

This commit is contained in:
Pan Qiancheng 2026-01-04 10:55:28 +08:00
parent a36a565d4e
commit 7fa85577c1
2 changed files with 242 additions and 68 deletions

View File

@ -437,16 +437,9 @@ function dealWithActionTypeNode(moduleName, filename, actionTypeNode, program, s
});
pushStatementIntoActionAst(moduleName, factory.createVariableStatement([factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier("actions"), undefined, undefined, factory.createArrayLiteralExpression(actionTexts.map(ele => factory.createStringLiteral(ele)), false))], ts.NodeFlags.Const)), sourceFile);
}
/**
*
* @param moduleName
* @param initializer
* @param program
* @returns 返回是否显式定义了is
*/
function dealWithActionDefInitializer(moduleName, initializer, program) {
if (ts.isIdentifier(initializer) || ts.isCallExpression(initializer)) {
// 是从别处的引用,注入到mportActionDefFrom
// 是从别处的引用注入到importActionDefFrom
const checker = program.getTypeChecker();
const identifier = ts.isIdentifier(initializer) ? initializer : initializer.expression;
(0, assert_1.default)(ts.isIdentifier(identifier), "ActionDef的initializer不是一个Identifier");
@ -455,31 +448,49 @@ function dealWithActionDefInitializer(moduleName, initializer, program) {
(0, assert_1.default)(ts.isImportSpecifier(declaration), "ActionDef的initializer不是一个ImportSpecifier");
const importDeclartion = declaration.parent.parent.parent;
addImportedFrom(moduleName, identifier.text, importDeclartion);
// todo要去分析外部引用的actionDef中的is目前只有src/action/action.ts中的makeAbleActionDef这种情况无法在编译时确定is是否为空统一设置为空
// 分析外部引用的actionDef中的is
const aliasedSymbol = checker.getAliasedSymbol(symbol);
const aliasedDeclaration = aliasedSymbol.getDeclarations()[0];
(0, assert_1.default)(ts.isVariableDeclaration(aliasedDeclaration), "ActionDef的aliasedDeclaration不是一个VariableDeclaration");
const { initializer: aliDecInit } = aliasedDeclaration;
if (aliDecInit) {
// 如果是函数调用CallExpression默认返回false
if (ts.isCallExpression(initializer)) {
// 检查被调用的函数是否是箭头函数或函数表达式
if (ts.isArrowFunction(aliDecInit) || ts.isFunctionExpression(aliDecInit)) {
// 这是一个函数调用无法在编译时确定is返回false
return false;
}
}
// 如果是对象字面量检查is属性
if (ts.isObjectLiteralExpression(aliDecInit)) {
console.log('使用外部引用的ActionDef时可能无法确定is属性请确认');
const { properties } = aliDecInit;
const isProp = properties.find((ele) => ts.isPropertyAssignment(ele) && ts.isIdentifier(ele.name) && ele.name.text === 'is' && ts.isStringLiteral(ele.initializer));
if (isProp) {
return true;
}
}
// 如果是函数调用CallExpression无法在编译时确定is直接返回false
if (ts.isCallExpression(initializer)) {
return false;
}
// 如果是直接引用Identifier尝试分析外部引用的actionDef中的is
const aliasedSymbol = checker.getAliasedSymbol(symbol);
const aliasedDeclaration = aliasedSymbol?.getDeclarations()?.[0];
// 如果无法获取到声明返回false可能是第三方库
if (!aliasedDeclaration || !ts.isVariableDeclaration(aliasedDeclaration)) {
return false;
}
const { initializer: aliDecInit } = aliasedDeclaration;
// 如果没有初始化器,可能是.d.ts声明文件尝试从对应的.js文件读取
if (!aliDecInit) {
const sourceFile = aliasedDeclaration.getSourceFile();
const sourceFileName = sourceFile.fileName;
// 检查是否是.d.ts文件
if (sourceFileName.endsWith('.d.ts')) {
const jsFileName = sourceFileName.replace(/\.d\.ts$/, '.js');
// 尝试读取对应的.js文件
if ((0, fs_1.existsSync)(jsFileName)) {
try {
const jsContent = require('fs').readFileSync(jsFileName, 'utf-8');
const exportName = identifier.text;
// 尝试从JS文件中解析is属性
const hasIs = tryParseIsFromJsFile(jsContent, exportName);
return hasIs;
}
catch (error) {
console.warn(`无法解析JS文件 ${jsFileName}:`, error);
return false;
}
}
}
return false;
}
// 如果是对象字面量检查is属性
if (ts.isObjectLiteralExpression(aliDecInit)) {
const { properties } = aliDecInit;
const isProp = properties.find((ele) => ts.isPropertyAssignment(ele) && ts.isIdentifier(ele.name) && ele.name.text === 'is' && ts.isStringLiteral(ele.initializer));
return !!isProp;
}
// 其他情况返回false
return false;
}
else {
@ -494,6 +505,71 @@ function dealWithActionDefInitializer(moduleName, initializer, program) {
return false;
}
}
/**
* 尝试从JavaScript文件内容中解析导出对象的is属性
* @param jsContent JavaScript文件内容
* @param exportName 导出的变量名
* @returns 是否包含is属性
*/
function tryParseIsFromJsFile(jsContent, exportName) {
// 创建一个临时的SourceFile来解析JavaScript
const jsSourceFile = ts.createSourceFile('temp.js', jsContent, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
let hasIs = false;
// 遍历AST查找导出
const visit = (node) => {
// 处理 exports.xxx = {...} 或 module.exports.xxx = {...}
if (ts.isExpressionStatement(node)) {
const expr = node.expression;
if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
const left = expr.left;
const right = expr.right;
// 检查是否是 exports.exportName 或 module.exports.exportName
if (ts.isPropertyAccessExpression(left)) {
const propertyName = left.name.text;
if (propertyName === exportName) {
// 检查右侧是否是对象字面量
if (ts.isObjectLiteralExpression(right)) {
hasIs = checkObjectHasIsProperty(right);
return;
}
}
}
}
}
// 处理 const xxx = {...}; exports.xxx = xxx;
if (ts.isVariableStatement(node)) {
const declarations = node.declarationList.declarations;
for (const decl of declarations) {
if (ts.isIdentifier(decl.name) && decl.name.text === exportName) {
if (decl.initializer && ts.isObjectLiteralExpression(decl.initializer)) {
hasIs = checkObjectHasIsProperty(decl.initializer);
return;
}
}
}
}
ts.forEachChild(node, visit);
};
visit(jsSourceFile);
return hasIs;
}
/**
* 检查对象字面量是否包含is属性
*/
function checkObjectHasIsProperty(objLiteral) {
const { properties } = objLiteral;
const isProp = properties.find((ele) => {
if (ts.isPropertyAssignment(ele)) {
const name = ele.name;
if (ts.isIdentifier(name) && name.text === 'is') {
// 检查值是否是字符串字面量
return ts.isStringLiteral(ele.initializer);
}
}
return false;
});
return !!isProp;
}
/**
* entity的引用一定要以 import { Schema as XXX } from '..../XXX'这种形式
* @param declaration

View File

@ -646,16 +646,9 @@ function dealWithActionTypeNode(moduleName: string, filename: string, actionType
);
}
/**
*
* @param moduleName
* @param initializer
* @param program
* @returns is
*/
function dealWithActionDefInitializer(moduleName: string, initializer: ts.Expression, program: ts.Program) {
if (ts.isIdentifier(initializer) || ts.isCallExpression(initializer)) {
// 是从别处的引用,注入到mportActionDefFrom
// 是从别处的引用注入到importActionDefFrom
const checker = program.getTypeChecker();
const identifier = ts.isIdentifier(initializer) ? initializer : initializer.expression;
assert(ts.isIdentifier(identifier), "ActionDef的initializer不是一个Identifier");
@ -667,36 +660,60 @@ function dealWithActionDefInitializer(moduleName: string, initializer: ts.Expres
addImportedFrom(moduleName, identifier.text, importDeclartion as ts.ImportDeclaration);
// todo要去分析外部引用的actionDef中的is目前只有src/action/action.ts中的makeAbleActionDef这种情况无法在编译时确定is是否为空统一设置为空
// 分析外部引用的actionDef中的is
const aliasedSymbol = checker.getAliasedSymbol(symbol!);
const aliasedDeclaration = aliasedSymbol.getDeclarations()![0]!;
assert(ts.isVariableDeclaration(aliasedDeclaration), "ActionDef的aliasedDeclaration不是一个VariableDeclaration");
const { initializer: aliDecInit } = aliasedDeclaration;
if (aliDecInit) {
// 如果是函数调用CallExpression默认返回false
if (ts.isCallExpression(initializer)) {
// 检查被调用的函数是否是箭头函数或函数表达式
if (ts.isArrowFunction(aliDecInit) || ts.isFunctionExpression(aliDecInit)) {
// 这是一个函数调用无法在编译时确定is返回false
return false;
}
}
// 如果是对象字面量检查is属性
if (ts.isObjectLiteralExpression(aliDecInit)) {
console.log('使用外部引用的ActionDef时可能无法确定is属性请确认');
const { properties } = aliDecInit;
const isProp = properties.find(
(ele) => ts.isPropertyAssignment(ele) && ts.isIdentifier(ele.name) && ele.name.text === 'is' && ts.isStringLiteral(ele.initializer)
);
if (isProp) {
return true;
}
}
// 如果是函数调用CallExpression无法在编译时确定is直接返回false
if (ts.isCallExpression(initializer)) {
return false;
}
// 如果是直接引用Identifier尝试分析外部引用的actionDef中的is
const aliasedSymbol = checker.getAliasedSymbol(symbol!);
const aliasedDeclaration = aliasedSymbol?.getDeclarations()?.[0];
// 如果无法获取到声明返回false可能是第三方库
if (!aliasedDeclaration || !ts.isVariableDeclaration(aliasedDeclaration)) {
return false;
}
const { initializer: aliDecInit } = aliasedDeclaration;
// 如果没有初始化器,可能是.d.ts声明文件尝试从对应的.js文件读取
if (!aliDecInit) {
const sourceFile = aliasedDeclaration.getSourceFile();
const sourceFileName = sourceFile.fileName;
// 检查是否是.d.ts文件
if (sourceFileName.endsWith('.d.ts')) {
const jsFileName = sourceFileName.replace(/\.d\.ts$/, '.js');
// 尝试读取对应的.js文件
if (existsSync(jsFileName)) {
try {
const jsContent = require('fs').readFileSync(jsFileName, 'utf-8');
const exportName = identifier.text;
// 尝试从JS文件中解析is属性
const hasIs = tryParseIsFromJsFile(jsContent, exportName);
return hasIs;
} catch (error) {
console.warn(`无法解析JS文件 ${jsFileName}:`, error);
return false;
}
}
}
return false;
}
// 如果是对象字面量检查is属性
if (ts.isObjectLiteralExpression(aliDecInit)) {
const { properties } = aliDecInit;
const isProp = properties.find(
(ele) => ts.isPropertyAssignment(ele) && ts.isIdentifier(ele.name) && ele.name.text === 'is' && ts.isStringLiteral(ele.initializer)
);
return !!isProp;
}
// 其他情况返回false
return false;
}
else {
@ -716,6 +733,87 @@ function dealWithActionDefInitializer(moduleName: string, initializer: ts.Expres
}
}
/**
* JavaScript文件内容中解析导出对象的is属性
* @param jsContent JavaScript文件内容
* @param exportName
* @returns is属性
*/
function tryParseIsFromJsFile(jsContent: string, exportName: string): boolean {
// 创建一个临时的SourceFile来解析JavaScript
const jsSourceFile = ts.createSourceFile(
'temp.js',
jsContent,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.JS
);
let hasIs = false;
// 遍历AST查找导出
const visit = (node: ts.Node) => {
// 处理 exports.xxx = {...} 或 module.exports.xxx = {...}
if (ts.isExpressionStatement(node)) {
const expr = node.expression;
if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
const left = expr.left;
const right = expr.right;
// 检查是否是 exports.exportName 或 module.exports.exportName
if (ts.isPropertyAccessExpression(left)) {
const propertyName = left.name.text;
if (propertyName === exportName) {
// 检查右侧是否是对象字面量
if (ts.isObjectLiteralExpression(right)) {
hasIs = checkObjectHasIsProperty(right);
return;
}
}
}
}
}
// 处理 const xxx = {...}; exports.xxx = xxx;
if (ts.isVariableStatement(node)) {
const declarations = node.declarationList.declarations;
for (const decl of declarations) {
if (ts.isIdentifier(decl.name) && decl.name.text === exportName) {
if (decl.initializer && ts.isObjectLiteralExpression(decl.initializer)) {
hasIs = checkObjectHasIsProperty(decl.initializer);
return;
}
}
}
}
ts.forEachChild(node, visit);
};
visit(jsSourceFile);
return hasIs;
}
/**
* is属性
*/
function checkObjectHasIsProperty(objLiteral: ts.ObjectLiteralExpression): boolean {
const { properties } = objLiteral;
const isProp = properties.find(
(ele) => {
if (ts.isPropertyAssignment(ele)) {
const name = ele.name;
if (ts.isIdentifier(name) && name.text === 'is') {
// 检查值是否是字符串字面量
return ts.isStringLiteral(ele.initializer);
}
}
return false;
}
);
return !!isProp;
}
/**
* entity的引用一定要以 import { Schema as XXX } from '..../XXX'
* @param declaration