storm-lua-minify 0.2.0 → 0.3.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 (81) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +79 -37
  3. package/dist/ast2lua.js +67 -14
  4. package/dist/cli.js +28 -2
  5. package/dist/globalRename.js +35 -0
  6. package/dist/keywordLocator.js +68 -0
  7. package/dist/linker.js +13 -0
  8. package/dist/minifier.js +75 -1
  9. package/dist/renamer.js +16 -2
  10. package/dist/resolver.js +10 -4
  11. package/dist/transform.js +401 -0
  12. package/package.json +41 -33
  13. package/.github/workflows/ci.yml +0 -39
  14. package/.github/workflows/publish.yml +0 -39
  15. package/.prettierignore +0 -3
  16. package/.prettierrc.json +0 -1
  17. package/eslint.config.js +0 -29
  18. package/src/ast2lua.ts +0 -940
  19. package/src/cli.ts +0 -86
  20. package/src/linker.ts +0 -108
  21. package/src/minifier.ts +0 -284
  22. package/src/output.ts +0 -71
  23. package/src/renamer.ts +0 -134
  24. package/src/resolver.ts +0 -378
  25. package/test/circular-require.test.ts +0 -19
  26. package/test/fixtures/bare-require/main.lua +0 -2
  27. package/test/fixtures/bare-require/mod.lua +0 -1
  28. package/test/fixtures/bitwise-precedence/main.lua +0 -10
  29. package/test/fixtures/circular-require/a.lua +0 -2
  30. package/test/fixtures/circular-require/b.lua +0 -2
  31. package/test/fixtures/circular-require/main.lua +0 -2
  32. package/test/fixtures/dofile/greet.lua +0 -1
  33. package/test/fixtures/dofile/main.lua +0 -2
  34. package/test/fixtures/entry-scope-many-requires/dep_alpha.lua +0 -2
  35. package/test/fixtures/entry-scope-many-requires/dep_bravo.lua +0 -2
  36. package/test/fixtures/entry-scope-many-requires/dep_charlie.lua +0 -2
  37. package/test/fixtures/entry-scope-many-requires/dep_delta.lua +0 -2
  38. package/test/fixtures/entry-scope-many-requires/dep_echo.lua +0 -2
  39. package/test/fixtures/entry-scope-many-requires/dep_foxtrot.lua +0 -2
  40. package/test/fixtures/entry-scope-many-requires/dep_golf.lua +0 -2
  41. package/test/fixtures/entry-scope-many-requires/dep_hotel.lua +0 -2
  42. package/test/fixtures/entry-scope-many-requires/dep_india.lua +0 -2
  43. package/test/fixtures/entry-scope-many-requires/dep_juliet.lua +0 -2
  44. package/test/fixtures/entry-scope-many-requires/dep_kilo.lua +0 -2
  45. package/test/fixtures/entry-scope-many-requires/main.lua +0 -25
  46. package/test/fixtures/multi-require/common.lua +0 -1
  47. package/test/fixtures/multi-require/main.lua +0 -3
  48. package/test/fixtures/nested-module/main.lua +0 -2
  49. package/test/fixtures/nested-module/sub/deep.lua +0 -1
  50. package/test/fixtures/require-call/main.lua +0 -2
  51. package/test/fixtures/require-call/mod.lua +0 -5
  52. package/test/fixtures/require-in-expression/main.lua +0 -1
  53. package/test/fixtures/require-in-expression/mod.lua +0 -1
  54. package/test/fixtures/require-string-call/main.lua +0 -2
  55. package/test/fixtures/require-string-call/mod.lua +0 -5
  56. package/test/fixtures/single-file/main.lua +0 -15
  57. package/test/identifier-collision.test.ts +0 -28
  58. package/test/lib/collision.ts +0 -131
  59. package/test/lib/helpers.ts +0 -124
  60. package/test/no-rename.test.ts +0 -19
  61. package/test/output.test.ts +0 -95
  62. package/test/precedence.test.ts +0 -28
  63. package/test/renamer.test.ts +0 -130
  64. package/test/resolver.test.ts +0 -206
  65. package/test/roundtrip.test.ts +0 -26
  66. package/test/snapshot.test.ts +0 -27
  67. package/test/snapshots/bare-require.sl.lua +0 -1
  68. package/test/snapshots/bitwise-precedence.sl.lua +0 -2
  69. package/test/snapshots/dofile.sl.lua +0 -1
  70. package/test/snapshots/entry-scope-many-requires.m.lua +0 -14
  71. package/test/snapshots/multi-require.m.lua +0 -4
  72. package/test/snapshots/multi-require.sl.lua +0 -1
  73. package/test/snapshots/nested-module.m.lua +0 -4
  74. package/test/snapshots/require-call.m.lua +0 -5
  75. package/test/snapshots/require-call.sl.lua +0 -1
  76. package/test/snapshots/require-in-expression.sl.lua +0 -1
  77. package/test/snapshots/require-string-call.m.lua +0 -5
  78. package/test/snapshots/single-file.sl.lua +0 -6
  79. package/test/sourcemap.test.ts +0 -105
  80. package/tsconfig.eslint.json +0 -8
  81. package/tsconfig.json +0 -111
