oak-domain/lib/compiler/tscBuilder.js

1399 lines
60 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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();
// 限制显示的最大字符数,避免输出过长
const maxDisplayLength = 100;
const displayText = lineText.length > maxDisplayLength
? lineText.substring(0, maxDisplayLength) + colors.gray + '...' + colors.reset
: lineText;
// 如果代码被截断,调整错误标记的显示位置
const effectiveCharacter = character < maxDisplayLength ? character : maxDisplayLength - 1;
const effectiveLength = character + (diagnostic.length || 0) <= maxDisplayLength
? Math.max(1, diagnostic.length || 0)
: Math.max(1, maxDisplayLength - character);
console.log(`${colors.cyan}${colors.reset}`);
console.log(`${colors.cyan}${colors.reset} ${colors.gray}${line + 1}${colors.reset}${displayText}`);
console.log(`${colors.cyan}${colors.reset} ${' '.repeat(String(line + 1).length)}${' '.repeat(effectiveCharacter)}${colors.red}${'~'.repeat(effectiveLength)}${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;