@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,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One file's outgoing edges, and the fold that turns a pile of them into a graph.
|
|
3
|
+
*
|
|
4
|
+
* A record is what reading **one** file produced, and that is the unit on
|
|
5
|
+
* purpose. It makes the expensive half — open, parse, resolve — cacheable by
|
|
6
|
+
* content digest and parallelizable per file, and it leaves the graph a pure
|
|
7
|
+
* function of the records, so the structure can be rebuilt, sharded or merged
|
|
8
|
+
* without touching a disk. `core` performs no I/O (ADR-0006); whoever owns the
|
|
9
|
+
* disk produces these and hands them over.
|
|
10
|
+
*
|
|
11
|
+
* ## An empty record and an unreadable one are different facts
|
|
12
|
+
*
|
|
13
|
+
* A file with no imports and a file nothing could parse both produce a record
|
|
14
|
+
* with no edges, and treating them alike is the one mistake that turns this from
|
|
15
|
+
* a saving into a silent hole: the second one may import anything, including
|
|
16
|
+
* whatever just changed. So `unknown` carries the *reason* the edges are not
|
|
17
|
+
* known, and every consumer seeds its traversal with those files (ADR-0002 —
|
|
18
|
+
* absent is not empty, and here absent is not even absent, it is *unread*).
|
|
19
|
+
*/
|
|
20
|
+
import { idOf, nodeAt, relationsOf, } from './graph.js';
|
|
21
|
+
import { dependentsOf, trailOf } from './reach.js';
|
|
22
|
+
const file = (name) => ({ kind: 'file', name });
|
|
23
|
+
const component = (name) => ({ kind: 'component', name });
|
|
24
|
+
/**
|
|
25
|
+
* Fold file records into the graph.
|
|
26
|
+
*
|
|
27
|
+
* Every record contributes its own node even when it has no edges, so a changed
|
|
28
|
+
* file that imports nothing is still findable — otherwise the one file in the
|
|
29
|
+
* diff would be missing from the structure that exists to answer questions about
|
|
30
|
+
* it.
|
|
31
|
+
*
|
|
32
|
+
* A declaration becomes an edge from the **component to the file**, which is the
|
|
33
|
+
* direction the convention requires: the component depends on the file that
|
|
34
|
+
* declares it, so one walk against the arrows from a changed file reaches every
|
|
35
|
+
* importer and every component in the same pass.
|
|
36
|
+
*/
|
|
37
|
+
export function relationsOfFiles(records) {
|
|
38
|
+
const relations = [];
|
|
39
|
+
const isolated = [];
|
|
40
|
+
const unknown = [];
|
|
41
|
+
for (const record of records) {
|
|
42
|
+
const from = file(record.file);
|
|
43
|
+
isolated.push(from);
|
|
44
|
+
for (const edge of record.edges ?? []) {
|
|
45
|
+
relations.push({ from, to: file(edge.to), kind: edge.kind });
|
|
46
|
+
}
|
|
47
|
+
for (const name of record.declares ?? []) {
|
|
48
|
+
relations.push({ from: component(name), to: from, kind: 'declared-in' });
|
|
49
|
+
}
|
|
50
|
+
// The sentence travels with the node. Reduced to a flag here, the paragraph
|
|
51
|
+
// this record exists to produce could name a count and never a cause.
|
|
52
|
+
if (record.unknown !== undefined)
|
|
53
|
+
unknown.push([from, record.unknown]);
|
|
54
|
+
}
|
|
55
|
+
return relationsOf({ relations, isolated, unknown });
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* What a set of changed files could have moved.
|
|
59
|
+
*
|
|
60
|
+
* The seed set is the changed files **and every file whose edges are unknown**,
|
|
61
|
+
* which is the rule the whole structure rests on. An unreadable file might import
|
|
62
|
+
* the one that changed; seeding it means everything depending on *it* is observed
|
|
63
|
+
* too, and the cost is a collection rather than a green run over an unwatched
|
|
64
|
+
* surface.
|
|
65
|
+
*
|
|
66
|
+
* One breadth-first search, whatever the number of changed files.
|
|
67
|
+
*/
|
|
68
|
+
export function movedBy(relations, changed) {
|
|
69
|
+
const seeds = [];
|
|
70
|
+
const missing = [];
|
|
71
|
+
for (const path of changed) {
|
|
72
|
+
const id = idOf(relations, 'file', path);
|
|
73
|
+
if (id === undefined)
|
|
74
|
+
missing.push(path);
|
|
75
|
+
else
|
|
76
|
+
seeds.push(id);
|
|
77
|
+
}
|
|
78
|
+
const opaque = [];
|
|
79
|
+
for (let id = 0; id < relations.unknown.length; id += 1) {
|
|
80
|
+
if (relations.unknown[id] !== 1)
|
|
81
|
+
continue;
|
|
82
|
+
seeds.push(id);
|
|
83
|
+
const because = relations.reasons.get(id);
|
|
84
|
+
opaque.push({ file: relations.names[id], ...(because === undefined ? {} : { because }) });
|
|
85
|
+
}
|
|
86
|
+
const reach = dependentsOf(relations, seeds);
|
|
87
|
+
const files = [];
|
|
88
|
+
const components = [];
|
|
89
|
+
for (const id of reach.reached) {
|
|
90
|
+
const node = nodeAt(relations, id);
|
|
91
|
+
if (node === undefined)
|
|
92
|
+
continue;
|
|
93
|
+
(node.kind === 'file' ? files : components).push(node.name);
|
|
94
|
+
}
|
|
95
|
+
return { files, components, missing: missing.sort(byCodeUnit), opaque, reach };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* How one file or component was reached, as the chain that reached it.
|
|
99
|
+
*
|
|
100
|
+
* The sentence a run prints when somebody asks why a subject was observed. Empty
|
|
101
|
+
* when the node was not reached at all.
|
|
102
|
+
*/
|
|
103
|
+
export function explain(relations, moved, node) {
|
|
104
|
+
const id = idOf(relations, node.kind, node.name);
|
|
105
|
+
if (id === undefined)
|
|
106
|
+
return [];
|
|
107
|
+
return trailOf(moved.reach, id).map((step) => relations.names[step]);
|
|
108
|
+
}
|
|
109
|
+
function byCodeUnit(a, b) {
|
|
110
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../../src/relate/records.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One file's outgoing edges, and the fold that turns a pile of them into a graph.\n *\n * A record is what reading **one** file produced, and that is the unit on\n * purpose. It makes the expensive half — open, parse, resolve — cacheable by\n * content digest and parallelizable per file, and it leaves the graph a pure\n * function of the records, so the structure can be rebuilt, sharded or merged\n * without touching a disk. `core` performs no I/O (ADR-0006); whoever owns the\n * disk produces these and hands them over.\n *\n * ## An empty record and an unreadable one are different facts\n *\n * A file with no imports and a file nothing could parse both produce a record\n * with no edges, and treating them alike is the one mistake that turns this from\n * a saving into a silent hole: the second one may import anything, including\n * whatever just changed. So `unknown` carries the *reason* the edges are not\n * known, and every consumer seeds its traversal with those files (ADR-0002 —\n * absent is not empty, and here absent is not even absent, it is *unread*).\n */\n\nimport type { Digest } from '../format/hash.js';\nimport {\n idOf,\n nodeAt,\n relationsOf,\n type EdgeKind,\n type Node,\n type NodeId,\n type Relation,\n type Relations,\n} from './graph.js';\nimport { dependentsOf, trailOf, type Reach } from './reach.js';\n\nexport interface FileEdge {\n /** Repository-relative, already resolved. A specifier is not an edge. */\n readonly to: string;\n readonly kind: EdgeKind;\n}\n\nexport interface FileRecord {\n /** Repository-relative, so a graph is portable between machines and CI. */\n readonly file: string;\n\n /**\n * The content digest this record was read from.\n *\n * Two jobs. It lets a second scan skip the parse for a file that has not moved,\n * which is the difference between a scan that costs a second and one nobody\n * leaves enabled. And it is the leaf of the Merkle fold in\n * [`merkle.ts`](./merkle.ts) — a file with no digest is one nothing can prove\n * unchanged, and is marked as such rather than assumed still.\n */\n readonly digest?: Digest;\n\n /** Resolved outgoing edges. Absent and empty mean the same thing here. */\n readonly edges?: readonly FileEdge[];\n\n /** Component names this file declares, from `indexSource` or better. */\n readonly declares?: readonly string[];\n\n /**\n * Specifiers that were read but did not resolve.\n *\n * Kept for the report rather than for the graph. A bare specifier that failed\n * to resolve is usually a package that is not installed; a **relative** one\n * that failed is a hole, and whoever produced the record says so by also\n * setting `unknown`.\n */\n readonly unresolved?: readonly string[];\n\n /**\n * Why this file's outgoing edges could not be enumerated, when they could not.\n *\n * Present means *this file may depend on anything*. The sentence is carried\n * rather than a flag, because it ends up in the paragraph explaining why a run\n * observed more than the operator expected.\n */\n readonly unknown?: string;\n}\n\nconst file = (name: string): Node => ({ kind: 'file', name });\nconst component = (name: string): Node => ({ kind: 'component', name });\n\n/**\n * Fold file records into the graph.\n *\n * Every record contributes its own node even when it has no edges, so a changed\n * file that imports nothing is still findable — otherwise the one file in the\n * diff would be missing from the structure that exists to answer questions about\n * it.\n *\n * A declaration becomes an edge from the **component to the file**, which is the\n * direction the convention requires: the component depends on the file that\n * declares it, so one walk against the arrows from a changed file reaches every\n * importer and every component in the same pass.\n */\nexport function relationsOfFiles(records: Iterable<FileRecord>): Relations {\n const relations: Relation[] = [];\n const isolated: Node[] = [];\n const unknown: (readonly [Node, string])[] = [];\n\n for (const record of records) {\n const from = file(record.file);\n isolated.push(from);\n\n for (const edge of record.edges ?? []) {\n relations.push({ from, to: file(edge.to), kind: edge.kind });\n }\n for (const name of record.declares ?? []) {\n relations.push({ from: component(name), to: from, kind: 'declared-in' });\n }\n // The sentence travels with the node. Reduced to a flag here, the paragraph\n // this record exists to produce could name a count and never a cause.\n if (record.unknown !== undefined) unknown.push([from, record.unknown]);\n }\n\n return relationsOf({ relations, isolated, unknown });\n}\n\nexport interface Hole {\n /** The file whose outgoing edges could not be enumerated. */\n readonly file: string;\n /** Why, in the words of whoever read it. Absent when nobody said. */\n readonly because?: string;\n}\n\nexport interface Reached {\n /** Files the change could have moved, including the changed files themselves. */\n readonly files: readonly string[];\n /** Components declared in any of them. */\n readonly components: readonly string[];\n\n /**\n * Changed paths the graph does not hold.\n *\n * Never silently ignored. A `README.md` belongs here and means nothing; a\n * source file belongs here only because the scan never reached it, and the\n * caller — which knows where it told the scan to look — is the one that can\n * tell those apart.\n */\n readonly missing: readonly string[];\n\n /**\n * Files seeded because their own edges are unknown, rather than because\n * anything reached them, each with the reason it could not be read.\n *\n * Counted separately so that \"we widened\" never hides inside \"we found\", and\n * carrying its sentence so the widening is a work item rather than a tax: *four\n * files could not be read* is something to live with, and ``src/legacy.js — a\n * computed require()`` is something to fix.\n */\n readonly opaque: readonly Hole[];\n\n /** The traversal, kept so a caller can ask how any one file was reached. */\n readonly reach: Reach;\n}\n\n/**\n * What a set of changed files could have moved.\n *\n * The seed set is the changed files **and every file whose edges are unknown**,\n * which is the rule the whole structure rests on. An unreadable file might import\n * the one that changed; seeding it means everything depending on *it* is observed\n * too, and the cost is a collection rather than a green run over an unwatched\n * surface.\n *\n * One breadth-first search, whatever the number of changed files.\n */\nexport function movedBy(relations: Relations, changed: Iterable<string>): Reached {\n const seeds: NodeId[] = [];\n const missing: string[] = [];\n\n for (const path of changed) {\n const id = idOf(relations, 'file', path);\n if (id === undefined) missing.push(path);\n else seeds.push(id);\n }\n\n const opaque: Hole[] = [];\n for (let id = 0; id < relations.unknown.length; id += 1) {\n if (relations.unknown[id] !== 1) continue;\n seeds.push(id);\n\n const because = relations.reasons.get(id);\n opaque.push({ file: relations.names[id]!, ...(because === undefined ? {} : { because }) });\n }\n\n const reach = dependentsOf(relations, seeds);\n const files: string[] = [];\n const components: string[] = [];\n\n for (const id of reach.reached) {\n const node = nodeAt(relations, id);\n if (node === undefined) continue;\n (node.kind === 'file' ? files : components).push(node.name);\n }\n\n return { files, components, missing: missing.sort(byCodeUnit), opaque, reach };\n}\n\n/**\n * How one file or component was reached, as the chain that reached it.\n *\n * The sentence a run prints when somebody asks why a subject was observed. Empty\n * when the node was not reached at all.\n */\nexport function explain(relations: Relations, moved: Reached, node: Node): readonly string[] {\n const id = idOf(relations, node.kind, node.name);\n if (id === undefined) return [];\n return trailOf(moved.reach, id).map((step) => relations.names[step]!);\n}\n\nfunction byCodeUnit(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@variance-authority/core/rules` — the versioned opinions.
|
|
3
|
+
*
|
|
4
|
+
* What survives to be compared, and what a surviving value is turned into: the
|
|
5
|
+
* property allowlist, the applicability ruleset, variable resolution, shorthand
|
|
6
|
+
* expansion, colour and dimension canonicalization.
|
|
7
|
+
*
|
|
8
|
+
* Separate from the format because this is the part with a **version on it**.
|
|
9
|
+
* `RULESET_VERSION` and `ALLOWLIST_VERSION` are folded into the identity of every
|
|
10
|
+
* answer downstream, so changing a rule here makes stored results incomparable
|
|
11
|
+
* rather than silently comparable — and a reader who wants to know *why* two runs
|
|
12
|
+
* disagree ends up in this directory rather than anywhere else.
|
|
13
|
+
*/
|
|
14
|
+
export { normalize } from './normalize/index.js';
|
|
15
|
+
export type { NormalizeOptions } from './normalize/index.js';
|
|
16
|
+
export type { AliasMap, AliasResult, InheritContext, ResolvedStyle, DeclarationOrigin, } from './normalize/index.js';
|
|
17
|
+
export { buildAliasMap, aliasAttributeValue, aliasStyleValue, resolveStyle, resolveVariables, INHERITED_PROPERTIES, EMPTY_CONTEXT, canonicalizeValue, canonicalizeTokens, canonicalizeDimension, canonicalizeColor, parseColor, formatColor, expandDeclaration, SHORTHAND_PROPERTIES, } from './normalize/index.js';
|
|
18
|
+
export { RULESET_VERSION, ALLOWLIST_VERSION, STYLE_ALLOWLIST, ATTRIBUTE_ALLOWLIST, ID_REFERENCE_ATTRIBUTES, ID_REFERENCE_LIST_ATTRIBUTES, isAllowedProperty, isCustomProperty, admits, admitsAttribute, } from './ruleset.js';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@variance-authority/core/rules` — the versioned opinions.
|
|
3
|
+
*
|
|
4
|
+
* What survives to be compared, and what a surviving value is turned into: the
|
|
5
|
+
* property allowlist, the applicability ruleset, variable resolution, shorthand
|
|
6
|
+
* expansion, colour and dimension canonicalization.
|
|
7
|
+
*
|
|
8
|
+
* Separate from the format because this is the part with a **version on it**.
|
|
9
|
+
* `RULESET_VERSION` and `ALLOWLIST_VERSION` are folded into the identity of every
|
|
10
|
+
* answer downstream, so changing a rule here makes stored results incomparable
|
|
11
|
+
* rather than silently comparable — and a reader who wants to know *why* two runs
|
|
12
|
+
* disagree ends up in this directory rather than anywhere else.
|
|
13
|
+
*/
|
|
14
|
+
export { normalize } from './normalize/index.js';
|
|
15
|
+
export { buildAliasMap, aliasAttributeValue, aliasStyleValue, resolveStyle, resolveVariables, INHERITED_PROPERTIES, EMPTY_CONTEXT, canonicalizeValue, canonicalizeTokens, canonicalizeDimension, canonicalizeColor, parseColor, formatColor, expandDeclaration, SHORTHAND_PROPERTIES, } from './normalize/index.js';
|
|
16
|
+
export { RULESET_VERSION, ALLOWLIST_VERSION, STYLE_ALLOWLIST, ATTRIBUTE_ALLOWLIST, ID_REFERENCE_ATTRIBUTES, ID_REFERENCE_LIST_ATTRIBUTES, isAllowedProperty, isCustomProperty, admits, admitsAttribute, } from './ruleset.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AASjD,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,uBAAuB,EACvB,4BAA4B,EAC5B,iBAAiB,EACjB,gBAAgB,EAChB,MAAM,EACN,eAAe,GAChB,MAAM,cAAc,CAAC","sourcesContent":["/**\n * `@variance-authority/core/rules` — the versioned opinions.\n *\n * What survives to be compared, and what a surviving value is turned into: the\n * property allowlist, the applicability ruleset, variable resolution, shorthand\n * expansion, colour and dimension canonicalization.\n *\n * Separate from the format because this is the part with a **version on it**.\n * `RULESET_VERSION` and `ALLOWLIST_VERSION` are folded into the identity of every\n * answer downstream, so changing a rule here makes stored results incomparable\n * rather than silently comparable — and a reader who wants to know *why* two runs\n * disagree ends up in this directory rather than anywhere else.\n */\n\nexport { normalize } from './normalize/index.js';\nexport type { NormalizeOptions } from './normalize/index.js';\nexport type {\n AliasMap,\n AliasResult,\n InheritContext,\n ResolvedStyle,\n DeclarationOrigin,\n} from './normalize/index.js';\nexport {\n buildAliasMap,\n aliasAttributeValue,\n aliasStyleValue,\n resolveStyle,\n resolveVariables,\n INHERITED_PROPERTIES,\n EMPTY_CONTEXT,\n canonicalizeValue,\n canonicalizeTokens,\n canonicalizeDimension,\n canonicalizeColor,\n parseColor,\n formatColor,\n expandDeclaration,\n SHORTHAND_PROPERTIES,\n} from './normalize/index.js';\n\nexport {\n RULESET_VERSION,\n ALLOWLIST_VERSION,\n STYLE_ALLOWLIST,\n ATTRIBUTE_ALLOWLIST,\n ID_REFERENCE_ATTRIBUTES,\n ID_REFERENCE_LIST_ATTRIBUTES,\n isAllowedProperty,\n isCustomProperty,\n admits,\n admitsAttribute,\n} from './ruleset.js';\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { RawNode } from '../../format/capture.js';
|
|
2
|
+
/**
|
|
3
|
+
* Structural id aliasing (ADR-0003).
|
|
4
|
+
*
|
|
5
|
+
* React's `useId` allocates in mount order, so mounting an unrelated component
|
|
6
|
+
* earlier renumbers every id on the page. Headless component libraries generate
|
|
7
|
+
* an id for every `label ↔ input` and `aria-*` association. None of those values
|
|
8
|
+
* mean anything; all of them churn.
|
|
9
|
+
*
|
|
10
|
+
* Masking them to a constant would work, and would also erase the difference
|
|
11
|
+
* between "the id was renumbered" and "the association was broken" — the second
|
|
12
|
+
* being a real accessibility regression. Aliasing keeps the relationship and
|
|
13
|
+
* discards only the value: every id becomes `#a0`, `#a1`, … in document order,
|
|
14
|
+
* and every reference is rewritten to match.
|
|
15
|
+
*/
|
|
16
|
+
export interface AliasMap {
|
|
17
|
+
/** Original id → `#aN`, for ids defined inside the subject subtree. */
|
|
18
|
+
readonly local: ReadonlyMap<string, string>;
|
|
19
|
+
/** Original id → `#extern:N`, for references that escape the subtree. */
|
|
20
|
+
readonly external: ReadonlyMap<string, string>;
|
|
21
|
+
}
|
|
22
|
+
export interface AliasResult extends AliasMap {
|
|
23
|
+
/** Ids referenced but never defined anywhere. A real defect, kept visible. */
|
|
24
|
+
readonly dangling: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Assign aliases by walking the subtree in document order.
|
|
28
|
+
*
|
|
29
|
+
* Two passes, because a reference may precede its definition — `aria-labelledby`
|
|
30
|
+
* pointing forward at a sibling is ordinary markup. Collecting every definition
|
|
31
|
+
* first is what keeps the alias of an id independent of where it is mentioned.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildAliasMap(root: RawNode, portals?: readonly RawNode[]): AliasResult;
|
|
34
|
+
/**
|
|
35
|
+
* Rewrite one attribute value through the alias map.
|
|
36
|
+
*
|
|
37
|
+
* Only attributes that actually hold id references are touched. Aliasing
|
|
38
|
+
* anything else would rewrite ordinary values — `type="button"` has no id in it,
|
|
39
|
+
* and mapping it through would corrupt every attribute in the snapshot.
|
|
40
|
+
*/
|
|
41
|
+
export declare function aliasAttributeValue(map: AliasMap, name: string, value: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Rewrite `url(#…)` fragments inside a style value.
|
|
44
|
+
*
|
|
45
|
+
* SVG filters, clip paths, masks, and gradients are referenced this way, and
|
|
46
|
+
* their ids are generated exactly as often as any other. A `filter: url(#blur-7)`
|
|
47
|
+
* that renumbers to `url(#blur-9)` paints identical pixels.
|
|
48
|
+
*/
|
|
49
|
+
export declare function aliasStyleValue(map: AliasMap, value: string): string;
|
|
50
|
+
//# sourceMappingURL=alias.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { ID_REFERENCE_ATTRIBUTES, ID_REFERENCE_LIST_ATTRIBUTES } from '../ruleset.js';
|
|
2
|
+
const ID_LIST_ATTRIBUTES = new Set(ID_REFERENCE_LIST_ATTRIBUTES);
|
|
3
|
+
const ID_REFERENCE = new Set(ID_REFERENCE_ATTRIBUTES);
|
|
4
|
+
/**
|
|
5
|
+
* Assign aliases by walking the subtree in document order.
|
|
6
|
+
*
|
|
7
|
+
* Two passes, because a reference may precede its definition — `aria-labelledby`
|
|
8
|
+
* pointing forward at a sibling is ordinary markup. Collecting every definition
|
|
9
|
+
* first is what keeps the alias of an id independent of where it is mentioned.
|
|
10
|
+
*/
|
|
11
|
+
export function buildAliasMap(root, portals = []) {
|
|
12
|
+
const local = new Map();
|
|
13
|
+
const external = new Map();
|
|
14
|
+
const dangling = [];
|
|
15
|
+
// Portalled content is part of the subject (ADR-0007), so it shares one alias
|
|
16
|
+
// space with the container. A dialog whose `aria-labelledby` points at a title
|
|
17
|
+
// inside the portal must resolve, not report as dangling.
|
|
18
|
+
const roots = [root, ...portals];
|
|
19
|
+
const walkAll = (visit) => {
|
|
20
|
+
for (const each of roots)
|
|
21
|
+
walk(each, visit);
|
|
22
|
+
};
|
|
23
|
+
walkAll((node) => {
|
|
24
|
+
const id = node.attributes['id'];
|
|
25
|
+
if (id !== undefined && id.length > 0 && !local.has(id)) {
|
|
26
|
+
local.set(id, `#a${local.size}`);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
walkAll((node) => {
|
|
30
|
+
for (const [name, value] of Object.entries(node.attributes)) {
|
|
31
|
+
for (const reference of referencesIn(name, value)) {
|
|
32
|
+
if (local.has(reference) || external.has(reference))
|
|
33
|
+
continue;
|
|
34
|
+
// Escaping the subtree is not the same as pointing at nothing, but from
|
|
35
|
+
// inside the subject the two are indistinguishable — so both are aliased
|
|
36
|
+
// and flagged rather than normalized into invisibility.
|
|
37
|
+
external.set(reference, `#extern:${external.size}`);
|
|
38
|
+
dangling.push(reference);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return { local, external, dangling };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Rewrite one attribute value through the alias map.
|
|
46
|
+
*
|
|
47
|
+
* Only attributes that actually hold id references are touched. Aliasing
|
|
48
|
+
* anything else would rewrite ordinary values — `type="button"` has no id in it,
|
|
49
|
+
* and mapping it through would corrupt every attribute in the snapshot.
|
|
50
|
+
*/
|
|
51
|
+
export function aliasAttributeValue(map, name, value) {
|
|
52
|
+
if (!ID_REFERENCE.has(name) && name !== 'href' && name !== 'xlink:href')
|
|
53
|
+
return value;
|
|
54
|
+
if (name === 'id')
|
|
55
|
+
return map.local.get(value) ?? aliasOf(map, value);
|
|
56
|
+
if (ID_LIST_ATTRIBUTES.has(name)) {
|
|
57
|
+
return value
|
|
58
|
+
.trim()
|
|
59
|
+
.split(/\s+/)
|
|
60
|
+
.filter((part) => part.length > 0)
|
|
61
|
+
.map((part) => aliasOf(map, part))
|
|
62
|
+
.join(' ');
|
|
63
|
+
}
|
|
64
|
+
if (name === 'href' || name === 'xlink:href') {
|
|
65
|
+
return value.startsWith('#') ? aliasOf(map, value.slice(1)) : value;
|
|
66
|
+
}
|
|
67
|
+
return aliasOf(map, value);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Rewrite `url(#…)` fragments inside a style value.
|
|
71
|
+
*
|
|
72
|
+
* SVG filters, clip paths, masks, and gradients are referenced this way, and
|
|
73
|
+
* their ids are generated exactly as often as any other. A `filter: url(#blur-7)`
|
|
74
|
+
* that renumbers to `url(#blur-9)` paints identical pixels.
|
|
75
|
+
*/
|
|
76
|
+
export function aliasStyleValue(map, value) {
|
|
77
|
+
return value.replace(/url\(\s*(['"]?)#([^'")]+)\1\s*\)/g, (_match, quote, id) => {
|
|
78
|
+
return `url(${quote}${aliasOf(map, id)}${quote})`;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function aliasOf(map, id) {
|
|
82
|
+
return map.local.get(id) ?? map.external.get(id) ?? `#extern:?${id.length}`;
|
|
83
|
+
}
|
|
84
|
+
function referencesIn(name, value) {
|
|
85
|
+
if (name === 'id')
|
|
86
|
+
return [];
|
|
87
|
+
if (ID_LIST_ATTRIBUTES.has(name)) {
|
|
88
|
+
return value.trim().split(/\s+/).filter((part) => part.length > 0);
|
|
89
|
+
}
|
|
90
|
+
if (name === 'for' || name === 'form' || name === 'list') {
|
|
91
|
+
return value.length > 0 ? [value] : [];
|
|
92
|
+
}
|
|
93
|
+
if (name === 'href' || name === 'xlink:href') {
|
|
94
|
+
return value.startsWith('#') ? [value.slice(1)] : [];
|
|
95
|
+
}
|
|
96
|
+
// Only the `aria-*` attributes that actually hold IDREFs. `aria-label` and
|
|
97
|
+
// `aria-hidden` carry text and booleans; treating them as references would
|
|
98
|
+
// report a dangling-id diagnostic for every labelled node in the tree.
|
|
99
|
+
if (ID_REFERENCE.has(name)) {
|
|
100
|
+
return value.length > 0 ? [value] : [];
|
|
101
|
+
}
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
function walk(node, visit) {
|
|
105
|
+
visit(node);
|
|
106
|
+
for (const child of node.children)
|
|
107
|
+
walk(child, visit);
|
|
108
|
+
for (const child of node.shadowChildren ?? [])
|
|
109
|
+
walk(child, visit);
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=alias.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alias.js","sourceRoot":"","sources":["../../../src/rules/normalize/alias.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RawNode } from '../../format/capture.js';\nimport { ID_REFERENCE_ATTRIBUTES, ID_REFERENCE_LIST_ATTRIBUTES } from '../ruleset.js';\n\n/**\n * Structural id aliasing (ADR-0003).\n *\n * React's `useId` allocates in mount order, so mounting an unrelated component\n * earlier renumbers every id on the page. Headless component libraries generate\n * an id for every `label ↔ input` and `aria-*` association. None of those values\n * mean anything; all of them churn.\n *\n * Masking them to a constant would work, and would also erase the difference\n * between \"the id was renumbered\" and \"the association was broken\" — the second\n * being a real accessibility regression. Aliasing keeps the relationship and\n * discards only the value: every id becomes `#a0`, `#a1`, … in document order,\n * and every reference is rewritten to match.\n */\n\nexport interface AliasMap {\n /** Original id → `#aN`, for ids defined inside the subject subtree. */\n readonly local: ReadonlyMap<string, string>;\n /** Original id → `#extern:N`, for references that escape the subtree. */\n readonly external: ReadonlyMap<string, string>;\n}\n\nexport interface AliasResult extends AliasMap {\n /** Ids referenced but never defined anywhere. A real defect, kept visible. */\n readonly dangling: readonly string[];\n}\n\nconst ID_LIST_ATTRIBUTES = new Set(ID_REFERENCE_LIST_ATTRIBUTES);\nconst ID_REFERENCE = new Set(ID_REFERENCE_ATTRIBUTES);\n\n/**\n * Assign aliases by walking the subtree in document order.\n *\n * Two passes, because a reference may precede its definition — `aria-labelledby`\n * pointing forward at a sibling is ordinary markup. Collecting every definition\n * first is what keeps the alias of an id independent of where it is mentioned.\n */\nexport function buildAliasMap(root: RawNode, portals: readonly RawNode[] = []): AliasResult {\n const local = new Map<string, string>();\n const external = new Map<string, string>();\n const dangling: string[] = [];\n\n // Portalled content is part of the subject (ADR-0007), so it shares one alias\n // space with the container. A dialog whose `aria-labelledby` points at a title\n // inside the portal must resolve, not report as dangling.\n const roots = [root, ...portals];\n const walkAll = (visit: (node: RawNode) => void): void => {\n for (const each of roots) walk(each, visit);\n };\n\n walkAll((node) => {\n const id = node.attributes['id'];\n if (id !== undefined && id.length > 0 && !local.has(id)) {\n local.set(id, `#a${local.size}`);\n }\n });\n\n walkAll((node) => {\n for (const [name, value] of Object.entries(node.attributes)) {\n for (const reference of referencesIn(name, value)) {\n if (local.has(reference) || external.has(reference)) continue;\n // Escaping the subtree is not the same as pointing at nothing, but from\n // inside the subject the two are indistinguishable — so both are aliased\n // and flagged rather than normalized into invisibility.\n external.set(reference, `#extern:${external.size}`);\n dangling.push(reference);\n }\n }\n });\n\n return { local, external, dangling };\n}\n\n/**\n * Rewrite one attribute value through the alias map.\n *\n * Only attributes that actually hold id references are touched. Aliasing\n * anything else would rewrite ordinary values — `type=\"button\"` has no id in it,\n * and mapping it through would corrupt every attribute in the snapshot.\n */\nexport function aliasAttributeValue(map: AliasMap, name: string, value: string): string {\n if (!ID_REFERENCE.has(name) && name !== 'href' && name !== 'xlink:href') return value;\n\n if (name === 'id') return map.local.get(value) ?? aliasOf(map, value);\n\n if (ID_LIST_ATTRIBUTES.has(name)) {\n return value\n .trim()\n .split(/\\s+/)\n .filter((part) => part.length > 0)\n .map((part) => aliasOf(map, part))\n .join(' ');\n }\n\n if (name === 'href' || name === 'xlink:href') {\n return value.startsWith('#') ? aliasOf(map, value.slice(1)) : value;\n }\n\n return aliasOf(map, value);\n}\n\n/**\n * Rewrite `url(#…)` fragments inside a style value.\n *\n * SVG filters, clip paths, masks, and gradients are referenced this way, and\n * their ids are generated exactly as often as any other. A `filter: url(#blur-7)`\n * that renumbers to `url(#blur-9)` paints identical pixels.\n */\nexport function aliasStyleValue(map: AliasMap, value: string): string {\n return value.replace(/url\\(\\s*(['\"]?)#([^'\")]+)\\1\\s*\\)/g, (_match, quote: string, id: string) => {\n return `url(${quote}${aliasOf(map, id)}${quote})`;\n });\n}\n\nfunction aliasOf(map: AliasMap, id: string): string {\n return map.local.get(id) ?? map.external.get(id) ?? `#extern:?${id.length}`;\n}\n\nfunction referencesIn(name: string, value: string): readonly string[] {\n if (name === 'id') return [];\n\n if (ID_LIST_ATTRIBUTES.has(name)) {\n return value.trim().split(/\\s+/).filter((part) => part.length > 0);\n }\n\n if (name === 'for' || name === 'form' || name === 'list') {\n return value.length > 0 ? [value] : [];\n }\n\n if (name === 'href' || name === 'xlink:href') {\n return value.startsWith('#') ? [value.slice(1)] : [];\n }\n\n // Only the `aria-*` attributes that actually hold IDREFs. `aria-label` and\n // `aria-hidden` carry text and booleans; treating them as references would\n // report a dangling-id diagnostic for every labelled node in the tree.\n if (ID_REFERENCE.has(name)) {\n return value.length > 0 ? [value] : [];\n }\n\n return [];\n}\n\nfunction walk(node: RawNode, visit: (node: RawNode) => void): void {\n visit(node);\n for (const child of node.children) walk(child, visit);\n for (const child of node.shadowChildren ?? []) walk(child, visit);\n}\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { MatchedRule } from '../../format/capture.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cascade resolution — ADR-0003 step 5.
|
|
4
|
+
*
|
|
5
|
+
* Losing declarations cannot reach a pixel, so they must not reach a hash. A
|
|
6
|
+
* specificity war that changes *which rule wins* without changing the *winning
|
|
7
|
+
* value* is a refactor, and refactors must not invalidate baselines.
|
|
8
|
+
*
|
|
9
|
+
* Under a profile with computed style the engine has already done this, and its
|
|
10
|
+
* answer is authoritative. This code runs anyway, for two reasons: it supplies
|
|
11
|
+
* the values under `declared-only` profiles, and under both profiles it supplies
|
|
12
|
+
* the attribution side-channel — *which rule set this?* — which computed style
|
|
13
|
+
* cannot answer at all.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Properties that inherit.
|
|
17
|
+
*
|
|
18
|
+
* Restricted to the allowlist: inheriting a property the snapshot does not
|
|
19
|
+
* record would cost work and change nothing. Without this, a `color` set once on
|
|
20
|
+
* a container would be invisible on every descendant that renders text under a
|
|
21
|
+
* declared-only profile — the most common styling pattern there is.
|
|
22
|
+
*/
|
|
23
|
+
export declare const INHERITED_PROPERTIES: readonly string[];
|
|
24
|
+
/** Values flowing into a node from its ancestors. */
|
|
25
|
+
export interface InheritContext {
|
|
26
|
+
/** Resolved values of inheritable properties. */
|
|
27
|
+
readonly inherited: Readonly<Record<string, string>>;
|
|
28
|
+
/** Custom properties in scope. Every custom property inherits. */
|
|
29
|
+
readonly customProperties: Readonly<Record<string, string>>;
|
|
30
|
+
/**
|
|
31
|
+
* For each inherited property, the token its value came from.
|
|
32
|
+
*
|
|
33
|
+
* Inheritance would otherwise launder away the attribution: a heading that
|
|
34
|
+
* inherits `color` from a container styled with `var(--brand)` produces a
|
|
35
|
+
* delta with no token, no owner chain of its own, and therefore its own
|
|
36
|
+
* `unattributed` root. One token edit would fan out into a docket entry per
|
|
37
|
+
* inheriting text node — destroying precisely the "one root, N collateral"
|
|
38
|
+
* claim the token band exists to make.
|
|
39
|
+
*/
|
|
40
|
+
readonly inheritedTokens?: Readonly<Record<string, string>>;
|
|
41
|
+
}
|
|
42
|
+
export declare const EMPTY_CONTEXT: InheritContext;
|
|
43
|
+
export interface ResolvedStyle {
|
|
44
|
+
/** Winning, canonicalized values for allowlisted properties. */
|
|
45
|
+
readonly style: Readonly<Record<string, string>>;
|
|
46
|
+
/** Custom properties a winning value resolved through: name → resolved value. */
|
|
47
|
+
readonly tokens: Readonly<Record<string, string>>;
|
|
48
|
+
/** Where each winning declaration came from. Outside the hash by design. */
|
|
49
|
+
readonly origins: Readonly<Record<string, DeclarationOrigin>>;
|
|
50
|
+
/**
|
|
51
|
+
* Which token each property's value came from, including inherited ones.
|
|
52
|
+
*
|
|
53
|
+
* Needed because "this node uses tokens" is not specific enough to attribute a
|
|
54
|
+
* delta: a node may resolve `color` through a token while its `padding` comes
|
|
55
|
+
* from a literal, and only the first is collateral of a token edit.
|
|
56
|
+
*/
|
|
57
|
+
readonly propertyTokens: Readonly<Record<string, string>>;
|
|
58
|
+
/** Context to pass to this node's children. */
|
|
59
|
+
readonly childContext: InheritContext;
|
|
60
|
+
}
|
|
61
|
+
export interface DeclarationOrigin {
|
|
62
|
+
readonly sheet: string;
|
|
63
|
+
readonly selector: string;
|
|
64
|
+
readonly source?: {
|
|
65
|
+
readonly file: string;
|
|
66
|
+
readonly line: number;
|
|
67
|
+
};
|
|
68
|
+
readonly tokenName?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface ResolveInput {
|
|
71
|
+
readonly matchedRules: readonly MatchedRule[];
|
|
72
|
+
readonly inlineStyle?: Readonly<Record<string, string>>;
|
|
73
|
+
/** Engine-resolved values. When present these win over anything computed here. */
|
|
74
|
+
readonly computedStyle?: Readonly<Record<string, string>>;
|
|
75
|
+
readonly context: InheritContext;
|
|
76
|
+
}
|
|
77
|
+
export declare function resolveStyle(input: ResolveInput): ResolvedStyle;
|
|
78
|
+
interface VariableResolution {
|
|
79
|
+
readonly value: string;
|
|
80
|
+
/** Custom property names consulted, in order of first appearance. */
|
|
81
|
+
readonly used: readonly string[];
|
|
82
|
+
}
|
|
83
|
+
export declare function resolveVariables(value: string, customProperties: Readonly<Record<string, string>>, depth?: number): VariableResolution;
|
|
84
|
+
export {};
|
|
85
|
+
//# sourceMappingURL=cascade.d.ts.map
|