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,372 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.combineCallGraphs = combineCallGraphs;
4
+ exports.analyzeCallGraph = analyzeCallGraph;
5
+ /** Combine generation-matched module graphs and replace proved method unknown edges. */
6
+ function combineCallGraphs(graphs, resolvedTargets, generation) {
7
+ if (graphs.some((graph) => graph.generation !== generation))
8
+ throw new Error("Cannot combine call graphs from different AST generations");
9
+ const functions = [...new Set(graphs.flatMap((graph) => graph.functions))];
10
+ const calls = [];
11
+ const callSiteByExpression = new WeakMap();
12
+ graphs
13
+ .flatMap((graph) => graph.calls)
14
+ .forEach((original) => {
15
+ const resolved = resolvedTargets.get(original);
16
+ const targets = resolved
17
+ ? new Set([...original.targets, resolved])
18
+ : original.targets;
19
+ const call = {
20
+ ...original,
21
+ id: calls.length,
22
+ targets,
23
+ hasUnknownTarget: resolved ? false : original.hasUnknownTarget,
24
+ };
25
+ calls.push(call);
26
+ callSiteByExpression.set(call.call, call);
27
+ });
28
+ const sccs = stronglyConnectedComponents(functions, calls);
29
+ return {
30
+ generation,
31
+ functions,
32
+ calls,
33
+ sccs,
34
+ functionOf: (declaration) => {
35
+ for (const graph of graphs) {
36
+ const found = graph.functionOf(declaration);
37
+ if (found)
38
+ return found;
39
+ }
40
+ return undefined;
41
+ },
42
+ functionOfSymbol: (symbol) => {
43
+ for (const graph of graphs) {
44
+ const found = graph.functionOfSymbol(symbol);
45
+ if (found)
46
+ return found;
47
+ }
48
+ return undefined;
49
+ },
50
+ callSiteOf: (call) => callSiteByExpression.get(call),
51
+ };
52
+ }
53
+ /**
54
+ * Resolve identityに基づくmodule-local call graphを構築する。
55
+ *
56
+ * 名前文字列やsource rangeからcall targetを推測しない。現行のoptimizer snapshotは
57
+ * module単位なので、global/module/external edgeはknown targetと偽らずunknown bitとして
58
+ * 保持する。local aliasは単一代入の関数値だけを有限固定点で解決する。
59
+ */
60
+ function analyzeCallGraph(chunk, resolved, facts) {
61
+ const functions = [];
62
+ const functionByDeclaration = new WeakMap();
63
+ const functionBySymbol = new Map();
64
+ const ownerFunction = new WeakMap();
65
+ const directFunctionBySymbol = new Map();
66
+ const aliasSources = new Map();
67
+ const assignmentOriginBySymbol = new Map();
68
+ const conflictingBindings = new Set();
69
+ const registerFunction = (declaration, symbol) => {
70
+ const existing = functionByDeclaration.get(declaration);
71
+ if (existing)
72
+ return existing;
73
+ const scope = resolved.scopeOfFunction(declaration);
74
+ const callable = {
75
+ id: functions.length,
76
+ declaration,
77
+ ...(symbol ? { symbol } : {}),
78
+ parameters: scope?.symbols.filter((candidate) => candidate.kind === "param") ?? [],
79
+ };
80
+ functions.push(callable);
81
+ functionByDeclaration.set(declaration, callable);
82
+ if (symbol)
83
+ directFunctionBySymbol.set(symbol, callable);
84
+ return callable;
85
+ };
86
+ const visitExpression = (expression, current) => {
87
+ switch (expression.type) {
88
+ case "FunctionDeclaration": {
89
+ const callable = registerFunction(expression);
90
+ visitBlock(expression.body, callable);
91
+ return;
92
+ }
93
+ case "CallExpression":
94
+ visitExpression(expression.base, current);
95
+ expression.arguments.forEach((argument) => {
96
+ visitExpression(argument, current);
97
+ });
98
+ return;
99
+ case "TableCallExpression":
100
+ visitExpression(expression.base, current);
101
+ visitExpression(expression.arguments, current);
102
+ return;
103
+ case "StringCallExpression":
104
+ visitExpression(expression.base, current);
105
+ visitExpression(expression.argument, current);
106
+ return;
107
+ case "BinaryExpression":
108
+ case "LogicalExpression":
109
+ visitExpression(expression.left, current);
110
+ visitExpression(expression.right, current);
111
+ return;
112
+ case "UnaryExpression":
113
+ visitExpression(expression.argument, current);
114
+ return;
115
+ case "MemberExpression":
116
+ visitExpression(expression.base, current);
117
+ return;
118
+ case "IndexExpression":
119
+ visitExpression(expression.base, current);
120
+ visitExpression(expression.index, current);
121
+ return;
122
+ case "TableConstructorExpression":
123
+ expression.fields.forEach((field) => {
124
+ if (field.type === "TableKey")
125
+ visitExpression(field.key, current);
126
+ visitExpression(field.value, current);
127
+ });
128
+ return;
129
+ case "Identifier":
130
+ case "NilLiteral":
131
+ case "BooleanLiteral":
132
+ case "NumericLiteral":
133
+ case "StringLiteral":
134
+ case "VarargLiteral":
135
+ return;
136
+ }
137
+ };
138
+ const rememberBinding = (target, expression, assignment = false) => {
139
+ const symbol = resolved.symbolOf(target);
140
+ if (!symbol || !expression)
141
+ return;
142
+ if (directFunctionBySymbol.has(symbol) ||
143
+ aliasSources.has(symbol) ||
144
+ assignmentOriginBySymbol.has(symbol))
145
+ conflictingBindings.add(symbol);
146
+ if (assignment)
147
+ assignmentOriginBySymbol.set(symbol, target);
148
+ if (expression.type === "FunctionDeclaration") {
149
+ directFunctionBySymbol.set(symbol, registerFunction(expression, symbol));
150
+ return;
151
+ }
152
+ if (expression.type !== "Identifier")
153
+ return;
154
+ const source = resolved.symbolOf(expression);
155
+ if (source)
156
+ aliasSources.set(symbol, source);
157
+ };
158
+ function visitStatement(statement, current) {
159
+ if (current)
160
+ ownerFunction.set(statement, current);
161
+ switch (statement.type) {
162
+ case "LocalStatement":
163
+ statement.variables.forEach((target, index) => {
164
+ rememberBinding(target, statement.init[index]);
165
+ });
166
+ statement.init.forEach((expression) => {
167
+ visitExpression(expression, current);
168
+ });
169
+ return;
170
+ case "AssignmentStatement":
171
+ statement.variables.forEach((target, index) => {
172
+ if (target.type === "Identifier")
173
+ rememberBinding(target, statement.init[index], true);
174
+ });
175
+ statement.init.forEach((expression) => {
176
+ visitExpression(expression, current);
177
+ });
178
+ return;
179
+ case "FunctionDeclaration": {
180
+ const symbol = statement.identifier?.type === "Identifier"
181
+ ? resolved.symbolOf(statement.identifier)
182
+ : undefined;
183
+ const callable = registerFunction(statement, symbol);
184
+ visitBlock(statement.body, callable);
185
+ return;
186
+ }
187
+ case "CallStatement":
188
+ visitExpression(statement.expression, current);
189
+ return;
190
+ case "ReturnStatement":
191
+ statement.arguments.forEach((expression) => {
192
+ visitExpression(expression, current);
193
+ });
194
+ return;
195
+ case "DoStatement":
196
+ visitBlock(statement.body, current);
197
+ return;
198
+ case "WhileStatement":
199
+ visitExpression(statement.condition, current);
200
+ visitBlock(statement.body, current);
201
+ return;
202
+ case "RepeatStatement":
203
+ visitBlock(statement.body, current);
204
+ visitExpression(statement.condition, current);
205
+ return;
206
+ case "IfStatement":
207
+ statement.clauses.forEach((clause) => {
208
+ if (clause.type !== "ElseClause")
209
+ visitExpression(clause.condition, current);
210
+ visitBlock(clause.body, current);
211
+ });
212
+ return;
213
+ case "ForNumericStatement":
214
+ visitExpression(statement.start, current);
215
+ visitExpression(statement.end, current);
216
+ if (statement.step)
217
+ visitExpression(statement.step, current);
218
+ visitBlock(statement.body, current);
219
+ return;
220
+ case "ForGenericStatement":
221
+ statement.iterators.forEach((expression) => {
222
+ visitExpression(expression, current);
223
+ });
224
+ visitBlock(statement.body, current);
225
+ return;
226
+ case "BreakStatement":
227
+ case "LabelStatement":
228
+ case "GotoStatement":
229
+ return;
230
+ }
231
+ }
232
+ function visitBlock(body, current) {
233
+ body.forEach((statement) => {
234
+ visitStatement(statement, current);
235
+ });
236
+ }
237
+ visitBlock(chunk.body);
238
+ const isStableBinding = (symbol) => {
239
+ if (conflictingBindings.has(symbol))
240
+ return false;
241
+ const writes = facts
242
+ .operationsOfSymbol(symbol)
243
+ .filter((operation) => operation.kind === "write");
244
+ const assignmentOrigin = assignmentOriginBySymbol.get(symbol);
245
+ return assignmentOrigin
246
+ ? writes.length === 1 && writes[0].origin === assignmentOrigin
247
+ : writes.length === 0;
248
+ };
249
+ directFunctionBySymbol.forEach((callable, symbol) => {
250
+ if (isStableBinding(symbol))
251
+ functionBySymbol.set(symbol, callable);
252
+ });
253
+ let changed = true;
254
+ while (changed) {
255
+ changed = false;
256
+ aliasSources.forEach((source, target) => {
257
+ if (functionBySymbol.has(target))
258
+ return;
259
+ if (!isStableBinding(target) || !isStableBinding(source))
260
+ return;
261
+ const callable = functionBySymbol.get(source);
262
+ if (!callable)
263
+ return;
264
+ functionBySymbol.set(target, callable);
265
+ changed = true;
266
+ });
267
+ }
268
+ const calls = [];
269
+ const callSiteByExpression = new WeakMap();
270
+ facts.operations.forEach((operation) => {
271
+ if (operation.kind !== "call")
272
+ return;
273
+ const targets = new Set();
274
+ if (operation.target.kind === "local" ||
275
+ operation.target.kind === "parameter" ||
276
+ operation.target.kind === "upvalue") {
277
+ const target = functionBySymbol.get(operation.target.symbol);
278
+ if (target)
279
+ targets.add(target);
280
+ }
281
+ const site = {
282
+ id: calls.length,
283
+ call: operation.call,
284
+ owner: operation.owner,
285
+ caller: ownerFunction.get(operation.owner),
286
+ targets,
287
+ hasUnknownTarget: targets.size === 0,
288
+ ...(operation.call.base.type === "Identifier" &&
289
+ (operation.target.kind === "global" ||
290
+ operation.target.kind === "external")
291
+ ? { externalTargetName: operation.call.base.name }
292
+ : {}),
293
+ };
294
+ calls.push(site);
295
+ callSiteByExpression.set(operation.call, site);
296
+ });
297
+ const sccs = stronglyConnectedComponents(functions, calls);
298
+ return {
299
+ generation: facts.generation,
300
+ functions,
301
+ calls,
302
+ sccs,
303
+ functionOf: (declaration) => functionByDeclaration.get(declaration),
304
+ functionOfSymbol: (symbol) => functionBySymbol.get(symbol),
305
+ callSiteOf: (call) => callSiteByExpression.get(call),
306
+ };
307
+ }
308
+ function stronglyConnectedComponents(functions, calls) {
309
+ const edges = new Map(functions.map((callable) => [callable, new Set()]));
310
+ calls.forEach((call) => {
311
+ if (!call.caller)
312
+ return;
313
+ const outgoing = edges.get(call.caller);
314
+ call.targets.forEach((target) => outgoing?.add(target));
315
+ });
316
+ let nextIndex = 0;
317
+ const index = new Map();
318
+ const lowlink = new Map();
319
+ const stack = [];
320
+ const onStack = new Set();
321
+ const components = [];
322
+ const connect = (callable) => {
323
+ index.set(callable, nextIndex);
324
+ lowlink.set(callable, nextIndex++);
325
+ stack.push(callable);
326
+ onStack.add(callable);
327
+ edges.get(callable)?.forEach((target) => {
328
+ if (!index.has(target)) {
329
+ connect(target);
330
+ const callableLowlink = lowlink.get(callable);
331
+ const targetLowlink = lowlink.get(target);
332
+ if (callableLowlink === undefined || targetLowlink === undefined)
333
+ throw new Error("Tarjan lowlink is missing");
334
+ lowlink.set(callable, Math.min(callableLowlink, targetLowlink));
335
+ }
336
+ else if (onStack.has(target)) {
337
+ const callableLowlink = lowlink.get(callable);
338
+ const targetIndex = index.get(target);
339
+ if (callableLowlink === undefined || targetIndex === undefined)
340
+ throw new Error("Tarjan index is missing");
341
+ lowlink.set(callable, Math.min(callableLowlink, targetIndex));
342
+ }
343
+ });
344
+ if (lowlink.get(callable) !== index.get(callable))
345
+ return;
346
+ const component = [];
347
+ while (stack.length > 0) {
348
+ const member = stack.pop();
349
+ if (!member)
350
+ throw new Error("Tarjan stack underflow");
351
+ onStack.delete(member);
352
+ component.push(member);
353
+ if (member === callable)
354
+ break;
355
+ }
356
+ components.push(component.sort((left, right) => left.id - right.id));
357
+ };
358
+ functions.forEach((callable) => {
359
+ if (!index.has(callable))
360
+ connect(callable);
361
+ });
362
+ return components
363
+ .sort((left, right) => left[0].id - right[0].id)
364
+ .map((component, id) => ({
365
+ id,
366
+ functions: component,
367
+ recursive: component.length > 1 ||
368
+ (component.length === 1 &&
369
+ edges.get(component[0])?.has(component[0])) ||
370
+ false,
371
+ }));
372
+ }
package/dist/cli.js CHANGED
@@ -6,21 +6,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
- const commander_1 = require("commander");
10
9
  const minifier_1 = require("./minifier");