package/src/ast2lua.ts DELETED
@@ -1,940 +0,0 @@
1
- // based on "luamin": Copyright Mathias Bynens <https://mathiasbynens.be/>
2
- // SPDX-License-Identifier: MIT
3
-
4
- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
5
- import Parser, { Comment } from "luaparse";
6
- import { SourceNode } from "source-map";
7
- import { Minifier, MinifierMode } from "./minifier";
8
- import { staticStringArgument } from "./linker";
9
-
10
- export type Chunk = Parser.Chunk & {
11
- globals?: (Parser.Base<"Identifer"> & {
12
- name: string;
13
- isLocal: boolean;
14
- })[];
15
- comments?: Comment[];
16
- };
17
-
18
- const PRECEDENCE: Record<string, number> = {
19
- or: 1,
20
- and: 2,
21
- "<": 3,
22
- ">": 3,
23
- "<=": 3,
24
- ">=": 3,
25
- "~=": 3,
26
- "==": 3,
27
- "|": 4,
28
- "~": 5, // binary ~ (bxor)
29
- "&": 6,
30
- "<<": 7,
31
- ">>": 7,
32
- "..": 9,
33
- "+": 10,
34
- "-": 10, // binary -
35
- "*": 11,
36
- "/": 11,
37
- "//": 11,
38
- "%": 11,
39
- unarynot: 12,
40
- "unary#": 12,
41
- "unary-": 12, // unary -
42
- "unary~": 12, // unary ~ (bnot)
43
- "^": 14,
44
- };
45
-
46
- export const IDENTIFIER_PARTS = [
47
- "a",
48
- "b",
49
- "c",
50
- "d",
51
- "e",
52
- "f",
53
- "g",
54
- "h",
55
- "i",
56
- "j",
57
- "k",
58
- "l",
59
- "m",
60
- "n",
61
- "o",
62
- "p",
63
- "q",
64
- "r",
65
- "s",
66
- "t",
67
- "u",
68
- "v",
69
- "w",
70
- "x",
71
- "y",
72
- "z",
73
- "A",
74
- "B",
75
- "C",
76
- "D",
77
- "E",
78
- "F",
79
- "G",
80
- "H",
81
- "I",
82
- "J",
83
- "K",
84
- "L",
85
- "M",
86
- "N",
87
- "O",
88
- "P",
89
- "Q",
90
- "R",
91
- "S",
92
- "T",
93
- "U",
94
- "V",
95
- "W",
96
- "X",
97
- "Y",
98
- "Z",
99
- "_",
100
- ];
101
-
102
- function wrapArray<T>(obj: T | T[]): T[] {
103
- if (Array.isArray(obj)) {
104
- return obj;
105
- }
106
- return [obj];
107
- }
108
-
109
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- 現時点では未参照だが、オリジナル(luamin)由来のコードのため残置
110
- function generateZeroes(length: number) {
111
- let zero = "0";
112
- let result = "";
113
- if (length < 1) {
114
- return result;
115
- }
116
- if (length == 1) {
117
- return zero;
118
- }
119
- while (length) {
120
- if (length & 1) {
121
- result += zero;
122
- }
123
- if ((length >>= 1)) {
124
- zero += zero;
125
- }
126
- }
127
- return result;
128
- }
129
-
130
- export function isKeyword(id: string) {
131
- switch (id.length) {
132
- case 2:
133
- return "do" == id || "if" == id || "in" == id || "or" == id;
134
- case 3:
135
- return (
136
- "and" == id || "end" == id || "for" == id || "nil" == id || "not" == id
137
- );
138
- case 4:
139
- return "else" == id || "goto" == id || "then" == id || "true" == id;
140
- case 5:
141
- return (
142
- "break" == id ||
143
- "false" == id ||
144
- "local" == id ||
145
- "until" == id ||
146
- "while" == id
147
- );
148
- case 6:
149
- return "elseif" == id || "repeat" == id || "return" == id;
150
- case 8:
151
- return "function" == id;
152
- }
153
- return false;
154
- }
155
-
156
- function isNeedSeparator(a: string, b: string) {
157
- const lastCharA = a.slice(-1);
158
- const firstCharB = b.charAt(0);
159
-
160
- const regexAlphaUnderscore = /[a-zA-Z_]/;
161
- const regexAlphaNumUnderscore = /[a-zA-Z0-9_]/;
162
- const regexDigits = /[0-9]/;
163
-
164
- if (lastCharA == "" || firstCharB == "") {
165
- return false;
166
- }
167
- if (regexAlphaUnderscore.test(lastCharA)) {
168
- if (regexAlphaNumUnderscore.test(firstCharB)) {
169
- // e.g. `while` + `1`
170
- // e.g. `local a` + `local b`
171
- return true;
172
- } else {
173
- // e.g. `not` + `(2>3 or 3<2)`
174
- // e.g. `x` + `^`
175
- return false;
176
- }
177
- }
178
- if (regexDigits.test(lastCharA)) {
179
- if (
180
- firstCharB == "(" ||
181
- !(firstCharB == "." || regexAlphaUnderscore.test(firstCharB))
182
- ) {
183
- // e.g. `1` + `+`
184
- // e.g. `1` + `==`
185
- return false;
186
- } else {
187
- // e.g. `1` + `..`
188
- // e.g. `1` + `and`
189
- return true;
190
- }
191
- }
192
- if (lastCharA == firstCharB && lastCharA == "-") {
193
- // e.g. `1-` + `-2`
194
- return true;
195
- }
196
- const secondLastCharA = a.slice(-2, -1);
197
- if (
198
- lastCharA == "." &&
199
- secondLastCharA != "." &&
200
- regexAlphaNumUnderscore.test(firstCharB)
201
- ) {
202
- // e.g. `1.` + `print`
203
- return true;
204
- }
205
- return false;
206
- }
207
-
208
- interface ExpressionOptoions {
209
- precedence?: number;
210
- preserveIdentifiers?: boolean;
211
- direction?: "left" | "right" | undefined;
212
- parent?: string | undefined;
213
- }
214
-
215
- function addWithSeparator(
216
- val: SourceNode,
217
- adding: (string | SourceNode)[] | SourceNode | string,
218
- separator = " ",
219
- ) {
220
- if (
221
- isNeedSeparator(
222
- val.toString(),
223
- wrapArray(adding)
224
- .map((p) => p.toString())
225
- .join(),
226
- )
227
- ) {
228
- val.add(separator);
229
- }
230
- val.add(adding);
231
- return val;
232
- }
233
-
234
- function prependWithSeparator(
235
- val: SourceNode,
236
- prepending: (string | SourceNode)[] | SourceNode | string,
237
- separator = " ",
238
- ) {
239
- if (
240
- isNeedSeparator(
241
- wrapArray(prepending)
242
- .map((p) => p.toString())
243
- .join(),
244
- val.toString(),
245
- )
246
- ) {
247
- val.prepend(separator);
248
- }
249
- val.prepend(prepending);
250
- return val;
251
- }
252
-
253
- function insertSeparator(
254
- a: string | SourceNode,
255
- b: string | SourceNode,
256
- separator = " ",
257
- ) {
258
- return isNeedSeparator(a.toString(), b.toString()) ? separator : undefined;
259
- }
260
-
261
- export class MinifyFile {
262
- private fileName: string;
263
- private moduleName: string;
264
- private ast: Chunk;
265
- private minifier: Minifier;
266
- private mode: MinifierMode;
267
-
268
- constructor(
269
- fileName: string,
270
- moduleName: string,
271
- ast: Chunk,
272
- minifier: Minifier,
273
- mode: MinifierMode,
274
- ) {
275
- this.fileName = fileName;
276
- this.moduleName = moduleName;
277
- this.ast = ast;
278
- this.minifier = minifier;
279
- this.mode = mode;
280
- }
281
-
282
- parse(noComment: boolean) {
283
- const body = this.formatStatementList(this.ast.body);
284
- if (!noComment && this.ast.comments) {
285
- const comments = this.ast.comments;
286
- comments
287
- .reverse()
288
- .filter((v) => v.raw.includes("--#") || v.raw.includes("[[#"))
289
- .forEach((comment) => {
290
- body.prepend([this.sourceNodeHelper(comment, comment.raw), "\n"]);
291
- });
292
- return body;
293
- } else {
294
- return body;
295
- }
296
- }
297
-
298
- /**
299
- * モジュール本体の最後の文が「単一の式を返すreturn文」であるときに限り、
300
- * それ以外の文と最後の式を分けて返す。requireを式(IIFE)ではなく文として
301
- * 展開したい呼び出し側(#29のレビュー対応)が利用する。
302
- * 該当しない場合はundefinedを返し、呼び出し側はIIFE方式へフォールバックする。
303
- */
304
- parseAsStatementsAndFinalExpression(
305
- noComment: boolean,
306
- ): { statements: SourceNode; finalExpression: SourceNode } | undefined {
307
- const body = this.ast.body;
308
- const last = body[body.length - 1];
309
- if (
310
- !last ||
311
- last.type !== "ReturnStatement" ||
312
- last.arguments.length !== 1
313
- ) {
314
- return undefined;
315
- }
316
-
317
- const statements = this.formatStatementList(body.slice(0, -1));
318
- if (!noComment && this.ast.comments) {
319
- this.ast.comments
320
- .slice()
321
- .reverse()
322
- .filter((v) => v.raw.includes("--#") || v.raw.includes("[[#"))
323
- .forEach((comment) => {
324
- statements.prepend([
325
- this.sourceNodeHelper(comment, comment.raw),
326
- "\n",
327
- ]);
328
- });
329
- }
330
-
331
- const finalExpression = this.formatExpression(last.arguments[0]);
332
- return { statements, finalExpression };
333
- }
334
-
335
- private sourceNodeHelper(
336
- node: Parser.Node | undefined,
337
- chuncks: (SourceNode | string)[] | SourceNode | string,
338
- name?: string,
339
- ) {
340
- const line = node?.loc?.start.line;
341
- const column = node?.loc?.start.column;
342
- // this.fileNameは常にこのMinifyFileインスタンスが担当するモジュール自身の
343
- // ファイル名(Linkパスで解決済み)なので、ここで出力するノードの由来ファイルとして正しい。
344
- return new SourceNode(
345
- line == undefined ? null : line,
346
- column == undefined ? null : column,
347
- this.fileName,
348
- chuncks,
349
- name,
350
- );
351
- }
352
-
353
- private formatStatementList(body: Parser.Statement[] | Parser.Statement) {
354
- const result = this.sourceNodeHelper(undefined, []);
355
- wrapArray(body).forEach((statement) => {
356
- addWithSeparator(result, this.formatStatement(statement), "\n");
357
- });
358
- return result;
359
- }
360
-
361
- /**
362
- * SLモード限定: `local x = require("m")` / `x = require("m")` の形(変数1個・
363
- * 初期化式1個)を、requireを式(IIFE)として埋め込むのではなく、モジュール本体の
364
- * 文をそのまま展開し最後にターゲットへ代入する「文」の並びとして出力する
365
- * (#29のレビュー対応。無オプション実行時の挙動互換性のため)。
366
- *
367
- * モジュール本体が「単一の式を返すreturn文」で終わっていない場合や、変数・
368
- * 初期化式が複数ある場合、-mモードの場合は対象外とし、undefinedを返す
369
- * (呼び出し側は従来のIIFE方式にフォールバックする)。
370
- */
371
- private trySpliceRequireStatement(
372
- statement: Parser.LocalStatement | Parser.AssignmentStatement,
373
- ): SourceNode | undefined {
374
- if (this.mode.moduleLikeLua) {
375
- return undefined;
376
- }
377
- if (statement.variables.length !== 1 || statement.init.length !== 1) {
378
- return undefined;
379
- }
380
-
381
- const moduleRef = this.matchModuleCallExpression(statement.init[0]);
382
- if (!moduleRef || moduleRef.kind !== "require") {
383
- return undefined;
384
- }
385
-
386
- const spliced = this.minifier.splitModuleForStatementSplice(
387
- moduleRef.moduleName,
388
- );
389
- if (!spliced) {
390
- return undefined;
391
- }
392
-
393
- const isLocal = statement.type === "LocalStatement";
394
- const target = statement.variables[0];
395
- const targetNode = isLocal
396
- ? this.generateIdentifier(target as Parser.Identifier)
397
- : this.formatExpression(target);
398
-
399
- const result = this.sourceNodeHelper(statement, []);
400
- addWithSeparator(result, spliced.statements);
401
- addWithSeparator(result, isLocal ? ["local ", targetNode] : [targetNode]);
402
- addWithSeparator(result, "=");
403
- addWithSeparator(result, spliced.finalExpression);
404
- return result;
405
- }
406
-
407
- /**
408
- * SLモード限定: `require("m")` を戻り値を使わない単独の文として書いた場合、
409
- * dofileと同じくfunctionで包まずそのまま展開する(Cプリプロセッサのincludeに
410
- * 近い、LifeBoat Modeでの一般的な使い方に合わせるための対応。#29のレビュー対応)。
411
- */
412
- private tryInlineBareRequireStatement(
413
- expr:
414
- | Parser.CallExpression
415
- | Parser.StringCallExpression
416
- | Parser.TableCallExpression,
417
- ): SourceNode | undefined {
418
- if (this.mode.moduleLikeLua) {
419
- return undefined;
420
- }
421
- const moduleRef = this.matchModuleCallExpression(expr);
422
- if (!moduleRef || moduleRef.kind !== "require") {
423
- return undefined;
424
- }
425
- return this.minifier.printModuleInline(moduleRef.moduleName);
426
- }
427
-
428
- private formatStatement(statement: Parser.Statement): SourceNode {
429
- if (
430
- statement.type == "AssignmentStatement" ||
431
- statement.type == "LocalStatement"
432
- ) {
433
- const spliced = this.trySpliceRequireStatement(statement);
434
- if (spliced) {
435
- return spliced;
436
- }
437
- }
438
- if (statement.type == "AssignmentStatement") {
439
- // left-hand side
440
- const variables = statement.variables
441
- .map((variable) => [this.formatExpression(variable), ","])
442
- .flat();
443
- const inits = statement.init
444
- .map((init) => [this.formatExpression(init), ","])
445
- .flat();
446
-
447
- const result = this.sourceNodeHelper(
448
- statement,
449
- this.sourceNodeHelper(undefined, variables.slice(0, -1)),
450
- );
451
- addWithSeparator(result, "=");
452
- addWithSeparator(
453
- result,
454
- this.sourceNodeHelper(undefined, inits.slice(0, -1)),
455
- );
456
- return result;
457
- } else if (statement.type == "LocalStatement") {
458
- const variables = statement.variables
459
- .map((variable) => [this.formatExpression(variable), ","])
460
- .flat();
461
- const result = this.sourceNodeHelper(statement, [
462
- "local ",
463
- this.sourceNodeHelper(undefined, variables.slice(0, -1)),
464
- ]);
465
-
466
- if (statement.init.length) {
467
- const inits = statement.init
468
- .map((init) => [this.formatExpression(init), ","])
469
- .flat();
470
-
471
- addWithSeparator(result, "=");
472
- addWithSeparator(
473
- result,
474
- this.sourceNodeHelper(undefined, inits.slice(0, -1)),
475
- );
476
- }
477
- return result;
478
- } else if (statement.type == "CallStatement") {
479
- const bareRequire = this.tryInlineBareRequireStatement(
480
- statement.expression,
481
- );
482
- if (bareRequire) {
483
- return bareRequire;
484
- }
485
- return this.formatExpression(statement.expression); // NOTE: もう一度囲んでもいい
486
- } else if (statement.type == "IfStatement") {
487
- const result = this.sourceNodeHelper(statement, []);
488
- statement.clauses.forEach((clause) => {
489
- const clauseMap = this.sourceNodeHelper(clause, []);
490
- if (clause.type == "IfClause") {
491
- addWithSeparator(clauseMap, "if");
492
- addWithSeparator(clauseMap, this.formatExpression(clause.condition));
493
- addWithSeparator(clauseMap, "then");
494
- } else if (clause.type == "ElseifClause") {
495
- addWithSeparator(clauseMap, "elseif");
496
- addWithSeparator(clauseMap, this.formatExpression(clause.condition));
497
- addWithSeparator(clauseMap, "then");
498
- } else {
499
- addWithSeparator(clauseMap, "else");
500
- }
501
- addWithSeparator(clauseMap, this.formatStatementList(clause.body));
502
- addWithSeparator(result, clauseMap);
503
- });
504
- addWithSeparator(result, "end");
505
- return result;
506
- } else if (statement.type == "WhileStatement") {
507
- const result = this.sourceNodeHelper(statement, "while");
508
- addWithSeparator(result, this.formatExpression(statement.condition));
509
- addWithSeparator(result, "do");
510
- addWithSeparator(result, this.formatStatementList(statement.body));
511
- addWithSeparator(result, "end");
512
- return result;
513
- } else if (statement.type == "DoStatement") {
514
- const result = this.sourceNodeHelper(statement, "do");
515
- addWithSeparator(result, this.formatStatementList(statement.body));
516
- addWithSeparator(result, "end");
517
- return result;
518
- } else if (statement.type == "ReturnStatement") {
519
- const result = this.sourceNodeHelper(statement, "return");
520
- if (statement.arguments.length) {
521
- const returns = statement.arguments
522
- .map((argument) => [this.formatExpression(argument), ","])
523
- .flat();
524
- addWithSeparator(result, returns.slice(0, -1));
525
- }
526
- return result;
527
- } else if (statement.type == "BreakStatement") {
528
- return this.sourceNodeHelper(statement, "break");
529
- } else if (statement.type == "RepeatStatement") {
530
- const result = this.sourceNodeHelper(statement, "repeat");
531
- addWithSeparator(result, this.formatStatementList(statement.body));
532
- addWithSeparator(result, "until");
533
- addWithSeparator(result, this.formatExpression(statement.condition));
534
- return result;
535
- } else if (statement.type == "FunctionDeclaration") {
536
- const result = this.sourceNodeHelper(
537
- statement,
538
- (statement.isLocal ? "local " : "") + "function ",
539
- );
540
- if (statement.identifier) {
541
- addWithSeparator(result, this.formatExpression(statement.identifier));
542
- }
543
- addWithSeparator(result, "(");
544
-
545
- if (statement.parameters.length) {
546
- const parameters = statement.parameters
547
- .map((parameter) => {
548
- return [
549
- parameter.type == "Identifier"
550
- ? this.generateIdentifier(parameter)
551
- : parameter.value,
552
- ",",
553
- ];
554
- })
555
- .flat();
556
- addWithSeparator(result, parameters.slice(0, -1));
557
- }
558
-
559
- addWithSeparator(result, ")");
560
- addWithSeparator(result, this.formatStatementList(statement.body));
561
- addWithSeparator(result, "end");
562
- return result;
563
- } else if (statement.type == "ForGenericStatement") {
564
- // see also `ForNumericStatement`
565
- const result = this.sourceNodeHelper(statement, "for");
566
- const variables = statement.variables
567
- .map((variable) => [this.generateIdentifier(variable), ","])
568
- .flat();
569
- const iterators = statement.iterators
570
- .map((iterator) => [this.formatExpression(iterator), ","])
571
- .flat();
572
- addWithSeparator(result, variables.slice(0, -1));
573
- addWithSeparator(result, "in");
574
- addWithSeparator(result, iterators.slice(0, -1));
575
- addWithSeparator(result, "do");
576
- addWithSeparator(result, this.formatStatementList(statement.body));
577
- addWithSeparator(result, "end");
578
- return result;
579
- } else if (statement.type == "ForNumericStatement") {
580
- // The variables in a `ForNumericStatement` are always local
581
- const result = this.sourceNodeHelper(statement, "for");
582
- addWithSeparator(result, this.generateIdentifier(statement.variable));
583
- addWithSeparator(result, "=");
584
- addWithSeparator(result, this.formatExpression(statement.start));
585
- addWithSeparator(result, ",");
586
- addWithSeparator(result, this.formatExpression(statement.end));
587
-
588
- if (statement.step) {
589
- addWithSeparator(result, ",");
590
- addWithSeparator(result, this.formatExpression(statement.step));
591
- }
592
-
593
- addWithSeparator(result, "do");
594
- addWithSeparator(result, this.formatStatementList(statement.body));
595
- addWithSeparator(result, "end");
596
- return result;
597
- } else if (statement.type == "LabelStatement") {
598
- // The identifier names in a `LabelStatement` can safely be renamed
599
- return this.sourceNodeHelper(statement, [
600
- "::",
601
- this.generateIdentifier(statement.label),
602
- "::",
603
- ]);
604
- } else if (statement.type == "GotoStatement") {
605
- // The identifier names in a `GotoStatement` can safely be renamed
606
- return this.sourceNodeHelper(statement, [
607
- "goto ",
608
- this.generateIdentifier(statement.label),
609
- ]);
610
- } else {
611
- throw TypeError(
612
- "Unknown statement type: `" + JSON.stringify(statement) + "`",
613
- );
614
- }
615
- }
616
-
617
- /*function joinStatements(a: string | SourceNode, b: string | SourceNode, separator = " ") {
618
- return isNeedSeparator(a.toString(), b.toString()) ? a.toString() + separator + b.toString() : a.toString() + b.toString();
619
- }*/
620
-
621
- private formatExpression(
622
- expression: Parser.Expression,
623
- argOptions?: ExpressionOptoions,
624
- ): SourceNode {
625
- if (expression.type == "Identifier") {
626
- return this.generateIdentifier(expression);
627
- } else if (
628
- expression.type == "StringLiteral" ||
629
- expression.type == "NumericLiteral" ||
630
- expression.type == "BooleanLiteral" ||
631
- expression.type == "NilLiteral" ||
632
- expression.type == "VarargLiteral"
633
- ) {
634
- return this.sourceNodeHelper(expression, expression.raw);
635
- } else if (
636
- expression.type == "LogicalExpression" ||
637
- expression.type == "BinaryExpression"
638
- ) {
639
- const operator = expression.operator;
640
- const currentPrecedence = PRECEDENCE[operator];
641
- let associativity: "left" | "right" = "left";
642
- const options = {
643
- precedence: 0,
644
- preserveIdentifiers: false,
645
- ...argOptions,
646
- };
647
-
648
- const leftHand = this.formatExpression(expression.left, {
649
- precedence: currentPrecedence,
650
- direction: "left",
651
- parent: operator,
652
- });
653
- const rightHand = this.formatExpression(expression.right, {
654
- precedence: currentPrecedence,
655
- direction: "right",
656
- parent: operator,
657
- });
658
- if (operator == "^" || operator == "..") {
659
- associativity = "right";
660
- } else if (
661
- currentPrecedence < options.precedence ||
662
- (currentPrecedence == options.precedence &&
663
- associativity != options.direction &&
664
- options.parent != "+" &&
665
- !(options.parent == "*" && (operator == "/" || operator == "*")))
666
- ) {
667
- return this.sourceNodeHelper(
668
- expression,
669
- [
670
- "(",
671
- leftHand,
672
- insertSeparator(leftHand, operator),
673
- operator,
674
- insertSeparator(operator, rightHand),
675
- rightHand,
676
- ")",
677
- ].filter((p): p is Exclude<typeof p, undefined> => p !== undefined),
678
- );
679
- }
680
- return this.sourceNodeHelper(
681
- expression,
682
- [
683
- leftHand,
684
- insertSeparator(leftHand, operator),
685
- operator,
686
- insertSeparator(operator, rightHand),
687
- rightHand,
688
- ].filter((p): p is Exclude<typeof p, undefined> => p !== undefined),
689
- );
690
- } else if (expression.type == "UnaryExpression") {
691
- const operator = expression.operator;
692
- const currentPrecedence = PRECEDENCE["unary" + operator];
693
- const options = {
694
- precedence: 0,
695
- ...argOptions,
696
- };
697
-
698
- const p2 = this.formatExpression(expression.argument, {
699
- precedence: currentPrecedence,
700
- });
701
- const result = this.sourceNodeHelper(
702
- expression,
703
- [operator, insertSeparator(operator, p2), p2].filter(
704
- (p): p is Exclude<typeof p, undefined> => p !== undefined,
705
- ),
706
- );
707
-
708
- if (
709
- currentPrecedence < options.precedence &&
710
- // In principle, we should parenthesize the RHS of an
711
- // expression like `3^-2`, because `^` has higher precedence
712
- // than unary `-` according to the manual. But that is
713
- // misleading on the RHS of `^`, since the parser will
714
- // always try to find a unary operator regardless of
715
- // precedence.
716
- !(options.parent == "^" && options.direction == "right")
717
- ) {
718
- result.prepend("(");
719
- result.add(")");
720
- }
721
- return result;
722
- } else if (expression.type == "CallExpression") {
723
- const moduleRef = this.matchModuleCallExpression(expression);
724
- if (moduleRef) {
725
- const replacement = this.formatModuleReference(expression, moduleRef);
726
- if (replacement) {
727
- return replacement;
728
- }
729
- }
730
- const args = expression.arguments
731
- .map((arg) => [this.formatExpression(arg), ","])
732
- .flat();
733
- return this.sourceNodeHelper(expression, [
734
- this.formatBase(expression.base),
735
- "(",
736
- this.sourceNodeHelper(undefined, args.slice(0, -1)),
737
- ")",
738
- ]);
739
- } else if (expression.type == "TableCallExpression") {
740
- return this.sourceNodeHelper(expression, [
741
- this.formatExpression(expression.base),
742
- this.formatExpression(expression.arguments),
743
- ]);
744
- } else if (expression.type == "StringCallExpression") {
745
- const moduleRef = this.matchModuleCallExpression(expression);
746
- if (moduleRef) {
747
- const replacement = this.formatModuleReference(expression, moduleRef);
748
- if (replacement) {
749
- return replacement;
750
- }
751
- }
752
- return this.sourceNodeHelper(expression, [
753
- this.formatExpression(expression.base),
754
- this.formatExpression(expression.argument),
755
- ]);
756
- } else if (expression.type == "IndexExpression") {
757
- return this.sourceNodeHelper(expression, [
758
- this.formatBase(expression.base),
759
- "[",
760
- this.formatExpression(expression.index),
761
- "]",
762
- ]);
763
- } else if (expression.type == "MemberExpression") {
764
- return this.sourceNodeHelper(expression, [
765
- this.formatBase(expression.base),
766
- expression.indexer,
767
- this.formatExpression(expression.identifier, {
768
- preserveIdentifiers: true,
769
- }),
770
- ]);
771
- } else if (expression.type == "FunctionDeclaration") {
772
- const result = this.sourceNodeHelper(expression, ["function", "("]);
773
-
774
- if (expression.parameters.length) {
775
- const parameters = expression.parameters
776
- .map((parameter) => {
777
- return [
778
- this.sourceNodeHelper(
779
- parameter,
780
- parameter.type === "Identifier"
781
- ? this.generateIdentifier(parameter)
782
- : parameter.value,
783
- ),
784
- ",",
785
- ];
786
- })
787
- .flat();
788
- addWithSeparator(result, parameters.slice(0, -1));
789
- }
790
- result.add(")");
791
- const body = this.formatStatementList(expression.body);
792
- addWithSeparator(result, body);
793
- addWithSeparator(result, "end");
794
- return result;
795
- } else if (expression.type == "TableConstructorExpression") {
796
- const result = this.sourceNodeHelper(expression, "{");
797
- const fields = expression.fields
798
- .map((field, ix, ar) => {
799
- // Stormworks "propert" Trailing Comma: https://nona-takahara.github.io/blog/entry11.html
800
- const comma =
801
- ix !== ar.length - 1 ||
802
- this.formatExpression(field.value).toString().includes("property")
803
- ? ","
804
- : undefined;
805
-
806
- if (field.type == "TableKey") {
807
- return this.sourceNodeHelper(
808
- field,
809
- [
810
- this.sourceNodeHelper(undefined, [
811
- "[",
812
- this.formatExpression(field.key),
813
- "]",
814
- ]),
815
- "=",
816
- this.formatExpression(field.value),
817
- comma,
818
- ].filter(
819
- (p): p is Exclude<typeof p, undefined> => p !== undefined,
820
- ),
821
- );
822
- } else if (field.type == "TableValue") {
823
- return [this.formatExpression(field.value), comma].filter(
824
- (p): p is Exclude<typeof p, undefined> => p !== undefined,
825
- );
826
- } else {
827
- // at this point, `field.type == 'TableKeyString'`
828
- // TODO: keep track of nested scopes (#18)
829
- return this.sourceNodeHelper(
830
- field,
831
- [
832
- this.formatExpression(field.key, { preserveIdentifiers: true }),
833
- "=",
834
- this.formatExpression(field.value),
835
- comma,
836
- ].filter(
837
- (p): p is Exclude<typeof p, undefined> => p !== undefined,
838
- ),
839
- );
840
- }
841
- })
842
- .flat();
843
- addWithSeparator(result, fields);
844
- addWithSeparator(result, "}");
845
- return result;
846
- } else {
847
- throw TypeError(
848
- "Unknown expression type: `" + JSON.stringify(expression) + "`",
849
- );
850
- }
851
- }
852
-
853
- private formatBase(base: Parser.Expression): SourceNode {
854
- const type = base.type;
855
- const needsParens =
856
- type == "CallExpression" ||
857
- type == "BinaryExpression" ||
858
- type == "FunctionDeclaration" ||
859
- type == "TableConstructorExpression" ||
860
- type == "LogicalExpression" ||
861
- type == "StringLiteral";
862
- const result = this.sourceNodeHelper(base, this.formatExpression(base));
863
- if (needsParens) {
864
- prependWithSeparator(result, "(");
865
- addWithSeparator(result, ")");
866
- }
867
- return result;
868
- }
869
-
870
- // require/dofileへの静的な呼び出しを検出する。実際のモジュール解決はLinkパス
871
- // (Minifier.link)が事前に済ませているため、ここでは呼び出し形が
872
- // require/dofile への静的文字列呼び出しかどうかを判定するだけでよい(#18)。
873
- private matchModuleCall(
874
- base: Parser.Expression,
875
- argument: Parser.Expression | undefined,
876
- ): { kind: "require" | "dofile"; moduleName: string } | undefined {
877
- if (base.type !== "Identifier") {
878
- return undefined;
879
- }
880
- if (base.name !== "require" && base.name !== "dofile") {
881
- return undefined;
882
- }
883
- const moduleName = staticStringArgument(argument);
884
- if (moduleName === undefined) {
885
- return undefined;
886
- }
887
- return { kind: base.name, moduleName };
888
- }
889
-
890
- // CallExpression / StringCallExpression のどちらの構文でも呼べるmatchModuleCall
891
- private matchModuleCallExpression(
892
- expr: Parser.Expression,
893
- ): { kind: "require" | "dofile"; moduleName: string } | undefined {
894
- if (expr.type === "CallExpression") {
895
- return this.matchModuleCall(expr.base, expr.arguments[0]);
896
- }
897
- if (expr.type === "StringCallExpression") {
898
- return this.matchModuleCall(expr.base, expr.argument);
899
- }
900
- return undefined;
901
- }
902
-
903
- private formatModuleReference(
904
- expression: Parser.Expression,
905
- ref: { kind: "require" | "dofile"; moduleName: string },
906
- ): SourceNode | undefined {
907
- if (ref.kind === "dofile") {
908
- // dofileは呼び出しごとに毎回展開しなおす(キャッシュしない)
909
- return this.minifier.printModuleInline(ref.moduleName);
910
- }
911
-
912
- if (!this.mode.moduleLikeLua) {
913
- // SLモード(無オプション): requireもキャッシュせずその場展開する
914
- // (挙動互換性のため、ホイストした共有ローカルへの参照にはしない)。
915
- // 式の位置に置けるようにIIFEで包む。
916
- const body = this.minifier.printModuleInline(ref.moduleName);
917
- return this.sourceNodeHelper(expression, [
918
- "(function() ",
919
- body,
920
- " end)()",
921
- ]);
922
- }
923
-
924
- // -mモードのrequireはそのまま呼び出しとして出力し、実行時のrequire関数に委ねる
925
- return undefined;
926
- }
927
-
928
- // Renameパス(#20)が解決済みシンボルテーブルをもとに割り当てた短縮名を参照する。
929
- // 対応するローカルシンボルが無い場合(グローバル参照や"self")は元の名前のまま出力する。
930
- private generateIdentifier(nameItem: Parser.Identifier): SourceNode {
931
- const renamed = this.minifier
932
- .getRenameResult(this.moduleName)
933
- .nameOf(nameItem);
934
- return this.sourceNodeHelper(
935
- nameItem,
936
- renamed ?? nameItem.name,
937
- nameItem.name,
938
- );
939
- }
940
- }