@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/graph.ts
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AnalyzerCandidate,
|
|
5
|
+
CandidateFactory,
|
|
6
|
+
ExpressionReference,
|
|
7
|
+
HostModuleInput,
|
|
8
|
+
HostResolvedImport,
|
|
9
|
+
HostResolvedProject,
|
|
10
|
+
ProjectInput,
|
|
11
|
+
ResolvedModuleId,
|
|
12
|
+
SymbolDefinition,
|
|
13
|
+
SymbolResolver,
|
|
14
|
+
} from './contracts'
|
|
15
|
+
import { expressionReference, resolutionKey, resolvedModuleId } from './contracts'
|
|
16
|
+
import { linkedBailout, localBailout, type BailoutReason } from './diagnostics'
|
|
17
|
+
import {
|
|
18
|
+
evaluateBinding,
|
|
19
|
+
evaluateConditionalExpression,
|
|
20
|
+
evaluateExpression,
|
|
21
|
+
type ConditionalEvaluation,
|
|
22
|
+
type EvaluationResult,
|
|
23
|
+
} from './evaluate'
|
|
24
|
+
import type { ElementIRResult } from './ir'
|
|
25
|
+
import { nodeAtSpan, normalizeElements } from './normalize'
|
|
26
|
+
|
|
27
|
+
interface ModuleRecord {
|
|
28
|
+
input: HostModuleInput
|
|
29
|
+
hash: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface CachedElements {
|
|
33
|
+
hash: string
|
|
34
|
+
result: ElementIRResult
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface GraphInvalidation {
|
|
38
|
+
changed: boolean
|
|
39
|
+
id: ResolvedModuleId
|
|
40
|
+
previousHash: string | null
|
|
41
|
+
contentHash: string | null
|
|
42
|
+
invalidatedIds: ResolvedModuleId[]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function compareIds(left: ResolvedModuleId, right: ResolvedModuleId): number {
|
|
46
|
+
return compareCodeUnits(left, right)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function compareCodeUnits(left: string, right: string): number {
|
|
50
|
+
return left < right ? -1 : left > right ? 1 : 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function stableImports(imports: readonly HostResolvedImport[]): HostResolvedImport[] {
|
|
54
|
+
return [...imports].sort((left, right) => {
|
|
55
|
+
const bySpecifier = compareCodeUnits(left.specifier, right.specifier)
|
|
56
|
+
return bySpecifier || compareCodeUnits(left.resolvedId, right.resolvedId)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function moduleContentHash(input: HostModuleInput): string {
|
|
61
|
+
const hash = createHash('sha256')
|
|
62
|
+
hash.update(input.source)
|
|
63
|
+
hash.update('\0')
|
|
64
|
+
for (const dependency of stableImports(input.imports)) {
|
|
65
|
+
hash.update(dependency.specifier)
|
|
66
|
+
hash.update('\0')
|
|
67
|
+
hash.update(dependency.resolvedId)
|
|
68
|
+
hash.update('\0')
|
|
69
|
+
hash.update(dependency.external ? 'external' : 'internal')
|
|
70
|
+
hash.update('\0')
|
|
71
|
+
}
|
|
72
|
+
return hash.digest('hex')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Long-lived semantic graph. The host owns resolution and supplies every canonical id;
|
|
77
|
+
* this service owns parsing, linking, hashes, reverse edges, IR, and evaluation caches.
|
|
78
|
+
*/
|
|
79
|
+
export class ProjectGraph implements SymbolResolver {
|
|
80
|
+
readonly #files = new Map<string, string>()
|
|
81
|
+
readonly #resolutions = new Map<string, string>()
|
|
82
|
+
readonly #modules = new Map<ResolvedModuleId, ModuleRecord>()
|
|
83
|
+
readonly #dependencies = new Map<ResolvedModuleId, Set<ResolvedModuleId>>()
|
|
84
|
+
readonly #dependents = new Map<ResolvedModuleId, Set<ResolvedModuleId>>()
|
|
85
|
+
readonly #elementCache = new Map<ResolvedModuleId, CachedElements>()
|
|
86
|
+
readonly #candidate: AnalyzerCandidate
|
|
87
|
+
|
|
88
|
+
constructor(factory: CandidateFactory, project: HostResolvedProject) {
|
|
89
|
+
for (const input of project.modules) this.#installHostRecord(input)
|
|
90
|
+
const candidateProject: ProjectInput = {
|
|
91
|
+
files: this.#files,
|
|
92
|
+
resolutions: this.#resolutions,
|
|
93
|
+
}
|
|
94
|
+
this.#candidate = factory.create(candidateProject)
|
|
95
|
+
this.#candidate.link()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
moduleIds(): ResolvedModuleId[] {
|
|
99
|
+
return [...this.#modules.keys()].sort(compareIds)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
contentHash(id: ResolvedModuleId): string | null {
|
|
103
|
+
return this.#modules.get(id)?.hash ?? null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
sourceOf(id: ResolvedModuleId): string {
|
|
107
|
+
const source = this.#modules.get(id)?.input.source
|
|
108
|
+
if (source === undefined) throw new Error(`Unknown host module ${id}`)
|
|
109
|
+
return source
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
dependenciesOf(id: ResolvedModuleId): ResolvedModuleId[] {
|
|
113
|
+
return [...(this.#dependencies.get(id) ?? [])].sort(compareIds)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
dependentsOf(id: ResolvedModuleId): ResolvedModuleId[] {
|
|
117
|
+
return [...(this.#dependents.get(id) ?? [])].sort(compareIds)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
affectedBy(id: ResolvedModuleId): ResolvedModuleId[] {
|
|
121
|
+
const affected = new Set<ResolvedModuleId>([id])
|
|
122
|
+
const queue = [id]
|
|
123
|
+
while (queue.length) {
|
|
124
|
+
const current = queue.shift()!
|
|
125
|
+
for (const dependent of this.#dependents.get(current) ?? []) {
|
|
126
|
+
if (affected.has(dependent)) continue
|
|
127
|
+
affected.add(dependent)
|
|
128
|
+
queue.push(dependent)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return [...affected].sort(compareIds)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
parseCount(id: ResolvedModuleId): number {
|
|
135
|
+
return this.#candidate.parseCount(id)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
resolveBinding(id: ResolvedModuleId, localName: string): SymbolDefinition | null {
|
|
139
|
+
const definition = this.#candidate.definitionOf(id, localName)
|
|
140
|
+
if (!definition) return null
|
|
141
|
+
return {
|
|
142
|
+
id: definition.id,
|
|
143
|
+
name: definition.name,
|
|
144
|
+
span: {
|
|
145
|
+
id: definition.id,
|
|
146
|
+
start: definition.start,
|
|
147
|
+
end: definition.end,
|
|
148
|
+
},
|
|
149
|
+
initializer: definition.initializer
|
|
150
|
+
? expressionReference(definition.id, definition.initializer)
|
|
151
|
+
: null,
|
|
152
|
+
constant: definition.constant,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
expressionNode(reference: ExpressionReference) {
|
|
157
|
+
if (!this.#modules.has(reference.id)) return null
|
|
158
|
+
return nodeAtSpan(
|
|
159
|
+
this.#candidate.programOf(reference.id),
|
|
160
|
+
reference.start,
|
|
161
|
+
reference.end
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
evaluate(reference: ExpressionReference): EvaluationResult {
|
|
166
|
+
return evaluateExpression(this, reference)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
evaluateConditional(reference: ExpressionReference): ConditionalEvaluation | null {
|
|
170
|
+
return evaluateConditionalExpression(this, reference)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
evaluateBinding(id: ResolvedModuleId, localName: string): EvaluationResult {
|
|
174
|
+
return evaluateBinding(this, id, localName)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
elementsOf(id: ResolvedModuleId): ElementIRResult {
|
|
178
|
+
const record = this.#modules.get(id)
|
|
179
|
+
if (!record) throw new Error(`Unknown host module ${id}`)
|
|
180
|
+
const cached = this.#elementCache.get(id)
|
|
181
|
+
if (cached?.hash === record.hash) return cached.result
|
|
182
|
+
const result = normalizeElements(this.#candidate, id, record.input.imports)
|
|
183
|
+
this.#elementCache.set(id, { hash: record.hash, result })
|
|
184
|
+
return result
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
diagnostics(): BailoutReason[] {
|
|
188
|
+
const diagnostics = this.#candidate
|
|
189
|
+
.diagnostics()
|
|
190
|
+
.map((diagnostic) =>
|
|
191
|
+
diagnostic.kind === 'parse'
|
|
192
|
+
? localBailout(
|
|
193
|
+
'local/parse-error',
|
|
194
|
+
{ id: diagnostic.id, start: 0, end: 0 },
|
|
195
|
+
diagnostic.message
|
|
196
|
+
)
|
|
197
|
+
: linkedBailout(
|
|
198
|
+
'linked/unresolved-binding',
|
|
199
|
+
{ id: diagnostic.id, start: 0, end: 0 },
|
|
200
|
+
diagnostic.message
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
for (const [id, record] of this.#modules) {
|
|
204
|
+
for (const dependency of record.input.imports) {
|
|
205
|
+
if (dependency.external) continue
|
|
206
|
+
if (this.#modules.has(dependency.resolvedId)) continue
|
|
207
|
+
diagnostics.push(
|
|
208
|
+
linkedBailout(
|
|
209
|
+
'linked/unresolved-import',
|
|
210
|
+
{ id, start: 0, end: 0 },
|
|
211
|
+
`Host-resolved import ${dependency.specifier} points outside the project graph`,
|
|
212
|
+
dependency.resolvedId
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return diagnostics.sort((left, right) => {
|
|
218
|
+
const byId = compareCodeUnits(left.span.id, right.span.id)
|
|
219
|
+
return (
|
|
220
|
+
byId ||
|
|
221
|
+
left.span.start - right.span.start ||
|
|
222
|
+
compareCodeUnits(left.code, right.code)
|
|
223
|
+
)
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
updateModule(input: HostModuleInput): GraphInvalidation {
|
|
228
|
+
const previous = this.#modules.get(input.id)
|
|
229
|
+
const nextHash = moduleContentHash(input)
|
|
230
|
+
if (previous?.hash === nextHash) {
|
|
231
|
+
return {
|
|
232
|
+
changed: false,
|
|
233
|
+
id: input.id,
|
|
234
|
+
previousHash: previous.hash,
|
|
235
|
+
contentHash: previous.hash,
|
|
236
|
+
invalidatedIds: [],
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const invalidatedIds = this.affectedBy(input.id)
|
|
241
|
+
const previousHash = previous?.hash ?? null
|
|
242
|
+
if (previous) this.#candidate.removeFile(input.id)
|
|
243
|
+
this.#installHostRecord(input)
|
|
244
|
+
this.#candidate.addFile(input.id, input.source)
|
|
245
|
+
this.#candidate.link()
|
|
246
|
+
for (const id of invalidatedIds) this.#elementCache.delete(id)
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
changed: true,
|
|
250
|
+
id: input.id,
|
|
251
|
+
previousHash,
|
|
252
|
+
contentHash: nextHash,
|
|
253
|
+
invalidatedIds,
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
removeModule(id: ResolvedModuleId): GraphInvalidation {
|
|
258
|
+
const previous = this.#modules.get(id)
|
|
259
|
+
if (!previous) {
|
|
260
|
+
return {
|
|
261
|
+
changed: false,
|
|
262
|
+
id,
|
|
263
|
+
previousHash: null,
|
|
264
|
+
contentHash: null,
|
|
265
|
+
invalidatedIds: [],
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const invalidatedIds = this.affectedBy(id)
|
|
269
|
+
this.#candidate.removeFile(id)
|
|
270
|
+
this.#removeOutgoingEdges(id)
|
|
271
|
+
this.#removeResolutions(id)
|
|
272
|
+
this.#modules.delete(id)
|
|
273
|
+
this.#files.delete(id)
|
|
274
|
+
this.#candidate.link()
|
|
275
|
+
for (const invalidated of invalidatedIds) this.#elementCache.delete(invalidated)
|
|
276
|
+
return {
|
|
277
|
+
changed: true,
|
|
278
|
+
id,
|
|
279
|
+
previousHash: previous.hash,
|
|
280
|
+
contentHash: null,
|
|
281
|
+
invalidatedIds,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
#installHostRecord(input: HostModuleInput): void {
|
|
286
|
+
resolvedModuleId(input.id)
|
|
287
|
+
const seenSpecifiers = new Set<string>()
|
|
288
|
+
for (const dependency of input.imports) {
|
|
289
|
+
resolvedModuleId(dependency.resolvedId)
|
|
290
|
+
if (seenSpecifiers.has(dependency.specifier)) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Duplicate host resolution for ${input.id}:${dependency.specifier}`
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
seenSpecifiers.add(dependency.specifier)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this.#removeOutgoingEdges(input.id)
|
|
299
|
+
this.#removeResolutions(input.id)
|
|
300
|
+
const dependencies = new Set(
|
|
301
|
+
input.imports
|
|
302
|
+
.filter(({ external }) => !external)
|
|
303
|
+
.map(({ resolvedId }) => resolvedId)
|
|
304
|
+
)
|
|
305
|
+
this.#dependencies.set(input.id, dependencies)
|
|
306
|
+
for (const dependency of dependencies) {
|
|
307
|
+
let reverse = this.#dependents.get(dependency)
|
|
308
|
+
if (!reverse) {
|
|
309
|
+
reverse = new Set()
|
|
310
|
+
this.#dependents.set(dependency, reverse)
|
|
311
|
+
}
|
|
312
|
+
reverse.add(input.id)
|
|
313
|
+
}
|
|
314
|
+
for (const dependency of input.imports) {
|
|
315
|
+
if (dependency.external) continue
|
|
316
|
+
this.#resolutions.set(
|
|
317
|
+
resolutionKey(input.id, dependency.specifier),
|
|
318
|
+
dependency.resolvedId
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
const installedInput: HostModuleInput = {
|
|
322
|
+
...input,
|
|
323
|
+
imports: input.imports.map((dependency) => ({ ...dependency })),
|
|
324
|
+
}
|
|
325
|
+
this.#files.set(installedInput.id, installedInput.source)
|
|
326
|
+
this.#modules.set(installedInput.id, {
|
|
327
|
+
input: installedInput,
|
|
328
|
+
hash: moduleContentHash(installedInput),
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
#removeOutgoingEdges(id: ResolvedModuleId): void {
|
|
333
|
+
for (const dependency of this.#dependencies.get(id) ?? []) {
|
|
334
|
+
const reverse = this.#dependents.get(dependency)
|
|
335
|
+
reverse?.delete(id)
|
|
336
|
+
if (reverse?.size === 0) this.#dependents.delete(dependency)
|
|
337
|
+
}
|
|
338
|
+
this.#dependencies.delete(id)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
#removeResolutions(id: ResolvedModuleId): void {
|
|
342
|
+
const prefix = `${id}\0`
|
|
343
|
+
for (const key of this.#resolutions.keys()) {
|
|
344
|
+
if (key.startsWith(prefix)) this.#resolutions.delete(key)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
package/src/hash.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
/** The one content hash every compiler cache identity is built from. */
|
|
4
|
+
export function contentHash(value: string | Uint8Array): string {
|
|
5
|
+
return createHash('sha256').update(value).digest('hex')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function compareCodeUnits(left: string, right: string): number {
|
|
9
|
+
return left < right ? -1 : left > right ? 1 : 0
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function stringify(value: unknown): string | undefined {
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
return `[${value.map((child) => stringify(child) ?? 'null').join(',')}]`
|
|
15
|
+
}
|
|
16
|
+
if (value && typeof value === 'object') {
|
|
17
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
18
|
+
.sort(([left], [right]) => compareCodeUnits(left, right))
|
|
19
|
+
.map(([key, child]) => [key, stringify(child)] as const)
|
|
20
|
+
.filter((entry): entry is readonly [string, string] => entry[1] !== undefined)
|
|
21
|
+
return `{${entries
|
|
22
|
+
.map(([key, child]) => `${JSON.stringify(key)}:${child}`)
|
|
23
|
+
.join(',')}}`
|
|
24
|
+
}
|
|
25
|
+
return JSON.stringify(value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Key-order-independent JSON. Cache identities are hashed from this, so two
|
|
30
|
+
* objects that differ only in property order must serialize identically.
|
|
31
|
+
*/
|
|
32
|
+
export function stableStringify(value: unknown): string {
|
|
33
|
+
const serialized = stringify(value)
|
|
34
|
+
if (serialized === undefined) throw new Error('Cannot serialize undefined cache root')
|
|
35
|
+
return serialized
|
|
36
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './ast'
|
|
2
|
+
export * from './contracts'
|
|
3
|
+
export * from './diagnostics'
|
|
4
|
+
export * from './evaluate'
|
|
5
|
+
export * from './graph'
|
|
6
|
+
export * from './hash'
|
|
7
|
+
export * from './ir'
|
|
8
|
+
export * from './lower'
|
|
9
|
+
export * from './materialize'
|
|
10
|
+
export * from './output'
|
|
11
|
+
export * from './planCache'
|
|
12
|
+
export * from './session'
|
|
13
|
+
export { normalizeElements } from './normalize'
|
|
14
|
+
export * from './zero'
|
|
15
|
+
export { parseModuleAst, yukuFactory } from './yuku'
|
package/src/ir.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExpressionReference,
|
|
3
|
+
HostResolvedImport,
|
|
4
|
+
ResolvedModuleId,
|
|
5
|
+
SourceSpan,
|
|
6
|
+
SymbolDefinition,
|
|
7
|
+
} from './contracts'
|
|
8
|
+
import type { BailoutReason } from './diagnostics'
|
|
9
|
+
|
|
10
|
+
export type ElementForm = 'jsx' | 'jsx-runtime' | 'create-element'
|
|
11
|
+
|
|
12
|
+
export type StaticValue = string | number | boolean | null
|
|
13
|
+
|
|
14
|
+
export type ElementValue =
|
|
15
|
+
| { kind: 'static'; value: StaticValue; span: SourceSpan }
|
|
16
|
+
| ExpressionReference
|
|
17
|
+
| DOMStyleProgramIR
|
|
18
|
+
|
|
19
|
+
export interface DOMStyleProgramIR {
|
|
20
|
+
kind: 'dom-style'
|
|
21
|
+
span: SourceSpan
|
|
22
|
+
items: {
|
|
23
|
+
condition: ExpressionReference | null
|
|
24
|
+
value: ExpressionReference
|
|
25
|
+
}[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ComponentImportProvenance {
|
|
29
|
+
specifier: string
|
|
30
|
+
importedName: string
|
|
31
|
+
resolvedId: ResolvedModuleId
|
|
32
|
+
external: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ElementComponentIR {
|
|
36
|
+
kind: 'binding' | 'intrinsic'
|
|
37
|
+
name: string
|
|
38
|
+
span: SourceSpan
|
|
39
|
+
closingSpan: SourceSpan | null
|
|
40
|
+
definition: SymbolDefinition | null
|
|
41
|
+
provenance: ComponentImportProvenance | null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ElementPropIR {
|
|
45
|
+
kind: 'prop'
|
|
46
|
+
name: string
|
|
47
|
+
span: SourceSpan
|
|
48
|
+
value: ElementValue
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ElementSpreadIR {
|
|
52
|
+
kind: 'spread'
|
|
53
|
+
span: SourceSpan
|
|
54
|
+
value: ExpressionReference
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ElementChildIR {
|
|
58
|
+
kind: 'child'
|
|
59
|
+
span: SourceSpan
|
|
60
|
+
value:
|
|
61
|
+
| ElementValue
|
|
62
|
+
| { kind: 'element'; span: SourceSpan }
|
|
63
|
+
| { kind: 'empty'; span: SourceSpan }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Entries remain in source order. Consumers must apply duplicate props in this order;
|
|
68
|
+
* the last statically evaluated value wins exactly as it does at runtime.
|
|
69
|
+
*/
|
|
70
|
+
export type ElementEntryIR = ElementPropIR | ElementSpreadIR | ElementChildIR
|
|
71
|
+
|
|
72
|
+
export interface ElementIRBase {
|
|
73
|
+
kind: 'element'
|
|
74
|
+
form: ElementForm
|
|
75
|
+
id: ResolvedModuleId
|
|
76
|
+
span: SourceSpan
|
|
77
|
+
/** JSX opening element or compiled props argument, used for punctuation-safe edits. */
|
|
78
|
+
propsSpan: SourceSpan | null
|
|
79
|
+
component: ElementComponentIR
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface CompleteElementIR extends ElementIRBase {
|
|
83
|
+
complete: true
|
|
84
|
+
entries: ElementEntryIR[]
|
|
85
|
+
bailouts: readonly []
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface IncompleteElementIR extends ElementIRBase {
|
|
89
|
+
complete: false
|
|
90
|
+
/** Preserved only for diagnostics; incomplete entries must never be optimized. */
|
|
91
|
+
bailedEntries: ElementEntryIR[]
|
|
92
|
+
bailouts: readonly [BailoutReason, ...BailoutReason[]]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type ElementIR = CompleteElementIR | IncompleteElementIR
|
|
96
|
+
|
|
97
|
+
export interface StyledDefinitionIRBase {
|
|
98
|
+
kind: 'styled-definition'
|
|
99
|
+
id: ResolvedModuleId
|
|
100
|
+
name: string
|
|
101
|
+
span: SourceSpan
|
|
102
|
+
definitionSpan: SourceSpan
|
|
103
|
+
factory: ComponentImportProvenance
|
|
104
|
+
base: ElementComponentIR
|
|
105
|
+
baseClassName: ElementValue | null
|
|
106
|
+
options: ExpressionReference
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface CompleteStyledDefinitionIR extends StyledDefinitionIRBase {
|
|
110
|
+
complete: true
|
|
111
|
+
bailouts: readonly []
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface IncompleteStyledDefinitionIR extends StyledDefinitionIRBase {
|
|
115
|
+
complete: false
|
|
116
|
+
bailouts: readonly [BailoutReason, ...BailoutReason[]]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type StyledDefinitionIR = CompleteStyledDefinitionIR | IncompleteStyledDefinitionIR
|
|
120
|
+
|
|
121
|
+
/** A compile-time style handle declared by the standalone DOM frontend. */
|
|
122
|
+
export interface DOMStyleDefinitionIR {
|
|
123
|
+
kind: 'dom-style-definition'
|
|
124
|
+
id: ResolvedModuleId
|
|
125
|
+
name: string
|
|
126
|
+
/** Span of the complete style(...) call. */
|
|
127
|
+
span: SourceSpan
|
|
128
|
+
definitionSpan: SourceSpan
|
|
129
|
+
factory: ComponentImportProvenance
|
|
130
|
+
value: ExpressionReference
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface ElementIRResult {
|
|
134
|
+
elements: ElementIR[]
|
|
135
|
+
styledDefinitions: StyledDefinitionIR[]
|
|
136
|
+
domStyleDefinitions: DOMStyleDefinitionIR[]
|
|
137
|
+
bailouts: BailoutReason[]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function hostImportProvenance(
|
|
141
|
+
imports: readonly HostResolvedImport[],
|
|
142
|
+
specifier: string,
|
|
143
|
+
importedName: string
|
|
144
|
+
): ComponentImportProvenance | null {
|
|
145
|
+
const resolution = imports.find((candidate) => candidate.specifier === specifier)
|
|
146
|
+
return resolution
|
|
147
|
+
? {
|
|
148
|
+
specifier,
|
|
149
|
+
importedName,
|
|
150
|
+
resolvedId: resolution.resolvedId,
|
|
151
|
+
external: resolution.external === true,
|
|
152
|
+
}
|
|
153
|
+
: null
|
|
154
|
+
}
|