ether-code 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +130 -0
  3. package/cli/compiler.js +298 -0
  4. package/cli/ether.js +532 -0
  5. package/cli/watcher.js +106 -0
  6. package/generators/css-generator.js +583 -0
  7. package/generators/graphql-generator.js +868 -0
  8. package/generators/html-generator.js +745 -0
  9. package/generators/js-generator.js +909 -0
  10. package/generators/node-generator.js +467 -0
  11. package/generators/php-generator.js +706 -0
  12. package/generators/python-generator.js +913 -0
  13. package/generators/react-generator.js +599 -0
  14. package/generators/ruby-generator.js +904 -0
  15. package/generators/sql-generator.js +988 -0
  16. package/generators/ts-generator.js +569 -0
  17. package/i18n/i18n-css.json +743 -0
  18. package/i18n/i18n-graphql.json +1531 -0
  19. package/i18n/i18n-html.json +572 -0
  20. package/i18n/i18n-js.json +2790 -0
  21. package/i18n/i18n-node.json +2442 -0
  22. package/i18n/i18n-php.json +4306 -0
  23. package/i18n/i18n-python.json +3080 -0
  24. package/i18n/i18n-react.json +1784 -0
  25. package/i18n/i18n-ruby.json +1858 -0
  26. package/i18n/i18n-sql.json +3466 -0
  27. package/i18n/i18n-ts.json +442 -0
  28. package/lexer/ether-lexer.js +728 -0
  29. package/lexer/tokens.js +292 -0
  30. package/package.json +45 -0
  31. package/parsers/ast-css.js +545 -0
  32. package/parsers/ast-graphql.js +424 -0
  33. package/parsers/ast-html.js +886 -0
  34. package/parsers/ast-js.js +750 -0
  35. package/parsers/ast-node.js +2440 -0
  36. package/parsers/ast-php.js +957 -0
  37. package/parsers/ast-react.js +580 -0
  38. package/parsers/ast-ruby.js +895 -0
  39. package/parsers/ast-ts.js +1352 -0
  40. package/parsers/css-parser.js +1981 -0
  41. package/parsers/graphql-parser.js +2011 -0
  42. package/parsers/html-parser.js +1181 -0
  43. package/parsers/js-parser.js +2564 -0
  44. package/parsers/node-parser.js +2644 -0
  45. package/parsers/php-parser.js +3037 -0
  46. package/parsers/react-parser.js +1035 -0
  47. package/parsers/ruby-parser.js +2680 -0
  48. package/parsers/ts-parser.js +3881 -0