11
10
  const output_1 = require("./output");
12
- const program = new commander_1.Command();
13
- program
14
- .version("0.3.0")
15
- .description("A Lua minifier also outputs source map")
16
- .option("-m, --module-like-lua", "require・dofileの動作を実際のLuaに近づけます")
17
- .option("--no-rename", "識別子の短縮(リネーム)を無効にします(デバッグ用途)")
18
- .option("--no-global-rename", "内部でのみ使用するグローバル識別子の短縮を無効にします(デバッグ用途)")
19
- .option("--no-merge-locals", "連続するローカル変数宣言のまとめ上げを無効にします(デバッグ用途)")
20
- .option("--no-global-alias", "外部グローバル識別子(リネームできないもの)のローカル代入短縮を無効にします(デバッグ用途)")
21
- .option("--reserved-globals-config <path>", '代入されていても短縮しないグローバル名を列挙したJSON設定ファイルのパス({"neverRenameGlobals":["onTick",...]}形式)。エンジン側のコールバック規約名など、常に元の名前のまま残す必要がある識別子を指定します')
22
- .option("--single-line-source-mapping-url", "sourceMappingURLアノテーションを単一行の--コメントで出力します(Source Map仕様の「最終行」ルールに従いますが、既定の複数行ブロックコメント形式を前提とするツールとは組み合わせられません)")
23
- .option("--strict-source-mapping-url", "sourceMappingURLアノテーションをLuaコメントで一切包まず、Source Map仕様のマーカー文字列(//# sourceMappingURL=...)そのままを出力します。Luaの文法上この形式と有効なLuaコードは両立できないため、出力ファイルの最終行は有効なLua文ではなくなります");
11
+ const cliOptions_1 = require("./cliOptions");
12
+ const config_1 = require("./config");
13
+ const options_1 = require("./options");
14
+ const cliProgress_1 = require("./cliProgress");
15
+ const program = (0, cliOptions_1.createCliProgram)();
24
16
  program.parse(process.argv);
