이 글에서는 VSCode실용적인 플러그인 개발, 코드 진단 플러그인 개발, 기본 원리 분석 및 단계별 구현에 대해 공유하겠습니다. 모든 분들께 도움이 되길 바랍니다!
최근 내부적으로 코드 검토 가이드를 게시했습니다. 그러나 코드 검토 프로세스는 시간이 많이 걸리고 사람들이 코드를 너무 신중하게 검토하지 않기 때문에 개발자가 사용할 수 있도록 플러그인을 사용하고 싶습니다. 개발 단계에서 작성 방법을 이해하기 위해 아래와 같은 효과를 보여줍니다
다음으로 이러한 기능을 0부터 구현하는 방법을 소개하겠습니다.
Visual Studio Code의 프로그래밍 언어 기능 확장은 Language Server에 의해 구현됩니다. 이는 결국 언어 기능을 확인하는 데 성능이 소모되고 언어 서비스 역할을 하는 또 다른 프로세스가 필요합니다. . [추천 학습: "vscode 입문 튜토리얼"]
Language Server는 다양한 프로그래밍 언어에 대한 편집 환경을 제공하는 특별한 Visual Studio Code 확장입니다. 언어 서버를 사용하면 자동 완성, 오류 검사(진단), 정의로 이동 및 VS Code에서 지원하는 기타 여러 언어 기능을 구현할 수 있습니다.
서버에서 제공하는 구문 검사 기능이 있으므로 클라이언트는 언어 서버에 연결한 후 서버와 상호 작용해야 합니다. 예를 들어 사용자는 클라이언트에서 코드를 편집할 때 언어 검사를 수행합니다. 구체적인 상호 작용은 다음과 같습니다.
Vue 파일이 열리면 플러그인이 활성화되고 언어 서버가 시작됩니다. 문서가 변경되면 언어 서버가 코드를 다시 진단하고 진단 결과를 고객에게 전송합니다.
코드 진단 효과는 물결선이며, 마우스를 위로 올리면 프롬프트 메시지가 표시됩니다. 빠른 수정이 있는 경우 팝업 프롬프트 창 아래에 빠른 수정 버튼이 나타납니다
코드 진단의 기본 원리 이해 그 후 구현을 시작했습니다. 위의 기본 원리를 통해 두 가지 주요 기능을 구현해야 함을 알 수 있습니다.
클라이언트와 언어 서버 간의 상호 작용
언어 서버의 진단 및 빠른 복구 기능
공식 문서는 일반 텍스트 파일을 위한 간단한 언어 서버의 예를 제공하며 이를 기반으로 수정할 수 있습니다. 이 예.
> git clone https://github.com/microsoft/vscode-extension-samples.git > cd vscode-extension-samples/lsp-sample > npm install > npm run compile > code .
먼저 클라이언트에서 서버를 생성하세요.
// 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)
. 이 두 가지 방법은 각각 문서에 대한 진단 데이터와 빠른 복구 작업을 제공하는 것입니다.
전체 프로세스
클라이언트가 전달한 Vue 코드 텍스트를 처리할 때 vue/컴파일러로 구문 분석해야 합니다. -dom ast 형식 데이터 구조의 세 부분은 템플릿, JS 및 CSS입니다. 현재 프런트 엔드 코드는 TypeScript를 사용하므로 JS 부분은 AST로 구문 분석되지 않으므로 TypeScript를 구문 분석하려면 babel/parser를 사용해야 합니다. 최종 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
JS AST
. 각 코드 노드는 코드 검토 요구 사항을 충족하는지 확인하기 위해 검사되므로 각 노드를 처리하려면 구문 트리를 탐색해야 합니다.
템플릿의 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) { ... } }
根据 ast 语法节点去判断语法是否合规,如果不符合要求,需要在代码处生成诊断,一个基础的诊断对象(diagnostics)包括下面几个属性:
range: 诊断有问题的范围,也就是画波浪线的地方
severity: 严重性,分别有四个等级,不同等级标记的颜色不同,分别是:
message: 诊断的提示信息
source: 来源,比如说来源是 Eslint
data:携带数据,可以将修复好的数据放在这里,用于后面的快速修复功能
比如实现一个提示函数过长的诊断:
function isLongFunction(node: Record<string, any>) { 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<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 중국어 웹사이트의 기타 관련 기사를 참조하세요!