feat: 新增传递性检查
This commit is contained in:
parent
9a9066f2f2
commit
d71a223138
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue