From 7fa85577c16ef57a3a60458f1e2dbf85bbda7bb5 Mon Sep 17 00:00:00 2001 From: qcqcqc <1220204124@zust.edu.cn> Date: Sun, 4 Jan 2026 10:55:28 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A4=84=E7=90=86=E5=A4=96=E9=83=A8dts?= =?UTF-8?q?=E5=92=8Cjs=E5=BC=95=E5=85=A5=E7=9A=84actionDef=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/compiler/schemalBuilder.js | 140 ++++++++++++++++++++------- src/compiler/schemalBuilder.ts | 170 ++++++++++++++++++++++++++------- 2 files changed, 242 insertions(+), 68 deletions(-) diff --git a/lib/compiler/schemalBuilder.js b/lib/compiler/schemalBuilder.js index 9617bd7..b9a3aa7 100644 --- a/lib/compiler/schemalBuilder.js +++ b/lib/compiler/schemalBuilder.js @@ -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 diff --git a/src/compiler/schemalBuilder.ts b/src/compiler/schemalBuilder.ts index 598936d..5489b88 100644 --- a/src/compiler/schemalBuilder.ts +++ b/src/compiler/schemalBuilder.ts @@ -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