搜索
首页开发工具VSCodeVSCode插件开发实战:实现一个代码诊断插件

本篇文章给大家分享一个VSCode插件开发实战,开发一个代码诊断插件,分析一下基本原理,并一步步实现,希望对大家有所帮助!

VSCode插件开发实战:实现一个代码诊断插件

最近,我们内部出了一份 Code Review 指南,但是 Code Review 过程非常占时间,大家不会太仔细去 review 代码,因此想通过一个插件让开发者在开发阶段就能感知到写法的错误,做出的效果如下图

Kapture 2022-02-10 at 15.36.49.gif

接下来将介绍如何从 0 实现这么一个功能。

基本原理

Visual Studio Code 的编程语言功能扩展是有 Language Server 来实现的,这很好理解,毕竟检查语言功能是耗费性能的,需要另起一个进程来作为语言服务,这就是 Language Server 语言服务器。【推荐学习:《vscode入门教程》】

Language Server 是一种特殊的 Visual Studio Code 扩展,可为许多编程语言提供编辑体验。使用语言服务器,您可以实现自动完成、错误检查(诊断)、跳转到定义以及VS Code 支持的许多其他语言功能。

既然有了服务器提供的语法检查功能,就需要客户端去连接语言服务器,然后和服务器进行交互,比如用户在客户端进行代码编辑时,进行语言检查。具体交互如下:

2.gif

当打开 Vue 文件时会激活插件,此时就会启动 Language Server,当文档发生变化时,语言服务器就会重新诊断代码,并把诊断结果发送给客户端。

代码诊断的效果是出现波浪线,鼠标移上显示提示消息,如果有快速修复,会在弹出提示的窗口下出现快速修复的按钮

动手实现

了解了代码诊断的基本原理之后,开始动手实现,从上面的基本原理可知,我们需要实现两大部分的功能:

  • 客户端与语言服务器交互

  • 语言服务器的诊断和快速修复功能

客户端与语言服务器交互

官方文档 提供了一个示例 - 用于纯文本文件的简单语言服务器,我们可以在这个示例的基础上去修改。

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

首先在 client 建立服务器

// 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 中,编写于客户端的交互逻辑,比如在客户端文档发生变化的时候,校验代码:

// 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);
}

在完成上面客户端与服务端交互之后,可以注意到这两个方法 getDiagnostics(textDocument, settings)quickfix(textDocument, params)。 这两个方法分别是为文档提供诊断数据和快速修复的操作。

代码诊断

整体流程

3.gif

1. 将代码文档转成 AST 语法树

在处理客户端传递过来的 Vue 代码文本的,需要通过 vue/compiler-dom 解析成三部分 ast 格式的数据结构,分别是 template、JS、CSS, 由于现在前端代码使用的都是 TypeScript,JS 部分没有解析成 AST,因此需要使用 babel/parser 去解析 TypeScript 代码生成最终的 JS 的 AST 数据结构。

const VueParser = require(&#39;@vue/compiler-dom&#39;);
// 该函数返回诊断结果客户端
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: &#39;module&#39;,
    plugins: [
      &#39;typescript&#39;, // typescript
      [&#39;decorators&#39;, { decoratorsBeforeExport: true }], // 装饰器
      &#39;classProperties&#39;, // ES6 class 写法
      &#39;classPrivateProperties&#39;,
    ],
  });

得到的 AST 语法树结构如下:

Template AST

4.gif

JS AST

5.gif

2. 遍历语法树对代码校验

在得到代码的语法树之后,我们需要对每一个代码节点进行检查,来判断是否符合 Code Review 的要求,因此需要遍历语法树来对每个节点处理。

使用深度优先搜索对 template 的 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[]){
   // ...对代码节点检查
}

而对于 JS AST 遍历,可以使用 babel/traverse 遍历:

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

3. 发现不合规代码,生成诊断

根据 ast 语法节点去判断语法是否合规,如果不符合要求,需要在代码处生成诊断,一个基础的诊断对象(diagnostics)包括下面几个属性:

  • range:  诊断有问题的范围,也就是画波浪线的地方

  • severity: 严重性,分别有四个等级,不同等级标记的颜色不同,分别是:

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

  • source: 来源,比如说来源是 Eslint

  • data:携带数据,可以将修复好的数据放在这里,用于后面的快速修复功能

比如实现一个提示函数过长的诊断:

