storm-lua-minify 0.3.0 → 0.9.0

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.
Files changed (43) hide show
  1. package/README.md +119 -41
  2. package/dist/aggregateSpecialization.js +406 -0
  3. package/dist/ast2lua.js +156 -68
  4. package/dist/astWalk.js +162 -0
  5. package/dist/callGraph.js +372 -0
  6. package/dist/cli.js +53 -58
  7. package/dist/cliOptions.js +36 -0
  8. package/dist/cliProgress.js +87 -0
  9. package/dist/config.js +73 -0
  10. package/dist/constantFold.js +798 -0
  11. package/dist/controlFlow.js +266 -0
  12. package/dist/functionRewrites.js +580 -0
  13. package/dist/generatedAst.js +108 -0
  14. package/dist/generatedNode.js +23 -0
  15. package/dist/interproceduralAnalysis.js +842 -0
  16. package/dist/interproceduralConstants.js +120 -0
  17. package/dist/luaString.js +157 -0
  18. package/dist/minifier.js +1178 -44
  19. package/dist/optimizerAnalysis.js +43 -0
  20. package/dist/optimizerDiagnostics.js +65 -0
  21. package/dist/optimizerFacts.js +529 -0
  22. package/dist/optimizerPass.js +96 -0
  23. package/dist/optimizerTransaction.js +56 -0
  24. package/dist/optimizerValueDomain.js +180 -0
  25. package/dist/options.js +233 -0
  26. package/dist/progress.js +2 -0
  27. package/dist/removeUnused.js +145 -0
  28. package/dist/renamer.js +223 -54
  29. package/dist/resolver.js +28 -11
  30. package/dist/runtimeEnvironment.js +105 -0
  31. package/dist/sourceMetadata.js +314 -0
  32. package/dist/statementDataflow.js +259 -0
  33. package/dist/statementScheduler.js +595 -0
  34. package/dist/symbolLiveness.js +92 -0
  35. package/dist/tableEffects.js +356 -0
  36. package/dist/transform.js +10 -371
  37. package/dist/valueFlow.js +409 -0
  38. package/dist/wholeProgramExports.js +646 -0
  39. package/dist/wholeProgramFieldRenames.js +583 -0
  40. package/dist/wholeProgramFields.js +672 -0
  41. package/dist/wholeProgramObjects.js +783 -0
  42. package/package.json +11 -2
  43. package/dist/index.js +0 -27
