@tamagui/compiler-core 0.0.0-bootstrap.0 → 3.0.0-beta.637.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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -1
  3. package/dist/cjs/ast.cjs +95 -0
  4. package/dist/cjs/contracts.cjs +62 -0
  5. package/dist/cjs/diagnostics.cjs +69 -0
  6. package/dist/cjs/evaluate.cjs +351 -0
  7. package/dist/cjs/graph.cjs +279 -0
  8. package/dist/cjs/hash.cjs +49 -0
  9. package/dist/cjs/index.cjs +44 -0
  10. package/dist/cjs/ir.cjs +33 -0
  11. package/dist/cjs/lower.cjs +225 -0
  12. package/dist/cjs/materialize.cjs +200 -0
  13. package/dist/cjs/normalize.cjs +622 -0
  14. package/dist/cjs/output.cjs +149 -0
  15. package/dist/cjs/planCache.cjs +224 -0
  16. package/dist/cjs/session.cjs +119 -0
  17. package/dist/cjs/yuku.cjs +159 -0
  18. package/dist/cjs/zero.cjs +387 -0
  19. package/dist/esm/ast.mjs +65 -0
  20. package/dist/esm/ast.mjs.map +1 -0
  21. package/dist/esm/contracts.mjs +34 -0
  22. package/dist/esm/contracts.mjs.map +1 -0
  23. package/dist/esm/diagnostics.mjs +44 -0
  24. package/dist/esm/diagnostics.mjs.map +1 -0
  25. package/dist/esm/evaluate.mjs +327 -0
  26. package/dist/esm/evaluate.mjs.map +1 -0
  27. package/dist/esm/graph.mjs +256 -0
  28. package/dist/esm/graph.mjs.map +1 -0
  29. package/dist/esm/hash.mjs +26 -0
  30. package/dist/esm/hash.mjs.map +1 -0
  31. package/dist/esm/index.mjs +30 -0
  32. package/dist/esm/ir.mjs +12 -0
  33. package/dist/esm/ir.mjs.map +1 -0
  34. package/dist/esm/lower.mjs +202 -0
  35. package/dist/esm/lower.mjs.map +1 -0
  36. package/dist/esm/materialize.mjs +179 -0
  37. package/dist/esm/materialize.mjs.map +1 -0
  38. package/dist/esm/normalize.mjs +596 -0
  39. package/dist/esm/normalize.mjs.map +1 -0
  40. package/dist/esm/output.mjs +119 -0
  41. package/dist/esm/output.mjs.map +1 -0
  42. package/dist/esm/planCache.mjs +194 -0
  43. package/dist/esm/planCache.mjs.map +1 -0
  44. package/dist/esm/session.mjs +99 -0
  45. package/dist/esm/session.mjs.map +1 -0
  46. package/dist/esm/yuku.mjs +136 -0
  47. package/dist/esm/yuku.mjs.map +1 -0
  48. package/dist/esm/zero.mjs +352 -0
  49. package/dist/esm/zero.mjs.map +1 -0
  50. package/package.json +42 -4
  51. package/src/ast.ts +86 -0
  52. package/src/contracts.ts +148 -0
  53. package/src/diagnostics.ts +96 -0
  54. package/src/evaluate.ts +497 -0
  55. package/src/graph.ts +347 -0
  56. package/src/hash.ts +36 -0
  57. package/src/index.ts +15 -0
  58. package/src/ir.ts +154 -0
  59. package/src/lower.ts +418 -0
  60. package/src/materialize.ts +330 -0
  61. package/src/normalize.ts +846 -0
  62. package/src/output.ts +175 -0
  63. package/src/planCache.ts +327 -0
  64. package/src/session.ts +172 -0
  65. package/src/yuku.ts +189 -0
  66. package/src/zero.ts +598 -0
  67. package/types/ast.d.ts +10 -0
  68. package/types/ast.d.ts.map +1 -0
  69. package/types/contracts.d.ts +97 -0
  70. package/types/contracts.d.ts.map +1 -0
  71. package/types/diagnostics.d.ts +31 -0
  72. package/types/diagnostics.d.ts.map +1 -0
  73. package/types/evaluate.d.ts +36 -0
  74. package/types/evaluate.d.ts.map +1 -0
  75. package/types/graph.d.ts +37 -0
  76. package/types/graph.d.ts.map +1 -0
  77. package/types/hash.d.ts +8 -0
  78. package/types/hash.d.ts.map +1 -0
  79. package/types/index.d.ts +16 -0
  80. package/types/index.d.ts.map +1 -0
  81. package/types/ir.d.ts +118 -0
  82. package/types/ir.d.ts.map +1 -0
  83. package/types/lower.d.ts +99 -0
  84. package/types/lower.d.ts.map +1 -0
  85. package/types/materialize.d.ts +104 -0
  86. package/types/materialize.d.ts.map +1 -0
  87. package/types/normalize.d.ts +8 -0
  88. package/types/normalize.d.ts.map +1 -0
  89. package/types/output.d.ts +32 -0
  90. package/types/output.d.ts.map +1 -0
  91. package/types/planCache.d.ts +113 -0
  92. package/types/planCache.d.ts.map +1 -0
  93. package/types/session.d.ts +44 -0
  94. package/types/session.d.ts.map +1 -0
  95. package/types/yuku.d.ts +4 -0
  96. package/types/yuku.d.ts.map +1 -0
  97. package/types/zero.d.ts +98 -0
  98. package/types/zero.d.ts.map +1 -0
