(base: ApiSnapshot, head: ApiSnapshot)
| 397 | } |
| 398 | |
| 399 | export const diffSnapshots = (base: ApiSnapshot, head: ApiSnapshot): ApiDiff => { |
| 400 | const unmatchedBase = new Map(base.entities.map((entity) => [entity.id, entity])) |
| 401 | const unmatchedHead = new Map(head.entities.map((entity) => [entity.id, entity])) |
| 402 | const matches: Array<Match> = [] |
| 403 | const suggestedMatches: Array<Match> = [] |
| 404 | |
| 405 | const addMatch = ( |
| 406 | baseEntity: ApiEntity | undefined, |
| 407 | headEntity: ApiEntity | undefined, |
| 408 | details: Omit<Match, "base" | "head"> |
| 409 | ): boolean => { |
| 410 | if ( |
| 411 | baseEntity === undefined || headEntity === undefined || |
| 412 | !unmatchedBase.has(baseEntity.id) || !unmatchedHead.has(headEntity.id) |
| 413 | ) { |
| 414 | return false |
| 415 | } |
| 416 | unmatchedBase.delete(baseEntity.id) |
| 417 | unmatchedHead.delete(headEntity.id) |
| 418 | matches.push({ base: baseEntity, head: headEntity, ...details }) |
| 419 | return true |
| 420 | } |
| 421 | |
| 422 | for (const baseEntity of unmatchedBase.values()) { |
| 423 | addMatch(baseEntity, unmatchedHead.get(baseEntity.id), { |
| 424 | confidence: 1, |
| 425 | authoritative: true |
| 426 | }) |
| 427 | } |
| 428 | |
| 429 | const headGroups = groupEntities(unmatchedHead.values()) |
| 430 | for (const baseGroup of groupEntities(unmatchedBase.values())) { |
| 431 | const ranked = headGroups |
| 432 | .filter((group) => |
| 433 | groupName(group) === groupName(baseGroup) || |
| 434 | group.module === baseGroup.module || |
| 435 | baseGroup.entities.some((baseEntity) => |
| 436 | group.entities.some((headEntity) => |
| 437 | baseEntity.bucket === headEntity.bucket && |
| 438 | baseEntity.fingerprint === headEntity.fingerprint |
| 439 | ) |
| 440 | ) |
| 441 | ) |
| 442 | .map((group) => ({ group, score: groupSimilarity(baseGroup, group) })) |
| 443 | .filter(({ score }) => score > 0) |
| 444 | .sort((left, right) => |
| 445 | right.score - left.score || |
| 446 | left.group.module.localeCompare(right.group.module) || |
| 447 | left.group.path.join(".").localeCompare(right.group.path.join(".")) |
| 448 | ) |
| 449 | const best = ranked[0] |
| 450 | const next = ranked[1] |
| 451 | if (best === undefined || best.score < 0.5 || (next !== undefined && best.score - next.score < 0.05)) { |
| 452 | continue |
| 453 | } |
| 454 | for (const baseEntity of baseGroup.entities) { |
| 455 | const headEntity = best.group.entities.find((candidate) => candidate.bucket === baseEntity.bucket) |
| 456 | if (headEntity !== undefined) { |
no test coverage detected