@variance-authority/core 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +454 -0
  2. package/README.md +13 -10
  3. package/dist/attribute/component-hash.d.ts +3 -2
  4. package/dist/attribute/component-hash.js +3 -2
  5. package/dist/attribute/component-hash.js.map +1 -1
  6. package/dist/attribute/composition.d.ts +16 -0
  7. package/dist/attribute/composition.js +4 -6
  8. package/dist/attribute/composition.js.map +1 -1
  9. package/dist/attribute/index.d.ts +3 -1
  10. package/dist/attribute/index.js +2 -1
  11. package/dist/attribute/index.js.map +1 -1
  12. package/dist/attribute/region.js +1 -5
  13. package/dist/attribute/region.js.map +1 -1
  14. package/dist/attribute/source.d.ts +10 -24
  15. package/dist/attribute/source.js +4 -38
  16. package/dist/attribute/source.js.map +1 -1
  17. package/dist/attribute/subject-pieces.d.ts +37 -0
  18. package/dist/attribute/subject-pieces.js +122 -0
  19. package/dist/attribute/subject-pieces.js.map +1 -0
  20. package/dist/format/capture.d.ts +25 -0
  21. package/dist/format/capture.js +6 -0
  22. package/dist/format/capture.js.map +1 -1
  23. package/dist/format/document.d.ts +2 -2
  24. package/dist/format/document.js +8 -2
  25. package/dist/format/document.js.map +1 -1
  26. package/dist/format/drive.d.ts +191 -0
  27. package/dist/format/drive.js +343 -0
  28. package/dist/format/drive.js.map +1 -0
  29. package/dist/format/environment.d.ts +20 -2
  30. package/dist/format/environment.js +7 -0
  31. package/dist/format/environment.js.map +1 -1
  32. package/dist/format/index.d.ts +6 -3
  33. package/dist/format/index.js +4 -2
  34. package/dist/format/index.js.map +1 -1
  35. package/dist/format/intervention.d.ts +79 -0
  36. package/dist/format/intervention.js.map +1 -1
  37. package/dist/format/origin.d.ts +32 -0
  38. package/dist/format/origin.js +54 -0
  39. package/dist/format/origin.js.map +1 -0
  40. package/dist/format/snapshot.d.ts +2 -0
  41. package/dist/format/snapshot.js.map +1 -1
  42. package/dist/format/stabilize.d.ts +28 -5
  43. package/dist/format/stabilize.js +48 -100
  44. package/dist/format/stabilize.js.map +1 -1
  45. package/dist/format/waits.d.ts +12 -0
  46. package/dist/format/waits.js +156 -0
  47. package/dist/format/waits.js.map +1 -0
  48. package/dist/index.d.ts +2 -1
  49. package/dist/index.js +2 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/nearest.d.ts +46 -0
  52. package/dist/nearest.js +102 -0
  53. package/dist/nearest.js.map +1 -0
  54. package/dist/relate/before.d.ts +19 -24
  55. package/dist/relate/before.js +35 -37
  56. package/dist/relate/before.js.map +1 -1
  57. package/dist/relate/beyond.d.ts +59 -0
  58. package/dist/relate/beyond.js +98 -0
  59. package/dist/relate/beyond.js.map +1 -0
  60. package/dist/relate/carried.d.ts +122 -0
  61. package/dist/relate/carried.js +279 -0
  62. package/dist/relate/carried.js.map +1 -0
  63. package/dist/relate/dominate.d.ts +19 -0
  64. package/dist/relate/dominate.js +77 -0
  65. package/dist/relate/dominate.js.map +1 -0
  66. package/dist/relate/index.d.ts +3 -0
  67. package/dist/relate/index.js +3 -0
  68. package/dist/relate/index.js.map +1 -1
  69. package/dist/relate/merkle.js +2 -11
  70. package/dist/relate/merkle.js.map +1 -1
  71. package/dist/relate/narrow.d.ts +2 -2
  72. package/dist/relate/narrow.js +26 -73
  73. package/dist/relate/narrow.js.map +1 -1
  74. package/dist/relate/reach.d.ts +1 -1
  75. package/dist/relate/reach.js +10 -53
  76. package/dist/relate/reach.js.map +1 -1
  77. package/dist/relate/records.d.ts +1 -1
  78. package/dist/relate/records.js.map +1 -1
  79. package/dist/rules/index.d.ts +3 -3
  80. package/dist/rules/index.js +3 -3
  81. package/dist/rules/index.js.map +1 -1
  82. package/dist/rules/normalize/cascade.d.ts +12 -0
  83. package/dist/rules/normalize/cascade.js +34 -16
  84. package/dist/rules/normalize/cascade.js.map +1 -1
  85. package/dist/rules/normalize/fixture.d.ts +1 -0
  86. package/dist/rules/normalize/fixture.js +1 -0
  87. package/dist/rules/normalize/fixture.js.map +1 -1
  88. package/dist/rules/normalize/index.d.ts +2 -2
  89. package/dist/rules/normalize/index.js +9 -2
  90. package/dist/rules/normalize/index.js.map +1 -1
  91. package/dist/rules/normalize/value.d.ts +13 -2
  92. package/dist/rules/normalize/value.js +42 -8
  93. package/dist/rules/normalize/value.js.map +1 -1
  94. package/dist/rules/normalize/wrapper.js +5 -0
  95. package/dist/rules/normalize/wrapper.js.map +1 -1
  96. package/dist/rules/ruleset.d.ts +24 -4
  97. package/dist/rules/ruleset.js +24 -4
  98. package/dist/rules/ruleset.js.map +1 -1
  99. package/dist/segment/index.d.ts +20 -4
  100. package/dist/segment/index.js +28 -7
  101. package/dist/segment/index.js.map +1 -1
  102. package/dist/shard/index.d.ts +11 -0
  103. package/dist/shard/index.js +11 -0
  104. package/dist/shard/index.js.map +1 -0
  105. package/dist/shard/placement.d.ts +117 -0
  106. package/dist/shard/placement.js +170 -0
  107. package/dist/shard/placement.js.map +1 -0
  108. package/package.json +8 -1
