@variance-authority/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/artifact.d.ts +47 -0
- package/dist/artifact.js +2 -0
- package/dist/artifact.js.map +1 -0
- package/dist/attribute/because.d.ts +43 -0
- package/dist/attribute/because.js +64 -0
- package/dist/attribute/because.js.map +1 -0
- package/dist/attribute/boundary.d.ts +191 -0
- package/dist/attribute/boundary.js +328 -0
- package/dist/attribute/boundary.js.map +1 -0
- package/dist/attribute/call-site.d.ts +96 -0
- package/dist/attribute/call-site.js +183 -0
- package/dist/attribute/call-site.js.map +1 -0
- package/dist/attribute/component-hash.d.ts +188 -0
- package/dist/attribute/component-hash.js +312 -0
- package/dist/attribute/component-hash.js.map +1 -0
- package/dist/attribute/composition-fixture.d.ts +18 -0
- package/dist/attribute/composition-fixture.js +53 -0
- package/dist/attribute/composition-fixture.js.map +1 -0
- package/dist/attribute/composition.d.ts +182 -0
- package/dist/attribute/composition.js +169 -0
- package/dist/attribute/composition.js.map +1 -0
- package/dist/attribute/control.d.ts +43 -0
- package/dist/attribute/control.js +54 -0
- package/dist/attribute/control.js.map +1 -0
- package/dist/attribute/coverage.d.ts +99 -0
- package/dist/attribute/coverage.js +86 -0
- package/dist/attribute/coverage.js.map +1 -0
- package/dist/attribute/divergence.d.ts +102 -0
- package/dist/attribute/divergence.js +152 -0
- package/dist/attribute/divergence.js.map +1 -0
- package/dist/attribute/index.d.ts +43 -0
- package/dist/attribute/index.js +29 -0
- package/dist/attribute/index.js.map +1 -0
- package/dist/attribute/instability.d.ts +89 -0
- package/dist/attribute/instability.js +183 -0
- package/dist/attribute/instability.js.map +1 -0
- package/dist/attribute/instances.d.ts +145 -0
- package/dist/attribute/instances.js +140 -0
- package/dist/attribute/instances.js.map +1 -0
- package/dist/attribute/locate.d.ts +22 -0
- package/dist/attribute/locate.js +100 -0
- package/dist/attribute/locate.js.map +1 -0
- package/dist/attribute/mask.d.ts +156 -0
- package/dist/attribute/mask.js +201 -0
- package/dist/attribute/mask.js.map +1 -0
- package/dist/attribute/movement.d.ts +211 -0
- package/dist/attribute/movement.js +181 -0
- package/dist/attribute/movement.js.map +1 -0
- package/dist/attribute/region.d.ts +188 -0
- package/dist/attribute/region.js +144 -0
- package/dist/attribute/region.js.map +1 -0
- package/dist/attribute/source-map.d.ts +98 -0
- package/dist/attribute/source-map.js +264 -0
- package/dist/attribute/source-map.js.map +1 -0
- package/dist/attribute/source.d.ts +77 -0
- package/dist/attribute/source.js +95 -0
- package/dist/attribute/source.js.map +1 -0
- package/dist/attribute/stack.d.ts +80 -0
- package/dist/attribute/stack.js +108 -0
- package/dist/attribute/stack.js.map +1 -0
- package/dist/compare/band.d.ts +85 -0
- package/dist/compare/band.js +70 -0
- package/dist/compare/band.js.map +1 -0
- package/dist/compare/cascade.d.ts +45 -0
- package/dist/compare/cascade.js +71 -0
- package/dist/compare/cascade.js.map +1 -0
- package/dist/compare/derive.d.ts +78 -0
- package/dist/compare/derive.js +39 -0
- package/dist/compare/derive.js.map +1 -0
- package/dist/compare/diff/attribution.d.ts +23 -0
- package/dist/compare/diff/attribution.js +244 -0
- package/dist/compare/diff/attribution.js.map +1 -0
- package/dist/compare/diff/compare-nodes.d.ts +15 -0
- package/dist/compare/diff/compare-nodes.js +227 -0
- package/dist/compare/diff/compare-nodes.js.map +1 -0
- package/dist/compare/diff/components.d.ts +20 -0
- package/dist/compare/diff/components.js +76 -0
- package/dist/compare/diff/components.js.map +1 -0
- package/dist/compare/diff/delta.d.ts +165 -0
- package/dist/compare/diff/delta.js +12 -0
- package/dist/compare/diff/delta.js.map +1 -0
- package/dist/compare/diff/index.d.ts +69 -0
- package/dist/compare/diff/index.js +101 -0
- package/dist/compare/diff/index.js.map +1 -0
- package/dist/compare/diff/match.d.ts +37 -0
- package/dist/compare/diff/match.js +167 -0
- package/dist/compare/diff/match.js.map +1 -0
- package/dist/compare/explain.d.ts +23 -0
- package/dist/compare/explain.js +190 -0
- package/dist/compare/explain.js.map +1 -0
- package/dist/compare/holding-diff.d.ts +25 -0
- package/dist/compare/holding-diff.js +74 -0
- package/dist/compare/holding-diff.js.map +1 -0
- package/dist/compare/impact.d.ts +56 -0
- package/dist/compare/impact.js +124 -0
- package/dist/compare/impact.js.map +1 -0
- package/dist/compare/index.d.ts +30 -0
- package/dist/compare/index.js +22 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/compare/instance.d.ts +30 -0
- package/dist/compare/instance.js +145 -0
- package/dist/compare/instance.js.map +1 -0
- package/dist/compare/observability.d.ts +49 -0
- package/dist/compare/observability.js +57 -0
- package/dist/compare/observability.js.map +1 -0
- package/dist/compare/parting.d.ts +179 -0
- package/dist/compare/parting.js +244 -0
- package/dist/compare/parting.js.map +1 -0
- package/dist/compare/slice.d.ts +74 -0
- package/dist/compare/slice.js +79 -0
- package/dist/compare/slice.js.map +1 -0
- package/dist/compare/value.d.ts +60 -0
- package/dist/compare/value.js +111 -0
- package/dist/compare/value.js.map +1 -0
- package/dist/format/accessibility.d.ts +27 -0
- package/dist/format/accessibility.js +17 -0
- package/dist/format/accessibility.js.map +1 -0
- package/dist/format/canonical.d.ts +38 -0
- package/dist/format/canonical.js +65 -0
- package/dist/format/canonical.js.map +1 -0
- package/dist/format/capture.d.ts +212 -0
- package/dist/format/capture.js +2 -0
- package/dist/format/capture.js.map +1 -0
- package/dist/format/document.d.ts +279 -0
- package/dist/format/document.js +65 -0
- package/dist/format/document.js.map +1 -0
- package/dist/format/environment.d.ts +120 -0
- package/dist/format/environment.js +53 -0
- package/dist/format/environment.js.map +1 -0
- package/dist/format/hash.d.ts +45 -0
- package/dist/format/hash.js +41 -0
- package/dist/format/hash.js.map +1 -0
- package/dist/format/holding.d.ts +134 -0
- package/dist/format/holding.js +2 -0
- package/dist/format/holding.js.map +1 -0
- package/dist/format/index.d.ts +39 -0
- package/dist/format/index.js +25 -0
- package/dist/format/index.js.map +1 -0
- package/dist/format/intervention.d.ts +87 -0
- package/dist/format/intervention.js +11 -0
- package/dist/format/intervention.js.map +1 -0
- package/dist/format/profile.d.ts +59 -0
- package/dist/format/profile.js +66 -0
- package/dist/format/profile.js.map +1 -0
- package/dist/format/provenance.d.ts +179 -0
- package/dist/format/provenance.js +222 -0
- package/dist/format/provenance.js.map +1 -0
- package/dist/format/sha256.d.ts +34 -0
- package/dist/format/sha256.js +152 -0
- package/dist/format/sha256.js.map +1 -0
- package/dist/format/snapshot.d.ts +263 -0
- package/dist/format/snapshot.js +2 -0
- package/dist/format/snapshot.js.map +1 -0
- package/dist/format/stabilize.d.ts +190 -0
- package/dist/format/stabilize.js +333 -0
- package/dist/format/stabilize.js.map +1 -0
- package/dist/format/tier.d.ts +19 -0
- package/dist/format/tier.js +11 -0
- package/dist/format/tier.js.map +1 -0
- package/dist/format/value.d.ts +115 -0
- package/dist/format/value.js +182 -0
- package/dist/format/value.js.map +1 -0
- package/dist/format/wiring.d.ts +115 -0
- package/dist/format/wiring.js +60 -0
- package/dist/format/wiring.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/judge/docket.d.ts +85 -0
- package/dist/judge/docket.js +162 -0
- package/dist/judge/docket.js.map +1 -0
- package/dist/judge/fingerprint.d.ts +53 -0
- package/dist/judge/fingerprint.js +144 -0
- package/dist/judge/fingerprint.js.map +1 -0
- package/dist/judge/ignore.d.ts +206 -0
- package/dist/judge/ignore.js +237 -0
- package/dist/judge/ignore.js.map +1 -0
- package/dist/judge/index.d.ts +44 -0
- package/dist/judge/index.js +34 -0
- package/dist/judge/index.js.map +1 -0
- package/dist/judge/inspect.d.ts +148 -0
- package/dist/judge/inspect.js +208 -0
- package/dist/judge/inspect.js.map +1 -0
- package/dist/judge/intent.d.ts +146 -0
- package/dist/judge/intent.js +150 -0
- package/dist/judge/intent.js.map +1 -0
- package/dist/judge/locale.d.ts +111 -0
- package/dist/judge/locale.js +247 -0
- package/dist/judge/locale.js.map +1 -0
- package/dist/judge/scope.d.ts +41 -0
- package/dist/judge/scope.js +47 -0
- package/dist/judge/scope.js.map +1 -0
- package/dist/judge/sensitivity.d.ts +202 -0
- package/dist/judge/sensitivity.js +152 -0
- package/dist/judge/sensitivity.js.map +1 -0
- package/dist/judge/trail.d.ts +122 -0
- package/dist/judge/trail.js +96 -0
- package/dist/judge/trail.js.map +1 -0
- package/dist/judge/verdict.d.ts +39 -0
- package/dist/judge/verdict.js +32 -0
- package/dist/judge/verdict.js.map +1 -0
- package/dist/plan/index.d.ts +12 -0
- package/dist/plan/index.js +11 -0
- package/dist/plan/index.js.map +1 -0
- package/dist/plan/tool.d.ts +85 -0
- package/dist/plan/tool.js +87 -0
- package/dist/plan/tool.js.map +1 -0
- package/dist/relate/graph.d.ts +190 -0
- package/dist/relate/graph.js +234 -0
- package/dist/relate/graph.js.map +1 -0
- package/dist/relate/index.d.ts +16 -0
- package/dist/relate/index.js +16 -0
- package/dist/relate/index.js.map +1 -0
- package/dist/relate/merkle.d.ts +110 -0
- package/dist/relate/merkle.js +246 -0
- package/dist/relate/merkle.js.map +1 -0
- package/dist/relate/reach.d.ts +65 -0
- package/dist/relate/reach.js +111 -0
- package/dist/relate/reach.js.map +1 -0
- package/dist/relate/records.d.ts +129 -0
- package/dist/relate/records.js +112 -0
- package/dist/relate/records.js.map +1 -0
- package/dist/rules/index.d.ts +19 -0
- package/dist/rules/index.js +17 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/normalize/alias.d.ts +50 -0
- package/dist/rules/normalize/alias.js +111 -0
- package/dist/rules/normalize/alias.js.map +1 -0
- package/dist/rules/normalize/cascade.d.ts +85 -0
- package/dist/rules/normalize/cascade.js +324 -0
- package/dist/rules/normalize/cascade.js.map +1 -0
- package/dist/rules/normalize/color.d.ts +30 -0
- package/dist/rules/normalize/color.js +185 -0
- package/dist/rules/normalize/color.js.map +1 -0
- package/dist/rules/normalize/fixture.d.ts +83 -0
- package/dist/rules/normalize/fixture.js +74 -0
- package/dist/rules/normalize/fixture.js.map +1 -0
- package/dist/rules/normalize/index.d.ts +39 -0
- package/dist/rules/normalize/index.js +268 -0
- package/dist/rules/normalize/index.js.map +1 -0
- package/dist/rules/normalize/project.d.ts +42 -0
- package/dist/rules/normalize/project.js +83 -0
- package/dist/rules/normalize/project.js.map +1 -0
- package/dist/rules/normalize/shorthand.d.ts +38 -0
- package/dist/rules/normalize/shorthand.js +388 -0
- package/dist/rules/normalize/shorthand.js.map +1 -0
- package/dist/rules/normalize/value.d.ts +23 -0
- package/dist/rules/normalize/value.js +162 -0
- package/dist/rules/normalize/value.js.map +1 -0
- package/dist/rules/normalize/wrapper.d.ts +16 -0
- package/dist/rules/normalize/wrapper.js +137 -0
- package/dist/rules/normalize/wrapper.js.map +1 -0
- package/dist/rules/ruleset.d.ts +93 -0
- package/dist/rules/ruleset.js +175 -0
- package/dist/rules/ruleset.js.map +1 -0
- package/mark.svg +30 -0
- package/package.json +69 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where one component's nodes stop and the next component's begin.
|
|
3
|
+
*
|
|
4
|
+
* Two relations run upward out of a rendered node and they are not the same
|
|
5
|
+
* relation. **Parent** is what is above it — React's `return` chain, the boxes
|
|
6
|
+
* it ended up inside. **Owner** is what rendered it — React's `_debugOwner`, the
|
|
7
|
+
* code that wrote the element. A layout wrapper is the parent of everything
|
|
8
|
+
* handed to it and the owner of none of it.
|
|
9
|
+
*
|
|
10
|
+
* Both are needed here, for different halves of the same answer:
|
|
11
|
+
*
|
|
12
|
+
* - **Parent decides nesting.** A boundary owns a contiguous region of the
|
|
13
|
+
* document, and what encloses that region is a fact about the tree. Reading
|
|
14
|
+
* the owner for this would produce a boundary set that is not a partition.
|
|
15
|
+
* - **Owner decides membership and naming.** A component's own content is what
|
|
16
|
+
* *it* wrote. Content it was handed is a hole in its output — present, sized,
|
|
17
|
+
* positioned by it, and authored somewhere else.
|
|
18
|
+
*
|
|
19
|
+
* Reading the parent for both — which is what a walk over `owners[0]` does — has
|
|
20
|
+
* two consequences, and they are the two shapes a design system is made of:
|
|
21
|
+
*
|
|
22
|
+
* - **A component that renders only components disappears.** It authors no host
|
|
23
|
+
* node, so it is never any node's nearest enclosing composite, so it is a
|
|
24
|
+
* boundary nowhere. That is every variant wrapper (`DangerButton` returning a
|
|
25
|
+
* `Button`) and every page-level assembly. The information is not missing —
|
|
26
|
+
* the composite is in the chain, one rung up — it is discarded by reading only
|
|
27
|
+
* the head of it. {@link boundaries} enters every rung the chain crosses.
|
|
28
|
+
* - **A container absorbs its caller's content.** `Card` renders one `div` and
|
|
29
|
+
* whatever it was given; hashed by enclosure, its digest moves whenever a
|
|
30
|
+
* caller passes something else, while its props digest — which excludes
|
|
31
|
+
* `children` — says its inputs held. {@link shapeOf} names a child boundary
|
|
32
|
+
* only when this component placed it, and leaves an anonymous hole otherwise.
|
|
33
|
+
*
|
|
34
|
+
* The owner half is a development-build artefact: React populates `_debugOwner`
|
|
35
|
+
* from `element._owner`, and a production bundle does not. Absent, both rules
|
|
36
|
+
* degrade to naming everything, which is the enclosure answer — coarser, never
|
|
37
|
+
* wrong in a new direction.
|
|
38
|
+
*
|
|
39
|
+
* Nothing here is exported from the package. The callers are siblings.
|
|
40
|
+
*/
|
|
41
|
+
/** A node whose provenance chain broke. Not a filler category; see `RootKind`. */
|
|
42
|
+
export const UNATTRIBUTED = '(unattributed)';
|
|
43
|
+
/**
|
|
44
|
+
* Properties whose *computed* value is layout output rather than authored input.
|
|
45
|
+
*
|
|
46
|
+
* The distinction this list exists for is not stylistic. Under a profile with a
|
|
47
|
+
* layout engine the snapshot carries the engine's resolved values, so a block
|
|
48
|
+
* element's computed `height` is whatever its contents made it — and a button
|
|
49
|
+
* two levels down growing by six pixels moves the computed height of every
|
|
50
|
+
* ancestor. Hashed as *style*, that reports every enclosing component as having
|
|
51
|
+
* changed, which is precisely the "area ranks the displaced above the displacer"
|
|
52
|
+
* failure the cause hashes exist to fix, arriving through a different door.
|
|
53
|
+
*
|
|
54
|
+
* Measured on `cases/storybook-case`: one padding edit inside `Button` made
|
|
55
|
+
* `Tokens`, `Stack`, `Card` and the unattributed root all report a moved style
|
|
56
|
+
* hash, so *every* component in every affected story was named a cause.
|
|
57
|
+
*
|
|
58
|
+
* They are folded into `geometry` instead, where "this component's box is a
|
|
59
|
+
* different size" already belongs and where it correctly does not make a cause.
|
|
60
|
+
* Under a profile *without* layout there are no computed values, so these are
|
|
61
|
+
* authored declarations like any other and stay in `style` — that tier has no
|
|
62
|
+
* `geometry` digest to move them to, and a declared `width: 100px` really is the
|
|
63
|
+
* component's own content.
|
|
64
|
+
*/
|
|
65
|
+
export const LAYOUT_OUTPUT = new Set([
|
|
66
|
+
'width',
|
|
67
|
+
'height',
|
|
68
|
+
'min-width',
|
|
69
|
+
'min-height',
|
|
70
|
+
'max-width',
|
|
71
|
+
'max-height',
|
|
72
|
+
// The one nobody guesses, and the one that was actually doing the damage.
|
|
73
|
+
// `transform-origin` computes to half the border box — `512px 41px` — so it
|
|
74
|
+
// moves whenever the box does, on every element, including ones that declare
|
|
75
|
+
// no transform at all. Removing `width` and `height` changed nothing on the
|
|
76
|
+
// case measurement; removing this is what made a padding edit inside `Button`
|
|
77
|
+
// stop naming `Tokens`, `Stack`, `Card` and the root as causes.
|
|
78
|
+
'transform-origin',
|
|
79
|
+
// Chromium returns *used* track sizes here — `"70.39px 953.61px"` — never the
|
|
80
|
+
// author's `auto 1fr`, so nothing declared survives to be lost. That is what
|
|
81
|
+
// makes these two safe on a list, and it is exactly what is not true of the
|
|
82
|
+
// properties named below.
|
|
83
|
+
'grid-template-columns',
|
|
84
|
+
'grid-template-rows',
|
|
85
|
+
]);
|
|
86
|
+
function ownershipOf(node) {
|
|
87
|
+
const owners = node.provenance?.owners;
|
|
88
|
+
if (owners === undefined || owners.length === 0) {
|
|
89
|
+
return { stack: [UNATTRIBUTED], placedBy: [undefined], props: [undefined] };
|
|
90
|
+
}
|
|
91
|
+
const stack = [];
|
|
92
|
+
const placedBy = [];
|
|
93
|
+
const props = [];
|
|
94
|
+
for (let index = owners.length - 1; index >= 0; index -= 1) {
|
|
95
|
+
const frame = owners[index];
|
|
96
|
+
stack.push(frame.name);
|
|
97
|
+
placedBy.push(frame.createdBy);
|
|
98
|
+
props.push(frame.propsDigest);
|
|
99
|
+
}
|
|
100
|
+
// FIXME: what should this name when the author is a slot?
|
|
101
|
+
//
|
|
102
|
+
// Radix `Slot` under `asChild` merges the caller's className onto the child's
|
|
103
|
+
// own host node, so `<Button asChild><Link/></Button>` renders one `<a>` whose
|
|
104
|
+
// author is next/link's `LinkComponent` and whose every styling decision came
|
|
105
|
+
// from `Button`. The owner rule then reports a name out of `node_modules` for a
|
|
106
|
+
// region an edit to `button.tsx` moved, and both halves are individually
|
|
107
|
+
// correct: `Button` really did author no host node, and `LinkComponent` really
|
|
108
|
+
// did write the one that moved.
|
|
109
|
+
//
|
|
110
|
+
// Open, because the naive fix is worse. Preferring the nearest caller whenever
|
|
111
|
+
// the author is unfamiliar would credit every container with its children's
|
|
112
|
+
// changes, which is exactly the enclosure answer the owner rule exists to
|
|
113
|
+
// refuse. Whether a slot is distinguishable from an ordinary hand-off at this
|
|
114
|
+
// rung has not been read — `Slot` is a component like any other from here.
|
|
115
|
+
const author = node.provenance?.createdBy;
|
|
116
|
+
if (author !== undefined && author !== stack.at(-1)) {
|
|
117
|
+
// A slotted region received no props of its own: it is markup, handed over
|
|
118
|
+
// whole. Absent rather than the container's digest, which would claim the
|
|
119
|
+
// author was called with inputs it never saw.
|
|
120
|
+
stack.push(author);
|
|
121
|
+
placedBy.push(author);
|
|
122
|
+
props.push(undefined);
|
|
123
|
+
}
|
|
124
|
+
return { stack, placedBy, props };
|
|
125
|
+
}
|
|
126
|
+
/** How many rungs two stacks agree on, from the outside in. */
|
|
127
|
+
function sharedRungs(a, b) {
|
|
128
|
+
const limit = Math.min(a.length, b.length);
|
|
129
|
+
let shared = 0;
|
|
130
|
+
while (shared < limit && a[shared] === b[shared])
|
|
131
|
+
shared += 1;
|
|
132
|
+
return shared;
|
|
133
|
+
}
|
|
134
|
+
/** Whether a node's own content belongs to this boundary rather than one below. */
|
|
135
|
+
export function holds(boundary, node) {
|
|
136
|
+
const { stack } = ownershipOf(node);
|
|
137
|
+
return stack.length === boundary.rung + 1 && stack[boundary.rung] === boundary.component;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Every boundary root in the subject, in document order.
|
|
141
|
+
*
|
|
142
|
+
* A boundary opens at each rung a node's ownership stack adds to its parent's,
|
|
143
|
+
* plus the subject root. Several can open at one node — a page assembly, the
|
|
144
|
+
* card it returns and the stack inside that card all begin at the same element —
|
|
145
|
+
* which is what makes a component that renders only components visible.
|
|
146
|
+
*
|
|
147
|
+
* Collected in a separate pre-order pass rather than discovered during hashing,
|
|
148
|
+
* so that instance order is document order exactly. Discovering them while
|
|
149
|
+
* walking would order them by boundary depth instead, and "the second instance"
|
|
150
|
+
* would mean something different in a nested tree.
|
|
151
|
+
*/
|
|
152
|
+
export function boundaries(root) {
|
|
153
|
+
const found = [];
|
|
154
|
+
const visit = (node, parent, enclosing, depth) => {
|
|
155
|
+
const { stack, placedBy, props } = ownershipOf(node);
|
|
156
|
+
const opened = parent === null ? 0 : sharedRungs(parent, stack);
|
|
157
|
+
let within = enclosing;
|
|
158
|
+
let below = depth;
|
|
159
|
+
for (let rung = opened; rung < stack.length; rung += 1) {
|
|
160
|
+
const digest = props[rung];
|
|
161
|
+
const placer = placedBy[rung];
|
|
162
|
+
found.push({
|
|
163
|
+
node,
|
|
164
|
+
component: stack[rung],
|
|
165
|
+
...(within === null ? {} : { within }),
|
|
166
|
+
depth: below,
|
|
167
|
+
rung,
|
|
168
|
+
...(digest === undefined ? {} : { props: digest }),
|
|
169
|
+
...(placer === undefined ? {} : { placedBy: placer }),
|
|
170
|
+
});
|
|
171
|
+
within = stack[rung];
|
|
172
|
+
below += 1;
|
|
173
|
+
}
|
|
174
|
+
for (const child of node.children)
|
|
175
|
+
visit(child, stack, within, below);
|
|
176
|
+
};
|
|
177
|
+
visit(root, null, null, 0);
|
|
178
|
+
return found;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* One boundary's content, stopping at nested boundaries.
|
|
182
|
+
*
|
|
183
|
+
* Where a child belongs to another component, this records a placeholder rather
|
|
184
|
+
* than descending. That is the whole design: a component's hash moves when *its
|
|
185
|
+
* own* code changes, and what a child renders internally is not that. Hashing
|
|
186
|
+
* whole subtrees instead would move every ancestor on any leaf edit, and the
|
|
187
|
+
* page root would change on every commit.
|
|
188
|
+
*
|
|
189
|
+
* **The placeholder names the child only when this component placed it.** A
|
|
190
|
+
* component that wrote `<Stack>` owns that choice, and swapping it for a `<Card>`
|
|
191
|
+
* is its own change; a container that was handed a `<Stack>` chose nothing, and
|
|
192
|
+
* naming it would make the container's digest a function of its callers. So a
|
|
193
|
+
* slot is anonymous, and `Card` renders the same bytes on every page that uses
|
|
194
|
+
* it, whatever it was given. The cost is stated in ADR-0035: the *number* of
|
|
195
|
+
* slotted children still reaches the digest, so a caller passing three where it
|
|
196
|
+
* passed two moves the container.
|
|
197
|
+
*
|
|
198
|
+
* Falls back to naming every child where `_debugOwner` is absent, which is the
|
|
199
|
+
* enclosure answer this replaced.
|
|
200
|
+
*
|
|
201
|
+
* Paths are not hashed. A path is an address that shifts when an unrelated
|
|
202
|
+
* sibling is inserted, so hashing one reports a change nobody made.
|
|
203
|
+
*/
|
|
204
|
+
export function shapeOf(boundary, layout, rename) {
|
|
205
|
+
const { node, component, rung } = boundary;
|
|
206
|
+
const style = [];
|
|
207
|
+
const geometry = [];
|
|
208
|
+
const semantics = [];
|
|
209
|
+
const text = [];
|
|
210
|
+
const wiring = [];
|
|
211
|
+
const renders = [];
|
|
212
|
+
const tokens = new Set();
|
|
213
|
+
let nodes = 0;
|
|
214
|
+
const walk = (current) => {
|
|
215
|
+
nodes += 1;
|
|
216
|
+
for (const token of Object.keys(current.tokens ?? {}))
|
|
217
|
+
tokens.add(token);
|
|
218
|
+
const declared = {};
|
|
219
|
+
const measured = {};
|
|
220
|
+
for (const [property, value] of Object.entries(current.style)) {
|
|
221
|
+
const resolved = rename === undefined ? value : rename.style(value);
|
|
222
|
+
if (layout && LAYOUT_OUTPUT.has(property))
|
|
223
|
+
measured[property] = resolved;
|
|
224
|
+
else
|
|
225
|
+
declared[property] = resolved;
|
|
226
|
+
}
|
|
227
|
+
style.push({ style: declared, tokens: renamedTokens(current, rename) });
|
|
228
|
+
// An entry per node, always. `null` for a node with no rect keeps position in
|
|
229
|
+
// the list meaningful: dropping the entry would let two different trees agree
|
|
230
|
+
// by coincidence.
|
|
231
|
+
geometry.push({
|
|
232
|
+
rect: current.rect ? { ...current.rect } : null,
|
|
233
|
+
// Beside the rect, not instead of it. A computed `max-height` that changed
|
|
234
|
+
// while the box did not is still a fact about this element's geometry, and
|
|
235
|
+
// dropping it would make the two digests disagree about what a box is.
|
|
236
|
+
...(Object.keys(measured).length > 0 ? { measured } : {}),
|
|
237
|
+
});
|
|
238
|
+
// One entry per node in each list, always — `null` rather than omitted, for
|
|
239
|
+
// the reason the geometry list gives: dropping an entry lets two different
|
|
240
|
+
// trees agree by coincidence, and here it would let a heading losing its
|
|
241
|
+
// name look like a heading that never had one.
|
|
242
|
+
semantics.push({
|
|
243
|
+
role: current.role ?? null,
|
|
244
|
+
name: current.name ?? null,
|
|
245
|
+
state: (current.state ?? null),
|
|
246
|
+
});
|
|
247
|
+
text.push(current.text ?? null);
|
|
248
|
+
// `null` for a node that reports none, for the reason every list here gives:
|
|
249
|
+
// an omitted entry lets two different trees agree by coincidence. Most nodes
|
|
250
|
+
// report `null` — wiring attaches to a component's root node and not to the
|
|
251
|
+
// elements beneath it (see `wiringOf`), so a boundary's band is a short
|
|
252
|
+
// signal in a long run of nulls, and the nulls are what make its position
|
|
253
|
+
// mean anything.
|
|
254
|
+
wiring.push((current.wiring ?? null));
|
|
255
|
+
return {
|
|
256
|
+
tag: current.tag,
|
|
257
|
+
alias: renamedAlias(current, rename),
|
|
258
|
+
portalled: current.portalled,
|
|
259
|
+
attributes: renamedAttributes(current, rename),
|
|
260
|
+
children: current.children.map((child) => entry(child)),
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Every node inside one boundary carries the same stack, by construction.
|
|
265
|
+
*
|
|
266
|
+
* So the comparison a child is measured against is fixed for the whole walk,
|
|
267
|
+
* and a zero-node boundary — a component that renders only components, whose
|
|
268
|
+
* root node already belongs to the boundary below it — measures against the
|
|
269
|
+
* same thing.
|
|
270
|
+
*/
|
|
271
|
+
const home = ownershipOf(node).stack.slice(0, rung + 1);
|
|
272
|
+
/**
|
|
273
|
+
* A node's contribution: its own content, or a stand-in for what is beneath.
|
|
274
|
+
*
|
|
275
|
+
* The rung a child leaves at is where its stack stops agreeing with `home` —
|
|
276
|
+
* normally the next rung down, and shallower wherever a subject root carries
|
|
277
|
+
* no provenance and the application starts again inside it.
|
|
278
|
+
*/
|
|
279
|
+
const entry = (child) => {
|
|
280
|
+
if (holds(boundary, child))
|
|
281
|
+
return walk(child);
|
|
282
|
+
const own = ownershipOf(child);
|
|
283
|
+
const left = sharedRungs(home, own.stack);
|
|
284
|
+
const beneath = own.stack[left] ?? own.stack.at(-1) ?? UNATTRIBUTED;
|
|
285
|
+
// Kept whichever way the digest goes: the edge is a true fact about the
|
|
286
|
+
// document, and the graph wants it even where the hash must not carry it.
|
|
287
|
+
renders.push(beneath);
|
|
288
|
+
const placer = own.placedBy[left];
|
|
289
|
+
return placer === undefined || placer === component ? { boundary: beneath } : { slot: null };
|
|
290
|
+
};
|
|
291
|
+
const structure = entry(node);
|
|
292
|
+
return {
|
|
293
|
+
structure,
|
|
294
|
+
semantics,
|
|
295
|
+
text,
|
|
296
|
+
style,
|
|
297
|
+
geometry,
|
|
298
|
+
wiring,
|
|
299
|
+
box: node.rect ?? null,
|
|
300
|
+
renders,
|
|
301
|
+
nodes,
|
|
302
|
+
tokens: [...tokens].sort(),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function renamedAlias(node, rename) {
|
|
306
|
+
if (rename === undefined || node.alias === undefined)
|
|
307
|
+
return node.alias;
|
|
308
|
+
return rename.alias(node.alias);
|
|
309
|
+
}
|
|
310
|
+
function renamedAttributes(node, rename) {
|
|
311
|
+
if (rename === undefined)
|
|
312
|
+
return node.attributes;
|
|
313
|
+
const renamed = {};
|
|
314
|
+
for (const [name, value] of Object.entries(node.attributes)) {
|
|
315
|
+
renamed[name] = rename.attribute(name, value);
|
|
316
|
+
}
|
|
317
|
+
return renamed;
|
|
318
|
+
}
|
|
319
|
+
function renamedTokens(node, rename) {
|
|
320
|
+
if (rename === undefined || node.tokens === undefined)
|
|
321
|
+
return node.tokens;
|
|
322
|
+
const renamed = {};
|
|
323
|
+
for (const [name, value] of Object.entries(node.tokens)) {
|
|
324
|
+
renamed[name] = rename.style(value);
|
|
325
|
+
}
|
|
326
|
+
return renamed;
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boundary.js","sourceRoot":"","sources":["../../src/attribute/boundary.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,kFAAkF;AAClF,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC;IACxD,OAAO;IACP,QAAQ;IACR,WAAW;IACX,YAAY;IACZ,WAAW;IACX,YAAY;IACZ,0EAA0E;IAC1E,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,8EAA8E;IAC9E,gEAAgE;IAChE,kBAAkB;IAClB,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,0BAA0B;IAC1B,uBAAuB;IACvB,oBAAoB;CACrB,CAAC,CAAC;AAoDH,SAAS,WAAW,CAAC,IAAkB;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC;IACvC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,OAAO,EAAE,KAAK,EAAE,CAAC,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;IAC9E,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,KAAK,IAAI,KAAK,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAChC,CAAC;IAED,0DAA0D;IAC1D,EAAE;IACF,8EAA8E;IAC9E,+EAA+E;IAC/E,8EAA8E;IAC9E,gFAAgF;IAChF,yEAAyE;IACzE,+EAA+E;IAC/E,gCAAgC;IAChC,EAAE;IACF,+EAA+E;IAC/E,4EAA4E;IAC5E,0EAA0E;IAC1E,8EAA8E;IAC9E,2EAA2E;IAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC;IAC1C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,2EAA2E;QAC3E,0EAA0E;QAC1E,8CAA8C;QAC9C,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACxB,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AACpC,CAAC;AAED,+DAA+D;AAC/D,SAAS,WAAW,CAAC,CAAoB,EAAE,CAAoB;IAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IAC3C,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO,MAAM,GAAG,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,CAAC,CAAC;IAC9D,OAAO,MAAM,CAAC;AAChB,CAAC;AA8BD,mFAAmF;AACnF,MAAM,UAAU,KAAK,CAAC,QAAkB,EAAE,IAAkB;IAC1D,MAAM,EAAE,KAAK,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,SAAS,CAAC;AAC3F,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,UAAU,CAAC,IAAkB;IAC3C,MAAM,KAAK,GAAe,EAAE,CAAC;IAE7B,MAAM,KAAK,GAAG,CACZ,IAAkB,EAClB,MAAgC,EAChC,SAAwB,EACxB,KAAa,EACP,EAAE;QACR,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAEhE,IAAI,MAAM,GAAG,SAAS,CAAC;QACvB,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,KAAK,IAAI,IAAI,GAAG,MAAM,EAAE,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC;YACvD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI;gBACJ,SAAS,EAAE,KAAK,CAAC,IAAI,CAAE;gBACvB,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;gBACtC,KAAK,EAAE,KAAK;gBACZ,IAAI;gBACJ,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;gBAClD,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;aACtD,CAAC,CAAC;YACH,MAAM,GAAG,KAAK,CAAC,IAAI,CAAE,CAAC;YACtB,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ;YAAE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACxE,CAAC,CAAC;IAEF,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3B,OAAO,KAAK,CAAC;AACf,CAAC;AA4DD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,OAAO,CAAC,QAAkB,EAAE,MAAe,EAAE,MAAe;IAC1E,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC;IAC3C,MAAM,KAAK,GAAqB,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAqB,EAAE,CAAC;IACtC,MAAM,SAAS,GAAqB,EAAE,CAAC;IACvC,MAAM,IAAI,GAAqB,EAAE,CAAC;IAClC,MAAM,MAAM,GAAqB,EAAE,CAAC;IACpC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,MAAM,IAAI,GAAG,CAAC,OAAqB,EAAkB,EAAE;QACrD,KAAK,IAAI,CAAC,CAAC;QACX,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzE,MAAM,QAAQ,GAA2B,EAAE,CAAC;QAC5C,MAAM,QAAQ,GAA2B,EAAE,CAAC;QAE5C,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9D,MAAM,QAAQ,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACpE,IAAI,MAAM,IAAI,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;;gBACpE,QAAQ,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;QACrC,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACxE,8EAA8E;QAC9E,8EAA8E;QAC9E,kBAAkB;QAClB,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI;YAC/C,2EAA2E;YAC3E,2EAA2E;YAC3E,uEAAuE;YACvE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1D,CAAC,CAAC;QAEH,4EAA4E;QAC5E,2EAA2E;QAC3E,yEAAyE;QACzE,+CAA+C;QAC/C,SAAS,CAAC,IAAI,CAAC;YACb,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,IAAI;YAC1B,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,IAAI;YAC1B,KAAK,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAmB;SACjD,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;QAChC,6EAA6E;QAC7E,6EAA6E;QAC7E,4EAA4E;QAC5E,wEAAwE;QACxE,0EAA0E;QAC1E,iBAAiB;QACjB,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAmB,CAAC,CAAC;QAExD,OAAO;YACL,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,KAAK,EAAE,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC;YACpC,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,UAAU,EAAE,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC;YAC9C,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;SACxD,CAAC;IACJ,CAAC,CAAC;IAEF;;;;;;;OAOG;IACH,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;IAExD;;;;;;OAMG;IACH,MAAM,KAAK,GAAG,CAAC,KAAmB,EAAkB,EAAE;QACpD,IAAI,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;QAE/C,MAAM,GAAG,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC;QACpE,wEAAwE;QACxE,0EAA0E;QAC1E,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEtB,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAClC,OAAO,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC/F,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO;QACL,SAAS;QACT,SAAS;QACT,IAAI;QACJ,KAAK;QACL,QAAQ;QACR,MAAM;QACN,GAAG,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;QACtB,OAAO;QACP,KAAK;QACL,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE;KAC3B,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,IAAkB,EAAE,MAAe;IACvD,IAAI,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC;IACxE,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,iBAAiB,CACxB,IAAkB,EAClB,MAAe;IAEf,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,UAAU,CAAC;IAEjD,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5D,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,aAAa,CACpB,IAAkB,EAClB,MAAe;IAEf,IAAI,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC;IAE1E,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import type { CanonicalValue } from '../format/canonical.js';\nimport type { Rect } from '../format/capture.js';\nimport type { Digest } from '../format/hash.js';\nimport type { SemanticNode } from '../format/snapshot.js';\n\n/**\n * Where one component's nodes stop and the next component's begin.\n *\n * Two relations run upward out of a rendered node and they are not the same\n * relation. **Parent** is what is above it — React's `return` chain, the boxes\n * it ended up inside. **Owner** is what rendered it — React's `_debugOwner`, the\n * code that wrote the element. A layout wrapper is the parent of everything\n * handed to it and the owner of none of it.\n *\n * Both are needed here, for different halves of the same answer:\n *\n * - **Parent decides nesting.** A boundary owns a contiguous region of the\n * document, and what encloses that region is a fact about the tree. Reading\n * the owner for this would produce a boundary set that is not a partition.\n * - **Owner decides membership and naming.** A component's own content is what\n * *it* wrote. Content it was handed is a hole in its output — present, sized,\n * positioned by it, and authored somewhere else.\n *\n * Reading the parent for both — which is what a walk over `owners[0]` does — has\n * two consequences, and they are the two shapes a design system is made of:\n *\n * - **A component that renders only components disappears.** It authors no host\n * node, so it is never any node's nearest enclosing composite, so it is a\n * boundary nowhere. That is every variant wrapper (`DangerButton` returning a\n * `Button`) and every page-level assembly. The information is not missing —\n * the composite is in the chain, one rung up — it is discarded by reading only\n * the head of it. {@link boundaries} enters every rung the chain crosses.\n * - **A container absorbs its caller's content.** `Card` renders one `div` and\n * whatever it was given; hashed by enclosure, its digest moves whenever a\n * caller passes something else, while its props digest — which excludes\n * `children` — says its inputs held. {@link shapeOf} names a child boundary\n * only when this component placed it, and leaves an anonymous hole otherwise.\n *\n * The owner half is a development-build artefact: React populates `_debugOwner`\n * from `element._owner`, and a production bundle does not. Absent, both rules\n * degrade to naming everything, which is the enclosure answer — coarser, never\n * wrong in a new direction.\n *\n * Nothing here is exported from the package. The callers are siblings.\n */\n\n/** A node whose provenance chain broke. Not a filler category; see `RootKind`. */\nexport const UNATTRIBUTED = '(unattributed)';\n\n/**\n * Properties whose *computed* value is layout output rather than authored input.\n *\n * The distinction this list exists for is not stylistic. Under a profile with a\n * layout engine the snapshot carries the engine's resolved values, so a block\n * element's computed `height` is whatever its contents made it — and a button\n * two levels down growing by six pixels moves the computed height of every\n * ancestor. Hashed as *style*, that reports every enclosing component as having\n * changed, which is precisely the \"area ranks the displaced above the displacer\"\n * failure the cause hashes exist to fix, arriving through a different door.\n *\n * Measured on `cases/storybook-case`: one padding edit inside `Button` made\n * `Tokens`, `Stack`, `Card` and the unattributed root all report a moved style\n * hash, so *every* component in every affected story was named a cause.\n *\n * They are folded into `geometry` instead, where \"this component's box is a\n * different size\" already belongs and where it correctly does not make a cause.\n * Under a profile *without* layout there are no computed values, so these are\n * authored declarations like any other and stay in `style` — that tier has no\n * `geometry` digest to move them to, and a declared `width: 100px` really is the\n * component's own content.\n */\nexport const LAYOUT_OUTPUT: ReadonlySet<string> = new Set([\n 'width',\n 'height',\n 'min-width',\n 'min-height',\n 'max-width',\n 'max-height',\n // The one nobody guesses, and the one that was actually doing the damage.\n // `transform-origin` computes to half the border box — `512px 41px` — so it\n // moves whenever the box does, on every element, including ones that declare\n // no transform at all. Removing `width` and `height` changed nothing on the\n // case measurement; removing this is what made a padding edit inside `Button`\n // stop naming `Tokens`, `Stack`, `Card` and the root as causes.\n 'transform-origin',\n // Chromium returns *used* track sizes here — `\"70.39px 953.61px\"` — never the\n // author's `auto 1fr`, so nothing declared survives to be lost. That is what\n // makes these two safe on a list, and it is exactly what is not true of the\n // properties named below.\n 'grid-template-columns',\n 'grid-template-rows',\n]);\n\n/**\n * Five more shapes leak, and a longer list is the wrong fix. Measured.\n *\n * Editing only a descendant's padding in a real Chromium moves the computed\n * value of an ancestor's `padding-*` (percentage padding in a shrink-to-fit\n * box), `margin-*` (an `auto` margin centring a `fit-content` block),\n * `top`/`right`/`bottom`/`left` (an absolutely positioned element anchored to an\n * edge that moved) and `transform` (a `translate(-50%,-50%)` matrix, the exact\n * sibling of the `transform-origin` case above). Each one makes an enclosing\n * component a false cause.\n *\n * Adding them here was tried and is worse than the disease: with `padding-*` on\n * the list, `causes.test.ts`'s headline case — a padding edit *inside* `Button`\n * — stops naming `Button` at all. For these properties the computed value **is**\n * the authored value in the ordinary case, and a property name cannot tell the\n * two apart. Only a *value* can, and the only place that knows is `resolveStyle`\n * in `rules/normalize/cascade.ts`, which sees both the cascade's winner and the\n * engine's override and currently keeps no record of which superseded which.\n *\n * So the limit is stated rather than half-fixed: a component whose own\n * declarations use percentage padding, auto margins, edge-anchored absolute\n * positioning or a percentage translate can be named a cause by a change that\n * was not its own. The failure is a *false* cause, never a missed one, which is\n * the direction that costs a reader attention rather than a regression.\n */\n\n/**\n * Every component holding a node, outermost first, and who placed each one.\n *\n * The whole chain, not its head. `TodoFooter` rendering nothing but a `Stack`\n * appears here at the rung below `Stack`, which is the only place it appears at\n * all — it authors no element, so no node has it as a nearest enclosing\n * composite, and a walk that reads `owners[0]` cannot see it.\n *\n * `placedBy[i]` is who wrote the element for `stack[i]`: `OwnerFrame.createdBy`,\n * which is the component that decided a `<Stack>` belongs at this point, as\n * distinct from `Stack` itself. Absent per rung on a production build.\n *\n * A host node whose author is not the innermost component is content that was\n * handed in: it sits inside the container's box and belongs to whoever wrote it,\n * so the author is pushed as a further rung. That keeps the boundary set a\n * partition of the document — the slotted region is nested inside the container\n * rather than lifted out of it — while attributing it to the code responsible.\n */\ninterface Ownership {\n readonly stack: readonly string[];\n readonly placedBy: readonly (string | undefined)[];\n readonly props: readonly (Digest | undefined)[];\n}\n\nfunction ownershipOf(node: SemanticNode): Ownership {\n const owners = node.provenance?.owners;\n if (owners === undefined || owners.length === 0) {\n return { stack: [UNATTRIBUTED], placedBy: [undefined], props: [undefined] };\n }\n\n const stack: string[] = [];\n const placedBy: (string | undefined)[] = [];\n const props: (Digest | undefined)[] = [];\n for (let index = owners.length - 1; index >= 0; index -= 1) {\n const frame = owners[index]!;\n stack.push(frame.name);\n placedBy.push(frame.createdBy);\n props.push(frame.propsDigest);\n }\n\n // FIXME: what should this name when the author is a slot?\n //\n // Radix `Slot` under `asChild` merges the caller's className onto the child's\n // own host node, so `<Button asChild><Link/></Button>` renders one `<a>` whose\n // author is next/link's `LinkComponent` and whose every styling decision came\n // from `Button`. The owner rule then reports a name out of `node_modules` for a\n // region an edit to `button.tsx` moved, and both halves are individually\n // correct: `Button` really did author no host node, and `LinkComponent` really\n // did write the one that moved.\n //\n // Open, because the naive fix is worse. Preferring the nearest caller whenever\n // the author is unfamiliar would credit every container with its children's\n // changes, which is exactly the enclosure answer the owner rule exists to\n // refuse. Whether a slot is distinguishable from an ordinary hand-off at this\n // rung has not been read — `Slot` is a component like any other from here.\n const author = node.provenance?.createdBy;\n if (author !== undefined && author !== stack.at(-1)) {\n // A slotted region received no props of its own: it is markup, handed over\n // whole. Absent rather than the container's digest, which would claim the\n // author was called with inputs it never saw.\n stack.push(author);\n placedBy.push(author);\n props.push(undefined);\n }\n\n return { stack, placedBy, props };\n}\n\n/** How many rungs two stacks agree on, from the outside in. */\nfunction sharedRungs(a: readonly string[], b: readonly string[]): number {\n const limit = Math.min(a.length, b.length);\n let shared = 0;\n while (shared < limit && a[shared] === b[shared]) shared += 1;\n return shared;\n}\n\nexport interface Boundary {\n readonly node: SemanticNode;\n readonly component: string;\n /** The enclosing boundary's component, absent on the subject root. */\n readonly within?: string;\n /** Boundaries between this one and the subject root. The root is `0`. */\n readonly depth: number;\n /**\n * This component's rung in the node's ownership stack.\n *\n * Distinct from `depth`, which counts enclosing boundaries. They diverge\n * wherever a subject root has no provenance: the `(unattributed)` container is\n * a boundary at depth 0 holding a stack of one, and the application inside it\n * starts again at rung 0 while sitting at depth 1.\n */\n readonly rung: number;\n /**\n * Digest of the props this boundary received, from its own `OwnerFrame`.\n *\n * Read at this rung rather than from `owners[0]`, which is the innermost\n * component and belongs to whichever boundary is deepest at this node — the\n * distinction only exists because several boundaries can share a root.\n */\n readonly props?: Digest;\n /** The component whose JSX placed this one. Absent on a production build. */\n readonly placedBy?: string;\n}\n\n/** Whether a node's own content belongs to this boundary rather than one below. */\nexport function holds(boundary: Boundary, node: SemanticNode): boolean {\n const { stack } = ownershipOf(node);\n return stack.length === boundary.rung + 1 && stack[boundary.rung] === boundary.component;\n}\n\n/**\n * Every boundary root in the subject, in document order.\n *\n * A boundary opens at each rung a node's ownership stack adds to its parent's,\n * plus the subject root. Several can open at one node — a page assembly, the\n * card it returns and the stack inside that card all begin at the same element —\n * which is what makes a component that renders only components visible.\n *\n * Collected in a separate pre-order pass rather than discovered during hashing,\n * so that instance order is document order exactly. Discovering them while\n * walking would order them by boundary depth instead, and \"the second instance\"\n * would mean something different in a nested tree.\n */\nexport function boundaries(root: SemanticNode): readonly Boundary[] {\n const found: Boundary[] = [];\n\n const visit = (\n node: SemanticNode,\n parent: readonly string[] | null,\n enclosing: string | null,\n depth: number,\n ): void => {\n const { stack, placedBy, props } = ownershipOf(node);\n const opened = parent === null ? 0 : sharedRungs(parent, stack);\n\n let within = enclosing;\n let below = depth;\n for (let rung = opened; rung < stack.length; rung += 1) {\n const digest = props[rung];\n const placer = placedBy[rung];\n found.push({\n node,\n component: stack[rung]!,\n ...(within === null ? {} : { within }),\n depth: below,\n rung,\n ...(digest === undefined ? {} : { props: digest }),\n ...(placer === undefined ? {} : { placedBy: placer }),\n });\n within = stack[rung]!;\n below += 1;\n }\n\n for (const child of node.children) visit(child, stack, within, below);\n };\n\n visit(root, null, null, 0);\n return found;\n}\n\nexport interface Shape {\n readonly structure: CanonicalValue;\n readonly semantics: CanonicalValue;\n readonly text: CanonicalValue;\n readonly style: CanonicalValue;\n readonly geometry: CanonicalValue;\n\n /**\n * How the framework holds this boundary, per node that reports it.\n *\n * Its own band because it answers its own question. `structure` through\n * `geometry` all read what the renderer produced; this reads what the\n * component *is* — its hook shape, its wrappers, the contexts it subscribes\n * to, the keys it is reconciled under. Two components can agree on all five\n * content bands and disagree here, and when they do, they behave differently\n * under every change that follows.\n */\n readonly wiring: CanonicalValue;\n\n /**\n * The box this boundary's root node occupies. Not part of any digest.\n *\n * Beside the digests rather than inside `geometry`, which is the whole subtree:\n * this is the only thing on the record that can say a control grew eight pixels\n * rather than merely stopped being what it was. `null` where nothing was laid out.\n */\n readonly box: Rect | null;\n\n /** Child boundaries encountered, in document order. Not part of any digest. */\n readonly renders: readonly string[];\n /** Nodes this boundary owns, counting its own root. Not part of any digest. */\n readonly nodes: number;\n /**\n * Custom properties this boundary's own nodes resolved through, sorted.\n *\n * Not part of any digest — the *values* are already inside `style`, and the\n * names are carried beside it so a token that moved can be joined to the\n * components that read it. Off the boundary rather than off the subject on\n * purpose: every subject on a themed page resolves through every token in the\n * theme, so a subject-level list names them all and explains nothing.\n */\n readonly tokens: readonly string[];\n}\n\n/**\n * How a value that may carry a structural alias is rewritten before hashing.\n *\n * `undefined` for the per-name hashes, which have always hashed the alias the\n * normalizer assigned and must keep doing so byte for byte. Supplied by the\n * per-instance hashes, which need a boundary-local alias space — see\n * {@link ./instances.js}, where the argument for it lives.\n */\nexport interface Rename {\n readonly attribute: (name: string, value: string) => string;\n readonly style: (value: string) => string;\n readonly alias: (alias: string) => string;\n}\n\n/**\n * One boundary's content, stopping at nested boundaries.\n *\n * Where a child belongs to another component, this records a placeholder rather\n * than descending. That is the whole design: a component's hash moves when *its\n * own* code changes, and what a child renders internally is not that. Hashing\n * whole subtrees instead would move every ancestor on any leaf edit, and the\n * page root would change on every commit.\n *\n * **The placeholder names the child only when this component placed it.** A\n * component that wrote `<Stack>` owns that choice, and swapping it for a `<Card>`\n * is its own change; a container that was handed a `<Stack>` chose nothing, and\n * naming it would make the container's digest a function of its callers. So a\n * slot is anonymous, and `Card` renders the same bytes on every page that uses\n * it, whatever it was given. The cost is stated in ADR-0035: the *number* of\n * slotted children still reaches the digest, so a caller passing three where it\n * passed two moves the container.\n *\n * Falls back to naming every child where `_debugOwner` is absent, which is the\n * enclosure answer this replaced.\n *\n * Paths are not hashed. A path is an address that shifts when an unrelated\n * sibling is inserted, so hashing one reports a change nobody made.\n */\nexport function shapeOf(boundary: Boundary, layout: boolean, rename?: Rename): Shape {\n const { node, component, rung } = boundary;\n const style: CanonicalValue[] = [];\n const geometry: CanonicalValue[] = [];\n const semantics: CanonicalValue[] = [];\n const text: CanonicalValue[] = [];\n const wiring: CanonicalValue[] = [];\n const renders: string[] = [];\n const tokens = new Set<string>();\n let nodes = 0;\n\n const walk = (current: SemanticNode): CanonicalValue => {\n nodes += 1;\n for (const token of Object.keys(current.tokens ?? {})) tokens.add(token);\n const declared: Record<string, string> = {};\n const measured: Record<string, string> = {};\n\n for (const [property, value] of Object.entries(current.style)) {\n const resolved = rename === undefined ? value : rename.style(value);\n if (layout && LAYOUT_OUTPUT.has(property)) measured[property] = resolved;\n else declared[property] = resolved;\n }\n\n style.push({ style: declared, tokens: renamedTokens(current, rename) });\n // An entry per node, always. `null` for a node with no rect keeps position in\n // the list meaningful: dropping the entry would let two different trees agree\n // by coincidence.\n geometry.push({\n rect: current.rect ? { ...current.rect } : null,\n // Beside the rect, not instead of it. A computed `max-height` that changed\n // while the box did not is still a fact about this element's geometry, and\n // dropping it would make the two digests disagree about what a box is.\n ...(Object.keys(measured).length > 0 ? { measured } : {}),\n });\n\n // One entry per node in each list, always — `null` rather than omitted, for\n // the reason the geometry list gives: dropping an entry lets two different\n // trees agree by coincidence, and here it would let a heading losing its\n // name look like a heading that never had one.\n semantics.push({\n role: current.role ?? null,\n name: current.name ?? null,\n state: (current.state ?? null) as CanonicalValue,\n });\n text.push(current.text ?? null);\n // `null` for a node that reports none, for the reason every list here gives:\n // an omitted entry lets two different trees agree by coincidence. Most nodes\n // report `null` — wiring attaches to a component's root node and not to the\n // elements beneath it (see `wiringOf`), so a boundary's band is a short\n // signal in a long run of nulls, and the nulls are what make its position\n // mean anything.\n wiring.push((current.wiring ?? null) as CanonicalValue);\n\n return {\n tag: current.tag,\n alias: renamedAlias(current, rename),\n portalled: current.portalled,\n attributes: renamedAttributes(current, rename),\n children: current.children.map((child) => entry(child)),\n };\n };\n\n /**\n * Every node inside one boundary carries the same stack, by construction.\n *\n * So the comparison a child is measured against is fixed for the whole walk,\n * and a zero-node boundary — a component that renders only components, whose\n * root node already belongs to the boundary below it — measures against the\n * same thing.\n */\n const home = ownershipOf(node).stack.slice(0, rung + 1);\n\n /**\n * A node's contribution: its own content, or a stand-in for what is beneath.\n *\n * The rung a child leaves at is where its stack stops agreeing with `home` —\n * normally the next rung down, and shallower wherever a subject root carries\n * no provenance and the application starts again inside it.\n */\n const entry = (child: SemanticNode): CanonicalValue => {\n if (holds(boundary, child)) return walk(child);\n\n const own = ownershipOf(child);\n const left = sharedRungs(home, own.stack);\n const beneath = own.stack[left] ?? own.stack.at(-1) ?? UNATTRIBUTED;\n // Kept whichever way the digest goes: the edge is a true fact about the\n // document, and the graph wants it even where the hash must not carry it.\n renders.push(beneath);\n\n const placer = own.placedBy[left];\n return placer === undefined || placer === component ? { boundary: beneath } : { slot: null };\n };\n\n const structure = entry(node);\n return {\n structure,\n semantics,\n text,\n style,\n geometry,\n wiring,\n box: node.rect ?? null,\n renders,\n nodes,\n tokens: [...tokens].sort(),\n };\n}\n\nfunction renamedAlias(node: SemanticNode, rename?: Rename): string | undefined {\n if (rename === undefined || node.alias === undefined) return node.alias;\n return rename.alias(node.alias);\n}\n\nfunction renamedAttributes(\n node: SemanticNode,\n rename?: Rename,\n): Readonly<Record<string, string>> {\n if (rename === undefined) return node.attributes;\n\n const renamed: Record<string, string> = {};\n for (const [name, value] of Object.entries(node.attributes)) {\n renamed[name] = rename.attribute(name, value);\n }\n return renamed;\n}\n\nfunction renamedTokens(\n node: SemanticNode,\n rename?: Rename,\n): Readonly<Record<string, string>> | undefined {\n if (rename === undefined || node.tokens === undefined) return node.tokens;\n\n const renamed: Record<string, string> = {};\n for (const [name, value] of Object.entries(node.tokens)) {\n renamed[name] = rename.style(value);\n }\n return renamed;\n}\n"]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Provenance, SourceLocation, StackFrame } from '../format/provenance.js';
|
|
2
|
+
import type { NodePath, SemanticSnapshot } from '../format/snapshot.js';
|
|
3
|
+
/**
|
|
4
|
+
* Spending the frames a page read, to get the file a reviewer opens.
|
|
5
|
+
*
|
|
6
|
+
* This is the half of the zero-install path that cannot happen in the page. A
|
|
7
|
+
* frame names the module the browser was *served*, and turning that into a line
|
|
8
|
+
* in the repository means fetching that module and reading the source map the
|
|
9
|
+
* build already emitted beside it. Fetching is not something a page agent should
|
|
10
|
+
* do — it runs inside somebody else's application, on a budget measured against
|
|
11
|
+
* their render loop — and it is exactly what the collector is for.
|
|
12
|
+
*
|
|
13
|
+
* **The economics are what make this worth doing rather than clever.** Measured
|
|
14
|
+
* on a 4211-node document: every fiber carried a stack, and between them they
|
|
15
|
+
* held **14 distinct call sites**. A hundred-row table writes two thousand cells
|
|
16
|
+
* from one line of JSX. So the work is per *call site*, not per node, and the
|
|
17
|
+
* cache below is not an optimization but the thing that makes the cost bounded —
|
|
18
|
+
* a handful of module fetches for a whole page.
|
|
19
|
+
*
|
|
20
|
+
* **And it is asked on a signal rather than on every capture.** Fourteen sites is
|
|
21
|
+
* a bounded cost, not a free one, and a run that settles every subject on its
|
|
22
|
+
* document digest has nobody to hand a location to: no region was drawn, no
|
|
23
|
+
* finding was raised, and the fetches would answer a question nothing asked. So
|
|
24
|
+
* frames ride the snapshot — they are provenance, and no hash projects
|
|
25
|
+
* provenance — and {@link locateSites} spends them for the handful of nodes a
|
|
26
|
+
* report is about to name. A page whose only change is one button resolves one
|
|
27
|
+
* call site, not fourteen; a page that did not change resolves none.
|
|
28
|
+
*
|
|
29
|
+
* `fetchModule` is injected because `core` may not assume a network (ADR-0013),
|
|
30
|
+
* and because the right way to fetch differs by caller: a browser-driving
|
|
31
|
+
* collector should fetch from the page's own context, where the origin, the
|
|
32
|
+
* cookies and the dev server's module graph are already correct.
|
|
33
|
+
*/
|
|
34
|
+
/** Fetch a module's text, or answer that it cannot be had. Never throws. */
|
|
35
|
+
export type FetchModule = (url: string) => Promise<string | null>;
|
|
36
|
+
export interface CallSiteResolver {
|
|
37
|
+
/** The location that wrote an element, from the frames its fiber carried. */
|
|
38
|
+
locate(frames: readonly StackFrame[]): Promise<SourceLocation | null>;
|
|
39
|
+
/** How many modules were fetched, and how many call sites they answered. */
|
|
40
|
+
readonly stats: CallSiteStats;
|
|
41
|
+
}
|
|
42
|
+
export interface CallSiteStats {
|
|
43
|
+
/** Distinct modules fetched. The number that costs anything. */
|
|
44
|
+
readonly modules: number;
|
|
45
|
+
/** Distinct call sites asked about — the ratio to `modules` is the point. */
|
|
46
|
+
readonly sites: number;
|
|
47
|
+
/** Call sites that resolved to a file. */
|
|
48
|
+
readonly located: number;
|
|
49
|
+
}
|
|
50
|
+
export declare function createCallSiteResolver(fetchModule: FetchModule): CallSiteResolver;
|
|
51
|
+
/**
|
|
52
|
+
* Provenance with its frames spent: `source` filled in, `stack` gone.
|
|
53
|
+
*
|
|
54
|
+
* Returned unchanged when there is nothing to do — which is the common case once
|
|
55
|
+
* a project installs `jsx-source`, and the case a production build is always in.
|
|
56
|
+
* Never throws: a report that names a location for most of its nodes is the
|
|
57
|
+
* normal outcome, and one node's unreachable module must not take the run down.
|
|
58
|
+
*/
|
|
59
|
+
export declare function locateProvenance(provenance: Provenance, resolver: CallSiteResolver): Promise<Provenance>;
|
|
60
|
+
/**
|
|
61
|
+
* Anything a report points at: it names a node, and may already know its line.
|
|
62
|
+
*
|
|
63
|
+
* An `AttributedRegion` and a `Finding` are both this, arrived at from opposite
|
|
64
|
+
* directions — one from a mask, one from an inspection — and both are the *few*.
|
|
65
|
+
* That is the whole reason this shape is worth having rather than two functions:
|
|
66
|
+
* what makes resolution affordable is being asked about a handful of nodes, and
|
|
67
|
+
* a handful is what a region list and a finding list are.
|
|
68
|
+
*/
|
|
69
|
+
export interface LocatableSite {
|
|
70
|
+
readonly path?: NodePath;
|
|
71
|
+
readonly source?: SourceLocation;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The line for each site a report is about to name, and for nothing else.
|
|
75
|
+
*
|
|
76
|
+
* This is the demand side of the zero-install path. The page reads frames off
|
|
77
|
+
* every fiber because reading them is nearly free; turning one into a file is
|
|
78
|
+
* not, and this is the only place that spends it.
|
|
79
|
+
*
|
|
80
|
+
* Three ways a site costs nothing, all of them the common case:
|
|
81
|
+
*
|
|
82
|
+
* - **It has no `path`.** A region no box contains names no node.
|
|
83
|
+
* - **It already has a `source`.** React ≤18 and `@variance-authority/jsx-source`
|
|
84
|
+
* both record the location outright, so there is nothing to resolve. Only
|
|
85
|
+
* React 19, which throws its location away and captures an `Error` instead,
|
|
86
|
+
* reaches the map.
|
|
87
|
+
* - **Its node carried no frames.** A production build captures nothing.
|
|
88
|
+
*
|
|
89
|
+
* And a subject that settled on its document digest never calls this at all,
|
|
90
|
+
* which is the point: no region, no finding, no fetch.
|
|
91
|
+
*
|
|
92
|
+
* Sites are resolved concurrently. The resolver's own cache collapses them onto
|
|
93
|
+
* the modules they share, so two regions in one component cost one fetch.
|
|
94
|
+
*/
|
|
95
|
+
export declare function locateSites<T extends LocatableSite>(sites: readonly T[], snapshot: SemanticSnapshot, resolver: CallSiteResolver): Promise<readonly T[]>;
|
|
96
|
+
//# sourceMappingURL=call-site.d.ts.map
|