功用
想对从react-native中引进的Image和ImageBackground组件进行底部波涛线符号,所在行的后边添加文字提示,而且鼠标hover呈现提示弹窗。
考虑
要完成这个功用,首要要知道怎么判别组件是从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 中的方位。
核心代码
- 获取页面中真实需求符号的元素。
// 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;
}
- 给需求符号的元素进行装修。
// 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);
}
- 在页面触发的时机。前两个时机基本能够满足需求了。
// 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);
}
}
}
)
}