@thigasdevelopment/luam 0.19.0 → 0.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/lua/class.lua +48 -10
- package/lua/validate.lua +239 -0
- package/luam.mjs +2062 -448
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -275,12 +275,15 @@ VSCodium and Windsurf. The language server itself is editor-agnostic and speaks
|
|
|
275
275
|
|
|
276
276
|
## Known limitations
|
|
277
277
|
|
|
278
|
-
- **Narrowing
|
|
279
|
-
|
|
278
|
+
- **Narrowing follows a path, not an alias.** `if self.value ~= nil then` refines
|
|
279
|
+
the field inside the block; storing the test in a variable does not carry it.
|
|
280
280
|
- **A class is a type everywhere, a value from its declaration** — `extends` may
|
|
281
281
|
name a parent written further down, a top-level `new` may not.
|
|
282
282
|
- **The MTA catalog can lag a release** — a newer function stays `any`.
|
|
283
|
-
- **
|
|
283
|
+
- **Three metamethods stay blocked** — `__index`, `__newindex` and `__call`.
|
|
284
|
+
Generic classes are supported.
|
|
285
|
+
- **A method the receiver does not declare is not reported** — `counter:missing()`
|
|
286
|
+
keeps returning `any`; annotate the receiver to have the call checked.
|
|
284
287
|
- **The editor re-checks by declaration** — a declaration change re-analyzes every
|
|
285
288
|
file that can see it, an edit inside a function body only its own file.
|
|
286
289
|
- **An export is named, never verified** against the side that calls it.
|
package/lua/class.lua
CHANGED
|
@@ -2,6 +2,22 @@ local classes = {}
|
|
|
2
2
|
|
|
3
3
|
local constructors = {}
|
|
4
4
|
|
|
5
|
+
local ALLOWED_METAMETHODS = {
|
|
6
|
+
['__tostring'] = true,
|
|
7
|
+
['__eq'] = true,
|
|
8
|
+
['__lt'] = true,
|
|
9
|
+
['__le'] = true,
|
|
10
|
+
['__len'] = true,
|
|
11
|
+
['__concat'] = true,
|
|
12
|
+
['__unm'] = true,
|
|
13
|
+
['__add'] = true,
|
|
14
|
+
['__sub'] = true,
|
|
15
|
+
['__mul'] = true,
|
|
16
|
+
['__div'] = true,
|
|
17
|
+
['__mod'] = true,
|
|
18
|
+
['__pow'] = true,
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
local BLOCKED_METAMETHODS = {
|
|
6
22
|
['__call'] = true,
|
|
7
23
|
['__index'] = true,
|
|
@@ -73,7 +89,7 @@ local function create(name, struct, options)
|
|
|
73
89
|
|
|
74
90
|
definition = definition or { __name = name }
|
|
75
91
|
|
|
76
|
-
definition.__pending =
|
|
92
|
+
definition.__pending = false
|
|
77
93
|
definition.__super = options.super
|
|
78
94
|
definition.__metamethods = options.metamethods
|
|
79
95
|
|
|
@@ -140,19 +156,41 @@ function class(name)
|
|
|
140
156
|
})
|
|
141
157
|
end
|
|
142
158
|
|
|
143
|
-
local function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if not definition.__metamethods then
|
|
147
|
-
return meta
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
for key, value in pairs(definition.__metamethods) do
|
|
159
|
+
local function installMetamethods(meta, source)
|
|
160
|
+
for key, value in pairs(source) do
|
|
151
161
|
if BLOCKED_METAMETHODS[key] then
|
|
152
162
|
error('Cannot override metamethod ' .. tostring(key) .. '.')
|
|
153
163
|
end
|
|
154
164
|
|
|
155
|
-
|
|
165
|
+
if ALLOWED_METAMETHODS[key] then
|
|
166
|
+
meta[key] = value
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
local function inheritedMetamethods(meta, definition)
|
|
172
|
+
local chain = {}
|
|
173
|
+
local current = definition.__super
|
|
174
|
+
|
|
175
|
+
while type(current) == 'table' do
|
|
176
|
+
table.insert(chain, current)
|
|
177
|
+
|
|
178
|
+
current = current.__super
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
for index = #chain, 1, -1 do
|
|
182
|
+
installMetamethods(meta, chain[index])
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
local function instanceMetatable(definition)
|
|
187
|
+
local meta = { __index = definition }
|
|
188
|
+
|
|
189
|
+
inheritedMetamethods(meta, definition)
|
|
190
|
+
installMetamethods(meta, definition)
|
|
191
|
+
|
|
192
|
+
if definition.__metamethods then
|
|
193
|
+
installMetamethods(meta, definition.__metamethods)
|
|
156
194
|
end
|
|
157
195
|
|
|
158
196
|
return meta
|
package/lua/validate.lua
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
local MAX_DEPTH = 16
|
|
2
|
+
local MAX_ENTRIES = 4096
|
|
3
|
+
local MAX_STRING = 65536
|
|
4
|
+
|
|
5
|
+
local describe
|
|
6
|
+
|
|
7
|
+
local function joinPath(path, segment)
|
|
8
|
+
if path == '' then
|
|
9
|
+
return segment
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
return path .. '.' .. segment
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
local function describeUnion(descriptor)
|
|
16
|
+
local parts = {}
|
|
17
|
+
|
|
18
|
+
for index = 1, #descriptor.options do
|
|
19
|
+
parts[index] = describe(descriptor.options[index])
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
return table.concat(parts, ' | ')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe = function(descriptor)
|
|
26
|
+
local kind = descriptor.kind
|
|
27
|
+
|
|
28
|
+
if kind == 'literal' then
|
|
29
|
+
if type(descriptor.value) == 'string' then
|
|
30
|
+
return "'" .. descriptor.value .. "'"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
return tostring(descriptor.value)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if kind == 'optional' then
|
|
37
|
+
return describe(descriptor.element) .. '?'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if kind == 'array' then
|
|
41
|
+
return describe(descriptor.element) .. '[]'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if kind == 'map' then
|
|
45
|
+
return 'table<' .. describe(descriptor.key) .. ', ' .. describe(descriptor.value) .. '>'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if kind == 'union' then
|
|
49
|
+
return describeUnion(descriptor)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if kind == 'record' or kind == 'instance' then
|
|
53
|
+
return descriptor.name
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
return kind
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
local function fail(path, descriptor, reason)
|
|
60
|
+
local place = path == '' and 'value' or ('"' .. path .. '"')
|
|
61
|
+
|
|
62
|
+
if reason ~= nil then
|
|
63
|
+
error('luam-validate: ' .. place .. ' ' .. reason, 0)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
error('luam-validate: ' .. place .. ' expected "' .. describe(descriptor) .. '"', 0)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
local function countEntries(value, path, descriptor)
|
|
70
|
+
local total = 0
|
|
71
|
+
|
|
72
|
+
for _ in pairs(value) do
|
|
73
|
+
total = total + 1
|
|
74
|
+
|
|
75
|
+
if total > MAX_ENTRIES then
|
|
76
|
+
fail(path, descriptor, 'has more than ' .. MAX_ENTRIES .. ' entries')
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
return total
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
local function isInstanceOf(value, name)
|
|
84
|
+
local meta = getmetatable(value)
|
|
85
|
+
local definition = meta and meta.__index
|
|
86
|
+
local depth = 0
|
|
87
|
+
|
|
88
|
+
while type(definition) == 'table' and depth <= MAX_DEPTH do
|
|
89
|
+
if definition.__name == name then
|
|
90
|
+
return true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
definition = definition.__super
|
|
94
|
+
depth = depth + 1
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
return false
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
local check
|
|
101
|
+
|
|
102
|
+
local function checkRecord(value, descriptor, path, depth)
|
|
103
|
+
countEntries(value, path, descriptor)
|
|
104
|
+
|
|
105
|
+
for index = 1, #descriptor.members do
|
|
106
|
+
local member = descriptor.members[index]
|
|
107
|
+
|
|
108
|
+
check(value[member.key], member.value, joinPath(path, member.key), depth + 1)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
return value
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
local function checkArray(value, descriptor, path, depth)
|
|
115
|
+
local total = countEntries(value, path, descriptor)
|
|
116
|
+
|
|
117
|
+
for index = 1, total do
|
|
118
|
+
check(value[index], descriptor.element, path .. '[' .. index .. ']', depth + 1)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
return value
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
local function checkMap(value, descriptor, path, depth)
|
|
125
|
+
countEntries(value, path, descriptor)
|
|
126
|
+
|
|
127
|
+
for key, entry in pairs(value) do
|
|
128
|
+
check(key, descriptor.key, joinPath(path, tostring(key)) .. ' (key)', depth + 1)
|
|
129
|
+
check(entry, descriptor.value, joinPath(path, tostring(key)), depth + 1)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
return value
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
local function matches(value, descriptor, depth)
|
|
136
|
+
local ok = pcall(check, value, descriptor, '', depth)
|
|
137
|
+
|
|
138
|
+
return ok
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
check = function(value, descriptor, path, depth)
|
|
142
|
+
if depth > MAX_DEPTH then
|
|
143
|
+
fail(path, descriptor, 'is nested deeper than ' .. MAX_DEPTH .. ' levels')
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
local kind = descriptor.kind
|
|
147
|
+
|
|
148
|
+
if kind == 'any' then
|
|
149
|
+
return value
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
if kind == 'optional' then
|
|
153
|
+
if value == nil then
|
|
154
|
+
return value
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
return check(value, descriptor.element, path, depth)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
if kind == 'nil' then
|
|
161
|
+
if value ~= nil then
|
|
162
|
+
fail(path, descriptor)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
return value
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
if kind == 'literal' then
|
|
169
|
+
if value ~= descriptor.value then
|
|
170
|
+
fail(path, descriptor)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
return value
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
if kind == 'string' or kind == 'number' or kind == 'boolean' or kind == 'userdata' or kind == 'thread' or kind == 'function' then
|
|
177
|
+
if type(value) ~= kind then
|
|
178
|
+
fail(path, descriptor)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
if kind == 'string' and #value > MAX_STRING then
|
|
182
|
+
fail(path, descriptor, 'is longer than ' .. MAX_STRING .. ' characters')
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
return value
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
if kind == 'table' then
|
|
189
|
+
if type(value) ~= 'table' then
|
|
190
|
+
fail(path, descriptor)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
return value
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
if kind == 'union' then
|
|
197
|
+
for index = 1, #descriptor.options do
|
|
198
|
+
if matches(value, descriptor.options[index], depth) then
|
|
199
|
+
return value
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
fail(path, descriptor)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
if type(value) ~= 'table' then
|
|
207
|
+
fail(path, descriptor)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
if kind == 'record' then
|
|
211
|
+
return checkRecord(value, descriptor, path, depth)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
if kind == 'array' then
|
|
215
|
+
return checkArray(value, descriptor, path, depth)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
if kind == 'map' then
|
|
219
|
+
return checkMap(value, descriptor, path, depth)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
if kind == 'instance' then
|
|
223
|
+
if not isInstanceOf(value, descriptor.name) then
|
|
224
|
+
fail(path, descriptor)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
return value
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
return value
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
function __luam_validate(value, descriptor)
|
|
234
|
+
return check(value, descriptor, '', 1)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
function __luam_matches(value, descriptor)
|
|
238
|
+
return matches(value, descriptor, 1)
|
|
239
|
+
end
|