Skip to content

Commit ee213d9

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
feat(design-diff): group binary findings by changed source
1 parent b92ff21 commit ee213d9

20 files changed

Lines changed: 1399 additions & 184 deletions

scripts/design-diff/README.md

Lines changed: 98 additions & 36 deletions
Large diffs are not rendered by default.

scripts/design-diff/analyze.ts

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { cssValue, extractCss } from '#design-diff/extract/css'
55
import { extractDocument } from '#design-diff/extract/documents'
66
import { extractTsx } from '#design-diff/extract/tsx'
77
import { GitReader } from '#design-diff/git'
8+
import { groupFindings } from '#design-diff/group'
9+
import { reclaimMemory } from '#design-diff/memory'
810
import { limitations } from '#design-diff/policy'
911
import { Resolver } from '#design-diff/resolve'
1012
import {
@@ -15,13 +17,13 @@ import {
1517
scriptPattern,
1618
} from '#design-diff/source'
1719
import { TailwindNormalizer } from '#design-diff/tailwind'
18-
import type { Config, Definition, Finding, Report } from '#design-diff/types'
20+
import type { Change, Config, Definition, Report } from '#design-diff/types'
1921

2022
export function emptyReport(): Report {
2123
return {
22-
schemaVersion: '1.0.0',
23-
engineVersion: '0.1.0',
24-
policyVersion: '1.0.0',
24+
schemaVersion: '2.0.0',
25+
engineVersion: '0.2.0',
26+
policyVersion: '2.0.0',
2527
commits: null,
2628
status: 'failed',
2729
flagged: null,
@@ -68,6 +70,7 @@ export async function analyze(
6870
const changes = reader.changes(report.commits.mergeBase, report.commits.head)
6971
const before = new SourceTree(reader, report.commits.mergeBase, config)
7072
const after = new SourceTree(reader, report.commits.head, config)
73+
reclaimMemory()
7174
const changed = new Set<string>()
7275
for (const change of changes) {
7376
const file = change.after ?? (change.before as string)
@@ -99,19 +102,31 @@ export async function analyze(
99102
if (change.before) changed.add(change.before)
100103
if (change.after) changed.add(change.after)
101104
}
102-
const findings: Finding[] = []
105+
const findings: Change[] = []
106+
let causes = new Map<string, Set<string>>()
107+
const renames = new Map(
108+
changes
109+
.filter((change) => change.status.startsWith('R'))
110+
.map((change) => [change.after as string, change.before as string])
111+
)
103112
if (changed.size) {
104113
before.buildGraph()
105114
after.buildGraph()
106-
const affected = new Set([...before.affected(changed), ...after.affected(changed)])
115+
causes = before.graph.causes(changed, after.graph)
116+
const affected = new Set(causes.keys())
107117
for (const theme of config.themes) {
108118
if (!affected.has(theme.path)) continue
109119
for (const file of new Set([...before.texts.keys(), ...after.texts.keys()])) {
110120
if (
111121
theme.roots.some((root) => file.startsWith(root)) &&
112122
/\.(?:[jt]sx|css|html?|mdx?)$/.test(file)
113-
)
123+
) {
114124
affected.add(file)
125+
causes.set(
126+
file,
127+
new Set([...(causes.get(file) ?? []), ...(causes.get(theme.path) ?? [theme.path])])
128+
)
129+
}
115130
}
116131
}
117132
const previousResolver = new Resolver(before)
@@ -184,12 +199,9 @@ export async function analyze(
184199
}
185200
return []
186201
}
187-
const renames = new Map(
188-
changes
189-
.filter((change) => change.status.startsWith('R'))
190-
.map((change) => [change.after as string, change.before as string])
191-
)
202+
let extracted = 0
192203
for (const file of [...affected].sort()) {
204+
if (++extracted % 32 === 0) reclaimMemory()
193205
if (!scoped(file, config) && !infrastructure(file, config)) continue
194206
if ([...renames.values()].includes(file) && !after.entries.has(file)) continue
195207
const oldFile = renames.get(file) ?? file
@@ -287,15 +299,7 @@ export async function analyze(
287299
}
288300
}
289301
report.status = 'completed'
290-
report.findings = [...new Map(findings.map((item) => [item.id, item])).values()].sort((a, b) => {
291-
const left = a.after?.location ?? a.before?.location
292-
const right = b.after?.location ?? b.before?.location
293-
return (
294-
(left?.file ?? '').localeCompare(right?.file ?? '', 'en') ||
295-
(left?.line ?? 0) - (right?.line ?? 0) ||
296-
a.id.localeCompare(b.id, 'en')
297-
)
298-
})
299-
report.flagged = report.findings.some((item) => item.decision !== 'exempt')
302+
report.findings = groupFindings(findings, causes, before, after, renames)
303+
report.flagged = report.findings.some((item) => item.decision === 'flag')
300304
return report
301305
}

scripts/design-diff/compare.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createHash } from 'node:crypto'
22
import { pureMovement } from '#design-diff/movement'
33
import { changedCategory } from '#design-diff/policy'
4-
import type { Definition, Finding } from '#design-diff/types'
4+
import type { Change, Definition } from '#design-diff/types'
55

66
function signature(definition: Definition) {
77
return JSON.stringify([definition.value, definition.conditions])
@@ -11,30 +11,27 @@ export function finding(
1111
before: Definition | undefined,
1212
after: Definition | undefined,
1313
reason?: string
14-
): Finding {
14+
): Change {
1515
const definition = after ?? before
1616
if (!definition) throw new Error('Finding needs evidence')
1717
const unresolved = [
1818
...new Set([...(before?.unresolved ?? []), ...(after?.unresolved ?? [])]),
1919
].sort()
2020
const movement = before && after && pureMovement(before, after)
2121
const category = changedCategory(before, after)
22-
const decision = movement
23-
? 'exempt'
24-
: unresolved.length ||
25-
definition.kind === 'review' ||
26-
['movement', 'unresolved'].includes(category)
27-
? 'review'
28-
: 'flag'
29-
const result: Omit<Finding, 'id'> = {
30-
decision,
22+
const uncertain =
23+
unresolved.length ||
24+
definition.kind === 'review' ||
25+
['movement', 'unresolved'].includes(category)
26+
const result: Omit<Change, 'id'> = {
27+
decision: movement ? 'exempt' : 'flag',
3128
category: movement ? 'movement' : category,
3229
reason:
3330
reason ??
3431
(movement
3532
? 'Static geometry establishes movement within unchanged bounds'
36-
: decision === 'review'
37-
? 'Potential visual effect needs review; static evidence is incomplete'
33+
: uncertain
34+
? 'Potential visual effect; static evidence is incomplete'
3835
: 'Visual definition changed'),
3936
before: before
4037
? {
@@ -77,10 +74,10 @@ export function compareDefinitions(
7774
before: Definition[],
7875
after: Definition[],
7976
changed: Set<string>
80-
): Finding[] {
77+
): Change[] {
8178
const previous = new Map(before.map((definition) => [definition.key, definition]))
8279
const next = new Map(after.map((definition) => [definition.key, definition]))
83-
const result: Finding[] = []
80+
const result: Change[] = []
8481
const reviewedDependencies = new Set<string>()
8582
for (const key of [...new Set([...previous.keys(), ...next.keys()])].sort()) {
8683
const a = previous.get(key)

0 commit comments

Comments
 (0)