1389 lines
60 KiB
JavaScript
1389 lines
60 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.build = exports.OAK_IGNORE_TAGS = void 0;
|
||
const tslib_1 = require("tslib");
|
||
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 log = verboseLogging
|
||
? (...args) => console.log('[tscBuilder]', ...args)
|
||
: () => { }; // 空函数,避免参数计算开销
|
||
exports.OAK_IGNORE_TAGS = [
|
||
'@oak-ignore',
|
||
'@oak-ignore-asynccontext',
|
||
];
|
||
// ANSI 颜色代码
|
||
const colors = {
|
||
reset: '\x1b[0m',
|
||
cyan: '\x1b[36m',
|
||
red: '\x1b[91m',
|
||
yellow: '\x1b[93m',
|
||
gray: '\x1b[90m',
|
||
green: '\x1b[92m',
|
||
};
|
||
// 解析命令行参数
|
||
function parseArgs(pargs) {
|
||
const args = pargs.slice(2);
|
||
let configPath = 'tsconfig.json';
|
||
for (let i = 0; i < args.length; i++) {
|
||
if (args[i] === '-p' || args[i] === '--project') {
|
||
if (i + 1 < args.length) {
|
||
configPath = args[i + 1];
|
||
break;
|
||
}
|
||
else {
|
||
console.error('error: option \'-p, --project\' argument missing');
|
||
process.exit(1);
|
||
}
|
||
}
|
||
}
|
||
return configPath;
|
||
}
|
||
// 读取自定义配置
|
||
function readCustomConfig(configPath) {
|
||
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
||
if (configFile.error) {
|
||
return {}; // 返回默认配置
|
||
}
|
||
const config = configFile.config;
|
||
// 返回自定义配置,如果不存在则返回默认值
|
||
return config.oakBuildChecks || {};
|
||
}
|
||
const getContextLocationText = (callNode) => {
|
||
const sourceFile = callNode.getSourceFile();
|
||
const { line, character } = sourceFile.getLineAndCharacterOfPosition(callNode.getStart());
|
||
// const callText = callNode.getText(sourceFile);
|
||
// return `${callText}@${path.relative(process.cwd(), sourceFile.fileName)}:${line + 1}:${character + 1}`;
|
||
return `${path.relative(process.cwd(), sourceFile.fileName)}:${line + 1}:${character + 1}`;
|
||
};
|
||
function printDiagnostic(diagnostic, index) {
|
||
const isCustom = 'callChain' in diagnostic;
|
||
if (diagnostic.file && diagnostic.start !== undefined) {
|
||
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
||
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
||
const isError = diagnostic.category === ts.DiagnosticCategory.Error;
|
||
const category = isError ? 'error' : 'warning';
|
||
const categoryColor = isError ? colors.red : colors.yellow;
|
||
// 主要错误信息
|
||
console.log(`\n${colors.cyan}┌─ Issue #${index + 1}${colors.reset}`);
|
||
console.log(`${colors.cyan}│${colors.reset} ${colors.cyan}${path.relative(process.cwd(), diagnostic.file.fileName)}${colors.reset}:${colors.yellow}${line + 1}${colors.reset}:${colors.yellow}${character + 1}${colors.reset}`);
|
||
console.log(`${colors.cyan}│${colors.reset} ${categoryColor}${category}${colors.reset} ${colors.gray}TS${diagnostic.code}${colors.reset}: ${message}`);
|
||
// 显示代码片段
|
||
const sourceFile = diagnostic.file;
|
||
const lineStart = sourceFile.getPositionOfLineAndCharacter(line, 0);
|
||
const lineEnd = sourceFile.getPositionOfLineAndCharacter(line + 1, 0);
|
||
const lineText = sourceFile.text.substring(lineStart, lineEnd).trimEnd();
|
||
console.log(`${colors.cyan}│${colors.reset}`);
|
||
console.log(`${colors.cyan}│${colors.reset} ${colors.gray}${line + 1}${colors.reset} │ ${lineText}`);
|
||
console.log(`${colors.cyan}│${colors.reset} ${' '.repeat(String(line + 1).length)}│ ${' '.repeat(character)}${colors.red}${'~'.repeat(Math.max(1, diagnostic.length || 0))}${colors.reset}`);
|
||
// 如果是自定义诊断,显示额外信息
|
||
if (isCustom) {
|
||
const customDiag = diagnostic;
|
||
// 显示调用链
|
||
if (customDiag.callChain && customDiag.callChain.length > 1) {
|
||
console.log(`${colors.cyan}│${colors.reset}`);
|
||
console.log(`${colors.cyan}│${colors.reset} ${colors.yellow}调用链:${colors.reset}`);
|
||
customDiag.callChain.forEach((func, idx) => {
|
||
console.log(`${colors.cyan}│${colors.reset} ${colors.gray}→ ${func}${colors.reset}`);
|
||
});
|
||
}
|
||
// 显示实际的context调用位置
|
||
if (customDiag.contextCallNode) {
|
||
console.log(`${colors.cyan}│${colors.reset}`);
|
||
console.log(`${colors.cyan}│${colors.reset} ${colors.red}→ ${getContextLocationText(customDiag.contextCallNode)}${colors.reset}`);
|
||
}
|
||
}
|
||
console.log(`${colors.cyan}└─${colors.reset}`);
|
||
}
|
||
else {
|
||
const isError = diagnostic.category === ts.DiagnosticCategory.Error;
|
||
const category = isError ? 'error' : 'warning';
|
||
const categoryColor = isError ? colors.red : colors.yellow;
|
||
console.log(`\n${index + 1}. ${categoryColor}${category}${colors.reset} ${colors.gray}TS${diagnostic.code}${colors.reset}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`);
|
||
}
|
||
}
|
||
function performCustomChecks(program, typeChecker, customConfig) {
|
||
const diagnostics = [];
|
||
// 如果自定义检查被禁用,直接返回
|
||
if (!customConfig.context?.checkAsyncContext) {
|
||
console.log('Custom AsyncContext checks are disabled.');
|
||
return diagnostics;
|
||
}
|
||
// 数据结构定义
|
||
const functionsWithContextCalls = new Set();
|
||
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 callsToCheckByFile = new Map();
|
||
// 忽略注释节点缓存
|
||
const ignoreCommentNodes = new Set();
|
||
// 符号缓存
|
||
const symbolCache = new Map();
|
||
const getSymbolCached = (node) => {
|
||
if (symbolCache.has(node)) {
|
||
return symbolCache.get(node);
|
||
}
|
||
const symbol = typeChecker.getSymbolAtLocation(node);
|
||
symbolCache.set(node, symbol);
|
||
return symbol;
|
||
};
|
||
const addDiagnostic = (node, messageText, code, options) => {
|
||
const sourceFile = node.getSourceFile();
|
||
// 构建调用链文本
|
||
let callChain;
|
||
if (options?.callChain && options.callChain.length > 0) {
|
||
callChain = options.callChain.map(symbol => {
|
||
const decl = functionDeclarations.get(symbol);
|
||
if (decl && decl.name && ts.isIdentifier(decl.name)) {
|
||
const file = decl.getSourceFile();
|
||
const { line } = file.getLineAndCharacterOfPosition(decl.getStart());
|
||
return `${symbol.name} (${path.basename(file.fileName)}:${line + 1})`;
|
||
}
|
||
return symbol.name;
|
||
});
|
||
}
|
||
diagnostics.push({
|
||
file: sourceFile,
|
||
start: node.getStart(sourceFile),
|
||
length: node.getWidth(sourceFile),
|
||
messageText,
|
||
category: ts.DiagnosticCategory.Warning,
|
||
code,
|
||
callChain,
|
||
contextCallNode: options?.contextCall,
|
||
suggestedFix: options?.suggestedFix
|
||
});
|
||
};
|
||
const shouldReportUnawaitedCall = (callNode, sourceFile) => {
|
||
// 1. 检查忽略注释(现在很快,因为有缓存)
|
||
if (hasIgnoreComment(callNode, sourceFile)) {
|
||
return false;
|
||
}
|
||
// 2. 检查是否 await(通常很快)
|
||
if (isAwaited(callNode)) {
|
||
return false;
|
||
}
|
||
// 3. 检查特定场景(很快)
|
||
if (shouldSkipCheck(callNode)) {
|
||
return false;
|
||
}
|
||
// 4. 最后检查复杂的赋值处理(较慢)
|
||
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;
|
||
};
|
||
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)$/, '');
|
||
pathCache.set(filePath, normalized);
|
||
return normalized;
|
||
};
|
||
const preprocessIgnoreComments = (sourceFile) => {
|
||
const fullText = sourceFile.getFullText();
|
||
const visit = (node) => {
|
||
const nodeFullStart = node.getFullStart();
|
||
const leadingComments = ts.getLeadingCommentRanges(fullText, nodeFullStart);
|
||
if (leadingComments) {
|
||
for (const comment of leadingComments) {
|
||
const commentText = fullText.substring(comment.pos, comment.end);
|
||
if (isIgnoreComment(commentText)) {
|
||
// 标记当前节点及其子节点
|
||
markNodeAndChildren(node);
|
||
return; // 不需要继续遍历子节点
|
||
}
|
||
}
|
||
}
|
||
ts.forEachChild(node, visit);
|
||
};
|
||
const markNodeAndChildren = (node) => {
|
||
ignoreCommentNodes.add(node);
|
||
ts.forEachChild(node, markNodeAndChildren);
|
||
};
|
||
visit(sourceFile);
|
||
};
|
||
// 修改 hasIgnoreComment 函数
|
||
const hasIgnoreComment = (node, sourceFile) => {
|
||
// 直接查缓存
|
||
if (ignoreCommentNodes.has(node)) {
|
||
return true;
|
||
}
|
||
// 向上查找父节点(最多3层)
|
||
let current = node.parent;
|
||
let depth = 0;
|
||
while (current && depth < 3) {
|
||
if (ignoreCommentNodes.has(current)) {
|
||
return true;
|
||
}
|
||
current = current.parent;
|
||
depth++;
|
||
}
|
||
return false;
|
||
};
|
||
const isIgnoreComment = (commentText) => {
|
||
return exports.OAK_IGNORE_TAGS.some(tag => commentText.includes(tag));
|
||
};
|
||
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;
|
||
}
|
||
// 检查联合类型(例如 RuntimeCxt = FRC | BRC)
|
||
if (type.isUnion()) {
|
||
return type.types.some(t => isAsyncContextType(t, modules));
|
||
}
|
||
// 检查交叉类型
|
||
if (type.isIntersection()) {
|
||
return type.types.some(t => isAsyncContextType(t, modules));
|
||
}
|
||
// 检查基类型(继承关系)
|
||
const baseTypes = type.getBaseTypes?.() || [];
|
||
for (const baseType of baseTypes) {
|
||
if (isAsyncContextType(baseType, modules)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
const checkTypeSymbol = (type, modules) => {
|
||
const symbol = type.getSymbol();
|
||
if (!symbol) {
|
||
return false;
|
||
}
|
||
const declarations = symbol.getDeclarations();
|
||
if (!declarations || declarations.length === 0) {
|
||
return false;
|
||
}
|
||
for (const declaration of declarations) {
|
||
const sourceFile = declaration.getSourceFile();
|
||
const fileName = sourceFile.fileName;
|
||
const normalizedFileName = normalizeModulePath(fileName);
|
||
// 检查是否来自目标模块
|
||
for (const moduleName of modules) {
|
||
// 直接路径匹配(处理已解析的路径)
|
||
if (normalizedFileName.includes(moduleName.replace(/\\/g, '/'))) {
|
||
return true;
|
||
}
|
||
// 尝试解析模块别名
|
||
const resolvedPath = resolveModuleName(moduleName, sourceFile.fileName);
|
||
if (resolvedPath) {
|
||
const normalizedResolvedPath = normalizeModulePath(resolvedPath);
|
||
if (normalizedFileName === normalizedResolvedPath ||
|
||
normalizedFileName.includes(normalizedResolvedPath)) {
|
||
return true;
|
||
}
|
||
}
|
||
// 检查模块说明符(从 import 语句中获取)
|
||
const importDeclarations = sourceFile.statements.filter(ts.isImportDeclaration);
|
||
for (const importDecl of importDeclarations) {
|
||
if (importDecl.moduleSpecifier && ts.isStringLiteral(importDecl.moduleSpecifier)) {
|
||
const importPath = importDecl.moduleSpecifier.text;
|
||
if (importPath === moduleName || importPath.includes(moduleName)) {
|
||
// 检查当前符号是否来自这个 import
|
||
const importClause = importDecl.importClause;
|
||
if (importClause) {
|
||
const importSymbol = typeChecker.getSymbolAtLocation(importDecl.moduleSpecifier);
|
||
if (importSymbol && isSymbolRelated(symbol, importSymbol)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
const isSymbolRelated = (symbol, moduleSymbol) => {
|
||
// 检查符号是否与模块相关
|
||
const exports = typeChecker.getExportsOfModule(moduleSymbol);
|
||
return exports.some(exp => exp === symbol || exp.name === symbol.name);
|
||
};
|
||
const isTransparentWrapper = (node) => {
|
||
return ts.isParenthesizedExpression(node) ||
|
||
ts.isAsExpression(node) ||
|
||
ts.isTypeAssertionExpression(node) ||
|
||
ts.isNonNullExpression(node);
|
||
};
|
||
// 辅助函数:获取去除透明包装后的实际节点
|
||
const unwrapTransparentWrappers = (node) => {
|
||
let current = node;
|
||
while (isTransparentWrapper(current)) {
|
||
if (ts.isParenthesizedExpression(current)) {
|
||
current = current.expression;
|
||
}
|
||
else if (ts.isAsExpression(current)) {
|
||
current = current.expression;
|
||
}
|
||
else if (ts.isTypeAssertionExpression(current)) {
|
||
current = current.expression;
|
||
}
|
||
else if (ts.isNonNullExpression(current)) {
|
||
current = current.expression;
|
||
}
|
||
else {
|
||
break;
|
||
}
|
||
}
|
||
return current;
|
||
};
|
||
const isAwaited = (node) => {
|
||
let parent = node.parent;
|
||
// 向上遍历父节点,查找 await 表达式
|
||
while (parent) {
|
||
// 检查是否被 await 修饰(考虑透明包装)
|
||
if (ts.isAwaitExpression(parent)) {
|
||
// 去除 await 表达式中的透明包装层
|
||
const awaitedExpression = unwrapTransparentWrappers(parent.expression);
|
||
// 检查去除包装后是否就是目标节点
|
||
if (awaitedExpression === node) {
|
||
return true;
|
||
}
|
||
// 也检查是否在子树中(处理更复杂的嵌套情况)
|
||
if (isNodeInSubtree(parent.expression, node)) {
|
||
return true;
|
||
}
|
||
}
|
||
// 如果遇到函数边界,停止向上查找
|
||
if (ts.isFunctionDeclaration(parent) ||
|
||
ts.isFunctionExpression(parent) ||
|
||
ts.isArrowFunction(parent) ||
|
||
ts.isMethodDeclaration(parent)) {
|
||
break;
|
||
}
|
||
parent = parent.parent;
|
||
}
|
||
return false;
|
||
};
|
||
const isPromiseType = (type) => {
|
||
// 检查类型符号
|
||
const symbol = type.getSymbol();
|
||
if (symbol) {
|
||
const name = symbol.getName();
|
||
if (name === 'Promise') {
|
||
return true;
|
||
}
|
||
}
|
||
// 检查类型字符串表示
|
||
const typeString = typeChecker.typeToString(type);
|
||
if (typeString.startsWith('Promise<') || typeString === 'Promise') {
|
||
return true;
|
||
}
|
||
// 检查联合类型(例如 Promise<T> | undefined)
|
||
if (type.isUnion()) {
|
||
return type.types.some(t => isPromiseType(t));
|
||
}
|
||
// 检查基类型
|
||
const baseTypes = type.getBaseTypes?.() || [];
|
||
for (const baseType of baseTypes) {
|
||
if (isPromiseType(baseType)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
const isNodeInSubtree = (root, target) => {
|
||
if (root === target) {
|
||
return true;
|
||
}
|
||
let found = false;
|
||
ts.forEachChild(root, (child) => {
|
||
if (found)
|
||
return;
|
||
if (child === target || isNodeInSubtree(child, target)) {
|
||
found = true;
|
||
}
|
||
});
|
||
return found;
|
||
};
|
||
// 辅助函数:获取函数调用的名称
|
||
const 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);
|
||
};
|
||
// 辅助函数:获取声明的符号
|
||
const getSymbolOfDeclaration = (declaration) => {
|
||
// 处理有名称的声明(函数声明、方法声明等)
|
||
if ('name' in declaration && declaration.name) {
|
||
return typeChecker.getSymbolAtLocation(declaration.name);
|
||
}
|
||
// 处理箭头函数:尝试从父节点(变量声明)获取符号
|
||
if (ts.isArrowFunction(declaration) || ts.isFunctionExpression(declaration)) {
|
||
const parent = declaration.parent;
|
||
if (ts.isVariableDeclaration(parent) && parent.name) {
|
||
return typeChecker.getSymbolAtLocation(parent.name);
|
||
}
|
||
// 处理作为属性值的情况
|
||
if (ts.isPropertyAssignment(parent) && parent.name) {
|
||
return typeChecker.getSymbolAtLocation(parent.name);
|
||
}
|
||
}
|
||
return undefined;
|
||
};
|
||
const collectAndCheck = (sourceFile) => {
|
||
let currentFunction;
|
||
const localCallsToCheck = []; // 收集需要检查的调用
|
||
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;
|
||
}
|
||
}
|
||
// 处理调用表达式 - 合并原来的两个逻辑
|
||
if (ts.isCallExpression(node)) {
|
||
// 检查 context 调用
|
||
if (ts.isPropertyAccessExpression(node.expression)) {
|
||
const objectType = typeChecker.getTypeAtLocation(node.expression.expression);
|
||
const targetModules = customConfig.context?.targetModules ||
|
||
['@project/context/BackendRuntimeContext'];
|
||
if (isAsyncContextType(objectType, targetModules)) {
|
||
if (!hasIgnoreComment(node, sourceFile)) {
|
||
allContextCalls.push(node);
|
||
if (currentFunction) {
|
||
if (!directContextCalls.has(currentFunction)) {
|
||
directContextCalls.set(currentFunction, []);
|
||
}
|
||
directContextCalls.get(currentFunction).push(node);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 记录函数调用关系 + 收集需要检查的调用
|
||
if (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);
|
||
}
|
||
}
|
||
}
|
||
// 收集可能需要检查的调用(延迟到标记传播后)
|
||
localCallsToCheck.push(node);
|
||
}
|
||
}
|
||
ts.forEachChild(node, visit);
|
||
};
|
||
visit(sourceFile);
|
||
// 保存需要检查的调用
|
||
callsToCheckByFile.set(sourceFile, localCallsToCheck);
|
||
};
|
||
// 过滤掉非 Promise 返回的 context 调用
|
||
const filterAsyncContextCalls = () => {
|
||
// 过滤 allContextCalls
|
||
const asyncContextCalls = [];
|
||
for (const callNode of allContextCalls) {
|
||
// 添加忽略检查
|
||
const sourceFile = callNode.getSourceFile();
|
||
if (hasIgnoreComment(callNode, sourceFile)) {
|
||
continue;
|
||
}
|
||
const signature = typeChecker.getResolvedSignature(callNode);
|
||
if (signature) {
|
||
const returnType = typeChecker.getReturnTypeOfSignature(signature);
|
||
if (isPromiseType(returnType)) {
|
||
asyncContextCalls.push(callNode);
|
||
}
|
||
}
|
||
}
|
||
allContextCalls.length = 0;
|
||
allContextCalls.push(...asyncContextCalls);
|
||
// 过滤 directContextCalls
|
||
const newDirectContextCalls = new Map();
|
||
for (const [functionSymbol, calls] of directContextCalls.entries()) {
|
||
const asyncCalls = calls.filter(callNode => {
|
||
// 添加忽略检查
|
||
const sourceFile = callNode.getSourceFile();
|
||
if (hasIgnoreComment(callNode, sourceFile)) {
|
||
return false;
|
||
}
|
||
const signature = typeChecker.getResolvedSignature(callNode);
|
||
if (!signature)
|
||
return false;
|
||
const returnType = typeChecker.getReturnTypeOfSignature(signature);
|
||
return isPromiseType(returnType);
|
||
});
|
||
// 只保留有异步调用的函数
|
||
if (asyncCalls.length > 0) {
|
||
newDirectContextCalls.set(functionSymbol, asyncCalls);
|
||
}
|
||
}
|
||
directContextCalls.clear();
|
||
newDirectContextCalls.forEach((calls, symbol) => {
|
||
directContextCalls.set(symbol, calls);
|
||
});
|
||
};
|
||
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));
|
||
};
|
||
// 检查直接的 context 调用
|
||
const checkDirectContextCalls = () => {
|
||
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}()`, 9100, {
|
||
contextCall: callNode,
|
||
suggestedFix: `await ${objectName}.${methodName}()`
|
||
});
|
||
}
|
||
}
|
||
};
|
||
// 检查间接调用(使用缓存的调用列表)
|
||
const checkIndirectCalls = () => {
|
||
// 使用之前收集的调用列表,避免重复遍历
|
||
for (const [sourceFile, callsToCheck] of callsToCheckByFile.entries()) {
|
||
if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('node_modules')) {
|
||
continue;
|
||
}
|
||
for (const node of callsToCheck) {
|
||
if (!ts.isCallExpression(node))
|
||
continue;
|
||
if (shouldSkipCheck(node))
|
||
continue;
|
||
const signature = typeChecker.getResolvedSignature(node);
|
||
if (!signature)
|
||
continue;
|
||
const declaration = signature.getDeclaration();
|
||
if (!declaration)
|
||
continue;
|
||
const symbol = getSymbolOfDeclaration(declaration);
|
||
if (!symbol)
|
||
continue;
|
||
// 检查是否调用了标记的函数
|
||
if (functionsWithContextCalls.has(symbol)) {
|
||
const returnType = typeChecker.getReturnTypeOfSignature(signature);
|
||
if (!isPromiseType(returnType))
|
||
continue;
|
||
if (shouldReportUnawaitedCall(node, sourceFile)) {
|
||
const functionName = getFunctionCallName(node, sourceFile);
|
||
// 追踪调用链
|
||
const callChain = traceCallChain(symbol);
|
||
const contextCall = findContextCallInChain(symbol);
|
||
addDiagnostic(node, `未await的函数调用可能导致事务不受控: ${functionName}()`, 9101, {
|
||
callChain,
|
||
contextCall,
|
||
suggestedFix: `await ${functionName}()`
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
// 追踪调用链
|
||
const traceCallChain = (symbol) => {
|
||
const chain = [symbol];
|
||
const visited = new Set();
|
||
const trace = (currentSymbol) => {
|
||
if (visited.has(currentSymbol))
|
||
return false;
|
||
visited.add(currentSymbol);
|
||
// 如果直接包含context调用,返回true
|
||
if (directContextCalls.has(currentSymbol)) {
|
||
return true;
|
||
}
|
||
// 检查调用的函数
|
||
const callees = functionCallGraph.get(currentSymbol);
|
||
if (callees) {
|
||
for (const callee of callees) {
|
||
if (functionsWithContextCalls.has(callee)) {
|
||
chain.push(callee);
|
||
if (trace(callee)) {
|
||
return true;
|
||
}
|
||
chain.pop();
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
trace(symbol);
|
||
return chain;
|
||
};
|
||
// 找到调用链中的context调用
|
||
const findContextCallInChain = (symbol) => {
|
||
const visited = new Set();
|
||
const find = (currentSymbol) => {
|
||
if (visited.has(currentSymbol))
|
||
return undefined;
|
||
visited.add(currentSymbol);
|
||
// 如果直接包含context调用,返回第一个
|
||
const calls = directContextCalls.get(currentSymbol);
|
||
if (calls && calls.length > 0) {
|
||
return calls[0];
|
||
}
|
||
// 递归查找
|
||
const callees = functionCallGraph.get(currentSymbol);
|
||
if (callees) {
|
||
for (const callee of callees) {
|
||
if (functionsWithContextCalls.has(callee)) {
|
||
const result = find(callee);
|
||
if (result)
|
||
return result;
|
||
}
|
||
}
|
||
}
|
||
return undefined;
|
||
};
|
||
return find(symbol);
|
||
};
|
||
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 = getSymbolCached(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;
|
||
};
|
||
// 检查节点中是否包含指定变量
|
||
const containsVariable = (node, symbol) => {
|
||
if (ts.isIdentifier(node)) {
|
||
const nodeSymbol = getSymbolCached(node);
|
||
return nodeSymbol === symbol;
|
||
}
|
||
let found = false;
|
||
ts.forEachChild(node, (child) => {
|
||
if (found)
|
||
return;
|
||
if (containsVariable(child, symbol)) {
|
||
found = true;
|
||
}
|
||
});
|
||
return found;
|
||
};
|
||
// 检查在代码块中是否处理了 Promise
|
||
const isPromiseHandledInBlock = (block, symbol) => {
|
||
let handled = false;
|
||
const checkNode = (node) => {
|
||
if (handled)
|
||
return;
|
||
// 检查是否调用了 Promise 方法
|
||
if (ts.isCallExpression(node) &&
|
||
ts.isPropertyAccessExpression(node.expression)) {
|
||
const object = node.expression.expression;
|
||
if (ts.isIdentifier(object)) {
|
||
const objSymbol = getSymbolCached(object);
|
||
if (objSymbol === symbol) {
|
||
const methodName = node.expression.name.text;
|
||
if (methodName === 'then' || methodName === 'catch' ||
|
||
methodName === 'finally') {
|
||
handled = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 检查是否被 await
|
||
if (ts.isAwaitExpression(node)) {
|
||
const expression = unwrapTransparentWrappers(node.expression);
|
||
if (ts.isIdentifier(expression)) {
|
||
const exprSymbol = getSymbolCached(expression);
|
||
if (exprSymbol === symbol) {
|
||
handled = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
// 检查是否被 return(返回 Promise 也算处理)
|
||
if (ts.isReturnStatement(node) && node.expression) {
|
||
if (containsVariable(node.expression, symbol)) {
|
||
handled = true;
|
||
return;
|
||
}
|
||
}
|
||
// 不进入嵌套函数
|
||
if (ts.isFunctionDeclaration(node) ||
|
||
ts.isFunctionExpression(node) ||
|
||
ts.isArrowFunction(node)) {
|
||
return;
|
||
}
|
||
ts.forEachChild(node, checkNode);
|
||
};
|
||
checkNode(block);
|
||
return handled;
|
||
};
|
||
// 综合检查:Promise 是否被正确处理
|
||
const isPromiseProperlyHandled = (symbol, declarationNode) => {
|
||
const checkPromiseHandlingInNode = (node) => {
|
||
let handled = false;
|
||
const checkNode = (n) => {
|
||
if (handled)
|
||
return;
|
||
// 检查 await
|
||
if (ts.isAwaitExpression(n)) {
|
||
const expression = unwrapTransparentWrappers(n.expression);
|
||
if (ts.isIdentifier(expression)) {
|
||
const exprSymbol = getSymbolCached(expression);
|
||
if (exprSymbol === symbol) {
|
||
handled = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
// 检查 instanceof Promise
|
||
if (ts.isBinaryExpression(n) &&
|
||
n.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
|
||
const left = n.left;
|
||
if (ts.isIdentifier(left)) {
|
||
const leftSymbol = getSymbolCached(left);
|
||
if (leftSymbol === symbol) {
|
||
const right = n.right;
|
||
if (ts.isIdentifier(right) && right.text === 'Promise') {
|
||
handled = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 检查 Promise 方法
|
||
if (ts.isCallExpression(n) &&
|
||
ts.isPropertyAccessExpression(n.expression)) {
|
||
const object = n.expression.expression;
|
||
if (ts.isIdentifier(object)) {
|
||
const objSymbol = getSymbolCached(object);
|
||
if (objSymbol === symbol) {
|
||
const methodName = n.expression.name.text;
|
||
if (methodName === 'then' || methodName === 'catch' ||
|
||
methodName === 'finally') {
|
||
handled = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 不进入嵌套函数
|
||
if (ts.isFunctionDeclaration(n) ||
|
||
ts.isFunctionExpression(n) ||
|
||
ts.isArrowFunction(n)) {
|
||
return;
|
||
}
|
||
ts.forEachChild(n, checkNode);
|
||
};
|
||
checkNode(node);
|
||
return handled;
|
||
};
|
||
if (variableHandlingCache.has(symbol)) {
|
||
return variableHandlingCache.get(symbol);
|
||
}
|
||
if (checkingVariables.has(symbol)) {
|
||
return false;
|
||
}
|
||
checkingVariables.add(symbol);
|
||
try {
|
||
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) {
|
||
variableHandlingCache.set(symbol, false);
|
||
return false;
|
||
}
|
||
// 合并所有检查到一次遍历中
|
||
let hasInstanceOf = false;
|
||
let isAwaited = false;
|
||
let hasPromiseMethod = false;
|
||
let inCallback = false;
|
||
let passedToFunction = false;
|
||
const visit = (node) => {
|
||
// 如果已经找到任何一种处理方式,可以提前返回
|
||
if (hasInstanceOf || isAwaited || hasPromiseMethod || passedToFunction || inCallback) {
|
||
return;
|
||
}
|
||
// 检查 await
|
||
if (ts.isAwaitExpression(node)) {
|
||
const expression = unwrapTransparentWrappers(node.expression);
|
||
if (ts.isIdentifier(expression)) {
|
||
const exprSymbol = getSymbolCached(expression);
|
||
if (exprSymbol === symbol) {
|
||
isAwaited = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
// 检查 instanceof Promise
|
||
if (ts.isBinaryExpression(node) &&
|
||
node.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
|
||
const left = node.left;
|
||
if (ts.isIdentifier(left)) {
|
||
const leftSymbol = getSymbolCached(left);
|
||
if (leftSymbol === symbol) {
|
||
const right = node.right;
|
||
if (ts.isIdentifier(right) && right.text === 'Promise') {
|
||
// 找到 instanceof Promise 所在的 if 语句
|
||
let ifStatement = node.parent;
|
||
while (ifStatement && !ts.isIfStatement(ifStatement)) {
|
||
ifStatement = ifStatement.parent;
|
||
}
|
||
if (ifStatement && ts.isIfStatement(ifStatement)) {
|
||
// 检查 if 语句的 then 块中是否处理了 Promise
|
||
const thenBlock = ifStatement.thenStatement;
|
||
if (isPromiseHandledInBlock(thenBlock, symbol)) {
|
||
hasInstanceOf = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 检查 Promise 方法调用
|
||
if (ts.isCallExpression(node) &&
|
||
ts.isPropertyAccessExpression(node.expression)) {
|
||
const object = node.expression.expression;
|
||
// 检查 .then/.catch/.finally
|
||
if (ts.isIdentifier(object)) {
|
||
const objSymbol = getSymbolCached(object);
|
||
if (objSymbol === symbol) {
|
||
const methodName = node.expression.name.text;
|
||
if (methodName === 'then' || methodName === 'catch' ||
|
||
methodName === 'finally') {
|
||
hasPromiseMethod = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
// 检查 Promise.all 等
|
||
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)) {
|
||
hasPromiseMethod = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 检查作为参数传递
|
||
if (!passedToFunction) {
|
||
for (let i = 0; i < node.arguments.length; i++) {
|
||
const arg = node.arguments[i];
|
||
if (ts.isIdentifier(arg)) {
|
||
const argSymbol = getSymbolCached(arg);
|
||
if (argSymbol === symbol) {
|
||
const signature = typeChecker.getResolvedSignature(node);
|
||
if (signature) {
|
||
const declaration = signature.getDeclaration();
|
||
if (declaration) {
|
||
const calledSymbol = getSymbolOfDeclaration(declaration);
|
||
if (calledSymbol) {
|
||
const paramHandling = getFunctionParameterHandling(calledSymbol);
|
||
if (paramHandling.get(i) === true) {
|
||
passedToFunction = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 检查变量是否作为参数传递给会处理 Promise 的函数
|
||
if (ts.isCallExpression(node) && !passedToFunction) {
|
||
for (let i = 0; i < node.arguments.length; i++) {
|
||
const arg = node.arguments[i];
|
||
// 检查参数是否是我们要找的变量
|
||
if (ts.isIdentifier(arg)) {
|
||
const argSymbol = getSymbolCached(arg);
|
||
if (argSymbol === symbol) {
|
||
// 获取被调用函数的签名
|
||
const signature = typeChecker.getResolvedSignature(node);
|
||
if (signature) {
|
||
const declaration = signature.getDeclaration();
|
||
if (declaration) {
|
||
const calledSymbol = getSymbolOfDeclaration(declaration);
|
||
if (calledSymbol) {
|
||
// 检查该函数是否会处理这个参数位置的 Promise
|
||
const paramHandling = getFunctionParameterHandling(calledSymbol);
|
||
if (paramHandling.get(i) === true) {
|
||
passedToFunction = true;
|
||
return;
|
||
}
|
||
// 处理剩余参数的情况
|
||
if (declaration && 'parameters' in declaration) {
|
||
const params = declaration.parameters;
|
||
if (params.length > 0) {
|
||
const lastParam = params[params.length - 1];
|
||
if (lastParam && lastParam.dotDotDotToken && i >= params.length - 1) {
|
||
if (paramHandling.get(params.length - 1) === true) {
|
||
passedToFunction = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 检查变量是否在回调函数中被处理
|
||
if (ts.isCallExpression(node) && !inCallback) {
|
||
for (const arg of node.arguments) {
|
||
if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
|
||
// 检查回调函数体内是否包含该变量
|
||
if (arg.body && containsVariable(arg.body, symbol)) {
|
||
// 检查回调函数体内是否有处理逻辑
|
||
if (checkPromiseHandlingInNode(arg.body)) {
|
||
inCallback = 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);
|
||
}
|
||
const result = hasInstanceOf || isAwaited || hasPromiseMethod || passedToFunction || inCallback;
|
||
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;
|
||
}
|
||
preprocessIgnoreComments(sourceFile); // 预处理注释
|
||
collectAndCheck(sourceFile); // 合并后的收集和检查
|
||
}
|
||
// 过滤掉非异步的 context 调用
|
||
filterAsyncContextCalls();
|
||
// 预分析所有函数的参数处理情况
|
||
for (const [symbol, declaration] of functionDeclarations.entries()) {
|
||
getFunctionParameterHandling(symbol);
|
||
}
|
||
// 标记传播
|
||
propagateContextMarks();
|
||
checkDirectContextCalls(); // 检查直接调用
|
||
checkIndirectCalls(); // 检查间接调用(使用缓存的调用列表)
|
||
return diagnostics;
|
||
}
|
||
const compile = (configPath) => {
|
||
// 读取自定义配置
|
||
const customConfig = readCustomConfig(configPath);
|
||
// 读取 tsconfig.json
|
||
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
||
if (configFile.error) {
|
||
console.error(ts.formatDiagnostic(configFile.error, {
|
||
getCanonicalFileName: (f) => f,
|
||
getCurrentDirectory: process.cwd,
|
||
getNewLine: () => '\n'
|
||
}));
|
||
process.exit(1);
|
||
}
|
||
// 解析配置
|
||
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configPath));
|
||
if (parsedConfig.errors.length > 0) {
|
||
parsedConfig.errors.forEach((diagnostic) => {
|
||
console.error(ts.formatDiagnostic(diagnostic, {
|
||
getCanonicalFileName: (f) => f,
|
||
getCurrentDirectory: process.cwd,
|
||
getNewLine: () => '\n'
|
||
}));
|
||
});
|
||
process.exit(1);
|
||
}
|
||
// 创建编译程序
|
||
let program;
|
||
if (parsedConfig.options.incremental || parsedConfig.options.composite) {
|
||
const host = ts.createIncrementalCompilerHost(parsedConfig.options);
|
||
const incrementalProgram = ts.createIncrementalProgram({
|
||
rootNames: parsedConfig.fileNames,
|
||
options: parsedConfig.options,
|
||
host: host,
|
||
configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(parsedConfig),
|
||
});
|
||
program = incrementalProgram.getProgram();
|
||
}
|
||
else {
|
||
program = ts.createProgram({
|
||
rootNames: parsedConfig.fileNames,
|
||
options: parsedConfig.options,
|
||
});
|
||
}
|
||
// 获取类型检查器
|
||
const typeChecker = program.getTypeChecker();
|
||
// 执行自定义检查(传入自定义配置)
|
||
const customDiagnostics = performCustomChecks(program, typeChecker, customConfig);
|
||
if (customDiagnostics.length > 0) {
|
||
// 按错误代码分组
|
||
const groupedDiagnostics = new Map();
|
||
customDiagnostics.forEach(diag => {
|
||
if (!groupedDiagnostics.has(diag.code)) {
|
||
groupedDiagnostics.set(diag.code, []);
|
||
}
|
||
groupedDiagnostics.get(diag.code).push(diag);
|
||
});
|
||
// 输出统计
|
||
console.log(`${colors.cyan}发现 ${customDiagnostics.length} 个潜在问题:${colors.reset}`);
|
||
groupedDiagnostics.forEach((diags, code) => {
|
||
const description = code === 9100 ? '直接context调用' : '间接context调用';
|
||
console.log(` • ${description}: ${diags.length} 个`);
|
||
});
|
||
// 输出详细信息
|
||
let index = 0;
|
||
groupedDiagnostics.forEach((diags, code) => {
|
||
const title = code === 9100 ? '直接Context调用问题' : '间接Context调用问题';
|
||
console.log(`\n${colors.yellow}▼ ${title}${colors.reset}`);
|
||
diags.forEach(diag => {
|
||
printDiagnostic(diag, index++);
|
||
});
|
||
});
|
||
// 输出忽略提示
|
||
console.log(`\n${colors.yellow}═══════════════════════════════════════════════════════════${colors.reset}`);
|
||
console.log(`${colors.cyan}如果确定逻辑正确,可以使用以下注释标签来忽略检查:${colors.reset}`);
|
||
exports.OAK_IGNORE_TAGS.forEach(tag => {
|
||
console.log(` ${colors.green}// ${tag}${colors.reset}`);
|
||
});
|
||
console.log(`${colors.yellow}═══════════════════════════════════════════════════════════${colors.reset}\n`);
|
||
}
|
||
// 执行编译
|
||
const emitResult = program.emit();
|
||
// 获取诊断信息
|
||
const allDiagnostics = [
|
||
...ts.getPreEmitDiagnostics(program),
|
||
...emitResult.diagnostics,
|
||
];
|
||
// 输出诊断信息
|
||
allDiagnostics.forEach(printDiagnostic);
|
||
// 输出编译统计
|
||
const errorCount = allDiagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length;
|
||
const warningCount = allDiagnostics.filter(d => d.category === ts.DiagnosticCategory.Warning).length;
|
||
if (errorCount > 0 || warningCount > 0) {
|
||
if (allDiagnostics.length > 0) {
|
||
console.log('');
|
||
}
|
||
const parts = [];
|
||
if (errorCount > 0) {
|
||
parts.push(`${errorCount} error${errorCount !== 1 ? 's' : ''}`);
|
||
}
|
||
if (warningCount > 0) {
|
||
parts.push(`${warningCount} warning${warningCount !== 1 ? 's' : ''}`);
|
||
}
|
||
console.log(`Found ${parts.join(' and ')}.`);
|
||
}
|
||
if (errorCount > 0) {
|
||
process.exit(1);
|
||
}
|
||
console.log('Compilation completed successfully.');
|
||
};
|
||
const build = (pwd, args) => {
|
||
// 执行编译
|
||
const configPathArg = parseArgs(args);
|
||
let configPath;
|
||
// 判断参数是目录还是文件
|
||
if (fs.existsSync(configPathArg)) {
|
||
const stat = fs.statSync(configPathArg);
|
||
if (stat.isDirectory()) {
|
||
configPath = path.resolve(configPathArg, 'tsconfig.json');
|
||
}
|
||
else {
|
||
configPath = path.resolve(configPathArg);
|
||
}
|
||
}
|
||
else {
|
||
configPath = path.resolve(pwd, configPathArg);
|
||
if (!fs.existsSync(configPath)) {
|
||
const dirPath = path.resolve(pwd, configPathArg);
|
||
if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
|
||
configPath = path.join(dirPath, 'tsconfig.json');
|
||
}
|
||
}
|
||
}
|
||
if (!fs.existsSync(configPath)) {
|
||
console.error(`error TS5058: The specified path does not exist: '${configPath}'.`);
|
||
process.exit(1);
|
||
}
|
||
compile(configPath);
|
||
};
|
||
exports.build = build;
|