@variance-authority/core 0.15.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +424 -0
  2. package/README.md +25 -7
  3. package/dist/attribute/component-hash.d.ts +11 -3
  4. package/dist/attribute/component-hash.js +11 -3
  5. package/dist/attribute/component-hash.js.map +1 -1
  6. package/dist/attribute/movement.d.ts +1 -1
  7. package/dist/attribute/movement.js.map +1 -1
  8. package/dist/attribute/region.d.ts +47 -4
  9. package/dist/attribute/region.js +90 -17
  10. package/dist/attribute/region.js.map +1 -1
  11. package/dist/attribute/source.d.ts +1 -1
  12. package/dist/attribute/source.js +1 -1
  13. package/dist/attribute/source.js.map +1 -1
  14. package/dist/compare/band.d.ts +8 -2
  15. package/dist/compare/band.js +7 -2
  16. package/dist/compare/band.js.map +1 -1
  17. package/dist/compare/diff/attribution.js +6 -4
  18. package/dist/compare/diff/attribution.js.map +1 -1
  19. package/dist/compare/diff/compare-nodes.js +9 -1
  20. package/dist/compare/diff/compare-nodes.js.map +1 -1
  21. package/dist/compare/diff/match.d.ts +2 -1
  22. package/dist/compare/diff/match.js +52 -0
  23. package/dist/compare/diff/match.js.map +1 -1
  24. package/dist/compare/parting.js +6 -4
  25. package/dist/compare/parting.js.map +1 -1
  26. package/dist/format/document.d.ts +26 -3
  27. package/dist/format/document.js.map +1 -1
  28. package/dist/format/index.d.ts +7 -0
  29. package/dist/format/index.js +4 -0
  30. package/dist/format/index.js.map +1 -1
  31. package/dist/format/packed.d.ts +32 -0
  32. package/dist/format/packed.js +23 -0
  33. package/dist/format/packed.js.map +1 -0
  34. package/dist/format/shape.d.ts +31 -0
  35. package/dist/format/shape.js +269 -0
  36. package/dist/format/shape.js.map +1 -0
  37. package/dist/format/stabilize.d.ts +7 -1
  38. package/dist/format/stabilize.js +10 -3
  39. package/dist/format/stabilize.js.map +1 -1
  40. package/dist/format/steps.d.ts +44 -0
  41. package/dist/format/steps.js +67 -0
  42. package/dist/format/steps.js.map +1 -0
  43. package/dist/format/width.d.ts +35 -0
  44. package/dist/format/width.js +38 -0
  45. package/dist/format/width.js.map +1 -0
  46. package/dist/judge/docket.js +1 -0
  47. package/dist/judge/docket.js.map +1 -1
  48. package/dist/judge/index.d.ts +1 -0
  49. package/dist/judge/index.js +1 -0
  50. package/dist/judge/index.js.map +1 -1
  51. package/dist/judge/restamp.d.ts +20 -0
  52. package/dist/judge/restamp.js +20 -0
  53. package/dist/judge/restamp.js.map +1 -0
  54. package/dist/relate/before.js +2 -2
  55. package/dist/relate/before.js.map +1 -1
  56. package/dist/relate/carried.d.ts +1 -1
  57. package/dist/relate/carried.js +5 -5
  58. package/dist/relate/carried.js.map +1 -1
  59. package/dist/relate/graph.d.ts +14 -12
  60. package/dist/relate/graph.js +12 -11
  61. package/dist/relate/graph.js.map +1 -1
  62. package/dist/relate/index.d.ts +1 -2
  63. package/dist/relate/index.js +1 -2
  64. package/dist/relate/index.js.map +1 -1
  65. package/dist/relate/reach.d.ts +5 -5
  66. package/dist/relate/reach.js.map +1 -1
  67. package/dist/relate/records.d.ts +5 -4
  68. package/dist/relate/records.js +3 -2
  69. package/dist/relate/records.js.map +1 -1
  70. package/dist/rules/normalize/fixture.d.ts +2 -0
  71. package/dist/rules/normalize/fixture.js +2 -1
  72. package/dist/rules/normalize/fixture.js.map +1 -1
  73. package/dist/shard/placement.d.ts +10 -8
  74. package/dist/shard/placement.js +7 -21
  75. package/dist/shard/placement.js.map +1 -1
  76. package/dist/share/index.d.ts +1 -1
  77. package/dist/share/index.js +1 -1
  78. package/dist/share/index.js.map +1 -1
  79. package/package.json +2 -2
  80. package/dist/relate/dominate.d.ts +0 -19
  81. package/dist/relate/dominate.js +0 -77
  82. package/dist/relate/dominate.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"placement.js","sourceRoot":"","sources":["../../src/shard/placement.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which shard runs which group, and how many shards a suite is worth.\n *\n * A group is placed whole: every story of one file, every width of one route,\n * every case of one test file loads the same modules, and split across\n * machines each machine pays for them. Three rules decide where it goes:\n *\n * - **With recorded times, the time decides.** Groups go longest first to the\n * shard with the least time so far (LPT, within 4/3 of the best split). Ties\n * are broken by key in code-unit order, so the placement is a function of\n * the groups and their times and never of the order they were found in.\n * - **A member nothing recorded is priced at the median of the recorded ones.**\n * A new file is a file, and pricing it at zero would stack every new one on\n * one shard.\n * - **With nothing recorded, a checksum decides.** Each group goes to the shard\n * whose `sha256(key, shard)` is highest, which depends on the key and the\n * shard count alone, so adding a group moves no other.\n *\n * Every shard computes the whole placement and keeps its own part. Nothing is\n * exchanged between shards, so two shards agree exactly when they were handed\n * the same groups and the same times.\n */\n\nimport { sha256Hex } from '../format/sha256.js';\nimport { codeUnitOrder } from '../segment/index.js';\n\n/** One group placed whole, and the milliseconds recorded for each of its members. */\nexport interface Placeable {\n readonly key: string;\n /** `undefined` for a member nothing recorded a time for. */\n readonly costs: readonly (number | undefined)[];\n}\n\nexport interface Placement {\n /** The shard, from 1, of each group in the order they were handed in. */\n readonly owner: readonly number[];\n readonly by: 'checksum' | 'recorded cost';\n /** Milliseconds each group is priced at; absent when placed by checksum. */\n readonly cost?: readonly number[];\n /** Milliseconds each shard was given, shard 1 first; absent when placed by checksum. */\n readonly load?: readonly number[];\n}\n\n/**\n * Each group's price: its members' recorded milliseconds, with the median of\n * every recorded member standing in for one nothing recorded. Absent when no\n * member of any group is recorded, because then there is no price to give.\n */\nexport function priced(groups: readonly Placeable[]): number[] | undefined {\n const known = groups.flatMap((group) => group.costs.filter((cost): cost is number => cost !== undefined));\n if (known.length === 0) return undefined;\n const median = known.sort((a, b) => a - b)[Math.floor(known.length / 2)]!;\n return groups.map((group) => group.costs.reduce<number>((sum, cost) => sum + (cost ?? median), 0));\n}\n\n/** Place every group on one of `total` shards. */\nexport function place(groups: readonly Placeable[], total: number): Placement {\n const cost = priced(groups);\n if (cost === undefined) {\n return { owner: groups.map((group) => (total === 1 ? 1 : rendezvous(group.key, total))), by: 'checksum' };\n }\n const order = longestFirst(groups.map((group) => group.key), cost);\n const { owner, load } = packed(order.map((index) => cost[index]!), total);\n const byGroup: number[] = Array.from({ length: groups.length }, () => 0);\n order.forEach((index, at) => (byGroup[index] = owner[at]!));\n return { owner: byGroup, by: 'recorded cost', cost, load };\n}\n\n/**\n * Group indices, longest first, ties by key in code-unit order: the order a\n * shard's workers take its groups in, so one that finishes early takes the\n * short ones at the end.\n */\nexport function longestFirst(keys: readonly string[], cost: readonly number[]): number[] {\n return keys.map((_, index) => index).sort((a, b) => cost[b]! - cost[a]! || codeUnitOrder(keys[a]!, keys[b]!));\n}\n\n/** Why a shard count is the one {@link shardCount} chose. */\nexport type ShardCountReason =\n /** No group to run, so no shard to start. */\n | 'nothing to run'\n /** The fewest shards whose setup and slowest share fit the budget. */\n | 'within budget'\n /** One more shard would save less waiting than the setup it spends. */\n | 'setup'\n /** The slowest group is the longest share already, and no shard finishes before it. */\n | 'slowest group'\n /**\n * The budget is no longer than the setup, which every shard spends before its\n * first group, so no count meets it; the count is the one that finishes soonest.\n */\n | 'setup over budget'\n /** The most shards allowed. */\n | 'max';\n\nexport interface ShardCountOptions {\n /** Milliseconds a shard spends before its first test: start, checkout, install, build. */\n readonly setup: number;\n /** Milliseconds one shard may take, setup included. */\n readonly budget?: number;\n /** The most shards to start. */\n readonly max?: number;\n /**\n * How many groups one shard runs at once, its runner's workers: a shard is\n * done when its share, divided among them, is, and never before its longest\n * group. One when absent, a shard that runs its groups in turn.\n */\n readonly workers?: number;\n}\n\nexport interface ShardCount {\n readonly shards: number;\n /** Milliseconds of groups each shard runs, shard 1 first, as {@link place} would place them. */\n readonly load: readonly number[];\n /** Milliseconds until the last shard finishes: the setup and the longest share, divided among its workers. */\n readonly wall: number;\n readonly why: ShardCountReason;\n /** What one more shard would finish in, when the setup it spends is why it was not added. */\n readonly next?: { readonly wall: number };\n}\n\n/**\n * GitHub Actions starts at most 256 jobs from one matrix, the largest limit\n * any CI this tool is run on documents, so a count past it could not be used.\n */\nexport const MATRIX_LIMIT = 256;\n\n/**\n * How many shards to split groups priced at `costs` milliseconds across.\n *\n * Every shard pays `setup` before it runs a group, so the wait is the setup\n * plus the longest share. With a budget, the count is the fewest shards whose\n * wait fits it. Without one, each shard is charged one setup, and the count is\n * the one whose wait and setups come to least, the fewer of two that tie: a\n * shard is worth starting when it shortens the wait by more than it spends.\n * Every count is weighed, not only the next, because whole groups split\n * unevenly: a third shard can save nothing where a fourth saves a lot. Either\n * way the count stops where the longest share is the slowest group, because\n * past that no shard finishes sooner. A budget no longer than the setup is met\n * by no count, and the setup, not the slowest group, is why.\n */\nexport function shardCount(costs: readonly number[], options: ShardCountOptions): ShardCount {\n if (costs.length === 0) return { shards: 0, load: [], wall: 0, why: 'nothing to run' };\n const sorted = [...costs].sort((a, b) => b - a);\n const slowest = sorted[0]!;\n const total = sorted.reduce((sum, cost) => sum + cost, 0);\n const limit = Math.max(1, Math.min(options.max ?? MATRIX_LIMIT, sorted.length));\n const workers = options.workers ?? 1;\n const at = (shards: number) => {\n const { load, first } = packed(sorted, shards);\n const share = Math.max(...load.map((part, shard) => Math.max(part / workers, first[shard]!)));\n return { shards, load, wall: options.setup + share };\n };\n const counted = (shards: ReturnType<typeof at>, why: ShardCountReason, next?: ReturnType<typeof at>): ShardCount =>\n ({ ...shards, why, ...(next === undefined ? {} : { next: { wall: next.wall } }) });\n const reached = (shards: ReturnType<typeof at>) => shards.wall - options.setup <= slowest;\n\n if (options.budget !== undefined) {\n // With no setup, the slowest group is what no count gets under, not the setup.\n const unmet = options.setup > 0 && options.budget <= options.setup;\n for (let here = at(1); ; here = at(here.shards + 1)) {\n if (here.wall <= options.budget) return counted(here, 'within budget');\n if (reached(here)) return counted(here, unmet ? 'setup over budget' : 'slowest group');\n if (here.shards === limit) return counted(here, unmet ? 'setup over budget' : 'max');\n }\n }\n\n const spent = (shards: ReturnType<typeof at>) => shards.wall + options.setup * shards.shards;\n let best = at(1);\n for (let shards = 2; shards <= limit && !reached(best); shards++) {\n // No split of `shards` waits less than the slowest group or an even share,\n // and every count past this one spends more setup, so none can do better.\n if (options.setup + Math.max(slowest, total / shards / workers) + options.setup * shards >= spent(best)) break;\n const here = at(shards);\n if (spent(here) < spent(best)) best = here;\n }\n if (reached(best)) return counted(best, 'slowest group');\n if (best.shards === limit) return counted(best, 'max');\n return counted(best, 'setup', at(best.shards + 1));\n}\n\n/** Milliseconds as a person reads them in a shard's line: `340 ms`, `9.0 s`. */\nexport function durationText(milliseconds: number): string {\n return milliseconds < 1000 ? `${Math.round(milliseconds)} ms` : `${(milliseconds / 1000).toFixed(1)} s`;\n}\n\n/**\n * LPT over costs already sorted longest first: each to the shard with the\n * least so far, the lowest-numbered of equals. `first` is each shard's\n * longest group, the first it was given.\n */\nfunction packed(sorted: readonly number[], total: number): { owner: number[]; load: number[]; first: number[] } {\n const load: number[] = Array.from({ length: total }, () => 0);\n const first: number[] = Array.from({ length: total }, () => 0);\n // A binary heap of shard indices by (load, index), so a suite of tens of\n // thousands of files is placed in n log k rather than n·k.\n const heap = Array.from({ length: total }, (_, index) => index);\n const before = (a: number, b: number) => load[a]! < load[b]! || (load[a] === load[b] && a < b);\n const owner = sorted.map((cost) => {\n const least = heap[0]!;\n if (load[least] === 0) first[least] = cost;\n load[least] = load[least]! + cost;\n for (let at = 0; ; ) {\n const left = 2 * at + 1;\n const right = left + 1;\n let next = at;\n if (left < total && before(heap[left]!, heap[next]!)) next = left;\n if (right < total && before(heap[right]!, heap[next]!)) next = right;\n if (next === at) break;\n [heap[at], heap[next]] = [heap[next]!, heap[at]!];\n at = next;\n }\n return least + 1;\n });\n return { owner, load, first };\n}\n\nfunction rendezvous(key: string, total: number): number {\n let best = 1;\n let bestScore = '';\n for (let shard = 1; shard <= total; shard++) {\n const score = sha256Hex(`${key}\\0${shard}`);\n if (score > bestScore) {\n best = shard;\n bestScore = score;\n }\n }\n return best;\n}\n"]}
