ether-code 0.1.6 → 0.1.8

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 (45) hide show
  1. package/cli/compiler.js +9 -53
  2. package/cli/ether.js +1 -1
  3. package/generators/css-generator.js +42 -55
  4. package/generators/graphql-generator.js +19 -22
  5. package/generators/html-generator.js +51 -220
  6. package/generators/js-generator.js +76 -157
  7. package/generators/node-generator.js +49 -93
  8. package/generators/php-generator.js +46 -68
  9. package/generators/python-generator.js +35 -54
  10. package/generators/react-generator.js +37 -47
  11. package/generators/ruby-generator.js +59 -119
  12. package/generators/sql-generator.js +42 -63
  13. package/generators/ts-generator.js +59 -133
  14. package/i18n/i18n-css.json +147 -147
  15. package/i18n/i18n-graphql.json +6 -6
  16. package/i18n/i18n-html.json +135 -135
  17. package/i18n/i18n-js.json +107 -107
  18. package/i18n/i18n-node.json +14 -14
  19. package/i18n/i18n-php.json +177 -177
  20. package/i18n/i18n-python.json +16 -16
  21. package/i18n/i18n-react.json +97 -97
  22. package/i18n/i18n-ruby.json +22 -22
  23. package/i18n/i18n-sql.json +153 -153
  24. package/i18n/i18n-ts.json +10 -10
  25. package/lexer/ether-lexer.js +175 -34
  26. package/lexer/tokens.js +6 -6
  27. package/package.json +1 -1
  28. package/parsers/ast-css.js +0 -545
  29. package/parsers/ast-graphql.js +0 -424
  30. package/parsers/ast-html.js +0 -886
  31. package/parsers/ast-js.js +0 -750
  32. package/parsers/ast-node.js +0 -2440
  33. package/parsers/ast-php.js +0 -957
  34. package/parsers/ast-react.js +0 -580
  35. package/parsers/ast-ruby.js +0 -895
  36. package/parsers/ast-ts.js +0 -1352
  37. package/parsers/css-parser.js +0 -1981
  38. package/parsers/graphql-parser.js +0 -2011
  39. package/parsers/html-parser.js +0 -1182
  40. package/parsers/js-parser.js +0 -2564
  41. package/parsers/node-parser.js +0 -2644
  42. package/parsers/php-parser.js +0 -3037
  43. package/parsers/react-parser.js +0 -1035
  44. package/parsers/ruby-parser.js +0 -2680
  45. package/parsers/ts-parser.js +0 -3881
