Artikel ini akan berkongsi dengan anda VSCode amalan pembangunan pemalam, membangunkan pemalam diagnosis kod, menganalisis prinsip asas dan melaksanakannya langkah demi langkah semua orang!
Baru-baru ini, kami telah menerbitkan panduan Semakan Kod secara dalaman, tetapi proses Semakan Kod mengambil banyak masa dan orang ramai tidak menyemak kod itu dengan berhati-hati, jadi kami ingin menggunakan pemalam untuk membolehkan Pembangun dapat mengesan ralat secara bertulis semasa peringkat pembangunan, dan kesannya adalah seperti yang ditunjukkan di bawah
Seterusnya, kami akan memperkenalkan cara untuk melaksanakan fungsi sedemikian dari awal.
Peluasan fungsi bahasa pengaturcaraan Visual Studio dilaksanakan oleh Pelayan Bahasa Ini mudah difahami, menyemak fungsi bahasa memerlukan prestasi dan memerlukan proses lain perkhidmatan, ini ialah pelayan bahasa Pelayan Bahasa. [Pembelajaran yang disyorkan: "tutorial pengenalan vscode"]
Pelayan Bahasa ialah sambungan Kod Visual Studio khas yang menyediakan pengalaman penyuntingan untuk banyak bahasa pengaturcaraan. Menggunakan pelayan bahasa, anda boleh melaksanakan autolengkap, semakan ralat (diagnostik), lompat ke definisi dan banyak lagi ciri bahasa yang disokong oleh Kod VS.
Memandangkan terdapat fungsi semakan sintaks yang disediakan oleh pelayan, pelanggan perlu menyambung ke pelayan bahasa dan kemudian berinteraksi dengan pelayan Contohnya, pengguna melakukan semakan bahasa semasa mengedit kod pelanggan. Interaksi khusus adalah seperti berikut:
Apabila fail Vue dibuka, pemalam akan diaktifkan dan Pelayan Bahasa akan dimulakan Apabila dokumen berubah, pelayan bahasa akan mendiagnosis semula kod Dan menghantar keputusan diagnosis kepada pelanggan.
Kesan diagnosis kod ialah garisan bergelombang muncul, dan mesej gesaan dipaparkan apabila tetikus dialihkan ke atas Jika terdapat pembetulan pantas, butang pembetulan pantas akan muncul di bawah pop-. up window
Selepas memahami prinsip asas diagnosis kod, kami mula melaksanakannya Daripada prinsip asas di atas, kami perlu melaksanakan dua fungsi utama:
Pelanggan berinteraksi dengan pelayan bahasa
Fungsi pembaikan diagnostik dan cepat pelayan bahasa
Dokumentasi rasmi menyediakan contoh - pelayan bahasa mudah untuk fail teks biasa, yang boleh kami ubah suai berdasarkan contoh ini.
> git clone https://github.com/microsoft/vscode-extension-samples.git > cd vscode-extension-samples/lsp-sample > npm install > npm run compile > code .
Mula-mula buat pelayan dalam klien
// client/src/extension.ts export function activate(context: ExtensionContext) { ... const clientOptions: LanguageClientOptions = { documentSelector: [{ scheme: 'file', language: 'vue' }], // 打开 vue 文件时才激活 ... }; client = new LanguageClient(...); client.start(); }
Kemudian dalam pelayan/src/server.ts, tulis logik interaktif pada klien, seperti seperti dalam klien Apabila dokumen klien berubah, sahkan kod:
// server/src/server.ts import { createConnection TextDocuments, ProposedFeatures, ... } from 'vscode-languageserver/node'; const connection = createConnection(ProposedFeatures.all); const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument); documents.onDidChangeContent(change => { // 文档发生变化时,校验文档 validateTextDocument(change.document); }); async function validateTextDocument(textDocument: TextDocument): Promise<void> { ... // 拿到诊断结果 const diagnostics = getDiagnostics(textDocument, settings); // 发给客户端 connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); } // 提供快速修复的操作 connection.onCodeAction(provideCodeActions); async function provideCodeActions(params: CodeActionParams): Promise<CodeAction[]> { ... return quickfix(textDocument, params); }
Selepas melengkapkan interaksi di atas antara klien dan pelayan, anda boleh melihat dua kaedah ini getDiagnostics(textDocument, settings)
dan quickfix(textDocument, params)
. Kedua-dua kaedah ini adalah untuk menyediakan data diagnostik dan operasi pembaikan pantas untuk dokumen masing-masing.
Proses keseluruhan
Apabila memproses teks kod Vue yang diluluskan oleh pelanggan, ia perlu dihuraikan kepada tiga bahagian struktur data format ast melalui vue/compiler-dom, iaitu templat, JS dan CSS . Disebabkan kod bahagian hadapan semasa TypeScript digunakan, dan bahagian JS tidak dihuraikan ke dalam AST, jadi babel/parser perlu digunakan untuk menghuraikan kod TypeScript untuk menjana struktur data JS AST akhir.
const VueParser = require('@vue/compiler-dom'); // 该函数返回诊断结果客户端 function getDiagnostics(textDocument: TextDocument, settings: any): Diagnostic[] { const text = textDocument.getText(); const res = VueParser.parse(text); const [template, script] = res.children; return [ ...analyzeTemplate(template), // 解析 template 得到诊断结果 ...analyzeScript(script, textDocument), // 解析 js 得到诊断结果 ]; } // 分析 js 语法 function analyzeScript(script: any, textDocument: TextDocument) { const scriptAst = parser.parse(script.children[0]?.content, { sourceType: 'module', plugins: [ 'typescript', // typescript ['decorators', { decoratorsBeforeExport: true }], // 装饰器 'classProperties', // ES6 class 写法 'classPrivateProperties', ], });
Struktur pokok sintaks AST yang terhasil adalah seperti berikut:
Templat AST
JS AST
Selepas mendapat pepohon sintaks kod, kita perlu menyemak setiap nod kod untuk menentukan sama ada ia memenuhi keperluan Semakan Kod, oleh itu adalah perlu untuk melintasi pepohon sintaks untuk memproses setiap nod.
Gunakan carian depth-first untuk melintasi AST templat:
function deepLoopData( data: AstTemplateInterface[], handler: Function, diagnostics: Diagnostic[], ) { function dfs(data: AstTemplateInterface[]) { for (let i = 0; i < data.length; i++) { handler(data[i], diagnostics); // 在这一步对代码进行处理 if (data[i]?.children?.length) { dfs(data[i].children); } else { continue; } } } dfs(data); } function analyzeTemplate(template: any) { const diagnostics: Diagnostic[] = []; deepLoopData(template.children, templateHandler, diagnostics); return diagnostics; } function templateHandler(currData: AstTemplateInterface, diagnostics: Diagnostic[]){ // ...对代码节点检查 }
Untuk JS AST traversal, anda boleh menggunakan babel/traverse traversal:
traverse(scriptAst, { enter(path: any) { ... } }
根据 ast 语法节点去判断语法是否合规,如果不符合要求,需要在代码处生成诊断,一个基础的诊断对象(diagnostics)包括下面几个属性:
range: 诊断有问题的范围,也就是画波浪线的地方
severity: 严重性,分别有四个等级,不同等级标记的颜色不同,分别是:
message: 诊断的提示信息
source: 来源,比如说来源是 Eslint
data:携带数据,可以将修复好的数据放在这里,用于后面的快速修复功能
比如实现一个提示函数过长的诊断:
function isLongFunction(node: Record<string, any>) { return ( // 如果结束位置的行 - 开始位置的行 > 80 的话,我们认为这个函数写得太长了 node.type === 'ClassMethod' && node.loc.end.line - node.loc.start.line > 80 ); }
在遍历 AST 时如果遇到某个节点是出现函数过长的时候,就往诊断数据中添加此诊断
traverse(scriptAst, { enter(path: any) { const { node } = path; if (isLongFunction(node)) { const diagnostic: Diagnostic ={ severity: DiagnosticSeverity.Warning, range: getPositionRange(node, scriptStart), message: '尽可能保持一个函数的单一职责原则,单个函数不宜超过 80 行', source: 'Code Review 指南', } diagnostics.push(diagnostic); } ... } });
文档中所有的诊断结果会保存在 diagnostics 数组中,最后通过交互返回给客户端。
上面那个函数过长的诊断没办法快速修复,如果能快速修复的话,可以将修正后的结果放在 diagnostics.data
。换个例子写一个快速修复, 比如 Vue template 属性排序不正确,我们需要把代码自动修复
// attributeOrderValidator 得到判断结果 和 修复后的代码 const {isGoodSort, newText} = attributeOrderValidator(props, currData.loc.source); if (!isGoodSort) { const range = { start: { line: props[0].loc.start.line - 1, character: props[0].loc.start.column - 1, }, end: { line: props[props.length - 1].loc.end.line - 1, character: props[props.length - 1].loc.end.column - 1, }, } let diagnostic: Diagnostic = genDiagnostics( 'vue template 上的属性顺序', range ); if (newText) { // 如果有修复后的代码 // 将快速修复数据保存在 diagnostic.data diagnostic.data = { title: '按照 Code Review 指南的顺序修复', newText, } } diagnostics.push(diagnostic); }
quickfix(textDocument, params)
export function quickfix( textDocument: TextDocument, params: CodeActionParams ): CodeAction[] { const diagnostics = params.context.diagnostics; if (isNullOrUndefined(diagnostics) || diagnostics.length === 0) { return []; } const codeActions: CodeAction[] = []; diagnostics.forEach((diag) => { if (diag.severity === DiagnosticSeverity.Warning) { if (diag.data) { // 如果有快速修复数据 // 添加快速修复 codeActions.push({ title: (diag.data as any)?.title, kind: CodeActionKind.QuickFix, // 快速修复 diagnostics: [diag], // 属于哪个诊断的操作 edit: { changes: { [params.textDocument.uri]: [ { range: diag.range, newText: (diag.data as any)?.newText, // 修复后的内容 }, ], }, }, }); } } });
有快速修复的诊断会保存在 codeActions
中,并且返回给客户端, 重新回看交互的代码,在 documents.onDidChangeContent
事件中,通过 connection.sendDiagnostics({ uri: textDocument.uri, diagnostics })
把诊断发送给客户端。quickfix
结果通过 connection.onCodeAction
发给客户端。
import { createConnection TextDocuments, ProposedFeatures, ... } from 'vscode-languageserver/node'; const connection = createConnection(ProposedFeatures.all); const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument); documents.onDidChangeContent(change => { ... // 拿到诊断结果 const diagnostics = getDiagnostics(textDocument, settings); // 发给客户端 connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); }); // 提供快速修复的操作 connection.onCodeAction(provideCodeActions); async function provideCodeActions(params: CodeActionParams): Promise<CodeAction[]> { ... return quickfix(textDocument, params); }
实现一个代码诊断的插件功能,需要两个步骤,首先建立语言服务器,并且建立客户端与语言服务器的交互。接着需要 服务器根据客户端的代码进行校验,把诊断结果放入 Diagnostics
,快速修复结果放在 CodeActions
,通过与客户端的通信,把两个结果返回给客户端,客户端即可出现黄色波浪线的问题提示。
更多关于VSCode的相关知识,请访问:vscode教程!!
Atas ialah kandungan terperinci Amalan pembangunan pemalam VSCode: melaksanakan pemalam diagnosis kod. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!