(
filteredFiles: { path: string; hunks: { oldStart: number }[] }[],
chapters: Chapter[],
)
| 120 | } |
| 121 | |
| 122 | function validateHunkCoverage( |
| 123 | filteredFiles: { path: string; hunks: { oldStart: number }[] }[], |
| 124 | chapters: Chapter[], |
| 125 | ): void { |
| 126 | const expected = new Map<string, Set<number>>(); |
| 127 | for (const file of filteredFiles) { |
| 128 | const starts = new Set<number>(); |
| 129 | for (const hunk of file.hunks) { |
| 130 | starts.add(hunk.oldStart); |
| 131 | } |
| 132 | if (starts.size > 0) { |
| 133 | expected.set(file.path, starts); |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | const actual = new Map<string, Map<number, number>>(); |
| 138 | const duplicates: string[] = []; |
| 139 | for (const chapter of chapters) { |
| 140 | for (const ref of chapter.hunkRefs) { |
| 141 | let starts = actual.get(ref.filePath); |
| 142 | if (!starts) { |
| 143 | starts = new Map(); |
| 144 | actual.set(ref.filePath, starts); |
| 145 | } |
| 146 | const count = starts.get(ref.oldStart) ?? 0; |
| 147 | if (count > 0) { |
| 148 | duplicates.push(` filePath: "${ref.filePath}", oldStart: ${ref.oldStart}`); |
| 149 | } |
| 150 | starts.set(ref.oldStart, count + 1); |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | const missing: string[] = []; |
| 155 | for (const [filePath, starts] of expected) { |
| 156 | const actualStarts = actual.get(filePath); |
| 157 | for (const oldStart of starts) { |
| 158 | if (!actualStarts?.has(oldStart)) { |
| 159 | missing.push(` filePath: "${filePath}", oldStart: ${oldStart}`); |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | const extra: string[] = []; |
| 165 | for (const [filePath, starts] of actual) { |
| 166 | const expectedStarts = expected.get(filePath); |
| 167 | for (const oldStart of starts.keys()) { |
| 168 | if (!expectedStarts?.has(oldStart)) { |
| 169 | extra.push(` filePath: "${filePath}", oldStart: ${oldStart}`); |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | if (missing.length === 0 && extra.length === 0 && duplicates.length === 0) return; |
| 175 | |
| 176 | const lines = ["Hunk coverage validation failed."]; |
| 177 | if (missing.length > 0) { |
| 178 | lines.push(`Missing hunks (${missing.length}) — not assigned to any chapter:`); |
| 179 | lines.push(...missing); |
no outgoing calls
no test coverage detected