storm-lua-minify 0.3.0 → 0.9.1

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 (44) 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/globalRename.js +17 -4
  16. package/dist/interproceduralAnalysis.js +842 -0
  17. package/dist/interproceduralConstants.js +120 -0
  18. package/dist/luaString.js +157 -0
  19. package/dist/minifier.js +1219 -49
  20. package/dist/optimizerAnalysis.js +43 -0
  21. package/dist/optimizerDiagnostics.js +65 -0
  22. package/dist/optimizerFacts.js +529 -0
  23. package/dist/optimizerPass.js +96 -0
  24. package/dist/optimizerTransaction.js +56 -0
  25. package/dist/optimizerValueDomain.js +200 -0
  26. package/dist/options.js +233 -0
  27. package/dist/progress.js +2 -0
  28. package/dist/removeUnused.js +145 -0
  29. package/dist/renamer.js +280 -54
  30. package/dist/resolver.js +35 -11
  31. package/dist/runtimeEnvironment.js +105 -0
  32. package/dist/sourceMetadata.js +314 -0
  33. package/dist/statementDataflow.js +259 -0
  34. package/dist/statementScheduler.js +598 -0
  35. package/dist/symbolLiveness.js +92 -0
  36. package/dist/tableEffects.js +356 -0
  37. package/dist/transform.js +10 -371
  38. package/dist/valueFlow.js +409 -0
  39. package/dist/wholeProgramExports.js +646 -0
  40. package/dist/wholeProgramFieldRenames.js +583 -0
  41. package/dist/wholeProgramFields.js +672 -0
  42. package/dist/wholeProgramObjects.js +783 -0
  43. package/package.json +11 -2
  44. package/dist/index.js +0 -27
