@variance-authority/core 0.1.1 → 0.3.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 +245 -10
- package/README.md +279 -110
- package/dist/attribute/call-site.d.ts +20 -2
- package/dist/attribute/call-site.js +17 -20
- package/dist/attribute/call-site.js.map +1 -1
- package/dist/attribute/closure.d.ts +105 -0
- package/dist/attribute/closure.js +180 -0
- package/dist/attribute/closure.js.map +1 -0
- package/dist/attribute/composition.d.ts +5 -3
- package/dist/attribute/composition.js +67 -11
- package/dist/attribute/composition.js.map +1 -1
- package/dist/attribute/divergence.js +7 -1
- package/dist/attribute/divergence.js.map +1 -1
- package/dist/attribute/index.d.ts +7 -1
- package/dist/attribute/index.js +4 -1
- package/dist/attribute/index.js.map +1 -1
- package/dist/attribute/landmark.d.ts +97 -0
- package/dist/attribute/landmark.js +144 -0
- package/dist/attribute/landmark.js.map +1 -0
- package/dist/attribute/lexicon.d.ts +112 -0
- package/dist/attribute/lexicon.js +210 -0
- package/dist/attribute/lexicon.js.map +1 -0
- package/dist/attribute/source.d.ts +19 -2
- package/dist/attribute/source.js +12 -0
- package/dist/attribute/source.js.map +1 -1
- package/dist/attribute/stack.d.ts +62 -1
- package/dist/attribute/stack.js +116 -5
- package/dist/attribute/stack.js.map +1 -1
- package/dist/compare/derive.d.ts +17 -0
- package/dist/compare/derive.js.map +1 -1
- package/dist/compare/diff/index.d.ts +25 -1
- package/dist/compare/diff/index.js +8 -5
- package/dist/compare/diff/index.js.map +1 -1
- package/dist/compare/explain.d.ts +1 -1
- package/dist/compare/explain.js +15 -8
- package/dist/compare/explain.js.map +1 -1
- package/dist/compare/index.d.ts +2 -2
- package/dist/compare/index.js +1 -1
- package/dist/compare/index.js.map +1 -1
- package/dist/compare/observability.d.ts +25 -1
- package/dist/compare/observability.js +54 -0
- package/dist/compare/observability.js.map +1 -1
- package/dist/compare/parting.d.ts +24 -2
- package/dist/compare/parting.js +14 -9
- package/dist/compare/parting.js.map +1 -1
- package/dist/compare/slice.d.ts +15 -2
- package/dist/compare/slice.js +6 -2
- package/dist/compare/slice.js.map +1 -1
- package/dist/format/capture.d.ts +15 -0
- package/dist/format/capture.js +30 -1
- package/dist/format/capture.js.map +1 -1
- package/dist/format/document.d.ts +65 -5
- package/dist/format/document.js +34 -0
- package/dist/format/document.js.map +1 -1
- package/dist/format/hash.d.ts +26 -0
- package/dist/format/hash.js +41 -0
- package/dist/format/hash.js.map +1 -1
- package/dist/format/index.d.ts +4 -3
- package/dist/format/index.js +3 -2
- package/dist/format/index.js.map +1 -1
- package/dist/format/sha256.d.ts +10 -2
- package/dist/format/sha256.js +42 -9
- package/dist/format/sha256.js.map +1 -1
- package/dist/format/stabilize.d.ts +15 -0
- package/dist/format/stabilize.js +22 -1
- package/dist/format/stabilize.js.map +1 -1
- package/dist/index.d.ts +3 -11
- package/dist/index.js +3 -11
- package/dist/index.js.map +1 -1
- package/dist/judge/inspect.js +57 -4
- package/dist/judge/inspect.js.map +1 -1
- package/dist/relate/before.d.ts +137 -0
- package/dist/relate/before.js +155 -0
- package/dist/relate/before.js.map +1 -0
- package/dist/relate/graph.d.ts +53 -16
- package/dist/relate/graph.js +33 -15
- package/dist/relate/graph.js.map +1 -1
- package/dist/relate/index.d.ts +3 -2
- package/dist/relate/index.js +2 -1
- package/dist/relate/index.js.map +1 -1
- package/dist/relate/merkle.d.ts +14 -5
- package/dist/relate/merkle.js +14 -6
- package/dist/relate/merkle.js.map +1 -1
- package/dist/relate/reach.d.ts +19 -5
- package/dist/relate/reach.js +10 -8
- package/dist/relate/reach.js.map +1 -1
- package/dist/relate/records.d.ts +95 -4
- package/dist/relate/records.js +93 -13
- package/dist/relate/records.js.map +1 -1
- package/dist/segment/index.d.ts +123 -0
- package/dist/segment/index.js +265 -0
- package/dist/segment/index.js.map +1 -0
- package/dist/share/index.d.ts +148 -0
- package/dist/share/index.js +190 -0
- package/dist/share/index.js.map +1 -0
- package/package.json +10 -2
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a run rests on that nothing in it imports.
|
|
3
|
+
*
|
|
4
|
+
* Selection walks one line. It starts at the test harness, moves right through
|
|
5
|
+
* the tests into the repository's own files, and goes out past them into the
|
|
6
|
+
* dependencies ([`records.ts`](./records.ts) holds all three as nodes). Every
|
|
7
|
+
* question it asks is asked *against* the arrows: what depends on the thing
|
|
8
|
+
* that moved.
|
|
9
|
+
*
|
|
10
|
+
* The far-left end of that line answers no such question. A `vitest.config.ts`,
|
|
11
|
+
* a `jest.config.js`, a Next.js setup, a CI workflow, the Node version in a
|
|
12
|
+
* `.nvmrc`: nothing imports them, so nothing has an edge to them, so a walk
|
|
13
|
+
* from one reaches nothing and a walk to one arrives never. They are *before
|
|
14
|
+
* reach* — able to repaint the whole suite and invisible to the only mechanism
|
|
15
|
+
* that could say so.
|
|
16
|
+
*
|
|
17
|
+
* ## Why the accident is not enough
|
|
18
|
+
*
|
|
19
|
+
* A diff that names only such a file already runs everything, because a diff no
|
|
20
|
+
* part of which is in the graph is refused. That holds only while it is the
|
|
21
|
+
* *whole* diff. A CI workflow edited beside one component file is a diff with a
|
|
22
|
+
* seed in it, and the walk from that seed answers confidently for a change it
|
|
23
|
+
* never looked at. The widening was a side effect of having nothing to say, and
|
|
24
|
+
* a side effect stops the moment somebody else has something to say.
|
|
25
|
+
*
|
|
26
|
+
* ## Why the operator names them
|
|
27
|
+
*
|
|
28
|
+
* Because no rule can. *Every changed path the graph does not hold* is the
|
|
29
|
+
* README, the changelog, the fixture JSON, the editor settings: a whole run
|
|
30
|
+
* each, forever, and an operator who turns that off has turned off the config
|
|
31
|
+
* files too. Which paths govern a run is a fact about the repository that only
|
|
32
|
+
* the repository knows, so it is declared — and once declared it is exact,
|
|
33
|
+
* where a heuristic would be both too wide and too narrow on the same diff.
|
|
34
|
+
*
|
|
35
|
+
* ## What declaring one buys beyond its own name
|
|
36
|
+
*
|
|
37
|
+
* Everything below it. A `setup.ts` the config loads, a fixture module only
|
|
38
|
+
* that setup imports, a polyfill, the environment package it names: each is an
|
|
39
|
+
* ordinary file the scan already holds, that nothing imports, whose change
|
|
40
|
+
* reaches no component and narrows the run to nothing. Declared, the entry
|
|
41
|
+
* point is walked *along* the arrows once and that whole set comes with it.
|
|
42
|
+
*
|
|
43
|
+
* ## Where the descent stops
|
|
44
|
+
*
|
|
45
|
+
* At the first file the scan can already reason about. A setup file that
|
|
46
|
+
* imports `src/theme.ts` does not drag `src/theme.ts` in with it: that file has
|
|
47
|
+
* dependents, a change to it is answered exactly by walking them, and pulling
|
|
48
|
+
* it into the harness would trade an exact answer for a whole run — the one
|
|
49
|
+
* direction this is not allowed to be wrong in. The walk avoids sensed files,
|
|
50
|
+
* and avoiding a node stops the walk there rather than stepping over it: what
|
|
51
|
+
* lies below a sensed file is reached *through* it, and it already answers.
|
|
52
|
+
*
|
|
53
|
+
* ## Where the two ends meet
|
|
54
|
+
*
|
|
55
|
+
* The same walk carries into the package layer, and that is the whole of the
|
|
56
|
+
* mixed case. A config imports `jest-environment-jsdom`, which rests on
|
|
57
|
+
* `jsdom`; `jsdom` is bumped; the install comparison names it. It is a package
|
|
58
|
+
* the harness reaches, so the run does not narrow — and no file in the
|
|
59
|
+
* repository ever wrote the word. A change beyond reach, arriving before it.
|
|
60
|
+
*
|
|
61
|
+
* That cost is real and is the operator's to spend: a config that imports a
|
|
62
|
+
* bundler reaches everything the bundler rests on, so a bump inside that set
|
|
63
|
+
* widens. It is the correct answer — the harness did move — and a repository
|
|
64
|
+
* that finds it too wide narrows what it declares rather than what this
|
|
65
|
+
* concludes.
|
|
66
|
+
*/
|
|
67
|
+
import { nodeAt } from './graph.js';
|
|
68
|
+
import { dependenciesOf } from './reach.js';
|
|
69
|
+
/**
|
|
70
|
+
* Walk down from each declared entry point, stopping where the scan takes over.
|
|
71
|
+
*
|
|
72
|
+
* Along the arrows, unlike every other walk here, because this is the one
|
|
73
|
+
* question whose subject has no dependents: *what does the harness rest on*.
|
|
74
|
+
* Type-only edges are not followed — a config's `import type` is erased before
|
|
75
|
+
* the suite runs, exactly as a component's is.
|
|
76
|
+
*/
|
|
77
|
+
export function beforeReach(relations, entries, options = {}) {
|
|
78
|
+
const files = new Set();
|
|
79
|
+
const packages = new Set();
|
|
80
|
+
const unread = [];
|
|
81
|
+
if (entries.length === 0)
|
|
82
|
+
return { entries, files, packages, unread };
|
|
83
|
+
const sensed = options.sensed ?? [];
|
|
84
|
+
const held = new Map();
|
|
85
|
+
for (let id = 0; id < relations.names.length; id += 1) {
|
|
86
|
+
const node = nodeAt(relations, id);
|
|
87
|
+
if (node?.kind === 'file')
|
|
88
|
+
held.set(node.name, id);
|
|
89
|
+
}
|
|
90
|
+
const seeds = [];
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
const id = held.get(entry);
|
|
93
|
+
if (id === undefined)
|
|
94
|
+
unread.push(entry);
|
|
95
|
+
else
|
|
96
|
+
seeds.push(id);
|
|
97
|
+
}
|
|
98
|
+
if (seeds.length === 0)
|
|
99
|
+
return { entries, files, packages, unread };
|
|
100
|
+
// Avoided before the walk rather than filtered after it. A sensed file is not
|
|
101
|
+
// a step on the way to something else here: whatever it imports, it imports
|
|
102
|
+
// as a file the scan holds, and the dependents of *that* file are the exact
|
|
103
|
+
// answer the harness would have replaced with a whole run.
|
|
104
|
+
const entered = new Set(seeds);
|
|
105
|
+
const avoid = [];
|
|
106
|
+
for (const [name, id] of held) {
|
|
107
|
+
if (!entered.has(id) && within(name, sensed))
|
|
108
|
+
avoid.push(id);
|
|
109
|
+
}
|
|
110
|
+
for (const id of dependenciesOf(relations, seeds, { avoid }).reached) {
|
|
111
|
+
const node = nodeAt(relations, id);
|
|
112
|
+
if (node === undefined)
|
|
113
|
+
continue;
|
|
114
|
+
if (node.kind === 'file')
|
|
115
|
+
files.add(node.name);
|
|
116
|
+
else if (node.kind === 'package')
|
|
117
|
+
packages.add(node.name);
|
|
118
|
+
}
|
|
119
|
+
return { entries, files, packages, unread };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Which of a diff's own inputs the harness rests on, as they were named.
|
|
123
|
+
*
|
|
124
|
+
* Files and packages together and in that order, because the caller's next act
|
|
125
|
+
* is to print them in one sentence and a reader does not care which list a name
|
|
126
|
+
* came out of. Empty is a real answer: the diff moved nothing the run rests on.
|
|
127
|
+
*
|
|
128
|
+
* A declared entry claims every path under it, so naming a directory of
|
|
129
|
+
* workflows is one line of configuration rather than one per file. A file the
|
|
130
|
+
* walk found claims only itself: it was reached, so it is known exactly, and
|
|
131
|
+
* widening it to its directory would put its neighbours before reach without
|
|
132
|
+
* anybody saying they were.
|
|
133
|
+
*/
|
|
134
|
+
export function movedBefore(before, changed, packages = []) {
|
|
135
|
+
return [
|
|
136
|
+
...changed.filter((file) => before.files.has(file) || within(file, before.entries)),
|
|
137
|
+
...packages.filter((name) => before.packages.has(name)),
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Whether a path lies under one of these directories.
|
|
142
|
+
*
|
|
143
|
+
* On directory boundaries rather than characters: `src` must not claim
|
|
144
|
+
* `srcery/`, or a repository would put half of itself before reach and find out
|
|
145
|
+
* from the runs that never narrowed. Exported because every caller that draws
|
|
146
|
+
* this boundary over paths draws the same one, and two spellings of it would
|
|
147
|
+
* disagree on the day one of them was fixed.
|
|
148
|
+
*/
|
|
149
|
+
export function within(file, dirs) {
|
|
150
|
+
return dirs.some((dir) => {
|
|
151
|
+
const normalized = dir.replace(/\/+$/, '');
|
|
152
|
+
return normalized === '.' || file === normalized || file.startsWith(`${normalized}/`);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=before.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"before.js","sourceRoot":"","sources":["../../src/relate/before.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a run rests on that nothing in it imports.\n *\n * Selection walks one line. It starts at the test harness, moves right through\n * the tests into the repository's own files, and goes out past them into the\n * dependencies ([`records.ts`](./records.ts) holds all three as nodes). Every\n * question it asks is asked *against* the arrows: what depends on the thing\n * that moved.\n *\n * The far-left end of that line answers no such question. A `vitest.config.ts`,\n * a `jest.config.js`, a Next.js setup, a CI workflow, the Node version in a\n * `.nvmrc`: nothing imports them, so nothing has an edge to them, so a walk\n * from one reaches nothing and a walk to one arrives never. They are *before\n * reach* — able to repaint the whole suite and invisible to the only mechanism\n * that could say so.\n *\n * ## Why the accident is not enough\n *\n * A diff that names only such a file already runs everything, because a diff no\n * part of which is in the graph is refused. That holds only while it is the\n * *whole* diff. A CI workflow edited beside one component file is a diff with a\n * seed in it, and the walk from that seed answers confidently for a change it\n * never looked at. The widening was a side effect of having nothing to say, and\n * a side effect stops the moment somebody else has something to say.\n *\n * ## Why the operator names them\n *\n * Because no rule can. *Every changed path the graph does not hold* is the\n * README, the changelog, the fixture JSON, the editor settings: a whole run\n * each, forever, and an operator who turns that off has turned off the config\n * files too. Which paths govern a run is a fact about the repository that only\n * the repository knows, so it is declared — and once declared it is exact,\n * where a heuristic would be both too wide and too narrow on the same diff.\n *\n * ## What declaring one buys beyond its own name\n *\n * Everything below it. A `setup.ts` the config loads, a fixture module only\n * that setup imports, a polyfill, the environment package it names: each is an\n * ordinary file the scan already holds, that nothing imports, whose change\n * reaches no component and narrows the run to nothing. Declared, the entry\n * point is walked *along* the arrows once and that whole set comes with it.\n *\n * ## Where the descent stops\n *\n * At the first file the scan can already reason about. A setup file that\n * imports `src/theme.ts` does not drag `src/theme.ts` in with it: that file has\n * dependents, a change to it is answered exactly by walking them, and pulling\n * it into the harness would trade an exact answer for a whole run — the one\n * direction this is not allowed to be wrong in. The walk avoids sensed files,\n * and avoiding a node stops the walk there rather than stepping over it: what\n * lies below a sensed file is reached *through* it, and it already answers.\n *\n * ## Where the two ends meet\n *\n * The same walk carries into the package layer, and that is the whole of the\n * mixed case. A config imports `jest-environment-jsdom`, which rests on\n * `jsdom`; `jsdom` is bumped; the install comparison names it. It is a package\n * the harness reaches, so the run does not narrow — and no file in the\n * repository ever wrote the word. A change beyond reach, arriving before it.\n *\n * That cost is real and is the operator's to spend: a config that imports a\n * bundler reaches everything the bundler rests on, so a bump inside that set\n * widens. It is the correct answer — the harness did move — and a repository\n * that finds it too wide narrows what it declares rather than what this\n * concludes.\n */\n\nimport { nodeAt, type NodeId, type Relations } from './graph.js';\nimport { dependenciesOf } from './reach.js';\n\n/** The part of a run that sits before the tests, as names. */\nexport interface BeforeReach {\n /**\n * The entry points as they were declared, matched against a diff by path.\n *\n * Kept apart from what they reach because they are matched differently: a\n * declared path claims everything under it, so a directory of workflows is\n * one entry, while a file the walk found is one file.\n */\n readonly entries: readonly string[];\n /** Files below the entry points that the scan does not otherwise sense. */\n readonly files: ReadonlySet<string>;\n /** Packages they rest on, direct and transitive. */\n readonly packages: ReadonlySet<string>;\n /**\n * Declared entry points the graph does not hold, and so has no closure for.\n *\n * Ordinary for most of them: a `.nvmrc`, a CI workflow and a `tsconfig` have\n * nothing below them a scan could read, and they contribute their own name,\n * which is all they have. Not ordinary for a config the scan should have\n * read, where it means the closure that would have come with it — the setup\n * module, the environment — is missing and those files still narrow to\n * nothing. The two are indistinguishable from here, so both are reported and\n * neither is guessed at.\n */\n readonly unread: readonly string[];\n}\n\nexport interface BeforeReachOptions {\n /**\n * Files the scan already answers for, as the directories they live under.\n *\n * The descent stops at these. Absent, nothing stops it, and one setup import\n * into `src/` would put the repository's own source before reach.\n */\n readonly sensed?: readonly string[];\n}\n\n/**\n * Walk down from each declared entry point, stopping where the scan takes over.\n *\n * Along the arrows, unlike every other walk here, because this is the one\n * question whose subject has no dependents: *what does the harness rest on*.\n * Type-only edges are not followed — a config's `import type` is erased before\n * the suite runs, exactly as a component's is.\n */\nexport function beforeReach(\n relations: Relations,\n entries: readonly string[],\n options: BeforeReachOptions = {},\n): BeforeReach {\n const files = new Set<string>();\n const packages = new Set<string>();\n const unread: string[] = [];\n if (entries.length === 0) return { entries, files, packages, unread };\n\n const sensed = options.sensed ?? [];\n const held = new Map<string, NodeId>();\n for (let id = 0; id < relations.names.length; id += 1) {\n const node = nodeAt(relations, id);\n if (node?.kind === 'file') held.set(node.name, id);\n }\n\n const seeds: NodeId[] = [];\n for (const entry of entries) {\n const id = held.get(entry);\n if (id === undefined) unread.push(entry);\n else seeds.push(id);\n }\n if (seeds.length === 0) return { entries, files, packages, unread };\n\n // Avoided before the walk rather than filtered after it. A sensed file is not\n // a step on the way to something else here: whatever it imports, it imports\n // as a file the scan holds, and the dependents of *that* file are the exact\n // answer the harness would have replaced with a whole run.\n const entered = new Set(seeds);\n const avoid: NodeId[] = [];\n for (const [name, id] of held) {\n if (!entered.has(id) && within(name, sensed)) avoid.push(id);\n }\n\n for (const id of dependenciesOf(relations, seeds, { avoid }).reached) {\n const node = nodeAt(relations, id);\n if (node === undefined) continue;\n if (node.kind === 'file') files.add(node.name);\n else if (node.kind === 'package') packages.add(node.name);\n }\n\n return { entries, files, packages, unread };\n}\n\n/**\n * Which of a diff's own inputs the harness rests on, as they were named.\n *\n * Files and packages together and in that order, because the caller's next act\n * is to print them in one sentence and a reader does not care which list a name\n * came out of. Empty is a real answer: the diff moved nothing the run rests on.\n *\n * A declared entry claims every path under it, so naming a directory of\n * workflows is one line of configuration rather than one per file. A file the\n * walk found claims only itself: it was reached, so it is known exactly, and\n * widening it to its directory would put its neighbours before reach without\n * anybody saying they were.\n */\nexport function movedBefore(\n before: BeforeReach,\n changed: readonly string[],\n packages: readonly string[] = [],\n): readonly string[] {\n return [\n ...changed.filter((file) => before.files.has(file) || within(file, before.entries)),\n ...packages.filter((name) => before.packages.has(name)),\n ];\n}\n\n/**\n * Whether a path lies under one of these directories.\n *\n * On directory boundaries rather than characters: `src` must not claim\n * `srcery/`, or a repository would put half of itself before reach and find out\n * from the runs that never narrowed. Exported because every caller that draws\n * this boundary over paths draws the same one, and two spellings of it would\n * disagree on the day one of them was fixed.\n */\nexport function within(file: string, dirs: readonly string[]): boolean {\n return dirs.some((dir) => {\n const normalized = dir.replace(/\\/+$/, '');\n return normalized === '.' || file === normalized || file.startsWith(`${normalized}/`);\n });\n}\n"]}
|
package/dist/relate/graph.d.ts
CHANGED
|
@@ -42,15 +42,22 @@
|
|
|
42
42
|
*
|
|
43
43
|
* ## Nodes are typed, because files are not the last kind
|
|
44
44
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* That is deliberate rather than incidental. A
|
|
48
|
-
* renders `Chip`* — is another edge kind
|
|
49
|
-
* same structure, and
|
|
45
|
+
* Three kinds and the joins between them: a `file` graph built from imports, the
|
|
46
|
+
* `component` nodes the source index already knows how to find, and a `package`
|
|
47
|
+
* for each name an install resolved. That is deliberate rather than incidental. A
|
|
48
|
+
* component relation — *`TodoFooter` renders `Chip`* — is another edge kind
|
|
49
|
+
* between nodes of another kind in this same structure, and
|
|
50
50
|
* [`composition.md`](../../../../docs/composition.md) already computes exactly
|
|
51
51
|
* that relation from what a run rendered. One graph means the static answer and
|
|
52
52
|
* the rendered answer are joinable rather than adjacent.
|
|
53
53
|
*
|
|
54
|
+
* A `package` earns its kind for the same reason and one more: it is the far end
|
|
55
|
+
* of the same line. A run starts at the harness, arrives at our code through the
|
|
56
|
+
* tests, and leaves it again at the packages — so a bumped dependency is a node
|
|
57
|
+
* like any other, and *which of our files a new `jsdom` can have moved* is the
|
|
58
|
+
* one traversal this file already performs, seeded on the other side
|
|
59
|
+
* ([`docs/selecting.md`](../../../../docs/selecting.md)).
|
|
60
|
+
*
|
|
54
61
|
* ## What it is not
|
|
55
62
|
*
|
|
56
63
|
* **Not a build graph.** It has no notion of a task, an output or a cache key,
|
|
@@ -62,17 +69,17 @@
|
|
|
62
69
|
* disk.
|
|
63
70
|
*/
|
|
64
71
|
/** Node kinds, in id order. An id is an index into this, never the word. */
|
|
65
|
-
export declare const NODE_KINDS: readonly ['file', 'component'];
|
|
72
|
+
export declare const NODE_KINDS: readonly ['file', 'component', 'package'];
|
|
66
73
|
export type NodeKind = (typeof NODE_KINDS)[number];
|
|
67
74
|
/**
|
|
68
75
|
* Edge kinds, in id order.
|
|
69
76
|
*
|
|
70
|
-
* The distinctions are kept because they explain a finding
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
77
|
+
* The distinctions are kept because they explain a finding. One of them also
|
|
78
|
+
* decides a walk: `type` is erased by every compiler, so nothing behind it runs
|
|
79
|
+
* and nothing behind it renders, and the default traversal leaves it out
|
|
80
|
+
* (`RUNTIME_EDGES`). A caller whose question is about source rather than about a
|
|
81
|
+
* runtime — a docgen that reads prop types, a type-check gate — passes
|
|
82
|
+
* `EDGE_KINDS` and walks it.
|
|
76
83
|
*/
|
|
77
84
|
export declare const EDGE_KINDS: readonly [
|
|
78
85
|
/** `import x from './y'` — a value import. */
|
|
@@ -86,11 +93,26 @@ export declare const EDGE_KINDS: readonly [
|
|
|
86
93
|
/** A stylesheet's `@import`, or a `url()` reaching a font or an image. */
|
|
87
94
|
'asset',
|
|
88
95
|
/** A component to the file that declares it. */
|
|
89
|
-
'declared-in'
|
|
96
|
+
'declared-in',
|
|
97
|
+
/** A package to a package its install resolved beneath it. */
|
|
98
|
+
'depends-on'];
|
|
90
99
|
export type EdgeKind = (typeof EDGE_KINDS)[number];
|
|
100
|
+
/**
|
|
101
|
+
* The edge kinds something at runtime can follow: every kind but `type`. The
|
|
102
|
+
* default for a reach, and the list a caller widens from. A closure folds a
|
|
103
|
+
* shorter list (`CLOSURE_EDGES`), because a digest is a claim about bytes and
|
|
104
|
+
* one of these kinds leads nowhere any bytes are.
|
|
105
|
+
*/
|
|
106
|
+
export declare const RUNTIME_EDGES: readonly EdgeKind[];
|
|
91
107
|
export interface Node {
|
|
92
108
|
readonly kind: NodeKind;
|
|
93
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* Repository-relative for a file, the identifier for a component, and for a
|
|
111
|
+
* package the name source code imports it by — `@mui/material`, never a
|
|
112
|
+
* version and never a resolution. Which copy a resolver handed any one
|
|
113
|
+
* importer is unanswerable without running that resolver, and the question
|
|
114
|
+
* this graph is asked never needs it ([`sense/lock`](../../../sense/src/lock/index.ts)).
|
|
115
|
+
*/
|
|
94
116
|
readonly name: string;
|
|
95
117
|
}
|
|
96
118
|
export interface Relation {
|
|
@@ -143,13 +165,24 @@ export interface Relations {
|
|
|
143
165
|
readonly reasons: ReadonlyMap<NodeId, string>;
|
|
144
166
|
/** `keyOf` to id — the interning table, kept for lookup. */
|
|
145
167
|
readonly index: ReadonlyMap<string, NodeId>;
|
|
168
|
+
/**
|
|
169
|
+
* Per file, the files its run never reaches at any depth: what a mock
|
|
170
|
+
* replaces for the whole of a test's run.
|
|
171
|
+
*
|
|
172
|
+
* Carried with the graph rather than handed to each walk, because the graph
|
|
173
|
+
* crosses every seam a walk does and a table left behind at one of them is a
|
|
174
|
+
* selection that quietly widened. Empty for a graph nobody tainted. A walk
|
|
175
|
+
* against the arrows consults it: a file is moved by a change only when some
|
|
176
|
+
* trail from the change arrives without crossing one of that file's shadows.
|
|
177
|
+
*/
|
|
178
|
+
readonly shadows: ReadonlyMap<string, readonly string[]>;
|
|
146
179
|
}
|
|
147
180
|
/**
|
|
148
181
|
* The lookup key for a node.
|
|
149
182
|
*
|
|
150
183
|
* Runtime only; nothing serializes this, and no code parses it back — the
|
|
151
|
-
* separator only has to be injective, and it is, because a kind is one of
|
|
152
|
-
* fixed words and
|
|
184
|
+
* separator only has to be injective, and it is, because a kind is one of a few
|
|
185
|
+
* fixed words and none contains a `:`. A name that contains one is therefore
|
|
153
186
|
* fine.
|
|
154
187
|
*
|
|
155
188
|
* The obvious separator is a NUL, and it is the wrong one: a source file
|
|
@@ -158,7 +191,9 @@ export interface Relations {
|
|
|
158
191
|
* `tools/boundaries.check.ts` fails a build over it.
|
|
159
192
|
*/
|
|
160
193
|
export declare function keyOf(kind: NodeKind, name: string): string;
|
|
194
|
+
/** The id a node is known by in these relations, or `undefined` for a name the scan never met. */
|
|
161
195
|
export declare function idOf(relations: Relations, kind: NodeKind, name: string): NodeId | undefined;
|
|
196
|
+
/** The node an id names: its kind and name, or `undefined` past the end. */
|
|
162
197
|
export declare function nodeAt(relations: Relations, id: NodeId): Node | undefined;
|
|
163
198
|
/** Every node of one kind, as ids. Contiguous, because the order is kind-major. */
|
|
164
199
|
export declare function nodesOfKind(relations: Relations, kind: NodeKind): readonly NodeId[];
|
|
@@ -186,5 +221,7 @@ export declare function relationsOf(input: {
|
|
|
186
221
|
readonly unknown?: Iterable<Node | readonly [Node, string]>;
|
|
187
222
|
/** Nodes with no edges at all, which would otherwise be absent from the graph. */
|
|
188
223
|
readonly isolated?: Iterable<Node>;
|
|
224
|
+
/** Per file, the files its run never reaches; see {@link Relations.shadows}. */
|
|
225
|
+
readonly shadows?: ReadonlyMap<string, readonly string[]>;
|
|
189
226
|
}): Relations;
|
|
190
227
|
//# sourceMappingURL=graph.d.ts.map
|
package/dist/relate/graph.js
CHANGED
|
@@ -42,15 +42,22 @@
|
|
|
42
42
|
*
|
|
43
43
|
* ## Nodes are typed, because files are not the last kind
|
|
44
44
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* That is deliberate rather than incidental. A
|
|
48
|
-
* renders `Chip`* — is another edge kind
|
|
49
|
-
* same structure, and
|
|
45
|
+
* Three kinds and the joins between them: a `file` graph built from imports, the
|
|
46
|
+
* `component` nodes the source index already knows how to find, and a `package`
|
|
47
|
+
* for each name an install resolved. That is deliberate rather than incidental. A
|
|
48
|
+
* component relation — *`TodoFooter` renders `Chip`* — is another edge kind
|
|
49
|
+
* between nodes of another kind in this same structure, and
|
|
50
50
|
* [`composition.md`](../../../../docs/composition.md) already computes exactly
|
|
51
51
|
* that relation from what a run rendered. One graph means the static answer and
|
|
52
52
|
* the rendered answer are joinable rather than adjacent.
|
|
53
53
|
*
|
|
54
|
+
* A `package` earns its kind for the same reason and one more: it is the far end
|
|
55
|
+
* of the same line. A run starts at the harness, arrives at our code through the
|
|
56
|
+
* tests, and leaves it again at the packages — so a bumped dependency is a node
|
|
57
|
+
* like any other, and *which of our files a new `jsdom` can have moved* is the
|
|
58
|
+
* one traversal this file already performs, seeded on the other side
|
|
59
|
+
* ([`docs/selecting.md`](../../../../docs/selecting.md)).
|
|
60
|
+
*
|
|
54
61
|
* ## What it is not
|
|
55
62
|
*
|
|
56
63
|
* **Not a build graph.** It has no notion of a task, an output or a cache key,
|
|
@@ -62,16 +69,16 @@
|
|
|
62
69
|
* disk.
|
|
63
70
|
*/
|
|
64
71
|
/** Node kinds, in id order. An id is an index into this, never the word. */
|
|
65
|
-
export const NODE_KINDS = ['file', 'component'];
|
|
72
|
+
export const NODE_KINDS = ['file', 'component', 'package'];
|
|
66
73
|
/**
|
|
67
74
|
* Edge kinds, in id order.
|
|
68
75
|
*
|
|
69
|
-
* The distinctions are kept because they explain a finding
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
76
|
+
* The distinctions are kept because they explain a finding. One of them also
|
|
77
|
+
* decides a walk: `type` is erased by every compiler, so nothing behind it runs
|
|
78
|
+
* and nothing behind it renders, and the default traversal leaves it out
|
|
79
|
+
* (`RUNTIME_EDGES`). A caller whose question is about source rather than about a
|
|
80
|
+
* runtime — a docgen that reads prop types, a type-check gate — passes
|
|
81
|
+
* `EDGE_KINDS` and walks it.
|
|
75
82
|
*/
|
|
76
83
|
export const EDGE_KINDS = [
|
|
77
84
|
/** `import x from './y'` — a value import. */
|
|
@@ -86,13 +93,22 @@ export const EDGE_KINDS = [
|
|
|
86
93
|
'asset',
|
|
87
94
|
/** A component to the file that declares it. */
|
|
88
95
|
'declared-in',
|
|
96
|
+
/** A package to a package its install resolved beneath it. */
|
|
97
|
+
'depends-on',
|
|
89
98
|
];
|
|
99
|
+
/**
|
|
100
|
+
* The edge kinds something at runtime can follow: every kind but `type`. The
|
|
101
|
+
* default for a reach, and the list a caller widens from. A closure folds a
|
|
102
|
+
* shorter list (`CLOSURE_EDGES`), because a digest is a claim about bytes and
|
|
103
|
+
* one of these kinds leads nowhere any bytes are.
|
|
104
|
+
*/
|
|
105
|
+
export const RUNTIME_EDGES = EDGE_KINDS.filter((kind) => kind !== 'type');
|
|
90
106
|
/**
|
|
91
107
|
* The lookup key for a node.
|
|
92
108
|
*
|
|
93
109
|
* Runtime only; nothing serializes this, and no code parses it back — the
|
|
94
|
-
* separator only has to be injective, and it is, because a kind is one of
|
|
95
|
-
* fixed words and
|
|
110
|
+
* separator only has to be injective, and it is, because a kind is one of a few
|
|
111
|
+
* fixed words and none contains a `:`. A name that contains one is therefore
|
|
96
112
|
* fine.
|
|
97
113
|
*
|
|
98
114
|
* The obvious separator is a NUL, and it is the wrong one: a source file
|
|
@@ -103,9 +119,11 @@ export const EDGE_KINDS = [
|
|
|
103
119
|
export function keyOf(kind, name) {
|
|
104
120
|
return `${kind}:${name}`;
|
|
105
121
|
}
|
|
122
|
+
/** The id a node is known by in these relations, or `undefined` for a name the scan never met. */
|
|
106
123
|
export function idOf(relations, kind, name) {
|
|
107
124
|
return relations.index.get(keyOf(kind, name));
|
|
108
125
|
}
|
|
126
|
+
/** The node an id names: its kind and name, or `undefined` past the end. */
|
|
109
127
|
export function nodeAt(relations, id) {
|
|
110
128
|
const name = relations.names[id];
|
|
111
129
|
if (name === undefined)
|
|
@@ -176,7 +194,7 @@ export function relationsOf(input) {
|
|
|
176
194
|
if (because !== undefined)
|
|
177
195
|
reasons.set(id, because);
|
|
178
196
|
}
|
|
179
|
-
return { names, kinds, depends, dependents, unknown, reasons, index };
|
|
197
|
+
return { names, kinds, depends, dependents, unknown, reasons, index, shadows: input.shadows ?? new Map() };
|
|
180
198
|
}
|
|
181
199
|
/**
|
|
182
200
|
* Edges as ids, sorted and deduplicated.
|
package/dist/relate/graph.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/relate/graph.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What reaches what, stored so that asking is cheap.\n *\n * Every question this project asks about a change is a reachability question. A\n * diff names files; a subject is made of components; a component is declared in a\n * file that imports other files. *Which subjects could this diff have changed* is\n * one traversal of one graph, and the only reason it is not asked that way today\n * is that there has never been a graph to traverse.\n *\n * ## Why a structure rather than a `Map`\n *\n * The obvious shape is `Map<string, string[]>`, and it is wrong at the size this\n * runs at. A monorepo scan produces tens of thousands of files and several times\n * as many edges; the map holds one array object and one string copy per edge, and\n * a traversal chases a pointer for every step. What replaces it here is the\n * standard answer, and it is standard because nothing has beaten it:\n *\n * - **strings are interned once.** A node is an integer. Comparison is an integer\n * compare, membership is an index into a byte array, and a visited set is a\n * `Uint8Array` rather than a `Set<string>`.\n * - **edges are two typed arrays** in compressed-sparse-row form: `offset` says\n * where a node's row starts, `target` holds the rows end to end. One node's\n * edges are contiguous, so a traversal walks memory forwards instead of\n * following pointers into the heap.\n * - **both directions are materialized.** *What does this depend on* and *what\n * depends on this* are different questions, and a graph answering only the\n * first makes the second a whole-graph scan. The transpose is a counting sort —\n * one pass to count, one to place — so holding it costs `O(n + m)` once instead\n * of `O(n + m)` per query.\n *\n * ## Edges point at what a thing depends on\n *\n * One convention, held everywhere: **`A → B` means A depends on B**, so a change\n * in B may move A. Every affected-set question is therefore a walk against the\n * arrows, which is what `dependentsOf` does, and every *what would I have to read\n * to understand this* question is a walk along them.\n *\n * It decides the direction of the less obvious edges too. A component is\n * `declared-in` the file that declares it — the component depends on the file and\n * not the other way round — so editing that file reaches the component in the\n * same traversal that reaches every importer.\n *\n * ## Nodes are typed, because files are not the last kind\n *\n * This ships with two kinds and the join between them: a `file` graph built from\n * imports, and the `component` nodes the source index already knows how to find.\n * That is deliberate rather than incidental. A component relation — *`TodoFooter`\n * renders `Chip`* — is another edge kind between nodes of another kind in this\n * same structure, and\n * [`composition.md`](../../../../docs/composition.md) already computes exactly\n * that relation from what a run rendered. One graph means the static answer and\n * the rendered answer are joinable rather than adjacent.\n *\n * ## What it is not\n *\n * **Not a build graph.** It has no notion of a task, an output or a cache key,\n * and it does not know what an install produced. `nx` and `turbo` own that layer,\n * and what reads them reads their answer rather than competing with it.\n *\n * **Not a resolver.** Nothing here opens a file or knows what a specifier means.\n * `core` performs no I/O (ADR-0006); the records this folds are somebody else's\n * disk.\n */\n\n/** Node kinds, in id order. An id is an index into this, never the word. */\nexport const NODE_KINDS = ['file', 'component'] as const;\nexport type NodeKind = (typeof NODE_KINDS)[number];\n\n/**\n * Edge kinds, in id order.\n *\n * The distinctions are kept because they explain a finding, not because the\n * default traversal narrows on them. `type` is erased by every compiler and can\n * change no rendering, and it is still walked unless a caller says otherwise —\n * for the reason every rule in selection resolves the same way: a subject skipped\n * in error is a green run over an unwatched surface, and a subject observed in\n * error costs a collection.\n */\nexport const EDGE_KINDS = [\n /** `import x from './y'` — a value import. */\n 'imports',\n /** `export … from './y'` — an import that also republishes. */\n 'reexports',\n /** `import('./y')`, with a specifier that was a literal. */\n 'dynamic',\n /** `import type { T } from './y'` — erased before anything renders. */\n 'type',\n /** A stylesheet's `@import`, or a `url()` reaching a font or an image. */\n 'asset',\n /** A component to the file that declares it. */\n 'declared-in',\n] as const;\nexport type EdgeKind = (typeof EDGE_KINDS)[number];\n\nexport interface Node {\n readonly kind: NodeKind;\n /** Repository-relative for a file; the identifier for a component. */\n readonly name: string;\n}\n\nexport interface Relation {\n readonly from: Node;\n readonly to: Node;\n readonly kind: EdgeKind;\n}\n\n/** An index into `Relations.names`. Valid only against the graph it came from. */\nexport type NodeId = number;\n\n/**\n * One direction of the edge set, compressed-sparse-row.\n *\n * Node `i`'s edges are `target[offset[i] … offset[i + 1])`, and `kind[j]`\n * describes `target[j]`. `offset` holds one more entry than there are nodes, so\n * the last row needs no special case.\n */\nexport interface Adjacency {\n readonly offset: Uint32Array;\n readonly target: Uint32Array;\n readonly kind: Uint8Array;\n}\n\nexport interface Relations {\n /** Node names by id, ordered by kind and then by code unit. */\n readonly names: readonly string[];\n /** The `NODE_KINDS` index of each node. */\n readonly kinds: Uint8Array;\n\n /** What each node depends on. */\n readonly depends: Adjacency;\n /** What depends on each node — the transpose, materialized. */\n readonly dependents: Adjacency;\n\n /**\n * `1` where this node's outgoing edges could not be enumerated.\n *\n * A parse failure, a specifier that was not a literal, a file in a language\n * nothing here reads. It is *not* \"this file has no dependencies\", and the\n * difference is the whole safety argument: a file whose edges are unknown may\n * depend on anything that changed, so selection seeds the traversal with every\n * one of them (ADR-0002 — absent is not empty).\n */\n readonly unknown: Uint8Array;\n\n /**\n * Why, for each of those nodes, in the words whoever read the file used.\n *\n * Sparse — a repository the scan understands carries none of these — and kept\n * apart from the mask because the mask is what the inner loop tests and this is\n * what an operator reads. Without it, a run that observed forty subjects instead\n * of twelve can say *because eight files could not be read* and can never say\n * **which**, which is the difference between a fact and a work item.\n */\n readonly reasons: ReadonlyMap<NodeId, string>;\n\n /** `keyOf` to id — the interning table, kept for lookup. */\n readonly index: ReadonlyMap<string, NodeId>;\n}\n\n/**\n * The lookup key for a node.\n *\n * Runtime only; nothing serializes this, and no code parses it back — the\n * separator only has to be injective, and it is, because a kind is one of two\n * fixed words and neither contains a `:`. A name that contains one is therefore\n * fine.\n *\n * The obvious separator is a NUL, and it is the wrong one: a source file\n * carrying that byte stops being text to `grep`, `file` and `git diff`, which is\n * a search finding nothing looking exactly like a search that returned nothing.\n * `tools/boundaries.check.ts` fails a build over it.\n */\nexport function keyOf(kind: NodeKind, name: string): string {\n return `${kind}:${name}`;\n}\n\nexport function idOf(relations: Relations, kind: NodeKind, name: string): NodeId | undefined {\n return relations.index.get(keyOf(kind, name));\n}\n\nexport function nodeAt(relations: Relations, id: NodeId): Node | undefined {\n const name = relations.names[id];\n if (name === undefined) return undefined;\n return { kind: NODE_KINDS[relations.kinds[id]!]!, name };\n}\n\n/** Every node of one kind, as ids. Contiguous, because the order is kind-major. */\nexport function nodesOfKind(relations: Relations, kind: NodeKind): readonly NodeId[] {\n const wanted = NODE_KINDS.indexOf(kind);\n const found: NodeId[] = [];\n for (let id = 0; id < relations.kinds.length; id += 1) {\n if (relations.kinds[id] === wanted) found.push(id);\n }\n return found;\n}\n\n/**\n * Fold relations into the queryable form.\n *\n * Deterministic to the byte: nodes are ordered by kind and then by **code unit** —\n * never `localeCompare`, which would make an id a promise about `LANG` — and edges\n * are sorted and deduplicated, so two runs over the same records produce identical\n * arrays and a serialized graph is stable.\n *\n * `O(n log n + m log m)` for the two sorts and `O(n + m)` for everything else. The\n * transpose is a counting sort rather than a second pass over a map, which is why\n * both directions cost one build instead of one per query.\n */\nexport function relationsOf(input: {\n readonly relations: Iterable<Relation>;\n /**\n * Nodes whose outgoing edges are not fully known, and must be treated as such.\n *\n * A bare node says *this one is blind*. A pair says why, in whatever words the\n * reader of the file used — and the sentence is the half an operator can act on,\n * so it travels with the fact rather than being reconstructed from it.\n */\n readonly unknown?: Iterable<Node | readonly [Node, string]>;\n /** Nodes with no edges at all, which would otherwise be absent from the graph. */\n readonly isolated?: Iterable<Node>;\n}): Relations {\n const relations = [...input.relations];\n const unknownNodes = [...(input.unknown ?? [])].map((entry) =>\n Array.isArray(entry) ? entry : ([entry, undefined] as const),\n ) as readonly (readonly [Node, string | undefined])[];\n\n const seen = new Map<string, Node>();\n const see = (node: Node): void => {\n const key = keyOf(node.kind, node.name);\n if (!seen.has(key)) seen.set(key, node);\n };\n\n for (const relation of relations) {\n see(relation.from);\n see(relation.to);\n }\n for (const [node] of unknownNodes) see(node);\n for (const node of input.isolated ?? []) see(node);\n\n // Kind-major, then code unit. Kind-major keeps one kind's nodes contiguous,\n // which is what lets a caller filter to files without touching a string.\n const ordered = [...seen.values()].sort(\n (a, b) =>\n NODE_KINDS.indexOf(a.kind) - NODE_KINDS.indexOf(b.kind) ||\n (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),\n );\n\n const index = new Map<string, NodeId>();\n const names: string[] = Array.from<string>({ length: ordered.length });\n const kinds = new Uint8Array(ordered.length);\n\n for (const [id, node] of ordered.entries()) {\n index.set(keyOf(node.kind, node.name), id);\n names[id] = node.name;\n kinds[id] = NODE_KINDS.indexOf(node.kind);\n }\n\n const edges = sortedEdges(relations, index);\n const depends = adjacencyOf(edges, ordered.length, 'from');\n const dependents = adjacencyOf(edges, ordered.length, 'to');\n\n const unknown = new Uint8Array(ordered.length);\n const reasons = new Map<NodeId, string>();\n for (const [node, because] of unknownNodes) {\n const id = index.get(keyOf(node.kind, node.name));\n if (id === undefined) continue;\n\n unknown[id] = 1;\n if (because !== undefined) reasons.set(id, because);\n }\n\n return { names, kinds, depends, dependents, unknown, reasons, index };\n}\n\ninterface Edge {\n readonly from: NodeId;\n readonly to: NodeId;\n readonly kind: number;\n}\n\n/**\n * Edges as ids, sorted and deduplicated.\n *\n * Sorting the whole list once is what makes every row of *both* adjacencies\n * sorted without sorting any row: each fill walks the list in order, so a row\n * receives its targets in order. Two files related twice — a value import and a\n * type import — stay two edges, because the kinds differ and one of them explains\n * a finding the other does not.\n */\nfunction sortedEdges(\n relations: readonly Relation[],\n index: ReadonlyMap<string, NodeId>,\n): readonly Edge[] {\n const edges: Edge[] = [];\n\n for (const relation of relations) {\n const from = index.get(keyOf(relation.from.kind, relation.from.name));\n const to = index.get(keyOf(relation.to.kind, relation.to.name));\n if (from === undefined || to === undefined) continue;\n edges.push({ from, to, kind: EDGE_KINDS.indexOf(relation.kind) });\n }\n\n edges.sort((a, b) => a.from - b.from || a.to - b.to || a.kind - b.kind);\n\n const unique: Edge[] = [];\n for (const edge of edges) {\n const last = unique[unique.length - 1];\n const same =\n last !== undefined && last.from === edge.from && last.to === edge.to && last.kind === edge.kind;\n if (!same) unique.push(edge);\n }\n\n return unique;\n}\n\n/**\n * One direction, by counting sort.\n *\n * `by` names the endpoint a row is keyed on: `from` produces *what this depends\n * on*, `to` produces the transpose. Two passes — count the degrees, then place —\n * and no per-node array is ever allocated.\n */\nfunction adjacencyOf(edges: readonly Edge[], nodes: number, by: 'from' | 'to'): Adjacency {\n const offset = new Uint32Array(nodes + 1);\n for (const edge of edges) offset[edge[by] + 1] = offset[edge[by] + 1]! + 1;\n for (let id = 0; id < nodes; id += 1) offset[id + 1] = offset[id + 1]! + offset[id]!;\n\n const target = new Uint32Array(edges.length);\n const kind = new Uint8Array(edges.length);\n const cursor = Uint32Array.from(offset.subarray(0, nodes));\n\n for (const edge of edges) {\n const row = edge[by];\n const at = cursor[row]!;\n target[at] = by === 'from' ? edge.to : edge.from;\n kind[at] = edge.kind;\n cursor[row] = at + 1;\n }\n\n return { offset, target, kind };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/relate/graph.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What reaches what, stored so that asking is cheap.\n *\n * Every question this project asks about a change is a reachability question. A\n * diff names files; a subject is made of components; a component is declared in a\n * file that imports other files. *Which subjects could this diff have changed* is\n * one traversal of one graph, and the only reason it is not asked that way today\n * is that there has never been a graph to traverse.\n *\n * ## Why a structure rather than a `Map`\n *\n * The obvious shape is `Map<string, string[]>`, and it is wrong at the size this\n * runs at. A monorepo scan produces tens of thousands of files and several times\n * as many edges; the map holds one array object and one string copy per edge, and\n * a traversal chases a pointer for every step. What replaces it here is the\n * standard answer, and it is standard because nothing has beaten it:\n *\n * - **strings are interned once.** A node is an integer. Comparison is an integer\n * compare, membership is an index into a byte array, and a visited set is a\n * `Uint8Array` rather than a `Set<string>`.\n * - **edges are two typed arrays** in compressed-sparse-row form: `offset` says\n * where a node's row starts, `target` holds the rows end to end. One node's\n * edges are contiguous, so a traversal walks memory forwards instead of\n * following pointers into the heap.\n * - **both directions are materialized.** *What does this depend on* and *what\n * depends on this* are different questions, and a graph answering only the\n * first makes the second a whole-graph scan. The transpose is a counting sort —\n * one pass to count, one to place — so holding it costs `O(n + m)` once instead\n * of `O(n + m)` per query.\n *\n * ## Edges point at what a thing depends on\n *\n * One convention, held everywhere: **`A → B` means A depends on B**, so a change\n * in B may move A. Every affected-set question is therefore a walk against the\n * arrows, which is what `dependentsOf` does, and every *what would I have to read\n * to understand this* question is a walk along them.\n *\n * It decides the direction of the less obvious edges too. A component is\n * `declared-in` the file that declares it — the component depends on the file and\n * not the other way round — so editing that file reaches the component in the\n * same traversal that reaches every importer.\n *\n * ## Nodes are typed, because files are not the last kind\n *\n * Three kinds and the joins between them: a `file` graph built from imports, the\n * `component` nodes the source index already knows how to find, and a `package`\n * for each name an install resolved. That is deliberate rather than incidental. A\n * component relation — *`TodoFooter` renders `Chip`* — is another edge kind\n * between nodes of another kind in this same structure, and\n * [`composition.md`](../../../../docs/composition.md) already computes exactly\n * that relation from what a run rendered. One graph means the static answer and\n * the rendered answer are joinable rather than adjacent.\n *\n * A `package` earns its kind for the same reason and one more: it is the far end\n * of the same line. A run starts at the harness, arrives at our code through the\n * tests, and leaves it again at the packages — so a bumped dependency is a node\n * like any other, and *which of our files a new `jsdom` can have moved* is the\n * one traversal this file already performs, seeded on the other side\n * ([`docs/selecting.md`](../../../../docs/selecting.md)).\n *\n * ## What it is not\n *\n * **Not a build graph.** It has no notion of a task, an output or a cache key,\n * and it does not know what an install produced. `nx` and `turbo` own that layer,\n * and what reads them reads their answer rather than competing with it.\n *\n * **Not a resolver.** Nothing here opens a file or knows what a specifier means.\n * `core` performs no I/O (ADR-0006); the records this folds are somebody else's\n * disk.\n */\n\n/** Node kinds, in id order. An id is an index into this, never the word. */\nexport const NODE_KINDS = ['file', 'component', 'package'] as const;\nexport type NodeKind = (typeof NODE_KINDS)[number];\n\n/**\n * Edge kinds, in id order.\n *\n * The distinctions are kept because they explain a finding. One of them also\n * decides a walk: `type` is erased by every compiler, so nothing behind it runs\n * and nothing behind it renders, and the default traversal leaves it out\n * (`RUNTIME_EDGES`). A caller whose question is about source rather than about a\n * runtime — a docgen that reads prop types, a type-check gate — passes\n * `EDGE_KINDS` and walks it.\n */\nexport const EDGE_KINDS = [\n /** `import x from './y'` — a value import. */\n 'imports',\n /** `export … from './y'` — an import that also republishes. */\n 'reexports',\n /** `import('./y')`, with a specifier that was a literal. */\n 'dynamic',\n /** `import type { T } from './y'` — erased before anything renders. */\n 'type',\n /** A stylesheet's `@import`, or a `url()` reaching a font or an image. */\n 'asset',\n /** A component to the file that declares it. */\n 'declared-in',\n /** A package to a package its install resolved beneath it. */\n 'depends-on',\n] as const;\nexport type EdgeKind = (typeof EDGE_KINDS)[number];\n\n/**\n * The edge kinds something at runtime can follow: every kind but `type`. The\n * default for a reach, and the list a caller widens from. A closure folds a\n * shorter list (`CLOSURE_EDGES`), because a digest is a claim about bytes and\n * one of these kinds leads nowhere any bytes are.\n */\nexport const RUNTIME_EDGES: readonly EdgeKind[] = EDGE_KINDS.filter((kind) => kind !== 'type');\n\nexport interface Node {\n readonly kind: NodeKind;\n /**\n * Repository-relative for a file, the identifier for a component, and for a\n * package the name source code imports it by — `@mui/material`, never a\n * version and never a resolution. Which copy a resolver handed any one\n * importer is unanswerable without running that resolver, and the question\n * this graph is asked never needs it ([`sense/lock`](../../../sense/src/lock/index.ts)).\n */\n readonly name: string;\n}\n\nexport interface Relation {\n readonly from: Node;\n readonly to: Node;\n readonly kind: EdgeKind;\n}\n\n/** An index into `Relations.names`. Valid only against the graph it came from. */\nexport type NodeId = number;\n\n/**\n * One direction of the edge set, compressed-sparse-row.\n *\n * Node `i`'s edges are `target[offset[i] … offset[i + 1])`, and `kind[j]`\n * describes `target[j]`. `offset` holds one more entry than there are nodes, so\n * the last row needs no special case.\n */\nexport interface Adjacency {\n readonly offset: Uint32Array;\n readonly target: Uint32Array;\n readonly kind: Uint8Array;\n}\n\nexport interface Relations {\n /** Node names by id, ordered by kind and then by code unit. */\n readonly names: readonly string[];\n /** The `NODE_KINDS` index of each node. */\n readonly kinds: Uint8Array;\n\n /** What each node depends on. */\n readonly depends: Adjacency;\n /** What depends on each node — the transpose, materialized. */\n readonly dependents: Adjacency;\n\n /**\n * `1` where this node's outgoing edges could not be enumerated.\n *\n * A parse failure, a specifier that was not a literal, a file in a language\n * nothing here reads. It is *not* \"this file has no dependencies\", and the\n * difference is the whole safety argument: a file whose edges are unknown may\n * depend on anything that changed, so selection seeds the traversal with every\n * one of them (ADR-0002 — absent is not empty).\n */\n readonly unknown: Uint8Array;\n\n /**\n * Why, for each of those nodes, in the words whoever read the file used.\n *\n * Sparse — a repository the scan understands carries none of these — and kept\n * apart from the mask because the mask is what the inner loop tests and this is\n * what an operator reads. Without it, a run that observed forty subjects instead\n * of twelve can say *because eight files could not be read* and can never say\n * **which**, which is the difference between a fact and a work item.\n */\n readonly reasons: ReadonlyMap<NodeId, string>;\n\n /** `keyOf` to id — the interning table, kept for lookup. */\n readonly index: ReadonlyMap<string, NodeId>;\n\n /**\n * Per file, the files its run never reaches at any depth: what a mock\n * replaces for the whole of a test's run.\n *\n * Carried with the graph rather than handed to each walk, because the graph\n * crosses every seam a walk does and a table left behind at one of them is a\n * selection that quietly widened. Empty for a graph nobody tainted. A walk\n * against the arrows consults it: a file is moved by a change only when some\n * trail from the change arrives without crossing one of that file's shadows.\n */\n readonly shadows: ReadonlyMap<string, readonly string[]>;\n}\n\n/**\n * The lookup key for a node.\n *\n * Runtime only; nothing serializes this, and no code parses it back — the\n * separator only has to be injective, and it is, because a kind is one of a few\n * fixed words and none contains a `:`. A name that contains one is therefore\n * fine.\n *\n * The obvious separator is a NUL, and it is the wrong one: a source file\n * carrying that byte stops being text to `grep`, `file` and `git diff`, which is\n * a search finding nothing looking exactly like a search that returned nothing.\n * `tools/boundaries.check.ts` fails a build over it.\n */\nexport function keyOf(kind: NodeKind, name: string): string {\n return `${kind}:${name}`;\n}\n\n/** The id a node is known by in these relations, or `undefined` for a name the scan never met. */\nexport function idOf(relations: Relations, kind: NodeKind, name: string): NodeId | undefined {\n return relations.index.get(keyOf(kind, name));\n}\n\n/** The node an id names: its kind and name, or `undefined` past the end. */\nexport function nodeAt(relations: Relations, id: NodeId): Node | undefined {\n const name = relations.names[id];\n if (name === undefined) return undefined;\n return { kind: NODE_KINDS[relations.kinds[id]!]!, name };\n}\n\n/** Every node of one kind, as ids. Contiguous, because the order is kind-major. */\nexport function nodesOfKind(relations: Relations, kind: NodeKind): readonly NodeId[] {\n const wanted = NODE_KINDS.indexOf(kind);\n const found: NodeId[] = [];\n for (let id = 0; id < relations.kinds.length; id += 1) {\n if (relations.kinds[id] === wanted) found.push(id);\n }\n return found;\n}\n\n/**\n * Fold relations into the queryable form.\n *\n * Deterministic to the byte: nodes are ordered by kind and then by **code unit** —\n * never `localeCompare`, which would make an id a promise about `LANG` — and edges\n * are sorted and deduplicated, so two runs over the same records produce identical\n * arrays and a serialized graph is stable.\n *\n * `O(n log n + m log m)` for the two sorts and `O(n + m)` for everything else. The\n * transpose is a counting sort rather than a second pass over a map, which is why\n * both directions cost one build instead of one per query.\n */\nexport function relationsOf(input: {\n readonly relations: Iterable<Relation>;\n /**\n * Nodes whose outgoing edges are not fully known, and must be treated as such.\n *\n * A bare node says *this one is blind*. A pair says why, in whatever words the\n * reader of the file used — and the sentence is the half an operator can act on,\n * so it travels with the fact rather than being reconstructed from it.\n */\n readonly unknown?: Iterable<Node | readonly [Node, string]>;\n /** Nodes with no edges at all, which would otherwise be absent from the graph. */\n readonly isolated?: Iterable<Node>;\n /** Per file, the files its run never reaches; see {@link Relations.shadows}. */\n readonly shadows?: ReadonlyMap<string, readonly string[]>;\n}): Relations {\n const relations = [...input.relations];\n const unknownNodes = [...(input.unknown ?? [])].map((entry) =>\n Array.isArray(entry) ? entry : ([entry, undefined] as const),\n ) as readonly (readonly [Node, string | undefined])[];\n\n const seen = new Map<string, Node>();\n const see = (node: Node): void => {\n const key = keyOf(node.kind, node.name);\n if (!seen.has(key)) seen.set(key, node);\n };\n\n for (const relation of relations) {\n see(relation.from);\n see(relation.to);\n }\n for (const [node] of unknownNodes) see(node);\n for (const node of input.isolated ?? []) see(node);\n\n // Kind-major, then code unit. Kind-major keeps one kind's nodes contiguous,\n // which is what lets a caller filter to files without touching a string.\n const ordered = [...seen.values()].sort(\n (a, b) =>\n NODE_KINDS.indexOf(a.kind) - NODE_KINDS.indexOf(b.kind) ||\n (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),\n );\n\n const index = new Map<string, NodeId>();\n const names: string[] = Array.from<string>({ length: ordered.length });\n const kinds = new Uint8Array(ordered.length);\n\n for (const [id, node] of ordered.entries()) {\n index.set(keyOf(node.kind, node.name), id);\n names[id] = node.name;\n kinds[id] = NODE_KINDS.indexOf(node.kind);\n }\n\n const edges = sortedEdges(relations, index);\n const depends = adjacencyOf(edges, ordered.length, 'from');\n const dependents = adjacencyOf(edges, ordered.length, 'to');\n\n const unknown = new Uint8Array(ordered.length);\n const reasons = new Map<NodeId, string>();\n for (const [node, because] of unknownNodes) {\n const id = index.get(keyOf(node.kind, node.name));\n if (id === undefined) continue;\n\n unknown[id] = 1;\n if (because !== undefined) reasons.set(id, because);\n }\n\n return { names, kinds, depends, dependents, unknown, reasons, index, shadows: input.shadows ?? new Map() };\n}\n\ninterface Edge {\n readonly from: NodeId;\n readonly to: NodeId;\n readonly kind: number;\n}\n\n/**\n * Edges as ids, sorted and deduplicated.\n *\n * Sorting the whole list once is what makes every row of *both* adjacencies\n * sorted without sorting any row: each fill walks the list in order, so a row\n * receives its targets in order. Two files related twice — a value import and a\n * type import — stay two edges, because the kinds differ and one of them explains\n * a finding the other does not.\n */\nfunction sortedEdges(\n relations: readonly Relation[],\n index: ReadonlyMap<string, NodeId>,\n): readonly Edge[] {\n const edges: Edge[] = [];\n\n for (const relation of relations) {\n const from = index.get(keyOf(relation.from.kind, relation.from.name));\n const to = index.get(keyOf(relation.to.kind, relation.to.name));\n if (from === undefined || to === undefined) continue;\n edges.push({ from, to, kind: EDGE_KINDS.indexOf(relation.kind) });\n }\n\n edges.sort((a, b) => a.from - b.from || a.to - b.to || a.kind - b.kind);\n\n const unique: Edge[] = [];\n for (const edge of edges) {\n const last = unique[unique.length - 1];\n const same =\n last !== undefined && last.from === edge.from && last.to === edge.to && last.kind === edge.kind;\n if (!same) unique.push(edge);\n }\n\n return unique;\n}\n\n/**\n * One direction, by counting sort.\n *\n * `by` names the endpoint a row is keyed on: `from` produces *what this depends\n * on*, `to` produces the transpose. Two passes — count the degrees, then place —\n * and no per-node array is ever allocated.\n */\nfunction adjacencyOf(edges: readonly Edge[], nodes: number, by: 'from' | 'to'): Adjacency {\n const offset = new Uint32Array(nodes + 1);\n for (const edge of edges) offset[edge[by] + 1] = offset[edge[by] + 1]! + 1;\n for (let id = 0; id < nodes; id += 1) offset[id + 1] = offset[id + 1]! + offset[id]!;\n\n const target = new Uint32Array(edges.length);\n const kind = new Uint8Array(edges.length);\n const cursor = Uint32Array.from(offset.subarray(0, nodes));\n\n for (const edge of edges) {\n const row = edge[by];\n const at = cursor[row]!;\n target[at] = by === 'from' ? edge.to : edge.from;\n kind[at] = edge.kind;\n cursor[row] = at + 1;\n }\n\n return { offset, target, kind };\n}\n"]}
|
package/dist/relate/index.d.ts
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
*
|
|
10
10
|
* See [`docs/selecting.md`](../../../../docs/selecting.md) for what it is for.
|
|
11
11
|
*/
|
|
12
|
-
export { EDGE_KINDS, NODE_KINDS, idOf, keyOf, nodeAt, nodesOfKind, relationsOf, type Adjacency, type EdgeKind, type Node, type NodeId, type NodeKind, type Relation, type Relations, } from './graph.js';
|
|
12
|
+
export { EDGE_KINDS, NODE_KINDS, RUNTIME_EDGES, idOf, keyOf, nodeAt, nodesOfKind, relationsOf, type Adjacency, type EdgeKind, type Node, type NodeId, type NodeKind, type Relation, type Relations, } from './graph.js';
|
|
13
|
+
export { beforeReach, movedBefore, within, type BeforeReach, type BeforeReachOptions, } from './before.js';
|
|
13
14
|
export { dependenciesOf, dependentsOf, trailOf, type Reach, type ReachOptions, } from './reach.js';
|
|
14
15
|
export { CLOSURE_EDGES, closureOf, driftedBetween, type Closure, type ClosureInput, type Drift, } from './merkle.js';
|
|
15
|
-
export { explain, movedBy, relationsOfFiles, type FileEdge, type FileRecord, type Hole, type Reached, } from './records.js';
|
|
16
|
+
export { explain, movedBy, relationsOfFiles, type FileEdge, type FileRecord, type Hole, type MovedOptions, type PackageEdge, type RelationsOptions, type Reached, } from './records.js';
|
|
16
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/relate/index.js
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*
|
|
10
10
|
* See [`docs/selecting.md`](../../../../docs/selecting.md) for what it is for.
|
|
11
11
|
*/
|
|
12
|
-
export { EDGE_KINDS, NODE_KINDS, idOf, keyOf, nodeAt, nodesOfKind, relationsOf, } from './graph.js';
|
|
12
|
+
export { EDGE_KINDS, NODE_KINDS, RUNTIME_EDGES, idOf, keyOf, nodeAt, nodesOfKind, relationsOf, } from './graph.js';
|
|
13
|
+
export { beforeReach, movedBefore, within, } from './before.js';
|
|
13
14
|
export { dependenciesOf, dependentsOf, trailOf, } from './reach.js';
|
|
14
15
|
export { CLOSURE_EDGES, closureOf, driftedBetween, } from './merkle.js';
|
|
15
16
|
export { explain, movedBy, relationsOfFiles, } from './records.js';
|
package/dist/relate/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/relate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,UAAU,EACV,UAAU,EACV,IAAI,EACJ,KAAK,EACL,MAAM,EACN,WAAW,EACX,WAAW,GAQZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,OAAO,GAGR,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,aAAa,EACb,SAAS,EACT,cAAc,GAIf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,OAAO,EACP,OAAO,EACP,gBAAgB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/relate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,UAAU,EACV,UAAU,EACV,aAAa,EACb,IAAI,EACJ,KAAK,EACL,MAAM,EACN,WAAW,EACX,WAAW,GAQZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,WAAW,EACX,WAAW,EACX,MAAM,GAGP,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,OAAO,GAGR,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,aAAa,EACb,SAAS,EACT,cAAc,GAIf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,OAAO,EACP,OAAO,EACP,gBAAgB,GAQjB,MAAM,cAAc,CAAC","sourcesContent":["/**\n * `core/relate` — what reaches what.\n *\n * A typed, interned, bidirectional graph and the two traversals over it, plus the\n * file-shaped record the scanner produces and the fold that turns a pile of them\n * into the structure. No I/O, no parser, no resolver: the expensive half belongs\n * to whoever owns the disk, and this is the half that has to be fast and has to\n * be provable without one.\n *\n * See [`docs/selecting.md`](../../../../docs/selecting.md) for what it is for.\n */\n\nexport {\n EDGE_KINDS,\n NODE_KINDS,\n RUNTIME_EDGES,\n idOf,\n keyOf,\n nodeAt,\n nodesOfKind,\n relationsOf,\n type Adjacency,\n type EdgeKind,\n type Node,\n type NodeId,\n type NodeKind,\n type Relation,\n type Relations,\n} from './graph.js';\n\nexport {\n beforeReach,\n movedBefore,\n within,\n type BeforeReach,\n type BeforeReachOptions,\n} from './before.js';\n\nexport {\n dependenciesOf,\n dependentsOf,\n trailOf,\n type Reach,\n type ReachOptions,\n} from './reach.js';\n\nexport {\n CLOSURE_EDGES,\n closureOf,\n driftedBetween,\n type Closure,\n type ClosureInput,\n type Drift,\n} from './merkle.js';\n\nexport {\n explain,\n movedBy,\n relationsOfFiles,\n type FileEdge,\n type FileRecord,\n type Hole,\n type MovedOptions,\n type PackageEdge,\n type RelationsOptions,\n type Reached,\n} from './records.js';\n"]}
|
package/dist/relate/merkle.d.ts
CHANGED
|
@@ -100,11 +100,20 @@ export declare function driftedBetween(before: Closure, after: Closure): Drift;
|
|
|
100
100
|
* The edge kinds a closure walks when the caller does not say.
|
|
101
101
|
*
|
|
102
102
|
* Listed rather than derived from `EDGE_KINDS`, and the difference is the whole
|
|
103
|
-
* point. A digest is a claim about *which inputs* were folded, so
|
|
104
|
-
* fold silently changes every digest that exists — one
|
|
105
|
-
* next run finds nothing it can prove unchanged and re-observes
|
|
106
|
-
* while reporting a successful narrowing.
|
|
107
|
-
* cost, and it should read like one.
|
|
103
|
+
* point. A digest is a claim about *which inputs* were folded, so changing the
|
|
104
|
+
* fold silently changes every digest that exists — one edge kind added or
|
|
105
|
+
* removed, and the next run finds nothing it can prove unchanged and re-observes
|
|
106
|
+
* the entire suite while reporting a successful narrowing. Changing this list is
|
|
107
|
+
* a decision with a cost, and it should read like one.
|
|
108
|
+
*
|
|
109
|
+
* `type` is not in it: a type-only import is erased before a render, so a change
|
|
110
|
+
* behind one is not an input the render folded.
|
|
111
|
+
*
|
|
112
|
+
* `depends-on` is not in it either, for the opposite reason. It runs between two
|
|
113
|
+
* packages, and a package node is a name rather than a content digest — an
|
|
114
|
+
* install that moved is a fact the diff carries, never one a hash of names could
|
|
115
|
+
* reveal. Folding it would add a term that cannot change and a walk that cannot
|
|
116
|
+
* answer.
|
|
108
117
|
*/
|
|
109
118
|
export declare const CLOSURE_EDGES: readonly EdgeKind[];
|
|
110
119
|
//# sourceMappingURL=merkle.d.ts.map
|
package/dist/relate/merkle.js
CHANGED
|
@@ -216,17 +216,25 @@ function condense(relations, allowed) {
|
|
|
216
216
|
* The edge kinds a closure walks when the caller does not say.
|
|
217
217
|
*
|
|
218
218
|
* Listed rather than derived from `EDGE_KINDS`, and the difference is the whole
|
|
219
|
-
* point. A digest is a claim about *which inputs* were folded, so
|
|
220
|
-
* fold silently changes every digest that exists — one
|
|
221
|
-
* next run finds nothing it can prove unchanged and re-observes
|
|
222
|
-
* while reporting a successful narrowing.
|
|
223
|
-
* cost, and it should read like one.
|
|
219
|
+
* point. A digest is a claim about *which inputs* were folded, so changing the
|
|
220
|
+
* fold silently changes every digest that exists — one edge kind added or
|
|
221
|
+
* removed, and the next run finds nothing it can prove unchanged and re-observes
|
|
222
|
+
* the entire suite while reporting a successful narrowing. Changing this list is
|
|
223
|
+
* a decision with a cost, and it should read like one.
|
|
224
|
+
*
|
|
225
|
+
* `type` is not in it: a type-only import is erased before a render, so a change
|
|
226
|
+
* behind one is not an input the render folded.
|
|
227
|
+
*
|
|
228
|
+
* `depends-on` is not in it either, for the opposite reason. It runs between two
|
|
229
|
+
* packages, and a package node is a name rather than a content digest — an
|
|
230
|
+
* install that moved is a fact the diff carries, never one a hash of names could
|
|
231
|
+
* reveal. Folding it would add a term that cannot change and a walk that cannot
|
|
232
|
+
* answer.
|
|
224
233
|
*/
|
|
225
234
|
export const CLOSURE_EDGES = [
|
|
226
235
|
'imports',
|
|
227
236
|
'reexports',
|
|
228
237
|
'dynamic',
|
|
229
|
-
'type',
|
|
230
238
|
'asset',
|
|
231
239
|
'declared-in',
|
|
232
240
|
];
|