From 716f8b6da38837b5a699be69cb25c10f1ab13687 Mon Sep 17 00:00:00 2001 From: pqcqaq <905739777@qq.com> Date: Fri, 1 Nov 2024 13:33:25 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AF=95=E7=9D=80=E5=86=99=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E4=B8=AAts=20server=20plugin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- esbuild.js | 4 ++ src/plugins/loadTsPlugin.ts | 23 +++++++++ src/typescript/trigger-checker-plugin.ts | 66 ++++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 src/plugins/loadTsPlugin.ts create mode 100644 src/typescript/trigger-checker-plugin.ts diff --git a/esbuild.js b/esbuild.js index b193fec..a22f66a 100644 --- a/esbuild.js +++ b/esbuild.js @@ -75,6 +75,10 @@ async function main() { 'src/extension.ts', 'src/utils/analyzeWorker.ts', // 'src/server/xmlLanguageServer.ts', + // 所有src/typescript下面的文件 + ...( + await fs.readdir('src/typescript') + ).map((file) => `src/typescript/${file}`), ], bundle: true, format: 'cjs', diff --git a/src/plugins/loadTsPlugin.ts b/src/plugins/loadTsPlugin.ts new file mode 100644 index 0000000..9120547 --- /dev/null +++ b/src/plugins/loadTsPlugin.ts @@ -0,0 +1,23 @@ +import path from 'path'; +import * as vscode from 'vscode'; + +export const activate = (context: vscode.ExtensionContext) => { + const pluginPath = path.join( + context.extensionPath, + 'utils', + 'trigger-checker-plugin.js' + ); + + // 注册 TypeScript 语言特性提供者 + // context.subscriptions.push( + // vscode.languages.registerTypeScriptServerPlugin({ + // languageIds: ['typescript', 'typescriptreact'], + // pluginPath, + // enableForWorkspaceTypeScriptVersions: true, + // }) + // ); + + console.log('Await Checker Plugin has been activated!'); +}; + +export function deactivate() {} diff --git a/src/typescript/trigger-checker-plugin.ts b/src/typescript/trigger-checker-plugin.ts new file mode 100644 index 0000000..0e4658d --- /dev/null +++ b/src/typescript/trigger-checker-plugin.ts @@ -0,0 +1,66 @@ +import * as ts from 'typescript/lib/tsserverlibrary'; + +function isPromiseType(typeChecker: ts.TypeChecker, type: ts.Type): boolean { + if (type.isUnion()) { + return type.types.some(t => isPromiseType(typeChecker, t)); + } + + const symbol = type.getSymbol(); + if (symbol && symbol.name === 'Promise') { + return true; + } + + const baseTypes = type.getBaseTypes(); + return baseTypes ? baseTypes.some(t => isPromiseType(typeChecker, t)) : false; +} + +function init(modules: { typescript: typeof ts }) { + const ts = modules.typescript; + + function create(info: ts.server.PluginCreateInfo) { + const proxy: ts.LanguageService = Object.create(null); + for (let k of Object.keys(info.languageService) as Array) { + const x = info.languageService[k] as Function; + proxy[k] = (...args: any[]) => x.apply(info.languageService, args); + } + + proxy.getSemanticDiagnostics = (fileName) => { + const prior = info.languageService.getSemanticDiagnostics(fileName); + const sourceFile = info.languageService.getProgram()?.getSourceFile(fileName); + + if (sourceFile) { + const typeChecker = info.languageService.getProgram()?.getTypeChecker(); + + ts.forEachChild(sourceFile, function checkNode(node) { + if (ts.isCallExpression(node)) { + const signature = typeChecker?.getResolvedSignature(node); + const returnType = signature?.getReturnType(); + + if (returnType && typeChecker && isPromiseType(typeChecker, returnType)) { + if (!ts.isAwaitExpression(node.parent)) { + prior.push({ + file: sourceFile, + start: node.getStart(), + length: node.getWidth(), + messageText: "This function returns a Promise and should be awaited.", + category: ts.DiagnosticCategory.Warning, + code: 9999, + }); + } + } + } + + ts.forEachChild(node, checkNode); + }); + } + + return prior; + }; + + return proxy; + } + + return { create }; +} + +export = init;