ether-code 0.9.0 → 0.9.1

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.
@@ -6,6 +6,7 @@ class PHPGenerator {
6
6
  this.indent = 0
7
7
  this.output = ''
8
8
  this.translationMap = {}
9
+ this.functionMap = {}
9
10
  this.supportedLanguages = ['fr', 'en', 'es', 'ru', 'zh', 'ja']
10
11
 
11
12
  if (i18nPath) {
@@ -21,6 +22,7 @@ class PHPGenerator {
21
22
 
22
23
  buildTranslationMap() {
23
24
  this.translationMap = {}
25
+ this.functionMap = {}
24
26
 
25
27
  if (!this.i18n) return
26
28
 
@@ -34,6 +36,15 @@ class PHPGenerator {
34
36
  const fixed = this.normalizeUTF8(etherWord)
35
37
  const normalizedKey = fixed.toLowerCase().trim()
36
38
  const cleanPhp = this.cleanPhpValue(phpCode)
39
+
40
+ if (phpCode.endsWith('()')) {
41
+ this.functionMap[normalizedKey] = cleanPhp
42
+ const withoutAccents = this.removeAccents(normalizedKey)
43
+ if (withoutAccents !== normalizedKey) {
44
+ this.functionMap[withoutAccents] = cleanPhp
45
+ }
46
+ }
47
+
37
48
  this.translationMap[normalizedKey] = cleanPhp
38
49
 
39
50
  const withoutAccents = this.removeAccents(normalizedKey)
@@ -47,7 +58,6 @@ class PHPGenerator {
47
58
 
48
59
  normalizeUTF8(str) {
49
60
  if (typeof str !== 'string') return String(str)
50
-
51
61
  try {
52
62
  const bytes = new Uint8Array(str.length)
53
63
  for (let i = 0; i < str.length; i++) {
@@ -67,8 +77,7 @@ class PHPGenerator {
67
77
  }
68
78
 
69
79
  translate(word) {
70
- if (!word) return word
71
- if (typeof word !== 'string') return String(word)
80
+ if (!word || typeof word !== 'string') return word
72
81
 
73
82
  const normalized = word.toLowerCase().trim()
74
83
 
@@ -81,41 +90,35 @@ class PHPGenerator {
81
90
  return this.translationMap[withoutAccents]
82
91
  }
83
92
 
84
- if (normalized.includes(' ')) {
85
- const words = normalized.split(' ')
86
-
87
- for (let i = words.length - 1; i >= 1; i--) {
88
- const partial = words.slice(0, i + 1).join(' ')
89
- if (this.translationMap[partial]) {
90
- return this.translationMap[partial]
91
- }
92
- const partialNoAccent = this.removeAccents(partial)
93
- if (this.translationMap[partialNoAccent]) {
94
- return this.translationMap[partialNoAccent]
95
- }
96
- }
97
-
98
- const firstWord = words[0]
99
- if (this.translationMap[firstWord]) {
100
- const result = this.translationMap[firstWord]
101
- if (!result.startsWith('__') && !result.startsWith('$')) {
102
- return result
103
- }
104
- }
105
- const firstWordNoAccent = this.removeAccents(firstWord)
106
- if (this.translationMap[firstWordNoAccent]) {
107
- const result = this.translationMap[firstWordNoAccent]
108
- if (!result.startsWith('__') && !result.startsWith('$')) {
109
- return result
110
- }
111
- }
93
+ return word
94
+ }
95
+
96
+ translateFunction(word) {
97
+ if (!word || typeof word !== 'string') return word
98
+
99
+ const normalized = word.toLowerCase().trim()
100
+ const withoutAccents = this.removeAccents(normalized)
101
+
102
+ if (this.functionMap[normalized]) {
103
+ return this.functionMap[normalized]
104
+ }
105
+
106
+ if (this.functionMap[withoutAccents]) {
107
+ return this.functionMap[withoutAccents]
108
+ }
109
+
110
+ if (this.translationMap[normalized]) {
111
+ return this.translationMap[normalized]
112
+ }
113
+
114
+ if (this.translationMap[withoutAccents]) {
115
+ return this.translationMap[withoutAccents]
112
116
  }
113
117
 
114
118
  return word
115
119
  }
116
120
 
117
121
  removeAccents(str) {
118
- if (typeof str !== 'string') return String(str)
119
122
  return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
120
123
  }
121
124
 
@@ -152,18 +155,16 @@ class PHPGenerator {
152
155
 
153
156
  const generators = {
154
157
  'Program': () => this.generateProgram(node),
155
- 'DeclareStatement': () => this.generateDeclareStatement(node),
156
- 'NamespaceDeclaration': () => this.generateNamespaceDeclaration(node),
157
158
  'FunctionDeclaration': () => this.generateFunctionDeclaration(node),
158
159
  'ClassDeclaration': () => this.generateClassDeclaration(node),
159
160
  'InterfaceDeclaration': () => this.generateInterfaceDeclaration(node),
160
161
  'TraitDeclaration': () => this.generateTraitDeclaration(node),
161
162
  'EnumDeclaration': () => this.generateEnumDeclaration(node),
162
163
  'MethodDefinition': () => this.generateMethodDefinition(node),
164
+ 'MethodDeclaration': () => this.generateMethodDefinition(node),
163
165
  'PropertyDeclaration': () => this.generatePropertyDeclaration(node),
164
166
  'VariableDeclaration': () => this.generateVariableDeclaration(node),
165
167
  'ConstantDeclaration': () => this.generateConstantDeclaration(node),
166
- 'DefineDeclaration': () => this.generateDefineDeclaration(node),
167
168
  'IfStatement': () => this.generateIfStatement(node),
168
169
  'ForStatement': () => this.generateForStatement(node),
169
170
  'ForEachStatement': () => this.generateForEachStatement(node),
@@ -174,6 +175,7 @@ class PHPGenerator {
174
175
  'MatchExpression': () => this.generateMatchExpression(node),
175
176
  'TryStatement': () => this.generateTryStatement(node),
176
177
  'ReturnStatement': () => this.generateReturnStatement(node),
178
+ 'YieldExpression': () => this.generateYieldExpression(node),
177
179
  'ThrowStatement': () => this.generateThrowStatement(node),
178
180
  'BreakStatement': () => this.writeLine('break;'),
179
181
  'ContinueStatement': () => this.writeLine('continue;'),
@@ -182,10 +184,13 @@ class PHPGenerator {
182
184
  'PrintStatement': () => this.generatePrintStatement(node),
183
185
  'Namespace': () => this.generateNamespace(node),
184
186
  'UseStatement': () => this.generateUseStatement(node),
187
+ 'DeclareStatement': () => this.generateDeclareStatement(node),
185
188
  'IncludeStatement': () => this.generateIncludeStatement(node),
186
189
  'Include': () => this.generateInclude(node),
187
190
  'CallExpression': () => this.generateCallExpression(node),
188
191
  'MemberExpression': () => this.generateMemberExpression(node),
192
+ 'StaticMemberExpression': () => this.generateStaticMemberExpression(node),
193
+ 'IndexExpression': () => this.generateIndexExpression(node),
189
194
  'BinaryExpression': () => this.generateBinaryExpression(node),
190
195
  'LogicalExpression': () => this.generateBinaryExpression(node),
191
196
  'UnaryExpression': () => this.generateUnaryExpression(node),
@@ -197,19 +202,19 @@ class PHPGenerator {
197
202
  'ObjectExpression': () => this.generateObjectExpression(node),
198
203
  'ArrowFunction': () => this.generateArrowFunction(node),
199
204
  'ArrowFunctionExpression': () => this.generateArrowFunction(node),
205
+ 'FunctionExpression': () => this.generateFunctionExpression(node),
200
206
  'Closure': () => this.generateClosure(node),
201
- 'FunctionExpression': () => this.generateClosure(node),
202
207
  'Identifier': () => this.generateIdentifier(node),
203
- 'Variable': () => this.generateVariable(node),
208
+ 'ThisExpression': () => '$this',
204
209
  'Literal': () => this.generateLiteral(node),
205
- 'StringLiteral': () => this.generateStringLiteral(node),
210
+ 'StringLiteral': () => this.generateLiteral(node),
211
+ 'NumericLiteral': () => this.generateLiteral(node),
212
+ 'BooleanLiteral': () => this.generateLiteral(node),
213
+ 'NullLiteral': () => 'null',
206
214
  'NewExpression': () => this.generateNewExpression(node),
207
215
  'InstanceOf': () => this.generateInstanceOf(node),
208
216
  'BlockStatement': () => this.generateBlockStatement(node),
209
- 'ThisExpression': () => '$this',
210
- 'StaticMemberExpression': () => this.generateStaticMemberExpression(node),
211
- 'IndexExpression': () => this.generateIndexExpression(node),
212
- 'YieldExpression': () => this.generateYieldExpression(node),
217
+ 'NamespaceDeclaration': () => this.generateNamespace(node),
213
218
  'EmptyStatement': () => '',
214
219
  'Comment': () => ''
215
220
  }
@@ -231,40 +236,12 @@ class PHPGenerator {
231
236
  }
232
237
  }
233
238
 
234
- generateDeclareStatement(node) {
235
- const directives = (node.directives || []).map(d => {
236
- const name = this.safeString(d.name)
237
- const value = this.generateNode(d.value)
238
- return `${name}=${value}`
239
- }).join(', ')
240
-
241
- this.writeLine(`declare(${directives});`)
242
- this.writeLine('')
243
- }
244
-
245
- generateNamespaceDeclaration(node) {
246
- const name = this.safeString(node.name)
247
- this.writeLine(`namespace ${name};`)
248
- this.writeLine('')
249
-
250
- if (node.body) {
251
- this.generateNode(node.body)
252
- }
253
- }
254
-
255
- generateDefineDeclaration(node) {
256
- const name = this.safeString(node.name)
257
- const value = this.generateNode(node.value)
258
- this.writeLine(`define('${name}', ${value});`)
259
- }
260
-
261
239
  generateFunctionDeclaration(node) {
262
- const name = this.safeString(node.name || node.id?.name || 'anonymous')
263
- const translatedName = this.translate(name)
240
+ const name = node.name || node.id?.name || 'anonymous'
264
241
  const params = this.generateParams(node.params || [])
265
242
  const returnType = node.returnType ? ': ' + this.translateType(node.returnType) : ''
266
243
 
267
- this.writeLine(`function ${translatedName}(${params})${returnType} {`)
244
+ this.writeLine(`function ${name}(${params})${returnType} {`)
268
245
  this.indent++
269
246
  this.generateNode(node.body)
270
247
  this.indent--
@@ -280,25 +257,20 @@ class PHPGenerator {
280
257
  result += this.translate(p.visibility) + ' '
281
258
  }
282
259
 
283
- if (p.readonly) {
284
- result += 'readonly '
285
- }
286
-
287
- const typeHint = p.typeHint || (p.type !== 'Parameter' ? p.type : null)
288
- if (typeHint && typeHint !== 'Parameter') {
289
- result += this.translateType(typeHint) + ' '
260
+ if (p.typeHint) {
261
+ result += this.translateType(p.typeHint) + ' '
290
262
  }
291
263
 
292
264
  if (p.variadic) {
293
265
  result += '...'
294
266
  }
295
267
 
296
- if (p.byReference || p.reference) {
268
+ if (p.reference || p.byReference) {
297
269
  result += '&'
298
270
  }
299
271
 
300
- const name = this.safeString(p.name || p.id?.name || p)
301
- result += '$' + name
272
+ const name = p.name || p.id?.name || p
273
+ result += '$' + (typeof name === 'string' ? name : name.name || 'param')
302
274
 
303
275
  if (p.default !== undefined && p.default !== null) {
304
276
  result += ' = ' + this.generateNode(p.default)
@@ -308,18 +280,11 @@ class PHPGenerator {
308
280
  }).join(', ')
309
281
  }
310
282
 
311
- safeString(value) {
312
- if (typeof value === 'string') return value
313
- if (value && typeof value === 'object') {
314
- return value.name || value.value || 'unknown'
315
- }
316
- return String(value || 'unknown')
317
- }
318
-
319
283
  translateType(type) {
320
284
  if (!type) return ''
321
285
 
322
286
  if (typeof type === 'string') {
287
+ const translated = this.translate(type)
323
288
  const typeMap = {
324
289
  'chaîne': 'string', 'chaine': 'string', 'string': 'string',
325
290
  'entier': 'int', 'integer': 'int', 'int': 'int',
@@ -334,50 +299,53 @@ class PHPGenerator {
334
299
  'itérable': 'iterable', 'iterable': 'iterable',
335
300
  'jamais': 'never', 'never': 'never'
336
301
  }
337
- const lower = type.toLowerCase()
338
- return typeMap[lower] || type
302
+ return typeMap[translated.toLowerCase()] || typeMap[type.toLowerCase()] || type
339
303
  }
340
304
 
341
- if (type.type === 'TypeHint' && type.types) {
342
- const typeStr = type.types.join('|')
343
- return (type.nullable ? '?' : '') + typeStr
305
+ if (type.type === 'TypeHint' && type.types && type.types.length > 0) {
306
+ const prefix = type.nullable ? '?' : ''
307
+ if (type.union || type.types.length > 1) {
308
+ return prefix + type.types.map(t => this.translateType(t)).join('|')
309
+ }
310
+ return prefix + this.translateType(type.types[0])
344
311
  }
345
312
 
346
- if (type.union && type.types) {
313
+ if (type.union) {
347
314
  return type.types.map(t => this.translateType(t)).join('|')
348
315
  }
349
316
 
350
- if (type.intersection && type.types) {
317
+ if (type.intersection) {
351
318
  return type.types.map(t => this.translateType(t)).join('&')
352
319
  }
353
320
 
354
- if (type.nullable && type.type) {
321
+ if (type.nullable) {
355
322
  return '?' + this.translateType(type.type)
356
323
  }
357
324
 
358
- if (type.name) {
359
- return this.translateType(type.name)
360
- }
361
-
362
- return String(type)
325
+ return type.name || type
363
326
  }
364
327
 
365
328
  generateClassDeclaration(node) {
366
329
  let declaration = ''
367
330
 
368
- if (node.abstract) declaration += 'abstract '
369
- if (node.final) declaration += 'final '
370
- if (node.readonly) declaration += 'readonly '
331
+ const modifiers = node.modifiers || []
332
+ if (modifiers.includes('abstract') || node.abstract) declaration += 'abstract '
333
+ if (modifiers.includes('final') || node.final) declaration += 'final '
334
+ if (modifiers.includes('readonly') || node.readonly) declaration += 'readonly '
371
335
 
372
- const className = this.safeString(node.name || node.id?.name)
373
- declaration += 'class ' + this.translate(className)
336
+ declaration += 'class ' + (node.name || node.id?.name)
374
337
 
375
- if (node.extends) {
376
- declaration += ' extends ' + this.translate(this.safeString(node.extends))
338
+ if (node.superClass) {
339
+ const superName = typeof node.superClass === 'string' ? node.superClass : node.superClass.name
340
+ declaration += ' extends ' + superName
341
+ } else if (node.extends) {
342
+ declaration += ' extends ' + (typeof node.extends === 'string' ? node.extends : node.extends.name)
377
343
  }
378
344
 
379
- if (node.implements && node.implements.length > 0) {
380
- declaration += ' implements ' + node.implements.map(i => this.translate(this.safeString(i))).join(', ')
345
+ if (node.interfaces && node.interfaces.length > 0) {
346
+ declaration += ' implements ' + node.interfaces.map(i => typeof i === 'string' ? i : i.name).join(', ')
347
+ } else if (node.implements && node.implements.length > 0) {
348
+ declaration += ' implements ' + node.implements.map(i => typeof i === 'string' ? i : i.name).join(', ')
381
349
  }
382
350
 
383
351
  this.writeLine(declaration + ' {')
@@ -393,11 +361,10 @@ class PHPGenerator {
393
361
  }
394
362
 
395
363
  generateInterfaceDeclaration(node) {
396
- const name = this.safeString(node.name || node.id?.name)
397
- let declaration = 'interface ' + this.translate(name)
364
+ let declaration = 'interface ' + this.translate(node.name || node.id?.name)
398
365
 
399
366
  if (node.extends && node.extends.length > 0) {
400
- declaration += ' extends ' + node.extends.map(e => this.translate(this.safeString(e))).join(', ')
367
+ declaration += ' extends ' + node.extends.map(e => this.translate(e)).join(', ')
401
368
  }
402
369
 
403
370
  this.writeLine(declaration + ' {')
@@ -413,8 +380,7 @@ class PHPGenerator {
413
380
  }
414
381
 
415
382
  generateTraitDeclaration(node) {
416
- const name = this.safeString(node.name || node.id?.name)
417
- this.writeLine('trait ' + this.translate(name) + ' {')
383
+ this.writeLine('trait ' + this.translate(node.name || node.id?.name) + ' {')
418
384
  this.indent++
419
385
 
420
386
  for (const member of node.body || node.members || []) {
@@ -427,8 +393,7 @@ class PHPGenerator {
427
393
  }
428
394
 
429
395
  generateEnumDeclaration(node) {
430
- const name = this.safeString(node.name || node.id?.name)
431
- let declaration = 'enum ' + this.translate(name)
396
+ let declaration = 'enum ' + this.translate(node.name || node.id?.name)
432
397
 
433
398
  if (node.backingType) {
434
399
  declaration += ': ' + this.translateType(node.backingType)
@@ -464,16 +429,19 @@ class PHPGenerator {
464
429
  if (node.final) declaration += 'final '
465
430
  if (node.abstract) declaration += 'abstract '
466
431
 
467
- const name = this.translateMethodName(this.safeString(node.name || node.key?.name))
432
+ const name = this.translateMethodName(node.name || node.key?.name)
468
433
  const params = this.generateParams(node.params || node.value?.params || [])
469
434
  const returnType = node.returnType ? ': ' + this.translateType(node.returnType) : ''
470
435
 
471
- if (node.abstract) {
436
+ const body = node.body || node.value?.body
437
+ const hasEmptyBody = !body || (body.type === 'BlockStatement' && (!body.body || body.body.length === 0))
438
+
439
+ if (node.abstract || hasEmptyBody) {
472
440
  this.writeLine(`${declaration}function ${name}(${params})${returnType};`)
473
441
  } else {
474
442
  this.writeLine(`${declaration}function ${name}(${params})${returnType} {`)
475
443
  this.indent++
476
- this.generateNode(node.body || node.value?.body)
444
+ this.generateNode(body)
477
445
  this.indent--
478
446
  this.writeLine('}')
479
447
  }
@@ -527,7 +495,7 @@ class PHPGenerator {
527
495
  }
528
496
 
529
497
  const normalized = name.toLowerCase().trim()
530
- return magicMethods[normalized] || this.translate(name)
498
+ return magicMethods[normalized] || name
531
499
  }
532
500
 
533
501
  generatePropertyDeclaration(node) {
@@ -539,14 +507,16 @@ class PHPGenerator {
539
507
  if (node.static) declaration += 'static '
540
508
  if (node.readonly) declaration += 'readonly '
541
509
 
542
- if (node.type) {
543
- declaration += this.translateType(node.type) + ' '
510
+ if (node.typeHint) {
511
+ declaration += this.translateType(node.typeHint) + ' '
544
512
  }
545
513
 
546
- const name = this.safeString(node.name || node.key?.name)
547
- declaration += '$' + this.translate(name)
514
+ const name = node.name || node.key?.name
515
+ declaration += '$' + name
548
516
 
549
- if (node.value !== undefined) {
517
+ if (node.default !== undefined && node.default !== null) {
518
+ declaration += ' = ' + this.generateNode(node.default)
519
+ } else if (node.value !== undefined && node.value !== null) {
550
520
  declaration += ' = ' + this.generateNode(node.value)
551
521
  }
552
522
 
@@ -555,8 +525,8 @@ class PHPGenerator {
555
525
 
556
526
  generateVariableDeclaration(node) {
557
527
  for (const decl of node.declarations || [node]) {
558
- const name = this.safeString(decl.name || decl.id?.name || decl.id)
559
- const varName = '$' + name
528
+ const name = decl.name || decl.id?.name || decl.id
529
+ const varName = '$' + (typeof name === 'string' ? name : name.name || 'var')
560
530
 
561
531
  if (decl.init !== undefined) {
562
532
  const value = this.generateNode(decl.init)
@@ -568,7 +538,7 @@ class PHPGenerator {
568
538
  }
569
539
 
570
540
  generateConstantDeclaration(node) {
571
- const name = this.safeString(node.name || node.id?.name)
541
+ const name = node.name || node.id?.name
572
542
  const value = this.generateNode(node.value || node.init)
573
543
 
574
544
  if (node.classLevel) {
@@ -587,8 +557,8 @@ class PHPGenerator {
587
557
  this.indent--
588
558
 
589
559
  if (node.alternate) {
590
- if (node.alternate.type === 'IfStatement') {
591
- this.write('} else ')
560
+ if (node.alternate.type === 'IfStatement' || node.alternate.type === 'ElseIfStatement') {
561
+ this.write('} else')
592
562
  this.generateIfStatement({ ...node.alternate, isElseIf: true })
593
563
  } else {
594
564
  this.writeLine('} else {')
@@ -616,8 +586,17 @@ class PHPGenerator {
616
586
 
617
587
  generateForEachStatement(node) {
618
588
  const array = this.generateNode(node.array || node.right || node.iterable)
619
- const value = this.generateVariable(node.value || node.left || node.valueVar)
620
- const key = node.key || node.keyVar ? this.generateVariable(node.key || node.keyVar) + ' => ' : ''
589
+
590
+ const valueNode = node.value || node.left || node.valueVar
591
+ const value = typeof valueNode === 'string'
592
+ ? '$' + valueNode
593
+ : this.generateIdentifier(valueNode)
594
+
595
+ let key = ''
596
+ const keyNode = node.key || node.keyVar
597
+ if (keyNode) {
598
+ key = (typeof keyNode === 'string' ? '$' + keyNode : this.generateIdentifier(keyNode)) + ' => '
599
+ }
621
600
 
622
601
  this.writeLine(`foreach (${array} as ${key}${value}) {`)
623
602
  this.indent++
@@ -692,19 +671,25 @@ class PHPGenerator {
692
671
  this.generateNode(node.block)
693
672
  this.indent--
694
673
 
695
- for (const handler of node.handlers || (node.handler ? [node.handler] : [])) {
696
- const exceptionType = handler.type || handler.param?.type || 'Exception'
697
- const param = handler.param ? this.generateVariable(handler.param) : '$e'
698
- this.writeLine(`} catch (${exceptionType} ${param}) {`)
674
+ const handlers = node.catches || node.handlers || (node.handler ? [node.handler] : [])
675
+ for (const handler of handlers) {
676
+ const types = handler.types && handler.types.length > 0
677
+ ? handler.types.join(' | ')
678
+ : (handler.type || 'Exception')
679
+ const param = handler.param
680
+ ? (typeof handler.param === 'string' ? '$' + handler.param : this.generateIdentifier(handler.param))
681
+ : '$e'
682
+ this.writeLine(`} catch (${types} ${param}) {`)
699
683
  this.indent++
700
684
  this.generateNode(handler.body)
701
685
  this.indent--
702
686
  }
703
687
 
704
- if (node.finalizer) {
688
+ const finallyBlock = node.finally || node.finalizer
689
+ if (finallyBlock) {
705
690
  this.writeLine('} finally {')
706
691
  this.indent++
707
- this.generateNode(node.finalizer)
692
+ this.generateNode(finallyBlock)
708
693
  this.indent--
709
694
  }
710
695
 
@@ -720,6 +705,14 @@ class PHPGenerator {
720
705
  }
721
706
  }
722
707
 
708
+ generateYieldExpression(node) {
709
+ const arg = node.argument ? this.generateNode(node.argument) : ''
710
+ if (node.delegate) {
711
+ return `yield from ${arg}`
712
+ }
713
+ return `yield ${arg}`
714
+ }
715
+
723
716
  generateThrowStatement(node) {
724
717
  const arg = this.generateNode(node.argument)
725
718
  this.writeLine(`throw ${arg};`)
@@ -742,8 +735,7 @@ class PHPGenerator {
742
735
  }
743
736
 
744
737
  generateNamespace(node) {
745
- const name = this.safeString(node.name)
746
- this.writeLine(`namespace ${name};`)
738
+ this.writeLine(`namespace ${node.name};`)
747
739
  this.writeLine('')
748
740
  }
749
741
 
@@ -752,17 +744,25 @@ class PHPGenerator {
752
744
 
753
745
  if (node.imports && node.imports.length > 0) {
754
746
  for (const imp of node.imports) {
755
- const path = this.safeString(imp.path)
747
+ const path = imp.path || imp.name || imp
756
748
  const alias = imp.alias ? ' as ' + imp.alias : ''
757
749
  this.writeLine(`use ${typePrefix}${path}${alias};`)
758
750
  }
759
751
  } else {
760
- const name = this.safeString(node.name || node.source)
752
+ const name = node.name || node.source || 'undefined'
761
753
  const alias = node.alias ? ' as ' + node.alias : ''
762
754
  this.writeLine(`use ${typePrefix}${name}${alias};`)
763
755
  }
764
756
  }
765
757
 
758
+ generateDeclareStatement(node) {
759
+ const directives = (node.directives || []).map(d => {
760
+ const value = typeof d.value === 'object' ? this.generateNode(d.value) : d.value
761
+ return `${d.name}=${value}`
762
+ }).join(', ')
763
+ this.writeLine(`declare(${directives});`)
764
+ }
765
+
766
766
  generateIncludeStatement(node) {
767
767
  const type = node.once
768
768
  ? (node.required ? 'require_once' : 'include_once')
@@ -783,23 +783,30 @@ class PHPGenerator {
783
783
  let callee
784
784
 
785
785
  if (typeof node.callee === 'string') {
786
- callee = this.translate(node.callee)
787
- } else if (node.callee && node.callee.type === 'MemberExpression') {
786
+ callee = this.translateFunction(node.callee)
787
+ } else if (node.callee.type === 'MemberExpression') {
788
788
  callee = this.generateMemberExpression(node.callee)
789
- } else if (node.callee && node.callee.type === 'StaticMemberExpression') {
789
+ } else if (node.callee.type === 'StaticMemberExpression') {
790
790
  callee = this.generateStaticMemberExpression(node.callee)
791
- } else if (node.callee && node.callee.type === 'Identifier') {
792
- const name = this.safeString(node.callee.name)
793
- callee = this.translate(name)
791
+ } else if (node.callee.type === 'Identifier') {
792
+ const name = node.callee.name
793
+ const translated = this.translateFunction(name)
794
+ if (translated !== name) {
795
+ callee = translated
796
+ } else if (name.includes(' ')) {
797
+ callee = name.replace(/ /g, '_')
798
+ } else {
799
+ callee = name
800
+ }
794
801
  } else {
795
802
  callee = this.generateNode(node.callee)
796
803
  }
797
804
 
798
805
  const args = (node.arguments || []).map(a => {
799
- if (a && a.type === 'SpreadElement') {
806
+ if (a.spread) {
800
807
  return '...' + this.generateNode(a.argument || a)
801
808
  }
802
- if (a && a.type === 'NamedArgument') {
809
+ if (a.name && a.value) {
803
810
  return `${a.name}: ${this.generateNode(a.value)}`
804
811
  }
805
812
  return this.generateNode(a)
@@ -813,11 +820,18 @@ class PHPGenerator {
813
820
  let property
814
821
 
815
822
  if (typeof node.property === 'string') {
816
- property = this.translateMethodName(node.property)
817
- } else if (node.property && node.property.name) {
818
- property = this.translateMethodName(node.property.name)
819
- } else if (node.property && node.property.type === 'Identifier') {
820
- property = this.translateMethodName(node.property.name || node.property.value)
823
+ property = node.property
824
+ } else if (node.property.type === 'Identifier') {
825
+ let propName = node.property.name
826
+ let translated = this.translateFunction(propName)
827
+ if (translated.includes('->')) {
828
+ translated = translated.split('->').pop()
829
+ } else if (translated.includes('::')) {
830
+ translated = translated.split('::').pop()
831
+ } else if (translated !== propName && !translated.includes('(')) {
832
+ translated = propName
833
+ }
834
+ property = translated
821
835
  } else {
822
836
  property = this.generateNode(node.property)
823
837
  }
@@ -828,55 +842,35 @@ class PHPGenerator {
828
842
  if (node.computed) {
829
843
  return `${object}[${property}]`
830
844
  }
831
- if (node.nullsafe || node.operator === '?->') {
845
+ if (node.nullSafe) {
832
846
  return `${object}?->${property}`
833
847
  }
834
848
  return `${object}->${property}`
835
849
  }
836
850
 
837
- translateMethodName(name) {
838
- if (!name) return name
839
- const translated = this.translate(name)
840
- if (translated.includes('->')) {
841
- const parts = translated.split('->')
842
- return parts[parts.length - 1].replace(/\(\)$/, '')
843
- }
844
- if (translated.includes('::')) {
845
- const parts = translated.split('::')
846
- return parts[parts.length - 1].replace(/\(\)$/, '')
847
- }
848
- return translated.replace(/\(\)$/, '')
849
- }
850
-
851
851
  generateStaticMemberExpression(node) {
852
- const cls = this.generateNode(node.class)
853
- let member
854
-
855
- if (typeof node.member === 'string') {
856
- member = node.member
857
- } else if (node.member && node.member.name) {
858
- member = node.member.name
852
+ const classNode = node.class || node.object
853
+ const className = classNode.type === 'Identifier' ? classNode.name : this.generateNode(classNode)
854
+
855
+ const memberNode = node.member || node.property
856
+ let memberName
857
+ if (typeof memberNode === 'string') {
858
+ memberName = memberNode
859
+ } else if (memberNode.type === 'Identifier') {
860
+ memberName = this.translateMethodName(memberNode.name)
859
861
  } else {
860
- member = this.generateNode(node.member)
862
+ memberName = this.generateNode(memberNode)
861
863
  }
862
864
 
863
- return `${cls}::${member}`
865
+ return `${className}::${memberName}`
864
866
  }
865
867
 
866
868
  generateIndexExpression(node) {
867
869
  const object = this.generateNode(node.object)
868
- const index = node.index ? this.generateNode(node.index) : ''
870
+ const index = this.generateNode(node.index)
869
871
  return `${object}[${index}]`
870
872
  }
871
873
 
872
- generateYieldExpression(node) {
873
- const argument = node.argument ? this.generateNode(node.argument) : ''
874
- if (node.delegate) {
875
- return `yield from ${argument}`
876
- }
877
- return argument ? `yield ${argument}` : 'yield'
878
- }
879
-
880
874
  generateBinaryExpression(node) {
881
875
  const left = this.generateNode(node.left)
882
876
  const right = this.generateNode(node.right)
@@ -893,16 +887,15 @@ class PHPGenerator {
893
887
  'fusion null': '??', 'null coalescing': '??'
894
888
  }
895
889
 
896
- const opLower = op ? op.toLowerCase() : op
897
- op = opMap[opLower] || op
890
+ op = opMap[op?.toLowerCase()] || op
898
891
 
899
- return `${left} ${op} ${right}`
892
+ const result = `${left} ${op} ${right}`
893
+ return node.parenthesized ? `(${result})` : result
900
894
  }
901
895
 
902
896
  generateUnaryExpression(node) {
903
897
  const argument = this.generateNode(node.argument || node.operand)
904
- let op = node.operator
905
- if (op === 'non' || op === 'not') op = '!'
898
+ const op = node.operator === 'non' || node.operator === 'not' ? '!' : node.operator
906
899
 
907
900
  if (node.prefix !== false) {
908
901
  return `${op}${argument}`
@@ -914,7 +907,7 @@ class PHPGenerator {
914
907
  const argument = this.generateNode(node.argument || node.operand)
915
908
  const op = node.operator
916
909
 
917
- if (node.prefix !== false) {
910
+ if (node.prefix) {
918
911
  return `${op}${argument}`
919
912
  }
920
913
  return `${argument}${op}`
@@ -943,9 +936,6 @@ class PHPGenerator {
943
936
  generateArrayExpression(node) {
944
937
  const elements = (node.elements || []).map(e => {
945
938
  if (!e) return 'null'
946
- if (e.type === 'SpreadElement') {
947
- return '...' + this.generateNode(e.argument || e)
948
- }
949
939
  if (e.type === 'ArrayElement') {
950
940
  if (e.key !== null && e.key !== undefined) {
951
941
  const key = this.generateNode(e.key)
@@ -959,6 +949,9 @@ class PHPGenerator {
959
949
  const value = this.generateNode(e.value || e)
960
950
  return `${key} => ${value}`
961
951
  }
952
+ if (e.spread) {
953
+ return '...' + this.generateNode(e.argument || e)
954
+ }
962
955
  return this.generateNode(e)
963
956
  })
964
957
 
@@ -979,68 +972,94 @@ class PHPGenerator {
979
972
  return `fn(${params}) => ${body}`
980
973
  }
981
974
 
982
- generateClosure(node) {
975
+ generateFunctionExpression(node) {
983
976
  const params = this.generateParams(node.params || [])
984
- const useVars = node.uses && node.uses.length > 0
985
- ? ` use (${node.uses.map(u => (u.byRef ? '&' : '') + '$' + this.translate(this.safeString(u.name || u))).join(', ')})`
977
+ const uses = node.uses || node.use || []
978
+ const useVars = uses.length > 0
979
+ ? ` use (${uses.map(u => (u.byReference || u.byRef ? '&' : '') + '$' + (u.name || u)).join(', ')})`
986
980
  : ''
987
981
  const returnType = node.returnType ? ': ' + this.translateType(node.returnType) : ''
988
982
 
989
983
  let result = `function(${params})${useVars}${returnType} {\n`
984
+
985
+ const savedOutput = this.output
986
+ const savedIndent = this.indent
987
+ this.output = ''
990
988
  this.indent++
991
989
 
992
- if (node.body && Array.isArray(node.body.body)) {
993
- for (const stmt of node.body.body) {
994
- result += this.getIndent() + this.generateNode(stmt)
990
+ const body = node.body
991
+ if (body && body.type === 'BlockStatement' && body.body) {
992
+ for (const stmt of body.body) {
993
+ this.generateNode(stmt)
995
994
  }
996
- } else if (node.body) {
997
- result += this.getIndent() + 'return ' + this.generateNode(node.body) + ';\n'
995
+ } else if (body) {
996
+ this.writeLine('return ' + this.generateNode(body) + ';')
998
997
  }
999
998
 
1000
- this.indent--
999
+ const bodyOutput = this.output
1000
+ this.output = savedOutput
1001
+ this.indent = savedIndent
1002
+
1003
+ result += bodyOutput
1001
1004
  result += this.getIndent() + '}'
1002
1005
  return result
1003
1006
  }
1004
1007
 
1005
- generateVariable(node) {
1006
- if (!node) return '$var'
1008
+ generateClosure(node) {
1009
+ const params = this.generateParams(node.params || [])
1010
+ const useVars = node.use && node.use.length > 0
1011
+ ? ` use (${node.use.map(u => (u.byRef ? '&' : '') + '$' + this.translate(u.name || u)).join(', ')})`
1012
+ : ''
1013
+ const returnType = node.returnType ? ': ' + this.translateType(node.returnType) : ''
1014
+
1015
+ let result = `function(${params})${useVars}${returnType} {\n`
1016
+ this.indent++
1007
1017
 
1008
- const name = this.safeString(node.name || node.id?.name || node)
1009
- const nameStr = String(name)
1018
+ if (Array.isArray(node.body)) {
1019
+ for (const stmt of node.body) {
1020
+ result += this.getIndent() + this.generateNode(stmt)
1021
+ }
1022
+ } else {
1023
+ result += this.getIndent() + 'return ' + this.generateNode(node.body) + ';\n'
1024
+ }
1010
1025
 
1011
- if (nameStr.startsWith('$')) return nameStr
1012
- return '$' + nameStr
1026
+ this.indent--
1027
+ result += this.getIndent() + '}'
1028
+ return result
1013
1029
  }
1014
1030
 
1015
1031
  generateIdentifier(node) {
1016
- if (!node) return '$var'
1017
-
1018
- const name = this.safeString(node.name || node.id?.name || node)
1019
- const nameStr = String(name)
1020
-
1021
- if (nameStr.startsWith('$')) return nameStr
1032
+ const name = node.name || node.id?.name || node
1033
+ if (typeof name !== 'string') return '$var'
1022
1034
 
1023
1035
  const keywords = [
1024
1036
  'true', 'false', 'null',
1025
1037
  'self', 'parent', 'static',
1026
- 'this', '$this'
1038
+ 'this', '$this', 'vrai', 'faux', 'nul', 'ceci'
1027
1039
  ]
1028
- if (keywords.includes(nameStr.toLowerCase())) {
1029
- if (nameStr.toLowerCase() === 'this') return '$this'
1030
- return nameStr
1040
+ const lowerName = name.toLowerCase()
1041
+
1042
+ if (lowerName === 'vrai') return 'true'
1043
+ if (lowerName === 'faux') return 'false'
1044
+ if (lowerName === 'nul') return 'null'
1045
+ if (lowerName === 'ceci' || lowerName === 'this') return '$this'
1046
+
1047
+ if (keywords.includes(lowerName)) {
1048
+ return name
1031
1049
  }
1032
1050
 
1033
- if (/^[A-Z]/.test(nameStr)) return nameStr
1051
+ if (name.startsWith('$')) return name
1034
1052
 
1035
- if (nameStr.includes('(') || nameStr.includes('::')) return nameStr
1053
+ if (/^[A-Z]/.test(name)) return name
1054
+
1055
+ if (name.includes('(') || name.includes('::') || name.includes('->')) return name
1036
1056
 
1037
1057
  const superglobals = ['_GET', '_POST', '_SERVER', '_SESSION', '_COOKIE', '_FILES', '_REQUEST', '_ENV', 'GLOBALS']
1038
- const upper = nameStr.toUpperCase()
1039
- if (superglobals.includes(nameStr) || superglobals.includes(upper)) {
1040
- return '$' + upper
1058
+ if (superglobals.includes(name) || superglobals.includes(name.toUpperCase())) {
1059
+ return '$' + name.toUpperCase()
1041
1060
  }
1042
1061
 
1043
- return '$' + nameStr
1062
+ return '$' + name
1044
1063
  }
1045
1064
 
1046
1065
  generateLiteral(node) {
@@ -1060,26 +1079,10 @@ class PHPGenerator {
1060
1079
  return String(node.value)
1061
1080
  }
1062
1081
 
1063
- generateStringLiteral(node) {
1064
- const value = node.value || ''
1065
- const escaped = value
1066
- .replace(/\\/g, '\\\\')
1067
- .replace(/"/g, '\\"')
1068
- .replace(/\n/g, '\\n')
1069
- .replace(/\r/g, '\\r')
1070
- .replace(/\t/g, '\\t')
1071
- return node.doubleQuoted !== false ? `"${escaped}"` : `'${escaped}'`
1072
- }
1073
-
1074
1082
  generateNewExpression(node) {
1075
- let callee
1076
- if (typeof node.callee === 'string') {
1077
- callee = this.translate(node.callee)
1078
- } else if (node.callee && node.callee.name) {
1079
- callee = this.translate(node.callee.name)
1080
- } else {
1081
- callee = this.generateNode(node.callee)
1082
- }
1083
+ const callee = typeof node.callee === 'string'
1084
+ ? this.translate(node.callee)
1085
+ : this.generateNode(node.callee)
1083
1086
  const args = (node.arguments || []).map(a => this.generateNode(a)).join(', ')
1084
1087
  return `new ${callee}(${args})`
1085
1088
  }