@@ -0,0 +1,846 @@
1
+ import {
2
+ childNode,
3
+ childNodes,
4
+ findAstNode,
5
+ identifierName,
6
+ isAstNode,
7
+ walkAst,
8
+ } from './ast'
9
+ import {
10
+ expressionReference,
11
+ resolvedModuleId,
12
+ spanOf,
13
+ type AnalyzerCandidate,
14
+ type AstNode,
15
+ type DefinitionSite,
16
+ type HostResolvedImport,
17
+ type ResolvedModuleId,
18
+ type SymbolDefinition,
19
+ } from './contracts'
20
+ import { linkedBailout, localBailout, type BailoutReason } from './diagnostics'
21
+ import type {
22
+ ElementChildIR,
23
+ ElementComponentIR,
24
+ ElementEntryIR,
25
+ ElementIR,
26
+ ElementIRBase,
27
+ ElementIRResult,
28
+ ElementPropIR,
29
+ ElementSpreadIR,
30
+ ElementValue,
31
+ DOMStyleDefinitionIR,
32
+ DOMStyleProgramIR,
33
+ StaticValue,
34
+ StyledDefinitionIR,
35
+ } from './ir'
36
+ import { hostImportProvenance } from './ir'
37
+
38
+ function finalizeElement(
39
+ base: ElementIRBase,
40
+ entries: ElementEntryIR[],
41
+ bailouts: BailoutReason[]
42
+ ): ElementIR {
43
+ if (bailouts.length === 0) {
44
+ return { ...base, complete: true, entries, bailouts: [] }
45
+ }
46
+ return {
47
+ ...base,
48
+ complete: false,
49
+ bailedEntries: entries,
50
+ bailouts: bailouts as [BailoutReason, ...BailoutReason[]],
51
+ }
52
+ }
53
+
54
+ export function definitionFromDeclaration(
55
+ id: string,
56
+ name: string,
57
+ program: AstNode,
58
+ declaration: AstNode
59
+ ): DefinitionSite {
60
+ let identifier = declaration
61
+ let initializer: AstNode | null = null
62
+ let constant = false
63
+
64
+ if (declaration.type === 'VariableDeclarator') {
65
+ identifier = childNode(declaration, 'id') ?? declaration
66
+ initializer = childNode(declaration, 'init')
67
+ constant = !!findAstNode(
68
+ program,
69
+ (node) =>
70
+ node.type === 'VariableDeclaration' &&
71
+ node.kind === 'const' &&
72
+ childNodes(node, 'declarations').some(
73
+ (candidate) => candidate.start === declaration.start
74
+ )
75
+ )
76
+ } else if (declaration.type === 'Identifier') {
77
+ const declarator = findAstNode(
78
+ program,
79
+ (node) =>
80
+ node.type === 'VariableDeclarator' &&
81
+ childNode(node, 'id')?.start === declaration.start
82
+ )
83
+ if (declarator) {
84
+ initializer = childNode(declarator, 'init')
85
+ constant = !!findAstNode(
86
+ program,
87
+ (node) =>
88
+ node.type === 'VariableDeclaration' &&
89
+ node.kind === 'const' &&
90
+ childNodes(node, 'declarations').some(
91
+ (candidate) => candidate.start === declarator.start
92
+ )
93
+ )
94
+ }
95
+ }
96
+
97
+ return {
98
+ id: resolvedModuleId(id),
99
+ name,
100
+ start: identifier.start,
101
+ end: identifier.end,
102
+ initializer,
103
+ constant,
104
+ }
105
+ }
106
+
107
+ interface ImportBinding {
108
+ source: string
109
+ imported: string
110
+ }
111
+
112
+ function importBinding(program: AstNode, localName: string): ImportBinding | null {
113
+ for (const statement of childNodes(program, 'body')) {
114
+ if (statement.type !== 'ImportDeclaration') continue
115
+ const source = childNode(statement, 'source')
116
+ const sourceValue = source && typeof source.value === 'string' ? source.value : null
117
+ if (!sourceValue) continue
118
+
119
+ for (const specifier of childNodes(statement, 'specifiers')) {
120
+ if (identifierName(childNode(specifier, 'local')) !== localName) continue
121
+ if (specifier.type === 'ImportDefaultSpecifier') {
122
+ return { source: sourceValue, imported: 'default' }
123
+ }
124
+ if (specifier.type === 'ImportNamespaceSpecifier') {
125
+ return { source: sourceValue, imported: '*' }
126
+ }
127
+ const imported = childNode(specifier, 'imported')
128
+ const importedName =
129
+ identifierName(imported) ??
130
+ (typeof imported?.value === 'string' ? imported.value : null)
131
+ return importedName ? { source: sourceValue, imported: importedName } : null
132
+ }
133
+ }
134
+ return null
135
+ }
136
+
137
+ function asStaticValue(node: AstNode | null): StaticValue | undefined {
138
+ if (!node) return undefined
139
+ if (node.type === 'NullLiteral') return null
140
+ if (node.type === 'Literal' || node.type.endsWith('Literal')) {
141
+ const value = node.value
142
+ if (
143
+ typeof value === 'string' ||
144
+ typeof value === 'number' ||
145
+ typeof value === 'boolean' ||
146
+ value === null
147
+ ) {
148
+ return value
149
+ }
150
+ }
151
+ return undefined
152
+ }
153
+
154
+ function elementValue(id: ResolvedModuleId, node: AstNode): ElementValue {
155
+ const staticValue = asStaticValue(node)
156
+ return staticValue === undefined
157
+ ? expressionReference(id, node)
158
+ : { kind: 'static', value: staticValue, span: spanOf(id, node) }
159
+ }
160
+
161
+ function symbolDefinition(definition: DefinitionSite | null): SymbolDefinition | null {
162
+ if (!definition) return null
163
+ return {
164
+ id: definition.id,
165
+ name: definition.name,
166
+ span: {
167
+ id: definition.id,
168
+ start: definition.start,
169
+ end: definition.end,
170
+ },
171
+ initializer: definition.initializer
172
+ ? expressionReference(definition.id, definition.initializer)
173
+ : null,
174
+ constant: definition.constant,
175
+ }
176
+ }
177
+
178
+ function componentFromNode(
179
+ candidate: AnalyzerCandidate,
180
+ id: ResolvedModuleId,
181
+ program: AstNode,
182
+ imports: readonly HostResolvedImport[],
183
+ node: AstNode,
184
+ bailouts: BailoutReason[]
185
+ ): ElementComponentIR | null {
186
+ const literal = asStaticValue(node)
187
+ if (typeof literal === 'string') {
188
+ return {
189
+ kind: 'intrinsic',
190
+ name: literal,
191
+ span: spanOf(id, node),
192
+ closingSpan: null,
193
+ definition: null,
194
+ provenance: null,
195
+ }
196
+ }
197
+
198
+ if (
199
+ (node.type === 'MemberExpression' || node.type === 'JSXMemberExpression') &&
200
+ node.computed !== true
201
+ ) {
202
+ const objectName = identifierName(childNode(node, 'object'))
203
+ const memberName = identifierName(childNode(node, 'property'))
204
+ const binding = objectName ? importBinding(program, objectName) : null
205
+ if (memberName && binding?.imported === 'html') {
206
+ const provenance = hostImportProvenance(imports, binding.source, binding.imported)
207
+ if (provenance) {
208
+ return {
209
+ kind: 'binding',
210
+ name: memberName,
211
+ span: spanOf(id, node),
212
+ closingSpan: null,
213
+ definition: null,
214
+ provenance,
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ const name = identifierName(node)
221
+ if (!name) {
222
+ bailouts.push(
223
+ localBailout(
224
+ 'local/unsupported-element-name',
225
+ spanOf(id, node),
226
+ `Element target ${node.type} is not a stable identifier or intrinsic tag`
227
+ )
228
+ )
229
+ return null
230
+ }
231
+
232
+ const intrinsic = /^[a-z]/.test(name)
233
+ const binding = intrinsic ? null : importBinding(program, name)
234
+ const provenance = binding
235
+ ? hostImportProvenance(imports, binding.source, binding.imported)
236
+ : null
237
+ const definition = intrinsic ? null : symbolDefinition(candidate.definitionOf(id, name))
238
+ if (!intrinsic && !definition && !provenance) {
239
+ bailouts.push(
240
+ linkedBailout(
241
+ 'linked/unresolved-component-binding',
242
+ spanOf(id, node),
243
+ `Component binding ${name} has no linked definition`
244
+ )
245
+ )
246
+ }
247
+ return {
248
+ kind: intrinsic ? 'intrinsic' : 'binding',
249
+ name,
250
+ span: spanOf(id, node),
251
+ closingSpan: null,
252
+ definition,
253
+ provenance,
254
+ }
255
+ }
256
+
257
+ function jsxComponentNode(opening: AstNode): AstNode | null {
258
+ return childNode(opening, 'name')
259
+ }
260
+
261
+ function jsxEntries(
262
+ id: ResolvedModuleId,
263
+ element: AstNode,
264
+ opening: AstNode,
265
+ bailouts: BailoutReason[]
266
+ ): ElementEntryIR[] {
267
+ const entries: ElementEntryIR[] = []
268
+ for (const attribute of childNodes(opening, 'attributes')) {
269
+ if (attribute.type === 'JSXSpreadAttribute') {
270
+ const argument = childNode(attribute, 'argument')
271
+ if (argument) {
272
+ entries.push({
273
+ kind: 'spread',
274
+ span: spanOf(id, attribute),
275
+ value: expressionReference(id, argument),
276
+ })
277
+ }
278
+ continue
279
+ }
280
+ if (attribute.type !== 'JSXAttribute') continue
281
+ const nameNode = childNode(attribute, 'name')
282
+ const name = identifierName(nameNode)
283
+ if (!name) {
284
+ bailouts.push(
285
+ localBailout(
286
+ 'local/unsupported-prop-key',
287
+ spanOf(id, attribute),
288
+ 'Namespaced JSX attributes are not statically supported'
289
+ )
290
+ )
291
+ continue
292
+ }
293
+
294
+ const rawValue = childNode(attribute, 'value')
295
+ let value: ElementValue
296
+ if (!rawValue) {
297
+ value = { kind: 'static', value: true, span: spanOf(id, attribute) }
298
+ } else if (rawValue.type === 'JSXExpressionContainer') {
299
+ const expression = childNode(rawValue, 'expression')
300
+ if (!expression || expression.type === 'JSXEmptyExpression') {
301
+ bailouts.push(
302
+ localBailout(
303
+ 'local/unsupported-expression',
304
+ spanOf(id, rawValue),
305
+ `JSX prop ${name} has no expression`
306
+ )
307
+ )
308
+ continue
309
+ }
310
+ value = elementValue(id, expression)
311
+ } else {
312
+ value = elementValue(id, rawValue)
313
+ }
314
+ entries.push({ kind: 'prop', name, span: spanOf(id, attribute), value })
315
+ }
316
+
317
+ for (const child of childNodes(element, 'children')) {
318
+ let value: ElementChildIR['value']
319
+ if (child.type === 'JSXText') {
320
+ if (typeof child.value !== 'string' || child.value.trim() === '') continue
321
+ value = {
322
+ kind: 'static',
323
+ value: child.value,
324
+ span: spanOf(id, child),
325
+ }
326
+ } else if (child.type === 'JSXExpressionContainer') {
327
+ const expression = childNode(child, 'expression')
328
+ value =
329
+ !expression || expression.type === 'JSXEmptyExpression'
330
+ ? { kind: 'empty', span: spanOf(id, child) }
331
+ : elementValue(id, expression)
332
+ } else if (child.type === 'JSXElement' || child.type === 'JSXFragment') {
333
+ value = { kind: 'element', span: spanOf(id, child) }
334
+ } else {
335
+ bailouts.push(
336
+ localBailout(
337
+ 'local/unsupported-child',
338
+ spanOf(id, child),
339
+ `JSX child ${child.type} cannot be normalized`
340
+ )
341
+ )
342
+ continue
343
+ }
344
+ entries.push({ kind: 'child', span: spanOf(id, child), value })
345
+ }
346
+
347
+ return entries.sort((left, right) => left.span.start - right.span.start)
348
+ }
349
+
350
+ function propertyName(property: AstNode): string | null {
351
+ if (property.computed === true) return null
352
+ const key = childNode(property, 'key')
353
+ const name = identifierName(key)
354
+ if (name) return name
355
+ const literal = asStaticValue(key)
356
+ return typeof literal === 'string' || typeof literal === 'number'
357
+ ? String(literal)
358
+ : null
359
+ }
360
+
361
+ function runtimeChildEntries(
362
+ id: ResolvedModuleId,
363
+ program: AstNode,
364
+ node: AstNode,
365
+ bailouts: BailoutReason[]
366
+ ): ElementChildIR[] {
367
+ const rawNodes = node.type === 'ArrayExpression' ? node.elements : null
368
+ if (
369
+ Array.isArray(rawNodes) &&
370
+ rawNodes.some((child) => child === null || !isAstNode(child))
371
+ ) {
372
+ bailouts.push(
373
+ localBailout(
374
+ 'local/unsupported-child',
375
+ spanOf(id, node),
376
+ 'Sparse compiled children cannot be normalized without changing order'
377
+ )
378
+ )
379
+ return []
380
+ }
381
+ const nodes = Array.isArray(rawNodes) ? rawNodes.filter(isAstNode) : [node]
382
+ return nodes.flatMap((child): ElementChildIR[] => {
383
+ if (child.type === 'SpreadElement') {
384
+ bailouts.push(
385
+ localBailout(
386
+ 'local/unsupported-child',
387
+ spanOf(id, child),
388
+ 'Spread children cannot be statically ordered'
389
+ )
390
+ )
391
+ return []
392
+ }
393
+ if (child.type === 'CallExpression' && runtimeCall(program, child)) {
394
+ return [
395
+ {
396
+ kind: 'child',
397
+ span: spanOf(id, child),
398
+ value: { kind: 'element', span: spanOf(id, child) },
399
+ },
400
+ ]
401
+ }
402
+ return [{ kind: 'child', span: spanOf(id, child), value: elementValue(id, child) }]
403
+ })
404
+ }
405
+
406
+ function objectEntries(
407
+ id: ResolvedModuleId,
408
+ program: AstNode,
409
+ object: AstNode,
410
+ bailouts: BailoutReason[]
411
+ ): ElementEntryIR[] {
412
+ const entries: ElementEntryIR[] = []
413
+ for (const property of childNodes(object, 'properties')) {
414
+ if (property.type === 'SpreadElement') {
415
+ const argument = childNode(property, 'argument')
416
+ if (argument) {
417
+ const spread: ElementSpreadIR = {
418
+ kind: 'spread',
419
+ span: spanOf(id, property),
420
+ value: expressionReference(id, argument),
421
+ }
422
+ entries.push(spread)
423
+ }
424
+ continue
425
+ }
426
+ if (property.type !== 'Property' && property.type !== 'ObjectProperty') {
427
+ bailouts.push(
428
+ localBailout(
429
+ 'local/unsupported-prop-key',
430
+ spanOf(id, property),
431
+ `Runtime props entry ${property.type} is not a data property`
432
+ )
433
+ )
434
+ continue
435
+ }
436
+ const name = propertyName(property)
437
+ const valueNode = childNode(property, 'value')
438
+ if (!name || !valueNode) {
439
+ bailouts.push(
440
+ localBailout(
441
+ 'local/unsupported-prop-key',
442
+ spanOf(id, property),
443
+ 'Computed runtime prop keys are not statically supported'
444
+ )
445
+ )
446
+ continue
447
+ }
448
+ if (name === 'children') {
449
+ entries.push(...runtimeChildEntries(id, program, valueNode, bailouts))
450
+ continue
451
+ }
452
+ const prop: ElementPropIR = {
453
+ kind: 'prop',
454
+ name,
455
+ span: spanOf(id, property),
456
+ value: elementValue(id, valueNode),
457
+ }
458
+ entries.push(prop)
459
+ }
460
+ return entries
461
+ }
462
+
463
+ type RuntimeCall = { form: 'jsx-runtime' | 'create-element' }
464
+
465
+ function runtimeCall(program: AstNode, call: AstNode): RuntimeCall | null {
466
+ const callee = childNode(call, 'callee')
467
+ if (!callee) return null
468
+ const directName = identifierName(callee)
469
+ if (directName) {
470
+ const provenance = importBinding(program, directName)
471
+ if (
472
+ provenance?.source === 'react/jsx-runtime' &&
473
+ (provenance.imported === 'jsx' || provenance.imported === 'jsxs')
474
+ ) {
475
+ return { form: 'jsx-runtime' }
476
+ }
477
+ if (provenance?.source === 'react' && provenance.imported === 'createElement') {
478
+ return { form: 'create-element' }
479
+ }
480
+ }
481
+
482
+ if (callee.type !== 'MemberExpression' || callee.computed === true) return null
483
+ const objectName = identifierName(childNode(callee, 'object'))
484
+ const propertyName = identifierName(childNode(callee, 'property'))
485
+ if (propertyName !== 'createElement' || !objectName) return null
486
+ const provenance = importBinding(program, objectName)
487
+ return provenance?.source === 'react' &&
488
+ (provenance.imported === 'default' || provenance.imported === '*')
489
+ ? { form: 'create-element' }
490
+ : null
491
+ }
492
+
493
+ function normalizeRuntimeElement(
494
+ candidate: AnalyzerCandidate,
495
+ id: ResolvedModuleId,
496
+ program: AstNode,
497
+ imports: readonly HostResolvedImport[],
498
+ call: AstNode,
499
+ runtime: RuntimeCall,
500
+ bailouts: BailoutReason[]
501
+ ): ElementIR | null {
502
+ const bailoutStart = bailouts.length
503
+ const args = childNodes(call, 'arguments')
504
+ const target = args[0]
505
+ if (!target) {
506
+ bailouts.push(
507
+ localBailout(
508
+ 'local/invalid-element-call',
509
+ spanOf(id, call),
510
+ `${runtime.form} call has no element target`
511
+ )
512
+ )
513
+ return null
514
+ }
515
+ const component = componentFromNode(candidate, id, program, imports, target, bailouts)
516
+ if (!component) return null
517
+
518
+ const entries: ElementEntryIR[] = []
519
+ const props = args[1]
520
+ if (props && asStaticValue(props) !== null) {
521
+ if (props.type === 'ObjectExpression') {
522
+ entries.push(...objectEntries(id, program, props, bailouts))
523
+ } else {
524
+ entries.push({
525
+ kind: 'spread',
526
+ span: spanOf(id, props),
527
+ value: expressionReference(id, props),
528
+ })
529
+ }
530
+ }
531
+ if (runtime.form === 'create-element') {
532
+ for (const child of args.slice(2)) {
533
+ entries.push(...runtimeChildEntries(id, program, child, bailouts))
534
+ }
535
+ }
536
+
537
+ return finalizeElement(
538
+ {
539
+ kind: 'element',
540
+ form: runtime.form,
541
+ id,
542
+ span: spanOf(id, call),
543
+ propsSpan: props ? spanOf(id, props) : null,
544
+ component,
545
+ },
546
+ entries.sort((left, right) => left.span.start - right.span.start),
547
+ bailouts.slice(bailoutStart)
548
+ )
549
+ }
550
+
551
+ function styledDefinitions(
552
+ candidate: AnalyzerCandidate,
553
+ id: ResolvedModuleId,
554
+ program: AstNode,
555
+ imports: readonly HostResolvedImport[],
556
+ bailouts: BailoutReason[]
557
+ ): StyledDefinitionIR[] {
558
+ const definitions: StyledDefinitionIR[] = []
559
+ walkAst(program, (node) => {
560
+ if (node.type !== 'VariableDeclarator') return
561
+ const identifier = childNode(node, 'id')
562
+ const name = identifierName(identifier)
563
+ const call = childNode(node, 'init')
564
+ if (!name || !identifier || call?.type !== 'CallExpression') return
565
+ const callee = childNode(call, 'callee')
566
+ const factoryName = callee && identifierName(callee)
567
+ if (!factoryName) return
568
+ const binding = importBinding(program, factoryName)
569
+ if (!binding || binding.imported !== 'styled') return
570
+ const factory = hostImportProvenance(imports, binding.source, binding.imported)
571
+ if (!factory) return
572
+
573
+ const definitionBailouts: BailoutReason[] = []
574
+ const args = childNodes(call, 'arguments')
575
+ const baseNode = args[0]
576
+ const optionsNode = args.length >= 3 ? args[2] : args[1]
577
+ if (!baseNode || !optionsNode) {
578
+ definitionBailouts.push(
579
+ localBailout(
580
+ 'local/unsupported-styled-definition',
581
+ spanOf(id, call),
582
+ `styled definition ${name} must provide a base and options object`
583
+ )
584
+ )
585
+ bailouts.push(...definitionBailouts)
586
+ return
587
+ }
588
+ const base = componentFromNode(
589
+ candidate,
590
+ id,
591
+ program,
592
+ imports,
593
+ baseNode,
594
+ definitionBailouts
595
+ )
596
+ if (!base) {
597
+ bailouts.push(...definitionBailouts)
598
+ return
599
+ }
600
+ const baseClassNode = args.length >= 3 ? args[1] : null
601
+ const baseDefinition = {
602
+ kind: 'styled-definition' as const,
603
+ id,
604
+ name,
605
+ span: spanOf(id, call),
606
+ definitionSpan: spanOf(id, identifier),
607
+ factory,
608
+ base,
609
+ baseClassName: baseClassNode ? elementValue(id, baseClassNode) : null,
610
+ options: expressionReference(id, optionsNode),
611
+ }
612
+ definitions.push(
613
+ definitionBailouts.length === 0
614
+ ? { ...baseDefinition, complete: true, bailouts: [] }
615
+ : {
616
+ ...baseDefinition,
617
+ complete: false,
618
+ bailouts: definitionBailouts as [BailoutReason, ...BailoutReason[]],
619
+ }
620
+ )
621
+ bailouts.push(...definitionBailouts)
622
+ })
623
+ return definitions.sort((left, right) => left.span.start - right.span.start)
624
+ }
625
+
626
+ const DOM_STYLE_FRONTENDS = new Set([
627
+ 'tamagui/dom',
628
+ '@tamagui/core/dom',
629
+ '@tamagui/tailwind',
630
+ ])
631
+
632
+ function domStyleDefinitions(
633
+ id: ResolvedModuleId,
634
+ program: AstNode,
635
+ imports: readonly HostResolvedImport[]
636
+ ): DOMStyleDefinitionIR[] {
637
+ const definitions: DOMStyleDefinitionIR[] = []
638
+ walkAst(program, (node) => {
639
+ if (node.type !== 'VariableDeclarator') return
640
+ const identifier = childNode(node, 'id')
641
+ const name = identifierName(identifier)
642
+ const call = childNode(node, 'init')
643
+ if (!name || !identifier || call?.type !== 'CallExpression') return
644
+ const callee = childNode(call, 'callee')
645
+ const factoryName = callee && identifierName(callee)
646
+ if (!factoryName) return
647
+ const binding = importBinding(program, factoryName)
648
+ if (
649
+ !binding ||
650
+ binding.imported !== 'style' ||
651
+ !DOM_STYLE_FRONTENDS.has(binding.source)
652
+ ) {
653
+ return
654
+ }
655
+ const factory = hostImportProvenance(imports, binding.source, binding.imported)
656
+ const value = childNodes(call, 'arguments')[0]
657
+ if (!factory || !value) return
658
+ definitions.push({
659
+ kind: 'dom-style-definition',
660
+ id,
661
+ name,
662
+ span: spanOf(id, call),
663
+ definitionSpan: spanOf(id, identifier),
664
+ factory,
665
+ value: expressionReference(id, value),
666
+ })
667
+ })
668
+ return definitions.sort((left, right) => left.span.start - right.span.start)
669
+ }
670
+
671
+ function domStyleArgument(
672
+ candidate: AnalyzerCandidate,
673
+ id: ResolvedModuleId,
674
+ program: AstNode,
675
+ node: AstNode
676
+ ): AstNode | null {
677
+ let call = node
678
+ if (node.type === 'Identifier') {
679
+ const name = identifierName(node)
680
+ const definition = name ? candidate.definitionOf(id, name) : null
681
+ if (!definition?.constant || !definition.initializer) return null
682
+ call = definition.initializer
683
+ }
684
+ if (call.type !== 'CallExpression') return null
685
+ const callee = childNode(call, 'callee')
686
+ const factoryName = callee && identifierName(callee)
687
+ if (!factoryName) return null
688
+ const binding = importBinding(program, factoryName)
689
+ if (
690
+ !binding ||
691
+ binding.imported !== 'style' ||
692
+ !DOM_STYLE_FRONTENDS.has(binding.source)
693
+ ) {
694
+ return null
695
+ }
696
+ return childNodes(call, 'arguments')[0] ?? null
697
+ }
698
+
699
+ function domStyleProgram(
700
+ candidate: AnalyzerCandidate,
701
+ id: ResolvedModuleId,
702
+ program: AstNode,
703
+ node: AstNode
704
+ ): DOMStyleProgramIR | null {
705
+ if (node.type !== 'ArrayExpression' || !Array.isArray(node.elements)) {
706
+ const argument = domStyleArgument(candidate, id, program, node)
707
+ return argument
708
+ ? {
709
+ kind: 'dom-style',
710
+ span: spanOf(id, node),
711
+ items: [{ condition: null, value: expressionReference(id, argument) }],
712
+ }
713
+ : null
714
+ }
715
+ const items: DOMStyleProgramIR['items'] = []
716
+ for (const rawItem of node.elements) {
717
+ if (!isAstNode(rawItem)) return null
718
+ let item = rawItem
719
+ let condition: DOMStyleProgramIR['items'][number]['condition'] = null
720
+ if (item.type === 'LogicalExpression' && item.operator === '&&') {
721
+ const left = childNode(item, 'left')
722
+ const right = childNode(item, 'right')
723
+ if (!left || !right) return null
724
+ condition = expressionReference(id, left)
725
+ item = right
726
+ }
727
+ const argument = domStyleArgument(candidate, id, program, item)
728
+ if (!argument) {
729
+ const staticValue = asStaticValue(item)
730
+ if (staticValue === false || staticValue === null) continue
731
+ return null
732
+ }
733
+ items.push({ condition, value: expressionReference(id, argument) })
734
+ }
735
+ return { kind: 'dom-style', span: spanOf(id, node), items }
736
+ }
737
+
738
+ export function normalizeElements(
739
+ candidate: AnalyzerCandidate,
740
+ rawId: string,
741
+ imports: readonly HostResolvedImport[] = []
742
+ ): ElementIRResult {
743
+ const id = resolvedModuleId(rawId)
744
+ const program = candidate.programOf(id)
745
+ const elements: ElementIR[] = []
746
+ const bailouts: BailoutReason[] = []
747
+
748
+ walkAst(program, (node) => {
749
+ if (node.type === 'JSXElement') {
750
+ const bailoutStart = bailouts.length
751
+ const opening = childNode(node, 'openingElement')
752
+ const target = opening && jsxComponentNode(opening)
753
+ if (!opening || !target) return
754
+ const component = componentFromNode(
755
+ candidate,
756
+ id,
757
+ program,
758
+ imports,
759
+ target,
760
+ bailouts
761
+ )
762
+ if (!component) return
763
+ const closingElement = childNode(node, 'closingElement')
764
+ const closingName = closingElement && childNode(closingElement, 'name')
765
+ if (closingName) component.closingSpan = spanOf(id, closingName)
766
+ const entries = jsxEntries(id, node, opening, bailouts)
767
+ for (const entry of entries) {
768
+ if (entry.kind !== 'prop' || entry.name !== 'style') continue
769
+ const raw =
770
+ entry.value.kind === 'expression'
771
+ ? nodeAtSpan(program, entry.value.start, entry.value.end)
772
+ : null
773
+ const styleProgram = raw && domStyleProgram(candidate, id, program, raw)
774
+ const argument = raw && domStyleArgument(candidate, id, program, raw)
775
+ if (styleProgram) entry.value = styleProgram
776
+ else if (argument) entry.value = expressionReference(id, argument)
777
+ }
778
+ elements.push(
779
+ finalizeElement(
780
+ {
781
+ kind: 'element',
782
+ form: 'jsx',
783
+ id,
784
+ span: spanOf(id, node),
785
+ propsSpan: spanOf(id, opening),
786
+ component,
787
+ },
788
+ entries,
789
+ bailouts.slice(bailoutStart)
790
+ )
791
+ )
792
+ return
793
+ }
794
+
795
+ if (node.type !== 'CallExpression') return
796
+ const runtime = runtimeCall(program, node)
797
+ if (!runtime) return
798
+ const element = normalizeRuntimeElement(
799
+ candidate,
800
+ id,
801
+ program,
802
+ imports,
803
+ node,
804
+ runtime,
805
+ bailouts
806
+ )
807
+ if (element) {
808
+ const entries = element.complete ? element.entries : element.bailedEntries
809
+ for (const entry of entries) {
810
+ if (entry.kind !== 'prop' || entry.name !== 'style') continue
811
+ const raw =
812
+ entry.value.kind === 'expression'
813
+ ? nodeAtSpan(program, entry.value.start, entry.value.end)
814
+ : null
815
+ const styleProgram = raw && domStyleProgram(candidate, id, program, raw)
816
+ const argument = raw && domStyleArgument(candidate, id, program, raw)
817
+ if (styleProgram) entry.value = styleProgram
818
+ else if (argument) entry.value = expressionReference(id, argument)
819
+ }
820
+ elements.push(element)
821
+ }
822
+ })
823
+
824
+ return {
825
+ elements: elements.sort((left, right) => left.span.start - right.span.start),
826
+ styledDefinitions: styledDefinitions(candidate, id, program, imports, bailouts),
827
+ domStyleDefinitions: domStyleDefinitions(id, program, imports),
828
+ bailouts: bailouts.sort((left, right) => left.span.start - right.span.start),
829
+ }
830
+ }
831
+
832
+ export function declarationForName(program: AstNode, name: string): AstNode | null {
833
+ return findAstNode(program, (node) => {
834
+ if (node.type !== 'VariableDeclarator') return false
835
+ return identifierName(childNode(node, 'id')) === name
836
+ })
837
+ }
838
+
839
+ export function nodeAtSpan(program: AstNode, start: number, end: number): AstNode | null {
840
+ return findAstNode(program, (node) => node.start === start && node.end === end)
841
+ }
842
+
843
+ export function asAstNode(value: unknown, label: string): AstNode {
844
+ if (!isAstNode(value)) throw new Error(`${label} is not an AST node`)
845
+ return value
846
+ }