oak-domain/lib/compiler/tscBuilder.js

1691 lines
72 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;
exports.performCustomChecks = performCustomChecks;
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 glob_1 = require("../utils/glob");
const ARRAY_METHODS = ['map', 'forEach', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex', 'flatMap'];
const ARRAY_TRANSFORM_METHODS = ['map', 'filter', 'flatMap'];
const PROMISE_METHODS = ['then', 'catch', 'finally'];
const PROMISE_STATIC_METHODS = ['all', 'race', 'allSettled', 'any'];
// 判断是否是函数类声明
const isFunctionLikeDeclaration = (node) => {
// FunctionDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | ConstructorDeclaration | FunctionExpression | ArrowFunction;
return ts.isFunctionDeclaration(node) ||
ts.isMethodDeclaration(node) ||
ts.isGetAccessorDeclaration(node) ||
ts.isSetAccessorDeclaration(node) ||
ts.isConstructorDeclaration(node) ||
ts.isFunctionExpression(node) ||
ts.isArrowFunction(node);
};
// 查找最近的函数作用域
const findNearestFunctionScope = (node) => {
let current = node;
while (current && !isFunctionLikeDeclaration(current) && !ts.isSourceFile(current)) {
current = current.parent;
}
return current && !ts.isSourceFile(current) ? current : undefined;
};
// 查找最近的函数或块作用域
const findNearestScope = (node) => {
let current = node;
while (current &&
!isFunctionLikeDeclaration(current) &&
!ts.isSourceFile(current) &&
!ts.isBlock(current)) {
current = current.parent;
}
return current;
};
// 判断是否是 Promise 静态方法调用
const isPromiseStaticMethodCall = (node) => {
if (!ts.isPropertyAccessExpression(node.expression)) {
return false;
}
const object = node.expression.expression;
if (!ts.isIdentifier(object) || object.text !== 'Promise') {
return false;
}
const methodName = node.expression.name.text;
return PROMISE_STATIC_METHODS.includes(methodName);
};
// 判断是否是数组方法调用
const isArrayMethodCall = (node, methodNames) => {
if (!ts.isPropertyAccessExpression(node.expression)) {
return false;
}
const methodName = node.expression.name.text;
return methodNames.includes(methodName);
};
// 检查节点是否在数组方法回调中
const isInArrayMethodCallback = (node, methodNames = ARRAY_METHODS) => {
let current = node;
while (current) {
if (isFunctionLikeDeclaration(current)) {
const parent = current.parent;
if (ts.isCallExpression(parent) && isArrayMethodCall(parent, methodNames)) {
return true;
}
break;
}
current = current.parent;
}
return false;
};
// 检查变量是否在节点中被引用
const isSymbolReferencedInNode = (node, symbol, typeChecker) => {
if (ts.isIdentifier(node)) {
const nodeSymbol = typeChecker.getSymbolAtLocation(node);
return nodeSymbol === symbol;
}
let found = false;
ts.forEachChild(node, (child) => {
if (!found && isSymbolReferencedInNode(child, symbol, typeChecker)) {
found = true;
}
});
return found;
};
// 辅助函数:检查节点是否是透明包装(括号、类型断言等)
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;
};
/**
* 检查类型是否是 Promise 类型
* @param type ts.Type
* @returns boolean
*/
const isPromiseType = (type, typeChecker) => {
// 检查类型符号
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, typeChecker));
}
// 检查基类型
const baseTypes = type.getBaseTypes?.() || [];
for (const baseType of baseTypes) {
if (isPromiseType(baseType, typeChecker)) {
return true;
}
}
return false;
};
// 类型守卫:检查节点是否是标识符且匹配指定符号
const isIdentifierWithSymbol = (node, symbol, typeChecker) => {
if (!ts.isIdentifier(node)) {
return false;
}
const nodeSymbol = typeChecker.getSymbolAtLocation(node);
return nodeSymbol === symbol;
};
/**
* 判断文件是否在检查范围内
* @param fileName 文件完整路径
* @param customConfig 自定义配置
* @returns boolean
*/
const isFileInCheckScope = (fileName, customConfig) => {
const patterns = customConfig.context?.filePatterns || ['**/*.ts', '**/*.tsx'];
const normalizedFileName = path.normalize(path.relative(process.cwd(), fileName)).replace(/\\/g, '/');
for (const pattern of patterns) {
if ((0, glob_1.matchGlobPattern)(normalizedFileName, pattern.replace(/\\/g, '/'))) {
return true;
}
}
return false;
};
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);
let lineText = sourceFile.text.substring(lineStart, lineEnd).trimEnd();
// 确保只显示单行内容,去除换行符
const newlineIndex = lineText.indexOf('\n');
if (newlineIndex !== -1) {
lineText = lineText.substring(0, newlineIndex);
}
// 限制显示的最大字符数,避免输出过长
const maxDisplayLength = 100;
const isTruncated = lineText.length > maxDisplayLength;
const displayText = isTruncated
? lineText.substring(0, maxDisplayLength) + colors.gray + '...' + colors.reset
: lineText;
// 计算实际显示的文本长度(不包括颜色代码)
const actualDisplayLength = Math.min(lineText.length, maxDisplayLength);
// 调整错误标记的显示位置和长度
const effectiveCharacter = Math.min(character, actualDisplayLength);
const maxPossibleLength = actualDisplayLength - effectiveCharacter;
const effectiveLength = Math.min(Math.max(1, diagnostic.length || 1), maxPossibleLength);
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}`);
}
// 显示检测原因
if (customDiag.reason) {
console.log(`${colors.cyan}${colors.reset}`);
console.log(`${colors.cyan}${colors.reset} ${colors.yellow}检测原因: ${customDiag.reason}${colors.reset}`);
}
// 显示详细原因
if (customDiag.reasonDetails && customDiag.reasonDetails.length > 0) {
console.log(`${colors.cyan}${colors.reset}`);
console.log(`${colors.cyan}${colors.reset} ${colors.yellow}详细分析:${colors.reset}`);
customDiag.reasonDetails.forEach(detail => {
// 根据符号选择颜色
let color = colors.gray;
if (detail.includes('✓')) {
color = colors.green;
}
else if (detail.includes('✘')) {
color = colors.red;
}
else if (detail.includes('¡')) {
color = colors.cyan;
}
console.log(`${colors.cyan}${colors.reset} ${color}${detail}${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')}`);
}
}
/**
* 执行自定义检查
* @param program ts.Program
* @param typeChecker ts.TypeChecker
* @param customConfig 自定义配置
* @returns CustomDiagnostic[] 自定义诊断列表
*/
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,
reason: options?.reason,
reasonDetails: options?.reasonDetails
});
};
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 analyzeUnhandledReason = (callNode) => {
const details = [];
let reason = '未正确处理Promise';
let suggestedFix = '';
// 检查是否被 await
if (!isAwaited(callNode)) {
details.push('✘ 调用未使用 await 关键字');
suggestedFix = `await ${callNode.getText()}`;
}
else {
details.push('✓ 调用已使用 await');
}
// 检查是否在 return 语句中
const parent = callNode.parent;
if (ts.isReturnStatement(parent)) {
details.push('✓ 在 return 语句中(已传递给调用者)');
}
else if (ts.isArrowFunction(parent) && parent.body === callNode) {
details.push('✓ 作为箭头函数返回值(已传递给调用者)');
}
else {
details.push('✘ 未通过 return 传递给调用者');
}
// 检查是否赋值给变量
if (ts.isVariableDeclaration(parent) && parent.initializer === callNode) {
const variableDecl = parent;
if (variableDecl.name && ts.isIdentifier(variableDecl.name)) {
const variableName = variableDecl.name.text;
const variableSymbol = typeChecker.getSymbolAtLocation(variableDecl.name);
if (variableSymbol) {
details.push(`¡ 赋值给变量: ${variableName}`);
// 检查变量的各种处理方式
const scope = findNearestFunctionScope(variableDecl);
if (scope) {
const funcLike = scope;
if (funcLike.body) {
const check = checkPromiseHandlingWithInstanceOf(funcLike.body, variableSymbol, {
stopAtFunctionBoundary: true
});
if (check.hasAwait) {
details.push(` ✓ 变量 ${variableName} 后续被 await`);
}
else {
details.push(` ✘ 变量 ${variableName} 未被 await`);
}
if (check.hasPromiseMethod) {
details.push(` ✓ 变量 ${variableName} 调用了 .then/.catch/.finally`);
}
else {
details.push(` ✘ 变量 ${variableName} 未调用 .then/.catch/.finally`);
}
if (check.hasPromiseStatic) {
details.push(` ✓ 变量 ${variableName} 被传入 Promise.all/race 等`);
}
else {
details.push(` ✘ 变量 ${variableName} 未被传入 Promise.all/race 等`);
}
if (check.hasReturn) {
details.push(` ✓ 变量 ${variableName} 被 return`);
}
else {
details.push(` ✘ 变量 ${variableName} 未被 return`);
}
if (check.hasInstanceOf) {
details.push(` ✓ 变量 ${variableName} 在 instanceof Promise 检查后被处理`);
}
// 检查是否传递给其他函数
if (isSymbolPassedToHandlingFunction(variableSymbol, funcLike.body)) {
details.push(` ✓ 变量 ${variableName} 被传递给会处理Promise的函数`);
}
else {
details.push(` ✘ 变量 ${variableName} 未被传递给会处理Promise的函数`);
}
}
}
reason = `变量 ${variableName} 未被正确处理`;
if (!suggestedFix) {
suggestedFix = `await ${variableName}`;
}
}
}
}
else {
details.push('✘ 未赋值给变量进行后续处理');
}
// 检查是否作为参数传递
if (ts.isCallExpression(parent)) {
const argIndex = parent.arguments.indexOf(callNode);
if (argIndex !== -1) {
details.push(`¡ 作为第 ${argIndex + 1} 个参数传递给函数`);
if (isCallPassedToHandlingFunction(callNode)) {
details.push(' ✓ 该函数会正确处理Promise参数');
}
else {
details.push(' ✘ 该函数不会处理Promise参数');
reason = '传递给不处理Promise的函数';
}
}
}
// 检查是否在数组方法中
if (isInArrayMethodCallback(callNode, ARRAY_TRANSFORM_METHODS)) {
details.push('¡ 在数组转换方法map/filter/flatMap的回调中');
if (isCallInHandledArrayMethod(callNode)) {
details.push(' ✓ 数组方法的结果被正确处理如传入Promise.all');
}
else {
details.push(' ✘ 数组方法的结果未被正确处理');
reason = '在数组方法中但结果未被处理';
suggestedFix = '将数组方法结果传入 Promise.all()';
}
}
return { reason, details, suggestedFix };
};
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);
};
/**
* 检查节点是否被 await 修饰
* @param node ts.Node
* @returns boolean
*/
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 (isFunctionLikeDeclaration(parent)) {
break;
}
parent = parent.parent;
}
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 (isFunctionLikeDeclaration(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 (isFunctionLikeDeclaration(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, typeChecker)) {
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, typeChecker);
});
// 只保留有异步调用的函数
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, typeChecker))
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);
// 分析具体原因
// const analysis = analyzeUnhandledReason(callNode);
addDiagnostic(callNode, `未await的context调用可能导致事务不受控: ${objectName}.${methodName}()`, 9100, {
contextCall: callNode,
// reason: analysis.reason,
// reasonDetails: analysis.details
});
}
}
};
// 检查间接调用(使用缓存的调用列表)
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, typeChecker))
continue;
if (shouldReportUnawaitedCall(node, sourceFile)) {
const functionName = getFunctionCallName(node, sourceFile);
// 追踪调用链
const callChain = traceCallChain(symbol);
const contextCall = findContextCallInChain(symbol);
// 分析具体原因
// const analysis = analyzeUnhandledReason(node);
addDiagnostic(node, `未await的函数调用可能导致事务不受控: ${functionName}()`, 9101, {
callChain,
contextCall,
// reason: analysis.reason,
// reasonDetails: analysis.details
});
}
}
}
}
};
// 追踪调用链
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)) {
if (isInArrayMethodCallback(node)) {
return false;
}
return true;
}
// 箭头函数的直接返回值
if (ts.isArrowFunction(node.parent) && node.parent.body === node) {
if (isInArrayMethodCallback(node)) {
return false;
}
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) => {
const scope = findNearestFunctionScope(declarationNode);
if (!scope) {
return false;
}
let found = false;
const visit = (node) => {
if (found)
return;
if (ts.isReturnStatement(node) && node.expression) {
if (isSymbolReferencedInNode(node.expression, symbol, typeChecker)) {
found = true;
return;
}
}
// 不进入嵌套函数
if (isFunctionLikeDeclaration(node)) {
return;
}
ts.forEachChild(node, visit);
};
const funcLike = scope;
if (funcLike.body) {
visit(funcLike.body);
}
return found;
};
// 检查节点中是否包含指定变量
const containsVariable = (node, symbol) => {
return isSymbolReferencedInNode(node, symbol, typeChecker);
};
const checkPromiseHandlingInNode = (node, symbol, options = {}) => {
const result = {};
const visit = (n) => {
// 检查 await
if (ts.isAwaitExpression(n)) {
const expression = unwrapTransparentWrappers(n.expression);
if (isIdentifierWithSymbol(expression, symbol, typeChecker)) {
result.hasAwait = 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 (PROMISE_METHODS.includes(methodName)) {
result.hasPromiseMethod = true;
return;
}
}
}
// 检查 Promise 静态方法
if (isPromiseStaticMethodCall(n)) {
for (const arg of n.arguments) {
if (isSymbolReferencedInNode(arg, symbol, typeChecker)) {
result.hasPromiseStatic = true;
return;
}
}
}
}
// 检查 return
if (ts.isReturnStatement(n) && n.expression) {
if (isSymbolReferencedInNode(n.expression, symbol, typeChecker)) {
result.hasReturn = true;
return;
}
}
// 函数边界处理
if (options.stopAtFunctionBoundary && isFunctionLikeDeclaration(n)) {
return;
}
ts.forEachChild(n, visit);
};
visit(node);
return result;
};
const checkPromiseHandlingWithInstanceOf = (node, symbol, options = {}) => {
const result = {};
const visit = (n) => {
// 复用基础检查
const baseCheck = checkPromiseHandlingInNode(n, symbol, options);
Object.assign(result, baseCheck);
// 额外检查 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') {
// 找到 instanceof Promise 所在的 if 语句
let ifStatement = n.parent;
while (ifStatement && !ts.isIfStatement(ifStatement)) {
ifStatement = ifStatement.parent;
}
if (ifStatement && ts.isIfStatement(ifStatement)) {
const thenBlock = ifStatement.thenStatement;
if (isPromiseHandledInBlock(thenBlock, symbol)) {
result.hasInstanceOf = true;
return;
}
}
}
}
}
}
if (options.stopAtFunctionBoundary && isFunctionLikeDeclaration(n)) {
return;
}
ts.forEachChild(n, visit);
};
visit(node);
return result;
};
// 检查符号是否作为参数传递给会处理 Promise 的函数
const isSymbolPassedToHandlingFunction = (symbol, scope) => {
let found = false;
const visit = (node) => {
if (found)
return;
if (ts.isCallExpression(node)) {
// 检查直接参数传递
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) {
found = true;
return;
}
// 处理剩余参数
if ('parameters' in declaration) {
const params = declaration.parameters;
if (params.length > 0) {
const lastParam = params[params.length - 1];
if (lastParam?.dotDotDotToken && i >= params.length - 1) {
if (paramHandling.get(params.length - 1) === true) {
found = true;
return;
}
}
}
}
}
}
}
}
}
}
// 检查回调函数中的处理
for (const arg of node.arguments) {
if (isFunctionLikeDeclaration(arg)) {
if (arg.body && containsVariable(arg.body, symbol)) {
const callbackCheck = checkPromiseHandlingInNode(arg.body, symbol, {
stopAtFunctionBoundary: true
});
if (callbackCheck.hasAwait || callbackCheck.hasPromiseMethod ||
callbackCheck.hasPromiseStatic || callbackCheck.hasReturn) {
found = true;
return;
}
}
}
}
}
if (isFunctionLikeDeclaration(node)) {
return;
}
ts.forEachChild(node, visit);
};
visit(scope);
return found;
};
// 检查调用节点是否在数组方法回调中,且该数组方法的结果被正确处理
const isCallInHandledArrayMethod = (callNode) => {
let current = callNode;
// 向上查找,看是否在数组方法的回调中
while (current) {
// 跳过 return 语句
if (ts.isReturnStatement(current)) {
current = current.parent;
continue;
}
// 检查是否是箭头函数或函数表达式
if (isFunctionLikeDeclaration(current)) {
const functionParent = current.parent;
// 检查这个函数是否是数组方法的参数
if (ts.isCallExpression(functionParent) &&
isArrayMethodCall(functionParent, ARRAY_TRANSFORM_METHODS)) {
// 检查这个数组方法调用是否被正确处理
return isArrayMethodResultHandled(functionParent);
}
break;
}
current = current.parent;
}
return false;
};
// 检查数组方法的结果是否被正确处理
const isArrayMethodResultHandled = (arrayMethodCall) => {
let current = arrayMethodCall;
while (current) {
const currentParent = current.parent;
// 跳过透明包装
if (isTransparentWrapper(currentParent)) {
current = currentParent;
continue;
}
// 情况1直接作为 Promise 静态方法的参数
if (ts.isCallExpression(currentParent) && isPromiseStaticMethodCall(currentParent)) {
return true;
}
// 情况2赋值给变量
if (ts.isVariableDeclaration(currentParent) &&
currentParent.initializer === current) {
if (currentParent.name && ts.isIdentifier(currentParent.name)) {
const variableSymbol = getSymbolCached(currentParent.name);
if (variableSymbol) {
return isVariablePassedToPromiseAll(variableSymbol, currentParent);
}
}
}
// 情况3作为属性赋值暂不处理
if (ts.isPropertyAssignment(currentParent) &&
currentParent.initializer === current) {
return false;
}
break;
}
return false;
};
// 检查变量是否被传给 Promise.all
const isVariablePassedToPromiseAll = (symbol, declarationNode) => {
// 使用函数作用域而不是块作用域,以便找到更远的 Promise.all 调用
const scope = findNearestFunctionScope(declarationNode);
if (!scope) {
return false;
}
let found = false;
const visit = (node) => {
if (found)
return;
if (ts.isIfStatement(node)) {
const condition = node.expression;
if (ts.isBinaryExpression(condition) &&
condition.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
// 检查条件左侧是否引用了目标变量(支持数组元素访问)
const left = condition.left;
let isTargetVariable = false;
if (ts.isIdentifier(left)) {
const leftSymbol = getSymbolCached(left);
isTargetVariable = leftSymbol === symbol;
}
else if (ts.isElementAccessExpression(left)) {
// 处理 childLegalAuths[0] 这种情况
if (ts.isIdentifier(left.expression)) {
const arraySymbol = getSymbolCached(left.expression);
isTargetVariable = arraySymbol === symbol;
}
}
// 检查条件右侧是否是 Promise
const right = condition.right;
if (isTargetVariable && ts.isIdentifier(right) && right.text === 'Promise') {
// 在 then 分支中递归查找 Promise.all
const thenBlock = node.thenStatement;
const visitThen = (n) => {
if (found)
return;
if (ts.isCallExpression(n) && isPromiseStaticMethodCall(n)) {
for (const arg of n.arguments) {
if (ts.isIdentifier(arg)) {
const argSymbol = getSymbolCached(arg);
if (argSymbol === symbol) {
found = true;
return;
}
}
if (ts.isArrayLiteralExpression(arg)) {
for (const element of arg.elements) {
if (ts.isSpreadElement(element) &&
ts.isIdentifier(element.expression)) {
const spreadSymbol = getSymbolCached(element.expression);
if (spreadSymbol === symbol) {
found = true;
return;
}
}
}
}
}
}
ts.forEachChild(n, visitThen);
};
visitThen(thenBlock);
if (found)
return;
}
}
}
// 检查是否是 Promise 静态方法调用
if (ts.isCallExpression(node) && isPromiseStaticMethodCall(node)) {
// 检查参数中是否包含该变量
for (const arg of node.arguments) {
if (ts.isIdentifier(arg)) {
const argSymbol = getSymbolCached(arg);
if (argSymbol === symbol) {
found = true;
return;
}
}
// 检查展开运算符Promise.all([...variable])
if (ts.isArrayLiteralExpression(arg)) {
for (const element of arg.elements) {
if (ts.isSpreadElement(element) &&
ts.isIdentifier(element.expression)) {
const spreadSymbol = getSymbolCached(element.expression);
if (spreadSymbol === symbol) {
found = true;
return;
}
}
}
}
}
}
// 不进入嵌套函数
if (node !== scope && isFunctionLikeDeclaration(node)) {
return;
}
ts.forEachChild(node, visit);
};
visit(scope);
return found;
};
// 检查在代码块中是否处理了 Promise
const isPromiseHandledInBlock = (block, symbol) => {
const check = checkPromiseHandlingInNode(block, symbol, { stopAtFunctionBoundary: true });
return !!(check.hasAwait || check.hasPromiseMethod || check.hasPromiseStatic || check.hasReturn);
};
// 综合检查Promise 是否被正确处理
const isPromiseProperlyHandled = (symbol, declarationNode) => {
// 检查缓存
if (variableHandlingCache.has(symbol)) {
return variableHandlingCache.get(symbol);
}
// 防止循环检查
if (checkingVariables.has(symbol)) {
return false;
}
checkingVariables.add(symbol);
try {
const scope = findNearestFunctionScope(declarationNode);
if (!scope) {
variableHandlingCache.set(symbol, false);
return false;
}
const funcLike = scope;
if (!funcLike.body) {
variableHandlingCache.set(symbol, false);
return false;
}
// 使用统一的检查函数
const check = checkPromiseHandlingWithInstanceOf(funcLike.body, symbol, {
stopAtFunctionBoundary: true
});
// 使用统一的参数传递检查
const passedToFunction = isSymbolPassedToHandlingFunction(symbol, funcLike.body);
const result = !!(check.hasAwait ||
check.hasPromiseMethod ||
check.hasPromiseStatic ||
check.hasReturn ||
check.hasInstanceOf ||
passedToFunction);
variableHandlingCache.set(symbol, result);
return result;
}
finally {
checkingVariables.delete(symbol);
}
};
// 分析函数参数是否在函数体内被正确处理
const analyzeParameterHandling = (functionSymbol, declaration) => {
const parameterHandling = new Map();
if (!declaration.parameters || declaration.parameters.length === 0) {
return parameterHandling;
}
// 获取函数体
const body = declaration.body;
if (!body) {
return parameterHandling;
}
// 分析每个参数
declaration.parameters.forEach((param, index) => {
if (!param.name) {
parameterHandling.set(index, false);
return;
}
// 处理标识符参数
if (ts.isIdentifier(param.name)) {
const paramSymbol = typeChecker.getSymbolAtLocation(param.name);
if (paramSymbol) {
const isHandled = isPromiseProperlyHandled(paramSymbol, body);
parameterHandling.set(index, isHandled);
return;
}
}
// 处理解构参数 - 保守处理,标记为未处理
// TODO: 可以进一步分析解构后的变量是否被处理
parameterHandling.set(index, false);
});
return parameterHandling;
};
// 获取函数参数处理信息(带缓存)
const getFunctionParameterHandling = (functionSymbol) => {
if (functionParameterHandling.has(functionSymbol)) {
return functionParameterHandling.get(functionSymbol);
}
const declaration = functionDeclarations.get(functionSymbol);
if (!declaration) {
return new Map();
}
const handling = analyzeParameterHandling(functionSymbol, declaration);
functionParameterHandling.set(functionSymbol, handling);
return handling;
};
// 检查调用是否作为参数传递给会处理 Promise 的函数
const isCallPassedToHandlingFunction = (callNode) => {
const parent = callNode.parent;
// 检查是否作为函数调用的参数
if (ts.isCallExpression(parent)) {
const argIndex = parent.arguments.indexOf(callNode);
if (argIndex === -1) {
return false;
}
const signature = typeChecker.getResolvedSignature(parent);
if (!signature) {
return false;
}
const declaration = signature.getDeclaration();
if (!declaration) {
return false;
}
const calledSymbol = getSymbolOfDeclaration(declaration);
if (!calledSymbol) {
return false;
}
// 检查剩余参数
if ('parameters' in declaration) {
const params = declaration.parameters;
const lastParam = params[params.length - 1];
if (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) && isPromiseStaticMethodCall(grandParent)) {
return true;
}
}
// 检查是否在数组方法回调中返回,且该数组方法的结果被正确处理
return isCallInHandledArrayMethod(callNode);
};
// 信息收集
for (const sourceFile of program.getSourceFiles()) {
if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('node_modules')) {
continue;
}
// 文件名是否符合检查范围
if (!isFileInCheckScope(sourceFile.fileName, customConfig)) {
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) {
console.log(`${colors.red}Compilation failed due to errors.${colors.reset}`);
process.exit(1);
}
console.log(`${colors.green}Compilation completed successfully.${colors.reset}`);
};
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;