@@ -1,1035 +0,0 @@
1
- const fs = require('fs')
2
- const { JSLexer, JSParser, JSCodeGenerator } = require('./js-parser')
3
- const AST = require('./ast-react')
4
-
5
- const SELF_CLOSING_TAGS = new Set([
6
- 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
7
- 'link', 'meta', 'param', 'source', 'track', 'wbr'
8
- ])
9
-
10
- const REACT_HOOKS = {
11
- 'useState': 'useState', 'utiliser etat': 'useState', 'utiliser état': 'useState', 'usar estado': 'useState',
12
- 'использовать состояние': 'useState', '使用状态': 'useState', 'ステート使用': 'useState',
13
-
14
- 'useEffect': 'useEffect', 'utiliser effet': 'useEffect', 'usar efecto': 'useEffect',
15
- 'использовать эффект': 'useEffect', '使用副作用': 'useEffect', 'エフェクト使用': 'useEffect',
16
-
17
- 'useContext': 'useContext', 'utiliser contexte': 'useContext', 'usar contexto': 'useContext',
18
- 'использовать контекст': 'useContext', '使用上下文': 'useContext', 'コンテキスト使用': 'useContext',
19
-
20
- 'useReducer': 'useReducer', 'utiliser reducteur': 'useReducer', 'utiliser réducteur': 'useReducer', 'usar reductor': 'useReducer',
21
- 'использовать редьюсер': 'useReducer', '使用归约器': 'useReducer', 'リデューサー使用': 'useReducer',
22
-
23
- 'useCallback': 'useCallback', 'utiliser rappel': 'useCallback', 'usar callback': 'useCallback',
24
- 'использовать колбэк': 'useCallback', '使用回调': 'useCallback', 'コールバック使用': 'useCallback',
25
-
26
- 'useMemo': 'useMemo', 'utiliser memo': 'useMemo', 'utiliser mémo': 'useMemo', 'usar memo': 'useMemo',
27
- 'использовать мемо': 'useMemo', '使用记忆': 'useMemo', 'メモ使用': 'useMemo',
28
-
29
- 'useRef': 'useRef', 'utiliser reference': 'useRef', 'utiliser référence': 'useRef', 'usar referencia': 'useRef',
30
- 'использовать ссылку': 'useRef', '使用引用': 'useRef', 'リファレンス使用': 'useRef',
31
-
32
- 'useImperativeHandle': 'useImperativeHandle', 'utiliser handle imperatif': 'useImperativeHandle', 'utiliser handle impératif': 'useImperativeHandle',
33
- 'usar handle imperativo': 'useImperativeHandle', 'использовать императивный хэндл': 'useImperativeHandle',
34
- '使用命令式句柄': 'useImperativeHandle', '命令型ハンドル使用': 'useImperativeHandle',
35
-
36
- 'useLayoutEffect': 'useLayoutEffect', 'utiliser effet mise en page': 'useLayoutEffect',
37
- 'usar efecto diseño': 'useLayoutEffect', 'usar efecto diseno': 'useLayoutEffect',
38
- 'использовать эффект макета': 'useLayoutEffect', '使用布局副作用': 'useLayoutEffect', 'レイアウトエフェクト使用': 'useLayoutEffect',
39
-
40
- 'useInsertionEffect': 'useInsertionEffect', 'utiliser effet insertion': 'useInsertionEffect',
41
- 'usar efecto insercion': 'useInsertionEffect', 'usar efecto inserción': 'useInsertionEffect',
42
- 'использовать эффект вставки': 'useInsertionEffect', '使用插入副作用': 'useInsertionEffect', '挿入エフェクト使用': 'useInsertionEffect',
43
-
44
- 'useDebugValue': 'useDebugValue', 'utiliser valeur debug': 'useDebugValue',
45
- 'usar valor debug': 'useDebugValue', 'использовать значение отладки': 'useDebugValue',
46
- '使用调试值': 'useDebugValue', 'デバッグ値使用': 'useDebugValue',
47
-
48
- 'useTransition': 'useTransition', 'utiliser transition': 'useTransition',
49
- 'usar transicion': 'useTransition', 'usar transición': 'useTransition',
50
- 'использовать переход': 'useTransition', '使用过渡': 'useTransition', 'トランジション使用': 'useTransition',
51
-
52
- 'useDeferredValue': 'useDeferredValue', 'utiliser valeur differee': 'useDeferredValue', 'utiliser valeur différée': 'useDeferredValue',
53
- 'usar valor diferido': 'useDeferredValue', 'использовать отложенное значение': 'useDeferredValue',
54
- '使用延迟值': 'useDeferredValue', '遅延値使用': 'useDeferredValue',
55
-
56
- 'useId': 'useId', 'utiliser identifiant': 'useId', 'usar id': 'useId', 'usar identificador': 'useId',
57
- 'использовать идентификатор': 'useId', '使用ID': 'useId', 'ID使用': 'useId',
58
-
59
- 'useSyncExternalStore': 'useSyncExternalStore', 'utiliser etat synchronise': 'useSyncExternalStore', 'utiliser état synchronisé': 'useSyncExternalStore',
60
- 'usar almacen externo sincronizado': 'useSyncExternalStore', 'usar almacén externo sincronizado': 'useSyncExternalStore',
61
- 'использовать синхронизированное внешнее хранилище': 'useSyncExternalStore',
62
- '使用同步外部存储': 'useSyncExternalStore', '外部ストア同期使用': 'useSyncExternalStore',
63
-
64
- 'useActionState': 'useActionState', 'utiliser etat action': 'useActionState', 'utiliser état action': 'useActionState',
65
- 'usar estado accion': 'useActionState', 'usar estado acción': 'useActionState',
66
- 'использовать состояние действия': 'useActionState', '使用动作状态': 'useActionState', 'アクション状態使用': 'useActionState',
67
-
68
- 'useFormStatus': 'useFormStatus', 'utiliser statut formulaire': 'useFormStatus',
69
- 'usar estado formulario': 'useFormStatus', 'использовать статус формы': 'useFormStatus',
70
- '使用表单状态': 'useFormStatus', 'フォームステータス使用': 'useFormStatus',
71
-
72
- 'useOptimistic': 'useOptimistic', 'utiliser optimiste': 'useOptimistic',
73
- 'usar optimista': 'useOptimistic', 'использовать оптимистичный': 'useOptimistic',
74
- '使用乐观': 'useOptimistic', 'オプティミスティック使用': 'useOptimistic',
75
-
76
- 'use': 'use', 'utiliser': 'use', 'usar': 'use', 'использовать': 'use', '使用': 'use', '使う': 'use'
77
- }
78
-
79
- const REACT_COMPONENTS = {
80
- 'Fragment': 'Fragment', 'fragment': 'Fragment', 'fragmento': 'Fragment',
81
- 'фрагмент': 'Fragment', '片段': 'Fragment', 'フラグメント': 'Fragment',
82
-
83
- 'Suspense': 'Suspense', 'suspense': 'Suspense', 'suspenso': 'Suspense',
84
- 'ожидание': 'Suspense', '悬念': 'Suspense', 'サスペンス': 'Suspense',
85
-
86
- 'StrictMode': 'StrictMode', 'strict mode': 'StrictMode', 'mode strict': 'StrictMode', 'modo estricto': 'StrictMode',
87
- 'строгий режим': 'StrictMode', '严格模式': 'StrictMode', 'ストリクトモード': 'StrictMode',
88
-
89
- 'Profiler': 'Profiler', 'profileur': 'Profiler', 'perfilador': 'Profiler',
90
- 'профайлер': 'Profiler', '性能分析器': 'Profiler', 'プロファイラー': 'Profiler',
91
-
92
- 'ErrorBoundary': 'ErrorBoundary', 'limite erreur': 'ErrorBoundary', 'límite error': 'ErrorBoundary', 'limite error': 'ErrorBoundary',
93
- 'граница ошибки': 'ErrorBoundary', '错误边界': 'ErrorBoundary', 'エラーバウンダリ': 'ErrorBoundary',
94
-
95
- 'Provider': 'Provider', 'fournisseur': 'Provider', 'proveedor': 'Provider',
96
- 'провайдер': 'Provider', '提供者': 'Provider', 'プロバイダー': 'Provider',
97
-
98
- 'Consumer': 'Consumer', 'consommateur': 'Consumer', 'consumidor': 'Consumer',
99
- 'потребитель': 'Consumer', '消费者': 'Consumer', 'コンシューマー': 'Consumer',
100
-
101
- 'Portal': 'Portal', 'portail': 'Portal', 'portal': 'Portal',
102
- 'портал': 'Portal', '传送门': 'Portal', 'ポータル': 'Portal',
103
-
104
- 'Lazy': 'Lazy', 'lazy': 'Lazy', 'paresseux': 'Lazy', 'perezoso': 'Lazy',
105
- 'ленивый': 'Lazy', '懒加载': 'Lazy', 'レイジー': 'Lazy',
106
-
107
- 'Memo': 'Memo', 'memo': 'Memo', 'мемо': 'Memo', '记忆': 'Memo', 'メモ': 'Memo',
108
-
109
- 'ForwardRef': 'ForwardRef', 'reference avancee': 'ForwardRef', 'référence avancée': 'ForwardRef',
110
- 'referencia adelantada': 'ForwardRef', 'перенаправленная ссылка': 'ForwardRef',
111
- '转发引用': 'ForwardRef', 'フォワードリファレンス': 'ForwardRef'
112
- }
113
-
114
- class ReactLexer extends JSLexer {
115
- constructor(source, i18n) {
116
- super(source, i18n)
117
- this.inJSX = false
118
- }
119
-
120
- tokenize() {
121
- while (this.pos < this.source.length) {
122
- this.skipWhitespace()
123
- if (this.pos >= this.source.length) break
124
-
125
- const loc = { line: this.line, column: this.column }
126
- const char = this.peek()
127
-
128
- if (char === '<') {
129
- if (this.peek(1) === '/' || this.peek(1) === '>' || /[a-zA-Z_]/.test(this.peek(1))) {
130
- this.tokens.push(this.readJSXToken())
131
- continue
132
- }
133
- }
134
-
135
- if (char === '"' || char === "'") {
136
- this.tokens.push(this.readString(char))
137
- continue
138
- }
139
-
140
- if (char === '`') {
141
- this.tokens.push(this.readTemplateLiteral())
142
- continue
143
- }
144
-
145
- if (/[0-9]/.test(char) || (char === '.' && /[0-9]/.test(this.peek(1)))) {
146
- this.tokens.push(this.readNumber())
147
- continue
148
- }
149
-
150
- if (/[a-zA-ZÀ-ÿА-яぁ-ゟァ-ヿ一-龯_$]/.test(char)) {
151
- const { word, loc: wordLoc } = this.readWord()
152
- const tokenType = this.classifyReactWord(word)
153
- this.tokens.push({ type: tokenType, value: word, loc: wordLoc })
154
- continue
155
- }
156
-
157
- if (char === '{') {
158
- this.tokens.push({ type: 'LBRACE', loc })
159
- this.advance()
160
- continue
161
- }
162
- if (char === '}') {
163
- this.tokens.push({ type: 'RBRACE', loc })
164
- this.advance()
165
- continue
166
- }
167
- if (char === '(') {
168
- this.tokens.push({ type: 'LPAREN', loc })
169
- this.advance()
170
- continue
171
- }
172
- if (char === ')') {
173
- this.tokens.push({ type: 'RPAREN', loc })
174
- this.advance()
175
- continue
176
- }
177
- if (char === '[') {
178
- this.tokens.push({ type: 'LBRACKET', loc })
179
- this.advance()
180
- continue
181
- }
182
- if (char === ']') {
183
- this.tokens.push({ type: 'RBRACKET', loc })
184
- this.advance()
185
- continue
186
- }
187
-
188
- const threeChar = this.source.slice(this.pos, this.pos + 3)
189
- const twoChar = this.source.slice(this.pos, this.pos + 2)
190
-
191
- if (threeChar === '...') {
192
- this.tokens.push({ type: 'SPREAD', value: '...', loc })
193
- this.advance(); this.advance(); this.advance()
194
- continue
195
- }
196
- if (twoChar === '=>') {
197
- this.tokens.push({ type: 'ARROW', value: '=>', loc })
198
- this.advance(); this.advance()
199
- continue
200
- }
201
- if (twoChar === '?.') {
202
- this.tokens.push({ type: 'OPTIONAL_CHAIN', value: '?.', loc })
203
- this.advance(); this.advance()
204
- continue
205
- }
206
- if (twoChar === '&&') {
207
- this.tokens.push({ type: 'AND', value: '&&', loc })
208
- this.advance(); this.advance()
209
- continue
210
- }
211
- if (twoChar === '||') {
212
- this.tokens.push({ type: 'OR', value: '||', loc })
213
- this.advance(); this.advance()
214
- continue
215
- }
216
- if (twoChar === '??') {
217
- this.tokens.push({ type: 'NULLISH', value: '??', loc })
218
- this.advance(); this.advance()
219
- continue
220
- }
221
- if (twoChar === '===') {
222
- this.tokens.push({ type: 'STRICT_EQUAL', value: '===', loc })
223
- this.advance(); this.advance(); this.advance()
224
- continue
225
- }
226
- if (twoChar === '==') {
227
- this.tokens.push({ type: 'EQUAL', value: '==', loc })
228
- this.advance(); this.advance()
229
- continue
230
- }
231
- if (twoChar === '!==') {
232
- this.tokens.push({ type: 'STRICT_NOT_EQUAL', value: '!==', loc })
233
- this.advance(); this.advance(); this.advance()
234
- continue
235
- }
236
- if (twoChar === '!=') {
237
- this.tokens.push({ type: 'NOT_EQUAL', value: '!=', loc })
238
- this.advance(); this.advance()
239
- continue
240
- }
241
-
242
- if (char === '=') {
243
- this.tokens.push({ type: 'ASSIGN', value: '=', loc })
244
- this.advance()
245
- continue
246
- }
247
- if (char === '+') {
248
- this.tokens.push({ type: 'PLUS', value: '+', loc })
249
- this.advance()
250
- continue
251
- }
252
- if (char === '-') {
253
- this.tokens.push({ type: 'MINUS', value: '-', loc })
254
- this.advance()
255
- continue
256
- }
257
- if (char === '*') {
258
- this.tokens.push({ type: 'STAR', value: '*', loc })
259
- this.advance()
260
- continue
261
- }
262
- if (char === '/') {
263
- this.tokens.push({ type: 'SLASH', value: '/', loc })
264
- this.advance()
265
- continue
266
- }
267
- if (char === '%') {
268
- this.tokens.push({ type: 'PERCENT', value: '%', loc })
269
- this.advance()
270
- continue
271
- }
272
- if (char === '<') {
273
- this.tokens.push({ type: 'LT', value: '<', loc })
274
- this.advance()
275
- continue
276
- }
277
- if (char === '>') {
278
- this.tokens.push({ type: 'GT', value: '>', loc })
279
- this.advance()
280
- continue
281
- }
282
- if (char === '!') {
283
- this.tokens.push({ type: 'NOT', value: '!', loc })
284
- this.advance()
285
- continue
286
- }
287
- if (char === '?') {
288
- this.tokens.push({ type: 'QUESTION', value: '?', loc })
289
- this.advance()
290
- continue
291
- }
292
- if (char === ':') {
293
- this.tokens.push({ type: 'COLON', value: ':', loc })
294
- this.advance()
295
- continue
296
- }
297
- if (char === '.') {
298
- this.tokens.push({ type: 'DOT', value: '.', loc })
299
- this.advance()
300
- continue
301
- }
302
- if (char === ',') {
303
- this.tokens.push({ type: 'COMMA', value: ',', loc })
304
- this.advance()
305
- continue
306
- }
307
- if (char === ';') {
308
- this.tokens.push({ type: 'SEMICOLON', value: ';', loc })
309
- this.advance()
310
- continue
311
- }
312
-
313
- this.advance()
314
- }
315
-
316
- this.tokens.push({ type: 'EOF', loc: { line: this.line, column: this.column } })
317
- return this.tokens
318
- }
319
-
320
- readJSXToken() {
321
- const loc = { line: this.line, column: this.column }
322
- this.advance()
323
-
324
- if (this.peek() === '/') {
325
- this.advance()
326
- if (this.peek() === '>') {
327
- this.advance()
328
- return { type: 'JSX_FRAGMENT_CLOSE', loc }
329
- }
330
- const name = this.readJSXName()
331
- this.skipWhitespace()
332
- if (this.peek() === '>') this.advance()
333
- return { type: 'JSX_CLOSE', name, loc }
334
- }
335
-
336
- if (this.peek() === '>') {
337
- this.advance()
338
- return { type: 'JSX_FRAGMENT_OPEN', loc }
339
- }
340
-
341
- const name = this.readJSXName()
342
- const attributes = []
343
- let selfClosing = false
344
-
345
- while (this.pos < this.source.length) {
346
- this.skipWhitespace()
347
- const char = this.peek()
348
-
349
- if (char === '>') {
350
- this.advance()
351
- break
352
- }
353
-
354
- if (char === '/' && this.peek(1) === '>') {
355
- selfClosing = true
356
- this.advance()
357
- this.advance()
358
- break
359
- }
360
-
361
- if (char === '{') {
362
- this.advance()
363
- if (this.peek() === '.') {
364
- this.advance()
365
- this.advance()
366
- this.advance()
367
- let expr = ''
368
- let braceCount = 1
369
- while (braceCount > 0 && this.pos < this.source.length) {
370
- const c = this.peek()
371
- if (c === '{') braceCount++
372
- if (c === '}') braceCount--
373
- if (braceCount > 0) expr += this.advance()
374
- else this.advance()
375
- }
376
- attributes.push({ type: 'spread', value: expr.trim() })
377
- }
378
- continue
379
- }
380
-
381
- if (/[a-zA-Z_\-]/.test(char)) {
382
- const attrName = this.readJSXAttributeName()
383
- let attrValue = true
384
-
385
- this.skipWhitespace()
386
- if (this.peek() === '=') {
387
- this.advance()
388
- this.skipWhitespace()
389
-
390
- if (this.peek() === '"' || this.peek() === "'") {
391
- const quote = this.advance()
392
- attrValue = ''
393
- while (this.pos < this.source.length && this.peek() !== quote) {
394
- attrValue += this.advance()
395
- }
396
- if (this.peek() === quote) this.advance()
397
- } else if (this.peek() === '{') {
398
- this.advance()
399
- attrValue = { type: 'expression', value: '' }
400
- let braceCount = 1
401
- while (braceCount > 0 && this.pos < this.source.length) {
402
- const c = this.peek()
403
- if (c === '{') braceCount++
404
- if (c === '}') braceCount--
405
- if (braceCount > 0) attrValue.value += this.advance()
406
- else this.advance()
407
- }
408
- attrValue.value = attrValue.value.trim()
409
- }
410
- }
411
-
412
- attributes.push({ name: attrName, value: attrValue })
413
- continue
414
- }
415
-
416
- this.advance()
417
- }
418
-
419
- return { type: 'JSX_OPEN', name, attributes, selfClosing, loc }
420
- }
421
-
422
- readJSXName() {
423
- let name = ''
424
- while (this.pos < this.source.length) {
425
- const char = this.peek()
426
- if (/[a-zA-Z0-9_\-:.]/.test(char)) {
427
- name += this.advance()
428
- } else {
429
- break
430
- }
431
- }
432
- return name
433
- }
434
-
435
- readJSXAttributeName() {
436
- let name = ''
437
- while (this.pos < this.source.length) {
438
- const char = this.peek()
439
- if (/[a-zA-Z0-9_\-:]/.test(char)) {
440
- name += this.advance()
441
- } else {
442
- break
443
- }
444
- }
445
- return name
446
- }
447
-
448
- classifyReactWord(word) {
449
- const lowerWord = word.toLowerCase()
450
-
451
- if (REACT_HOOKS[lowerWord] || REACT_HOOKS[word]) {
452
- return 'REACT_HOOK'
453
- }
454
-
455
- if (REACT_COMPONENTS[lowerWord] || REACT_COMPONENTS[word]) {
456
- return 'REACT_COMPONENT'
457
- }
458
-
459
- return this.classifyWord(word)
460
- }
461
- }
462
-
463
- class ReactParser extends JSParser {
464
- constructor(i18nPath = null, jsI18nPath = null) {
465
- super(jsI18nPath)
466
- this.reactI18n = {}
467
-
468
- if (i18nPath) {
469
- this.loadReactI18n(i18nPath)
470
- }
471
- }
472
-
473
- loadReactI18n(filePath) {
474
- try {
475
- const content = fs.readFileSync(filePath, 'utf-8')
476
- this.reactI18n = JSON.parse(content)
477
- } catch (e) {
478
- console.error(`Erreur chargement i18n React: ${e.message}`)
479
- this.reactI18n = {}
480
- }
481
- }
482
-
483
- setReactI18n(i18nData) {
484
- this.reactI18n = i18nData
485
- }
486
-
487
- parse(source) {
488
- const lexer = new ReactLexer(source, this.i18n)
489
- this.tokens = lexer.tokenize()
490
- this.pos = 0
491
- return this.parseProgram()
492
- }
493
-
494
- parsePrimaryExpression() {
495
- const token = this.peek()
496
-
497
- if (token.type === 'JSX_OPEN' || token.type === 'JSX_FRAGMENT_OPEN') {
498
- return this.parseJSXElement()
499
- }
500
-
501
- if (token.type === 'REACT_HOOK') {
502
- return this.parseHookCall()
503
- }
504
-
505
- return super.parsePrimaryExpression()
506
- }
507
-
508
- parseJSXElement() {
509
- const token = this.peek()
510
-
511
- if (token.type === 'JSX_FRAGMENT_OPEN') {
512
- return this.parseJSXFragment()
513
- }
514
-
515
- const openingToken = this.advance()
516
- const name = this.parseJSXElementName(openingToken.name)
517
- const attributes = this.parseJSXAttributes(openingToken.attributes)
518
-
519
- const openingElement = new AST.JSXOpeningElement(
520
- name,
521
- attributes,
522
- openingToken.selfClosing,
523
- openingToken.loc
524
- )
525
-
526
- if (openingToken.selfClosing) {
527
- return new AST.JSXElement(openingElement, null, [], openingToken.loc)
528
- }
529
-
530
- const children = this.parseJSXChildren(openingToken.name)
531
-
532
- let closingElement = null
533
- if (this.match('JSX_CLOSE')) {
534
- const closeToken = this.advance()
535
- closingElement = new AST.JSXClosingElement(
536
- this.parseJSXElementName(closeToken.name),
537
- closeToken.loc
538
- )
539
- }
540
-
541
- return new AST.JSXElement(openingElement, closingElement, children, openingToken.loc)
542
- }
543
-
544
- parseJSXFragment() {
545
- const openToken = this.advance()
546
- const openingFragment = new AST.JSXOpeningFragment(openToken.loc)
547
-
548
- const children = this.parseJSXChildren(null)
549
-
550
- let closingFragment = null
551
- if (this.match('JSX_FRAGMENT_CLOSE')) {
552
- const closeToken = this.advance()
553
- closingFragment = new AST.JSXClosingFragment(closeToken.loc)
554
- }
555
-
556
- return new AST.JSXFragment(openingFragment, closingFragment, children, openToken.loc)
557
- }
558
-
559
- parseJSXElementName(name) {
560
- if (!name) return new AST.JSXIdentifier('', null)
561
-
562
- if (name.includes(':')) {
563
- const [ns, local] = name.split(':')
564
- return new AST.JSXNamespacedName(
565
- new AST.JSXIdentifier(ns, null),
566
- new AST.JSXIdentifier(local, null),
567
- null
568
- )
569
- }
570
-
571
- if (name.includes('.')) {
572
- const parts = name.split('.')
573
- let result = new AST.JSXIdentifier(parts[0], null)
574
- for (let i = 1; i < parts.length; i++) {
575
- result = new AST.JSXMemberExpression(
576
- result,
577
- new AST.JSXIdentifier(parts[i], null),
578
- null
579
- )
580
- }
581
- return result
582
- }
583
-
584
- return new AST.JSXIdentifier(name, null)
585
- }
586
-
587
- parseJSXAttributes(attrs) {
588
- const attributes = []
589
-
590
- for (const attr of attrs) {
591
- if (attr.type === 'spread') {
592
- const parser = new ReactParser()
593
- parser.setI18n(this.i18n)
594
- parser.setReactI18n(this.reactI18n)
595
- const ast = parser.parse(attr.value)
596
- const expr = ast.body[0]?.expression || new AST.Identifier(attr.value, null)
597
- attributes.push(new AST.JSXSpreadAttribute(expr, null))
598
- } else {
599
- const attrName = this.parseJSXAttributeName(attr.name)
600
- let attrValue = null
601
-
602
- if (attr.value !== true) {
603
- if (typeof attr.value === 'string') {
604
- attrValue = new AST.StringLiteral(attr.value, `"${attr.value}"`, null)
605
- } else if (attr.value?.type === 'expression') {
606
- const parser = new ReactParser()
607
- parser.setI18n(this.i18n)
608
- parser.setReactI18n(this.reactI18n)
609
- const ast = parser.parse(attr.value.value)
610
- const expr = ast.body[0]?.expression || new AST.Identifier(attr.value.value, null)
611
- attrValue = new AST.JSXExpressionContainer(expr, null)
612
- }
613
- }
614
-
615
- attributes.push(new AST.JSXAttribute(attrName, attrValue, null))
616
- }
617
- }
618
-
619
- return attributes
620
- }
621
-
622
- parseJSXAttributeName(name) {
623
- if (name.includes(':')) {
624
- const [ns, local] = name.split(':')
625
- return new AST.JSXNamespacedName(
626
- new AST.JSXIdentifier(ns, null),
627
- new AST.JSXIdentifier(local, null),
628
- null
629
- )
630
- }
631
- return new AST.JSXIdentifier(name, null)
632
- }
633
-
634
- parseJSXChildren(parentName) {
635
- const children = []
636
-
637
- while (this.pos < this.tokens.length) {
638
- const token = this.peek()
639
-
640
- if (token.type === 'JSX_CLOSE') {
641
- if (token.name === parentName || parentName === null) break
642
- }
643
-
644
- if (token.type === 'JSX_FRAGMENT_CLOSE') {
645
- if (parentName === null) break
646
- }
647
-
648
- if (token.type === 'EOF') break
649
-
650
- if (token.type === 'JSX_OPEN' || token.type === 'JSX_FRAGMENT_OPEN') {
651
- children.push(this.parseJSXElement())
652
- continue
653
- }
654
-
655
- if (token.type === 'LBRACE') {
656
- children.push(this.parseJSXExpressionContainer())
657
- continue
658
- }
659
-
660
- if (token.type === 'STRING') {
661
- const strToken = this.advance()
662
- children.push(new AST.JSXText(strToken.value, strToken.value, strToken.loc))
663
- continue
664
- }
665
-
666
- if (token.type === 'IDENTIFIER' || token.type === 'KEYWORD') {
667
- const textToken = this.advance()
668
- children.push(new AST.JSXText(textToken.value, textToken.value, textToken.loc))
669
- continue
670
- }
671
-
672
- this.advance()
673
- }
674
-
675
- return children
676
- }
677
-
678
- parseJSXExpressionContainer() {
679
- const loc = this.peek().loc
680
- this.expect('LBRACE')
681
-
682
- if (this.match('RBRACE')) {
683
- this.advance()
684
- return new AST.JSXExpressionContainer(new AST.JSXEmptyExpression(loc), loc)
685
- }
686
-
687
- if (this.match('SPREAD')) {
688
- this.advance()
689
- const expr = this.parseAssignmentExpression()
690
- this.expect('RBRACE')
691
- return new AST.JSXSpreadChild(expr, loc)
692
- }
693
-
694
- const expression = this.parseExpression()
695
- this.expect('RBRACE')
696
-
697
- return new AST.JSXExpressionContainer(expression, loc)
698
- }
699
-
700
- parseHookCall() {
701
- const token = this.advance()
702
- const hookName = this.translateHook(token.value)
703
-
704
- this.expect('LPAREN')
705
- const args = []
706
-
707
- while (!this.match('RPAREN', 'EOF')) {
708
- args.push(this.parseAssignmentExpression())
709
- if (!this.match('RPAREN')) {
710
- this.expect('COMMA')
711
- }
712
- }
713
-
714
- this.expect('RPAREN')
715
-
716
- return new AST.ReactHookCall(hookName, args, token.loc)
717
- }
718
-
719
- translateHook(name) {
720
- const lowerName = name.toLowerCase()
721
- return REACT_HOOKS[lowerName] || REACT_HOOKS[name] || name
722
- }
723
-
724
- translateComponent(name) {
725
- const lowerName = name.toLowerCase()
726
- return REACT_COMPONENTS[lowerName] || REACT_COMPONENTS[name] || name
727
- }
728
- }
729
-
730
- class ReactCodeGenerator extends JSCodeGenerator {
731
- constructor(options = {}) {
732
- super(options)
733
- }
734
-
735
- generate(ast) {
736
- if (!ast) return ''
737
-
738
- const method = `generate${ast.type}`
739
- if (this[method]) {
740
- return this[method](ast)
741
- }
742
-
743
- return super.generate(ast)
744
- }
745
-
746
- generateJSXElement(node) {
747
- let result = this.generate(node.openingElement)
748
-
749
- if (!node.openingElement.selfClosing) {
750
- for (const child of node.children) {
751
- result += this.generate(child)
752
- }
753
- if (node.closingElement) {
754
- result += this.generate(node.closingElement)
755
- }
756
- }
757
-
758
- return result
759
- }
760
-
761
- generateJSXOpeningElement(node) {
762
- let result = '<' + this.generate(node.name)
763
-
764
- for (const attr of node.attributes) {
765
- result += ' ' + this.generate(attr)
766
- }
767
-
768
- if (node.selfClosing) {
769
- result += ' />'
770
- } else {
771
- result += '>'
772
- }
773
-
774
- return result
775
- }
776
-
777
- generateJSXClosingElement(node) {
778
- return '</' + this.generate(node.name) + '>'
779
- }
780
-
781
- generateJSXFragment(node) {
782
- let result = '<>'
783
-
784
- for (const child of node.children) {
785
- result += this.generate(child)
786
- }
787
-
788
- result += '</>'
789
- return result
790
- }
791
-
792
- generateJSXOpeningFragment() {
793
- return '<>'
794
- }
795
-
796
- generateJSXClosingFragment() {
797
- return '</>'
798
- }
799
-
800
- generateJSXIdentifier(node) {
801
- return node.name
802
- }
803
-
804
- generateJSXMemberExpression(node) {
805
- return this.generate(node.object) + '.' + this.generate(node.property)
806
- }
807
-
808
- generateJSXNamespacedName(node) {
809
- return this.generate(node.namespace) + ':' + this.generate(node.name)
810
- }
811
-
812
- generateJSXAttribute(node) {
813
- let result = this.generate(node.name)
814
-
815
- if (node.value !== null) {
816
- result += '='
817
- if (node.value.type === 'StringLiteral') {
818
- result += this.generate(node.value)
819
- } else {
820
- result += this.generate(node.value)
821
- }
822
- }
823
-
824
- return result
825
- }
826
-
827
- generateJSXSpreadAttribute(node) {
828
- return '{...' + this.generate(node.argument) + '}'
829
- }
830
-
831
- generateJSXText(node) {
832
- return node.value
833
- }
834
-
835
- generateJSXExpressionContainer(node) {
836
- return '{' + this.generate(node.expression) + '}'
837
- }
838
-
839
- generateJSXSpreadChild(node) {
840
- return '{...' + this.generate(node.expression) + '}'
841
- }
842
-
843
- generateJSXEmptyExpression() {
844
- return ''
845
- }
846
-
847
- generateReactHookCall(node) {
848
- const args = node.arguments.map(a => this.generate(a)).join(', ')
849
- return `${node.hookName}(${args})`
850
- }
851
-
852
- generateReactComponent(node) {
853
- const props = node.props.length > 0 ? `{ ${node.props.join(', ')} }` : ''
854
-
855
- if (node.componentType === 'function') {
856
- return `function ${node.name}(${props}) ${this.generate(node.body)}`
857
- } else {
858
- return `class ${node.name} extends React.Component ${this.generate(node.body)}`
859
- }
860
- }
861
-
862
- generateReactDirective(node) {
863
- return `"${node.directive}";`
864
- }
865
-
866
- generateUseStateHook(node) {
867
- const init = node.initialValue ? this.generate(node.initialValue) : ''
868
- return `const [${node.stateName}, ${node.setterName}] = useState(${init})`
869
- }
870
-
871
- generateUseEffectHook(node) {
872
- const callback = this.generate(node.callback)
873
- const deps = node.dependencies ? this.generate(node.dependencies) : ''
874
- return `useEffect(${callback}${deps ? ', ' + deps : ''})`
875
- }
876
-
877
- generateUseRefHook(node) {
878
- const init = node.initialValue ? this.generate(node.initialValue) : 'null'
879
- return `const ${node.refName} = useRef(${init})`
880
- }
881
-
882
- generateUseContextHook(node) {
883
- return `useContext(${this.generate(node.context)})`
884
- }
885
-
886
- generateUseReducerHook(node) {
887
- const reducer = this.generate(node.reducer)
888
- const init = this.generate(node.initialState)
889
- return `const [${node.stateName}, ${node.dispatchName}] = useReducer(${reducer}, ${init})`
890
- }
891
-
892
- generateUseMemoHook(node) {
893
- const factory = this.generate(node.factory)
894
- const deps = this.generate(new AST.ArrayExpression(node.dependencies, null))
895
- return `useMemo(${factory}, ${deps})`
896
- }
897
-
898
- generateUseCallbackHook(node) {
899
- const callback = this.generate(node.callback)
900
- const deps = this.generate(new AST.ArrayExpression(node.dependencies, null))
901
- return `useCallback(${callback}, ${deps})`
902
- }
903
-
904
- generateUseTransitionHook(node) {
905
- return `const [${node.isPendingName}, ${node.startTransitionName}] = useTransition()`
906
- }
907
-
908
- generateUseDeferredValueHook(node) {
909
- return `useDeferredValue(${this.generate(node.value)})`
910
- }
911
-
912
- generateUseIdHook(node) {
913
- return `const ${node.idName} = useId()`
914
- }
915
-
916
- generateCreateContextCall(node) {
917
- const defaultVal = node.defaultValue ? this.generate(node.defaultValue) : ''
918
- return `createContext(${defaultVal})`
919
- }
920
-
921
- generateContextProvider(node) {
922
- const value = this.generate(node.value)
923
- const children = node.children.map(c => this.generate(c)).join('')
924
- return `<${this.generate(node.context)}.Provider value={${value}}>${children}</${this.generate(node.context)}.Provider>`
925
- }
926
-
927
- generateForwardRefCall(node) {
928
- return `forwardRef(${this.generate(node.render)})`
929
- }
930
-
931
- generateMemoCall(node) {
932
- const component = this.generate(node.component)
933
- const areEqual = node.areEqual ? ', ' + this.generate(node.areEqual) : ''
934
- return `memo(${component}${areEqual})`
935
- }
936
-
937
- generateLazyCall(node) {
938
- return `lazy(${this.generate(node.load)})`
939
- }
940
-
941
- generateSuspenseComponent(node) {
942
- const fallback = this.generate(node.fallback)
943
- const children = node.children.map(c => this.generate(c)).join('')
944
- return `<Suspense fallback={${fallback}}>${children}</Suspense>`
945
- }
946
-
947
- generateStrictModeComponent(node) {
948
- const children = node.children.map(c => this.generate(c)).join('')
949
- return `<StrictMode>${children}</StrictMode>`
950
- }
951
-
952
- generateProfilerComponent(node) {
953
- const id = this.generate(node.id)
954
- const onRender = this.generate(node.onRender)
955
- const children = node.children.map(c => this.generate(c)).join('')
956
- return `<Profiler id=${id} onRender={${onRender}}>${children}</Profiler>`
957
- }
958
-
959
- generateCreatePortalCall(node) {
960
- const children = this.generate(node.children)
961
- const container = this.generate(node.container)
962
- const key = node.key ? ', ' + this.generate(node.key) : ''
963
- return `createPortal(${children}, ${container}${key})`
964
- }
965
-
966
- generateCreateRootCall(node) {
967
- const container = this.generate(node.container)
968
- const options = node.options ? ', ' + this.generate(node.options) : ''
969
- return `createRoot(${container}${options})`
970
- }
971
-
972
- generateHydrateRootCall(node) {
973
- const container = this.generate(node.container)
974
- const children = this.generate(node.initialChildren)
975
- const options = node.options ? ', ' + this.generate(node.options) : ''
976
- return `hydrateRoot(${container}, ${children}${options})`
977
- }
978
-
979
- generateStartTransitionCall(node) {
980
- return `startTransition(${this.generate(node.callback)})`
981
- }
982
-
983
- generateFlushSyncCall(node) {
984
- return `flushSync(${this.generate(node.callback)})`
985
- }
986
-
987
- generateCreateElementCall(node) {
988
- const type = typeof node.elementType === 'string' ? `"${node.elementType}"` : this.generate(node.elementType)
989
- const props = node.props ? this.generate(node.props) : 'null'
990
- const children = node.children.map(c => this.generate(c)).join(', ')
991
- return `createElement(${type}, ${props}${children ? ', ' + children : ''})`
992
- }
993
-
994
- generateCloneElementCall(node) {
995
- const element = this.generate(node.element)
996
- const props = node.props ? this.generate(node.props) : 'null'
997
- const children = node.children.map(c => this.generate(c)).join(', ')
998
- return `cloneElement(${element}, ${props}${children ? ', ' + children : ''})`
999
- }
1000
-
1001
- generateServerActionFunction(node) {
1002
- const params = node.params.map(p => this.generate(p)).join(', ')
1003
- return `async function ${node.name}(${params}) {\n "use server";\n${this.generate(node.body)}\n}`
1004
- }
1005
-
1006
- generateEtherReactComponent(node) {
1007
- const props = node.props.length > 0 ? `{ ${node.props.join(', ')} }` : ''
1008
- const hooks = node.hooks.map(h => ' ' + this.generate(h)).join('\n')
1009
- const jsx = this.generate(node.returnJSX)
1010
-
1011
- return `function ${node.name}(${props}) {\n${hooks}\n return (\n ${jsx}\n );\n}`
1012
- }
1013
-
1014
- generateEtherHookDeclaration(node) {
1015
- const bindings = node.bindings.join(', ')
1016
- const args = node.arguments.map(a => this.generate(a)).join(', ')
1017
-
1018
- if (node.bindings.length === 1) {
1019
- return `const ${bindings} = ${node.hookType}(${args});`
1020
- } else if (node.bindings.length === 2) {
1021
- return `const [${bindings}] = ${node.hookType}(${args});`
1022
- }
1023
-
1024
- return `${node.hookType}(${args});`
1025
- }
1026
- }
1027
-
1028
- module.exports = {
1029
- ReactLexer,
1030
- ReactParser,
1031
- ReactCodeGenerator,
1032
- REACT_HOOKS,
1033
- REACT_COMPONENTS,
1034
- SELF_CLOSING_TAGS
1035
- }