oak-domain/src/compiler/tscBuilder.ts

832 lines
28 KiB
TypeScript
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.

import * as ts from 'typescript';
import * as path from 'path';
import * as fs from 'fs';
const verboseLogging = false;
const MAX_RECURSION_DEPTH = 10; // 最大递归深度,防止无限递归
const log = (...args: any[]) => {
if (verboseLogging) {
console.log('[tscBuilder]', ...args);
}
};
export const OAK_IGNORE_TAGS = [
'@oak-ignore',
'@oak-ignore-asynccontext',
];
// 定义自定义配置的类型
interface OakBuildChecksConfig {
context?: {
checkAsyncContext?: boolean; // 是否启用 AsyncContext 检查,默认启用
targetModules?: string[]; // 目标模块列表,默认 ['@project/context/BackendRuntimeContext']
}
}
// 扩展的配置类型
interface ExtendedTsConfig {
compilerOptions?: ts.CompilerOptions;
oakBuildChecks?: OakBuildChecksConfig;
[key: string]: any; // 允许其他自定义字段
}
// 自定义诊断信息接口
interface CustomDiagnostic {
file: ts.SourceFile;
start: number;
length: number;
messageText: string;
category: ts.DiagnosticCategory;
code: number;
}
// ANSI 颜色代码
const colors = {
reset: '\x1b[0m',
cyan: '\x1b[36m',
red: '\x1b[91m',
yellow: '\x1b[93m',
gray: '\x1b[90m'
} as const;
// 解析命令行参数
function parseArgs(pargs: string[]): string {
const args: string[] = 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: string): OakBuildChecksConfig {
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
if (configFile.error) {
return {}; // 返回默认配置
}
const config = configFile.config as ExtendedTsConfig;
// 返回自定义配置,如果不存在则返回默认值
return config.oakBuildChecks || {
};
}
function printDiagnostic(diagnostic: ts.Diagnostic | CustomDiagnostic, index: number) {
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: ts.Program,
typeChecker: ts.TypeChecker,
customConfig: OakBuildChecksConfig
): CustomDiagnostic[] {
const diagnostics: CustomDiagnostic[] = [];
// 如果自定义检查被禁用,直接返回
if (!customConfig.context?.checkAsyncContext) {
console.log('Custom AsyncContext checks are disabled.');
return diagnostics;
}
const functionsWithContextCalls = new Set<ts.Symbol>();
const checkedFunctions = new Map<ts.Symbol, boolean>();
// 遍历所有源文件
for (const sourceFile of program.getSourceFiles()) {
// 跳过声明文件和 node_modules
if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('node_modules')) {
continue;
}
// 遍历 AST 节点
visitNode(sourceFile, 0);
}
function resolveModuleName(moduleName: string, containingFile: string): string | undefined {
const compilerOptions = program.getCompilerOptions();
const resolvedModule = ts.resolveModuleName(
moduleName,
containingFile,
compilerOptions,
ts.sys
);
return resolvedModule.resolvedModule?.resolvedFileName;
}
function normalizeModulePath(filePath: string): string {
// 规范化路径,移除 node_modules 和文件扩展名
return filePath
.replace(/\\/g, '/')
.replace(/\.(ts|tsx|js|jsx|d\.ts)$/, '');
}
function visitNode(node: ts.Node, deepth: number): void {
if (deepth > MAX_RECURSION_DEPTH) {
log(`[DEBUG] 达到最大递归深度,停止进一步检查`);
return;
}
// 检查 AsyncContext 方法调用
checkAsyncContextMethodCall(node);
//检查函数调用(检查是否调用了包含 context 的函数)
checkFunctionCall(node);
// 递归遍历子节点
ts.forEachChild(node, (i) => visitNode(i, deepth + 1));
}
function checkAsyncContextMethodCall(node: ts.Node): void {
// 只检查方法调用表达式
if (!ts.isCallExpression(node)) {
return;
}
// 检查是否是属性访问表达式
if (!ts.isPropertyAccessExpression(node.expression)) {
return;
}
// 如果上一层是return则先跳过让外层函数调用来处理
if (ts.isReturnStatement(node.parent)) {
return;
}
// 检查箭头函数的隐式返回,让外层函数调用来处理
// 例如:() => context.select()
if (ts.isArrowFunction(node.parent) && node.parent.body === node) {
return;
}
const propertyAccess = node.expression;
const objectExpression = propertyAccess.expression;
// 获取对象的类型
const objectType = typeChecker.getTypeAtLocation(objectExpression);
const targetModules = customConfig.context?.targetModules || ['@project/context/BackendRuntimeContext'];
// 检查类型是否继承自 AsyncContext
if (!isAsyncContextType(objectType, targetModules)) {
return;
}
// 获取方法调用的返回类型
const callSignature = typeChecker.getResolvedSignature(node);
if (!callSignature) {
return;
}
const returnType = typeChecker.getReturnTypeOfSignature(callSignature);
// 只检查返回 Promise 的方法
if (!isPromiseType(returnType)) {
return;
}
// 检查是否被 await 修饰
if (!isAwaited(node)) {
const sourceFile = node.getSourceFile();
// 检查是否有忽略注释
if (hasIgnoreComment(node, sourceFile)) {
return;
}
const methodName = propertyAccess.name.getText(sourceFile);
const objectName = objectExpression.getText(sourceFile);
diagnostics.push({
file: sourceFile,
start: node.getStart(sourceFile),
length: node.getWidth(sourceFile),
messageText: `未await的context调用可能导致事务不受控: ${objectName}.${methodName}() 需要使用 await`,
category: ts.DiagnosticCategory.Warning,
code: 9100
});
}
}
function hasIgnoreComment(node: ts.Node, sourceFile: ts.SourceFile): boolean {
const fullText = sourceFile.getFullText();
// 检查当前节点及其父节点向上查找最多3层
let currentNode: ts.Node | undefined = 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;
}
function isIgnoreComment(commentText: string): boolean {
return OAK_IGNORE_TAGS.some(tag => commentText.includes(tag));
}
function isAsyncContextType(type: ts.Type, modules: string[]): boolean {
// 检查类型本身
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;
}
function checkTypeSymbol(type: ts.Type, modules: string[]): boolean {
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;
}
function isSymbolRelated(symbol: ts.Symbol, moduleSymbol: ts.Symbol): boolean {
// 检查符号是否与模块相关
const exports = typeChecker.getExportsOfModule(moduleSymbol);
return exports.some(exp => exp === symbol || exp.name === symbol.name);
}
function isAwaited(node: ts.Node): boolean {
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;
}
function isPromiseType(type: ts.Type): boolean {
// 检查类型符号
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;
}
function isNodeInSubtree(root: ts.Node, target: ts.Node): boolean {
if (root === target) {
return true;
}
let found = false;
ts.forEachChild(root, (child: ts.Node) => {
if (found) return;
if (child === target || isNodeInSubtree(child, target)) {
found = true;
}
});
return found;
}
function checkFunctionCall(node: ts.Node): void {
// 只检查调用表达式
if (!ts.isCallExpression(node)) {
return;
}
// 打印所有函数调用
const sourceFile = node.getSourceFile();
const callText = node.expression.getText(sourceFile);
log(`[DEBUG] 检查函数调用: ${callText}`);
// 获取被调用函数的符号
const signature = typeChecker.getResolvedSignature(node);
if (!signature) {
log(`[DEBUG] ${callText} - 无法获取签名`);
return;
}
const declaration = signature.getDeclaration();
if (!declaration) {
log(`[DEBUG] ${callText} - 无法获取声明`);
return;
}
log(`[DEBUG] ${callText} - 声明类型: ${ts.SyntaxKind[declaration.kind]}`);
// 检查这个函数是否包含 context 调用
const hasContext = containsContextCall(declaration);
log(`[DEBUG] ${callText} - 包含context调用: ${hasContext}`);
if (hasContext) {
const returnType = typeChecker.getReturnTypeOfSignature(signature);
// 如果函数不返回 Promise说明内部已经正确处理了异步调用
// 此时调用该函数不需要 await
if (!isPromiseType(returnType)) {
return;
}
// 标记当前所在的函数
const containingFunction = findContainingFunction(node);
if (containingFunction) {
const fname = getFunctionName(containingFunction);
if (fname) { // 只有当有名称时才标记,但不影响后续检查
const symbol = typeChecker.getSymbolAtLocation(fname);
if (symbol) {
functionsWithContextCalls.add(symbol);
}
}
}
if (ts.isReturnStatement(node.parent)) {
// 如果在 return 语句中,不在这里报警告
// 警告会传播到调用当前函数的地方
return;
}
// 检查箭头函数的隐式返回
// 例如:() => someFunction()
if (ts.isArrowFunction(node.parent) && node.parent.body === node) {
return;
}
// 检查是否被 await
if (!isAwaited(node)) {
const sourceFile = node.getSourceFile();
// 检查是否有忽略注释
if (hasIgnoreComment(node, sourceFile)) {
return;
}
const functionName = getFunctionCallName(node, sourceFile);
diagnostics.push({
file: sourceFile,
start: node.getStart(sourceFile),
length: node.getWidth(sourceFile),
messageText: `未await的函数调用可能导致事务不受控: ${functionName}() 内部包含 context 调用,需要使用 await`,
category: ts.DiagnosticCategory.Warning,
code: 9101
});
}
}
}
// 辅助函数:检查函数声明是否包含 context 调用
function containsContextCall(node: ts.Node): boolean {
const symbol = getSymbolOfDeclaration(node as ts.SignatureDeclaration);
log(`[DEBUG containsContextCall] 节点类型: ${ts.SyntaxKind[node.kind]}, 有符号: ${!!symbol}`);
if (symbol && checkedFunctions.has(symbol)) {
log(`[DEBUG containsContextCall] 使用缓存结果: ${checkedFunctions.get(symbol)}`);
return checkedFunctions.get(symbol)!;
}
let hasContextCall = false;
function visit(n: ts.Node): void {
if (hasContextCall) return;
// 检查是否是 context 方法调用
if (ts.isCallExpression(n) &&
ts.isPropertyAccessExpression(n.expression)) {
const objectType = typeChecker.getTypeAtLocation(n.expression.expression);
const targetModules = customConfig.context?.targetModules ||
['@project/context/BackendRuntimeContext'];
if (isAsyncContextType(objectType, targetModules)) {
log(`[DEBUG containsContextCall] 找到context调用: ${n.expression.getText()}`);
hasContextCall = true;
return;
}
}
// 递归检查调用的其他函数
if (ts.isCallExpression(n)) {
const signature = typeChecker.getResolvedSignature(n);
if (signature) {
const declaration = signature.getDeclaration();
if (declaration && !isSameOrDescendant(declaration, node)) {
// 避免无限递归
const symbol = getSymbolOfDeclaration(declaration);
// 先检查缓存
if (symbol && functionsWithContextCalls.has(symbol)) {
hasContextCall = true;
return;
}
// 如果缓存中没有,递归检查该函数
if (containsContextCall(declaration)) {
hasContextCall = true;
return;
}
}
}
}
ts.forEachChild(n, visit);
}
visit(node);
log(`[DEBUG containsContextCall] 最终结果: ${hasContextCall}`);
if (symbol) {
checkedFunctions.set(symbol, hasContextCall);
}
return hasContextCall;
}
// 辅助函数:查找包含当前节点的函数
function findContainingFunction(node: ts.Node): ts.FunctionLikeDeclaration | undefined {
let current = node.parent;
while (current) {
if (ts.isFunctionDeclaration(current) ||
ts.isFunctionExpression(current) ||
ts.isArrowFunction(current) ||
ts.isMethodDeclaration(current)) {
return current;
}
current = current.parent;
}
return undefined;
}
// 辅助函数:获取函数名称节点
function getFunctionName(func: ts.FunctionLikeDeclaration): ts.Node | undefined {
if (ts.isFunctionDeclaration(func) || ts.isMethodDeclaration(func)) {
return func.name;
}
return undefined;
}
// 辅助函数:获取函数调用的名称
function getFunctionCallName(node: ts.CallExpression, sourceFile: ts.SourceFile): string {
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);
}
// 辅助函数:检查是否是同一个节点或后代
function isSameOrDescendant(node: ts.Node, ancestor: ts.Node): boolean {
let current: ts.Node | undefined = node;
while (current) {
if (current === ancestor) {
return true;
}
current = current.parent;
}
return false;
}
// 辅助函数:获取声明的符号
function getSymbolOfDeclaration(declaration: ts.SignatureDeclaration): ts.Symbol | undefined {
// 处理有名称的声明(函数声明、方法声明等)
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;
}
return diagnostics;
}
function compile(configPath: string): void {
// 读取自定义配置
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: string) => 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: ts.Diagnostic) => {
console.error(ts.formatDiagnostic(diagnostic, {
getCanonicalFileName: (f: string) => f,
getCurrentDirectory: process.cwd,
getNewLine: () => '\n'
}));
});
process.exit(1);
}
// 创建编译程序
let program: ts.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}`);
OAK_IGNORE_TAGS.forEach(tag => {
console.log(` ${colors.cyan}// ${tag}${colors.reset}`);
});
console.log('');
customDiagnostics.forEach(printDiagnostic);
}
// 执行编译
const emitResult = program.emit();
// 获取诊断信息
const allDiagnostics: readonly ts.Diagnostic[] = [
...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: string[] = [];
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.');
}
export const build = (pwd: string, args: any[]) => {
// 执行编译
const configPathArg: string = parseArgs(args);
let configPath: string;
// 判断参数是目录还是文件
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);
}