gofront 0.0.8 → 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.
Files changed (51) hide show
  1. package/README.md +88 -12
  2. package/package.json +8 -4
  3. package/src/codegen/expressions.js +520 -1717
  4. package/src/{codegen.js → codegen/index.js} +155 -184
  5. package/src/codegen/runtime.js +151 -0
  6. package/src/codegen/statements.js +305 -296
  7. package/src/codegen/stdlib/builder.js +36 -0
  8. package/src/codegen/stdlib/bytes.js +62 -0
  9. package/src/codegen/stdlib/errors.js +17 -0
  10. package/src/codegen/stdlib/fmt.js +101 -0
  11. package/src/codegen/stdlib/gom.js +152 -0
  12. package/src/codegen/stdlib/html.js +17 -0
  13. package/src/codegen/stdlib/index.js +95 -0
  14. package/src/codegen/stdlib/io.js +33 -0
  15. package/src/codegen/stdlib/maps.js +42 -0
  16. package/src/codegen/stdlib/math.js +53 -0
  17. package/src/codegen/stdlib/os.js +10 -0
  18. package/src/codegen/stdlib/path.js +31 -0
  19. package/src/codegen/stdlib/rand.js +24 -0
  20. package/src/codegen/stdlib/regexp.js +51 -0
  21. package/src/codegen/stdlib/slices.js +48 -0
  22. package/src/codegen/stdlib/sort.js +28 -0
  23. package/src/codegen/stdlib/strconv.js +30 -0
  24. package/src/codegen/stdlib/strings.js +83 -0
  25. package/src/codegen/stdlib/time.js +54 -0
  26. package/src/codegen/stdlib/unicode.js +22 -0
  27. package/src/codegen/stdlib/utf8.js +23 -0
  28. package/src/codegen/templ.js +281 -0
  29. package/src/compiler.js +102 -62
  30. package/src/dts-parser.js +355 -371
  31. package/src/index.js +22 -54
  32. package/src/lexer.js +200 -286
  33. package/src/minifier.js +305 -318
  34. package/src/parser/expressions.js +54 -36
  35. package/src/{parser.js → parser/index.js} +12 -5
  36. package/src/parser/statements.js +29 -35
  37. package/src/parser/types.js +31 -29
  38. package/src/templ-lexer.js +737 -0
  39. package/src/templ-parser.js +235 -0
  40. package/src/tokens.js +109 -0
  41. package/src/typechecker/assignability.js +258 -0
  42. package/src/typechecker/expressions.js +559 -549
  43. package/src/typechecker/index.js +555 -0
  44. package/src/typechecker/resolve.js +421 -0
  45. package/src/typechecker/statements.js +267 -280
  46. package/src/typechecker/stdlib/core.js +592 -0
  47. package/src/typechecker/stdlib/extended.js +332 -0
  48. package/src/typechecker/stdlib.js +9 -1060
  49. package/src/typechecker/termination.js +83 -0
  50. package/src/typechecker/types.js +6 -3
  51. package/src/typechecker.js +0 -1271
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
- // Regex literal — only after certain tokens
105
- if (ch === "/") {
106
- const prev = lastNonWhitespaceToken(tokens);
107
- const prevVal = prev?.value;
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
- let j = i + 1;
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
- let j = i + 1;
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
- let j = i + 1;
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
- let j = i + 1;
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
- // Number
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
- if (
271
- two === "==" ||
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
- right.type === "ident" ||
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 mangle(code) {
428
- const tokens = tokenize(code);
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
- // Collect local variable declarations and their scopes
431
- const nameGen = shortNameGeneratorFn();
432
- const renameMap = new Map();
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
- // Find all identifiers that are local declarations (let, const, var, function params)
435
- // and not class/method names or properties
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
- // class Name or extends Name — protect
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
- // let/const/var name — local
463
- if (tok.value === "let" || tok.value === "const" || tok.value === "var") {
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
- // Function params
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
- // Remove protected names from locals
491
- for (const name of protectedNames) {
492
- declaredLocals.delete(name);
493
- }
482
+ for (const name of protectedNames) declaredLocals.delete(name);
483
+ return { declaredLocals };
484
+ }
494
485
 
495
- // Also protect known globals and short names
496
- const globals = new Set([
497
- "console",
498
- "document",
499
- "window",
500
- "Math",
501
- "JSON",
502
- "String",
503
- "Number",
504
- "Boolean",
505
- "Array",
506
- "Object",
507
- "Error",
508
- "Date",
509
- "RegExp",
510
- "Map",
511
- "Set",
512
- "Promise",
513
- "Symbol",
514
- "parseInt",
515
- "parseFloat",
516
- "isNaN",
517
- "isFinite",
518
- "undefined",
519
- "null",
520
- "NaN",
521
- "Infinity",
522
- "TextEncoder",
523
- "TextDecoder",
524
- "setTimeout",
525
- "setInterval",
526
- "clearTimeout",
527
- "clearInterval",
528
- "prototype",
529
- "constructor",
530
- "this",
531
- "super",
532
- "arguments",
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
- // Assign short names
525
+ function _filterRenameCandidates(declaredLocals) {
526
+ for (const name of MANGLE_GLOBALS) declaredLocals.delete(name);
544
527
  for (const name of declaredLocals) {
545
- const short = nameGen();
546
- renameMap.set(name, short);
528
+ if (name.startsWith("__") || name.length <= 1) declaredLocals.delete(name);
547
529
  }
530
+ }
548
531
 
549
- // Apply renaming
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
- if (prev && prev.value === ".") {
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
- // Check if not a default value expression
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