@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
package/src/lower.ts ADDED
@@ -0,0 +1,418 @@
1
+ import type { ResolvedModuleId, SourceSpan } from './contracts'
2
+ import { localBailout, type BailoutReason } from './diagnostics'
3
+ import type {
4
+ MaterializedElement,
5
+ MaterializedElementEntry,
6
+ MaterializedModule,
7
+ MaterializedStyledDefinition,
8
+ } from './materialize'
9
+ import {
10
+ sourceContentHash,
11
+ validateSourceEdits,
12
+ type ApplicableLoweredModulePlan,
13
+ type SourceEdit,
14
+ } from './output'
15
+
16
+ export const LOWERED_MODULE_PLAN_VERSION = 2
17
+
18
+ export type CompilerTarget = 'web' | 'native'
19
+
20
+ export interface LoweredModuleStats {
21
+ found: number
22
+ lowered: number
23
+ flattened: number
24
+ styled: number
25
+ bailed: number
26
+ }
27
+
28
+ export interface LoweringComponent {
29
+ /** Canonical resolved module id plus export name, supplied by the host registry. */
30
+ key: string
31
+ canFlatten: boolean
32
+ staticConfig: unknown
33
+ }
34
+
35
+ export interface LoweringCandidateInput {
36
+ id: ResolvedModuleId
37
+ source: string
38
+ target: CompilerTarget
39
+ module: MaterializedModule
40
+ element: MaterializedElement
41
+ styledDefinition: MaterializedStyledDefinition | null
42
+ component: LoweringComponent
43
+ }
44
+
45
+ export interface CandidateImport {
46
+ content: string
47
+ origin: SourceSpan
48
+ }
49
+
50
+ export type LoweringCandidateResult =
51
+ | {
52
+ ok: true
53
+ edits: SourceEdit[]
54
+ css: string[]
55
+ imports: CandidateImport[]
56
+ dependencies?: ResolvedModuleId[]
57
+ diagnostics?: BailoutReason[]
58
+ flattened?: boolean
59
+ }
60
+ | { ok: false; bailout: BailoutReason }
61
+
62
+ /**
63
+ * Tamagui's static adapter supplies stable style primitives and registry data. It does
64
+ * not traverse modules, apply edits, commit partial candidates, or choose a fallback.
65
+ */
66
+ export interface CompilerLoweringHost {
67
+ resolveComponent(
68
+ element: MaterializedElement,
69
+ styledDefinition: MaterializedStyledDefinition | null
70
+ ): LoweringComponent | null
71
+ isStyleProp(name: string, component: LoweringComponent): boolean
72
+ /** The host can retain this dynamic prop while committing other safe candidate edits. */
73
+ canLowerDynamicStyleProp?(
74
+ name: string,
75
+ component: LoweringComponent,
76
+ valueKind?: 'bailout' | 'conditional'
77
+ ): boolean
78
+ lowerCandidate(input: LoweringCandidateInput): LoweringCandidateResult
79
+ /** Development-only source instrumentation for an existing component debug channel. */
80
+ developmentDebugInstrumentation?(
81
+ input: LoweringCandidateInput,
82
+ result: LoweringCandidateResult
83
+ ): SourceEdit[] | undefined
84
+ }
85
+
86
+ export interface LowerModuleOptions {
87
+ projectGeneration: string
88
+ }
89
+
90
+ export interface StructuralModulePassResult {
91
+ module: MaterializedModule
92
+ edits: SourceEdit[]
93
+ imports: CandidateImport[]
94
+ diagnostics: BailoutReason[]
95
+ dependencies: ResolvedModuleId[]
96
+ }
97
+
98
+ export interface StructuralModulePass {
99
+ /** Stable implementation/data hash included in every lowerer cache identity. */
100
+ versionHash: string
101
+ transform(input: {
102
+ module: MaterializedModule
103
+ source: string
104
+ target: CompilerTarget
105
+ }): StructuralModulePassResult
106
+ }
107
+
108
+ export interface LoweredModulePlan extends ApplicableLoweredModulePlan {
109
+ version: typeof LOWERED_MODULE_PLAN_VERSION
110
+ target: CompilerTarget
111
+ inputHash: string
112
+ projectGeneration: string
113
+ structuralPassHash: string
114
+ edits: SourceEdit[]
115
+ css: string
116
+ diagnostics: BailoutReason[]
117
+ dependencies: ResolvedModuleId[]
118
+ stats: LoweredModuleStats
119
+ }
120
+
121
+ export interface LowerModuleInput {
122
+ module: MaterializedModule
123
+ source: string
124
+ target: CompilerTarget
125
+ host: CompilerLoweringHost
126
+ options: LowerModuleOptions
127
+ structuralPass?: StructuralModulePass
128
+ }
129
+
130
+ function compareCodeUnits(left: string, right: string): number {
131
+ return left < right ? -1 : left > right ? 1 : 0
132
+ }
133
+
134
+ /**
135
+ * A diagnostic that stopped its candidate from lowering. Zero mode reports
136
+ * exactly these; the ones recorded next to a successful lowering describe a
137
+ * dropped prop and leave no runtime behind.
138
+ */
139
+ function blocking(reason: BailoutReason): BailoutReason {
140
+ return { ...reason, blocking: true }
141
+ }
142
+
143
+ function diagnostic(
144
+ code: Extract<BailoutReason['code'], `local/${string}`>,
145
+ element: MaterializedElement,
146
+ message: string,
147
+ prop?: string
148
+ ): BailoutReason {
149
+ return {
150
+ ...localBailout(code, element.span, message),
151
+ component: element.component.name,
152
+ specifier: element.component.provenance?.specifier,
153
+ prop,
154
+ }
155
+ }
156
+
157
+ function styledDefinitionFor(
158
+ module: MaterializedModule,
159
+ element: MaterializedElement
160
+ ): MaterializedStyledDefinition | null {
161
+ const definition = element.component.definition
162
+ if (!definition) return null
163
+ return (
164
+ module.styledDefinitions.find(
165
+ (candidate) => candidate.id === definition.id && candidate.name === definition.name
166
+ ) ?? null
167
+ )
168
+ }
169
+
170
+ function unsafeEntry(
171
+ element: MaterializedElement,
172
+ entry: MaterializedElementEntry,
173
+ component: LoweringComponent,
174
+ host: CompilerLoweringHost
175
+ ): BailoutReason | null {
176
+ if (entry.kind === 'spread' && entry.value.kind !== 'static') {
177
+ return diagnostic(
178
+ 'local/unsafe-style-spread',
179
+ element,
180
+ 'Unevaluated spread may change style and duplicate-prop precedence'
181
+ )
182
+ }
183
+ if (
184
+ entry.kind === 'prop' &&
185
+ host.isStyleProp(entry.name, component) &&
186
+ (entry.value.kind === 'bailout' || entry.value.kind === 'conditional') &&
187
+ !host.canLowerDynamicStyleProp?.(entry.name, component, entry.value.kind)
188
+ ) {
189
+ return diagnostic(
190
+ 'local/dynamic-style-value',
191
+ element,
192
+ `Style prop ${entry.name} could not be evaluated`,
193
+ entry.name
194
+ )
195
+ }
196
+ return null
197
+ }
198
+
199
+ function editsAreCandidateLocal(
200
+ element: MaterializedElement,
201
+ edits: readonly SourceEdit[]
202
+ ): boolean {
203
+ return edits.every(
204
+ (edit) =>
205
+ edit.origin.id === element.id &&
206
+ edit.start >= element.span.start &&
207
+ edit.end <= element.span.end
208
+ )
209
+ }
210
+
211
+ function overlapsCommitted(
212
+ edits: readonly SourceEdit[],
213
+ committed: readonly SourceEdit[]
214
+ ) {
215
+ return edits.some((edit) =>
216
+ committed.some((existing) => {
217
+ if (edit.start === edit.end) {
218
+ return edit.start > existing.start && edit.start < existing.end
219
+ }
220
+ if (existing.start === existing.end) {
221
+ return existing.start > edit.start && existing.start < edit.end
222
+ }
223
+ return edit.start < existing.end && existing.start < edit.end
224
+ })
225
+ )
226
+ }
227
+
228
+ /** Builds one JSON-safe plan. Each recognized candidate commits all edits/CSS/imports or none. */
229
+ export function lowerModule({
230
+ module,
231
+ source,
232
+ target,
233
+ host,
234
+ options,
235
+ structuralPass,
236
+ }: LowerModuleInput): LoweredModulePlan {
237
+ const stats: LoweredModuleStats = {
238
+ found: 0,
239
+ lowered: 0,
240
+ flattened: 0,
241
+ styled: 0,
242
+ bailed: 0,
243
+ }
244
+ const structural = structuralPass
245
+ ? structuralPass.transform({ module, source, target })
246
+ : {
247
+ module,
248
+ edits: [],
249
+ imports: [],
250
+ diagnostics: [],
251
+ dependencies: [],
252
+ }
253
+ if (structural.module.id !== module.id) {
254
+ throw new Error(
255
+ `Structural pass changed module identity from ${module.id} to ${structural.module.id}`
256
+ )
257
+ }
258
+ if (structuralPass && !structuralPass.versionHash) {
259
+ throw new Error('Structural pass must provide a non-empty version hash')
260
+ }
261
+ module = structural.module
262
+ validateSourceEdits(source, structural.edits)
263
+ const edits: SourceEdit[] = [...structural.edits]
264
+ // One rule per identifier, first use wins, which is exactly what the runtime
265
+ // does: insertStyleRule's shouldInsertStyleRules skips an identifier already
266
+ // in the sheet (maxToInsert is 1) and appends the rest in first-use order.
267
+ // Emitting a rule once per element instead put the same
268
+ // `._t-1731853650{...}` into the output for every element that used it.
269
+ const css = new Set<string>()
270
+ const diagnostics = [...module.diagnostics, ...structural.diagnostics]
271
+ const dependencies = new Set([...module.dependencies, ...structural.dependencies])
272
+ const imports = new Map<string, CandidateImport>(
273
+ structural.imports.map((candidateImport) => [
274
+ candidateImport.content,
275
+ candidateImport,
276
+ ])
277
+ )
278
+
279
+ for (const element of module.elements) {
280
+ const styledDefinition = styledDefinitionFor(module, element)
281
+ const component = host.resolveComponent(element, styledDefinition)
282
+ if (!component) continue
283
+ stats.found++
284
+
285
+ const input: LoweringCandidateInput = {
286
+ id: module.id,
287
+ source,
288
+ target,
289
+ module,
290
+ element,
291
+ styledDefinition,
292
+ component,
293
+ }
294
+
295
+ const commitDevelopmentDebug = (result: LoweringCandidateResult) => {
296
+ const debugEdits = host.developmentDebugInstrumentation?.(input, result)
297
+ if (!debugEdits) return
298
+ validateSourceEdits(source, debugEdits)
299
+ if (
300
+ !editsAreCandidateLocal(element, debugEdits) ||
301
+ overlapsCommitted(debugEdits, edits)
302
+ ) {
303
+ throw new Error('Development debug instrumentation must stay candidate-local')
304
+ }
305
+ edits.push(...debugEdits)
306
+ }
307
+
308
+ if (!element.complete || (styledDefinition && !styledDefinition.complete)) {
309
+ const reasons = [
310
+ ...element.bailouts.map(blocking),
311
+ ...(styledDefinition?.bailouts ?? []).map(blocking),
312
+ ]
313
+ diagnostics.push(...reasons)
314
+ const reason = reasons[0]
315
+ if (reason) commitDevelopmentDebug({ ok: false, bailout: reason })
316
+ stats.bailed++
317
+ continue
318
+ }
319
+ const unsafe = element.entries
320
+ .map((entry) => unsafeEntry(element, entry, component, host))
321
+ .find((result): result is BailoutReason => result !== null)
322
+ if (unsafe) {
323
+ diagnostics.push(blocking(unsafe))
324
+ commitDevelopmentDebug({ ok: false, bailout: blocking(unsafe) })
325
+ stats.bailed++
326
+ continue
327
+ }
328
+
329
+ let result: LoweringCandidateResult
330
+ try {
331
+ result = host.lowerCandidate(input)
332
+ } catch (error) {
333
+ result = {
334
+ ok: false,
335
+ bailout: diagnostic(
336
+ 'local/style-resolution-failed',
337
+ element,
338
+ `Style resolution failed: ${error instanceof Error ? error.message : String(error)}`
339
+ ),
340
+ }
341
+ }
342
+ if (!result.ok) {
343
+ diagnostics.push(blocking(result.bailout))
344
+ commitDevelopmentDebug(result)
345
+ stats.bailed++
346
+ continue
347
+ }
348
+ try {
349
+ validateSourceEdits(source, result.edits)
350
+ } catch (error) {
351
+ const reason = blocking(
352
+ diagnostic(
353
+ 'local/overlapping-edit',
354
+ element,
355
+ error instanceof Error ? error.message : String(error)
356
+ )
357
+ )
358
+ diagnostics.push(reason)
359
+ commitDevelopmentDebug({ ok: false, bailout: reason })
360
+ stats.bailed++
361
+ continue
362
+ }
363
+ if (
364
+ !editsAreCandidateLocal(element, result.edits) ||
365
+ overlapsCommitted(result.edits, edits)
366
+ ) {
367
+ const reason = blocking(
368
+ diagnostic(
369
+ 'local/overlapping-edit',
370
+ element,
371
+ 'Candidate edits overlap another candidate or escape the element span'
372
+ )
373
+ )
374
+ diagnostics.push(reason)
375
+ commitDevelopmentDebug({ ok: false, bailout: reason })
376
+ stats.bailed++
377
+ continue
378
+ }
379
+
380
+ diagnostics.push(...(result.diagnostics ?? []))
381
+ edits.push(...result.edits)
382
+ commitDevelopmentDebug(result)
383
+ for (const rule of result.css) css.add(rule)
384
+ for (const candidateImport of result.imports) {
385
+ if (!imports.has(candidateImport.content)) {
386
+ imports.set(candidateImport.content, candidateImport)
387
+ }
388
+ }
389
+ for (const dependency of result.dependencies ?? []) dependencies.add(dependency)
390
+ stats.lowered++
391
+ if (result.flattened) stats.flattened++
392
+ if (styledDefinition) stats.styled++
393
+ }
394
+
395
+ for (const candidateImport of imports.values()) {
396
+ edits.push({
397
+ start: source.length,
398
+ end: source.length,
399
+ content: candidateImport.content,
400
+ origin: candidateImport.origin,
401
+ })
402
+ }
403
+
404
+ return {
405
+ version: LOWERED_MODULE_PLAN_VERSION,
406
+ id: module.id,
407
+ target,
408
+ inputHash: module.inputHash,
409
+ sourceHash: sourceContentHash(source),
410
+ projectGeneration: options.projectGeneration,
411
+ structuralPassHash: structuralPass?.versionHash ?? `${target}-noop-v1`,
412
+ edits,
413
+ css: [...css].join(''),
414
+ diagnostics,
415
+ dependencies: [...dependencies].sort(compareCodeUnits),
416
+ stats,
417
+ }
418
+ }