mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
1035 lines
34 KiB
TypeScript
1035 lines
34 KiB
TypeScript
import { shellInternals } from "bun:internal-for-testing";
|
|
import { createTestBuilder, redirect } from "./util";
|
|
const { parse } = shellInternals;
|
|
const TestBuilder = createTestBuilder(import.meta.path);
|
|
|
|
describe("parse shell", () => {
|
|
test("basic", () => {
|
|
const expected = {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [
|
|
{
|
|
simple: {
|
|
Text: "echo",
|
|
},
|
|
},
|
|
{
|
|
simple: {
|
|
Text: "foo",
|
|
},
|
|
},
|
|
],
|
|
redirect: redirect({}),
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = parse`echo foo`;
|
|
expect(JSON.parse(result)).toEqual(expected);
|
|
});
|
|
|
|
test("basic redirect", () => {
|
|
const expected = {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "foo" } }],
|
|
redirect: redirect({ stdout: true }),
|
|
redirect_file: { atom: { simple: { Text: "lmao.txt" } } },
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = JSON.parse(parse`echo foo > lmao.txt`);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
test("compound atom", () => {
|
|
const expected = {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [
|
|
{
|
|
compound: {
|
|
atoms: [{ Text: "FOO " }, { Var: "NICE" }, { Text: "!" }],
|
|
brace_expansion_hint: false,
|
|
glob_hint: false,
|
|
},
|
|
},
|
|
],
|
|
redirect: redirect({}),
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = JSON.parse(parse`"FOO $NICE!"`);
|
|
console.log("Result", JSON.stringify(result));
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
test("pipelines", () => {
|
|
const expected = {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
pipeline: {
|
|
items: [
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }],
|
|
redirect: redirect({ stdout: true }),
|
|
redirect_file: { atom: { simple: { Text: "foo.txt" } } },
|
|
},
|
|
},
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "hi" } }],
|
|
redirect: redirect({}),
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const result = JSON.parse(parse`echo > foo.txt | echo hi`);
|
|
// console.log(result);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
test("binary expressions", () => {
|
|
const expected = {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
binary: {
|
|
op: "Or",
|
|
left: {
|
|
binary: {
|
|
op: "And",
|
|
left: {
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "foo" } }],
|
|
redirect: redirect(),
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
right: {
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "bar" } }],
|
|
redirect: redirect(),
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
right: {
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "lmao" } }],
|
|
redirect: redirect(),
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const result = JSON.parse(parse`echo foo && echo bar || echo lmao`);
|
|
// console.log(result);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
test("precedence", () => {
|
|
const expected = {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
binary: {
|
|
op: "And",
|
|
left: {
|
|
binary: {
|
|
op: "And",
|
|
left: {
|
|
assign: [{ label: "FOO", value: { simple: { Text: "bar" } } }],
|
|
},
|
|
right: {
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "foo" } }],
|
|
redirect: redirect(),
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
right: {
|
|
pipeline: {
|
|
items: [
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "bar" } }],
|
|
redirect: redirect(),
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "lmao" } }],
|
|
redirect: redirect(),
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "cat" } }],
|
|
redirect: redirect({ stdout: true }),
|
|
redirect_file: {
|
|
atom: { simple: { Text: "foo.txt" } },
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = parse`FOO=bar && echo foo && echo bar | echo lmao | cat > foo.txt`;
|
|
// console.log(result);
|
|
expect(JSON.parse(result)).toEqual(expected);
|
|
});
|
|
|
|
test("assigns", () => {
|
|
const expected = {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
cmd: {
|
|
assigns: [
|
|
{ label: "FOO", value: { simple: { Text: "bar" } } },
|
|
{ label: "BAR", value: { simple: { Text: "baz" } } },
|
|
],
|
|
name_and_args: [{ simple: { Text: "export" } }, { simple: { Text: "LMAO=nice" } }],
|
|
redirect: {
|
|
stdin: false,
|
|
stdout: false,
|
|
stderr: false,
|
|
append: false,
|
|
duplicate_out: false,
|
|
__unused: 0,
|
|
},
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = JSON.parse(parse`FOO=bar BAR=baz export LMAO=nice`);
|
|
console.log("Result", JSON.stringify(result));
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
test("redirect js obj", () => {
|
|
const expected = {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
binary: {
|
|
op: "And",
|
|
left: {
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "foo" } }],
|
|
redirect: redirect({ stdout: true }),
|
|
redirect_file: { jsbuf: { idx: 0 } },
|
|
},
|
|
},
|
|
right: {
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "foo" } }],
|
|
redirect: redirect({ stdout: true }),
|
|
redirect_file: { jsbuf: { idx: 1 } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const buffer = new Uint8Array(1 << 20);
|
|
const buffer2 = new Uint8Array(1 << 20);
|
|
const result = JSON.parse(parse`echo foo > ${buffer} && echo foo > ${buffer2}`);
|
|
|
|
// console.log("Result", JSON.stringify(result));
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
test("cmd subst", () => {
|
|
const expected = {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [
|
|
{ simple: { Text: "echo" } },
|
|
{
|
|
simple: {
|
|
cmd_subst: {
|
|
script: {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "1" } }],
|
|
redirect: {
|
|
stdin: false,
|
|
stdout: false,
|
|
stderr: false,
|
|
append: false,
|
|
duplicate_out: false,
|
|
__unused: 0,
|
|
},
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
exprs: [
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "2" } }],
|
|
redirect: {
|
|
stdin: false,
|
|
stdout: false,
|
|
stderr: false,
|
|
append: false,
|
|
duplicate_out: false,
|
|
__unused: 0,
|
|
},
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
quoted: true,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
redirect: {
|
|
stdin: false,
|
|
stdout: false,
|
|
stderr: false,
|
|
append: false,
|
|
duplicate_out: false,
|
|
__unused: 0,
|
|
},
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = JSON.parse(parse`echo "$(echo 1; echo 2)"`);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
test("cmd subst edgecase", () => {
|
|
const expected = {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
binary: {
|
|
op: "And",
|
|
left: {
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [
|
|
{ simple: { Text: "echo" } },
|
|
{
|
|
simple: {
|
|
cmd_subst: {
|
|
script: {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "ls" } }, { simple: { Text: "foo" } }],
|
|
redirect: {
|
|
stdin: false,
|
|
stdout: false,
|
|
stderr: false,
|
|
append: false,
|
|
duplicate_out: false,
|
|
__unused: 0,
|
|
},
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
quoted: false,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
redirect: {
|
|
stdin: false,
|
|
stdout: false,
|
|
stderr: false,
|
|
append: false,
|
|
duplicate_out: false,
|
|
__unused: 0,
|
|
},
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
right: {
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [{ simple: { Text: "echo" } }, { simple: { Text: "nice" } }],
|
|
redirect: {
|
|
stdin: false,
|
|
stdout: false,
|
|
stderr: false,
|
|
append: false,
|
|
duplicate_out: false,
|
|
__unused: 0,
|
|
},
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const result = JSON.parse(parse`echo $(ls foo) && echo nice`);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
describe("if_clause", () => {
|
|
test("basic", () => {
|
|
const expected = {
|
|
"stmts": [
|
|
{
|
|
"exprs": [
|
|
{
|
|
"if": {
|
|
"cond": [
|
|
{
|
|
"exprs": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "echo" } }, { "simple": { "Text": "hi" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
"then": [
|
|
{
|
|
"exprs": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "echo" } }, { "simple": { "Text": "lmao" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
"else_parts": [
|
|
[
|
|
{
|
|
"exprs": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "echo" } }, { "simple": { "Text": "lol" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
let result = JSON.parse(parse`if echo hi; then echo lmao; else echo lol; fi`);
|
|
expect(result).toEqual(expected);
|
|
result = JSON.parse(parse`if echo hi
|
|
then echo lmao
|
|
else echo lol
|
|
fi`);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
test("elif", () => {
|
|
const expected = {
|
|
"stmts": [
|
|
{
|
|
"exprs": [
|
|
{
|
|
"if": {
|
|
"cond": [
|
|
{
|
|
"exprs": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "a" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
"then": [
|
|
{
|
|
"exprs": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "b" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
"else_parts": [
|
|
[
|
|
{
|
|
"exprs": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "c" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
[
|
|
{
|
|
"exprs": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "d" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
[
|
|
{
|
|
"exprs": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "e" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
let result = JSON.parse(parse`if a; then b; elif c; then d; else e; fi`);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
describe("precedence", () => {
|
|
test("in pipeline", () => {
|
|
const expected = {
|
|
"stmts": [
|
|
{
|
|
"exprs": [
|
|
{
|
|
"pipeline": {
|
|
"items": [
|
|
{
|
|
"if": {
|
|
"cond": [
|
|
{
|
|
"exprs": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "echo" } }, { "simple": { "Text": "hi" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
"then": [
|
|
{
|
|
"exprs": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [
|
|
{ "simple": { "Text": "echo" } },
|
|
{ "simple": { "Text": "lmao" } },
|
|
],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
"else_parts": [
|
|
[
|
|
{
|
|
"exprs": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [
|
|
{ "simple": { "Text": "echo" } },
|
|
{ "simple": { "Text": "lol" } },
|
|
],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
],
|
|
},
|
|
},
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "cat" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = JSON.parse(parse`if echo hi; then echo lmao; else echo lol; fi | cat`);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe.todo("async", () => {
|
|
TestBuilder.command`echo foo & && echo hi`
|
|
.error('"&" is not allowed on the left-hand side of "&&"')
|
|
.runAsTest("left side of binary not allowed");
|
|
|
|
TestBuilder.command`echo hi && echo foo & && echo hi`
|
|
.error('"&" is not allowed on the left-hand side of "&&"')
|
|
.runAsTest("left side of binary not allowed 2");
|
|
|
|
test("right side of binary works", () => {
|
|
const expected = {
|
|
"stmts": [
|
|
{
|
|
"exprs": [
|
|
{
|
|
"binary": {
|
|
"op": "And",
|
|
"left": {
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "echo" } }, { "simple": { "Text": "hi" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
"right": {
|
|
"async": {
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "echo" } }, { "simple": { "Text": "foo" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = JSON.parse(parse`echo hi && echo foo &`);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
test("pipeline", () => {
|
|
const expected = {
|
|
"stmts": [
|
|
{
|
|
"exprs": [
|
|
{
|
|
"async": {
|
|
"pipeline": {
|
|
"items": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "echo" } }, { "simple": { "Text": "hi" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
{
|
|
"cmd": {
|
|
"assigns": [],
|
|
"name_and_args": [{ "simple": { "Text": "echo" } }, { "simple": { "Text": "foo" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = JSON.parse(parse`echo hi | echo foo &`);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
});
|
|
|
|
describe("bad syntax", () => {
|
|
test("cmd subst edgecase", () => {
|
|
const expected = {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
cmd: {
|
|
assigns: [],
|
|
name_and_args: [
|
|
{ simple: { Text: "echo" } },
|
|
{
|
|
simple: {
|
|
cmd_subst: {
|
|
script: {
|
|
stmts: [
|
|
{
|
|
exprs: [
|
|
{
|
|
cmd: {
|
|
assigns: [
|
|
{
|
|
label: "FOO",
|
|
value: { simple: { Text: "bar" } },
|
|
},
|
|
],
|
|
name_and_args: [{ simple: { Var: "FOO" } }],
|
|
redirect: {
|
|
stdin: false,
|
|
stdout: false,
|
|
stderr: false,
|
|
append: false,
|
|
duplicate_out: false,
|
|
__unused: 0,
|
|
},
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
quoted: false,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
redirect: {
|
|
stdin: false,
|
|
stdout: false,
|
|
stderr: false,
|
|
append: false,
|
|
duplicate_out: false,
|
|
__unused: 0,
|
|
},
|
|
redirect_file: null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const result = JSON.parse(parse`echo $(FOO=bar $FOO)`);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
test("cmd edgecase", () => {
|
|
const expected = {
|
|
"stmts": [
|
|
{
|
|
"exprs": [
|
|
{
|
|
"assign": [
|
|
{ "label": "FOO", "value": { "simple": { "Text": "bar" } } },
|
|
{ "label": "BAR", "value": { "simple": { "Text": "baz" } } },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"exprs": [
|
|
{
|
|
"cmd": {
|
|
"assigns": [{ "label": "BUN_DEBUG_QUIET_LOGS", "value": { "simple": { "Text": "1" } } }],
|
|
"name_and_args": [{ "simple": { "Text": "echo" } }],
|
|
"redirect": {
|
|
"stdin": false,
|
|
"stdout": false,
|
|
"stderr": false,
|
|
"append": false,
|
|
"duplicate_out": false,
|
|
"__unused": 0,
|
|
},
|
|
"redirect_file": null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
const result = JSON.parse(parse`FOO=bar BAR=baz; BUN_DEBUG_QUIET_LOGS=1 echo`);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("parse shell invalid input", () => {
|
|
test("invalid js obj", async () => {
|
|
const file = new Uint8Array(420);
|
|
await TestBuilder.command`${file} | cat`.error(`expected a command or assignment but got: "JSObjRef"`).run();
|
|
});
|
|
|
|
test("subshell", async () => {
|
|
await TestBuilder.command`echo (echo foo && echo hi)`.error("Unexpected token: `(`").run();
|
|
|
|
await TestBuilder.command`echo foo >`.error("Redirection with no file").run();
|
|
});
|
|
});
|