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,646 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeWholeProgramExports = analyzeWholeProgramExports;
4
+ exports.applyWholeProgramExportDce = applyWholeProgramExportDce;
5
+ const astWalk_1 = require("./astWalk");
6
+ const linker_1 = require("./linker");
7
+ function sourceRangeOf(node) {
8
+ return node.range;
9
+ }
10
+ function fieldKey(field) {
11
+ if (field.type === "TableKeyString")
12
+ return field.key.name;
13
+ return (0, linker_1.staticStringArgument)(field.key);
14
+ }
15
+ function isCallExpression(expression) {
16
+ return (expression.type === "CallExpression" ||
17
+ expression.type === "TableCallExpression" ||
18
+ expression.type === "StringCallExpression");
19
+ }
20
+ function topLevelReturn(module) {
21
+ const statement = module.chunk.body.at(-1);
22
+ return statement?.type === "ReturnStatement" &&
23
+ statement.arguments.length === 1
24
+ ? statement
25
+ : undefined;
26
+ }
27
+ function sameObject(analysis, expression, object) {
28
+ return analysis.objectOf(expression)?.id === object.id;
29
+ }
30
+ function markExpressionOwner(expression, owner, owners) {
31
+ (0, astWalk_1.walkExpression)(expression, {
32
+ onExpression: (nested) => owners.set(nested, owner),
33
+ onBlock: (body) => {
34
+ (0, astWalk_1.walkBlockDeep)(body, {
35
+ onStatement: (statement) => owners.set(statement, owner),
36
+ onExpression: (nested) => owners.set(nested, owner),
37
+ });
38
+ },
39
+ });
40
+ }
41
+ /**
42
+ * Compute export reachability from resolved module-return allocation identity.
43
+ * Static uses become graph edges; any operation that can observe the table shape
44
+ * roots the affected allocation instead of attempting a string-based guess.
45
+ */
46
+ function analyzeWholeProgramExports(objects, entryModule, metadataOf) {
47
+ const exportObjects = objects.objects.filter((object) => object.kind === "module-return");
48
+ const exportObjectIds = new Set(exportObjects.map((object) => object.id));
49
+ const objectById = new Map(exportObjects.map((object) => [object.id, object]));
50
+ const fieldsByObject = new Map();
51
+ const definitionMembers = new WeakSet();
52
+ const ownerOfNode = new WeakMap();
53
+ const callableByDeclaration = new WeakMap();
54
+ const callSiteByExpression = new WeakMap();
55
+ const bodyOfStatement = new WeakMap();
56
+ const statementOfExpression = new WeakMap();
57
+ const boundaryDiagnostics = [];
58
+ const objectBoundaryReasons = new Map();
59
+ const addObjectBoundary = (object, reason) => {
60
+ const reasons = objectBoundaryReasons.get(object) ?? new Set();
61
+ reasons.add(reason);
62
+ objectBoundaryReasons.set(object, reasons);
63
+ };
64
+ objects.callGraph.calls.forEach((call) => callSiteByExpression.set(call.call, call));
65
+ objects.modules.forEach((module) => {
66
+ indexStatementOwners(module.chunk.body, bodyOfStatement, statementOfExpression);
67
+ module.analysis.callGraph.functions.forEach((callable) => {
68
+ callableByDeclaration.set(callable.declaration, callable);
69
+ callable.declaration.body.forEach((statement) => {
70
+ (0, astWalk_1.walkBlockDeep)([statement], {
71
+ onStatement: (nested) => ownerOfNode.set(nested, callable),
72
+ onExpression: (expression) => ownerOfNode.set(expression, callable),
73
+ });
74
+ });
75
+ });
76
+ });
77
+ const mutableField = (object, key) => {
78
+ let fields = fieldsByObject.get(object.id);
79
+ if (!fields) {
80
+ fields = new Map();
81
+ fieldsByObject.set(object.id, fields);
82
+ }
83
+ let field = fields.get(key);
84
+ if (!field) {
85
+ field = {
86
+ id: `${object.id}:field:${key}`,
87
+ moduleName: object.moduleName,
88
+ object,
89
+ key,
90
+ definitions: [],
91
+ dependencies: new Set(),
92
+ callableDependencies: new Set(),
93
+ shapeDependencies: new Set(),
94
+ rootReasons: new Set(),
95
+ live: false,
96
+ };
97
+ fields.set(key, field);
98
+ }
99
+ return field;
100
+ };
101
+ const exportObjectOf = (expression) => {
102
+ const object = objects.objectOf(expression);
103
+ return object && exportObjectIds.has(object.id)
104
+ ? objectById.get(object.id)
105
+ : undefined;
106
+ };
107
+ // Collect every statically named export definition before classifying uses.
108
+ objects.modules.forEach((module) => {
109
+ const metadata = metadataOf(module.name);
110
+ (0, astWalk_1.walkBlockDeep)(module.chunk.body, {
111
+ onStatement: (statement) => {
112
+ if (statement.type === "FunctionDeclaration" &&
113
+ statement.identifier?.type === "MemberExpression") {
114
+ const object = exportObjectOf(statement.identifier.base);
115
+ if (!object)
116
+ return;
117
+ const field = mutableField(object, statement.identifier.identifier.name);
118
+ field.definitions.push({
119
+ kind: "member-function",
120
+ module,
121
+ body: bodyOfStatement.get(statement) ?? module.chunk.body,
122
+ statement,
123
+ value: statement,
124
+ });
125
+ definitionMembers.add(statement.identifier);
126
+ statement.body.forEach((bodyStatement) => {
127
+ (0, astWalk_1.walkBlockDeep)([bodyStatement], {
128
+ onStatement: (nested) => ownerOfNode.set(nested, field),
129
+ onExpression: (expression) => ownerOfNode.set(expression, field),
130
+ });
131
+ });
132
+ if (metadata.annotationsOf(statement).keep ||
133
+ metadata.annotationsOf(statement).exported)
134
+ field.rootReasons.add("annotation-root");
135
+ return;
136
+ }
137
+ if (statement.type === "AssignmentStatement") {
138
+ statement.variables.forEach((variable, index) => {
139
+ if (variable.type !== "MemberExpression" &&
140
+ variable.type !== "IndexExpression")
141
+ return;
142
+ const object = exportObjectOf(variable.base);
143
+ const value = statement.init.at(index);
144
+ if (!object || !value)
145
+ return;
146
+ const key = variable.type === "MemberExpression"
147
+ ? variable.identifier.name
148
+ : (0, linker_1.staticStringArgument)(variable.index);
149
+ if (key === undefined) {
150
+ addObjectBoundary(object, "dynamic-key");
151
+ return;
152
+ }
153
+ const field = mutableField(object, key);
154
+ field.definitions.push({
155
+ kind: "member-assignment",
156
+ module,
157
+ body: bodyOfStatement.get(statement) ?? module.chunk.body,
158
+ statement,
159
+ value,
160
+ });
161
+ definitionMembers.add(variable);
162
+ markExpressionOwner(value, field, ownerOfNode);
163
+ if (isDynamicRequire(value))
164
+ boundaryDiagnostics.push({
165
+ moduleName: module.name,
166
+ field: key,
167
+ reason: "unresolved-re-export",
168
+ sourceRange: sourceRangeOf(statement),
169
+ });
170
+ if (metadata.annotationsOf(statement).keep ||
171
+ metadata.annotationsOf(statement).exported)
172
+ field.rootReasons.add("annotation-root");
173
+ });
174
+ }
175
+ if (statement.type === "LocalStatement") {
176
+ statement.init.forEach((initializer) => {
177
+ const object = exportObjectOf(initializer);
178
+ if (object &&
179
+ (metadata.annotationsOf(statement).keep ||
180
+ metadata.annotationsOf(statement).exported)) {
181
+ fieldsByObject
182
+ .get(object.id)
183
+ ?.forEach((field) => field.rootReasons.add("annotation-root"));
184
+ }
185
+ });
186
+ }
187
+ },
188
+ onExpression: (expression) => {
189
+ if (expression.type !== "TableConstructorExpression")
190
+ return;
191
+ const object = exportObjectOf(expression);
192
+ if (!object)
193
+ return;
194
+ const statement = statementOfExpression.get(expression);
195
+ if (!statement)
196
+ return;
197
+ expression.fields.forEach((tableField) => {
198
+ if (tableField.type === "TableValue") {
199
+ addObjectBoundary(object, "shape-observation");
200
+ return;
201
+ }
202
+ const key = fieldKey(tableField);
203
+ if (key === undefined) {
204
+ addObjectBoundary(object, "dynamic-key");
205
+ return;
206
+ }
207
+ const field = mutableField(object, key);
208
+ field.definitions.push({
209
+ kind: "table-field",
210
+ module,
211
+ body: bodyOfStatement.get(statement) ?? module.chunk.body,
212
+ statement,
213
+ table: expression,
214
+ field: tableField,
215
+ value: tableField.value,
216
+ });
217
+ markExpressionOwner(tableField.value, field, ownerOfNode);
218
+ if (isDynamicRequire(tableField.value))
219
+ boundaryDiagnostics.push({
220
+ moduleName: module.name,
221
+ field: key,
222
+ reason: "unresolved-re-export",
223
+ sourceRange: sourceRangeOf(tableField),
224
+ });
225
+ const annotations = metadata.annotationsOf(statement);
226
+ if (annotations.keep || annotations.exported)
227
+ field.rootReasons.add("annotation-root");
228
+ });
229
+ },
230
+ });
231
+ });
232
+ const allFields = () => [...fieldsByObject.values()].flatMap((fields) => [...fields.values()]);
233
+ const rootObject = (object, reason) => {
234
+ fieldsByObject
235
+ .get(object.id)
236
+ ?.forEach((field) => field.rootReasons.add(reason));
237
+ };
238
+ objectBoundaryReasons.forEach((reasons, object) => {
239
+ reasons.forEach((reason) => {
240
+ rootObject(object, reason);
241
+ });
242
+ });
243
+ const addFieldUse = (object, key, owner) => {
244
+ const field = mutableField(object, key);
245
+ if (!owner)
246
+ field.rootReasons.add("static-field-root");
247
+ else if (isMutableField(owner))
248
+ owner.dependencies.add(field);
249
+ else
250
+ callableFieldDependencies(owner).add(field);
251
+ };
252
+ const fieldsOfCallable = new Map();
253
+ const callsOfCallable = new Map();
254
+ const shapeOfCallable = new Map();
255
+ const callableRoots = new Set();
256
+ const callableFieldDependencies = (callable) => {
257
+ let fields = fieldsOfCallable.get(callable);
258
+ if (!fields) {
259
+ fields = new Set();
260
+ fieldsOfCallable.set(callable, fields);
261
+ }
262
+ return fields;
263
+ };
264
+ const callableCallDependencies = (callable) => {
265
+ let calls = callsOfCallable.get(callable);
266
+ if (!calls) {
267
+ calls = new Set();
268
+ callsOfCallable.set(callable, calls);
269
+ }
270
+ return calls;
271
+ };
272
+ const callableShapeDependencies = (callable) => {
273
+ let dependencies = shapeOfCallable.get(callable);
274
+ if (!dependencies) {
275
+ dependencies = new Set();
276
+ shapeOfCallable.set(callable, dependencies);
277
+ }
278
+ return dependencies;
279
+ };
280
+ const addShapeUse = (object, owner, reason) => {
281
+ if (!owner)
282
+ rootObject(object, reason);
283
+ else if (isMutableField(owner))
284
+ owner.shapeDependencies.add(object);
285
+ else
286
+ callableShapeDependencies(owner).add(object);
287
+ };
288
+ objects.modules.forEach((module) => {
289
+ const returned = topLevelReturn(module);
290
+ (0, astWalk_1.walkBlockDeep)(module.chunk.body, {
291
+ onStatement: (statement) => {
292
+ const owner = ownerOfNode.get(statement);
293
+ if (statement.type === "ReturnStatement") {
294
+ statement.arguments.forEach((argument) => {
295
+ const object = exportObjectOf(argument);
296
+ if (!object)
297
+ return;
298
+ const isOwnTopLevelReturn = returned === statement && object.moduleName === module.name;
299
+ if (!isOwnTopLevelReturn)
300
+ addShapeUse(object, owner, "export-escape");
301
+ });
302
+ }
303
+ if (statement.type === "LocalStatement" ||
304
+ statement.type === "AssignmentStatement") {
305
+ const values = statement.type === "LocalStatement"
306
+ ? statement.init
307
+ : statement.init;
308
+ values.forEach((value) => {
309
+ const object = exportObjectOf(value);
310
+ if (!object)
311
+ return;
312
+ if (statement.type === "AssignmentStatement") {
313
+ const stableAlias = statement.variables.some((variable) => variable.type === "Identifier" &&
314
+ sameObject(objects, variable, object));
315
+ if (!stableAlias)
316
+ addShapeUse(object, owner, "export-escape");
317
+ }
318
+ });
319
+ }
320
+ },
321
+ onExpression: (expression) => {
322
+ const owner = ownerOfNode.get(expression);
323
+ if (expression.type === "MemberExpression") {
324
+ if (definitionMembers.has(expression))
325
+ return;
326
+ const object = exportObjectOf(expression.base);
327
+ if (object)
328
+ addFieldUse(object, expression.identifier.name, owner);
329
+ return;
330
+ }
331
+ if (expression.type === "IndexExpression") {
332
+ if (definitionMembers.has(expression))
333
+ return;
334
+ const object = exportObjectOf(expression.base);
335
+ if (!object)
336
+ return;
337
+ const key = (0, linker_1.staticStringArgument)(expression.index);
338
+ if (key === undefined)
339
+ addShapeUse(object, owner, "dynamic-key");
340
+ else
341
+ addFieldUse(object, key, owner);
342
+ return;
343
+ }
344
+ if (expression.type === "UnaryExpression" &&
345
+ expression.operator === "#") {
346
+ const object = exportObjectOf(expression.argument);
347
+ if (object)
348
+ addShapeUse(object, owner, "shape-observation");
349
+ return;
350
+ }
351
+ if (!isCallExpression(expression))
352
+ return;
353
+ const combinedCall = callSiteByExpression.get(expression);
354
+ if (combinedCall) {
355
+ if (!combinedCall.caller)
356
+ combinedCall.targets.forEach((target) => callableRoots.add(target));
357
+ else {
358
+ const caller = combinedCall.caller;
359
+ combinedCall.targets.forEach((target) => {
360
+ callableCallDependencies(caller).add(target);
361
+ });
362
+ }
363
+ }
364
+ if (expression.type !== "CallExpression")
365
+ return;
366
+ const calleeName = expression.base.type === "Identifier"
367
+ ? expression.base.name
368
+ : undefined;
369
+ const shapeObserver = calleeName === "pairs" || calleeName === "next";
370
+ const metatableObserver = calleeName === "setmetatable" ||
371
+ calleeName === "getmetatable" ||
372
+ calleeName === "rawget" ||
373
+ calleeName === "rawset";
374
+ const localCall = module.analysis.callGraph.callSiteOf(expression);
375
+ expression.arguments.forEach((argument, argumentIndex) => {
376
+ const object = exportObjectOf(argument);
377
+ if (!object)
378
+ return;
379
+ if (shapeObserver || metatableObserver)
380
+ addShapeUse(object, owner, "shape-observation");
381
+ else if (!localCall ||
382
+ localCall.hasUnknownTarget ||
383
+ localCall.targets.size === 0 ||
384
+ module.analysis.interprocedural.escapesArgument(localCall, argumentIndex))
385
+ addShapeUse(object, owner, "export-escape");
386
+ });
387
+ },
388
+ });
389
+ });
390
+ // A module with more than one possible return allocation cannot be field-pruned.
391
+ const objectsByModule = new Map();
392
+ exportObjects.forEach((object) => {
393
+ const group = objectsByModule.get(object.moduleName) ?? [];
394
+ group.push(object);
395
+ objectsByModule.set(object.moduleName, group);
396
+ });
397
+ objectsByModule.forEach((moduleObjects, moduleName) => {
398
+ if (moduleObjects.length > 1) {
399
+ moduleObjects.forEach((object) => {
400
+ rootObject(object, "multiple-return-allocations");
401
+ });
402
+ }
403
+ if (moduleName === entryModule) {
404
+ moduleObjects.forEach((object) => {
405
+ rootObject(object, "entry-contract");
406
+ });
407
+ }
408
+ });
409
+ // Object invalidation from #83 is also an export-shape observation boundary.
410
+ exportObjects.forEach((object) => {
411
+ if (object.invalidationReasons.size > 0)
412
+ rootObject(object, "export-escape");
413
+ });
414
+ // Function values and helper references participate in the same graph. A
415
+ // reference owned by an export field is an edge, while an unowned reference
416
+ // makes the callable externally reachable.
417
+ objects.modules.forEach((module) => {
418
+ module.analysis.callGraph.functions.forEach((callable) => {
419
+ callable.symbol?.references.forEach((reference) => {
420
+ const owner = ownerOfNode.get(reference);
421
+ if (!owner)
422
+ callableRoots.add(callable);
423
+ else if (isMutableField(owner))
424
+ owner.callableDependencies.add(callable);
425
+ else
426
+ callableCallDependencies(owner).add(callable);
427
+ });
428
+ });
429
+ });
430
+ allFields().forEach((field) => {
431
+ field.definitions.forEach((definition) => {
432
+ const expression = definition.value;
433
+ if (expression.type !== "Identifier")
434
+ return;
435
+ const symbol = definition.module.resolved.symbolOf(expression);
436
+ const callable = symbol
437
+ ? definition.module.analysis.callGraph.functionOfSymbol(symbol)
438
+ : undefined;
439
+ if (callable)
440
+ field.callableDependencies.add(callable);
441
+ });
442
+ });
443
+ const liveCallables = new Set();
444
+ const callableQueue = [...callableRoots];
445
+ const fieldQueue = allFields().filter((field) => field.rootReasons.size > 0);
446
+ const enqueueObject = (object) => {
447
+ fieldsByObject.get(object.id)?.forEach((field) => {
448
+ if (!field.live)
449
+ fieldQueue.push(field);
450
+ });
451
+ };
452
+ while (fieldQueue.length > 0 || callableQueue.length > 0) {
453
+ while (fieldQueue.length > 0) {
454
+ const field = fieldQueue.shift();
455
+ if (!field)
456
+ break;
457
+ if (field.live)
458
+ continue;
459
+ field.live = true;
460
+ field.dependencies.forEach((dependency) => {
461
+ if (!dependency.live)
462
+ fieldQueue.push(dependency);
463
+ });
464
+ field.callableDependencies.forEach((callable) => callableQueue.push(callable));
465
+ field.shapeDependencies.forEach(enqueueObject);
466
+ field.definitions.forEach((definition) => {
467
+ const callable = definition.value.type === "FunctionDeclaration"
468
+ ? callableByDeclaration.get(definition.value)
469
+ : undefined;
470
+ if (callable)
471
+ callableQueue.push(callable);
472
+ const reExport = exportObjectOf(definition.value);
473
+ if (reExport && reExport.id !== field.object.id)
474
+ enqueueObject(reExport);
475
+ });
476
+ }
477
+ while (callableQueue.length > 0) {
478
+ const callable = callableQueue.shift();
479
+ if (!callable)
480
+ break;
481
+ if (liveCallables.has(callable))
482
+ continue;
483
+ liveCallables.add(callable);
484
+ callableFieldDependencies(callable).forEach((field) => {
485
+ if (!field.live)
486
+ fieldQueue.push(field);
487
+ });
488
+ callableCallDependencies(callable).forEach((target) => callableQueue.push(target));
489
+ callableShapeDependencies(callable).forEach(enqueueObject);
490
+ }
491
+ }
492
+ const fields = allFields().sort((left, right) => left.id.localeCompare(right.id));
493
+ const diagnostics = [...boundaryDiagnostics];
494
+ fields.forEach((field) => {
495
+ diagnostics.push({
496
+ moduleName: field.moduleName,
497
+ field: field.key,
498
+ reason: "export-field-candidate",
499
+ sourceRange: sourceRangeOf(field.definitions[0]?.statement ?? {}),
500
+ });
501
+ [...field.rootReasons].sort().forEach((reason) => diagnostics.push({
502
+ moduleName: field.moduleName,
503
+ field: field.key,
504
+ reason,
505
+ sourceRange: sourceRangeOf(field.definitions[0]?.statement ?? {}),
506
+ }));
507
+ diagnostics.push({
508
+ moduleName: field.moduleName,
509
+ field: field.key,
510
+ reason: field.live ? "field-live" : "field-unreachable",
511
+ sourceRange: sourceRangeOf(field.definitions[0]?.statement ?? {}),
512
+ });
513
+ });
514
+ return {
515
+ generation: objects.generation,
516
+ fields: fields.map((field) => ({
517
+ id: field.id,
518
+ moduleName: field.moduleName,
519
+ object: field.object,
520
+ key: field.key,
521
+ definitions: field.definitions,
522
+ live: field.live,
523
+ rootReasons: field.rootReasons,
524
+ })),
525
+ diagnostics,
526
+ };
527
+ }
528
+ function indexStatementOwners(body, bodyOfStatement, statementOfExpression) {
529
+ body.forEach((statement) => {
530
+ bodyOfStatement.set(statement, body);
531
+ (0, astWalk_1.walkStatement)(statement, {
532
+ onExpression: (expression) => {
533
+ statementOfExpression.set(expression, statement);
534
+ },
535
+ onBlock: (nested) => {
536
+ indexStatementOwners(nested, bodyOfStatement, statementOfExpression);
537
+ },
538
+ });
539
+ });
540
+ }
541
+ function isDynamicRequire(expression) {
542
+ return (expression.type === "CallExpression" &&
543
+ expression.base.type === "Identifier" &&
544
+ expression.base.name === "require" &&
545
+ (0, linker_1.staticStringArgument)(expression.arguments[0]) === undefined);
546
+ }
547
+ function isMutableField(owner) {
548
+ return "definitions" in owner && "dependencies" in owner;
549
+ }
550
+ function callStatement(expression) {
551
+ return {
552
+ type: "CallStatement",
553
+ expression,
554
+ loc: expression.loc,
555
+ range: sourceRangeOf(expression),
556
+ };
557
+ }
558
+ /** Apply one already-proved reachability snapshot without rebuilding analysis. */
559
+ function applyWholeProgramExportDce(analysis, metadataOf, discardable) {
560
+ const replacements = new Map();
561
+ const removedTableFields = new Map();
562
+ const removedFields = [];
563
+ const preservedEffectFields = new Set();
564
+ const refusedEffectfulInitializerFields = [];
565
+ analysis.fields.forEach((field) => {
566
+ if (field.live || field.definitions.length === 0)
567
+ return;
568
+ const canRemove = field.definitions.every((definition) => {
569
+ if (definition.kind === "table-field")
570
+ return discardable(definition.module.name, definition.value);
571
+ if (definition.kind === "member-function")
572
+ return true;
573
+ return (definition.statement.variables.length === 1 &&
574
+ definition.statement.init.length === 1 &&
575
+ (discardable(definition.module.name, definition.value) ||
576
+ isCallExpression(definition.value)));
577
+ });
578
+ if (!canRemove) {
579
+ refusedEffectfulInitializerFields.push(field);
580
+ return;
581
+ }
582
+ const planned = [];
583
+ field.definitions.forEach((definition) => {
584
+ if (definition.kind === "table-field") {
585
+ planned.push(() => {
586
+ const removed = removedTableFields.get(definition.table) ?? new Set();
587
+ removed.add(definition.field);
588
+ removedTableFields.set(definition.table, removed);
589
+ });
590
+ return;
591
+ }
592
+ if (definition.kind === "member-function") {
593
+ planned.push(() => replacements.set(definition.statement, []));
594
+ return;
595
+ }
596
+ if (definition.statement.variables.length !== 1 ||
597
+ definition.statement.init.length !== 1) {
598
+ return;
599
+ }
600
+ if (discardable(definition.module.name, definition.value)) {
601
+ planned.push(() => replacements.set(definition.statement, []));
602
+ }
603
+ else if (isCallExpression(definition.value)) {
604
+ planned.push(() => {
605
+ replacements.set(definition.statement, [
606
+ callStatement(definition.value),
607
+ ]);
608
+ preservedEffectFields.add(field);
609
+ });
610
+ }
611
+ else {
612
+ return;
613
+ }
614
+ });
615
+ planned.forEach((apply) => {
616
+ apply();
617
+ });
618
+ removedFields.push(field);
619
+ });
620
+ removedTableFields.forEach((removed, table) => {
621
+ table.fields = table.fields.filter((field) => field.type === "TableValue" || !removed.has(field));
622
+ });
623
+ analysis.fields.forEach((field) => {
624
+ field.definitions.forEach((definition) => {
625
+ const replacement = replacements.get(definition.statement);
626
+ if (replacement === undefined)
627
+ return;
628
+ const body = definition.body;
629
+ const index = body.indexOf(definition.statement);
630
+ if (index < 0)
631
+ return;
632
+ const metadata = metadataOf(definition.module.name);
633
+ if (replacement.length > 0)
634
+ metadata.replaceStatement(definition.statement, replacement);
635
+ else
636
+ metadata.removeStatement(definition.statement, body[index + 1]);
637
+ body.splice(index, 1, ...replacement);
638
+ });
639
+ });
640
+ return {
641
+ changed: removedFields.length > 0,
642
+ removedFields,
643
+ preservedEffectFields: [...preservedEffectFields],
644
+ refusedEffectfulInitializerFields,
645
+ };
646
+ }