This repository has been archived by the owner on Nov 10, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
DiffAPI.lua
316 lines (275 loc) · 7.28 KB
/
DiffAPI.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
--[[
# DiffAPI
This function is used to get the differences between to API versions.
## Usage
The DiffAPI function expects two API dumps that have been generated by
ParseAPI or LexAPI.
local DiffAPI = require 'DiffAPI'
local oldDump = LexAPI(oldSource)
local newDump = LexAPI(newSource)
local diff = DiffAPI(oldDump, newDump)
DiffAPI returns a table containing the differences between the two versions.
The format of this table is described here:
https://github.com/Anaminus/roblox-api-dump/wiki/API-diff-format
## More Info
https://github.com/Anaminus/roblox-api-dump
]]
-- Check if two lists of arguments match. If not, return a copy of the newer
-- list.
local function argDiff(a,b)
local equal = true
if #a ~= #b then
equal = false
else
for i = 1,#a do
if a[i].Name ~= b[i].Name or a[i].Type ~= b[i].Type or a[i].Default ~= b[i].Default then
equal = false
break
end
end
end
if not equal then
local copy = {}
for i = 1,#b do
local arg = b[i]
copy[i] = {Type=arg.Type, Name=arg.Name, Default=arg.Default}
end
return copy
else
return nil
end
end
-- Returns an immutable identifer for the given item.
local function itemName(item)
if item.Class then
return item.Class .. '.' .. item.Name
elseif item.type == 'EnumItem' then
return item.Enum .. '.' .. item.Name
else
return item.Name
end
end
-- Return hash table of dump list, so that items are easily comparable.
local function getRef(dump)
local ref = {}
for i = 1,#dump do
local item = dump[i]
ref[item.type .. ' ' .. itemName(item)] = item
end
return ref
end
return function(a,b)
local diffs = {}
-- compare mutable fields of two items for changes, per item type
local compare = {}
function compare.Class(a,b)
if b.Superclass ~= a.Superclass then
diffs[#diffs+1] = {0,'Superclass',a,b.Superclass}
end
end
function compare.Property(a,b)
if b.ValueType ~= a.ValueType then
diffs[#diffs+1] = {0,'ValueType',a,b.ValueType}
end
end
function compare.Function(a,b)
if b.ReturnType ~= a.ReturnType then
diffs[#diffs+1] = {0,'ReturnType',a,b.ReturnType}
end
local d = argDiff(a.Arguments,b.Arguments)
if d then
diffs[#diffs+1] = {0,'Arguments',a,d}
end
end
compare.YieldFunction = compare.Function
compare.Callback = compare.Function
function compare.Event(a,b)
local d = argDiff(a.Arguments,b.Arguments)
if d then
diffs[#diffs+1] = {0,'Arguments',a,d}
end
end
function compare.Enum(a,b)
end
function compare.EnumItem(a,b)
if a.Value ~= b.Value then
diffs[#diffs+1] = {0,'Value',a,b.Value}
end
end
local aref = getRef(a)
local bref = getRef(b)
-- Do initial search through table, looking for added/removed classes and
-- enums. This will be used later to exclude their members/enumitems from
-- the top-level diff list, which would also be added/removed.
local addClass = {}
local delClass = {}
local addEnum = {}
local delEnum = {}
for name,item in pairs(bref) do
if not aref[name] then
if item.type == 'Class' then
local list = {}
addClass[item.Name] = list
-- Add the difference right now. Since the member list is
-- referenced, it will be populated later.
diffs[#diffs+1] = {1,'Class',item,list}
elseif item.type == 'Enum' then
local list = {}
addEnum[item.Name] = list
diffs[#diffs+1] = {1,'Enum',item,list}
end
end
end
for name,item in pairs(aref) do
if not bref[name] then
if item.type == 'Class' then
local list = {}
delClass[item.Name] = list
diffs[#diffs+1] = {-1,'Class',item,list}
elseif item.type == 'Enum' then
local list = {}
delEnum[item.Name] = list
diffs[#diffs+1] = {-1,'Enum',item,list}
end
end
end
local secTag = {
['LocalUserSecurity'] = true;
['RobloxSecurity'] = true;
['RobloxPlaceSecurity'] = true;
['RobloxScriptSecurity'] = true;
['WritePlayerSecurity'] = true;
}
for name,item in pairs(bref) do
local aitem = aref[name]
if aitem then
-- item exists in both `a` and `b`, so compare them for changes
compare[item.type](aitem,item)
-- Security tags are (hopefully) mutually exclusive, so we'll
-- detect them as a change in security level, instead of the
-- removal of one tag, and the addition of another.
local secAdd,secRem
for tag in pairs(item.tags) do
if not aitem.tags[tag] then
if secTag[tag] then
secAdd = tag
else
diffs[#diffs+1] = {1,'Tag',aitem,tag}
end
end
end
for tag in pairs(aitem.tags) do
if not item.tags[tag] then
if secTag[tag] then
secRem = tag
else
diffs[#diffs+1] = {-1,'Tag',aitem,tag}
end
end
end
if secAdd or secRem then
-- secAdd or secRem may be nil, which can be interpreted as no
-- security
diffs[#diffs+1] = {0,'Security',aitem,secRem,secAdd}
end
else
-- Item does not exist in `a`, which means it was added.
if item.Class then
-- If the item is a member, check to see if it was added
-- because its class was added.
local list = addClass[item.Class]
if list then
-- If so, then add it do that class's member list, which
-- will be included with the class's diff struct.
list[#list+1] = item
else
-- If not, then the member is an addition to an existing
-- class.
diffs[#diffs+1] = {1,'Item',item}
end
elseif item.type == 'EnumItem' then
-- Same thing as members, but for enumitems.
local list = addEnum[item.Enum]
if list then
list[#list+1] = item
else
diffs[#diffs+1] = {1,'Item',item}
end
elseif item.type ~= 'Class' and item.type ~= 'Enum' then
-- Classes and Enum were already added to the diff list.
diffs[#diffs+1] = {1,'Item',item}
end
end
end
-- detect removals
for name,item in pairs(aref) do
if not bref[name] then
if item.Class then
local list = delClass[item.Class]
if list then
list[#list+1] = item
else
diffs[#diffs+1] = {-1,'Item',item}
end
elseif item.type == 'EnumItem' then
local list = delEnum[item.Enum]
if list then
list[#list+1] = item
else
diffs[#diffs+1] = {-1,'Item',item}
end
elseif item.type ~= 'Class' and item.type ~= 'Enum' then
diffs[#diffs+1] = {-1,'Item',item}
end
end
end
local typeSort = {
Class = 1;
Property = 2;
Function = 3;
YieldFunction = 4;
Event = 5;
Callback = 6;
Enum = 7;
EnumItem = 8;
}
-- Diffs will probably be sorted in some way by the user, but it's nice to
-- have a consistent order to begin with. Because these are generated from
-- hash tables, they may not be the same every time. Sorts by diff type,
-- then item type, then item name.
table.sort(diffs,function(a,b)
if a[1] == b[1] then
if a[3].type == b[3].type then
return itemName(a[3]) < itemName(b[3])
else
return typeSort[a[3].type] < typeSort[b[3].type]
end
else
return a[1] > b[1]
end
end)
-- Also sort the member and enumitem lists.
local function sort(a,b)
if a.type == b.type then
return a.Name < b.Name
else
return typeSort[a.type] < typeSort[b.type]
end
end
for _,list in pairs(addClass) do
table.sort(list,sort)
end
for _,list in pairs(delClass) do
table.sort(list,sort)
end
local function sort(a,b)
return a.Value < b.Value
end
for _,list in pairs(addEnum) do
table.sort(list,sort)
end
for _,list in pairs(delEnum) do
table.sort(list,sort)
end
return diffs
end