功用

想对从react-native中引进的Image和ImageBackground组件进行底部波涛线符号,所在行的后边添加文字提示,而且鼠标hover呈现提示弹窗。

vscode插件完成扫描页面Image标签

vscode插件完成扫描页面Image标签

考虑

要完成这个功用,首要要知道怎么判别组件是从react-native中引进的,以及react-native是否运用引进了Image和ImageBackground组件,这是一个双向判别的过程。然后就得确定需求符号的组件称号,比如这种Image as NewImage 别号的方式,就不是原名Image了,而是NewImage。这样能够确保符号的组件不会犯错。然后确定好了终究需求符号的组件称号,就得在页面中找到详细的方位。

我一开始运用正则去匹配<Image../>和<ImageBackground../>的闭合标签。虽然能够正确找到在页面中的方位,可是如果呈现了别号的方式(Image as NewImage ),以及这个Image或者ImageBackground不从react-native中引进的,这样就会导致符号不准确了。

   const warnDecorationType = vscode.window.createTextEditorDecorationType({
     textDecoration: "rgb(182, 138, 47) wavy underline 0.1px",
   });   // 界说波涛线装修样式
   const text = editor.document.getText(); // 当时活动页的文本内容
   const imageTagMatches: vscode.DecorationOptions[] = []; // 保存需求装修的标签
   const imageTagRegex =
     /<(Image|ImageBackground)(s+[^>]*?)?(?:/>|>([sS]*?)</1>)/g;
   let match: RegExpExecArray | null;
   while ((match = imageTagRegex.exec(text))) {
     const startPosition = editor.document.positionAt(match.index);
     const endPosition = editor.document.positionAt(
       match.index + match[0].length
     );
     const decoration = { range: new vscode.Range(startPosition, endPosition) };
     imageTagMatches.push(decoration);
   }
   editor.setDecorations(warnLineDecorationType, imageTagMatches); // 设置装修

最终考虑得用ast语法树解析了,由于这个解析语法树能够查找一切import的组件,以及获取页面一切dom元素,能够拿到元素全部信息,包括称号,方位等。这正是咱们所需求的。所以咱们运用两个东西@babel/parser和@babel/traverse。大家也能够运用AST explorer去看生成的语法树结构。

  • @babel/parser 是一个解析器,能够将 JavaScript 代码转换为 AST(笼统语法树)。它支撑解析最新版的 ECMAScript 标准,并具有一个可扩展的插件系统,答应添加自界说的解析器。
  • @babel/traverse 则是一个遍历器,能够深化遍历 AST,并访问、修正节点。它支撑根据事件的遍历形式,从而能够优化功能。它还具有一个途径(path)目标,能够跟踪到当时遍历节点在 AST 中的方位。

核心代码

  1. 获取页面中真实需求符号的元素。
// getWarnImage.ts
import * as vscode from "vscode";
import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
export default function (document: vscode.TextDocument) {
  const warnImageTags: any = []; // 保存需求符号的标签
  function parseTsxCode(code) {
    return parse(code, {
      sourceType: "module",
      plugins: ["jsx", "typescript", "classProperties", "decorators-legacy"],
    });
  }
  const text = document.getText();
  const ast = parseTsxCode(text);
  const elements = []; // 保存页面一切dom元素
  traverse(ast, {
    JSXOpeningElement(path) { // 获取开标签元素,比如<div>...</div>,只获取前半个div的信息
      elements.push(path.node);
    },
  });
  const componentNames = []; // 保存需求符号的组件称号
  // 遍历AST并查找一切import的组件
  traverse(ast, {
    ImportDeclaration(path) {
      const specifiers = path.node.specifiers;
      const source = path.node.source.value;
      if (source === "react-native") { // 判别是从react-native中引进的组件
        if (!specifiers[0].imported) {
          // 考虑import Native from 'react-native';这种写法
          componentNames.push(
            ...[
              `${specifiers[0].local.name}.Image`,
              `${specifiers[0].local.name}.ImageBackground`,
            ]
          );
          return;
        }
        for (let i = 0, l = specifiers.length; i < l; i++) {
          const specifier = specifiers[i];
          const oldName = specifier.imported.name;
          const newName = specifier.local.name; // 别号后得用local里面获取终究的称号
          if (oldName === "Image" || oldName === "ImageBackground") {
            componentNames.push(newName);
          }
        }
      }
    },
  });
  elements.forEach((item) => {
   // 如果是import Native from 'react-native';这种写法,页面中运用就是 <Native.Image ../>,这个时候获取称号就是别的一种写法。
    let newName = item.name.name
      ? item.name.name
      : item.name.object.name + "." + item.name.property.name;
    if (componentNames.includes(newName)) {
      warnImageTags.push(item);
    }
  });
  return warnImageTags;
}
  1. 给需求符号的元素进行装修。
