feat: 新增传递性检查

This commit is contained in:
Pan Qiancheng 2026-01-04 14:45:04 +08:00
parent 9a9066f2f2
commit d71a223138
2 changed files with 325 additions and 0 deletions

View File

@ -53,6 +53,8 @@ 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
@ -76,6 +78,8 @@ function performCustomChecks(program, typeChecker, customConfig) {
function visitNode(node) {
// 检查 AsyncContext 方法调用
checkAsyncContextMethodCall(node);
//检查函数调用(检查是否调用了包含 context 的函数)
checkFunctionCall(node);
// 递归遍历子节点
ts.forEachChild(node, visitNode);
}
@ -306,6 +310,152 @@ function performCustomChecks(program, typeChecker, customConfig) {
});
return found;
}
function checkFunctionCall(node) {
// 只检查调用表达式
if (!ts.isCallExpression(node)) {
return;
}
// 获取被调用函数的符号
const signature = typeChecker.getResolvedSignature(node);
if (!signature) {
return;
}
const declaration = signature.getDeclaration();
if (!declaration) {
return;
}
// 检查这个函数是否包含 context 调用
if (containsContextCall(declaration)) {
const returnType = typeChecker.getReturnTypeOfSignature(signature);
// 如果函数不返回 Promise说明内部已经正确处理了异步调用
// 此时调用该函数不需要 await
if (!isPromiseType(returnType)) {
return;
}
// 标记当前所在的函数
const containingFunction = findContainingFunction(node);
if (containingFunction) {
const fname = getFunctionName(containingFunction);
if (!fname) {
return;
}
const symbol = typeChecker.getSymbolAtLocation(fname);
if (symbol) {
functionsWithContextCalls.add(symbol);
}
}
// 检查是否被 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);
if (symbol && checkedFunctions.has(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)) {
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;
}
}
}
}
ts.forEachChild(n, visit);
}
visit(node);
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) {
if (ts.isIdentifier(node.expression)) {
return node.expression.getText(sourceFile);
}
else if (ts.isPropertyAccessExpression(node.expression)) {
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) {
if ('name' in declaration && declaration.name) {
return typeChecker.getSymbolAtLocation(declaration.name);
}
return undefined;
}
return diagnostics;
}
function compile(configPath) {

View File

@ -90,6 +90,9 @@ function performCustomChecks(
return diagnostics;
}
const functionsWithContextCalls = new Set<ts.Symbol>();
const checkedFunctions = new Map<ts.Symbol, boolean>();
// 遍历所有源文件
for (const sourceFile of program.getSourceFiles()) {
// 跳过声明文件和 node_modules
@ -124,6 +127,9 @@ function performCustomChecks(
// 检查 AsyncContext 方法调用
checkAsyncContextMethodCall(node);
//检查函数调用(检查是否调用了包含 context 的函数)
checkFunctionCall(node);
// 递归遍历子节点
ts.forEachChild(node, visitNode);
}
@ -404,6 +410,175 @@ function performCustomChecks(
return found;
}
function checkFunctionCall(node: ts.Node): void {
// 只检查调用表达式
if (!ts.isCallExpression(node)) {
return;
}
// 获取被调用函数的符号
const signature = typeChecker.getResolvedSignature(node);
if (!signature) {
return;
}
const declaration = signature.getDeclaration();
if (!declaration) {
return;
}
// 检查这个函数是否包含 context 调用
if (containsContextCall(declaration)) {
const returnType = typeChecker.getReturnTypeOfSignature(signature);
// 如果函数不返回 Promise说明内部已经正确处理了异步调用
// 此时调用该函数不需要 await
if (!isPromiseType(returnType)) {
return;
}
// 标记当前所在的函数
const containingFunction = findContainingFunction(node);
if (containingFunction) {
const fname = getFunctionName(containingFunction)
if (!fname) {
return;
}
const symbol = typeChecker.getSymbolAtLocation(
fname
);
if (symbol) {
functionsWithContextCalls.add(symbol);
}
}
// 检查是否被 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);
if (symbol && checkedFunctions.has(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)) {
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;
}
}
}
}
ts.forEachChild(n, visit);
}
visit(node);
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 {
if (ts.isIdentifier(node.expression)) {
return node.expression.getText(sourceFile);
} else if (ts.isPropertyAccessExpression(node.expression)) {
return node.expression.name.getText(sourceFile);
}
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 {
if ('name' in declaration && declaration.name) {
return typeChecker.getSymbolAtLocation(declaration.name);
}
return undefined;
}
return diagnostics;
}