feat: 最大递归深度限制
This commit is contained in:
parent
78449b584b
commit
b777ef0ad9
|
|
@ -6,6 +6,7 @@ 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 MAX_RECURSION_DEPTH = 10;
|
||||
const log = (...args) => {
|
||||
if (verboseLogging) {
|
||||
console.log('[tscBuilder]', ...args);
|
||||
|
|
@ -83,7 +84,7 @@ function performCustomChecks(program, typeChecker, customConfig) {
|
|||
continue;
|
||||
}
|
||||
// 遍历 AST 节点
|
||||
visitNode(sourceFile);
|
||||
visitNode(sourceFile, 0);
|
||||
}
|
||||
function resolveModuleName(moduleName, containingFile) {
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
|
|
@ -96,13 +97,17 @@ function performCustomChecks(program, typeChecker, customConfig) {
|
|||
.replace(/\\/g, '/')
|
||||
.replace(/\.(ts|tsx|js|jsx|d\.ts)$/, '');
|
||||
}
|
||||
function visitNode(node) {
|
||||
function visitNode(node, deepth) {
|
||||
if (deepth > MAX_RECURSION_DEPTH) {
|
||||
log(`[DEBUG] 达到最大递归深度,停止进一步检查`);
|
||||
return;
|
||||
}
|
||||
// 检查 AsyncContext 方法调用
|
||||
checkAsyncContextMethodCall(node);
|
||||
//检查函数调用(检查是否调用了包含 context 的函数)
|
||||
checkFunctionCall(node);
|
||||
// 递归遍历子节点
|
||||
ts.forEachChild(node, visitNode);
|
||||
ts.forEachChild(node, (i) => visitNode(i, deepth + 1));
|
||||
}
|
||||
function checkAsyncContextMethodCall(node) {
|
||||
// 只检查方法调用表达式
|
||||
|
|
@ -214,6 +219,10 @@ function performCustomChecks(program, typeChecker, customConfig) {
|
|||
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) {
|
||||
|
|
|
|||
205
scripts/build.js
205
scripts/build.js
|
|
@ -1,190 +1,31 @@
|
|||
const ts = require('typescript');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { cwd } = require('process');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// 解析命令行参数
|
||||
function parseArgs() {
|
||||
const args = process.argv.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);
|
||||
}
|
||||
function tryRequire(modulePath) {
|
||||
try {
|
||||
require.resolve(modulePath);
|
||||
return require(modulePath);
|
||||
} catch (e) {
|
||||
if (e.code === 'MODULE_NOT_FOUND') {
|
||||
return null;
|
||||
}
|
||||
throw e; // 如果是其他错误,继续抛出
|
||||
}
|
||||
|
||||
return configPath;
|
||||
}
|
||||
|
||||
// ANSI 颜色代码
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
cyan: '\x1b[36m',
|
||||
red: '\x1b[91m',
|
||||
yellow: '\x1b[93m',
|
||||
gray: '\x1b[90m'
|
||||
};
|
||||
const tscBuilder = tryRequire('../lib/compiler/tscBuilder.js');
|
||||
|
||||
function compile(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) {
|
||||
// 对于增量编译,使用 createIncrementalProgram
|
||||
const host = ts.createIncrementalCompilerHost(parsedConfig.options);
|
||||
program = ts.createIncrementalProgram({
|
||||
rootNames: parsedConfig.fileNames,
|
||||
options: parsedConfig.options,
|
||||
host: host,
|
||||
configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(parsedConfig),
|
||||
});
|
||||
} else {
|
||||
// 普通编译
|
||||
program = ts.createProgram({
|
||||
rootNames: parsedConfig.fileNames,
|
||||
options: parsedConfig.options,
|
||||
});
|
||||
}
|
||||
|
||||
// 执行编译
|
||||
const emitResult = program.emit();
|
||||
|
||||
// 获取诊断信息
|
||||
const allDiagnostics = ts
|
||||
.getPreEmitDiagnostics(program)
|
||||
.concat(emitResult.diagnostics);
|
||||
|
||||
// 输出诊断信息
|
||||
allDiagnostics.forEach(diagnostic => {
|
||||
if (diagnostic.file) {
|
||||
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(
|
||||
`${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(
|
||||
`${categoryColor}${category}${colors.reset} ${colors.gray}TS${diagnostic.code}${colors.reset}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// 输出编译统计
|
||||
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 ')}.`);
|
||||
}
|
||||
|
||||
// tsc 的行为:
|
||||
// 1. 默认情况下(noEmitOnError: false):
|
||||
// - 即使有类型错误也会生成 .js 文件
|
||||
// - 但如果有错误,退出码是 1
|
||||
// 2. noEmitOnError: true 时:
|
||||
// - 有错误时不生成文件(emitSkipped 为 true)
|
||||
// - 退出码是 1
|
||||
// 3. 没有错误时:
|
||||
// - 生成文件,退出码 0
|
||||
|
||||
// 无论 emitSkipped 与否,只要有错误就应该退出 1
|
||||
if (errorCount > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Compilation completed successfully.');
|
||||
}
|
||||
|
||||
// 执行编译
|
||||
const configPathArg = parseArgs();
|
||||
let configPath;
|
||||
|
||||
// 判断参数是目录还是文件
|
||||
if (fs.existsSync(configPathArg)) {
|
||||
const stat = fs.statSync(configPathArg);
|
||||
if (stat.isDirectory()) {
|
||||
// 如果是目录,拼接 tsconfig.json
|
||||
configPath = path.resolve(configPathArg, 'tsconfig.json');
|
||||
} else {
|
||||
// 如果是文件,直接使用
|
||||
configPath = path.resolve(configPathArg);
|
||||
}
|
||||
if (tscBuilder && tscBuilder.build) {
|
||||
console.log('Using tscBuilder for compilation');
|
||||
tscBuilder.build(process.cwd(), process.argv);
|
||||
} else {
|
||||
// 尝试作为相对路径解析
|
||||
configPath = path.resolve(cwd(), configPathArg);
|
||||
if (!fs.existsSync(configPath)) {
|
||||
// 如果还是不存在,尝试添加 tsconfig.json
|
||||
const dirPath = path.resolve(cwd(), configPathArg);
|
||||
if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
|
||||
configPath = path.join(dirPath, 'tsconfig.json');
|
||||
}
|
||||
console.log('tscBuilder not found, falling back to tsc');
|
||||
try {
|
||||
execSync('npx tsc', {
|
||||
stdio: 'inherit',
|
||||
cwd: process.cwd()
|
||||
});
|
||||
} catch (tscError) {
|
||||
console.error('Failed to run tsc:', tscError.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
console.error(`error TS5058: The specified path does not exist: '${configPath}'.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
compile(configPath);
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import * as path from 'path';
|
|||
import * as fs from 'fs';
|
||||
|
||||
const verboseLogging = false;
|
||||
const MAX_RECURSION_DEPTH = 10; // 最大递归深度,防止无限递归
|
||||
|
||||
const log = (...args: any[]) => {
|
||||
if (verboseLogging) {
|
||||
|
|
@ -135,7 +136,7 @@ function performCustomChecks(
|
|||
}
|
||||
|
||||
// 遍历 AST 节点
|
||||
visitNode(sourceFile);
|
||||
visitNode(sourceFile, 0);
|
||||
}
|
||||
|
||||
function resolveModuleName(moduleName: string, containingFile: string): string | undefined {
|
||||
|
|
@ -157,7 +158,12 @@ function performCustomChecks(
|
|||
.replace(/\.(ts|tsx|js|jsx|d\.ts)$/, '');
|
||||
}
|
||||
|
||||
function visitNode(node: ts.Node): void {
|
||||
function visitNode(node: ts.Node, deepth: number): void {
|
||||
if (deepth > MAX_RECURSION_DEPTH) {
|
||||
log(`[DEBUG] 达到最大递归深度,停止进一步检查`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查 AsyncContext 方法调用
|
||||
checkAsyncContextMethodCall(node);
|
||||
|
||||
|
|
@ -165,7 +171,7 @@ function performCustomChecks(
|
|||
checkFunctionCall(node);
|
||||
|
||||
// 递归遍历子节点
|
||||
ts.forEachChild(node, visitNode);
|
||||
ts.forEachChild(node, (i) => visitNode(i, deepth + 1));
|
||||
}
|
||||
|
||||
function checkAsyncContextMethodCall(node: ts.Node): void {
|
||||
|
|
@ -307,6 +313,11 @@ function performCustomChecks(
|
|||
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) {
|
||||
|
|
@ -466,7 +477,7 @@ function performCustomChecks(
|
|||
return;
|
||||
}
|
||||
|
||||
// 添加这里 - 打印所有函数调用
|
||||
// 打印所有函数调用
|
||||
const sourceFile = node.getSourceFile();
|
||||
const callText = node.expression.getText(sourceFile);
|
||||
log(`[DEBUG] 检查函数调用: ${callText}`);
|
||||
|
|
|
|||
Loading…
Reference in New Issue