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.
- package/LICENSE +21 -21
- package/README.md +79 -37
- package/dist/ast2lua.js +67 -14
- package/dist/cli.js +28 -2
- package/dist/globalRename.js +35 -0
- package/dist/keywordLocator.js +68 -0
- package/dist/linker.js +13 -0
- package/dist/minifier.js +75 -1
- package/dist/renamer.js +16 -2
- package/dist/resolver.js +10 -4
- package/dist/transform.js +401 -0
- package/package.json +41 -33
- package/.github/workflows/ci.yml +0 -39
- package/.github/workflows/publish.yml +0 -39
- package/.prettierignore +0 -3
- package/.prettierrc.json +0 -1
- package/eslint.config.js +0 -29
- package/src/ast2lua.ts +0 -940
- package/src/cli.ts +0 -86
- package/src/linker.ts +0 -108
- package/src/minifier.ts +0 -284
- package/src/output.ts +0 -71
- package/src/renamer.ts +0 -134
- package/src/resolver.ts +0 -378
- package/test/circular-require.test.ts +0 -19
- package/test/fixtures/bare-require/main.lua +0 -2
- package/test/fixtures/bare-require/mod.lua +0 -1
- package/test/fixtures/bitwise-precedence/main.lua +0 -10
- package/test/fixtures/circular-require/a.lua +0 -2
- package/test/fixtures/circular-require/b.lua +0 -2
- package/test/fixtures/circular-require/main.lua +0 -2
- package/test/fixtures/dofile/greet.lua +0 -1
- package/test/fixtures/dofile/main.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_alpha.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_bravo.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_charlie.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_delta.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_echo.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_foxtrot.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_golf.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_hotel.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_india.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_juliet.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_kilo.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/main.lua +0 -25
- package/test/fixtures/multi-require/common.lua +0 -1
- package/test/fixtures/multi-require/main.lua +0 -3
- package/test/fixtures/nested-module/main.lua +0 -2
- package/test/fixtures/nested-module/sub/deep.lua +0 -1
- package/test/fixtures/require-call/main.lua +0 -2
- package/test/fixtures/require-call/mod.lua +0 -5
- package/test/fixtures/require-in-expression/main.lua +0 -1
- package/test/fixtures/require-in-expression/mod.lua +0 -1
- package/test/fixtures/require-string-call/main.lua +0 -2
- package/test/fixtures/require-string-call/mod.lua +0 -5
- package/test/fixtures/single-file/main.lua +0 -15
- package/test/identifier-collision.test.ts +0 -28
- package/test/lib/collision.ts +0 -131
- package/test/lib/helpers.ts +0 -124
- package/test/no-rename.test.ts +0 -19
- package/test/output.test.ts +0 -95
- package/test/precedence.test.ts +0 -28
- package/test/renamer.test.ts +0 -130
- package/test/resolver.test.ts +0 -206
- package/test/roundtrip.test.ts +0 -26
- package/test/snapshot.test.ts +0 -27
- package/test/snapshots/bare-require.sl.lua +0 -1
- package/test/snapshots/bitwise-precedence.sl.lua +0 -2
- package/test/snapshots/dofile.sl.lua +0 -1
- package/test/snapshots/entry-scope-many-requires.m.lua +0 -14
- package/test/snapshots/multi-require.m.lua +0 -4
- package/test/snapshots/multi-require.sl.lua +0 -1
- package/test/snapshots/nested-module.m.lua +0 -4
- package/test/snapshots/require-call.m.lua +0 -5
- package/test/snapshots/require-call.sl.lua +0 -1
- package/test/snapshots/require-in-expression.sl.lua +0 -1
- package/test/snapshots/require-string-call.m.lua +0 -5
- package/test/snapshots/single-file.sl.lua +0 -6
- package/test/sourcemap.test.ts +0 -105
- package/tsconfig.eslint.json +0 -8
- package/tsconfig.json +0 -111
package/dist/resolver.js
CHANGED
|
@@ -6,6 +6,7 @@ function resolveScopes(chunk) {
|
|
|
6
6
|
const allSymbols = [];
|
|
7
7
|
const globals = new Map();
|
|
8
8
|
const identifierSymbols = new WeakMap();
|
|
9
|
+
const globalReferenceNodes = new WeakSet();
|
|
9
10
|
function createScope(kind, parent) {
|
|
10
11
|
const scope = {
|
|
11
12
|
kind,
|
|
@@ -67,7 +68,7 @@ function resolveScopes(chunk) {
|
|
|
67
68
|
}
|
|
68
69
|
return undefined;
|
|
69
70
|
}
|
|
70
|
-
function reference(scope, node) {
|
|
71
|
+
function reference(scope, node, isWrite = false) {
|
|
71
72
|
const symbol = lookupBinding(scope, node.name);
|
|
72
73
|
if (symbol) {
|
|
73
74
|
symbol.references.push(node);
|
|
@@ -76,10 +77,14 @@ function resolveScopes(chunk) {
|
|
|
76
77
|
}
|
|
77
78
|
let binding = globals.get(node.name);
|
|
78
79
|
if (!binding) {
|
|
79
|
-
binding = { name: node.name, references: [] };
|
|
80
|
+
binding = { name: node.name, references: [], writes: [] };
|
|
80
81
|
globals.set(node.name, binding);
|
|
81
82
|
}
|
|
82
83
|
binding.references.push(node);
|
|
84
|
+
if (isWrite) {
|
|
85
|
+
binding.writes.push(node);
|
|
86
|
+
}
|
|
87
|
+
globalReferenceNodes.add(node);
|
|
83
88
|
}
|
|
84
89
|
// ラベルはブロック内での宣言位置に関わらずブロック全体から参照できる
|
|
85
90
|
// (前方goto)ため、通常の宣言文と違い先読みで登録する。
|
|
@@ -112,7 +117,7 @@ function resolveScopes(chunk) {
|
|
|
112
117
|
});
|
|
113
118
|
statement.variables.forEach((v) => {
|
|
114
119
|
if (v.type === "Identifier") {
|
|
115
|
-
reference(scope, v);
|
|
120
|
+
reference(scope, v, true);
|
|
116
121
|
}
|
|
117
122
|
else {
|
|
118
123
|
resolveExpression(v, scope);
|
|
@@ -205,7 +210,7 @@ function resolveScopes(chunk) {
|
|
|
205
210
|
}
|
|
206
211
|
else {
|
|
207
212
|
// 非local: 既存のローカル/グローバルへの代入として扱う(新規宣言ではない)
|
|
208
|
-
reference(scope, fn.identifier);
|
|
213
|
+
reference(scope, fn.identifier, true);
|
|
209
214
|
}
|
|
210
215
|
}
|
|
211
216
|
else {
|
|
@@ -299,5 +304,6 @@ function resolveScopes(chunk) {
|
|
|
299
304
|
symbols: allSymbols,
|
|
300
305
|
globals,
|
|
301
306
|
symbolOf: (identifier) => identifierSymbols.get(identifier),
|
|
307
|
+
isGlobalReference: (identifier) => globalReferenceNodes.has(identifier),
|
|
302
308
|
};
|
|
303
309
|
}
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeLocalDeclarations = mergeLocalDeclarations;
|
|
4
|
+
exports.originalNameOf = originalNameOf;
|
|
5
|
+
exports.insertGlobalAliases = insertGlobalAliases;
|
|
6
|
+
const linker_1 = require("./linker");
|
|
7
|
+
function mergeLocalDeclarations(chunk, resolveResult, options) {
|
|
8
|
+
processBlock(chunk.body, resolveResult, options);
|
|
9
|
+
}
|
|
10
|
+
// 十分に非現実的な接頭辞を使い、実在の識別子との衝突を避ける
|
|
11
|
+
// (このパスの直後にresolveScopesが再実行され、最終的な短縮名は
|
|
12
|
+
// Renameパスが決めるため、この仮名自体が出力に残ることはない)。
|
|
13
|
+
const ALIAS_TEMP_NAME_PREFIX = "__mergeAlias";
|
|
14
|
+
// エイリアス化で`.name`を書き換えた参照ノードについて、Source Mapに載せる
|
|
15
|
+
// べき「元の名前」を記録する。ast2lua.tsのgenerateIdentifierはこれを参照し、
|
|
16
|
+
// 位置情報(loc)はそのままなのにSource Mapのnamesフィールドだけ書き換え後の
|
|
17
|
+
// 仮名(あるいは最終的な短縮名)になってしまう不具合を避ける。
|
|
18
|
+
const aliasedOriginalNames = new WeakMap();
|
|
19
|
+
function originalNameOf(identifier) {
|
|
20
|
+
return aliasedOriginalNames.get(identifier);
|
|
21
|
+
}
|
|
22
|
+
// 代入コスト(`local X=`相当、8バイト)を、参照1回あたりの節約バイト数
|
|
23
|
+
// (元の名前の長さ-1文字、エイリアス名を1文字と仮定)で上回るかどうかで判定する。
|
|
24
|
+
function isWorthAliasing(name, referenceCount) {
|
|
25
|
+
const savingsPerReference = name.length - 1;
|
|
26
|
+
const declarationCost = 8 + name.length;
|
|
27
|
+
return referenceCount * savingsPerReference > declarationCost;
|
|
28
|
+
}
|
|
29
|
+
function insertGlobalAliases(chunk, resolveResult, options) {
|
|
30
|
+
const newLocals = [];
|
|
31
|
+
let aliasCounter = 0;
|
|
32
|
+
resolveResult.globals.forEach((binding) => {
|
|
33
|
+
// 一度でも代入されているグローバルは#8aが直接リネームする対象(か、そちらが
|
|
34
|
+
// 保護対象として保持する)なので、ここでの対象は代入が一度もないものに限る。
|
|
35
|
+
if (binding.writes.length > 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (options.excludeNames.has(binding.name)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// require/dofileはPrintパスが名前文字列で再検出するため、
|
|
42
|
+
// エイリアス化して名前を変えると検出できなくなり壊れる。
|
|
43
|
+
if (linker_1.RESERVED_MODULE_FUNCTION_NAMES.has(binding.name)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!isWorthAliasing(binding.name, binding.references.length)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const tempName = ALIAS_TEMP_NAME_PREFIX + String(aliasCounter++);
|
|
50
|
+
binding.references.forEach((ref) => {
|
|
51
|
+
aliasedOriginalNames.set(ref, ref.name);
|
|
52
|
+
ref.name = tempName;
|
|
53
|
+
});
|
|
54
|
+
newLocals.push({
|
|
55
|
+
type: "LocalStatement",
|
|
56
|
+
variables: [{ type: "Identifier", name: tempName }],
|
|
57
|
+
init: [{ type: "Identifier", name: binding.name }],
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
if (newLocals.length > 0) {
|
|
61
|
+
chunk.body.unshift(...newLocals);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// resolver.tsのresolveStatement/resolveExpressionの分岐を鏡写しにした
|
|
65
|
+
// 汎用ウォーカー。「参照として現れる識別子」と「ネストしたブロック本体」を
|
|
66
|
+
// どちらも一度の走査で収集できるようにし、resolver.tsの走査ロジックの
|
|
67
|
+
// 重複を避ける。
|
|
68
|
+
function walkStatement(statement, visitor) {
|
|
69
|
+
switch (statement.type) {
|
|
70
|
+
case "LocalStatement":
|
|
71
|
+
// 宣言される変数自体は参照ではないため辿らない
|
|
72
|
+
statement.init.forEach((expr) => {
|
|
73
|
+
walkExpression(expr, visitor);
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
case "AssignmentStatement":
|
|
77
|
+
statement.variables.forEach((v) => {
|
|
78
|
+
if (v.type === "Identifier") {
|
|
79
|
+
visitor.onIdentifierReference?.(v);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
walkExpression(v, visitor);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
statement.init.forEach((expr) => {
|
|
86
|
+
walkExpression(expr, visitor);
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
case "CallStatement":
|
|
90
|
+
walkExpression(statement.expression, visitor);
|
|
91
|
+
return;
|
|
92
|
+
case "DoStatement":
|
|
93
|
+
visitor.onBlock?.(statement.body);
|
|
94
|
+
return;
|
|
95
|
+
case "WhileStatement":
|
|
96
|
+
walkExpression(statement.condition, visitor);
|
|
97
|
+
visitor.onBlock?.(statement.body);
|
|
98
|
+
return;
|
|
99
|
+
case "RepeatStatement":
|
|
100
|
+
visitor.onBlock?.(statement.body);
|
|
101
|
+
walkExpression(statement.condition, visitor);
|
|
102
|
+
return;
|
|
103
|
+
case "IfStatement":
|
|
104
|
+
statement.clauses.forEach((clause) => {
|
|
105
|
+
if (clause.type !== "ElseClause") {
|
|
106
|
+
walkExpression(clause.condition, visitor);
|
|
107
|
+
}
|
|
108
|
+
visitor.onBlock?.(clause.body);
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
case "ForNumericStatement":
|
|
112
|
+
walkExpression(statement.start, visitor);
|
|
113
|
+
walkExpression(statement.end, visitor);
|
|
114
|
+
if (statement.step) {
|
|
115
|
+
walkExpression(statement.step, visitor);
|
|
116
|
+
}
|
|
117
|
+
visitor.onBlock?.(statement.body);
|
|
118
|
+
return;
|
|
119
|
+
case "ForGenericStatement":
|
|
120
|
+
statement.iterators.forEach((iterator) => {
|
|
121
|
+
walkExpression(iterator, visitor);
|
|
122
|
+
});
|
|
123
|
+
visitor.onBlock?.(statement.body);
|
|
124
|
+
return;
|
|
125
|
+
case "FunctionDeclaration":
|
|
126
|
+
if (statement.identifier) {
|
|
127
|
+
if (statement.identifier.type === "Identifier") {
|
|
128
|
+
if (!statement.isLocal) {
|
|
129
|
+
// 非local: 既存のローカル/グローバルへの代入(参照扱い)
|
|
130
|
+
visitor.onIdentifierReference?.(statement.identifier);
|
|
131
|
+
}
|
|
132
|
+
// isLocalの場合は宣言自体であり参照ではないため辿らない
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
walkExpression(statement.identifier, visitor);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
visitor.onBlock?.(statement.body);
|
|
139
|
+
return;
|
|
140
|
+
case "ReturnStatement":
|
|
141
|
+
statement.arguments.forEach((argument) => {
|
|
142
|
+
walkExpression(argument, visitor);
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
case "BreakStatement":
|
|
146
|
+
case "LabelStatement":
|
|
147
|
+
case "GotoStatement":
|
|
148
|
+
return;
|
|
149
|
+
default: {
|
|
150
|
+
const exhaustive = statement;
|
|
151
|
+
throw new TypeError("Unknown statement type: `" + JSON.stringify(exhaustive) + "`");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function walkExpression(expr, visitor) {
|
|
156
|
+
switch (expr.type) {
|
|
157
|
+
case "Identifier":
|
|
158
|
+
visitor.onIdentifierReference?.(expr);
|
|
159
|
+
return;
|
|
160
|
+
case "StringLiteral":
|
|
161
|
+
case "NumericLiteral":
|
|
162
|
+
case "BooleanLiteral":
|
|
163
|
+
case "NilLiteral":
|
|
164
|
+
case "VarargLiteral":
|
|
165
|
+
return;
|
|
166
|
+
case "LogicalExpression":
|
|
167
|
+
case "BinaryExpression":
|
|
168
|
+
walkExpression(expr.left, visitor);
|
|
169
|
+
walkExpression(expr.right, visitor);
|
|
170
|
+
return;
|
|
171
|
+
case "UnaryExpression":
|
|
172
|
+
walkExpression(expr.argument, visitor);
|
|
173
|
+
return;
|
|
174
|
+
case "CallExpression":
|
|
175
|
+
walkExpression(expr.base, visitor);
|
|
176
|
+
expr.arguments.forEach((argument) => {
|
|
177
|
+
walkExpression(argument, visitor);
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
case "TableCallExpression":
|
|
181
|
+
walkExpression(expr.base, visitor);
|
|
182
|
+
walkExpression(expr.arguments, visitor);
|
|
183
|
+
return;
|
|
184
|
+
case "StringCallExpression":
|
|
185
|
+
walkExpression(expr.base, visitor);
|
|
186
|
+
walkExpression(expr.argument, visitor);
|
|
187
|
+
return;
|
|
188
|
+
case "IndexExpression":
|
|
189
|
+
walkExpression(expr.base, visitor);
|
|
190
|
+
walkExpression(expr.index, visitor);
|
|
191
|
+
return;
|
|
192
|
+
case "MemberExpression":
|
|
193
|
+
walkExpression(expr.base, visitor);
|
|
194
|
+
// フィールド名(`.identifier`)は変数参照ではないため辿らない
|
|
195
|
+
return;
|
|
196
|
+
case "FunctionDeclaration":
|
|
197
|
+
visitor.onBlock?.(expr.body);
|
|
198
|
+
return;
|
|
199
|
+
case "TableConstructorExpression":
|
|
200
|
+
expr.fields.forEach((field) => {
|
|
201
|
+
if (field.type === "TableKey") {
|
|
202
|
+
walkExpression(field.key, visitor);
|
|
203
|
+
walkExpression(field.value, visitor);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
walkExpression(field.value, visitor);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
default: {
|
|
211
|
+
const exhaustive = expr;
|
|
212
|
+
throw new TypeError("Unknown expression type: `" + JSON.stringify(exhaustive) + "`");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function processBlock(body, resolveResult, options) {
|
|
217
|
+
// 先に子ブロック(ネストしたスコープ)を処理する。子ブロックは別配列なので
|
|
218
|
+
// このレベルでのまとめ上げには影響しない。
|
|
219
|
+
body.forEach((statement) => {
|
|
220
|
+
walkStatement(statement, {
|
|
221
|
+
onBlock: (nested) => {
|
|
222
|
+
processBlock(nested, resolveResult, options);
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
mergeRunsInPlace(body, resolveResult, options);
|
|
227
|
+
}
|
|
228
|
+
// あるexpr(候補文のinit式)の中に現れる識別子参照をすべて収集する。
|
|
229
|
+
// ネストした関数リテラルの本体も再帰的に辿る(ハザード1: クロージャの
|
|
230
|
+
// 字句スコープは実行タイミングに関わらず即座に確定するため)。
|
|
231
|
+
function collectIdentifierReferences(expr) {
|
|
232
|
+
const found = [];
|
|
233
|
+
const visitor = {
|
|
234
|
+
onIdentifierReference: (id) => found.push(id),
|
|
235
|
+
onBlock: (body) => {
|
|
236
|
+
body.forEach((s) => {
|
|
237
|
+
walkStatement(s, visitor);
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
walkExpression(expr, visitor);
|
|
242
|
+
return found;
|
|
243
|
+
}
|
|
244
|
+
// ============================================================
|
|
245
|
+
// ハザード判定
|
|
246
|
+
// ============================================================
|
|
247
|
+
function isExpandable(expr) {
|
|
248
|
+
return (expr.type === "CallExpression" ||
|
|
249
|
+
expr.type === "TableCallExpression" ||
|
|
250
|
+
expr.type === "StringCallExpression" ||
|
|
251
|
+
expr.type === "VarargLiteral");
|
|
252
|
+
}
|
|
253
|
+
// ハザード2(複数値展開): statementをグループの非末尾要素として
|
|
254
|
+
// 後ろに文を連結してよいかどうかを判定する。
|
|
255
|
+
// - 変数と初期化式の個数が一致: 常に安全(パディング不要)
|
|
256
|
+
// - 変数が多い(不足): 最後の初期化式が展開可能でなければnilパディングで安全。
|
|
257
|
+
// 展開可能な式に依存している場合は非末尾にできない。
|
|
258
|
+
// - 初期化式が多い(過剰): 常に非末尾として不安全
|
|
259
|
+
// (余剰の値が後続文の変数へずれ込むため、パディングでは救えない)
|
|
260
|
+
function classifyNonTerminal(statement) {
|
|
261
|
+
const varCount = statement.variables.length;
|
|
262
|
+
const initCount = statement.init.length;
|
|
263
|
+
if (varCount === initCount) {
|
|
264
|
+
return { safe: true, needsPadding: false };
|
|
265
|
+
}
|
|
266
|
+
if (varCount > initCount) {
|
|
267
|
+
const lastInit = initCount > 0 ? statement.init[initCount - 1] : undefined;
|
|
268
|
+
if (!lastInit || !isExpandable(lastInit)) {
|
|
269
|
+
return { safe: true, needsPadding: true };
|
|
270
|
+
}
|
|
271
|
+
return { safe: false };
|
|
272
|
+
}
|
|
273
|
+
return { safe: false };
|
|
274
|
+
}
|
|
275
|
+
// ハザード1(参照順序): candidateのinit式が、groupSoFarで宣言済みの
|
|
276
|
+
// 変数を参照していないかを判定する。参照していれば統合するとLuaの
|
|
277
|
+
// スコープ規則上意味が変わってしまうため、そこでグループを閉じる必要がある。
|
|
278
|
+
function initReferencesDeclaredInGroup(candidate, groupSoFar, resolveResult) {
|
|
279
|
+
const declaredInGroup = new Set();
|
|
280
|
+
groupSoFar.forEach((statement) => {
|
|
281
|
+
statement.variables.forEach((v) => {
|
|
282
|
+
const symbol = resolveResult.symbolOf(v);
|
|
283
|
+
if (symbol) {
|
|
284
|
+
declaredInGroup.add(symbol);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
return candidate.init.some((expr) => collectIdentifierReferences(expr).some((id) => {
|
|
289
|
+
const symbol = resolveResult.symbolOf(id);
|
|
290
|
+
return symbol !== undefined && declaredInGroup.has(symbol);
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
// ハザード4: SLモードでtrySpliceRequireStatement(#29)の対象になり得る
|
|
294
|
+
// `local x = require(<静的文字列>)`形式の文かどうかを判定する。
|
|
295
|
+
function isSpliceEligibleRequire(statement) {
|
|
296
|
+
if (statement.variables.length !== 1 || statement.init.length !== 1) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
return isStaticRequireCall(statement.init[0]);
|
|
300
|
+
}
|
|
301
|
+
function isStaticRequireCall(expr) {
|
|
302
|
+
if (expr.type === "CallExpression") {
|
|
303
|
+
return (expr.base.type === "Identifier" &&
|
|
304
|
+
expr.base.name === "require" &&
|
|
305
|
+
expr.arguments.length > 0 &&
|
|
306
|
+
(0, linker_1.staticStringArgument)(expr.arguments[0]) !== undefined);
|
|
307
|
+
}
|
|
308
|
+
if (expr.type === "StringCallExpression") {
|
|
309
|
+
return (expr.base.type === "Identifier" &&
|
|
310
|
+
expr.base.name === "require" &&
|
|
311
|
+
(0, linker_1.staticStringArgument)(expr.argument) !== undefined);
|
|
312
|
+
}
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
// ============================================================
|
|
316
|
+
// グループ化とマージ
|
|
317
|
+
// ============================================================
|
|
318
|
+
function nilLiteral() {
|
|
319
|
+
return { type: "NilLiteral", value: null, raw: "nil" };
|
|
320
|
+
}
|
|
321
|
+
function combineGroup(group) {
|
|
322
|
+
const variables = [];
|
|
323
|
+
const init = [];
|
|
324
|
+
group.forEach((statement, index) => {
|
|
325
|
+
variables.push(...statement.variables);
|
|
326
|
+
const isLast = index === group.length - 1;
|
|
327
|
+
init.push(...statement.init);
|
|
328
|
+
if (!isLast) {
|
|
329
|
+
const safety = classifyNonTerminal(statement);
|
|
330
|
+
if (safety.safe && safety.needsPadding) {
|
|
331
|
+
const padCount = statement.variables.length - statement.init.length;
|
|
332
|
+
for (let i = 0; i < padCount; i++) {
|
|
333
|
+
init.push(nilLiteral());
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
// 末尾の合成nilは、Luaが変数超過分を自動的にnilで埋めるため省略できる
|
|
339
|
+
// (元から書かれていた明示的なnilを末尾で省略しても意味は変わらない)。
|
|
340
|
+
while (init.length > 0 && init[init.length - 1].type === "NilLiteral") {
|
|
341
|
+
init.pop();
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
type: "LocalStatement",
|
|
345
|
+
variables,
|
|
346
|
+
init,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function mergeRun(run, resolveResult, options) {
|
|
350
|
+
const output = [];
|
|
351
|
+
let groupStart = 0;
|
|
352
|
+
const flushGroup = (endExclusive) => {
|
|
353
|
+
if (endExclusive <= groupStart) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const group = run.slice(groupStart, endExclusive);
|
|
357
|
+
output.push(group.length >= 2 ? combineGroup(group) : group[0]);
|
|
358
|
+
};
|
|
359
|
+
for (let k = 0; k < run.length; k++) {
|
|
360
|
+
const statement = run[k];
|
|
361
|
+
if (options.preserveRequireSplice && isSpliceEligibleRequire(statement)) {
|
|
362
|
+
flushGroup(k);
|
|
363
|
+
output.push(statement);
|
|
364
|
+
groupStart = k + 1;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (k === groupStart) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const previous = run[k - 1];
|
|
371
|
+
const previousSafety = classifyNonTerminal(previous);
|
|
372
|
+
const referencesGroupLocal = initReferencesDeclaredInGroup(statement, run.slice(groupStart, k), resolveResult);
|
|
373
|
+
if (!previousSafety.safe || referencesGroupLocal) {
|
|
374
|
+
flushGroup(k);
|
|
375
|
+
groupStart = k;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
flushGroup(run.length);
|
|
379
|
+
return output;
|
|
380
|
+
}
|
|
381
|
+
function mergeRunsInPlace(body, resolveResult, options) {
|
|
382
|
+
const result = [];
|
|
383
|
+
let i = 0;
|
|
384
|
+
while (i < body.length) {
|
|
385
|
+
const statement = body[i];
|
|
386
|
+
if (statement.type !== "LocalStatement") {
|
|
387
|
+
result.push(statement);
|
|
388
|
+
i++;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
let j = i;
|
|
392
|
+
const run = [];
|
|
393
|
+
while (j < body.length && body[j].type === "LocalStatement") {
|
|
394
|
+
run.push(body[j]);
|
|
395
|
+
j++;
|
|
396
|
+
}
|
|
397
|
+
result.push(...mergeRun(run, resolveResult, options));
|
|
398
|
+
i = j;
|
|
399
|
+
}
|
|
400
|
+
body.splice(0, body.length, ...result);
|
|
401
|
+
}
|
package/package.json
CHANGED
|
@@ -1,33 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "storm-lua-minify",
|
|
3
|
-
"description": "A Lua minifier also outputs source map",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "storm-lua-minify",
|
|
3
|
+
"description": "A Lua minifier also outputs source map",
|
|
4
|
+
"version": "0.3.0",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=22"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"commander": "^12.1.0",
|
|
13
|
+
"luaparse": "^0.3.1",
|
|
14
|
+
"source-map": "^0.7.4"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@eslint/js": "^9.39.5",
|
|
18
|
+
"@types/luaparse": "^0.2.13",
|
|
19
|
+
"@types/node": "^22.20.1",
|
|
20
|
+
"eslint": "^9.39.5",
|
|
21
|
+
"eslint-config-prettier": "^10.1.8",
|
|
22
|
+
"globals": "^17.7.0",
|
|
23
|
+
"prettier": "^3.9.5",
|
|
24
|
+
"typescript": "^5.9.3",
|
|
25
|
+
"typescript-eslint": "^8.63.0",
|
|
26
|
+
"vitest": "^4.1.10"
|
|
27
|
+
},
|
|
28
|
+
"author": "Nona Takahara <nona_t_2612@yahoo.co.jp>",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"scripts": {
|
|
31
|
+
"start": "node ./dist/cli.js",
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"test": "tsc -p tsconfig.eslint.json && npx vitest run",
|
|
34
|
+
"lint": "eslint .",
|
|
35
|
+
"format": "prettier --write .",
|
|
36
|
+
"format:check": "prettier --check ."
|
|
37
|
+
},
|
|
38
|
+
"bin": {
|
|
39
|
+
"storm-lua-minify": "dist/cli.js"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
- develop
|
|
8
|
-
pull_request:
|
|
9
|
-
branches:
|
|
10
|
-
- main
|
|
11
|
-
- develop
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
build-lint-test:
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
steps:
|
|
17
|
-
- name: Checkout
|
|
18
|
-
uses: actions/checkout@v4
|
|
19
|
-
|
|
20
|
-
- name: Setup Node.js
|
|
21
|
-
uses: actions/setup-node@v4
|
|
22
|
-
with:
|
|
23
|
-
node-version: "22.x"
|
|
24
|
-
cache: "npm"
|
|
25
|
-
|
|
26
|
-
- name: Install dependencies
|
|
27
|
-
run: npm ci
|
|
28
|
-
|
|
29
|
-
- name: Build
|
|
30
|
-
run: npm run build
|
|
31
|
-
|
|
32
|
-
- name: Lint
|
|
33
|
-
run: npm run lint
|
|
34
|
-
|
|
35
|
-
- name: Check formatting
|
|
36
|
-
run: npm run format:check
|
|
37
|
-
|
|
38
|
-
- name: Test
|
|
39
|
-
run: npm test
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
|
|
3
|
-
# NOTE: npm publish is disabled for now (see issue #10).
|
|
4
|
-
# Once the repository is ready to publish (NPM_TOKEN secret configured,
|
|
5
|
-
# owner sign-off), uncomment the "Publish" step below.
|
|
6
|
-
on:
|
|
7
|
-
release:
|
|
8
|
-
types: [published]
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
publish:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
steps:
|
|
14
|
-
- name: Checkout
|
|
15
|
-
uses: actions/checkout@v4
|
|
16
|
-
|
|
17
|
-
- name: Setup Node.js
|
|
18
|
-
uses: actions/setup-node@v4
|
|
19
|
-
with:
|
|
20
|
-
node-version: "22.x"
|
|
21
|
-
cache: "npm"
|
|
22
|
-
registry-url: "https://registry.npmjs.org"
|
|
23
|
-
|
|
24
|
-
- name: Install dependencies
|
|
25
|
-
run: npm ci
|
|
26
|
-
|
|
27
|
-
- name: Build
|
|
28
|
-
run: npm run build
|
|
29
|
-
|
|
30
|
-
- name: Lint
|
|
31
|
-
run: npm run lint
|
|
32
|
-
|
|
33
|
-
- name: Test
|
|
34
|
-
run: npm test
|
|
35
|
-
|
|
36
|
-
# - name: Publish
|
|
37
|
-
# run: npm publish
|
|
38
|
-
# env:
|
|
39
|
-
# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/.prettierignore
DELETED
package/.prettierrc.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{}
|
package/eslint.config.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
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
|
-
);
|