@vxrn/compiler 1.25.16 → 1.26.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 (130) hide show
  1. package/dist/cjs/cache.cjs +4 -2
  2. package/dist/cjs/configure.cjs +9 -1
  3. package/dist/cjs/index.cjs +87 -5
  4. package/dist/cjs/reactNativeCodegen.cjs +140 -0
  5. package/dist/cjs/reactNativeViewConfig.cjs +489 -0
  6. package/dist/cjs/transformBabel.cjs +15 -20
  7. package/dist/cjs/transformBabel.test.cjs +260 -0
  8. package/dist/cjs/transformHermesAsync.cjs +61 -0
  9. package/dist/cjs/transformHermesLoops.cjs +319 -0
  10. package/dist/cjs/transformHermesLoops.test.cjs +257 -0
  11. package/dist/cjs/transformSWC.cjs +110 -138
  12. package/dist/cjs/transformSWC.test.cjs +117 -0
  13. package/dist/cjs/transformWorklets.cjs +123 -0
  14. package/dist/cjs/transformWorklets.test.cjs +604 -0
  15. package/dist/cjs/worklets/autoworklet.cjs +265 -0
  16. package/dist/cjs/worklets/globals.cjs +73 -0
  17. package/dist/cjs/worklets/hash.cjs +38 -0
  18. package/dist/cjs/worklets/scope.cjs +370 -0
  19. package/dist/cjs/worklets/serialize.cjs +101 -0
  20. package/dist/cjs/worklets/transform.cjs +266 -0
  21. package/dist/cjs/worklets/types.cjs +18 -0
  22. package/dist/esm/cache.mjs +5 -3
  23. package/dist/esm/cache.mjs.map +1 -1
  24. package/dist/esm/configure.mjs +8 -1
  25. package/dist/esm/configure.mjs.map +1 -1
  26. package/dist/esm/index.js +75 -6
  27. package/dist/esm/index.js.map +1 -1
  28. package/dist/esm/index.mjs +75 -6
  29. package/dist/esm/index.mjs.map +1 -1
  30. package/dist/esm/reactNativeCodegen.mjs +104 -0
  31. package/dist/esm/reactNativeCodegen.mjs.map +1 -0
  32. package/dist/esm/reactNativeViewConfig.mjs +460 -0
  33. package/dist/esm/reactNativeViewConfig.mjs.map +1 -0
  34. package/dist/esm/transformBabel.mjs +14 -19
  35. package/dist/esm/transformBabel.mjs.map +1 -1
  36. package/dist/esm/transformBabel.test.mjs +261 -1
  37. package/dist/esm/transformBabel.test.mjs.map +1 -1
  38. package/dist/esm/transformHermesAsync.mjs +25 -0
  39. package/dist/esm/transformHermesAsync.mjs.map +1 -0
  40. package/dist/esm/transformHermesLoops.mjs +283 -0
  41. package/dist/esm/transformHermesLoops.mjs.map +1 -0
  42. package/dist/esm/transformHermesLoops.test.mjs +234 -0
  43. package/dist/esm/transformHermesLoops.test.mjs.map +1 -0
  44. package/dist/esm/transformSWC.mjs +96 -139
  45. package/dist/esm/transformSWC.mjs.map +1 -1
  46. package/dist/esm/transformSWC.test.mjs +118 -0
  47. package/dist/esm/transformSWC.test.mjs.map +1 -0
  48. package/dist/esm/transformWorklets.mjs +82 -0
  49. package/dist/esm/transformWorklets.mjs.map +1 -0
  50. package/dist/esm/transformWorklets.test.mjs +581 -0
  51. package/dist/esm/transformWorklets.test.mjs.map +1 -0
  52. package/dist/esm/worklets/autoworklet.mjs +234 -0
  53. package/dist/esm/worklets/autoworklet.mjs.map +1 -0
  54. package/dist/esm/worklets/globals.mjs +46 -0
  55. package/dist/esm/worklets/globals.mjs.map +1 -0
  56. package/dist/esm/worklets/hash.mjs +13 -0
  57. package/dist/esm/worklets/hash.mjs.map +1 -0
  58. package/dist/esm/worklets/scope.mjs +345 -0
  59. package/dist/esm/worklets/scope.mjs.map +1 -0
  60. package/dist/esm/worklets/serialize.mjs +75 -0
  61. package/dist/esm/worklets/serialize.mjs.map +1 -0
  62. package/dist/esm/worklets/transform.mjs +229 -0
  63. package/dist/esm/worklets/transform.mjs.map +1 -0
  64. package/dist/esm/worklets/types.mjs +2 -0
  65. package/dist/esm/worklets/types.mjs.map +1 -0
  66. package/package.json +11 -6
  67. package/src/cache.ts +10 -3
  68. package/src/configure.ts +12 -0
  69. package/src/index.ts +96 -13
  70. package/src/reactNativeCodegen.ts +181 -0
  71. package/src/reactNativeViewConfig.ts +593 -0
  72. package/src/transformBabel.test.ts +268 -1
  73. package/src/transformBabel.ts +21 -31
  74. package/src/transformHermesAsync.ts +30 -0
  75. package/src/transformHermesLoops.test.ts +341 -0
  76. package/src/transformHermesLoops.ts +435 -0
  77. package/src/transformSWC.test.ts +132 -0
  78. package/src/transformSWC.ts +121 -161
  79. package/src/transformWorklets.test.ts +708 -0
  80. package/src/transformWorklets.ts +160 -0
  81. package/src/types.ts +4 -4
  82. package/src/worklets/autoworklet.ts +391 -0
  83. package/src/worklets/globals.ts +129 -0
  84. package/src/worklets/hash.ts +14 -0
  85. package/src/worklets/scope.ts +401 -0
  86. package/src/worklets/serialize.ts +114 -0
  87. package/src/worklets/transform.ts +319 -0
  88. package/src/worklets/types.ts +26 -0
  89. package/types/cache.d.ts +1 -0
  90. package/types/cache.d.ts.map +1 -1
  91. package/types/configure.d.ts +2 -0
  92. package/types/configure.d.ts.map +1 -1
  93. package/types/index.d.ts +6 -0
  94. package/types/index.d.ts.map +1 -1
  95. package/types/reactNativeCodegen.d.ts +5 -0
  96. package/types/reactNativeCodegen.d.ts.map +1 -0
  97. package/types/reactNativeViewConfig.d.ts +35 -0
  98. package/types/reactNativeViewConfig.d.ts.map +1 -0
  99. package/types/transformBabel.d.ts +3 -3
  100. package/types/transformBabel.d.ts.map +1 -1
  101. package/types/transformHermesAsync.d.ts +5 -0
  102. package/types/transformHermesAsync.d.ts.map +1 -0
  103. package/types/transformHermesLoops.d.ts +5 -0
  104. package/types/transformHermesLoops.d.ts.map +1 -0
  105. package/types/transformHermesLoops.test.d.ts +2 -0
  106. package/types/transformHermesLoops.test.d.ts.map +1 -0
  107. package/types/transformSWC.d.ts +13 -3
  108. package/types/transformSWC.d.ts.map +1 -1
  109. package/types/transformSWC.test.d.ts +2 -0
  110. package/types/transformSWC.test.d.ts.map +1 -0
  111. package/types/transformWorklets.d.ts +23 -0
  112. package/types/transformWorklets.d.ts.map +1 -0
  113. package/types/transformWorklets.test.d.ts +2 -0
  114. package/types/transformWorklets.test.d.ts.map +1 -0
  115. package/types/types.d.ts +4 -4
  116. package/types/types.d.ts.map +1 -1
  117. package/types/worklets/autoworklet.d.ts +14 -0
  118. package/types/worklets/autoworklet.d.ts.map +1 -0
  119. package/types/worklets/globals.d.ts +4 -0
  120. package/types/worklets/globals.d.ts.map +1 -0
  121. package/types/worklets/hash.d.ts +5 -0
  122. package/types/worklets/hash.d.ts.map +1 -0
  123. package/types/worklets/scope.d.ts +7 -0
  124. package/types/worklets/scope.d.ts.map +1 -0
  125. package/types/worklets/serialize.d.ts +12 -0
  126. package/types/worklets/serialize.d.ts.map +1 -0
  127. package/types/worklets/transform.d.ts +10 -0
  128. package/types/worklets/transform.d.ts.map +1 -0
  129. package/types/worklets/types.d.ts +21 -0
  130. package/types/worklets/types.d.ts.map +1 -0
