pyjsclear 0.1.0__py3-none-any.whl

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 (38) hide show
  1. pyjsclear/__init__.py +47 -0
  2. pyjsclear/__main__.py +37 -0
  3. pyjsclear/deobfuscator.py +191 -0
  4. pyjsclear/generator.py +772 -0
  5. pyjsclear/parser.py +46 -0
  6. pyjsclear/scope.py +283 -0
  7. pyjsclear/transforms/__init__.py +0 -0
  8. pyjsclear/transforms/aa_decode.py +83 -0
  9. pyjsclear/transforms/anti_tamper.py +106 -0
  10. pyjsclear/transforms/base.py +24 -0
  11. pyjsclear/transforms/constant_prop.py +103 -0
  12. pyjsclear/transforms/control_flow.py +330 -0
  13. pyjsclear/transforms/dead_branch.py +71 -0
  14. pyjsclear/transforms/eval_unpack.py +133 -0
  15. pyjsclear/transforms/expression_simplifier.py +403 -0
  16. pyjsclear/transforms/hex_escapes.py +68 -0
  17. pyjsclear/transforms/jj_decode.py +91 -0
  18. pyjsclear/transforms/jsfuck_decode.py +93 -0
  19. pyjsclear/transforms/logical_to_if.py +173 -0
  20. pyjsclear/transforms/object_packer.py +133 -0
  21. pyjsclear/transforms/object_simplifier.py +192 -0
  22. pyjsclear/transforms/property_simplifier.py +43 -0
  23. pyjsclear/transforms/proxy_functions.py +193 -0
  24. pyjsclear/transforms/reassignment.py +183 -0
  25. pyjsclear/transforms/sequence_splitter.py +215 -0
  26. pyjsclear/transforms/string_revealer.py +1259 -0
  27. pyjsclear/transforms/unused_vars.py +111 -0
  28. pyjsclear/traverser.py +195 -0
  29. pyjsclear/utils/__init__.py +0 -0
  30. pyjsclear/utils/ast_helpers.py +257 -0
  31. pyjsclear/utils/string_decoders.py +141 -0
  32. pyjsclear-0.1.0.dist-info/METADATA +168 -0
  33. pyjsclear-0.1.0.dist-info/RECORD +38 -0
  34. pyjsclear-0.1.0.dist-info/WHEEL +5 -0
  35. pyjsclear-0.1.0.dist-info/entry_points.txt +2 -0
  36. pyjsclear-0.1.0.dist-info/licenses/LICENSE +201 -0
  37. pyjsclear-0.1.0.dist-info/licenses/NOTICE +19 -0
  38. pyjsclear-0.1.0.dist-info/top_level.txt +1 -0
