Home>Article>Development Tools> VSCode plug-in development practice: implementing a code diagnosis plug-in
This article will share with you aVSCodeplug-in development practice, develop a code diagnosis plug-in, analyze the basic principles, and implement it step by step, I hope it will be helpful to everyone!
Recently, we have published a Code Review guide internally. However, the Code Review process takes up a lot of time. People do not review the code too carefully, so we want to use a plug-in to make it easier. Developers can sense writing errors during the development stage, and the effect is as shown below
The following will introduce how to implement such a function from scratch.
The programming language function expansion of Visual Studio Code is implemented by Language Server. This is easy to understand. After all, checking language functions consumes performance and requires starting another process. As a language service, this is the Language Server language server. [Recommended learning: "vscode Getting Started Tutorial"]
Language Server is a special Visual Studio Code extension that provides an editing experience for many programming languages. Using a language server, you can implement autocomplete, error checking (diagnostics), jump to definition, and many other language features supported by VS Code.
Since there is a syntax checking function provided by the server, the client needs to connect to the language server and then interact with the server. For example, the user performs language checking when editing code on the client. The specific interaction is as follows:
When the Vue file is opened, the plug-in will be activated, and the Language Server will be started. When the document changes, the language server will re-diagnose the code. And send the diagnosis results to the client.
The effect of code diagnosis is that wavy lines appear, and a prompt message is displayed when the mouse is moved up. If there is a quick fix, a quick fix button will appear under the pop-up prompt window
After understanding the basic principles of code diagnosis, we started to implement it. From the above basic principles, we can know that we need to implement two major functions:
Interaction between client and language server
Diagnosis and quick repair function of language server
Official Documentationprovides an example - a simple language server for plain text files, which we can modify based on this example.
> git clone https://github.com/microsoft/vscode-extension-samples.git > cd vscode-extension-samples/lsp-sample > npm install > npm run compile > code .
First create the server in the client
// client/src/extension.ts export function activate(context: ExtensionContext) { ... const clientOptions: LanguageClientOptions = { documentSelector: [{ scheme: 'file', language: 'vue' }], // 打开 vue 文件时才激活 ... }; client = new LanguageClient(...); client.start(); }
Then in server/src/server.ts, write the client-side interaction logic, such as when the client document changes When, verify the code:
// server/src/server.ts import { createConnection TextDocuments, ProposedFeatures, ... } from 'vscode-languageserver/node'; const connection = createConnection(ProposedFeatures.all); const documents: TextDocuments= new TextDocuments(TextDocument); documents.onDidChangeContent(change => { // 文档发生变化时,校验文档 validateTextDocument(change.document); }); async function validateTextDocument(textDocument: TextDocument): Promise { ... // 拿到诊断结果 const diagnostics = getDiagnostics(textDocument, settings); // 发给客户端 connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); } // 提供快速修复的操作 connection.onCodeAction(provideCodeActions); async function provideCodeActions(params: CodeActionParams): Promise { ... return quickfix(textDocument, params); }
After completing the above interaction between the client and the server, you can notice these two methodsgetDiagnostics(textDocument, settings)
andquickfix( textDocument, params)
. These two methods are to provide diagnostic data and quick repair operations for the document respectively.
Overall process
When processing the Vue code text passed by the client, it needs to be parsed into three parts of the ast format data structure through vue/compiler-dom, which are template, JS, and CSS. Due to the current front-end code TypeScript is used, and the JS part is not parsed into AST, so babel/parser needs to be used to parse the TypeScript code to generate the final JS AST data structure.
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', ], });
The obtained AST syntax tree structure is as follows:
Template AST
JS AST
After getting the syntax tree of the code, we need to check each code node to determine whether it complies with Code Review requirements, so it is necessary to traverse the syntax tree to process each node.
Use depth-first search to traverse the template's AST:
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[]){ // ...对代码节点检查 }
For JS AST traversal, you can use babel/traverse traversal:
traverse(scriptAst, { enter(path: any) { ... } }
根据 ast 语法节点去判断语法是否合规,如果不符合要求,需要在代码处生成诊断,一个基础的诊断对象(diagnostics)包括下面几个属性:
range: 诊断有问题的范围,也就是画波浪线的地方
severity: 严重性,分别有四个等级,不同等级标记的颜色不同,分别是:
message: 诊断的提示信息
source: 来源,比如说来源是 Eslint
data:携带数据,可以将修复好的数据放在这里,用于后面的快速修复功能
比如实现一个提示函数过长的诊断:
function isLongFunction(node: Record) { 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= 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 { ... return quickfix(textDocument, params); }
实现一个代码诊断的插件功能,需要两个步骤,首先建立语言服务器,并且建立客户端与语言服务器的交互。接着需要 服务器根据客户端的代码进行校验,把诊断结果放入Diagnostics
,快速修复结果放在CodeActions
,通过与客户端的通信,把两个结果返回给客户端,客户端即可出现黄色波浪线的问题提示。
更多关于VSCode的相关知识,请访问:vscode教程!!
The above is the detailed content of VSCode plug-in development practice: implementing a code diagnosis plug-in. For more information, please follow other related articles on the PHP Chinese website!