1
+ {"version":3,"file":"placement.js","sourceRoot":"","sources":["../../src/shard/placement.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Which shard runs which group, and how many shards a suite is worth.\n *\n * A group is placed whole: every story of one file, every width of one route,\n * every case of one test file loads the same modules, and split across\n * machines each machine pays for them. Three rules decide where it goes:\n *\n * - **With recorded times, the time decides.** Groups go longest first to the\n * shard with the least time so far (LPT, within 4/3 of the best split). Ties\n * are broken by key in code-unit order, so the placement is a function of\n * the groups and their times and never of the order they were found in.\n * - **A member nothing recorded is priced at the median of the recorded ones.**\n * A new file is a file, and pricing it at zero would stack every new one on\n * one shard.\n * - **With nothing recorded, every group weighs the same.** The same rule\n * places them, so the shards are balanced by count, ties broken by key and\n * then by the lowest shard, and no shard is left empty while there are as\n * many groups as shards.\n *\n * Every shard computes the whole placement and keeps its own part. Nothing is\n * exchanged between shards, so two shards agree exactly when they were handed\n * the same groups and the same times.\n */\n\nimport { codeUnitOrder } from '../segment/index.js';\n\n/** One group placed whole, and the milliseconds recorded for each of its members. */\nexport interface Placeable {\n readonly key: string;\n /** `undefined` for a member nothing recorded a time for. */\n readonly costs: readonly (number | undefined)[];\n}\n\nexport interface Placement {\n /** The shard, from 1, of each group in the order they were handed in. */\n readonly owner: readonly number[];\n /** `count` when nothing was recorded and every group weighed one. */\n readonly by: 'count' | 'recorded cost';\n /** Milliseconds each group is priced at, or one each when placed by count. */\n readonly cost: readonly number[];\n /** What each shard was given, shard 1 first: milliseconds, or groups when placed by count. */\n readonly load: readonly number[];\n}\n\n/**\n * Each group's price: its members' recorded milliseconds, with the median of\n * every recorded member standing in for one nothing recorded. Absent when no\n * member of any group is recorded, because then there is no price to give.\n */\nexport function priced(groups: readonly Placeable[]): number[] | undefined {\n const known = groups.flatMap((group) => group.costs.filter((cost): cost is number => cost !== undefined));\n if (known.length === 0) return undefined;\n const median = known.sort((a, b) => a - b)[Math.floor(known.length / 2)]!;\n return groups.map((group) => group.costs.reduce<number>((sum, cost) => sum + (cost ?? median), 0));\n}\n\n/** Place every group on one of `total` shards. */\nexport function place(groups: readonly Placeable[], total: number): Placement {\n const recorded = priced(groups);\n const cost = recorded ?? groups.map(() => 1);\n const order = longestFirst(groups.map((group) => group.key), cost);\n const { owner, load } = packed(order.map((index) => cost[index]!), total);\n const byGroup: number[] = Array.from({ length: groups.length }, () => 0);\n order.forEach((index, at) => (byGroup[index] = owner[at]!));\n return { owner: byGroup, by: recorded === undefined ? 'count' : 'recorded cost', cost, load };\n}\n\n/**\n * Group indices, longest first, ties by key in code-unit order: the order a\n * shard's workers take its groups in, so one that finishes early takes the\n * short ones at the end.\n */\nexport function longestFirst(keys: readonly string[], cost: readonly number[]): number[] {\n return keys.map((_, index) => index).sort((a, b) => cost[b]! - cost[a]! || codeUnitOrder(keys[a]!, keys[b]!));\n}\n\n/** Why a shard count is the one {@link shardCount} chose. */\nexport type ShardCountReason =\n /** No group to run, so no shard to start. */\n | 'nothing to run'\n /** The fewest shards whose setup and slowest share fit the budget. */\n | 'within budget'\n /** One more shard would save less waiting than the setup it spends. */\n | 'setup'\n /** The slowest group is the longest share already, and no shard finishes before it. */\n | 'slowest group'\n /**\n * The budget is no longer than the setup, which every shard spends before its\n * first group, so no count meets it; the count is the one that finishes soonest.\n */\n | 'setup over budget'\n /** The most shards allowed. */\n | 'max';\n\nexport interface ShardCountOptions {\n /** Milliseconds a shard spends before its first test: start, checkout, install, build. */\n readonly setup: number;\n /** Milliseconds one shard may take, setup included. */\n readonly budget?: number;\n /** The most shards to start. */\n readonly max?: number;\n /**\n * How many groups one shard runs at once, its runner's workers: a shard is\n * done when its share, divided among them, is, and never before its longest\n * group. One when absent, a shard that runs its groups in turn.\n */\n readonly workers?: number;\n}\n\nexport interface ShardCount {\n readonly shards: number;\n /** Milliseconds of groups each shard runs, shard 1 first, as {@link place} would place them. */\n readonly load: readonly number[];\n /** Milliseconds until the last shard finishes: the setup and the longest share, divided among its workers. */\n readonly wall: number;\n readonly why: ShardCountReason;\n /** What one more shard would finish in, when the setup it spends is why it was not added. */\n readonly next?: { readonly wall: number };\n}\n\n/**\n * GitHub Actions starts at most 256 jobs from one matrix, the largest limit\n * any CI this tool is run on documents, so a count past it could not be used.\n */\nexport const MATRIX_LIMIT = 256;\n\n/**\n * How many shards to split groups priced at `costs` milliseconds across.\n *\n * Every shard pays `setup` before it runs a group, so the wait is the setup\n * plus the longest share. With a budget, the count is the fewest shards whose\n * wait fits it. Without one, each shard is charged one setup, and the count is\n * the one whose wait and setups come to least, the fewer of two that tie: a\n * shard is worth starting when it shortens the wait by more than it spends.\n * Every count is weighed, not only the next, because whole groups split\n * unevenly: a third shard can save nothing where a fourth saves a lot. Either\n * way the count stops where the longest share is the slowest group, because\n * past that no shard finishes sooner. A budget no longer than the setup is met\n * by no count, and the setup, not the slowest group, is why.\n */\nexport function shardCount(costs: readonly number[], options: ShardCountOptions): ShardCount {\n if (costs.length === 0) return { shards: 0, load: [], wall: 0, why: 'nothing to run' };\n const sorted = [...costs].sort((a, b) => b - a);\n const slowest = sorted[0]!;\n const total = sorted.reduce((sum, cost) => sum + cost, 0);\n const limit = Math.max(1, Math.min(options.max ?? MATRIX_LIMIT, sorted.length));\n const workers = options.workers ?? 1;\n const at = (shards: number) => {\n const { load, first } = packed(sorted, shards);\n const share = Math.max(...load.map((part, shard) => Math.max(part / workers, first[shard]!)));\n return { shards, load, wall: options.setup + share };\n };\n const counted = (shards: ReturnType<typeof at>, why: ShardCountReason, next?: ReturnType<typeof at>): ShardCount =>\n ({ ...shards, why, ...(next === undefined ? {} : { next: { wall: next.wall } }) });\n const reached = (shards: ReturnType<typeof at>) => shards.wall - options.setup <= slowest;\n\n if (options.budget !== undefined) {\n // With no setup, the slowest group is what no count gets under, not the setup.\n const unmet = options.setup > 0 && options.budget <= options.setup;\n for (let here = at(1); ; here = at(here.shards + 1)) {\n if (here.wall <= options.budget) return counted(here, 'within budget');\n if (reached(here)) return counted(here, unmet ? 'setup over budget' : 'slowest group');\n if (here.shards === limit) return counted(here, unmet ? 'setup over budget' : 'max');\n }\n }\n\n const spent = (shards: ReturnType<typeof at>) => shards.wall + options.setup * shards.shards;\n let best = at(1);\n for (let shards = 2; shards <= limit && !reached(best); shards++) {\n // No split of `shards` waits less than the slowest group or an even share,\n // and every count past this one spends more setup, so none can do better.\n if (options.setup + Math.max(slowest, total / shards / workers) + options.setup * shards >= spent(best)) break;\n const here = at(shards);\n if (spent(here) < spent(best)) best = here;\n }\n if (reached(best)) return counted(best, 'slowest group');\n if (best.shards === limit) return counted(best, 'max');\n return counted(best, 'setup', at(best.shards + 1));\n}\n\n/** Milliseconds as a person reads them in a shard's line: `340 ms`, `9.0 s`. */\nexport function durationText(milliseconds: number): string {\n return milliseconds < 1000 ? `${Math.round(milliseconds)} ms` : `${(milliseconds / 1000).toFixed(1)} s`;\n}\n\n/**\n * LPT over costs already sorted longest first: each to the shard with the\n * least so far, the lowest-numbered of equals. `first` is each shard's\n * longest group, the first it was given.\n */\nfunction packed(sorted: readonly number[], total: number): { owner: number[]; load: number[]; first: number[] } {\n const load: number[] = Array.from({ length: total }, () => 0);\n const first: number[] = Array.from({ length: total }, () => 0);\n // A binary heap of shard indices by (load, index), so a suite of tens of\n // thousands of files is placed in n log k rather than n·k.\n const heap = Array.from({ length: total }, (_, index) => index);\n const before = (a: number, b: number) => load[a]! < load[b]! || (load[a] === load[b] && a < b);\n const owner = sorted.map((cost) => {\n const least = heap[0]!;\n if (load[least] === 0) first[least] = cost;\n load[least] = load[least]! + cost;\n for (let at = 0; ; ) {\n const left = 2 * at + 1;\n const right = left + 1;\n let next = at;\n if (left < total && before(heap[left]!, heap[next]!)) next = left;\n if (right < total && before(heap[right]!, heap[next]!)) next = right;\n if (next === at) break;\n [heap[at], heap[next]] = [heap[next]!, heap[at]!];\n at = next;\n }\n return least + 1;\n });\n return { owner, load, first };\n}\n"]}
@@ -19,7 +19,7 @@
19
19
  * Not a baseline store. A baseline is the thing a comparison is *against*, it