pyjsclear/generator.py ADDED
@@ -0,0 +1,772 @@
1
+ """ESTree AST to JavaScript code generator."""
2
+
3
+ # Operator precedence (higher = binds tighter)
4
+ _PRECEDENCE = {
5
+ '=': 3,
6
+ '+=': 3,
7
+ '-=': 3,
8
+ '*=': 3,
9
+ '/=': 3,
10
+ '%=': 3,
11
+ '<<=': 3,
12
+ '>>=': 3,
13
+ '>>>=': 3,
14
+ '|=': 3,
15
+ '^=': 3,
16
+ '&=': 3,
17
+ '**=': 3,
18
+ '||': 5,
19
+ '??': 5,
20
+ '&&': 6,
21
+ '|': 7,
22
+ '^': 8,
23
+ '&': 9,
24
+ '==': 10,
25
+ '!=': 10,
26
+ '===': 10,
27
+ '!==': 10,
28
+ '<': 11,
29
+ '>': 11,
30
+ '<=': 11,
31
+ '>=': 11,
32
+ 'instanceof': 11,
33
+ 'in': 11,
34
+ '<<': 12,
35
+ '>>': 12,
36
+ '>>>': 12,
37
+ '+': 13,
38
+ '-': 13,
39
+ '*': 14,
40
+ '/': 14,
41
+ '%': 14,
42
+ '**': 15,
43
+ }
44
+
45
+ _UNARY_PRECEDENCE = 16
46
+
47
+ _NO_SEMI_TYPES = frozenset(
48
+ {
49
+ 'BlockStatement',
50
+ 'IfStatement',
51
+ 'WhileStatement',
52
+ 'DoWhileStatement',
53
+ 'ForStatement',
54
+ 'ForInStatement',
55
+ 'ForOfStatement',
56
+ 'SwitchStatement',
57
+ 'TryStatement',
58
+ 'FunctionDeclaration',
59
+ 'ClassDeclaration',
60
+ 'LabeledStatement',
61
+ 'EmptyStatement',
62
+ }
63
+ )
64
+
65
+
66
+ def generate(node, indent=0):
67
+ """Generate JavaScript source from an ESTree AST node."""
68
+ if node is None:
69
+ return ''
70
+ if not isinstance(node, dict):
71
+ return str(node)
72
+
73
+ node_type = node.get('type', '')
74
+ gen = _GENERATORS.get(node_type)
75
+ if gen:
76
+ return gen(node, indent)
77
+ return f'/* unknown: {node_type} */'
78
+
79
+
80
+ def _indent_str(level):
81
+ return ' ' * level
82
+
83
+
84
+ def _is_directive(stmt):
85
+ """Check if a statement is a string-literal directive (like 'use strict')."""
86
+ return (
87
+ stmt.get('type') == 'ExpressionStatement'
88
+ and isinstance(stmt.get('expression'), dict)
89
+ and stmt['expression'].get('type') == 'Literal'
90
+ and isinstance(stmt['expression'].get('value'), str)
91
+ )
92
+
93
+
94
+ def _gen_program(node, indent):
95
+ parts = []
96
+ body = node.get('body', [])
97
+ for i, stmt in enumerate(body):
98
+ if stmt is None:
99
+ continue
100
+ if stmt.get('type') == 'EmptyStatement':
101
+ continue
102
+ statement_code = _gen_stmt(stmt, indent)
103
+ if statement_code.strip():
104
+ parts.append(statement_code)
105
+ if _is_directive(stmt) and i + 1 < len(body):
106
+ parts.append('')
107
+ return '\n'.join(parts)
108
+
109
+
110
+ def _gen_stmt(node, indent):
111
+ """Generate a statement with indentation."""
112
+ if node is None:
113
+ return ''
114
+ prefix = _indent_str(indent)
115
+ code = generate(node, indent)
116
+ node_type = node.get('type', '')
117
+ if node_type in _NO_SEMI_TYPES:
118
+ return prefix + code
119
+ if code.endswith(';'):
120
+ return prefix + code
121
+ return prefix + code + ';'
122
+
123
+
124
+ def _gen_block(node, indent):
125
+ if not node.get('body'):
126
+ return '{}'
127
+ lines = ['{']
128
+ body = node.get('body', [])
129
+ for i, stmt in enumerate(body):
130
+ lines.append(_gen_stmt(stmt, indent + 1))
131
+ if _is_directive(stmt) and i + 1 < len(body):
132
+ lines.append('')
133
+ lines.append(_indent_str(indent) + '}')
134
+ return '\n'.join(lines)
135
+
136
+
137
+ def _gen_var_declaration(node, indent):
138
+ kind = node.get('kind', 'var')
139
+ declarations = []
140
+ for declaration in node.get('declarations', []):
141
+ name = generate(declaration['id'], indent)
142
+ initializer = declaration.get('init')
143
+ if initializer:
144
+ declarations.append(f'{name} = {generate(initializer, indent)}')
145
+ else:
146
+ declarations.append(name)
147
+ return f'{kind} {", ".join(declarations)}'
148
+
149
+
150
+ def _gen_function(node, indent, is_expression=False):
151
+ """Shared generator for FunctionDeclaration and FunctionExpression."""
152
+ name = generate(node['id'], indent) if node.get('id') else ''
153
+ params = ', '.join(generate(p, indent) for p in node.get('params', []))
154
+ async_prefix = 'async ' if node.get('async') else ''
155
+ gen_prefix = '*' if node.get('generator') else ''
156
+ body = generate(node['body'], indent)
157
+ if name:
158
+ return f'{async_prefix}function{gen_prefix} {name}({params}) {body}'
159
+ # Anonymous: always put space before parens (Babel style)
160
+ return f'{async_prefix}function{gen_prefix} ({params}) {body}'
161
+
162
+
163
+ def _gen_function_decl(node, indent):
164
+ return _gen_function(node, indent)
165
+
166
+
167
+ def _gen_function_expr(node, indent):
168
+ return _gen_function(node, indent, is_expression=True)
169
+
170
+
171
+ def _gen_arrow(node, indent):
172
+ params = node.get('params', [])
173
+ async_prefix = 'async ' if node.get('async') else ''
174
+ param_str = '(' + ', '.join(generate(p, indent) for p in params) + ')'
175
+ body = node.get('body', {})
176
+ body_str = generate(body, indent)
177
+ # Wrap object literal in parens to avoid ambiguity with block
178
+ if body.get('type') == 'ObjectExpression':
179
+ body_str = '(' + body_str + ')'
180
+ return f'{async_prefix}{param_str} => {body_str}'
181
+
182
+
183
+ def _gen_return(node, indent):
184
+ argument = node.get('argument')
185
+ if argument:
186
+ return f'return {generate(argument, indent)}'
187
+ return 'return'
188
+
189
+
190
+ def _gen_if(node, indent):
191
+ test = generate(node['test'], indent)
192
+ consequent_code = generate(node['consequent'], indent)
193
+ if node['consequent'].get('type') != 'BlockStatement':
194
+ consequent_code = '\n' + _gen_stmt(node['consequent'], indent + 1)
195
+ alternate = node.get('alternate')
196
+ if alternate:
197
+ if alternate.get('type') in ('IfStatement', 'BlockStatement'):
198
+ alternate_code = ' else ' + generate(alternate, indent)
199
+ else:
200
+ alternate_code = ' else\n' + _gen_stmt(alternate, indent + 1)
201
+ return f'if ({test}) {consequent_code}{alternate_code}'
202
+ return f'if ({test}) {consequent_code}'
203
+
204
+
205
+ def _gen_while(node, indent):
206
+ test = generate(node['test'], indent)
207
+ body = generate(node['body'], indent)
208
+ return f'while ({test}) {body}'
209
+
210
+
211
+ def _gen_do_while(node, indent):
212
+ body = generate(node['body'], indent)
213
+ test = generate(node['test'], indent)
214
+ return f'do {body} while ({test})'
215
+
216
+
217
+ def _gen_for(node, indent):
218
+ init = ''
219
+ if node.get('init'):
220
+ init = generate(node['init'], indent)
221
+ test = generate(node.get('test'), indent) if node.get('test') else ''
222
+ update = generate(node.get('update'), indent) if node.get('update') else ''
223
+ body = generate(node['body'], indent)
224
+ return f'for ({init}; {test}; {update}) {body}'
225
+
226
+
227
+ def _gen_for_in(node, indent):
228
+ left = generate(node['left'], indent)
229
+ right = generate(node['right'], indent)
230
+ body = generate(node['body'], indent)
231
+ return f'for ({left} in {right}) {body}'
232
+
233
+
234
+ def _gen_for_of(node, indent):
235
+ left = generate(node['left'], indent)
236
+ right = generate(node['right'], indent)
237
+ body = generate(node['body'], indent)
238
+ return f'for ({left} of {right}) {body}'
239
+
240
+
241
+ def _gen_switch(node, indent):
242
+ discriminant = generate(node['discriminant'], indent)
243
+ lines = [f'switch ({discriminant}) {{']
244
+ for case in node.get('cases', []):
245
+ if case.get('test'):
246
+ lines.append(_indent_str(indent + 1) + f'case {generate(case["test"], indent + 1)}:')
247
+ else:
248
+ lines.append(_indent_str(indent + 1) + 'default:')
249
+ for stmt in case.get('consequent', []):
250
+ lines.append(_gen_stmt(stmt, indent + 2))
251
+ lines.append(_indent_str(indent) + '}')
252
+ return '\n'.join(lines)
253
+
254
+
255
+ def _gen_try(node, indent):
256
+ block = generate(node['block'], indent)
257
+ result = f'try {block}'
258
+ handler = node.get('handler')
259
+ if handler:
260
+ param = generate(handler.get('param'), indent) if handler.get('param') else ''
261
+ handler_body = generate(handler['body'], indent)
262
+ if param:
263
+ result += f' catch ({param}) {handler_body}'
264
+ else:
265
+ result += f' catch {handler_body}'
266
+ finalizer = node.get('finalizer')
267
+ if finalizer:
268
+ result += f' finally {generate(finalizer, indent)}'
269
+ return result
270
+
271
+
272
+ def _gen_throw(node, indent):
273
+ return f'throw {generate(node["argument"], indent)}'
274
+
275
+
276
+ def _gen_break(node, indent):
277
+ if node.get('label'):
278
+ return f'break {generate(node["label"], indent)}'
279
+ return 'break'
280
+
281
+
282
+ def _gen_continue(node, indent):
283
+ if node.get('label'):
284
+ return f'continue {generate(node["label"], indent)}'
285
+ return 'continue'
286
+
287
+
288
+ def _gen_labeled(node, indent):
289
+ label = generate(node['label'], indent)
290
+ body = _gen_stmt(node['body'], indent)
291
+ return f'{label}:\n{body}'
292
+
293
+
294
+ def _gen_expr_stmt(node, indent):
295
+ return generate(node['expression'], indent)
296
+
297
+
298
+ def _gen_binary(node, indent):
299
+ operator = node.get('operator', '')
300
+ left = generate(node['left'], indent)
301
+ right = generate(node['right'], indent)
302
+ my_prec = _PRECEDENCE.get(operator, 1)
303
+ left_prec = _expr_precedence(node['left'])
304
+ right_prec = _expr_precedence(node['right'])
305
+ if left_prec < my_prec:
306
+ left = f'({left})'
307
+ if right_prec < my_prec or (right_prec == my_prec and operator not in ('+', '*', '|', '&', '^')):
308
+ right = f'({right})'
309
+ return f'{left} {operator} {right}'
310
+
311
+
312
+ def _gen_logical(node, indent):
313
+ return _gen_binary(node, indent)
314
+
315
+
316
+ def _gen_unary(node, indent):
317
+ operator = node.get('operator', '')
318
+ operand = generate(node['argument'], indent)
319
+ operand_prec = _expr_precedence(node['argument'])
320
+ if operand_prec < _UNARY_PRECEDENCE:
321
+ operand = f'({operand})'
322
+ if operator in ('typeof', 'void', 'delete'):
323
+ return f'{operator} {operand}'
324
+ if node.get('prefix', True):
325
+ return f'{operator}{operand}'
326
+ return f'{operand}{operator}'
327
+
328
+
329
+ def _gen_update(node, indent):
330
+ argument = generate(node['argument'], indent)
331
+ operator = node.get('operator', '++')
332
+ if node.get('prefix'):
333
+ return f'{operator}{argument}'
334
+ return f'{argument}{operator}'
335
+
336
+
337
+ def _gen_assignment(node, indent):
338
+ left = generate(node['left'], indent)
339
+ right = generate(node['right'], indent)
340
+ operator = node.get('operator', '=')
341
+ return f'{left} {operator} {right}'
342
+
343
+
344
+ def _gen_member(node, indent):
345
+ object_code = generate(node['object'], indent)
346
+ obj_type = node['object'].get('type', '')
347
+ computed = node.get('computed')
348
+
349
+ needs_parens = False
350
+ if obj_type == 'Literal' and isinstance(node['object'].get('value'), (int, float)):
351
+ needs_parens = not computed
352
+ elif obj_type in (
353
+ 'BinaryExpression',
354
+ 'UnaryExpression',
355
+ 'ConditionalExpression',
356
+ 'AssignmentExpression',
357
+ 'SequenceExpression',
358
+ 'ArrowFunctionExpression',
359
+ ):
360
+ needs_parens = _expr_precedence(node['object']) < 19
361
+
362
+ if needs_parens:
363
+ object_code = f'({object_code})'
364
+
365
+ property_code = generate(node['property'], indent)
366
+ if computed:
367
+ return f'{object_code}[{property_code}]'
368
+ return f'{object_code}.{property_code}'
369
+
370
+
371
+ def _gen_call(node, indent):
372
+ callee = generate(node['callee'], indent)
373
+ callee_type = node['callee'].get('type', '')
374
+ if callee_type in ('FunctionExpression', 'ArrowFunctionExpression', 'SequenceExpression'):
375
+ callee = f'({callee})'
376
+ args = ', '.join(generate(a, indent) for a in node.get('arguments', []))
377
+ return f'{callee}({args})'
378
+
379
+
380
+ def _gen_new(node, indent):
381
+ callee = generate(node['callee'], indent)
382
+ args = node.get('arguments', [])
383
+ if args:
384
+ arg_str = ', '.join(generate(a, indent) for a in args)
385
+ return f'new {callee}({arg_str})'
386
+ return f'new {callee}()'
387
+
388
+
389
+ def _wrap_if_sequence(node, code):
390
+ """Wrap code in parens if node is a SequenceExpression."""
391
+ if isinstance(node, dict) and node.get('type') == 'SequenceExpression':
392
+ return f'({code})'
393
+ return code
394
+
395
+
396
+ def _gen_conditional(node, indent):
397
+ test = generate(node['test'], indent)
398
+ consequent_code = _wrap_if_sequence(node.get('consequent'), generate(node['consequent'], indent))
399
+ alternate_code = _wrap_if_sequence(node.get('alternate'), generate(node['alternate'], indent))
400
+ return f'{test} ? {consequent_code} : {alternate_code}'
401
+
402
+
403
+ def _gen_sequence(node, indent):
404
+ exprs = ', '.join(generate(e, indent) for e in node.get('expressions', []))
405
+ return exprs
406
+
407
+
408
+ def _gen_bracket_list(elements, indent):
409
+ """Generate a bracketed list of elements, replacing None with empty slots."""
410
+ elems = [generate(e, indent) if e is not None else '' for e in elements]
411
+ return '[' + ', '.join(elems) + ']'
412
+
413
+
414
+ def _gen_array(node, indent):
415
+ return _gen_bracket_list(node.get('elements', []), indent)
416
+
417
+
418
+ def _gen_object_property(property_node, indent):
419
+ """Generate a single object property string."""
420
+ if property_node.get('type') == 'SpreadElement':
421
+ return '...' + generate(property_node['argument'], indent)
422
+
423
+ key = generate(property_node['key'], indent)
424
+ if property_node.get('computed'):
425
+ key = f'[{key}]'
426
+
427
+ kind = property_node.get('kind', 'init')
428
+ if kind in ('get', 'set') or property_node.get('method'):
429
+ prefix = f'{kind} ' if kind in ('get', 'set') else ''
430
+ params = ', '.join(generate(pp, indent) for pp in property_node['value'].get('params', []))
431
+ body = generate(property_node['value'].get('body'), indent)
432
+ return f'{prefix}{key}({params}) {body}'
433
+
434
+ if property_node.get('shorthand'):
435
+ return key
436
+
437
+ value = generate(property_node['value'], indent)
438
+ return f'{key}: {value}'
439
+
440
+
441
+ def _gen_object(node, indent):
442
+ properties = node.get('properties', [])
443
+ if not properties:
444
+ return '{}'
445
+ parts = [_gen_object_property(property_node, indent + 1) for property_node in properties]
446
+ inner_indent = _indent_str(indent + 1)
447
+ outer_indent = _indent_str(indent)
448
+ lines = ',\n'.join(inner_indent + part for part in parts)
449
+ return '{\n' + lines + '\n' + outer_indent + '}'
450
+
451
+
452
+ def _gen_property(node, indent):
453
+ key = generate(node['key'], indent)
454
+ value = generate(node['value'], indent)
455
+ return f'{key}: {value}'
456
+
457
+
458
+ def _gen_spread(node, indent):
459
+ return '...' + generate(node['argument'], indent)
460
+
461
+
462
+ def _escape_string(val, raw):
463
+ """Escape a string value and wrap in the appropriate quotes."""
464
+ if raw and len(raw) >= 2 and raw[0] in ('"', "'"):
465
+ quote = raw[0]
466
+ else:
467
+ quote = '"'
468
+ escaped = val.replace('\\', '\\\\')
469
+ escaped = escaped.replace('\n', '\\n')
470
+ escaped = escaped.replace('\r', '\\r')
471
+ escaped = escaped.replace('\t', '\\t')
472
+ escaped = escaped.replace(quote, '\\' + quote)
473
+ return f'{quote}{escaped}{quote}'
474
+
475
+
476
+ def _gen_literal(node, indent):
477
+ raw = node.get('raw')
478
+ value = node.get('value')
479
+ if isinstance(value, str):
480
+ return _escape_string(value, raw)
481
+ if raw is not None:
482
+ return str(raw)
483
+ match value:
484
+ case None:
485
+ return 'null'
486
+ case bool():
487
+ return 'true' if value else 'false'
488
+ case int() | float():
489
+ if isinstance(value, float) and value == int(value) and value >= 0:
490
+ return str(int(value))
491
+ return str(value)
492
+ case _:
493
+ return str(value)
494
+
495
+
496
+ def _gen_identifier(node, indent):
497
+ return node.get('name', '')
498
+
499
+
500
+ def _gen_this(node, indent):
501
+ return 'this'
502
+
503
+
504
+ def _gen_empty(node, indent):
505
+ return ';'
506
+
507
+
508
+ def _gen_template_literal(node, indent):
509
+ quasis = node.get('quasis', [])
510
+ exprs = node.get('expressions', [])
511
+ parts = []
512
+ for i, quasi in enumerate(quasis):
513
+ raw = quasi.get('value', {}).get('raw', '')
514
+ parts.append(raw)
515
+ if i < len(exprs):
516
+ parts.append('${' + generate(exprs[i], indent) + '}')
517
+ return '`' + ''.join(parts) + '`'
518
+
519
+
520
+ def _gen_tagged_template(node, indent):
521
+ tag = generate(node['tag'], indent)
522
+ quasi = generate(node['quasi'], indent)
523
+ return f'{tag}{quasi}'
524
+
525
+
526
+ def _gen_class_decl(node, indent):
527
+ name = generate(node['id'], indent) if node.get('id') else ''
528
+ superclass_clause = ''
529
+ if node.get('superClass'):
530
+ superclass_clause = f' extends {generate(node["superClass"], indent)}'
531
+ body = generate(node['body'], indent)
532
+ if name:
533
+ return f'class {name}{superclass_clause} {body}'
534
+ return f'class{superclass_clause} {body}'
535
+
536
+
537
+ def _gen_class_body(node, indent):
538
+ if not node.get('body'):
539
+ return '{}'
540
+ lines = ['{']
541
+ for method in node.get('body', []):
542
+ lines.append(_indent_str(indent + 1) + generate(method, indent + 1))
543
+ lines.append(_indent_str(indent) + '}')
544
+ return '\n'.join(lines)
545
+
546
+
547
+ def _gen_method_def(node, indent):
548
+ key = generate(node['key'], indent)
549
+ if node.get('computed') or node['key'].get('type') == 'Literal':
550
+ key = f'[{key}]'
551
+ static = 'static ' if node.get('static') else ''
552
+ kind = node.get('kind', 'method')
553
+ value = node.get('value', {})
554
+ params = ', '.join(generate(p, indent) for p in value.get('params', []))
555
+ body = generate(value.get('body'), indent)
556
+ match kind:
557
+ case 'constructor':
558
+ return f'{static}constructor({params}) {body}'
559
+ case 'get':
560
+ return f'{static}get {key}({params}) {body}'
561
+ case 'set':
562
+ return f'{static}set {key}({params}) {body}'
563
+ case _:
564
+ async_prefix = 'async ' if value.get('async') else ''
565
+ gen_prefix = '*' if value.get('generator') else ''
566
+ return f'{static}{async_prefix}{gen_prefix}{key}({params}) {body}'
567
+
568
+
569
+ def _gen_yield(node, indent):
570
+ argument = generate(node.get('argument'), indent) if node.get('argument') else ''
571
+ delegate = '*' if node.get('delegate') else ''
572
+ if argument:
573
+ return f'yield{delegate} {argument}'
574
+ return f'yield{delegate}'
575
+
576
+
577
+ def _gen_await(node, indent):
578
+ return f'await {generate(node["argument"], indent)}'
579
+
580
+
581
+ def _gen_assignment_pattern(node, indent):
582
+ left = generate(node['left'], indent)
583
+ right = generate(node['right'], indent)
584
+ return f'{left} = {right}'
585
+
586
+
587
+ def _gen_array_pattern(node, indent):
588
+ return _gen_bracket_list(node.get('elements', []), indent)
589
+
590
+
591
+ def _gen_object_pattern_part(property_node, indent):
592
+ """Generate a single destructuring pattern property."""
593
+ if property_node.get('type') == 'RestElement':
594
+ return '...' + generate(property_node['argument'], indent)
595
+ key = generate(property_node['key'], indent)
596
+ if property_node.get('shorthand'):
597
+ return key
598
+ value = generate(property_node['value'], indent)
599
+ return f'{key}: {value}'
600
+
601
+
602
+ def _gen_object_pattern(node, indent):
603
+ properties = [_gen_object_pattern_part(property_node, indent + 1) for property_node in node.get('properties', [])]
604
+ if not properties:
605
+ return '{}'
606
+ inner_indent = _indent_str(indent + 1)
607
+ outer_indent = _indent_str(indent)
608
+ lines = ',\n'.join(inner_indent + part for part in properties)
609
+ return '{\n' + lines + '\n' + outer_indent + '}'
610
+
611
+
612
+ def _gen_rest_element(node, indent):
613
+ return '...' + generate(node['argument'], indent)
614
+
615
+
616
+ def _gen_import_specifier(spec, indent):
617
+ """Generate a single import specifier."""
618
+ spec_type = spec.get('type', '')
619
+ if spec_type == 'ImportDefaultSpecifier':
620
+ return generate(spec['local'], indent)
621
+ if spec_type == 'ImportNamespaceSpecifier':
622
+ return '* as ' + generate(spec['local'], indent)
623
+ # ImportSpecifier
624
+ imported = generate(spec['imported'], indent)
625
+ local = generate(spec['local'], indent)
626
+ if imported == local:
627
+ return imported
628
+ return f'{imported} as {local}'
629
+
630
+
631
+ def _gen_import_declaration(node, indent):
632
+ source = generate(node['source'], indent)
633
+ specifiers = node.get('specifiers', [])
634
+ if not specifiers:
635
+ return f'import {source}'
636
+ default_specs = [s for s in specifiers if s.get('type') == 'ImportDefaultSpecifier']
637
+ namespace_specs = [s for s in specifiers if s.get('type') == 'ImportNamespaceSpecifier']
638
+ named_specs = [s for s in specifiers if s.get('type') == 'ImportSpecifier']
639
+ parts = []
640
+ if default_specs:
641
+ parts.append(_gen_import_specifier(default_specs[0], indent))
642
+ if namespace_specs:
643
+ parts.append(_gen_import_specifier(namespace_specs[0], indent))
644
+ if named_specs:
645
+ names = ', '.join(_gen_import_specifier(s, indent) for s in named_specs)
646
+ parts.append('{' + names + '}')
647
+ return f'import {", ".join(parts)} from {source}'
648
+
649
+
650
+ def _gen_export_specifier(spec, indent):
651
+ exported = generate(spec['exported'], indent)
652
+ local = generate(spec['local'], indent)
653
+ if exported == local:
654
+ return exported
655
+ return f'{local} as {exported}'
656
+
657
+
658
+ def _gen_export_named(node, indent):
659
+ declaration = node.get('declaration')
660
+ if declaration:
661
+ return f'export {generate(declaration, indent)}'
662
+ specifiers = node.get('specifiers', [])
663
+ names = ', '.join(_gen_export_specifier(s, indent) for s in specifiers)
664
+ source = node.get('source')
665
+ if source:
666
+ return f'export {{{names}}} from {generate(source, indent)}'
667
+ return f'export {{{names}}}'
668
+
669
+
670
+ def _gen_export_default(node, indent):
671
+ declaration = node.get('declaration', {})
672
+ return f'export default {generate(declaration, indent)}'
673
+
674
+
675
+ def _gen_export_all(node, indent):
676
+ source = generate(node['source'], indent)
677
+ return f'export * from {source}'
678
+
679
+
680
+ def _expr_precedence(node):
681
+ """Get the precedence level of an expression node."""
682
+ if not isinstance(node, dict):
683
+ return 20
684
+ match node.get('type', ''):
685
+ case (
686
+ 'Literal'
687
+ | 'Identifier'
688
+ | 'ThisExpression'
689
+ | 'ArrayExpression'
690
+ | 'ObjectExpression'
691
+ | 'FunctionExpression'
692
+ | 'ClassExpression'
693
+ | 'TemplateLiteral'
694
+ ):
695
+ return 20
696
+ case 'MemberExpression' | 'CallExpression' | 'NewExpression' | 'TaggedTemplateExpression':
697
+ return 19
698
+ case 'UpdateExpression':
699
+ return 17 if node.get('prefix') else 18
700
+ case 'UnaryExpression':
701
+ return _UNARY_PRECEDENCE
702
+ case 'BinaryExpression' | 'LogicalExpression':
703
+ return _PRECEDENCE.get(node.get('operator', ''), 1)
704
+ case 'ConditionalExpression':
705
+ return 4
706
+ case 'AssignmentExpression' | 'ArrowFunctionExpression':
707
+ return 3
708
+ case 'YieldExpression':
709
+ return 2
710
+ case 'SequenceExpression':
711
+ return 1
712
+ case _:
713
+ return 0
714
+
715
+
716
+ # Generator dispatch table
717
+ _GENERATORS = {
718
+ 'Program': _gen_program,
719
+ 'BlockStatement': _gen_block,
720
+ 'VariableDeclaration': _gen_var_declaration,
721
+ 'FunctionDeclaration': _gen_function_decl,
722
+ 'FunctionExpression': _gen_function_expr,
723
+ 'ArrowFunctionExpression': _gen_arrow,
724
+ 'ReturnStatement': _gen_return,
725
+ 'IfStatement': _gen_if,
726
+ 'WhileStatement': _gen_while,
727
+ 'DoWhileStatement': _gen_do_while,
728
+ 'ForStatement': _gen_for,
729
+ 'ForInStatement': _gen_for_in,
730
+ 'ForOfStatement': _gen_for_of,
731
+ 'SwitchStatement': _gen_switch,
732
+ 'TryStatement': _gen_try,
733
+ 'ThrowStatement': _gen_throw,
734
+ 'BreakStatement': _gen_break,
735
+ 'ContinueStatement': _gen_continue,
736
+ 'LabeledStatement': _gen_labeled,
737
+ 'ExpressionStatement': _gen_expr_stmt,
738
+ 'BinaryExpression': _gen_binary,
739
+ 'LogicalExpression': _gen_logical,
740
+ 'UnaryExpression': _gen_unary,
741
+ 'UpdateExpression': _gen_update,
742
+ 'AssignmentExpression': _gen_assignment,
743
+ 'MemberExpression': _gen_member,
744
+ 'CallExpression': _gen_call,
745
+ 'NewExpression': _gen_new,
746
+ 'ConditionalExpression': _gen_conditional,
747
+ 'SequenceExpression': _gen_sequence,
748
+ 'ArrayExpression': _gen_array,
749
+ 'ObjectExpression': _gen_object,
750
+ 'Property': _gen_property,
751
+ 'SpreadElement': _gen_spread,
752
+ 'Literal': _gen_literal,
753
+ 'Identifier': _gen_identifier,
754
+ 'ThisExpression': _gen_this,
755
+ 'EmptyStatement': _gen_empty,
756
+ 'TemplateLiteral': _gen_template_literal,
757
+ 'TaggedTemplateExpression': _gen_tagged_template,
758
+ 'ClassDeclaration': _gen_class_decl,
759
+ 'ClassExpression': _gen_class_decl,
760
+ 'ClassBody': _gen_class_body,
761
+ 'MethodDefinition': _gen_method_def,
762
+ 'YieldExpression': _gen_yield,
763
+ 'AwaitExpression': _gen_await,
764
+ 'AssignmentPattern': _gen_assignment_pattern,
765
+ 'ArrayPattern': _gen_array_pattern,
766
+ 'ObjectPattern': _gen_object_pattern,
767
+ 'RestElement': _gen_rest_element,
768
+ 'ImportDeclaration': _gen_import_declaration,
769
+ 'ExportNamedDeclaration': _gen_export_named,
770
+ 'ExportDefaultDeclaration': _gen_export_default,
771
+ 'ExportAllDeclaration': _gen_export_all,
772
+ }