欧美亚洲中文,在线国自产视频,欧洲一区在线观看视频,亚洲综合中文字幕在线观看

      1. <dfn id="rfwes"></dfn>
          <object id="rfwes"></object>
        1. 站長資訊網(wǎng)
          最全最豐富的資訊網(wǎng)站

          VSCode插件開發(fā)實戰(zhàn):實現(xiàn)一個代碼診斷插件

          本篇文章給大家分享一個VSCode插件開發(fā)實戰(zhàn),開發(fā)一個代碼診斷插件,分析一下基本原理,并一步步實現(xiàn),希望對大家有所幫助!

          VSCode插件開發(fā)實戰(zhàn):實現(xiàn)一個代碼診斷插件

          最近,我們內(nèi)部出了一份 Code Review 指南,但是 Code Review 過程非常占時間,大家不會太仔細(xì)去 review 代碼,因此想通過一個插件讓開發(fā)者在開發(fā)階段就能感知到寫法的錯誤,做出的效果如下圖

          VSCode插件開發(fā)實戰(zhàn):實現(xiàn)一個代碼診斷插件

          接下來將介紹如何從 0 實現(xiàn)這么一個功能。

          基本原理

          Visual Studio Code 的編程語言功能擴(kuò)展是有 Language Server 來實現(xiàn)的,這很好理解,畢竟檢查語言功能是耗費性能的,需要另起一個進(jìn)程來作為語言服務(wù),這就是 Language Server 語言服務(wù)器。【推薦學(xué)習(xí):《vscode入門教程》】

          Language Server 是一種特殊的 Visual Studio Code 擴(kuò)展,可為許多編程語言提供編輯體驗。使用語言服務(wù)器,您可以實現(xiàn)自動完成、錯誤檢查(診斷)、跳轉(zhuǎn)到定義以及VS Code 支持的許多其他語言功能。

          既然有了服務(wù)器提供的語法檢查功能,就需要客戶端去連接語言服務(wù)器,然后和服務(wù)器進(jìn)行交互,比如用戶在客戶端進(jìn)行代碼編輯時,進(jìn)行語言檢查。具體交互如下:

          VSCode插件開發(fā)實戰(zhàn):實現(xiàn)一個代碼診斷插件

          當(dāng)打開 Vue 文件時會激活插件,此時就會啟動 Language Server,當(dāng)文檔發(fā)生變化時,語言服務(wù)器就會重新診斷代碼,并把診斷結(jié)果發(fā)送給客戶端。

          代碼診斷的效果是出現(xiàn)波浪線,鼠標(biāo)移上顯示提示消息,如果有快速修復(fù),會在彈出提示的窗口下出現(xiàn)快速修復(fù)的按鈕

          動手實現(xiàn)

          了解了代碼診斷的基本原理之后,開始動手實現(xiàn),從上面的基本原理可知,我們需要實現(xiàn)兩大部分的功能:

          • 客戶端與語言服務(wù)器交互

          • 語言服務(wù)器的診斷和快速修復(fù)功能

          客戶端與語言服務(wù)器交互

          官方文檔 提供了一個示例 – 用于純文本文件的簡單語言服務(wù)器,我們可以在這個示例的基礎(chǔ)上去修改。

          > git clone https://github.com/microsoft/vscode-extension-samples.git > cd vscode-extension-samples/lsp-sample > npm install > npm run compile > code .

          首先在 client 建立服務(wù)器

          // client/src/extension.ts export function activate(context: ExtensionContext) {     ...     const clientOptions: LanguageClientOptions = {         documentSelector: [{ scheme: 'file', language: 'vue' }], // 打開 vue 文件時才激活         ...     };     client = new LanguageClient(...);     client.start(); }

          接著在 server/src/server.ts 中,編寫于客戶端的交互邏輯,比如在客戶端文檔發(fā)生變化的時候,校驗代碼:

          // 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 => {     // 文檔發(fā)生變化時,校驗文檔     validateTextDocument(change.document); });  async function validateTextDocument(textDocument: TextDocument): Promise<void> {     ...     // 拿到診斷結(jié)果     const diagnostics = getDiagnostics(textDocument, settings);     // 發(fā)給客戶端     connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); } // 提供快速修復(fù)的操作 connection.onCodeAction(provideCodeActions);  async function provideCodeActions(params: CodeActionParams): Promise<CodeAction[]> {     ...     return quickfix(textDocument, params); }

          在完成上面客戶端與服務(wù)端交互之后,可以注意到這兩個方法 getDiagnostics(textDocument, settings)quickfix(textDocument, params)。 這兩個方法分別是為文檔提供診斷數(shù)據(jù)和快速修復(fù)的操作。

          代碼診斷

          整體流程

          VSCode插件開發(fā)實戰(zhàn):實現(xiàn)一個代碼診斷插件

          1. 將代碼文檔轉(zhuǎn)成 AST 語法樹

          在處理客戶端傳遞過來的 Vue 代碼文本的,需要通過 vue/compiler-dom 解析成三部分 ast 格式的數(shù)據(jù)結(jié)構(gòu),分別是 template、JS、CSS, 由于現(xiàn)在前端代碼使用的都是 TypeScript,JS 部分沒有解析成 AST,因此需要使用 babel/parser 去解析 TypeScript 代碼生成最終的 JS 的 AST 數(shù)據(jù)結(jié)構(gòu)。

          const VueParser = require('@vue/compiler-dom'); // 該函數(shù)返回診斷結(jié)果客戶端 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 得到診斷結(jié)果 		...analyzeScript(script, textDocument), // 解析 js 得到診斷結(jié)果 	]; } // 分析 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',     ],   });

          得到的 AST 語法樹結(jié)構(gòu)如下:

          Template AST

          VSCode插件開發(fā)實戰(zhàn):實現(xiàn)一個代碼診斷插件

          JS AST

          VSCode插件開發(fā)實戰(zhàn):實現(xiàn)一個代碼診斷插件

          2. 遍歷語法樹對代碼校驗

          在得到代碼的語法樹之后,我們需要對每一個代碼節(jié)點進(jìn)行檢查,來判斷是否符合 Code Review 的要求,因此需要遍歷語法樹來對每個節(jié)點處理。

          使用深度優(yōu)先搜索對 template 的 AST 進(jìn)行遍歷:

          function deepLoopData(   data: AstTemplateInterface[],   handler: Function,   diagnostics: Diagnostic[], ) {   function dfs(data: AstTemplateInterface[]) {     for (let i = 0; i < data.length; i++) {       handler(data[i], diagnostics); // 在這一步對代碼進(jìn)行處理       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[]){    // ...對代碼節(jié)點檢查 }

          而對于 JS AST 遍歷,可以使用 babel/traverse 遍歷:

           traverse(scriptAst, {     enter(path: any) {       ...     }  }

          3. 發(fā)現(xiàn)不合規(guī)代碼,生成診斷

          根據(jù) ast 語法節(jié)點去判斷語法是否合規(guī),如果不符合要求,需要在代碼處生成診斷,一個基礎(chǔ)的診斷對象(diagnostics)包括下面幾個屬性:

          • range: 診斷有問題的范圍,也就是畫波浪線的地方

          • severity: 嚴(yán)重性,分別有四個等級,不同等級標(biāo)記的顏色不同,分別是:

            • Error: 1
            • Warning: 2
            • Information:3
            • Hint:4
          • message: 診斷的提示信息

          • source: 來源,比如說來源是 Eslint

          • data:攜帶數(shù)據(jù),可以將修復(fù)好的數(shù)據(jù)放在這里,用于后面的快速修復(fù)功能

          比如實現(xiàn)一個提示函數(shù)過長的診斷:

          function isLongFunction(node: Record<string, any>) {   return (     // 如果結(jié)束位置的行 - 開始位置的行 > 80 的話,我們認(rèn)為這個函數(shù)寫得太長了     node.type === 'ClassMethod' && node.loc.end.line - node.loc.start.line > 80   ); }

          在遍歷 AST 時如果遇到某個節(jié)點是出現(xiàn)函數(shù)過長的時候,就往診斷數(shù)據(jù)中添加此診斷

          traverse(scriptAst, {     enter(path: any) {         const { node } = path;         if (isLongFunction(node)) {             const diagnostic: Diagnostic ={                 severity: DiagnosticSeverity.Warning,                 range: getPositionRange(node, scriptStart),                 message: '盡可能保持一個函數(shù)的單一職責(zé)原則,單個函數(shù)不宜超過 80 行',                 source: 'Code Review 指南',             }             diagnostics.push(diagnostic);         }         ...        } });

          文檔中所有的診斷結(jié)果會保存在 diagnostics 數(shù)組中,最后通過交互返回給客戶端。

          4. 提供快速修復(fù)

          上面那個函數(shù)過長的診斷沒辦法快速修復(fù),如果能快速修復(fù)的話,可以將修正后的結(jié)果放在 diagnostics.data 。換個例子寫一個快速修復(fù), 比如 Vue template 屬性排序不正確,我們需要把代碼自動修復(fù)

          // attributeOrderValidator 得到判斷結(jié)果 和 修復(fù)后的代碼 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) { // 如果有修復(fù)后的代碼         // 將快速修復(fù)數(shù)據(jù)保存在 diagnostic.data         diagnostic.data = {           title: '按照 Code Review 指南的順序修復(fù)',           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) { // 如果有快速修復(fù)數(shù)據(jù)         // 添加快速修復(fù)         codeActions.push({           title: (diag.data as any)?.title,           kind: CodeActionKind.QuickFix, // 快速修復(fù)           diagnostics: [diag], // 屬于哪個診斷的操作           edit: {             changes: {                 [params.textDocument.uri]: [                   {                     range: diag.range,                     newText: (diag.data as any)?.newText, // 修復(fù)后的內(nèi)容                   },                 ],               },            },         });     }    } });

          有快速修復(fù)的診斷會保存在 codeActions 中,并且返回給客戶端, 重新回看交互的代碼,在 documents.onDidChangeContent 事件中,通過 connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }) 把診斷發(fā)送給客戶端。quickfix 結(jié)果通過 connection.onCodeAction 發(fā)給客戶端。

          import {     createConnection     TextDocuments,     ProposedFeatures,     ... } from 'vscode-languageserver/node'; const connection = createConnection(ProposedFeatures.all); const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument); documents.onDidChangeContent(change => {     ...     // 拿到診斷結(jié)果     const diagnostics = getDiagnostics(textDocument, settings);     // 發(fā)給客戶端     connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); });  // 提供快速修復(fù)的操作 connection.onCodeAction(provideCodeActions);  async function provideCodeActions(params: CodeActionParams): Promise<CodeAction[]> {     ...     return quickfix(textDocument, params); }

          總結(jié)

          實現(xiàn)一個代碼診斷的插件功能,需要兩個步驟,首先建立語言服務(wù)器,并且建立客戶端與語言服務(wù)器的交互。接著需要 服務(wù)器根據(jù)客戶端的代碼進(jìn)行校驗,把診斷結(jié)果放入 Diagnostics,快速修復(fù)結(jié)果放在 CodeActions,通過與客戶端的通信,把兩個結(jié)果返回給客戶端,客戶端即可出現(xiàn)黃色波浪線的問題提示。

          贊(0)
          分享到: 更多 (0)
          網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號