init
This commit is contained in:
commit
b870a4d144
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
.cache/
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
import { OakBuildChecksConfig, CustomDiagnostic } from './checker';
|
||||||
|
export declare class AsyncContextChecker {
|
||||||
|
private pwd;
|
||||||
|
private config;
|
||||||
|
private program;
|
||||||
|
private typeChecker;
|
||||||
|
private tsLib;
|
||||||
|
private functionsWithContextCalls;
|
||||||
|
private functionDeclarations;
|
||||||
|
private functionCallGraph;
|
||||||
|
private directContextCalls;
|
||||||
|
private ignoreCommentNodes;
|
||||||
|
private isInitialized;
|
||||||
|
constructor(pwd: string, config: OakBuildChecksConfig, program: ts.Program, typeChecker: ts.TypeChecker, tsLib: typeof ts);
|
||||||
|
/**
|
||||||
|
* 初始化:构建全局调用图
|
||||||
|
*/
|
||||||
|
private initialize;
|
||||||
|
/**
|
||||||
|
* 检查单个文件
|
||||||
|
*/
|
||||||
|
checkFile(sourceFile: ts.SourceFile): CustomDiagnostic[];
|
||||||
|
private isFileInCheckScope;
|
||||||
|
private preprocessIgnoreComments;
|
||||||
|
private collectCallGraph;
|
||||||
|
private filterAsyncContextCalls;
|
||||||
|
private propagateContextMarks;
|
||||||
|
private checkDirectContextCalls;
|
||||||
|
private checkIndirectCalls;
|
||||||
|
/**
|
||||||
|
* 清除缓存(当文件变化时)
|
||||||
|
*/
|
||||||
|
clearCache(): void;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.AsyncContextChecker = void 0;
|
||||||
|
class AsyncContextChecker {
|
||||||
|
constructor(pwd, config, program, typeChecker, tsLib) {
|
||||||
|
this.pwd = pwd;
|
||||||
|
this.config = config;
|
||||||
|
this.program = program;
|
||||||
|
this.typeChecker = typeChecker;
|
||||||
|
this.tsLib = tsLib;
|
||||||
|
// 全局状态(跨文件)
|
||||||
|
this.functionsWithContextCalls = new Set();
|
||||||
|
this.functionDeclarations = new Map();
|
||||||
|
this.functionCallGraph = new Map();
|
||||||
|
this.directContextCalls = new Map();
|
||||||
|
// 缓存
|
||||||
|
this.ignoreCommentNodes = new Map();
|
||||||
|
this.isInitialized = false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 初始化:构建全局调用图
|
||||||
|
*/
|
||||||
|
initialize() {
|
||||||
|
if (this.isInitialized)
|
||||||
|
return;
|
||||||
|
// 遍历所有文件,构建调用图
|
||||||
|
for (const sourceFile of this.program.getSourceFiles()) {
|
||||||
|
if (sourceFile.isDeclarationFile)
|
||||||
|
continue;
|
||||||
|
if (!this.isFileInCheckScope(sourceFile.fileName))
|
||||||
|
continue;
|
||||||
|
this.preprocessIgnoreComments(sourceFile);
|
||||||
|
this.collectCallGraph(sourceFile);
|
||||||
|
}
|
||||||
|
// 过滤非 Promise 返回的调用
|
||||||
|
this.filterAsyncContextCalls();
|
||||||
|
// 传播标记
|
||||||
|
this.propagateContextMarks();
|
||||||
|
this.isInitialized = true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 检查单个文件
|
||||||
|
*/
|
||||||
|
checkFile(sourceFile) {
|
||||||
|
// 确保已初始化
|
||||||
|
this.initialize();
|
||||||
|
if (!this.isFileInCheckScope(sourceFile.fileName)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const diagnostics = [];
|
||||||
|
// 检查直接的 context 调用
|
||||||
|
diagnostics.push(...this.checkDirectContextCalls(sourceFile));
|
||||||
|
// 检查间接调用
|
||||||
|
diagnostics.push(...this.checkIndirectCalls(sourceFile));
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
isFileInCheckScope(fileName) {
|
||||||
|
// 复用原有的 isFileInCheckScope 逻辑
|
||||||
|
// ...
|
||||||
|
return true; // 简化示例
|
||||||
|
}
|
||||||
|
preprocessIgnoreComments(sourceFile) {
|
||||||
|
// 复用原有的 preprocessIgnoreComments 逻辑
|
||||||
|
// 将结果存储在 this.ignoreCommentNodes 中
|
||||||
|
}
|
||||||
|
collectCallGraph(sourceFile) {
|
||||||
|
// 复用原有的 collectAndCheck 逻辑
|
||||||
|
// 收集函数声明和调用关系
|
||||||
|
}
|
||||||
|
filterAsyncContextCalls() {
|
||||||
|
// 复用原有逻辑
|
||||||
|
}
|
||||||
|
propagateContextMarks() {
|
||||||
|
// 复用原有逻辑
|
||||||
|
}
|
||||||
|
checkDirectContextCalls(sourceFile) {
|
||||||
|
// 复用原有逻辑,但只检查当前文件
|
||||||
|
const diagnostics = [];
|
||||||
|
// ...
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
checkIndirectCalls(sourceFile) {
|
||||||
|
// 复用原有逻辑,但只检查当前文件
|
||||||
|
const diagnostics = [];
|
||||||
|
// ...
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 清除缓存(当文件变化时)
|
||||||
|
*/
|
||||||
|
clearCache() {
|
||||||
|
this.isInitialized = false;
|
||||||
|
this.functionsWithContextCalls.clear();
|
||||||
|
this.functionDeclarations.clear();
|
||||||
|
this.functionCallGraph.clear();
|
||||||
|
this.directContextCalls.clear();
|
||||||
|
this.ignoreCommentNodes.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.AsyncContextChecker = AsyncContextChecker;
|
||||||
|
//# sourceMappingURL=asyncContextChecker.js.map
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"asyncContextChecker.js","sourceRoot":"","sources":["../../src/core/asyncContextChecker.ts"],"names":[],"mappings":"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"}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
export interface OakBuildChecksConfig {
|
||||||
|
context?: {
|
||||||
|
checkAsyncContext?: boolean;
|
||||||
|
targetModules?: string[];
|
||||||
|
filePatterns?: string[];
|
||||||
|
};
|
||||||
|
locale?: {
|
||||||
|
checkI18nKeys?: boolean;
|
||||||
|
tFunctionModules?: string[];
|
||||||
|
checkTemplateLiterals?: boolean;
|
||||||
|
warnStringKeys?: boolean;
|
||||||
|
checkJsxLiterals?: boolean;
|
||||||
|
jsxLiteralPattern?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface CustomDiagnostic {
|
||||||
|
file: ts.SourceFile;
|
||||||
|
start: number;
|
||||||
|
length: number;
|
||||||
|
messageText: string;
|
||||||
|
category: ts.DiagnosticCategory;
|
||||||
|
code: number;
|
||||||
|
callChain?: string[];
|
||||||
|
contextCallNode?: ts.CallExpression;
|
||||||
|
reason?: string;
|
||||||
|
reasonDetails?: string[];
|
||||||
|
relatedInfo?: Array<{
|
||||||
|
file: ts.SourceFile;
|
||||||
|
start: number;
|
||||||
|
length: number;
|
||||||
|
message: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 核心检查器类 - 支持增量检查
|
||||||
|
*/
|
||||||
|
export declare class OakCustomChecker {
|
||||||
|
private pwd;
|
||||||
|
private config;
|
||||||
|
private program;
|
||||||
|
private typeChecker;
|
||||||
|
private tsLib;
|
||||||
|
private asyncContextChecker?;
|
||||||
|
private i18nChecker?;
|
||||||
|
private jsxLiteralChecker?;
|
||||||
|
private diagnosticsCache;
|
||||||
|
private fileVersionCache;
|
||||||
|
constructor(pwd: string, config: OakBuildChecksConfig, program: ts.Program, typeChecker: ts.TypeChecker, tsLib: typeof ts);
|
||||||
|
private initializeCheckers;
|
||||||
|
/**
|
||||||
|
* 检查单个文件(用于 TSServer 插件)
|
||||||
|
*/
|
||||||
|
checkFile(fileName: string, fileVersion?: string): CustomDiagnostic[];
|
||||||
|
/**
|
||||||
|
* 检查所有文件(用于 CLI 编译)
|
||||||
|
*/
|
||||||
|
checkAllFiles(): CustomDiagnostic[];
|
||||||
|
/**
|
||||||
|
* 清除缓存
|
||||||
|
*/
|
||||||
|
clearCache(fileName?: string): void;
|
||||||
|
/**
|
||||||
|
* 更新 Program(当项目重新编译时)
|
||||||
|
*/
|
||||||
|
updateProgram(program: ts.Program): void;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.OakCustomChecker = void 0;
|
||||||
|
const asyncContextChecker_1 = require("./asyncContextChecker");
|
||||||
|
const i18nChecker_1 = require("./i18nChecker");
|
||||||
|
const jsxLiteralChecker_1 = require("./jsxLiteralChecker");
|
||||||
|
/**
|
||||||
|
* 核心检查器类 - 支持增量检查
|
||||||
|
*/
|
||||||
|
class OakCustomChecker {
|
||||||
|
constructor(pwd, config, program, typeChecker, tsLib) {
|
||||||
|
this.pwd = pwd;
|
||||||
|
this.config = config;
|
||||||
|
this.program = program;
|
||||||
|
this.typeChecker = typeChecker;
|
||||||
|
this.tsLib = tsLib;
|
||||||
|
// 缓存:文件名 -> 诊断结果
|
||||||
|
this.diagnosticsCache = new Map();
|
||||||
|
// 缓存:文件名 -> 文件版本
|
||||||
|
this.fileVersionCache = new Map();
|
||||||
|
this.initializeCheckers();
|
||||||
|
}
|
||||||
|
initializeCheckers() {
|
||||||
|
var _a, _b, _c;
|
||||||
|
if (((_a = this.config.context) === null || _a === void 0 ? void 0 : _a.checkAsyncContext) !== false) {
|
||||||
|
this.asyncContextChecker = new asyncContextChecker_1.AsyncContextChecker(this.pwd, this.config, this.program, this.typeChecker, this.tsLib);
|
||||||
|
}
|
||||||
|
if (((_b = this.config.locale) === null || _b === void 0 ? void 0 : _b.checkI18nKeys) !== false) {
|
||||||
|
this.i18nChecker = new i18nChecker_1.I18nChecker(this.pwd, this.config, this.program, this.typeChecker, this.tsLib);
|
||||||
|
}
|
||||||
|
if (((_c = this.config.locale) === null || _c === void 0 ? void 0 : _c.checkJsxLiterals) === true) {
|
||||||
|
this.jsxLiteralChecker = new jsxLiteralChecker_1.JsxLiteralChecker(this.pwd, this.config, this.program, this.typeChecker, this.tsLib);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 检查单个文件(用于 TSServer 插件)
|
||||||
|
*/
|
||||||
|
checkFile(fileName, fileVersion) {
|
||||||
|
// 检查缓存
|
||||||
|
if (fileVersion && this.fileVersionCache.get(fileName) === fileVersion) {
|
||||||
|
const cached = this.diagnosticsCache.get(fileName);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sourceFile = this.program.getSourceFile(fileName);
|
||||||
|
if (!sourceFile) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const diagnostics = [];
|
||||||
|
// 执行各项检查
|
||||||
|
if (this.asyncContextChecker) {
|
||||||
|
diagnostics.push(...this.asyncContextChecker.checkFile(sourceFile));
|
||||||
|
}
|
||||||
|
if (this.i18nChecker) {
|
||||||
|
diagnostics.push(...this.i18nChecker.checkFile(sourceFile));
|
||||||
|
}
|
||||||
|
if (this.jsxLiteralChecker) {
|
||||||
|
diagnostics.push(...this.jsxLiteralChecker.checkFile(sourceFile));
|
||||||
|
}
|
||||||
|
// 更新缓存
|
||||||
|
if (fileVersion) {
|
||||||
|
this.fileVersionCache.set(fileName, fileVersion);
|
||||||
|
this.diagnosticsCache.set(fileName, diagnostics);
|
||||||
|
}
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 检查所有文件(用于 CLI 编译)
|
||||||
|
*/
|
||||||
|
checkAllFiles() {
|
||||||
|
const allDiagnostics = [];
|
||||||
|
for (const sourceFile of this.program.getSourceFiles()) {
|
||||||
|
if (sourceFile.isDeclarationFile)
|
||||||
|
continue;
|
||||||
|
const diagnostics = this.checkFile(sourceFile.fileName);
|
||||||
|
allDiagnostics.push(...diagnostics);
|
||||||
|
}
|
||||||
|
return allDiagnostics;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 清除缓存
|
||||||
|
*/
|
||||||
|
clearCache(fileName) {
|
||||||
|
if (fileName) {
|
||||||
|
this.diagnosticsCache.delete(fileName);
|
||||||
|
this.fileVersionCache.delete(fileName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.diagnosticsCache.clear();
|
||||||
|
this.fileVersionCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新 Program(当项目重新编译时)
|
||||||
|
*/
|
||||||
|
updateProgram(program) {
|
||||||
|
this.program = program;
|
||||||
|
this.typeChecker = program.getTypeChecker();
|
||||||
|
this.clearCache(); // 清除所有缓存
|
||||||
|
this.initializeCheckers(); // 重新初始化检查器
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.OakCustomChecker = OakCustomChecker;
|
||||||
|
//# sourceMappingURL=checker.js.map
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"checker.js","sourceRoot":"","sources":["../../src/core/checker.ts"],"names":[],"mappings":"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"}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
import { OakBuildChecksConfig, CustomDiagnostic } from './checker';
|
||||||
|
export declare class I18nChecker {
|
||||||
|
private pwd;
|
||||||
|
private config;
|
||||||
|
private program;
|
||||||
|
private typeChecker;
|
||||||
|
private tsLib;
|
||||||
|
private localeDataCache;
|
||||||
|
private tFunctionCalls;
|
||||||
|
constructor(pwd: string, config: OakBuildChecksConfig, program: ts.Program, typeChecker: ts.TypeChecker, tsLib: typeof ts);
|
||||||
|
checkFile(sourceFile: ts.SourceFile): CustomDiagnostic[];
|
||||||
|
private collectTFunctionCalls;
|
||||||
|
private checkI18nKeys;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.I18nChecker = void 0;
|
||||||
|
class I18nChecker {
|
||||||
|
constructor(pwd, config, program, typeChecker, tsLib) {
|
||||||
|
this.pwd = pwd;
|
||||||
|
this.config = config;
|
||||||
|
this.program = program;
|
||||||
|
this.typeChecker = typeChecker;
|
||||||
|
this.tsLib = tsLib;
|
||||||
|
// i18n 数据缓存
|
||||||
|
this.localeDataCache = new Map();
|
||||||
|
this.tFunctionCalls = new Map();
|
||||||
|
}
|
||||||
|
checkFile(sourceFile) {
|
||||||
|
// 收集 t() 函数调用
|
||||||
|
const tCalls = this.collectTFunctionCalls(sourceFile);
|
||||||
|
// 检查 i18n keys
|
||||||
|
return this.checkI18nKeys(sourceFile, tCalls);
|
||||||
|
}
|
||||||
|
collectTFunctionCalls(sourceFile) {
|
||||||
|
// 复用原有的 isTCall 逻辑
|
||||||
|
const calls = [];
|
||||||
|
// ...
|
||||||
|
return calls;
|
||||||
|
}
|
||||||
|
checkI18nKeys(sourceFile, calls) {
|
||||||
|
// 复用原有的 checkI18nKeys 逻辑
|
||||||
|
const diagnostics = [];
|
||||||
|
// ...
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.I18nChecker = I18nChecker;
|
||||||
|
//# sourceMappingURL=i18nChecker.js.map
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"i18nChecker.js","sourceRoot":"","sources":["../../src/core/i18nChecker.ts"],"names":[],"mappings":";;;AAGA,MAAa,WAAW;IAKpB,YACY,GAAW,EACX,MAA4B,EAC5B,OAAmB,EACnB,WAA2B,EAC3B,KAAgB;QAJhB,QAAG,GAAH,GAAG,CAAQ;QACX,WAAM,GAAN,MAAM,CAAsB;QAC5B,YAAO,GAAP,OAAO,CAAY;QACnB,gBAAW,GAAX,WAAW,CAAgB;QAC3B,UAAK,GAAL,KAAK,CAAW;QAT5B,YAAY;QACJ,oBAAe,GAAG,IAAI,GAAG,EAAyC,CAAC;QACnE,mBAAc,GAAG,IAAI,GAAG,EAAsC,CAAC;IAQnE,CAAC;IAEE,SAAS,CAAC,UAAyB;QACtC,cAAc;QACd,MAAM,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC;QAEtD,eAAe;QACf,OAAO,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAClD,CAAC;IAEO,qBAAqB,CAAC,UAAyB;QACnD,mBAAmB;QACnB,MAAM,KAAK,GAAwB,EAAE,CAAC;QACtC,MAAM;QACN,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,aAAa,CACjB,UAAyB,EACzB,KAA0B;QAE1B,yBAAyB;QACzB,MAAM,WAAW,GAAuB,EAAE,CAAC;QAC3C,MAAM;QACN,OAAO,WAAW,CAAC;IACvB,CAAC;CACJ;AArCD,kCAqCC"}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
import { OakBuildChecksConfig, CustomDiagnostic } from './checker';
|
||||||
|
export declare class JsxLiteralChecker {
|
||||||
|
private pwd;
|
||||||
|
private config;
|
||||||
|
private program;
|
||||||
|
private typeChecker;
|
||||||
|
private tsLib;
|
||||||
|
private localeDataCache;
|
||||||
|
private tFunctionCalls;
|
||||||
|
constructor(pwd: string, config: OakBuildChecksConfig, program: ts.Program, typeChecker: ts.TypeChecker, tsLib: typeof ts);
|
||||||
|
checkFile(sourceFile: ts.SourceFile): CustomDiagnostic[];
|
||||||
|
private collectTFunctionCalls;
|
||||||
|
private checkI18nKeys;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.JsxLiteralChecker = void 0;
|
||||||
|
class JsxLiteralChecker {
|
||||||
|
constructor(pwd, config, program, typeChecker, tsLib) {
|
||||||
|
this.pwd = pwd;
|
||||||
|
this.config = config;
|
||||||
|
this.program = program;
|
||||||
|
this.typeChecker = typeChecker;
|
||||||
|
this.tsLib = tsLib;
|
||||||
|
// i18n 数据缓存
|
||||||
|
this.localeDataCache = new Map();
|
||||||
|
this.tFunctionCalls = new Map();
|
||||||
|
}
|
||||||
|
checkFile(sourceFile) {
|
||||||
|
// 收集 t() 函数调用
|
||||||
|
const tCalls = this.collectTFunctionCalls(sourceFile);
|
||||||
|
// 检查 i18n keys
|
||||||
|
return this.checkI18nKeys(sourceFile, tCalls);
|
||||||
|
}
|
||||||
|
collectTFunctionCalls(sourceFile) {
|
||||||
|
// 复用原有的 isTCall 逻辑
|
||||||
|
const calls = [];
|
||||||
|
// ...
|
||||||
|
return calls;
|
||||||
|
}
|
||||||
|
checkI18nKeys(sourceFile, calls) {
|
||||||
|
// 复用原有的 checkI18nKeys 逻辑
|
||||||
|
const diagnostics = [];
|
||||||
|
// ...
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.JsxLiteralChecker = JsxLiteralChecker;
|
||||||
|
//# sourceMappingURL=jsxLiteralChecker.js.map
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"jsxLiteralChecker.js","sourceRoot":"","sources":["../../src/core/jsxLiteralChecker.ts"],"names":[],"mappings":";;;AAGA,MAAa,iBAAiB;IAK1B,YACY,GAAW,EACX,MAA4B,EAC5B,OAAmB,EACnB,WAA2B,EAC3B,KAAgB;QAJhB,QAAG,GAAH,GAAG,CAAQ;QACX,WAAM,GAAN,MAAM,CAAsB;QAC5B,YAAO,GAAP,OAAO,CAAY;QACnB,gBAAW,GAAX,WAAW,CAAgB;QAC3B,UAAK,GAAL,KAAK,CAAW;QAT5B,YAAY;QACJ,oBAAe,GAAG,IAAI,GAAG,EAAyC,CAAC;QACnE,mBAAc,GAAG,IAAI,GAAG,EAAsC,CAAC;IAQnE,CAAC;IAEE,SAAS,CAAC,UAAyB;QACtC,cAAc;QACd,MAAM,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC;QAEtD,eAAe;QACf,OAAO,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAClD,CAAC;IAEO,qBAAqB,CAAC,UAAyB;QACnD,mBAAmB;QACnB,MAAM,KAAK,GAAwB,EAAE,CAAC;QACtC,MAAM;QACN,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,aAAa,CACjB,UAAyB,EACzB,KAA0B;QAE1B,yBAAyB;QACzB,MAAM,WAAW,GAAuB,EAAE,CAAC;QAC3C,MAAM;QACN,OAAO,WAAW,CAAC;IACvB,CAAC;CACJ;AArCD,8CAqCC"}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
import { CustomDiagnostic } from './core/checker';
|
||||||
|
/**
|
||||||
|
* 将自定义诊断转换为 TypeScript 诊断
|
||||||
|
*/
|
||||||
|
export declare function convertToTsDiagnostics(customDiagnostics: CustomDiagnostic[]): ts.Diagnostic[];
|
||||||
|
/**
|
||||||
|
* 将自定义诊断转换为代码修复
|
||||||
|
*/
|
||||||
|
export declare function convertToCodeFixes(customDiagnostics: CustomDiagnostic[], fileName: string): ts.CodeFixAction[];
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
"use strict";
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || (function () {
|
||||||
|
var ownKeys = function(o) {
|
||||||
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||||
|
var ar = [];
|
||||||
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||||
|
return ar;
|
||||||
|
};
|
||||||
|
return ownKeys(o);
|
||||||
|
};
|
||||||
|
return function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.convertToTsDiagnostics = convertToTsDiagnostics;
|
||||||
|
exports.convertToCodeFixes = convertToCodeFixes;
|
||||||
|
const ts = __importStar(require("typescript/lib/tsserverlibrary"));
|
||||||
|
/**
|
||||||
|
* 将自定义诊断转换为 TypeScript 诊断
|
||||||
|
*/
|
||||||
|
function convertToTsDiagnostics(customDiagnostics) {
|
||||||
|
return customDiagnostics.map((custom) => {
|
||||||
|
const diagnostic = {
|
||||||
|
file: custom.file,
|
||||||
|
start: custom.start,
|
||||||
|
length: custom.length,
|
||||||
|
messageText: custom.messageText,
|
||||||
|
category: custom.category,
|
||||||
|
code: custom.code,
|
||||||
|
};
|
||||||
|
// 添加相关信息
|
||||||
|
if (custom.relatedInfo && custom.relatedInfo.length > 0) {
|
||||||
|
diagnostic.relatedInformation = custom.relatedInfo.map((info) => ({
|
||||||
|
file: info.file,
|
||||||
|
start: info.start,
|
||||||
|
length: info.length,
|
||||||
|
messageText: info.message,
|
||||||
|
category: ts.DiagnosticCategory.Message,
|
||||||
|
code: 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return diagnostic;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 将自定义诊断转换为代码修复
|
||||||
|
*/
|
||||||
|
function convertToCodeFixes(customDiagnostics, fileName) {
|
||||||
|
const fixes = [];
|
||||||
|
for (const diagnostic of customDiagnostics) {
|
||||||
|
// 为 AsyncContext 错误提供修复
|
||||||
|
if (diagnostic.code === 9100 || diagnostic.code === 9101) {
|
||||||
|
fixes.push({
|
||||||
|
fixName: 'addAwait',
|
||||||
|
description: '添加 await 关键字',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
fileName,
|
||||||
|
textChanges: [
|
||||||
|
{
|
||||||
|
span: { start: diagnostic.start, length: 0 },
|
||||||
|
newText: 'await ',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
fixes.push({
|
||||||
|
fixName: 'addIgnoreComment',
|
||||||
|
description: '添加 @oak-ignore 注释',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
fileName,
|
||||||
|
textChanges: [
|
||||||
|
{
|
||||||
|
span: { start: diagnostic.start, length: 0 },
|
||||||
|
newText: '// @oak-ignore\n',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 为 i18n 错误提供修复
|
||||||
|
if (diagnostic.code >= 9200 && diagnostic.code < 9300) {
|
||||||
|
// TODO: 提供创建 i18n key 的修复
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fixes;
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=diagnosticConverter.js.map
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"diagnosticConverter.js","sourceRoot":"","sources":["../src/diagnosticConverter.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,wDA0BC;AAKD,gDAiDC;AAtFD,mEAAqD;AAGrD;;GAEG;AACH,SAAgB,sBAAsB,CACpC,iBAAqC;IAErC,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QACtC,MAAM,UAAU,GAAkB;YAChC,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,IAAI,EAAE,MAAM,CAAC,IAAI;SAAE,CAAC;QAEtB,SAAS;QACT,IAAI,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxD,UAAU,CAAC,kBAAkB,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAChE,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,WAAW,EAAE,IAAI,CAAC,OAAO;gBACzB,QAAQ,EAAE,EAAE,CAAC,kBAAkB,CAAC,OAAO;gBACvC,IAAI,EAAE,CAAC;aACR,CAAC,CAAC,CAAC;QACN,CAAC;QAED,OAAO,UAAU,CAAC;IACpB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAgB,kBAAkB,CAChC,iBAAqC,EACrC,QAAgB;IAEhB,MAAM,KAAK,GAAuB,EAAE,CAAC;IAErC,KAAK,MAAM,UAAU,IAAI,iBAAiB,EAAE,CAAC;QAC3C,wBAAwB;QACxB,IAAI,UAAU,CAAC,IAAI,KAAK,IAAI,IAAI,UAAU,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACzD,KAAK,CAAC,IAAI,CAAC;gBACT,OAAO,EAAE,UAAU;gBACnB,WAAW,EAAE,cAAc;gBAC3B,OAAO,EAAE;oBACP;wBACE,QAAQ;wBACR,WAAW,EAAE;4BACX;gCACE,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE;gCAC5C,OAAO,EAAE,QAAQ;6BAClB;yBACF;qBACF;iBACF;aACF,CAAC,CAAC;YAEH,KAAK,CAAC,IAAI,CAAC;gBACT,OAAO,EAAE,kBAAkB;gBAC3B,WAAW,EAAE,mBAAmB;gBAChC,OAAO,EAAE;oBACP;wBACE,QAAQ;wBACR,WAAW,EAAE;4BACX;gCACE,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE;gCAC5C,OAAO,EAAE,kBAAkB;6BAC5B;yBACF;qBACF;iBACF;aACF,CAAC,CAAC;QACL,CAAC;QAED,gBAAgB;QAChB,IAAI,UAAU,CAAC,IAAI,IAAI,IAAI,IAAI,UAAU,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC;YACtD,0BAA0B;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
declare function init(modules: any): {
|
||||||
|
create: (info: ts.server.PluginCreateInfo) => ts.LanguageService;
|
||||||
|
};
|
||||||
|
export = init;
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
"use strict";
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || (function () {
|
||||||
|
var ownKeys = function(o) {
|
||||||
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||||
|
var ar = [];
|
||||||
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||||
|
return ar;
|
||||||
|
};
|
||||||
|
return ownKeys(o);
|
||||||
|
};
|
||||||
|
return function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
const languageServiceProxy_1 = require("./languageServiceProxy");
|
||||||
|
const checker_1 = require("./core/checker");
|
||||||
|
const diagnosticConverter_1 = require("./diagnosticConverter");
|
||||||
|
const path = __importStar(require("path"));
|
||||||
|
function init(modules) {
|
||||||
|
const ts = modules.typescript;
|
||||||
|
function create(info) {
|
||||||
|
const logger = (msg) => info.project.projectService.logger.info(`[oak-tsc-plugin] ${msg}`);
|
||||||
|
logger('Plugin starting...');
|
||||||
|
// 读取配置
|
||||||
|
const config = info.config || {};
|
||||||
|
logger(`Config: ${JSON.stringify(config)}`);
|
||||||
|
// 检查是否禁用
|
||||||
|
if (process.env['OAK_TSC_PLUGIN_DISABLED']) {
|
||||||
|
logger('Plugin disabled by environment variable');
|
||||||
|
return info.languageService;
|
||||||
|
}
|
||||||
|
// 获取项目目录
|
||||||
|
const projectDir = path.dirname(info.project.getProjectName());
|
||||||
|
logger(`Project directory: ${projectDir}`);
|
||||||
|
// 创建检查器实例
|
||||||
|
let checker = null;
|
||||||
|
const getChecker = () => {
|
||||||
|
const program = info.languageService.getProgram();
|
||||||
|
if (!program) {
|
||||||
|
throw new Error('Program not available');
|
||||||
|
}
|
||||||
|
// 如果 program 变化,重新创建 checker
|
||||||
|
if (!checker || checker['program'] !== program) {
|
||||||
|
logger('Creating new checker instance');
|
||||||
|
checker = new checker_1.OakCustomChecker(projectDir, config, program, program.getTypeChecker(), ts);
|
||||||
|
}
|
||||||
|
return checker;
|
||||||
|
};
|
||||||
|
// 构建代理
|
||||||
|
const proxy = new languageServiceProxy_1.LanguageServiceProxyBuilder(info)
|
||||||
|
.wrap('getSemanticDiagnostics', (delegate) => {
|
||||||
|
return (fileName) => {
|
||||||
|
logger(`Getting diagnostics for ${fileName}`);
|
||||||
|
try {
|
||||||
|
// 获取原始诊断
|
||||||
|
const original = delegate(fileName);
|
||||||
|
// 执行自定义检查
|
||||||
|
const checker = getChecker();
|
||||||
|
const fileVersion = info.languageServiceHost.getScriptVersion(fileName);
|
||||||
|
const customDiagnostics = checker.checkFile(fileName, fileVersion);
|
||||||
|
// 转换并合并诊断
|
||||||
|
const tsDiagnostics = (0, diagnosticConverter_1.convertToTsDiagnostics)(customDiagnostics);
|
||||||
|
logger(`Found ${customDiagnostics.length} custom diagnostics`);
|
||||||
|
return [...original, ...tsDiagnostics];
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
logger(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
if (error instanceof Error && error.stack) {
|
||||||
|
logger(error.stack);
|
||||||
|
}
|
||||||
|
return delegate(fileName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.wrap('getCodeFixesAtPosition', (delegate) => {
|
||||||
|
return (fileName, start, end, errorCodes, formatOptions, preferences) => {
|
||||||
|
const original = delegate(fileName, start, end, errorCodes, formatOptions, preferences);
|
||||||
|
// TODO: 为自定义诊断提供 code fixes
|
||||||
|
// 例如:自动添加 await、自动添加 @oak-ignore 注释等
|
||||||
|
return original;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.wrap('getSupportedCodeFixes', (delegate) => {
|
||||||
|
return (fileName) => {
|
||||||
|
const original = delegate(fileName);
|
||||||
|
// 添加自定义错误代码
|
||||||
|
return [...original, '9100', '9101', '9200', '9300'];
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
// 监听配置文件变化
|
||||||
|
const configFiles = ['.oakrc', 'oak.config.json'];
|
||||||
|
configFiles.forEach((configFile) => {
|
||||||
|
const configPath = path.resolve(projectDir, configFile);
|
||||||
|
info.serverHost.watchFile(configPath, () => {
|
||||||
|
logger(`Config file changed: ${configFile}`);
|
||||||
|
// 清除缓存
|
||||||
|
if (checker) {
|
||||||
|
checker.clearCache();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
logger('Plugin initialized successfully');
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
return { create };
|
||||||
|
}
|
||||||
|
module.exports = init;
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
type LanguageServiceMethodWrapper<K extends keyof ts.LanguageService> = (delegate: ts.LanguageService[K], info?: ts.server.PluginCreateInfo) => ts.LanguageService[K];
|
||||||
|
export declare class LanguageServiceProxyBuilder {
|
||||||
|
private readonly info;
|
||||||
|
private readonly wrappers;
|
||||||
|
constructor(info: ts.server.PluginCreateInfo);
|
||||||
|
wrap<K extends keyof ts.LanguageService>(name: K, wrapper: LanguageServiceMethodWrapper<K>): this;
|
||||||
|
build(): ts.LanguageService;
|
||||||
|
}
|
||||||
|
export {};
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.LanguageServiceProxyBuilder = void 0;
|
||||||
|
class LanguageServiceProxyBuilder {
|
||||||
|
constructor(info) {
|
||||||
|
this.info = info;
|
||||||
|
this.wrappers = [];
|
||||||
|
}
|
||||||
|
wrap(name, wrapper) {
|
||||||
|
this.wrappers.push({ name, wrapper });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
build() {
|
||||||
|
const proxy = Object.create(null);
|
||||||
|
const languageService = this.info.languageService;
|
||||||
|
// 复制所有方法
|
||||||
|
for (const k of Object.keys(languageService)) {
|
||||||
|
const original = languageService[k];
|
||||||
|
if (typeof original === 'function') {
|
||||||
|
proxy[k] = (...args) => original.apply(languageService, args);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
proxy[k] = original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 应用包装器
|
||||||
|
for (const { name, wrapper } of this.wrappers) {
|
||||||
|
const original = languageService[name];
|
||||||
|
if (typeof original === 'function') {
|
||||||
|
proxy[name] = wrapper(original, this.info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.LanguageServiceProxyBuilder = LanguageServiceProxyBuilder;
|
||||||
|
//# sourceMappingURL=languageServiceProxy.js.map
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"languageServiceProxy.js","sourceRoot":"","sources":["../src/languageServiceProxy.ts"],"names":[],"mappings":";;;AAOA,MAAa,2BAA2B;IAMtC,YAA6B,IAAgC;QAAhC,SAAI,GAAJ,IAAI,CAA4B;QAL5C,aAAQ,GAGpB,EAAE,CAAC;IAEwD,CAAC;IAE1D,IAAI,CACT,IAAO,EACP,OAAwC;QAExC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,KAAK;QACV,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAuB,CAAC;QACxD,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC;QAElD,SAAS;QACT,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,eAAe,CAAoC,EAAE,CAAC;YAChF,MAAM,QAAQ,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;YACpC,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;gBAClC,KAAa,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAW,EAAE,EAAE,CAAE,QAAgB,CAAC,KAAK,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;YACzF,CAAC;iBAAM,CAAC;gBACL,KAAa,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC;YAC/B,CAAC;QACH,CAAC;QAED,QAAQ;QACR,KAAK,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9C,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;gBAClC,KAAa,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,QAAe,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAxCD,kEAwCC"}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"name": "ts-oak-plugin",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "TypeScript language service plugin for OAK",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf -g local lib \"e2e/**/*.log\"",
|
||||||
|
"build": "tsc -p tsconfig.build.json",
|
||||||
|
"build:local": "tsc -p tsconfig.build.json --outDir local",
|
||||||
|
"lint": "eslint \"src/**/*.ts\" \"e2e/**/*.ts\"",
|
||||||
|
"test": "jest",
|
||||||
|
"prettier": "prettier \"*.md\" \"*.json\" \"*.yml\" \"src/**/*\" \"e2e/**/*\"",
|
||||||
|
"format": "npm run prettier -- --write",
|
||||||
|
"format:check": "npm run prettier -- --check",
|
||||||
|
"e2e": "jest --config e2e/jest.config.json",
|
||||||
|
"prepare": "husky install"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "qcqcqc",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">= 4.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "29.5.14",
|
||||||
|
"@types/node": "20.19.25",
|
||||||
|
"fretted-strings": "2.0.0",
|
||||||
|
"husky": "9.1.7",
|
||||||
|
"jest": "29.7.0",
|
||||||
|
"prettier": "3.6.2",
|
||||||
|
"pretty-quick": "4.2.2",
|
||||||
|
"rimraf": "5.0.10",
|
||||||
|
"ts-jest": "29.4.5",
|
||||||
|
"typescript": "5.9.3"
|
||||||
|
},
|
||||||
|
"files":[
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"oak-domain": "^5.1.33"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
import { OakBuildChecksConfig, CustomDiagnostic } from './checker';
|
||||||
|
|
||||||
|
export class AsyncContextChecker {
|
||||||
|
// 全局状态(跨文件)
|
||||||
|
private functionsWithContextCalls = new Set<ts.Symbol>();
|
||||||
|
private functionDeclarations = new Map<ts.Symbol, ts.FunctionLikeDeclaration>();
|
||||||
|
private functionCallGraph = new Map<ts.Symbol, Set<ts.Symbol>>();
|
||||||
|
private directContextCalls = new Map<ts.Symbol, ts.CallExpression[]>();
|
||||||
|
|
||||||
|
// 缓存
|
||||||
|
private ignoreCommentNodes = new Map<ts.SourceFile, Set<ts.Node>>();
|
||||||
|
private isInitialized = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private pwd: string,
|
||||||
|
private config: OakBuildChecksConfig,
|
||||||
|
private program: ts.Program,
|
||||||
|
private typeChecker: ts.TypeChecker,
|
||||||
|
private tsLib: typeof ts
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化:构建全局调用图
|
||||||
|
*/
|
||||||
|
private initialize(): void {
|
||||||
|
if (this.isInitialized) return;
|
||||||
|
|
||||||
|
// 遍历所有文件,构建调用图
|
||||||
|
for (const sourceFile of this.program.getSourceFiles()) {
|
||||||
|
if (sourceFile.isDeclarationFile) continue;
|
||||||
|
if (!this.isFileInCheckScope(sourceFile.fileName)) continue;
|
||||||
|
|
||||||
|
this.preprocessIgnoreComments(sourceFile);
|
||||||
|
this.collectCallGraph(sourceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤非 Promise 返回的调用
|
||||||
|
this.filterAsyncContextCalls();
|
||||||
|
// 传播标记
|
||||||
|
this.propagateContextMarks();
|
||||||
|
|
||||||
|
this.isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查单个文件
|
||||||
|
*/
|
||||||
|
public checkFile(sourceFile: ts.SourceFile): CustomDiagnostic[] {
|
||||||
|
// 确保已初始化
|
||||||
|
this.initialize();
|
||||||
|
|
||||||
|
if (!this.isFileInCheckScope(sourceFile.fileName)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const diagnostics: CustomDiagnostic[] = [];
|
||||||
|
|
||||||
|
// 检查直接的 context 调用
|
||||||
|
diagnostics.push(...this.checkDirectContextCalls(sourceFile));
|
||||||
|
|
||||||
|
// 检查间接调用
|
||||||
|
diagnostics.push(...this.checkIndirectCalls(sourceFile));
|
||||||
|
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isFileInCheckScope(fileName: string): boolean {
|
||||||
|
// 复用原有的 isFileInCheckScope 逻辑
|
||||||
|
// ...
|
||||||
|
return true; // 简化示例
|
||||||
|
}
|
||||||
|
|
||||||
|
private preprocessIgnoreComments(sourceFile: ts.SourceFile): void {
|
||||||
|
// 复用原有的 preprocessIgnoreComments 逻辑
|
||||||
|
// 将结果存储在 this.ignoreCommentNodes 中
|
||||||
|
}
|
||||||
|
|
||||||
|
private collectCallGraph(sourceFile: ts.SourceFile): void {
|
||||||
|
// 复用原有的 collectAndCheck 逻辑
|
||||||
|
// 收集函数声明和调用关系
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterAsyncContextCalls(): void {
|
||||||
|
// 复用原有逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
private propagateContextMarks(): void {
|
||||||
|
// 复用原有逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkDirectContextCalls(sourceFile: ts.SourceFile): CustomDiagnostic[] {
|
||||||
|
// 复用原有逻辑,但只检查当前文件
|
||||||
|
const diagnostics: CustomDiagnostic[] = [];
|
||||||
|
// ...
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkIndirectCalls(sourceFile: ts.SourceFile): CustomDiagnostic[] {
|
||||||
|
// 复用原有逻辑,但只检查当前文件
|
||||||
|
const diagnostics: CustomDiagnostic[] = [];
|
||||||
|
// ...
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除缓存(当文件变化时)
|
||||||
|
*/
|
||||||
|
public clearCache(): void {
|
||||||
|
this.isInitialized = false;
|
||||||
|
this.functionsWithContextCalls.clear();
|
||||||
|
this.functionDeclarations.clear();
|
||||||
|
this.functionCallGraph.clear();
|
||||||
|
this.directContextCalls.clear();
|
||||||
|
this.ignoreCommentNodes.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
import { AsyncContextChecker } from './asyncContextChecker';
|
||||||
|
import { I18nChecker } from './i18nChecker';
|
||||||
|
import { JsxLiteralChecker } from './jsxLiteralChecker';
|
||||||
|
|
||||||
|
export interface OakBuildChecksConfig {
|
||||||
|
context?: {
|
||||||
|
checkAsyncContext?: boolean;
|
||||||
|
targetModules?: string[];
|
||||||
|
filePatterns?: string[];
|
||||||
|
};
|
||||||
|
locale?: {
|
||||||
|
checkI18nKeys?: boolean;
|
||||||
|
tFunctionModules?: string[];
|
||||||
|
checkTemplateLiterals?: boolean; warnStringKeys?: boolean;
|
||||||
|
checkJsxLiterals?: boolean;
|
||||||
|
jsxLiteralPattern?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomDiagnostic {
|
||||||
|
file: ts.SourceFile;
|
||||||
|
start: number;
|
||||||
|
length: number;
|
||||||
|
messageText: string;
|
||||||
|
category: ts.DiagnosticCategory;
|
||||||
|
code: number;
|
||||||
|
callChain?: string[]; contextCallNode?: ts.CallExpression; reason?: string;
|
||||||
|
reasonDetails?: string[]; relatedInfo?: Array<{
|
||||||
|
file: ts.SourceFile;
|
||||||
|
start: number;
|
||||||
|
length: number;
|
||||||
|
message: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心检查器类 - 支持增量检查
|
||||||
|
*/
|
||||||
|
export class OakCustomChecker {
|
||||||
|
private asyncContextChecker?: AsyncContextChecker;
|
||||||
|
private i18nChecker?: I18nChecker;
|
||||||
|
private jsxLiteralChecker?: JsxLiteralChecker;
|
||||||
|
// 缓存:文件名 -> 诊断结果
|
||||||
|
private diagnosticsCache = new Map<string, CustomDiagnostic[]>();
|
||||||
|
// 缓存:文件名 -> 文件版本
|
||||||
|
private fileVersionCache = new Map<string, string>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private pwd: string,
|
||||||
|
private config: OakBuildChecksConfig,
|
||||||
|
private program: ts.Program,
|
||||||
|
private typeChecker: ts.TypeChecker,
|
||||||
|
private tsLib: typeof ts
|
||||||
|
) {
|
||||||
|
this.initializeCheckers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeCheckers(): void {
|
||||||
|
if (this.config.context?.checkAsyncContext !== false) {
|
||||||
|
this.asyncContextChecker = new AsyncContextChecker(
|
||||||
|
this.pwd,
|
||||||
|
this.config,
|
||||||
|
this.program,
|
||||||
|
this.typeChecker,
|
||||||
|
this.tsLib
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.locale?.checkI18nKeys !== false) {
|
||||||
|
this.i18nChecker = new I18nChecker(
|
||||||
|
this.pwd,
|
||||||
|
this.config,
|
||||||
|
this.program,
|
||||||
|
this.typeChecker,
|
||||||
|
this.tsLib
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.locale?.checkJsxLiterals === true) {
|
||||||
|
this.jsxLiteralChecker = new JsxLiteralChecker(
|
||||||
|
this.pwd,
|
||||||
|
this.config,
|
||||||
|
this.program,
|
||||||
|
this.typeChecker,
|
||||||
|
this.tsLib
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查单个文件(用于 TSServer 插件)
|
||||||
|
*/
|
||||||
|
public checkFile(fileName: string, fileVersion?: string): CustomDiagnostic[] {
|
||||||
|
// 检查缓存
|
||||||
|
if (fileVersion && this.fileVersionCache.get(fileName) === fileVersion) {
|
||||||
|
const cached = this.diagnosticsCache.get(fileName);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceFile = this.program.getSourceFile(fileName);
|
||||||
|
if (!sourceFile) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const diagnostics: CustomDiagnostic[] = [];
|
||||||
|
|
||||||
|
// 执行各项检查
|
||||||
|
if (this.asyncContextChecker) {
|
||||||
|
diagnostics.push(...this.asyncContextChecker.checkFile(sourceFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.i18nChecker) {
|
||||||
|
diagnostics.push(...this.i18nChecker.checkFile(sourceFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.jsxLiteralChecker) {
|
||||||
|
diagnostics.push(...this.jsxLiteralChecker.checkFile(sourceFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新缓存
|
||||||
|
if (fileVersion) {
|
||||||
|
this.fileVersionCache.set(fileName, fileVersion);
|
||||||
|
this.diagnosticsCache.set(fileName, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查所有文件(用于 CLI 编译)
|
||||||
|
*/
|
||||||
|
public checkAllFiles(): CustomDiagnostic[] {
|
||||||
|
const allDiagnostics: CustomDiagnostic[] = [];
|
||||||
|
|
||||||
|
for (const sourceFile of this.program.getSourceFiles()) {
|
||||||
|
if (sourceFile.isDeclarationFile) continue;
|
||||||
|
|
||||||
|
const diagnostics = this.checkFile(sourceFile.fileName);
|
||||||
|
allDiagnostics.push(...diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allDiagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除缓存
|
||||||
|
*/
|
||||||
|
public clearCache(fileName?: string): void {
|
||||||
|
if (fileName) {
|
||||||
|
this.diagnosticsCache.delete(fileName);
|
||||||
|
this.fileVersionCache.delete(fileName);
|
||||||
|
} else {
|
||||||
|
this.diagnosticsCache.clear();
|
||||||
|
this.fileVersionCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新 Program(当项目重新编译时)
|
||||||
|
*/
|
||||||
|
public updateProgram(program: ts.Program): void {
|
||||||
|
this.program = program;
|
||||||
|
this.typeChecker = program.getTypeChecker();
|
||||||
|
this.clearCache(); // 清除所有缓存
|
||||||
|
this.initializeCheckers(); // 重新初始化检查器
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
import { OakBuildChecksConfig, CustomDiagnostic } from './checker';
|
||||||
|
|
||||||
|
export class I18nChecker {
|
||||||
|
// i18n 数据缓存
|
||||||
|
private localeDataCache = new Map<string, Record<string, string> | null>();
|
||||||
|
private tFunctionCalls = new Map<ts.SourceFile, ts.CallExpression[]>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private pwd: string,
|
||||||
|
private config: OakBuildChecksConfig,
|
||||||
|
private program: ts.Program,
|
||||||
|
private typeChecker: ts.TypeChecker,
|
||||||
|
private tsLib: typeof ts
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public checkFile(sourceFile: ts.SourceFile): CustomDiagnostic[] {
|
||||||
|
// 收集 t() 函数调用
|
||||||
|
const tCalls = this.collectTFunctionCalls(sourceFile);
|
||||||
|
|
||||||
|
// 检查 i18n keys
|
||||||
|
return this.checkI18nKeys(sourceFile, tCalls);
|
||||||
|
}
|
||||||
|
|
||||||
|
private collectTFunctionCalls(sourceFile: ts.SourceFile): ts.CallExpression[] {
|
||||||
|
// 复用原有的 isTCall 逻辑
|
||||||
|
const calls: ts.CallExpression[] = [];
|
||||||
|
// ...
|
||||||
|
return calls;
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkI18nKeys(
|
||||||
|
sourceFile: ts.SourceFile,
|
||||||
|
calls: ts.CallExpression[]
|
||||||
|
): CustomDiagnostic[] {
|
||||||
|
// 复用原有的 checkI18nKeys 逻辑
|
||||||
|
const diagnostics: CustomDiagnostic[] = [];
|
||||||
|
// ...
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
import { OakBuildChecksConfig, CustomDiagnostic } from './checker';
|
||||||
|
|
||||||
|
export class JsxLiteralChecker {
|
||||||
|
// i18n 数据缓存
|
||||||
|
private localeDataCache = new Map<string, Record<string, string> | null>();
|
||||||
|
private tFunctionCalls = new Map<ts.SourceFile, ts.CallExpression[]>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private pwd: string,
|
||||||
|
private config: OakBuildChecksConfig,
|
||||||
|
private program: ts.Program,
|
||||||
|
private typeChecker: ts.TypeChecker,
|
||||||
|
private tsLib: typeof ts
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public checkFile(sourceFile: ts.SourceFile): CustomDiagnostic[] {
|
||||||
|
// 收集 t() 函数调用
|
||||||
|
const tCalls = this.collectTFunctionCalls(sourceFile);
|
||||||
|
|
||||||
|
// 检查 i18n keys
|
||||||
|
return this.checkI18nKeys(sourceFile, tCalls);
|
||||||
|
}
|
||||||
|
|
||||||
|
private collectTFunctionCalls(sourceFile: ts.SourceFile): ts.CallExpression[] {
|
||||||
|
// 复用原有的 isTCall 逻辑
|
||||||
|
const calls: ts.CallExpression[] = [];
|
||||||
|
// ...
|
||||||
|
return calls;
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkI18nKeys(
|
||||||
|
sourceFile: ts.SourceFile,
|
||||||
|
calls: ts.CallExpression[]
|
||||||
|
): CustomDiagnostic[] {
|
||||||
|
// 复用原有的 checkI18nKeys 逻辑
|
||||||
|
const diagnostics: CustomDiagnostic[] = [];
|
||||||
|
// ...
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
import { CustomDiagnostic } from './core/checker';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将自定义诊断转换为 TypeScript 诊断
|
||||||
|
*/
|
||||||
|
export function convertToTsDiagnostics(
|
||||||
|
customDiagnostics: CustomDiagnostic[]
|
||||||
|
): ts.Diagnostic[] {
|
||||||
|
return customDiagnostics.map((custom) => {
|
||||||
|
const diagnostic: ts.Diagnostic = {
|
||||||
|
file: custom.file,
|
||||||
|
start: custom.start,
|
||||||
|
length: custom.length,
|
||||||
|
messageText: custom.messageText,
|
||||||
|
category: custom.category,
|
||||||
|
code: custom.code,};
|
||||||
|
|
||||||
|
// 添加相关信息
|
||||||
|
if (custom.relatedInfo && custom.relatedInfo.length > 0) {
|
||||||
|
diagnostic.relatedInformation = custom.relatedInfo.map((info) => ({
|
||||||
|
file: info.file,
|
||||||
|
start: info.start,
|
||||||
|
length: info.length,
|
||||||
|
messageText: info.message,
|
||||||
|
category: ts.DiagnosticCategory.Message,
|
||||||
|
code: 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return diagnostic;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将自定义诊断转换为代码修复
|
||||||
|
*/
|
||||||
|
export function convertToCodeFixes(
|
||||||
|
customDiagnostics: CustomDiagnostic[],
|
||||||
|
fileName: string
|
||||||
|
): ts.CodeFixAction[] {
|
||||||
|
const fixes: ts.CodeFixAction[] = [];
|
||||||
|
|
||||||
|
for (const diagnostic of customDiagnostics) {
|
||||||
|
// 为 AsyncContext 错误提供修复
|
||||||
|
if (diagnostic.code === 9100 || diagnostic.code === 9101) {
|
||||||
|
fixes.push({
|
||||||
|
fixName: 'addAwait',
|
||||||
|
description: '添加 await 关键字',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
fileName,
|
||||||
|
textChanges: [
|
||||||
|
{
|
||||||
|
span: { start: diagnostic.start, length: 0 },
|
||||||
|
newText: 'await ',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
fixes.push({
|
||||||
|
fixName: 'addIgnoreComment',
|
||||||
|
description: '添加 @oak-ignore 注释',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
fileName,
|
||||||
|
textChanges: [
|
||||||
|
{
|
||||||
|
span: { start: diagnostic.start, length: 0 },
|
||||||
|
newText: '// @oak-ignore\n',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为 i18n 错误提供修复
|
||||||
|
if (diagnostic.code >= 9200 && diagnostic.code < 9300) {
|
||||||
|
// TODO: 提供创建 i18n key 的修复
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fixes;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
import { LanguageServiceProxyBuilder } from './languageServiceProxy';
|
||||||
|
import { OakCustomChecker, OakBuildChecksConfig } from './core/checker';
|
||||||
|
import { convertToTsDiagnostics } from './diagnosticConverter';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
function init(modules: any) {
|
||||||
|
const ts = modules.typescript as typeof import('typescript/lib/tsserverlibrary');
|
||||||
|
|
||||||
|
function create(info: ts.server.PluginCreateInfo): ts.LanguageService {
|
||||||
|
const logger = (msg: string) =>
|
||||||
|
info.project.projectService.logger.info(`[oak-tsc-plugin] ${msg}`);
|
||||||
|
|
||||||
|
logger('Plugin starting...');
|
||||||
|
|
||||||
|
// 读取配置
|
||||||
|
const config: OakBuildChecksConfig = info.config || {};
|
||||||
|
logger(`Config: ${JSON.stringify(config)}`);
|
||||||
|
|
||||||
|
// 检查是否禁用
|
||||||
|
if (process.env['OAK_TSC_PLUGIN_DISABLED']) {
|
||||||
|
logger('Plugin disabled by environment variable');
|
||||||
|
return info.languageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取项目目录
|
||||||
|
const projectDir = path.dirname(info.project.getProjectName());
|
||||||
|
logger(`Project directory: ${projectDir}`);
|
||||||
|
|
||||||
|
// 创建检查器实例
|
||||||
|
let checker: OakCustomChecker | null = null;
|
||||||
|
|
||||||
|
const getChecker = (): OakCustomChecker => {
|
||||||
|
const program = info.languageService.getProgram();
|
||||||
|
if (!program) {
|
||||||
|
throw new Error('Program not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 program 变化,重新创建 checker
|
||||||
|
if (!checker || checker['program'] !== program) {
|
||||||
|
logger('Creating new checker instance');
|
||||||
|
checker = new OakCustomChecker(
|
||||||
|
projectDir,
|
||||||
|
config,
|
||||||
|
program,
|
||||||
|
program.getTypeChecker(),
|
||||||
|
ts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checker;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 构建代理
|
||||||
|
const proxy = new LanguageServiceProxyBuilder(info)
|
||||||
|
.wrap('getSemanticDiagnostics', (delegate) => {
|
||||||
|
return (fileName: string) => {
|
||||||
|
logger(`Getting diagnostics for ${fileName}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取原始诊断
|
||||||
|
const original = delegate(fileName);
|
||||||
|
|
||||||
|
// 执行自定义检查
|
||||||
|
const checker = getChecker();
|
||||||
|
const fileVersion = info.languageServiceHost.getScriptVersion(fileName);
|
||||||
|
const customDiagnostics = checker.checkFile(fileName, fileVersion);
|
||||||
|
|
||||||
|
// 转换并合并诊断
|
||||||
|
const tsDiagnostics = convertToTsDiagnostics(customDiagnostics);
|
||||||
|
|
||||||
|
logger(`Found ${customDiagnostics.length} custom diagnostics`);
|
||||||
|
|
||||||
|
return [...original, ...tsDiagnostics];
|
||||||
|
} catch (error) {
|
||||||
|
logger(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
if (error instanceof Error && error.stack) {
|
||||||
|
logger(error.stack);
|
||||||
|
}
|
||||||
|
return delegate(fileName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.wrap('getCodeFixesAtPosition', (delegate) => {
|
||||||
|
return (
|
||||||
|
fileName: string,
|
||||||
|
start: number,
|
||||||
|
end: number,
|
||||||
|
errorCodes: readonly number[],
|
||||||
|
formatOptions: ts.FormatCodeSettings,
|
||||||
|
preferences: ts.UserPreferences
|
||||||
|
) => {
|
||||||
|
const original = delegate(fileName, start, end, errorCodes, formatOptions, preferences);
|
||||||
|
|
||||||
|
// TODO: 为自定义诊断提供 code fixes
|
||||||
|
// 例如:自动添加 await、自动添加 @oak-ignore 注释等
|
||||||
|
|
||||||
|
return original;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.wrap('getSupportedCodeFixes', (delegate) => {
|
||||||
|
return (fileName?: string) => {
|
||||||
|
const original = delegate(fileName);
|
||||||
|
// 添加自定义错误代码
|
||||||
|
return [...original, '9100', '9101', '9200', '9300'];
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 监听配置文件变化
|
||||||
|
const configFiles = ['.oakrc', 'oak.config.json'];
|
||||||
|
configFiles.forEach((configFile) => {
|
||||||
|
const configPath = path.resolve(projectDir, configFile);
|
||||||
|
info.serverHost.watchFile(
|
||||||
|
configPath,
|
||||||
|
() => {
|
||||||
|
logger(`Config file changed: ${configFile}`);
|
||||||
|
// 清除缓存
|
||||||
|
if (checker) {
|
||||||
|
checker.clearCache();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
50
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger('Plugin initialized successfully');
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { create };
|
||||||
|
}
|
||||||
|
|
||||||
|
export = init;
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
||||||
|
|
||||||
|
type LanguageServiceMethodWrapper<K extends keyof ts.LanguageService> = (
|
||||||
|
delegate: ts.LanguageService[K],
|
||||||
|
info?: ts.server.PluginCreateInfo
|
||||||
|
) => ts.LanguageService[K];
|
||||||
|
|
||||||
|
export class LanguageServiceProxyBuilder {
|
||||||
|
private readonly wrappers: Array<{
|
||||||
|
name: keyof ts.LanguageService;
|
||||||
|
wrapper: LanguageServiceMethodWrapper<any>;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
constructor(private readonly info: ts.server.PluginCreateInfo) {}
|
||||||
|
|
||||||
|
public wrap<K extends keyof ts.LanguageService>(
|
||||||
|
name: K,
|
||||||
|
wrapper: LanguageServiceMethodWrapper<K>
|
||||||
|
): this {
|
||||||
|
this.wrappers.push({ name, wrapper });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public build(): ts.LanguageService {
|
||||||
|
const proxy = Object.create(null) as ts.LanguageService;
|
||||||
|
const languageService = this.info.languageService;
|
||||||
|
|
||||||
|
// 复制所有方法
|
||||||
|
for (const k of Object.keys(languageService) as Array<keyof ts.LanguageService>) {
|
||||||
|
const original = languageService[k];
|
||||||
|
if (typeof original === 'function') {
|
||||||
|
(proxy as any)[k] = (...args: any[]) => (original as any).apply(languageService, args);
|
||||||
|
} else {
|
||||||
|
(proxy as any)[k] = original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用包装器
|
||||||
|
for (const { name, wrapper } of this.wrappers) {
|
||||||
|
const original = languageService[name];
|
||||||
|
if (typeof original === 'function') {
|
||||||
|
(proxy as any)[name] = wrapper(original as any, this.info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "lib"
|
||||||
|
},
|
||||||
|
"exclude": ["e2e", "node_modules", "lib", "local", "test-fixtures"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"skipLibCheck": true,
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "es2019" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
|
||||||
|
"module": "nodenext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
|
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
// "outDir": "./", /* Redirect output structure to the directory. */
|
||||||
|
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
"noUnusedLocals": true /* Report errors on unused locals. */,
|
||||||
|
"noUnusedParameters": true /* Report errors on unused parameters. */,
|
||||||
|
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
|
||||||
|
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
"moduleResolution": "nodenext" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue