VSCode プラグイン開発の実践: コード診断プラグインの実装

青灯夜游
リリース: 2022-02-16 20:01:18
転載
4169 人が閲覧しました

この記事では、VSCode プラグイン開発の実践、コード診断プラグインの開発、基本原則の分析、およびそれを段階的に実装する方法について説明します。みんな!

VSCode プラグイン開発の実践: コード診断プラグインの実装

最近、コード レビュー ガイドを社内で公開しました。しかし、コード レビュー プロセスには多くの時間がかかります。人々はコードをあまり注意深くレビューしません。プラグインを使って作業を楽にしたい 開発者は開発段階で書き込みミスを感知することができ、その効果は以下の通りです

Kapture 2022-02-10 at 15.36.49.gif

以下にその方法を紹介します。そのような機能を最初から実装します。

基本原理

Visual Studio Codeのプログラミング言語の機能拡張は、Language Serverで実装されています。これはわかりやすいですね。結局のところ、言語機能のチェックはパフォーマンスを消費し、別のプロセスを起動する必要があります。言語サービス。これは Language Server 言語サーバーです。 [推奨学習: 「vscode 入門チュートリアル 」]

Language Server は、多くのプログラミング言語の編集エクスペリエンスを提供する特別な Visual Studio Code 拡張機能です。言語サーバーを使用すると、オートコンプリート、エラー チェック (診断)、定義へのジャンプ、および VS Code でサポートされるその他の多くの言語機能を実装できます。

サーバーによって提供される構文チェック機能があるため、クライアントは言語サーバーに接続してからサーバーと対話する必要があります。たとえば、ユーザーは言語サーバー上でコードを編集するときに言語チェックを実行します。クライアント。具体的な操作は次のとおりです:

VSCode プラグイン開発の実践: コード診断プラグインの実装

Vue ファイルを開くと、プラグインが有効になり、言語サーバーが起動します。ドキュメントが変更されると、言語サーバーはコードを再診断し、診断結果をクライアントに送信します。

コード診断の効果は、波線が表示され、マウスを上に移動するとプロンプト メッセージが表示されることです。クイック フィックスがある場合は、ポップの下にクイック フィックス ボタンが表示されます。プロンプト ウィンドウを起動

実践的な実装

#コード診断の基本原則を理解した後、実装を開始しました。上記の基本原則から、次のことが必要であることがわかります。 2 つの主要な機能を実装します。

  • # クライアントと言語サーバー間の対話

  • # 言語サーバーの診断とクイック修復機能
クライアントと言語サーバー間の対話

公式ドキュメント

に例が示されています。これは、プレーン テキスト ファイル用の単純な言語サーバーであり、これに基づいて変更できます。この例。

最初にクライアントでサーバーを作成します

// 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);
}
ログイン後にコピー

クライアントとサーバー間の上記の対話が完了すると、2 つのメソッド

getDiagnostics(textDocument, settings)

および quickfix に気づくことができます。 ( textDocument、params)。これら 2 つの方法は、それぞれ診断データを提供し、ドキュメントの迅速な修復操作を提供します。

コード診断

全体プロセス

VSCode プラグイン開発の実践: コード診断プラグインの実装

1. コードドキュメントを AST 構文に変換します。ツリー

クライアントから渡された Vue コード テキストを処理する場合、vue/compiler-dom を通じて ast 形式のデータ構造の 3 つの部分 (テンプレート、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 構文ツリー構造は次のとおりです。

テンプレート AST

VSCode プラグイン開発の実践: コード診断プラグインの実装JS AST

VSCode プラグイン開発の実践: コード診断プラグインの実装##2. 構文ツリーをたどってコードを確認する

コードの構文ツリーを取得した後、各コード ノードをチェックして、それが準拠しているかどうかを判断する必要があります。コード レビューの要件があるため、各ノードを処理するには構文ツリーをたどる必要があります。

深さ優先検索を使用してテンプレートの 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 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート