From acb80dec2706359027c0461073aded3420eaec56 Mon Sep 17 00:00:00 2001 From: Li Jin Date: Thu, 23 Apr 2026 17:04:06 +0800 Subject: Add annotation statements and expand annotation tests --- spec/outputs/test/annotation_spec.lua | 162 ++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 spec/outputs/test/annotation_spec.lua (limited to 'spec/outputs/test/annotation_spec.lua') diff --git a/spec/outputs/test/annotation_spec.lua b/spec/outputs/test/annotation_spec.lua new file mode 100644 index 0000000..866b9dc --- /dev/null +++ b/spec/outputs/test/annotation_spec.lua @@ -0,0 +1,162 @@ +local to_lua +do + local _obj_0 = require("yue") + to_lua = _obj_0.to_lua +end +local compile_and_run +compile_and_run = function(code, config) + if config == nil then + config = { } + end + local lua_code, err = to_lua(code, config) + assert.is_nil(err) + assert.is_not_nil(lua_code) + local chunk, load_err = load(lua_code) + assert.is_nil(load_err) + assert.is_not_nil(chunk) + return chunk() +end +return describe("annotation", function() + it("should append generated text after annotated class by default", function() + local code = [[macro ClsDef = (code`ClassDecl) -> + className = code\match "^class%s+(%w+)" + return + type: "text" + before: false + code: "-- after:" .. className + +$[ClsDef] +class A + getName: => "A" + +return +]] + local result, err = to_lua(code) + assert.is_nil(err) + assert.is_not_nil(result) + assert.is_true(result:find("__name = \"A\"") ~= nil) + assert.is_true(result:find("%-%- after:A") ~= nil) + return assert.is_true(result:find("__name = \"A\"") < result:find("%-%- after:A")) + end) + it("should place generated text before the annotated statement when before is true", function() + local code = [[macro Before = (code`ClassDecl) -> + className = code\match "^class%s+(%w+)" + return + type: "text" + before: true + code: "-- before:" .. className + +$[Before] +class B + getName: => "B" + +return +]] + local result, err = to_lua(code) + assert.is_nil(err) + assert.is_not_nil(result) + assert.is_true(result:find("%-%- before:B") ~= nil) + assert.is_true(result:find("local B") ~= nil) + return assert.is_true(result:find("%-%- before:B") < result:find("local B")) + end) + it("should support annotation invocation arguments", function() + local code = [[macro Tag = (tag, code`ClassDecl) -> + className = code\match "^class%s+(%w+)" + return + type: "text" + before: false + code: "-- " .. tag .. ":" .. className + +$[Tag("entity")] +class C + getName: => "C" + +return +]] + local result, err = to_lua(code) + assert.is_nil(err) + assert.is_not_nil(result) + return assert.is_true(result:find("%-%- \"entity\":C") ~= nil) + end) + it("should report an error when annotation is not followed by a statement", function() + local code = [[macro Invalid = (code) -> "" +$[Invalid] +]] + local result, err = to_lua(code) + assert.is_nil(result) + return assert.is_true(err:match("annotation must be followed by a statement") ~= nil) + end) + it("should wrap annotated function to validate numeric arguments", function() + local code = [[macro ValidateNumberArgs = (code) -> + funcName = code\match "^(%w+)%s*=" + return + type: "text" + before: false + code: table.concat { + "local __orig_#{funcName} = #{funcName}" + "#{funcName} = function(a, b)" + "\tassert(type(a) == \"number\", \"expected number for a\")" + "\tassert(type(b) == \"number\", \"expected number for b\")" + "\treturn __orig_#{funcName}(a, b)" + "end" + }, "\n" + +$[ValidateNumberArgs] +add = (a, b) -> a + b + +ok, value = pcall -> add 3, 4 +bad_ok, bad_err = pcall -> add "3", 4 +return ok, value, bad_ok, bad_err +]] + local ok, value, bad_ok, bad_err = compile_and_run(code) + assert.is_true(ok) + assert.same(value, 7) + assert.is_false(bad_ok) + return assert.is_true(bad_err:match("expected number for a") ~= nil) + end) + it("should wrap annotated function to validate return value", function() + local code = [[macro ValidateNumberReturn = (code) -> + funcName = code\match "^(%w+)%s*=" + return + type: "text" + before: false + code: table.concat { + "local __orig_#{funcName} = #{funcName}" + "#{funcName} = function(...)" + "\tlocal result = __orig_#{funcName}(...)" + "\tassert(type(result) == \"number\", \"expected numeric return\")" + "\treturn result" + "end" + }, "\n" + +$[ValidateNumberReturn] +toText = (value) -> tostring value + +ok, err = pcall -> toText 42 +return ok, err +]] + local ok, err = compile_and_run(code) + assert.is_false(ok) + return assert.is_true(err:match("expected numeric return") ~= nil) + end) + return it("should use annotation arguments to register annotated classes", function() + local code = [[macro Register = (registry, code`ClassDecl) -> + className = code\match "^class%s+(%w+)" + return + type: "text" + before: false + code: "#{registry}[\"#{className}\"] = #{className}" + +registry = {} + +$[Register(registry)] +class Worker + run: => "ok" + +return registry.Worker != nil, registry.Worker!\run! +]] + local exists, result = compile_and_run(code) + assert.is_true(exists) + return assert.same(result, "ok") + end) +end) -- cgit v1.2.3-55-g6feb