storm-lua-minify 0.1.2 → 0.2.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/.github/workflows/ci.yml +39 -0
  2. package/.github/workflows/publish.yml +39 -0
  3. package/.prettierignore +3 -0
  4. package/.prettierrc.json +1 -0
  5. package/LICENSE +21 -21
  6. package/README.md +37 -15
  7. package/dist/ast2lua.js +188 -85
  8. package/dist/cli.js +17 -7
  9. package/dist/index.js +3 -19
  10. package/dist/linker.js +96 -0
  11. package/dist/minifier.js +176 -51
  12. package/dist/output.js +33 -0
  13. package/dist/renamer.js +87 -0
  14. package/dist/resolver.js +303 -0
  15. package/eslint.config.js +29 -0
  16. package/package.json +33 -27
  17. package/src/ast2lua.ts +940 -812
  18. package/src/cli.ts +86 -56
  19. package/src/linker.ts +108 -0
  20. package/src/minifier.ts +284 -114
  21. package/src/output.ts +71 -0
  22. package/src/renamer.ts +134 -0
  23. package/src/resolver.ts +378 -0
  24. package/test/circular-require.test.ts +19 -0
  25. package/test/fixtures/bare-require/main.lua +2 -0
  26. package/test/fixtures/bare-require/mod.lua +1 -0
  27. package/test/fixtures/bitwise-precedence/main.lua +10 -0
  28. package/test/fixtures/circular-require/a.lua +2 -0
  29. package/test/fixtures/circular-require/b.lua +2 -0
  30. package/test/fixtures/circular-require/main.lua +2 -0
  31. package/test/fixtures/dofile/greet.lua +1 -0
  32. package/test/fixtures/dofile/main.lua +2 -0
  33. package/test/fixtures/entry-scope-many-requires/dep_alpha.lua +2 -0
  34. package/test/fixtures/entry-scope-many-requires/dep_bravo.lua +2 -0
  35. package/test/fixtures/entry-scope-many-requires/dep_charlie.lua +2 -0
  36. package/test/fixtures/entry-scope-many-requires/dep_delta.lua +2 -0
  37. package/test/fixtures/entry-scope-many-requires/dep_echo.lua +2 -0
  38. package/test/fixtures/entry-scope-many-requires/dep_foxtrot.lua +2 -0
  39. package/test/fixtures/entry-scope-many-requires/dep_golf.lua +2 -0
  40. package/test/fixtures/entry-scope-many-requires/dep_hotel.lua +2 -0
  41. package/test/fixtures/entry-scope-many-requires/dep_india.lua +2 -0
  42. package/test/fixtures/entry-scope-many-requires/dep_juliet.lua +2 -0
  43. package/test/fixtures/entry-scope-many-requires/dep_kilo.lua +2 -0
  44. package/test/fixtures/entry-scope-many-requires/main.lua +25 -0
  45. package/test/fixtures/multi-require/common.lua +1 -0
  46. package/test/fixtures/multi-require/main.lua +3 -0
  47. package/test/fixtures/nested-module/main.lua +2 -0
  48. package/test/fixtures/nested-module/sub/deep.lua +1 -0
  49. package/test/fixtures/require-call/main.lua +2 -0
  50. package/test/fixtures/require-call/mod.lua +5 -0
  51. package/test/fixtures/require-in-expression/main.lua +1 -0
  52. package/test/fixtures/require-in-expression/mod.lua +1 -0
  53. package/test/fixtures/require-string-call/main.lua +2 -0
  54. package/test/fixtures/require-string-call/mod.lua +5 -0
  55. package/test/fixtures/single-file/main.lua +15 -0
  56. package/test/identifier-collision.test.ts +28 -0
  57. package/test/lib/collision.ts +131 -0
  58. package/test/lib/helpers.ts +124 -0
  59. package/test/no-rename.test.ts +19 -0
  60. package/test/output.test.ts +95 -0
  61. package/test/precedence.test.ts +28 -0
  62. package/test/renamer.test.ts +130 -0
  63. package/test/resolver.test.ts +206 -0
  64. package/test/roundtrip.test.ts +26 -0
  65. package/test/snapshot.test.ts +27 -0
  66. package/test/snapshots/bare-require.sl.lua +1 -0
  67. package/test/snapshots/bitwise-precedence.sl.lua +2 -0
  68. package/test/snapshots/dofile.sl.lua +1 -0
  69. package/test/snapshots/entry-scope-many-requires.m.lua +14 -0
  70. package/test/snapshots/multi-require.m.lua +4 -0
  71. package/test/snapshots/multi-require.sl.lua +1 -0
  72. package/test/snapshots/nested-module.m.lua +4 -0
  73. package/test/snapshots/require-call.m.lua +5 -0
  74. package/test/snapshots/require-call.sl.lua +1 -0
  75. package/test/snapshots/require-in-expression.sl.lua +1 -0
  76. package/test/snapshots/require-string-call.m.lua +5 -0
  77. package/test/snapshots/single-file.sl.lua +6 -0
  78. package/test/sourcemap.test.ts +105 -0
  79. package/tsconfig.eslint.json +8 -0
  80. package/tsconfig.json +111 -109
  81. package/.eslintrc.json +0 -20
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveScopes = resolveScopes;
4
+ function resolveScopes(chunk) {
5
+ let nextSymbolId = 0;
6
+ const allSymbols = [];
7
+ const globals = new Map();
8
+ const identifierSymbols = new WeakMap();
9
+ function createScope(kind, parent) {
10
+ const scope = {
11
+ kind,
12
+ parent,
13
+ children: [],
14
+ symbols: [],
15
+ bindings: new Map(),
16
+ labels: new Map(),
17
+ };
18
+ parent?.children.push(scope);
19
+ return scope;
20
+ }
21
+ function declare(scope, node, kind) {
22
+ const symbol = {
23
+ id: nextSymbolId++,
24
+ name: node.name,
25
+ kind,
26
+ scope,
27
+ declaration: node,
28
+ references: [],
29
+ };
30
+ // 同名の再宣言はスコープ内の以後の参照から見た束縛を上書きする(Luaの通常のシャドーイング)
31
+ scope.symbols.push(symbol);
32
+ scope.bindings.set(node.name, symbol);
33
+ allSymbols.push(symbol);
34
+ identifierSymbols.set(node, symbol);
35
+ return symbol;
36
+ }
37
+ function declareLabel(scope, node) {
38
+ const symbol = {
39
+ id: nextSymbolId++,
40
+ name: node.name,
41
+ kind: "label",
42
+ scope,
43
+ declaration: node,
44
+ references: [],
45
+ };
46
+ scope.symbols.push(symbol);
47
+ scope.labels.set(node.name, symbol);
48
+ allSymbols.push(symbol);
49
+ identifierSymbols.set(node, symbol);
50
+ return symbol;
51
+ }
52
+ function lookupBinding(scope, name) {
53
+ for (let s = scope; s; s = s.parent) {
54
+ const found = s.bindings.get(name);
55
+ if (found) {
56
+ return found;
57
+ }
58
+ }
59
+ return undefined;
60
+ }
61
+ function lookupLabel(scope, name) {
62
+ for (let s = scope; s; s = s.parent) {
63
+ const found = s.labels.get(name);
64
+ if (found) {
65
+ return found;
66
+ }
67
+ }
68
+ return undefined;
69
+ }
70
+ function reference(scope, node) {
71
+ const symbol = lookupBinding(scope, node.name);
72
+ if (symbol) {
73
+ symbol.references.push(node);
74
+ identifierSymbols.set(node, symbol);
75
+ return;
76
+ }
77
+ let binding = globals.get(node.name);
78
+ if (!binding) {
79
+ binding = { name: node.name, references: [] };
80
+ globals.set(node.name, binding);
81
+ }
82
+ binding.references.push(node);
83
+ }
84
+ // ラベルはブロック内での宣言位置に関わらずブロック全体から参照できる
85
+ // (前方goto)ため、通常の宣言文と違い先読みで登録する。
86
+ function hoistLabels(body, scope) {
87
+ body.forEach((statement) => {
88
+ if (statement.type === "LabelStatement") {
89
+ declareLabel(scope, statement.label);
90
+ }
91
+ });
92
+ }
93
+ function resolveBlock(body, scope) {
94
+ hoistLabels(body, scope);
95
+ body.forEach((statement) => {
96
+ resolveStatement(statement, scope);
97
+ });
98
+ }
99
+ function resolveStatement(statement, scope) {
100
+ switch (statement.type) {
101
+ case "LocalStatement":
102
+ // 初期化式は新しいローカルが宣言される前の束縛で解決する
103
+ // (`local x = x` の右辺は外側のxを指す)
104
+ statement.init.forEach((expr) => {
105
+ resolveExpression(expr, scope);
106
+ });
107
+ statement.variables.forEach((v) => declare(scope, v, "local"));
108
+ return;
109
+ case "AssignmentStatement":
110
+ statement.init.forEach((expr) => {
111
+ resolveExpression(expr, scope);
112
+ });
113
+ statement.variables.forEach((v) => {
114
+ if (v.type === "Identifier") {
115
+ reference(scope, v);
116
+ }
117
+ else {
118
+ resolveExpression(v, scope);
119
+ }
120
+ });
121
+ return;
122
+ case "CallStatement":
123
+ resolveExpression(statement.expression, scope);
124
+ return;
125
+ case "DoStatement": {
126
+ const inner = createScope("block", scope);
127
+ resolveBlock(statement.body, inner);
128
+ return;
129
+ }
130
+ case "WhileStatement": {
131
+ resolveExpression(statement.condition, scope);
132
+ const inner = createScope("block", scope);
133
+ resolveBlock(statement.body, inner);
134
+ return;
135
+ }
136
+ case "RepeatStatement": {
137
+ // `until`の条件式は本体で宣言されたローカルを参照できる
138
+ const inner = createScope("block", scope);
139
+ resolveBlock(statement.body, inner);
140
+ resolveExpression(statement.condition, inner);
141
+ return;
142
+ }
143
+ case "IfStatement":
144
+ statement.clauses.forEach((clause) => {
145
+ if (clause.type !== "ElseClause") {
146
+ resolveExpression(clause.condition, scope);
147
+ }
148
+ const inner = createScope("block", scope);
149
+ resolveBlock(clause.body, inner);
150
+ });
151
+ return;
152
+ case "ForNumericStatement": {
153
+ resolveExpression(statement.start, scope);
154
+ resolveExpression(statement.end, scope);
155
+ if (statement.step) {
156
+ resolveExpression(statement.step, scope);
157
+ }
158
+ const inner = createScope("block", scope);
159
+ declare(inner, statement.variable, "for");
160
+ resolveBlock(statement.body, inner);
161
+ return;
162
+ }
163
+ case "ForGenericStatement": {
164
+ statement.iterators.forEach((iterator) => {
165
+ resolveExpression(iterator, scope);
166
+ });
167
+ const inner = createScope("block", scope);
168
+ statement.variables.forEach((v) => declare(inner, v, "for"));
169
+ resolveBlock(statement.body, inner);
170
+ return;
171
+ }
172
+ case "FunctionDeclaration":
173
+ resolveFunctionDeclaration(statement, scope);
174
+ return;
175
+ case "ReturnStatement":
176
+ statement.arguments.forEach((argument) => {
177
+ resolveExpression(argument, scope);
178
+ });
179
+ return;
180
+ case "BreakStatement":
181
+ return;
182
+ case "LabelStatement":
183
+ // hoistLabelsで宣言済みのため、ここでは何もしない
184
+ return;
185
+ case "GotoStatement": {
186
+ const symbol = lookupLabel(scope, statement.label.name);
187
+ if (symbol) {
188
+ symbol.references.push(statement.label);
189
+ identifierSymbols.set(statement.label, symbol);
190
+ }
191
+ return;
192
+ }
193
+ default: {
194
+ const exhaustive = statement;
195
+ throw new TypeError("Unknown statement type: `" + JSON.stringify(exhaustive) + "`");
196
+ }
197
+ }
198
+ }
199
+ function resolveFunctionDeclaration(fn, scope) {
200
+ if (fn.identifier) {
201
+ if (fn.identifier.type === "Identifier") {
202
+ if (fn.isLocal) {
203
+ // `local function`は再帰呼び出しのため、本体を解決する前に自身を宣言する
204
+ declare(scope, fn.identifier, "local");
205
+ }
206
+ else {
207
+ // 非local: 既存のローカル/グローバルへの代入として扱う(新規宣言ではない)
208
+ reference(scope, fn.identifier);
209
+ }
210
+ }
211
+ else {
212
+ resolveExpression(fn.identifier, scope);
213
+ }
214
+ }
215
+ const inner = createScope("function", scope);
216
+ fn.parameters.forEach((parameter) => {
217
+ if (parameter.type === "Identifier") {
218
+ declare(inner, parameter, "param");
219
+ }
220
+ });
221
+ resolveBlock(fn.body, inner);
222
+ }
223
+ function resolveExpression(expr, scope) {
224
+ switch (expr.type) {
225
+ case "Identifier":
226
+ reference(scope, expr);
227
+ return;
228
+ case "StringLiteral":
229
+ case "NumericLiteral":
230
+ case "BooleanLiteral":
231
+ case "NilLiteral":
232
+ case "VarargLiteral":
233
+ return;
234
+ case "LogicalExpression":
235
+ case "BinaryExpression":
236
+ resolveExpression(expr.left, scope);
237
+ resolveExpression(expr.right, scope);
238
+ return;
239
+ case "UnaryExpression":
240
+ resolveExpression(expr.argument, scope);
241
+ return;
242
+ case "CallExpression":
243
+ resolveExpression(expr.base, scope);
244
+ expr.arguments.forEach((argument) => {
245
+ resolveExpression(argument, scope);
246
+ });
247
+ return;
248
+ case "TableCallExpression":
249
+ resolveExpression(expr.base, scope);
250
+ resolveExpression(expr.arguments, scope);
251
+ return;
252
+ case "StringCallExpression":
253
+ resolveExpression(expr.base, scope);
254
+ resolveExpression(expr.argument, scope);
255
+ return;
256
+ case "IndexExpression":
257
+ resolveExpression(expr.base, scope);
258
+ resolveExpression(expr.index, scope);
259
+ return;
260
+ case "MemberExpression":
261
+ resolveExpression(expr.base, scope);
262
+ // フィールド名(`.identifier`)は変数参照ではないため解決しない
263
+ return;
264
+ case "FunctionDeclaration": {
265
+ const inner = createScope("function", scope);
266
+ expr.parameters.forEach((parameter) => {
267
+ if (parameter.type === "Identifier") {
268
+ declare(inner, parameter, "param");
269
+ }
270
+ });
271
+ resolveBlock(expr.body, inner);
272
+ return;
273
+ }
274
+ case "TableConstructorExpression":
275
+ expr.fields.forEach((field) => {
276
+ if (field.type === "TableKey") {
277
+ resolveExpression(field.key, scope);
278
+ resolveExpression(field.value, scope);
279
+ }
280
+ else if (field.type === "TableValue") {
281
+ resolveExpression(field.value, scope);
282
+ }
283
+ else {
284
+ // TableKeyString: キー名(`{ key = value }`のkey)は変数参照ではない
285
+ resolveExpression(field.value, scope);
286
+ }
287
+ });
288
+ return;
289
+ default: {
290
+ const exhaustive = expr;
291
+ throw new TypeError("Unknown expression type: `" + JSON.stringify(exhaustive) + "`");
292
+ }
293
+ }
294
+ }
295
+ const chunkScope = createScope("chunk", null);
296
+ resolveBlock(chunk.body, chunkScope);
297
+ return {
298
+ chunkScope,
299
+ symbols: allSymbols,
300
+ globals,
301
+ symbolOf: (identifier) => identifierSymbols.get(identifier),
302
+ };
303
+ }
@@ -0,0 +1,29 @@
1
+ // @ts-check
2
+ const eslint = require("@eslint/js");
3
+ const tseslint = require("typescript-eslint");
4
+ const globals = require("globals");
5
+ const eslintConfigPrettier = require("eslint-config-prettier");
6
+
7
+ module.exports = tseslint.config(
8
+ {
9
+ ignores: ["dist/**"],
10
+ },
11
+ {
12
+ files: ["src/**/*.ts", "test/**/*.ts"],
13
+ extends: [
14
+ eslint.configs.recommended,
15
+ ...tseslint.configs.strictTypeChecked,
16
+ ...tseslint.configs.stylistic,
17
+ ],
18
+ languageOptions: {
19
+ globals: {
20
+ ...globals.node,
21
+ },
22
+ parserOptions: {
23
+ project: "./tsconfig.eslint.json",
24
+ tsconfigRootDir: __dirname,
25
+ },
26
+ },
27
+ },
28
+ eslintConfigPrettier,
29
+ );
package/package.json CHANGED
@@ -1,27 +1,33 @@
1
- {
2
- "name": "storm-lua-minify",
3
- "description": "A Lua minifier also outputs source map",
4
- "version": "0.1.2",
5
- "dependencies": {
6
- "commander": "^12.1.0",
7
- "luamin": "^1.0.4",
8
- "luaparse": "^0.3.1",
9
- "source-map": "^0.7.4"
10
- },
11
- "devDependencies": {
12
- "@types/node": "^20.12.8",
13
- "@types/luaparse": "^0.2.12",
14
- "@typescript-eslint/eslint-plugin": "^7.8.0",
15
- "@typescript-eslint/parser": "^7.8.0",
16
- "eslint": "^8.57.0",
17
- "ts-node": "^10.9.2",
18
- "typescript": "^5.4.5"
19
- },
20
- "bin": "dist/cli.js",
21
- "scripts": {
22
- "start": "node ./dist/index.js",
23
- "build": "npx tsc"
24
- },
25
- "author": "Nona Takahara <nona_t_2612@yahoo.co.jp>",
26
- "license": "MIT"
27
- }
1
+ {
2
+ "name": "storm-lua-minify",
3
+ "description": "A Lua minifier also outputs source map",
4
+ "version": "0.2.0",
5
+ "dependencies": {
6
+ "commander": "^12.1.0",
7
+ "luaparse": "^0.3.1",
8
+ "source-map": "^0.7.4"
9
+ },
10
+ "devDependencies": {
11
+ "@eslint/js": "^9.39.5",
12
+ "@types/node": "^22.20.1",
13
+ "@types/luaparse": "^0.2.13",
14
+ "eslint": "^9.39.5",
15
+ "eslint-config-prettier": "^10.1.8",
16
+ "globals": "^17.7.0",
17
+ "prettier": "^3.9.5",
18
+ "ts-node": "^10.9.2",
19
+ "typescript": "^5.9.3",
20
+ "typescript-eslint": "^8.63.0"
21
+ },
22
+ "bin": "dist/cli.js",
23
+ "scripts": {
24
+ "start": "node ./dist/index.js",
25
+ "build": "npx tsc",
26
+ "test": "node -r ts-node/register/transpile-only --test \"test/**/*.test.ts\"",
27
+ "lint": "eslint .",
28
+ "format": "prettier --write .",
29
+ "format:check": "prettier --check ."
30
+ },
31
+ "author": "Nona Takahara <nona_t_2612@yahoo.co.jp>",
32
+ "license": "MIT"
33
+ }