@tamagui/compiler-core 0.0.0-bootstrap.0 → 3.0.0-beta.643.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.
- package/LICENSE +21 -0
- package/README.md +57 -1
- package/dist/cjs/ast.cjs +95 -0
- package/dist/cjs/contracts.cjs +62 -0
- package/dist/cjs/diagnostics.cjs +69 -0
- package/dist/cjs/evaluate.cjs +351 -0
- package/dist/cjs/graph.cjs +279 -0
- package/dist/cjs/hash.cjs +49 -0
- package/dist/cjs/index.cjs +44 -0
- package/dist/cjs/ir.cjs +33 -0
- package/dist/cjs/lower.cjs +225 -0
- package/dist/cjs/materialize.cjs +200 -0
- package/dist/cjs/normalize.cjs +622 -0
- package/dist/cjs/output.cjs +149 -0
- package/dist/cjs/planCache.cjs +224 -0
- package/dist/cjs/session.cjs +119 -0
- package/dist/cjs/yuku.cjs +159 -0
- package/dist/cjs/zero.cjs +387 -0
- package/dist/esm/ast.mjs +65 -0
- package/dist/esm/ast.mjs.map +1 -0
- package/dist/esm/contracts.mjs +34 -0
- package/dist/esm/contracts.mjs.map +1 -0
- package/dist/esm/diagnostics.mjs +44 -0
- package/dist/esm/diagnostics.mjs.map +1 -0
- package/dist/esm/evaluate.mjs +327 -0
- package/dist/esm/evaluate.mjs.map +1 -0
- package/dist/esm/graph.mjs +256 -0
- package/dist/esm/graph.mjs.map +1 -0
- package/dist/esm/hash.mjs +26 -0
- package/dist/esm/hash.mjs.map +1 -0
- package/dist/esm/index.mjs +30 -0
- package/dist/esm/ir.mjs +12 -0
- package/dist/esm/ir.mjs.map +1 -0
- package/dist/esm/lower.mjs +202 -0
- package/dist/esm/lower.mjs.map +1 -0
- package/dist/esm/materialize.mjs +179 -0
- package/dist/esm/materialize.mjs.map +1 -0
- package/dist/esm/normalize.mjs +596 -0
- package/dist/esm/normalize.mjs.map +1 -0
- package/dist/esm/output.mjs +119 -0
- package/dist/esm/output.mjs.map +1 -0
- package/dist/esm/planCache.mjs +194 -0
- package/dist/esm/planCache.mjs.map +1 -0
- package/dist/esm/session.mjs +99 -0
- package/dist/esm/session.mjs.map +1 -0
- package/dist/esm/yuku.mjs +136 -0
- package/dist/esm/yuku.mjs.map +1 -0
- package/dist/esm/zero.mjs +352 -0
- package/dist/esm/zero.mjs.map +1 -0
- package/package.json +42 -4
- package/src/ast.ts +86 -0
- package/src/contracts.ts +148 -0
- package/src/diagnostics.ts +96 -0
- package/src/evaluate.ts +497 -0
- package/src/graph.ts +347 -0
- package/src/hash.ts +36 -0
- package/src/index.ts +15 -0
- package/src/ir.ts +154 -0
- package/src/lower.ts +418 -0
- package/src/materialize.ts +330 -0
- package/src/normalize.ts +846 -0
- package/src/output.ts +175 -0
- package/src/planCache.ts +327 -0
- package/src/session.ts +172 -0
- package/src/yuku.ts +189 -0
- package/src/zero.ts +598 -0
- package/types/ast.d.ts +10 -0
- package/types/ast.d.ts.map +1 -0
- package/types/contracts.d.ts +97 -0
- package/types/contracts.d.ts.map +1 -0
- package/types/diagnostics.d.ts +31 -0
- package/types/diagnostics.d.ts.map +1 -0
- package/types/evaluate.d.ts +36 -0
- package/types/evaluate.d.ts.map +1 -0
- package/types/graph.d.ts +37 -0
- package/types/graph.d.ts.map +1 -0
- package/types/hash.d.ts +8 -0
- package/types/hash.d.ts.map +1 -0
- package/types/index.d.ts +16 -0
- package/types/index.d.ts.map +1 -0
- package/types/ir.d.ts +118 -0
- package/types/ir.d.ts.map +1 -0
- package/types/lower.d.ts +99 -0
- package/types/lower.d.ts.map +1 -0
- package/types/materialize.d.ts +104 -0
- package/types/materialize.d.ts.map +1 -0
- package/types/normalize.d.ts +8 -0
- package/types/normalize.d.ts.map +1 -0
- package/types/output.d.ts +32 -0
- package/types/output.d.ts.map +1 -0
- package/types/planCache.d.ts +113 -0
- package/types/planCache.d.ts.map +1 -0
- package/types/session.d.ts +44 -0
- package/types/session.d.ts.map +1 -0
- package/types/yuku.d.ts +4 -0
- package/types/yuku.d.ts.map +1 -0
- package/types/zero.d.ts +98 -0
- package/types/zero.d.ts.map +1 -0
package/src/normalize.ts
ADDED
|
@@ -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
|
+
}
|