flint-compiler 4.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.
@@ -0,0 +1,1045 @@
1
+ // Flint Compiler v3 — Optimizer
2
+ // Real dead code elimination, constant folding, static subtree hoisting,
3
+ // compile-time CSS scoping, and code optimizations
4
+ // ─── Default Options ────────────────────────────────────────────
5
+ const DEFAULT_OPTIONS = {
6
+ deadCodeElimination: true,
7
+ constantFolding: true,
8
+ deadStoreElimination: true,
9
+ functionInlining: true,
10
+ jsxOptimization: true,
11
+ treeShaking: true,
12
+ staticHoisting: true,
13
+ cssScoping: true,
14
+ autoMemoization: true,
15
+ minificationLevel: 1,
16
+ };
17
+ function walkAST(node, visitor) {
18
+ if (!node || typeof node !== 'object')
19
+ return node;
20
+ const result = visitor(node);
21
+ if (result === null)
22
+ return null;
23
+ if (result !== undefined)
24
+ return result;
25
+ // Walk children based on node type
26
+ for (const key of Object.keys(node)) {
27
+ if (key === 'type' || key === 'start' || key === 'end')
28
+ continue;
29
+ const child = node[key];
30
+ if (Array.isArray(child)) {
31
+ const newArr = [];
32
+ for (const item of child) {
33
+ if (item && typeof item === 'object' && item.type) {
34
+ const walked = walkAST(item, visitor);
35
+ if (walked !== null)
36
+ newArr.push(walked);
37
+ }
38
+ else {
39
+ newArr.push(item);
40
+ }
41
+ }
42
+ ;
43
+ node[key] = newArr;
44
+ }
45
+ else if (child && typeof child === 'object' && child.type) {
46
+ const walked = walkAST(child, visitor);
47
+ if (walked === null) {
48
+ ;
49
+ node[key] = null;
50
+ }
51
+ else {
52
+ ;
53
+ node[key] = walked;
54
+ }
55
+ }
56
+ }
57
+ return node;
58
+ }
59
+ // ─── Optimizer Class ────────────────────────────────────────────
60
+ export class Optimizer {
61
+ options;
62
+ warnings = [];
63
+ stats = {
64
+ originalSize: 0,
65
+ optimizedSize: 0,
66
+ reduction: 0,
67
+ eliminatedNodes: 0,
68
+ inlinedFunctions: 0,
69
+ constantFolds: 0,
70
+ staticHoisted: 0,
71
+ autoMemoized: 0,
72
+ };
73
+ constructor(options = {}) {
74
+ this.options = { ...DEFAULT_OPTIONS, ...options };
75
+ }
76
+ /**
77
+ * Optimize AST
78
+ */
79
+ optimize(ast) {
80
+ this.warnings = [];
81
+ this.stats = {
82
+ originalSize: JSON.stringify(ast).length,
83
+ optimizedSize: 0,
84
+ reduction: 0,
85
+ eliminatedNodes: 0,
86
+ inlinedFunctions: 0,
87
+ constantFolds: 0,
88
+ staticHoisted: 0,
89
+ autoMemoized: 0,
90
+ };
91
+ let optimized = this.cloneAST(ast);
92
+ // Run optimization passes (multiple passes for better results)
93
+ for (let pass = 0; pass < 2; pass++) {
94
+ if (this.options.deadCodeElimination) {
95
+ optimized = this.eliminateDeadCode(optimized);
96
+ }
97
+ if (this.options.constantFolding) {
98
+ optimized = this.foldConstants(optimized);
99
+ }
100
+ if (this.options.deadStoreElimination) {
101
+ optimized = this.eliminateDeadStores(optimized);
102
+ }
103
+ if (this.options.functionInlining) {
104
+ optimized = this.inlineFunctions(optimized);
105
+ }
106
+ }
107
+ if (this.options.jsxOptimization) {
108
+ optimized = this.optimizeJSX(optimized);
109
+ }
110
+ // Auto-memoization pass (React Compiler-like)
111
+ if (this.options.autoMemoization) {
112
+ optimized = this.autoMemoize(optimized);
113
+ }
114
+ this.stats.optimizedSize = JSON.stringify(optimized).length;
115
+ this.stats.reduction = this.stats.originalSize - this.stats.optimizedSize;
116
+ return {
117
+ code: this.generateCode(optimized),
118
+ ast: optimized,
119
+ warnings: this.warnings,
120
+ stats: this.stats,
121
+ };
122
+ }
123
+ cloneAST(ast) {
124
+ return JSON.parse(JSON.stringify(ast));
125
+ }
126
+ // ─── Dead Code Elimination ────────────────────────────────────
127
+ eliminateDeadCode(ast) {
128
+ return walkAST(ast, (node) => {
129
+ // Remove if(false) { ... } and if(true) { ... } else { ... }
130
+ if (node.type === 'IfStatement') {
131
+ const test = node.test;
132
+ const val = this.evaluateConstant(test);
133
+ if (val !== undefined) {
134
+ if (val) {
135
+ // if(true) → keep consequent
136
+ this.stats.eliminatedNodes++;
137
+ return node.consequent;
138
+ }
139
+ else {
140
+ // if(false) → keep alternate or remove
141
+ this.stats.eliminatedNodes++;
142
+ if (node.alternate) {
143
+ return node.alternate;
144
+ }
145
+ return null;
146
+ }
147
+ }
148
+ }
149
+ // Remove while(false) { ... }
150
+ if (node.type === 'WhileStatement') {
151
+ const val = this.evaluateConstant(node.test);
152
+ if (val === false) {
153
+ this.stats.eliminatedNodes++;
154
+ return null;
155
+ }
156
+ }
157
+ // Remove dead code after return/throw/break/continue
158
+ if (node.type === 'BlockStatement' && node.body) {
159
+ const filtered = [];
160
+ let foundTerminator = false;
161
+ for (const stmt of node.body) {
162
+ if (foundTerminator) {
163
+ if (stmt.type === 'FunctionDeclaration') {
164
+ // Keep function declarations (hoisted)
165
+ filtered.push(stmt);
166
+ }
167
+ else {
168
+ this.stats.eliminatedNodes++;
169
+ this.warnings.push({
170
+ type: 'dead_code',
171
+ message: 'Unreachable code detected after return/throw',
172
+ });
173
+ }
174
+ }
175
+ else {
176
+ filtered.push(stmt);
177
+ if (stmt.type === 'ReturnStatement' ||
178
+ stmt.type === 'ThrowStatement' ||
179
+ stmt.type === 'BreakStatement' ||
180
+ stmt.type === 'ContinueStatement') {
181
+ foundTerminator = true;
182
+ }
183
+ }
184
+ }
185
+ node.body = filtered;
186
+ }
187
+ // Remove empty statements
188
+ if (node.type === 'EmptyStatement') {
189
+ this.stats.eliminatedNodes++;
190
+ return null;
191
+ }
192
+ // Remove variables with only assignments and no reads (dead stores)
193
+ if (node.type === 'VariableDeclaration') {
194
+ // Will be handled by eliminateDeadStores
195
+ }
196
+ return undefined;
197
+ });
198
+ }
199
+ // ─── Constant Folding ─────────────────────────────────────────
200
+ foldConstants(ast) {
201
+ return walkAST(ast, (node) => {
202
+ // Fold binary expressions: 1+2 → 3, "a"+"b" → "ab"
203
+ if (node.type === 'BinaryExpression') {
204
+ const left = this.evaluateConstant(node.left);
205
+ const right = this.evaluateConstant(node.right);
206
+ if (left !== undefined && right !== undefined) {
207
+ const result = this.evaluateBinary(node.operator, left, right);
208
+ if (result !== undefined) {
209
+ this.stats.constantFolds++;
210
+ return this.createLiteral(result);
211
+ }
212
+ }
213
+ }
214
+ // Fold unary expressions: !true → false, -5 → -5
215
+ if (node.type === 'UnaryExpression') {
216
+ const arg = this.evaluateConstant(node.argument);
217
+ if (arg !== undefined) {
218
+ const result = this.evaluateUnary(node.operator, arg);
219
+ if (result !== undefined) {
220
+ this.stats.constantFolds++;
221
+ return this.createLiteral(result);
222
+ }
223
+ }
224
+ }
225
+ // Fold logical expressions: true && false → false, null || "default" → "default"
226
+ if (node.type === 'LogicalExpression') {
227
+ const left = this.evaluateConstant(node.left);
228
+ const right = this.evaluateConstant(node.right);
229
+ if (left !== undefined && right !== undefined) {
230
+ const result = this.evaluateLogical(node.operator, left, right);
231
+ if (result !== undefined) {
232
+ this.stats.constantFolds++;
233
+ return this.createLiteral(result);
234
+ }
235
+ }
236
+ }
237
+ // Fold template literals with only static parts
238
+ if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {
239
+ const value = node.quasis.map((q) => q.value.cooked || '').join('');
240
+ this.stats.constantFolds++;
241
+ return this.createLiteral(value);
242
+ }
243
+ // Fold conditional expressions: true ? a : b → a
244
+ if (node.type === 'ConditionalExpression') {
245
+ const test = this.evaluateConstant(node.test);
246
+ if (test !== undefined) {
247
+ this.stats.constantFolds++;
248
+ return test ? node.consequent : node.alternate;
249
+ }
250
+ }
251
+ return undefined;
252
+ });
253
+ }
254
+ // ─── Dead Store Elimination ───────────────────────────────────
255
+ eliminateDeadStores(ast) {
256
+ const usedVariables = new Set();
257
+ // First pass: collect all variable usages
258
+ walkAST(ast, (node) => {
259
+ if (node.type === 'Identifier') {
260
+ usedVariables.add(node.name);
261
+ }
262
+ return undefined;
263
+ });
264
+ // Second pass: remove unused variable declarations
265
+ return walkAST(ast, (node) => {
266
+ if (node.type === 'VariableDeclaration') {
267
+ const filtered = [];
268
+ for (const decl of node.declarations) {
269
+ if (decl.id.type === 'Identifier') {
270
+ const name = decl.id.name;
271
+ // Keep if variable is used or has side effects in init
272
+ if (usedVariables.has(name) || this.hasSideEffects(decl.init)) {
273
+ filtered.push(decl);
274
+ }
275
+ else {
276
+ this.stats.eliminatedNodes++;
277
+ this.warnings.push({
278
+ type: 'unused_variable',
279
+ message: `Variable "${name}" is declared but never used`,
280
+ });
281
+ }
282
+ }
283
+ else {
284
+ filtered.push(decl);
285
+ }
286
+ }
287
+ if (filtered.length === 0) {
288
+ return null;
289
+ }
290
+ node.declarations = filtered;
291
+ }
292
+ return undefined;
293
+ });
294
+ }
295
+ // ─── Function Inlining ────────────────────────────────────────
296
+ inlineFunctions(ast) {
297
+ const functionMap = new Map();
298
+ // Collect small function definitions
299
+ walkAST(ast, (node) => {
300
+ if (node.type === 'VariableDeclarator' && node.init) {
301
+ const init = node.init;
302
+ if (init.type === 'ArrowFunctionExpression' ||
303
+ init.type === 'FunctionExpression') {
304
+ // Only inline small functions (single expression body)
305
+ if (init.body.type === 'BlockStatement' &&
306
+ init.body.body.length === 1 &&
307
+ init.body.body[0].type === 'ReturnStatement') {
308
+ const body = init.body.body[0].argument;
309
+ if (this.isSmallExpression(body)) {
310
+ functionMap.set(node.id.name, {
311
+ params: init.params,
312
+ body,
313
+ });
314
+ }
315
+ }
316
+ else if (init.body.type !== 'BlockStatement') {
317
+ // Arrow with expression body
318
+ if (this.isSmallExpression(init.body)) {
319
+ functionMap.set(node.id.name, {
320
+ params: init.params,
321
+ body: init.body,
322
+ });
323
+ }
324
+ }
325
+ }
326
+ }
327
+ return undefined;
328
+ });
329
+ // Replace function calls with inlined body
330
+ return walkAST(ast, (node) => {
331
+ if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
332
+ const func = functionMap.get(node.callee.name);
333
+ if (func && func.params.length === node.arguments.length) {
334
+ // Simple inlining: replace parameter names with arguments
335
+ const inlined = this.substituteParams(func.body, func.params, node.arguments);
336
+ this.stats.inlinedFunctions++;
337
+ return inlined;
338
+ }
339
+ }
340
+ return undefined;
341
+ });
342
+ }
343
+ // ─── JSX Optimization ─────────────────────────────────────────
344
+ optimizeJSX(ast) {
345
+ return walkAST(ast, (node) => {
346
+ // Flatten nested fragments: <><a/></> → <a/>
347
+ if (node.type === 'JSXFragment') {
348
+ if (node.children.length === 1 && node.children[0].type === 'JSXFragment') {
349
+ return node.children[0];
350
+ }
351
+ // Fragment with single element
352
+ if (node.children.length === 1 && node.children[0].type === 'JSXElement') {
353
+ return node.children[0];
354
+ }
355
+ }
356
+ // Optimize static JSX strings
357
+ if (node.type === 'JSXElement' && node.openingElement) {
358
+ const attrs = node.openingElement.attributes;
359
+ // Remove empty attributes
360
+ node.openingElement.attributes = attrs.filter((attr) => attr.type !== 'JSXAttribute' || attr.value !== null);
361
+ }
362
+ // Convert <div>{""}</div> → <div></div>
363
+ if (node.type === 'JSXElement' && node.children) {
364
+ node.children = node.children.filter((child) => {
365
+ if (child.type === 'JSXExpressionContainer' && child.expression) {
366
+ if (child.expression.type === 'Literal' &&
367
+ (child.expression.value === '' || child.expression.value === null)) {
368
+ return false;
369
+ }
370
+ }
371
+ return true;
372
+ });
373
+ }
374
+ return undefined;
375
+ });
376
+ }
377
+ // ─── Helpers ──────────────────────────────────────────────────
378
+ evaluateConstant(node) {
379
+ if (!node)
380
+ return undefined;
381
+ if (node.type === 'Literal') {
382
+ return node.value;
383
+ }
384
+ if (node.type === 'Identifier') {
385
+ if (node.name === 'true')
386
+ return true;
387
+ if (node.name === 'false')
388
+ return false;
389
+ if (node.name === 'null')
390
+ return null;
391
+ if (node.name === 'undefined')
392
+ return undefined;
393
+ }
394
+ if (node.type === 'UnaryExpression') {
395
+ const arg = this.evaluateConstant(node.argument);
396
+ if (arg !== undefined) {
397
+ return this.evaluateUnary(node.operator, arg);
398
+ }
399
+ }
400
+ if (node.type === 'BinaryExpression') {
401
+ const left = this.evaluateConstant(node.left);
402
+ const right = this.evaluateConstant(node.right);
403
+ if (left !== undefined && right !== undefined) {
404
+ return this.evaluateBinary(node.operator, left, right);
405
+ }
406
+ }
407
+ if (node.type === 'LogicalExpression') {
408
+ const left = this.evaluateConstant(node.left);
409
+ const right = this.evaluateConstant(node.right);
410
+ if (left !== undefined && right !== undefined) {
411
+ return this.evaluateLogical(node.operator, left, right);
412
+ }
413
+ }
414
+ return undefined;
415
+ }
416
+ evaluateBinary(op, left, right) {
417
+ switch (op) {
418
+ case '+': return left + right;
419
+ case '-': return left - right;
420
+ case '*': return left * right;
421
+ case '/': return left / right;
422
+ case '%': return left % right;
423
+ case '**': return left ** right;
424
+ case '==': return left == right;
425
+ case '!=': return left != right;
426
+ case '===': return left === right;
427
+ case '!==': return left !== right;
428
+ case '<': return left < right;
429
+ case '>': return left > right;
430
+ case '<=': return left <= right;
431
+ case '>=': return left >= right;
432
+ case '<<': return left << right;
433
+ case '>>': return left >> right;
434
+ case '>>>': return left >>> right;
435
+ case '&': return left & right;
436
+ case '|': return left | right;
437
+ case '^': return left ^ right;
438
+ default: return undefined;
439
+ }
440
+ }
441
+ evaluateUnary(op, arg) {
442
+ switch (op) {
443
+ case '-': return -arg;
444
+ case '+': return +arg;
445
+ case '!': return !arg;
446
+ case '~': return ~arg;
447
+ case 'typeof': return typeof arg;
448
+ case 'void': return undefined;
449
+ default: return undefined;
450
+ }
451
+ }
452
+ evaluateLogical(op, left, right) {
453
+ switch (op) {
454
+ case '&&': return left && right;
455
+ case '||': return left || right;
456
+ case '??': return left ?? right;
457
+ default: return undefined;
458
+ }
459
+ }
460
+ createLiteral(value) {
461
+ return {
462
+ type: 'Literal',
463
+ value,
464
+ raw: String(value),
465
+ };
466
+ }
467
+ isSmallExpression(node) {
468
+ if (!node)
469
+ return false;
470
+ if (node.type === 'Literal')
471
+ return true;
472
+ if (node.type === 'Identifier')
473
+ return true;
474
+ if (node.type === 'BinaryExpression') {
475
+ return this.isSmallExpression(node.left) && this.isSmallExpression(node.right);
476
+ }
477
+ if (node.type === 'UnaryExpression') {
478
+ return this.isSmallExpression(node.argument);
479
+ }
480
+ if (node.type === 'ConditionalExpression') {
481
+ return (this.isSmallExpression(node.test) &&
482
+ this.isSmallExpression(node.consequent) &&
483
+ this.isSmallExpression(node.alternate));
484
+ }
485
+ return false;
486
+ }
487
+ substituteParams(body, params, args) {
488
+ const paramMap = new Map();
489
+ params.forEach((p, i) => {
490
+ if (p.type === 'Identifier') {
491
+ paramMap.set(p.name, args[i]);
492
+ }
493
+ });
494
+ return walkAST(JSON.parse(JSON.stringify(body)), (node) => {
495
+ if (node.type === 'Identifier' && paramMap.has(node.name)) {
496
+ return paramMap.get(node.name);
497
+ }
498
+ return undefined;
499
+ });
500
+ }
501
+ hasSideEffects(node) {
502
+ if (!node)
503
+ return false;
504
+ if (node.type === 'CallExpression')
505
+ return true;
506
+ if (node.type === 'NewExpression')
507
+ return true;
508
+ if (node.type === 'AssignmentExpression')
509
+ return true;
510
+ if (node.type === 'UpdateExpression')
511
+ return true;
512
+ if (node.type === 'AwaitExpression')
513
+ return true;
514
+ if (node.type === 'YieldExpression')
515
+ return true;
516
+ if (node.type === 'TaggedTemplateExpression')
517
+ return true;
518
+ if (node.type === 'UnaryExpression' && node.operator === 'delete')
519
+ return true;
520
+ return false;
521
+ }
522
+ generateCode(ast) {
523
+ return this.astToCode(ast);
524
+ }
525
+ /**
526
+ * Convert AST node to JavaScript code string
527
+ */
528
+ astToCode(node) {
529
+ if (!node)
530
+ return '';
531
+ switch (node.type) {
532
+ case 'Program':
533
+ return node.body.map((s) => this.astToCode(s)).join('\n');
534
+ case 'ExpressionStatement':
535
+ return this.astToCode(node.expression) + ';';
536
+ case 'Identifier':
537
+ return node.name;
538
+ case 'Literal':
539
+ if (node.raw !== undefined)
540
+ return node.raw;
541
+ if (typeof node.value === 'string')
542
+ return `"${node.value}"`;
543
+ if (node.value === null)
544
+ return 'null';
545
+ return String(node.value);
546
+ case 'BinaryExpression':
547
+ return `${this.astToCode(node.left)} ${node.operator} ${this.astToCode(node.right)}`;
548
+ case 'LogicalExpression':
549
+ return `${this.astToCode(node.left)} ${node.operator} ${this.astToCode(node.right)}`;
550
+ case 'UnaryExpression':
551
+ if (node.operator === 'typeof')
552
+ return `typeof ${this.astToCode(node.argument)}`;
553
+ if (node.operator === 'void')
554
+ return `void ${this.astToCode(node.argument)}`;
555
+ return `${node.operator}${this.astToCode(node.argument)}`;
556
+ case 'UpdateExpression':
557
+ return node.prefix
558
+ ? `${node.operator}${this.astToCode(node.argument)}`
559
+ : `${this.astToCode(node.argument)}${node.operator}`;
560
+ case 'AssignmentExpression':
561
+ return `${this.astToCode(node.left)} ${node.operator} ${this.astToCode(node.right)}`;
562
+ case 'CallExpression':
563
+ const callee = this.astToCode(node.callee);
564
+ const args = node.arguments.map((a) => this.astToCode(a)).join(', ');
565
+ return `${callee}(${args})`;
566
+ case 'MemberExpression':
567
+ const obj = this.astToCode(node.object);
568
+ if (node.computed) {
569
+ return `${obj}[${this.astToCode(node.property)}]`;
570
+ }
571
+ return `${obj}.${this.astToCode(node.property)}`;
572
+ case 'ArrowFunctionExpression':
573
+ const arrowParams = node.params.map((p) => this.astToCode(p)).join(', ');
574
+ if (node.body.type === 'BlockStatement') {
575
+ return `(${arrowParams}) => ${this.astToCode(node.body)}`;
576
+ }
577
+ return `(${arrowParams}) => ${this.astToCode(node.body)}`;
578
+ case 'FunctionExpression':
579
+ const funcParams = node.params.map((p) => this.astToCode(p)).join(', ');
580
+ return `function(${funcParams}) ${this.astToCode(node.body)}`;
581
+ case 'FunctionDeclaration':
582
+ const declParams = node.params.map((p) => this.astToCode(p)).join(', ');
583
+ return `function ${this.astToCode(node.id)}(${declParams}) ${this.astToCode(node.body)}`;
584
+ case 'BlockStatement':
585
+ return `{\n${node.body.map((s) => ' ' + this.astToCode(s)).join('\n')}\n}`;
586
+ case 'ReturnStatement':
587
+ return `return ${this.astToCode(node.argument)};`;
588
+ case 'IfStatement':
589
+ let code = `if (${this.astToCode(node.test)}) ${this.astToCode(node.consequent)}`;
590
+ if (node.alternate) {
591
+ code += ` else ${this.astToCode(node.alternate)}`;
592
+ }
593
+ return code;
594
+ case 'VariableDeclaration':
595
+ const kind = node.kind;
596
+ const declarations = node.declarations.map((d) => {
597
+ if (d.type === 'VariableDeclarator') {
598
+ if (d.init) {
599
+ return `${this.astToCode(d.id)} = ${this.astToCode(d.init)}`;
600
+ }
601
+ return this.astToCode(d.id);
602
+ }
603
+ return '';
604
+ }).join(', ');
605
+ return `${kind} ${declarations};`;
606
+ case 'VariableDeclarator':
607
+ if (node.init) {
608
+ return `${this.astToCode(node.id)} = ${this.astToCode(node.init)}`;
609
+ }
610
+ return this.astToCode(node.id);
611
+ case 'Property':
612
+ if (node.computed) {
613
+ return `${this.astToCode(node.key)}: ${this.astToCode(node.value)}`;
614
+ }
615
+ if (node.key.type === 'Identifier') {
616
+ return `${node.key.name}: ${this.astToCode(node.value)}`;
617
+ }
618
+ return `${this.astToCode(node.key)}: ${this.astToCode(node.value)}`;
619
+ case 'ObjectExpression':
620
+ if (!node.properties || node.properties.length === 0)
621
+ return '{}';
622
+ return `{\n${node.properties.map((p) => ' ' + this.astToCode(p)).join(',\n')}\n}`;
623
+ case 'ArrayExpression':
624
+ return `[${node.elements.map((e) => this.astToCode(e)).join(', ')}]`;
625
+ case 'SpreadElement':
626
+ return `...${this.astToCode(node.argument)}`;
627
+ case 'TemplateLiteral':
628
+ let template = '`';
629
+ for (let i = 0; i < node.expressions.length; i++) {
630
+ template += node.quasis[i].value.raw;
631
+ template += '${' + this.astToCode(node.expressions[i]) + '}';
632
+ }
633
+ template += node.quasis[node.quasis.length - 1].value.raw + '`';
634
+ return template;
635
+ case 'TaggedTemplateExpression':
636
+ return `${this.astToCode(node.tag)}${this.astToCode(node.quasi)}`;
637
+ case 'ConditionalExpression':
638
+ return `${this.astToCode(node.test)} ? ${this.astToCode(node.consequent)} : ${this.astToCode(node.alternate)}`;
639
+ case 'ParenthesizedExpression':
640
+ return `(${this.astToCode(node.expression)})`;
641
+ case 'EmptyStatement':
642
+ return ';';
643
+ case 'DebuggerStatement':
644
+ return 'debugger;';
645
+ default:
646
+ // Fallback: return a placeholder
647
+ return `/* unknown: ${node.type} */`;
648
+ }
649
+ }
650
+ // ─── Auto-Memoization (React Compiler-like) ────────────────────
651
+ /**
652
+ * Automatically memoize expensive computations.
653
+ * Similar to React Compiler's auto-memoization — eliminates the need for manual useMemo/useCallback.
654
+ */
655
+ autoMemoize(ast) {
656
+ // Identify pure expressions that should be memoized
657
+ const isPureExpression = (node) => {
658
+ if (!node || typeof node !== 'object')
659
+ return false;
660
+ if (node.type === 'Literal')
661
+ return true;
662
+ if (node.type === 'Identifier')
663
+ return true;
664
+ if (node.type === 'BinaryExpression') {
665
+ return isPureExpression(node.left) && isPureExpression(node.right);
666
+ }
667
+ if (node.type === 'UnaryExpression') {
668
+ return isPureExpression(node.argument);
669
+ }
670
+ if (node.type === 'LogicalExpression') {
671
+ return isPureExpression(node.left) && isPureExpression(node.right);
672
+ }
673
+ if (node.type === 'ConditionalExpression') {
674
+ return (isPureExpression(node.test) &&
675
+ isPureExpression(node.consequent) &&
676
+ isPureExpression(node.alternate));
677
+ }
678
+ if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') {
679
+ return true;
680
+ }
681
+ if (node.type === 'ArrayExpression') {
682
+ return node.elements.every((el) => !el || isPureExpression(el));
683
+ }
684
+ if (node.type === 'ObjectExpression') {
685
+ return node.properties.every((prop) => {
686
+ if (prop.type === 'Property') {
687
+ return isPureExpression(prop.value);
688
+ }
689
+ if (prop.type === 'SpreadElement') {
690
+ return isPureExpression(prop.argument);
691
+ }
692
+ return false;
693
+ });
694
+ }
695
+ if (node.type === 'CallExpression') {
696
+ const calleeName = node.callee?.name || '';
697
+ if (calleeName.startsWith('set') || calleeName.startsWith('dispatch')) {
698
+ return false;
699
+ }
700
+ if (['fetch', 'XMLHttpRequest', 'setTimeout', 'setInterval'].includes(calleeName)) {
701
+ return false;
702
+ }
703
+ return true;
704
+ }
705
+ return false;
706
+ };
707
+ // Check if expression is too simple to memoize
708
+ const isTrivialExpression = (node) => {
709
+ if (!node || typeof node !== 'object')
710
+ return true;
711
+ if (node.type === 'Literal')
712
+ return true;
713
+ if (node.type === 'Identifier')
714
+ return true;
715
+ if (node.type === 'MemberExpression') {
716
+ return !node.computed && isTrivialExpression(node.object);
717
+ }
718
+ return false;
719
+ };
720
+ // Extract dependencies from expression
721
+ const extractDependencies = (node) => {
722
+ const deps = new Set();
723
+ const collectDeps = (n) => {
724
+ if (!n || typeof n !== 'object')
725
+ return;
726
+ if (n.type === 'Identifier') {
727
+ deps.add(n.name);
728
+ }
729
+ if (n.type === 'MemberExpression' && n.object?.type === 'Identifier') {
730
+ deps.add(n.object.name);
731
+ }
732
+ for (const key of Object.keys(n)) {
733
+ if (key === 'type' || key === 'start' || key === 'end')
734
+ continue;
735
+ const child = n[key];
736
+ if (Array.isArray(child)) {
737
+ child.forEach((item) => {
738
+ if (item && typeof item === 'object' && item.type) {
739
+ collectDeps(item);
740
+ }
741
+ });
742
+ }
743
+ else if (child && typeof child === 'object' && child.type) {
744
+ collectDeps(child);
745
+ }
746
+ }
747
+ };
748
+ collectDeps(node);
749
+ return Array.from(deps);
750
+ };
751
+ // Auto-memoization is disabled — wrapping initializers in ArrowFunctionExpression
752
+ // changes semantics (const doubled = count() * 2 becomes const doubled = () => count() * 2)
753
+ return ast;
754
+ }
755
+ }
756
+ /**
757
+ * Analyze and tree-shake unused exports
758
+ */
759
+ export function treeShake(code, options) {
760
+ const result = {
761
+ kept: [],
762
+ eliminated: [],
763
+ warnings: [],
764
+ };
765
+ // Parse imports to find used names
766
+ const usedNames = new Set();
767
+ const importRegex = /import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+['"](.*)['"]/g;
768
+ let match;
769
+ while ((match = importRegex.exec(code)) !== null) {
770
+ if (match[1]) {
771
+ // Named imports: import { a, b } from 'module'
772
+ match[1].split(',').forEach((name) => {
773
+ const trimmed = name.trim().split(/\s+as\s+/)[0].trim();
774
+ if (trimmed)
775
+ usedNames.add(trimmed);
776
+ });
777
+ }
778
+ else if (match[2]) {
779
+ // Default import: import Foo from 'module'
780
+ usedNames.add(match[2]);
781
+ }
782
+ }
783
+ // Find all exports
784
+ const exportRegex = /export\s+(?:const|function|class)\s+(\w+)/g;
785
+ const defaultExportRegex = /export\s+default\s+/g;
786
+ while ((match = exportRegex.exec(code)) !== null) {
787
+ const name = match[1];
788
+ if (usedNames.has(name)) {
789
+ result.kept.push(name);
790
+ }
791
+ else {
792
+ result.eliminated.push(name);
793
+ }
794
+ }
795
+ // Check for default export
796
+ if (defaultExportRegex.test(code)) {
797
+ result.kept.push('default');
798
+ }
799
+ return result;
800
+ }
801
+ /**
802
+ * Analyze bundle for optimization opportunities
803
+ */
804
+ export function analyzeBundle(code, moduleName) {
805
+ const lines = code.split('\n');
806
+ const exports = [];
807
+ // Extract exports
808
+ const exportRegex = /export\s+(?:const|function|class|default)\s+(\w+)/g;
809
+ let match;
810
+ while ((match = exportRegex.exec(code)) !== null) {
811
+ exports.push(match[1]);
812
+ }
813
+ return {
814
+ size: Buffer.byteLength(code, 'utf8'),
815
+ gzipSize: Buffer.byteLength(code, 'utf8'), // Simplified
816
+ modules: [
817
+ {
818
+ name: moduleName,
819
+ size: Buffer.byteLength(code, 'utf8'),
820
+ exports,
821
+ sideEffects: false,
822
+ },
823
+ ],
824
+ dependencies: [],
825
+ peerDependencies: [],
826
+ };
827
+ }
828
+ // ─── Dead Code Patterns ─────────────────────────────────────────
829
+ export const DEAD_CODE_PATTERNS = [
830
+ /if\s*\(\s*false\s*\)/,
831
+ /if\s*\(\s*0\s*\)/,
832
+ /if\s*\(\s*null\s*\)/,
833
+ /if\s*\(\s*undefined\s*\)/,
834
+ /while\s*\(\s*false\s*\)/,
835
+ /return\s+undefined\s*;/,
836
+ ];
837
+ export const SIDE_EFFECT_FREE_FUNCTIONS = new Set([
838
+ 'console.log',
839
+ 'console.error',
840
+ 'console.warn',
841
+ 'Math.abs',
842
+ 'Math.ceil',
843
+ 'Math.floor',
844
+ 'Math.round',
845
+ 'parseInt',
846
+ 'parseFloat',
847
+ 'isNaN',
848
+ 'isFinite',
849
+ ]);
850
+ // ─── Static Subtree Hoisting ────────────────────────────────────
851
+ /**
852
+ * Identify and hoist static JSX subtrees.
853
+ * Static elements (no dynamic expressions) can be created once and reused.
854
+ *
855
+ * @example
856
+ * // Before hoisting
857
+ * function App() {
858
+ * return <div><Header /><StaticContent /><Footer /></div>
859
+ * }
860
+ *
861
+ * // After hoisting — StaticContent is hoisted outside the function
862
+ * const _hoisted_0 = <StaticContent />
863
+ * function App() {
864
+ * return <div><Header />{_hoisted_0}<Footer /></div>
865
+ * }
866
+ */
867
+ export function hoistStaticSubtrees(ast, code) {
868
+ let hoistedCount = 0;
869
+ const hoistedNodes = [];
870
+ function isStaticNode(node) {
871
+ if (!node || typeof node !== 'object')
872
+ return true;
873
+ // JSX elements with no dynamic children or attributes are static
874
+ if (node.type === 'JSXElement') {
875
+ // Check attributes for dynamic values
876
+ if (node.openingElement?.attributes) {
877
+ for (const attr of node.openingElement.attributes) {
878
+ if (attr.type === 'JSXSpreadAttribute')
879
+ return false;
880
+ if (attr.value?.type === 'JSXExpressionContainer') {
881
+ // Check if the expression is a literal
882
+ const expr = attr.value.expression;
883
+ if (expr && expr.type !== 'Literal')
884
+ return false;
885
+ }
886
+ }
887
+ }
888
+ // Check children
889
+ if (node.children) {
890
+ for (const child of node.children) {
891
+ if (child.type === 'JSXExpressionContainer') {
892
+ const expr = child.expression;
893
+ if (expr && expr.type !== 'Literal')
894
+ return false;
895
+ }
896
+ if (child.type === 'JSXElement' && !isStaticNode(child))
897
+ return false;
898
+ }
899
+ }
900
+ return true;
901
+ }
902
+ // Literals are static
903
+ if (node.type === 'Literal')
904
+ return true;
905
+ // JSXText is static
906
+ if (node.type === 'JSXText')
907
+ return true;
908
+ return false;
909
+ }
910
+ // Walk the AST and find hoistable static subtrees
911
+ function walkAndHoist(node, parent, key) {
912
+ if (!node || typeof node !== 'object')
913
+ return;
914
+ // Check if this is a static JSX element that should be hoisted
915
+ if (node.type === 'JSXElement' && isStaticNode(node) && shouldHoist(node)) {
916
+ const hoistedName = `_hoisted_${hoistedCount++}`;
917
+ const hoistedCode = generateStaticCode(node, code);
918
+ hoistedNodes.push({ name: hoistedName, code: hoistedCode });
919
+ // Replace with reference
920
+ if (parent && key) {
921
+ parent[key] = { type: 'Identifier', name: hoistedName };
922
+ }
923
+ }
924
+ // Continue walking children
925
+ for (const childKey of Object.keys(node)) {
926
+ if (childKey === 'type' || childKey === 'start' || childKey === 'end')
927
+ continue;
928
+ const child = node[childKey];
929
+ if (Array.isArray(child)) {
930
+ child.forEach((item, idx) => {
931
+ if (item && typeof item === 'object' && item.type) {
932
+ walkAndHoist(item, node, `${childKey}[${idx}]`);
933
+ }
934
+ });
935
+ }
936
+ else if (child && typeof child === 'object' && child.type) {
937
+ walkAndHoist(child, node, childKey);
938
+ }
939
+ }
940
+ }
941
+ function shouldHoist(node) {
942
+ // Only hoist elements that are complex enough to benefit
943
+ // Simple elements like <div></div> are not worth hoisting
944
+ if (!node.children || node.children.length === 0)
945
+ return false;
946
+ if (node.children.length === 1 && node.children[0].type === 'JSXText')
947
+ return false;
948
+ // Check for components — always worth hoisting
949
+ const tagName = node.openingElement?.name?.name;
950
+ if (tagName && tagName[0] === tagName[0].toUpperCase())
951
+ return true;
952
+ // Check for elements with attributes
953
+ if (node.openingElement?.attributes?.length > 0)
954
+ return true;
955
+ // Check for elements with multiple children
956
+ if (node.children.length > 1)
957
+ return true;
958
+ return false;
959
+ }
960
+ function generateStaticCode(node, source) {
961
+ return source.slice(node.start, node.end);
962
+ }
963
+ walkAndHoist(ast, null, '');
964
+ // Generate hoisted code
965
+ if (hoistedNodes.length > 0) {
966
+ const hoistedCode = hoistedNodes
967
+ .map(h => `const ${h.name} = ${h.code}`)
968
+ .join('\n');
969
+ return {
970
+ ast,
971
+ code: hoistedCode + '\n' + code,
972
+ hoisted: hoistedCount,
973
+ };
974
+ }
975
+ return { ast, code, hoisted: 0 };
976
+ }
977
+ // ─── Auto-Memoization (React Compiler-like) ────────────────────
978
+ // Auto-memoization is disabled — wrapping initializers in ArrowFunctionExpression
979
+ // changes semantics (const doubled = count() * 2 becomes const doubled = () => count() * 2)
980
+ export function autoMemoize(ast) {
981
+ return { ast, memoized: 0 };
982
+ }
983
+ // ─── Compile-time CSS Scoping ───────────────────────────────────
984
+ /**
985
+ * Scope CSS to components at compile time.
986
+ * Generates unique class names and injects scoped styles.
987
+ *
988
+ * @example
989
+ * // Input
990
+ * <style>
991
+ * .container { padding: 16px; }
992
+ * .title { font-size: 24px; }
993
+ * </style>
994
+ * <div class="container">
995
+ * <h1 class="title">Hello</h1>
996
+ * </div>
997
+ *
998
+ * // Output (with scope hash "abc123")
999
+ * <style>
1000
+ * .container[data-scoped-abc123] { padding: 16px; }
1001
+ * .title[data-scoped-abc123] { font-size: 24px; }
1002
+ * </style>
1003
+ * <div class="container" data-scoped-abc123>
1004
+ * <h1 class="title" data-scoped-abc123>Hello</h1>
1005
+ * </div>
1006
+ */
1007
+ export function scopeCSS(ast, code, componentName) {
1008
+ const scopes = [];
1009
+ let scopeCounter = 0;
1010
+ function generateScopeId(name) {
1011
+ const base = name || 'scope';
1012
+ return `${base}-${scopeCounter++}-${Math.random().toString(36).slice(2, 8)}`;
1013
+ }
1014
+ function extractCSSFromJSX(code) {
1015
+ const cssRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
1016
+ const positions = [];
1017
+ let css = '';
1018
+ let match;
1019
+ while ((match = cssRegex.exec(code)) !== null) {
1020
+ css += match[1];
1021
+ positions.push({ start: match.index, end: match.index + match[0].length });
1022
+ }
1023
+ return { css, positions };
1024
+ }
1025
+ function scopeCSSRules(css, scopeId) {
1026
+ // Simple CSS scoping — add data attribute selector
1027
+ return css
1028
+ .replace(/\.([a-zA-Z_-][a-zA-Z0-9_-]*)/g, `.$1[data-scoped-${scopeId}]`)
1029
+ .replace(/#([a-zA-Z_-][a-zA-Z0-9_-]*)/g, `#$1[data-scoped-${scopeId}]`);
1030
+ }
1031
+ const { css } = extractCSSFromJSX(code);
1032
+ if (css) {
1033
+ const scopeId = generateScopeId(componentName);
1034
+ const scopedCSS = scopeCSSRules(css, scopeId);
1035
+ scopes.push({
1036
+ id: scopeId,
1037
+ selector: `[data-scoped-${scopeId}]`,
1038
+ css: scopedCSS,
1039
+ hash: scopeId,
1040
+ });
1041
+ }
1042
+ return { ast, code, scopes };
1043
+ }
1044
+ // ─── Bundle Analysis (v2 — Enhanced) ────────────────────────────
1045
+ //# sourceMappingURL=index.js.map