1190 lines
50 KiB
JavaScript
1190 lines
50 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 = (...args) => {
|
||
if (verboseLogging) {
|
||
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'
|
||
};
|
||
// 解析命令行参数
|
||
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 || {};
|
||
}
|
||
function printDiagnostic(diagnostic, index) {
|
||
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(`${index + 1}.→ ${colors.cyan}${diagnostic.file.fileName}${colors.reset}:${colors.yellow}${line + 1}${colors.reset}:${colors.yellow}${character + 1}${colors.reset} - ${categoryColor}${category}${colors.reset} ${colors.gray}TS${diagnostic.code}${colors.reset}: ${message}`);
|
||
}
|
||
else {
|
||
const isError = diagnostic.category === ts.DiagnosticCategory.Error;
|
||
const category = isError ? 'error' : 'warning';
|
||
const categoryColor = isError ? colors.red : colors.yellow;
|
||
console.log(`${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 addDiagnostic = (node, messageText, code) => {
|
||
const sourceFile = node.getSourceFile();
|
||
diagnostics.push({
|
||
file: sourceFile,
|
||
start: node.getStart(sourceFile),
|
||
length: node.getWidth(sourceFile),
|
||
messageText,
|
||
category: ts.DiagnosticCategory.Warning,
|
||
code
|
||
});
|
||
};
|
||
const shouldReportUnawaitedCall = (callNode, sourceFile) => {
|
||
// 跳过特定场景
|
||
if (shouldSkipCheck(callNode)) {
|
||
return false;
|
||
}
|
||
// 检查忽略注释
|
||
if (hasIgnoreComment(callNode, sourceFile)) {
|
||
return false;
|
||
}
|
||
// 检查是否已 await
|
||
if (isAwaited(callNode)) {
|
||
return false;
|
||
}
|
||
// 检查是否赋值给变量并正确处理
|
||
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 hasIgnoreComment = (node, sourceFile) => {
|
||
const fullText = sourceFile.getFullText();
|
||
// 检查当前节点及其父节点(向上查找最多3层)
|
||
let currentNode = node;
|
||
let depth = 0;
|
||
const maxDepth = 5;
|
||
while (currentNode && depth < maxDepth) {
|
||
// 检查前导注释
|
||
const nodeFullStart = currentNode.getFullStart();
|
||
const leadingComments = ts.getLeadingCommentRanges(fullText, nodeFullStart);
|
||
if (leadingComments && leadingComments.length > 0) {
|
||
for (const comment of leadingComments) {
|
||
const commentText = fullText.substring(comment.pos, comment.end);
|
||
if (isIgnoreComment(commentText)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
// 检查尾随注释
|
||
const nodeEnd = currentNode.getEnd();
|
||
const trailingComments = ts.getTrailingCommentRanges(fullText, nodeEnd);
|
||
if (trailingComments && trailingComments.length > 0) {
|
||
for (const comment of trailingComments) {
|
||
const commentText = fullText.substring(comment.pos, comment.end);
|
||
if (isIgnoreComment(commentText)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
// 向上查找父节点
|
||
currentNode = currentNode.parent;
|
||
depth++;
|
||
// 如果遇到函数边界,停止查找
|
||
if (currentNode && (ts.isFunctionDeclaration(currentNode) ||
|
||
ts.isFunctionExpression(currentNode) ||
|
||
ts.isArrowFunction(currentNode) ||
|
||
ts.isMethodDeclaration(currentNode))) {
|
||
break;
|
||
}
|
||
}
|
||
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 isAwaited = (node) => {
|
||
let parent = node.parent;
|
||
// 向上遍历父节点,查找 await 表达式
|
||
while (parent) {
|
||
// 直接被 await 修饰
|
||
if (ts.isAwaitExpression(parent) && parent.expression === node) {
|
||
return true;
|
||
}
|
||
// 在 await 表达式的子表达式中
|
||
if (ts.isAwaitExpression(parent)) {
|
||
const current = parent.expression;
|
||
if (isNodeInSubtree(current, 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 collectInformation = (sourceFile) => {
|
||
let currentFunction;
|
||
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;
|
||
}
|
||
}
|
||
// 记录 context 方法调用
|
||
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
||
const objectType = typeChecker.getTypeAtLocation(node.expression.expression);
|
||
const targetModules = customConfig.context?.targetModules ||
|
||
['@project/context/BackendRuntimeContext'];
|
||
if (isAsyncContextType(objectType, targetModules)) {
|
||
allContextCalls.push(node);
|
||
// 记录到当前函数
|
||
if (currentFunction) {
|
||
if (!directContextCalls.has(currentFunction)) {
|
||
directContextCalls.set(currentFunction, []);
|
||
}
|
||
directContextCalls.get(currentFunction).push(node);
|
||
}
|
||
}
|
||
}
|
||
// 记录函数调用关系
|
||
if (ts.isCallExpression(node) && 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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
ts.forEachChild(node, visit);
|
||
};
|
||
visit(sourceFile);
|
||
};
|
||
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));
|
||
};
|
||
const checkCallSites = () => {
|
||
// 检查所有直接的 context 调用
|
||
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}() 需要使用 await 或通过 instanceof Promise 判断后处理`, 9100);
|
||
}
|
||
}
|
||
// 检查所有标记函数的调用点
|
||
for (const sourceFile of program.getSourceFiles()) {
|
||
if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('node_modules')) {
|
||
continue;
|
||
}
|
||
checkFunctionCallsInFile(sourceFile);
|
||
}
|
||
};
|
||
const checkFunctionCallsInFile = (sourceFile) => {
|
||
const visit = (node) => {
|
||
if (ts.isCallExpression(node)) {
|
||
if (shouldSkipCheck(node)) {
|
||
ts.forEachChild(node, visit);
|
||
return;
|
||
}
|
||
const signature = typeChecker.getResolvedSignature(node);
|
||
if (signature) {
|
||
const declaration = signature.getDeclaration();
|
||
if (declaration) {
|
||
const symbol = getSymbolOfDeclaration(declaration);
|
||
// 检查是否调用了标记的函数
|
||
if (symbol && functionsWithContextCalls.has(symbol)) {
|
||
const returnType = typeChecker.getReturnTypeOfSignature(signature);
|
||
if (!isPromiseType(returnType)) {
|
||
ts.forEachChild(node, visit);
|
||
return;
|
||
}
|
||
if (shouldReportUnawaitedCall(node, sourceFile)) {
|
||
const functionName = getFunctionCallName(node, sourceFile);
|
||
addDiagnostic(node, `未await的函数调用可能导致事务不受控: ${functionName}() 内部包含 context 调用,需要使用 await 或通过 instanceof Promise 判断后处理`, 9101);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
ts.forEachChild(node, visit);
|
||
};
|
||
visit(sourceFile);
|
||
};
|
||
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 = typeChecker.getSymbolAtLocation(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;
|
||
};
|
||
// 检查变量是否通过 instanceof Promise 判断
|
||
// 检查变量是否通过 instanceof Promise 判断
|
||
const hasInstanceOfPromiseCheck = (symbol, scope) => {
|
||
let found = false;
|
||
const visit = (node) => {
|
||
if (found)
|
||
return;
|
||
// 检查 instanceof Promise
|
||
if (ts.isBinaryExpression(node) &&
|
||
node.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
|
||
const left = node.left;
|
||
if (ts.isIdentifier(left)) {
|
||
const leftSymbol = typeChecker.getSymbolAtLocation(left);
|
||
if (leftSymbol === symbol) {
|
||
const right = node.right;
|
||
// 修改:检查右侧是否是 Promise 标识符
|
||
if (ts.isIdentifier(right) && right.text === 'Promise') {
|
||
found = true;
|
||
return;
|
||
}
|
||
// 也检查类型(兼容其他情况)
|
||
const rightType = typeChecker.getTypeAtLocation(right);
|
||
const rightSymbol = rightType.getSymbol();
|
||
if (rightSymbol && rightSymbol.name === 'PromiseConstructor') {
|
||
found = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
ts.forEachChild(node, visit);
|
||
};
|
||
visit(scope);
|
||
return found;
|
||
};
|
||
// 辅助函数:检查是否有 return 语句返回 Promise 处理结果
|
||
const hasReturnWithPromiseHandling = (node, symbol) => {
|
||
let found = false;
|
||
const visit = (n) => {
|
||
if (found)
|
||
return;
|
||
if (ts.isReturnStatement(n) && n.expression) {
|
||
// 检查返回的表达式是否是对该变量的 Promise 方法调用
|
||
if (ts.isCallExpression(n.expression) &&
|
||
ts.isPropertyAccessExpression(n.expression.expression)) {
|
||
const object = n.expression.expression.expression;
|
||
if (ts.isIdentifier(object)) {
|
||
const objSymbol = typeChecker.getSymbolAtLocation(object);
|
||
if (objSymbol === symbol) {
|
||
const methodName = n.expression.expression.name.text;
|
||
if (methodName === 'then' || methodName === 'catch' || methodName === 'finally') {
|
||
found = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 不进入嵌套函数
|
||
if (ts.isFunctionDeclaration(n) ||
|
||
ts.isFunctionExpression(n) ||
|
||
ts.isArrowFunction(n)) {
|
||
return;
|
||
}
|
||
ts.forEachChild(n, visit);
|
||
};
|
||
visit(node);
|
||
return found;
|
||
};
|
||
// 检查变量是否在作用域内被 await
|
||
const isVariableAwaitedInScope = (symbol, scope) => {
|
||
let found = false;
|
||
const visit = (node) => {
|
||
if (found)
|
||
return;
|
||
// 检查 await 表达式
|
||
if (ts.isAwaitExpression(node)) {
|
||
const expression = node.expression;
|
||
if (ts.isIdentifier(expression)) {
|
||
const exprSymbol = typeChecker.getSymbolAtLocation(expression);
|
||
if (exprSymbol === symbol) {
|
||
found = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
// 不进入嵌套函数
|
||
if (ts.isFunctionDeclaration(node) ||
|
||
ts.isFunctionExpression(node) ||
|
||
ts.isArrowFunction(node)) {
|
||
return;
|
||
}
|
||
ts.forEachChild(node, visit);
|
||
};
|
||
visit(scope);
|
||
return found;
|
||
};
|
||
// 检查变量是否通过 Promise 方法处理(.then, .catch, Promise.all 等)
|
||
const isVariableHandledByPromiseMethod = (symbol, scope) => {
|
||
let found = false;
|
||
const visit = (node) => {
|
||
if (found)
|
||
return;
|
||
// 检查 .then() 或 .catch() 调用
|
||
if (ts.isCallExpression(node) &&
|
||
ts.isPropertyAccessExpression(node.expression)) {
|
||
const object = node.expression.expression;
|
||
if (ts.isIdentifier(object)) {
|
||
const objSymbol = typeChecker.getSymbolAtLocation(object);
|
||
if (objSymbol === symbol) {
|
||
const methodName = node.expression.name.text;
|
||
if (methodName === 'then' || methodName === 'catch' ||
|
||
methodName === 'finally') {
|
||
found = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 检查 Promise.all, Promise.race 等
|
||
if (ts.isCallExpression(node) &&
|
||
ts.isPropertyAccessExpression(node.expression)) {
|
||
const object = node.expression.expression;
|
||
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)) {
|
||
found = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
ts.forEachChild(node, visit);
|
||
};
|
||
visit(scope);
|
||
return found;
|
||
};
|
||
// 检查节点中是否包含指定变量
|
||
const containsVariable = (node, symbol) => {
|
||
if (ts.isIdentifier(node)) {
|
||
const nodeSymbol = typeChecker.getSymbolAtLocation(node);
|
||
return nodeSymbol === symbol;
|
||
}
|
||
let found = false;
|
||
ts.forEachChild(node, (child) => {
|
||
if (found)
|
||
return;
|
||
if (containsVariable(child, symbol)) {
|
||
found = true;
|
||
}
|
||
});
|
||
return found;
|
||
};
|
||
const isVariableHandledInCallback = (symbol, scope, depth = 0) => {
|
||
// 深度限制
|
||
if (depth > 2) {
|
||
return false;
|
||
}
|
||
let found = false;
|
||
const visit = (node) => {
|
||
if (found)
|
||
return;
|
||
// 检查是否作为回调参数传递
|
||
if (ts.isCallExpression(node)) {
|
||
for (const arg of node.arguments) {
|
||
if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
|
||
// 检查回调函数体内是否处理了该变量
|
||
if (arg.body && containsVariable(arg.body, symbol)) {
|
||
// 使用简化版本检查,避免深度递归
|
||
if (hasInstanceOfPromiseCheck(symbol, arg.body) ||
|
||
isVariableAwaitedInScope(symbol, arg.body) ||
|
||
isVariableHandledByPromiseMethod(symbol, arg.body)) {
|
||
found = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
ts.forEachChild(node, visit);
|
||
};
|
||
visit(scope);
|
||
return found;
|
||
};
|
||
// 检查变量是否被传递给会处理 Promise 的函数参数
|
||
const isVariablePassedToHandlingFunction = (symbol, scope, depth = 0) => {
|
||
// 深度限制,防止过深的递归
|
||
if (depth > 2) {
|
||
return false;
|
||
}
|
||
log('[DEBUG] isVariablePassedToHandlingFunction: checking', symbol.name, 'depth:', depth);
|
||
log('[DEBUG] Scope kind:', ts.SyntaxKind[scope.kind]);
|
||
let found = false;
|
||
let callCount = 0;
|
||
const visit = (node) => {
|
||
if (found)
|
||
return;
|
||
log('[DEBUG] Visiting node:', ts.SyntaxKind[node.kind]);
|
||
// 检查函数调用
|
||
if (ts.isCallExpression(node)) {
|
||
callCount++;
|
||
log('[DEBUG] Found call expression #', callCount);
|
||
// 遍历所有参数
|
||
node.arguments.forEach((arg, argIndex) => {
|
||
if (found)
|
||
return;
|
||
log('[DEBUG] Checking argument', argIndex, 'kind:', ts.SyntaxKind[arg.kind]);
|
||
// 检查参数是否是我们要找的变量
|
||
if (ts.isIdentifier(arg)) {
|
||
log('[DEBUG] Argument is identifier:', arg.text);
|
||
const argSymbol = typeChecker.getSymbolAtLocation(arg);
|
||
if (argSymbol === symbol) {
|
||
log('[DEBUG] ✓ Found variable', symbol.name, 'passed as argument at index:', argIndex);
|
||
// 获取被调用函数的签名
|
||
const signature = typeChecker.getResolvedSignature(node);
|
||
if (!signature) {
|
||
log('[DEBUG] ✗ No signature found');
|
||
return;
|
||
}
|
||
const declaration = signature.getDeclaration();
|
||
if (!declaration) {
|
||
log('[DEBUG] ✗ No declaration found');
|
||
return;
|
||
}
|
||
const calledSymbol = getSymbolOfDeclaration(declaration);
|
||
if (!calledSymbol) {
|
||
log('[DEBUG] ✗ No called symbol found');
|
||
return;
|
||
}
|
||
log('[DEBUG] Called function:', calledSymbol.name);
|
||
// 检查该函数是否会处理这个参数位置的 Promise
|
||
const paramHandling = getFunctionParameterHandling(calledSymbol);
|
||
log('[DEBUG] Parameter handling map:', Array.from(paramHandling.entries()));
|
||
log('[DEBUG] Parameter handling for index', argIndex, ':', paramHandling.get(argIndex));
|
||
if (paramHandling.get(argIndex) === true) {
|
||
log('[DEBUG] ✓✓✓ Parameter is handled!');
|
||
found = true;
|
||
return;
|
||
}
|
||
else {
|
||
log('[DEBUG] ✗ Parameter is NOT handled');
|
||
}
|
||
// 处理剩余参数的情况
|
||
if (declaration && 'parameters' in declaration) {
|
||
const params = declaration.parameters;
|
||
if (params.length > 0) {
|
||
const lastParam = params[params.length - 1];
|
||
if (lastParam && lastParam.dotDotDotToken && argIndex >= params.length - 1) {
|
||
log('[DEBUG] Checking rest parameter at index', params.length - 1);
|
||
if (paramHandling.get(params.length - 1) === true) {
|
||
log('[DEBUG] ✓✓✓ Rest parameter is handled!');
|
||
found = true;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
log('[DEBUG] Argument symbol mismatch:', argSymbol?.name, 'vs', symbol.name);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
// 不进入嵌套函数的内部
|
||
if (ts.isFunctionDeclaration(node) ||
|
||
ts.isFunctionExpression(node) ||
|
||
ts.isArrowFunction(node) ||
|
||
ts.isMethodDeclaration(node)) {
|
||
log('[DEBUG] Skipping nested function body');
|
||
return; // 不调用 ts.forEachChild,直接返回
|
||
}
|
||
ts.forEachChild(node, visit);
|
||
};
|
||
// 关键修改:从函数体开始遍历,而不是从函数声明开始
|
||
let startNode;
|
||
if (ts.isFunctionDeclaration(scope) || ts.isFunctionExpression(scope) ||
|
||
ts.isArrowFunction(scope) || ts.isMethodDeclaration(scope)) {
|
||
const funcLike = scope;
|
||
startNode = funcLike.body;
|
||
log('[DEBUG] Starting from function body');
|
||
}
|
||
else if (ts.isSourceFile(scope)) {
|
||
startNode = scope;
|
||
log('[DEBUG] Starting from source file');
|
||
}
|
||
else {
|
||
startNode = scope;
|
||
log('[DEBUG] Starting from scope directly');
|
||
}
|
||
if (startNode) {
|
||
visit(startNode);
|
||
}
|
||
log('[DEBUG] isVariablePassedToHandlingFunction result for', symbol.name, ':', found, '(checked', callCount, 'calls)');
|
||
return found;
|
||
};
|
||
// 综合检查:Promise 是否被正确处理
|
||
const isPromiseProperlyHandled = (symbol, declarationNode) => {
|
||
// 检查缓存
|
||
if (variableHandlingCache.has(symbol)) {
|
||
return variableHandlingCache.get(symbol);
|
||
}
|
||
// 检查是否正在检查这个变量(防止循环)
|
||
if (checkingVariables.has(symbol)) {
|
||
return false;
|
||
}
|
||
checkingVariables.add(symbol);
|
||
try {
|
||
log('[DEBUG] isPromiseProperlyHandled: variable', symbol.name, 'declarationNode kind:', ts.SyntaxKind[declarationNode.kind]);
|
||
// 找到包含该声明的函数作用域
|
||
let scope = declarationNode;
|
||
let depth = 0;
|
||
while (scope && !ts.isFunctionDeclaration(scope) &&
|
||
!ts.isFunctionExpression(scope) &&
|
||
!ts.isArrowFunction(scope) &&
|
||
!ts.isMethodDeclaration(scope) &&
|
||
!ts.isSourceFile(scope)) {
|
||
log('[DEBUG] Moving up from', ts.SyntaxKind[scope.kind], 'to parent');
|
||
scope = scope.parent;
|
||
depth++;
|
||
if (depth > 20) {
|
||
log('[DEBUG] Too deep, breaking');
|
||
break;
|
||
}
|
||
}
|
||
if (!scope) {
|
||
log('[DEBUG] No scope found');
|
||
variableHandlingCache.set(symbol, false);
|
||
return false;
|
||
}
|
||
log('[DEBUG] Found scope:', ts.SyntaxKind[scope.kind]);
|
||
// 打印 scope 的一些信息
|
||
if (ts.isFunctionDeclaration(scope) || ts.isMethodDeclaration(scope)) {
|
||
const funcDecl = scope;
|
||
if (funcDecl.name) {
|
||
log('[DEBUG] Scope function name:', funcDecl.name.getText());
|
||
}
|
||
}
|
||
log('[DEBUG] Checking variable:', symbol.name, 'in scope');
|
||
// 检查各种处理方式
|
||
const hasInstanceOf = hasInstanceOfPromiseCheck(symbol, scope);
|
||
const isAwaited = isVariableAwaitedInScope(symbol, scope);
|
||
const hasPromiseMethod = isVariableHandledByPromiseMethod(symbol, scope);
|
||
const inCallback = isVariableHandledInCallback(symbol, scope);
|
||
const passedToFunction = isVariablePassedToHandlingFunction(symbol, scope);
|
||
log('[DEBUG] Check results:', {
|
||
hasInstanceOf,
|
||
isAwaited,
|
||
hasPromiseMethod,
|
||
inCallback,
|
||
passedToFunction
|
||
});
|
||
const result = hasInstanceOf || isAwaited || hasPromiseMethod || inCallback || 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 (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;
|
||
}
|
||
collectInformation(sourceFile);
|
||
}
|
||
// 预分析所有函数的参数处理情况
|
||
for (const [symbol, declaration] of functionDeclarations.entries()) {
|
||
getFunctionParameterHandling(symbol);
|
||
}
|
||
// 标记传播
|
||
propagateContextMarks();
|
||
// 检查调用点
|
||
checkCallSites();
|
||
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) {
|
||
log(`Found ${customDiagnostics.length} custom diagnostics.`);
|
||
console.log(`${colors.yellow}Oak-Compiler 发现了相关问题,如果确定逻辑正确,可以使用注释标签来忽略:${colors.reset}`);
|
||
exports.OAK_IGNORE_TAGS.forEach(tag => {
|
||
console.log(` ${colors.cyan}// ${tag}${colors.reset}`);
|
||
});
|
||
console.log('');
|
||
customDiagnostics.forEach(printDiagnostic);
|
||
}
|
||
// 执行编译
|
||
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;
|