// scanImage.ts
import * as vscode from "vscode";
import getWarnImage from "./getWarnImage";
let warnLineDecorationType: vscode.TextEditorDecorationType;
let warnTextDecorationType: vscode.TextEditorDecorationType;
export default function (editor: vscode.TextEditor) {
  if (!editor) { // 这儿需求判别下,否则编辑器里面没有打开的文件的时候,插件会报错
    return;
  }
  if (warnTextDecorationType) { // 铲除以前的符号目标,确保每一次页面变化都会从头符号
    warnTextDecorationType.dispose();
  }
  if (warnLineDecorationType) {
    warnLineDecorationType.dispose();
  }
  // 界说装修案牍
  warnTextDecorationType = vscode.window.createTextEditorDecorationType({
    after: {
      contentText: `    ⚠️⚠️引荐运用@kds/image。概况见 https://ksurl.cn/SgHR5tpv`,
      color: "rgb(182, 138, 47)",
    },
  });
  // 界说装修破浪线样式
  warnLineDecorationType = vscode.window.createTextEditorDecorationType({
    textDecoration: "rgb(182, 138, 47) wavy underline",
  });
  const componentNames = getWarnImage(editor.document); // 需求装修的元素列表
  const imageTagMatches: vscode.DecorationOptions[] = [];
  const imageNamesMatches: any = [];
  componentNames.length &&
    componentNames.forEach((item: any) => {
      // 在组件称号所在的这一行后边添加案牍提示
      const curLine = editor.document.lineAt(item.name.loc.start.line - 1);// 组件称号所在行
      const startPosition = editor.document.positionAt(item.name.start);//装修开始方位
      const endPosition = editor.document.positionAt( // 装修完毕方位
        item.name.start + curLine.text.trim().length - 1
      );
      const decoration = {
        range: new vscode.Range(startPosition, endPosition),
        hoverMessage: // 界说hover内容
          "引荐运用@kds/image。概况见 [https://ksurl.cn/SgHR5tpv](https://ksurl.cn/SgHR5tpv)",
      };
      imageTagMatches.push(decoration);
      // 对组件称号添加破浪线装修
      const nameStartPosition = editor.document.positionAt(item.name.start);
      const nameEndPosition = editor.document.positionAt(item.name.end);
      const nameDecoration = {
        range: new vscode.Range(nameStartPosition, nameEndPosition),
      };
      imageNamesMatches.push(nameDecoration);
    });
  editor.setDecorations(warnTextDecorationType, imageTagMatches);
  editor.setDecorations(warnLineDecorationType, imageNamesMatches);
}
  1. 在页面触发的时机。前两个时机基本能够满足需求了。
// extensin.ts
import decorateImageTags from './utilities/scanImage';
export async function activate(context: ExtensionContext): Promise<void> {
 decorateImageTags(vscode.window.activeTextEditor); // 1. 首次进入页面触发
 // 2. 文档(文件)的内容发生更改时触发
  vscode.workspace.onDidChangeTextDocument(
    async (e) => {
      if (e.contentChanges.length > 0) {
        decorateImageTags(vscode.window.activeTextEditor);
      }
    },
    null,
    context.subscriptions
  );
 // 3. VS Code窗口中的活动文本编辑器更改时触
 vscode.window.onDidChangeActiveTextEditor(
     async (e: vscode.TextEditor | undefined) => {
      if (e && supportedFilesSelector.includes(e.document.languageId)) {
        try {
          decorateImageTags(e);
        } catch (e) {
          console.log(e);
        }
      }
    }
 )
}