Files
plainleaf/client/space_lua/pretty_print.test.ts
T

250 lines
6.4 KiB
TypeScript

import { expect, test } from "vitest";
import { parseBlock, parseExpressionString } from "./parse.ts";
import { prettyPrintBlock, prettyPrintExpression } from "./pretty_print.ts";
import type { PrintOptions } from "./pretty_print.ts";
function fmtExpr(code: string, opts?: PrintOptions): string {
return prettyPrintExpression(parseExpressionString(code), opts);
}
function fmtBlock(code: string, opts?: PrintOptions): string {
return prettyPrintBlock(parseBlock(code), opts);
}
test("literals", () => {
expect(fmtExpr("nil")).toBe("nil");
expect(fmtExpr("true")).toBe("true");
expect(fmtExpr("false")).toBe("false");
expect(fmtExpr("42")).toBe("42");
expect(fmtExpr("3.5")).toBe("3.5");
expect(fmtExpr('"hi"')).toBe('"hi"');
});
test("string re-quoting and escaping", () => {
expect(fmtExpr(`'hi'`)).toBe('"hi"');
expect(fmtExpr(`'say "hi"'`)).toBe('"say \\"hi\\""');
expect(fmtExpr(`"a\\nb"`)).toBe('"a\\nb"');
expect(fmtExpr(`'x'`, { quote: "single" })).toBe("'x'");
});
test("variable and access expressions", () => {
expect(fmtExpr("foo")).toBe("foo");
expect(fmtExpr("a.b.c")).toBe("a.b.c");
expect(fmtExpr("t[1]")).toBe("t[1]");
expect(fmtExpr('t["k"]')).toBe('t["k"]');
// the parser preserves parens (dropping them could change multi-return semantics)
expect(fmtExpr("(a)")).toBe("(a)");
});
test("binary operators with spacing", () => {
expect(fmtExpr("1+2")).toBe("1 + 2");
expect(fmtExpr("a..b")).toBe("a .. b");
expect(fmtExpr("a and b or c")).toBe("a and b or c");
expect(fmtExpr("a==b")).toBe("a == b");
});
test("unary operators", () => {
expect(fmtExpr("not x")).toBe("not x");
expect(fmtExpr("-x")).toBe("-x");
expect(fmtExpr("#t")).toBe("#t");
expect(fmtExpr("~x")).toBe("~x");
});
test("precedence parenthesization preserves meaning", () => {
expect(fmtExpr("(1+2)*3")).toBe("(1 + 2) * 3");
expect(fmtExpr("1+2*3")).toBe("1 + 2 * 3");
expect(fmtExpr("(a or b) and c")).toBe("(a or b) and c");
expect(fmtExpr("a-(b-c)")).toBe("a - (b - c)");
expect(fmtExpr("-x^2")).toBe("-x ^ 2");
expect(fmtExpr("(-x)^2")).toBe("(-x) ^ 2");
});
test("tables: empty and single field inline", () => {
expect(fmtExpr("{}")).toBe("{}");
expect(fmtExpr("{a=1}")).toBe("{a = 1}");
expect(fmtExpr("{42}")).toBe("{42}");
expect(fmtExpr('{["a b"]=1}')).toBe('{["a b"] = 1}');
});
test("tables: 2+ fields break multiline with trailing comma", () => {
expect(fmtExpr("{a=1,b=2}")).toBe(`{
a = 1,
b = 2,
}`);
expect(fmtExpr("{1,2,3}")).toBe(`{
1,
2,
3,
}`);
});
test("tables: trailingComma option off", () => {
expect(fmtExpr("{a=1,b=2}", { trailingComma: false })).toBe(`{
a = 1,
b = 2
}`);
});
test("function definitions", () => {
expect(fmtExpr("function() end")).toBe("function() end");
expect(fmtExpr("function(a,b) return a end")).toBe(`function(a, b)
return a
end`);
});
test("function calls and sugar", () => {
expect(fmtExpr("f(1, 2)")).toBe("f(1, 2)");
expect(fmtExpr("obj:m(1)")).toBe("obj:m(1)");
expect(fmtExpr('f "x"')).toBe('f "x"');
expect(fmtExpr('f("x")')).toBe('f "x"');
expect(fmtExpr("f {a=1,b=2}")).toBe(`f {
a = 1,
b = 2,
}`);
});
test("simple statements", () => {
expect(fmtBlock("local x = 1")).toBe("local x = 1");
expect(fmtBlock("local a, b = 1, 2")).toBe("local a, b = 1, 2");
expect(fmtBlock("local x <const> = 1")).toBe("local x <const> = 1");
expect(fmtBlock("x = 1")).toBe("x = 1");
expect(fmtBlock("a, b = 1, 2")).toBe("a, b = 1, 2");
expect(fmtBlock("print(1)")).toBe("print(1)");
expect(fmtBlock("return 1, 2")).toBe("return 1, 2");
expect(fmtBlock("return")).toBe("return");
expect(fmtBlock("do return end")).toBe(`do
return
end`);
});
test("multi-statement block, one per line", () => {
expect(fmtBlock("local x = 1\nlocal y = 2\nreturn x + y")).toBe(`local x = 1
local y = 2
return x + y`);
});
test("comments are retained", () => {
expect(
fmtBlock(`--- Documents f.
function f()
-- body comment
return 1 -- result
end`),
).toBe(`--- Documents f.
function f()
-- body comment
return 1
-- result
end`);
});
test("goto, label, break", () => {
expect(fmtBlock("::top::")).toBe("::top::");
expect(fmtBlock("goto done\n::done::")).toBe(`goto done
::done::`);
expect(fmtBlock("while true do break end")).toBe(`while true do
break
end`);
});
test("if / elseif / else", () => {
expect(fmtBlock("if a then return 1 end")).toBe(`if a then
return 1
end`);
expect(
fmtBlock("if a then return 1 elseif b then return 2 else return 3 end"),
).toBe(`if a then
return 1
elseif b then
return 2
else
return 3
end`);
});
test("while / repeat", () => {
expect(fmtBlock("while a do f() end")).toBe(`while a do
f()
end`);
expect(fmtBlock("repeat f() until a")).toBe(`repeat
f()
until a`);
});
test("numeric and generic for", () => {
expect(fmtBlock("for i=1,10 do f() end")).toBe(`for i = 1, 10 do
f()
end`);
expect(fmtBlock("for i=1,10,2 do f() end")).toBe(`for i = 1, 10, 2 do
f()
end`);
expect(
fmtBlock("for k,v in pairs(t) do f() end"),
).toBe(`for k, v in pairs(t) do
f()
end`);
});
test("function statements", () => {
expect(fmtBlock("function a.b:m(x) return x end")).toBe(`function a.b:m(x)
return x
end`);
expect(fmtBlock("local function g() end")).toBe("local function g() end");
});
test("blank line between definitions", () => {
expect(
fmtBlock("function a() end\nfunction b() end\nlocal x = 1"),
).toBe(`function a() end
function b() end
local x = 1`);
});
test("query expression", () => {
const code =
'query[[from p = index.pages() where p.tag == "x" order by p.name limit 5]]';
expect(fmtExpr(code)).toBe(`query[[
from p = index.pages()
where p.tag == "x"
order by p.name
limit 5
]]`);
});
test("query order by direction", () => {
const code = "query[[from p = t order by p.n desc]]";
expect(fmtExpr(code)).toBe(`query[[
from p = t
order by p.n desc
]]`);
});
test("idempotency: print(parse(print(parse(x)))) === print(parse(x))", () => {
const samples = [
`local x = 1
return x + 2`,
`config.define("a", {
type = "object",
properties = {
x = true,
y = false,
},
})`,
`function f(a, b)
if a then
return b
end
end`,
`for i = 1, 10 do
print(i)
end`,
"local t = {a = 1, b = {c = 2, d = 3}}",
];
for (const code of samples) {
const once = prettyPrintBlock(parseBlock(code));
const twice = prettyPrintBlock(parseBlock(once));
expect(twice).toBe(once);
}
});