@gitsense/gsc-utils 0.1.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/LICENSE +21 -0
- package/dist/gitsense-chat-utils.cjs.js +10977 -0
- package/dist/gitsense-chat-utils.esm.js +10975 -0
- package/dist/gsc-utils.cjs.js +11043 -0
- package/dist/gsc-utils.esm.js +11041 -0
- package/package.json +37 -0
- package/src/AnalysisBlockUtils.js +151 -0
- package/src/ChatUtils.js +126 -0
- package/src/CodeBlockUtils/blockExtractor.js +277 -0
- package/src/CodeBlockUtils/blockProcessor.js +559 -0
- package/src/CodeBlockUtils/blockProcessor.js.rej +8 -0
- package/src/CodeBlockUtils/constants.js +62 -0
- package/src/CodeBlockUtils/continuationUtils.js +191 -0
- package/src/CodeBlockUtils/headerParser.js +175 -0
- package/src/CodeBlockUtils/headerUtils.js +236 -0
- package/src/CodeBlockUtils/index.js +83 -0
- package/src/CodeBlockUtils/lineNumberFormatter.js +117 -0
- package/src/CodeBlockUtils/markerRemover.js +89 -0
- package/src/CodeBlockUtils/patchIntegration.js +38 -0
- package/src/CodeBlockUtils/relationshipUtils.js +159 -0
- package/src/CodeBlockUtils/updateCodeBlock.js +372 -0
- package/src/CodeBlockUtils/uuidUtils.js +48 -0
- package/src/ContextUtils.js +180 -0
- package/src/GSToolBlockUtils.js +108 -0
- package/src/GitSenseChatUtils.js +386 -0
- package/src/JsonUtils.js +101 -0
- package/src/LLMUtils.js +31 -0
- package/src/MessageUtils.js +460 -0
- package/src/PatchUtils/constants.js +72 -0
- package/src/PatchUtils/diagnosticReporter.js +213 -0
- package/src/PatchUtils/enhancedPatchProcessor.js +390 -0
- package/src/PatchUtils/fuzzyMatcher.js +252 -0
- package/src/PatchUtils/hunkCorrector.js +204 -0
- package/src/PatchUtils/hunkValidator.js +305 -0
- package/src/PatchUtils/index.js +135 -0
- package/src/PatchUtils/patchExtractor.js +175 -0
- package/src/PatchUtils/patchHeaderFormatter.js +143 -0
- package/src/PatchUtils/patchParser.js +289 -0
- package/src/PatchUtils/patchProcessor.js +389 -0
- package/src/PatchUtils/patchVerifier/constants.js +23 -0
- package/src/PatchUtils/patchVerifier/detectAndFixOverlappingHunks.js +281 -0
- package/src/PatchUtils/patchVerifier/detectAndFixRedundantChanges.js +404 -0
- package/src/PatchUtils/patchVerifier/formatAndAddLineNumbers.js +165 -0
- package/src/PatchUtils/patchVerifier/index.js +25 -0
- package/src/PatchUtils/patchVerifier/verifyAndCorrectHunkHeaders.js +202 -0
- package/src/PatchUtils/patchVerifier/verifyAndCorrectLineNumbers.js +254 -0
- package/src/SharedUtils/timestampUtils.js +41 -0
- package/src/SharedUtils/versionUtils.js +58 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: PatchUtils Detect and Fix Redundant Changes
|
|
3
|
+
* Block-UUID: ffd6e8fc-399d-4254-827c-ef17e2f7d0d5
|
|
4
|
+
* Parent-UUID: 2308ed72-91ff-48ba-bc80-310c23c01ff1
|
|
5
|
+
* Version: 1.0.0
|
|
6
|
+
* Description: Detects and optionally fixes redundant changes in a patch where content is deleted and re-added identically.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-05-14T03:48:02.248Z
|
|
9
|
+
* Authors: Gemini 2.5 Flash Thinking (v1.0.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const { CONTENT_LINE_REGEX, HUNK_HEADER_REGEX } = require('./constants');
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Represents an error found during patch verification.
|
|
18
|
+
* @typedef {object} VerificationError
|
|
19
|
+
* @property {number} [originalLineNumber] - The line number reported in the patch file (for line errors).
|
|
20
|
+
* @property {number} [hunkIndex] - The index (0-based) of the hunk where the error occurred (for header errors).
|
|
21
|
+
* @property {string} lineContent - The full content of the problematic line or header in the patch.
|
|
22
|
+
* @property {string} message - Description of the error.
|
|
23
|
+
* @property {number} [searchStart] - The starting line number (1-based) of the search window in the source.
|
|
24
|
+
* @property {number} [searchEnd] - The ending line number (1-based) of the search window in the source.
|
|
25
|
+
* @property {string} [errorType] - Type of error (e.g., 'redundant-change', 'line-mismatch', 'header-mismatch').
|
|
26
|
+
* @property {string[]} [suggestedFix] - Suggested lines to replace the problematic section.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Detects and optionally fixes redundant changes in a patch, where content is unnecessarily
|
|
32
|
+
* deleted and then re-added with identical content. This is a common issue in LLM-generated
|
|
33
|
+
* patches that can cause "malformed patch" errors when applying the patch.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} patchText - The full text of the patch (including metadata and markers).
|
|
36
|
+
* @param {boolean} [autoFix=false] - Whether to automatically fix detected redundant changes.
|
|
37
|
+
* @returns {{
|
|
38
|
+
* correctedPatchText: string,
|
|
39
|
+
* correctionsMade: boolean,
|
|
40
|
+
* errors: VerificationError[]
|
|
41
|
+
* }} - An object containing the potentially corrected patch text, a flag indicating if corrections were made, and an array of errors encountered.
|
|
42
|
+
* @throws {Error} If patchText is not a string.
|
|
43
|
+
*/
|
|
44
|
+
function detectAndFixRedundantChanges(patchText, autoFix = false) {
|
|
45
|
+
if (typeof patchText !== 'string') {
|
|
46
|
+
throw new Error("Invalid input: patchText must be a string.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const lines = patchText.split('\n');
|
|
50
|
+
const outputLines = [];
|
|
51
|
+
const errors = [];
|
|
52
|
+
let correctionsMade = false;
|
|
53
|
+
let currentHunkIndex = -1;
|
|
54
|
+
let processingHunk = false;
|
|
55
|
+
let currentHunkStart = -1;
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < lines.length; i++) {
|
|
58
|
+
const line = lines[i];
|
|
59
|
+
|
|
60
|
+
if (line.startsWith('@@ ')) {
|
|
61
|
+
// Start of a new hunk
|
|
62
|
+
if (processingHunk) {
|
|
63
|
+
// Process the previous hunk before starting a new one
|
|
64
|
+
const result = processRedundantChanges(
|
|
65
|
+
lines.slice(currentHunkStart, i),
|
|
66
|
+
currentHunkIndex,
|
|
67
|
+
autoFix
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
outputLines.push(...result.correctedLines);
|
|
71
|
+
errors.push(...result.errors);
|
|
72
|
+
if (result.corrected) correctionsMade = true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
processingHunk = true;
|
|
76
|
+
currentHunkIndex++;
|
|
77
|
+
currentHunkStart = i;
|
|
78
|
+
outputLines.push(line); // Add the hunk header
|
|
79
|
+
} else if (line === '# --- PATCH END MARKER ---') {
|
|
80
|
+
// End of patch content
|
|
81
|
+
if (processingHunk) {
|
|
82
|
+
// Process the last hunk
|
|
83
|
+
const result = processRedundantChanges(
|
|
84
|
+
lines.slice(currentHunkStart, i),
|
|
85
|
+
currentHunkIndex,
|
|
86
|
+
autoFix
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Remove the hunk header we already added
|
|
90
|
+
outputLines.pop();
|
|
91
|
+
outputLines.push(...result.correctedLines);
|
|
92
|
+
errors.push(...result.errors);
|
|
93
|
+
if (result.corrected) correctionsMade = true;
|
|
94
|
+
processingHunk = false;
|
|
95
|
+
}
|
|
96
|
+
outputLines.push(line); // Add the end marker
|
|
97
|
+
} else if (!processingHunk) {
|
|
98
|
+
// Outside of a hunk (metadata, markers, etc.)
|
|
99
|
+
outputLines.push(line);
|
|
100
|
+
}
|
|
101
|
+
// We don't add hunk content lines here - they're handled in processRedundantChanges
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Process the last hunk if we're still in one and there was no end marker
|
|
105
|
+
if (processingHunk) {
|
|
106
|
+
const result = processRedundantChanges(
|
|
107
|
+
lines.slice(currentHunkStart),
|
|
108
|
+
currentHunkIndex,
|
|
109
|
+
autoFix
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Remove the hunk header we already added
|
|
113
|
+
outputLines.pop();
|
|
114
|
+
outputLines.push(...result.correctedLines);
|
|
115
|
+
errors.push(...result.errors);
|
|
116
|
+
if (result.corrected) correctionsMade = true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
correctedPatchText: outputLines.join('\n'),
|
|
121
|
+
correctionsMade,
|
|
122
|
+
errors
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Helper function to process a single hunk for redundant changes.
|
|
128
|
+
* @param {string[]} hunkLines - Array of lines in the hunk, including the header.
|
|
129
|
+
* @param {number} hunkIndex - The 0-based index of this hunk in the patch.
|
|
130
|
+
* @param {boolean} autoFix - Whether to automatically fix detected issues.
|
|
131
|
+
* @returns {{correctedLines: string[], corrected: boolean, errors: VerificationError[]}}
|
|
132
|
+
* @private
|
|
133
|
+
*/
|
|
134
|
+
function processRedundantChanges(hunkLines, hunkIndex, autoFix) {
|
|
135
|
+
if (hunkLines.length < 2) {
|
|
136
|
+
return { correctedLines: hunkLines, corrected: false, errors: [] };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const errors = [];
|
|
140
|
+
let corrected = false;
|
|
141
|
+
|
|
142
|
+
// First line should be the hunk header
|
|
143
|
+
const headerLine = hunkLines[0];
|
|
144
|
+
const headerMatch = headerLine.match(HUNK_HEADER_REGEX);
|
|
145
|
+
if (!headerMatch) {
|
|
146
|
+
return {
|
|
147
|
+
correctedLines: hunkLines,
|
|
148
|
+
corrected: false,
|
|
149
|
+
errors: [{
|
|
150
|
+
hunkIndex,
|
|
151
|
+
lineContent: headerLine,
|
|
152
|
+
message: "Failed to parse hunk header.",
|
|
153
|
+
errorType: 'header-parse-error'
|
|
154
|
+
}]
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Extract content lines (skip the header)
|
|
159
|
+
const contentLines = hunkLines.slice(1);
|
|
160
|
+
|
|
161
|
+
// Group consecutive deletion and addition lines
|
|
162
|
+
const groups = [];
|
|
163
|
+
let currentGroup = { deletions: [], additions: [], startIndex: -1 };
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < contentLines.length; i++) {
|
|
166
|
+
const line = contentLines[i];
|
|
167
|
+
|
|
168
|
+
if (line.startsWith('-')) {
|
|
169
|
+
if (currentGroup.additions.length > 0) {
|
|
170
|
+
// We've been collecting additions and now hit a deletion
|
|
171
|
+
// This is a new group
|
|
172
|
+
groups.push({ ...currentGroup });
|
|
173
|
+
currentGroup = { deletions: [line], additions: [], startIndex: i };
|
|
174
|
+
} else {
|
|
175
|
+
// Continue collecting deletions
|
|
176
|
+
if (currentGroup.startIndex === -1) currentGroup.startIndex = i;
|
|
177
|
+
currentGroup.deletions.push(line);
|
|
178
|
+
}
|
|
179
|
+
} else if (line.startsWith('+')) {
|
|
180
|
+
// Continue collecting additions
|
|
181
|
+
if (currentGroup.startIndex === -1) currentGroup.startIndex = i;
|
|
182
|
+
currentGroup.additions.push(line);
|
|
183
|
+
} else {
|
|
184
|
+
// Context line - end the current group if it has any content
|
|
185
|
+
if (currentGroup.deletions.length > 0 || currentGroup.additions.length > 0) {
|
|
186
|
+
groups.push({ ...currentGroup });
|
|
187
|
+
currentGroup = { deletions: [], additions: [], startIndex: -1 };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Add the last group if it has content
|
|
193
|
+
if (currentGroup.deletions.length > 0 || currentGroup.additions.length > 0) {
|
|
194
|
+
groups.push(currentGroup);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check each group for redundant changes
|
|
198
|
+
const redundantGroups = [];
|
|
199
|
+
|
|
200
|
+
for (const group of groups) {
|
|
201
|
+
if (group.deletions.length > 0 && group.additions.length > 0) {
|
|
202
|
+
// Extract content without diff markers and line numbers
|
|
203
|
+
const deletionContents = group.deletions.map(line => {
|
|
204
|
+
const match = line.match(CONTENT_LINE_REGEX);
|
|
205
|
+
return match ? match[3].trim() : line.substring(1).trim();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const additionContents = group.additions.map(line => {
|
|
209
|
+
const match = line.match(CONTENT_LINE_REGEX);
|
|
210
|
+
return match ? match[3].trim() : line.substring(1).trim();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Check if the contents are identical
|
|
214
|
+
if (deletionContents.length === additionContents.length) {
|
|
215
|
+
let identical = true;
|
|
216
|
+
for (let i = 0; i < deletionContents.length; i++) {
|
|
217
|
+
if (deletionContents[i] !== additionContents[i]) {
|
|
218
|
+
identical = false;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (identical) {
|
|
224
|
+
redundantGroups.push({
|
|
225
|
+
...group,
|
|
226
|
+
fixedLines: group.deletions.map((line, i) => {
|
|
227
|
+
// Convert to context lines by replacing '-' with ' '
|
|
228
|
+
const match = line.match(CONTENT_LINE_REGEX);
|
|
229
|
+
if (match) {
|
|
230
|
+
return ` ${match[2]}: ${match[3]}`;
|
|
231
|
+
} else {
|
|
232
|
+
// Fallback if regex fails, try to preserve original line number if possible
|
|
233
|
+
const fallbackMatch = line.match(/^-\s*(\d+):\s?(.*)$/);
|
|
234
|
+
if (fallbackMatch) {
|
|
235
|
+
return ` ${fallbackMatch[1]}: ${fallbackMatch[2]}`;
|
|
236
|
+
}
|
|
237
|
+
return ` ${line.substring(1)}`; // Just remove the '-'
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// If we found redundant groups, report them and optionally fix
|
|
247
|
+
if (redundantGroups.length > 0) {
|
|
248
|
+
for (const group of redundantGroups) {
|
|
249
|
+
// Calculate line numbers relative to the start of the hunk content lines (after the header)
|
|
250
|
+
const startLineInHunkContent = group.startIndex;
|
|
251
|
+
const endLineInHunkContent = startLineInHunkContent + group.deletions.length + group.additions.length - 1;
|
|
252
|
+
|
|
253
|
+
// Find the actual line number in the patch file for the start of the redundant block
|
|
254
|
+
// This requires iterating through the original hunkLines to find the line index
|
|
255
|
+
let actualStartLineInPatch = -1;
|
|
256
|
+
let currentContentLineIndex = 0;
|
|
257
|
+
for(let k = 1; k < hunkLines.length; k++) { // Start from 1 to skip header
|
|
258
|
+
if (currentContentLineIndex === startLineInHunkContent) {
|
|
259
|
+
// The line index in the original patch file is the index in hunkLines + the start line of the hunk in the full patch
|
|
260
|
+
// We don't have the start line of the hunk in the full patch here, so we'll report relative to the hunk start.
|
|
261
|
+
// A better approach might be to pass the global line index to processHunk.
|
|
262
|
+
// For now, report relative to the start of the hunk content.
|
|
263
|
+
actualStartLineInPatch = k; // Index within hunkLines (0-based)
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
// Only increment content line index for lines that are part of the content (not header)
|
|
267
|
+
if (!hunkLines[k].startsWith('@@ ')) {
|
|
268
|
+
currentContentLineIndex++;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
errors.push({
|
|
274
|
+
hunkIndex,
|
|
275
|
+
// Report the line in the original hunkLines array where the redundant block starts
|
|
276
|
+
lineContent: hunkLines[actualStartLineInPatch !== -1 ? actualStartLineInPatch : group.startIndex + 1], // Fallback to index relative to content start + 1 for header
|
|
277
|
+
message: `Redundant change detected: content is deleted and then re-added with identical content (starts at line ${startLineInHunkContent + 1} within hunk content).`,
|
|
278
|
+
errorType: 'redundant-change',
|
|
279
|
+
suggestedFix: autoFix ? group.fixedLines : undefined // Only suggest fix if autoFix is true
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// If autoFix is enabled, replace the redundant changes with context lines
|
|
284
|
+
if (autoFix) {
|
|
285
|
+
let correctedContentLines = [...contentLines];
|
|
286
|
+
|
|
287
|
+
// Process groups in reverse order to avoid index shifting
|
|
288
|
+
redundantGroups.sort((a, b) => b.startIndex - a.startIndex);
|
|
289
|
+
|
|
290
|
+
for (const group of redundantGroups) {
|
|
291
|
+
const startIdx = group.startIndex;
|
|
292
|
+
const endIdx = startIdx + group.deletions.length + group.additions.length;
|
|
293
|
+
|
|
294
|
+
// Replace the redundant deletion+addition with context lines
|
|
295
|
+
correctedContentLines.splice(
|
|
296
|
+
startIdx,
|
|
297
|
+
endIdx - startIdx,
|
|
298
|
+
...group.fixedLines
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Count the different types of lines in the corrected content
|
|
303
|
+
let contextLineCount = 0;
|
|
304
|
+
let deletionLineCount = 0;
|
|
305
|
+
let additionLineCount = 0;
|
|
306
|
+
|
|
307
|
+
for (const line of correctedContentLines) {
|
|
308
|
+
if (line.startsWith(' ')) contextLineCount++;
|
|
309
|
+
else if (line.startsWith('-')) deletionLineCount++;
|
|
310
|
+
else if (line.startsWith('+')) additionLineCount++;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Parse the original header
|
|
314
|
+
const oldStart = parseInt(headerMatch[1], 10);
|
|
315
|
+
const newStart = parseInt(headerMatch[3], 10);
|
|
316
|
+
|
|
317
|
+
// Calculate the new counts
|
|
318
|
+
const oldCount = contextLineCount + deletionLineCount;
|
|
319
|
+
const newCount = contextLineCount + additionLineCount;
|
|
320
|
+
|
|
321
|
+
// If both counts are 0, this means the entire hunk was redundant changes
|
|
322
|
+
// In this case, we should preserve at least one context line to maintain a valid hunk
|
|
323
|
+
if (oldCount === 0 && newCount === 0) {
|
|
324
|
+
// Find the first line number from the original content
|
|
325
|
+
let lineNumber = null;
|
|
326
|
+
for (const line of contentLines) {
|
|
327
|
+
const match = line.match(CONTENT_LINE_REGEX);
|
|
328
|
+
if (match) {
|
|
329
|
+
lineNumber = match[2];
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// If we found a line number, add a context line
|
|
335
|
+
if (lineNumber) {
|
|
336
|
+
// Find the corresponding line in the original content
|
|
337
|
+
// Need to find the original line content to reconstruct the context line
|
|
338
|
+
let originalLineContent = '';
|
|
339
|
+
for (const line of contentLines) {
|
|
340
|
+
const match = line.match(CONTENT_LINE_REGEX);
|
|
341
|
+
if (match && match[2] === lineNumber) {
|
|
342
|
+
originalLineContent = match[3];
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (originalLineContent) {
|
|
348
|
+
// Reconstruct the context line with the found line number and content
|
|
349
|
+
const padding = String(lineNumber).length; // Use the line number's length for padding
|
|
350
|
+
correctedContentLines = [` ${String(lineNumber).padStart(padding, ' ')}: ${originalLineContent}`];
|
|
351
|
+
contextLineCount = 1;
|
|
352
|
+
} else {
|
|
353
|
+
// If we couldn't find the original content, just add a generic context line
|
|
354
|
+
correctedContentLines = [` ${String(oldStart).padStart(String(oldStart).length, ' ')}: ...`]; // Use oldStart as a fallback line number
|
|
355
|
+
contextLineCount = 1;
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
// If no line numbers were found at all, add a minimal valid hunk
|
|
359
|
+
correctedContentLines = [` ${String(oldStart).padStart(String(oldStart).length, ' ')}: ...`];
|
|
360
|
+
contextLineCount = 1;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Recalculate in case we added a context line
|
|
365
|
+
const finalOldCount = contextLineCount + deletionLineCount;
|
|
366
|
+
const finalNewCount = contextLineCount + additionLineCount;
|
|
367
|
+
|
|
368
|
+
// Format the new header
|
|
369
|
+
const oldPart = finalOldCount === 1 ? `${oldStart}` : `${oldStart},${finalOldCount}`;
|
|
370
|
+
const newPart = finalNewCount === 1 ? `${newStart}` : `${newStart},${finalNewCount}`;
|
|
371
|
+
const newHeader = `@@ -${oldPart} +${newPart} @@`;
|
|
372
|
+
|
|
373
|
+
// If we have no content lines left, this hunk should be removed entirely
|
|
374
|
+
if (correctedContentLines.length === 0) {
|
|
375
|
+
return {
|
|
376
|
+
correctedLines: [], // Return empty array to signal this hunk should be removed
|
|
377
|
+
corrected: true,
|
|
378
|
+
errors: [...errors, {
|
|
379
|
+
hunkIndex,
|
|
380
|
+
lineContent: headerLine,
|
|
381
|
+
message: "Entire hunk consists of redundant changes and has been removed.",
|
|
382
|
+
errorType: 'hunk-removed'
|
|
383
|
+
}]
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
correctedLines: [newHeader, ...correctedContentLines],
|
|
389
|
+
corrected: true,
|
|
390
|
+
errors
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
correctedLines: hunkLines,
|
|
397
|
+
corrected: false,
|
|
398
|
+
errors
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
module.exports = {
|
|
403
|
+
detectAndFixRedundantChanges,
|
|
404
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: PatchUtils Format and Add Line Numbers
|
|
3
|
+
* Block-UUID: 37c3f528-dede-4d2f-9e4f-df87d1dce78f
|
|
4
|
+
* Parent-UUID: 2308ed72-91ff-48ba-bc80-310c23c01ff1
|
|
5
|
+
* Version: 1.0.0
|
|
6
|
+
* Description: Ensures patch content lines have NNN: line number prefixes with consistent padding, adding them if missing.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-05-14T03:46:14.708Z
|
|
9
|
+
* Authors: Gemini 2.5 Flash Thinking (v1.0.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const { removeLineNumbers } = require('../../CodeBlockUtils/lineNumberFormatter'); // Assuming this utility exists
|
|
14
|
+
const { CONTENT_LINE_REGEX, HUNK_HEADER_REGEX } = require('./constants');
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Ensures patch content lines have NNN: line number prefixes with consistent padding,
|
|
19
|
+
* adding them if missing. This function focuses solely on the line number prefix
|
|
20
|
+
* format and presence, not the correctness of the numbers themselves (which is
|
|
21
|
+
* handled by verifyAndCorrectLineNumbers).
|
|
22
|
+
*
|
|
23
|
+
* @param {string} patchText - The full text of the patch (including metadata, markers, etc.).
|
|
24
|
+
* @param {string} sourceText - The original source code text (MUST be clean, without NNN: prefixes).
|
|
25
|
+
* @returns {{
|
|
26
|
+
* formattedPatchText: string,
|
|
27
|
+
* correctionsMade: boolean
|
|
28
|
+
* }} - An object containing the potentially corrected patch text and a flag indicating if corrections were made.
|
|
29
|
+
* @throws {Error} If inputs are invalid (e.g., non-string text).
|
|
30
|
+
*/
|
|
31
|
+
function formatAndAddLineNumbers(patchText, sourceText) {
|
|
32
|
+
if (typeof patchText !== 'string') {
|
|
33
|
+
throw new Error("Invalid input: patchText must be a string.");
|
|
34
|
+
}
|
|
35
|
+
if (typeof sourceText !== 'string') {
|
|
36
|
+
throw new Error("Invalid input: sourceText must be a string.");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const patchLines = patchText.split('\n');
|
|
40
|
+
const sourceLineCount = sourceText.split('\n').length;
|
|
41
|
+
// Calculate padding width based on the number of lines in the source file
|
|
42
|
+
const padding = String(sourceLineCount).length;
|
|
43
|
+
// Ensure minimum padding is 1 even for empty source
|
|
44
|
+
const requiredPadding = Math.max(1, padding);
|
|
45
|
+
|
|
46
|
+
const outputLines = [];
|
|
47
|
+
let correctionsMade = false;
|
|
48
|
+
let currentHunkOldStart = null;
|
|
49
|
+
let currentHunkNewStart = null;
|
|
50
|
+
let oldLineCounter = 0; // Counter for lines in the original file perspective within the hunk
|
|
51
|
+
let inHunk = false;
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < patchLines.length; i++) {
|
|
54
|
+
const currentPatchLine = patchLines[i];
|
|
55
|
+
|
|
56
|
+
const headerMatch = currentPatchLine.match(HUNK_HEADER_REGEX);
|
|
57
|
+
if (headerMatch) {
|
|
58
|
+
// Start of a new hunk
|
|
59
|
+
inHunk = true;
|
|
60
|
+
currentHunkOldStart = parseInt(headerMatch[1], 10);
|
|
61
|
+
currentHunkNewStart = parseInt(headerMatch[3], 10);
|
|
62
|
+
// Reset counter for the new hunk
|
|
63
|
+
oldLineCounter = 0;
|
|
64
|
+
outputLines.push(currentPatchLine);
|
|
65
|
+
continue; // Move to the next line
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check for patch markers to potentially reset hunk state
|
|
69
|
+
if (currentPatchLine === '# --- PATCH START MARKER ---' || currentPatchLine === '# --- PATCH END MARKER ---') {
|
|
70
|
+
// Assume markers are outside hunks and reset hunk processing
|
|
71
|
+
inHunk = false;
|
|
72
|
+
outputLines.push(currentPatchLine);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (inHunk) {
|
|
77
|
+
const contentMatch = currentPatchLine.match(CONTENT_LINE_REGEX);
|
|
78
|
+
const prefix = currentPatchLine.charAt(0); // Get the diff prefix (+, -, or space)
|
|
79
|
+
|
|
80
|
+
if (prefix === '+' || prefix === '-' || prefix === ' ') {
|
|
81
|
+
// This is a content line within a hunk
|
|
82
|
+
// Get content after the potential prefix and line number
|
|
83
|
+
const content = contentMatch ? contentMatch[3] : currentPatchLine.substring(1);
|
|
84
|
+
|
|
85
|
+
let newLineCounter = oldLineCounter; // Track position in the new file
|
|
86
|
+
|
|
87
|
+
let lineNumberToUse;
|
|
88
|
+
if (contentMatch) {
|
|
89
|
+
// Line already has a number prefix, use the parsed number
|
|
90
|
+
lineNumberToUse = parseInt(contentMatch[2], 10);
|
|
91
|
+
} else {
|
|
92
|
+
// Line is missing a number prefix, estimate based on hunk start and counter
|
|
93
|
+
// The number prefix NNN: always refers to the original file's line number
|
|
94
|
+
if (prefix === '+') {
|
|
95
|
+
// For addition lines, use the new file's line number
|
|
96
|
+
lineNumberToUse = currentHunkNewStart + newLineCounter;
|
|
97
|
+
} else {
|
|
98
|
+
// For context and deletion lines, use the original file's line number
|
|
99
|
+
lineNumberToUse = currentHunkOldStart + oldLineCounter;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const formattedLine = `${prefix} ${String(lineNumberToUse).padStart(requiredPadding, ' ')}: ${content}`;
|
|
104
|
+
|
|
105
|
+
if (formattedLine !== currentPatchLine) {
|
|
106
|
+
correctionsMade = true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
outputLines.push(formattedLine);
|
|
110
|
+
|
|
111
|
+
// Increment the oldLineCounter for context and deletion lines
|
|
112
|
+
// Addition lines (+), while part of the hunk, don't exist in the original file
|
|
113
|
+
// sequence for the purpose of this counter.
|
|
114
|
+
if (prefix === ' ' || prefix === '-') {
|
|
115
|
+
oldLineCounter++;
|
|
116
|
+
}
|
|
117
|
+
// For context and addition lines, increment the new file counter
|
|
118
|
+
if (prefix === ' ' || prefix === '+') {
|
|
119
|
+
newLineCounter++;
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
// Line is within a hunk but not a standard content line (e.g., comments within hunk)
|
|
123
|
+
// Add as is, but check if it looks like a malformed content line without prefix
|
|
124
|
+
if (currentPatchLine.length > 0 && (currentPatchLine.startsWith('+') || currentPatchLine.startsWith('-') || currentPatchLine.startsWith(' '))) {
|
|
125
|
+
// It starts with a diff prefix but didn't match the regex, likely missing NNN:
|
|
126
|
+
// Treat it as missing the prefix and add one
|
|
127
|
+
const content = currentPatchLine.substring(1);
|
|
128
|
+
|
|
129
|
+
let estimatedLineNumber;
|
|
130
|
+
if (prefix === '+') {
|
|
131
|
+
estimatedLineNumber = currentHunkNewStart + oldLineCounter; // Use new file position for additions
|
|
132
|
+
} else {
|
|
133
|
+
estimatedLineNumber = currentHunkOldStart + oldLineCounter; // Use old file position for context/deletions
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
const formattedLine = `${prefix} ${String(estimatedLineNumber).padStart(requiredPadding, ' ')}: ${content}`;
|
|
138
|
+
outputLines.push(formattedLine);
|
|
139
|
+
correctionsMade = true;
|
|
140
|
+
|
|
141
|
+
// Increment counter if it's a line type that affects the original file sequence
|
|
142
|
+
if (prefix === ' ' || prefix === '-') {
|
|
143
|
+
oldLineCounter++;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
} else {
|
|
147
|
+
// Truly not a content line, add as is
|
|
148
|
+
outputLines.push(currentPatchLine);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
// Line is outside a hunk (metadata, etc.)
|
|
153
|
+
outputLines.push(currentPatchLine);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
formattedPatchText: outputLines.join('\n'),
|
|
159
|
+
correctionsMade: correctionsMade
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
formatAndAddLineNumbers,
|
|
165
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: PatchUtils Verifier Index
|
|
3
|
+
* Block-UUID: 0bca5027-9656-44f8-89e1-e32991b386bd
|
|
4
|
+
* Parent-UUID: 2308ed72-91ff-48ba-bc80-310c23c01ff1
|
|
5
|
+
* Version: 1.0.0
|
|
6
|
+
* Description: Provides a backwards-compatible entry point for the patch verification utilities.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-05-14T03:49:18.423Z
|
|
9
|
+
* Authors: Gemini 2.5 Flash Thinking (v1.0.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const { formatAndAddLineNumbers } = require('./formatAndAddLineNumbers');
|
|
14
|
+
const { verifyAndCorrectLineNumbers } = require('./verifyAndCorrectLineNumbers');
|
|
15
|
+
const { verifyAndCorrectHunkHeaders } = require('./verifyAndCorrectHunkHeaders');
|
|
16
|
+
const { detectAndFixRedundantChanges } = require('./detectAndFixRedundantChanges');
|
|
17
|
+
const { detectAndFixOverlappingHunks } = require('./detectAndFixOverlappingHunks');
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
formatAndAddLineNumbers,
|
|
21
|
+
verifyAndCorrectLineNumbers,
|
|
22
|
+
verifyAndCorrectHunkHeaders,
|
|
23
|
+
detectAndFixRedundantChanges,
|
|
24
|
+
detectAndFixOverlappingHunks,
|
|
25
|
+
};
|