From 4c0e22ddec2691a3da28381a746d5c9c6da696f5 Mon Sep 17 00:00:00 2001 From: Shararvev Date: Mon, 27 Jan 2025 07:00:43 +0500 Subject: [PATCH 1/4] LuaLS plugin for Garry's Mod Lua API specific features --- plugin.lua | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 plugin.lua diff --git a/plugin.lua b/plugin.lua new file mode 100644 index 0000000..559c046 --- /dev/null +++ b/plugin.lua @@ -0,0 +1,190 @@ +-- LuaLS plugin for Garry's Mod Lua API specific features + +local guide = require "parser.guide" +local helper = require "plugins.astHelper" + + +local scriptedScopes = { + { global = "ENT", folder = "entities" }, + { global = "SWEP", folder = "weapons" }, + { global = "EFFECT", folder = "effects" } +} + +---@param uri string +---@return string?, string? +local function GetScopedClass(uri) + for _, scope in ipairs(scriptedScopes) do + -- gamemode based uri: + -- .../gamemodes/my_gamemode/entities/entities/ent_class_name/any.lua + -- .../gamemodes/my_gamemode/entities/entities/ent_class_name.lua + local class = uri:match("/entities/" .. scope.folder .. "/([^/]+)/?[^/]*%.lua$") + if not class then + -- addon or root based uri: + -- .../addons/my_addon/lua/entities/ent_class_name/any.lua + -- .../addons/my_addon/lua/entities/ent_class_name.lua + class = uri:match("/lua/" .. scope.folder .. "/([^/]+)/?[^/]*%.lua$") + end + if class then + ---@cast class string + return scope.global, class + end + end +end + + +---@class diff +---@field start integer # The number of bytes at the beginning of the replacement +---@field finish integer # The number of bytes at the end of the replacement +---@field text string # What to replace + +---@param uri string # The uri of file +---@param text string # The content of file +---@return diff[]? +function OnSetText(uri, text) + ---@type diff[] + local diffs = {} + + -- Replace preprocessor keyword DEFINE_BASECLASS. The preprocessor is not smart enough, so this is text patching. + for start, finish in text:gmatch("()DEFINE_BASECLASS()") do + diffs[#diffs+1] = { + ---@cast start integer + start = start, + finish = finish - 1, + text = "local BaseClass = baseclass.Get", + } + end + + -- TODO: Do real line insert if its possible, instead of text patching. + -- Detect "scripted" scope by folder and localize its global table to @class annotation. + local global = GetScopedClass(uri) + if global then + diffs[#diffs+1] = { + start = 1, + finish = 0, + text = "local " .. global .. "\n\n" + } + end + + if #diffs == 0 then + return nil + end + + return diffs +end + + +local dtTypes = { + String = "string", + Bool = "boolean", + Float = "number", + Int = "integer", + Vector = "Vector", + Angle = "Angle", + Entity = "Entity" +} + +---@param ast parser.object +---@param classNode parser.object +---@param source parser.object +---@param group table +---@param isElement boolean +---@return boolean? +local function BindNetworkVar(ast, classNode, source, group, isElement) + local args = guide.getParams(source) + if not args or #args < (isElement and 4 or 3) then + return + end + + -- TODO: How to check argSelf "luadoc type" instead of double "node type"? + local argSelf = args[1] + local targetSelf = guide.getSelfNode(argSelf) + if not targetSelf then + return + end + if targetSelf.node ~= classNode then + -- auto generated function self after colon + targetSelf = guide.getSelfNode(targetSelf) + if not targetSelf or targetSelf.node ~= classNode then + return + end + end + + local argType = args[2] + local argSlot = args[3] + local argName = args[isElement and 5 or 4] + ---@cast argName +? + + -- TODO: How to check argName "luadoc type" instead of "node type"? + if isElement then + local argElement = args[4] + if argSlot.type == "string" and argElement.type == "string" then + argName = argElement + end + else + if argSlot.type == "string" and (not argName or argName.type == "table") then + argName = argSlot + end + end + if not (argType.type == "string" and argName and argName.type == "string") then + return + end + + local dtType = isElement and "number" or dtTypes[argType[1]] + local name = argName[1] ---@cast name string + if not dtType then + return + end + + local ok = helper.addDoc(ast, classNode, "field", ("Set%s fun(self: Entity, value: %s)"):format(name, dtType), group) + if not ok then + return false + end + ok = helper.addDoc(ast, classNode, "field", ("Get%s fun(self: Entity): %s"):format(name, dtType), group) + if not ok then + return false + end +end + +---@param uri string # The uri of file +---@param ast parser.object # The file ast +---@return parser.object? ast +function OnTransformAst(uri, ast) + -- Detect "scripted" scope by folder and localize its global table to @class annotation. + local global, class = GetScopedClass(uri) + if not global then + return + end + + local classNode = ast[1] + if not (classNode and classNode.type == "local" and guide.getKeyName(classNode) == global) then + return + end + + local group = {} + local ok = helper.addClassDoc(ast, classNode, class .. ": " .. global, group) + if not ok then + return + end + + ok = guide.eachSourceType(ast, "call", function (source) + local targetMethod = source.node + local targetName = guide.getKeyName(targetMethod) + if targetName ~= "NetworkVar" and targetName ~= "NetworkVarElement" then + return + end + if guide.getKeyName(source.parent.parent) ~= "SetupDataTables" then + return + end + -- TODO: How to check call self "luadoc type" instead of "node type"? + local targetSelf = targetMethod.node and guide.getSelfNode(targetMethod.node) + if not targetSelf or targetSelf.node ~= classNode then + return + end + return BindNetworkVar(ast, classNode, source, group, targetName == "NetworkVarElement") + end) + if ok == false then + return + end + + return ast +end From f3b9ea031e2a8924912bf250491e5f0ebfe8aded Mon Sep 17 00:00:00 2001 From: luttje <2738114+luttje@users.noreply.github.com> Date: Sat, 1 Feb 2025 11:37:07 +0100 Subject: [PATCH 2/4] copy plugin.lua to library --- src/cli-library-publisher.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cli-library-publisher.ts b/src/cli-library-publisher.ts index 575ea5e..bb2571b 100644 --- a/src/cli-library-publisher.ts +++ b/src/cli-library-publisher.ts @@ -82,6 +82,8 @@ async function main() { fs.writeFileSync(path.join(options.output, 'config.json'), JSON.stringify(config, null, 2)); + fs.copyFileSync(path.join(options.input, '../plugin.lua'), path.join(options.output, 'plugin.lua')); + const files = walk(options.input, (file, isDirectory) => isDirectory || (file.endsWith(`.lua`))); files.forEach((file) => { From 52a712b3b3929648b5f172e8134a7ddea02724c1 Mon Sep 17 00:00:00 2001 From: Shararvev Date: Sun, 2 Feb 2025 09:41:17 +0500 Subject: [PATCH 3/4] Swap text diffs, so the localized variable should be first. --- plugin.lua | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/plugin.lua b/plugin.lua index 559c046..ac267b3 100644 --- a/plugin.lua +++ b/plugin.lua @@ -44,16 +44,7 @@ function OnSetText(uri, text) ---@type diff[] local diffs = {} - -- Replace preprocessor keyword DEFINE_BASECLASS. The preprocessor is not smart enough, so this is text patching. - for start, finish in text:gmatch("()DEFINE_BASECLASS()") do - diffs[#diffs+1] = { - ---@cast start integer - start = start, - finish = finish - 1, - text = "local BaseClass = baseclass.Get", - } - end - + -- This should be the first diff, so that when the start positions match, it doesn't break the entire file. -- TODO: Do real line insert if its possible, instead of text patching. -- Detect "scripted" scope by folder and localize its global table to @class annotation. local global = GetScopedClass(uri) @@ -65,6 +56,16 @@ function OnSetText(uri, text) } end + -- Replace preprocessor keyword DEFINE_BASECLASS. The preprocessor is not smart enough, so this is text patching. + for start, finish in text:gmatch("()DEFINE_BASECLASS()") do + diffs[#diffs+1] = { + ---@cast start integer + start = start, + finish = finish - 1, + text = "local BaseClass = baseclass.Get", + } + end + if #diffs == 0 then return nil end From dd5866a1afbeca103be8b1df3fa50c2163ecb2a5 Mon Sep 17 00:00:00 2001 From: Shararvev Date: Mon, 3 Feb 2025 03:27:32 +0500 Subject: [PATCH 4/4] Added TOOL scope --- plugin.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin.lua b/plugin.lua index ac267b3..12d2fd2 100644 --- a/plugin.lua +++ b/plugin.lua @@ -7,7 +7,8 @@ local helper = require "plugins.astHelper" local scriptedScopes = { { global = "ENT", folder = "entities" }, { global = "SWEP", folder = "weapons" }, - { global = "EFFECT", folder = "effects" } + { global = "EFFECT", folder = "effects" }, + { global = "TOOL", folder = "weapons/gmod_tool/stools" } } ---@param uri string