karajan-code 4.57.2 → 4.57.3
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/package.json +1 -1
- package/src/review/diff-batches.js +40 -1
- package/src/review/split-advice.js +13 -4
package/package.json
CHANGED
|
@@ -20,6 +20,42 @@ export function splitByFile(diff) {
|
|
|
20
20
|
return parts.map(({ file, lines }) => ({ file, text: lines.join("\n"), changed: changedLines(lines) }));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
const HUNK_HEAD = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@(.*)$/;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* KJC-BUG-0384: one hunk cut into windows that fit `room` characters with their own
|
|
27
|
+
* `@@ -old,count +new,count @@` header, so the reviewer reads the right line numbers.
|
|
28
|
+
* A single line that cannot fit stays alone in a window larger than the room, which
|
|
29
|
+
* batchDiff leaves out and names; no batch ever exceeds the limit.
|
|
30
|
+
*/
|
|
31
|
+
function splitHunk(lines, budget) {
|
|
32
|
+
const head = HUNK_HEAD.exec(lines[0]);
|
|
33
|
+
if (!head) return [lines];
|
|
34
|
+
// The rewritten header: the original's trailing context plus four line numbers of up to 10 digits.
|
|
35
|
+
const room = budget - (head[3].length + 50);
|
|
36
|
+
let oldAt = Number(head[1]);
|
|
37
|
+
let newAt = Number(head[2]);
|
|
38
|
+
const windows = [];
|
|
39
|
+
let cur = [];
|
|
40
|
+
let size = 0;
|
|
41
|
+
const flush = () => {
|
|
42
|
+
const oldCount = cur.filter((l) => l[0] === " " || l[0] === "-").length;
|
|
43
|
+
const newCount = cur.filter((l) => l[0] === " " || l[0] === "+").length;
|
|
44
|
+
windows.push([`@@ -${oldAt},${oldCount} +${newAt},${newCount} @@${head[3]}`, ...cur]);
|
|
45
|
+
oldAt += oldCount;
|
|
46
|
+
newAt += newCount;
|
|
47
|
+
cur = [];
|
|
48
|
+
size = 0;
|
|
49
|
+
};
|
|
50
|
+
for (const line of lines.slice(1)) {
|
|
51
|
+
if (cur.length > 0 && size + line.length + 1 > room) flush();
|
|
52
|
+
cur.push(line);
|
|
53
|
+
size += line.length + 1;
|
|
54
|
+
}
|
|
55
|
+
if (cur.length > 0) flush();
|
|
56
|
+
return windows;
|
|
57
|
+
}
|
|
58
|
+
|
|
23
59
|
const changedLines = (lines) => lines.filter((l) => /^[+-]/.test(l) && !/^(\+\+\+|---) /.test(l)).length;
|
|
24
60
|
|
|
25
61
|
/**
|
|
@@ -69,7 +105,10 @@ function piecesOf(part, limit) {
|
|
|
69
105
|
hunks.at(-1).push(line);
|
|
70
106
|
}
|
|
71
107
|
const pieces = [];
|
|
72
|
-
|
|
108
|
+
// KJC-BUG-0384: a hunk larger than a batch (a new file is one hunk) is cut by lines.
|
|
109
|
+
const room = limit - header.length - 1;
|
|
110
|
+
const fitted = hunks.flatMap((h) => (h.join("\n").length > room ? splitHunk(h, room) : [h]));
|
|
111
|
+
for (const hunk of fitted.map((h) => h.join("\n"))) {
|
|
73
112
|
const last = pieces.at(-1);
|
|
74
113
|
if (last && last.text.length + 1 + hunk.length <= limit) last.text = `${last.text}\n${hunk}`;
|
|
75
114
|
else pieces.push({ file: part.file, text: `${header}\n${hunk}` });
|
|
@@ -31,7 +31,8 @@ function commonPrefix(lists) {
|
|
|
31
31
|
|
|
32
32
|
/** The structural path of each file: the segments that tell its responsibility. */
|
|
33
33
|
function keysOf(files) {
|
|
34
|
-
|
|
34
|
+
// KJC-BUG-0383: main and test source sets alike; a Maven PR can live wholly under src/test/java.
|
|
35
|
+
const javaMain = /(^|\/)src\/(?:main|test)\/(java|kotlin)\/(.+)$/;
|
|
35
36
|
const javaDirs = files.map((f) => javaMain.exec(f.file)).filter(Boolean).map((m) => dirsOf(m[3]));
|
|
36
37
|
// The root package (com/acme/app), never a layer: a PR that only touches pages/ keeps "pages".
|
|
37
38
|
const common = commonPrefix(javaDirs);
|
|
@@ -39,12 +40,12 @@ function keysOf(files) {
|
|
|
39
40
|
const prefix = layerAt === -1 ? common : common.slice(0, layerAt);
|
|
40
41
|
return files.map(({ file }) => {
|
|
41
42
|
if (/(^|\/)src\/(main|test)\/resources\//.test(file)) return ["resources"];
|
|
42
|
-
if (isTest(file)) return ["tests"];
|
|
43
43
|
const java = javaMain.exec(file);
|
|
44
44
|
if (java) {
|
|
45
45
|
const rest = dirsOf(java[3]).slice(prefix.length);
|
|
46
46
|
return rest.length > 0 ? rest : ["main"];
|
|
47
47
|
}
|
|
48
|
+
if (isTest(file)) return ["tests"];
|
|
48
49
|
const src = /(^|\/)src\/(.+)$/.exec(file);
|
|
49
50
|
if (src && dirsOf(src[2]).length > 0) return dirsOf(src[2]);
|
|
50
51
|
const dirs = dirsOf(file);
|
|
@@ -61,14 +62,22 @@ function group(entries, depth) {
|
|
|
61
62
|
return [...groups].map(([name, items]) => ({ name, items, lines: items.reduce((n, i) => n + i.changed, 0), depth }));
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
// KJC-BUG-0383: a chunk holding more than this share of the files is split one level down.
|
|
66
|
+
const MAX_FILE_SHARE = 0.6;
|
|
67
|
+
|
|
68
|
+
/** Groups by the first segment, then splits any group over `limit` or the file share by its next level. */
|
|
65
69
|
function chunksOf(entries, limit) {
|
|
66
70
|
const out = [];
|
|
67
71
|
const pending = group(entries, 1);
|
|
68
72
|
while (pending.length > 0) {
|
|
69
73
|
const g = pending.shift();
|
|
70
74
|
const deeper = g.items.some((i) => i.key.length > g.depth);
|
|
71
|
-
|
|
75
|
+
const tooBig = g.lines > limit || g.items.length > entries.length * MAX_FILE_SHARE;
|
|
76
|
+
// Going down only helps when it really splits the group: past a lone subpackage, keep going.
|
|
77
|
+
let depth = g.depth + 1;
|
|
78
|
+
let sub = tooBig && deeper ? group(g.items, depth) : [];
|
|
79
|
+
while (sub.length === 1 && g.items.some((i) => i.key.length > depth)) sub = group(g.items, ++depth);
|
|
80
|
+
if (sub.length > 1) pending.push(...sub);
|
|
72
81
|
else out.push(g);
|
|
73
82
|
}
|
|
74
83
|
return out;
|