@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,183 @@
|
|
|
1
|
+
import { inlineSourceMapOf, originalPositionFor, parseSourceMap, sourceMappingUrlOf, } from './source-map.js';
|
|
2
|
+
import { isVendorPath, writerLocationOf } from './stack.js';
|
|
3
|
+
export function createCallSiteResolver(fetchModule) {
|
|
4
|
+
/** Module URL → its map, `null` once we know it has none. Also the in-flight guard. */
|
|
5
|
+
const maps = new Map();
|
|
6
|
+
/** `url:line:column` → the answer, so a page's 4211 nodes cost 14 resolutions. */
|
|
7
|
+
const sites = new Map();
|
|
8
|
+
const stats = { modules: 0, sites: 0, located: 0 };
|
|
9
|
+
async function mapsFor(url) {
|
|
10
|
+
const cached = maps.get(url);
|
|
11
|
+
if (cached !== undefined)
|
|
12
|
+
return cached;
|
|
13
|
+
const pending = (async () => {
|
|
14
|
+
stats.modules += 1;
|
|
15
|
+
const code = await fetchModule(url);
|
|
16
|
+
if (code === null)
|
|
17
|
+
return null;
|
|
18
|
+
const annotation = sourceMappingUrlOf(code);
|
|
19
|
+
if (annotation === null)
|
|
20
|
+
return null;
|
|
21
|
+
const inline = inlineSourceMapOf(annotation);
|
|
22
|
+
if (inline !== null)
|
|
23
|
+
return parseSourceMap(inline);
|
|
24
|
+
// A sibling file, which is what a production bundler writes. Resolved
|
|
25
|
+
// against the module rather than the page: `//# sourceMappingURL=out.js.map`
|
|
26
|
+
// means "beside this module", and a page several directories deep would
|
|
27
|
+
// otherwise ask for it in the wrong place.
|
|
28
|
+
const sibling = absolute(annotation, url);
|
|
29
|
+
const text = sibling === null ? null : await fetchModule(sibling);
|
|
30
|
+
return text === null ? null : parseSourceMap(text);
|
|
31
|
+
})();
|
|
32
|
+
maps.set(url, pending);
|
|
33
|
+
return pending;
|
|
34
|
+
}
|
|
35
|
+
async function locateFrame(frame) {
|
|
36
|
+
const parsed = await mapsFor(frame.url);
|
|
37
|
+
// No map is not failure. A dev server serving a plain `.js` as written, and a
|
|
38
|
+
// Node test runner — which applies source maps to `Error.stack` itself, so
|
|
39
|
+
// the frame arrives already original — both land here with the answer in
|
|
40
|
+
// hand. `writerLocationOf` keeps the frame's own coordinates in that case.
|
|
41
|
+
if (parsed === null || parsed.length === 0)
|
|
42
|
+
return null;
|
|
43
|
+
const original = originalPositionFor(parsed, frame.line, frame.column);
|
|
44
|
+
if (original === null)
|
|
45
|
+
return null;
|
|
46
|
+
return {
|
|
47
|
+
file: sourcePath(original.source, frame.url),
|
|
48
|
+
line: original.line,
|
|
49
|
+
column: original.column,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function locate(frames) {
|
|
53
|
+
for (const frame of frames) {
|
|
54
|
+
if (isVendorPath(frame.url))
|
|
55
|
+
continue;
|
|
56
|
+
const key = `${frame.url}:${frame.line}:${frame.column}`;
|
|
57
|
+
let answer = sites.get(key);
|
|
58
|
+
if (answer === undefined) {
|
|
59
|
+
stats.sites += 1;
|
|
60
|
+
answer = locateFrame(frame);
|
|
61
|
+
sites.set(key, answer);
|
|
62
|
+
}
|
|
63
|
+
const located = await answer;
|
|
64
|
+
// The frame's own coordinates, for a module served as written. Delegated so
|
|
65
|
+
// there is one statement of the choosing rule rather than two.
|
|
66
|
+
const chosen = located === null
|
|
67
|
+
? writerLocationOf([frame], () => null)
|
|
68
|
+
: isVendorPath(located.file)
|
|
69
|
+
? null
|
|
70
|
+
: located;
|
|
71
|
+
if (chosen !== null) {
|
|
72
|
+
stats.located += 1;
|
|
73
|
+
return chosen;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return { locate, stats: stats };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Provenance with its frames spent: `source` filled in, `stack` gone.
|
|
82
|
+
*
|
|
83
|
+
* Returned unchanged when there is nothing to do — which is the common case once
|
|
84
|
+
* a project installs `jsx-source`, and the case a production build is always in.
|
|
85
|
+
* Never throws: a report that names a location for most of its nodes is the
|
|
86
|
+
* normal outcome, and one node's unreachable module must not take the run down.
|
|
87
|
+
*/
|
|
88
|
+
export async function locateProvenance(provenance, resolver) {
|
|
89
|
+
const { stack, ...carried } = provenance;
|
|
90
|
+
if (stack === undefined || stack.length === 0)
|
|
91
|
+
return provenance;
|
|
92
|
+
let source = null;
|
|
93
|
+
try {
|
|
94
|
+
source = await resolver.locate(stack);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
source = null;
|
|
98
|
+
}
|
|
99
|
+
return source === null ? carried : { ...carried, source };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The line for each site a report is about to name, and for nothing else.
|
|
103
|
+
*
|
|
104
|
+
* This is the demand side of the zero-install path. The page reads frames off
|
|
105
|
+
* every fiber because reading them is nearly free; turning one into a file is
|
|
106
|
+
* not, and this is the only place that spends it.
|
|
107
|
+
*
|
|
108
|
+
* Three ways a site costs nothing, all of them the common case:
|
|
109
|
+
*
|
|
110
|
+
* - **It has no `path`.** A region no box contains names no node.
|
|
111
|
+
* - **It already has a `source`.** React ≤18 and `@variance-authority/jsx-source`
|
|
112
|
+
* both record the location outright, so there is nothing to resolve. Only
|
|
113
|
+
* React 19, which throws its location away and captures an `Error` instead,
|
|
114
|
+
* reaches the map.
|
|
115
|
+
* - **Its node carried no frames.** A production build captures nothing.
|
|
116
|
+
*
|
|
117
|
+
* And a subject that settled on its document digest never calls this at all,
|
|
118
|
+
* which is the point: no region, no finding, no fetch.
|
|
119
|
+
*
|
|
120
|
+
* Sites are resolved concurrently. The resolver's own cache collapses them onto
|
|
121
|
+
* the modules they share, so two regions in one component cost one fetch.
|
|
122
|
+
*/
|
|
123
|
+
export async function locateSites(sites, snapshot, resolver) {
|
|
124
|
+
const wanted = sites.some((site) => site.source === undefined && site.path !== undefined);
|
|
125
|
+
if (!wanted)
|
|
126
|
+
return sites;
|
|
127
|
+
// Built once and only when something asked. A path lookup on a tree is a walk,
|
|
128
|
+
// and doing it per site would make a fifty-region page walk it fifty times.
|
|
129
|
+
const nodes = index(snapshot.root);
|
|
130
|
+
const located = await Promise.all(sites.map(async (site) => {
|
|
131
|
+
if (site.source !== undefined || site.path === undefined)
|
|
132
|
+
return site;
|
|
133
|
+
const provenance = nodes.get(site.path)?.provenance;
|
|
134
|
+
if (provenance?.stack === undefined)
|
|
135
|
+
return site;
|
|
136
|
+
const spent = await locateProvenance(provenance, resolver);
|
|
137
|
+
return spent.source === undefined ? site : { ...site, source: spent.source };
|
|
138
|
+
}));
|
|
139
|
+
return located.every((site, at) => site === sites[at]) ? sites : located;
|
|
140
|
+
}
|
|
141
|
+
function index(root) {
|
|
142
|
+
const nodes = new Map();
|
|
143
|
+
const walk = (node) => {
|
|
144
|
+
nodes.set(node.path, node);
|
|
145
|
+
for (const child of node.children)
|
|
146
|
+
walk(child);
|
|
147
|
+
};
|
|
148
|
+
walk(root);
|
|
149
|
+
return nodes;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* A map's `sources` entry as a path, resolved against the module it describes.
|
|
153
|
+
*
|
|
154
|
+
* Maps state sources relatively — Vite writes `probe.jsx` for `/src/probe.jsx` —
|
|
155
|
+
* so the entry alone is ambiguous between two directories. Resolved and then
|
|
156
|
+
* stripped to a path, because the origin is a fact about the machine that ran
|
|
157
|
+
* the capture: a baseline holding `http://localhost:5199/src/probe.jsx` would
|
|
158
|
+
* disagree with the next run on a different port. What is left is what the
|
|
159
|
+
* source index and `relativizeSource` already speak.
|
|
160
|
+
*/
|
|
161
|
+
function sourcePath(source, moduleUrl) {
|
|
162
|
+
const resolved = absolute(source, moduleUrl) ?? source;
|
|
163
|
+
const parsed = parseUrl(resolved);
|
|
164
|
+
// Not a URL: a bundler that wrote an absolute filesystem path, or a
|
|
165
|
+
// `webpack://` specifier. Left as it is — `relativizeSource` handles the
|
|
166
|
+
// first, and inventing a shape for the second would be a guess.
|
|
167
|
+
return parsed === null ? resolved : parsed.pathname.replace(/^\/+/, '');
|
|
168
|
+
}
|
|
169
|
+
function absolute(url, base) {
|
|
170
|
+
return parseUrl(url, base)?.href ?? null;
|
|
171
|
+
}
|
|
172
|
+
const URL_OF = globalThis.URL;
|
|
173
|
+
function parseUrl(url, base) {
|
|
174
|
+
if (URL_OF === undefined)
|
|
175
|
+
return null;
|
|
176
|
+
try {
|
|
177
|
+
return new URL_OF(url, base);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=call-site.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-site.js","sourceRoot":"","sources":["../../src/attribute/call-site.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Provenance, SourceLocation, StackFrame } from '../format/provenance.js';\nimport type { NodePath, SemanticNode, SemanticSnapshot } from '../format/snapshot.js';\nimport {\n inlineSourceMapOf,\n originalPositionFor,\n parseSourceMap,\n sourceMappingUrlOf,\n type SourceMap,\n} from './source-map.js';\nimport { isVendorPath, writerLocationOf } from './stack.js';\n\n/**\n * Spending the frames a page read, to get the file a reviewer opens.\n *\n * This is the half of the zero-install path that cannot happen in the page. A\n * frame names the module the browser was *served*, and turning that into a line\n * in the repository means fetching that module and reading the source map the\n * build already emitted beside it. Fetching is not something a page agent should\n * do — it runs inside somebody else's application, on a budget measured against\n * their render loop — and it is exactly what the collector is for.\n *\n * **The economics are what make this worth doing rather than clever.** Measured\n * on a 4211-node document: every fiber carried a stack, and between them they\n * held **14 distinct call sites**. A hundred-row table writes two thousand cells\n * from one line of JSX. So the work is per *call site*, not per node, and the\n * cache below is not an optimization but the thing that makes the cost bounded —\n * a handful of module fetches for a whole page.\n *\n * **And it is asked on a signal rather than on every capture.** Fourteen sites is\n * a bounded cost, not a free one, and a run that settles every subject on its\n * document digest has nobody to hand a location to: no region was drawn, no\n * finding was raised, and the fetches would answer a question nothing asked. So\n * frames ride the snapshot — they are provenance, and no hash projects\n * provenance — and {@link locateSites} spends them for the handful of nodes a\n * report is about to name. A page whose only change is one button resolves one\n * call site, not fourteen; a page that did not change resolves none.\n *\n * `fetchModule` is injected because `core` may not assume a network (ADR-0013),\n * and because the right way to fetch differs by caller: a browser-driving\n * collector should fetch from the page's own context, where the origin, the\n * cookies and the dev server's module graph are already correct.\n */\n\n/** Fetch a module's text, or answer that it cannot be had. Never throws. */\nexport type FetchModule = (url: string) => Promise<string | null>;\n\nexport interface CallSiteResolver {\n /** The location that wrote an element, from the frames its fiber carried. */\n locate(frames: readonly StackFrame[]): Promise<SourceLocation | null>;\n /** How many modules were fetched, and how many call sites they answered. */\n readonly stats: CallSiteStats;\n}\n\nexport interface CallSiteStats {\n /** Distinct modules fetched. The number that costs anything. */\n readonly modules: number;\n /** Distinct call sites asked about — the ratio to `modules` is the point. */\n readonly sites: number;\n /** Call sites that resolved to a file. */\n readonly located: number;\n}\n\nexport function createCallSiteResolver(fetchModule: FetchModule): CallSiteResolver {\n /** Module URL → its map, `null` once we know it has none. Also the in-flight guard. */\n const maps = new Map<string, Promise<readonly SourceMap[] | null>>();\n /** `url:line:column` → the answer, so a page's 4211 nodes cost 14 resolutions. */\n const sites = new Map<string, Promise<SourceLocation | null>>();\n\n const stats = { modules: 0, sites: 0, located: 0 };\n\n async function mapsFor(url: string): Promise<readonly SourceMap[] | null> {\n const cached = maps.get(url);\n if (cached !== undefined) return cached;\n\n const pending = (async () => {\n stats.modules += 1;\n\n const code = await fetchModule(url);\n if (code === null) return null;\n\n const annotation = sourceMappingUrlOf(code);\n if (annotation === null) return null;\n\n const inline = inlineSourceMapOf(annotation);\n if (inline !== null) return parseSourceMap(inline);\n\n // A sibling file, which is what a production bundler writes. Resolved\n // against the module rather than the page: `//# sourceMappingURL=out.js.map`\n // means \"beside this module\", and a page several directories deep would\n // otherwise ask for it in the wrong place.\n const sibling = absolute(annotation, url);\n const text = sibling === null ? null : await fetchModule(sibling);\n return text === null ? null : parseSourceMap(text);\n })();\n\n maps.set(url, pending);\n return pending;\n }\n\n async function locateFrame(frame: StackFrame): Promise<SourceLocation | null> {\n const parsed = await mapsFor(frame.url);\n\n // No map is not failure. A dev server serving a plain `.js` as written, and a\n // Node test runner — which applies source maps to `Error.stack` itself, so\n // the frame arrives already original — both land here with the answer in\n // hand. `writerLocationOf` keeps the frame's own coordinates in that case.\n if (parsed === null || parsed.length === 0) return null;\n\n const original = originalPositionFor(parsed, frame.line, frame.column);\n if (original === null) return null;\n\n return {\n file: sourcePath(original.source, frame.url),\n line: original.line,\n column: original.column,\n };\n }\n\n async function locate(frames: readonly StackFrame[]): Promise<SourceLocation | null> {\n for (const frame of frames) {\n if (isVendorPath(frame.url)) continue;\n\n const key = `${frame.url}:${frame.line}:${frame.column}`;\n let answer = sites.get(key);\n\n if (answer === undefined) {\n stats.sites += 1;\n answer = locateFrame(frame);\n sites.set(key, answer);\n }\n\n const located = await answer;\n\n // The frame's own coordinates, for a module served as written. Delegated so\n // there is one statement of the choosing rule rather than two.\n const chosen =\n located === null\n ? writerLocationOf([frame], () => null)\n : isVendorPath(located.file)\n ? null\n : located;\n\n if (chosen !== null) {\n stats.located += 1;\n return chosen;\n }\n }\n\n return null;\n }\n\n return { locate, stats: stats as CallSiteStats };\n}\n\n/**\n * Provenance with its frames spent: `source` filled in, `stack` gone.\n *\n * Returned unchanged when there is nothing to do — which is the common case once\n * a project installs `jsx-source`, and the case a production build is always in.\n * Never throws: a report that names a location for most of its nodes is the\n * normal outcome, and one node's unreachable module must not take the run down.\n */\nexport async function locateProvenance(\n provenance: Provenance,\n resolver: CallSiteResolver,\n): Promise<Provenance> {\n const { stack, ...carried } = provenance;\n if (stack === undefined || stack.length === 0) return provenance;\n\n let source: SourceLocation | null = null;\n try {\n source = await resolver.locate(stack);\n } catch {\n source = null;\n }\n\n return source === null ? carried : { ...carried, source };\n}\n\n/**\n * Anything a report points at: it names a node, and may already know its line.\n *\n * An `AttributedRegion` and a `Finding` are both this, arrived at from opposite\n * directions — one from a mask, one from an inspection — and both are the *few*.\n * That is the whole reason this shape is worth having rather than two functions:\n * what makes resolution affordable is being asked about a handful of nodes, and\n * a handful is what a region list and a finding list are.\n */\nexport interface LocatableSite {\n readonly path?: NodePath;\n readonly source?: SourceLocation;\n}\n\n/**\n * The line for each site a report is about to name, and for nothing else.\n *\n * This is the demand side of the zero-install path. The page reads frames off\n * every fiber because reading them is nearly free; turning one into a file is\n * not, and this is the only place that spends it.\n *\n * Three ways a site costs nothing, all of them the common case:\n *\n * - **It has no `path`.** A region no box contains names no node.\n * - **It already has a `source`.** React ≤18 and `@variance-authority/jsx-source`\n * both record the location outright, so there is nothing to resolve. Only\n * React 19, which throws its location away and captures an `Error` instead,\n * reaches the map.\n * - **Its node carried no frames.** A production build captures nothing.\n *\n * And a subject that settled on its document digest never calls this at all,\n * which is the point: no region, no finding, no fetch.\n *\n * Sites are resolved concurrently. The resolver's own cache collapses them onto\n * the modules they share, so two regions in one component cost one fetch.\n */\nexport async function locateSites<T extends LocatableSite>(\n sites: readonly T[],\n snapshot: SemanticSnapshot,\n resolver: CallSiteResolver,\n): Promise<readonly T[]> {\n const wanted = sites.some((site) => site.source === undefined && site.path !== undefined);\n if (!wanted) return sites;\n\n // Built once and only when something asked. A path lookup on a tree is a walk,\n // and doing it per site would make a fifty-region page walk it fifty times.\n const nodes = index(snapshot.root);\n\n const located = await Promise.all(\n sites.map(async (site) => {\n if (site.source !== undefined || site.path === undefined) return site;\n\n const provenance = nodes.get(site.path)?.provenance;\n if (provenance?.stack === undefined) return site;\n\n const spent = await locateProvenance(provenance, resolver);\n return spent.source === undefined ? site : { ...site, source: spent.source };\n }),\n );\n\n return located.every((site, at) => site === sites[at]) ? sites : located;\n}\n\nfunction index(root: SemanticNode): ReadonlyMap<NodePath, SemanticNode> {\n const nodes = new Map<NodePath, SemanticNode>();\n\n const walk = (node: SemanticNode): void => {\n nodes.set(node.path, node);\n for (const child of node.children) walk(child);\n };\n walk(root);\n\n return nodes;\n}\n\n/**\n * A map's `sources` entry as a path, resolved against the module it describes.\n *\n * Maps state sources relatively — Vite writes `probe.jsx` for `/src/probe.jsx` —\n * so the entry alone is ambiguous between two directories. Resolved and then\n * stripped to a path, because the origin is a fact about the machine that ran\n * the capture: a baseline holding `http://localhost:5199/src/probe.jsx` would\n * disagree with the next run on a different port. What is left is what the\n * source index and `relativizeSource` already speak.\n */\nfunction sourcePath(source: string, moduleUrl: string): string {\n const resolved = absolute(source, moduleUrl) ?? source;\n const parsed = parseUrl(resolved);\n\n // Not a URL: a bundler that wrote an absolute filesystem path, or a\n // `webpack://` specifier. Left as it is — `relativizeSource` handles the\n // first, and inventing a shape for the second would be a guess.\n return parsed === null ? resolved : parsed.pathname.replace(/^\\/+/, '');\n}\n\nfunction absolute(url: string, base: string): string | null {\n return parseUrl(url, base)?.href ?? null;\n}\n\n/**\n * The one host global this file needs, asked for rather than assumed.\n *\n * `core` types no host library (ADR-0001) and joining URLs by hand is a bug farm\n * — `..` past the root, a query string on the base, a protocol-relative\n * specifier. `URL` is present in every Node release this project supports and in\n * every browser, so the honest thing is to use it and to degrade if it is not\n * there, exactly as `inlineSourceMapOf` does for `atob`.\n */\ninterface ParsedUrl {\n readonly href: string;\n readonly pathname: string;\n}\n\nconst URL_OF = (globalThis as { URL?: new (url: string, base?: string) => ParsedUrl }).URL;\n\nfunction parseUrl(url: string, base?: string): ParsedUrl | null {\n if (URL_OF === undefined) return null;\n\n try {\n return new URL_OF(url, base);\n } catch {\n return null;\n }\n}\n"]}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { type Band } from '../compare/band.js';
|
|
2
|
+
import type { Digest } from '../format/hash.js';
|
|
3
|
+
import type { ComponentHash, SemanticSnapshot } from '../format/snapshot.js';
|
|
4
|
+
export type { ComponentHash } from '../format/snapshot.js';
|
|
5
|
+
export { UNATTRIBUTED } from './boundary.js';
|
|
6
|
+
/**
|
|
7
|
+
* The digests a comparison of two component states actually reads.
|
|
8
|
+
*
|
|
9
|
+
* `movedBands` used to take two `ComponentHash`es, which carry a name and an
|
|
10
|
+
* instance count neither side of the comparison consults. Narrowing it to the
|
|
11
|
+
* digests is what lets a per-instance record (`ComponentInstance`, which has no
|
|
12
|
+
* `instances` count because it *is* one) be compared by the same function —
|
|
13
|
+
* rather than by a second copy of the band mapping, which is the one thing in
|
|
14
|
+
* this file that must not exist twice: a subject relaxed to `layout` has to
|
|
15
|
+
* absorb the same bands whichever shape the caller happened to be holding.
|
|
16
|
+
*/
|
|
17
|
+
export interface BandDigests {
|
|
18
|
+
readonly structure: Digest;
|
|
19
|
+
readonly semantics: Digest;
|
|
20
|
+
readonly text: Digest;
|
|
21
|
+
readonly style: Digest;
|
|
22
|
+
readonly geometry?: Digest;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Per-component content hashes, one per band.
|
|
26
|
+
*
|
|
27
|
+
* `SemanticSnapshot` hashes a whole subject. That answers "did anything change"
|
|
28
|
+
* and nothing else: it moves whenever anything inside it moves, so it cannot say
|
|
29
|
+
* *which area*, and a record built on it says "the page changed" on every commit.
|
|
30
|
+
*
|
|
31
|
+
* These hashes are the unit a history is kept in. Text, and small — a component
|
|
32
|
+
* that did not change contributes the same digest it did last time, so the only
|
|
33
|
+
* thing worth recording is the difference.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Hash every component boundary in a subject.
|
|
37
|
+
*
|
|
38
|
+
* Ordered by component name so an unchanged subject produces byte-identical
|
|
39
|
+
* output across runs.
|
|
40
|
+
*
|
|
41
|
+
* The walk and the per-boundary shape live in {@link ./boundary.js}, which
|
|
42
|
+
* {@link ./instances.js} shares — one definition of what a component's own
|
|
43
|
+
* content is, read by the aggregate and by the per-instance form.
|
|
44
|
+
*/
|
|
45
|
+
export declare function hashComponents(snapshot: SemanticSnapshot): readonly ComponentHash[];
|
|
46
|
+
/**
|
|
47
|
+
* Which components *caused* a change, given both revisions' hashes.
|
|
48
|
+
*
|
|
49
|
+
* The arithmetic behind cause-first ranking, and the reason a baseline carries
|
|
50
|
+
* its component hashes at all. `rankRegions` takes a list of cause names and,
|
|
51
|
+
* given none, falls back to area — which
|
|
52
|
+
* [journal 0013](../../../../docs/context/journal/0013-observability.md)
|
|
53
|
+
* measured as backwards by 6×, because area measures displacement rather than
|
|
54
|
+
* cause. This is where that list comes from on a path with no second document.
|
|
55
|
+
*
|
|
56
|
+
* The distinction is the split ADR-0018 built the bands for:
|
|
57
|
+
*
|
|
58
|
+
* - **`structure` or `style` moved** — the component's own content is different.
|
|
59
|
+
* It is a cause.
|
|
60
|
+
* - **only `geometry` moved** — the component is byte-identical and its box is
|
|
61
|
+
* somewhere else. Something *else* moved it, so it is collateral, and naming
|
|
62
|
+
* it would send a reviewer to a file nobody edited.
|
|
63
|
+
* - **appeared or disappeared** — a component present on one side only is a
|
|
64
|
+
* cause. Something decided to render it or to stop.
|
|
65
|
+
*
|
|
66
|
+
* `instances` is deliberately not consulted. A component rendered five times
|
|
67
|
+
* instead of four has a different `structure` digest for the subject, and the
|
|
68
|
+
* count moving on its own — the same component, the same content, one more of
|
|
69
|
+
* them — is a change in whatever decided how many, not in this component.
|
|
70
|
+
*
|
|
71
|
+
* A profile with no layout supplies no `geometry`, so on that tier every
|
|
72
|
+
* difference is `structure` or `style` and every changed component is a cause.
|
|
73
|
+
* That is correct rather than degraded: with no boxes, nothing was displaced.
|
|
74
|
+
*/
|
|
75
|
+
export declare function causesBetween(before: readonly ComponentHash[], after: readonly ComponentHash[]): readonly string[];
|
|
76
|
+
/**
|
|
77
|
+
* Which frequency bands moved between one component's two hashes.
|
|
78
|
+
*
|
|
79
|
+
* The reason the digests were split. A baseline carries hashes and not
|
|
80
|
+
* documents, so "what changed here" used to be answerable only as a boolean —
|
|
81
|
+
* and a boolean cannot serve a route-level test, whose entire request is *tell
|
|
82
|
+
* me when the page stops assembling and never when it is repainted*.
|
|
83
|
+
*
|
|
84
|
+
* The mapping is exact and it is the same one `bandOf` applies to a delta, which
|
|
85
|
+
* is the property that matters: a subject relaxed to `layout` must absorb the
|
|
86
|
+
* same things whether the run held two documents or two sidecars. Two mappings
|
|
87
|
+
* would be one drift away from a config key meaning different things on the two
|
|
88
|
+
* paths, discovered as a regression somebody let through.
|
|
89
|
+
*
|
|
90
|
+
* | digest | band | what it covers |
|
|
91
|
+
* |---|---|---|
|
|
92
|
+
* | `semantics` | `a11y` | role, accessible name, ARIA state |
|
|
93
|
+
* | `text` | `content` | text runs |
|
|
94
|
+
* | `structure` | `geometry` | tags, aliases, attributes, child boundaries |
|
|
95
|
+
* | `geometry` | `geometry` | rects and computed layout output |
|
|
96
|
+
* | `style` | `token` | declared values and custom properties |
|
|
97
|
+
*
|
|
98
|
+
* `texture` never appears. It is raster residue by definition, and a component
|
|
99
|
+
* hash is built from a document — so the band a comparison of hashes cannot
|
|
100
|
+
* decide is *absent* from the answer rather than reported as unmoved, which is
|
|
101
|
+
* ADR-0002's rule applied to a narrower question.
|
|
102
|
+
*
|
|
103
|
+
* A missing `geometry` on either side is the profile saying it has no layout
|
|
104
|
+
* engine, and is not a difference. Treating absent as a change would report
|
|
105
|
+
* every component as having moved the moment a jsdom baseline met a Chromium
|
|
106
|
+
* run — which the environment key already refuses as `incomparable`, so this
|
|
107
|
+
* would be a second, wronger answer to a question already settled.
|
|
108
|
+
*/
|
|
109
|
+
export declare function movedBands(before: BandDigests, after: BandDigests): readonly Band[];
|
|
110
|
+
/**
|
|
111
|
+
* Every band that moved anywhere in the subject, given both revisions' hashes.
|
|
112
|
+
*
|
|
113
|
+
* A component present on one side only contributes `geometry`: something was
|
|
114
|
+
* added or removed, which is the structural half of that band however the rest
|
|
115
|
+
* of it compares. It deliberately does not contribute `a11y` or `content` as
|
|
116
|
+
* well — a component that is simply not there did not *rename* anything, and
|
|
117
|
+
* inflating the answer would make a level that absorbs nothing look like the
|
|
118
|
+
* only safe choice.
|
|
119
|
+
*/
|
|
120
|
+
export declare function bandsBetween(before: readonly ComponentHash[], after: readonly ComponentHash[]): readonly Band[];
|
|
121
|
+
/**
|
|
122
|
+
* Which bands moved, kept per component instead of folded into one list.
|
|
123
|
+
*
|
|
124
|
+
* {@link bandsBetween} answers *what kind of change is in this subject*, which is
|
|
125
|
+
* the question a sensitivity level asks. It cannot answer the one a reviewer
|
|
126
|
+
* asks — *what changed, and where* — because the fold is lossy in exactly the
|
|
127
|
+
* place attribution lives: a subject reporting `content, geometry, token` has
|
|
128
|
+
* told you a colour and a string and a size all moved somewhere in it, and left
|
|
129
|
+
* you to guess which of the forty components on the page owns which.
|
|
130
|
+
*
|
|
131
|
+
* Unfolded, the same two sidecars say `Button — geometry, token` and
|
|
132
|
+
* `CardFooter — a11y, content`, and that is a sentence a page can print beside a
|
|
133
|
+
* picture. It is also the record that survives when the raster tier loses the
|
|
134
|
+
* name: a difference that reflowed its neighbours merges into one blob whose box
|
|
135
|
+
* fits no component, so the region resolves to the document root and the edit
|
|
136
|
+
* arrives unattributed — while the hashes, which never looked at a pixel, still
|
|
137
|
+
* hold the component that moved and the sense in which it moved.
|
|
138
|
+
*
|
|
139
|
+
* `cause` repeats the {@link causesBetween} predicate rather than being derived
|
|
140
|
+
* from `bands`, and the repetition is the point: `structure` and `geometry` both
|
|
141
|
+
* map to the `geometry` band, so a component that edited its own tree and one
|
|
142
|
+
* that was merely pushed by a neighbour are indistinguishable *after* the band
|
|
143
|
+
* mapping. Losing that here would make every reflowed container a culprit.
|
|
144
|
+
*/
|
|
145
|
+
export interface ComponentBands {
|
|
146
|
+
readonly component: string;
|
|
147
|
+
/** Non-empty: a component whose digests all matched has no entry at all. */
|
|
148
|
+
readonly bands: readonly Band[];
|
|
149
|
+
/** Its own content moved, as opposed to only its rect. `causesBetween`'s test. */
|
|
150
|
+
readonly cause: boolean;
|
|
151
|
+
/** Set only when the component is on one side alone, which `bands` cannot say. */
|
|
152
|
+
readonly presence?: 'added' | 'removed';
|
|
153
|
+
/**
|
|
154
|
+
* How much bigger its own box got, when every instance agrees on the answer.
|
|
155
|
+
*
|
|
156
|
+
* The band a reviewer actually wanted. `geometry` says a rect under here is
|
|
157
|
+
* not the rect it was; this says the control is eight pixels taller and
|
|
158
|
+
* thirty-six wider, which is the padding somebody edited, arriving as a
|
|
159
|
+
* measurement rather than as a guess about which property produced it.
|
|
160
|
+
*
|
|
161
|
+
* Absent for four different reasons and they are one reason: nobody can say.
|
|
162
|
+
* No boxes on one side, a different number of instances, instances that
|
|
163
|
+
* disagree about the delta, or a box that did not change size. Present is
|
|
164
|
+
* always non-zero on at least one axis.
|
|
165
|
+
*/
|
|
166
|
+
readonly grew?: {
|
|
167
|
+
readonly width: number;
|
|
168
|
+
readonly height: number;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Every component whose hashes differ, with the bands it differs in.
|
|
173
|
+
*
|
|
174
|
+
* One-sided components contribute `geometry` and nothing else, which is
|
|
175
|
+
* {@link bandsBetween}'s rule and must stay identical to it: a component that is
|
|
176
|
+
* simply not there did not rename anything, and inflating the answer would put
|
|
177
|
+
* an `a11y` claim on a page for a component nobody can look at. `presence` is
|
|
178
|
+
* what carries the rest of that meaning, so no reader has to infer *appeared*
|
|
179
|
+
* from a lone `geometry`.
|
|
180
|
+
*
|
|
181
|
+
* `(unattributed)` is excluded for the reason {@link causesBetween} excludes it:
|
|
182
|
+
* it is a bucket for nodes whose provenance chain broke, so it collects
|
|
183
|
+
* unrelated parts of a page under one name and nothing downstream could act on
|
|
184
|
+
* it. The broken chain is reported where it means something, not here as a
|
|
185
|
+
* component that moved.
|
|
186
|
+
*/
|
|
187
|
+
export declare function movedBandsBetween(before: readonly ComponentHash[], after: readonly ComponentHash[]): readonly ComponentBands[];
|
|
188
|
+
//# sourceMappingURL=component-hash.d.ts.map
|