25
17
  const luaFiles = program.args;
26
18
  const luaparseSetting = {
@@ -29,53 +21,56 @@ const luaparseSetting = {
29
21
  ranges: true,
30
22
  scope: true,
31
23
  };
32
- function isReservedGlobalsConfig(value) {
33
- if (typeof value !== "object" || value === null) {
34
- return false;
35
- }
36
- const candidate = value;
37
- return (Array.isArray(candidate.neverRenameGlobals) &&
38
- candidate.neverRenameGlobals.every((name) => typeof name === "string"));
39
- }
40
- function loadNeverRenameGlobals(configPath) {
41
- if (!fs_1.default.existsSync(configPath)) {
42
- throw new Error("Reserved globals config not found: " + configPath);
43
- }
44
- const parsed = JSON.parse(fs_1.default.readFileSync(configPath).toString());
45
- if (!isReservedGlobalsConfig(parsed)) {
46
- throw new Error(configPath +
47
- ' must be a JSON object of the form {"neverRenameGlobals": ["name", ...]}');
48
- }
49
- return new Set(parsed.neverRenameGlobals);
50
- }
51
- const { singleLineSourceMappingUrl, strictSourceMappingUrl, reservedGlobalsConfig, ...mode } = program.opts();
52
- if (reservedGlobalsConfig) {
53
- mode.neverRenameGlobals = loadNeverRenameGlobals(reservedGlobalsConfig);
54
- }
55
- // 既定は旧バージョンと互換の複数行ブロックコメント("legacy")。
56
- // --strict-source-mapping-url > --single-line-source-mapping-url の優先順で上書きする。
57
- const sourceMappingUrlStyle = strictSourceMappingUrl
58
- ? "strict"
59
- : singleLineSourceMappingUrl
60
- ? "line"
61
- : "legacy";
62
- luaFiles.forEach((fileName) => {
24
+ const { config: configPath, neverRenameGlobal, progress: progressOption, requiredWhitespace, sourceMappingUrlStyle: cliSourceMappingUrlStyle, ...cliModeOptions } = program.opts();
25
+ const cliMode = cliModeOptions;
26
+ if (neverRenameGlobal !== undefined)
27
+ cliMode.neverRenameGlobals = new Set(neverRenameGlobal);
28
+ if (requiredWhitespace !== undefined)
29
+ cliMode.requiredWhitespace = requiredWhitespace === "space" ? " " : "\n";
30
+ const configuration = configPath
31
+ ? (0, config_1.loadConfiguration)(configPath)
32
+ : { mode: {} };
33
+ const mode = (0, options_1.resolveMinifierMode)({
34
+ config: configuration.mode,
35
+ cli: cliMode,
36
+ defaults: { requireWrapper: false, runtimeProfile: "stormworks" },
37
+ });
38
+ const sourceMappingUrlStyle = cliSourceMappingUrlStyle ?? configuration.sourceMappingUrlStyle ?? "legacy";
39
+ luaFiles.forEach((fileName, fileIndex) => {
63
40
  const parsedFileName = path_1.default.parse(fileName);
64
41
  if (fs_1.default.existsSync(fileName)) {
65
- const map = new minifier_1.Minifier(fileName, luaparseSetting, mode).parse();
66
- const minFileName = path_1.default.format({
67
- dir: parsedFileName.dir,
68
- name: parsedFileName.name + ".min",
69
- ext: ".lua",
70
- });
71
- const mapFileName = path_1.default.format({
72
- dir: parsedFileName.dir,
73
- name: parsedFileName.name,
74
- ext: parsedFileName.ext + ".map",
75
- });
76
- const { code, map: mapJson } = (0, output_1.buildMinifiedOutput)(map, minFileName, mapFileName, { sourceMappingUrlStyle });
77
- fs_1.default.writeFileSync(minFileName, code);
78
- fs_1.default.writeFileSync(mapFileName, mapJson);
42
+ const progress = (0, cliProgress_1.progressEnabled)(progressOption, process.stderr)
43
+ ? new cliProgress_1.CliProgress({
44
+ fileName,
45
+ fileIndex: fileIndex + 1,
46
+ fileCount: luaFiles.length,
47
+ output: process.stderr,
48
+ })
49
+ : undefined;
50
+ const startedAt = performance.now();
51
+ try {
52
+ const map = new minifier_1.Minifier(fileName, luaparseSetting, mode, progress).parse();
53
+ const minFileName = path_1.default.format({
54
+ dir: parsedFileName.dir,
55
+ name: parsedFileName.name + ".min",
56
+ ext: ".lua",
57
+ });
58
+ const mapFileName = path_1.default.format({
59
+ dir: parsedFileName.dir,
60
+ name: parsedFileName.name,
61
+ ext: parsedFileName.ext + ".map",
62
+ });
63
+ progress?.addSteps(1);
64
+ progress?.startStep("Write Lua and source map files");
65
+ const { code, map: mapJson } = (0, output_1.buildMinifiedOutput)(map, minFileName, mapFileName, { sourceMappingUrlStyle });
66
+ fs_1.default.writeFileSync(minFileName, code);
67
+ fs_1.default.writeFileSync(mapFileName, mapJson);
68
+ progress?.finish([minFileName, mapFileName], performance.now() - startedAt);
69
+ }
70
+ catch (error) {
71
+ progress?.fail();
72
+ throw error;
73
+ }
79
74
  }
80
75
  else {
81
76
  console.error("No such file: " + fileName);
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCliProgram = createCliProgram;
4
+ const commander_1 = require("commander");
5
+ const options_1 = require("./options");
6
+ function addBooleanSwitch(command, name, description, shortName) {
7
+ command.addOption(new commander_1.Option(shortName ? `${shortName}, --${name}` : `--${name}`, description).default(undefined));
8
+ command.addOption(new commander_1.Option(`--no-${name}`, `Disable ${description.toLowerCase()}`).default(undefined));
9
+ }
10
+ /** Build the v1 CLI surface without materializing inherited defaults. */
11
+ function createCliProgram() {
12
+ const command = new commander_1.Command()
13
+ .version("0.9.0")
14
+ .description("A Lua minifier also outputs source map")
15
+ .option("--config <path>", "JSON configuration file")
16
+ .addOption(new commander_1.Option("--runtime-profile <profile>", "Target runtime semantics")
17
+ .choices(["stormworks", "lua53"])
18
+ .default(undefined))
19
+ .addOption(new commander_1.Option("--required-whitespace <style>", "Required token separator style")
20
+ .choices(["space", "lf"])
21
+ .default(undefined))
22
+ .addOption(new commander_1.Option("--never-rename-global <name>", "Global name that must remain externally visible (repeatable)").argParser((name, names) => [...(names ?? []), name]))
23
+ .addOption(new commander_1.Option("--source-mapping-url-style <style>", "sourceMappingURL output style")
24
+ .choices(["legacy", "line", "strict"])
25
+ .default(undefined));
26
+ addBooleanSwitch(command, "progress", "Show compilation progress");
27
+ addBooleanSwitch(command, "require-wrapper", "Expand require through a generated function wrapper", "-m");
28
+ options_1.optimizationOptionDefinitions.forEach((definition) => {
29
+ addBooleanSwitch(command, definition.name, `Enable ${definition.name}`);
30
+ });
31
+ addBooleanSwitch(command, "allow-introspection-changes", "Allow changes observable through debug introspection");
32
+ addBooleanSwitch(command, "allow-observable-table-read-changes", "Allow table reads to cross writes that may change their values");
33
+ addBooleanSwitch(command, "assume-annotations", "Trust supported EmmyLua annotations as optimizer facts");
34
+ addBooleanSwitch(command, "collect-optimization-diagnostics", "Collect optimizer decision diagnostics");
35
+ return command;
36
+ }
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CliProgress = exports.CLI_PROGRESS_INTERVAL_MS = void 0;
7
+ exports.progressEnabled = progressEnabled;
8
+ const path_1 = __importDefault(require("path"));
9
+ const SPINNER_FRAMES = ["|", "/", "-", "\\"];
10
+ /** Minimum elapsed time between spinner frames; change and rebuild to tune it. */
11
+ exports.CLI_PROGRESS_INTERVAL_MS = 200;
12
+ class CliProgress {
13
+ displayName;
14
+ filePosition;
15
+ output;
16
+ now;
17
+ tty;
18
+ currentStep = 0;
19
+ totalSteps = 0;
20
+ label = "Starting";
21
+ spinnerIndex = 0;
22
+ lastRenderAt;
23
+ activeLine = false;
24
+ constructor(options) {
25
+ this.displayName = path_1.default.basename(options.fileName);
26
+ this.filePosition =
27
+ String(options.fileIndex) + "/" + String(options.fileCount) + " files";
28
+ this.output = options.output;
29
+ this.now = options.now ?? (() => performance.now());
30
+ this.tty = options.output.isTTY === true;
31
+ }
32
+ addSteps(count) {
33
+ if (!Number.isInteger(count) || count < 0)
34
+ throw new Error("Progress step count must be a non-negative integer");
35
+ this.totalSteps += count;
36
+ }
37
+ startStep(label) {
38
+ this.currentStep++;
39
+ this.label = label;
40
+ if (this.currentStep > this.totalSteps)
41
+ this.totalSteps = this.currentStep;
42
+ if (this.tty)
43
+ this.render(this.now());
44
+ else
45
+ this.output.write(this.line() + "\n");
46
+ }
47
+ tick() {
48
+ if (!this.tty || this.lastRenderAt === undefined)
49
+ return;
50
+ const now = this.now();
51
+ if (now - this.lastRenderAt < exports.CLI_PROGRESS_INTERVAL_MS)
52
+ return;
53
+ this.spinnerIndex = (this.spinnerIndex + 1) % SPINNER_FRAMES.length;
54
+ this.render(now);
55
+ }
56
+ finish(outputFiles, elapsedMs) {
57
+ const destinations = outputFiles.join(", ");
58
+ const line = `[done] [${this.filePosition}] ${this.displayName} (${formatDuration(elapsedMs)}) -> ${destinations}`;
59
+ this.writeFinalLine(line);
60
+ }
61
+ fail() {
62
+ this.writeFinalLine(`[failed] [${this.filePosition}] ${this.displayName} — ${this.label}`);
63
+ }
64
+ line() {
65
+ return `${SPINNER_FRAMES[this.spinnerIndex]} [${this.filePosition}] ${this.displayName} — Step ${String(this.currentStep)}/${String(this.totalSteps)}: ${this.label}`;
66
+ }
67
+ render(now) {
68
+ this.output.write(`\r\x1b[2K${this.line()}`);
69
+ this.lastRenderAt = now;
70
+ this.activeLine = true;
71
+ }
72
+ writeFinalLine(line) {
73
+ if (this.tty && this.activeLine)
74
+ this.output.write("\r\x1b[2K");
75
+ this.output.write(line + "\n");
76
+ this.activeLine = false;
77
+ }
78
+ }
79
+ exports.CliProgress = CliProgress;
80
+ function formatDuration(elapsedMs) {
81
+ if (elapsedMs < 1000)
82
+ return `${String(Math.round(elapsedMs))} ms`;
83
+ return `${(elapsedMs / 1000).toFixed(1)} s`;
84
+ }
85
+ function progressEnabled(explicit, output) {
86
+ return explicit ?? output.isTTY === true;
87
+ }