storm-lua-minify 0.2.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.
- package/LICENSE +21 -21
- package/README.md +157 -37
- package/dist/aggregateSpecialization.js +406 -0
- package/dist/ast2lua.js +223 -82
- package/dist/astWalk.js +162 -0
- package/dist/callGraph.js +372 -0
- package/dist/cli.js +53 -32
- package/dist/cliOptions.js +36 -0
- package/dist/cliProgress.js +87 -0
- package/dist/config.js +73 -0
- package/dist/constantFold.js +798 -0
- package/dist/controlFlow.js +266 -0
- package/dist/functionRewrites.js +580 -0
- package/dist/generatedAst.js +108 -0
- package/dist/generatedNode.js +23 -0
- package/dist/globalRename.js +35 -0
- package/dist/interproceduralAnalysis.js +842 -0
- package/dist/interproceduralConstants.js +120 -0
- package/dist/keywordLocator.js +68 -0
- package/dist/linker.js +13 -0
- package/dist/luaString.js +157 -0
- package/dist/minifier.js +1243 -35
- package/dist/optimizerAnalysis.js +43 -0
- package/dist/optimizerDiagnostics.js +65 -0
- package/dist/optimizerFacts.js +529 -0
- package/dist/optimizerPass.js +96 -0
- package/dist/optimizerTransaction.js +56 -0
- package/dist/optimizerValueDomain.js +180 -0
- package/dist/options.js +233 -0
- package/dist/progress.js +2 -0
- package/dist/removeUnused.js +145 -0
- package/dist/renamer.js +236 -53
- package/dist/resolver.js +37 -14
- package/dist/runtimeEnvironment.js +105 -0
- package/dist/sourceMetadata.js +314 -0
- package/dist/statementDataflow.js +259 -0
- package/dist/statementScheduler.js +595 -0
- package/dist/symbolLiveness.js +92 -0
- package/dist/tableEffects.js +356 -0
- package/dist/transform.js +40 -0
- package/dist/valueFlow.js +409 -0
- package/dist/wholeProgramExports.js +646 -0
- package/dist/wholeProgramFieldRenames.js +583 -0
- package/dist/wholeProgramFields.js +672 -0
- package/dist/wholeProgramObjects.js +783 -0
- package/package.json +50 -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/dist/index.js +0 -27
- 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/ast2lua.js
CHANGED
|
@@ -6,6 +6,9 @@ exports.MinifyFile = exports.IDENTIFIER_PARTS = void 0;
|
|
|
6
6
|
exports.isKeyword = isKeyword;
|
|
7
7
|
const source_map_1 = require("source-map");
|
|
8
8
|
const linker_1 = require("./linker");
|
|
9
|
+
const keywordLocator_1 = require("./keywordLocator");
|
|
10
|
+
const transform_1 = require("./transform");
|
|
11
|
+
const sourceMetadata_1 = require("./sourceMetadata");
|
|
9
12
|
const PRECEDENCE = {
|
|
10
13
|
or: 1,
|
|
11
14
|
and: 2,
|
|
@@ -135,6 +138,10 @@ function isKeyword(id) {
|
|
|
135
138
|
}
|
|
136
139
|
return false;
|
|
137
140
|
}
|
|
141
|
+
// 末尾が16進整数リテラル(0x/0Xの後に16進数字が1つ以上続く)かどうか。
|
|
142
|
+
// a〜fは識別子の文字集合とも重なるため、isNeedSeparatorのalpha分岐だけでは
|
|
143
|
+
// 区別できない(#53)。
|
|
144
|
+
const HEX_INT_LITERAL_TAIL = /0[xX][0-9a-fA-F]+$/;
|
|
138
145
|
function isNeedSeparator(a, b) {
|
|
139
146
|
const lastCharA = a.slice(-1);
|
|
140
147
|
const firstCharB = b.charAt(0);
|
|
@@ -150,11 +157,15 @@ function isNeedSeparator(a, b) {
|
|
|
150
157
|
// e.g. `local a` + `local b`
|
|
151
158
|
return true;
|
|
152
159
|
}
|
|
153
|
-
|
|
154
|
-
// e.g. `
|
|
155
|
-
//
|
|
156
|
-
|
|
160
|
+
if (firstCharB == "." && HEX_INT_LITERAL_TAIL.test(a)) {
|
|
161
|
+
// e.g. `0xff` + `..` : 16進の浮動小数点(`0xff.8`)も読めてしまうため、
|
|
162
|
+
// 続く`.`をそのまま出すと`0xff.`まで数値として読まれ、10進では起きない
|
|
163
|
+
// 区切り落ち(malformed number)になる(#53)。
|
|
164
|
+
return true;
|
|
157
165
|
}
|
|
166
|
+
// e.g. `not` + `(2>3 or 3<2)`
|
|
167
|
+
// e.g. `x` + `^`
|
|
168
|
+
return false;
|
|
158
169
|
}
|
|
159
170
|
if (regexDigits.test(lastCharA)) {
|
|
160
171
|
if (firstCharB == "(" ||
|
|
@@ -182,7 +193,18 @@ function isNeedSeparator(a, b) {
|
|
|
182
193
|
}
|
|
183
194
|
return false;
|
|
184
195
|
}
|
|
185
|
-
|
|
196
|
+
const requiredWhitespaceByNode = new WeakMap();
|
|
197
|
+
function whitespaceOf(...values) {
|
|
198
|
+
for (const value of values) {
|
|
199
|
+
if (value instanceof source_map_1.SourceNode) {
|
|
200
|
+
const whitespace = requiredWhitespaceByNode.get(value);
|
|
201
|
+
if (whitespace)
|
|
202
|
+
return whitespace;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return "\n";
|
|
206
|
+
}
|
|
207
|
+
function addWithSeparator(val, adding, separator = whitespaceOf(val)) {
|
|
186
208
|
if (isNeedSeparator(val.toString(), wrapArray(adding)
|
|
187
209
|
.map((p) => p.toString())
|
|
188
210
|
.join())) {
|
|
@@ -191,7 +213,7 @@ function addWithSeparator(val, adding, separator = " ") {
|
|
|
191
213
|
val.add(adding);
|
|
192
214
|
return val;
|
|
193
215
|
}
|
|
194
|
-
function prependWithSeparator(val, prepending, separator =
|
|
216
|
+
function prependWithSeparator(val, prepending, separator = whitespaceOf(val)) {
|
|
195
217
|
if (isNeedSeparator(wrapArray(prepending)
|
|
196
218
|
.map((p) => p.toString())
|
|
197
219
|
.join(), val.toString())) {
|
|
@@ -200,7 +222,7 @@ function prependWithSeparator(val, prepending, separator = " ") {
|
|
|
200
222
|
val.prepend(prepending);
|
|
201
223
|
return val;
|
|
202
224
|
}
|
|
203
|
-
function insertSeparator(a, b, separator =
|
|
225
|
+
function insertSeparator(a, b, separator = whitespaceOf(a, b)) {
|
|
204
226
|
return isNeedSeparator(a.toString(), b.toString()) ? separator : undefined;
|
|
205
227
|
}
|
|
206
228
|
class MinifyFile {
|
|
@@ -209,28 +231,29 @@ class MinifyFile {
|
|
|
209
231
|
ast;
|
|
210
232
|
minifier;
|
|
211
233
|
mode;
|
|
234
|
+
requiredWhitespace;
|
|
212
235
|
constructor(fileName, moduleName, ast, minifier, mode) {
|
|
213
236
|
this.fileName = fileName;
|
|
214
237
|
this.moduleName = moduleName;
|
|
215
238
|
this.ast = ast;
|
|
216
239
|
this.minifier = minifier;
|
|
217
240
|
this.mode = mode;
|
|
241
|
+
this.requiredWhitespace = mode.requiredWhitespace ?? "\n";
|
|
218
242
|
}
|
|
219
|
-
|
|
243
|
+
/** 合成文同士の境界も、通常の文リストと同じ区切り判定で出力する。 */
|
|
244
|
+
printGeneratedStatements(statements) {
|
|
245
|
+
return this.formatStatementList(statements);
|
|
246
|
+
}
|
|
247
|
+
parse() {
|
|
220
248
|
const body = this.formatStatementList(this.ast.body);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return body;
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
return body;
|
|
233
|
-
}
|
|
249
|
+
this.minifier
|
|
250
|
+
.getSourceMetadata(this.moduleName)
|
|
251
|
+
.afterModuleComments()
|
|
252
|
+
.filter(sourceMetadata_1.isPreservedComment)
|
|
253
|
+
.forEach((comment) => {
|
|
254
|
+
body.add(["\n", this.sourceNodeHelper(comment, comment.raw)]);
|
|
255
|
+
});
|
|
256
|
+
return body;
|
|
234
257
|
}
|
|
235
258
|
/**
|
|
236
259
|
* モジュール本体の最後の文が「単一の式を返すreturn文」であるときに限り、
|
|
@@ -238,7 +261,7 @@ class MinifyFile {
|
|
|
238
261
|
* 展開したい呼び出し側(#29のレビュー対応)が利用する。
|
|
239
262
|
* 該当しない場合はundefinedを返し、呼び出し側はIIFE方式へフォールバックする。
|
|
240
263
|
*/
|
|
241
|
-
parseAsStatementsAndFinalExpression(
|
|
264
|
+
parseAsStatementsAndFinalExpression() {
|
|
242
265
|
const body = this.ast.body;
|
|
243
266
|
const last = body[body.length - 1];
|
|
244
267
|
if (!last ||
|
|
@@ -247,18 +270,20 @@ class MinifyFile {
|
|
|
247
270
|
return undefined;
|
|
248
271
|
}
|
|
249
272
|
const statements = this.formatStatementList(body.slice(0, -1));
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
273
|
+
const metadata = this.minifier.getSourceMetadata(this.moduleName);
|
|
274
|
+
[
|
|
275
|
+
...metadata.beforeOf(last),
|
|
276
|
+
...metadata.trailingOf(last),
|
|
277
|
+
...metadata.afterModuleComments(),
|
|
278
|
+
]
|
|
279
|
+
.filter(sourceMetadata_1.isPreservedComment)
|
|
280
|
+
.forEach((comment) => {
|
|
281
|
+
statements.add([
|
|
282
|
+
"\n",
|
|
283
|
+
this.sourceNodeHelper(comment, comment.raw),
|
|
284
|
+
"\n",
|
|
285
|
+
]);
|
|
286
|
+
});
|
|
262
287
|
const finalExpression = this.formatExpression(last.arguments[0]);
|
|
263
288
|
return { statements, finalExpression };
|
|
264
289
|
}
|
|
@@ -267,12 +292,79 @@ class MinifyFile {
|
|
|
267
292
|
const column = node?.loc?.start.column;
|
|
268
293
|
// this.fileNameは常にこのMinifyFileインスタンスが担当するモジュール自身の
|
|
269
294
|
// ファイル名(Linkパスで解決済み)なので、ここで出力するノードの由来ファイルとして正しい。
|
|
270
|
-
|
|
295
|
+
const sourceNode = new source_map_1.SourceNode(line == undefined ? null : line, column == undefined ? null : column, this.fileName, chuncks, name);
|
|
296
|
+
requiredWhitespaceByNode.set(sourceNode, this.requiredWhitespace);
|
|
297
|
+
return sourceNode;
|
|
298
|
+
}
|
|
299
|
+
_keywordLocator;
|
|
300
|
+
/**
|
|
301
|
+
* luaparseのASTはキーワード単体の位置を持たないため、`then`/`do`/`until`の
|
|
302
|
+
* ような「文・式の間に挟まるキーワード」の正確な位置は、モジュールの
|
|
303
|
+
* ソースを再トークン化して求める(#14)。初回アクセス時に1回だけ構築する。
|
|
304
|
+
*/
|
|
305
|
+
keywordLocator() {
|
|
306
|
+
if (!this._keywordLocator) {
|
|
307
|
+
const sourceText = this.minifier.moduleSourceText.get(this.moduleName);
|
|
308
|
+
if (sourceText == undefined) {
|
|
309
|
+
throw new Error(this.moduleName + " is not found");
|
|
310
|
+
}
|
|
311
|
+
this._keywordLocator = new keywordLocator_1.KeywordLocator(sourceText, this.minifier.luaParseSettings);
|
|
312
|
+
}
|
|
313
|
+
return this._keywordLocator;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* `anchor`ノードの直後(`anchor.loc.end`以降)で最初に現れる`value`という
|
|
317
|
+
* 値のキーワードトークンを探し、その位置を持つ`SourceNode`を返す。
|
|
318
|
+
* `anchor`は「探しているキーワードの直前に必ず存在する、既に位置が分かって
|
|
319
|
+
* いるノード」(例: `then`の場合は条件式)を渡す。ASTの入れ子構造上、
|
|
320
|
+
* その直前ノードの`loc.end`以降を走査すれば、ネストした同名キーワード
|
|
321
|
+
* (入れ子の`if...then`等)を誤って拾うことはない。
|
|
322
|
+
*/
|
|
323
|
+
keywordAfter(anchor, value) {
|
|
324
|
+
if (!anchor.loc?.end) {
|
|
325
|
+
return this.sourceNodeHelper(undefined, value);
|
|
326
|
+
}
|
|
327
|
+
return this.keywordFrom(anchor.loc?.end, value);
|
|
328
|
+
}
|
|
329
|
+
keywordFrom(from, value) {
|
|
330
|
+
const pos = from ? this.keywordLocator().findFrom(from, value) : undefined;
|
|
331
|
+
return new source_map_1.SourceNode(pos?.line ?? null, pos?.column ?? null, this.fileName, value);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* `if`/`while`/`do`/`for`/`function`を締める`end`キーワードの位置を返す。
|
|
335
|
+
* これらの文は必ず`end`で終わり、末尾に余計な内容が続かないため、
|
|
336
|
+
* 文全体の`loc.end`(luaparseが既に計算済み)から`end`の文字数(3)を
|
|
337
|
+
* 引くだけで、再トークン化なしに正確な位置を求められる。
|
|
338
|
+
*/
|
|
339
|
+
endKeyword(node) {
|
|
340
|
+
const end = node.loc?.end;
|
|
341
|
+
return new source_map_1.SourceNode(end?.line ?? null, end == undefined ? null : end.column - 3, this.fileName, "end");
|
|
271
342
|
}
|
|
272
343
|
formatStatementList(body) {
|
|
273
344
|
const result = this.sourceNodeHelper(undefined, []);
|
|
345
|
+
const metadata = this.minifier.getSourceMetadata(this.moduleName);
|
|
274
346
|
wrapArray(body).forEach((statement) => {
|
|
275
|
-
|
|
347
|
+
const statementNode = this.sourceNodeHelper(undefined, []);
|
|
348
|
+
const sourceStatement = statement.type === "ModuleSplice" ? undefined : statement;
|
|
349
|
+
(sourceStatement ? metadata.beforeOf(sourceStatement) : [])
|
|
350
|
+
.filter(sourceMetadata_1.isPreservedComment)
|
|
351
|
+
.forEach((comment) => {
|
|
352
|
+
statementNode.add([
|
|
353
|
+
this.sourceNodeHelper(comment, comment.raw),
|
|
354
|
+
"\n",
|
|
355
|
+
]);
|
|
356
|
+
});
|
|
357
|
+
statementNode.add(this.formatStatement(statement));
|
|
358
|
+
(sourceStatement ? metadata.trailingOf(sourceStatement) : [])
|
|
359
|
+
.filter(sourceMetadata_1.isPreservedComment)
|
|
360
|
+
.forEach((comment) => {
|
|
361
|
+
statementNode.add([
|
|
362
|
+
this.requiredWhitespace,
|
|
363
|
+
this.sourceNodeHelper(comment, comment.raw),
|
|
364
|
+
"\n",
|
|
365
|
+
]);
|
|
366
|
+
});
|
|
367
|
+
addWithSeparator(result, statementNode, "\n");
|
|
276
368
|
});
|
|
277
369
|
return result;
|
|
278
370
|
}
|
|
@@ -287,7 +379,7 @@ class MinifyFile {
|
|
|
287
379
|
* (呼び出し側は従来のIIFE方式にフォールバックする)。
|
|
288
380
|
*/
|
|
289
381
|
trySpliceRequireStatement(statement) {
|
|
290
|
-
if (this.mode.
|
|
382
|
+
if (this.mode.requireWrapper) {
|
|
291
383
|
return undefined;
|
|
292
384
|
}
|
|
293
385
|
if (statement.variables.length !== 1 || statement.init.length !== 1) {
|
|
@@ -308,7 +400,10 @@ class MinifyFile {
|
|
|
308
400
|
: this.formatExpression(target);
|
|
309
401
|
const result = this.sourceNodeHelper(statement, []);
|
|
310
402
|
addWithSeparator(result, spliced.statements);
|
|
311
|
-
|
|
403
|
+
if (isLocal) {
|
|
404
|
+
addWithSeparator(result, "local");
|
|
405
|
+
}
|
|
406
|
+
addWithSeparator(result, targetNode);
|
|
312
407
|
addWithSeparator(result, "=");
|
|
313
408
|
addWithSeparator(result, spliced.finalExpression);
|
|
314
409
|
return result;
|
|
@@ -319,16 +414,26 @@ class MinifyFile {
|
|
|
319
414
|
* 近い、LifeBoat Modeでの一般的な使い方に合わせるための対応。#29のレビュー対応)。
|
|
320
415
|
*/
|
|
321
416
|
tryInlineBareRequireStatement(expr) {
|
|
322
|
-
if (this.mode.
|
|
417
|
+
if (this.mode.requireWrapper) {
|
|
323
418
|
return undefined;
|
|
324
419
|
}
|
|
325
420
|
const moduleRef = this.matchModuleCallExpression(expr);
|
|
326
421
|
if (!moduleRef || moduleRef.kind !== "require") {
|
|
327
422
|
return undefined;
|
|
328
423
|
}
|
|
424
|
+
// 戻り値を使わないrequireでは、依存モジュール末尾のreturn式も不要になる。
|
|
425
|
+
// return文ごと展開すると呼び出し元を途中でreturnし、後続文がある場合は
|
|
426
|
+
// 構文上も不正になるため、分離可能なら副作用を持つ先行文だけを出力する。
|
|
427
|
+
const spliced = this.minifier.splitModuleForStatementSplice(moduleRef.moduleName);
|
|
428
|
+
if (spliced) {
|
|
429
|
+
return spliced.statements;
|
|
430
|
+
}
|
|
329
431
|
return this.minifier.printModuleInline(moduleRef.moduleName);
|
|
330
432
|
}
|
|
331
433
|
formatStatement(statement) {
|
|
434
|
+
if (statement.type === "ModuleSplice") {
|
|
435
|
+
return this.minifier.printModuleInline(statement.moduleName);
|
|
436
|
+
}
|
|
332
437
|
if (statement.type == "AssignmentStatement" ||
|
|
333
438
|
statement.type == "LocalStatement") {
|
|
334
439
|
const spliced = this.trySpliceRequireStatement(statement);
|
|
@@ -353,10 +458,8 @@ class MinifyFile {
|
|
|
353
458
|
const variables = statement.variables
|
|
354
459
|
.map((variable) => [this.formatExpression(variable), ","])
|
|
355
460
|
.flat();
|
|
356
|
-
const result = this.sourceNodeHelper(statement,
|
|
357
|
-
|
|
358
|
-
this.sourceNodeHelper(undefined, variables.slice(0, -1)),
|
|
359
|
-
]);
|
|
461
|
+
const result = this.sourceNodeHelper(statement, "local");
|
|
462
|
+
addWithSeparator(result, this.sourceNodeHelper(undefined, variables.slice(0, -1)));
|
|
360
463
|
if (statement.init.length) {
|
|
361
464
|
const inits = statement.init
|
|
362
465
|
.map((init) => [this.formatExpression(init), ","])
|
|
@@ -380,12 +483,12 @@ class MinifyFile {
|
|
|
380
483
|
if (clause.type == "IfClause") {
|
|
381
484
|
addWithSeparator(clauseMap, "if");
|
|
382
485
|
addWithSeparator(clauseMap, this.formatExpression(clause.condition));
|
|
383
|
-
addWithSeparator(clauseMap, "then");
|
|
486
|
+
addWithSeparator(clauseMap, this.keywordAfter(clause.condition, "then"));
|
|
384
487
|
}
|
|
385
488
|
else if (clause.type == "ElseifClause") {
|
|
386
489
|
addWithSeparator(clauseMap, "elseif");
|
|
387
490
|
addWithSeparator(clauseMap, this.formatExpression(clause.condition));
|
|
388
|
-
addWithSeparator(clauseMap, "then");
|
|
491
|
+
addWithSeparator(clauseMap, this.keywordAfter(clause.condition, "then"));
|
|
389
492
|
}
|
|
390
493
|
else {
|
|
391
494
|
addWithSeparator(clauseMap, "else");
|
|
@@ -393,21 +496,21 @@ class MinifyFile {
|
|
|
393
496
|
addWithSeparator(clauseMap, this.formatStatementList(clause.body));
|
|
394
497
|
addWithSeparator(result, clauseMap);
|
|
395
498
|
});
|
|
396
|
-
addWithSeparator(result,
|
|
499
|
+
addWithSeparator(result, this.endKeyword(statement));
|
|
397
500
|
return result;
|
|
398
501
|
}
|
|
399
502
|
else if (statement.type == "WhileStatement") {
|
|
400
503
|
const result = this.sourceNodeHelper(statement, "while");
|
|
401
504
|
addWithSeparator(result, this.formatExpression(statement.condition));
|
|
402
|
-
addWithSeparator(result, "do");
|
|
505
|
+
addWithSeparator(result, this.keywordAfter(statement.condition, "do"));
|
|
403
506
|
addWithSeparator(result, this.formatStatementList(statement.body));
|
|
404
|
-
addWithSeparator(result,
|
|
507
|
+
addWithSeparator(result, this.endKeyword(statement));
|
|
405
508
|
return result;
|
|
406
509
|
}
|
|
407
510
|
else if (statement.type == "DoStatement") {
|
|
408
511
|
const result = this.sourceNodeHelper(statement, "do");
|
|
409
512
|
addWithSeparator(result, this.formatStatementList(statement.body));
|
|
410
|
-
addWithSeparator(result,
|
|
513
|
+
addWithSeparator(result, this.endKeyword(statement));
|
|
411
514
|
return result;
|
|
412
515
|
}
|
|
413
516
|
else if (statement.type == "ReturnStatement") {
|
|
@@ -426,12 +529,21 @@ class MinifyFile {
|
|
|
426
529
|
else if (statement.type == "RepeatStatement") {
|
|
427
530
|
const result = this.sourceNodeHelper(statement, "repeat");
|
|
428
531
|
addWithSeparator(result, this.formatStatementList(statement.body));
|
|
429
|
-
|
|
532
|
+
// body内の最後の文があればその直後、無ければ`repeat`キーワード自身の
|
|
533
|
+
// 開始位置から`until`を探す(bodyが空でも、その手前には`repeat`しか
|
|
534
|
+
// 存在しないため安全に検索できる)。
|
|
535
|
+
const untilAnchor = statement.body.length
|
|
536
|
+
? statement.body[statement.body.length - 1].loc?.end
|
|
537
|
+
: statement.loc?.start;
|
|
538
|
+
addWithSeparator(result, this.keywordFrom(untilAnchor, "until"));
|
|
430
539
|
addWithSeparator(result, this.formatExpression(statement.condition));
|
|
431
540
|
return result;
|
|
432
541
|
}
|
|
433
542
|
else if (statement.type == "FunctionDeclaration") {
|
|
434
|
-
const result = this.sourceNodeHelper(statement,
|
|
543
|
+
const result = this.sourceNodeHelper(statement, statement.isLocal ? "local" : "function");
|
|
544
|
+
if (statement.isLocal) {
|
|
545
|
+
addWithSeparator(result, "function");
|
|
546
|
+
}
|
|
435
547
|
if (statement.identifier) {
|
|
436
548
|
addWithSeparator(result, this.formatExpression(statement.identifier));
|
|
437
549
|
}
|
|
@@ -451,7 +563,7 @@ class MinifyFile {
|
|
|
451
563
|
}
|
|
452
564
|
addWithSeparator(result, ")");
|
|
453
565
|
addWithSeparator(result, this.formatStatementList(statement.body));
|
|
454
|
-
addWithSeparator(result,
|
|
566
|
+
addWithSeparator(result, this.endKeyword(statement));
|
|
455
567
|
return result;
|
|
456
568
|
}
|
|
457
569
|
else if (statement.type == "ForGenericStatement") {
|
|
@@ -466,9 +578,9 @@ class MinifyFile {
|
|
|
466
578
|
addWithSeparator(result, variables.slice(0, -1));
|
|
467
579
|
addWithSeparator(result, "in");
|
|
468
580
|
addWithSeparator(result, iterators.slice(0, -1));
|
|
469
|
-
addWithSeparator(result, "do");
|
|
581
|
+
addWithSeparator(result, this.keywordAfter(statement.iterators[statement.iterators.length - 1], "do"));
|
|
470
582
|
addWithSeparator(result, this.formatStatementList(statement.body));
|
|
471
|
-
addWithSeparator(result,
|
|
583
|
+
addWithSeparator(result, this.endKeyword(statement));
|
|
472
584
|
return result;
|
|
473
585
|
}
|
|
474
586
|
else if (statement.type == "ForNumericStatement") {
|
|
@@ -483,9 +595,9 @@ class MinifyFile {
|
|
|
483
595
|
addWithSeparator(result, ",");
|
|
484
596
|
addWithSeparator(result, this.formatExpression(statement.step));
|
|
485
597
|
}
|
|
486
|
-
addWithSeparator(result, "do");
|
|
598
|
+
addWithSeparator(result, this.keywordAfter(statement.step ?? statement.end, "do"));
|
|
487
599
|
addWithSeparator(result, this.formatStatementList(statement.body));
|
|
488
|
-
addWithSeparator(result,
|
|
600
|
+
addWithSeparator(result, this.endKeyword(statement));
|
|
489
601
|
return result;
|
|
490
602
|
}
|
|
491
603
|
else if (statement.type == "LabelStatement") {
|
|
@@ -498,10 +610,9 @@ class MinifyFile {
|
|
|
498
610
|
}
|
|
499
611
|
else if (statement.type == "GotoStatement") {
|
|
500
612
|
// The identifier names in a `GotoStatement` can safely be renamed
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
]);
|
|
613
|
+
const result = this.sourceNodeHelper(statement, "goto");
|
|
614
|
+
addWithSeparator(result, this.generateIdentifier(statement.label));
|
|
615
|
+
return result;
|
|
505
616
|
}
|
|
506
617
|
else {
|
|
507
618
|
throw TypeError("Unknown statement type: `" + JSON.stringify(statement) + "`");
|
|
@@ -514,8 +625,13 @@ class MinifyFile {
|
|
|
514
625
|
if (expression.type == "Identifier") {
|
|
515
626
|
return this.generateIdentifier(expression);
|
|
516
627
|
}
|
|
517
|
-
else if (expression.type == "StringLiteral"
|
|
518
|
-
|
|
628
|
+
else if (expression.type == "StringLiteral") {
|
|
629
|
+
const fieldRename = this.minifier.getFieldRename(expression);
|
|
630
|
+
return fieldRename
|
|
631
|
+
? this.sourceNodeHelper(expression, JSON.stringify(fieldRename.name), fieldRename.originalName)
|
|
632
|
+
: this.sourceNodeHelper(expression, expression.raw);
|
|
633
|
+
}
|
|
634
|
+
else if (expression.type == "NumericLiteral" ||
|
|
519
635
|
expression.type == "BooleanLiteral" ||
|
|
520
636
|
expression.type == "NilLiteral" ||
|
|
521
637
|
expression.type == "VarargLiteral") {
|
|
@@ -544,7 +660,11 @@ class MinifyFile {
|
|
|
544
660
|
if (operator == "^" || operator == "..") {
|
|
545
661
|
associativity = "right";
|
|
546
662
|
}
|
|
547
|
-
else if
|
|
663
|
+
// 上のif(結合則の決定)とは独立に判定する必要がある。else ifだと`^`と`..`が
|
|
664
|
+
// ここに来ず、優先順位の高い演算子の中に現れても丸括弧が付かなくなる
|
|
665
|
+
// (#52。例: `0.5 % (2 .. 16)`が`0.5%2 ..16`になり、`%`の方が優先順位が
|
|
666
|
+
// 高いため意味の違うコードとして読み直されてしまっていた)。
|
|
667
|
+
if (currentPrecedence < options.precedence ||
|
|
548
668
|
(currentPrecedence == options.precedence &&
|
|
549
669
|
associativity != options.direction &&
|
|
550
670
|
options.parent != "+" &&
|
|
@@ -637,12 +757,15 @@ class MinifyFile {
|
|
|
637
757
|
]);
|
|
638
758
|
}
|
|
639
759
|
else if (expression.type == "MemberExpression") {
|
|
760
|
+
const fieldRename = this.minifier.getFieldRename(expression.identifier);
|
|
640
761
|
return this.sourceNodeHelper(expression, [
|
|
641
762
|
this.formatBase(expression.base),
|
|
642
763
|
expression.indexer,
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
764
|
+
fieldRename
|
|
765
|
+
? this.sourceNodeHelper(expression.identifier, fieldRename.name, fieldRename.originalName)
|
|
766
|
+
: this.formatExpression(expression.identifier, {
|
|
767
|
+
preserveIdentifiers: true,
|
|
768
|
+
}),
|
|
646
769
|
]);
|
|
647
770
|
}
|
|
648
771
|
else if (expression.type == "FunctionDeclaration") {
|
|
@@ -663,7 +786,7 @@ class MinifyFile {
|
|
|
663
786
|
result.add(")");
|
|
664
787
|
const body = this.formatStatementList(expression.body);
|
|
665
788
|
addWithSeparator(result, body);
|
|
666
|
-
addWithSeparator(result,
|
|
789
|
+
addWithSeparator(result, this.endKeyword(expression));
|
|
667
790
|
return result;
|
|
668
791
|
}
|
|
669
792
|
else if (expression.type == "TableConstructorExpression") {
|
|
@@ -693,8 +816,13 @@ class MinifyFile {
|
|
|
693
816
|
else {
|
|
694
817
|
// at this point, `field.type == 'TableKeyString'`
|
|
695
818
|
// TODO: keep track of nested scopes (#18)
|
|
819
|
+
const fieldRename = this.minifier.getFieldRename(field.key);
|
|
696
820
|
return this.sourceNodeHelper(field, [
|
|
697
|
-
|
|
821
|
+
fieldRename
|
|
822
|
+
? this.sourceNodeHelper(field.key, fieldRename.name, fieldRename.originalName)
|
|
823
|
+
: this.formatExpression(field.key, {
|
|
824
|
+
preserveIdentifiers: true,
|
|
825
|
+
}),
|
|
698
826
|
"=",
|
|
699
827
|
this.formatExpression(field.value),
|
|
700
828
|
comma,
|
|
@@ -710,16 +838,25 @@ class MinifyFile {
|
|
|
710
838
|
throw TypeError("Unknown expression type: `" + JSON.stringify(expression) + "`");
|
|
711
839
|
}
|
|
712
840
|
}
|
|
841
|
+
/**
|
|
842
|
+
* インデックス・メンバー参照・呼び出しの基底を出力する。
|
|
843
|
+
*
|
|
844
|
+
* Luaの文法で基底にそのまま置けるのは、変数・インデックス式・関数呼び出しだけで
|
|
845
|
+
* ある。それ以外の式は丸括弧で包まないと基底に置けない(`3 .x` も `"a":upper()`
|
|
846
|
+
* も構文エラーになる)。そのため、包む必要のある型を数え上げるのではなく、
|
|
847
|
+
* そのまま置ける形かどうかだけを見る。式の種類が増えても、元のソースには現れない
|
|
848
|
+
* 式を基底へ置く変換(定数畳み込みなど)が入っても、この判定だけで正しい括弧が付く。
|
|
849
|
+
*/
|
|
713
850
|
formatBase(base) {
|
|
714
851
|
const type = base.type;
|
|
715
|
-
const
|
|
716
|
-
type == "
|
|
717
|
-
type == "
|
|
718
|
-
type == "
|
|
719
|
-
type == "
|
|
720
|
-
type == "
|
|
852
|
+
const canStandAsBase = type == "Identifier" ||
|
|
853
|
+
type == "MemberExpression" ||
|
|
854
|
+
type == "IndexExpression" ||
|
|
855
|
+
type == "CallExpression" ||
|
|
856
|
+
type == "StringCallExpression" ||
|
|
857
|
+
type == "TableCallExpression";
|
|
721
858
|
const result = this.sourceNodeHelper(base, this.formatExpression(base));
|
|
722
|
-
if (
|
|
859
|
+
if (!canStandAsBase) {
|
|
723
860
|
prependWithSeparator(result, "(");
|
|
724
861
|
addWithSeparator(result, ")");
|
|
725
862
|
}
|
|
@@ -756,16 +893,16 @@ class MinifyFile {
|
|
|
756
893
|
// dofileは呼び出しごとに毎回展開しなおす(キャッシュしない)
|
|
757
894
|
return this.minifier.printModuleInline(ref.moduleName);
|
|
758
895
|
}
|
|
759
|
-
if (!this.mode.
|
|
896
|
+
if (!this.mode.requireWrapper) {
|
|
760
897
|
// SLモード(無オプション): requireもキャッシュせずその場展開する
|
|
761
898
|
// (挙動互換性のため、ホイストした共有ローカルへの参照にはしない)。
|
|
762
899
|
// 式の位置に置けるようにIIFEで包む。
|
|
763
900
|
const body = this.minifier.printModuleInline(ref.moduleName);
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
901
|
+
const result = this.sourceNodeHelper(expression, "(function()");
|
|
902
|
+
addWithSeparator(result, body);
|
|
903
|
+
addWithSeparator(result, "end");
|
|
904
|
+
result.add(")()");
|
|
905
|
+
return result;
|
|
769
906
|
}
|
|
770
907
|
// -mモードのrequireはそのまま呼び出しとして出力し、実行時のrequire関数に委ねる
|
|
771
908
|
return undefined;
|
|
@@ -776,7 +913,11 @@ class MinifyFile {
|
|
|
776
913
|
const renamed = this.minifier
|
|
777
914
|
.getRenameResult(this.moduleName)
|
|
778
915
|
.nameOf(nameItem);
|
|
779
|
-
|
|
916
|
+
// #8bのエイリアス化はノードの`.name`自体を書き換えるため、loc(元のソース上の
|
|
917
|
+
// 位置)はそのままでもnameItem.nameはもう元の識別子名ではない。Source Mapの
|
|
918
|
+
// namesフィールドには常に「元のソースで書かれていた名前」を載せる必要がある。
|
|
919
|
+
const originalName = (0, transform_1.originalNameOf)(nameItem) ?? nameItem.name;
|
|
920
|
+
return this.sourceNodeHelper(nameItem, renamed ?? nameItem.name, originalName);
|
|
780
921
|
}
|
|
781
922
|
}
|
|
782
923
|
exports.MinifyFile = MinifyFile;
|