function isLongFunction(node: Record<string, any>) {
  return (
    // 如果结束位置的行 - 开始位置的行 > 80 的话,我们认为这个函数写得太长了
    node.type === &#39;ClassMethod&#39; && 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: &#39;尽可能保持一个函数的单一职责原则,单个函数不宜超过 80 行&#39;,
                source: &#39;Code Review 指南&#39;,
            }
            diagnostics.push(diagnostic);
        }
        ...   
    }
});

文档中所有的诊断结果会保存在 diagnostics 数组中,最后通过交互返回给客户端。

4. 提供快速修复

上面那个函数过长的诊断没办法快速修复,如果能快速修复的话,可以将修正后的结果放在 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(
        &#39;vue template 上的属性顺序&#39;,
        range
      );
      if (newText) { // 如果有修复后的代码
        // 将快速修复数据保存在 diagnostic.data
        diagnostic.data = {
          title: &#39;按照 Code Review 指南的顺序修复&#39;,
          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 &#39;vscode-languageserver/node&#39;;
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教程!!

以上是VSCode插件开发实战:实现一个代码诊断插件的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:掘金社区。如有侵权,请联系admin@php.cn删除
Visual Studio专业和企业:付费版本和功能Visual Studio专业和企业:付费版本和功能May 10, 2025 am 12:20 AM

VisualStudioProfessional和Enterprise的区别在于功能和目标用户群体。Professional版适合专业开发者,提供代码分析等功能;Enterprise版面向大型团队,增加了测试管理等高级工具。

在Visual Studio和VS代码之间进行选择:适合您的合适工具在Visual Studio和VS代码之间进行选择:适合您的合适工具May 09, 2025 am 12:21 AM

VisualStudio适合大型项目,VSCode适用于各种规模的项目。1.VisualStudio提供全面的IDE功能,支持多种语言,集成调试和测试工具。2.VSCode是轻量级编辑器,通过扩展支持多种语言,界面简洁,启动快。

Visual Studio:开发人员的强大工具Visual Studio:开发人员的强大工具May 08, 2025 am 12:19 AM

VisualStudio是微软开发的强大IDE,支持多种编程语言和平台。它的核心优势包括:1.智能代码提示和调试功能,2.集成开发、调试、测试和版本控制,3.可通过插件扩展功能,4.提供性能优化和最佳实践工具,帮助开发者提高效率和代码质量。

Visual Studio与代码:定价,许可和可用性Visual Studio与代码:定价,许可和可用性May 07, 2025 am 12:11 AM

VisualStudio和VSCode在定价、许可和可用性上的差异如下:1.定价:VSCode完全免费,而VisualStudio提供免费社区版和付费企业版。2.许可:VSCode采用灵活的MIT许可证,VisualStudio的许可根据版本不同而有所不同。3.可用性:VSCode跨平台支持,而VisualStudio在Windows上表现最佳。

视觉工作室:从代码到生产视觉工作室:从代码到生产May 06, 2025 am 12:10 AM

VisualStudio支持从代码编写到生产部署的全流程。1)代码编写:提供智能代码补全和重构功能。2)调试与测试:集成强大调试工具和单元测试框架。3)版本控制:与Git无缝集成,简化代码管理。4)部署与发布:支持多种部署选项,简化应用发布过程。

Visual Studio:查看许可景观Visual Studio:查看许可景观May 05, 2025 am 12:17 AM

VisualStudio提供了三种许可证类型:社区版、专业版和企业版。社区版免费,适合个人开发者和小型团队;专业版按年订阅,适用于需要更多功能的专业开发者;企业版价格最高,适用于大型团队和企业。选择许可证时需考虑项目规模、预算和团队协作需求。

终极摊牌:Visual Studio与代码终极摊牌:Visual Studio与代码May 04, 2025 am 12:01 AM

VisualStudio适合大型项目开发,而VSCode适用于各种规模的项目。 1.VisualStudio提供全面的开发工具,如集成调试器、版本控制和测试工具。 2.VSCode以其扩展性、跨平台和快速启动着称,适合快速编辑和小型项目开发。

Visual Studio与代码:比较两个IDEVisual Studio与代码:比较两个IDEMay 03, 2025 am 12:04 AM

VisualStudio适合大型项目和Windows开发,而VSCode适用于跨平台和小型项目。1.VisualStudio提供全功能的IDE,支持.NET框架和强大调试工具。2.VSCode则是轻量级编辑器,强调灵活性和扩展性,适用于各种开发场景。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具