Skip to content

Commit 89d16fa

Browse files
committed
LuaLS plugin for Garry's Mod Lua API specific features
1 parent 404b595 commit 89d16fa

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed

plugin.lua

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
-- LuaLS plugin for Garry's Mod Lua API specific features
2+
3+
local guide = require "parser.guide"
4+
local helper = require "plugins.astHelper"
5+
6+
7+
local scriptedScopes = {
8+
{ global = "ENT", folder = "entities" },
9+
{ global = "SWEP", folder = "weapons" },
10+
{ global = "EFFECT", folder = "effects" }
11+
}
12+
13+
---@param uri string
14+
---@return string?, string?
15+
local function GetScopedClass(uri)
16+
for _, scope in ipairs(scriptedScopes) do
17+
-- gamemode based uri:
18+
-- .../gamemodes/my_gamemode/entities/entities/ent_class_name/any.lua
19+
-- .../gamemodes/my_gamemode/entities/entities/ent_class_name.lua
20+
local class = uri:match("/entities/" .. scope.folder .. "/([^/]+)/?[^/]*%.lua$")
21+
if not class then
22+
-- addon or root based uri:
23+
-- .../addons/my_addon/lua/entities/ent_class_name/any.lua
24+
-- .../addons/my_addon/lua/entities/ent_class_name.lua
25+
class = uri:match("/lua/" .. scope.folder .. "/([^/]+)/?[^/]*%.lua$")
26+
end
27+
if class then
28+
---@cast class string
29+
return scope.global, class
30+
end
31+
end
32+
end
33+
34+
35+
---@class diff
36+
---@field start integer # The number of bytes at the beginning of the replacement
37+
---@field finish integer # The number of bytes at the end of the replacement
38+
---@field text string # What to replace
39+
40+
---@param uri string # The uri of file
41+
---@param text string # The content of file
42+
---@return diff[]?
43+
function OnSetText(uri, text)
44+
---@type diff[]
45+
local diffs = {}
46+
47+
-- Replace preprocessor keyword DEFINE_BASECLASS. The preprocessor is not smart enough, so this is text patching.
48+
for start, finish in text:gmatch("()DEFINE_BASECLASS()") do
49+
diffs[#diffs+1] = {
50+
---@cast start integer
51+
start = start,
52+
finish = finish - 1,
53+
text = "local BaseClass = baseclass.Get",
54+
}
55+
end
56+
57+
-- TODO: Do real line insert if its possible, instead of text patching.
58+
-- Detect "scripted" scope by folder and localize its global table to @class annotation.
59+
local global = GetScopedClass(uri)
60+
if global then
61+
diffs[#diffs+1] = {
62+
start = 1,
63+
finish = 0,
64+
text = "local ENT\n\n"
65+
}
66+
end
67+
68+
if #diffs == 0 then
69+
return nil
70+
end
71+
72+
return diffs
73+
end
74+
75+
76+
local dtTypes = {
77+
String = "string",
78+
Bool = "boolean",
79+
Float = "number",
80+
Int = "integer",
81+
Vector = "Vector",
82+
Angle = "Angle",
83+
Entity = "Entity"
84+
}
85+
86+
---@param ast parser.object
87+
---@param classNode parser.object
88+
---@param source parser.object
89+
---@param group table
90+
---@param isElement boolean
91+
---@return boolean?
92+
local function BindNetworkVar(ast, classNode, source, group, isElement)
93+
local args = guide.getParams(source)
94+
if not args or #args < (isElement and 4 or 3) then
95+
return
96+
end
97+
98+
-- TODO: How to check argSelf "luadoc type" instead of double "node type"?
99+
local argSelf = args[1]
100+
local targetSelf = guide.getSelfNode(argSelf)
101+
if not targetSelf then
102+
return
103+
end
104+
if targetSelf.node ~= classNode then
105+
-- auto generated function self after colon
106+
targetSelf = guide.getSelfNode(targetSelf)
107+
if not targetSelf or targetSelf.node ~= classNode then
108+
return
109+
end
110+
end
111+
112+
local argType = args[2]
113+
local argSlot = args[3]
114+
local argName = args[isElement and 5 or 4]
115+
---@cast argName +?
116+
117+
-- TODO: How to check argName "luadoc type" instead of "node type"?
118+
if isElement then
119+
local argElement = args[4]
120+
if argSlot.type == "string" and argElement.type == "string" then
121+
argName = argElement
122+
end
123+
else
124+
if argSlot.type == "string" and (not argName or argName.type == "table") then
125+
argName = argSlot
126+
end
127+
end
128+
if not (argType.type == "string" and argName and argName.type == "string") then
129+
return
130+
end
131+
132+
local dtType = isElement and "number" or dtTypes[argType[1]]
133+
local name = argName[1] ---@cast name string
134+
if not dtType then
135+
return
136+
end
137+
138+
local ok = helper.addDoc(ast, classNode, "field", ("Set%s fun(self: Entity, value: %s)"):format(name, dtType), group)
139+
if not ok then
140+
return false
141+
end
142+
ok = helper.addDoc(ast, classNode, "field", ("Get%s fun(self: Entity): %s"):format(name, dtType), group)
143+
if not ok then
144+
return false
145+
end
146+
end
147+
148+
---@param uri string # The uri of file
149+
---@param ast parser.object # The file ast
150+
---@return parser.object? ast
151+
function OnTransformAst(uri, ast)
152+
-- Detect "scripted" scope by folder and localize its global table to @class annotation.
153+
local global, class = GetScopedClass(uri)
154+
if not global then
155+
return
156+
end
157+
158+
local classNode = ast[1]
159+
if not (classNode and classNode.type == "local" or guide.getKeyName(classNode) == global) then
160+
return
161+
end
162+
163+
local group = {}
164+
local ok = helper.addClassDoc(ast, classNode, class .. ": " .. global, group)
165+
if not ok then
166+
return
167+
end
168+
169+
ok = guide.eachSourceType(ast, "call", function (source)
170+
local targetMethod = source.node
171+
local targetName = guide.getKeyName(targetMethod)
172+
if targetName ~= "NetworkVar" and targetName ~= "NetworkVarElement" then
173+
return
174+
end
175+
if guide.getKeyName(source.parent.parent) ~= "SetupDataTables" then
176+
return
177+
end
178+
-- TODO: How to check call self "luadoc type" instead of "node type"?
179+
local targetSelf = targetMethod.node and guide.getSelfNode(targetMethod.node)
180+
if not targetSelf or targetSelf.node ~= classNode then
181+
return
182+
end
183+
return BindNetworkVar(ast, classNode, source, group, targetName == "NetworkVarElement")
184+
end)
185+
if ok == false then
186+
return
187+
end
188+
189+
return ast
190+
end

0 commit comments

Comments
 (0)