storm-lua-minify 0.1.3 → 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/README.md +64 -0
- package/dist/ast2lua.js +254 -98
- package/dist/cli.js +43 -7
- package/dist/globalRename.js +35 -0
- package/dist/index.js +3 -19
- package/dist/keywordLocator.js +68 -0
- package/dist/linker.js +109 -0
- package/dist/minifier.js +248 -53
- package/dist/output.js +33 -0
- package/dist/renamer.js +101 -0
- package/dist/resolver.js +309 -0
- package/dist/transform.js +401 -0
- package/package.json +29 -15
- package/.eslintrc.json +0 -20
- package/src/ast2lua.ts +0 -812
- package/src/cli.ts +0 -56
- package/src/minifier.ts +0 -118
- package/tsconfig.json +0 -109
package/src/ast2lua.ts
DELETED
|
@@ -1,812 +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
|
-
|
|
9
|
-
export type Chunk = Parser.Chunk & {
|
|
10
|
-
globals?: (Parser.Base<"Identifer"> & {
|
|
11
|
-
name: string;
|
|
12
|
-
isLocal: boolean;
|
|
13
|
-
})[];
|
|
14
|
-
comments?: Comment[];
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const PRECEDENCE: Record<string, number> = {
|
|
18
|
-
or: 1,
|
|
19
|
-
and: 2,
|
|
20
|
-
"<": 3,
|
|
21
|
-
">": 3,
|
|
22
|
-
"<=": 3,
|
|
23
|
-
">=": 3,
|
|
24
|
-
"~=": 3,
|
|
25
|
-
"==": 3,
|
|
26
|
-
"..": 5,
|
|
27
|
-
"+": 6,
|
|
28
|
-
"-": 6, // binary -
|
|
29
|
-
"*": 7,
|
|
30
|
-
"/": 7,
|
|
31
|
-
"%": 7,
|
|
32
|
-
unarynot: 8,
|
|
33
|
-
"unary#": 8,
|
|
34
|
-
"unary-": 8, // unary -
|
|
35
|
-
"^": 10,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const IDENTIFIER_PARTS = [
|
|
39
|
-
"0",
|
|
40
|
-
"1",
|
|
41
|
-
"2",
|
|
42
|
-
"3",
|
|
43
|
-
"4",
|
|
44
|
-
"5",
|
|
45
|
-
"6",
|
|
46
|
-
"7",
|
|
47
|
-
"8",
|
|
48
|
-
"9",
|
|
49
|
-
"a",
|
|
50
|
-
"b",
|
|
51
|
-
"c",
|
|
52
|
-
"d",
|
|
53
|
-
"e",
|
|
54
|
-
"f",
|
|
55
|
-
"g",
|
|
56
|
-
"h",
|
|
57
|
-
"i",
|
|
58
|
-
"j",
|
|
59
|
-
"k",
|
|
60
|
-
"l",
|
|
61
|
-
"m",
|
|
62
|
-
"n",
|
|
63
|
-
"o",
|
|
64
|
-
"p",
|
|
65
|
-
"q",
|
|
66
|
-
"r",
|
|
67
|
-
"s",
|
|
68
|
-
"t",
|
|
69
|
-
"u",
|
|
70
|
-
"v",
|
|
71
|
-
"w",
|
|
72
|
-
"x",
|
|
73
|
-
"y",
|
|
74
|
-
"z",
|
|
75
|
-
"A",
|
|
76
|
-
"B",
|
|
77
|
-
"C",
|
|
78
|
-
"D",
|
|
79
|
-
"E",
|
|
80
|
-
"F",
|
|
81
|
-
"G",
|
|
82
|
-
"H",
|
|
83
|
-
"I",
|
|
84
|
-
"J",
|
|
85
|
-
"K",
|
|
86
|
-
"L",
|
|
87
|
-
"M",
|
|
88
|
-
"N",
|
|
89
|
-
"O",
|
|
90
|
-
"P",
|
|
91
|
-
"Q",
|
|
92
|
-
"R",
|
|
93
|
-
"S",
|
|
94
|
-
"T",
|
|
95
|
-
"U",
|
|
96
|
-
"V",
|
|
97
|
-
"W",
|
|
98
|
-
"X",
|
|
99
|
-
"Y",
|
|
100
|
-
"Z",
|
|
101
|
-
"_",
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
function wrapArray<T>(obj: T | T[]): T[] {
|
|
105
|
-
if (Array.isArray(obj)) {
|
|
106
|
-
return obj;
|
|
107
|
-
}
|
|
108
|
-
return [obj];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function generateZeroes(length: number) {
|
|
112
|
-
let zero = "0";
|
|
113
|
-
let result = "";
|
|
114
|
-
if (length < 1) {
|
|
115
|
-
return result;
|
|
116
|
-
}
|
|
117
|
-
if (length == 1) {
|
|
118
|
-
return zero;
|
|
119
|
-
}
|
|
120
|
-
while (length) {
|
|
121
|
-
if (length & 1) {
|
|
122
|
-
result += zero;
|
|
123
|
-
}
|
|
124
|
-
// eslint-disable-next-line no-cond-assign
|
|
125
|
-
if ((length >>= 1)) {
|
|
126
|
-
zero += zero;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return result;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function isKeyword(id: string) {
|
|
133
|
-
switch (id.length) {
|
|
134
|
-
case 2:
|
|
135
|
-
return "do" == id || "if" == id || "in" == id || "or" == id;
|
|
136
|
-
case 3:
|
|
137
|
-
return (
|
|
138
|
-
"and" == id || "end" == id || "for" == id || "nil" == id || "not" == id
|
|
139
|
-
);
|
|
140
|
-
case 4:
|
|
141
|
-
return "else" == id || "goto" == id || "then" == id || "true" == id;
|
|
142
|
-
case 5:
|
|
143
|
-
return (
|
|
144
|
-
"break" == id ||
|
|
145
|
-
"false" == id ||
|
|
146
|
-
"local" == id ||
|
|
147
|
-
"until" == id ||
|
|
148
|
-
"while" == id
|
|
149
|
-
);
|
|
150
|
-
case 6:
|
|
151
|
-
return "elseif" == id || "repeat" == id || "return" == id;
|
|
152
|
-
case 8:
|
|
153
|
-
return "function" == id;
|
|
154
|
-
}
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function isNeedSeparator(a: string, b: string) {
|
|
159
|
-
const lastCharA = a.slice(-1);
|
|
160
|
-
const firstCharB = b.charAt(0);
|
|
161
|
-
|
|
162
|
-
const regexAlphaUnderscore = /[a-zA-Z_]/;
|
|
163
|
-
const regexAlphaNumUnderscore = /[a-zA-Z0-9_]/;
|
|
164
|
-
const regexDigits = /[0-9]/;
|
|
165
|
-
|
|
166
|
-
if (lastCharA == "" || firstCharB == "") {
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
if (regexAlphaUnderscore.test(lastCharA)) {
|
|
170
|
-
if (regexAlphaNumUnderscore.test(firstCharB)) {
|
|
171
|
-
// e.g. `while` + `1`
|
|
172
|
-
// e.g. `local a` + `local b`
|
|
173
|
-
return true;
|
|
174
|
-
} else {
|
|
175
|
-
// e.g. `not` + `(2>3 or 3<2)`
|
|
176
|
-
// e.g. `x` + `^`
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (regexDigits.test(lastCharA)) {
|
|
181
|
-
if (
|
|
182
|
-
firstCharB == "(" ||
|
|
183
|
-
!(firstCharB == "." || regexAlphaUnderscore.test(firstCharB))
|
|
184
|
-
) {
|
|
185
|
-
// e.g. `1` + `+`
|
|
186
|
-
// e.g. `1` + `==`
|
|
187
|
-
return false;
|
|
188
|
-
} else {
|
|
189
|
-
// e.g. `1` + `..`
|
|
190
|
-
// e.g. `1` + `and`
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (lastCharA == firstCharB && lastCharA == "-") {
|
|
195
|
-
// e.g. `1-` + `-2`
|
|
196
|
-
return true;
|
|
197
|
-
}
|
|
198
|
-
const secondLastCharA = a.slice(-2, -1);
|
|
199
|
-
if (
|
|
200
|
-
lastCharA == "." &&
|
|
201
|
-
secondLastCharA != "." &&
|
|
202
|
-
regexAlphaNumUnderscore.test(firstCharB)
|
|
203
|
-
) {
|
|
204
|
-
// e.g. `1.` + `print`
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
interface ExpressionOptoions {
|
|
211
|
-
precedence?: number;
|
|
212
|
-
preserveIdentifiers?: boolean;
|
|
213
|
-
direction?: "left" | "right" | undefined;
|
|
214
|
-
parent?: string | undefined;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function addWithSeparator(
|
|
218
|
-
val: SourceNode,
|
|
219
|
-
adding: (string | SourceNode)[] | SourceNode | string,
|
|
220
|
-
separator = " "
|
|
221
|
-
) {
|
|
222
|
-
if (
|
|
223
|
-
isNeedSeparator(
|
|
224
|
-
val.toString(),
|
|
225
|
-
wrapArray(adding)
|
|
226
|
-
.map((p) => p.toString())
|
|
227
|
-
.join()
|
|
228
|
-
)
|
|
229
|
-
) {
|
|
230
|
-
val.add(separator);
|
|
231
|
-
}
|
|
232
|
-
val.add(adding);
|
|
233
|
-
return val;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function prependWithSeparator(
|
|
237
|
-
val: SourceNode,
|
|
238
|
-
prepending: (string | SourceNode)[] | SourceNode | string,
|
|
239
|
-
separator = " "
|
|
240
|
-
) {
|
|
241
|
-
if (
|
|
242
|
-
isNeedSeparator(
|
|
243
|
-
wrapArray(prepending)
|
|
244
|
-
.map((p) => p.toString())
|
|
245
|
-
.join(),
|
|
246
|
-
val.toString()
|
|
247
|
-
)
|
|
248
|
-
) {
|
|
249
|
-
val.prepend(separator);
|
|
250
|
-
}
|
|
251
|
-
val.prepend(prepending);
|
|
252
|
-
return val;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function insertSeparator(
|
|
256
|
-
a: string | SourceNode,
|
|
257
|
-
b: string | SourceNode,
|
|
258
|
-
separator = " "
|
|
259
|
-
) {
|
|
260
|
-
return isNeedSeparator(a.toString(), b.toString()) ? separator : undefined;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
export class MinifyFile {
|
|
264
|
-
private fileName: string;
|
|
265
|
-
private ast: Chunk;
|
|
266
|
-
private minifier: Minifier;
|
|
267
|
-
private mode: MinifierMode;
|
|
268
|
-
|
|
269
|
-
constructor(
|
|
270
|
-
fileName: string,
|
|
271
|
-
ast: Chunk,
|
|
272
|
-
minifier: Minifier,
|
|
273
|
-
mode: MinifierMode
|
|
274
|
-
) {
|
|
275
|
-
this.fileName = fileName;
|
|
276
|
-
this.ast = ast;
|
|
277
|
-
this.minifier = minifier;
|
|
278
|
-
this.mode = mode;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
parse(noComment: boolean) {
|
|
282
|
-
const body = this.formatStatementList(this.ast.body);
|
|
283
|
-
if (!noComment && this.ast.comments) {
|
|
284
|
-
const comments = this.ast.comments as Comment[];
|
|
285
|
-
comments.reverse().filter(v => v.raw.includes("--#") || v.raw.includes("[[#")).forEach(comment => {
|
|
286
|
-
body.prepend([this.sourceNodeHelper(comment, comment.raw), "\n"]);
|
|
287
|
-
})
|
|
288
|
-
return body;
|
|
289
|
-
} else {
|
|
290
|
-
return body;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
private sourceNodeHelper(
|
|
295
|
-
node: Parser.Node | undefined,
|
|
296
|
-
chuncks: (SourceNode | string)[] | SourceNode | string,
|
|
297
|
-
name?: string
|
|
298
|
-
) {
|
|
299
|
-
const line = node?.loc?.start.line;
|
|
300
|
-
const column = node?.loc?.start.column;
|
|
301
|
-
return new SourceNode(
|
|
302
|
-
line == undefined ? null : line,
|
|
303
|
-
column == undefined ? null : column,
|
|
304
|
-
this.fileName, // 本当に自分のファイル名でよいかは要検討
|
|
305
|
-
chuncks,
|
|
306
|
-
name
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
private formatStatementList(body: Parser.Statement[] | Parser.Statement) {
|
|
311
|
-
const result = this.sourceNodeHelper(undefined, []);
|
|
312
|
-
wrapArray(body).forEach((statement) => {
|
|
313
|
-
addWithSeparator(result, this.formatStatement(statement), "\n");
|
|
314
|
-
});
|
|
315
|
-
return result;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
private formatStatement(statement: Parser.Statement): SourceNode {
|
|
319
|
-
if (statement.type == "AssignmentStatement") {
|
|
320
|
-
// left-hand side
|
|
321
|
-
const variables = statement.variables
|
|
322
|
-
.map((variable) => [this.formatExpression(variable), ","])
|
|
323
|
-
.flat();
|
|
324
|
-
const inits = statement.init
|
|
325
|
-
.map((init) => [this.formatExpression(init), ","])
|
|
326
|
-
.flat();
|
|
327
|
-
|
|
328
|
-
const result = this.sourceNodeHelper(
|
|
329
|
-
statement,
|
|
330
|
-
this.sourceNodeHelper(undefined, variables.slice(0, -1))
|
|
331
|
-
);
|
|
332
|
-
addWithSeparator(result, "=");
|
|
333
|
-
addWithSeparator(
|
|
334
|
-
result,
|
|
335
|
-
this.sourceNodeHelper(undefined, inits.slice(0, -1))
|
|
336
|
-
);
|
|
337
|
-
return result;
|
|
338
|
-
} else if (statement.type == "LocalStatement") {
|
|
339
|
-
const variables = statement.variables
|
|
340
|
-
.map((variable) => [this.formatExpression(variable), ","])
|
|
341
|
-
.flat();
|
|
342
|
-
const result = this.sourceNodeHelper(statement, [
|
|
343
|
-
"local ",
|
|
344
|
-
this.sourceNodeHelper(undefined, variables.slice(0, -1)),
|
|
345
|
-
]);
|
|
346
|
-
|
|
347
|
-
if (statement.init.length) {
|
|
348
|
-
const inits = statement.init
|
|
349
|
-
.map((init) => [this.formatExpression(init), ","])
|
|
350
|
-
.flat();
|
|
351
|
-
|
|
352
|
-
addWithSeparator(result, "=");
|
|
353
|
-
addWithSeparator(
|
|
354
|
-
result,
|
|
355
|
-
this.sourceNodeHelper(undefined, inits.slice(0, -1))
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
return result;
|
|
359
|
-
} else if (statement.type == "CallStatement") {
|
|
360
|
-
return this.formatExpression(statement.expression); // NOTE: もう一度囲んでもいい
|
|
361
|
-
} else if (statement.type == "IfStatement") {
|
|
362
|
-
const result = this.sourceNodeHelper(statement, []);
|
|
363
|
-
statement.clauses.forEach((clause) => {
|
|
364
|
-
const clauseMap = this.sourceNodeHelper(clause, []);
|
|
365
|
-
if (clause.type == "IfClause") {
|
|
366
|
-
addWithSeparator(clauseMap, "if");
|
|
367
|
-
addWithSeparator(clauseMap, this.formatExpression(clause.condition));
|
|
368
|
-
addWithSeparator(clauseMap, "then");
|
|
369
|
-
} else if (clause.type == "ElseifClause") {
|
|
370
|
-
addWithSeparator(clauseMap, "elseif");
|
|
371
|
-
addWithSeparator(clauseMap, this.formatExpression(clause.condition));
|
|
372
|
-
addWithSeparator(clauseMap, "then");
|
|
373
|
-
} else {
|
|
374
|
-
addWithSeparator(clauseMap, "else");
|
|
375
|
-
}
|
|
376
|
-
addWithSeparator(clauseMap, this.formatStatementList(clause.body));
|
|
377
|
-
addWithSeparator(result, clauseMap);
|
|
378
|
-
});
|
|
379
|
-
addWithSeparator(result, "end");
|
|
380
|
-
return result;
|
|
381
|
-
} else if (statement.type == "WhileStatement") {
|
|
382
|
-
const result = this.sourceNodeHelper(statement, "while");
|
|
383
|
-
addWithSeparator(result, this.formatExpression(statement.condition));
|
|
384
|
-
addWithSeparator(result, "do");
|
|
385
|
-
addWithSeparator(result, this.formatStatementList(statement.body));
|
|
386
|
-
addWithSeparator(result, "end");
|
|
387
|
-
return result;
|
|
388
|
-
} else if (statement.type == "DoStatement") {
|
|
389
|
-
const result = this.sourceNodeHelper(statement, "do");
|
|
390
|
-
addWithSeparator(result, this.formatStatementList(statement.body));
|
|
391
|
-
addWithSeparator(result, "end");
|
|
392
|
-
return result;
|
|
393
|
-
} else if (statement.type == "ReturnStatement") {
|
|
394
|
-
const result = this.sourceNodeHelper(statement, "return");
|
|
395
|
-
if (statement.arguments.length) {
|
|
396
|
-
const returns = statement.arguments
|
|
397
|
-
.map((argument) => [this.formatExpression(argument), ","])
|
|
398
|
-
.flat();
|
|
399
|
-
addWithSeparator(result, returns.slice(0, -1));
|
|
400
|
-
}
|
|
401
|
-
return result;
|
|
402
|
-
} else if (statement.type == "BreakStatement") {
|
|
403
|
-
return this.sourceNodeHelper(statement, "break");
|
|
404
|
-
} else if (statement.type == "RepeatStatement") {
|
|
405
|
-
const result = this.sourceNodeHelper(statement, "repeat");
|
|
406
|
-
addWithSeparator(result, this.formatStatementList(statement.body));
|
|
407
|
-
addWithSeparator(result, "until");
|
|
408
|
-
addWithSeparator(result, this.formatExpression(statement.condition));
|
|
409
|
-
return result;
|
|
410
|
-
} else if (statement.type == "FunctionDeclaration") {
|
|
411
|
-
const result = this.sourceNodeHelper(
|
|
412
|
-
statement,
|
|
413
|
-
(statement.isLocal ? "local " : "") + "function "
|
|
414
|
-
);
|
|
415
|
-
if (statement.identifier) {
|
|
416
|
-
addWithSeparator(result, this.formatExpression(statement.identifier));
|
|
417
|
-
}
|
|
418
|
-
addWithSeparator(result, "(");
|
|
419
|
-
|
|
420
|
-
if (statement.parameters.length) {
|
|
421
|
-
const parameters = statement.parameters
|
|
422
|
-
.map((parameter) => {
|
|
423
|
-
return [
|
|
424
|
-
parameter.type == "Identifier"
|
|
425
|
-
? this.generateIdentifier(parameter)
|
|
426
|
-
: parameter.value,
|
|
427
|
-
",",
|
|
428
|
-
];
|
|
429
|
-
})
|
|
430
|
-
.flat();
|
|
431
|
-
addWithSeparator(result, parameters.slice(0, -1));
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
addWithSeparator(result, ")");
|
|
435
|
-
addWithSeparator(result, this.formatStatementList(statement.body));
|
|
436
|
-
addWithSeparator(result, "end");
|
|
437
|
-
return result;
|
|
438
|
-
} else if (statement.type == "ForGenericStatement") {
|
|
439
|
-
// see also `ForNumericStatement`
|
|
440
|
-
const result = this.sourceNodeHelper(statement, "for");
|
|
441
|
-
const variables = statement.variables
|
|
442
|
-
.map((variable) => [this.generateIdentifier(variable), ","])
|
|
443
|
-
.flat();
|
|
444
|
-
const iterators = statement.iterators
|
|
445
|
-
.map((iterator) => [this.formatExpression(iterator), ","])
|
|
446
|
-
.flat();
|
|
447
|
-
addWithSeparator(result, variables.slice(0, -1));
|
|
448
|
-
addWithSeparator(result, "in");
|
|
449
|
-
addWithSeparator(result, iterators.slice(0, -1));
|
|
450
|
-
addWithSeparator(result, "do");
|
|
451
|
-
addWithSeparator(result, this.formatStatementList(statement.body));
|
|
452
|
-
addWithSeparator(result, "end");
|
|
453
|
-
return result;
|
|
454
|
-
} else if (statement.type == "ForNumericStatement") {
|
|
455
|
-
// The variables in a `ForNumericStatement` are always local
|
|
456
|
-
const result = this.sourceNodeHelper(statement, "for");
|
|
457
|
-
addWithSeparator(result, this.generateIdentifier(statement.variable));
|
|
458
|
-
addWithSeparator(result, "=");
|
|
459
|
-
addWithSeparator(result, this.formatExpression(statement.start));
|
|
460
|
-
addWithSeparator(result, ",");
|
|
461
|
-
addWithSeparator(result, this.formatExpression(statement.end));
|
|
462
|
-
|
|
463
|
-
if (statement.step) {
|
|
464
|
-
addWithSeparator(result, ",");
|
|
465
|
-
addWithSeparator(result, this.formatExpression(statement.step));
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
addWithSeparator(result, "do");
|
|
469
|
-
addWithSeparator(result, this.formatStatementList(statement.body));
|
|
470
|
-
addWithSeparator(result, "end");
|
|
471
|
-
return result;
|
|
472
|
-
} else if (statement.type == "LabelStatement") {
|
|
473
|
-
// The identifier names in a `LabelStatement` can safely be renamed
|
|
474
|
-
return this.sourceNodeHelper(statement, [
|
|
475
|
-
"::",
|
|
476
|
-
this.generateIdentifier(statement.label),
|
|
477
|
-
"::",
|
|
478
|
-
]);
|
|
479
|
-
} else if (statement.type == "GotoStatement") {
|
|
480
|
-
// The identifier names in a `GotoStatement` can safely be renamed
|
|
481
|
-
return this.sourceNodeHelper(statement, [
|
|
482
|
-
"goto ",
|
|
483
|
-
this.generateIdentifier(statement.label),
|
|
484
|
-
]);
|
|
485
|
-
} else {
|
|
486
|
-
throw TypeError(
|
|
487
|
-
"Unknown statement type: `" + JSON.stringify(statement) + "`"
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/*function joinStatements(a: string | SourceNode, b: string | SourceNode, separator = " ") {
|
|
493
|
-
return isNeedSeparator(a.toString(), b.toString()) ? a.toString() + separator + b.toString() : a.toString() + b.toString();
|
|
494
|
-
}*/
|
|
495
|
-
|
|
496
|
-
private formatExpression(
|
|
497
|
-
expression: Parser.Expression,
|
|
498
|
-
argOptions?: ExpressionOptoions
|
|
499
|
-
): SourceNode {
|
|
500
|
-
if (expression.type == "Identifier") {
|
|
501
|
-
return this.sourceNodeHelper(
|
|
502
|
-
expression,
|
|
503
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error
|
|
504
|
-
//@ts-ignore
|
|
505
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
506
|
-
expression.isLocal
|
|
507
|
-
? this.generateIdentifier(expression, true)
|
|
508
|
-
: expression.name,
|
|
509
|
-
expression.name
|
|
510
|
-
);
|
|
511
|
-
} else if (
|
|
512
|
-
expression.type == "StringLiteral" ||
|
|
513
|
-
expression.type == "NumericLiteral" ||
|
|
514
|
-
expression.type == "BooleanLiteral" ||
|
|
515
|
-
expression.type == "NilLiteral" ||
|
|
516
|
-
expression.type == "VarargLiteral"
|
|
517
|
-
) {
|
|
518
|
-
return this.sourceNodeHelper(expression, expression.raw);
|
|
519
|
-
} else if (
|
|
520
|
-
expression.type == "LogicalExpression" ||
|
|
521
|
-
expression.type == "BinaryExpression"
|
|
522
|
-
) {
|
|
523
|
-
const operator = expression.operator;
|
|
524
|
-
const currentPrecedence = PRECEDENCE[operator];
|
|
525
|
-
let associativity: "left" | "right" = "left";
|
|
526
|
-
const options = {
|
|
527
|
-
precedence: 0,
|
|
528
|
-
preserveIdentifiers: false,
|
|
529
|
-
...argOptions,
|
|
530
|
-
};
|
|
531
|
-
|
|
532
|
-
const leftHand = this.formatExpression(expression.left, {
|
|
533
|
-
precedence: currentPrecedence,
|
|
534
|
-
direction: "left",
|
|
535
|
-
parent: operator,
|
|
536
|
-
});
|
|
537
|
-
const rightHand = this.formatExpression(expression.right, {
|
|
538
|
-
precedence: currentPrecedence,
|
|
539
|
-
direction: "right",
|
|
540
|
-
parent: operator,
|
|
541
|
-
});
|
|
542
|
-
if (operator == "^" || operator == "..") {
|
|
543
|
-
associativity = "right";
|
|
544
|
-
} else if (
|
|
545
|
-
currentPrecedence < options.precedence ||
|
|
546
|
-
(currentPrecedence == options.precedence &&
|
|
547
|
-
associativity != options.direction &&
|
|
548
|
-
options.parent != "+" &&
|
|
549
|
-
!(options.parent == "*" && (operator == "/" || operator == "*")))
|
|
550
|
-
) {
|
|
551
|
-
return this.sourceNodeHelper(
|
|
552
|
-
expression,
|
|
553
|
-
[
|
|
554
|
-
"(",
|
|
555
|
-
leftHand,
|
|
556
|
-
insertSeparator(leftHand, operator),
|
|
557
|
-
operator,
|
|
558
|
-
insertSeparator(operator, rightHand),
|
|
559
|
-
rightHand,
|
|
560
|
-
")",
|
|
561
|
-
].filter((p): p is Exclude<typeof p, undefined> => p !== undefined)
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
return this.sourceNodeHelper(
|
|
565
|
-
expression,
|
|
566
|
-
[
|
|
567
|
-
leftHand,
|
|
568
|
-
insertSeparator(leftHand, operator),
|
|
569
|
-
operator,
|
|
570
|
-
insertSeparator(operator, rightHand),
|
|
571
|
-
rightHand,
|
|
572
|
-
].filter((p): p is Exclude<typeof p, undefined> => p !== undefined)
|
|
573
|
-
);
|
|
574
|
-
} else if (expression.type == "UnaryExpression") {
|
|
575
|
-
const operator = expression.operator;
|
|
576
|
-
const currentPrecedence = PRECEDENCE["unary" + operator];
|
|
577
|
-
const options = {
|
|
578
|
-
precedence: 0,
|
|
579
|
-
...argOptions,
|
|
580
|
-
};
|
|
581
|
-
|
|
582
|
-
const p2 = this.formatExpression(expression.argument, {
|
|
583
|
-
precedence: currentPrecedence,
|
|
584
|
-
});
|
|
585
|
-
const result = this.sourceNodeHelper(
|
|
586
|
-
expression,
|
|
587
|
-
[operator, insertSeparator(operator, p2), p2].filter(
|
|
588
|
-
(p): p is Exclude<typeof p, undefined> => p !== undefined
|
|
589
|
-
)
|
|
590
|
-
);
|
|
591
|
-
|
|
592
|
-
if (
|
|
593
|
-
currentPrecedence < options.precedence &&
|
|
594
|
-
// In principle, we should parenthesize the RHS of an
|
|
595
|
-
// expression like `3^-2`, because `^` has higher precedence
|
|
596
|
-
// than unary `-` according to the manual. But that is
|
|
597
|
-
// misleading on the RHS of `^`, since the parser will
|
|
598
|
-
// always try to find a unary operator regardless of
|
|
599
|
-
// precedence.
|
|
600
|
-
!(options.parent == "^" && options.direction == "right")
|
|
601
|
-
) {
|
|
602
|
-
result.prepend("(");
|
|
603
|
-
result.add(")");
|
|
604
|
-
}
|
|
605
|
-
return result;
|
|
606
|
-
} else if (expression.type == "CallExpression") {
|
|
607
|
-
// requireの展開モードは2種類: SLモード-その場に読み込み, FLモード-require相当の関数で呼び出し
|
|
608
|
-
const callExpr = this.formatBase(expression.base).toString();
|
|
609
|
-
if (
|
|
610
|
-
(callExpr === "require" || callExpr === "dofile") &&
|
|
611
|
-
expression?.arguments[0].type === "StringLiteral"
|
|
612
|
-
) {
|
|
613
|
-
const moduleName = expression.arguments[0].raw
|
|
614
|
-
.replaceAll('"', "")
|
|
615
|
-
.replaceAll("'", "");
|
|
616
|
-
|
|
617
|
-
const res = this.minifier.parseModule(moduleName);
|
|
618
|
-
|
|
619
|
-
if (this.mode.moduleLikeLua) {
|
|
620
|
-
if (callExpr === "dofile") {
|
|
621
|
-
return (
|
|
622
|
-
res || this.sourceNodeHelper(undefined, "")
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
// requireならスキップ
|
|
626
|
-
} else {
|
|
627
|
-
return (
|
|
628
|
-
res || this.sourceNodeHelper(undefined, "")
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
const args = expression.arguments
|
|
633
|
-
.map((arg) => [this.formatExpression(arg), ","])
|
|
634
|
-
.flat();
|
|
635
|
-
return this.sourceNodeHelper(expression, [
|
|
636
|
-
this.formatBase(expression.base),
|
|
637
|
-
"(",
|
|
638
|
-
this.sourceNodeHelper(undefined, args.slice(0, -1)),
|
|
639
|
-
")",
|
|
640
|
-
]);
|
|
641
|
-
} else if (expression.type == "TableCallExpression") {
|
|
642
|
-
return this.sourceNodeHelper(expression, [
|
|
643
|
-
this.formatExpression(expression.base),
|
|
644
|
-
this.formatExpression(expression.arguments),
|
|
645
|
-
]);
|
|
646
|
-
} else if (expression.type == "StringCallExpression") {
|
|
647
|
-
return this.sourceNodeHelper(expression, [
|
|
648
|
-
this.formatExpression(expression.base),
|
|
649
|
-
this.formatExpression(expression.argument),
|
|
650
|
-
]);
|
|
651
|
-
} else if (expression.type == "IndexExpression") {
|
|
652
|
-
return this.sourceNodeHelper(expression, [
|
|
653
|
-
this.formatBase(expression.base),
|
|
654
|
-
"[",
|
|
655
|
-
this.formatExpression(expression.index),
|
|
656
|
-
"]",
|
|
657
|
-
]);
|
|
658
|
-
} else if (expression.type == "MemberExpression") {
|
|
659
|
-
return this.sourceNodeHelper(expression, [
|
|
660
|
-
this.formatBase(expression.base),
|
|
661
|
-
expression.indexer,
|
|
662
|
-
this.formatExpression(expression.identifier, {
|
|
663
|
-
preserveIdentifiers: true,
|
|
664
|
-
}),
|
|
665
|
-
]);
|
|
666
|
-
} else if (expression.type == "FunctionDeclaration") {
|
|
667
|
-
const result = this.sourceNodeHelper(expression, ["function", "("]);
|
|
668
|
-
|
|
669
|
-
if (expression.parameters.length) {
|
|
670
|
-
const parameters = expression.parameters
|
|
671
|
-
.map((parameter) => {
|
|
672
|
-
return [
|
|
673
|
-
this.sourceNodeHelper(
|
|
674
|
-
parameter,
|
|
675
|
-
parameter.type === "Identifier"
|
|
676
|
-
? this.generateIdentifier(parameter)
|
|
677
|
-
: parameter.value
|
|
678
|
-
),
|
|
679
|
-
",",
|
|
680
|
-
];
|
|
681
|
-
})
|
|
682
|
-
.flat();
|
|
683
|
-
addWithSeparator(result, parameters.slice(0, -1));
|
|
684
|
-
}
|
|
685
|
-
result.add(")");
|
|
686
|
-
const body = this.formatStatementList(expression.body);
|
|
687
|
-
addWithSeparator(result, body);
|
|
688
|
-
addWithSeparator(result, "end");
|
|
689
|
-
return result;
|
|
690
|
-
} else if (expression.type == "TableConstructorExpression") {
|
|
691
|
-
const result = this.sourceNodeHelper(expression, "{");
|
|
692
|
-
const fields = expression.fields
|
|
693
|
-
.map((field, ix, ar) => {
|
|
694
|
-
// Stormworks "propert" Trailing Comma: https://nona-takahara.github.io/blog/entry11.html
|
|
695
|
-
const comma =
|
|
696
|
-
ix !== ar.length - 1 ||
|
|
697
|
-
this.formatExpression(field.value).toString().includes("property")
|
|
698
|
-
? ","
|
|
699
|
-
: undefined;
|
|
700
|
-
|
|
701
|
-
if (field.type == "TableKey") {
|
|
702
|
-
return this.sourceNodeHelper(
|
|
703
|
-
field,
|
|
704
|
-
[
|
|
705
|
-
this.sourceNodeHelper(undefined, [
|
|
706
|
-
"[",
|
|
707
|
-
this.formatExpression(field.key),
|
|
708
|
-
"]",
|
|
709
|
-
]),
|
|
710
|
-
"=",
|
|
711
|
-
this.formatExpression(field.value),
|
|
712
|
-
comma,
|
|
713
|
-
].filter(
|
|
714
|
-
(p): p is Exclude<typeof p, undefined> => p !== undefined
|
|
715
|
-
)
|
|
716
|
-
);
|
|
717
|
-
} else if (field.type == "TableValue") {
|
|
718
|
-
return [this.formatExpression(field.value), comma].filter(
|
|
719
|
-
(p): p is Exclude<typeof p, undefined> => p !== undefined
|
|
720
|
-
);
|
|
721
|
-
} else {
|
|
722
|
-
// at this point, `field.type == 'TableKeyString'`
|
|
723
|
-
// TODO: keep track of nested scopes (#18)
|
|
724
|
-
return this.sourceNodeHelper(
|
|
725
|
-
field,
|
|
726
|
-
[
|
|
727
|
-
this.formatExpression(field.key, { preserveIdentifiers: true }),
|
|
728
|
-
"=",
|
|
729
|
-
this.formatExpression(field.value),
|
|
730
|
-
comma,
|
|
731
|
-
].filter(
|
|
732
|
-
(p): p is Exclude<typeof p, undefined> => p !== undefined
|
|
733
|
-
)
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
})
|
|
737
|
-
.flat();
|
|
738
|
-
addWithSeparator(result, fields);
|
|
739
|
-
addWithSeparator(result, "}");
|
|
740
|
-
return result;
|
|
741
|
-
} else {
|
|
742
|
-
throw TypeError(
|
|
743
|
-
"Unknown expression type: `" + JSON.stringify(expression) + "`"
|
|
744
|
-
);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
private formatBase(base: Parser.Expression): SourceNode {
|
|
749
|
-
const type = base.type;
|
|
750
|
-
const needsParens =
|
|
751
|
-
type == "CallExpression" ||
|
|
752
|
-
type == "BinaryExpression" ||
|
|
753
|
-
type == "FunctionDeclaration" ||
|
|
754
|
-
type == "TableConstructorExpression" ||
|
|
755
|
-
type == "LogicalExpression" ||
|
|
756
|
-
type == "StringLiteral";
|
|
757
|
-
const result = this.sourceNodeHelper(base, this.formatExpression(base));
|
|
758
|
-
if (needsParens) {
|
|
759
|
-
prependWithSeparator(result, "(");
|
|
760
|
-
addWithSeparator(result, ")");
|
|
761
|
-
}
|
|
762
|
-
return result;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
private currentIdentifier = "";
|
|
766
|
-
|
|
767
|
-
private generateIdentifier(
|
|
768
|
-
nameItem: Parser.Identifier,
|
|
769
|
-
nested = false
|
|
770
|
-
): SourceNode {
|
|
771
|
-
if (nameItem.name === "self") {
|
|
772
|
-
return this.sourceNodeHelper(nameItem, "self", "self");
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
const defined = this.minifier.identifierMap.get(nameItem.name);
|
|
776
|
-
if (defined) {
|
|
777
|
-
return this.sourceNodeHelper(nameItem, defined, nameItem.name); // 第3引数は要調査
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
const length = this.currentIdentifier.length;
|
|
781
|
-
let position = length - 1;
|
|
782
|
-
let character: string;
|
|
783
|
-
let index;
|
|
784
|
-
while (position >= 0) {
|
|
785
|
-
character = this.currentIdentifier.charAt(position);
|
|
786
|
-
index = IDENTIFIER_PARTS.indexOf(character);
|
|
787
|
-
if (index != IDENTIFIER_PARTS.length - 1) {
|
|
788
|
-
this.currentIdentifier =
|
|
789
|
-
this.currentIdentifier.substring(0, position) +
|
|
790
|
-
IDENTIFIER_PARTS[index + 1] +
|
|
791
|
-
generateZeroes(length - (position + 1));
|
|
792
|
-
if (
|
|
793
|
-
isKeyword(this.currentIdentifier) ||
|
|
794
|
-
this.minifier.identifiersInUse.has(this.currentIdentifier)
|
|
795
|
-
) {
|
|
796
|
-
return this.generateIdentifier(nameItem, nested);
|
|
797
|
-
}
|
|
798
|
-
this.minifier.identifierMap.set(nameItem.name, this.currentIdentifier);
|
|
799
|
-
return this.generateIdentifier(nameItem, nested);
|
|
800
|
-
}
|
|
801
|
-
--position;
|
|
802
|
-
}
|
|
803
|
-
this.currentIdentifier = "a" + generateZeroes(length);
|
|
804
|
-
if (this.minifier.identifiersInUse.has(this.currentIdentifier)) {
|
|
805
|
-
return this.generateIdentifier(nameItem, nested);
|
|
806
|
-
}
|
|
807
|
-
this.minifier.identifierMap.set(nameItem.name, this.currentIdentifier);
|
|
808
|
-
return this.generateIdentifier(nameItem, nested);
|
|
809
|
-
|
|
810
|
-
// return this.sourceNodeHelper(nameItem, nameItem.name, nested ? nameItem.name : undefined);
|
|
811
|
-
}
|
|
812
|
-
}
|