@@ -1 +1 @@
1
- {"version":3,"file":"merkle.js","sourceRoot":"","sources":["../../src/relate/merkle.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The graph, hashed the way a build system hashes an action's inputs.\n *\n * Reachability answers *what could this diff affect*. It needs a diff, which\n * means it needs `git`, a ref that exists, a checkout deep enough to contain it,\n * and the assumption that the ref is where this branch actually diverged. Every\n * one of those is a thing CI gets wrong, and each failure narrows a run.\n *\n * A Merkle digest asks a different question and needs none of them: **is this\n * subject's entire input closure byte-identical to the one that produced the\n * baseline?** One digest per node, over the node's own content and the digests of\n * everything it rests on — the same construction `bazel` uses to key an action,\n * and the same idea as this project's own content addressing, applied one level\n * out from the document to the source that produced it.\n *\n * What the difference buys:\n *\n * | situation | reachability | closure digest |\n * |---|---|---|\n * | a change, then a revert | both commits are in the diff, so it widens | identical digest, nothing runs |\n * | rebase, squash, branch switch | the merge base moves and the diff with it | unaffected — no ref is consulted |\n * | a shallow clone with no merge base | cannot answer; runs everything | unaffected |\n * | a dependency changed two hops away | affected, and observed | different digest, and observed |\n * | *why* a subject is being observed | a chain of files | a digest that differs |\n *\n * Neither replaces the other. The trail is the explanation, and the digest is the\n * proof; a run wants the digest to decide and the trail to justify.\n *\n * ## Cycles\n *\n * A dependency graph has them and a Merkle tree cannot. Strongly connected\n * components are condensed and hashed as a unit, so every file in a cycle carries\n * the same digest — which is exactly the truth about a cycle: no member of one can\n * be called unchanged while another changed.\n *\n * ## The digest that must never lie\n *\n * A digest says *these inputs are the same*. A file whose content was not supplied\n * breaks that claim — its bytes may have changed with no digest in this structure\n * changing. Those nodes are marked **volatile**, the mark propagates to everything\n * that rests on them, and a volatile node is treated as changed however its digest\n * compares. A cache that cannot be trusted must not be silently trusted.\n *\n * A file whose imports could not all be read is not volatile. Its bytes are\n * hashed, and the edges that were read are folded; the one nobody could read is\n * left to a recorded run, which sees the module load whatever named it.\n */\n\nimport { digestCombine, digestString, type Digest } from '../format/hash.js';\nimport {\n EDGE_KINDS,\n NODE_KINDS,\n keyOf,\n type EdgeKind,\n type NodeId,\n type Relations,\n} from './graph.js';\n\nexport interface ClosureInput {\n readonly relations: Relations;\n\n /**\n * Content digest per file, keyed by the file's path.\n *\n * A file with no entry is volatile: nothing here can tell whether it changed.\n * Component nodes take no entry — a component has no bytes of its own, and its\n * content is the file that declares it, which it already depends on.\n */\n readonly content: ReadonlyMap<string, Digest>;\n\n /** Restrict the closure to these edge kinds. Every kind by default. */\n readonly through?: Iterable<EdgeKind>;\n}\n\nexport interface Closure {\n /** `keyOf(kind, name)` → the digest of everything that node rests on. */\n readonly digests: ReadonlyMap<string, Digest>;\n\n /**\n * Nodes whose content was not supplied, and everything resting on them.\n *\n * Not an error. It is the honest half of the answer, and a caller that ignores\n * it converts a missing input into a subject nobody observed.\n */\n readonly volatile: ReadonlySet<string>;\n}\n\n/**\n * Hash every node over its own content and its whole dependency closure.\n *\n * One pass of Tarjan's algorithm and one pass over the components it emits, which\n * is `O(n + m)` — the same cost as the traversal next door, for an answer that\n * survives a rebase.\n */\nexport function closureOf(input: ClosureInput): Closure {\n const { relations, content } = input;\n const allowed = allowedKinds(input.through);\n const count = relations.names.length;\n\n const { component, order } = condense(relations, allowed);\n const digests = new Map<string, Digest>();\n const unstable = new Set<string>();\n\n // One digest per component, so every member of a cycle carries the same one.\n const byComponent: Digest[] = [];\n const shakyComponent: boolean[] = [];\n\n // Tarjan emits a component only after everything it can reach, so a dependency\n // digest is always already computed by the time it is read here.\n for (const [id, members] of order.entries()) {\n const terms: string[] = [];\n const outward = new Set<string>();\n let shaky = false;\n\n for (const node of members) {\n const kind = NODE_KINDS[relations.kinds[node]!]!;\n const name = relations.names[node]!;\n const own = kind === 'file' ? content.get(name) : SETTLED;\n\n if (own === undefined) shaky = true;\n terms.push(`${kind}\\u0000${name}\\u0000${own ?? UNREAD}`);\n\n const { offset, target, kind: edgeKind } = relations.depends;\n for (let at = offset[node]!; at < offset[node + 1]!; at += 1) {\n if (allowed[edgeKind[at]!] !== 1) continue;\n\n const other = target[at]!;\n if (component[other] === id) continue;\n\n outward.add(`${EDGE_KINDS[edgeKind[at]!]!}\\u0000${byComponent[component[other]!]!}`);\n if (shakyComponent[component[other]!] === true) shaky = true;\n }\n }\n\n byComponent.push(\n digestCombine('relate.closure', [\n digestString([...terms].sort(byCodeUnit).join('\u0001')),\n digestString([...outward].sort(byCodeUnit).join('\u0001')),\n ]),\n );\n shakyComponent.push(shaky);\n }\n\n for (let node = 0; node < count; node += 1) {\n const key = keyOf(NODE_KINDS[relations.kinds[node]!]!, relations.names[node]!);\n digests.set(key, byComponent[component[node]!]!);\n if (shakyComponent[component[node]!] === true) unstable.add(key);\n }\n\n return { digests, volatile: unstable };\n}\n\n/** The term a node with content contributes when the content is not known. */\nconst UNREAD = '(unread)';\n\n/** The term a node that has no content of its own contributes. */\nconst SETTLED = '(declared)';\n\nexport interface Drift {\n /** Node keys whose closure is not provably the one it was. */\n readonly changed: readonly string[];\n /** The file paths among them. */\n readonly files: readonly string[];\n /** The component names among them. */\n readonly components: readonly string[];\n /** Node keys the earlier closure held and this one does not. */\n readonly gone: readonly string[];\n}\n\n/**\n * What changed between two closures, without consulting a repository.\n *\n * Three ways a node lands in `changed`, and only the first is a difference: its\n * digest differs, it is **new**, or it is **volatile** — the last because a node\n * whose inputs were not fully read has a digest that can match by accident, and a\n * selector that believed it would skip a subject on the strength of a hash over\n * bytes nobody hashed.\n */\nexport function driftedBetween(before: Closure, after: Closure): Drift {\n const changed: string[] = [];\n\n for (const [key, digest] of after.digests) {\n const was = before.digests.get(key);\n if (was === digest && !after.volatile.has(key) && !before.volatile.has(key)) continue;\n changed.push(key);\n }\n\n const gone = [...before.digests.keys()].filter((key) => !after.digests.has(key));\n\n return {\n changed: changed.sort(byCodeUnit),\n files: named(changed, 'file'),\n components: named(changed, 'component'),\n gone: gone.sort(byCodeUnit),\n };\n}\n\nfunction named(keys: readonly string[], kind: 'file' | 'component'): readonly string[] {\n const prefix = `${kind}:`;\n\n return keys\n .filter((key) => key.startsWith(prefix))\n .map((key) => key.slice(prefix.length))\n .sort(byCodeUnit);\n}\n\ninterface Condensation {\n /** Component index per node. */\n readonly component: Int32Array;\n /** Components in emission order — every one after everything it reaches. */\n readonly order: readonly (readonly NodeId[])[];\n}\n\n/**\n * Tarjan's strongly connected components, iteratively.\n *\n * Iterative because the recursion depth is the longest dependency chain in the\n * repository, and a deep monorepo would overflow a call stack on a structure whose\n * whole point is to be cheap. The emission order is the useful half: a component\n * appears only once everything it can reach has, which is the order a Merkle fold\n * needs and costs nothing extra to get.\n */\nfunction condense(relations: Relations, allowed: Uint8Array): Condensation {\n const count = relations.names.length;\n const { offset, target, kind } = relations.depends;\n\n const index = new Int32Array(count).fill(-1);\n const low = new Int32Array(count);\n const onStack = new Uint8Array(count);\n const component = new Int32Array(count).fill(-1);\n\n const pending: NodeId[] = [];\n const order: NodeId[][] = [];\n\n // Frames as two parallel arrays: the node, and how far through its edges it is.\n const frameNode: number[] = [];\n const frameEdge: number[] = [];\n let counter = 0;\n\n for (let root = 0; root < count; root += 1) {\n if (index[root] !== -1) continue;\n\n index[root] = counter;\n low[root] = counter;\n counter += 1;\n pending.push(root);\n onStack[root] = 1;\n frameNode.push(root);\n frameEdge.push(offset[root]!);\n\n while (frameNode.length > 0) {\n const node = frameNode[frameNode.length - 1]!;\n const at = frameEdge[frameEdge.length - 1]!;\n\n if (at < offset[node + 1]!) {\n frameEdge[frameEdge.length - 1] = at + 1;\n if (allowed[kind[at]!] !== 1) continue;\n\n const next = target[at]!;\n if (index[next] === -1) {\n index[next] = counter;\n low[next] = counter;\n counter += 1;\n pending.push(next);\n onStack[next] = 1;\n frameNode.push(next);\n frameEdge.push(offset[next]!);\n } else if (onStack[next] === 1) {\n if (index[next]! < low[node]!) low[node] = index[next]!;\n }\n continue;\n }\n\n frameNode.pop();\n frameEdge.pop();\n\n const parent = frameNode[frameNode.length - 1];\n if (parent !== undefined && low[node]! < low[parent]!) low[parent] = low[node]!;\n\n if (low[node] !== index[node]) continue;\n\n const members: NodeId[] = [];\n for (;;) {\n const member = pending.pop()!;\n onStack[member] = 0;\n component[member] = order.length;\n members.push(member);\n if (member === node) break;\n }\n order.push(members.sort((a, b) => a - b));\n }\n }\n\n return { component, order };\n}\n\n/**\n * The edge kinds a closure walks when the caller does not say.\n *\n * Listed rather than derived from `EDGE_KINDS`, and the difference is the whole\n * point. A digest is a claim about *which inputs* were folded, so changing the\n * fold silently changes every digest that exists — one edge kind added or\n * removed, and the next run finds nothing it can prove unchanged and re-observes\n * the entire suite while reporting a successful narrowing. Changing this list is\n * a decision with a cost, and it should read like one.\n *\n * `type` is not in it: a type-only import is erased before a render, so a change\n * behind one is not an input the render folded. `depends` is: the module's author\n * declared that it reads the file, which makes the file's bytes an input.\n *\n * `depends-on` is not in it either, for the opposite reason. It runs between two\n * packages, and a package node is a name rather than a content digest — an\n * install that changed is a fact the diff carries, never one a hash of names could\n * reveal. Folding it would add a term that cannot change and a walk that cannot\n * answer.\n */\nexport const CLOSURE_EDGES: readonly EdgeKind[] = [\n 'imports',\n 'reexports',\n 'dynamic',\n 'asset',\n 'declared-in',\n 'depends',\n];\n\n/** Edge kinds as a lookup, so the inner loop tests a byte. */\nfunction allowedKinds(through: Iterable<EdgeKind> | undefined): Uint8Array {\n const allowed = new Uint8Array(EDGE_KINDS.length);\n\n for (const kind of through ?? CLOSURE_EDGES) {\n const at = EDGE_KINDS.indexOf(kind);\n if (at !== -1) allowed[at] = 1;\n }\n\n return allowed;\n}\n\nfunction byCodeUnit(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n"]}
1
+ {"version":3,"file":"merkle.js","sourceRoot":"","sources":["../../src/relate/merkle.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The graph, hashed the way a build system hashes an action's inputs.\n *\n * Reachability answers *what could this diff affect*. It needs a diff, which\n * means it needs `git`, a ref that exists, a checkout deep enough to contain it,\n * and the assumption that the ref is where this branch actually diverged. Every\n * one of those is a thing CI gets wrong, and each failure narrows a run.\n *\n * A Merkle digest asks a different question and needs none of them: **is this\n * subject's entire input closure byte-identical to the one that produced the\n * baseline?** One digest per node, over the node's own content and the digests of\n * everything it rests on — the same construction `bazel` uses to key an action,\n * and the same idea as this project's own content addressing, applied one level\n * out from the document to the source that produced it.\n *\n * What the difference buys:\n *\n * | situation | reachability | closure digest |\n * |---|---|---|\n * | a change, then a revert | both commits are in the diff, so it widens | identical digest, nothing runs |\n * | rebase, squash, branch switch | the merge base moves and the diff with it | unaffected — no ref is consulted |\n * | a shallow clone with no merge base | cannot answer; runs everything | unaffected |\n * | a dependency changed two hops away | affected, and observed | different digest, and observed |\n * | *why* a subject is being observed | a chain of files | a digest that differs |\n *\n * Neither replaces the other. The trail is the explanation, and the digest is the\n * proof; a run wants the digest to decide and the trail to justify.\n *\n * ## Cycles\n *\n * A dependency graph has them and a Merkle tree cannot. Strongly connected\n * components are condensed and hashed as a unit, so every file in a cycle carries\n * the same digest — which is exactly the truth about a cycle: no member of one can\n * be called unchanged while another changed.\n *\n * ## The digest that must never lie\n *\n * A digest says *these inputs are the same*. A file whose content was not supplied\n * breaks that claim — its bytes may have changed with no digest in this structure\n * changing. Those nodes are marked **volatile**, the mark propagates to everything\n * that rests on them, and a volatile node is treated as changed however its digest\n * compares. A cache that cannot be trusted must not be silently trusted.\n *\n * A file whose imports could not all be read is not volatile. Its bytes are\n * hashed, and the edges that were read are folded; the one nobody could read is\n * left to a recorded run, which sees the module load whatever named it.\n */\n\nimport { digestCombine, digestString, type Digest } from '../format/hash.js';\nimport { allowedKinds } from './carried.js';\nimport {\n EDGE_KINDS,\n NODE_KINDS,\n keyOf,\n type EdgeKind,\n type NodeId,\n type Relations,\n} from './graph.js';\n\nexport interface ClosureInput {\n readonly relations: Relations;\n\n /**\n * Content digest per file, keyed by the file's path.\n *\n * A file with no entry is volatile: nothing here can tell whether it changed.\n * Component nodes take no entry — a component has no bytes of its own, and its\n * content is the file that declares it, which it already depends on.\n */\n readonly content: ReadonlyMap<string, Digest>;\n\n /** Restrict the closure to these edge kinds. Every kind by default. */\n readonly through?: Iterable<EdgeKind>;\n}\n\nexport interface Closure {\n /** `keyOf(kind, name)` → the digest of everything that node rests on. */\n readonly digests: ReadonlyMap<string, Digest>;\n\n /**\n * Nodes whose content was not supplied, and everything resting on them.\n *\n * Not an error. It is the honest half of the answer, and a caller that ignores\n * it converts a missing input into a subject nobody observed.\n */\n readonly volatile: ReadonlySet<string>;\n}\n\n/**\n * Hash every node over its own content and its whole dependency closure.\n *\n * One pass of Tarjan's algorithm and one pass over the components it emits, which\n * is `O(n + m)` — the same cost as the traversal next door, for an answer that\n * survives a rebase.\n */\nexport function closureOf(input: ClosureInput): Closure {\n const { relations, content } = input;\n const allowed = allowedKinds(input.through, CLOSURE_EDGES);\n const count = relations.names.length;\n\n const { component, order } = condense(relations, allowed);\n const digests = new Map<string, Digest>();\n const unstable = new Set<string>();\n\n // One digest per component, so every member of a cycle carries the same one.\n const byComponent: Digest[] = [];\n const shakyComponent: boolean[] = [];\n\n // Tarjan emits a component only after everything it can reach, so a dependency\n // digest is always already computed by the time it is read here.\n for (const [id, members] of order.entries()) {\n const terms: string[] = [];\n const outward = new Set<string>();\n let shaky = false;\n\n for (const node of members) {\n const kind = NODE_KINDS[relations.kinds[node]!]!;\n const name = relations.names[node]!;\n const own = kind === 'file' ? content.get(name) : SETTLED;\n\n if (own === undefined) shaky = true;\n terms.push(`${kind}\\u0000${name}\\u0000${own ?? UNREAD}`);\n\n const { offset, target, kind: edgeKind } = relations.depends;\n for (let at = offset[node]!; at < offset[node + 1]!; at += 1) {\n if (allowed[edgeKind[at]!] !== 1) continue;\n\n const other = target[at]!;\n if (component[other] === id) continue;\n\n outward.add(`${EDGE_KINDS[edgeKind[at]!]!}\\u0000${byComponent[component[other]!]!}`);\n if (shakyComponent[component[other]!] === true) shaky = true;\n }\n }\n\n byComponent.push(\n digestCombine('relate.closure', [\n digestString([...terms].sort(byCodeUnit).join('\u0001')),\n digestString([...outward].sort(byCodeUnit).join('\u0001')),\n ]),\n );\n shakyComponent.push(shaky);\n }\n\n for (let node = 0; node < count; node += 1) {\n const key = keyOf(NODE_KINDS[relations.kinds[node]!]!, relations.names[node]!);\n digests.set(key, byComponent[component[node]!]!);\n if (shakyComponent[component[node]!] === true) unstable.add(key);\n }\n\n return { digests, volatile: unstable };\n}\n\n/** The term a node with content contributes when the content is not known. */\nconst UNREAD = '(unread)';\n\n/** The term a node that has no content of its own contributes. */\nconst SETTLED = '(declared)';\n\nexport interface Drift {\n /** Node keys whose closure is not provably the one it was. */\n readonly changed: readonly string[];\n /** The file paths among them. */\n readonly files: readonly string[];\n /** The component names among them. */\n readonly components: readonly string[];\n /** Node keys the earlier closure held and this one does not. */\n readonly gone: readonly string[];\n}\n\n/**\n * What changed between two closures, without consulting a repository.\n *\n * Three ways a node lands in `changed`, and only the first is a difference: its\n * digest differs, it is **new**, or it is **volatile** — the last because a node\n * whose inputs were not fully read has a digest that can match by accident, and a\n * selector that believed it would skip a subject on the strength of a hash over\n * bytes nobody hashed.\n */\nexport function driftedBetween(before: Closure, after: Closure): Drift {\n const changed: string[] = [];\n\n for (const [key, digest] of after.digests) {\n const was = before.digests.get(key);\n if (was === digest && !after.volatile.has(key) && !before.volatile.has(key)) continue;\n changed.push(key);\n }\n\n const gone = [...before.digests.keys()].filter((key) => !after.digests.has(key));\n\n return {\n changed: changed.sort(byCodeUnit),\n files: named(changed, 'file'),\n components: named(changed, 'component'),\n gone: gone.sort(byCodeUnit),\n };\n}\n\nfunction named(keys: readonly string[], kind: 'file' | 'component'): readonly string[] {\n const prefix = `${kind}:`;\n\n return keys\n .filter((key) => key.startsWith(prefix))\n .map((key) => key.slice(prefix.length))\n .sort(byCodeUnit);\n}\n\ninterface Condensation {\n /** Component index per node. */\n readonly component: Int32Array;\n /** Components in emission order — every one after everything it reaches. */\n readonly order: readonly (readonly NodeId[])[];\n}\n\n/**\n * Tarjan's strongly connected components, iteratively.\n *\n * Iterative because the recursion depth is the longest dependency chain in the\n * repository, and a deep monorepo would overflow a call stack on a structure whose\n * whole point is to be cheap. The emission order is the useful half: a component\n * appears only once everything it can reach has, which is the order a Merkle fold\n * needs and costs nothing extra to get.\n */\nfunction condense(relations: Relations, allowed: Uint8Array): Condensation {\n const count = relations.names.length;\n const { offset, target, kind } = relations.depends;\n\n const index = new Int32Array(count).fill(-1);\n const low = new Int32Array(count);\n const onStack = new Uint8Array(count);\n const component = new Int32Array(count).fill(-1);\n\n const pending: NodeId[] = [];\n const order: NodeId[][] = [];\n\n // Frames as two parallel arrays: the node, and how far through its edges it is.\n const frameNode: number[] = [];\n const frameEdge: number[] = [];\n let counter = 0;\n\n for (let root = 0; root < count; root += 1) {\n if (index[root] !== -1) continue;\n\n index[root] = counter;\n low[root] = counter;\n counter += 1;\n pending.push(root);\n onStack[root] = 1;\n frameNode.push(root);\n frameEdge.push(offset[root]!);\n\n while (frameNode.length > 0) {\n const node = frameNode[frameNode.length - 1]!;\n const at = frameEdge[frameEdge.length - 1]!;\n\n if (at < offset[node + 1]!) {\n frameEdge[frameEdge.length - 1] = at + 1;\n if (allowed[kind[at]!] !== 1) continue;\n\n const next = target[at]!;\n if (index[next] === -1) {\n index[next] = counter;\n low[next] = counter;\n counter += 1;\n pending.push(next);\n onStack[next] = 1;\n frameNode.push(next);\n frameEdge.push(offset[next]!);\n } else if (onStack[next] === 1) {\n if (index[next]! < low[node]!) low[node] = index[next]!;\n }\n continue;\n }\n\n frameNode.pop();\n frameEdge.pop();\n\n const parent = frameNode[frameNode.length - 1];\n if (parent !== undefined && low[node]! < low[parent]!) low[parent] = low[node]!;\n\n if (low[node] !== index[node]) continue;\n\n const members: NodeId[] = [];\n for (;;) {\n const member = pending.pop()!;\n onStack[member] = 0;\n component[member] = order.length;\n members.push(member);\n if (member === node) break;\n }\n order.push(members.sort((a, b) => a - b));\n }\n }\n\n return { component, order };\n}\n\n/**\n * The edge kinds a closure walks when the caller does not say.\n *\n * Listed rather than derived from `EDGE_KINDS`, and the difference is the whole\n * point. A digest is a claim about *which inputs* were folded, so changing the\n * fold silently changes every digest that exists — one edge kind added or\n * removed, and the next run finds nothing it can prove unchanged and re-observes\n * the entire suite while reporting a successful narrowing. Changing this list is\n * a decision with a cost, and it should read like one.\n *\n * `type` is not in it: a type-only import is erased before a render, so a change\n * behind one is not an input the render folded. `depends` is: the module's author\n * declared that it reads the file, which makes the file's bytes an input.\n *\n * `depends-on` is not in it either, for the opposite reason. It runs between two\n * packages, and a package node is a name rather than a content digest — an\n * install that changed is a fact the diff carries, never one a hash of names could\n * reveal. Folding it would add a term that cannot change and a walk that cannot\n * answer.\n */\nexport const CLOSURE_EDGES: readonly EdgeKind[] = [\n 'imports',\n 'reexports',\n 'dynamic',\n 'asset',\n 'declared-in',\n 'depends',\n];\n\nfunction byCodeUnit(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n"]}
@@ -26,7 +26,7 @@
26
26
  * reaches the same fixpoint whatever order the seeds arrive in.
