agentmap 0.7.1 → 0.9.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.
- package/CHANGELOG.md +96 -0
- package/README.md +24 -0
- package/dist/cli.js +44 -12
- package/dist/cli.js.map +1 -1
- package/dist/extract/definitions.js +12 -12
- package/dist/extract/definitions.js.map +1 -1
- package/dist/extract/definitions.test.js +30 -259
- package/dist/extract/definitions.test.js.map +1 -1
- package/dist/extract/git-status.d.ts +11 -4
- package/dist/extract/git-status.d.ts.map +1 -1
- package/dist/extract/git-status.js +21 -16
- package/dist/extract/git-status.js.map +1 -1
- package/dist/extract/markdown.js +1 -1
- package/dist/extract/markdown.test.js +3 -3
- package/dist/extract/markdown.test.js.map +1 -1
- package/dist/extract/marker.js +1 -1
- package/dist/extract/marker.test.js +4 -4
- package/dist/extract/marker.test.js.map +1 -1
- package/dist/extract/submodules.d.ts +12 -0
- package/dist/extract/submodules.d.ts.map +1 -0
- package/dist/extract/submodules.js +234 -0
- package/dist/extract/submodules.js.map +1 -0
- package/dist/extract/submodules.test.d.ts +2 -0
- package/dist/extract/submodules.test.d.ts.map +1 -0
- package/dist/extract/submodules.test.js +84 -0
- package/dist/extract/submodules.test.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -9
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +10 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +41 -0
- package/dist/logger.js.map +1 -0
- package/dist/map/builder.d.ts +3 -3
- package/dist/map/builder.d.ts.map +1 -1
- package/dist/map/builder.js +59 -9
- package/dist/map/builder.js.map +1 -1
- package/dist/map/builder.test.d.ts +2 -0
- package/dist/map/builder.test.d.ts.map +1 -0
- package/dist/map/builder.test.js +66 -0
- package/dist/map/builder.test.js.map +1 -0
- package/dist/map/truncate.d.ts +7 -3
- package/dist/map/truncate.d.ts.map +1 -1
- package/dist/map/truncate.js +90 -9
- package/dist/map/truncate.js.map +1 -1
- package/dist/map/yaml.d.ts.map +1 -1
- package/dist/map/yaml.js +13 -3
- package/dist/map/yaml.js.map +1 -1
- package/dist/scanner.d.ts +9 -2
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +172 -49
- package/dist/scanner.js.map +1 -1
- package/dist/scanner.test.d.ts +2 -0
- package/dist/scanner.test.d.ts.map +1 -0
- package/dist/scanner.test.js +84 -0
- package/dist/scanner.test.js.map +1 -0
- package/dist/test-helpers/git-test-helpers.d.ts +13 -0
- package/dist/test-helpers/git-test-helpers.d.ts.map +1 -0
- package/dist/test-helpers/git-test-helpers.js +48 -0
- package/dist/test-helpers/git-test-helpers.js.map +1 -0
- package/dist/types.d.ts +42 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +15 -3
- package/src/cli.ts +164 -0
- package/src/extract/definitions.test.ts +2040 -0
- package/src/extract/definitions.ts +379 -0
- package/src/extract/git-status.test.ts +507 -0
- package/src/extract/git-status.ts +359 -0
- package/src/extract/markdown.test.ts +159 -0
- package/src/extract/markdown.ts +202 -0
- package/src/extract/marker.test.ts +566 -0
- package/src/extract/marker.ts +398 -0
- package/src/extract/submodules.test.ts +95 -0
- package/src/extract/submodules.ts +269 -0
- package/src/extract/utils.ts +27 -0
- package/src/index.ts +106 -0
- package/src/languages/cpp.ts +129 -0
- package/src/languages/go.ts +72 -0
- package/src/languages/index.ts +231 -0
- package/src/languages/javascript.ts +33 -0
- package/src/languages/python.ts +41 -0
- package/src/languages/rust.ts +72 -0
- package/src/languages/typescript.ts +74 -0
- package/src/languages/zig.ts +106 -0
- package/src/logger.ts +55 -0
- package/src/map/builder.test.ts +72 -0
- package/src/map/builder.ts +175 -0
- package/src/map/truncate.ts +188 -0
- package/src/map/yaml.ts +66 -0
- package/src/parser/index.ts +53 -0
- package/src/parser/languages.ts +64 -0
- package/src/scanner.test.ts +95 -0
- package/src/scanner.ts +364 -0
- package/src/test-helpers/git-test-helpers.ts +62 -0
- package/src/types.ts +191 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
parseDiff,
|
|
4
|
+
parseNumstat,
|
|
5
|
+
parseHunkHeader,
|
|
6
|
+
calculateDefinitionDiff,
|
|
7
|
+
calculateFileDiff
|
|
8
|
+
} from './git-status.js'
|
|
9
|
+
import type { Definition, DiffHunk } from '../types.js'
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// parseNumstat - Machine-readable file stats (most reliable)
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
describe('parseNumstat', () => {
|
|
16
|
+
test('parses simple numstat output', () => {
|
|
17
|
+
const output = `10\t5\tsrc/foo.ts
|
|
18
|
+
3\t1\tsrc/bar.ts`
|
|
19
|
+
const result = parseNumstat(output)
|
|
20
|
+
expect(result.size).toBe(2)
|
|
21
|
+
expect(result.get('src/foo.ts')).toMatchInlineSnapshot(`
|
|
22
|
+
{
|
|
23
|
+
"added": 10,
|
|
24
|
+
"deleted": 5,
|
|
25
|
+
}
|
|
26
|
+
`)
|
|
27
|
+
expect(result.get('src/bar.ts')).toMatchInlineSnapshot(`
|
|
28
|
+
{
|
|
29
|
+
"added": 3,
|
|
30
|
+
"deleted": 1,
|
|
31
|
+
}
|
|
32
|
+
`)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('handles additions only', () => {
|
|
36
|
+
const output = `15\t0\tsrc/new-file.ts`
|
|
37
|
+
const result = parseNumstat(output)
|
|
38
|
+
expect(result.get('src/new-file.ts')).toMatchInlineSnapshot(`
|
|
39
|
+
{
|
|
40
|
+
"added": 15,
|
|
41
|
+
"deleted": 0,
|
|
42
|
+
}
|
|
43
|
+
`)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('handles deletions only', () => {
|
|
47
|
+
const output = `0\t20\tsrc/deleted-content.ts`
|
|
48
|
+
const result = parseNumstat(output)
|
|
49
|
+
expect(result.get('src/deleted-content.ts')).toMatchInlineSnapshot(`
|
|
50
|
+
{
|
|
51
|
+
"added": 0,
|
|
52
|
+
"deleted": 20,
|
|
53
|
+
}
|
|
54
|
+
`)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('skips binary files (shown as - -)', () => {
|
|
58
|
+
const output = `-\t-\timage.png
|
|
59
|
+
10\t5\tsrc/code.ts`
|
|
60
|
+
const result = parseNumstat(output)
|
|
61
|
+
expect(result.size).toBe(1)
|
|
62
|
+
expect(result.has('image.png')).toBe(false)
|
|
63
|
+
expect(result.has('src/code.ts')).toBe(true)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('handles empty output', () => {
|
|
67
|
+
const result = parseNumstat('')
|
|
68
|
+
expect(result.size).toBe(0)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('handles whitespace-only output', () => {
|
|
72
|
+
const result = parseNumstat(' \n\n ')
|
|
73
|
+
expect(result.size).toBe(0)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('skips malformed lines', () => {
|
|
77
|
+
const output = `not valid
|
|
78
|
+
10\t5\tsrc/valid.ts
|
|
79
|
+
also invalid line`
|
|
80
|
+
const result = parseNumstat(output)
|
|
81
|
+
expect(result.size).toBe(1)
|
|
82
|
+
expect(result.has('src/valid.ts')).toBe(true)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('handles paths with spaces', () => {
|
|
86
|
+
const output = `5\t3\tpath/with spaces/file.ts`
|
|
87
|
+
const result = parseNumstat(output)
|
|
88
|
+
expect(result.has('path/with spaces/file.ts')).toBe(true)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('normalizes Windows backslashes to forward slashes', () => {
|
|
92
|
+
const output = `5\t3\tpath\\to\\file.ts`
|
|
93
|
+
const result = parseNumstat(output)
|
|
94
|
+
expect(result.has('path/to/file.ts')).toBe(true)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('handles quoted paths (special characters)', () => {
|
|
98
|
+
const output = `5\t3\t"path/with\\"quotes\\"/file.ts"`
|
|
99
|
+
const result = parseNumstat(output)
|
|
100
|
+
// The path should be unquoted and escapes resolved
|
|
101
|
+
expect(result.size).toBe(1)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('skips files with zero changes', () => {
|
|
105
|
+
const output = `0\t0\tsrc/unchanged.ts
|
|
106
|
+
5\t3\tsrc/changed.ts`
|
|
107
|
+
const result = parseNumstat(output)
|
|
108
|
+
expect(result.size).toBe(1)
|
|
109
|
+
expect(result.has('src/unchanged.ts')).toBe(false)
|
|
110
|
+
expect(result.has('src/changed.ts')).toBe(true)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// parseHunkHeader - Extract line numbers from @@ headers
|
|
116
|
+
// ============================================================================
|
|
117
|
+
|
|
118
|
+
describe('parseHunkHeader', () => {
|
|
119
|
+
test('parses standard hunk header', () => {
|
|
120
|
+
const result = parseHunkHeader('@@ -10,5 +12,7 @@ function name() {')
|
|
121
|
+
expect(result).toMatchInlineSnapshot(`
|
|
122
|
+
{
|
|
123
|
+
"newCount": 7,
|
|
124
|
+
"newStart": 12,
|
|
125
|
+
"oldCount": 5,
|
|
126
|
+
"oldStart": 10,
|
|
127
|
+
}
|
|
128
|
+
`)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('parses hunk with single old line (no comma)', () => {
|
|
132
|
+
const result = parseHunkHeader('@@ -10 +12,7 @@')
|
|
133
|
+
expect(result).toMatchInlineSnapshot(`
|
|
134
|
+
{
|
|
135
|
+
"newCount": 7,
|
|
136
|
+
"newStart": 12,
|
|
137
|
+
"oldCount": 1,
|
|
138
|
+
"oldStart": 10,
|
|
139
|
+
}
|
|
140
|
+
`)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('parses hunk with single new line (no comma)', () => {
|
|
144
|
+
const result = parseHunkHeader('@@ -10,5 +12 @@')
|
|
145
|
+
expect(result).toMatchInlineSnapshot(`
|
|
146
|
+
{
|
|
147
|
+
"newCount": 1,
|
|
148
|
+
"newStart": 12,
|
|
149
|
+
"oldCount": 5,
|
|
150
|
+
"oldStart": 10,
|
|
151
|
+
}
|
|
152
|
+
`)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('parses hunk with both single lines', () => {
|
|
156
|
+
const result = parseHunkHeader('@@ -1 +1 @@')
|
|
157
|
+
expect(result).toMatchInlineSnapshot(`
|
|
158
|
+
{
|
|
159
|
+
"newCount": 1,
|
|
160
|
+
"newStart": 1,
|
|
161
|
+
"oldCount": 1,
|
|
162
|
+
"oldStart": 1,
|
|
163
|
+
}
|
|
164
|
+
`)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('returns null for invalid header', () => {
|
|
168
|
+
expect(parseHunkHeader('not a hunk')).toBeNull()
|
|
169
|
+
expect(parseHunkHeader('@@@ invalid @@@')).toBeNull()
|
|
170
|
+
expect(parseHunkHeader('')).toBeNull()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('handles zero counts', () => {
|
|
174
|
+
const result = parseHunkHeader('@@ -10,0 +10,5 @@')
|
|
175
|
+
expect(result).toMatchInlineSnapshot(`
|
|
176
|
+
{
|
|
177
|
+
"newCount": 5,
|
|
178
|
+
"newStart": 10,
|
|
179
|
+
"oldCount": 0,
|
|
180
|
+
"oldStart": 10,
|
|
181
|
+
}
|
|
182
|
+
`)
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// parseDiff - Extract hunks from full diff output
|
|
188
|
+
// ============================================================================
|
|
189
|
+
|
|
190
|
+
describe('parseDiff', () => {
|
|
191
|
+
test('parses single file with one hunk', () => {
|
|
192
|
+
const diffOutput = `diff --git a/src/foo.ts b/src/foo.ts
|
|
193
|
+
index abc123..def456 100644
|
|
194
|
+
--- a/src/foo.ts
|
|
195
|
+
+++ b/src/foo.ts
|
|
196
|
+
@@ -10,3 +10,5 @@ function existing() {
|
|
197
|
+
+ const x = 1
|
|
198
|
+
+ const y = 2
|
|
199
|
+
`
|
|
200
|
+
const result = parseDiff(diffOutput)
|
|
201
|
+
expect(result.size).toBe(1)
|
|
202
|
+
expect(result.get('src/foo.ts')).toMatchInlineSnapshot(`
|
|
203
|
+
{
|
|
204
|
+
"hunks": [
|
|
205
|
+
{
|
|
206
|
+
"newCount": 5,
|
|
207
|
+
"newStart": 10,
|
|
208
|
+
"oldCount": 3,
|
|
209
|
+
"oldStart": 10,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
"path": "src/foo.ts",
|
|
213
|
+
}
|
|
214
|
+
`)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('parses single file with multiple hunks', () => {
|
|
218
|
+
const diffOutput = `diff --git a/src/bar.ts b/src/bar.ts
|
|
219
|
+
index abc123..def456 100644
|
|
220
|
+
--- a/src/bar.ts
|
|
221
|
+
+++ b/src/bar.ts
|
|
222
|
+
@@ -5,2 +5,4 @@ header
|
|
223
|
+
+line1
|
|
224
|
+
+line2
|
|
225
|
+
@@ -20,1 +22,3 @@ other
|
|
226
|
+
+more
|
|
227
|
+
+lines
|
|
228
|
+
`
|
|
229
|
+
const result = parseDiff(diffOutput)
|
|
230
|
+
expect(result.size).toBe(1)
|
|
231
|
+
const file = result.get('src/bar.ts')!
|
|
232
|
+
expect(file.hunks).toHaveLength(2)
|
|
233
|
+
expect(file.hunks[0]).toMatchInlineSnapshot(`
|
|
234
|
+
{
|
|
235
|
+
"newCount": 4,
|
|
236
|
+
"newStart": 5,
|
|
237
|
+
"oldCount": 2,
|
|
238
|
+
"oldStart": 5,
|
|
239
|
+
}
|
|
240
|
+
`)
|
|
241
|
+
expect(file.hunks[1]).toMatchInlineSnapshot(`
|
|
242
|
+
{
|
|
243
|
+
"newCount": 3,
|
|
244
|
+
"newStart": 22,
|
|
245
|
+
"oldCount": 1,
|
|
246
|
+
"oldStart": 20,
|
|
247
|
+
}
|
|
248
|
+
`)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
test('parses multiple files', () => {
|
|
252
|
+
const diffOutput = `diff --git a/src/a.ts b/src/a.ts
|
|
253
|
+
index abc..def 100644
|
|
254
|
+
--- a/src/a.ts
|
|
255
|
+
+++ b/src/a.ts
|
|
256
|
+
@@ -1,1 +1,2 @@
|
|
257
|
+
+added line
|
|
258
|
+
diff --git a/src/b.ts b/src/b.ts
|
|
259
|
+
index 123..456 100644
|
|
260
|
+
--- a/src/b.ts
|
|
261
|
+
+++ b/src/b.ts
|
|
262
|
+
@@ -10,2 +10,1 @@
|
|
263
|
+
-removed line
|
|
264
|
+
`
|
|
265
|
+
const result = parseDiff(diffOutput)
|
|
266
|
+
expect(result.size).toBe(2)
|
|
267
|
+
expect(result.has('src/a.ts')).toBe(true)
|
|
268
|
+
expect(result.has('src/b.ts')).toBe(true)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test('parses hunk with single line (no count)', () => {
|
|
272
|
+
const diffOutput = `diff --git a/src/x.ts b/src/x.ts
|
|
273
|
+
--- a/src/x.ts
|
|
274
|
+
+++ b/src/x.ts
|
|
275
|
+
@@ -5 +5,2 @@
|
|
276
|
+
+new line
|
|
277
|
+
`
|
|
278
|
+
const result = parseDiff(diffOutput)
|
|
279
|
+
const file = result.get('src/x.ts')!
|
|
280
|
+
expect(file.hunks[0]).toMatchInlineSnapshot(`
|
|
281
|
+
{
|
|
282
|
+
"newCount": 2,
|
|
283
|
+
"newStart": 5,
|
|
284
|
+
"oldCount": 1,
|
|
285
|
+
"oldStart": 5,
|
|
286
|
+
}
|
|
287
|
+
`)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
test('handles empty diff', () => {
|
|
291
|
+
const result = parseDiff('')
|
|
292
|
+
expect(result.size).toBe(0)
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
test('handles whitespace-only diff', () => {
|
|
296
|
+
const result = parseDiff(' \n\n ')
|
|
297
|
+
expect(result.size).toBe(0)
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
test('skips binary files', () => {
|
|
301
|
+
const diffOutput = `diff --git a/image.png b/image.png
|
|
302
|
+
Binary files a/image.png and b/image.png differ
|
|
303
|
+
diff --git a/src/code.ts b/src/code.ts
|
|
304
|
+
--- a/src/code.ts
|
|
305
|
+
+++ b/src/code.ts
|
|
306
|
+
@@ -1,1 +1,2 @@
|
|
307
|
+
+new line
|
|
308
|
+
`
|
|
309
|
+
const result = parseDiff(diffOutput)
|
|
310
|
+
expect(result.size).toBe(1)
|
|
311
|
+
expect(result.has('image.png')).toBe(false)
|
|
312
|
+
expect(result.has('src/code.ts')).toBe(true)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
test('normalizes Windows paths', () => {
|
|
316
|
+
const diffOutput = `diff --git a/src\\path\\file.ts b/src\\path\\file.ts
|
|
317
|
+
--- a/src\\path\\file.ts
|
|
318
|
+
+++ b/src\\path\\file.ts
|
|
319
|
+
@@ -1,1 +1,2 @@
|
|
320
|
+
+new line
|
|
321
|
+
`
|
|
322
|
+
const result = parseDiff(diffOutput)
|
|
323
|
+
expect(result.has('src/path/file.ts')).toBe(true)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
test('skips files with no hunks', () => {
|
|
327
|
+
const diffOutput = `diff --git a/src/empty.ts b/src/empty.ts
|
|
328
|
+
index abc..def 100644
|
|
329
|
+
diff --git a/src/real.ts b/src/real.ts
|
|
330
|
+
--- a/src/real.ts
|
|
331
|
+
+++ b/src/real.ts
|
|
332
|
+
@@ -1,1 +1,2 @@
|
|
333
|
+
+content
|
|
334
|
+
`
|
|
335
|
+
const result = parseDiff(diffOutput)
|
|
336
|
+
expect(result.size).toBe(1)
|
|
337
|
+
expect(result.has('src/empty.ts')).toBe(false)
|
|
338
|
+
expect(result.has('src/real.ts')).toBe(true)
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// calculateDefinitionDiff - Determine if definition is added/updated
|
|
344
|
+
// ============================================================================
|
|
345
|
+
|
|
346
|
+
describe('calculateDefinitionDiff', () => {
|
|
347
|
+
function makeDef(line: number, endLine: number): Definition {
|
|
348
|
+
return {
|
|
349
|
+
name: 'test',
|
|
350
|
+
line,
|
|
351
|
+
endLine,
|
|
352
|
+
type: 'function',
|
|
353
|
+
exported: false,
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
test('returns null for definition with no changes', () => {
|
|
358
|
+
const def = makeDef(10, 20)
|
|
359
|
+
const hunks: DiffHunk[] = [
|
|
360
|
+
{ oldStart: 1, oldCount: 2, newStart: 1, newCount: 3 }, // changes lines 1-3
|
|
361
|
+
]
|
|
362
|
+
const result = calculateDefinitionDiff(def, hunks)
|
|
363
|
+
expect(result).toBeNull()
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
test('detects fully added definition', () => {
|
|
367
|
+
const def = makeDef(10, 15) // 6 lines
|
|
368
|
+
const hunks: DiffHunk[] = [
|
|
369
|
+
{ oldStart: 5, oldCount: 0, newStart: 10, newCount: 6 }, // adds lines 10-15
|
|
370
|
+
]
|
|
371
|
+
const result = calculateDefinitionDiff(def, hunks)
|
|
372
|
+
expect(result).toMatchInlineSnapshot(`
|
|
373
|
+
{
|
|
374
|
+
"added": 6,
|
|
375
|
+
"deleted": 0,
|
|
376
|
+
"status": "added",
|
|
377
|
+
}
|
|
378
|
+
`)
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
test('detects updated definition (partial overlap)', () => {
|
|
382
|
+
const def = makeDef(10, 20) // 11 lines
|
|
383
|
+
const hunks: DiffHunk[] = [
|
|
384
|
+
{ oldStart: 12, oldCount: 2, newStart: 12, newCount: 4 }, // changes lines 12-15
|
|
385
|
+
]
|
|
386
|
+
const result = calculateDefinitionDiff(def, hunks)
|
|
387
|
+
expect(result).toMatchInlineSnapshot(`
|
|
388
|
+
{
|
|
389
|
+
"added": 4,
|
|
390
|
+
"deleted": 2,
|
|
391
|
+
"status": "updated",
|
|
392
|
+
}
|
|
393
|
+
`)
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
test('handles multiple hunks in definition range', () => {
|
|
397
|
+
const def = makeDef(10, 30) // 21 lines
|
|
398
|
+
const hunks: DiffHunk[] = [
|
|
399
|
+
{ oldStart: 12, oldCount: 1, newStart: 12, newCount: 2 }, // +1 line
|
|
400
|
+
{ oldStart: 20, oldCount: 3, newStart: 21, newCount: 5 }, // +2 lines
|
|
401
|
+
]
|
|
402
|
+
const result = calculateDefinitionDiff(def, hunks)
|
|
403
|
+
expect(result?.status).toBe('updated')
|
|
404
|
+
expect(result?.added).toBe(7) // 2 + 5
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
test('handles definition at exact hunk boundary', () => {
|
|
408
|
+
const def = makeDef(10, 12) // 3 lines
|
|
409
|
+
const hunks: DiffHunk[] = [
|
|
410
|
+
{ oldStart: 8, oldCount: 0, newStart: 10, newCount: 3 }, // adds exactly lines 10-12
|
|
411
|
+
]
|
|
412
|
+
const result = calculateDefinitionDiff(def, hunks)
|
|
413
|
+
expect(result?.status).toBe('added')
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
test('handles hunk that extends beyond definition', () => {
|
|
417
|
+
const def = makeDef(15, 20) // 6 lines
|
|
418
|
+
const hunks: DiffHunk[] = [
|
|
419
|
+
{ oldStart: 10, oldCount: 5, newStart: 10, newCount: 20 }, // changes lines 10-29
|
|
420
|
+
]
|
|
421
|
+
const result = calculateDefinitionDiff(def, hunks)
|
|
422
|
+
// Definition lines 15-20 overlap with hunk's new lines 10-29
|
|
423
|
+
expect(result?.status).toBe('updated')
|
|
424
|
+
expect(result?.added).toBe(6) // all 6 lines of def are in the added range
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
test('handles empty hunks array', () => {
|
|
428
|
+
const def = makeDef(10, 20)
|
|
429
|
+
const result = calculateDefinitionDiff(def, [])
|
|
430
|
+
expect(result).toBeNull()
|
|
431
|
+
})
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
// ============================================================================
|
|
435
|
+
// calculateFileDiff - Sum hunks for file-level stats (legacy)
|
|
436
|
+
// ============================================================================
|
|
437
|
+
|
|
438
|
+
describe('calculateFileDiff', () => {
|
|
439
|
+
test('returns null for empty hunks', () => {
|
|
440
|
+
const result = calculateFileDiff([])
|
|
441
|
+
expect(result).toBeNull()
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
test('sums single hunk correctly', () => {
|
|
445
|
+
const hunks: DiffHunk[] = [
|
|
446
|
+
{ oldStart: 10, oldCount: 3, newStart: 10, newCount: 5 },
|
|
447
|
+
]
|
|
448
|
+
const result = calculateFileDiff(hunks)
|
|
449
|
+
expect(result).toMatchInlineSnapshot(`
|
|
450
|
+
{
|
|
451
|
+
"added": 5,
|
|
452
|
+
"deleted": 3,
|
|
453
|
+
}
|
|
454
|
+
`)
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
test('sums multiple hunks correctly', () => {
|
|
458
|
+
const hunks: DiffHunk[] = [
|
|
459
|
+
{ oldStart: 5, oldCount: 2, newStart: 5, newCount: 4 }, // +4-2
|
|
460
|
+
{ oldStart: 20, oldCount: 5, newStart: 22, newCount: 3 }, // +3-5
|
|
461
|
+
{ oldStart: 40, oldCount: 0, newStart: 40, newCount: 10 }, // +10-0
|
|
462
|
+
]
|
|
463
|
+
const result = calculateFileDiff(hunks)
|
|
464
|
+
expect(result).toMatchInlineSnapshot(`
|
|
465
|
+
{
|
|
466
|
+
"added": 17,
|
|
467
|
+
"deleted": 7,
|
|
468
|
+
}
|
|
469
|
+
`)
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
test('handles additions only', () => {
|
|
473
|
+
const hunks: DiffHunk[] = [
|
|
474
|
+
{ oldStart: 10, oldCount: 0, newStart: 10, newCount: 5 },
|
|
475
|
+
{ oldStart: 20, oldCount: 0, newStart: 25, newCount: 3 },
|
|
476
|
+
]
|
|
477
|
+
const result = calculateFileDiff(hunks)
|
|
478
|
+
expect(result).toMatchInlineSnapshot(`
|
|
479
|
+
{
|
|
480
|
+
"added": 8,
|
|
481
|
+
"deleted": 0,
|
|
482
|
+
}
|
|
483
|
+
`)
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
test('handles deletions only', () => {
|
|
487
|
+
const hunks: DiffHunk[] = [
|
|
488
|
+
{ oldStart: 10, oldCount: 5, newStart: 10, newCount: 0 },
|
|
489
|
+
{ oldStart: 20, oldCount: 3, newStart: 15, newCount: 0 },
|
|
490
|
+
]
|
|
491
|
+
const result = calculateFileDiff(hunks)
|
|
492
|
+
expect(result).toMatchInlineSnapshot(`
|
|
493
|
+
{
|
|
494
|
+
"added": 0,
|
|
495
|
+
"deleted": 8,
|
|
496
|
+
}
|
|
497
|
+
`)
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
test('returns null when both added and deleted are zero', () => {
|
|
501
|
+
const hunks: DiffHunk[] = [
|
|
502
|
+
{ oldStart: 10, oldCount: 0, newStart: 10, newCount: 0 },
|
|
503
|
+
]
|
|
504
|
+
const result = calculateFileDiff(hunks)
|
|
505
|
+
expect(result).toBeNull()
|
|
506
|
+
})
|
|
507
|
+
})
|