-- Verifies that leading `//` comments in a .proto file propagate into -- the generated _pb.lua module as LuaLS-friendly `---` doc lines and -- as plain `--` lines inside table literals where LuaLS attachment -- doesn't apply. -- -- Strategy mirrors test/doc_test.lua: write a small fixture proto into a -- tempdir, invoke the plugin via protoc, then grep the output. We run -- the plugin in both `full` and `runtime` modes — the doc emission is -- mode-independent, but covering both prevents a regression that wires -- comments into only one path. local t = require('luatest') local fio = require('fio') local REPO_ROOT = fio.abspath(fio.pathjoin( fio.dirname(debug.getinfo(1, 'S').source:sub(2)), '..')) local OPTIONS_DIR = fio.pathjoin(REPO_ROOT, 'options') local PLUGIN = fio.pathjoin(REPO_ROOT, 'protoc-gen-tarantool') local function slurp(path) local f = assert(io.open(path, 'rb')) local s = f:read('*a') f:close() return s end local function spit(path, content) local f = assert(io.open(path, 'wb')) f:write(content) f:close() end local function ensure_plugin() if fio.path.exists(PLUGIN) then return end local cmd = string.format('cd %q && go build -o %s ./cmd/protoc-gen-tarantool', REPO_ROOT, fio.basename(PLUGIN)) assert(os.execute(cmd) == 0 or os.execute(cmd) == true, 'failed to build plugin: ' .. cmd) end -- A purpose-built proto exercising every comment-bearing surface: -- enum, enum value, message, field, oneof field, repeated field, -- map field, service, RPC method (unary + streaming flavors). local FIXTURE_PROTO = [[ syntax = "proto3"; package docs; // Status describes the outcome of an operation. // Multi-line description for hover docs. enum Status { // Sentinel zero value. UNKNOWN = 0; // Operation succeeded normally. OK = 1; // Operation failed; see Result.code for detail. ERROR = 2; } // Result is the outcome envelope returned from every RPC. message Result { // Unique identifier for this result row. int32 id = 1; // Effective status of the operation. Status status = 2; // Either a human-readable text or a numeric code, never both. oneof outcome { // Free-form success text. string text = 3; // Machine-readable error code. int32 code = 4; } // Labels collected during processing. repeated string labels = 5; // Per-key counters captured by the handler. map counters = 6; } message Probe { string id = 1; } // Pinger drives a single liveness probe and an open-ended stream. service Pinger { // Ping issues a single round-trip probe. rpc Ping(Probe) returns (Result); // Watch streams results until the client cancels. rpc Watch(Probe) returns (stream Result); } ]] local function generate(mode, prefix) local tmp = fio.tempdir() local proto_dir = fio.pathjoin(tmp, 'proto') local out_dir = fio.pathjoin(tmp, 'out') assert(fio.mkdir(proto_dir)) assert(fio.mkdir(out_dir)) spit(fio.pathjoin(proto_dir, 'comments.proto'), FIXTURE_PROTO) local cmd = string.format( 'protoc --plugin=%q --tarantool_out=%q --tarantool_opt=%s -I %q -I %q %q', PLUGIN, out_dir, string.format('mode=%s,prefix=%s', mode, prefix), proto_dir, OPTIONS_DIR, fio.pathjoin(proto_dir, 'comments.proto')) local ok = os.execute(cmd) assert(ok == 0 or ok == true, 'plugin failed: ' .. cmd) return slurp(fio.pathjoin(out_dir, prefix, 'docs', 'comments_pb.lua')) end ensure_plugin() local LUA_FULL = generate('full', 'full') local LUA_RUNTIME = generate('runtime', 'runtime') -- Each modes-parameterized group runs the same assertions against both -- modes, matching the pattern used elsewhere in the suite. local function make_group(name, lua) local g = t.group('codegen_doc_comments.' .. name) g.test_message_leading_comment = function() -- The message description should appear as a `---` block -- immediately above its `---@class` declaration so LuaLS picks -- it up as the type's hover docstring. t.assert_str_contains(lua, '--- Result is the outcome envelope returned from every RPC.\n' .. '---@class docs.Result') end g.test_enum_leading_comment_multiline = function() -- Multi-line proto comments should produce one `--- line` per -- source line, preserving the user's wording in order. t.assert_str_contains(lua, '--- Status describes the outcome of an operation.\n' .. '--- Multi-line description for hover docs.\n' .. '---@alias docs.Status') end g.test_field_trailing_description = function() -- Field comments collapse to a single trailing `@ description` -- on the `---@field` line so LuaLS shows them on hover/complete. t.assert_str_contains(lua, '---@field id integer @ Unique identifier for this result row.') t.assert_str_contains(lua, '---@field status docs.Status @ Effective status of the operation.') t.assert_str_contains(lua, '---@field labels string[] @ Labels collected during processing.') t.assert_str_contains(lua, '---@field counters table ' .. '@ Per-key counters captured by the handler.') end g.test_oneof_field_descriptions = function() -- Oneof branches are marked optional and should still carry the -- per-field description. t.assert_str_contains(lua, '---@field text? string @ Free-form success text.') t.assert_str_contains(lua, '---@field code? integer @ Machine-readable error code.') end g.test_enum_value_inline_comment = function() -- Inside the `pb.enum(...)` table, value comments survive as -- regular Lua comments — they're not LSP-attachable but readers -- of the generated module should still see them. t.assert_str_contains(lua, ' -- Sentinel zero value.\n' .. ' UNKNOWN = 0,') t.assert_str_contains(lua, ' -- Operation succeeded normally.\n' .. ' OK = 1,') t.assert_str_contains(lua, ' -- Operation failed; see Result.code for detail.\n' .. ' ERROR = 2,') end g.test_service_leading_comment = function() -- Service-level comment lands as a `---` block above the -- `-- Service:` banner so LuaLS can attach it to the descriptor. t.assert_str_contains(lua, '--- Pinger drives a single liveness probe and an open-ended stream.\n' .. '-- Service: docs.Pinger\n' .. 'M.Pinger_service') end g.test_method_descriptor_comment = function() -- Each method entry inside `methods = { ... }` gets its leading -- comment indented to match the table position. t.assert_str_contains(lua, ' -- Ping issues a single round-trip probe.\n' .. ' Ping = {') t.assert_str_contains(lua, ' -- Watch streams results until the client cancels.\n' .. ' Watch = {') end g.test_method_client_function_comment = function() -- Client functions also carry the per-method comment so users -- reading the client stubs see the proto context inline. t.assert_str_contains(lua, ' -- Ping issues a single round-trip probe.\n' .. ' Ping = function(req, ctx)') t.assert_str_contains(lua, ' -- Watch streams results until the client cancels.\n' .. ' Watch = function(req, ctx)') end g.test_method_server_handler_comment = function() -- Server handlers get the same treatment — the unary handler is -- in `methods`, streaming handlers are in `streams`. t.assert_str_contains(lua, ' -- Ping issues a single round-trip probe.\n' .. ' ["/docs.Pinger/Ping"]') t.assert_str_contains(lua, ' -- Watch streams results until the client cancels.\n' .. ' ["/docs.Pinger/Watch"]') end g.test_no_comment_means_no_doc_line = function() -- The `Probe` message has no leading comment. Make sure we -- haven't started inventing one — its class declaration should -- be preceded only by a blank line, not a stray `---`. t.assert_str_contains(lua, '\n---@class docs.Probe\n') -- And the single field has no description suffix. t.assert_str_contains(lua, '\n---@field id string\n') end end make_group('full', LUA_FULL) make_group('runtime', LUA_RUNTIME)