27
27
  */
28
28
  import { type Adjacency, type NodeId } from './graph.js';
29
- import type { Traversal } from './reach.js';
29
+ import type { Traversal, TraversalOptions } from './reach.js';
30
30
  /** How one file uses one file it imports, read from the parse that recorded the edge. */
31
31
  export interface EdgeUse {
32
32
  /**
@@ -49,5 +49,5 @@ export type Uses = (importer: string, target: string) => EdgeUse | undefined;
49
49
  * Breadth-first against the arrows from `seeds`, each seed in `moved` carrying
50
50
  * the exports its change moved and every other one whole.
51
51
  */
52
- export declare function narrowed(dependents: Adjacency, names: readonly string[], seeds: Iterable<NodeId>, moved: ReadonlyMap<NodeId, readonly string[]>, uses: Uses, allowed: Uint8Array, avoid?: Iterable<NodeId>): Traversal;
52
+ export declare function narrowed(dependents: Adjacency, names: readonly string[], seeds: Iterable<NodeId>, moved: ReadonlyMap<NodeId, readonly string[]>, uses: Uses, options?: Pick<TraversalOptions, 'avoid' | 'through'>): Traversal;
53
53
  //# sourceMappingURL=narrow.d.ts.map
@@ -25,6 +25,7 @@
25
25
  * A file's names only grow, and a file that grew is expanded again, so the walk
26
26
  * reaches the same fixpoint whatever order the seeds arrive in.
27
27
  */
28
+ import { carried, inIdOrder, NOTHING, WHOLE } from './carried.js';
28
29
  import { EDGE_KINDS } from './graph.js';
29
30
  const IMPORTS = EDGE_KINDS.indexOf('imports');
30
31
  const REEXPORTS = EDGE_KINDS.indexOf('reexports');
@@ -47,87 +48,39 @@ function republished(passes, moved) {
47
48
  * Breadth-first against the arrows from `seeds`, each seed in `moved` carrying
48
49
  * the exports its change moved and every other one whole.
49
50
  */
50
- export function narrowed(dependents, names, seeds, moved, uses, allowed, avoid = []) {
51
- const nodes = names.length;
52
- const mask = new Uint8Array(nodes);
53
- const via = new Int32Array(nodes).fill(-1);
54
- const avoided = new Uint8Array(nodes);
55
- for (const id of avoid)
56
- if (id >= 0 && id < nodes)
57
- avoided[id] = 1;
58
- // A visited node absent here is whole.
59
- const partial = new Map();
60
- const queue = [];
61
- const enter = (next, from, exports) => {
62
- if (avoided[next] === 1)
63
- return;
64
- if (mask[next] !== 1) {
65
- mask[next] = 1;
66
- via[next] = from;
67
- if (exports !== undefined)
68
- partial.set(next, new Set(exports));
69
- queue.push(next);
70
- return;
51
+ export function narrowed(dependents, names, seeds, moved, uses, options = {}) {
52
+ const via = new Int32Array(names.length).fill(-1);
53
+ const starts = function* () {
54
+ for (const seed of seeds) {
55
+ const exports = moved.get(seed);
56
+ yield [seed, exports === undefined ? WHOLE : namesOf(exports)];
71
57
  }
72
- const held = partial.get(next);
73
- if (held === undefined)
74
- return;
75
- if (exports === undefined) {
76
- partial.delete(next);
77
- queue.push(next);
78
- return;
79
- }
80
- const size = held.size;
81
- for (const name of exports)
82
- held.add(name);
83
- if (held.size > size)
84
- queue.push(next);
85
58
  };
86
- for (const seed of seeds) {
87
- if (seed < 0 || seed >= nodes || mask[seed] === 1 || avoided[seed] === 1)
88
- continue;
89
- enter(seed, -1, moved.get(seed));
90
- }
91
- for (let head = 0; head < queue.length; head += 1) {
92
- const node = queue[head];
93
- const held = partial.get(node);
94
- if (held?.size === 0)
95
- continue;
96
- const end = dependents.offset[node + 1];
97
- for (let at = dependents.offset[node]; at < end; at += 1) {
98
- const kind = dependents.kind[at];
99
- if (allowed[kind] !== 1)
100
- continue;
101
- const next = dependents.target[at];
102
- if (held === undefined || (kind !== IMPORTS && kind !== REEXPORTS)) {
103
- enter(next, node);
104
- continue;
105
- }
59
+ // Each edge reads everything the node holds, not only what this entry
60
+ // carried: a name that reached it by another path is as moved as this one.
61
+ const step = {
62
+ arrive(next, node, kind, _carry, held) {
63
+ if (held === WHOLE || (kind !== IMPORTS && kind !== REEXPORTS))
64
+ return WHOLE;
106
65
  const use = uses(names[next], names[node]);
107
66
  if (kind === IMPORTS) {
108
67
  const imported = use?.imports;
109
68
  // TODO: enter the importer carrying the exports whose bodies read the
110
69
  // name, which needs the addon's per-export reach within the importer;
111
70
  // until then an import hop two out is charged whole.
112
- if (imported === undefined || imported.some((name) => held.has(name)))
113
- enter(next, node);
114
- }
115
- else if (use?.reexports === undefined) {
116
- enter(next, node);
117
- }
118
- else {
119
- const out = republished(use.reexports, held);
120
- if (out.length > 0)
121
- enter(next, node, out);
71
+ if (imported === undefined)
72
+ return undefined;
73
+ return imported.some((name) => held.has(name)) ? WHOLE : NOTHING;
122
74
  }
123
- }
124
- }
125
- // In id order, so a report built from it is byte-stable whatever order the
126
- // caller listed its changed files in.
127
- const visited = [];
128
- for (let id = 0; id < nodes; id += 1)
129
- if (mask[id] === 1)
130
- visited.push(id);
131
- return { mask, via, nodes: visited };
75
+ if (use?.reexports === undefined)
76
+ return undefined;
77
+ return namesOf(republished(use.reexports, held));
78
+ },
79
+ };
80
+ const { mask } = carried(dependents, starts(), step, { ...options, via });
81
+ return { mask, via, nodes: inIdOrder(mask) };
82
+ }
83
+ function namesOf(exports) {
84
+ return new Map(exports.map((name) => [name, name]));
132
85
  }
133
86
  //# sourceMappingURL=narrow.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"narrow.js","sourceRoot":"","sources":["../../src/relate/narrow.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The walk against the arrows, narrowed by the names each importer uses.\n *\n * A file-graph edge says *this file imports that one*, and a plain walk charges\n * every importer for every change. An importer reaches a module only through\n * the names it binds, though, so a change that moved `total` and left `label`\n * alone reaches the files that import `total` and not the ones that import\n * `label`. That is tree shaking, asked of reach instead of a bundle.\n *\n * Each visited file carries one of two states:\n *\n * - **whole** — what it does as it loads moved, or nobody said which of its\n * exports did. Every dependent is entered, as the plain walk does.\n * - **the exports that moved** — read from the file's two texts for a seed,\n * or carried through a re-export for a barrel. A dependent that imports one\n * of them is entered whole: which of *its* exports read the name is a\n * question about its body, and nothing here reads one. A dependent that\n * re-exports one is entered carrying the names it republishes them under.\n *\n * Every answer the use cannot give is whole: an edge the lookup does not\n * know, a namespace import, a `require`, an `import()`, a stylesheet, a\n * component declared in the file. A narrower walk has to be earned by a name\n * the parse recorded, so an unknown can only cost a wider one.\n *\n * A file's names only grow, and a file that grew is expanded again, so the walk\n * reaches the same fixpoint whatever order the seeds arrive in.\n */\n\nimport { EDGE_KINDS, type Adjacency, type NodeId } from './graph.js';\nimport type { Traversal } from './reach.js';\n\n/** How one file uses one file it imports, read from the parse that recorded the edge. */\nexport interface EdgeUse {\n /**\n * The names an `import` binds from the target, `default` for a default\n * import. Absent when the use is the whole module: a namespace, a `require`,\n * an `import` that binds nothing, which a `require` is stored as.\n */\n readonly imports?: readonly string[];\n /**\n * What a re-export republishes, as `[the target's name, this file's name]`.\n * `['*', '*']` is `export * from`, which republishes every name but\n * `default`; `['*', 'ns']` is `export * as ns from`. Absent when the parse\n * did not record what the file publishes.\n */\n readonly reexports?: readonly (readonly [string, string])[];\n}\n\n/** How `importer` uses `target`, both by file path; `undefined` is whole. */\nexport type Uses = (importer: string, target: string) => EdgeUse | undefined;\n\nconst IMPORTS = EDGE_KINDS.indexOf('imports');\nconst REEXPORTS = EDGE_KINDS.indexOf('reexports');\n\n/** What a re-export hands on from `moved`, under the names the importer publishes. */\nfunction republished(passes: readonly (readonly [string, string])[], moved: ReadonlySet<string>): string[] {\n const out: string[] = [];\n for (const [imported, exported] of passes) {\n if (imported === '*' && exported === '*') {\n for (const name of moved) if (name !== 'default') out.push(name);\n } else if (imported === '*' || moved.has(imported)) {\n out.push(exported);\n }\n }\n return out;\n}\n\n/**\n * Breadth-first against the arrows from `seeds`, each seed in `moved` carrying\n * the exports its change moved and every other one whole.\n */\nexport function narrowed(\n dependents: Adjacency,\n names: readonly string[],\n seeds: Iterable<NodeId>,\n moved: ReadonlyMap<NodeId, readonly string[]>,\n uses: Uses,\n allowed: Uint8Array,\n avoid: Iterable<NodeId> = [],\n): Traversal {\n const nodes = names.length;\n const mask = new Uint8Array(nodes);\n const via = new Int32Array(nodes).fill(-1);\n const avoided = new Uint8Array(nodes);\n for (const id of avoid) if (id >= 0 && id < nodes) avoided[id] = 1;\n\n // A visited node absent here is whole.\n const partial = new Map<NodeId, Set<string>>();\n const queue: NodeId[] = [];\n\n const enter = (next: NodeId, from: NodeId, exports?: readonly string[]): void => {\n if (avoided[next] === 1) return;\n if (mask[next] !== 1) {\n mask[next] = 1;\n via[next] = from;\n if (exports !== undefined) partial.set(next, new Set(exports));\n queue.push(next);\n return;\n }\n const held = partial.get(next);\n if (held === undefined) return;\n if (exports === undefined) {\n partial.delete(next);\n queue.push(next);\n return;\n }\n const size = held.size;\n for (const name of exports) held.add(name);\n if (held.size > size) queue.push(next);\n };\n\n for (const seed of seeds) {\n if (seed < 0 || seed >= nodes || mask[seed] === 1 || avoided[seed] === 1) continue;\n enter(seed, -1, moved.get(seed));\n }\n\n for (let head = 0; head < queue.length; head += 1) {\n const node = queue[head]!;\n const held = partial.get(node);\n if (held?.size === 0) continue;\n const end = dependents.offset[node + 1]!;\n\n for (let at = dependents.offset[node]!; at < end; at += 1) {\n const kind = dependents.kind[at]!;\n if (allowed[kind] !== 1) continue;\n const next = dependents.target[at]!;\n if (held === undefined || (kind !== IMPORTS && kind !== REEXPORTS)) {\n enter(next, node);\n continue;\n }\n\n const use = uses(names[next]!, names[node]!);\n if (kind === IMPORTS) {\n const imported = use?.imports;\n // TODO: enter the importer carrying the exports whose bodies read the\n // name, which needs the addon's per-export reach within the importer;\n // until then an import hop two out is charged whole.\n if (imported === undefined || imported.some((name) => held.has(name))) enter(next, node);\n } else if (use?.reexports === undefined) {\n enter(next, node);\n } else {\n const out = republished(use.reexports, held);\n if (out.length > 0) enter(next, node, out);\n }\n }\n }\n\n // In id order, 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"]}
1
+ {"version":3,"file":"narrow.js","sourceRoot":"","sources":["../../src/relate/narrow.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The walk against the arrows, narrowed by the names each importer uses.\n *\n * A file-graph edge says *this file imports that one*, and a plain walk charges\n * every importer for every change. An importer reaches a module only through\n * the names it binds, though, so a change that moved `total` and left `label`\n * alone reaches the files that import `total` and not the ones that import\n * `label`. That is tree shaking, asked of reach instead of a bundle.\n *\n * Each visited file carries one of two states:\n *\n * - **whole** — what it does as it loads moved, or nobody said which of its\n * exports did. Every dependent is entered, as the plain walk does.\n * - **the exports that moved** — read from the file's two texts for a seed,\n * or carried through a re-export for a barrel. A dependent that imports one\n * of them is entered whole: which of *its* exports read the name is a\n * question about its body, and nothing here reads one. A dependent that\n * re-exports one is entered carrying the names it republishes them under.\n *\n * Every answer the use cannot give is whole: an edge the lookup does not\n * know, a namespace import, a `require`, an `import()`, a stylesheet, a\n * component declared in the file. A narrower walk has to be earned by a name\n * the parse recorded, so an unknown can only cost a wider one.\n *\n * A file's names only grow, and a file that grew is expanded again, so the walk\n * reaches the same fixpoint whatever order the seeds arrive in.\n */\n\nimport { carried, inIdOrder, NOTHING, WHOLE, type Carry, type CarriedStep } from './carried.js';\nimport { EDGE_KINDS, type Adjacency, type NodeId } from './graph.js';\nimport type { Traversal, TraversalOptions } from './reach.js';\n\n/** How one file uses one file it imports, read from the parse that recorded the edge. */\nexport interface EdgeUse {\n /**\n * The names an `import` binds from the target, `default` for a default\n * import. Absent when the use is the whole module: a namespace, a `require`,\n * an `import` that binds nothing, which a `require` is stored as.\n */\n readonly imports?: readonly string[];\n /**\n * What a re-export republishes, as `[the target's name, this file's name]`.\n * `['*', '*']` is `export * from`, which republishes every name but\n * `default`; `['*', 'ns']` is `export * as ns from`. Absent when the parse\n * did not record what the file publishes.\n */\n readonly reexports?: readonly (readonly [string, string])[];\n}\n\n/** How `importer` uses `target`, both by file path; `undefined` is whole. */\nexport type Uses = (importer: string, target: string) => EdgeUse | undefined;\n\nconst IMPORTS = EDGE_KINDS.indexOf('imports');\nconst REEXPORTS = EDGE_KINDS.indexOf('reexports');\n\n/** What a re-export hands on from `moved`, under the names the importer publishes. */\nfunction republished(passes: readonly (readonly [string, string])[], moved: ReadonlySet<string>): string[] {\n const out: string[] = [];\n for (const [imported, exported] of passes) {\n if (imported === '*' && exported === '*') {\n for (const name of moved) if (name !== 'default') out.push(name);\n } else if (imported === '*' || moved.has(imported)) {\n out.push(exported);\n }\n }\n return out;\n}\n\n/**\n * Breadth-first against the arrows from `seeds`, each seed in `moved` carrying\n * the exports its change moved and every other one whole.\n */\nexport function narrowed(\n dependents: Adjacency,\n names: readonly string[],\n seeds: Iterable<NodeId>,\n moved: ReadonlyMap<NodeId, readonly string[]>,\n uses: Uses,\n options: Pick<TraversalOptions, 'avoid' | 'through'> = {},\n): Traversal {\n const via = new Int32Array(names.length).fill(-1);\n const starts = function* () {\n for (const seed of seeds) {\n const exports = moved.get(seed);\n yield [seed, exports === undefined ? WHOLE : namesOf(exports)] as const;\n }\n };\n\n // Each edge reads everything the node holds, not only what this entry\n // carried: a name that reached it by another path is as moved as this one.\n const step: CarriedStep<string> = {\n arrive(next, node, kind, _carry, held): Carry<string> | undefined {\n if (held === WHOLE || (kind !== IMPORTS && kind !== REEXPORTS)) return WHOLE;\n const use = uses(names[next]!, names[node]!);\n if (kind === IMPORTS) {\n const imported = use?.imports;\n // TODO: enter the importer carrying the exports whose bodies read the\n // name, which needs the addon's per-export reach within the importer;\n // until then an import hop two out is charged whole.\n if (imported === undefined) return undefined;\n return imported.some((name) => held.has(name)) ? WHOLE : NOTHING;\n }\n if (use?.reexports === undefined) return undefined;\n return namesOf(republished(use.reexports, held));\n },\n };\n\n const { mask } = carried(dependents, starts(), step, { ...options, via });\n return { mask, via, nodes: inIdOrder(mask) };\n}\n\nfunction namesOf(exports: readonly string[]): ReadonlyMap<string, string> {\n return new Map(exports.map((name) => [name, name]));\n}\n"]}
@@ -27,7 +27,7 @@
27
27
  * cycle as an error: the visited mask means every node is expanded once, so a
28
28
  * cycle terminates the same way any revisit does.
29
29
  */
30
- import { type EdgeKind, type NodeId, type Relations } from './graph.js';
30
+ import type { EdgeKind, NodeId, Relations } from './graph.js';
31
31
  /**
32
32
  * What one walk from a set of seeds visited: a mark over every node, the node
33
33
  * each one was entered from, and the visited ids in id order. `trailOf` reads
@@ -27,13 +27,12 @@
27
27
  * cycle as an error: the visited mask means every node is expanded once, so a
28
28
  * cycle terminates the same way any revisit does.
29
29
  */
30
- import { EDGE_KINDS, RUNTIME_EDGES } from './graph.js';
30
+ import { carried, inIdOrder, WHOLE } from './carried.js';
31
31
  import { narrowed } from './narrow.js';
32
32
  /** What depends on these nodes, transitively. Against the arrows. */
33
33
  export function dependentsOf(relations, seeds, options = {}) {
34
34
  if (options.moved !== undefined && relations.uses !== undefined) {
35
- const allowed = allowedKinds(options.through);
36
- return narrowed(relations.dependents, relations.names, seeds, options.moved, relations.uses, allowed, options.avoid);
35
+ return narrowed(relations.dependents, relations.names, seeds, options.moved, relations.uses, options);
37
36
  }
38
37
  return search(relations.dependents, relations.names.length, seeds, options);
39
38
  }
@@ -57,62 +56,20 @@ export function trailOf(traversal, id) {
57
56
  return trail.reverse();
58
57
  }
59
58
  /**
60
- * Breadth-first over one direction, from every seed at once.
61
- *
62
- * A plain array as the queue with a moving head rather than `shift()`, which is
63
- * `O(n)` per call in every engine that does not special-case it and turns a
64
- * linear traversal quadratic on the graphs this exists for.
59
+ * Breadth-first over one direction, from every seed at once, carrying the whole
60
+ * of each node to everything one edge away ([`carried.ts`](./carried.ts)).
65
61
  *
66
62
  * Breadth-first rather than depth-first for the trail's sake: the parent
67
63
  * recorded is then the one on a *shortest* path, so the explanation a run prints
68
64
  * is the shortest true one rather than whichever the stack happened to unwind.
69
65
  */
70
66
  function search(adjacency, nodes, seeds, options) {
71
- const mask = new Uint8Array(nodes);
72
67
  const via = new Int32Array(nodes).fill(-1);
73
- const queue = [];
74
- const avoided = new Uint8Array(nodes);
75
- for (const id of options.avoid ?? [])
76
- if (id >= 0 && id < nodes)
77
- avoided[id] = 1;
78
- for (const seed of seeds) {
79
- if (seed < 0 || seed >= nodes || mask[seed] === 1 || avoided[seed] === 1)
80
- continue;
81
- mask[seed] = 1;
82
- queue.push(seed);
83
- }
84
- const allowed = allowedKinds(options.through);
85
- for (let head = 0; head < queue.length; head += 1) {
86
- const node = queue[head];
87
- const end = adjacency.offset[node + 1];
88
- for (let at = adjacency.offset[node]; at < end; at += 1) {
89
- if (allowed[adjacency.kind[at]] !== 1)
90
- continue;
91
- const next = adjacency.target[at];
92
- if (mask[next] === 1 || avoided[next] === 1)
93
- continue;
94
- mask[next] = 1;
95
- via[next] = node;
96
- queue.push(next);
97
- }
98
- }
99
- // `queue` is in visit order, which depends on seed order. The result is in id
100
- // order instead, so a report built from it is byte-stable whatever order the
101
- // caller listed its changed files in.
102
- const visited = [];
103
- for (let id = 0; id < nodes; id += 1)
104
- if (mask[id] === 1)
105
- visited.push(id);
106
- return { mask, via, nodes: visited };
107
- }
108
- /** A kind filter as a byte lookup. */
109
- function allowedKinds(through) {
110
- const allowed = new Uint8Array(EDGE_KINDS.length);
111
- for (const kind of through ?? RUNTIME_EDGES) {
112
- const at = EDGE_KINDS.indexOf(kind);
113
- if (at !== -1)
114
- allowed[at] = 1;
115
- }
116
- return allowed;
68
+ const whole = function* () {
69
+ for (const seed of seeds)
70
+ yield [seed, WHOLE];
71
+ };
72
+ const { mask } = carried(adjacency, whole(), {}, { ...options, via });
73
+ return { mask, via, nodes: inIdOrder(mask) };
117
74
  }
118
75
  //# sourceMappingURL=reach.js.map
@@ -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. `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 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"]}
@@ -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 declares, from `indexSource` or better. */
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.
@@ -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`](../../../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"]}
@@ -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 makes stored results incomparable
11
- * rather than silently comparable — and a reader who wants to know *why* two runs
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
@@ -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 makes stored results incomparable
11
- * rather than silently comparable — and a reader who wants to know *why* two runs
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
@@ -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 makes stored results incomparable\n * rather than silently comparable — and a reader who wants to know *why* two runs\n * disagree ends up in this directory rather than anywhere else.\n */\n\nexport { normalize } from './normalize/index.js';\nexport type { NormalizeOptions } from './normalize/index.js';\nexport type {\n AliasMap,\n AliasResult,\n InheritContext,\n ResolvedStyle,\n DeclarationOrigin,\n} from './normalize/index.js';\nexport {\n buildAliasMap,\n aliasAttributeValue,\n aliasStyleValue,\n resolveStyle,\n resolveVariables,\n INHERITED_PROPERTIES,\n EMPTY_CONTEXT,\n canonicalizeValue,\n canonicalizeTokens,\n canonicalizeDimension,\n canonicalizeColor,\n parseColor,\n formatColor,\n expandDeclaration,\n SHORTHAND_PROPERTIES,\n} from './normalize/index.js';\n\nexport {\n RULESET_VERSION,\n ALLOWLIST_VERSION,\n STYLE_ALLOWLIST,\n ATTRIBUTE_ALLOWLIST,\n ID_REFERENCE_ATTRIBUTES,\n ID_REFERENCE_LIST_ATTRIBUTES,\n isAllowedProperty,\n isCustomProperty,\n admits,\n admitsAttribute,\n} from './ruleset.js';\n"]}
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"]}