@@ -0,0 +1,672 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeWholeProgramFields = analyzeWholeProgramFields;
4
+ exports.applyWholeProgramFieldRewrites = applyWholeProgramFieldRewrites;
5
+ const astWalk_1 = require("./astWalk");
6
+ const callGraph_1 = require("./callGraph");
7
+ const luaString_1 = require("./luaString");
8
+ function analyzeWholeProgramFields(objects, options = {}) {
9
+ const facts = new Map();
10
+ const annotationFacts = [];
11
+ const constructorWrites = new WeakSet();
12
+ const constructorsByTarget = new Map();
13
+ objects.resolvedConstructors.forEach((constructor) => {
14
+ const calls = constructorsByTarget.get(constructor.target) ?? [];
15
+ calls.push(constructor);
16
+ constructorsByTarget.set(constructor.target, calls);
17
+ });
18
+ const factFor = (object, field) => {
19
+ const key = `${object.id}\0${field}`;
20
+ const existing = facts.get(key);
21
+ if (existing)
22
+ return existing;
23
+ const fact = {
24
+ object,
25
+ field,
26
+ evidence: [],
27
+ invalidationReasons: new Set(),
28
+ };
29
+ facts.set(key, fact);
30
+ return fact;
31
+ };
32
+ objects.modules.forEach((module) => {
33
+ const metadata = options.metadataOf?.(module.name);
34
+ if (!metadata)
35
+ return;
36
+ (0, astWalk_1.walkBlockDeep)(module.chunk.body, {
37
+ onStatement: (statement) => {
38
+ const directives = metadata.emmyLuaOf(statement);
39
+ if (directives.length === 0)
40
+ return;
41
+ if (statement.type !== "LocalStatement") {
42
+ directives.forEach((directive) => annotationFacts.push({
43
+ moduleName: module.name,
44
+ directive,
45
+ authorized: options.trustAnnotations === true,
46
+ }));
47
+ return;
48
+ }
49
+ statement.variables.forEach((identifier, index) => {
50
+ const symbol = module.resolved.symbolOf(identifier);
51
+ const expression = statement.init.at(index);
52
+ const object = expression ? objects.objectOf(expression) : undefined;
53
+ directives.forEach((directive) => {
54
+ const field = directive.kind === "field" ? directive.name : undefined;
55
+ annotationFacts.push({
56
+ moduleName: module.name,
57
+ directive,
58
+ ...(symbol ? { symbol } : {}),
59
+ ...(object ? { object } : {}),
60
+ ...(field ? { field } : {}),
61
+ authorized: options.trustAnnotations === true,
62
+ });
63
+ if (object &&
64
+ directive.kind === "field" &&
65
+ options.trustAnnotations === true) {
66
+ const value = singletonAnnotationValue(directive.valueType);
67
+ if (!value)
68
+ return;
69
+ const fact = factFor(object, directive.name);
70
+ fact.value = value;
71
+ fact.evidence.push({
72
+ kind: "annotation",
73
+ moduleName: module.name,
74
+ ...rangeOf(directive.comment),
75
+ assumption: "annotations",
76
+ });
77
+ }
78
+ });
79
+ });
80
+ },
81
+ });
82
+ });
83
+ constructorsByTarget.forEach((calls, target) => {
84
+ const module = objects.modules.find((candidate) => candidate.analysis.callGraph.functions.includes(target));
85
+ if (!module)
86
+ return;
87
+ const writes = [];
88
+ target.declaration.body.forEach((node) => {
89
+ if (node.type !== "AssignmentStatement")
90
+ return;
91
+ node.variables.forEach((variable, index) => {
92
+ const value = node.init.at(index);
93
+ if (value &&
94
+ variable.type === "MemberExpression" &&
95
+ variable.base.type === "Identifier" &&
96
+ module.resolved.symbolOf(variable.base) === calls[0].returnedSymbol) {
97
+ writes.push({
98
+ statement: node,
99
+ field: variable.identifier.name,
100
+ value,
101
+ });
102
+ constructorWrites.add(node);
103
+ }
104
+ });
105
+ });
106
+ const countByField = new Map();
107
+ writes.forEach((write) => countByField.set(write.field, (countByField.get(write.field) ?? 0) + 1));
108
+ calls.forEach((constructor) => {
109
+ writes.forEach((write) => {
110
+ const fact = factFor(constructor.object, write.field);
111
+ fact.initializer = write.statement;
112
+ if ((countByField.get(write.field) ?? 0) > 1) {
113
+ fact.invalidationReasons.add("field-reassignment");
114
+ return;
115
+ }
116
+ const value = valueAtCall(write.value, target, constructor, module, objects.modules.find((candidate) => candidate.name === constructor.moduleName) ?? module, options);
117
+ if (!value)
118
+ return;
119
+ fact.value = value.value;
120
+ fact.evidence.push(value.evidence);
121
+ if (value.annotationEvidence)
122
+ fact.evidence.push(value.annotationEvidence);
123
+ if (value.contradiction)
124
+ fact.invalidationReasons.add("contradictory-annotation");
125
+ });
126
+ });
127
+ });
128
+ // A derived factory allocation receives the base instance fields through the
129
+ // same provenance edge #83 uses for inherited methods.
130
+ let inherited = true;
131
+ while (inherited) {
132
+ inherited = false;
133
+ objects.objects.forEach((object) => {
134
+ object.sources.forEach((source) => {
135
+ [...facts.values()]
136
+ .filter((fact) => fact.object === source)
137
+ .forEach((sourceFact) => {
138
+ const key = `${object.id}\0${sourceFact.field}`;
139
+ if (facts.has(key))
140
+ return;
141
+ facts.set(key, {
142
+ object,
143
+ field: sourceFact.field,
144
+ value: sourceFact.value,
145
+ evidence: [...sourceFact.evidence],
146
+ invalidationReasons: new Set(sourceFact.invalidationReasons),
147
+ initializer: sourceFact.initializer,
148
+ });
149
+ inherited = true;
150
+ });
151
+ });
152
+ });
153
+ }
154
+ // #83 already proves the object boundary. Project its reasons onto only the
155
+ // affected allocation facts instead of inventing a second escape analysis.
156
+ facts.forEach((fact) => {
157
+ fact.object.invalidationReasons.forEach((reason) => {
158
+ if (reason === "dynamic-key")
159
+ fact.invalidationReasons.add("dynamic-key");
160
+ else if (reason === "metatable-mutation")
161
+ fact.invalidationReasons.add("metatable-mutation");
162
+ else if (reason === "instance-escape" || reason === "prototype-escape")
163
+ fact.invalidationReasons.add("alias-escape");
164
+ });
165
+ });
166
+ objects.modules.forEach((module) => {
167
+ module.analysis.callGraph.calls.forEach((call) => {
168
+ if (call.call.type !== "CallExpression")
169
+ return;
170
+ const callExpression = call.call;
171
+ const invalidateArgument = (argument, reason) => {
172
+ if (!argument)
173
+ return;
174
+ const object = objects.objectOf(argument);
175
+ if (!object)
176
+ return;
177
+ facts.forEach((fact) => {
178
+ if (fact.object === object)
179
+ fact.invalidationReasons.add(reason);
180
+ });
181
+ };
182
+ if (call.hasUnknownTarget) {
183
+ callExpression.arguments.forEach((argument) => {
184
+ invalidateArgument(argument, "unknown-call");
185
+ });
186
+ return;
187
+ }
188
+ call.targets.forEach((target) => {
189
+ const targetModule = objects.modules.find((candidate) => candidate.analysis.callGraph.functions.includes(target));
190
+ if (!targetModule)
191
+ return;
192
+ targetModule.analysis.interprocedural
193
+ .summaryOf(target)
194
+ .escapes.forEach((escape) => {
195
+ const argumentIndex = callExpression.base.type === "MemberExpression" &&
196
+ callExpression.base.indexer === ":"
197
+ ? escape.parameterIndex - 1
198
+ : escape.parameterIndex;
199
+ invalidateArgument(callExpression.arguments[argumentIndex], "alias-escape");
200
+ });
201
+ });
202
+ });
203
+ (0, astWalk_1.walkBlockDeep)(module.chunk.body, {
204
+ onStatement: (statement) => {
205
+ if (statement.type === "AssignmentStatement") {
206
+ statement.init.forEach((value, index) => {
207
+ const object = objects.objectOf(value);
208
+ const target = statement.variables[index];
209
+ if (!object ||
210
+ (target.type === "Identifier" &&
211
+ !module.resolved.isGlobalReference(target)))
212
+ return;
213
+ facts.forEach((fact) => {
214
+ if (fact.object === object)
215
+ fact.invalidationReasons.add("alias-escape");
216
+ });
217
+ });
218
+ }
219
+ },
220
+ });
221
+ });
222
+ objects.resolvedMethods.forEach((method) => {
223
+ const summary = objects.summaryOfMethodCall(method.call);
224
+ summary?.effects
225
+ .filter((effect) => effect.parameterIndex === 0 && effect.access === "write")
226
+ .forEach((effect) => {
227
+ facts.forEach((fact) => {
228
+ if (fact.object !== method.object)
229
+ return;
230
+ if (effect.staticKey === undefined ||
231
+ effect.staticKey ===
232
+ (0, luaString_1.luaByteStringKey)((0, luaString_1.luaByteStringOfText)(fact.field)))
233
+ fact.invalidationReasons.add("field-reassignment");
234
+ });
235
+ });
236
+ });
237
+ // Direct writes after construction invalidate the precise field only.
238
+ objects.modules.forEach((module) => {
239
+ (0, astWalk_1.walkBlockDeep)(module.chunk.body, {
240
+ onStatement: (node) => {
241
+ if (node.type !== "AssignmentStatement" || constructorWrites.has(node))
242
+ return;
243
+ node.variables.forEach((variable) => {
244
+ if (variable.type === "IndexExpression") {
245
+ const object = objects.objectOf(variable.base);
246
+ if (object)
247
+ facts.forEach((fact) => {
248
+ if (fact.object === object)
249
+ fact.invalidationReasons.add("dynamic-key");
250
+ });
251
+ }
252
+ else if (variable.type === "MemberExpression") {
253
+ const object = objects.objectOf(variable.base);
254
+ const fact = object
255
+ ? facts.get(`${object.id}\0${variable.identifier.name}`)
256
+ : undefined;
257
+ fact?.invalidationReasons.add("field-reassignment");
258
+ }
259
+ });
260
+ },
261
+ });
262
+ });
263
+ const methodObjects = new Map();
264
+ objects.resolvedMethods.forEach((method) => {
265
+ const receivers = methodObjects.get(method.target) ?? new Set();
266
+ receivers.add(method.object);
267
+ methodObjects.set(method.target, receivers);
268
+ });
269
+ const readFacts = new WeakMap();
270
+ objects.modules.forEach((module) => {
271
+ (0, astWalk_1.walkBlockDeep)(module.chunk.body, {
272
+ onExpression: (expression) => {
273
+ if (expression.type !== "MemberExpression" ||
274
+ expression.indexer !== ".")
275
+ return;
276
+ const directObject = objects.objectOf(expression.base);
277
+ if (directObject) {
278
+ const fact = facts.get(`${directObject.id}\0${expression.identifier.name}`);
279
+ if (fact && usable(fact))
280
+ readFacts.set(expression, fact);
281
+ return;
282
+ }
283
+ if (expression.base.type !== "Identifier")
284
+ return;
285
+ const symbol = module.resolved.symbolOf(expression.base);
286
+ const callable = module.analysis.callGraph.functions.find((candidate) => candidate.parameters[0] === symbol);
287
+ if (!callable)
288
+ return;
289
+ const receiverFacts = [...(methodObjects.get(callable) ?? [])]
290
+ .map((object) => facts.get(`${object.id}\0${expression.identifier.name}`))
291
+ .filter((fact) => fact !== undefined && usable(fact));
292
+ if (receiverFacts.length === 0)
293
+ return;
294
+ const first = receiverFacts[0];
295
+ if (receiverFacts.length === (methodObjects.get(callable)?.size ?? 0) &&
296
+ receiverFacts.every((fact) => sameValue(fact.value, first.value)))
297
+ readFacts.set(expression, first);
298
+ },
299
+ });
300
+ });
301
+ // A resolved method edge already supplies the exact receiver allocation.
302
+ // Index its parameter-zero field reads directly so callback calls do not
303
+ // depend on recovering the owning callable from a module-local scan.
304
+ objects.resolvedMethods.forEach((method) => {
305
+ const module = objects.modules.find((candidate) => candidate.analysis.callGraph.functions.includes(method.target));
306
+ const receiver = method.target.parameters[0];
307
+ if (!module)
308
+ return;
309
+ method.target.declaration.body.forEach((statement) => {
310
+ (0, astWalk_1.walkBlockDeep)([statement], {
311
+ onExpression: (expression) => {
312
+ if (expression.type !== "MemberExpression" ||
313
+ expression.indexer !== "." ||
314
+ expression.base.type !== "Identifier" ||
315
+ module.resolved.symbolOf(expression.base) !== receiver)
316
+ return;
317
+ const fact = facts.get(`${method.object.id}\0${expression.identifier.name}`);
318
+ if (fact && usable(fact))
319
+ readFacts.set(expression, fact);
320
+ },
321
+ });
322
+ });
323
+ });
324
+ const diagnostics = [];
325
+ facts.forEach((fact) => {
326
+ if (usable(fact))
327
+ diagnostics.push({
328
+ moduleName: fact.object.moduleName,
329
+ reason: "field-fact",
330
+ ...rangeOf(fact.initializer),
331
+ });
332
+ fact.invalidationReasons.forEach((reason) => diagnostics.push({
333
+ moduleName: fact.object.moduleName,
334
+ reason,
335
+ ...rangeOf(fact.initializer),
336
+ }));
337
+ });
338
+ const publicFacts = [...facts.values()];
339
+ const resolvedCallbacks = [];
340
+ const callbackTargets = new Map();
341
+ objects.callGraph.calls.forEach((call) => {
342
+ if (call.call.type !== "CallExpression" ||
343
+ call.call.base.type !== "MemberExpression" ||
344
+ call.call.base.indexer !== ".")
345
+ return;
346
+ const field = readFacts.get(call.call.base);
347
+ if (!field || field.value?.kind !== "function")
348
+ return;
349
+ callbackTargets.set(call, field.value.callable);
350
+ resolvedCallbacks.push({
351
+ call,
352
+ field,
353
+ target: field.value.callable,
354
+ });
355
+ });
356
+ const callGraph = (0, callGraph_1.combineCallGraphs)([objects.callGraph], callbackTargets, objects.generation);
357
+ return {
358
+ generation: objects.generation,
359
+ callGraph,
360
+ resolvedCallbacks,
361
+ facts: publicFacts,
362
+ diagnostics,
363
+ annotationFacts,
364
+ factOf: (object, field) => facts.get(`${object.id}\0${field}`),
365
+ factOfRead: (read) => readFacts.get(read),
366
+ };
367
+ }
368
+ function applyWholeProgramFieldRewrites(objects, analysis, metadataOf, options = {}) {
369
+ const assignmentTargets = new WeakSet();
370
+ if (options.replaceReads !== false)
371
+ objects.modules.forEach((module) => {
372
+ (0, astWalk_1.walkBlockDeep)(module.chunk.body, {
373
+ onStatement: (statement) => {
374
+ if (statement.type !== "AssignmentStatement")
375
+ return;
376
+ statement.variables.forEach((variable) => {
377
+ if (variable.type === "MemberExpression")
378
+ assignmentTargets.add(variable);
379
+ });
380
+ },
381
+ });
382
+ });
383
+ const readCount = new Map();
384
+ let replacedReads = 0;
385
+ if (options.removeInitializers !== false)
386
+ objects.modules.forEach((module) => {
387
+ (0, astWalk_1.walkBlockDeep)(module.chunk.body, {
388
+ onExpression: (expression) => {
389
+ if (expression.type !== "MemberExpression" ||
390
+ assignmentTargets.has(expression))
391
+ return;
392
+ const fact = analysis.factOfRead(expression);
393
+ if (!fact?.value)
394
+ return;
395
+ const replacement = literalFor(fact.value, expression);
396
+ if (!replacement) {
397
+ if (fact.initializer)
398
+ readCount.set(fact.initializer, (readCount.get(fact.initializer) ?? 0) + 1);
399
+ return;
400
+ }
401
+ replaceExpression(expression, replacement);
402
+ replacedReads++;
403
+ },
404
+ });
405
+ });
406
+ const factsByInitializer = new Map();
407
+ analysis.facts.forEach((fact) => {
408
+ if (!fact.initializer)
409
+ return;
410
+ const related = factsByInitializer.get(fact.initializer) ?? [];
411
+ related.push(fact);
412
+ factsByInitializer.set(fact.initializer, related);
413
+ });
414
+ let removedInitializers = 0;
415
+ let preservedEffects = 0;
416
+ objects.modules.forEach((module) => {
417
+ const metadata = metadataOf(module.name);
418
+ rewriteBlocks(module.chunk.body, (body, index, statement) => {
419
+ if (statement.type !== "AssignmentStatement")
420
+ return;
421
+ const related = factsByInitializer.get(statement);
422
+ if (!related ||
423
+ related.length === 0 ||
424
+ related.some((fact) => fact.invalidationReasons.size > 0 ||
425
+ (fact.initializer && (readCount.get(fact.initializer) ?? 0) > 0)) ||
426
+ metadata.annotationsOf(statement).keep)
427
+ return;
428
+ if (statement.variables.length !== 1 || statement.init.length !== 1)
429
+ return;
430
+ const value = statement.init[0];
431
+ const discardable = module.analysis.facts.discardabilityOf(value).discardable ||
432
+ (value.type === "Identifier" &&
433
+ module.resolved.symbolOf(value) !== undefined);
434
+ if (discardable) {
435
+ metadata.removeStatement(statement, body[index + 1]);
436
+ body.splice(index, 1);
437
+ }
438
+ else if (value.type === "CallExpression" ||
439
+ value.type === "TableCallExpression" ||
440
+ value.type === "StringCallExpression") {
441
+ const statementRange = statement
442
+ .range;
443
+ const replacement = {
444
+ type: "CallStatement",
445
+ expression: value,
446
+ ...(statement.loc ? { loc: statement.loc } : {}),
447
+ ...(statementRange ? { range: statementRange } : {}),
448
+ };
449
+ metadata.replaceStatement(statement, [replacement]);
450
+ body[index] = replacement;
451
+ preservedEffects++;
452
+ }
453
+ else
454
+ return;
455
+ removedInitializers++;
456
+ });
457
+ });
458
+ return {
459
+ changed: replacedReads > 0 || removedInitializers > 0,
460
+ replacedReads,
461
+ removedInitializers,
462
+ preservedEffects,
463
+ };
464
+ }
465
+ function valueAtCall(expression, target, constructor, module, callerModule, options) {
466
+ let source = expression;
467
+ let valueModule = module;
468
+ if (expression.type === "Identifier") {
469
+ const symbol = module.resolved.symbolOf(expression);
470
+ const index = symbol ? target.parameters.indexOf(symbol) : -1;
471
+ const metadata = options.metadataOf?.(module.name);
472
+ const annotation = metadata
473
+ ?.emmyLuaOf(target.declaration)
474
+ .find((directive) => directive.kind === "param" &&
475
+ index >= 0 &&
476
+ directive.name === target.parameters[index].name);
477
+ const annotationValue = options.trustAnnotations && annotation?.kind === "param"
478
+ ? singletonAnnotationValue(annotation.valueType)
479
+ : undefined;
480
+ const annotationEvidence = annotationValue && annotation
481
+ ? {
482
+ kind: "annotation",
483
+ moduleName: module.name,
484
+ ...rangeOf(annotation.comment),
485
+ assumption: "annotations",
486
+ }
487
+ : undefined;
488
+ const argumentIndex = constructor.call.call.type === "CallExpression" &&
489
+ constructor.call.call.base.type === "MemberExpression" &&
490
+ constructor.call.call.base.indexer === ":"
491
+ ? index - 1
492
+ : index;
493
+ if (argumentIndex >= 0 && constructor.arguments[argumentIndex]) {
494
+ source = constructor.arguments[argumentIndex];
495
+ valueModule = callerModule;
496
+ const actualValue = syntaxValue(source, valueModule);
497
+ if (actualValue &&
498
+ annotationValue &&
499
+ !sameValue(actualValue, annotationValue))
500
+ return {
501
+ value: actualValue,
502
+ evidence: {
503
+ kind: "code",
504
+ moduleName: constructor.moduleName,
505
+ ...rangeOf(source),
506
+ },
507
+ annotationEvidence,
508
+ contradiction: true,
509
+ };
510
+ }
511
+ else if (index >= 0 && annotationValue && annotationEvidence) {
512
+ return { value: annotationValue, evidence: annotationEvidence };
513
+ }
514
+ }
515
+ const value = syntaxValue(source, valueModule);
516
+ return value
517
+ ? {
518
+ value,
519
+ evidence: {
520
+ kind: "code",
521
+ moduleName: source === expression ? module.name : constructor.moduleName,
522
+ ...rangeOf(source),
523
+ },
524
+ }
525
+ : undefined;
526
+ }
527
+ function syntaxValue(expression, module) {
528
+ switch (expression.type) {
529
+ case "NilLiteral":
530
+ return { kind: "nil" };
531
+ case "BooleanLiteral":
532
+ return { kind: "boolean", value: expression.value };
533
+ case "NumericLiteral":
534
+ return { kind: "number", raw: expression.raw };
535
+ case "StringLiteral":
536
+ return { kind: "string", raw: expression.raw };
537
+ case "TableConstructorExpression":
538
+ return expression.fields.length === 0
539
+ ? { kind: "empty-table", origin: expression }
540
+ : undefined;
541
+ case "FunctionDeclaration": {
542
+ const callable = module.analysis.callGraph.functionOf(expression);
543
+ return callable ? { kind: "function", callable } : undefined;
544
+ }
545
+ case "Identifier": {
546
+ const symbol = module.resolved.symbolOf(expression);
547
+ const callable = symbol
548
+ ? module.analysis.callGraph.functionOfSymbol(symbol)
549
+ : undefined;
550
+ if (callable)
551
+ return { kind: "function", callable };
552
+ const value = module.analysis.facts.expressionFact(expression)?.value;
553
+ if (value?.kind === "allocation" &&
554
+ value.allocationKind === "table" &&
555
+ value.origin.type === "TableConstructorExpression" &&
556
+ value.origin.fields.length === 0)
557
+ return { kind: "empty-table", origin: value.origin };
558
+ if (!symbol)
559
+ return undefined;
560
+ let initializer;
561
+ (0, astWalk_1.walkBlockDeep)(module.chunk.body, {
562
+ onStatement: (statement) => {
563
+ if (statement.type !== "LocalStatement")
564
+ return;
565
+ statement.variables.forEach((variable, index) => {
566
+ if (variable === symbol.declaration)
567
+ initializer = statement.init[index];
568
+ });
569
+ },
570
+ });
571
+ return initializer?.type === "TableConstructorExpression" &&
572
+ initializer.fields.length === 0
573
+ ? { kind: "empty-table", origin: initializer }
574
+ : undefined;
575
+ }
576
+ default:
577
+ return undefined;
578
+ }
579
+ }
580
+ function singletonAnnotationValue(valueType) {
581
+ const value = valueType.trim();
582
+ if (value === "true" || value === "false")
583
+ return { kind: "boolean", value: value === "true" };
584
+ if (/^-?(?:\d+\.?\d*|\.\d+)$/.test(value))
585
+ return { kind: "number", raw: value };
586
+ if (/^(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')$/.test(value))
587
+ return { kind: "string", raw: value };
588
+ return undefined;
589
+ }
590
+ function literalFor(value, origin) {
591
+ const originRange = origin.range;
592
+ const source = {
593
+ ...(origin.loc ? { loc: origin.loc } : {}),
594
+ ...(originRange ? { range: originRange } : {}),
595
+ };
596
+ switch (value.kind) {
597
+ case "nil":
598
+ return { type: "NilLiteral", value: null, raw: "nil", ...source };
599
+ case "boolean":
600
+ return {
601
+ type: "BooleanLiteral",
602
+ value: value.value,
603
+ raw: value.value ? "true" : "false",
604
+ ...source,
605
+ };
606
+ case "number":
607
+ return {
608
+ type: "NumericLiteral",
609
+ value: Number(value.raw),
610
+ raw: value.raw,
611
+ ...source,
612
+ };
613
+ case "string":
614
+ return {
615
+ type: "StringLiteral",
616
+ value: value.raw.slice(1, -1),
617
+ raw: value.raw,
618
+ ...source,
619
+ };
620
+ case "function":
621
+ case "empty-table":
622
+ return undefined;
623
+ }
624
+ }
625
+ function replaceExpression(target, replacement) {
626
+ // Every AST consumer dispatches on `type`; source location and superseded
627
+ // member fields are inert provenance once the discriminant is replaced.
628
+ Object.assign(target, replacement);
629
+ }
630
+ function rewriteBlocks(body, visit) {
631
+ for (let index = body.length - 1; index >= 0; index--) {
632
+ const statement = body[index];
633
+ switch (statement.type) {
634
+ case "DoStatement":
635
+ case "WhileStatement":
636
+ case "RepeatStatement":
637
+ case "FunctionDeclaration":
638
+ case "ForNumericStatement":
639
+ case "ForGenericStatement":
640
+ rewriteBlocks(statement.body, visit);
641
+ break;
642
+ case "IfStatement":
643
+ statement.clauses.forEach((clause) => {
644
+ rewriteBlocks(clause.body, visit);
645
+ });
646
+ break;
647
+ }
648
+ visit(body, index, statement);
649
+ }
650
+ }
651
+ function usable(fact) {
652
+ return fact.value !== undefined && fact.invalidationReasons.size === 0;
653
+ }
654
+ function sameValue(left, right) {
655
+ if (!left || !right || left.kind !== right.kind)
656
+ return false;
657
+ if (left.kind === "boolean" && right.kind === "boolean")
658
+ return left.value === right.value;
659
+ if (left.kind === "number" && right.kind === "number")
660
+ return left.raw === right.raw;
661
+ if (left.kind === "string" && right.kind === "string")
662
+ return left.raw === right.raw;
663
+ if (left.kind === "function" && right.kind === "function")
664
+ return left.callable === right.callable;
665
+ if (left.kind === "empty-table" && right.kind === "empty-table")
666
+ return left.origin === right.origin;
667
+ return left.kind === "nil" && right.kind === "nil";
668
+ }
669
+ function rangeOf(node) {
670
+ const range = node ? node.range : undefined;
671
+ return range ? { sourceRange: range } : {};
672
+ }