20
20
  * cannot be re-derived, and losing one silently is how a suite records whatever
21
21
  * happened to be on screen — which is why
22
- * [`RasterStore`](../../../raster/src/store.ts) throws on every failure. A line
22
+ * [`RasterStore`](https://github.com/Variance-Authority/variance-authority/blob/main/packages/raster/src/store.ts) throws on every failure. A line
23
23
  * holds what can be derived again from the commit it names, so a miss costs the
24
24
  * derivation. The two are configured separately because they are answers to
25
25
  * different questions, and a share is disposable in a way a baseline may never
@@ -19,7 +19,7 @@
19
19
  * Not a baseline store. A baseline is the thing a comparison is *against*, it
20
20
  * cannot be re-derived, and losing one silently is how a suite records whatever
21
21
  * happened to be on screen — which is why
22
- * [`RasterStore`](../../../raster/src/store.ts) throws on every failure. A line
22
+ * [`RasterStore`](https://github.com/Variance-Authority/variance-authority/blob/main/packages/raster/src/store.ts) throws on every failure. A line
23
23
  * holds what can be derived again from the commit it names, so a miss costs the
24
24
  * derivation. The two are configured separately because they are answers to
25
25
  * different questions, and a share is disposable in a way a baseline may never
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/share/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC","sourcesContent":["/**\n * `@variance-authority/core/share` — one machine's evaluation, reachable from another.\n *\n * A run derives a great deal that is not about the run: what the suite is made\n * of, which subjects exist, every name each one carries, which files declare\n * which components. All of it is a fact about a *commit*, and all of it is\n * expensive — a source scan, a browser, a composition pass — while being\n * identical on every machine that starts from the same tree.\n *\n * So the second machine should not derive it again. A pull-request runner wants\n * what mainline already knows, and mainline already knew it an hour ago on a\n * runner that has since been destroyed. This module is the seam between those\n * two: a `LineCell` holds the latest of what one mainline or one branch\n * published, `publishLine` replaces it, and `readLine` reads it back\n * with the reason when it cannot.\n *\n * ## What this is not\n *\n * Not a baseline store. A baseline is the thing a comparison is *against*, it\n * cannot be re-derived, and losing one silently is how a suite records whatever\n * happened to be on screen — which is why\n * [`RasterStore`](../../../raster/src/store.ts) throws on every failure. A line\n * holds what can be derived again from the commit it names, so a miss costs the\n * derivation. The two are configured separately because they are answers to\n * different questions, and a share is disposable in a way a baseline may never\n * be.\n */\n\nexport * from './line.js';\nexport * from './publish.js';\nexport * from './http-line.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/share/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC","sourcesContent":["/**\n * `@variance-authority/core/share` — one machine's evaluation, reachable from another.\n *\n * A run derives a great deal that is not about the run: what the suite is made\n * of, which subjects exist, every name each one carries, which files declare\n * which components. All of it is a fact about a *commit*, and all of it is\n * expensive — a source scan, a browser, a composition pass — while being\n * identical on every machine that starts from the same tree.\n *\n * So the second machine should not derive it again. A pull-request runner wants\n * what mainline already knows, and mainline already knew it an hour ago on a\n * runner that has since been destroyed. This module is the seam between those\n * two: a `LineCell` holds the latest of what one mainline or one branch\n * published, `publishLine` replaces it, and `readLine` reads it back\n * with the reason when it cannot.\n *\n * ## What this is not\n *\n * Not a baseline store. A baseline is the thing a comparison is *against*, it\n * cannot be re-derived, and losing one silently is how a suite records whatever\n * happened to be on screen — which is why\n * [`RasterStore`](https://github.com/Variance-Authority/variance-authority/blob/main/packages/raster/src/store.ts) throws on every failure. A line\n * holds what can be derived again from the commit it names, so a miss costs the\n * derivation. The two are configured separately because they are answers to\n * different questions, and a share is disposable in a way a baseline may never\n * be.\n */\n\nexport * from './line.js';\nexport * from './publish.js';\nexport * from './http-line.js';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@variance-authority/core",
3
- "version": "0.15.0",
3
+ "version": "0.17.1",
4
4
  "description": "The Variance Authority format, rules, comparison, attribution and verdicts. Pure data in, pure data out, no DOM and no I/O.",
5
5
  "keywords": [
6
6
  "variance-authority",
@@ -79,6 +79,6 @@
79
79
  "build": "tsc --build"
80
80
  },
81
81
  "devDependencies": {
82
- "@variance-authority/sense": "^0.15.0"
82
+ "@variance-authority/sense": "^0.17.1"
83
83
  }
84
84
  }
@@ -1,19 +0,0 @@
1
- /**
2
- * Dominance over a graph with one root.
3
- *
4
- * D dominates N when every path from the root to N passes through D; N's
5
- * immediate dominator is the nearest such D, and the immediate dominators form
6
- * a tree. Cooper, Harvey and Kennedy's iterative algorithm ("A Simple, Fast
7
- * Dominance Algorithm", 2001): intersect the predecessors' dominators in
8
- * reverse postorder until nothing changes. It holds on cycles, and on the graphs
9
- * an import walk produces it settles in two or three passes.
10
- */
11
- /**
12
- * Each node the root reaches, mapped to its immediate dominator. The root and
13
- * every node it does not reach are absent.
14
- *
15
- * `successors` is the graph as the caller sees it: a walk that cuts an edge
16
- * leaves the edge out here.
17
- */
18
- export declare function dominatorsOf(root: string, successors: (node: string) => Iterable<string>): ReadonlyMap<string, string>;
19
- //# sourceMappingURL=dominate.d.ts.map
@@ -1,77 +0,0 @@
1
- // compass: variance-authority.reach
2
- /**
3
- * Dominance over a graph with one root.
4
- *
5
- * D dominates N when every path from the root to N passes through D; N's
6
- * immediate dominator is the nearest such D, and the immediate dominators form
7
- * a tree. Cooper, Harvey and Kennedy's iterative algorithm ("A Simple, Fast
8
- * Dominance Algorithm", 2001): intersect the predecessors' dominators in
9
- * reverse postorder until nothing changes. It holds on cycles, and on the graphs
10
- * an import walk produces it settles in two or three passes.
11
- */
12
- /**
13
- * Each node the root reaches, mapped to its immediate dominator. The root and
14
- * every node it does not reach are absent.
15
- *
16
- * `successors` is the graph as the caller sees it: a walk that cuts an edge
17
- * leaves the edge out here.
18
- */
19
- export function dominatorsOf(root, successors) {
20
- // Postorder by an explicit stack: an import chain is deeper than a call stack likes.
21
- const order = [];
22
- const number = new Map();
23
- const predecessors = new Map();
24
- const seen = new Set([root]);
25
- const stack = [{ node: root, next: successors(root)[Symbol.iterator]() }];
26
- while (stack.length > 0) {
27
- const top = stack[stack.length - 1];
28
- const step = top.next.next();
29
- if (step.done === true) {
30
- stack.pop();
31
- number.set(top.node, order.length);
32
- order.push(top.node);
33
- continue;
34
- }
35
- const child = step.value;
36
- const into = predecessors.get(child);
37
- if (into === undefined)
38
- predecessors.set(child, [top.node]);
39
- else
40
- into.push(top.node);
41
- if (seen.has(child))
42
- continue;
43
- seen.add(child);
44
- stack.push({ node: child, next: successors(child)[Symbol.iterator]() });
45
- }
46
- const idom = new Map([[root, root]]);
47
- const intersect = (left, right) => {
48
- let a = left;
49
- let b = right;
50
- while (a !== b) {
51
- while (number.get(a) < number.get(b))
52
- a = idom.get(a);
53
- while (number.get(b) < number.get(a))
54
- b = idom.get(b);
55
- }
56
- return a;
57
- };
58
- for (let changed = true; changed;) {
59
- changed = false;
60
- for (let at = order.length - 2; at >= 0; at -= 1) {
61
- const node = order[at];
62
- let next;
63
- for (const from of predecessors.get(node) ?? []) {
64
- if (!idom.has(from))
65
- continue;
66
- next = next === undefined ? from : intersect(from, next);
67
- }
68
- if (next !== undefined && idom.get(node) !== next) {
69
- idom.set(node, next);
70
- changed = true;
71
- }
72
- }
73
- }
74
- idom.delete(root);
75
- return idom;
76
- }
77
- //# sourceMappingURL=dominate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dominate.js","sourceRoot":"","sources":["../../src/relate/dominate.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC;;;;;;;;;GASG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,UAA8C;IAE9C,qFAAqF;IACrF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAoB,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7B,MAAM,KAAK,GAA+C,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IACtH,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;QACrC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACvB,KAAK,CAAC,GAAG,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YACnC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACrB,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,IAAI,KAAK,SAAS;YAAE,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;;YACvD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,SAAS;QAC9B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,CAAiB,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE;QACxD,IAAI,CAAC,GAAG,IAAI,CAAC;QACb,IAAI,CAAC,GAAG,KAAK,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACf,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE;gBAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC;YACzD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE;gBAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC;QAC3D,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IACF,KAAK,IAAI,OAAO,GAAG,IAAI,EAAE,OAAO,GAAG,CAAC;QAClC,OAAO,GAAG,KAAK,CAAC;QAChB,KAAK,IAAI,EAAE,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAE,CAAC;YACxB,IAAI,IAAwB,CAAC;YAC7B,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBAChD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAC9B,IAAI,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC3D,CAAC;YACD,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;gBAClD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBACrB,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["// compass: variance-authority.reach\n/**\n * Dominance over a graph with one root.\n *\n * D dominates N when every path from the root to N passes through D; N's\n * immediate dominator is the nearest such D, and the immediate dominators form\n * a tree. Cooper, Harvey and Kennedy's iterative algorithm (\"A Simple, Fast\n * Dominance Algorithm\", 2001): intersect the predecessors' dominators in\n * reverse postorder until nothing changes. It holds on cycles, and on the graphs\n * an import walk produces it settles in two or three passes.\n */\n\n/**\n * Each node the root reaches, mapped to its immediate dominator. The root and\n * every node it does not reach are absent.\n *\n * `successors` is the graph as the caller sees it: a walk that cuts an edge\n * leaves the edge out here.\n */\nexport function dominatorsOf(\n root: string,\n successors: (node: string) => Iterable<string>,\n): ReadonlyMap<string, string> {\n // Postorder by an explicit stack: an import chain is deeper than a call stack likes.\n const order: string[] = [];\n const number = new Map<string, number>();\n const predecessors = new Map<string, string[]>();\n const seen = new Set([root]);\n const stack: { node: string; next: Iterator<string> }[] = [{ node: root, next: successors(root)[Symbol.iterator]() }];\n while (stack.length > 0) {\n const top = stack[stack.length - 1]!;\n const step = top.next.next();\n if (step.done === true) {\n stack.pop();\n number.set(top.node, order.length);\n order.push(top.node);\n continue;\n }\n const child = step.value;\n const into = predecessors.get(child);\n if (into === undefined) predecessors.set(child, [top.node]);\n else into.push(top.node);\n if (seen.has(child)) continue;\n seen.add(child);\n stack.push({ node: child, next: successors(child)[Symbol.iterator]() });\n }\n\n const idom = new Map<string, string>([[root, root]]);\n const intersect = (left: string, right: string): string => {\n let a = left;\n let b = right;\n while (a !== b) {\n while (number.get(a)! < number.get(b)!) a = idom.get(a)!;\n while (number.get(b)! < number.get(a)!) b = idom.get(b)!;\n }\n return a;\n };\n for (let changed = true; changed;) {\n changed = false;\n for (let at = order.length - 2; at >= 0; at -= 1) {\n const node = order[at]!;\n let next: string | undefined;\n for (const from of predecessors.get(node) ?? []) {\n if (!idom.has(from)) continue;\n next = next === undefined ? from : intersect(from, next);\n }\n if (next !== undefined && idom.get(node) !== next) {\n idom.set(node, next);\n changed = true;\n }\n }\n }\n idom.delete(root);\n return idom;\n}\n"]}