@@ -0,0 +1,783 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeWholeProgramObjects = analyzeWholeProgramObjects;
4
+ const astWalk_1 = require("./astWalk");
5
+ const callGraph_1 = require("./callGraph");
6
+ const linker_1 = require("./linker");
7
+ /**
8
+ * Build the object/module identity layer once for a linked AST generation.
9
+ *
10
+ * This analysis intentionally publishes resolved methods as ordinary CallSite -> Callable
11
+ * relations. Consumers do not need to know whether a callable came from a lexical alias or
12
+ * from a factory allocation. Unknown Lua table behaviour invalidates the affected identity;
13
+ * it never creates a guessed edge.
14
+ */
15
+ function analyzeWholeProgramObjects(modules, generation) {
16
+ const moduleByName = new Map(modules.map((module) => [module.name, module]));
17
+ const moduleOfNode = new WeakMap();
18
+ const callableModule = new Map();
19
+ const callableOfDeclaration = new WeakMap();
20
+ const callSiteOfExpression = new WeakMap();
21
+ modules.forEach((module) => {
22
+ walkStatements(module.chunk.body, (node) => moduleOfNode.set(node, module));
23
+ module.analysis.callGraph.functions.forEach((callable) => {
24
+ callableModule.set(callable, module);
25
+ callableOfDeclaration.set(callable.declaration, callable);
26
+ });
27
+ module.analysis.callGraph.calls.forEach((call) => callSiteOfExpression.set(call.call, call));
28
+ });
29
+ const mutableObjects = [];
30
+ const objectOfTable = new WeakMap();
31
+ const valuesOfSymbol = new Map();
32
+ const valuesOfExpression = new WeakMap();
33
+ const moduleReturns = new Map();
34
+ const functionsOfSymbol = new Map();
35
+ const moduleFunctionReturns = new Map();
36
+ const methodDefinitions = new WeakSet();
37
+ const factoryTemplates = new Map();
38
+ const factoryCopyAssignments = new WeakSet();
39
+ const constructorTemplates = new Map();
40
+ const sourcesOfObject = new Map();
41
+ let nextObject = 0;
42
+ const objectForTable = (table, module) => {
43
+ const existing = objectOfTable.get(table);
44
+ if (existing)
45
+ return existing;
46
+ const object = {
47
+ id: `${module.name}:table:${String(nextObject++)}`,
48
+ moduleName: module.name,
49
+ kind: "allocation",
50
+ methods: new Map(),
51
+ invalidationReasons: new Set(),
52
+ };
53
+ objectOfTable.set(table, object);
54
+ mutableObjects.push(object);
55
+ return object;
56
+ };
57
+ modules.forEach((module) => {
58
+ visitExpressions(module.chunk.body, (expression) => {
59
+ if (expression.type === "TableConstructorExpression")
60
+ objectForTable(expression, module);
61
+ });
62
+ });
63
+ modules.forEach((module) => {
64
+ const returned = topLevelReturn(module.chunk);
65
+ if (!returned)
66
+ return;
67
+ let callable;
68
+ if (returned.type === "FunctionDeclaration")
69
+ callable = module.analysis.callGraph.functionOf(returned);
70
+ else if (returned.type === "Identifier") {
71
+ const symbol = module.resolved.symbolOf(returned);
72
+ if (symbol)
73
+ callable = module.analysis.callGraph.functionOfSymbol(symbol);
74
+ }
75
+ if (callable)
76
+ moduleFunctionReturns.set(module.name, new Set([callable]));
77
+ });
78
+ const directValues = (expression, module) => {
79
+ const cached = valuesOfExpression.get(expression);
80
+ if (cached)
81
+ return cached;
82
+ let result = new Set();
83
+ if (expression.type === "TableConstructorExpression") {
84
+ result.add(objectForTable(expression, module));
85
+ }
86
+ else if (expression.type === "Identifier") {
87
+ const symbol = module.resolved.symbolOf(expression);
88
+ if (symbol)
89
+ result = new Set(valuesOfSymbol.get(symbol) ?? []);
90
+ }
91
+ else if (expression.type === "CallExpression") {
92
+ const required = staticRequiredModule(expression);
93
+ if (required)
94
+ result = new Set(moduleReturns.get(required) ?? []);
95
+ }
96
+ else if (expression.type === "LogicalExpression") {
97
+ directValues(expression.left, module).forEach((object) => {
98
+ result.add(object);
99
+ });
100
+ directValues(expression.right, module).forEach((object) => {
101
+ result.add(object);
102
+ });
103
+ }
104
+ valuesOfExpression.set(expression, result);
105
+ return result;
106
+ };
107
+ // Stable table and require aliases form the seed identity relation.
108
+ let changed = true;
109
+ while (changed) {
110
+ changed = false;
111
+ modules.forEach((module) => {
112
+ visitBindings(module.chunk.body, (target, value) => {
113
+ const symbol = module.resolved.symbolOf(target);
114
+ if (!symbol || !stableSymbol(symbol, module.analysis))
115
+ return;
116
+ const incoming = directValues(value, module);
117
+ if (unionInto(valuesOfSymbol, symbol, incoming))
118
+ changed = true;
119
+ if (value.type === "CallExpression") {
120
+ const required = staticRequiredModule(value);
121
+ if (required &&
122
+ unionCallableInto(functionsOfSymbol, symbol, moduleFunctionReturns.get(required) ?? new Set()))
123
+ changed = true;
124
+ }
125
+ });
126
+ const returned = topLevelReturn(module.chunk);
127
+ if (returned) {
128
+ const incoming = directValues(returned, module);
129
+ const current = moduleReturns.get(module.name) ?? new Set();
130
+ const before = current.size;
131
+ incoming.forEach((object) => {
132
+ object.kind = "module-return";
133
+ current.add(object);
134
+ });
135
+ moduleReturns.set(module.name, current);
136
+ if (current.size !== before)
137
+ changed = true;
138
+ }
139
+ });
140
+ valuesOfExpressionCleanup(valuesOfExpression, modules);
141
+ }
142
+ // Member function declarations establish prototype fields by object identity.
143
+ modules.forEach((module) => {
144
+ walkStatements(module.chunk.body, (node) => {
145
+ if (node.type !== "FunctionDeclaration")
146
+ return;
147
+ const declaration = node;
148
+ if (declaration.identifier?.type !== "MemberExpression")
149
+ return;
150
+ const callable = callableOfDeclaration.get(declaration);
151
+ if (!callable)
152
+ return;
153
+ const key = declaration.identifier.identifier.name;
154
+ const bases = directValues(declaration.identifier.base, module);
155
+ bases.forEach((object) => {
156
+ object.kind =
157
+ object.kind === "module-return" ? object.kind : "prototype";
158
+ const prior = object.methods.get(key);
159
+ if (prior && prior !== callable)
160
+ object.invalidationReasons.add("method-field-mutation");
161
+ object.methods.set(key, callable);
162
+ });
163
+ if (declaration.identifier.indexer === ":") {
164
+ const self = callable.parameters[0];
165
+ if (self.name === "self")
166
+ bases.forEach((object) => {
167
+ unionInto(valuesOfSymbol, self, new Set([object]));
168
+ });
169
+ }
170
+ methodDefinitions.add(declaration.identifier);
171
+ });
172
+ });
173
+ modules.forEach((module) => {
174
+ module.analysis.callGraph.functions.forEach((callable) => {
175
+ const factory = recognizeMixinFactory(callable, module.resolved);
176
+ if (factory) {
177
+ factoryTemplates.set(callable, factory);
178
+ factory.copyAssignments.forEach((assignment) => factoryCopyAssignments.add(assignment));
179
+ }
180
+ });
181
+ walkStatements(module.chunk.body, (node) => {
182
+ if (node.type !== "ForGenericStatement")
183
+ return;
184
+ recognizeKeyPreservingTransfers(node, module.resolved).forEach((transfer) => {
185
+ factoryCopyAssignments.add(transfer.assignment);
186
+ const targets = directValues(transfer.target, module);
187
+ const sources = directValues(transfer.source, module);
188
+ targets.forEach((target) => {
189
+ const recordedSources = sourcesOfObject.get(target) ?? new Set();
190
+ sources.forEach((source) => {
191
+ recordedSources.add(source);
192
+ source.methods.forEach((callable, key) => target.methods.set(key, callable));
193
+ });
194
+ sourcesOfObject.set(target, recordedSources);
195
+ });
196
+ });
197
+ });
198
+ });
199
+ const directCallTargets = (call, module) => {
200
+ const targets = new Set(call.targets);
201
+ const expression = call.call;
202
+ if (expression.type !== "CallExpression")
203
+ return targets;
204
+ if (expression.base.type === "Identifier") {
205
+ const symbol = module.resolved.symbolOf(expression.base);
206
+ if (symbol)
207
+ functionsOfSymbol.get(symbol)?.forEach((target) => targets.add(target));
208
+ }
209
+ else if (expression.base.type === "MemberExpression") {
210
+ const member = expression.base;
211
+ directValues(member.base, module).forEach((object) => {
212
+ const target = object.methods.get(member.identifier.name);
213
+ if (target)
214
+ targets.add(target);
215
+ });
216
+ }
217
+ return targets;
218
+ };
219
+ modules.forEach((module) => {
220
+ module.analysis.callGraph.functions.forEach((callable) => {
221
+ const constructor = recognizeConstructor(callable, module, factoryTemplates, (call) => {
222
+ const site = callSiteOfExpression.get(call);
223
+ if (!site)
224
+ return new Set();
225
+ const targets = directCallTargets(site, module);
226
+ const member = call.base.type === "MemberExpression" ? call.base : undefined;
227
+ if (member)
228
+ directValues(member.base, module).forEach((object) => {
229
+ const target = object.methods.get(member.identifier.name);
230
+ if (target)
231
+ targets.add(target);
232
+ });
233
+ return targets;
234
+ });
235
+ if (constructor)
236
+ constructorTemplates.set(callable, constructor);
237
+ });
238
+ });
239
+ // Constructor calls allocate a distinct identity at each call site. The copied prototype
240
+ // and a factory-returned base contribute methods to that one identity.
241
+ const instanceOfCall = new WeakMap();
242
+ const constructorOfCall = new WeakMap();
243
+ changed = true;
244
+ while (changed) {
245
+ changed = false;
246
+ modules.forEach((module) => {
247
+ module.analysis.callGraph.calls.forEach((call) => {
248
+ if (call.call.type !== "CallExpression")
249
+ return;
250
+ const targets = directCallTargets(call, module);
251
+ const member = call.call.base.type === "MemberExpression"
252
+ ? call.call.base
253
+ : undefined;
254
+ if (member)
255
+ directValues(member.base, module).forEach((object) => {
256
+ const target = object.methods.get(member.identifier.name);
257
+ if (target)
258
+ targets.add(target);
259
+ });
260
+ const constructor = [...targets]
261
+ .map((target) => constructorTemplates.get(target))
262
+ .filter((value) => value !== undefined);
263
+ const mixin = [...targets]
264
+ .map((target) => factoryTemplates.get(target))
265
+ .filter((value) => value !== undefined);
266
+ if (constructor.length + mixin.length !== 1)
267
+ return;
268
+ let instance = instanceOfCall.get(call.call);
269
+ if (!instance) {
270
+ instance = {
271
+ id: `${module.name}:call:${String(call.id)}`,
272
+ moduleName: module.name,
273
+ kind: "allocation",
274
+ methods: new Map(),
275
+ invalidationReasons: new Set(),
276
+ };
277
+ instanceOfCall.set(call.call, instance);
278
+ mutableObjects.push(instance);
279
+ changed = true;
280
+ }
281
+ const sources = new Set();
282
+ if (constructor.length === 1) {
283
+ constructorOfCall.set(call.call, constructor[0]);
284
+ directValues(constructor[0].prototype, constructor[0].module).forEach((object) => sources.add(object));
285
+ directValues(constructor[0].base, constructor[0].module).forEach((object) => sources.add(object));
286
+ }
287
+ else {
288
+ const args = call.call.arguments;
289
+ const base = args.at(0);
290
+ const prototype = args.at(1);
291
+ if (!base || !prototype)
292
+ return;
293
+ directValues(base, module).forEach((object) => sources.add(object));
294
+ directValues(prototype, module).forEach((object) => sources.add(object));
295
+ }
296
+ sources.forEach((source) => {
297
+ const recordedSources = sourcesOfObject.get(instance) ?? new Set();
298
+ recordedSources.add(source);
299
+ sourcesOfObject.set(instance, recordedSources);
300
+ source.methods.forEach((target, key) => instance.methods.set(key, target));
301
+ source.invalidationReasons.forEach((reason) => instance.invalidationReasons.add(reason));
302
+ });
303
+ valuesOfExpression.set(call.call, new Set([instance]));
304
+ });
305
+ visitBindings(module.chunk.body, (target, value) => {
306
+ const symbol = module.resolved.symbolOf(target);
307
+ if (!symbol || !stableSymbol(symbol, module.analysis))
308
+ return;
309
+ if (unionInto(valuesOfSymbol, symbol, directValues(value, module)))
310
+ changed = true;
311
+ });
312
+ module.analysis.callGraph.calls.forEach((call) => {
313
+ const expression = call.call;
314
+ if (expression.type !== "CallExpression")
315
+ return;
316
+ const targets = directCallTargets(call, module);
317
+ if (targets.size !== 1)
318
+ return;
319
+ const target = [...targets][0];
320
+ target.parameters.forEach((parameter, index) => {
321
+ const actual = expression.arguments.at(index);
322
+ if (actual &&
323
+ unionInto(valuesOfSymbol, parameter, directValues(actual, module)))
324
+ changed = true;
325
+ });
326
+ });
327
+ });
328
+ valuesOfExpressionCleanup(valuesOfExpression, modules, true);
329
+ }
330
+ // Invalidate only identities that cross an unproved observation/mutation boundary.
331
+ modules.forEach((module) => {
332
+ walkStatements(module.chunk.body, (node) => {
333
+ if (node.type === "AssignmentStatement") {
334
+ const statement = node;
335
+ if (factoryCopyAssignments.has(statement))
336
+ return;
337
+ statement.variables.forEach((target) => {
338
+ if (target.type === "IndexExpression") {
339
+ const key = (0, linker_1.staticStringArgument)(target.index);
340
+ if (key === undefined)
341
+ directValues(target.base, module).forEach((object) => object.invalidationReasons.add("dynamic-key"));
342
+ else
343
+ directValues(target.base, module).forEach((object) => {
344
+ if (object.methods.has(key))
345
+ object.invalidationReasons.add("method-field-mutation");
346
+ });
347
+ }
348
+ else if (target.type === "MemberExpression") {
349
+ if (methodDefinitions.has(target))
350
+ return;
351
+ directValues(target.base, module).forEach((object) => {
352
+ if (object.methods.has(target.identifier.name))
353
+ object.invalidationReasons.add("method-field-mutation");
354
+ });
355
+ }
356
+ });
357
+ }
358
+ });
359
+ module.analysis.callGraph.calls.forEach((call) => {
360
+ if (call.call.type !== "CallExpression")
361
+ return;
362
+ if (call.call.base.type === "Identifier" &&
363
+ call.call.base.name === "setmetatable") {
364
+ directValues(call.call.arguments[0], module).forEach((object) => object.invalidationReasons.add("metatable-mutation"));
365
+ return;
366
+ }
367
+ if (directCallTargets(call, module).size > 0 ||
368
+ staticRequiredModule(call.call))
369
+ return;
370
+ if (call.caller &&
371
+ factoryTemplates.has(call.caller) &&
372
+ call.call.base.type === "Identifier" &&
373
+ (call.call.base.name === "pairs" || call.call.base.name === "type"))
374
+ return;
375
+ if (call.call.base.type === "MemberExpression") {
376
+ const member = call.call.base;
377
+ const knownFactory = [
378
+ ...directValues(call.call.base.base, module),
379
+ ].some((object) => {
380
+ const target = object.methods.get(member.identifier.name);
381
+ return target ? factoryTemplates.has(target) : false;
382
+ });
383
+ if (knownFactory)
384
+ return;
385
+ }
386
+ call.call.arguments.forEach((argument) => {
387
+ directValues(argument, module).forEach((object) => object.invalidationReasons.add(object.kind === "prototype" || object.kind === "module-return"
388
+ ? "prototype-escape"
389
+ : "instance-escape"));
390
+ });
391
+ });
392
+ });
393
+ changed = true;
394
+ while (changed) {
395
+ changed = false;
396
+ sourcesOfObject.forEach((sources, object) => {
397
+ const before = object.invalidationReasons.size;
398
+ sources.forEach((source) => {
399
+ source.invalidationReasons.forEach((reason) => object.invalidationReasons.add(reason));
400
+ });
401
+ if (object.invalidationReasons.size !== before)
402
+ changed = true;
403
+ });
404
+ }
405
+ const resolvedMethods = [];
406
+ const diagnostics = [];
407
+ modules.forEach((module) => {
408
+ module.analysis.callGraph.calls.forEach((call) => {
409
+ if (call.call.type === "CallExpression" &&
410
+ call.call.base.type === "Identifier" &&
411
+ call.call.base.name === "require") {
412
+ const required = staticRequiredModule(call.call);
413
+ if (!required)
414
+ diagnostics.push({
415
+ moduleName: module.name,
416
+ reason: "dynamic-module-boundary",
417
+ ...sourceRangeOf(call.call),
418
+ });
419
+ else if (!moduleByName.has(required))
420
+ diagnostics.push({
421
+ moduleName: module.name,
422
+ reason: "external-module-boundary",
423
+ ...sourceRangeOf(call.call),
424
+ });
425
+ }
426
+ if (call.call.type !== "CallExpression" ||
427
+ call.call.base.type !== "MemberExpression" ||
428
+ call.call.base.indexer !== ":")
429
+ return;
430
+ const receiver = call.call.base.base;
431
+ const candidates = [...directValues(receiver, module)];
432
+ const callerAllocations = candidates.filter((candidate) => candidate.kind === "allocation" &&
433
+ candidate.moduleName === module.name);
434
+ // A constructor template contains its own factory call, but each outer call owns the
435
+ // observable allocation identity. Keep the template allocation as provenance only.
436
+ const dispatchCandidates = callerAllocations.length > 0 ? callerAllocations : candidates;
437
+ if (dispatchCandidates.length !== 1) {
438
+ diagnostics.push({
439
+ moduleName: module.name,
440
+ reason: dispatchCandidates.length === 0
441
+ ? "allocation-unknown"
442
+ : "multiple-targets",
443
+ ...sourceRangeOf(call.call),
444
+ });
445
+ return;
446
+ }
447
+ const object = dispatchCandidates[0];
448
+ if (object.invalidationReasons.size > 0) {
449
+ object.invalidationReasons.forEach((reason) => diagnostics.push({
450
+ moduleName: module.name,
451
+ reason,
452
+ ...sourceRangeOf(call.call),
453
+ }));
454
+ return;
455
+ }
456
+ const target = object.methods.get(call.call.base.identifier.name);
457
+ if (!target)
458
+ return;
459
+ const resolved = { call, receiver, target, object };
460
+ resolvedMethods.push(resolved);
461
+ diagnostics.push({
462
+ moduleName: module.name,
463
+ reason: "resolved-method-target",
464
+ ...sourceRangeOf(call.call),
465
+ });
466
+ });
467
+ });
468
+ const publicObjects = mutableObjects.map((object) => ({
469
+ id: object.id,
470
+ moduleName: object.moduleName,
471
+ kind: object.kind,
472
+ methods: object.methods,
473
+ invalidationReasons: object.invalidationReasons,
474
+ sources: [],
475
+ }));
476
+ const publicOfMutable = new Map(mutableObjects.map((object, index) => [object, publicObjects[index]]));
477
+ mutableObjects.forEach((object, index) => {
478
+ const target = publicObjects[index].sources;
479
+ sourcesOfObject.get(object)?.forEach((source) => {
480
+ const publicSource = publicOfMutable.get(source);
481
+ if (publicSource)
482
+ target.push(publicSource);
483
+ });
484
+ });
485
+ const resolvedConstructors = [];
486
+ modules.forEach((module) => {
487
+ module.analysis.callGraph.calls.forEach((call) => {
488
+ if (call.call.type !== "CallExpression")
489
+ return;
490
+ const template = constructorOfCall.get(call.call);
491
+ const mutable = instanceOfCall.get(call.call);
492
+ const object = mutable ? publicOfMutable.get(mutable) : undefined;
493
+ if (!template || !object)
494
+ return;
495
+ resolvedConstructors.push({
496
+ moduleName: module.name,
497
+ call,
498
+ target: template.callable,
499
+ object,
500
+ returnedSymbol: template.returnedSymbol,
501
+ arguments: call.call.arguments,
502
+ });
503
+ });
504
+ });
505
+ const resolvedPublic = resolvedMethods.map((resolved) => ({
506
+ ...resolved,
507
+ object: publicOfMutable.get(resolved.object),
508
+ }));
509
+ const methodByCall = new Map(resolvedPublic.map((method) => [method.call, method]));
510
+ const methodByExpression = new WeakMap(resolvedPublic.map((method) => [method.call.call, method]));
511
+ const resolvedTargetByCall = new Map(resolvedPublic.map((method) => [method.call, method.target]));
512
+ modules.forEach((module) => {
513
+ module.analysis.callGraph.calls.forEach((call) => {
514
+ if (resolvedTargetByCall.has(call))
515
+ return;
516
+ const targets = directCallTargets(call, module);
517
+ if (targets.size === 1)
518
+ resolvedTargetByCall.set(call, [...targets][0]);
519
+ });
520
+ });
521
+ const callGraph = (0, callGraph_1.combineCallGraphs)(modules.map((module) => module.analysis.callGraph), resolvedTargetByCall, generation);
522
+ const summaryOfTarget = (target) => {
523
+ const module = callableModule.get(target);
524
+ return module?.analysis.interprocedural.summaryOf(target);
525
+ };
526
+ return {
527
+ generation,
528
+ modules,
529
+ objects: publicObjects,
530
+ callGraph,
531
+ resolvedMethods: resolvedPublic,
532
+ resolvedConstructors,
533
+ diagnostics,
534
+ objectsOf: (expression) => {
535
+ const module = moduleOfNode.get(expression);
536
+ const values = valuesOfExpression.get(expression) ??
537
+ (module ? directValues(expression, module) : undefined);
538
+ if (!values)
539
+ return [];
540
+ return [...values]
541
+ .map((value) => publicOfMutable.get(value))
542
+ .filter((value) => value !== undefined);
543
+ },
544
+ objectOf: (expression) => {
545
+ const module = moduleOfNode.get(expression);
546
+ const values = valuesOfExpression.get(expression) ??
547
+ (module ? directValues(expression, module) : undefined);
548
+ if (values?.size !== 1)
549
+ return undefined;
550
+ return publicOfMutable.get([...values][0]);
551
+ },
552
+ methodCallOf: (call) => methodByCall.get(call) ?? methodByExpression.get(call.call),
553
+ summaryOfMethodCall: (call) => {
554
+ const method = methodByCall.get(call) ?? methodByExpression.get(call.call);
555
+ return method ? summaryOfTarget(method.target) : undefined;
556
+ },
557
+ effectsOfMethodCall: (call) => {
558
+ const method = methodByCall.get(call) ?? methodByExpression.get(call.call);
559
+ if (!method)
560
+ return [];
561
+ return summaryOfTarget(method.target)?.effects ?? [];
562
+ },
563
+ };
564
+ }
565
+ function stableSymbol(symbol, analysis) {
566
+ return (analysis.facts
567
+ .operationsOfSymbol(symbol)
568
+ .filter((operation) => operation.kind === "write").length === 0);
569
+ }
570
+ function unionInto(target, symbol, incoming) {
571
+ const values = target.get(symbol) ?? new Set();
572
+ const before = values.size;
573
+ incoming.forEach((value) => values.add(value));
574
+ target.set(symbol, values);
575
+ return values.size !== before;
576
+ }
577
+ function unionCallableInto(target, symbol, incoming) {
578
+ const values = target.get(symbol) ?? new Set();
579
+ const before = values.size;
580
+ incoming.forEach((value) => values.add(value));
581
+ target.set(symbol, values);
582
+ return values.size !== before;
583
+ }
584
+ function staticRequiredModule(call) {
585
+ return call.base.type === "Identifier" && call.base.name === "require"
586
+ ? (0, linker_1.staticStringArgument)(call.arguments[0])
587
+ : undefined;
588
+ }
589
+ function topLevelReturn(chunk) {
590
+ const statement = chunk.body.at(-1);
591
+ return statement?.type === "ReturnStatement" &&
592
+ statement.arguments.length === 1
593
+ ? statement.arguments[0]
594
+ : undefined;
595
+ }
596
+ function recognizeMixinFactory(callable, resolved) {
597
+ if (callable.parameters.length < 2)
598
+ return undefined;
599
+ const targetParameter = callable.parameters[0];
600
+ const prototypeParameter = callable.parameters[1];
601
+ const returned = callable.declaration.body.at(-1);
602
+ if (returned?.type !== "ReturnStatement" ||
603
+ returned.arguments.length !== 1 ||
604
+ returned.arguments[0].type !== "Identifier" ||
605
+ resolved.symbolOf(returned.arguments[0]) !== targetParameter)
606
+ return undefined;
607
+ const copyAssignments = [];
608
+ callable.declaration.body.forEach((statement) => {
609
+ if (statement.type !== "ForGenericStatement")
610
+ return;
611
+ const iterator = statement.iterators[0];
612
+ if (iterator.type !== "CallExpression" ||
613
+ iterator.base.type !== "Identifier" ||
614
+ iterator.base.name !== "pairs" ||
615
+ iterator.arguments.length === 0 ||
616
+ iterator.arguments[0].type !== "Identifier" ||
617
+ resolved.symbolOf(iterator.arguments[0]) !== prototypeParameter)
618
+ return;
619
+ const keyVariable = statement.variables.at(0);
620
+ const valueVariable = statement.variables.at(1);
621
+ if (!keyVariable || !valueVariable)
622
+ return;
623
+ const keySymbol = resolved.symbolOf(keyVariable);
624
+ const valueSymbol = resolved.symbolOf(valueVariable);
625
+ if (!keySymbol || !valueSymbol)
626
+ return;
627
+ walkStatements(statement.body, (node) => {
628
+ if (node.type !== "AssignmentStatement")
629
+ return;
630
+ const assignment = node;
631
+ assignment.variables.forEach((target, index) => {
632
+ const value = assignment.init.at(index);
633
+ if (!value)
634
+ return;
635
+ if (target.type === "IndexExpression" &&
636
+ target.base.type === "Identifier" &&
637
+ resolved.symbolOf(target.base) === targetParameter &&
638
+ target.index.type === "Identifier" &&
639
+ resolved.symbolOf(target.index) === keySymbol &&
640
+ value.type === "Identifier" &&
641
+ resolved.symbolOf(value) === valueSymbol)
642
+ copyAssignments.push(assignment);
643
+ });
644
+ });
645
+ });
646
+ return copyAssignments.length > 0
647
+ ? { targetParameter, prototypeParameter, copyAssignments }
648
+ : undefined;
649
+ }
650
+ function recognizeKeyPreservingTransfers(statement, resolved) {
651
+ const keyVariable = statement.variables.at(0);
652
+ const valueVariable = statement.variables.at(1);
653
+ if (!keyVariable)
654
+ return [];
655
+ const keySymbol = resolved.symbolOf(keyVariable);
656
+ const valueSymbol = valueVariable
657
+ ? resolved.symbolOf(valueVariable)
658
+ : undefined;
659
+ if (!keySymbol)
660
+ return [];
661
+ const first = statement.iterators.at(0);
662
+ let source;
663
+ if (first?.type === "CallExpression" &&
664
+ first.base.type === "Identifier" &&
665
+ first.base.name === "pairs")
666
+ source = first.arguments.at(0);
667
+ else if (first?.type === "Identifier" &&
668
+ first.name === "next" &&
669
+ statement.iterators.length >= 2)
670
+ source = statement.iterators[1];
671
+ if (!source)
672
+ return [];
673
+ const result = [];
674
+ walkStatements(statement.body, (node) => {
675
+ if (node.type !== "AssignmentStatement")
676
+ return;
677
+ node.variables.forEach((target, index) => {
678
+ const value = node.init.at(index);
679
+ if (target.type !== "IndexExpression" ||
680
+ target.index.type !== "Identifier" ||
681
+ resolved.symbolOf(target.index) !== keySymbol ||
682
+ !value)
683
+ return;
684
+ const copiesIteratorValue = value.type === "Identifier" &&
685
+ valueSymbol !== undefined &&
686
+ resolved.symbolOf(value) === valueSymbol;
687
+ const copiesSourceIndex = value.type === "IndexExpression" &&
688
+ value.index.type === "Identifier" &&
689
+ resolved.symbolOf(value.index) === keySymbol &&
690
+ sameExpressionIdentity(value.base, source, resolved);
691
+ if (copiesIteratorValue || copiesSourceIndex)
692
+ result.push({ assignment: node, source, target: target.base });
693
+ });
694
+ });
695
+ return result;
696
+ }
697
+ function sameExpressionIdentity(left, right, resolved) {
698
+ if (left === right)
699
+ return true;
700
+ return (left.type === "Identifier" &&
701
+ right.type === "Identifier" &&
702
+ resolved.symbolOf(left) !== undefined &&
703
+ resolved.symbolOf(left) === resolved.symbolOf(right));
704
+ }
705
+ function recognizeConstructor(callable, module, factories, targetsOf) {
706
+ const returned = callable.declaration.body.at(-1);
707
+ if (returned?.type !== "ReturnStatement" ||
708
+ returned.arguments.length !== 1 ||
709
+ returned.arguments[0].type !== "Identifier")
710
+ return undefined;
711
+ const returnedSymbol = module.resolved.symbolOf(returned.arguments[0]);
712
+ if (!returnedSymbol)
713
+ return undefined;
714
+ let template;
715
+ callable.declaration.body.forEach((statement) => {
716
+ if (statement.type !== "LocalStatement")
717
+ return;
718
+ statement.variables.forEach((variable, index) => {
719
+ if (module.resolved.symbolOf(variable) !== returnedSymbol)
720
+ return;
721
+ const value = statement.init.at(index);
722
+ if (!value)
723
+ return;
724
+ if (value.type !== "CallExpression")
725
+ return;
726
+ if (![...targetsOf(value)].some((target) => factories.has(target)))
727
+ return;
728
+ if (value.arguments.length < 2)
729
+ return;
730
+ template = {
731
+ callable,
732
+ returnedSymbol,
733
+ base: value.arguments[0],
734
+ prototype: value.arguments[1],
735
+ module,
736
+ };
737
+ });
738
+ });
739
+ return template;
740
+ }
741
+ function sourceRangeOf(node) {
742
+ const range = node.range;
743
+ return range ? { sourceRange: range } : {};
744
+ }
745
+ function valuesOfExpressionCleanup(cache, modules, retainCalls = false) {
746
+ modules.forEach((module) => {
747
+ visitExpressions(module.chunk.body, (expression) => {
748
+ if (retainCalls && expression.type === "CallExpression")
749
+ return;
750
+ if (expression.type !== "TableConstructorExpression")
751
+ cache.delete(expression);
752
+ });
753
+ });
754
+ }
755
+ function visitBindings(body, visit) {
756
+ walkStatements(body, (node) => {
757
+ if (node.type === "LocalStatement") {
758
+ const statement = node;
759
+ statement.variables.forEach((target, index) => {
760
+ const value = statement.init.at(index);
761
+ if (!value)
762
+ return;
763
+ visit(target, value);
764
+ });
765
+ }
766
+ else if (node.type === "AssignmentStatement") {
767
+ const statement = node;
768
+ statement.variables.forEach((target, index) => {
769
+ const value = statement.init.at(index);
770
+ if (!value)
771
+ return;
772
+ if (target.type === "Identifier")
773
+ visit(target, value);
774
+ });
775
+ }
776
+ });
777
+ }
778
+ function walkStatements(body, visit) {
779
+ (0, astWalk_1.walkBlockDeep)(body, { onStatement: visit, onExpression: visit });
780
+ }
781
+ function visitExpressions(body, visit) {
782
+ (0, astWalk_1.walkBlockDeep)(body, { onExpression: visit });
783
+ }