Heim >Entwicklungswerkzeuge >VSCode >Entwicklungspraxis für VSCode-Plug-Ins: Implementierung eines Code-Diagnose-Plug-Ins
Dieser Artikel wird Ihnen eine VSCodepraktische Plug-In-Entwicklung vorstellen, ein Code-Diagnose-Plug-In entwickeln, die Grundprinzipien analysieren und es Schritt für Schritt umsetzen. Ich hoffe, es wird für alle hilfreich sein!
Kürzlich haben wir intern einen Code-Review-Leitfaden veröffentlicht. Der Code-Review-Prozess ist jedoch sehr zeitaufwändig und die Leute überprüfen den Code nicht allzu sorgfältig. Deshalb möchten wir ein Plug-in verwenden, um Entwicklern dies zu ermöglichen Um die Schreibmethode während der Entwicklungsphase zu verstehen, ist der Effekt wie folgt:
Als Nächstes stellen wir vor, wie eine solche Funktion ab 0 implementiert wird.
Die Erweiterung der Programmiersprache von Visual Studio Code ist leicht zu verstehen. Schließlich verbraucht die Überprüfung der Sprachfunktionen einen weiteren Prozess als Sprachserver . [Empfohlenes Lernen: „Vscode-Einführungs-Tutorial“]
Language Server ist eine spezielle Visual Studio Code-Erweiterung, die ein Bearbeitungserlebnis für viele Programmiersprachen bietet. Mit einem Sprachserver können Sie Autovervollständigung, Fehlerprüfung (Diagnose), Sprung zur Definition und viele andere von VS Code unterstützte Sprachfunktionen implementieren.
Da der Server über eine Syntaxprüfungsfunktion verfügt, muss der Client eine Verbindung zum Sprachserver herstellen und dann mit dem Server interagieren. Beispielsweise führt der Benutzer eine Sprachprüfung durch, wenn er Code auf dem Client bearbeitet. Die spezifische Interaktion ist wie folgt:
Wenn die Vue-Datei geöffnet wird, wird das Plug-in aktiviert und der Sprachserver gestartet. Wenn sich das Dokument ändert, diagnostiziert der Sprachserver den Code erneut Senden Sie die Diagnoseergebnisse an den Kunden.
Der Effekt der Code-Diagnose ist eine Wellenlinie, und wenn die Maus nach oben bewegt wird, wird eine Schnellkorrektur-Schaltfläche unter dem Popup-Eingabeaufforderungsfenster angezeigt.
Verstehen Sie die Grundprinzipien der Codediagnose. Danach haben wir mit der Implementierung begonnen. Aus den oben genannten Grundprinzipien können wir erkennen, dass wir zwei Hauptfunktionen implementieren müssen:
Interaktion zwischen Client und Sprachserver
Diagnose- und Schnellreparaturfunktion des Sprachservers
Die offizielle Dokumentation liefert ein Beispiel – einen einfachen Sprachserver für reine Textdateien, den wir basierend auf modifizieren können an diesem Beispiel.
> git clone https://github.com/microsoft/vscode-extension-samples.git > cd vscode-extension-samples/lsp-sample > npm install > npm run compile > code .
Erstellen Sie zuerst einen Server im Client
// client/src/extension.ts export function activate(context: ExtensionContext) { ... const clientOptions: LanguageClientOptions = { documentSelector: [{ scheme: 'file', language: 'vue' }], // 打开 vue 文件时才激活 ... }; client = new LanguageClient(...); client.start(); }
Dann schreiben Sie in server/src/server.ts die interaktive Logik auf dem Client, z. B. wenn sich das Clientdokument ändert, überprüfen Sie den Code:
// 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); }
Vervollständigen Sie danach Bei der Interaktion zwischen dem Client und dem Server oben können Sie diese beiden Methoden erkennen getDiagnostics(textDocument, settings)
和 quickfix(textDocument, params)
. Diese beiden Methoden dienen dazu, Diagnosedaten bzw. schnelle Reparaturvorgänge für das Dokument bereitzustellen.
Gesamtprozess
Bei der Verarbeitung des vom Client übergebenen Vue-Codetextes muss dieser in Vue/Compiler analysiert werden -dom Die drei Teile der Datenstruktur im ast-Format sind Vorlage, JS und CSS. Da der Front-End-Code derzeit TypeScript verwendet, wird der JS-Teil nicht in AST analysiert, sodass Babel/Parser zum Parsen von TypeScript verwendet werden muss Code zum Generieren der endgültigen JS-AST-Daten.
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', ], });
Die erhaltene AST-Syntaxbaumstruktur ist wie folgt:
Template AST
JS AST
Nachdem Sie den Syntaxbaum erhalten haben Code, den wir benötigen Jeder Codeknoten wird überprüft, um festzustellen, ob er die Anforderungen der Codeüberprüfung erfüllt. Daher ist es erforderlich, den Syntaxbaum zu durchlaufen, um jeden Knoten zu verarbeiten.
Verwenden Sie die Tiefensuche, um den AST der Vorlage zu durchlaufen:
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[]){ // ...对代码节点检查 }
Und für die JS-AST-Traversierung können Sie babel/traverse traversal:
verwendentraverse(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教程!!
Das obige ist der detaillierte Inhalt vonEntwicklungspraxis für VSCode-Plug-Ins: Implementierung eines Code-Diagnose-Plug-Ins. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!