zmk/docs/src/keymap-upgrade/parser.ts

124 lines
3.0 KiB
TypeScript

import Parser from "web-tree-sitter";
const TREE_SITTER_WASM_URL = new URL(
"/node_modules/web-tree-sitter/tree-sitter.wasm",
import.meta.url
);
export let Devicetree: Parser.Language;
export async function initParser() {
await Parser.init({
locateFile: (path: string, prefix: string) => {
// When locating tree-sitter.wasm, use a path that Webpack can map to the correct URL.
if (path == "tree-sitter.wasm") {
return TREE_SITTER_WASM_URL.href;
}
return prefix + path;
},
});
Devicetree = await Parser.Language.load("/tree-sitter-devicetree.wasm");
}
export function createParser() {
if (!Devicetree) {
throw new Error("Parser not loaded. Call initParser() first.");
}
const parser = new Parser();
parser.setLanguage(Devicetree);
return parser;
}
/**
* Returns the node for the named capture.
*/
export function findCapture(name: string, captures: Parser.QueryCapture[]) {
for (const c of captures) {
if (c.name === name) {
return c.node;
}
}
return null;
}
/**
* Returns whether the node for the named capture exists and has the given text.
*/
export function captureHasText(
name: string,
captures: Parser.QueryCapture[],
text: string
) {
const node = findCapture(name, captures);
return node?.text === text;
}
/**
* Get a list of SyntaxNodes representing a devicetree node with the given path.
* (The same node may be listed multiple times within a file.)
*
* @param path Absolute path to the node (must start with "/")
*/
export function findDevicetreeNode(
tree: Parser.Tree,
path: string
): Parser.SyntaxNode[] {
const query = Devicetree.query("(node) @node");
const matches = query.matches(tree.rootNode);
const result: Parser.SyntaxNode[] = [];
for (const { captures } of matches) {
const node = findCapture("node", captures);
if (node && getDevicetreeNodePath(node) === path) {
result.push(node);
}
}
return result;
}
export function getDevicetreeNodePath(node: Parser.SyntaxNode | null) {
const parts = getDevicetreeNodePathParts(node);
if (parts.length === 0) {
return "";
}
if (parts.length === 1) {
return parts[0];
}
const path = parts.join("/");
// The top-level node should be named "/", which is a special case since the
// path should not start with "//".
return parts[0] === "/" ? path.substring(1) : path;
}
export function getDevicetreeNodePathParts(
node: Parser.SyntaxNode | null
): string[] {
// There may be intermediate syntax nodes between devicetree nodes, such as
// #if blocks, so if we aren't currently on a "node" node, traverse up the
// tree until we find one.
const dtnode = getContainingDevicetreeNode(node);
if (!dtnode) {
return [];
}
const name = dtnode.childForFieldName("name")?.text ?? "";
return [...getDevicetreeNodePathParts(dtnode.parent), name];
}
function getContainingDevicetreeNode(node: Parser.SyntaxNode | null) {
while (node && node.type !== "node") {
node = node.parent;
}
return node;
}