From 09b1207af3eb8c6a790570ceee6608bad3c767e3 Mon Sep 17 00:00:00 2001 From: Pan Qiancheng <905739777@qq.com> Date: Sun, 4 Jan 2026 22:52:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A4=A7=E5=B9=85=E4=BC=98=E5=8C=96bui?= =?UTF-8?q?ld=E7=BC=96=E8=AF=91=E6=80=A7=E8=83=BD=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=A3=80=E6=9F=A5checker=E4=B8=AD=E7=9A=84=E5=A4=A7?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/compiler/tscBuilder.js | 1054 ++++++++++++++++++++++-------- lib/store/CascadeStore.d.ts | 2 +- lib/store/relation.d.ts | 2 +- package.json | 3 +- src/compiler/tscBuilder.ts | 1227 ++++++++++++++++++++++++++--------- tsconfig.json | 8 +- tsconfig.test.json | 33 + 7 files changed, 1763 insertions(+), 566 deletions(-) create mode 100644 tsconfig.test.json diff --git a/lib/compiler/tscBuilder.js b/lib/compiler/tscBuilder.js index be6efa2..e09d8d5 100644 --- a/lib/compiler/tscBuilder.js +++ b/lib/compiler/tscBuilder.js @@ -6,7 +6,6 @@ const ts = tslib_1.__importStar(require("typescript")); const path = tslib_1.__importStar(require("path")); const fs = tslib_1.__importStar(require("fs")); const verboseLogging = false; -const MAX_RECURSION_DEPTH = 10; const log = (...args) => { if (verboseLogging) { console.log('[tscBuilder]', ...args); @@ -75,97 +74,89 @@ function performCustomChecks(program, typeChecker, customConfig) { console.log('Custom AsyncContext checks are disabled.'); return diagnostics; } + // 数据结构定义 const functionsWithContextCalls = new Set(); - const checkedFunctions = new Map(); - // 遍历所有源文件 - for (const sourceFile of program.getSourceFiles()) { - // 跳过声明文件和 node_modules - if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('node_modules')) { - continue; + const functionDeclarations = new Map(); + const functionCallGraph = new Map(); // 谁调用了谁 + const directContextCalls = new Map(); // 函数内的直接 context 调用 + const allContextCalls = []; // 所有 context 调用点 + // 缓存:函数符号 -> 参数索引 -> 是否被处理 + const functionParameterHandling = new Map(); + // 缓存:变量符号 -> 是否被正确处理 + const variableHandlingCache = new Map(); + // 正在检查的变量集合(防止循环) + const checkingVariables = new Set(); + const typeCache = new Map(); + const addDiagnostic = (node, messageText, code) => { + const sourceFile = node.getSourceFile(); + diagnostics.push({ + file: sourceFile, + start: node.getStart(sourceFile), + length: node.getWidth(sourceFile), + messageText, + category: ts.DiagnosticCategory.Warning, + code + }); + }; + const shouldReportUnawaitedCall = (callNode, sourceFile) => { + // 跳过特定场景 + if (shouldSkipCheck(callNode)) { + return false; } - // 遍历 AST 节点 - visitNode(sourceFile, 0); - } - function resolveModuleName(moduleName, containingFile) { + // 检查忽略注释 + if (hasIgnoreComment(callNode, sourceFile)) { + return false; + } + // 检查是否已 await + if (isAwaited(callNode)) { + return false; + } + // 检查是否赋值给变量并正确处理 + if (isCallAssignedAndHandled(callNode)) { + return false; + } + return true; + }; + const isCallAssignedAndHandled = (callNode) => { + const parent = callNode.parent; + if (ts.isVariableDeclaration(parent) && parent.initializer === callNode) { + if (parent.name && ts.isIdentifier(parent.name)) { + const variableSymbol = typeChecker.getSymbolAtLocation(parent.name); + if (variableSymbol) { + // 如果变量被 return,则在 shouldSkipCheck 中已经处理,这里不需要再检查 + if (isVariableReturned(variableSymbol, parent)) { + return true; // 被 return 的变量,认为已处理 + } + // 否则检查是否在作用域内被正确处理 + if (isPromiseProperlyHandled(variableSymbol, parent)) { + return true; + } + } + } + } + // 检查是否作为参数传递给会处理 Promise 的函数 + if (isCallPassedToHandlingFunction(callNode)) { + return true; + } + return false; + }; + const resolveModuleName = (moduleName, containingFile) => { const compilerOptions = program.getCompilerOptions(); const resolvedModule = ts.resolveModuleName(moduleName, containingFile, compilerOptions, ts.sys); return resolvedModule.resolvedModule?.resolvedFileName; - } - function normalizeModulePath(filePath) { - // 规范化路径,移除 node_modules 和文件扩展名 - return filePath + }; + const pathCache = new Map(); + const normalizeModulePath = (filePath) => { + if (pathCache.has(filePath)) { + return pathCache.get(filePath); + } + const normalized = filePath .replace(/\\/g, '/') .replace(/\.(ts|tsx|js|jsx|d\.ts)$/, ''); - } - function visitNode(node, deepth) { - if (deepth > MAX_RECURSION_DEPTH) { - log(`[DEBUG] 达到最大递归深度,停止进一步检查`); - return; - } - // 检查 AsyncContext 方法调用 - checkAsyncContextMethodCall(node); - //检查函数调用(检查是否调用了包含 context 的函数) - checkFunctionCall(node); - // 递归遍历子节点 - ts.forEachChild(node, (i) => visitNode(i, deepth + 1)); - } - function checkAsyncContextMethodCall(node) { - // 只检查方法调用表达式 - if (!ts.isCallExpression(node)) { - return; - } - // 检查是否是属性访问表达式 - if (!ts.isPropertyAccessExpression(node.expression)) { - return; - } - // 如果上一层是return,则先跳过,让外层函数调用来处理 - if (ts.isReturnStatement(node.parent)) { - return; - } - // 检查箭头函数的隐式返回,让外层函数调用来处理 - // 例如:() => context.select() - if (ts.isArrowFunction(node.parent) && node.parent.body === node) { - return; - } - const propertyAccess = node.expression; - const objectExpression = propertyAccess.expression; - // 获取对象的类型 - const objectType = typeChecker.getTypeAtLocation(objectExpression); - const targetModules = customConfig.context?.targetModules || ['@project/context/BackendRuntimeContext']; - // 检查类型是否继承自 AsyncContext - if (!isAsyncContextType(objectType, targetModules)) { - return; - } - // 获取方法调用的返回类型 - const callSignature = typeChecker.getResolvedSignature(node); - if (!callSignature) { - return; - } - const returnType = typeChecker.getReturnTypeOfSignature(callSignature); - // 只检查返回 Promise 的方法 - if (!isPromiseType(returnType)) { - return; - } - // 检查是否被 await 修饰 - if (!isAwaited(node)) { - const sourceFile = node.getSourceFile(); - // 检查是否有忽略注释 - if (hasIgnoreComment(node, sourceFile)) { - return; - } - const methodName = propertyAccess.name.getText(sourceFile); - const objectName = objectExpression.getText(sourceFile); - diagnostics.push({ - file: sourceFile, - start: node.getStart(sourceFile), - length: node.getWidth(sourceFile), - messageText: `未await的context调用可能导致事务不受控: ${objectName}.${methodName}() 需要使用 await`, - category: ts.DiagnosticCategory.Warning, - code: 9100 - }); - } - } - function hasIgnoreComment(node, sourceFile) { + pathCache.set(filePath, normalized); + return normalized; + }; + const hasIgnoreComment = (node, sourceFile) => { const fullText = sourceFile.getFullText(); // 检查当前节点及其父节点(向上查找最多3层) let currentNode = node; @@ -206,11 +197,19 @@ function performCustomChecks(program, typeChecker, customConfig) { } } return false; - } - function isIgnoreComment(commentText) { + }; + const isIgnoreComment = (commentText) => { return exports.OAK_IGNORE_TAGS.some(tag => commentText.includes(tag)); - } - function isAsyncContextType(type, modules) { + }; + const isAsyncContextType = (type, modules) => { + if (typeCache.has(type)) { + return typeCache.get(type); + } + const result = checkIsAsyncContextType(type, modules); + typeCache.set(type, result); + return result; + }; + const checkIsAsyncContextType = (type, modules) => { // 检查类型本身 if (checkTypeSymbol(type, modules)) { return true; @@ -231,8 +230,8 @@ function performCustomChecks(program, typeChecker, customConfig) { } } return false; - } - function checkTypeSymbol(type, modules) { + }; + const checkTypeSymbol = (type, modules) => { const symbol = type.getSymbol(); if (!symbol) { return false; @@ -280,13 +279,13 @@ function performCustomChecks(program, typeChecker, customConfig) { } } return false; - } - function isSymbolRelated(symbol, moduleSymbol) { + }; + const isSymbolRelated = (symbol, moduleSymbol) => { // 检查符号是否与模块相关 const exports = typeChecker.getExportsOfModule(moduleSymbol); return exports.some(exp => exp === symbol || exp.name === symbol.name); - } - function isAwaited(node) { + }; + const isAwaited = (node) => { let parent = node.parent; // 向上遍历父节点,查找 await 表达式 while (parent) { @@ -311,8 +310,8 @@ function performCustomChecks(program, typeChecker, customConfig) { parent = parent.parent; } return false; - } - function isPromiseType(type) { + }; + const isPromiseType = (type) => { // 检查类型符号 const symbol = type.getSymbol(); if (symbol) { @@ -338,8 +337,8 @@ function performCustomChecks(program, typeChecker, customConfig) { } } return false; - } - function isNodeInSubtree(root, target) { + }; + const isNodeInSubtree = (root, target) => { if (root === target) { return true; } @@ -352,155 +351,9 @@ function performCustomChecks(program, typeChecker, customConfig) { } }); return found; - } - function checkFunctionCall(node) { - // 只检查调用表达式 - if (!ts.isCallExpression(node)) { - return; - } - // 添加这里 - 打印所有函数调用 - const sourceFile = node.getSourceFile(); - const callText = node.expression.getText(sourceFile); - log(`[DEBUG] 检查函数调用: ${callText}`); - // 获取被调用函数的符号 - const signature = typeChecker.getResolvedSignature(node); - if (!signature) { - log(`[DEBUG] ${callText} - 无法获取签名`); - return; - } - const declaration = signature.getDeclaration(); - if (!declaration) { - log(`[DEBUG] ${callText} - 无法获取声明`); - return; - } - log(`[DEBUG] ${callText} - 声明类型: ${ts.SyntaxKind[declaration.kind]}`); - // 检查这个函数是否包含 context 调用 - const hasContext = containsContextCall(declaration); - log(`[DEBUG] ${callText} - 包含context调用: ${hasContext}`); - if (hasContext) { - const returnType = typeChecker.getReturnTypeOfSignature(signature); - // 如果函数不返回 Promise,说明内部已经正确处理了异步调用 - // 此时调用该函数不需要 await - if (!isPromiseType(returnType)) { - return; - } - // 标记当前所在的函数 - const containingFunction = findContainingFunction(node); - if (containingFunction) { - const fname = getFunctionName(containingFunction); - if (fname) { // 只有当有名称时才标记,但不影响后续检查 - const symbol = typeChecker.getSymbolAtLocation(fname); - if (symbol) { - functionsWithContextCalls.add(symbol); - } - } - } - if (ts.isReturnStatement(node.parent)) { - // 如果在 return 语句中,不在这里报警告 - // 警告会传播到调用当前函数的地方 - return; - } - // 检查箭头函数的隐式返回 - // 例如:() => someFunction() - if (ts.isArrowFunction(node.parent) && node.parent.body === node) { - return; - } - // 检查是否被 await - if (!isAwaited(node)) { - const sourceFile = node.getSourceFile(); - // 检查是否有忽略注释 - if (hasIgnoreComment(node, sourceFile)) { - return; - } - const functionName = getFunctionCallName(node, sourceFile); - diagnostics.push({ - file: sourceFile, - start: node.getStart(sourceFile), - length: node.getWidth(sourceFile), - messageText: `未await的函数调用可能导致事务不受控: ${functionName}() 内部包含 context 调用,需要使用 await`, - category: ts.DiagnosticCategory.Warning, - code: 9101 - }); - } - } - } - // 辅助函数:检查函数声明是否包含 context 调用 - function containsContextCall(node) { - const symbol = getSymbolOfDeclaration(node); - log(`[DEBUG containsContextCall] 节点类型: ${ts.SyntaxKind[node.kind]}, 有符号: ${!!symbol}`); - if (symbol && checkedFunctions.has(symbol)) { - log(`[DEBUG containsContextCall] 使用缓存结果: ${checkedFunctions.get(symbol)}`); - return checkedFunctions.get(symbol); - } - let hasContextCall = false; - function visit(n) { - if (hasContextCall) - return; - // 检查是否是 context 方法调用 - if (ts.isCallExpression(n) && - ts.isPropertyAccessExpression(n.expression)) { - const objectType = typeChecker.getTypeAtLocation(n.expression.expression); - const targetModules = customConfig.context?.targetModules || - ['@project/context/BackendRuntimeContext']; - if (isAsyncContextType(objectType, targetModules)) { - log(`[DEBUG containsContextCall] 找到context调用: ${n.expression.getText()}`); - hasContextCall = true; - return; - } - } - // 递归检查调用的其他函数 - if (ts.isCallExpression(n)) { - const signature = typeChecker.getResolvedSignature(n); - if (signature) { - const declaration = signature.getDeclaration(); - if (declaration && !isSameOrDescendant(declaration, node)) { - // 避免无限递归 - const symbol = getSymbolOfDeclaration(declaration); - // 先检查缓存 - if (symbol && functionsWithContextCalls.has(symbol)) { - hasContextCall = true; - return; - } - // 如果缓存中没有,递归检查该函数 - if (containsContextCall(declaration)) { - hasContextCall = true; - return; - } - } - } - } - ts.forEachChild(n, visit); - } - visit(node); - log(`[DEBUG containsContextCall] 最终结果: ${hasContextCall}`); - if (symbol) { - checkedFunctions.set(symbol, hasContextCall); - } - return hasContextCall; - } - // 辅助函数:查找包含当前节点的函数 - function findContainingFunction(node) { - let current = node.parent; - while (current) { - if (ts.isFunctionDeclaration(current) || - ts.isFunctionExpression(current) || - ts.isArrowFunction(current) || - ts.isMethodDeclaration(current)) { - return current; - } - current = current.parent; - } - return undefined; - } - // 辅助函数:获取函数名称节点 - function getFunctionName(func) { - if (ts.isFunctionDeclaration(func) || ts.isMethodDeclaration(func)) { - return func.name; - } - return undefined; - } + }; // 辅助函数:获取函数调用的名称 - function getFunctionCallName(node, sourceFile) { + const getFunctionCallName = (node, sourceFile) => { if (ts.isIdentifier(node.expression)) { return node.expression.getText(sourceFile); } @@ -508,20 +361,9 @@ function performCustomChecks(program, typeChecker, customConfig) { return node.expression.name.getText(sourceFile); } return node.expression.getText(sourceFile); - } - // 辅助函数:检查是否是同一个节点或后代 - function isSameOrDescendant(node, ancestor) { - let current = node; - while (current) { - if (current === ancestor) { - return true; - } - current = current.parent; - } - return false; - } + }; // 辅助函数:获取声明的符号 - function getSymbolOfDeclaration(declaration) { + const getSymbolOfDeclaration = (declaration) => { // 处理有名称的声明(函数声明、方法声明等) if ('name' in declaration && declaration.name) { return typeChecker.getSymbolAtLocation(declaration.name); @@ -538,10 +380,698 @@ function performCustomChecks(program, typeChecker, customConfig) { } } return undefined; + }; + const collectInformation = (sourceFile) => { + let currentFunction; + const visit = (node) => { + // 记录函数声明 + if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || + ts.isArrowFunction(node) || ts.isMethodDeclaration(node)) { + const symbol = getSymbolOfDeclaration(node); + if (symbol) { + functionDeclarations.set(symbol, node); + const previousFunction = currentFunction; + currentFunction = symbol; + ts.forEachChild(node, visit); + currentFunction = previousFunction; + return; + } + } + // 记录 context 方法调用 + if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { + const objectType = typeChecker.getTypeAtLocation(node.expression.expression); + const targetModules = customConfig.context?.targetModules || + ['@project/context/BackendRuntimeContext']; + if (isAsyncContextType(objectType, targetModules)) { + allContextCalls.push(node); + // 记录到当前函数 + if (currentFunction) { + if (!directContextCalls.has(currentFunction)) { + directContextCalls.set(currentFunction, []); + } + directContextCalls.get(currentFunction).push(node); + } + } + } + // 记录函数调用关系 + if (ts.isCallExpression(node) && currentFunction) { + const signature = typeChecker.getResolvedSignature(node); + if (signature) { + const declaration = signature.getDeclaration(); + if (declaration) { + const calledSymbol = getSymbolOfDeclaration(declaration); + if (calledSymbol && calledSymbol !== currentFunction) { + if (!functionCallGraph.has(currentFunction)) { + functionCallGraph.set(currentFunction, new Set()); + } + functionCallGraph.get(currentFunction).add(calledSymbol); + } + } + } + } + ts.forEachChild(node, visit); + }; + visit(sourceFile); + }; + const propagateContextMarks = () => { + // 初始标记:直接包含 context 调用的函数 + const markedFunctions = new Set(directContextCalls.keys()); + // 迭代传播标记 + let changed = true; + while (changed) { + changed = false; + for (const [caller, callees] of functionCallGraph.entries()) { + if (markedFunctions.has(caller)) + continue; + // 如果调用了任何标记的函数,则标记当前函数 + for (const callee of callees) { + if (markedFunctions.has(callee)) { + markedFunctions.add(caller); + functionsWithContextCalls.add(caller); + changed = true; + break; + } + } + } + } + // 更新全局标记 + markedFunctions.forEach(symbol => functionsWithContextCalls.add(symbol)); + }; + const checkCallSites = () => { + // 检查所有直接的 context 调用 + for (const callNode of allContextCalls) { + if (shouldSkipCheck(callNode)) + continue; + const signature = typeChecker.getResolvedSignature(callNode); + if (!signature) + continue; + const returnType = typeChecker.getReturnTypeOfSignature(signature); + if (!isPromiseType(returnType)) + continue; + // 检查是否直接 await + const sourceFile = callNode.getSourceFile(); + if (shouldReportUnawaitedCall(callNode, sourceFile)) { + const propertyAccess = callNode.expression; + const methodName = propertyAccess.name.getText(sourceFile); + const objectName = propertyAccess.expression.getText(sourceFile); + addDiagnostic(callNode, `未await的context调用可能导致事务不受控: ${objectName}.${methodName}() 需要使用 await 或通过 instanceof Promise 判断后处理`, 9100); + } + } + // 检查所有标记函数的调用点 + for (const sourceFile of program.getSourceFiles()) { + if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('node_modules')) { + continue; + } + checkFunctionCallsInFile(sourceFile); + } + }; + const checkFunctionCallsInFile = (sourceFile) => { + const visit = (node) => { + if (ts.isCallExpression(node)) { + if (shouldSkipCheck(node)) { + ts.forEachChild(node, visit); + return; + } + const signature = typeChecker.getResolvedSignature(node); + if (signature) { + const declaration = signature.getDeclaration(); + if (declaration) { + const symbol = getSymbolOfDeclaration(declaration); + // 检查是否调用了标记的函数 + if (symbol && functionsWithContextCalls.has(symbol)) { + const returnType = typeChecker.getReturnTypeOfSignature(signature); + if (!isPromiseType(returnType)) { + ts.forEachChild(node, visit); + return; + } + if (shouldReportUnawaitedCall(node, sourceFile)) { + const functionName = getFunctionCallName(node, sourceFile); + addDiagnostic(node, `未await的函数调用可能导致事务不受控: ${functionName}() 内部包含 context 调用,需要使用 await 或通过 instanceof Promise 判断后处理`, 9101); + } + } + } + } + } + ts.forEachChild(node, visit); + }; + visit(sourceFile); + }; + const shouldSkipCheck = (node) => { + // 直接在 return 语句中 + if (ts.isReturnStatement(node.parent)) { + return true; + } + // 箭头函数的直接返回值 + if (ts.isArrowFunction(node.parent) && node.parent.body === node) { + return true; + } + // 检查是否赋值给变量,然后该变量被 return + if (ts.isVariableDeclaration(node.parent) && node.parent.initializer === node) { + const variableDecl = node.parent; + if (variableDecl.name && ts.isIdentifier(variableDecl.name)) { + const variableSymbol = typeChecker.getSymbolAtLocation(variableDecl.name); + if (variableSymbol && isVariableReturned(variableSymbol, variableDecl)) { + return true; + } + } + } + return false; + }; + // 检查变量是否在其作用域内被 return + const isVariableReturned = (symbol, declarationNode) => { + let scope = declarationNode; + while (scope && !ts.isFunctionDeclaration(scope) && + !ts.isFunctionExpression(scope) && + !ts.isArrowFunction(scope) && + !ts.isMethodDeclaration(scope) && + !ts.isSourceFile(scope)) { + scope = scope.parent; + } + if (!scope || ts.isSourceFile(scope)) { + return false; + } + let found = false; + const checkIfContainsVariable = (node) => { + if (ts.isIdentifier(node)) { + const nodeSymbol = typeChecker.getSymbolAtLocation(node); + return nodeSymbol === symbol; + } + let contains = false; + ts.forEachChild(node, (child) => { + if (contains) + return; + if (checkIfContainsVariable(child)) { + contains = true; + } + }); + return contains; + }; + const visit = (node) => { + if (found) + return; + // 检查 return 语句 + if (ts.isReturnStatement(node) && node.expression) { + // 检查 return 的表达式中是否包含该变量 + if (checkIfContainsVariable(node.expression)) { + found = true; + return; + } + } + // 不进入嵌套函数 + if (ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isArrowFunction(node)) { + return; + } + ts.forEachChild(node, visit); + }; + const funcLike = scope; + if (funcLike.body) { + visit(funcLike.body); + } + return found; + }; + // 检查变量是否通过 instanceof Promise 判断 + // 检查变量是否通过 instanceof Promise 判断 + const hasInstanceOfPromiseCheck = (symbol, scope) => { + let found = false; + const visit = (node) => { + if (found) + return; + // 检查 instanceof Promise + if (ts.isBinaryExpression(node) && + node.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) { + const left = node.left; + if (ts.isIdentifier(left)) { + const leftSymbol = typeChecker.getSymbolAtLocation(left); + if (leftSymbol === symbol) { + const right = node.right; + // 修改:检查右侧是否是 Promise 标识符 + if (ts.isIdentifier(right) && right.text === 'Promise') { + found = true; + return; + } + // 也检查类型(兼容其他情况) + const rightType = typeChecker.getTypeAtLocation(right); + const rightSymbol = rightType.getSymbol(); + if (rightSymbol && rightSymbol.name === 'PromiseConstructor') { + found = true; + return; + } + } + } + } + ts.forEachChild(node, visit); + }; + visit(scope); + return found; + }; + // 辅助函数:检查是否有 return 语句返回 Promise 处理结果 + const hasReturnWithPromiseHandling = (node, symbol) => { + let found = false; + const visit = (n) => { + if (found) + return; + if (ts.isReturnStatement(n) && n.expression) { + // 检查返回的表达式是否是对该变量的 Promise 方法调用 + if (ts.isCallExpression(n.expression) && + ts.isPropertyAccessExpression(n.expression.expression)) { + const object = n.expression.expression.expression; + if (ts.isIdentifier(object)) { + const objSymbol = typeChecker.getSymbolAtLocation(object); + if (objSymbol === symbol) { + const methodName = n.expression.expression.name.text; + if (methodName === 'then' || methodName === 'catch' || methodName === 'finally') { + found = true; + return; + } + } + } + } + } + // 不进入嵌套函数 + if (ts.isFunctionDeclaration(n) || + ts.isFunctionExpression(n) || + ts.isArrowFunction(n)) { + return; + } + ts.forEachChild(n, visit); + }; + visit(node); + return found; + }; + // 检查变量是否在作用域内被 await + const isVariableAwaitedInScope = (symbol, scope) => { + let found = false; + const visit = (node) => { + if (found) + return; + // 检查 await 表达式 + if (ts.isAwaitExpression(node)) { + const expression = node.expression; + if (ts.isIdentifier(expression)) { + const exprSymbol = typeChecker.getSymbolAtLocation(expression); + if (exprSymbol === symbol) { + found = true; + return; + } + } + } + // 不进入嵌套函数 + if (ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isArrowFunction(node)) { + return; + } + ts.forEachChild(node, visit); + }; + visit(scope); + return found; + }; + // 检查变量是否通过 Promise 方法处理(.then, .catch, Promise.all 等) + const isVariableHandledByPromiseMethod = (symbol, scope) => { + let found = false; + const visit = (node) => { + if (found) + return; + // 检查 .then() 或 .catch() 调用 + if (ts.isCallExpression(node) && + ts.isPropertyAccessExpression(node.expression)) { + const object = node.expression.expression; + if (ts.isIdentifier(object)) { + const objSymbol = typeChecker.getSymbolAtLocation(object); + if (objSymbol === symbol) { + const methodName = node.expression.name.text; + if (methodName === 'then' || methodName === 'catch' || + methodName === 'finally') { + found = true; + return; + } + } + } + } + // 检查 Promise.all, Promise.race 等 + if (ts.isCallExpression(node) && + ts.isPropertyAccessExpression(node.expression)) { + const object = node.expression.expression; + if (ts.isIdentifier(object) && object.text === 'Promise') { + const methodName = node.expression.name.text; + if (methodName === 'all' || methodName === 'race' || + methodName === 'allSettled' || methodName === 'any') { + // 检查参数中是否包含该变量 + for (const arg of node.arguments) { + if (containsVariable(arg, symbol)) { + found = true; + return; + } + } + } + } + } + ts.forEachChild(node, visit); + }; + visit(scope); + return found; + }; + // 检查节点中是否包含指定变量 + const containsVariable = (node, symbol) => { + if (ts.isIdentifier(node)) { + const nodeSymbol = typeChecker.getSymbolAtLocation(node); + return nodeSymbol === symbol; + } + let found = false; + ts.forEachChild(node, (child) => { + if (found) + return; + if (containsVariable(child, symbol)) { + found = true; + } + }); + return found; + }; + const isVariableHandledInCallback = (symbol, scope, depth = 0) => { + // 深度限制 + if (depth > 2) { + return false; + } + let found = false; + const visit = (node) => { + if (found) + return; + // 检查是否作为回调参数传递 + if (ts.isCallExpression(node)) { + for (const arg of node.arguments) { + if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) { + // 检查回调函数体内是否处理了该变量 + if (arg.body && containsVariable(arg.body, symbol)) { + // 使用简化版本检查,避免深度递归 + if (hasInstanceOfPromiseCheck(symbol, arg.body) || + isVariableAwaitedInScope(symbol, arg.body) || + isVariableHandledByPromiseMethod(symbol, arg.body)) { + found = true; + return; + } + } + } + } + } + ts.forEachChild(node, visit); + }; + visit(scope); + return found; + }; + // 检查变量是否被传递给会处理 Promise 的函数参数 + const isVariablePassedToHandlingFunction = (symbol, scope, depth = 0) => { + // 深度限制,防止过深的递归 + if (depth > 2) { + return false; + } + log('[DEBUG] isVariablePassedToHandlingFunction: checking', symbol.name, 'depth:', depth); + log('[DEBUG] Scope kind:', ts.SyntaxKind[scope.kind]); + let found = false; + let callCount = 0; + const visit = (node) => { + if (found) + return; + log('[DEBUG] Visiting node:', ts.SyntaxKind[node.kind]); + // 检查函数调用 + if (ts.isCallExpression(node)) { + callCount++; + log('[DEBUG] Found call expression #', callCount); + // 遍历所有参数 + node.arguments.forEach((arg, argIndex) => { + if (found) + return; + log('[DEBUG] Checking argument', argIndex, 'kind:', ts.SyntaxKind[arg.kind]); + // 检查参数是否是我们要找的变量 + if (ts.isIdentifier(arg)) { + log('[DEBUG] Argument is identifier:', arg.text); + const argSymbol = typeChecker.getSymbolAtLocation(arg); + if (argSymbol === symbol) { + log('[DEBUG] ✓ Found variable', symbol.name, 'passed as argument at index:', argIndex); + // 获取被调用函数的签名 + const signature = typeChecker.getResolvedSignature(node); + if (!signature) { + log('[DEBUG] ✗ No signature found'); + return; + } + const declaration = signature.getDeclaration(); + if (!declaration) { + log('[DEBUG] ✗ No declaration found'); + return; + } + const calledSymbol = getSymbolOfDeclaration(declaration); + if (!calledSymbol) { + log('[DEBUG] ✗ No called symbol found'); + return; + } + log('[DEBUG] Called function:', calledSymbol.name); + // 检查该函数是否会处理这个参数位置的 Promise + const paramHandling = getFunctionParameterHandling(calledSymbol); + log('[DEBUG] Parameter handling map:', Array.from(paramHandling.entries())); + log('[DEBUG] Parameter handling for index', argIndex, ':', paramHandling.get(argIndex)); + if (paramHandling.get(argIndex) === true) { + log('[DEBUG] ✓✓✓ Parameter is handled!'); + found = true; + return; + } + else { + log('[DEBUG] ✗ Parameter is NOT handled'); + } + // 处理剩余参数的情况 + if (declaration && 'parameters' in declaration) { + const params = declaration.parameters; + if (params.length > 0) { + const lastParam = params[params.length - 1]; + if (lastParam && lastParam.dotDotDotToken && argIndex >= params.length - 1) { + log('[DEBUG] Checking rest parameter at index', params.length - 1); + if (paramHandling.get(params.length - 1) === true) { + log('[DEBUG] ✓✓✓ Rest parameter is handled!'); + found = true; + return; + } + } + } + } + } + else { + log('[DEBUG] Argument symbol mismatch:', argSymbol?.name, 'vs', symbol.name); + } + } + }); + } + // 不进入嵌套函数的内部 + if (ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isArrowFunction(node) || + ts.isMethodDeclaration(node)) { + log('[DEBUG] Skipping nested function body'); + return; // 不调用 ts.forEachChild,直接返回 + } + ts.forEachChild(node, visit); + }; + // 关键修改:从函数体开始遍历,而不是从函数声明开始 + let startNode; + if (ts.isFunctionDeclaration(scope) || ts.isFunctionExpression(scope) || + ts.isArrowFunction(scope) || ts.isMethodDeclaration(scope)) { + const funcLike = scope; + startNode = funcLike.body; + log('[DEBUG] Starting from function body'); + } + else if (ts.isSourceFile(scope)) { + startNode = scope; + log('[DEBUG] Starting from source file'); + } + else { + startNode = scope; + log('[DEBUG] Starting from scope directly'); + } + if (startNode) { + visit(startNode); + } + log('[DEBUG] isVariablePassedToHandlingFunction result for', symbol.name, ':', found, '(checked', callCount, 'calls)'); + return found; + }; + // 综合检查:Promise 是否被正确处理 + const isPromiseProperlyHandled = (symbol, declarationNode) => { + // 检查缓存 + if (variableHandlingCache.has(symbol)) { + return variableHandlingCache.get(symbol); + } + // 检查是否正在检查这个变量(防止循环) + if (checkingVariables.has(symbol)) { + return false; + } + checkingVariables.add(symbol); + try { + log('[DEBUG] isPromiseProperlyHandled: variable', symbol.name, 'declarationNode kind:', ts.SyntaxKind[declarationNode.kind]); + // 找到包含该声明的函数作用域 + let scope = declarationNode; + let depth = 0; + while (scope && !ts.isFunctionDeclaration(scope) && + !ts.isFunctionExpression(scope) && + !ts.isArrowFunction(scope) && + !ts.isMethodDeclaration(scope) && + !ts.isSourceFile(scope)) { + log('[DEBUG] Moving up from', ts.SyntaxKind[scope.kind], 'to parent'); + scope = scope.parent; + depth++; + if (depth > 20) { + log('[DEBUG] Too deep, breaking'); + break; + } + } + if (!scope) { + log('[DEBUG] No scope found'); + variableHandlingCache.set(symbol, false); + return false; + } + log('[DEBUG] Found scope:', ts.SyntaxKind[scope.kind]); + // 打印 scope 的一些信息 + if (ts.isFunctionDeclaration(scope) || ts.isMethodDeclaration(scope)) { + const funcDecl = scope; + if (funcDecl.name) { + log('[DEBUG] Scope function name:', funcDecl.name.getText()); + } + } + log('[DEBUG] Checking variable:', symbol.name, 'in scope'); + // 检查各种处理方式 + const hasInstanceOf = hasInstanceOfPromiseCheck(symbol, scope); + const isAwaited = isVariableAwaitedInScope(symbol, scope); + const hasPromiseMethod = isVariableHandledByPromiseMethod(symbol, scope); + const inCallback = isVariableHandledInCallback(symbol, scope); + const passedToFunction = isVariablePassedToHandlingFunction(symbol, scope); + log('[DEBUG] Check results:', { + hasInstanceOf, + isAwaited, + hasPromiseMethod, + inCallback, + passedToFunction + }); + const result = hasInstanceOf || isAwaited || hasPromiseMethod || inCallback || passedToFunction; + variableHandlingCache.set(symbol, result); + return result; + } + finally { + checkingVariables.delete(symbol); + } + }; + // 分析函数参数是否在函数体内被正确处理 + const analyzeParameterHandling = (functionSymbol, declaration) => { + const parameterHandling = new Map(); + if (!declaration.parameters || declaration.parameters.length === 0) { + return parameterHandling; + } + // 获取函数体 + const body = declaration.body; + if (!body) { + return parameterHandling; + } + // 分析每个参数 + declaration.parameters.forEach((param, index) => { + if (!param.name) { + parameterHandling.set(index, false); + return; + } + // 处理标识符参数 + if (ts.isIdentifier(param.name)) { + const paramSymbol = typeChecker.getSymbolAtLocation(param.name); + if (paramSymbol) { + const isHandled = isPromiseProperlyHandled(paramSymbol, body); + parameterHandling.set(index, isHandled); + return; + } + } + // 处理解构参数 - 保守处理,标记为未处理 + // TODO: 可以进一步分析解构后的变量是否被处理 + parameterHandling.set(index, false); + }); + return parameterHandling; + }; + // 获取函数参数处理信息(带缓存) + const getFunctionParameterHandling = (functionSymbol) => { + if (functionParameterHandling.has(functionSymbol)) { + return functionParameterHandling.get(functionSymbol); + } + const declaration = functionDeclarations.get(functionSymbol); + if (!declaration) { + return new Map(); + } + const handling = analyzeParameterHandling(functionSymbol, declaration); + functionParameterHandling.set(functionSymbol, handling); + return handling; + }; + // 检查调用是否作为参数传递给会处理 Promise 的函数 + const isCallPassedToHandlingFunction = (callNode) => { + const parent = callNode.parent; + // 检查是否作为函数调用的参数 + if (ts.isCallExpression(parent)) { + // 找到当前调用在参数列表中的位置 + const argIndex = parent.arguments.indexOf(callNode); + if (argIndex === -1) { + return false; + } + // 获取被调用函数的签名 + const signature = typeChecker.getResolvedSignature(parent); + if (!signature) { + return false; + } + const declaration = signature.getDeclaration(); + if (!declaration) { + return false; + } + const calledSymbol = getSymbolOfDeclaration(declaration); + if (!calledSymbol) { + return false; + } + if (declaration && 'parameters' in declaration) { + const params = declaration.parameters; + const lastParam = params[params.length - 1]; + // 如果最后一个参数是剩余参数,且当前参数索引超出普通参数范围 + if (lastParam && lastParam.dotDotDotToken && argIndex >= params.length - 1) { + // 检查剩余参数是否被处理 + const paramHandling = getFunctionParameterHandling(calledSymbol); + return paramHandling.get(params.length - 1) === true; + } + } + // 检查该函数是否会处理这个参数位置的 Promise + const paramHandling = getFunctionParameterHandling(calledSymbol); + return paramHandling.get(argIndex) === true; + } + // 检查是否作为数组元素传递给 Promise.all 等 + if (ts.isArrayLiteralExpression(parent)) { + const grandParent = parent.parent; + if (ts.isCallExpression(grandParent) && + ts.isPropertyAccessExpression(grandParent.expression)) { + const object = grandParent.expression.expression; + if (ts.isIdentifier(object) && object.text === 'Promise') { + const methodName = grandParent.expression.name.text; + if (['all', 'race', 'allSettled', 'any'].includes(methodName)) { + return true; + } + } + } + } + return false; + }; + // 信息收集 + for (const sourceFile of program.getSourceFiles()) { + if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('node_modules')) { + continue; + } + collectInformation(sourceFile); } + // 预分析所有函数的参数处理情况 + for (const [symbol, declaration] of functionDeclarations.entries()) { + getFunctionParameterHandling(symbol); + } + // 标记传播 + propagateContextMarks(); + // 检查调用点 + checkCallSites(); return diagnostics; } -function compile(configPath) { +const compile = (configPath) => { // 读取自定义配置 const customConfig = readCustomConfig(configPath); // 读取 tsconfig.json @@ -626,7 +1156,7 @@ function compile(configPath) { process.exit(1); } console.log('Compilation completed successfully.'); -} +}; const build = (pwd, args) => { // 执行编译 const configPathArg = parseArgs(args); diff --git a/lib/store/CascadeStore.d.ts b/lib/store/CascadeStore.d.ts index f2c23f0..e9fb32b 100644 --- a/lib/store/CascadeStore.d.ts +++ b/lib/store/CascadeStore.d.ts @@ -76,7 +76,7 @@ export declare abstract class CascadeStore(entity: T, data: ED[T]['Create']['data']): void; protected preProcessDataUpdated(action: string, data: Record, async?: true): void; - judgeRelation(entity: keyof ED, attr: string): string | 1 | 2 | string[] | 0 | -1; + judgeRelation(entity: keyof ED, attr: string): string | 2 | 1 | string[] | 0 | -1; private tryMergeModi; /** * 和具体的update过程无关的例程放在这里,包括对later动作的处理、对oper的记录以及对record的收集等 diff --git a/lib/store/relation.d.ts b/lib/store/relation.d.ts index 5afd59c..5f94b00 100644 --- a/lib/store/relation.d.ts +++ b/lib/store/relation.d.ts @@ -9,4 +9,4 @@ import { StorageSchema } from "../types/Storage"; * @param row * @returns */ -export declare function judgeRelation(schema: StorageSchema, entity: keyof ED, attr: string, allowUnrecognized?: boolean): string | 1 | 2 | string[] | 0 | -1; +export declare function judgeRelation(schema: StorageSchema, entity: keyof ED, attr: string, allowUnrecognized?: boolean): string | 2 | 1 | string[] | 0 | -1; diff --git a/package.json b/package.json index 0a512e4..a9635d5 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ }, "scripts": { "make:domain": "ts-node scripts/make.ts", - "build": "node --stack-size=4096 ./scripts/build.js -p ./tsconfig.json", + "build": "node --stack-size=65500 ./scripts/build.js -p ./tsconfig.json", + "test:build": "node --stack-size=65500 ./scripts/build.js -p ./tsconfig.test.json", "prebuild": "npm run make:domain", "test": "ts-node test/test.ts" }, diff --git a/src/compiler/tscBuilder.ts b/src/compiler/tscBuilder.ts index 1d7dab2..c961c2d 100644 --- a/src/compiler/tscBuilder.ts +++ b/src/compiler/tscBuilder.ts @@ -3,7 +3,6 @@ import * as path from 'path'; import * as fs from 'fs'; const verboseLogging = false; -const MAX_RECURSION_DEPTH = 10; // 最大递归深度,防止无限递归 const log = (...args: any[]) => { if (verboseLogging) { @@ -125,21 +124,92 @@ function performCustomChecks( return diagnostics; } + // 数据结构定义 const functionsWithContextCalls = new Set(); - const checkedFunctions = new Map(); + const functionDeclarations = new Map(); + const functionCallGraph = new Map>(); // 谁调用了谁 + const directContextCalls = new Map(); // 函数内的直接 context 调用 + const allContextCalls: ts.CallExpression[] = []; // 所有 context 调用点 + // 缓存:函数符号 -> 参数索引 -> 是否被处理 + const functionParameterHandling = new Map>(); + // 缓存:变量符号 -> 是否被正确处理 + const variableHandlingCache = new Map(); + // 正在检查的变量集合(防止循环) + const checkingVariables = new Set(); - // 遍历所有源文件 - for (const sourceFile of program.getSourceFiles()) { - // 跳过声明文件和 node_modules - if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('node_modules')) { - continue; + const typeCache = new Map(); + + const addDiagnostic = ( + node: ts.Node, + messageText: string, + code: number + ): void => { + const sourceFile = node.getSourceFile(); + diagnostics.push({ + file: sourceFile, + start: node.getStart(sourceFile), + length: node.getWidth(sourceFile), + messageText, + category: ts.DiagnosticCategory.Warning, + code + }); + }; + + const shouldReportUnawaitedCall = ( + callNode: ts.CallExpression, + sourceFile: ts.SourceFile + ): boolean => { + // 跳过特定场景 + if (shouldSkipCheck(callNode)) { + return false; } - // 遍历 AST 节点 - visitNode(sourceFile, 0); - } + // 检查忽略注释 + if (hasIgnoreComment(callNode, sourceFile)) { + return false; + } - function resolveModuleName(moduleName: string, containingFile: string): string | undefined { + // 检查是否已 await + if (isAwaited(callNode)) { + return false; + } + + // 检查是否赋值给变量并正确处理 + if (isCallAssignedAndHandled(callNode)) { + return false; + } + + return true; + }; + + const isCallAssignedAndHandled = (callNode: ts.Node): boolean => { + const parent = callNode.parent; + if (ts.isVariableDeclaration(parent) && parent.initializer === callNode) { + if (parent.name && ts.isIdentifier(parent.name)) { + const variableSymbol = typeChecker.getSymbolAtLocation(parent.name); + if (variableSymbol) { + // 如果变量被 return,则在 shouldSkipCheck 中已经处理,这里不需要再检查 + if (isVariableReturned(variableSymbol, parent)) { + return true; // 被 return 的变量,认为已处理 + } + + // 否则检查是否在作用域内被正确处理 + if (isPromiseProperlyHandled(variableSymbol, parent)) { + return true; + } + } + } + } + + // 检查是否作为参数传递给会处理 Promise 的函数 + if (isCallPassedToHandlingFunction(callNode as ts.CallExpression)) { + return true; + } + + return false; + }; + + const resolveModuleName = (moduleName: string, containingFile: string): string | undefined => { const compilerOptions = program.getCompilerOptions(); const resolvedModule = ts.resolveModuleName( moduleName, @@ -151,101 +221,22 @@ function performCustomChecks( return resolvedModule.resolvedModule?.resolvedFileName; } - function normalizeModulePath(filePath: string): string { - // 规范化路径,移除 node_modules 和文件扩展名 - return filePath + const pathCache = new Map(); + + const normalizeModulePath = (filePath: string): string => { + if (pathCache.has(filePath)) { + return pathCache.get(filePath)!; + } + + const normalized = filePath .replace(/\\/g, '/') .replace(/\.(ts|tsx|js|jsx|d\.ts)$/, ''); + + pathCache.set(filePath, normalized); + return normalized; } - function visitNode(node: ts.Node, deepth: number): void { - if (deepth > MAX_RECURSION_DEPTH) { - log(`[DEBUG] 达到最大递归深度,停止进一步检查`); - return; - } - - // 检查 AsyncContext 方法调用 - checkAsyncContextMethodCall(node); - - //检查函数调用(检查是否调用了包含 context 的函数) - checkFunctionCall(node); - - // 递归遍历子节点 - ts.forEachChild(node, (i) => visitNode(i, deepth + 1)); - } - - function checkAsyncContextMethodCall(node: ts.Node): void { - // 只检查方法调用表达式 - if (!ts.isCallExpression(node)) { - return; - } - - // 检查是否是属性访问表达式 - if (!ts.isPropertyAccessExpression(node.expression)) { - return; - } - - // 如果上一层是return,则先跳过,让外层函数调用来处理 - if (ts.isReturnStatement(node.parent)) { - return; - } - - // 检查箭头函数的隐式返回,让外层函数调用来处理 - // 例如:() => context.select() - if (ts.isArrowFunction(node.parent) && node.parent.body === node) { - return; - } - - const propertyAccess = node.expression; - const objectExpression = propertyAccess.expression; - - // 获取对象的类型 - const objectType = typeChecker.getTypeAtLocation(objectExpression); - - const targetModules = customConfig.context?.targetModules || ['@project/context/BackendRuntimeContext']; - - // 检查类型是否继承自 AsyncContext - if (!isAsyncContextType(objectType, targetModules)) { - return; - } - - // 获取方法调用的返回类型 - const callSignature = typeChecker.getResolvedSignature(node); - if (!callSignature) { - return; - } - - const returnType = typeChecker.getReturnTypeOfSignature(callSignature); - - // 只检查返回 Promise 的方法 - if (!isPromiseType(returnType)) { - return; - } - - // 检查是否被 await 修饰 - if (!isAwaited(node)) { - const sourceFile = node.getSourceFile(); - - // 检查是否有忽略注释 - if (hasIgnoreComment(node, sourceFile)) { - return; - } - - const methodName = propertyAccess.name.getText(sourceFile); - const objectName = objectExpression.getText(sourceFile); - - diagnostics.push({ - file: sourceFile, - start: node.getStart(sourceFile), - length: node.getWidth(sourceFile), - messageText: `未await的context调用可能导致事务不受控: ${objectName}.${methodName}() 需要使用 await`, - category: ts.DiagnosticCategory.Warning, - code: 9100 - }); - } - } - - function hasIgnoreComment(node: ts.Node, sourceFile: ts.SourceFile): boolean { + const hasIgnoreComment = (node: ts.Node, sourceFile: ts.SourceFile): boolean => { const fullText = sourceFile.getFullText(); // 检查当前节点及其父节点(向上查找最多3层) @@ -298,11 +289,21 @@ function performCustomChecks( return false; } - function isIgnoreComment(commentText: string): boolean { + const isIgnoreComment = (commentText: string): boolean => { return OAK_IGNORE_TAGS.some(tag => commentText.includes(tag)); } - function isAsyncContextType(type: ts.Type, modules: string[]): boolean { + const isAsyncContextType = (type: ts.Type, modules: string[]): boolean => { + if (typeCache.has(type)) { + return typeCache.get(type)!; + } + const result = checkIsAsyncContextType(type, modules); + typeCache.set(type, result); + return result; + } + + const checkIsAsyncContextType = (type: ts.Type, modules: string[]): boolean => { + // 检查类型本身 if (checkTypeSymbol(type, modules)) { return true; @@ -327,9 +328,10 @@ function performCustomChecks( } return false; + } - function checkTypeSymbol(type: ts.Type, modules: string[]): boolean { + const checkTypeSymbol = (type: ts.Type, modules: string[]): boolean => { const symbol = type.getSymbol(); if (!symbol) { return false; @@ -385,13 +387,13 @@ function performCustomChecks( return false; } - function isSymbolRelated(symbol: ts.Symbol, moduleSymbol: ts.Symbol): boolean { + const isSymbolRelated = (symbol: ts.Symbol, moduleSymbol: ts.Symbol): boolean => { // 检查符号是否与模块相关 const exports = typeChecker.getExportsOfModule(moduleSymbol); return exports.some(exp => exp === symbol || exp.name === symbol.name); } - function isAwaited(node: ts.Node): boolean { + const isAwaited = (node: ts.Node): boolean => { let parent = node.parent; // 向上遍历父节点,查找 await 表达式 @@ -423,7 +425,7 @@ function performCustomChecks( return false; } - function isPromiseType(type: ts.Type): boolean { + const isPromiseType = (type: ts.Type): boolean => { // 检查类型符号 const symbol = type.getSymbol(); if (symbol) { @@ -455,7 +457,7 @@ function performCustomChecks( return false; } - function isNodeInSubtree(root: ts.Node, target: ts.Node): boolean { + const isNodeInSubtree = (root: ts.Node, target: ts.Node): boolean => { if (root === target) { return true; } @@ -471,184 +473,8 @@ function performCustomChecks( return found; } - function checkFunctionCall(node: ts.Node): void { - // 只检查调用表达式 - if (!ts.isCallExpression(node)) { - return; - } - - // 打印所有函数调用 - const sourceFile = node.getSourceFile(); - const callText = node.expression.getText(sourceFile); - log(`[DEBUG] 检查函数调用: ${callText}`); - - // 获取被调用函数的符号 - const signature = typeChecker.getResolvedSignature(node); - if (!signature) { - log(`[DEBUG] ${callText} - 无法获取签名`); - return; - } - - const declaration = signature.getDeclaration(); - if (!declaration) { - log(`[DEBUG] ${callText} - 无法获取声明`); - return; - } - - log(`[DEBUG] ${callText} - 声明类型: ${ts.SyntaxKind[declaration.kind]}`); - - // 检查这个函数是否包含 context 调用 - const hasContext = containsContextCall(declaration); - log(`[DEBUG] ${callText} - 包含context调用: ${hasContext}`); - - if (hasContext) { - const returnType = typeChecker.getReturnTypeOfSignature(signature); - - // 如果函数不返回 Promise,说明内部已经正确处理了异步调用 - // 此时调用该函数不需要 await - if (!isPromiseType(returnType)) { - return; - } - - // 标记当前所在的函数 - const containingFunction = findContainingFunction(node); - if (containingFunction) { - const fname = getFunctionName(containingFunction); - if (fname) { // 只有当有名称时才标记,但不影响后续检查 - const symbol = typeChecker.getSymbolAtLocation(fname); - if (symbol) { - functionsWithContextCalls.add(symbol); - } - } - } - - if (ts.isReturnStatement(node.parent)) { - // 如果在 return 语句中,不在这里报警告 - // 警告会传播到调用当前函数的地方 - return; - } - - // 检查箭头函数的隐式返回 - // 例如:() => someFunction() - if (ts.isArrowFunction(node.parent) && node.parent.body === node) { - return; - } - - // 检查是否被 await - if (!isAwaited(node)) { - const sourceFile = node.getSourceFile(); - - // 检查是否有忽略注释 - if (hasIgnoreComment(node, sourceFile)) { - return; - } - - const functionName = getFunctionCallName(node, sourceFile); - - diagnostics.push({ - file: sourceFile, - start: node.getStart(sourceFile), - length: node.getWidth(sourceFile), - messageText: `未await的函数调用可能导致事务不受控: ${functionName}() 内部包含 context 调用,需要使用 await`, - category: ts.DiagnosticCategory.Warning, - code: 9101 - }); - } - } - } - - // 辅助函数:检查函数声明是否包含 context 调用 - function containsContextCall(node: ts.Node): boolean { - const symbol = getSymbolOfDeclaration(node as ts.SignatureDeclaration); - - log(`[DEBUG containsContextCall] 节点类型: ${ts.SyntaxKind[node.kind]}, 有符号: ${!!symbol}`); - - if (symbol && checkedFunctions.has(symbol)) { - log(`[DEBUG containsContextCall] 使用缓存结果: ${checkedFunctions.get(symbol)}`); - return checkedFunctions.get(symbol)!; - } - - - let hasContextCall = false; - - function visit(n: ts.Node): void { - if (hasContextCall) return; - - // 检查是否是 context 方法调用 - if (ts.isCallExpression(n) && - ts.isPropertyAccessExpression(n.expression)) { - const objectType = typeChecker.getTypeAtLocation(n.expression.expression); - const targetModules = customConfig.context?.targetModules || - ['@project/context/BackendRuntimeContext']; - - if (isAsyncContextType(objectType, targetModules)) { - log(`[DEBUG containsContextCall] 找到context调用: ${n.expression.getText()}`); - hasContextCall = true; - return; - } - } - - // 递归检查调用的其他函数 - if (ts.isCallExpression(n)) { - const signature = typeChecker.getResolvedSignature(n); - if (signature) { - const declaration = signature.getDeclaration(); - if (declaration && !isSameOrDescendant(declaration, node)) { - // 避免无限递归 - const symbol = getSymbolOfDeclaration(declaration); - - // 先检查缓存 - if (symbol && functionsWithContextCalls.has(symbol)) { - hasContextCall = true; - return; - } - - // 如果缓存中没有,递归检查该函数 - if (containsContextCall(declaration)) { - hasContextCall = true; - return; - } - } - } - } - - ts.forEachChild(n, visit); - } - - visit(node); - log(`[DEBUG containsContextCall] 最终结果: ${hasContextCall}`); - - if (symbol) { - checkedFunctions.set(symbol, hasContextCall); - } - return hasContextCall; - } - - // 辅助函数:查找包含当前节点的函数 - function findContainingFunction(node: ts.Node): ts.FunctionLikeDeclaration | undefined { - let current = node.parent; - while (current) { - if (ts.isFunctionDeclaration(current) || - ts.isFunctionExpression(current) || - ts.isArrowFunction(current) || - ts.isMethodDeclaration(current)) { - return current; - } - current = current.parent; - } - return undefined; - } - - // 辅助函数:获取函数名称节点 - function getFunctionName(func: ts.FunctionLikeDeclaration): ts.Node | undefined { - if (ts.isFunctionDeclaration(func) || ts.isMethodDeclaration(func)) { - return func.name; - } - return undefined; - } - // 辅助函数:获取函数调用的名称 - function getFunctionCallName(node: ts.CallExpression, sourceFile: ts.SourceFile): string { + const getFunctionCallName = (node: ts.CallExpression, sourceFile: ts.SourceFile): string => { if (ts.isIdentifier(node.expression)) { return node.expression.getText(sourceFile); } else if (ts.isPropertyAccessExpression(node.expression)) { @@ -657,20 +483,8 @@ function performCustomChecks( return node.expression.getText(sourceFile); } - // 辅助函数:检查是否是同一个节点或后代 - function isSameOrDescendant(node: ts.Node, ancestor: ts.Node): boolean { - let current: ts.Node | undefined = node; - while (current) { - if (current === ancestor) { - return true; - } - current = current.parent; - } - return false; - } - // 辅助函数:获取声明的符号 - function getSymbolOfDeclaration(declaration: ts.SignatureDeclaration): ts.Symbol | undefined { + const getSymbolOfDeclaration = (declaration: ts.SignatureDeclaration): ts.Symbol | undefined => { // 处理有名称的声明(函数声明、方法声明等) if ('name' in declaration && declaration.name) { return typeChecker.getSymbolAtLocation(declaration.name); @@ -691,10 +505,823 @@ function performCustomChecks( return undefined; } + const collectInformation = (sourceFile: ts.SourceFile): void => { + let currentFunction: ts.Symbol | undefined; + + const visit = (node: ts.Node): void => { + // 记录函数声明 + if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || + ts.isArrowFunction(node) || ts.isMethodDeclaration(node)) { + const symbol = getSymbolOfDeclaration(node as ts.SignatureDeclaration); + if (symbol) { + functionDeclarations.set(symbol, node as ts.FunctionLikeDeclaration); + const previousFunction = currentFunction; + currentFunction = symbol; + ts.forEachChild(node, visit); + currentFunction = previousFunction; + return; + } + } + + // 记录 context 方法调用 + if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { + const objectType = typeChecker.getTypeAtLocation(node.expression.expression); + const targetModules = customConfig.context?.targetModules || + ['@project/context/BackendRuntimeContext']; + + if (isAsyncContextType(objectType, targetModules)) { + allContextCalls.push(node); + + // 记录到当前函数 + if (currentFunction) { + if (!directContextCalls.has(currentFunction)) { + directContextCalls.set(currentFunction, []); + } + directContextCalls.get(currentFunction)!.push(node); + } + } + } + + // 记录函数调用关系 + if (ts.isCallExpression(node) && currentFunction) { + const signature = typeChecker.getResolvedSignature(node); + if (signature) { + const declaration = signature.getDeclaration(); + if (declaration) { + const calledSymbol = getSymbolOfDeclaration(declaration); + if (calledSymbol && calledSymbol !== currentFunction) { + if (!functionCallGraph.has(currentFunction)) { + functionCallGraph.set(currentFunction, new Set()); + } + functionCallGraph.get(currentFunction)!.add(calledSymbol); + } + } + } + } + + ts.forEachChild(node, visit); + } + + visit(sourceFile); + } + + const propagateContextMarks = (): void => { + // 初始标记:直接包含 context 调用的函数 + const markedFunctions = new Set(directContextCalls.keys()); + + // 迭代传播标记 + let changed = true; + while (changed) { + changed = false; + + for (const [caller, callees] of functionCallGraph.entries()) { + if (markedFunctions.has(caller)) continue; + + // 如果调用了任何标记的函数,则标记当前函数 + for (const callee of callees) { + if (markedFunctions.has(callee)) { + markedFunctions.add(caller); + functionsWithContextCalls.add(caller); + changed = true; + break; + } + } + } + } + + + + // 更新全局标记 + markedFunctions.forEach(symbol => functionsWithContextCalls.add(symbol)); + } + + const checkCallSites = (): void => { + // 检查所有直接的 context 调用 + for (const callNode of allContextCalls) { + if (shouldSkipCheck(callNode)) continue; + + const signature = typeChecker.getResolvedSignature(callNode); + if (!signature) continue; + + const returnType = typeChecker.getReturnTypeOfSignature(signature); + if (!isPromiseType(returnType)) continue; + + // 检查是否直接 await + const sourceFile = callNode.getSourceFile(); + if (shouldReportUnawaitedCall(callNode, sourceFile)) { + const propertyAccess = callNode.expression as ts.PropertyAccessExpression; + const methodName = propertyAccess.name.getText(sourceFile); + const objectName = propertyAccess.expression.getText(sourceFile); + + addDiagnostic( + callNode, + `未await的context调用可能导致事务不受控: ${objectName}.${methodName}() 需要使用 await 或通过 instanceof Promise 判断后处理`, + 9100 + ); + } + } + + // 检查所有标记函数的调用点 + for (const sourceFile of program.getSourceFiles()) { + if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('node_modules')) { + continue; + } + checkFunctionCallsInFile(sourceFile); + } + } + + const checkFunctionCallsInFile = (sourceFile: ts.SourceFile): void => { + const visit = (node: ts.Node): void => { + if (ts.isCallExpression(node)) { + if (shouldSkipCheck(node)) { + ts.forEachChild(node, visit); + return; + } + + const signature = typeChecker.getResolvedSignature(node); + if (signature) { + const declaration = signature.getDeclaration(); + if (declaration) { + const symbol = getSymbolOfDeclaration(declaration); + + // 检查是否调用了标记的函数 + if (symbol && functionsWithContextCalls.has(symbol)) { + const returnType = typeChecker.getReturnTypeOfSignature(signature); + if (!isPromiseType(returnType)) { + ts.forEachChild(node, visit); + return; + } + + if (shouldReportUnawaitedCall(node, sourceFile)) { + const functionName = getFunctionCallName(node, sourceFile); + addDiagnostic( + node, + `未await的函数调用可能导致事务不受控: ${functionName}() 内部包含 context 调用,需要使用 await 或通过 instanceof Promise 判断后处理`, + 9101 + ); + } + } + } + } + } + + ts.forEachChild(node, visit); + } + + visit(sourceFile); + } + + const shouldSkipCheck = (node: ts.CallExpression): boolean => { + // 直接在 return 语句中 + if (ts.isReturnStatement(node.parent)) { + return true; + } + + // 箭头函数的直接返回值 + if (ts.isArrowFunction(node.parent) && node.parent.body === node) { + return true; + } + + // 检查是否赋值给变量,然后该变量被 return + if (ts.isVariableDeclaration(node.parent) && node.parent.initializer === node) { + const variableDecl = node.parent; + if (variableDecl.name && ts.isIdentifier(variableDecl.name)) { + const variableSymbol = typeChecker.getSymbolAtLocation(variableDecl.name); + if (variableSymbol && isVariableReturned(variableSymbol, variableDecl)) { + return true; + } + } + } + + return false; + }; + + // 检查变量是否在其作用域内被 return + const isVariableReturned = (symbol: ts.Symbol, declarationNode: ts.Node): boolean => { + let scope: ts.Node = declarationNode; + while (scope && !ts.isFunctionDeclaration(scope) && + !ts.isFunctionExpression(scope) && + !ts.isArrowFunction(scope) && + !ts.isMethodDeclaration(scope) && + !ts.isSourceFile(scope)) { + scope = scope.parent; + } + + if (!scope || ts.isSourceFile(scope)) { + return false; + } + + let found = false; + + const checkIfContainsVariable = (node: ts.Node): boolean => { + if (ts.isIdentifier(node)) { + const nodeSymbol = typeChecker.getSymbolAtLocation(node); + return nodeSymbol === symbol; + } + + let contains = false; + ts.forEachChild(node, (child) => { + if (contains) return; + if (checkIfContainsVariable(child)) { + contains = true; + } + }); + return contains; + }; + + const visit = (node: ts.Node): void => { + if (found) return; + + // 检查 return 语句 + if (ts.isReturnStatement(node) && node.expression) { + // 检查 return 的表达式中是否包含该变量 + if (checkIfContainsVariable(node.expression)) { + found = true; + return; + } + } + + // 不进入嵌套函数 + if (ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isArrowFunction(node)) { + return; + } + + ts.forEachChild(node, visit); + }; + + const funcLike = scope as ts.FunctionLikeDeclaration; + if (funcLike.body) { + visit(funcLike.body); + } + + return found; + }; + + // 检查变量是否通过 instanceof Promise 判断 + // 检查变量是否通过 instanceof Promise 判断 + const hasInstanceOfPromiseCheck = (symbol: ts.Symbol, scope: ts.Node): boolean => { + let found = false; + + const visit = (node: ts.Node): void => { + if (found) return; + + // 检查 instanceof Promise + if (ts.isBinaryExpression(node) && + node.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) { + const left = node.left; + if (ts.isIdentifier(left)) { + const leftSymbol = typeChecker.getSymbolAtLocation(left); + if (leftSymbol === symbol) { + const right = node.right; + // 修改:检查右侧是否是 Promise 标识符 + if (ts.isIdentifier(right) && right.text === 'Promise') { + found = true; + return; + } + // 也检查类型(兼容其他情况) + const rightType = typeChecker.getTypeAtLocation(right); + const rightSymbol = rightType.getSymbol(); + if (rightSymbol && rightSymbol.name === 'PromiseConstructor') { + found = true; + return; + } + } + } + } + + ts.forEachChild(node, visit); + }; + + visit(scope); + return found; + }; + + // 辅助函数:检查是否有 return 语句返回 Promise 处理结果 + const hasReturnWithPromiseHandling = (node: ts.Node, symbol: ts.Symbol): boolean => { + let found = false; + + const visit = (n: ts.Node): void => { + if (found) return; + + if (ts.isReturnStatement(n) && n.expression) { + // 检查返回的表达式是否是对该变量的 Promise 方法调用 + if (ts.isCallExpression(n.expression) && + ts.isPropertyAccessExpression(n.expression.expression)) { + const object = n.expression.expression.expression; + if (ts.isIdentifier(object)) { + const objSymbol = typeChecker.getSymbolAtLocation(object); + if (objSymbol === symbol) { + const methodName = n.expression.expression.name.text; + if (methodName === 'then' || methodName === 'catch' || methodName === 'finally') { + found = true; + return; + } + } + } + } + } + + // 不进入嵌套函数 + if (ts.isFunctionDeclaration(n) || + ts.isFunctionExpression(n) || + ts.isArrowFunction(n)) { + return; + } + + ts.forEachChild(n, visit); + }; + + visit(node); + return found; + }; + + // 检查变量是否在作用域内被 await + const isVariableAwaitedInScope = (symbol: ts.Symbol, scope: ts.Node): boolean => { + let found = false; + + const visit = (node: ts.Node): void => { + if (found) return; + + // 检查 await 表达式 + if (ts.isAwaitExpression(node)) { + const expression = node.expression; + if (ts.isIdentifier(expression)) { + const exprSymbol = typeChecker.getSymbolAtLocation(expression); + if (exprSymbol === symbol) { + found = true; + return; + } + } + } + + // 不进入嵌套函数 + if (ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isArrowFunction(node)) { + return; + } + + ts.forEachChild(node, visit); + }; + + visit(scope); + return found; + }; + + // 检查变量是否通过 Promise 方法处理(.then, .catch, Promise.all 等) + const isVariableHandledByPromiseMethod = (symbol: ts.Symbol, scope: ts.Node): boolean => { + let found = false; + + const visit = (node: ts.Node): void => { + if (found) return; + + // 检查 .then() 或 .catch() 调用 + if (ts.isCallExpression(node) && + ts.isPropertyAccessExpression(node.expression)) { + const object = node.expression.expression; + if (ts.isIdentifier(object)) { + const objSymbol = typeChecker.getSymbolAtLocation(object); + if (objSymbol === symbol) { + const methodName = node.expression.name.text; + if (methodName === 'then' || methodName === 'catch' || + methodName === 'finally') { + found = true; + return; + } + } + } + } + + // 检查 Promise.all, Promise.race 等 + if (ts.isCallExpression(node) && + ts.isPropertyAccessExpression(node.expression)) { + const object = node.expression.expression; + if (ts.isIdentifier(object) && object.text === 'Promise') { + const methodName = node.expression.name.text; + if (methodName === 'all' || methodName === 'race' || + methodName === 'allSettled' || methodName === 'any') { + // 检查参数中是否包含该变量 + for (const arg of node.arguments) { + if (containsVariable(arg, symbol)) { + found = true; + return; + } + } + } + } + } + + ts.forEachChild(node, visit); + }; + + visit(scope); + return found; + }; + + // 检查节点中是否包含指定变量 + const containsVariable = (node: ts.Node, symbol: ts.Symbol): boolean => { + if (ts.isIdentifier(node)) { + const nodeSymbol = typeChecker.getSymbolAtLocation(node); + return nodeSymbol === symbol; + } + + let found = false; + ts.forEachChild(node, (child) => { + if (found) return; + if (containsVariable(child, symbol)) { + found = true; + } + }); + + return found; + }; + + const isVariableHandledInCallback = (symbol: ts.Symbol, scope: ts.Node, depth: number = 0): boolean => { + // 深度限制 + if (depth > 2) { + return false; + } + + let found = false; + + const visit = (node: ts.Node): void => { + if (found) return; + + // 检查是否作为回调参数传递 + if (ts.isCallExpression(node)) { + for (const arg of node.arguments) { + if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) { + // 检查回调函数体内是否处理了该变量 + if (arg.body && containsVariable(arg.body, symbol)) { + // 使用简化版本检查,避免深度递归 + if (hasInstanceOfPromiseCheck(symbol, arg.body) || + isVariableAwaitedInScope(symbol, arg.body) || + isVariableHandledByPromiseMethod(symbol, arg.body)) { + found = true; + return; + } + } + } + } + } + + ts.forEachChild(node, visit); + }; + + visit(scope); + return found; + }; + + // 检查变量是否被传递给会处理 Promise 的函数参数 + const isVariablePassedToHandlingFunction = (symbol: ts.Symbol, scope: ts.Node, depth: number = 0): boolean => { + // 深度限制,防止过深的递归 + if (depth > 2) { + return false; + } + + log('[DEBUG] isVariablePassedToHandlingFunction: checking', symbol.name, 'depth:', depth); + log('[DEBUG] Scope kind:', ts.SyntaxKind[scope.kind]); + + let found = false; + let callCount = 0; + + const visit = (node: ts.Node): void => { + if (found) return; + + log('[DEBUG] Visiting node:', ts.SyntaxKind[node.kind]); + + // 检查函数调用 + if (ts.isCallExpression(node)) { + callCount++; + log('[DEBUG] Found call expression #', callCount); + + // 遍历所有参数 + node.arguments.forEach((arg, argIndex) => { + if (found) return; + + log('[DEBUG] Checking argument', argIndex, 'kind:', ts.SyntaxKind[arg.kind]); + + // 检查参数是否是我们要找的变量 + if (ts.isIdentifier(arg)) { + log('[DEBUG] Argument is identifier:', arg.text); + const argSymbol = typeChecker.getSymbolAtLocation(arg); + + if (argSymbol === symbol) { + log('[DEBUG] ✓ Found variable', symbol.name, 'passed as argument at index:', argIndex); + + // 获取被调用函数的签名 + const signature = typeChecker.getResolvedSignature(node); + if (!signature) { + log('[DEBUG] ✗ No signature found'); + return; + } + + const declaration = signature.getDeclaration(); + if (!declaration) { + log('[DEBUG] ✗ No declaration found'); + return; + } + + const calledSymbol = getSymbolOfDeclaration(declaration); + if (!calledSymbol) { + log('[DEBUG] ✗ No called symbol found'); + return; + } + + log('[DEBUG] Called function:', calledSymbol.name); + + // 检查该函数是否会处理这个参数位置的 Promise + const paramHandling = getFunctionParameterHandling(calledSymbol); + log('[DEBUG] Parameter handling map:', Array.from(paramHandling.entries())); + log('[DEBUG] Parameter handling for index', argIndex, ':', paramHandling.get(argIndex)); + + if (paramHandling.get(argIndex) === true) { + log('[DEBUG] ✓✓✓ Parameter is handled!'); + found = true; + return; + } else { + log('[DEBUG] ✗ Parameter is NOT handled'); + } + + // 处理剩余参数的情况 + if (declaration && 'parameters' in declaration) { + const params = (declaration as ts.FunctionLikeDeclaration).parameters; + if (params.length > 0) { + const lastParam = params[params.length - 1]; + if (lastParam && lastParam.dotDotDotToken && argIndex >= params.length - 1) { + log('[DEBUG] Checking rest parameter at index', params.length - 1); + if (paramHandling.get(params.length - 1) === true) { + log('[DEBUG] ✓✓✓ Rest parameter is handled!'); + found = true; + return; + } + } + } + } + } else { + log('[DEBUG] Argument symbol mismatch:', argSymbol?.name, 'vs', symbol.name); + } + } + }); + } + + // 不进入嵌套函数的内部 + if (ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isArrowFunction(node) || + ts.isMethodDeclaration(node)) { + log('[DEBUG] Skipping nested function body'); + return; // 不调用 ts.forEachChild,直接返回 + } + + ts.forEachChild(node, visit); + }; + + // 关键修改:从函数体开始遍历,而不是从函数声明开始 + let startNode: ts.Node | undefined; + + if (ts.isFunctionDeclaration(scope) || ts.isFunctionExpression(scope) || + ts.isArrowFunction(scope) || ts.isMethodDeclaration(scope)) { + const funcLike = scope as ts.FunctionLikeDeclaration; + startNode = funcLike.body; + log('[DEBUG] Starting from function body'); + } else if (ts.isSourceFile(scope)) { + startNode = scope; + log('[DEBUG] Starting from source file'); + } else { + startNode = scope; + log('[DEBUG] Starting from scope directly'); + } + + if (startNode) { + visit(startNode); + } + + log('[DEBUG] isVariablePassedToHandlingFunction result for', symbol.name, ':', found, '(checked', callCount, 'calls)'); + return found; + }; + + // 综合检查:Promise 是否被正确处理 + const isPromiseProperlyHandled = (symbol: ts.Symbol, declarationNode: ts.Node): boolean => { + // 检查缓存 + if (variableHandlingCache.has(symbol)) { + return variableHandlingCache.get(symbol)!; + } + + // 检查是否正在检查这个变量(防止循环) + if (checkingVariables.has(symbol)) { + return false; + } + + checkingVariables.add(symbol); + + try { + log('[DEBUG] isPromiseProperlyHandled: variable', symbol.name, 'declarationNode kind:', ts.SyntaxKind[declarationNode.kind]); + + // 找到包含该声明的函数作用域 + let scope: ts.Node = declarationNode; + let depth = 0; + while (scope && !ts.isFunctionDeclaration(scope) && + !ts.isFunctionExpression(scope) && + !ts.isArrowFunction(scope) && + !ts.isMethodDeclaration(scope) && + !ts.isSourceFile(scope)) { + log('[DEBUG] Moving up from', ts.SyntaxKind[scope.kind], 'to parent'); + scope = scope.parent; + depth++; + if (depth > 20) { + log('[DEBUG] Too deep, breaking'); + break; + } + } + + if (!scope) { + log('[DEBUG] No scope found'); + variableHandlingCache.set(symbol, false); + return false; + } + + log('[DEBUG] Found scope:', ts.SyntaxKind[scope.kind]); + + // 打印 scope 的一些信息 + if (ts.isFunctionDeclaration(scope) || ts.isMethodDeclaration(scope)) { + const funcDecl = scope as ts.FunctionDeclaration | ts.MethodDeclaration; + if (funcDecl.name) { + log('[DEBUG] Scope function name:', funcDecl.name.getText()); + } + } + + log('[DEBUG] Checking variable:', symbol.name, 'in scope'); + + // 检查各种处理方式 + const hasInstanceOf = hasInstanceOfPromiseCheck(symbol, scope); + const isAwaited = isVariableAwaitedInScope(symbol, scope); + const hasPromiseMethod = isVariableHandledByPromiseMethod(symbol, scope); + const inCallback = isVariableHandledInCallback(symbol, scope); + const passedToFunction = isVariablePassedToHandlingFunction(symbol, scope); + + log('[DEBUG] Check results:', { + hasInstanceOf, + isAwaited, + hasPromiseMethod, + inCallback, + passedToFunction + }); + + const result = hasInstanceOf || isAwaited || hasPromiseMethod || inCallback || passedToFunction; + + variableHandlingCache.set(symbol, result); + return result; + } finally { + checkingVariables.delete(symbol); + } + }; + + // 分析函数参数是否在函数体内被正确处理 + const analyzeParameterHandling = (functionSymbol: ts.Symbol, declaration: ts.FunctionLikeDeclaration): Map => { + const parameterHandling = new Map(); + + if (!declaration.parameters || declaration.parameters.length === 0) { + return parameterHandling; + } + + // 获取函数体 + const body = declaration.body; + if (!body) { + return parameterHandling; + } + + // 分析每个参数 + declaration.parameters.forEach((param, index) => { + if (!param.name) { + parameterHandling.set(index, false); + return; + } + + // 处理标识符参数 + if (ts.isIdentifier(param.name)) { + const paramSymbol = typeChecker.getSymbolAtLocation(param.name); + if (paramSymbol) { + const isHandled = isPromiseProperlyHandled(paramSymbol, body); + parameterHandling.set(index, isHandled); + return; + } + } + + // 处理解构参数 - 保守处理,标记为未处理 + // TODO: 可以进一步分析解构后的变量是否被处理 + parameterHandling.set(index, false); + }); + + return parameterHandling; + }; + + // 获取函数参数处理信息(带缓存) + const getFunctionParameterHandling = (functionSymbol: ts.Symbol): Map => { + if (functionParameterHandling.has(functionSymbol)) { + return functionParameterHandling.get(functionSymbol)!; + } + + const declaration = functionDeclarations.get(functionSymbol); + if (!declaration) { + return new Map(); + } + + const handling = analyzeParameterHandling(functionSymbol, declaration); + functionParameterHandling.set(functionSymbol, handling); + return handling; + }; + + // 检查调用是否作为参数传递给会处理 Promise 的函数 + const isCallPassedToHandlingFunction = (callNode: ts.CallExpression): boolean => { + const parent = callNode.parent; + + // 检查是否作为函数调用的参数 + if (ts.isCallExpression(parent)) { + // 找到当前调用在参数列表中的位置 + const argIndex = parent.arguments.indexOf(callNode); + if (argIndex === -1) { + return false; + } + + // 获取被调用函数的签名 + const signature = typeChecker.getResolvedSignature(parent); + if (!signature) { + return false; + } + + const declaration = signature.getDeclaration(); + if (!declaration) { + return false; + } + + const calledSymbol = getSymbolOfDeclaration(declaration); + if (!calledSymbol) { + return false; + } + + if (declaration && 'parameters' in declaration) { + const params = (declaration as ts.FunctionLikeDeclaration).parameters; + const lastParam = params[params.length - 1]; + + // 如果最后一个参数是剩余参数,且当前参数索引超出普通参数范围 + if (lastParam && lastParam.dotDotDotToken && argIndex >= params.length - 1) { + // 检查剩余参数是否被处理 + const paramHandling = getFunctionParameterHandling(calledSymbol); + return paramHandling.get(params.length - 1) === true; + } + } + + // 检查该函数是否会处理这个参数位置的 Promise + const paramHandling = getFunctionParameterHandling(calledSymbol); + return paramHandling.get(argIndex) === true; + } + + // 检查是否作为数组元素传递给 Promise.all 等 + if (ts.isArrayLiteralExpression(parent)) { + const grandParent = parent.parent; + if (ts.isCallExpression(grandParent) && + ts.isPropertyAccessExpression(grandParent.expression)) { + const object = grandParent.expression.expression; + if (ts.isIdentifier(object) && object.text === 'Promise') { + const methodName = grandParent.expression.name.text; + if (['all', 'race', 'allSettled', 'any'].includes(methodName)) { + return true; + } + } + } + } + + return false; + }; + + // 信息收集 + for (const sourceFile of program.getSourceFiles()) { + if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('node_modules')) { + continue; + } + + collectInformation(sourceFile); + } + + // 预分析所有函数的参数处理情况 + for (const [symbol, declaration] of functionDeclarations.entries()) { + getFunctionParameterHandling(symbol); + } + + // 标记传播 + propagateContextMarks(); + + // 检查调用点 + checkCallSites(); + + return diagnostics; } -function compile(configPath: string): void { +const compile = (configPath: string): void => { // 读取自定义配置 const customConfig = readCustomConfig(configPath); diff --git a/tsconfig.json b/tsconfig.json index 4169b2a..e95abc1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -71,5 +71,11 @@ "**/*.spec.ts", "test", "src/base-app-domain-back" - ] + ], + "oakBuildChecks": { + "context": { + "checkAsyncContext": true, + "targetModules": ["store/AsyncRowStore"] + } + } } \ No newline at end of file diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..98ff932 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "jsx": "preserve", + "target": "ESNEXT", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "declaration": true, /* Generates corresponding '.d.ts' file. */ + "importHelpers": true, + "rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + "strict": true, /* Enable all strict type-checking options. */ + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ + "noEmit": true + }, + "include": ["test/builder/**/*", "src/***/*.ts"], + "exclude": [ + "node_modules", + "**/*.spec.ts", + ], + "oakBuildChecks": { + "context": { + "checkAsyncContext": true, + "targetModules": ["store/AsyncRowStore"] + } + } +} \ No newline at end of file