@@ -0,0 +1,599 @@
1
+ const fs = require('fs')
2
+ const { JSGenerator } = require('./js-generator')
3
+
4
+ class ReactGenerator extends JSGenerator {
5
+ constructor(i18nPath = null) {
6
+ super(i18nPath)
7
+ this.reactMap = {}
8
+ this.hookMap = {}
9
+
10
+ if (i18nPath) {
11
+ this.loadReactI18n(i18nPath)
12
+ }
13
+
14
+ this.buildReactMaps()
15
+ }
16
+
17
+ loadReactI18n(filePath) {
18
+ const content = fs.readFileSync(filePath, 'utf-8')
19
+ const i18n = JSON.parse(content)
20
+
21
+ const addToMap = (map, section) => {
22
+ if (!section) return
23
+ for (const [key, translations] of Object.entries(section)) {
24
+ if (translations && translations.fr && translations.react) {
25
+ map[translations.fr.toLowerCase()] = translations.react
26
+ }
27
+ }
28
+ }
29
+
30
+ for (const sectionName of Object.keys(i18n)) {
31
+ addToMap(this.reactMap, i18n[sectionName])
32
+ }
33
+ }
34
+
35
+ buildReactMaps() {
36
+ this.hookMap = {
37
+ 'état': 'useState',
38
+ 'utiliser état': 'useState',
39
+ 'effet': 'useEffect',
40
+ 'utiliser effet': 'useEffect',
41
+ 'contexte': 'useContext',
42
+ 'utiliser contexte': 'useContext',
43
+ 'réducteur': 'useReducer',
44
+ 'utiliser réducteur': 'useReducer',
45
+ 'rappel': 'useCallback',
46
+ 'utiliser rappel': 'useCallback',
47
+ 'mémo': 'useMemo',
48
+ 'utiliser mémo': 'useMemo',
49
+ 'référence': 'useRef',
50
+ 'utiliser référence': 'useRef',
51
+ 'effet mise en page': 'useLayoutEffect',
52
+ 'utiliser effet mise en page': 'useLayoutEffect',
53
+ 'valeur imperative': 'useImperativeHandle',
54
+ 'debug': 'useDebugValue',
55
+ 'utiliser debug': 'useDebugValue',
56
+ 'utiliser id': 'useId',
57
+ 'transition': 'useTransition',
58
+ 'utiliser transition': 'useTransition',
59
+ 'valeur différée': 'useDeferredValue',
60
+ 'utiliser valeur différée': 'useDeferredValue',
61
+ 'effet insertion': 'useInsertionEffect',
62
+ 'valeur sync externe': 'useSyncExternalStore'
63
+ }
64
+
65
+ this.componentMap = {
66
+ 'fragment': 'Fragment',
67
+ 'suspense': 'Suspense',
68
+ 'limite erreur': 'ErrorBoundary',
69
+ 'portail': 'Portal',
70
+ 'strict': 'StrictMode',
71
+ 'mode strict': 'StrictMode',
72
+ 'profileur': 'Profiler'
73
+ }
74
+
75
+ this.apiMap = {
76
+ 'créer élément': 'createElement',
77
+ 'cloner élément': 'cloneElement',
78
+ 'est élément valide': 'isValidElement',
79
+ 'enfants': 'Children',
80
+ 'créer ref': 'createRef',
81
+ 'transferer ref': 'forwardRef',
82
+ 'memoriser': 'mémo',
83
+ 'paresseux': 'lazy',
84
+ 'créer contexte': 'createContext',
85
+ 'créer portail': 'createPortal'
86
+ }
87
+ }
88
+
89
+ translate(word) {
90
+ const lower = word.toLowerCase()
91
+ return this.hookMap[lower] ||
92
+ this.componentMap[lower] ||
93
+ this.apiMap[lower] ||
94
+ this.reactMap[lower] ||
95
+ super.translate(word)
96
+ }
97
+
98
+ generate(ast) {
99
+ this.output = ''
100
+ this.indent = 0
101
+
102
+ if (Array.isArray(ast)) {
103
+ for (const node of ast) {
104
+ const result = this.generateNode(node)
105
+ if (result !== undefined && result !== '' && this.output === '') {
106
+ return result
107
+ }
108
+ }
109
+ } else if (ast && ast.type) {
110
+ const result = this.generateNode(ast)
111
+ if (result !== undefined && result !== '' && this.output === '') {
112
+ return result
113
+ }
114
+ } else if (ast && ast.body) {
115
+ for (const node of ast.body) {
116
+ const result = this.generateNode(node)
117
+ if (result !== undefined && result !== '' && this.output === '') {
118
+ return result
119
+ }
120
+ }
121
+ }
122
+
123
+ return this.output.trim()
124
+ }
125
+
126
+ generateNode(node) {
127
+ if (!node) return ''
128
+
129
+ switch (node.type) {
130
+ case 'JSXElement':
131
+ return this.generateJSXElement(node)
132
+ case 'JSXFragment':
133
+ return this.generateJSXFragment(node)
134
+ case 'JSXText':
135
+ return this.generateJSXText(node)
136
+ case 'JSXExpression':
137
+ case 'JSXExpressionContainer':
138
+ return this.generateJSXExpression(node)
139
+ case 'JSXSpreadAttribute':
140
+ return this.generateJSXSpreadAttribute(node)
141
+ case 'JSXAttribute':
142
+ return this.generateJSXAttribute(node)
143
+ case 'ComponentDeclaration':
144
+ return this.generateComponent(node)
145
+ case 'HookCall':
146
+ return this.generateHookCall(node)
147
+ default:
148
+ return super.generateNode(node)
149
+ }
150
+ }
151
+
152
+ generateJSXElement(node) {
153
+ const tagName = this.translateJSXTag(node.openingElement?.name || node.name || node.tag)
154
+ const attributes = this.generateJSXAttributes(node.openingElement?.attributes || node.attributes || [])
155
+ const children = node.children || []
156
+ const selfClosing = node.selfClosing || node.openingElement?.selfClosing || children.length === 0
157
+
158
+ if (selfClosing && children.length === 0) {
159
+ return `<${tagName}${attributes} />`
160
+ }
161
+
162
+ let result = `<${tagName}${attributes}>`
163
+
164
+ const hasComplexChildren = children.some(c =>
165
+ c.type === 'JSXElement' || c.type === 'JSXFragment' ||
166
+ (c.type === 'JSXText' && c.value?.includes('\n'))
167
+ )
168
+
169
+ if (hasComplexChildren) {
170
+ result += '\n'
171
+ this.indent++
172
+ for (const child of children) {
173
+ const childContent = this.generateJSXChild(child)
174
+ if (childContent.trim()) {
175
+ result += this.getIndent() + childContent + '\n'
176
+ }
177
+ }
178
+ this.indent--
179
+ result += this.getIndent() + `</${tagName}>`
180
+ } else {
181
+ for (const child of children) {
182
+ result += this.generateJSXChild(child)
183
+ }
184
+ result += `</${tagName}>`
185
+ }
186
+
187
+ return result
188
+ }
189
+
190
+ generateJSXFragment(node) {
191
+ const children = node.children || []
192
+
193
+ if (children.length === 0) {
194
+ return '<></>'
195
+ }
196
+
197
+ let result = '<>\n'
198
+ this.indent++
199
+
200
+ for (const child of children) {
201
+ const childContent = this.generateJSXChild(child)
202
+ if (childContent.trim()) {
203
+ result += this.getIndent() + childContent + '\n'
204
+ }
205
+ }
206
+
207
+ this.indent--
208
+ result += this.getIndent() + '</>'
209
+
210
+ return result
211
+ }
212
+
213
+ generateJSXChild(child) {
214
+ if (!child) return ''
215
+
216
+ switch (child.type) {
217
+ case 'JSXElement':
218
+ return this.generateJSXElement(child)
219
+ case 'JSXFragment':
220
+ return this.generateJSXFragment(child)
221
+ case 'JSXText':
222
+ return this.generateJSXText(child)
223
+ case 'JSXExpressionContainer':
224
+ case 'JSXExpression':
225
+ return this.generateJSXExpression(child)
226
+ default:
227
+ if (typeof child === 'string') {
228
+ return child.trim()
229
+ }
230
+ return this.generateNode(child)
231
+ }
232
+ }
233
+
234
+ generateJSXText(node) {
235
+ const text = node.value || node.raw || ''
236
+ return text.trim()
237
+ }
238
+
239
+ generateJSXExpression(node) {
240
+ const expression = node.expression || node.value
241
+ if (!expression) return '{}'
242
+
243
+ if (expression.type === 'JSXEmptyExpression') {
244
+ return '{/* */}'
245
+ }
246
+
247
+ return '{' + this.generateNode(expression) + '}'
248
+ }
249
+
250
+ generateJSXAttributes(attributes) {
251
+ if (!attributes || attributes.length === 0) {
252
+ return ''
253
+ }
254
+
255
+ const parts = attributes.map(attr => {
256
+ if (attr.type === 'JSXSpreadAttribute') {
257
+ return this.generateJSXSpreadAttribute(attr)
258
+ }
259
+ return this.generateJSXAttribute(attr)
260
+ })
261
+
262
+ return ' ' + parts.join(' ')
263
+ }
264
+
265
+ generateJSXAttribute(attr) {
266
+ const name = this.translateJSXAttribute(attr.name?.name || attr.name)
267
+ const value = attr.value
268
+
269
+ if (value === null || value === undefined || value === true) {
270
+ return name
271
+ }
272
+
273
+ if (typeof value === 'string') {
274
+ return `${name}="${value}"`
275
+ }
276
+
277
+ if (value.type === 'JSXExpressionContainer' || value.type === 'JSXExpression') {
278
+ return `${name}=${this.generateJSXExpression(value)}`
279
+ }
280
+
281
+ if (value.type === 'Literal') {
282
+ if (typeof value.value === 'string') {
283
+ return `${name}="${value.value}"`
284
+ }
285
+ return `${name}={${this.generateLiteral(value)}}`
286
+ }
287
+
288
+ return `${name}={${this.generateNode(value)}}`
289
+ }
290
+
291
+ generateJSXSpreadAttribute(attr) {
292
+ const expression = attr.argument || attr.expression
293
+ return `{...${this.generateNode(expression)}}`
294
+ }
295
+
296
+ translateJSXTag(tag) {
297
+ if (typeof tag !== 'string') {
298
+ if (tag.type === 'JSXMemberExpression') {
299
+ return this.generateJSXMemberExpression(tag)
300
+ }
301
+ if (tag.type === 'Identifier') {
302
+ return this.translateJSXTag(tag.name)
303
+ }
304
+ return tag
305
+ }
306
+
307
+ const lower = tag.toLowerCase()
308
+
309
+ if (this.componentMap[lower]) {
310
+ return this.componentMap[lower]
311
+ }
312
+
313
+ const translations = {
314
+ 'division': 'div',
315
+ 'paragraphe': 'p',
316
+ 'titre1': 'h1',
317
+ 'titre2': 'h2',
318
+ 'titre3': 'h3',
319
+ 'titre4': 'h4',
320
+ 'titre5': 'h5',
321
+ 'titre6': 'h6',
322
+ 'portee': 'span',
323
+ 'bouton': 'button',
324
+ 'lien': 'a',
325
+ 'image': 'img',
326
+ 'formulaire': 'form',
327
+ 'champ': 'input',
328
+ 'zone texte': 'textarea',
329
+ 'étiquette': 'label',
330
+ 'etiquette': 'label',
331
+ 'sélection': 'select',
332
+ 'option': 'option',
333
+ 'liste': 'ul',
334
+ 'liste ordonnée': 'ol',
335
+ 'élément liste': 'li',
336
+ 'tableau': 'table',
337
+ 'ligne': 'tr',
338
+ 'cellule': 'td',
339
+ 'entête cellule': 'th',
340
+ 'corps tableau': 'tbody',
341
+ 'entête tableau': 'thead',
342
+ 'pied tableau': 'tfoot',
343
+ 'entête': 'header',
344
+ 'piedpage': 'footer',
345
+ 'navigation': 'nav',
346
+ 'section': 'section',
347
+ 'article': 'article',
348
+ 'côté': 'aside',
349
+ 'principal': 'main',
350
+ 'video': 'video',
351
+ 'audio': 'audio',
352
+ 'toile': 'canvas',
353
+ 'cadre': 'iframe'
354
+ }
355
+
356
+ if (translations[lower]) {
357
+ return translations[lower]
358
+ }
359
+
360
+ if (/^[A-Z]/.test(tag)) {
361
+ return tag
362
+ }
363
+
364
+ return tag
365
+ }
366
+
367
+ translateJSXAttribute(attr) {
368
+ const lower = attr.toLowerCase()
369
+
370
+ const translations = {
371
+ 'classe': 'className',
372
+ 'pour': 'htmlFor',
373
+ 'style': 'style',
374
+ 'au clic': 'onClick',
375
+ 'au double clic': 'onDoubleClick',
376
+ 'au changement': 'onChange',
377
+ 'a la soumission': 'onSubmit',
378
+ 'au survol': 'onMouseOver',
379
+ 'au survol fin': 'onMouseOut',
380
+ 'souris entrée': 'onMouseEnter',
381
+ 'souris sortie': 'onMouseLeave',
382
+ 'souris bas': 'onMouseDown',
383
+ 'souris haut': 'onMouseUp',
384
+ 'au focus': 'onFocus',
385
+ 'au focus perdu': 'onBlur',
386
+ 'touche bas': 'onKeyDown',
387
+ 'touche haut': 'onKeyUp',
388
+ 'touche presse': 'onKeyPress',
389
+ 'au chargement': 'onLoad',
390
+ 'erreur': 'onError',
391
+ 'au défilement': 'onScroll',
392
+ 'toucher début': 'onTouchStart',
393
+ 'toucher mouvement': 'onTouchMove',
394
+ 'toucher fin': 'onTouchEnd',
395
+ 'glisser début': 'onDragStart',
396
+ 'glisser': 'onDrag',
397
+ 'glisser fin': 'onDragEnd',
398
+ 'deposer': 'onDrop',
399
+ 'copier': 'onCopy',
400
+ 'coller': 'onPaste',
401
+ 'valeur': 'value',
402
+ 'valeur défaut': 'defaultValue',
403
+ 'coche': 'checked',
404
+ 'coché défaut': 'defaultChecked',
405
+ 'desactive': 'disabled',
406
+ 'lecture seule': 'readOnly',
407
+ 'requis': 'required',
408
+ 'placeholder': 'placeholder',
409
+ 'indicateur': 'placeholder',
410
+ 'type': 'type',
411
+ 'nom': 'name',
412
+ 'identifiant': 'id',
413
+ 'référence': 'ref',
414
+ 'clé': 'key',
415
+ 'enfants': 'children',
416
+ 'html interne': 'dangerouslySetInnerHTML',
417
+ 'tabindex': 'tabIndex',
418
+ 'largeur': 'width',
419
+ 'hauteur': 'height',
420
+ 'source': 'src',
421
+ 'alternatif': 'alt',
422
+ 'adresse': 'href',
423
+ 'cible': 'target',
424
+ 'role': 'role',
425
+ 'aria étiquette': 'aria-label',
426
+ 'aria etiquette': 'aria-label',
427
+ 'aria decrit par': 'aria-describedby',
428
+ 'aria cache': 'aria-hidden',
429
+ 'aria étendu': 'aria-expanded',
430
+ 'aria selectionne': 'aria-selected',
431
+ 'aria desactive': 'aria-disabled'
432
+ }
433
+
434
+ if (translations[lower]) {
435
+ return translations[lower]
436
+ }
437
+
438
+ if (attr.startsWith('on') || attr.startsWith('aria-') || attr.startsWith('data-')) {
439
+ return attr
440
+ }
441
+
442
+ return attr
443
+ }
444
+
445
+ generateJSXMemberExpression(node) {
446
+ const object = node.object?.name || this.generateNode(node.object)
447
+ const property = node.property?.name || this.generateNode(node.property)
448
+ return `${object}.${property}`
449
+ }
450
+
451
+ generateComponent(node) {
452
+ const name = node.name || node.id?.name || 'Component'
453
+ const isArrow = node.arrow !== false
454
+ const props = node.props || node.params || []
455
+ const propsStr = this.generateComponentProps(props)
456
+
457
+ if (isArrow) {
458
+ this.writeLine(`const ${name} = (${propsStr}) => {`)
459
+ } else {
460
+ this.writeLine(`function ${name}(${propsStr}) {`)
461
+ }
462
+
463
+ this.indent++
464
+
465
+ if (node.hooks) {
466
+ for (const hook of node.hooks) {
467
+ this.generateHookCall(hook)
468
+ }
469
+ this.writeLine('')
470
+ }
471
+
472
+ if (node.handlers) {
473
+ for (const handler of node.handlers) {
474
+ this.generateNode(handler)
475
+ }
476
+ this.writeLine('')
477
+ }
478
+
479
+ if (node.return || node.render) {
480
+ const jsx = this.generateNode(node.return || node.render)
481
+ if (jsx.includes('\n')) {
482
+ this.writeLine('return (')
483
+ this.indent++
484
+ this.writeLine(jsx)
485
+ this.indent--
486
+ this.writeLine(');')
487
+ } else {
488
+ this.writeLine(`return ${jsx};`)
489
+ }
490
+ } else if (node.body) {
491
+ const body = node.body?.body || node.body || []
492
+ for (const stmt of body) {
493
+ this.generateNode(stmt)
494
+ }
495
+ }
496
+
497
+ this.indent--
498
+ this.writeLine('};')
499
+ this.writeLine('')
500
+ }
501
+
502
+ generateComponentProps(props) {
503
+ if (!props || props.length === 0) {
504
+ return ''
505
+ }
506
+
507
+ if (props.length === 1 && props[0].destructure) {
508
+ const destructured = props[0].properties || props[0].names || []
509
+ const propsList = destructured.map(p => {
510
+ if (typeof p === 'string') return p
511
+ const name = p.name || p.key
512
+ if (p.default !== undefined) {
513
+ return `${name} = ${this.generateNode(p.default)}`
514
+ }
515
+ return name
516
+ })
517
+ return `{ ${propsList.join(', ')} }`
518
+ }
519
+
520
+ return props.map(p => p.name || p).join(', ')
521
+ }
522
+
523
+ generateHookCall(node) {
524
+ const hookName = this.translate(node.hook || node.name)
525
+ const args = (node.arguments || node.args || []).map(a => this.generateNode(a))
526
+
527
+ if (node.destructure) {
528
+ const [first, second] = node.destructure
529
+ if (second) {
530
+ this.writeLine(`const [${first}, ${second}] = ${hookName}(${args.join(', ')});`)
531
+ } else {
532
+ this.writeLine(`const ${first} = ${hookName}(${args.join(', ')});`)
533
+ }
534
+ } else if (node.result) {
535
+ this.writeLine(`const ${node.result} = ${hookName}(${args.join(', ')});`)
536
+ } else {
537
+ this.writeLine(`${hookName}(${args.join(', ')});`)
538
+ }
539
+ }
540
+
541
+ generateArrowFunction(node) {
542
+ if (node.jsx) {
543
+ const params = (node.params || []).map(p => this.generateNode(p)).join(', ')
544
+ const paramsStr = node.params?.length === 1 ? params : `(${params})`
545
+ const jsx = this.generateNode(node.body)
546
+
547
+ if (jsx.includes('\n')) {
548
+ return `${paramsStr} => (\n${this.getIndent()} ${jsx.split('\n').join('\n' + this.getIndent() + ' ')}\n${this.getIndent()})`
549
+ }
550
+ return `${paramsStr} => ${jsx}`
551
+ }
552
+
553
+ return super.generateArrowFunction(node)
554
+ }
555
+
556
+ generateImportDeclaration(node) {
557
+ const source = this.generateLiteral(node.source)
558
+ const specifiers = node.specifiers || []
559
+
560
+ if (specifiers.length === 0) {
561
+ this.writeLine(`import ${source};`)
562
+ return
563
+ }
564
+
565
+ const defaultSpec = specifiers.find(s => s.type === 'ImportDefaultSpecifier')
566
+ const namespaceSpec = specifiers.find(s => s.type === 'ImportNamespaceSpecifier')
567
+ const namedSpecs = specifiers.filter(s => s.type === 'ImportSpecifier')
568
+
569
+ let importParts = []
570
+
571
+ if (defaultSpec) {
572
+ const name = defaultSpec.local?.name || defaultSpec.local
573
+ importParts.push(this.translate(name))
574
+ }
575
+
576
+ if (namespaceSpec) {
577
+ importParts.push(`* as ${this.generateNode(namespaceSpec.local)}`)
578
+ }
579
+
580
+ if (namedSpecs.length > 0) {
581
+ const named = namedSpecs.map(s => {
582
+ const imported = s.imported?.name || s.imported
583
+ const local = s.local?.name || s.local
584
+ const translatedImported = this.translate(imported)
585
+ const translatedLocal = this.translate(local)
586
+ return translatedImported === translatedLocal
587
+ ? translatedImported
588
+ : `${translatedImported} as ${translatedLocal}`
589
+ }).join(', ')
590
+ importParts.push(`{ ${named} }`)
591
+ }
592
+
593
+ this.writeLine(`import ${importParts.join(', ')} from ${source};`)
594
+ }
595
+ }
596
+
597
+ module.exports = {
598
+ ReactGenerator
599
+ }