@@ -0,0 +1,593 @@
1
+ /**
2
+ * View-config generation for react-native component specs, without babel.
3
+ *
4
+ * `@react-native/codegen` builds a full CodegenSchema with @babel/parser and
5
+ * emits the module with @babel/generator, which is the last thing in the native
6
+ * pipeline that runs babel at all: 11,774 babel calls per bundle of the soot
7
+ * app, every one of them from codegen. The view config itself is a plain object
8
+ * literal, so it can be read off the spec's own AST and printed as text.
9
+ *
10
+ * Only what a view config needs is modeled here: the prop list in declaration
11
+ * order, the five prop types that get a custom attribute descriptor, the two
12
+ * event handler shapes, and the command list. Everything else about a prop's
13
+ * type is irrelevant to the runtime, which is why the generated `validAttributes`
14
+ * is almost entirely `true`.
15
+ *
16
+ * TypeScript specs are parsed with oxc. React Native's own specs are Flow, which
17
+ * oxc cannot parse, so those go through hermes-parser (which is not babel).
18
+ */
19
+
20
+ import { createRequire } from 'node:module'
21
+ import { basename, join } from 'node:path'
22
+ import { parseSync } from 'oxc-parser'
23
+
24
+ const SELF_URL: string = typeof __filename !== 'undefined' ? __filename : import.meta.url
25
+
26
+ /** attribute descriptors react-native needs for props it cannot diff by identity */
27
+ const COLOR_ATTRIBUTE =
28
+ "require('react-native/Libraries/Components/View/ReactNativeStyleAttributes').colorAttribute"
29
+ const COLOR_ARRAY_ATTRIBUTE =
30
+ "{\n process: (req => 'default' in req ? req.default : req)(require('react-native/Libraries/StyleSheet/processColorArray'))\n }"
31
+ const IMAGE_ATTRIBUTE =
32
+ "{\n process: (req => 'default' in req ? req.default : req)(require('react-native/Libraries/Image/resolveAssetSource'))\n }"
33
+ const POINT_ATTRIBUTE =
34
+ "{\n diff: (req => 'default' in req ? req.default : req)(require('react-native/Libraries/Utilities/differ/pointsDiffer'))\n }"
35
+ const EDGE_INSETS_ATTRIBUTE =
36
+ "{\n diff: (req => 'default' in req ? req.default : req)(require('react-native/Libraries/Utilities/differ/insetsDiffer'))\n }"
37
+
38
+ const IMPORT_REGISTRY =
39
+ "const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry');"
40
+ const IMPORT_IGNORE =
41
+ "const {ConditionallyIgnoredEventHandlers} = require('react-native/Libraries/NativeComponent/ViewConfigIgnore');"
42
+ const IMPORT_DISPATCH =
43
+ 'const {dispatchCommand} = require("react-native/Libraries/ReactNative/RendererProxy");'
44
+ const IMPORT_UIMANAGER = 'const {UIManager} = require("react-native")'
45
+
46
+ type Attribute = { name: string; value: string }
47
+ type EventProp = { name: string; bubbling: boolean; topName: string }
48
+ type Command = { name: string; params: string[] }
49
+
50
+ /** a prop or event read off the spec, before it becomes view-config text */
51
+ type Member = { name: string; type: TypeRef }
52
+
53
+ /** a type annotation reduced to the only two things that matter here */
54
+ type TypeRef = { name: string; args: TypeRef[] }
55
+
56
+ let hermesParser: any = undefined
57
+
58
+ function getHermesParser(projectRoot?: string) {
59
+ if (hermesParser === undefined) {
60
+ try {
61
+ const req = projectRoot
62
+ ? createRequire(join(projectRoot, 'package.json'))
63
+ : createRequire(SELF_URL)
64
+ hermesParser = req('hermes-parser')
65
+ } catch {
66
+ try {
67
+ hermesParser = createRequire(SELF_URL)('hermes-parser')
68
+ } catch {
69
+ hermesParser = null
70
+ }
71
+ }
72
+ }
73
+ return hermesParser
74
+ }
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // type annotations
78
+ // ---------------------------------------------------------------------------
79
+
80
+ /**
81
+ * Both dialects reduce to a name plus type arguments. Wrappers that do not
82
+ * change what the runtime does with a prop are unwrapped here rather than
83
+ * modeled: a nullable color is still a color.
84
+ */
85
+ function readType(node: any): TypeRef {
86
+ if (!node) return { name: '', args: [] }
87
+
88
+ switch (node.type) {
89
+ // flow
90
+ case 'NullableTypeAnnotation':
91
+ case 'TypeAnnotation':
92
+ return readType(node.typeAnnotation)
93
+ case 'GenericTypeAnnotation':
94
+ return {
95
+ name: readTypeName(node.id),
96
+ args: (node.typeParameters?.params ?? []).map(readType),
97
+ }
98
+ case 'ObjectTypeAnnotation':
99
+ return { name: '__object', args: [] }
100
+ case 'StringTypeAnnotation':
101
+ return { name: 'string', args: [] }
102
+ case 'BooleanTypeAnnotation':
103
+ return { name: 'boolean', args: [] }
104
+ case 'NumberTypeAnnotation':
105
+ return { name: 'number', args: [] }
106
+ case 'UnionTypeAnnotation':
107
+ return readUnion(node)
108
+ case 'FunctionTypeAnnotation':
109
+ return { name: '__function', args: [] }
110
+
111
+ // typescript
112
+ case 'TSTypeAnnotation':
113
+ return readType(node.typeAnnotation)
114
+ case 'TSTypeReference':
115
+ return {
116
+ name: readTypeName(node.typeName),
117
+ args: (node.typeArguments ?? node.typeParameters)?.params?.map(readType) ?? [],
118
+ }
119
+ case 'TSTypeLiteral':
120
+ return { name: '__object', args: [] }
121
+ case 'TSStringKeyword':
122
+ return { name: 'string', args: [] }
123
+ case 'TSBooleanKeyword':
124
+ return { name: 'boolean', args: [] }
125
+ case 'TSNumberKeyword':
126
+ return { name: 'number', args: [] }
127
+ case 'TSUnionType':
128
+ return readUnion(node)
129
+ case 'TSFunctionType':
130
+ return { name: '__function', args: [] }
131
+ case 'TSArrayType':
132
+ return { name: 'Array', args: [readType(node.elementType)] }
133
+ }
134
+
135
+ return { name: '__unknown', args: [] }
136
+ }
137
+
138
+ const NULLISH_TYPES = new Set([
139
+ 'TSUndefinedKeyword',
140
+ 'TSNullKeyword',
141
+ 'TSVoidKeyword',
142
+ 'NullLiteralTypeAnnotation',
143
+ 'VoidTypeAnnotation',
144
+ ])
145
+
146
+ /**
147
+ * `DirectEventHandler<E> | undefined` is the same prop as `DirectEventHandler<E>`.
148
+ * A union of anything else is an enum or a mixed type, which the runtime diffs
149
+ * by identity either way.
150
+ */
151
+ function readUnion(node: any): TypeRef {
152
+ const real = (node.types ?? []).filter((t: any) => !NULLISH_TYPES.has(t.type))
153
+ if (real.length === 1) return readType(real[0])
154
+ return { name: '__union', args: [] }
155
+ }
156
+
157
+ function readTypeName(id: any): string {
158
+ if (!id) return ''
159
+ if (id.type === 'Identifier') return id.name
160
+ // Qualified names (`Foo.Bar`) only appear on types the runtime treats as
161
+ // opaque, so the last segment is enough to classify them.
162
+ if (id.type === 'QualifiedTypeIdentifier') return readTypeName(id.id)
163
+ if (id.type === 'TSQualifiedName') return readTypeName(id.right)
164
+ return ''
165
+ }
166
+
167
+ const READONLY_ARRAYS = new Set(['ReadonlyArray', '$ReadOnlyArray', 'Array'])
168
+
169
+ /** wrappers that carry no runtime meaning for a view config */
170
+ const TRANSPARENT = new Set(['WithDefault', 'UnsafeMixed', 'Readonly', '$ReadOnly'])
171
+
172
+ function unwrap(type: TypeRef): TypeRef {
173
+ let cur = type
174
+ while (TRANSPARENT.has(cur.name) && cur.args.length > 0) {
175
+ cur = cur.args[0]
176
+ }
177
+ return cur
178
+ }
179
+
180
+ function attributeValue(type: TypeRef): string {
181
+ const t = unwrap(type)
182
+ switch (t.name) {
183
+ case 'ColorValue':
184
+ case 'ProcessedColorValue':
185
+ return COLOR_ATTRIBUTE
186
+ case 'ImageSource':
187
+ return IMAGE_ATTRIBUTE
188
+ case 'PointValue':
189
+ return POINT_ATTRIBUTE
190
+ case 'EdgeInsetsValue':
191
+ return EDGE_INSETS_ATTRIBUTE
192
+ }
193
+ if (READONLY_ARRAYS.has(t.name) && t.args.length === 1) {
194
+ const el = unwrap(t.args[0])
195
+ if (el.name === 'ColorValue' || el.name === 'ProcessedColorValue') {
196
+ return COLOR_ARRAY_ATTRIBUTE
197
+ }
198
+ }
199
+ return 'true'
200
+ }
201
+
202
+ function eventOf(name: string, type: TypeRef): EventProp | null {
203
+ const t = unwrap(type)
204
+ if (t.name !== 'DirectEventHandler' && t.name !== 'BubblingEventHandler') return null
205
+ // the second type argument is the deprecated paper-level event name, which
206
+ // renames the top-level event but never the registration name
207
+ const override = t.args[1]?.name
208
+ return {
209
+ name,
210
+ bubbling: t.name === 'BubblingEventHandler',
211
+ topName: normalizeInputEventName(override && override !== '__unknown' ? override : name),
212
+ }
213
+ }
214
+
215
+ function normalizeInputEventName(name: string) {
216
+ if (name.startsWith('on')) return name.replace(/^on/, 'top')
217
+ if (!name.startsWith('top')) return `top${name[0].toUpperCase()}${name.slice(1)}`
218
+ return name
219
+ }
220
+
221
+ // ---------------------------------------------------------------------------
222
+ // reading the spec
223
+ // ---------------------------------------------------------------------------
224
+
225
+ type Declarations = Map<string, any>
226
+
227
+ function collectDeclarations(body: any[]): Declarations {
228
+ const out: Declarations = new Map()
229
+ for (const raw of body) {
230
+ const node = raw?.type === 'ExportNamedDeclaration' ? raw.declaration : raw
231
+ if (!node) continue
232
+ switch (node.type) {
233
+ case 'TSTypeAliasDeclaration':
234
+ case 'TypeAlias':
235
+ case 'TSInterfaceDeclaration':
236
+ case 'InterfaceDeclaration':
237
+ if (node.id?.name) out.set(node.id.name, node)
238
+ break
239
+ }
240
+ }
241
+ return out
242
+ }
243
+
244
+ /**
245
+ * The members a props type contributes, in the order react-native's own codegen
246
+ * emits them: everything it extends first, in declaration order, then its own.
247
+ * `ViewProps` is the core view's props, which the runtime supplies itself.
248
+ */
249
+ function flattenMembers(
250
+ typeName: string,
251
+ decls: Declarations,
252
+ seen = new Set<string>()
253
+ ): Member[] {
254
+ if (typeName === 'ViewProps' || seen.has(typeName)) return []
255
+ seen.add(typeName)
256
+
257
+ const decl = decls.get(typeName)
258
+ if (!decl) return []
259
+
260
+ const out: Member[] = []
261
+
262
+ const heritage = decl.extends ?? decl.heritage ?? []
263
+ for (const h of heritage) {
264
+ const name = readTypeName(h.id ?? h.expression ?? h.typeName)
265
+ if (name) out.push(...flattenMembers(name, decls, seen))
266
+ }
267
+
268
+ const bodyNode =
269
+ decl.body ?? // interface
270
+ decl.right ?? // type alias
271
+ decl.typeAnnotation
272
+ out.push(...readObjectMembers(bodyNode, decls, seen))
273
+
274
+ return out
275
+ }
276
+
277
+ function readObjectMembers(node: any, decls: Declarations, seen: Set<string>): Member[] {
278
+ if (!node) return []
279
+
280
+ // `Readonly<{...}>` / `$ReadOnly<{...}>` around the real shape
281
+ if (node.type === 'TSTypeReference' || node.type === 'GenericTypeAnnotation') {
282
+ const name = readTypeName(node.typeName ?? node.id)
283
+ const args = (node.typeArguments ?? node.typeParameters)?.params ?? []
284
+ if (TRANSPARENT.has(name) && args.length) {
285
+ return readObjectMembers(args[0], decls, seen)
286
+ }
287
+ // an alias of another props type
288
+ return flattenMembers(name, decls, seen)
289
+ }
290
+
291
+ if (node.type === 'TSIntersectionType' || node.type === 'IntersectionTypeAnnotation') {
292
+ const out: Member[] = []
293
+ for (const t of node.types ?? []) out.push(...readObjectMembers(t, decls, seen))
294
+ return out
295
+ }
296
+
297
+ const members = node.members ?? node.body ?? node.properties ?? []
298
+ const out: Member[] = []
299
+ for (const m of members) {
300
+ switch (m.type) {
301
+ case 'TSPropertySignature':
302
+ case 'ObjectTypeProperty': {
303
+ const name = m.key?.name ?? m.key?.value
304
+ if (!name) break
305
+ out.push({ name, type: readType(m.typeAnnotation ?? m.value) })
306
+ break
307
+ }
308
+ // flow's `...OtherProps` inside an object type
309
+ case 'ObjectTypeSpreadProperty': {
310
+ const name = readTypeName(m.argument?.id ?? m.argument)
311
+ if (name) out.push(...flattenMembers(name, decls, seen))
312
+ break
313
+ }
314
+ }
315
+ }
316
+ return out
317
+ }
318
+
319
+ function findComponentCall(body: any[]): any {
320
+ for (const node of body) {
321
+ if (node.type !== 'ExportDefaultDeclaration') continue
322
+ let expr = node.declaration
323
+ while (expr) {
324
+ if (expr.type === 'CallExpression') break
325
+ expr =
326
+ expr.expression ??
327
+ (expr.type === 'SequenceExpression'
328
+ ? expr.expressions[expr.expressions.length - 1]
329
+ : null)
330
+ }
331
+ if (expr?.type === 'CallExpression' && expr.callee?.name === 'codegenNativeComponent') {
332
+ return expr
333
+ }
334
+ }
335
+ return null
336
+ }
337
+
338
+ function findCommandsCall(body: any[]): any {
339
+ for (const raw of body) {
340
+ const node = raw?.type === 'ExportNamedDeclaration' ? raw.declaration : raw
341
+ const decl = node?.declarations?.[0]
342
+ let init = decl?.init
343
+ while (init && init.type !== 'CallExpression') init = init.expression
344
+ if (init?.type === 'CallExpression' && init.callee?.name === 'codegenNativeCommands') {
345
+ return init
346
+ }
347
+ }
348
+ return null
349
+ }
350
+
351
+ function readStringOption(objectExpression: any, key: string): string | null {
352
+ for (const p of objectExpression?.properties ?? []) {
353
+ if ((p.key?.name ?? p.key?.value) === key && typeof p.value?.value === 'string') {
354
+ return p.value.value
355
+ }
356
+ }
357
+ return null
358
+ }
359
+
360
+ function readCommands(call: any, decls: Declarations): Command[] {
361
+ const typeName = readTypeName(
362
+ (call.typeArguments ?? call.typeParameters)?.params?.[0]?.typeName ??
363
+ (call.typeArguments ?? call.typeParameters)?.params?.[0]?.id
364
+ )
365
+ const supported: string[] = []
366
+ for (const p of call.arguments?.[0]?.properties ?? []) {
367
+ if ((p.key?.name ?? p.key?.value) !== 'supportedCommands') continue
368
+ for (const el of p.value?.elements ?? []) {
369
+ if (typeof el?.value === 'string') supported.push(el.value)
370
+ }
371
+ }
372
+ if (!supported.length) return []
373
+
374
+ // parameter names come from the commands interface; the first parameter is
375
+ // the view ref, which the generated method takes but never forwards
376
+ const decl = decls.get(typeName)
377
+ const byName = new Map<string, string[]>()
378
+ // a TS interface body holds `.body`; flow's is an object type with `.properties`
379
+ const commandMembers =
380
+ decl?.body?.body ?? decl?.body?.members ?? decl?.body?.properties ?? []
381
+ for (const m of commandMembers) {
382
+ const name = m.key?.name ?? m.key?.value
383
+ if (!name) continue
384
+ const fn = m.typeAnnotation?.typeAnnotation ?? m.value ?? m.typeAnnotation
385
+ const params = (fn?.params ?? fn?.parameters ?? []).slice(1)
386
+ byName.set(
387
+ name,
388
+ params.map((p: any, i: number) => p.name?.name ?? p.name ?? `arg${i}`)
389
+ )
390
+ }
391
+
392
+ return supported.map((name) => ({ name, params: byName.get(name) ?? [] }))
393
+ }
394
+
395
+ // ---------------------------------------------------------------------------
396
+ // printing
397
+ // ---------------------------------------------------------------------------
398
+
399
+ function indent(text: string, spaces: number) {
400
+ const pad = ' '.repeat(spaces)
401
+ return text
402
+ .split('\n')
403
+ .map((line, i) => (i === 0 || line === '' ? line : pad + line))
404
+ .join('\n')
405
+ }
406
+
407
+ function printViewConfig(
408
+ componentName: string,
409
+ attributes: Attribute[],
410
+ events: EventProp[]
411
+ ): string {
412
+ const lines: string[] = [` uiViewClassName: ${JSON.stringify(componentName)}`]
413
+
414
+ const bubbling = events.filter((e) => e.bubbling)
415
+ if (bubbling.length) {
416
+ lines.push(
417
+ ` bubblingEventTypes: {\n${bubbling
418
+ .map(
419
+ (e) =>
420
+ ` ${e.topName}: {\n phasedRegistrationNames: {\n captured: ${JSON.stringify(
421
+ `${e.name}Capture`
422
+ )},\n bubbled: ${JSON.stringify(e.name)}\n }\n }`
423
+ )
424
+ .join(',\n')}\n }`
425
+ )
426
+ }
427
+
428
+ const direct = events.filter((e) => !e.bubbling)
429
+ if (direct.length) {
430
+ lines.push(
431
+ ` directEventTypes: {\n${direct
432
+ .map(
433
+ (e) =>
434
+ ` ${e.topName}: {\n registrationName: ${JSON.stringify(e.name)}\n }`
435
+ )
436
+ .join(',\n')}\n }`
437
+ )
438
+ }
439
+
440
+ const attrLines = attributes.map((a) => ` ${a.name}: ${indent(a.value, 4)}`)
441
+ if (events.length) {
442
+ attrLines.push(
443
+ ` ...ConditionallyIgnoredEventHandlers({\n${events
444
+ .map((e) => ` ${e.name}: true`)
445
+ .join(',\n')}\n })`
446
+ )
447
+ }
448
+ lines.push(
449
+ attrLines.length ? ` validAttributes: {\n${attrLines.join(',\n')}\n }` : ' validAttributes: {}'
450
+ )
451
+
452
+ return `{\n${lines.join(',\n')}\n}`
453
+ }
454
+
455
+ function printCommands(commands: Command[]): string {
456
+ return `export const Commands = {\n${commands
457
+ .map(
458
+ (c) =>
459
+ ` ${c.name}(ref${c.params.map((p) => `, ${p}`).join('')}) {\n dispatchCommand(ref, ${JSON.stringify(
460
+ c.name
461
+ )}, [${c.params.join(', ')}]);\n }`
462
+ )
463
+ .join(',\n')}\n};`
464
+ }
465
+
466
+ const FILE_HEADER = `/**
467
+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
468
+ *
469
+ * Do not edit this file as changes may cause incorrect behavior and will be lost
470
+ * once the code is regenerated.
471
+ *
472
+ * @flow
473
+ *
474
+ * @generated by codegen project: GenerateViewConfigJs.js
475
+ */
476
+
477
+ 'use strict';
478
+ `
479
+
480
+ // ---------------------------------------------------------------------------
481
+ // entry
482
+ // ---------------------------------------------------------------------------
483
+
484
+ export function parseSpec(code: string, filename: string, projectRoot?: string): any {
485
+ const isTS = /\.[cm]?tsx?$/.test(filename)
486
+ if (isTS) {
487
+ const result = parseSync(filename, code, {
488
+ lang: filename.endsWith('x') ? 'tsx' : 'ts',
489
+ })
490
+ if (result.errors?.length) return null
491
+ return result.program
492
+ }
493
+
494
+ const hermes = getHermesParser(projectRoot)
495
+ if (!hermes) return null
496
+ try {
497
+ // `babel: false` keeps this on hermes' own ESTree output, which is what
498
+ // makes this path babel-free.
499
+ return hermes.parse(code, { babel: false, flow: 'all', sourceFilename: filename }) as any
500
+ } catch {
501
+ return null
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Generate the replacement for a spec's default export, plus the `Commands`
507
+ * export when the spec declares one. Returns null when the file is not a spec
508
+ * this can handle, so the caller can leave it alone.
509
+ */
510
+ export function generateViewConfig(
511
+ program: any
512
+ ): { imports: string[]; body: string } | null {
513
+ const body = program?.body
514
+ if (!body) return null
515
+
516
+ const call = findComponentCall(body)
517
+ if (!call) return null
518
+
519
+ const componentNameArg = call.arguments?.[0]
520
+ if (typeof componentNameArg?.value !== 'string') return null
521
+ const componentName = componentNameArg.value
522
+
523
+ const options = call.arguments?.[1]
524
+ const paperComponentName = readStringOption(options, 'paperComponentName')
525
+ const paperComponentNameDeprecated = readStringOption(
526
+ options,
527
+ 'paperComponentNameDeprecated'
528
+ )
529
+
530
+ const propsTypeName = readTypeName(
531
+ (call.typeArguments ?? call.typeParameters)?.params?.[0]?.typeName ??
532
+ (call.typeArguments ?? call.typeParameters)?.params?.[0]?.id
533
+ )
534
+ if (!propsTypeName) return null
535
+
536
+ const decls = collectDeclarations(body)
537
+ const members = flattenMembers(propsTypeName, decls)
538
+
539
+ const attributes: Attribute[] = []
540
+ const events: EventProp[] = []
541
+ for (const m of members) {
542
+ const event = eventOf(m.name, m.type)
543
+ if (event) {
544
+ events.push(event)
545
+ continue
546
+ }
547
+ attributes.push({ name: m.name, value: attributeValue(m.type) })
548
+ }
549
+
550
+ const commandsCall = findCommandsCall(body)
551
+ const commands = commandsCall ? readCommands(commandsCall, decls) : []
552
+
553
+ const imports = new Set([IMPORT_REGISTRY])
554
+ if (events.length) imports.add(IMPORT_IGNORE)
555
+ if (commands.length) imports.add(IMPORT_DISPATCH)
556
+ if (paperComponentNameDeprecated) imports.add(IMPORT_UIMANAGER)
557
+
558
+ const nativeName = paperComponentName ?? componentName
559
+ const parts: string[] = [`let nativeComponentName = '${nativeName}';`]
560
+
561
+ if (paperComponentNameDeprecated) {
562
+ parts.push(
563
+ `if (UIManager.hasViewManagerConfig('${componentName}')) {\n` +
564
+ ` nativeComponentName = '${componentName}';\n` +
565
+ `} else if (UIManager.hasViewManagerConfig('${paperComponentNameDeprecated}')) {\n` +
566
+ ` nativeComponentName = '${paperComponentNameDeprecated}';\n` +
567
+ `} else {\n` +
568
+ ` throw new Error('Failed to find native component for either "${componentName}" or "${paperComponentNameDeprecated}"');\n` +
569
+ `}`
570
+ )
571
+ }
572
+
573
+ parts.push(
574
+ `export const __INTERNAL_VIEW_CONFIG = ${printViewConfig(nativeName, attributes, events)};`
575
+ )
576
+ parts.push(
577
+ 'export default NativeComponentRegistry.get(nativeComponentName, () => __INTERNAL_VIEW_CONFIG);'
578
+ )
579
+ if (commands.length) parts.push(printCommands(commands))
580
+
581
+ return { imports: [...imports].sort(), body: parts.join('\n') }
582
+ }
583
+
584
+ /** the full generated block, matching the layout codegen's file template emits */
585
+ export function renderViewConfigModule(result: { imports: string[]; body: string }) {
586
+ return `\n${FILE_HEADER}\n${result.imports.join('\n')}\n\n${result.body}\n`
587
+ }
588
+
589
+ export function getLibraryName(filename: string): string {
590
+ const base = basename(filename)
591
+ const replaced = base.replace(/NativeComponent\.[cm]?[jt]sx?$/, '')
592
+ return replaced === base ? base.replace(/\.[cm]?[jt]sx?$/, '') : replaced
593
+ }