mirror of https://github.com/zmkfirmware/zmk.git
124 lines
3.0 KiB
TypeScript
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;
|
|
}
|