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/inputs/annotation.yue | 27 ++++++ spec/inputs/annotation_before.yue | 16 ++++ spec/inputs/test/annotation_spec.yue | 160 +++++++++++++++++++++++++++++++++++ spec/inputs/test/format_spec.yue | 4 +- 4 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 spec/inputs/annotation.yue create mode 100644 spec/inputs/annotation_before.yue create mode 100644 spec/inputs/test/annotation_spec.yue (limited to 'spec/inputs') diff --git a/spec/inputs/annotation.yue b/spec/inputs/annotation.yue new file mode 100644 index 0000000..2124ad3 --- /dev/null +++ b/spec/inputs/annotation.yue @@ -0,0 +1,27 @@ +macro ClsDef = (code`ClassDecl) -> + className = code\match "^class%s+(%w+)" + lines = table.concat [item\gsub "%-%-%-", "---@" for item in code\gmatch "(%-%-%-.-)\n"], "\n" + return + type: "text" + before: false + code: | + ---@class #{className} + #{lines} + ---@class #{className}Class + ---@operator call:#{className} + ---@cast #{className} #{className}Class + +$[ClsDef] +class A + ---field x number + ---field y number + new: (@x = 0, @y = 0) => + ---field setAdd fun(self: A, x: number, y: number): number Set fields and add number values. + setAdd: (@x, @y) => @x + @y + +a = A! +res = a::setAdd 1, 2 +print(a.x, a.y, a.y, res) + +return + diff --git a/spec/inputs/annotation_before.yue b/spec/inputs/annotation_before.yue new file mode 100644 index 0000000..9b11aad --- /dev/null +++ b/spec/inputs/annotation_before.yue @@ -0,0 +1,16 @@ +macro Tag = (tag, code`ClassDecl) -> + className = code\match "^class%s+(%w+)" + return + type: "text" + before: tag == "before" + code: "-- #{tag}:#{className}" + +$[Tag("before")] +class B + getTag: => "before" + +$[Tag("after")] +class C + getTag: => "after" + +return B!\getTag!, C!\getTag! diff --git a/spec/inputs/test/annotation_spec.yue b/spec/inputs/test/annotation_spec.yue new file mode 100644 index 0000000..3cd1d5a --- /dev/null +++ b/spec/inputs/test/annotation_spec.yue @@ -0,0 +1,160 @@ +import to_lua from require "yue" + +compile_and_run = (code, config = {}) -> + lua_code, err = to_lua code, config + assert.is_nil err + assert.is_not_nil lua_code + chunk, load_err = load lua_code + assert.is_nil load_err + assert.is_not_nil chunk + chunk! + +describe "annotation", -> + it "should append generated text after annotated class by default", -> + 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 +]] + 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 + assert.is_true result\find("__name = \"A\"") < result\find("%-%- after:A") + + it "should place generated text before the annotated statement when before is true", -> + 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 +]] + 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 + assert.is_true result\find("%-%- before:B") < result\find("local B") + + it "should support annotation invocation arguments", -> + 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 +]] + result, err = to_lua code + assert.is_nil err + assert.is_not_nil result + assert.is_true result\find("%-%- \"entity\":C") != nil + + it "should report an error when annotation is not followed by a statement", -> + code = [[ +macro Invalid = (code) -> "" +$[Invalid] +]] + result, err = to_lua code + assert.is_nil result + assert.is_true err\match("annotation must be followed by a statement") != nil + + it "should wrap annotated function to validate numeric arguments", -> + 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 +]] + ok, value, bad_ok, bad_err = compile_and_run code + assert.is_true ok + assert.same value, 7 + assert.is_false bad_ok + assert.is_true bad_err\match("expected number for a") != nil + + it "should wrap annotated function to validate return value", -> + 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 +]] + ok, err = compile_and_run code + assert.is_false ok + assert.is_true err\match("expected numeric return") != nil + + it "should use annotation arguments to register annotated classes", -> + 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! +]] + exists, result = compile_and_run code + assert.is_true exists + assert.same result, "ok" diff --git a/spec/inputs/test/format_spec.yue b/spec/inputs/test/format_spec.yue index 310b610..a76a5dd 100644 --- a/spec/inputs/test/format_spec.yue +++ b/spec/inputs/test/format_spec.yue @@ -26,6 +26,8 @@ files = [ "spec/inputs/export_default.yue" "spec/inputs/with_scope_shadow.yue" "spec/inputs/assign.yue" + "spec/inputs/annotation.yue" + "spec/inputs/annotation_before.yue" "spec/inputs/literals.yue" "spec/inputs/luarocks_upload.yue" "spec/inputs/comprehension_nested.yue" @@ -64,6 +66,7 @@ files = [ "spec/inputs/test/continue_spec.yue" "spec/inputs/test/varargs_assignment_spec.yue" "spec/inputs/test/advanced_macro_spec.yue" + "spec/inputs/test/annotation_spec.yue" "spec/inputs/test/pipe_spec.yue" "spec/inputs/test/export_spec.yue" "spec/inputs/test/existential_spec.yue" @@ -192,4 +195,3 @@ for file in *files assert.is_not_nil ast rewriteLineCol ast assert.same original_ast, ast - -- cgit v1.2.3-55-g6feb