mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 16:45:22 +00:00
feat: vendor permission system source
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { homedir } from "node:os";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getParser, type TSNode } from "#src/access-intent/bash/parser";
|
||||
import { resolvePlainVariableExpansion } from "#src/access-intent/bash/shell-variable-expansion";
|
||||
import { makeTSNode } from "#test/helpers/fake-ts-node";
|
||||
|
||||
/** `$NAME` as tree-sitter-bash builds it: a `$` delimiter plus the name. */
|
||||
function simpleExpansion(name: string): TSNode {
|
||||
return makeTSNode("simple_expansion", `$${name}`, [
|
||||
makeTSNode("$", "$"),
|
||||
makeTSNode("variable_name", name),
|
||||
]);
|
||||
}
|
||||
|
||||
/** `${NAME}` as tree-sitter-bash builds it: brace delimiters plus the name. */
|
||||
function bracedExpansion(name: string): TSNode {
|
||||
return makeTSNode("expansion", `\${${name}}`, [
|
||||
makeTSNode("${", "${"),
|
||||
makeTSNode("variable_name", name),
|
||||
makeTSNode("}", "}"),
|
||||
]);
|
||||
}
|
||||
|
||||
function findNodeOfType(node: TSNode, type: string): TSNode | null {
|
||||
if (node.type === type) return node;
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
const found = child ? findNodeOfType(child, type) : null;
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
describe("resolvePlainVariableExpansion", () => {
|
||||
describe("resolvable variables", () => {
|
||||
it("resolves $HOME to the OS home directory", () => {
|
||||
expect(resolvePlainVariableExpansion(simpleExpansion("HOME"))).toBe(
|
||||
homedir(),
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves ${HOME} to the OS home directory", () => {
|
||||
expect(resolvePlainVariableExpansion(bracedExpansion("HOME"))).toBe(
|
||||
homedir(),
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves $PWD to the base-relative marker", () => {
|
||||
// The shell's working directory is the projection's effective base, so
|
||||
// the base-relative form resolves correctly after any `cd` folding
|
||||
// without threading a base into this pure function.
|
||||
expect(resolvePlainVariableExpansion(simpleExpansion("PWD"))).toBe(".");
|
||||
});
|
||||
|
||||
it("resolves ${PWD} to the base-relative marker", () => {
|
||||
expect(resolvePlainVariableExpansion(bracedExpansion("PWD"))).toBe(".");
|
||||
});
|
||||
});
|
||||
|
||||
describe("variables outside the resolvable set", () => {
|
||||
it.each([
|
||||
"HOMEDIR",
|
||||
"CURRENT",
|
||||
"PATH",
|
||||
"PWDX",
|
||||
"TMPDIR",
|
||||
])("leaves $%s unresolved", (name) => {
|
||||
expect(resolvePlainVariableExpansion(simpleExpansion(name))).toBeNull();
|
||||
expect(resolvePlainVariableExpansion(bracedExpansion(name))).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("expansions carrying an operator", () => {
|
||||
it("leaves ${HOME:-/tmp} unresolved", () => {
|
||||
const node = makeTSNode("expansion", "${HOME:-/tmp}", [
|
||||
makeTSNode("${", "${"),
|
||||
makeTSNode("variable_name", "HOME"),
|
||||
makeTSNode(":-", ":-"),
|
||||
makeTSNode("word", "/tmp"),
|
||||
makeTSNode("}", "}"),
|
||||
]);
|
||||
expect(resolvePlainVariableExpansion(node)).toBeNull();
|
||||
});
|
||||
|
||||
it("leaves ${#HOME} unresolved", () => {
|
||||
const node = makeTSNode("expansion", "${#HOME}", [
|
||||
makeTSNode("${", "${"),
|
||||
makeTSNode("#", "#"),
|
||||
makeTSNode("variable_name", "HOME"),
|
||||
makeTSNode("}", "}"),
|
||||
]);
|
||||
expect(resolvePlainVariableExpansion(node)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("nodes that are not a plain variable reference", () => {
|
||||
it("returns null for a node with no children", () => {
|
||||
expect(
|
||||
resolvePlainVariableExpansion(makeTSNode("simple_expansion", "$HOME")),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for a node with no variable_name child", () => {
|
||||
const node = makeTSNode("expansion", "${}", [
|
||||
makeTSNode("${", "${"),
|
||||
makeTSNode("}", "}"),
|
||||
]);
|
||||
expect(resolvePlainVariableExpansion(node)).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for a variable_assignment naming a resolvable variable", () => {
|
||||
// `HOME=/tmp` binds the name; it is not a reference to its value.
|
||||
const node = makeTSNode("variable_assignment", "HOME=/tmp", [
|
||||
makeTSNode("variable_name", "HOME"),
|
||||
makeTSNode("=", "="),
|
||||
makeTSNode("word", "/tmp"),
|
||||
]);
|
||||
expect(resolvePlainVariableExpansion(node)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("fidelity to the shapes tree-sitter-bash actually produces", () => {
|
||||
it.each([
|
||||
["ls $HOME", "simple_expansion", homedir()],
|
||||
["ls ${HOME}", "expansion", homedir()],
|
||||
["ls $PWD", "simple_expansion", "."],
|
||||
["ls ${PWD}", "expansion", "."],
|
||||
["ls ${HOME:-/tmp}", "expansion", null],
|
||||
["ls ${#HOME}", "expansion", null],
|
||||
["ls $HOMEDIR", "simple_expansion", null],
|
||||
])("resolves %s to %s", async (command, nodeType, expected) => {
|
||||
const parser = await getParser();
|
||||
const tree = parser.parse(command);
|
||||
expect(tree).not.toBeNull();
|
||||
if (!tree) return;
|
||||
try {
|
||||
const node = findNodeOfType(tree.rootNode, nodeType);
|
||||
expect(node).not.toBeNull();
|
||||
if (!node) return;
|
||||
expect(resolvePlainVariableExpansion(node)).toBe(expected);
|
||||
} finally {
|
||||
tree.delete();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user