230 lines
7.0 KiB
TypeScript
230 lines
7.0 KiB
TypeScript
import { expect, test } from "vitest";
|
|
import { parseBlock, parseExpressionString, parseToAST } from "./parse.ts";
|
|
import type { LuaFunctionStatement, LuaNumberLiteral } from "./ast.ts";
|
|
|
|
test("Test Lua parser", () => {
|
|
// Basic block test
|
|
parseBlock(`
|
|
print("Hello, World!")
|
|
print(10)
|
|
`);
|
|
parseBlock("");
|
|
// Expression tests
|
|
parseBlock(
|
|
`e(1, 1.2, -3.8, -4, #lst, true, false, nil, "string", "", "Hello there \x00", ...)`,
|
|
);
|
|
parseBlock(`e([[hel]lo]], "Grinny face\\u{1F600}")`);
|
|
parseBlock(`e([=[Hello page [[index]] end scene]=], [[yo]])`);
|
|
|
|
parseBlock(`e([==[Hello page [[index]] end scene]==], [==[yo]==])`);
|
|
console.log(
|
|
JSON.stringify(
|
|
parseBlock(`e([==[Hello page [[bla]]
|
|
]==])`),
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
|
|
parseBlock(`e(10 << 10, 10 >> 10, 10 & 10, 10 | 10, 10 ~ 10)`);
|
|
|
|
parseBlock(`e(true and false or true)`);
|
|
parseBlock(`e(a < 3 and b > 4 or b == 5 or c <= 6 and d >= 7 or a /= 8)`);
|
|
parseBlock(`e(a.b.c)`);
|
|
parseBlock(`e((1+2))`);
|
|
|
|
// Use keywordy variables
|
|
parseBlock(`e(order, limit, where)`);
|
|
|
|
// Table expressions
|
|
parseBlock(`e({})`);
|
|
parseBlock(`e({1, 2, 3, })`);
|
|
parseBlock(`e({1 ; 2 ; 3})`);
|
|
parseBlock(`e({a = 1, b = 2, c = 3})`);
|
|
parseBlock(`e({[3] = 1, [10 * 10] = "sup"})`);
|
|
parseBlock(`e(tbl.name)`);
|
|
parseBlock(`e(tbl["name" + 10])`);
|
|
parseBlock(`e(test().bla)`);
|
|
|
|
// Function calls
|
|
parseBlock(`e(func(), func(1, 2, 3), a.b(), a.b.c:hello(), (a.b)(7))`);
|
|
|
|
// Function expression
|
|
parseBlock(`e(function(a, b) test() end)`);
|
|
parseBlock(`e(function(a, b, ...) end)`);
|
|
|
|
// Statements
|
|
parseBlock(`do end`);
|
|
parseBlock(`do print() end`);
|
|
parseBlock(`::hello::
|
|
goto hello`);
|
|
parseBlock(`while true do print() end`);
|
|
parseBlock(`repeat print() until false`);
|
|
parseBlock(
|
|
`if 1 == 2 then print() elseif 1 < 2 then print2() else print3() end`,
|
|
);
|
|
parseBlock(`if true then print() end`);
|
|
parseBlock(`if true then print() else print2() end`);
|
|
parseBlock(`if true then print() elseif false then print2() end`);
|
|
|
|
// For loops
|
|
parseBlock(`for i = 1, 10, 1 do print(i) end`);
|
|
parseBlock(`for i = 1, 10 do print(i) end`);
|
|
parseBlock(`for el in each({1, 2, 3}) do print(i) end`);
|
|
parseBlock(`for i, l in 1, pairs() do print(i) end`);
|
|
|
|
// Function statements
|
|
parseBlock(`function a() end`);
|
|
parseBlock(`function a:b() end`);
|
|
parseBlock(`function a.b.c:d() end`);
|
|
parseBlock(`function a.b.c() end`);
|
|
parseBlock(`function hello(a, b) end`);
|
|
parseBlock(`function hello(a, b, ...) end`);
|
|
parseBlock(`local function hello() end`);
|
|
|
|
// Assignments, local variables etc.
|
|
parseBlock(`a = 1`);
|
|
parseBlock(`a, b = 1, 2`);
|
|
parseBlock(`a.b.c = 1`);
|
|
parseBlock(`a["name"] = 1`);
|
|
parseBlock(`local a, b<const>`);
|
|
parseBlock(`local a = 1`);
|
|
parseBlock(`local a<const> = 4`);
|
|
parseBlock(`local a, b = 1, 2`);
|
|
|
|
// Function calls
|
|
parseBlock(`a(1, 2, 3)`);
|
|
parseBlock(`print "Sup"`);
|
|
parseBlock(`e(1 + print "8")`);
|
|
|
|
// Return statements
|
|
parseBlock(`return`);
|
|
parseBlock(`return 1`);
|
|
parseBlock(`return 1, 2, 3`);
|
|
});
|
|
|
|
test("Test comment handling", () => {
|
|
const code = `
|
|
-- Single line comment
|
|
--[====[ Multi
|
|
line
|
|
comment ]====]
|
|
f([[
|
|
hello
|
|
-- yo
|
|
]])`;
|
|
const block = parseBlock(code);
|
|
expect(block.comments?.map((comment) => comment.text)).toEqual([
|
|
"-- Single line comment",
|
|
"--[====[ Multi\nline\ncomment ]====]",
|
|
]);
|
|
expect(block.comments?.map((comment) => comment.kind)).toEqual([
|
|
"line",
|
|
"long",
|
|
]);
|
|
expect(
|
|
parseToAST(code).children?.some((node) => node.type === "Comment"),
|
|
).toBe(true);
|
|
});
|
|
|
|
test("comments can occur between expression tokens", () => {
|
|
const block = parseBlock("f(1, -- second argument\n2)");
|
|
expect(block.comments?.[0].text).toBe("-- second argument");
|
|
expect(block.statements).toHaveLength(1);
|
|
});
|
|
|
|
test("comment-only chunks parse and unfinished long comments fail", () => {
|
|
const block = parseBlock("-- nothing to execute");
|
|
expect(block.statements).toEqual([]);
|
|
expect(block.comments?.[0].text).toBe("-- nothing to execute");
|
|
expect(() => parseBlock("--[==[ unfinished")).toThrow();
|
|
});
|
|
|
|
test("LuaLS doc comments annotate the following function", () => {
|
|
const block = parseBlock(`--- Adds two values.
|
|
---@param a number First value.
|
|
---@param b? number Optional second value.
|
|
---@return number sum
|
|
---@see API/math
|
|
function add(a, b)
|
|
return a + b
|
|
end`);
|
|
const fn = block.statements[0] as LuaFunctionStatement;
|
|
expect(fn.body.documentation).toEqual({
|
|
description: "Adds two values.",
|
|
parameters: [
|
|
{
|
|
name: "a",
|
|
type: "number",
|
|
description: "First value.",
|
|
optional: false,
|
|
},
|
|
{
|
|
name: "b",
|
|
type: "number",
|
|
description: "Optional second value.",
|
|
optional: true,
|
|
},
|
|
],
|
|
returns: [{ type: "number", description: "sum" }],
|
|
see: "API/math",
|
|
});
|
|
});
|
|
|
|
test("a blank line separates doc comments from declarations", () => {
|
|
const block = parseBlock("--- Not attached.\n\nfunction f() end");
|
|
const fn = block.statements[0] as LuaFunctionStatement;
|
|
expect(fn.body.documentation).toBeUndefined();
|
|
});
|
|
|
|
test("Test query parsing", () => {
|
|
parseBlock(
|
|
`_(query[[from p = index.tag("page") where p.name == "John" limit 10, 3]])`,
|
|
);
|
|
parseBlock(
|
|
`_(query[[from index.tag("page") select {name="hello", age=10}]])`,
|
|
);
|
|
parseBlock(
|
|
`_(query[[from p = index.tag("page") order by p.lastModified desc, p.name]])`,
|
|
);
|
|
parseBlock(`_(query[[from p = index.tag("page") order by p.lastModified]])`);
|
|
// group by single key
|
|
parseBlock(`_(query[[from p = index.tag("page") group by p.category]])`);
|
|
// group by multiple keys
|
|
parseBlock(
|
|
`_(query[[from p = index.tag("page") group by p.category, p.status]])`,
|
|
);
|
|
// group by + having
|
|
parseBlock(
|
|
`_(query[[from p = index.tag("page") group by p.category having #group > 1]])`,
|
|
);
|
|
// group by + having + select
|
|
parseBlock(
|
|
`_(query[[from p = index.tag("page") group by p.category, p.status having #group > 2 select { key = key, count = #group }]])`,
|
|
);
|
|
});
|
|
|
|
test("Test numeric constant parsing", () => {
|
|
// Examples from Lua 5.4 Reference Manual, except hexadecimal constants with fractional part
|
|
expect((parseExpressionString(`3`) as LuaNumberLiteral).value).toEqual(3);
|
|
expect((parseExpressionString(`345`) as LuaNumberLiteral).value).toEqual(345);
|
|
expect((parseExpressionString(`0xff`) as LuaNumberLiteral).value).toEqual(
|
|
0xff,
|
|
);
|
|
expect((parseExpressionString(`0xBEBADA`) as LuaNumberLiteral).value).toEqual(
|
|
0xbebada,
|
|
);
|
|
expect((parseExpressionString(`3.0`) as LuaNumberLiteral).value).toEqual(3.0);
|
|
expect((parseExpressionString(`3.1416`) as LuaNumberLiteral).value) // biome-ignore lint/suspicious/noApproximativeNumericConstant: testing exact value 3.1416, not Math.PI
|
|
.toEqual(3.1416);
|
|
expect(
|
|
(parseExpressionString(`314.16e-2`) as LuaNumberLiteral).value,
|
|
).toEqual(314.16e-2);
|
|
expect(
|
|
(parseExpressionString(`0.31416E1`) as LuaNumberLiteral).value,
|
|
).toEqual(0.31416e1);
|
|
expect((parseExpressionString(`34e1`) as LuaNumberLiteral).value).toEqual(
|
|
34e1,
|
|
);
|
|
});
|