0

0

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

青灯夜游

青灯夜游

发布时间:2022-02-16 20:01:18

|

4598人浏览过

|

来源于掘金社区

转载

本篇文章给大家分享一个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 = 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);
}

在完成上面客户端与服务端交互之后,可以注意到这两个方法 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('@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',
    ],
  });

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

Template AST

MediPro企业网站管理系统
MediPro企业网站管理系统

一款基于PHP+MYSQL开发的企业网站管理软件,具有灵活的栏目内容管理功能和丰富的网站模版,可用于创建各种企业网站。v5.1版本支持了PHP5+MYSQL5环境,前台网站插件开放源码,更利于个性化的网站开发。具有以下功能特点和优越性:[>]模版精美实用具有百款适合企业网站的精美模版,并在不断增加中[>]多语言支持独立语言包,支持GBK,UTF8编码方式,可用于创建各种语言的网站[&g

下载

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) {
  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 数组中,最后通过交互返回给客户端。

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(
        '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教程!!

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
scripterror怎么解决
scripterror怎么解决

scripterror的解决办法有检查语法、文件路径、检查网络连接、浏览器兼容性、使用try-catch语句、使用开发者工具进行调试、更新浏览器和JavaScript库或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

228

2023.10.18

500error怎么解决
500error怎么解决

500error的解决办法有检查服务器日志、检查代码、检查服务器配置、更新软件版本、重新启动服务、调试代码和寻求帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

297

2023.10.25

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

539

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

21

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

28

2026.01.06

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

515

2023.06.20

js获取当前时间
js获取当前时间

JS全称JavaScript,是一种具有函数优先的轻量级,解释型或即时编译型的编程语言;它是一种属于网络的高级脚本语言,主要用于Web,常用来为网页添加各式各样的动态功能。js怎么获取当前时间呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

244

2023.07.28

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

320

2023.08.03

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

14

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
go语言零基础开发内容管理系统
go语言零基础开发内容管理系统

共34课时 | 2.6万人学习

第二十三期_前端开发
第二十三期_前端开发

共98课时 | 7.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号