gofront 0.0.9 → 1.0.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 +6 -6
- package/package.json +7 -4
- package/src/codegen/expressions.js +520 -1717
- package/src/codegen/index.js +576 -0
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +305 -296
- package/src/codegen/stdlib/builder.js +36 -0
- package/src/codegen/stdlib/bytes.js +62 -0
- package/src/codegen/stdlib/errors.js +17 -0
- package/src/codegen/stdlib/fmt.js +101 -0
- package/src/codegen/stdlib/gom.js +152 -0
- package/src/codegen/stdlib/html.js +17 -0
- package/src/codegen/stdlib/index.js +95 -0
- package/src/codegen/stdlib/io.js +33 -0
- package/src/codegen/stdlib/maps.js +42 -0
- package/src/codegen/stdlib/math.js +53 -0
- package/src/codegen/stdlib/os.js +10 -0
- package/src/codegen/stdlib/path.js +31 -0
- package/src/codegen/stdlib/rand.js +24 -0
- package/src/codegen/stdlib/regexp.js +51 -0
- package/src/codegen/stdlib/slices.js +48 -0
- package/src/codegen/stdlib/sort.js +28 -0
- package/src/codegen/stdlib/strconv.js +30 -0
- package/src/codegen/stdlib/strings.js +83 -0
- package/src/codegen/stdlib/time.js +54 -0
- package/src/codegen/stdlib/unicode.js +22 -0
- package/src/codegen/stdlib/utf8.js +23 -0
- package/src/codegen/templ.js +281 -0
- package/src/compiler.js +80 -74
- package/src/dts-parser.js +355 -371
- package/src/index.js +13 -49
- package/src/lexer.js +186 -272
- package/src/minifier.js +305 -318
- package/src/parser/expressions.js +51 -34
- package/src/{parser.js → parser/index.js} +4 -4
- package/src/parser/statements.js +24 -34
- package/src/parser/types.js +31 -29
- package/src/templ-parser.js +2 -2
- package/src/tokens.js +109 -0
- package/src/typechecker/assignability.js +258 -0
- package/src/typechecker/expressions.js +559 -549
- package/src/typechecker/index.js +555 -0
- package/src/typechecker/resolve.js +421 -0
- package/src/typechecker/statements.js +267 -280
- package/src/typechecker/stdlib/core.js +592 -0
- package/src/typechecker/stdlib/extended.js +332 -0
- package/src/typechecker/stdlib.js +9 -1060
- package/src/typechecker/termination.js +83 -0
- package/src/codegen.js +0 -882
- package/src/typechecker.js +0 -1299
package/src/minifier.js
CHANGED
|
@@ -75,6 +75,166 @@ const SPACE_AFTER_KW = new Set([
|
|
|
75
75
|
// ── Tokenizer ────────────────────────────────────────────────
|
|
76
76
|
// Splits JS source into tokens preserving strings, template literals, and regexes.
|
|
77
77
|
|
|
78
|
+
const THREE_CHAR_OPS = new Set(["===", "!==", "...", ">>>", "**="]);
|
|
79
|
+
const TWO_CHAR_OPS = new Set([
|
|
80
|
+
"==",
|
|
81
|
+
"!=",
|
|
82
|
+
"<=",
|
|
83
|
+
">=",
|
|
84
|
+
"&&",
|
|
85
|
+
"||",
|
|
86
|
+
"++",
|
|
87
|
+
"--",
|
|
88
|
+
"+=",
|
|
89
|
+
"-=",
|
|
90
|
+
"*=",
|
|
91
|
+
"/=",
|
|
92
|
+
"=>",
|
|
93
|
+
"**",
|
|
94
|
+
"??",
|
|
95
|
+
"?.",
|
|
96
|
+
">>",
|
|
97
|
+
"<<",
|
|
98
|
+
]);
|
|
99
|
+
// Tokens/values after which a `/` starts a regex rather than a division
|
|
100
|
+
const REGEX_CONTEXT_VALUES = new Set([
|
|
101
|
+
"(",
|
|
102
|
+
"[",
|
|
103
|
+
",",
|
|
104
|
+
";",
|
|
105
|
+
"!",
|
|
106
|
+
"=",
|
|
107
|
+
"==",
|
|
108
|
+
"===",
|
|
109
|
+
"!=",
|
|
110
|
+
"!==",
|
|
111
|
+
":",
|
|
112
|
+
"return",
|
|
113
|
+
"case",
|
|
114
|
+
"typeof",
|
|
115
|
+
"{",
|
|
116
|
+
"&&",
|
|
117
|
+
"||",
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
function _isRegexContext(tokens) {
|
|
121
|
+
const prev = lastNonWhitespaceToken(tokens);
|
|
122
|
+
if (!prev) return true;
|
|
123
|
+
return prev.type === "op" || REGEX_CONTEXT_VALUES.has(prev.value);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function _scanRegexLiteral(code, i, len) {
|
|
127
|
+
let j = i + 1;
|
|
128
|
+
let escaped = false;
|
|
129
|
+
let inCharClass = false;
|
|
130
|
+
while (j < len) {
|
|
131
|
+
const rc = code[j];
|
|
132
|
+
if (escaped) {
|
|
133
|
+
escaped = false;
|
|
134
|
+
} else if (rc === "\\") {
|
|
135
|
+
escaped = true;
|
|
136
|
+
} else if (rc === "[") {
|
|
137
|
+
inCharClass = true;
|
|
138
|
+
} else if (rc === "]") {
|
|
139
|
+
inCharClass = false;
|
|
140
|
+
} else if (rc === "/" && !inCharClass) {
|
|
141
|
+
j++;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
j++;
|
|
145
|
+
}
|
|
146
|
+
// consume flags
|
|
147
|
+
while (j < len && /[gimsuy]/.test(code[j])) j++;
|
|
148
|
+
return j;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function _scanStringLiteral(code, i, len, quote) {
|
|
152
|
+
let j = i + 1;
|
|
153
|
+
while (j < len) {
|
|
154
|
+
if (code[j] === "\\" && j + 1 < len) {
|
|
155
|
+
j += 2;
|
|
156
|
+
} else if (code[j] === quote) {
|
|
157
|
+
j++;
|
|
158
|
+
break;
|
|
159
|
+
} else {
|
|
160
|
+
j++;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return j;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function _scanTemplateLiteral(code, i, len) {
|
|
167
|
+
let j = i + 1;
|
|
168
|
+
let depth = 1;
|
|
169
|
+
let braceDepth = 0;
|
|
170
|
+
while (j < len && depth > 0) {
|
|
171
|
+
if (code[j] === "\\" && j + 1 < len) {
|
|
172
|
+
j += 2;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (braceDepth === 0 && code[j] === "`") {
|
|
176
|
+
depth--;
|
|
177
|
+
j++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (code[j] === "$" && code[j + 1] === "{" && braceDepth === 0) {
|
|
181
|
+
braceDepth = 1;
|
|
182
|
+
j += 2;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (braceDepth > 0) {
|
|
186
|
+
if (code[j] === "{") braceDepth++;
|
|
187
|
+
else if (code[j] === "}") braceDepth--;
|
|
188
|
+
}
|
|
189
|
+
j++;
|
|
190
|
+
}
|
|
191
|
+
return j;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function _scanDecimalNumber(code, j, len) {
|
|
195
|
+
while (j < len && /[0-9]/.test(code[j])) j++;
|
|
196
|
+
if (j < len && code[j] === ".") {
|
|
197
|
+
j++;
|
|
198
|
+
while (j < len && /[0-9]/.test(code[j])) j++;
|
|
199
|
+
}
|
|
200
|
+
if (j < len && (code[j] === "e" || code[j] === "E")) {
|
|
201
|
+
j++;
|
|
202
|
+
if (j < len && (code[j] === "+" || code[j] === "-")) j++;
|
|
203
|
+
while (j < len && /[0-9]/.test(code[j])) j++;
|
|
204
|
+
}
|
|
205
|
+
return j;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function _scanNumber(code, i, len) {
|
|
209
|
+
const ch = code[i];
|
|
210
|
+
let j = i + 1;
|
|
211
|
+
if (ch === "0" && j < len && (code[j] === "x" || code[j] === "X")) {
|
|
212
|
+
j++;
|
|
213
|
+
while (j < len && /[0-9a-fA-F]/.test(code[j])) j++;
|
|
214
|
+
} else {
|
|
215
|
+
j = _scanDecimalNumber(code, j, len);
|
|
216
|
+
}
|
|
217
|
+
return j;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function _scanWhitespace(code, i, len) {
|
|
221
|
+
let j = i + 1;
|
|
222
|
+
while (j < len && /\s/.test(code[j])) j++;
|
|
223
|
+
return j;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function _scanIdent(code, i, len) {
|
|
227
|
+
let j = i + 1;
|
|
228
|
+
while (j < len && /[a-zA-Z0-9_$]/.test(code[j])) j++;
|
|
229
|
+
return j;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function _isNumberStart(ch, code, i, len) {
|
|
233
|
+
return (
|
|
234
|
+
/[0-9]/.test(ch) || (ch === "." && i + 1 < len && /[0-9]/.test(code[i + 1]))
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
78
238
|
function tokenize(code) {
|
|
79
239
|
const tokens = [];
|
|
80
240
|
let i = 0;
|
|
@@ -83,7 +243,6 @@ function tokenize(code) {
|
|
|
83
243
|
while (i < len) {
|
|
84
244
|
const ch = code[i];
|
|
85
245
|
|
|
86
|
-
// Line comment
|
|
87
246
|
if (ch === "/" && code[i + 1] === "/") {
|
|
88
247
|
let end = code.indexOf("\n", i);
|
|
89
248
|
if (end === -1) end = len;
|
|
@@ -91,8 +250,6 @@ function tokenize(code) {
|
|
|
91
250
|
i = end;
|
|
92
251
|
continue;
|
|
93
252
|
}
|
|
94
|
-
|
|
95
|
-
// Block comment
|
|
96
253
|
if (ch === "/" && code[i + 1] === "*") {
|
|
97
254
|
const end = code.indexOf("*/", i + 2);
|
|
98
255
|
const closeAt = end === -1 ? len : end + 2;
|
|
@@ -100,123 +257,32 @@ function tokenize(code) {
|
|
|
100
257
|
i = closeAt;
|
|
101
258
|
continue;
|
|
102
259
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const isRegexContext =
|
|
109
|
-
!prev ||
|
|
110
|
-
prev.type === "op" ||
|
|
111
|
-
prevVal === "(" ||
|
|
112
|
-
prevVal === "[" ||
|
|
113
|
-
prevVal === "," ||
|
|
114
|
-
prevVal === ";" ||
|
|
115
|
-
prevVal === "!" ||
|
|
116
|
-
prevVal === "=" ||
|
|
117
|
-
prevVal === "==" ||
|
|
118
|
-
prevVal === "===" ||
|
|
119
|
-
prevVal === "!=" ||
|
|
120
|
-
prevVal === "!==" ||
|
|
121
|
-
prevVal === ":" ||
|
|
122
|
-
prevVal === "return" ||
|
|
123
|
-
prevVal === "case" ||
|
|
124
|
-
prevVal === "typeof" ||
|
|
125
|
-
prevVal === "{" ||
|
|
126
|
-
prevVal === "&&" ||
|
|
127
|
-
prevVal === "||";
|
|
128
|
-
if (isRegexContext) {
|
|
129
|
-
let j = i + 1;
|
|
130
|
-
let escaped = false;
|
|
131
|
-
let inCharClass = false;
|
|
132
|
-
while (j < len) {
|
|
133
|
-
const rc = code[j];
|
|
134
|
-
if (escaped) {
|
|
135
|
-
escaped = false;
|
|
136
|
-
} else if (rc === "\\") {
|
|
137
|
-
escaped = true;
|
|
138
|
-
} else if (rc === "[") {
|
|
139
|
-
inCharClass = true;
|
|
140
|
-
} else if (rc === "]") {
|
|
141
|
-
inCharClass = false;
|
|
142
|
-
} else if (rc === "/" && !inCharClass) {
|
|
143
|
-
j++;
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
j++;
|
|
147
|
-
}
|
|
148
|
-
// consume flags
|
|
149
|
-
while (j < len && /[gimsuy]/.test(code[j])) j++;
|
|
150
|
-
tokens.push({ type: "regex", value: code.slice(i, j) });
|
|
151
|
-
i = j;
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
260
|
+
if (ch === "/" && _isRegexContext(tokens)) {
|
|
261
|
+
const j = _scanRegexLiteral(code, i, len);
|
|
262
|
+
tokens.push({ type: "regex", value: code.slice(i, j) });
|
|
263
|
+
i = j;
|
|
264
|
+
continue;
|
|
154
265
|
}
|
|
155
|
-
|
|
156
|
-
// String literals (double or single quote)
|
|
157
266
|
if (ch === '"' || ch === "'") {
|
|
158
|
-
|
|
159
|
-
while (j < len) {
|
|
160
|
-
if (code[j] === "\\" && j + 1 < len) {
|
|
161
|
-
j += 2;
|
|
162
|
-
} else if (code[j] === ch) {
|
|
163
|
-
j++;
|
|
164
|
-
break;
|
|
165
|
-
} else {
|
|
166
|
-
j++;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
267
|
+
const j = _scanStringLiteral(code, i, len, ch);
|
|
169
268
|
tokens.push({ type: "string", value: code.slice(i, j) });
|
|
170
269
|
i = j;
|
|
171
270
|
continue;
|
|
172
271
|
}
|
|
173
|
-
|
|
174
|
-
// Template literal
|
|
175
272
|
if (ch === "`") {
|
|
176
|
-
|
|
177
|
-
let depth = 1;
|
|
178
|
-
let braceDepth = 0;
|
|
179
|
-
while (j < len && depth > 0) {
|
|
180
|
-
if (code[j] === "\\" && j + 1 < len) {
|
|
181
|
-
j += 2;
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
if (braceDepth === 0 && code[j] === "`") {
|
|
185
|
-
depth--;
|
|
186
|
-
j++;
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
if (code[j] === "$" && code[j + 1] === "{" && braceDepth === 0) {
|
|
190
|
-
braceDepth = 1;
|
|
191
|
-
j += 2;
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
if (braceDepth > 0) {
|
|
195
|
-
if (code[j] === "{") braceDepth++;
|
|
196
|
-
else if (code[j] === "}") {
|
|
197
|
-
braceDepth--;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
j++;
|
|
201
|
-
}
|
|
273
|
+
const j = _scanTemplateLiteral(code, i, len);
|
|
202
274
|
tokens.push({ type: "template", value: code.slice(i, j) });
|
|
203
275
|
i = j;
|
|
204
276
|
continue;
|
|
205
277
|
}
|
|
206
|
-
|
|
207
|
-
// Whitespace
|
|
208
278
|
if (/\s/.test(ch)) {
|
|
209
|
-
|
|
210
|
-
while (j < len && /\s/.test(code[j])) j++;
|
|
279
|
+
const j = _scanWhitespace(code, i, len);
|
|
211
280
|
tokens.push({ type: "ws", value: code.slice(i, j) });
|
|
212
281
|
i = j;
|
|
213
282
|
continue;
|
|
214
283
|
}
|
|
215
|
-
|
|
216
|
-
// Identifier or keyword
|
|
217
284
|
if (/[a-zA-Z_$]/.test(ch)) {
|
|
218
|
-
|
|
219
|
-
while (j < len && /[a-zA-Z0-9_$]/.test(code[j])) j++;
|
|
285
|
+
const j = _scanIdent(code, i, len);
|
|
220
286
|
const word = code.slice(i, j);
|
|
221
287
|
tokens.push({
|
|
222
288
|
type: JS_KEYWORDS.has(word) ? "kw" : "ident",
|
|
@@ -225,74 +291,24 @@ function tokenize(code) {
|
|
|
225
291
|
i = j;
|
|
226
292
|
continue;
|
|
227
293
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
/[0-9]/.test(ch) ||
|
|
232
|
-
(ch === "." && i + 1 < len && /[0-9]/.test(code[i + 1]))
|
|
233
|
-
) {
|
|
234
|
-
let j = i + 1;
|
|
235
|
-
// hex
|
|
236
|
-
if (ch === "0" && j < len && (code[j] === "x" || code[j] === "X")) {
|
|
237
|
-
j++;
|
|
238
|
-
while (j < len && /[0-9a-fA-F]/.test(code[j])) j++;
|
|
239
|
-
} else {
|
|
240
|
-
while (j < len && /[0-9]/.test(code[j])) j++;
|
|
241
|
-
if (j < len && code[j] === ".") {
|
|
242
|
-
j++;
|
|
243
|
-
while (j < len && /[0-9]/.test(code[j])) j++;
|
|
244
|
-
}
|
|
245
|
-
if (j < len && (code[j] === "e" || code[j] === "E")) {
|
|
246
|
-
j++;
|
|
247
|
-
if (j < len && (code[j] === "+" || code[j] === "-")) j++;
|
|
248
|
-
while (j < len && /[0-9]/.test(code[j])) j++;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
294
|
+
if (_isNumberStart(ch, code, i, len)) {
|
|
295
|
+
const j = _scanNumber(code, i, len);
|
|
251
296
|
tokens.push({ type: "num", value: code.slice(i, j) });
|
|
252
297
|
i = j;
|
|
253
298
|
continue;
|
|
254
299
|
}
|
|
255
|
-
|
|
256
|
-
// Multi-char operators
|
|
257
|
-
const two = code.slice(i, i + 2);
|
|
258
300
|
const three = code.slice(i, i + 3);
|
|
259
|
-
if (
|
|
260
|
-
three === "===" ||
|
|
261
|
-
three === "!==" ||
|
|
262
|
-
three === "..." ||
|
|
263
|
-
three === ">>>" ||
|
|
264
|
-
three === "**="
|
|
265
|
-
) {
|
|
301
|
+
if (THREE_CHAR_OPS.has(three)) {
|
|
266
302
|
tokens.push({ type: "op", value: three });
|
|
267
303
|
i += 3;
|
|
268
304
|
continue;
|
|
269
305
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
two === "!=" ||
|
|
273
|
-
two === "<=" ||
|
|
274
|
-
two === ">=" ||
|
|
275
|
-
two === "&&" ||
|
|
276
|
-
two === "||" ||
|
|
277
|
-
two === "++" ||
|
|
278
|
-
two === "--" ||
|
|
279
|
-
two === "+=" ||
|
|
280
|
-
two === "-=" ||
|
|
281
|
-
two === "*=" ||
|
|
282
|
-
two === "/=" ||
|
|
283
|
-
two === "=>" ||
|
|
284
|
-
two === "**" ||
|
|
285
|
-
two === "??" ||
|
|
286
|
-
two === "?." ||
|
|
287
|
-
two === ">>" ||
|
|
288
|
-
two === "<<"
|
|
289
|
-
) {
|
|
306
|
+
const two = code.slice(i, i + 2);
|
|
307
|
+
if (TWO_CHAR_OPS.has(two)) {
|
|
290
308
|
tokens.push({ type: "op", value: two });
|
|
291
309
|
i += 2;
|
|
292
310
|
continue;
|
|
293
311
|
}
|
|
294
|
-
|
|
295
|
-
// Single-char operator/punctuation
|
|
296
312
|
tokens.push({ type: "op", value: ch });
|
|
297
313
|
i += 1;
|
|
298
314
|
}
|
|
@@ -314,51 +330,51 @@ function isIdentLike(type) {
|
|
|
314
330
|
return type === "ident" || type === "kw" || type === "num";
|
|
315
331
|
}
|
|
316
332
|
|
|
333
|
+
// Token types that always need a space after a keyword
|
|
334
|
+
const SPACE_AFTER_KW_RIGHT_TYPES = new Set([
|
|
335
|
+
"ident",
|
|
336
|
+
"kw",
|
|
337
|
+
"num",
|
|
338
|
+
"string",
|
|
339
|
+
"template",
|
|
340
|
+
"regex",
|
|
341
|
+
]);
|
|
342
|
+
// Operator values that do NOT need a space after a keyword
|
|
343
|
+
const NO_SPACE_AFTER_KW_OPS = new Set(["(", "[", "!", "-", "+"]);
|
|
344
|
+
// Adjacent op pairs that would accidentally merge into a different operator
|
|
345
|
+
const _INCREMENT_MERGE_PAIRS = new Set([
|
|
346
|
+
"+|+",
|
|
347
|
+
"-|-",
|
|
348
|
+
"+|++",
|
|
349
|
+
"-|--",
|
|
350
|
+
"++|+",
|
|
351
|
+
"--|−-",
|
|
352
|
+
]);
|
|
353
|
+
|
|
354
|
+
function _wouldMergeUnaryOps(left, right) {
|
|
355
|
+
return (
|
|
356
|
+
(left.value === "+" && right.value === "+") ||
|
|
357
|
+
(left.value === "-" && right.value === "-") ||
|
|
358
|
+
(left.value === "+" && right.value === "++") ||
|
|
359
|
+
(left.value === "-" && right.value === "--") ||
|
|
360
|
+
(left.value === "++" && right.value === "+") ||
|
|
361
|
+
(left.value === "--" && right.value === "-")
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
317
365
|
function needsSpaceBetween(left, right) {
|
|
318
366
|
// Keywords that need space after when followed by ident/num/string/keyword
|
|
319
367
|
if (left.type === "kw" && SPACE_AFTER_KW.has(left.value)) {
|
|
320
|
-
if (
|
|
321
|
-
|
|
322
|
-
right.type === "kw" ||
|
|
323
|
-
right.type === "num" ||
|
|
324
|
-
right.type === "string" ||
|
|
325
|
-
right.type === "template" ||
|
|
326
|
-
right.type === "regex"
|
|
327
|
-
) {
|
|
328
|
-
return true;
|
|
329
|
-
}
|
|
330
|
-
// e.g., `return(` or `return!` — no space needed
|
|
331
|
-
if (
|
|
332
|
-
right.type === "op" &&
|
|
333
|
-
(right.value === "(" ||
|
|
334
|
-
right.value === "[" ||
|
|
335
|
-
right.value === "!" ||
|
|
336
|
-
right.value === "-" ||
|
|
337
|
-
right.value === "+")
|
|
338
|
-
) {
|
|
339
|
-
// "return(" is fine, "delete(" is fine, "new(" is fine
|
|
340
|
-
// But "function(" needs no space, "class{" needs no space... actually they do need space before name
|
|
368
|
+
if (SPACE_AFTER_KW_RIGHT_TYPES.has(right.type)) return true;
|
|
369
|
+
if (right.type === "op" && NO_SPACE_AFTER_KW_OPS.has(right.value))
|
|
341
370
|
return false;
|
|
342
|
-
}
|
|
343
371
|
}
|
|
344
372
|
|
|
345
373
|
// Two adjacent ident-like tokens need a space
|
|
346
374
|
if (isIdentLike(left.type) && isIdentLike(right.type)) return true;
|
|
347
375
|
|
|
348
|
-
// ident followed by string/template/regex needs space in some cases
|
|
349
|
-
// but generally not for GoFront output
|
|
350
|
-
|
|
351
376
|
// Prevent ++ or -- from merging with adjacent + or -
|
|
352
|
-
if (
|
|
353
|
-
(left.value === "+" && right.value === "+") ||
|
|
354
|
-
(left.value === "-" && right.value === "-") ||
|
|
355
|
-
(left.value === "+" && right.value === "++") ||
|
|
356
|
-
(left.value === "-" && right.value === "--") ||
|
|
357
|
-
(left.value === "++" && right.value === "+") ||
|
|
358
|
-
(left.value === "--" && right.value === "-")
|
|
359
|
-
) {
|
|
360
|
-
return true;
|
|
361
|
-
}
|
|
377
|
+
if (_wouldMergeUnaryOps(left, right)) return true;
|
|
362
378
|
|
|
363
379
|
return false;
|
|
364
380
|
}
|
|
@@ -424,15 +440,30 @@ function findPrevNonWs(tokens, index) {
|
|
|
424
440
|
|
|
425
441
|
// ── Stage 3: Identifier mangling ─────────────────────────────
|
|
426
442
|
|
|
427
|
-
function
|
|
428
|
-
|
|
443
|
+
function _collectProtectedIdent(tok, tokens, i, protectedNames) {
|
|
444
|
+
if (tok.value === "class" || tok.value === "extends" || tok.value === ".") {
|
|
445
|
+
const next = nextNonWs(tokens, i);
|
|
446
|
+
if (next && next.tok.type === "ident") protectedNames.add(next.tok.value);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
429
449
|
|
|
430
|
-
|
|
431
|
-
const
|
|
432
|
-
|
|
450
|
+
function _collectVarLocalDecls(tokens, i, declaredLocals) {
|
|
451
|
+
const next = nextNonWs(tokens, i);
|
|
452
|
+
if (next && next.tok.type === "ident") declaredLocals.add(next.tok.value);
|
|
453
|
+
if (next && (next.tok.value === "[" || next.tok.value === "{"))
|
|
454
|
+
collectDestructured(tokens, next.idx, declaredLocals);
|
|
455
|
+
}
|
|
433
456
|
|
|
434
|
-
|
|
435
|
-
|
|
457
|
+
function _collectParamDecls(tokens, i, declaredLocals) {
|
|
458
|
+
const prev = findPrevNonWsToken(tokens, i);
|
|
459
|
+
if (
|
|
460
|
+
prev &&
|
|
461
|
+
(prev.value === "function" || prev.type === "ident" || prev.value === ")")
|
|
462
|
+
)
|
|
463
|
+
collectParams(tokens, i, declaredLocals);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function _collectLocalDecls(tokens) {
|
|
436
467
|
const declaredLocals = new Set();
|
|
437
468
|
const protectedNames = new Set();
|
|
438
469
|
|
|
@@ -440,132 +471,88 @@ function mangle(code) {
|
|
|
440
471
|
const tok = tokens[i];
|
|
441
472
|
if (tok.type !== "kw" && tok.type !== "op") continue;
|
|
442
473
|
|
|
443
|
-
|
|
444
|
-
if (tok.value === "class" || tok.value === "extends") {
|
|
445
|
-
const next = nextNonWs(tokens, i);
|
|
446
|
-
if (next && next.tok.type === "ident") {
|
|
447
|
-
protectedNames.add(next.tok.value);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Property access: .name — protect
|
|
452
|
-
if (tok.value === ".") {
|
|
453
|
-
const next = nextNonWs(tokens, i);
|
|
454
|
-
if (next && next.tok.type === "ident") {
|
|
455
|
-
protectedNames.add(next.tok.value);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Method definitions: name( in class body — protect
|
|
460
|
-
// We approximate: ident followed by ( at class level
|
|
474
|
+
_collectProtectedIdent(tok, tokens, i, protectedNames);
|
|
461
475
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
const next = nextNonWs(tokens, i);
|
|
465
|
-
if (next && next.tok.type === "ident") {
|
|
466
|
-
declaredLocals.add(next.tok.value);
|
|
467
|
-
}
|
|
468
|
-
// Destructuring: let [a, b] or let {a, b}
|
|
469
|
-
if (next && (next.tok.value === "[" || next.tok.value === "{")) {
|
|
470
|
-
collectDestructured(tokens, next.idx, declaredLocals);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
476
|
+
if (tok.value === "let" || tok.value === "const" || tok.value === "var")
|
|
477
|
+
_collectVarLocalDecls(tokens, i, declaredLocals);
|
|
473
478
|
|
|
474
|
-
|
|
475
|
-
if (tok.value === "(") {
|
|
476
|
-
const prev = findPrevNonWsToken(tokens, i);
|
|
477
|
-
if (
|
|
478
|
-
prev &&
|
|
479
|
-
(prev.value === "function" ||
|
|
480
|
-
prev.type === "ident" ||
|
|
481
|
-
prev.value === ")")
|
|
482
|
-
) {
|
|
483
|
-
collectParams(tokens, i, declaredLocals);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Arrow function params handled by ( above
|
|
479
|
+
if (tok.value === "(") _collectParamDecls(tokens, i, declaredLocals);
|
|
488
480
|
}
|
|
489
481
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
482
|
+
for (const name of protectedNames) declaredLocals.delete(name);
|
|
483
|
+
return { declaredLocals };
|
|
484
|
+
}
|
|
494
485
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
]);
|
|
534
|
-
for (const name of globals) declaredLocals.delete(name);
|
|
535
|
-
|
|
536
|
-
// Don't mangle __ prefixed helpers or single-char names
|
|
537
|
-
for (const name of declaredLocals) {
|
|
538
|
-
if (name.startsWith("__") || name.length <= 1) {
|
|
539
|
-
declaredLocals.delete(name);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
486
|
+
const MANGLE_GLOBALS = new Set([
|
|
487
|
+
"console",
|
|
488
|
+
"document",
|
|
489
|
+
"window",
|
|
490
|
+
"Math",
|
|
491
|
+
"JSON",
|
|
492
|
+
"String",
|
|
493
|
+
"Number",
|
|
494
|
+
"Boolean",
|
|
495
|
+
"Array",
|
|
496
|
+
"Object",
|
|
497
|
+
"Error",
|
|
498
|
+
"Date",
|
|
499
|
+
"RegExp",
|
|
500
|
+
"Map",
|
|
501
|
+
"Set",
|
|
502
|
+
"Promise",
|
|
503
|
+
"Symbol",
|
|
504
|
+
"parseInt",
|
|
505
|
+
"parseFloat",
|
|
506
|
+
"isNaN",
|
|
507
|
+
"isFinite",
|
|
508
|
+
"undefined",
|
|
509
|
+
"null",
|
|
510
|
+
"NaN",
|
|
511
|
+
"Infinity",
|
|
512
|
+
"TextEncoder",
|
|
513
|
+
"TextDecoder",
|
|
514
|
+
"setTimeout",
|
|
515
|
+
"setInterval",
|
|
516
|
+
"clearTimeout",
|
|
517
|
+
"clearInterval",
|
|
518
|
+
"prototype",
|
|
519
|
+
"constructor",
|
|
520
|
+
"this",
|
|
521
|
+
"super",
|
|
522
|
+
"arguments",
|
|
523
|
+
]);
|
|
542
524
|
|
|
543
|
-
|
|
525
|
+
function _filterRenameCandidates(declaredLocals) {
|
|
526
|
+
for (const name of MANGLE_GLOBALS) declaredLocals.delete(name);
|
|
544
527
|
for (const name of declaredLocals) {
|
|
545
|
-
|
|
546
|
-
renameMap.set(name, short);
|
|
528
|
+
if (name.startsWith("__") || name.length <= 1) declaredLocals.delete(name);
|
|
547
529
|
}
|
|
530
|
+
}
|
|
548
531
|
|
|
549
|
-
|
|
532
|
+
function _applyRenaming(tokens, renameMap) {
|
|
550
533
|
let out = "";
|
|
551
534
|
for (let i = 0; i < tokens.length; i++) {
|
|
552
535
|
const tok = tokens[i];
|
|
553
536
|
if (tok.type === "ident" && renameMap.has(tok.value)) {
|
|
554
|
-
// Don't rename if preceded by dot (property access)
|
|
555
537
|
const prev = findPrevNonWsToken(tokens, i);
|
|
556
|
-
|
|
557
|
-
out += tok.value;
|
|
558
|
-
} else {
|
|
559
|
-
out += renameMap.get(tok.value);
|
|
560
|
-
}
|
|
538
|
+
out += prev && prev.value === "." ? tok.value : renameMap.get(tok.value);
|
|
561
539
|
} else {
|
|
562
540
|
out += tok.value;
|
|
563
541
|
}
|
|
564
542
|
}
|
|
565
|
-
|
|
566
543
|
return out;
|
|
567
544
|
}
|
|
568
545
|
|
|
546
|
+
function mangle(code) {
|
|
547
|
+
const tokens = tokenize(code);
|
|
548
|
+
const nameGen = shortNameGeneratorFn();
|
|
549
|
+
const renameMap = new Map();
|
|
550
|
+
const { declaredLocals } = _collectLocalDecls(tokens);
|
|
551
|
+
_filterRenameCandidates(declaredLocals);
|
|
552
|
+
for (const name of declaredLocals) renameMap.set(name, nameGen());
|
|
553
|
+
return _applyRenaming(tokens, renameMap);
|
|
554
|
+
}
|
|
555
|
+
|
|
569
556
|
function nextNonWs(tokens, index) {
|
|
570
557
|
for (let i = index + 1; i < tokens.length; i++) {
|
|
571
558
|
if (tokens[i].type !== "ws" && tokens[i].type !== "comment") {
|
|
@@ -597,25 +584,25 @@ function collectDestructured(tokens, startIdx, locals) {
|
|
|
597
584
|
}
|
|
598
585
|
}
|
|
599
586
|
|
|
587
|
+
function _isParamStart(tokens, i) {
|
|
588
|
+
const prev = findPrevNonWsToken(tokens, i);
|
|
589
|
+
return (
|
|
590
|
+
!prev ||
|
|
591
|
+
prev.value === "(" ||
|
|
592
|
+
prev.value === "," ||
|
|
593
|
+
prev.value === "[" ||
|
|
594
|
+
prev.value === "{"
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
|
|
600
598
|
function collectParams(tokens, openIdx, locals) {
|
|
601
599
|
let depth = 1;
|
|
602
600
|
for (let i = openIdx + 1; i < tokens.length && depth > 0; i++) {
|
|
603
601
|
const t = tokens[i];
|
|
604
602
|
if (t.value === "(") depth++;
|
|
605
603
|
else if (t.value === ")") depth--;
|
|
606
|
-
else if (depth === 1 && t.type === "ident")
|
|
607
|
-
|
|
608
|
-
const prev = findPrevNonWsToken(tokens, i);
|
|
609
|
-
if (
|
|
610
|
-
!prev ||
|
|
611
|
-
prev.value === "(" ||
|
|
612
|
-
prev.value === "," ||
|
|
613
|
-
prev.value === "[" ||
|
|
614
|
-
prev.value === "{"
|
|
615
|
-
) {
|
|
616
|
-
locals.add(t.value);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
604
|
+
else if (depth === 1 && t.type === "ident" && _isParamStart(tokens, i))
|
|
605
|
+
locals.add(t.value);
|
|
619
606
|
}
|
|
620
607
|
}
|
|
621
608
|
|