@variance-authority/core 0.14.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +878 -0
- package/README.md +38 -17
- package/dist/attribute/component-hash.d.ts +14 -5
- package/dist/attribute/component-hash.js +14 -5
- package/dist/attribute/component-hash.js.map +1 -1
- package/dist/attribute/composition.d.ts +16 -0
- package/dist/attribute/composition.js +4 -6
- package/dist/attribute/composition.js.map +1 -1
- package/dist/attribute/index.d.ts +3 -1
- package/dist/attribute/index.js +2 -1
- package/dist/attribute/index.js.map +1 -1
- package/dist/attribute/movement.d.ts +1 -1
- package/dist/attribute/movement.js.map +1 -1
- package/dist/attribute/region.d.ts +47 -4
- package/dist/attribute/region.js +91 -22
- package/dist/attribute/region.js.map +1 -1
- package/dist/attribute/source.d.ts +10 -24
- package/dist/attribute/source.js +4 -38
- package/dist/attribute/source.js.map +1 -1
- package/dist/attribute/subject-pieces.d.ts +37 -0
- package/dist/attribute/subject-pieces.js +122 -0
- package/dist/attribute/subject-pieces.js.map +1 -0
- package/dist/compare/band.d.ts +8 -2
- package/dist/compare/band.js +7 -2
- package/dist/compare/band.js.map +1 -1
- package/dist/compare/diff/attribution.js +6 -4
- package/dist/compare/diff/attribution.js.map +1 -1
- package/dist/compare/diff/compare-nodes.js +9 -1
- package/dist/compare/diff/compare-nodes.js.map +1 -1
- package/dist/compare/diff/match.d.ts +2 -1
- package/dist/compare/diff/match.js +52 -0
- package/dist/compare/diff/match.js.map +1 -1
- package/dist/compare/parting.js +6 -4
- package/dist/compare/parting.js.map +1 -1
- package/dist/format/capture.d.ts +25 -0
- package/dist/format/capture.js +6 -0
- package/dist/format/capture.js.map +1 -1
- package/dist/format/document.d.ts +28 -5
- package/dist/format/document.js +8 -2
- package/dist/format/document.js.map +1 -1
- package/dist/format/drive.d.ts +191 -0
- package/dist/format/drive.js +343 -0
- package/dist/format/drive.js.map +1 -0
- package/dist/format/environment.d.ts +20 -2
- package/dist/format/environment.js +7 -0
- package/dist/format/environment.js.map +1 -1
- package/dist/format/index.d.ts +13 -3
- package/dist/format/index.js +8 -2
- package/dist/format/index.js.map +1 -1
- package/dist/format/intervention.d.ts +79 -0
- package/dist/format/intervention.js.map +1 -1
- package/dist/format/origin.d.ts +32 -0
- package/dist/format/origin.js +54 -0
- package/dist/format/origin.js.map +1 -0
- package/dist/format/packed.d.ts +32 -0
- package/dist/format/packed.js +23 -0
- package/dist/format/packed.js.map +1 -0
- package/dist/format/shape.d.ts +31 -0
- package/dist/format/shape.js +269 -0
- package/dist/format/shape.js.map +1 -0
- package/dist/format/snapshot.d.ts +2 -0
- package/dist/format/snapshot.js.map +1 -1
- package/dist/format/stabilize.d.ts +35 -6
- package/dist/format/stabilize.js +58 -103
- package/dist/format/stabilize.js.map +1 -1
- package/dist/format/steps.d.ts +44 -0
- package/dist/format/steps.js +67 -0
- package/dist/format/steps.js.map +1 -0
- package/dist/format/waits.d.ts +12 -0
- package/dist/format/waits.js +156 -0
- package/dist/format/waits.js.map +1 -0
- package/dist/format/width.d.ts +35 -0
- package/dist/format/width.js +38 -0
- package/dist/format/width.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/judge/docket.js +1 -0
- package/dist/judge/docket.js.map +1 -1
- package/dist/judge/index.d.ts +1 -0
- package/dist/judge/index.js +1 -0
- package/dist/judge/index.js.map +1 -1
- package/dist/judge/restamp.d.ts +20 -0
- package/dist/judge/restamp.js +20 -0
- package/dist/judge/restamp.js.map +1 -0
- package/dist/nearest.d.ts +46 -0
- package/dist/nearest.js +102 -0
- package/dist/nearest.js.map +1 -0
- package/dist/relate/before.d.ts +19 -24
- package/dist/relate/before.js +37 -39
- package/dist/relate/before.js.map +1 -1
- package/dist/relate/beyond.d.ts +59 -0
- package/dist/relate/beyond.js +98 -0
- package/dist/relate/beyond.js.map +1 -0
- package/dist/relate/carried.d.ts +122 -0
- package/dist/relate/carried.js +279 -0
- package/dist/relate/carried.js.map +1 -0
- package/dist/relate/graph.d.ts +14 -12
- package/dist/relate/graph.js +12 -11
- package/dist/relate/graph.js.map +1 -1
- package/dist/relate/index.d.ts +3 -1
- package/dist/relate/index.js +3 -1
- package/dist/relate/index.js.map +1 -1
- package/dist/relate/merkle.js +2 -11
- package/dist/relate/merkle.js.map +1 -1
- package/dist/relate/narrow.d.ts +2 -2
- package/dist/relate/narrow.js +26 -73
- package/dist/relate/narrow.js.map +1 -1
- package/dist/relate/reach.d.ts +6 -6
- package/dist/relate/reach.js +10 -53
- package/dist/relate/reach.js.map +1 -1
- package/dist/relate/records.d.ts +6 -5
- package/dist/relate/records.js +3 -2
- package/dist/relate/records.js.map +1 -1
- package/dist/rules/index.d.ts +3 -3
- package/dist/rules/index.js +3 -3
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/normalize/cascade.d.ts +12 -0
- package/dist/rules/normalize/cascade.js +34 -16
- package/dist/rules/normalize/cascade.js.map +1 -1
- package/dist/rules/normalize/fixture.d.ts +3 -0
- package/dist/rules/normalize/fixture.js +3 -1
- package/dist/rules/normalize/fixture.js.map +1 -1
- package/dist/rules/normalize/index.d.ts +2 -2
- package/dist/rules/normalize/index.js +9 -2
- package/dist/rules/normalize/index.js.map +1 -1
- package/dist/rules/normalize/value.d.ts +13 -2
- package/dist/rules/normalize/value.js +42 -8
- package/dist/rules/normalize/value.js.map +1 -1
- package/dist/rules/normalize/wrapper.js +5 -0
- package/dist/rules/normalize/wrapper.js.map +1 -1
- package/dist/rules/ruleset.d.ts +24 -4
- package/dist/rules/ruleset.js +24 -4
- package/dist/rules/ruleset.js.map +1 -1
- package/dist/segment/index.d.ts +20 -4
- package/dist/segment/index.js +28 -7
- package/dist/segment/index.js.map +1 -1
- package/dist/shard/index.d.ts +11 -0
- package/dist/shard/index.js +11 -0
- package/dist/shard/index.js.map +1 -0
- package/dist/shard/placement.d.ts +119 -0
- package/dist/shard/placement.js +156 -0
- package/dist/shard/placement.js.map +1 -0
- package/dist/share/index.d.ts +1 -1
- package/dist/share/index.js +1 -1
- package/dist/share/index.js.map +1 -1
- package/package.json +8 -1
package/dist/relate/reach.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reach.js","sourceRoot":"","sources":["../../src/relate/reach.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Walking the graph, and being able to say why the walk arrived.\n *\n * Two questions, one traversal each way:\n *\n * - **`dependentsOf`** — against the arrows. *What could this change affect?*\n * This is the one selection asks, and it is asked once per run over the whole\n * seed set rather than once per seed, because a breadth-first search from many\n * sources costs the same as one from a single source.\n * - **`dependenciesOf`** — along them. *What does this rest on?* The cache-key\n * question, and the one a reviewer asks about a file they have not read.\n *\n * ## The trail is not an extra\n *\n * Both return the path to each node they visit, and that is load-bearing rather\n * than a convenience. A selector that says *observe 41 of 300* is asking to be\n * trusted; a selector that says *`Button` is affected because\n * `src/tokens.css` → `src/button.css` → `src/Button.tsx`* has shown its work, and\n * the operator can see the edge that is wrong when one is.\n *\n * Recording it costs one `Int32Array` and nothing in the inner loop — the parent\n * is written exactly where the visited mark already is.\n *\n * ## Cycles\n *\n * Import graphs have them, routinely and legitimately. Nothing here treats a\n * cycle as an error: the visited mask means every node is expanded once, so a\n * cycle terminates the same way any revisit does.\n */\n\nimport { EDGE_KINDS, RUNTIME_EDGES, type Adjacency, type EdgeKind, type NodeId, type Relations } from './graph.js';\nimport { narrowed } from './narrow.js';\n\n/**\n * What one walk from a set of seeds visited: a mark over every node, the node\n * each one was entered from, and the visited ids in id order. `trailOf` reads\n * the chain from any visited node back to its seed.\n */\nexport interface Traversal {\n /** `1` where the node was visited, including the seeds themselves. */\n readonly mask: Uint8Array;\n /**\n * The node each one was entered from, or `-1` for a seed and for anything\n * not visited. Walking it back lands on a seed, which is what `trailOf` does.\n */\n readonly via: Int32Array;\n /** Visited nodes in id order, which is kind-major and then code unit. */\n readonly nodes: readonly NodeId[];\n}\n\nexport interface TraversalOptions {\n /**\n * Edge kinds to walk. `RUNTIME_EDGES` when absent: every kind but `type`.\n *\n * A type-only import is erased before anything runs, so a change behind it\n * fails no test and changes no pixel, and walking it selects work nothing can fail.\n * A caller asking a question about source rather than about a runtime passes\n * `EDGE_KINDS`, or any narrower list, and gets exactly those.\n */\n readonly through?: Iterable<EdgeKind>;\n /**\n * Nodes the walk never enters, seeds included.\n *\n * A module a test file mocks is replaced for that file's whole run, so a\n * question asked from the file's point of view is asked of a graph with the\n * module taken out — not one edge, the node. Marking it visited before the\n * walk starts is exactly that removal, at no cost in the inner loop.\n */\n readonly avoid?: Iterable<NodeId>;\n /**\n * Per seed, the exports its change moved; a seed absent is charged whole.\n * Read against the arrows only, and only over a graph that carries\n * {@link Relations.uses}: an importer is then entered only through a name it\n * uses ([`narrow.ts`](./narrow.ts)).\n */\n readonly moved?: ReadonlyMap<NodeId, readonly string[]>;\n}\n\n/** What depends on these nodes, transitively. Against the arrows. */\nexport function dependentsOf(\n relations: Relations,\n seeds: Iterable<NodeId>,\n options: TraversalOptions = {},\n): Traversal {\n if (options.moved !== undefined && relations.uses !== undefined) {\n const allowed = allowedKinds(options.through);\n return narrowed(relations.dependents, relations.names, seeds, options.moved, relations.uses, allowed, options.avoid);\n }\n return search(relations.dependents, relations.names.length, seeds, options);\n}\n\n/** What these nodes depend on, transitively. Along the arrows. */\nexport function dependenciesOf(\n relations: Relations,\n seeds: Iterable<NodeId>,\n options: TraversalOptions = {},\n): Traversal {\n return search(relations.depends, relations.names.length, seeds, options);\n}\n\n/**\n * The chain from a visited node back to the seed the walk started it from.\n *\n * In arrival order — seed first, `id` last — because that is the direction the\n * sentence reads: *the token file, then the stylesheet, then the component*.\n * Empty when the node was never visited.\n */\nexport function trailOf(traversal: Traversal, id: NodeId): readonly NodeId[] {\n if (traversal.mask[id] !== 1) return [];\n\n const trail: NodeId[] = [];\n for (let at: number = id; at !== -1; at = traversal.via[at]!) trail.push(at);\n return trail.reverse();\n}\n\n/**\n * Breadth-first over one direction, from every seed at once.\n *\n * A plain array as the queue with a moving head rather than `shift()`, which is\n * `O(n)` per call in every engine that does not special-case it and turns a\n * linear traversal quadratic on the graphs this exists for.\n *\n * Breadth-first rather than depth-first for the trail's sake: the parent\n * recorded is then the one on a *shortest* path, so the explanation a run prints\n * is the shortest true one rather than whichever the stack happened to unwind.\n */\nfunction search(\n adjacency: Adjacency,\n nodes: number,\n seeds: Iterable<NodeId>,\n options: TraversalOptions,\n): Traversal {\n const mask = new Uint8Array(nodes);\n const via = new Int32Array(nodes).fill(-1);\n const queue: NodeId[] = [];\n\n const avoided = new Uint8Array(nodes);\n for (const id of options.avoid ?? []) if (id >= 0 && id < nodes) avoided[id] = 1;\n\n for (const seed of seeds) {\n if (seed < 0 || seed >= nodes || mask[seed] === 1 || avoided[seed] === 1) continue;\n mask[seed] = 1;\n queue.push(seed);\n }\n\n const allowed = allowedKinds(options.through);\n\n for (let head = 0; head < queue.length; head += 1) {\n const node = queue[head]!;\n const end = adjacency.offset[node + 1]!;\n\n for (let at = adjacency.offset[node]!; at < end; at += 1) {\n if (allowed[adjacency.kind[at]!] !== 1) continue;\n\n const next = adjacency.target[at]!;\n if (mask[next] === 1 || avoided[next] === 1) continue;\n\n mask[next] = 1;\n via[next] = node;\n queue.push(next);\n }\n }\n\n // `queue` is in visit order, which depends on seed order. The result is in id\n // order instead, so a report built from it is byte-stable whatever order the\n // caller listed its changed files in.\n const visited: NodeId[] = [];\n for (let id = 0; id < nodes; id += 1) if (mask[id] === 1) visited.push(id);\n\n return { mask, via, nodes: visited };\n}\n\n/** A kind filter as a byte lookup. */\nfunction allowedKinds(through: Iterable<EdgeKind> | undefined): Uint8Array {\n const allowed = new Uint8Array(EDGE_KINDS.length);\n for (const kind of through ?? RUNTIME_EDGES) {\n const at = EDGE_KINDS.indexOf(kind);\n if (at !== -1) allowed[at] = 1;\n }\n return allowed;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reach.js","sourceRoot":"","sources":["../../src/relate/reach.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AA+CvC,qEAAqE;AACrE,MAAM,UAAU,YAAY,CAC1B,SAAoB,EACpB,KAAuB,EACvB,OAAO,GAAqB,EAAE;IAE9B,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAChE,OAAO,QAAQ,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AAC9E,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,cAAc,CAC5B,SAAoB,EACpB,KAAuB,EACvB,OAAO,GAAqB,EAAE;IAE9B,OAAO,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AAC3E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,SAAoB,EAAE,EAAU;IACtD,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAExC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,EAAE,GAAW,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAAE;QAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7E,OAAO,KAAK,CAAC,OAAO,EAAE,CAAC;AACzB,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,MAAM,CACb,SAAoB,EACpB,KAAa,EACb,KAAuB,EACvB,OAAyB;IAEzB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,QAAQ,CAAC;QACrB,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,MAAM,CAAC,IAAI,EAAE,KAAK,CAAU,CAAC;IACzD,CAAC,CAAC;IACF,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;IACtE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;AAC/C,CAAC","sourcesContent":["/**\n * Walking the graph, and being able to say why the walk arrived.\n *\n * Two questions, one traversal each way:\n *\n * - **`dependentsOf`** — against the arrows. *What could this change affect?*\n * This is the one selection asks, and it is asked once per run over the whole\n * seed set rather than once per seed, because a breadth-first search from many\n * sources costs the same as one from a single source.\n * - **`dependenciesOf`** — along them. *What does this rest on?* The cache-key\n * question, and the one a reviewer asks about a file they have not read.\n *\n * ## The trail is not an extra\n *\n * Both return the path to each node they visit, and that is load-bearing rather\n * than a convenience. A selector that says *observe 41 of 300* is asking to be\n * trusted; a selector that says *`Button` is affected because\n * `src/tokens.css` → `src/button.css` → `src/Button.tsx`* has shown its work, and\n * the operator can see the edge that is wrong when one is.\n *\n * Recording it costs one `Int32Array` and nothing in the inner loop — the parent\n * is written exactly where the visited mark already is.\n *\n * ## Cycles\n *\n * Import graphs have them, routinely and legitimately. Nothing here treats a\n * cycle as an error: the visited mask means every node is expanded once, so a\n * cycle terminates the same way any revisit does.\n */\n\nimport { carried, inIdOrder, WHOLE } from './carried.js';\nimport type { Adjacency, EdgeKind, NodeId, Relations } from './graph.js';\nimport { narrowed } from './narrow.js';\n\n/**\n * What one walk from a set of seeds visited: a mark over every node, the node\n * each one was entered from, and the visited ids in id order. `trailOf` reads\n * the chain from any visited node back to its seed.\n */\nexport interface Traversal {\n /** `1` where the node was visited, including the seeds themselves. */\n readonly mask: Uint8Array;\n /**\n * The node each one was entered from, or `-1` for a seed and for anything\n * not visited. Walking it back lands on a seed, which is what `trailOf` does.\n */\n readonly via: Int32Array;\n /** Visited nodes in id order, which is kind-major and then code unit. */\n readonly nodes: readonly NodeId[];\n}\n\nexport interface TraversalOptions {\n /**\n * Edge kinds to walk. Every kind when absent, `type` included.\n *\n * A type-only import is a dependency: a change behind it can break the\n * importer's types and a consumer's published declarations. Only a question\n * about what a run loads leaves it out, because nothing behind it runs: test\n * selection passes `RUNTIME_EDGES`. Any other list is walked exactly.\n */\n readonly through?: Iterable<EdgeKind>;\n /**\n * Nodes the walk never enters, seeds included.\n *\n * A module a test file mocks is replaced for that file's whole run, so a\n * question asked from the file's point of view is asked of a graph with the\n * module taken out — not one edge, the node. Marking it visited before the\n * walk starts is exactly that removal, at no cost in the inner loop.\n */\n readonly avoid?: Iterable<NodeId>;\n /**\n * Per seed, the exports its change moved; a seed absent is charged whole.\n * Read against the arrows only, and only over a graph that carries\n * {@link Relations.uses}: an importer is then entered only through a name it\n * uses ([`narrow.ts`](./narrow.ts)).\n */\n readonly moved?: ReadonlyMap<NodeId, readonly string[]>;\n}\n\n/** What depends on these nodes, transitively. Against the arrows. */\nexport function dependentsOf(\n relations: Relations,\n seeds: Iterable<NodeId>,\n options: TraversalOptions = {},\n): Traversal {\n if (options.moved !== undefined && relations.uses !== undefined) {\n return narrowed(relations.dependents, relations.names, seeds, options.moved, relations.uses, options);\n }\n return search(relations.dependents, relations.names.length, seeds, options);\n}\n\n/** What these nodes depend on, transitively. Along the arrows. */\nexport function dependenciesOf(\n relations: Relations,\n seeds: Iterable<NodeId>,\n options: TraversalOptions = {},\n): Traversal {\n return search(relations.depends, relations.names.length, seeds, options);\n}\n\n/**\n * The chain from a visited node back to the seed the walk started it from.\n *\n * In arrival order — seed first, `id` last — because that is the direction the\n * sentence reads: *the token file, then the stylesheet, then the component*.\n * Empty when the node was never visited.\n */\nexport function trailOf(traversal: Traversal, id: NodeId): readonly NodeId[] {\n if (traversal.mask[id] !== 1) return [];\n\n const trail: NodeId[] = [];\n for (let at: number = id; at !== -1; at = traversal.via[at]!) trail.push(at);\n return trail.reverse();\n}\n\n/**\n * Breadth-first over one direction, from every seed at once, carrying the whole\n * of each node to everything one edge away ([`carried.ts`](./carried.ts)).\n *\n * Breadth-first rather than depth-first for the trail's sake: the parent\n * recorded is then the one on a *shortest* path, so the explanation a run prints\n * is the shortest true one rather than whichever the stack happened to unwind.\n */\nfunction search(\n adjacency: Adjacency,\n nodes: number,\n seeds: Iterable<NodeId>,\n options: TraversalOptions,\n): Traversal {\n const via = new Int32Array(nodes).fill(-1);\n const whole = function* () {\n for (const seed of seeds) yield [seed, WHOLE] as const;\n };\n const { mask } = carried(adjacency, whole(), {}, { ...options, via });\n return { mask, via, nodes: inIdOrder(mask) };\n}\n\n"]}
|
package/dist/relate/records.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export interface FileRecord {
|
|
|
56
56
|
readonly digest?: Digest;
|
|
57
57
|
/** Resolved outgoing edges. Absent and empty mean the same thing here. */
|
|
58
58
|
readonly edges?: readonly FileEdge[];
|
|
59
|
-
/** Component names this file
|
|
59
|
+
/** Component names this file's own top-level statements declare, read off its parse. */
|
|
60
60
|
readonly declares?: readonly string[];
|
|
61
61
|
/**
|
|
62
62
|
* Packages this file imports, by the name it imports them by.
|
|
@@ -65,7 +65,7 @@ export interface FileRecord {
|
|
|
65
65
|
* store or Yarn PnP there may not be one, and under a custom resolver the path
|
|
66
66
|
* would be a lie about a different machine — so the name stands for the
|
|
67
67
|
* dependency, and what the install currently has under that name is a question
|
|
68
|
-
* for the lockfile ([`sense/lock`](
|
|
68
|
+
* for the lockfile ([`sense/lock`](https://github.com/Variance-Authority/variance-authority/blob/main/packages/sense/src/lock/index.ts)).
|
|
69
69
|
*
|
|
70
70
|
* A file that imports `@mui/material` is affected when `@mui/material`
|
|
71
71
|
* changes; a file that does not, is not. That is the whole rule, and it is why this is
|
|
@@ -112,7 +112,7 @@ export interface RelationsOptions {
|
|
|
112
112
|
readonly uses?: Uses;
|
|
113
113
|
/**
|
|
114
114
|
* `[dependent, dependency]` pairs between packages, from a lockfile
|
|
115
|
-
* ([`packageRelations`](
|
|
115
|
+
* ([`packageRelations`](https://github.com/Variance-Authority/variance-authority/blob/main/packages/sense/src/lock/lockfile.ts)).
|
|
116
116
|
*
|
|
117
117
|
* Optional, and a graph without them is not wrong, only shorter-sighted: it
|
|
118
118
|
* answers a change to a package some file imports directly, and says nothing
|
|
@@ -198,8 +198,9 @@ export interface AffectedOptions extends Omit<TraversalOptions, 'moved'> {
|
|
|
198
198
|
* — a commit that bumps a dependency and edits a file is one seed set and one
|
|
199
199
|
* traversal.
|
|
200
200
|
*
|
|
201
|
-
* One breadth-first search, whatever the number of changed files, over
|
|
202
|
-
*
|
|
201
|
+
* One breadth-first search, whatever the number of changed files, over every
|
|
202
|
+
* edge kind unless the caller names others: test selection names
|
|
203
|
+
* `RUNTIME_EDGES`.
|
|
203
204
|
*/
|
|
204
205
|
export declare function affectedBy(relations: Relations, changed: Iterable<string | Node>, options?: AffectedOptions): Affected;
|
|
205
206
|
/**
|
package/dist/relate/records.js
CHANGED
|
@@ -87,8 +87,9 @@ export function relationsOfFiles(records, options = {}) {
|
|
|
87
87
|
* — a commit that bumps a dependency and edits a file is one seed set and one
|
|
88
88
|
* traversal.
|
|
89
89
|
*
|
|
90
|
-
* One breadth-first search, whatever the number of changed files, over
|
|
91
|
-
*
|
|
90
|
+
* One breadth-first search, whatever the number of changed files, over every
|
|
91
|
+
* edge kind unless the caller names others: test selection names
|
|
92
|
+
* `RUNTIME_EDGES`.
|
|
92
93
|
*/
|
|
93
94
|
export function affectedBy(relations, changed, options = {}) {
|
|
94
95
|
const seeds = [];
|
|
@@ -1 +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 unreadable file contributes what was read of it\n *\n * A `require(name)` with a variable for a name, an `import('./' + page)`, a\n * parse that did not finish: the edges a reader could see are recorded, and the\n * one it could not is not guessed at. `unknown` carries the sentence saying why,\n * for whoever reports on the scan. It widens no walk. Seeding every such file as\n * though it had changed turns one computed `require` into a whole run on every\n * diff, forever, and the edge it hides is exactly what a recorded run sees: the\n * module loaded, whatever expression named it.\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 NodeKind,\n type Relation,\n type Relations,\n} from './graph.js';\nimport type { Uses } from './narrow.js';\nimport { dependenciesOf, dependentsOf, trailOf, type Traversal, type TraversalOptions } 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\n/**\n * A file's import of something the install provides.\n *\n * The name a specifier asked for, never a version and never a resolution:\n * which copy a resolver handed this importer is unanswerable without\n * reproducing that resolver, and a selector that guessed would skip on the\n * guess. Names over-include, which is the safe direction.\n */\nexport interface PackageEdge {\n /** The package name the specifier asked for — `@mui/material`, not a path. */\n readonly to: string;\n /** The import's own kind, so a `type` import of a package is erased like any other. */\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 changed,\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 * Packages this file imports, by the name it imports them by.\n *\n * The far end of the line. Nothing resolves these to a path — under pnpm's\n * store or Yarn PnP there may not be one, and under a custom resolver the path\n * would be a lie about a different machine — so the name stands for the\n * dependency, and what the install currently has under that name is a question\n * for the lockfile ([`sense/lock`](../../../sense/src/lock/index.ts)).\n *\n * A file that imports `@mui/material` is affected when `@mui/material`\n * changes; a file that does not, is not. That is the whole rule, and it is why this is\n * read from the imports rather than from a manifest: a `package.json` says what\n * a workspace may use, and the imports say what a file does use.\n */\n readonly packages?: readonly PackageEdge[];\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 all be enumerated, when they could not.\n *\n * The edges that were read are in `edges`; this names what was not. A walk\n * treats the file like any other, and the edge nobody could read is left to a\n * recorded run, which sees the module load whatever named it.\n */\n readonly unknown?: string;\n}\n\nconst file = (name: string): Node => ({ kind: 'file', name });\nconst component = (name: string): Node => ({ kind: 'component', name });\nconst pkg = (name: string): Node => ({ kind: 'package', 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>, options: RelationsOptions = {}): 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 edge of record.packages ?? []) {\n relations.push({ from, to: pkg(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 // The lock's own edges arrive here rather than as records, because they are\n // not a file's reading of anything: one lockfile answers for the whole\n // install, and folding them in beside the imports is what lets a bump three\n // packages deep walk up to the file that imports the one at the top.\n for (const [from, to] of options.depends ?? []) {\n relations.push({ from: pkg(from), to: pkg(to), kind: 'depends-on' });\n }\n\n return relationsOf({\n relations,\n isolated,\n unknown,\n ...(options.shadows === undefined ? {} : { shadows: options.shadows }),\n ...(options.uses === undefined ? {} : { uses: options.uses }),\n });\n}\n\nexport interface RelationsOptions {\n /** Per file, the files its run never reaches; see {@link Relations.shadows}. */\n readonly shadows?: ReadonlyMap<string, readonly string[]>;\n /** How each file uses what it imports; see {@link Relations.uses}. */\n readonly uses?: Uses;\n\n /**\n * `[dependent, dependency]` pairs between packages, from a lockfile\n * ([`packageRelations`](../../../sense/src/lock/lockfile.ts)).\n *\n * Optional, and a graph without them is not wrong, only shorter-sighted: it\n * answers a change to a package some file imports directly, and says nothing\n * about a change three levels down. Supplying them is what turns *`jsdom`\n * changed* into *every test whose environment is built on it*.\n */\n readonly depends?: Iterable<readonly [string, string]>;\n}\n\n/**\n * What one backwards walk over the file graph found.\n *\n * Every field is part of one answer and none of them stands alone: `files` is\n * the answer, `missing` is what it could not say anything about, and\n * `traversal` is what an explanation is read back out of. A caller that took\n * `files` and dropped the rest would be a caller that cannot say whether the\n * list is narrow because the diff was small or because the scan never ran.\n */\nexport interface Affected {\n /** Files the change could affect, 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 * Packages the change could affect, including the changed packages\n * themselves and everything the install resolved beneath them.\n *\n * Reported rather than used: the answer a caller acts on is in `files`, and\n * this is the trail of package names that got there. Empty for a walk seeded\n * only with files, because a package is downstream of every file that imports\n * it and a walk against the arrows never descends.\n */\n readonly packages: 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 read 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 the walk visited and their own shadows cut: every trail from a\n * changed file to each one crossed a module the file replaces for its run.\n * Listed so the report can say a test was left out and by whose word.\n */\n readonly shadowed: readonly string[];\n\n /** The walk itself, kept so a caller can ask why any one file is in the answer. */\n readonly traversal: Traversal;\n}\n\nexport interface AffectedOptions extends Omit<TraversalOptions, 'moved'> {\n /**\n * Per changed file, the exports its change moved, read from both of its\n * texts; a file absent is charged whole. Narrows the walk only over a graph\n * that carries {@link Relations.uses}.\n */\n readonly moved?: ReadonlyMap<string, readonly string[]>;\n\n /**\n * Per file, the files it takes out of its own graph: what a test mocks.\n *\n * A row is a fact about one file's run and nothing else's, so it is kept\n * beside the records rather than in them — the records say what the text\n * imports, this says what the run never loads. A file with a row is affected\n * only if some trail from a changed file arrives at it without crossing a\n * file in its row, and what depends on the change only *through* it goes with it: the\n * component a story declares, the test a setup file's mocks hold for. The\n * graph's own table when absent ({@link Relations.shadows}).\n */\n readonly shadows?: ReadonlyMap<string, readonly string[]>;\n}\n\n/**\n * What a set of changed files could affect.\n *\n * The seed set is the changed files and nothing else. A file whose edges could\n * not all be read is walked through the edges that were; the one nobody saw is\n * a recorded run's to answer, not a reason to seed the file on every diff.\n *\n * A seed is a path, or a node naming its own kind. The second form is how a\n * changed *package* enters: a bare string keeps meaning a file, because that is\n * what every caller written before packages existed meant by one, and a caller\n * with a lockfile in hand passes `{ kind: 'package', name: 'jsdom' }` into the\n * same walk. There is no second selector, and there is no order between the two\n * — a commit that bumps a dependency and edits a file is one seed set and one\n * traversal.\n *\n * One breadth-first search, whatever the number of changed files, over the\n * runtime edges unless the caller names others.\n */\nexport function affectedBy(\n relations: Relations,\n changed: Iterable<string | Node>,\n options: AffectedOptions = {},\n): Affected {\n const seeds: NodeId[] = [];\n const missing: string[] = [];\n\n for (const entry of changed) {\n const node = typeof entry === 'string' ? file(entry) : entry;\n const id = idOf(relations, node.kind, node.name);\n if (id === undefined) missing.push(node.name);\n else seeds.push(id);\n }\n\n const { moved, ...rest } = options;\n const traversal = unshadowed(relations, seeds, moved === undefined ? rest : { ...rest, moved: movedIds(relations, moved) });\n const found: Record<NodeKind, string[]> = { file: [], component: [], package: [] };\n\n for (const id of traversal.nodes) {\n const node = nodeAt(relations, id);\n if (node === undefined) continue;\n found[node.kind].push(node.name);\n }\n\n return {\n files: found.file,\n components: found.component,\n packages: found.package,\n missing: missing.sort(byCodeUnit),\n shadowed: traversal.shadowed,\n traversal,\n };\n}\n\n/**\n * The walk against the arrows with every file left out whose own shadows cut\n * every trail to it, and with nothing visited through such a file.\n *\n * The walk records one parent per node, so it cannot say whether *another*\n * trail avoided the shadows. Asking the other way can: a walk along the arrows\n * from the file, entering none of its shadows, either finds a seed or does not.\n * It runs only for a visited file whose row names a visited node — a mock of\n * something the change never touched cuts nothing, and costs nothing. A file\n * found shadowed is then avoided and the walk repeated, so a component or a\n * file visited only through it is not visited either; the repeat ends when a\n * walk finds no new shadowed file, and a row once decided is not asked again.\n */\n/** The moved exports by node id, for the walk; files the graph never met drop out. */\nfunction movedIds(\n relations: Relations,\n moved: ReadonlyMap<string, readonly string[]>,\n): ReadonlyMap<NodeId, readonly string[]> {\n const ids = new Map<NodeId, readonly string[]>();\n for (const [name, exports] of moved) {\n const id = idOf(relations, 'file', name);\n if (id !== undefined) ids.set(id, exports);\n }\n return ids;\n}\n\nfunction unshadowed(\n relations: Relations,\n seeds: readonly NodeId[],\n options: Omit<AffectedOptions, 'moved'> & TraversalOptions,\n): Traversal & { readonly shadowed: readonly string[] } {\n const shadowed: string[] = [];\n const avoid = new Set<NodeId>(options.avoid ?? []);\n const shadows = options.shadows ?? relations.shadows;\n let traversal = dependentsOf(relations, seeds, options);\n if (shadows.size === 0) return { ...traversal, shadowed };\n\n const seeded = new Uint8Array(relations.names.length);\n for (const seed of seeds) seeded[seed] = 1;\n const decided = new Set<NodeId>();\n\n for (;;) {\n let found = 0;\n for (const [name, row] of shadows) {\n const id = idOf(relations, 'file', name);\n if (id === undefined || traversal.mask[id] !== 1 || seeded[id] === 1 || decided.has(id)) continue;\n\n const cut: NodeId[] = [];\n for (const shadow of row) {\n const target = idOf(relations, 'file', shadow);\n if (target !== undefined && traversal.mask[target] === 1) cut.push(target);\n }\n if (cut.length === 0) continue;\n decided.add(id);\n\n const forward = dependenciesOf(relations, [id], { ...options, avoid: cut });\n if (forward.nodes.some((node) => seeded[node] === 1)) continue;\n\n avoid.add(id);\n shadowed.push(name);\n found += 1;\n }\n if (found === 0) break;\n traversal = dependentsOf(relations, seeds, { ...options, avoid });\n }\n\n return { ...traversal, shadowed: shadowed.sort(byCodeUnit) };\n}\n\n/**\n * Why one file or component is affected: the chain of imports from a changed file to it.\n *\n * The sentence a run prints when somebody asks why a subject was observed. Empty\n * when the node is not affected at all.\n */\nexport function explain(relations: Relations, affected: Affected, node: Node): readonly string[] {\n const id = idOf(relations, node.kind, node.name);\n if (id === undefined) return [];\n return trailOf(affected.traversal, 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"]}
|
|
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 unreadable file contributes what was read of it\n *\n * A `require(name)` with a variable for a name, an `import('./' + page)`, a\n * parse that did not finish: the edges a reader could see are recorded, and the\n * one it could not is not guessed at. `unknown` carries the sentence saying why,\n * for whoever reports on the scan. It widens no walk. Seeding every such file as\n * though it had changed turns one computed `require` into a whole run on every\n * diff, forever, and the edge it hides is exactly what a recorded run sees: the\n * module loaded, whatever expression named it.\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 NodeKind,\n type Relation,\n type Relations,\n} from './graph.js';\nimport type { Uses } from './narrow.js';\nimport { dependenciesOf, dependentsOf, trailOf, type Traversal, type TraversalOptions } 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\n/**\n * A file's import of something the install provides.\n *\n * The name a specifier asked for, never a version and never a resolution:\n * which copy a resolver handed this importer is unanswerable without\n * reproducing that resolver, and a selector that guessed would skip on the\n * guess. Names over-include, which is the safe direction.\n */\nexport interface PackageEdge {\n /** The package name the specifier asked for — `@mui/material`, not a path. */\n readonly to: string;\n /** The import's own kind, so a `type` import of a package is erased like any other. */\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 changed,\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's own top-level statements declare, read off its parse. */\n readonly declares?: readonly string[];\n\n /**\n * Packages this file imports, by the name it imports them by.\n *\n * The far end of the line. Nothing resolves these to a path — under pnpm's\n * store or Yarn PnP there may not be one, and under a custom resolver the path\n * would be a lie about a different machine — so the name stands for the\n * dependency, and what the install currently has under that name is a question\n * for the lockfile ([`sense/lock`](https://github.com/Variance-Authority/variance-authority/blob/main/packages/sense/src/lock/index.ts)).\n *\n * A file that imports `@mui/material` is affected when `@mui/material`\n * changes; a file that does not, is not. That is the whole rule, and it is why this is\n * read from the imports rather than from a manifest: a `package.json` says what\n * a workspace may use, and the imports say what a file does use.\n */\n readonly packages?: readonly PackageEdge[];\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 all be enumerated, when they could not.\n *\n * The edges that were read are in `edges`; this names what was not. A walk\n * treats the file like any other, and the edge nobody could read is left to a\n * recorded run, which sees the module load whatever named it.\n */\n readonly unknown?: string;\n}\n\nconst file = (name: string): Node => ({ kind: 'file', name });\nconst component = (name: string): Node => ({ kind: 'component', name });\nconst pkg = (name: string): Node => ({ kind: 'package', 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>, options: RelationsOptions = {}): 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 edge of record.packages ?? []) {\n relations.push({ from, to: pkg(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 // The lock's own edges arrive here rather than as records, because they are\n // not a file's reading of anything: one lockfile answers for the whole\n // install, and folding them in beside the imports is what lets a bump three\n // packages deep walk up to the file that imports the one at the top.\n for (const [from, to] of options.depends ?? []) {\n relations.push({ from: pkg(from), to: pkg(to), kind: 'depends-on' });\n }\n\n return relationsOf({\n relations,\n isolated,\n unknown,\n ...(options.shadows === undefined ? {} : { shadows: options.shadows }),\n ...(options.uses === undefined ? {} : { uses: options.uses }),\n });\n}\n\nexport interface RelationsOptions {\n /** Per file, the files its run never reaches; see {@link Relations.shadows}. */\n readonly shadows?: ReadonlyMap<string, readonly string[]>;\n /** How each file uses what it imports; see {@link Relations.uses}. */\n readonly uses?: Uses;\n\n /**\n * `[dependent, dependency]` pairs between packages, from a lockfile\n * ([`packageRelations`](https://github.com/Variance-Authority/variance-authority/blob/main/packages/sense/src/lock/lockfile.ts)).\n *\n * Optional, and a graph without them is not wrong, only shorter-sighted: it\n * answers a change to a package some file imports directly, and says nothing\n * about a change three levels down. Supplying them is what turns *`jsdom`\n * changed* into *every test whose environment is built on it*.\n */\n readonly depends?: Iterable<readonly [string, string]>;\n}\n\n/**\n * What one backwards walk over the file graph found.\n *\n * Every field is part of one answer and none of them stands alone: `files` is\n * the answer, `missing` is what it could not say anything about, and\n * `traversal` is what an explanation is read back out of. A caller that took\n * `files` and dropped the rest would be a caller that cannot say whether the\n * list is narrow because the diff was small or because the scan never ran.\n */\nexport interface Affected {\n /** Files the change could affect, 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 * Packages the change could affect, including the changed packages\n * themselves and everything the install resolved beneath them.\n *\n * Reported rather than used: the answer a caller acts on is in `files`, and\n * this is the trail of package names that got there. Empty for a walk seeded\n * only with files, because a package is downstream of every file that imports\n * it and a walk against the arrows never descends.\n */\n readonly packages: 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 read 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 the walk visited and their own shadows cut: every trail from a\n * changed file to each one crossed a module the file replaces for its run.\n * Listed so the report can say a test was left out and by whose word.\n */\n readonly shadowed: readonly string[];\n\n /** The walk itself, kept so a caller can ask why any one file is in the answer. */\n readonly traversal: Traversal;\n}\n\nexport interface AffectedOptions extends Omit<TraversalOptions, 'moved'> {\n /**\n * Per changed file, the exports its change moved, read from both of its\n * texts; a file absent is charged whole. Narrows the walk only over a graph\n * that carries {@link Relations.uses}.\n */\n readonly moved?: ReadonlyMap<string, readonly string[]>;\n\n /**\n * Per file, the files it takes out of its own graph: what a test mocks.\n *\n * A row is a fact about one file's run and nothing else's, so it is kept\n * beside the records rather than in them — the records say what the text\n * imports, this says what the run never loads. A file with a row is affected\n * only if some trail from a changed file arrives at it without crossing a\n * file in its row, and what depends on the change only *through* it goes with it: the\n * component a story declares, the test a setup file's mocks hold for. The\n * graph's own table when absent ({@link Relations.shadows}).\n */\n readonly shadows?: ReadonlyMap<string, readonly string[]>;\n}\n\n/**\n * What a set of changed files could affect.\n *\n * The seed set is the changed files and nothing else. A file whose edges could\n * not all be read is walked through the edges that were; the one nobody saw is\n * a recorded run's to answer, not a reason to seed the file on every diff.\n *\n * A seed is a path, or a node naming its own kind. The second form is how a\n * changed *package* enters: a bare string keeps meaning a file, because that is\n * what every caller written before packages existed meant by one, and a caller\n * with a lockfile in hand passes `{ kind: 'package', name: 'jsdom' }` into the\n * same walk. There is no second selector, and there is no order between the two\n * — a commit that bumps a dependency and edits a file is one seed set and one\n * traversal.\n *\n * One breadth-first search, whatever the number of changed files, over every\n * edge kind unless the caller names others: test selection names\n * `RUNTIME_EDGES`.\n */\nexport function affectedBy(\n relations: Relations,\n changed: Iterable<string | Node>,\n options: AffectedOptions = {},\n): Affected {\n const seeds: NodeId[] = [];\n const missing: string[] = [];\n\n for (const entry of changed) {\n const node = typeof entry === 'string' ? file(entry) : entry;\n const id = idOf(relations, node.kind, node.name);\n if (id === undefined) missing.push(node.name);\n else seeds.push(id);\n }\n\n const { moved, ...rest } = options;\n const traversal = unshadowed(relations, seeds, moved === undefined ? rest : { ...rest, moved: movedIds(relations, moved) });\n const found: Record<NodeKind, string[]> = { file: [], component: [], package: [] };\n\n for (const id of traversal.nodes) {\n const node = nodeAt(relations, id);\n if (node === undefined) continue;\n found[node.kind].push(node.name);\n }\n\n return {\n files: found.file,\n components: found.component,\n packages: found.package,\n missing: missing.sort(byCodeUnit),\n shadowed: traversal.shadowed,\n traversal,\n };\n}\n\n/**\n * The walk against the arrows with every file left out whose own shadows cut\n * every trail to it, and with nothing visited through such a file.\n *\n * The walk records one parent per node, so it cannot say whether *another*\n * trail avoided the shadows. Asking the other way can: a walk along the arrows\n * from the file, entering none of its shadows, either finds a seed or does not.\n * It runs only for a visited file whose row names a visited node — a mock of\n * something the change never touched cuts nothing, and costs nothing. A file\n * found shadowed is then avoided and the walk repeated, so a component or a\n * file visited only through it is not visited either; the repeat ends when a\n * walk finds no new shadowed file, and a row once decided is not asked again.\n */\n/** The moved exports by node id, for the walk; files the graph never met drop out. */\nfunction movedIds(\n relations: Relations,\n moved: ReadonlyMap<string, readonly string[]>,\n): ReadonlyMap<NodeId, readonly string[]> {\n const ids = new Map<NodeId, readonly string[]>();\n for (const [name, exports] of moved) {\n const id = idOf(relations, 'file', name);\n if (id !== undefined) ids.set(id, exports);\n }\n return ids;\n}\n\nfunction unshadowed(\n relations: Relations,\n seeds: readonly NodeId[],\n options: Omit<AffectedOptions, 'moved'> & TraversalOptions,\n): Traversal & { readonly shadowed: readonly string[] } {\n const shadowed: string[] = [];\n const avoid = new Set<NodeId>(options.avoid ?? []);\n const shadows = options.shadows ?? relations.shadows;\n let traversal = dependentsOf(relations, seeds, options);\n if (shadows.size === 0) return { ...traversal, shadowed };\n\n const seeded = new Uint8Array(relations.names.length);\n for (const seed of seeds) seeded[seed] = 1;\n const decided = new Set<NodeId>();\n\n for (;;) {\n let found = 0;\n for (const [name, row] of shadows) {\n const id = idOf(relations, 'file', name);\n if (id === undefined || traversal.mask[id] !== 1 || seeded[id] === 1 || decided.has(id)) continue;\n\n const cut: NodeId[] = [];\n for (const shadow of row) {\n const target = idOf(relations, 'file', shadow);\n if (target !== undefined && traversal.mask[target] === 1) cut.push(target);\n }\n if (cut.length === 0) continue;\n decided.add(id);\n\n const forward = dependenciesOf(relations, [id], { ...options, avoid: cut });\n if (forward.nodes.some((node) => seeded[node] === 1)) continue;\n\n avoid.add(id);\n shadowed.push(name);\n found += 1;\n }\n if (found === 0) break;\n traversal = dependentsOf(relations, seeds, { ...options, avoid });\n }\n\n return { ...traversal, shadowed: shadowed.sort(byCodeUnit) };\n}\n\n/**\n * Why one file or component is affected: the chain of imports from a changed file to it.\n *\n * The sentence a run prints when somebody asks why a subject was observed. Empty\n * when the node is not affected at all.\n */\nexport function explain(relations: Relations, affected: Affected, node: Node): readonly string[] {\n const id = idOf(relations, node.kind, node.name);\n if (id === undefined) return [];\n return trailOf(affected.traversal, 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"]}
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Separate from the format because this is the part with a **version on it**.
|
|
9
9
|
* `RULESET_VERSION` and `ALLOWLIST_VERSION` are folded into the identity of every
|
|
10
|
-
* answer downstream, so changing a rule here
|
|
11
|
-
* rather than silently
|
|
10
|
+
* answer downstream, so changing a rule here is named as the cause of every
|
|
11
|
+
* stored result it moves rather than passing silently — and a reader who wants to know *why* two runs
|
|
12
12
|
* disagree ends up in this directory rather than anywhere else.
|
|
13
13
|
*/
|
|
14
14
|
export { normalize } from './normalize/index.js';
|
|
15
15
|
export type { NormalizeOptions } from './normalize/index.js';
|
|
16
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';
|
|
17
|
+
export { buildAliasMap, aliasAttributeValue, aliasStyleValue, declaredValue, resolveStyle, resolveVariables, INHERITED_PROPERTIES, EMPTY_CONTEXT, canonicalizeValue, canonicalizeTokens, canonicalizeDimension, matchingParen, canonicalizeColor, parseColor, formatColor, expandDeclaration, SHORTHAND_PROPERTIES, } from './normalize/index.js';
|
|
18
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
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/rules/index.js
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Separate from the format because this is the part with a **version on it**.
|
|
9
9
|
* `RULESET_VERSION` and `ALLOWLIST_VERSION` are folded into the identity of every
|
|
10
|
-
* answer downstream, so changing a rule here
|
|
11
|
-
* rather than silently
|
|
10
|
+
* answer downstream, so changing a rule here is named as the cause of every
|
|
11
|
+
* stored result it moves rather than passing silently — and a reader who wants to know *why* two runs
|
|
12
12
|
* disagree ends up in this directory rather than anywhere else.
|
|
13
13
|
*/
|
|
14
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';
|
|
15
|
+
export { buildAliasMap, aliasAttributeValue, aliasStyleValue, declaredValue, resolveStyle, resolveVariables, INHERITED_PROPERTIES, EMPTY_CONTEXT, canonicalizeValue, canonicalizeTokens, canonicalizeDimension, matchingParen, canonicalizeColor, parseColor, formatColor, expandDeclaration, SHORTHAND_PROPERTIES, } from './normalize/index.js';
|
|
16
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
17
|
//# sourceMappingURL=index.js.map
|
package/dist/rules/index.js.map
CHANGED
|
@@ -1 +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
|
|
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,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,aAAa,EACb,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 is named as the cause of every\n * stored result it moves rather than passing silently — 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 declaredValue,\n resolveStyle,\n resolveVariables,\n INHERITED_PROPERTIES,\n EMPTY_CONTEXT,\n canonicalizeValue,\n canonicalizeTokens,\n canonicalizeDimension,\n matchingParen,\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"]}
|
|
@@ -73,8 +73,20 @@ export interface ResolveInput {
|
|
|
73
73
|
/** Engine-resolved values. When present these win over anything computed here. */
|
|
74
74
|
readonly computedStyle?: Readonly<Record<string, string>>;
|
|
75
75
|
readonly context: InheritContext;
|
|
76
|
+
/** The page's origin, dropped from the `url()` values it serves. */
|
|
77
|
+
readonly origin?: string;
|
|
76
78
|
}
|
|
77
79
|
export declare function resolveStyle(input: ResolveInput): ResolvedStyle;
|
|
80
|
+
/**
|
|
81
|
+
* The value of `property` that wins among `matchedRules`, picked by the order
|
|
82
|
+
* {@link resolveStyle} applies, with no allowlist and no canonicalization.
|
|
83
|
+
*
|
|
84
|
+
* For a property the snapshot reads as something other than a style: `content`
|
|
85
|
+
* is left off the allowlist because a generated box's words are recorded as its
|
|
86
|
+
* text, so {@link resolveStyle} drops it, and the box still has to know which
|
|
87
|
+
* `content` won before it can say them.
|
|
88
|
+
*/
|
|
89
|
+
export declare function declaredValue(matchedRules: readonly MatchedRule[], property: string): string | undefined;
|
|
78
90
|
interface VariableResolution {
|
|
79
91
|
readonly value: string;
|
|
80
92
|
/** Custom property names consulted, in order of first appearance. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { admits, isCustomProperty } from '../ruleset.js';
|
|
2
|
-
import { canonicalizeValue } from './value.js';
|
|
2
|
+
import { canonicalizeValue, matchingParen } from './value.js';
|
|
3
3
|
import { expandDeclaration } from './shorthand.js';
|
|
4
4
|
/**
|
|
5
5
|
* Cascade resolution — ADR-0003 step 5.
|
|
@@ -78,7 +78,7 @@ export function resolveStyle(input) {
|
|
|
78
78
|
if (isCustomProperty(property))
|
|
79
79
|
continue;
|
|
80
80
|
const resolution = resolveVariables(candidate.value, customProperties);
|
|
81
|
-
style[property] = canonicalizeValue(property, resolution.value);
|
|
81
|
+
style[property] = canonicalizeValue(property, resolution.value, input.origin);
|
|
82
82
|
for (const name of resolution.used) {
|
|
83
83
|
const resolved = customProperties[name];
|
|
84
84
|
if (resolved !== undefined)
|
|
@@ -97,7 +97,7 @@ export function resolveStyle(input) {
|
|
|
97
97
|
for (const [property, value] of Object.entries(input.computedStyle)) {
|
|
98
98
|
if (!admits(property) || isCustomProperty(property))
|
|
99
99
|
continue;
|
|
100
|
-
style[property] = canonicalizeValue(property, value);
|
|
100
|
+
style[property] = canonicalizeValue(property, value, input.origin);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
// Which token drives each property, resolved once and reused for both this
|
|
@@ -223,6 +223,37 @@ function wins(candidate, incumbent) {
|
|
|
223
223
|
}
|
|
224
224
|
return candidate.order >= incumbent.order;
|
|
225
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* The value of `property` that wins among `matchedRules`, picked by the order
|
|
228
|
+
* {@link resolveStyle} applies, with no allowlist and no canonicalization.
|
|
229
|
+
*
|
|
230
|
+
* For a property the snapshot reads as something other than a style: `content`
|
|
231
|
+
* is left off the allowlist because a generated box's words are recorded as its
|
|
232
|
+
* text, so {@link resolveStyle} drops it, and the box still has to know which
|
|
233
|
+
* `content` won before it can say them.
|
|
234
|
+
*/
|
|
235
|
+
export function declaredValue(matchedRules, property) {
|
|
236
|
+
let winner;
|
|
237
|
+
for (const rule of matchedRules) {
|
|
238
|
+
for (const declaration of rule.declarations) {
|
|
239
|
+
if (declaration.property.trim().toLowerCase() !== property)
|
|
240
|
+
continue;
|
|
241
|
+
const candidate = {
|
|
242
|
+
property,
|
|
243
|
+
value: declaration.value.trim(),
|
|
244
|
+
important: declaration.important,
|
|
245
|
+
inline: false,
|
|
246
|
+
specificity: specificityRank(rule.specificity),
|
|
247
|
+
order: rule.order,
|
|
248
|
+
sheet: rule.sheet,
|
|
249
|
+
selector: rule.selector,
|
|
250
|
+
};
|
|
251
|
+
if (winner === undefined || wins(candidate, winner))
|
|
252
|
+
winner = candidate;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return winner?.value;
|
|
256
|
+
}
|
|
226
257
|
/**
|
|
227
258
|
* Collapse `[id, class, type]` into one comparable number.
|
|
228
259
|
*
|
|
@@ -295,19 +326,6 @@ export function resolveVariables(value, customProperties, depth = 0) {
|
|
|
295
326
|
}
|
|
296
327
|
return { value: result, used };
|
|
297
328
|
}
|
|
298
|
-
function matchingParen(input, openIndex) {
|
|
299
|
-
let depth = 0;
|
|
300
|
-
for (let i = openIndex; i < input.length; i += 1) {
|
|
301
|
-
if (input[i] === '(')
|
|
302
|
-
depth += 1;
|
|
303
|
-
else if (input[i] === ')') {
|
|
304
|
-
depth -= 1;
|
|
305
|
-
if (depth === 0)
|
|
306
|
-
return i;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return input.length;
|
|
310
|
-
}
|
|
311
329
|
function topLevelComma(input) {
|
|
312
330
|
let depth = 0;
|
|
313
331
|
for (let i = 0; i < input.length; i += 1) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cascade.js","sourceRoot":"","sources":["../../../src/rules/normalize/cascade.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Declaration, MatchedRule } from '../../format/capture.js';\nimport { admits, isCustomProperty } from '../ruleset.js';\nimport { canonicalizeValue } from './value.js';\nimport { expandDeclaration } from './shorthand.js';\n\n/**\n * Cascade resolution — ADR-0003 step 5.\n *\n * Losing declarations cannot reach a pixel, so they must not reach a hash. A\n * specificity war that changes *which rule wins* without changing the *winning\n * value* is a refactor, and refactors must not invalidate baselines.\n *\n * Under a profile with computed style the engine has already done this, and its\n * answer is authoritative. This code runs anyway, for two reasons: it supplies\n * the values under `declared-only` profiles, and under both profiles it supplies\n * the attribution side-channel — *which rule set this?* — which computed style\n * cannot answer at all.\n */\n\n/**\n * Properties that inherit.\n *\n * Restricted to the allowlist: inheriting a property the snapshot does not\n * record would cost work and change nothing. Without this, a `color` set once on\n * a container would be invisible on every descendant that renders text under a\n * declared-only profile — the most common styling pattern there is.\n */\nexport const INHERITED_PROPERTIES: readonly string[] = [\n 'color', 'font-family', 'font-size', 'font-weight', 'font-style', 'font-variant',\n 'font-stretch', 'line-height', 'letter-spacing', 'word-spacing',\n 'text-align', 'text-indent', 'text-transform', 'white-space', 'word-break',\n 'overflow-wrap', 'visibility', 'direction', 'writing-mode',\n 'caption-side', 'border-collapse', 'border-spacing',\n];\n\nconst INHERITED = new Set(INHERITED_PROPERTIES);\n\n/** Values flowing into a node from its ancestors. */\nexport interface InheritContext {\n /** Resolved values of inheritable properties. */\n readonly inherited: Readonly<Record<string, string>>;\n /** Custom properties in scope. Every custom property inherits. */\n readonly customProperties: Readonly<Record<string, string>>;\n\n /**\n * For each inherited property, the token its value came from.\n *\n * Inheritance would otherwise launder away the attribution: a heading that\n * inherits `color` from a container styled with `var(--brand)` produces a\n * delta with no token, no owner chain of its own, and therefore its own\n * `unattributed` root. One token edit would fan out into a docket entry per\n * inheriting text node — destroying precisely the \"one root, N collateral\"\n * claim the token band exists to make.\n */\n readonly inheritedTokens?: Readonly<Record<string, string>>;\n}\n\nexport const EMPTY_CONTEXT: InheritContext = { inherited: {}, customProperties: {} };\n\nexport interface ResolvedStyle {\n /** Winning, canonicalized values for allowlisted properties. */\n readonly style: Readonly<Record<string, string>>;\n /** Custom properties a winning value resolved through: name → resolved value. */\n readonly tokens: Readonly<Record<string, string>>;\n /** Where each winning declaration came from. Outside the hash by design. */\n readonly origins: Readonly<Record<string, DeclarationOrigin>>;\n /**\n * Which token each property's value came from, including inherited ones.\n *\n * Needed because \"this node uses tokens\" is not specific enough to attribute a\n * delta: a node may resolve `color` through a token while its `padding` comes\n * from a literal, and only the first is collateral of a token edit.\n */\n readonly propertyTokens: Readonly<Record<string, string>>;\n /** Context to pass to this node's children. */\n readonly childContext: InheritContext;\n}\n\nexport interface DeclarationOrigin {\n readonly sheet: string;\n readonly selector: string;\n readonly source?: { readonly file: string; readonly line: number };\n readonly tokenName?: string;\n}\n\ninterface Candidate {\n readonly property: string;\n readonly value: string;\n readonly important: boolean;\n readonly inline: boolean;\n readonly specificity: number;\n readonly order: number;\n readonly sheet: string;\n readonly selector: string;\n readonly source?: { readonly file: string; readonly line: number };\n}\n\nexport interface ResolveInput {\n readonly matchedRules: readonly MatchedRule[];\n readonly inlineStyle?: Readonly<Record<string, string>>;\n /** Engine-resolved values. When present these win over anything computed here. */\n readonly computedStyle?: Readonly<Record<string, string>>;\n readonly context: InheritContext;\n}\n\nexport function resolveStyle(input: ResolveInput): ResolvedStyle {\n const candidates = collectCandidates(input);\n const winners = new Map<string, Candidate>();\n\n for (const candidate of candidates) {\n const incumbent = winners.get(candidate.property);\n if (incumbent === undefined || wins(candidate, incumbent)) {\n winners.set(candidate.property, candidate);\n }\n }\n\n const customProperties: Record<string, string> = { ...input.context.customProperties };\n for (const [property, candidate] of winners) {\n if (isCustomProperty(property)) customProperties[property] = candidate.value.trim();\n }\n\n const style: Record<string, string> = {};\n const tokens: Record<string, string> = {};\n const origins: Record<string, DeclarationOrigin> = {};\n\n // Inherited values are the floor: a node that declares nothing still renders\n // with what flowed down to it, and a snapshot omitting that would report two\n // visually different nodes as identical.\n for (const [property, value] of Object.entries(input.context.inherited)) {\n style[property] = value;\n\n // The token's *own* value, not the property's. Storing the inherited\n // property value here made `tokens` mean two different things depending on\n // whether the node also declared the property: a `<button>` recorded\n // `--va-line-height: 1.4` (correct, from the second loop) while the text\n // node under it recorded `--va-line-height: 19.6px` — the used line-height.\n // The differ reads this map to decide whether a token moved, so a font-size\n // change made an untouched token look like it had moved, and the text node\n // became a second root for a change with one cause. Journal 0006's defect 5\n // in a place its corpus could not reach without a layout engine.\n const token = input.context.inheritedTokens?.[property];\n if (token !== undefined) {\n const resolved = customProperties[token];\n // Falls back to the property value when the token's own value is unknown,\n // which is the whole of a declared-only profile: `:root` is outside the\n // subject, so applicability pruning drops it and no custom property can be\n // resolved. The fallback is a deliberate over-report — a stand-in witness\n // that *something* under this token moved — because an absent entry is\n // indistinguishable from \"the token held\", and that reads as `unchanged`.\n tokens[token] = resolved === undefined ? value : tokenValue(property, resolved, customProperties);\n }\n }\n\n for (const [property, candidate] of winners) {\n if (isCustomProperty(property)) continue;\n\n const resolution = resolveVariables(candidate.value, customProperties);\n style[property] = canonicalizeValue(property, resolution.value);\n\n for (const name of resolution.used) {\n const resolved = customProperties[name];\n if (resolved !== undefined) tokens[name] = tokenValue(property, resolved, customProperties);\n }\n\n origins[property] = {\n sheet: candidate.sheet,\n selector: candidate.selector,\n ...(candidate.source ? { source: candidate.source } : {}),\n ...(resolution.used[0] !== undefined ? { tokenName: resolution.used[0] } : {}),\n };\n }\n\n // The engine's own answer supersedes ours wherever it has one. Ours still\n // produced the origins above, which is the half computed style cannot supply.\n if (input.computedStyle) {\n for (const [property, value] of Object.entries(input.computedStyle)) {\n if (!admits(property) || isCustomProperty(property)) continue;\n style[property] = canonicalizeValue(property, value);\n }\n }\n\n // Which token drives each property, resolved once and reused for both this\n // node's own record and what its children inherit.\n //\n // The precedence matters: a node with its own winning declaration is described\n // by *that* declaration's token, or by no token at all — never by the\n // ancestor's. Falling back to the inherited token when a node declared the\n // property itself makes a rule that *overrode* a token look like collateral of\n // it, which splits one docket root into two.\n const propertyTokens: Record<string, string> = {};\n for (const property of Object.keys(style)) {\n const own = origins[property];\n const token = own !== undefined ? own.tokenName : input.context.inheritedTokens?.[property];\n if (token !== undefined) propertyTokens[property] = token;\n }\n\n const childInherited: Record<string, string> = {};\n const childInheritedTokens: Record<string, string> = {};\n\n for (const [property, value] of Object.entries(style)) {\n if (!INHERITED.has(property)) continue;\n childInherited[property] = value;\n\n // Attribution rides along with the value, so a descendant that merely\n // inherits a token-driven colour is still collateral of that token.\n const token = propertyTokens[property];\n if (token !== undefined) childInheritedTokens[property] = token;\n }\n\n return {\n style,\n tokens,\n origins,\n propertyTokens,\n childContext: {\n inherited: childInherited,\n customProperties,\n inheritedTokens: childInheritedTokens,\n },\n };\n}\n\nfunction collectCandidates(input: ResolveInput): Candidate[] {\n const candidates: Candidate[] = [];\n\n for (const rule of input.matchedRules) {\n for (const declaration of rule.declarations) {\n for (const admitted of admitDeclaration(declaration)) {\n candidates.push({\n ...admitted,\n inline: false,\n specificity: specificityRank(rule.specificity),\n order: rule.order,\n sheet: rule.sheet,\n selector: rule.selector,\n ...(rule.source ? { source: rule.source } : {}),\n });\n }\n }\n }\n\n for (const [property, value] of Object.entries(input.inlineStyle ?? {})) {\n const important = value.trimEnd().toLowerCase().endsWith('!important');\n const bare = important ? value.replace(/!\\s*important\\s*$/i, '').trim() : value;\n\n for (const admitted of admitDeclaration({ property, value: bare, important })) {\n candidates.push({\n ...admitted,\n inline: true,\n specificity: 0,\n order: Number.MAX_SAFE_INTEGER,\n sheet: 'inline',\n selector: '[style]',\n });\n }\n }\n\n return candidates;\n}\n\n/**\n * Expand and project one declaration (ADR-0003 step 4).\n *\n * A shorthand that could not be decomposed with confidence is kept under its own\n * name rather than dropped. Dropping it would remove a real value from the hash.\n */\nfunction admitDeclaration(\n declaration: Declaration,\n): readonly { property: string; value: string; important: boolean }[] {\n const property = declaration.property.trim().toLowerCase();\n\n if (isCustomProperty(declaration.property)) {\n // Custom property names are case-sensitive, unlike every other property.\n return [{\n property: declaration.property.trim(),\n value: declaration.value,\n important: declaration.important,\n }];\n }\n\n const expansion = expandDeclaration(property, declaration.value);\n\n // An unexpandable shorthand is admitted under its own name. It is not on the\n // allowlist — nothing that decomposes ever is — but dropping it would remove a\n // real declared value from the hash, and a missing value is a false\n // `unchanged`. Under a profile with computed style the engine's longhands\n // arrive separately and supersede it, so this only widens the declared-only tier.\n if (!expansion.confident) {\n return [{ property, value: declaration.value, important: declaration.important }];\n }\n\n return expansion.declarations\n .filter((entry) => admits(entry.property))\n .map((entry) => ({\n property: entry.property,\n value: entry.value,\n important: declaration.important,\n }));\n}\n\n/**\n * The cascade order that matters here: importance, then origin, then\n * specificity, then document order.\n *\n * Author-origin only — user and user-agent stylesheets are the engine's business,\n * and under a profile with computed style the engine's answer overrides this\n * anyway.\n */\nfunction wins(candidate: Candidate, incumbent: Candidate): boolean {\n if (candidate.important !== incumbent.important) return candidate.important;\n if (candidate.inline !== incumbent.inline) return candidate.inline;\n if (candidate.specificity !== incumbent.specificity) {\n return candidate.specificity > incumbent.specificity;\n }\n return candidate.order >= incumbent.order;\n}\n\n/**\n * Collapse `[id, class, type]` into one comparable number.\n *\n * The 1 000-per-column base is far above any real selector's count in a column,\n * so this cannot produce the classic \"255 classes outrank an id\" overflow bug.\n */\nfunction specificityRank(specificity: readonly [number, number, number]): number {\n return specificity[0] * 1_000_000 + specificity[1] * 1_000 + specificity[2];\n}\n\ninterface VariableResolution {\n readonly value: string;\n /** Custom property names consulted, in order of first appearance. */\n readonly used: readonly string[];\n}\n\n/**\n * Substitute `var(--name, fallback)` against the in-scope custom properties.\n *\n * The *names* are recorded alongside the resolved value, and that is the point:\n * a name is what lets one design-token edit collapse into a single docket root\n * with counted collateral, instead of hundreds of unrelated colour diffs\n * (spec §5, `token` band). A resolved value alone cannot be grouped.\n */\n/**\n * A custom property's value, resolved through any custom properties it uses.\n *\n * The distinction is not cosmetic and it broke the product's headline claim.\n * `--ks-card-radius: var(--va-radius-md)` is how every real design system is\n * built — semantic tokens aliasing primitive ones — and recording the *declared*\n * value stores the literal string `var(--va-radius-md)` on both sides of a\n * change to `--va-radius-md`. The differ reads this map to decide whether a\n * token moved, sees two identical strings, and concludes it did not. So the root\n * fell through to `component:Card`: **one edit to the radius scale was reported\n * as \"Card changed internally\", once per consuming component**, instead of as\n * one token root with counted collateral.\n *\n * The corpus declared the right answer for `token-radius/card` — its `spec` line\n * reads \"token delta ⇒ token is the root\" — and asserted only the root *count*,\n * which is one either way.\n */\nfunction tokenValue(\n property: string,\n declared: string,\n customProperties: Readonly<Record<string, string>>,\n): string {\n return canonicalizeValue(property, resolveVariables(declared, customProperties).value);\n}\n\nexport function resolveVariables(\n value: string,\n customProperties: Readonly<Record<string, string>>,\n depth = 0,\n): VariableResolution {\n if (!value.includes('var(') || depth > 16) return { value, used: [] };\n\n const used: string[] = [];\n let result = '';\n let index = 0;\n\n while (index < value.length) {\n const start = value.indexOf('var(', index);\n if (start < 0) {\n result += value.slice(index);\n break;\n }\n\n result += value.slice(index, start);\n\n const end = matchingParen(value, start + 3);\n const inner = value.slice(start + 4, end);\n const comma = topLevelComma(inner);\n const name = (comma < 0 ? inner : inner.slice(0, comma)).trim();\n const fallback = comma < 0 ? undefined : inner.slice(comma + 1).trim();\n\n if (!used.includes(name)) used.push(name);\n\n const declared = customProperties[name];\n const substituted = declared ?? fallback ?? '';\n\n // A custom property may itself reference another. The depth cap is a cycle\n // guard: `--a: var(--b); --b: var(--a)` is invalid CSS the engine drops, and\n // recursing on it would hang the collector rather than report anything.\n const nested = resolveVariables(substituted, customProperties, depth + 1);\n result += nested.value;\n for (const nestedName of nested.used) {\n if (!used.includes(nestedName)) used.push(nestedName);\n }\n\n index = end + 1;\n }\n\n return { value: result, used };\n}\n\nfunction matchingParen(input: string, openIndex: number): number {\n let depth = 0;\n for (let i = openIndex; i < input.length; i += 1) {\n if (input[i] === '(') depth += 1;\n else if (input[i] === ')') {\n depth -= 1;\n if (depth === 0) return i;\n }\n }\n return input.length;\n}\n\nfunction topLevelComma(input: string): number {\n let depth = 0;\n for (let i = 0; i < input.length; i += 1) {\n const char = input[i];\n if (char === '(') depth += 1;\n else if (char === ')') depth -= 1;\n else if (char === ',' && depth === 0) return i;\n }\n return -1;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cascade.js","sourceRoot":"","sources":["../../../src/rules/normalize/cascade.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Declaration, MatchedRule } from '../../format/capture.js';\nimport { admits, isCustomProperty } from '../ruleset.js';\nimport { canonicalizeValue, matchingParen } from './value.js';\nimport { expandDeclaration } from './shorthand.js';\n\n/**\n * Cascade resolution — ADR-0003 step 5.\n *\n * Losing declarations cannot reach a pixel, so they must not reach a hash. A\n * specificity war that changes *which rule wins* without changing the *winning\n * value* is a refactor, and refactors must not invalidate baselines.\n *\n * Under a profile with computed style the engine has already done this, and its\n * answer is authoritative. This code runs anyway, for two reasons: it supplies\n * the values under `declared-only` profiles, and under both profiles it supplies\n * the attribution side-channel — *which rule set this?* — which computed style\n * cannot answer at all.\n */\n\n/**\n * Properties that inherit.\n *\n * Restricted to the allowlist: inheriting a property the snapshot does not\n * record would cost work and change nothing. Without this, a `color` set once on\n * a container would be invisible on every descendant that renders text under a\n * declared-only profile — the most common styling pattern there is.\n */\nexport const INHERITED_PROPERTIES: readonly string[] = [\n 'color', 'font-family', 'font-size', 'font-weight', 'font-style', 'font-variant',\n 'font-stretch', 'line-height', 'letter-spacing', 'word-spacing',\n 'text-align', 'text-indent', 'text-transform', 'white-space', 'word-break',\n 'overflow-wrap', 'visibility', 'direction', 'writing-mode',\n 'caption-side', 'border-collapse', 'border-spacing',\n];\n\nconst INHERITED = new Set(INHERITED_PROPERTIES);\n\n/** Values flowing into a node from its ancestors. */\nexport interface InheritContext {\n /** Resolved values of inheritable properties. */\n readonly inherited: Readonly<Record<string, string>>;\n /** Custom properties in scope. Every custom property inherits. */\n readonly customProperties: Readonly<Record<string, string>>;\n\n /**\n * For each inherited property, the token its value came from.\n *\n * Inheritance would otherwise launder away the attribution: a heading that\n * inherits `color` from a container styled with `var(--brand)` produces a\n * delta with no token, no owner chain of its own, and therefore its own\n * `unattributed` root. One token edit would fan out into a docket entry per\n * inheriting text node — destroying precisely the \"one root, N collateral\"\n * claim the token band exists to make.\n */\n readonly inheritedTokens?: Readonly<Record<string, string>>;\n}\n\nexport const EMPTY_CONTEXT: InheritContext = { inherited: {}, customProperties: {} };\n\nexport interface ResolvedStyle {\n /** Winning, canonicalized values for allowlisted properties. */\n readonly style: Readonly<Record<string, string>>;\n /** Custom properties a winning value resolved through: name → resolved value. */\n readonly tokens: Readonly<Record<string, string>>;\n /** Where each winning declaration came from. Outside the hash by design. */\n readonly origins: Readonly<Record<string, DeclarationOrigin>>;\n /**\n * Which token each property's value came from, including inherited ones.\n *\n * Needed because \"this node uses tokens\" is not specific enough to attribute a\n * delta: a node may resolve `color` through a token while its `padding` comes\n * from a literal, and only the first is collateral of a token edit.\n */\n readonly propertyTokens: Readonly<Record<string, string>>;\n /** Context to pass to this node's children. */\n readonly childContext: InheritContext;\n}\n\nexport interface DeclarationOrigin {\n readonly sheet: string;\n readonly selector: string;\n readonly source?: { readonly file: string; readonly line: number };\n readonly tokenName?: string;\n}\n\ninterface Candidate {\n readonly property: string;\n readonly value: string;\n readonly important: boolean;\n readonly inline: boolean;\n readonly specificity: number;\n readonly order: number;\n readonly sheet: string;\n readonly selector: string;\n readonly source?: { readonly file: string; readonly line: number };\n}\n\nexport interface ResolveInput {\n readonly matchedRules: readonly MatchedRule[];\n readonly inlineStyle?: Readonly<Record<string, string>>;\n /** Engine-resolved values. When present these win over anything computed here. */\n readonly computedStyle?: Readonly<Record<string, string>>;\n readonly context: InheritContext;\n /** The page's origin, dropped from the `url()` values it serves. */\n readonly origin?: string;\n}\n\nexport function resolveStyle(input: ResolveInput): ResolvedStyle {\n const candidates = collectCandidates(input);\n const winners = new Map<string, Candidate>();\n\n for (const candidate of candidates) {\n const incumbent = winners.get(candidate.property);\n if (incumbent === undefined || wins(candidate, incumbent)) {\n winners.set(candidate.property, candidate);\n }\n }\n\n const customProperties: Record<string, string> = { ...input.context.customProperties };\n for (const [property, candidate] of winners) {\n if (isCustomProperty(property)) customProperties[property] = candidate.value.trim();\n }\n\n const style: Record<string, string> = {};\n const tokens: Record<string, string> = {};\n const origins: Record<string, DeclarationOrigin> = {};\n\n // Inherited values are the floor: a node that declares nothing still renders\n // with what flowed down to it, and a snapshot omitting that would report two\n // visually different nodes as identical.\n for (const [property, value] of Object.entries(input.context.inherited)) {\n style[property] = value;\n\n // The token's *own* value, not the property's. Storing the inherited\n // property value here made `tokens` mean two different things depending on\n // whether the node also declared the property: a `<button>` recorded\n // `--va-line-height: 1.4` (correct, from the second loop) while the text\n // node under it recorded `--va-line-height: 19.6px` — the used line-height.\n // The differ reads this map to decide whether a token moved, so a font-size\n // change made an untouched token look like it had moved, and the text node\n // became a second root for a change with one cause. Journal 0006's defect 5\n // in a place its corpus could not reach without a layout engine.\n const token = input.context.inheritedTokens?.[property];\n if (token !== undefined) {\n const resolved = customProperties[token];\n // Falls back to the property value when the token's own value is unknown,\n // which is the whole of a declared-only profile: `:root` is outside the\n // subject, so applicability pruning drops it and no custom property can be\n // resolved. The fallback is a deliberate over-report — a stand-in witness\n // that *something* under this token moved — because an absent entry is\n // indistinguishable from \"the token held\", and that reads as `unchanged`.\n tokens[token] = resolved === undefined ? value : tokenValue(property, resolved, customProperties);\n }\n }\n\n for (const [property, candidate] of winners) {\n if (isCustomProperty(property)) continue;\n\n const resolution = resolveVariables(candidate.value, customProperties);\n style[property] = canonicalizeValue(property, resolution.value, input.origin);\n\n for (const name of resolution.used) {\n const resolved = customProperties[name];\n if (resolved !== undefined) tokens[name] = tokenValue(property, resolved, customProperties);\n }\n\n origins[property] = {\n sheet: candidate.sheet,\n selector: candidate.selector,\n ...(candidate.source ? { source: candidate.source } : {}),\n ...(resolution.used[0] !== undefined ? { tokenName: resolution.used[0] } : {}),\n };\n }\n\n // The engine's own answer supersedes ours wherever it has one. Ours still\n // produced the origins above, which is the half computed style cannot supply.\n if (input.computedStyle) {\n for (const [property, value] of Object.entries(input.computedStyle)) {\n if (!admits(property) || isCustomProperty(property)) continue;\n style[property] = canonicalizeValue(property, value, input.origin);\n }\n }\n\n // Which token drives each property, resolved once and reused for both this\n // node's own record and what its children inherit.\n //\n // The precedence matters: a node with its own winning declaration is described\n // by *that* declaration's token, or by no token at all — never by the\n // ancestor's. Falling back to the inherited token when a node declared the\n // property itself makes a rule that *overrode* a token look like collateral of\n // it, which splits one docket root into two.\n const propertyTokens: Record<string, string> = {};\n for (const property of Object.keys(style)) {\n const own = origins[property];\n const token = own !== undefined ? own.tokenName : input.context.inheritedTokens?.[property];\n if (token !== undefined) propertyTokens[property] = token;\n }\n\n const childInherited: Record<string, string> = {};\n const childInheritedTokens: Record<string, string> = {};\n\n for (const [property, value] of Object.entries(style)) {\n if (!INHERITED.has(property)) continue;\n childInherited[property] = value;\n\n // Attribution rides along with the value, so a descendant that merely\n // inherits a token-driven colour is still collateral of that token.\n const token = propertyTokens[property];\n if (token !== undefined) childInheritedTokens[property] = token;\n }\n\n return {\n style,\n tokens,\n origins,\n propertyTokens,\n childContext: {\n inherited: childInherited,\n customProperties,\n inheritedTokens: childInheritedTokens,\n },\n };\n}\n\nfunction collectCandidates(input: ResolveInput): Candidate[] {\n const candidates: Candidate[] = [];\n\n for (const rule of input.matchedRules) {\n for (const declaration of rule.declarations) {\n for (const admitted of admitDeclaration(declaration)) {\n candidates.push({\n ...admitted,\n inline: false,\n specificity: specificityRank(rule.specificity),\n order: rule.order,\n sheet: rule.sheet,\n selector: rule.selector,\n ...(rule.source ? { source: rule.source } : {}),\n });\n }\n }\n }\n\n for (const [property, value] of Object.entries(input.inlineStyle ?? {})) {\n const important = value.trimEnd().toLowerCase().endsWith('!important');\n const bare = important ? value.replace(/!\\s*important\\s*$/i, '').trim() : value;\n\n for (const admitted of admitDeclaration({ property, value: bare, important })) {\n candidates.push({\n ...admitted,\n inline: true,\n specificity: 0,\n order: Number.MAX_SAFE_INTEGER,\n sheet: 'inline',\n selector: '[style]',\n });\n }\n }\n\n return candidates;\n}\n\n/**\n * Expand and project one declaration (ADR-0003 step 4).\n *\n * A shorthand that could not be decomposed with confidence is kept under its own\n * name rather than dropped. Dropping it would remove a real value from the hash.\n */\nfunction admitDeclaration(\n declaration: Declaration,\n): readonly { property: string; value: string; important: boolean }[] {\n const property = declaration.property.trim().toLowerCase();\n\n if (isCustomProperty(declaration.property)) {\n // Custom property names are case-sensitive, unlike every other property.\n return [{\n property: declaration.property.trim(),\n value: declaration.value,\n important: declaration.important,\n }];\n }\n\n const expansion = expandDeclaration(property, declaration.value);\n\n // An unexpandable shorthand is admitted under its own name. It is not on the\n // allowlist — nothing that decomposes ever is — but dropping it would remove a\n // real declared value from the hash, and a missing value is a false\n // `unchanged`. Under a profile with computed style the engine's longhands\n // arrive separately and supersede it, so this only widens the declared-only tier.\n if (!expansion.confident) {\n return [{ property, value: declaration.value, important: declaration.important }];\n }\n\n return expansion.declarations\n .filter((entry) => admits(entry.property))\n .map((entry) => ({\n property: entry.property,\n value: entry.value,\n important: declaration.important,\n }));\n}\n\n/**\n * The cascade order that matters here: importance, then origin, then\n * specificity, then document order.\n *\n * Author-origin only — user and user-agent stylesheets are the engine's business,\n * and under a profile with computed style the engine's answer overrides this\n * anyway.\n */\nfunction wins(candidate: Candidate, incumbent: Candidate): boolean {\n if (candidate.important !== incumbent.important) return candidate.important;\n if (candidate.inline !== incumbent.inline) return candidate.inline;\n if (candidate.specificity !== incumbent.specificity) {\n return candidate.specificity > incumbent.specificity;\n }\n return candidate.order >= incumbent.order;\n}\n\n/**\n * The value of `property` that wins among `matchedRules`, picked by the order\n * {@link resolveStyle} applies, with no allowlist and no canonicalization.\n *\n * For a property the snapshot reads as something other than a style: `content`\n * is left off the allowlist because a generated box's words are recorded as its\n * text, so {@link resolveStyle} drops it, and the box still has to know which\n * `content` won before it can say them.\n */\nexport function declaredValue(matchedRules: readonly MatchedRule[], property: string): string | undefined {\n let winner: Candidate | undefined;\n\n for (const rule of matchedRules) {\n for (const declaration of rule.declarations) {\n if (declaration.property.trim().toLowerCase() !== property) continue;\n const candidate: Candidate = {\n property,\n value: declaration.value.trim(),\n important: declaration.important,\n inline: false,\n specificity: specificityRank(rule.specificity),\n order: rule.order,\n sheet: rule.sheet,\n selector: rule.selector,\n };\n if (winner === undefined || wins(candidate, winner)) winner = candidate;\n }\n }\n\n return winner?.value;\n}\n\n/**\n * Collapse `[id, class, type]` into one comparable number.\n *\n * The 1 000-per-column base is far above any real selector's count in a column,\n * so this cannot produce the classic \"255 classes outrank an id\" overflow bug.\n */\nfunction specificityRank(specificity: readonly [number, number, number]): number {\n return specificity[0] * 1_000_000 + specificity[1] * 1_000 + specificity[2];\n}\n\ninterface VariableResolution {\n readonly value: string;\n /** Custom property names consulted, in order of first appearance. */\n readonly used: readonly string[];\n}\n\n/**\n * Substitute `var(--name, fallback)` against the in-scope custom properties.\n *\n * The *names* are recorded alongside the resolved value, and that is the point:\n * a name is what lets one design-token edit collapse into a single docket root\n * with counted collateral, instead of hundreds of unrelated colour diffs\n * (spec §5, `token` band). A resolved value alone cannot be grouped.\n */\n/**\n * A custom property's value, resolved through any custom properties it uses.\n *\n * The distinction is not cosmetic and it broke the product's headline claim.\n * `--ks-card-radius: var(--va-radius-md)` is how every real design system is\n * built — semantic tokens aliasing primitive ones — and recording the *declared*\n * value stores the literal string `var(--va-radius-md)` on both sides of a\n * change to `--va-radius-md`. The differ reads this map to decide whether a\n * token moved, sees two identical strings, and concludes it did not. So the root\n * fell through to `component:Card`: **one edit to the radius scale was reported\n * as \"Card changed internally\", once per consuming component**, instead of as\n * one token root with counted collateral.\n *\n * The corpus declared the right answer for `token-radius/card` — its `spec` line\n * reads \"token delta ⇒ token is the root\" — and asserted only the root *count*,\n * which is one either way.\n */\nfunction tokenValue(\n property: string,\n declared: string,\n customProperties: Readonly<Record<string, string>>,\n): string {\n return canonicalizeValue(property, resolveVariables(declared, customProperties).value);\n}\n\nexport function resolveVariables(\n value: string,\n customProperties: Readonly<Record<string, string>>,\n depth = 0,\n): VariableResolution {\n if (!value.includes('var(') || depth > 16) return { value, used: [] };\n\n const used: string[] = [];\n let result = '';\n let index = 0;\n\n while (index < value.length) {\n const start = value.indexOf('var(', index);\n if (start < 0) {\n result += value.slice(index);\n break;\n }\n\n result += value.slice(index, start);\n\n const end = matchingParen(value, start + 3);\n const inner = value.slice(start + 4, end);\n const comma = topLevelComma(inner);\n const name = (comma < 0 ? inner : inner.slice(0, comma)).trim();\n const fallback = comma < 0 ? undefined : inner.slice(comma + 1).trim();\n\n if (!used.includes(name)) used.push(name);\n\n const declared = customProperties[name];\n const substituted = declared ?? fallback ?? '';\n\n // A custom property may itself reference another. The depth cap is a cycle\n // guard: `--a: var(--b); --b: var(--a)` is invalid CSS the engine drops, and\n // recursing on it would hang the collector rather than report anything.\n const nested = resolveVariables(substituted, customProperties, depth + 1);\n result += nested.value;\n for (const nestedName of nested.used) {\n if (!used.includes(nestedName)) used.push(nestedName);\n }\n\n index = end + 1;\n }\n\n return { value: result, used };\n}\n\nfunction topLevelComma(input: string): number {\n let depth = 0;\n for (let i = 0; i < input.length; i += 1) {\n const char = input[i];\n if (char === '(') depth += 1;\n else if (char === ')') depth -= 1;\n else if (char === ',' && depth === 0) return i;\n }\n return -1;\n}\n"]}
|
|
@@ -42,6 +42,8 @@ export interface NodeSpec {
|
|
|
42
42
|
* exercise the shipped rule rather than a stand-in.
|
|
43
43
|
*/
|
|
44
44
|
readonly owners?: readonly OwnerSpec[];
|
|
45
|
+
/** The component whose JSX created this node. See `Provenance.createdBy`. */
|
|
46
|
+
readonly createdBy?: string;
|
|
45
47
|
/** Where the JSX that created this node is written, as a runtime records it. */
|
|
46
48
|
readonly source?: SourceLocation;
|
|
47
49
|
/** Frames React captured, which a location has not yet been bought with. */
|
|
@@ -77,6 +79,7 @@ export interface CaptureSpec {
|
|
|
77
79
|
readonly subjectId?: string;
|
|
78
80
|
readonly engine?: string;
|
|
79
81
|
readonly fonts?: readonly string[];
|
|
82
|
+
readonly baseUrl?: string;
|
|
80
83
|
}
|
|
81
84
|
export declare function capture(spec: CaptureSpec): RawCapture;
|
|
82
85
|
export { JSDOM_PROFILE, CHROMIUM_PROFILE };
|
|
@@ -22,13 +22,14 @@ export function node(spec = {}) {
|
|
|
22
22
|
...(spec.rect ? { rect: spec.rect } : {}),
|
|
23
23
|
...(spec.text !== undefined ? { text: spec.text } : {}),
|
|
24
24
|
...(spec.holding ? { holding: spec.holding } : {}),
|
|
25
|
-
...(spec.owners || spec.source || spec.stack
|
|
25
|
+
...(spec.owners || spec.createdBy || spec.source || spec.stack
|
|
26
26
|
? {
|
|
27
27
|
provenance: {
|
|
28
28
|
owners: (spec.owners ?? []).map((owner) => ({
|
|
29
29
|
name: owner.name,
|
|
30
30
|
propsDigest: propsDigest(owner.props ?? {}),
|
|
31
31
|
})),
|
|
32
|
+
...(spec.createdBy ? { createdBy: spec.createdBy } : {}),
|
|
32
33
|
...(spec.source ? { source: spec.source } : {}),
|
|
33
34
|
...(spec.stack ? { stack: spec.stack } : {}),
|
|
34
35
|
},
|
|
@@ -67,6 +68,7 @@ export function capture(spec) {
|
|
|
67
68
|
},
|
|
68
69
|
root: spec.root,
|
|
69
70
|
inheritedSeed: spec.inheritedSeed ?? {},
|
|
71
|
+
...(spec.baseUrl !== undefined ? { baseUrl: spec.baseUrl } : {}),
|
|
70
72
|
diagnostics: [],
|
|
71
73
|
};
|
|
72
74
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixture.js","sourceRoot":"","sources":["../../../src/rules/normalize/fixture.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAA2B,MAAM,yBAAyB,CAAC;AAEnG,OAAO,EAAE,WAAW,EAAwC,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"fixture.js","sourceRoot":"","sources":["../../../src/rules/normalize/fixture.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Declaration,\n MatchedRule,\n RawCapture,\n RawNode,\n Rect,\n} from '../../format/capture.js';\nimport { CHROMIUM_PROFILE, JSDOM_PROFILE, type ObservationProfile } from '../../format/profile.js';\nimport type { Holding } from '../../format/holding.js';\nimport { propsDigest, type SourceLocation, type StackFrame } from '../../format/provenance.js';\n\n/**\n * Fixture builders for `RawCapture`.\n *\n * These exist because the collector/core split makes them possible: `core` never\n * touches a DOM, so its entire behavior is testable from hand-written data with\n * no browser, no JSDOM, and no React. That is not a testing convenience — it is\n * the property that lets the same normalizer serve a remote sub-renderer\n * (ADR-0002), and these fixtures are how we keep it honest.\n *\n * Exported rather than test-local so collectors can assert that what they emit\n * round-trips through the same shapes.\n */\n\nexport interface NodeSpec {\n readonly tag?: string;\n readonly attributes?: Record<string, string>;\n readonly role?: string;\n readonly name?: string;\n readonly description?: string;\n readonly state?: Record<string, string | boolean | number>;\n readonly rules?: readonly RuleSpec[];\n readonly inlineStyle?: Record<string, string>;\n readonly computedStyle?: Record<string, string>;\n readonly rect?: Rect;\n readonly text?: string;\n /**\n * Already-built nodes, not specs.\n *\n * Callers write `node({children: [node({...})]})`, so the children arrive\n * built. Re-running the builder over them would read `spec.rules` off a\n * `RawNode` — which has `matchedRules` — and silently produce a child with no\n * style at all. Every style assertion below such a node would then pass by\n * comparing nothing to nothing.\n */\n readonly children?: readonly RawNode[];\n\n /**\n * Owner chain, innermost first, with the props each boundary received.\n * Digests are computed with the real `propsDigest`, so attribution tests\n * exercise the shipped rule rather than a stand-in.\n */\n readonly owners?: readonly OwnerSpec[];\n\n /** The component whose JSX created this node. See `Provenance.createdBy`. */\n readonly createdBy?: string;\n\n /** Where the JSX that created this node is written, as a runtime records it. */\n readonly source?: SourceLocation;\n\n /** Frames React captured, which a location has not yet been bought with. */\n readonly stack?: readonly StackFrame[];\n\n /**\n * What the boundary rooted here was holding, as a framework adapter read it.\n *\n * Written out rather than derived from a props spec, because the cases worth\n * writing are the ones a real render makes awkward to reach: a hook chain that\n * ran out mid-read, a production build with no hook names at all, and a\n * boundary whose every input agreed. See `compare/parting.ts`.\n */\n readonly holding?: Holding;\n}\n\nexport interface OwnerSpec {\n readonly name: string;\n readonly props?: Record<string, unknown>;\n}\n\nexport interface RuleSpec {\n readonly selector: string;\n readonly declare: Record<string, string>;\n readonly specificity?: readonly [number, number, number];\n readonly order?: number;\n readonly sheet?: string;\n readonly important?: boolean;\n}\n\nexport function node(spec: NodeSpec = {}): RawNode {\n const aria =\n spec.role !== undefined ||\n spec.name !== undefined ||\n spec.description !== undefined ||\n spec.state !== undefined\n ? {\n role: spec.role ?? null,\n name: spec.name ?? null,\n ...(spec.description !== undefined ? { description: spec.description } : {}),\n state: spec.state ?? {},\n }\n : undefined;\n\n return {\n tag: spec.tag ?? 'div',\n attributes: spec.attributes ?? {},\n ...(aria ? { aria } : {}),\n matchedRules: (spec.rules ?? []).map(rule),\n ...(spec.inlineStyle ? { inlineStyle: spec.inlineStyle } : {}),\n ...(spec.computedStyle ? { computedStyle: spec.computedStyle } : {}),\n ...(spec.rect ? { rect: spec.rect } : {}),\n ...(spec.text !== undefined ? { text: spec.text } : {}),\n ...(spec.holding ? { holding: spec.holding } : {}),\n ...(spec.owners || spec.createdBy || spec.source || spec.stack\n ? {\n provenance: {\n owners: (spec.owners ?? []).map((owner) => ({\n name: owner.name,\n propsDigest: propsDigest(owner.props ?? {}),\n })),\n ...(spec.createdBy ? { createdBy: spec.createdBy } : {}),\n ...(spec.source ? { source: spec.source } : {}),\n ...(spec.stack ? { stack: spec.stack } : {}),\n },\n }\n : {}),\n children: spec.children ?? [],\n };\n}\n\nexport function rule(spec: RuleSpec): MatchedRule {\n const declarations: Declaration[] = Object.entries(spec.declare).map(([property, value]) => ({\n property,\n value,\n important: spec.important ?? false,\n }));\n\n return {\n sheet: spec.sheet ?? 'sheet.css',\n selector: spec.selector,\n specificity: spec.specificity ?? [0, 1, 0],\n order: spec.order ?? 0,\n declarations,\n };\n}\n\nexport interface CaptureSpec {\n readonly root: RawNode;\n readonly profile?: ObservationProfile;\n readonly inheritedSeed?: Record<string, string>;\n readonly subjectId?: string;\n readonly engine?: string;\n readonly fonts?: readonly string[];\n readonly baseUrl?: string;\n}\n\nexport function capture(spec: CaptureSpec): RawCapture {\n const profile = spec.profile ?? JSDOM_PROFILE;\n\n return {\n captureVersion: 1,\n subject: { id: spec.subjectId ?? 'fixture:subject', kind: 'fixture' },\n profile,\n environment: {\n profile: profile.id,\n engine: spec.engine ?? `${profile.id}@test`,\n viewport: { width: 1280, height: 720, deviceScaleFactor: 1, colorScheme: 'light' },\n fonts: spec.fonts ?? ['Inter/400/normal/deadbeef'],\n conditions: {},\n assets: {},\n },\n root: spec.root,\n inheritedSeed: spec.inheritedSeed ?? {},\n ...(spec.baseUrl !== undefined ? { baseUrl: spec.baseUrl } : {}),\n diagnostics: [],\n };\n}\n\nexport { JSDOM_PROFILE, CHROMIUM_PROFILE };\n"]}
|
|
@@ -3,9 +3,9 @@ import { type Digest } from '../../format/hash.js';
|
|
|
3
3
|
import type { SemanticSnapshot } from '../../format/snapshot.js';
|
|
4
4
|
export { buildAliasMap, aliasAttributeValue, aliasStyleValue } from './alias.js';
|
|
5
5
|
export type { AliasMap, AliasResult } from './alias.js';
|
|
6
|
-
export { resolveStyle, resolveVariables, INHERITED_PROPERTIES, EMPTY_CONTEXT, } from './cascade.js';
|
|
6
|
+
export { declaredValue, resolveStyle, resolveVariables, INHERITED_PROPERTIES, EMPTY_CONTEXT, } from './cascade.js';
|
|
7
7
|
export type { InheritContext, ResolvedStyle, DeclarationOrigin } from './cascade.js';
|
|
8
|
-
export { canonicalizeValue, canonicalizeTokens, canonicalizeDimension } from './value.js';
|
|
8
|
+
export { canonicalizeValue, canonicalizeTokens, canonicalizeDimension, matchingParen } from './value.js';
|
|
9
9
|
export { canonicalizeColor, parseColor, formatColor } from './color.js';
|
|
10
10
|
export { expandDeclaration, SHORTHAND_PROPERTIES } from './shorthand.js';
|
|
11
11
|
/**
|