This commit is contained in:
Pan Qiancheng 2026-01-14 12:12:51 +08:00
commit b870a4d144
33 changed files with 6904 additions and 0 deletions

37
.gitignore vendored Normal file
View File

@ -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/

35
lib/core/asyncContextChecker.d.ts vendored Normal file
View File

@ -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;
}

View File

@ -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

View File

@ -0,0 +1 @@
{"version":3,"file":"asyncContextChecker.js","sourceRoot":"","sources":["../../src/core/asyncContextChecker.ts"],"names":[],"mappings":"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"}

67
lib/core/checker.d.ts vendored Normal file
View File

@ -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;
}

105
lib/core/checker.js Normal file
View File

@ -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

1
lib/core/checker.js.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"checker.js","sourceRoot":"","sources":["../../src/core/checker.ts"],"names":[],"mappings":"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"}

15
lib/core/i18nChecker.d.ts vendored Normal file
View File

@ -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;
}

35
lib/core/i18nChecker.js Normal file
View File

@ -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

View File

@ -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"}

15
lib/core/jsxLiteralChecker.d.ts vendored Normal file
View File

@ -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;
}

View File

@ -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

View File

@ -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"}

10
lib/diagnosticConverter.d.ts vendored Normal file
View File

@ -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[];

112
lib/diagnosticConverter.js Normal file
View File

@ -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

View File

@ -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"}

5
lib/index.d.ts vendored Normal file
View File

@ -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;

129
lib/index.js Normal file
View File

@ -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

1
lib/index.js.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}

10
lib/languageServiceProxy.d.ts vendored Normal file
View File

@ -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 {};

View File

@ -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

View File

@ -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"}

5394
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
package.json Normal file
View File

@ -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"
}
}

View File

@ -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();
}
}

170
src/core/checker.ts Normal file
View File

@ -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(); // 重新初始化检查器
}
}

41
src/core/i18nChecker.ts Normal file
View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

134
src/index.ts Normal file
View File

@ -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;

View File

@ -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;
}
}

10
tsconfig.build.json Normal file
View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"sourceMap": true,
"rootDir": "src",
"outDir": "lib"
},
"exclude": ["e2e", "node_modules", "lib", "local", "test-fixtures"]
}

64
tsconfig.json Normal file
View File

@ -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. */
}
}