@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,559 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: CodeBlockUtils Block Processor
|
|
3
|
+
* Block-UUID: 1d8a559b-4f7a-4b0b-9e0a-13786f94a74e
|
|
4
|
+
* Parent-UUID: 082abf8b-079f-4c95-8464-0e66c0de45eb
|
|
5
|
+
* Version: 1.4.0
|
|
6
|
+
* Description: Processes the content of identified code blocks, parsing headers or patch details, and provides utilities like fixing UUIDs within blocks.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-07-21T00:33:22.312Z
|
|
9
|
+
* Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Pro (v1.1.0), Gemini 2.5 Pro (v1.2.0), Gemini 2.5 Flash Thinking (v1.3.0), Gemini 2.5 Flash Thinking (v1.4.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const { findAllCodeFences, matchFencesAndExtractBlocks } = require('./blockExtractor');
|
|
14
|
+
const { parseHeader } = require('./headerUtils');
|
|
15
|
+
const { validateUUID, generateUUID } = require('./uuidUtils');
|
|
16
|
+
const AnalysisBlockUtils = require('../AnalysisBlockUtils');
|
|
17
|
+
const PatchUtils = require('../PatchUtils');
|
|
18
|
+
const GSToolBlockUtils = require('../GSToolBlockUtils');
|
|
19
|
+
const { extractContinuationInfo } = require('./continuationUtils');
|
|
20
|
+
const { removeLineNumbers } = require('./lineNumberFormatter');
|
|
21
|
+
const { detectJsonComments } = require('../JsonUtils');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Processes a single block's content (internal helper)
|
|
25
|
+
* @param {string} content - The block content
|
|
26
|
+
* @param {string} language - The language of the block
|
|
27
|
+
* @param {number} position - The position in the original text
|
|
28
|
+
* @param {boolean} incomplete - Whether the block is incomplete
|
|
29
|
+
* @param {Object} options - Processing options (e.g., silent, validatePatches)
|
|
30
|
+
* @returns {Object} Processed block object
|
|
31
|
+
*/
|
|
32
|
+
function processBlockContent(content, language, position, incomplete, options = { silent: true, validatePatches: false }) {
|
|
33
|
+
const { silent, validatePatches } = options;
|
|
34
|
+
let warnings = []; // Collect warnings specific to this block
|
|
35
|
+
let header = {};
|
|
36
|
+
let headerText = ''; // Raw header text including comments
|
|
37
|
+
let headerParseError = null;
|
|
38
|
+
let headerDelimiterType = null; // 'triple' or 'double'
|
|
39
|
+
let codeText = content; // Default to all content being code
|
|
40
|
+
|
|
41
|
+
// Check if this is a GitSense Tool Block *first*
|
|
42
|
+
if (GSToolBlockUtils.isToolBlock(content)) {
|
|
43
|
+
let ignoreDueToComments = false;
|
|
44
|
+
let toolData = null;
|
|
45
|
+
let toolParseError = null;
|
|
46
|
+
try {
|
|
47
|
+
toolData = GSToolBlockUtils.parseToolBlock(content);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
// When chatting about the tool block, the LLM may add
|
|
50
|
+
// add comments in it so let's test for this
|
|
51
|
+
const comments = detectJsonComments(content);
|
|
52
|
+
|
|
53
|
+
if (comments.length) {
|
|
54
|
+
ignoreDueToComments = true;
|
|
55
|
+
} else {
|
|
56
|
+
toolParseError = error.message;
|
|
57
|
+
warnings.push({
|
|
58
|
+
position: position,
|
|
59
|
+
type: 'gs_tool_block_parse_error',
|
|
60
|
+
message: `Invalid GitSense tool block: ${error.message}`,
|
|
61
|
+
content: content
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (ignoreDueToComments) {
|
|
67
|
+
// Uncomment to show warning
|
|
68
|
+
// console.warn('GitSense Chat Tool JSON contains one or more comments');
|
|
69
|
+
} else {
|
|
70
|
+
return {
|
|
71
|
+
type: 'gs-tool',
|
|
72
|
+
language: language,
|
|
73
|
+
content: content,
|
|
74
|
+
position: position,
|
|
75
|
+
incomplete: incomplete,
|
|
76
|
+
toolData: toolData,
|
|
77
|
+
toolParseError: toolParseError,
|
|
78
|
+
warnings: warnings,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check if this is an analysis block *next*
|
|
84
|
+
if (AnalysisBlockUtils.isAnalysisBlock(content)) {
|
|
85
|
+
const analysisType = AnalysisBlockUtils.getAnalysisBlockType(content);
|
|
86
|
+
const analysisMetadata = AnalysisBlockUtils.parseOverviewMetadata(content);
|
|
87
|
+
|
|
88
|
+
// Validate the metadata if it was successfully parsed
|
|
89
|
+
let validationResult = { isValid: false, errors: ['Failed to parse analysis metadata'] };
|
|
90
|
+
if (analysisMetadata) {
|
|
91
|
+
validationResult = AnalysisBlockUtils.validateAnalysisMetadata(analysisMetadata);
|
|
92
|
+
|
|
93
|
+
if (!validationResult.isValid) {
|
|
94
|
+
warnings.push({
|
|
95
|
+
position: position,
|
|
96
|
+
type: 'analysis_metadata_error',
|
|
97
|
+
message: `Invalid analysis metadata: ${validationResult.errors.join(', ')}`,
|
|
98
|
+
content: content,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
type: analysisType,
|
|
105
|
+
language: language,
|
|
106
|
+
content: content,
|
|
107
|
+
position: position,
|
|
108
|
+
incomplete: incomplete,
|
|
109
|
+
analysisMetadata: analysisMetadata,
|
|
110
|
+
analysisValidation: validationResult,
|
|
111
|
+
warnings: warnings,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check if this is a gitsense-search-flow block *next*
|
|
116
|
+
if (language === 'gitsense-search-flow') {
|
|
117
|
+
// Future parsing logic for gitsense-search-flow content would go here.
|
|
118
|
+
// For now, we just identify the type and keep the raw content.
|
|
119
|
+
return {
|
|
120
|
+
type: 'gitsense-search-flow',
|
|
121
|
+
language: language,
|
|
122
|
+
content: content, // Keep raw content for now
|
|
123
|
+
position: position,
|
|
124
|
+
incomplete: incomplete,
|
|
125
|
+
// Add parsedData: {} or similar if parsing is implemented later
|
|
126
|
+
warnings: warnings, // Include any warnings collected so far
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Then check if this is a patch block
|
|
131
|
+
if (PatchUtils.isPatchBlock(content)) {
|
|
132
|
+
const patchFormat = PatchUtils.determinePatchFormat(content);
|
|
133
|
+
const metadata = PatchUtils.extractPatchMetadata(content); // Extract metadata regardless of validation
|
|
134
|
+
const patchContent = PatchUtils.extractPatchContent(content); // Extract raw patch content
|
|
135
|
+
|
|
136
|
+
let patchValidationResult = { valid: true, errors: [], patches: null }; // Default valid state
|
|
137
|
+
|
|
138
|
+
// Validate metadata (always useful)
|
|
139
|
+
const errors = PatchUtils.validatePatchMetadata(metadata);
|
|
140
|
+
if (errors.length) {
|
|
141
|
+
warnings.push({
|
|
142
|
+
position: position,
|
|
143
|
+
type: 'patch_metadata_error',
|
|
144
|
+
message: `Invalid patch metadata: ${errors.join(', ')}`,
|
|
145
|
+
content: content,
|
|
146
|
+
});
|
|
147
|
+
// Decide if invalid metadata makes the whole block invalid or just adds a warning
|
|
148
|
+
// For now, just warn, but return the extracted (potentially invalid) metadata.
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Optionally validate context patch structure if requested
|
|
152
|
+
if (patchFormat === 'context' && validatePatches) {
|
|
153
|
+
try {
|
|
154
|
+
const patches = PatchUtils.extractContextPatches(content);
|
|
155
|
+
if (!patches || patches.length === 0) {
|
|
156
|
+
patchValidationResult = { valid: false, errors: ['No valid context patch content found.'], patches: null };
|
|
157
|
+
warnings.push({
|
|
158
|
+
position: position,
|
|
159
|
+
type: 'patch_content_error',
|
|
160
|
+
message: 'No valid context patch content found.',
|
|
161
|
+
content: content,
|
|
162
|
+
});
|
|
163
|
+
} else {
|
|
164
|
+
// Basic validation passed (structure was parsable)
|
|
165
|
+
patchValidationResult = { valid: true, errors: [], patches: patches };
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
patchValidationResult = { valid: false, errors: [error.message], patches: null };
|
|
169
|
+
warnings.push({
|
|
170
|
+
position: position,
|
|
171
|
+
type: 'patch_parse_error',
|
|
172
|
+
message: `Error parsing context patch: ${error.message}`,
|
|
173
|
+
content: content,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
type: 'patch',
|
|
180
|
+
language: language === 'diff' ? 'diff' : language, // Use 'diff' if specified, else keep original
|
|
181
|
+
content: content, // Full original content within fences
|
|
182
|
+
position: position,
|
|
183
|
+
incomplete: incomplete,
|
|
184
|
+
metadata: metadata,
|
|
185
|
+
patch: patchContent, // Raw patch content after metadata
|
|
186
|
+
patchFormat: patchFormat,
|
|
187
|
+
patchValidation: validatePatches ? patchValidationResult : undefined, // Include validation only if requested
|
|
188
|
+
warnings: warnings,
|
|
189
|
+
};
|
|
190
|
+
} else {
|
|
191
|
+
// For non-patch blocks, remove display line numbers before processing content
|
|
192
|
+
content = removeLineNumbers(content); // This is the "cleaned" content
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// --- Process as a regular code block ---
|
|
196
|
+
// Attempt 1: Look for triple newline separator (our standard)
|
|
197
|
+
const tripleNewlineSeparator = '\n\n\n';
|
|
198
|
+
const tripleNewlineIndex = content.indexOf(tripleNewlineSeparator);
|
|
199
|
+
|
|
200
|
+
if (tripleNewlineIndex !== -1) {
|
|
201
|
+
const potentialHeaderText = content.substring(0, tripleNewlineIndex); // No trim here
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
header = parseHeader(potentialHeaderText, language);
|
|
205
|
+
headerText = potentialHeaderText;
|
|
206
|
+
codeText = content.substring(tripleNewlineIndex + tripleNewlineSeparator.length);
|
|
207
|
+
headerDelimiterType = 'triple';
|
|
208
|
+
} catch (error) {
|
|
209
|
+
// If parsing fails, it's not a valid header, log warning and try double newline
|
|
210
|
+
headerParseError = error.message;
|
|
211
|
+
warnings.push({
|
|
212
|
+
position: position,
|
|
213
|
+
type: 'header_parse_error',
|
|
214
|
+
message: `Invalid header (triple newline attempt): ${error.message}. Attempting double newline.`,
|
|
215
|
+
content: potentialHeaderText // Show the problematic header text
|
|
216
|
+
});
|
|
217
|
+
// Do not set header, headerText, codeText, or headerDelimiterType yet.
|
|
218
|
+
// Fall through to the next attempt.
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Attempt 2: Look for double newline separator (fallback)
|
|
223
|
+
// Only if a valid header wasn't found in the triple newline attempt
|
|
224
|
+
if (!headerDelimiterType) {
|
|
225
|
+
const doubleNewlineSeparator = '\n\n';
|
|
226
|
+
const doubleNewlineIndex = content.indexOf(doubleNewlineSeparator);
|
|
227
|
+
|
|
228
|
+
if (doubleNewlineIndex !== -1) {
|
|
229
|
+
const potentialHeaderText = content.substring(0, doubleNewlineIndex); // No trim here
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
header = parseHeader(potentialHeaderText, language);
|
|
233
|
+
headerText = potentialHeaderText;
|
|
234
|
+
codeText = content.substring(doubleNewlineIndex + doubleNewlineSeparator.length);
|
|
235
|
+
headerDelimiterType = 'double';
|
|
236
|
+
} catch (error) {
|
|
237
|
+
// If parsing fails, it's not a valid header, log warning and default to no header
|
|
238
|
+
headerParseError = error.message;
|
|
239
|
+
warnings.push({
|
|
240
|
+
position: position,
|
|
241
|
+
type: 'header_parse_error',
|
|
242
|
+
message: `Invalid header (double newline attempt): ${error.message}. Treating block as code only.`,
|
|
243
|
+
content: potentialHeaderText // Show the problematic header text
|
|
244
|
+
});
|
|
245
|
+
// Fall through to default case (no header)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Default: If no valid header was found after both attempts
|
|
251
|
+
if (!headerDelimiterType && !incomplete) { // Only warn about missing header if the block is complete
|
|
252
|
+
warnings.push({
|
|
253
|
+
position: position,
|
|
254
|
+
type: 'missing_header',
|
|
255
|
+
message: `No distinct header found or parsed as valid. Treating block as code only.`,
|
|
256
|
+
content: content.substring(0, 100) + '...' // Show beginning of content
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
type: 'code',
|
|
262
|
+
language: language,
|
|
263
|
+
header: header,
|
|
264
|
+
headerText: headerText.trim(), // Include the raw header text if successfully parsed
|
|
265
|
+
content: codeText, // The code part after the header (or full content if no header)
|
|
266
|
+
position: position, // Position of the opening fence
|
|
267
|
+
headerDelimiterType: headerDelimiterType, // 'triple', 'double', or null
|
|
268
|
+
incomplete: incomplete,
|
|
269
|
+
warnings: warnings,
|
|
270
|
+
headerParseError: headerParseError // Include error message if parsing failed
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Processes the content of complete and incomplete blocks (internal helper)
|
|
277
|
+
* @param {string} text - The input text
|
|
278
|
+
* @param {Array} completeBlocks - Array of complete block objects from extractor
|
|
279
|
+
* @param {Array} incompleteBlocks - Array of incomplete block objects from extractor
|
|
280
|
+
* @param {Object} options - Processing options
|
|
281
|
+
* @returns {Array} Array of processed block objects
|
|
282
|
+
*/
|
|
283
|
+
function processBlockContents(text, completeBlocks, incompleteBlocks, options) {
|
|
284
|
+
const processedBlocks = [];
|
|
285
|
+
const allWarnings = []; // Collect warnings from all blocks
|
|
286
|
+
|
|
287
|
+
// Process complete blocks
|
|
288
|
+
for (const block of completeBlocks) {
|
|
289
|
+
try {
|
|
290
|
+
const startPos = block.opening.position + block.opening.length;
|
|
291
|
+
const endPos = block.closing.position;
|
|
292
|
+
// Get content, including the newlines immediately after opening and before closing fence
|
|
293
|
+
const contentWithNewlines = text.substring(startPos, endPos);
|
|
294
|
+
const content = contentWithNewlines; //.replace(/^\s*\n|\n\s*$/g, '');
|
|
295
|
+
|
|
296
|
+
const language = block.opening.language;
|
|
297
|
+
|
|
298
|
+
// Process block content
|
|
299
|
+
const processedBlock = processBlockContent(content, language, block.opening.position, false, options);
|
|
300
|
+
processedBlocks.push(processedBlock);
|
|
301
|
+
if (processedBlock.warnings) {
|
|
302
|
+
allWarnings.push(...processedBlock.warnings);
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
// Catch errors during the processing of a single block
|
|
306
|
+
allWarnings.push({
|
|
307
|
+
position: block.opening?.position || -1,
|
|
308
|
+
type: 'block_processing_error',
|
|
309
|
+
message: `Error processing complete block: ${error.message}`,
|
|
310
|
+
content: text.substring(block.opening?.position, block.closing?.position + block.closing?.length) || "Error retrieving block content"
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Process incomplete blocks
|
|
316
|
+
for (const block of incompleteBlocks) {
|
|
317
|
+
try {
|
|
318
|
+
const startPos = block.opening.position + block.opening.length;
|
|
319
|
+
// Get content from opening fence to end of text
|
|
320
|
+
const contentWithNewline = text.substring(startPos);
|
|
321
|
+
// Trim only leading whitespace line
|
|
322
|
+
const content = contentWithNewline.replace(/^\s*\n/, '');
|
|
323
|
+
|
|
324
|
+
const language = block.opening.language;
|
|
325
|
+
|
|
326
|
+
// Process block content - it's expected to be incomplete
|
|
327
|
+
const processedBlock = processBlockContent(content, language, block.opening.position, true, options);
|
|
328
|
+
|
|
329
|
+
// Extract continuation info for incomplete blocks
|
|
330
|
+
// Requires extractContinuationInfo from continuationUtils
|
|
331
|
+
try {
|
|
332
|
+
// Pass the *processed* header info to continuation utils
|
|
333
|
+
let { 'Continuation-Part': partNumber } = processedBlock?.header || {};
|
|
334
|
+
partNumber = partNumber ? parseInt(partNumber) + 1 : 2; // Default to part 2 if not specified
|
|
335
|
+
processedBlock.continuationInfo = extractContinuationInfo(processedBlock.content, partNumber, language, processedBlock.header);
|
|
336
|
+
} catch (continuationError) {
|
|
337
|
+
allWarnings.push({
|
|
338
|
+
position: block.opening?.position || -1,
|
|
339
|
+
type: 'continuation_info_error',
|
|
340
|
+
message: `Error extracting continuation info: ${continuationError.message}`,
|
|
341
|
+
content: content
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
processedBlocks.push(processedBlock);
|
|
347
|
+
if (processedBlock.warnings) {
|
|
348
|
+
allWarnings.push(...processedBlock.warnings);
|
|
349
|
+
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
// Catch errors during the processing of a single incomplete block
|
|
352
|
+
allWarnings.push({
|
|
353
|
+
position: block.opening?.position || -1,
|
|
354
|
+
type: 'incomplete_block_processing_error',
|
|
355
|
+
message: `Error processing incomplete block: ${error.message}`,
|
|
356
|
+
content: text.substring(block.opening?.position) || "Error retrieving block content"
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Return processed blocks and collected warnings
|
|
362
|
+
// Note: The caller (processCodeBlocks) will handle adding these warnings to its own return object.
|
|
363
|
+
return { processedBlocks, allWarnings };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Core function to process code blocks with shared logic
|
|
369
|
+
* @param {string} text - The input text containing code blocks
|
|
370
|
+
* @param {Object} options - Processing options (e.g., silent, validatePatches)
|
|
371
|
+
* @returns {Object} { blocks: Array, warnings: Array, hasIncompleteBlocks: boolean, lastIncompleteBlock: Object|null }
|
|
372
|
+
*/
|
|
373
|
+
function processCodeBlocks(text, options = { silent: false, validatePatches: false }) {
|
|
374
|
+
if (typeof text !== "string") { // Allow empty strings
|
|
375
|
+
console.warn("Warning: Input must be a string.");
|
|
376
|
+
return {
|
|
377
|
+
blocks: [],
|
|
378
|
+
warnings: [{ type: 'invalid_input', message: 'Input must be a string.' }],
|
|
379
|
+
hasIncompleteBlocks: false,
|
|
380
|
+
lastIncompleteBlock: null }
|
|
381
|
+
;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (text.trim() === "") {
|
|
385
|
+
return { blocks: [], warnings: [], hasIncompleteBlocks: false, lastIncompleteBlock: null };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Step 1: Find all fence positions
|
|
389
|
+
const { openingPositions, closingPositions } = findAllCodeFences(text);
|
|
390
|
+
|
|
391
|
+
// Step 2: Match fences to get potential block boundaries
|
|
392
|
+
const { completeBlocks, incompleteBlocks, warnings: extractorWarnings } = matchFencesAndExtractBlocks(
|
|
393
|
+
text, openingPositions, closingPositions
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// Step 3: Process the content of these potential blocks
|
|
397
|
+
const { processedBlocks, allWarnings: processorWarnings } = processBlockContents(
|
|
398
|
+
text, completeBlocks, incompleteBlocks, options
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
// Combine warnings from extractor and processor
|
|
402
|
+
const allWarnings = [...extractorWarnings, ...processorWarnings];
|
|
403
|
+
|
|
404
|
+
// Log warnings if not silent
|
|
405
|
+
if (!options.silent && allWarnings.length > 0) {
|
|
406
|
+
console.warn('\nCode Block Processing Warnings:');
|
|
407
|
+
allWarnings.forEach((warning, index) => {
|
|
408
|
+
console.warn(`\nWarning ${index + 1}:`);
|
|
409
|
+
console.warn(`Type: ${warning.type}`);
|
|
410
|
+
console.warn(`Message: ${warning.message}`);
|
|
411
|
+
console.warn(`Position: ${warning.position}`);
|
|
412
|
+
if (warning.content) {
|
|
413
|
+
// Limit logged content length
|
|
414
|
+
const maxLogLength = 200;
|
|
415
|
+
const loggedContent = warning.content.length > maxLogLength
|
|
416
|
+
? warning.content.substring(0, maxLogLength) + '...'
|
|
417
|
+
: warning.content;
|
|
418
|
+
console.warn(`Content Hint: ${loggedContent}`);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const hasIncompleteBlocks = incompleteBlocks.length > 0;
|
|
424
|
+
const lastIncompleteBlock = hasIncompleteBlocks
|
|
425
|
+
? processedBlocks.find(block => block.incomplete) // Find the corresponding processed block
|
|
426
|
+
: null;
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
blocks: processedBlocks,
|
|
430
|
+
warnings: allWarnings,
|
|
431
|
+
hasIncompleteBlocks: hasIncompleteBlocks,
|
|
432
|
+
lastIncompleteBlock: lastIncompleteBlock
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Public API function - maintains backward compatibility for simple extraction.
|
|
438
|
+
* Use processCodeBlocks for more detailed results (warnings, incomplete status).
|
|
439
|
+
* @param {string} text - The input text containing code blocks
|
|
440
|
+
* @param {Object} options - Options for extraction (e.g., { silent: true })
|
|
441
|
+
* @returns {Object} - Object containing extracted blocks and warnings { blocks: Array, warnings: Array }
|
|
442
|
+
*/
|
|
443
|
+
function extractCodeBlocks(text, options = { silent: false, validatePatches: false, debug: false }) {
|
|
444
|
+
const { blocks, warnings } = processCodeBlocks(text, options);
|
|
445
|
+
return { blocks, warnings };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Fixes invalid UUIDs in code blocks within text
|
|
451
|
+
* @param {string} text - The input text containing code blocks
|
|
452
|
+
* @returns {Object} - Object containing fixed text and whether changes were made { text: string, modified: boolean }
|
|
453
|
+
*/
|
|
454
|
+
function fixTextCodeBlocks(text) {
|
|
455
|
+
if (typeof text !== "string" || text.trim() === "") {
|
|
456
|
+
return {
|
|
457
|
+
text: text,
|
|
458
|
+
modified: false
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let modified = false;
|
|
463
|
+
let fixedText = text;
|
|
464
|
+
|
|
465
|
+
// Regex to match code blocks with their language
|
|
466
|
+
const codeBlockRegex = /```([a-zA-Z-]*)\n([\s\S]*?)\n```/g;
|
|
467
|
+
|
|
468
|
+
// Store all matches to process in reverse
|
|
469
|
+
const matches = Array.from(text.matchAll(codeBlockRegex));
|
|
470
|
+
|
|
471
|
+
// Process matches in reverse to maintain correct positions
|
|
472
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
473
|
+
const match = matches[i];
|
|
474
|
+
const fullMatch = match[0];
|
|
475
|
+
const language = match[1];
|
|
476
|
+
const blockContent = match[2];
|
|
477
|
+
let blockModified = false;
|
|
478
|
+
|
|
479
|
+
// Check if this is a patch block or a gitsense-search-flow block (if they contain UUIDs)
|
|
480
|
+
if (PatchUtils.isPatchBlock(blockContent)) {
|
|
481
|
+
// Process patch block UUIDs
|
|
482
|
+
const lines = blockContent.split('\n');
|
|
483
|
+
let blockModified = false;
|
|
484
|
+
|
|
485
|
+
const newLines = lines.map(line => {
|
|
486
|
+
// Check for UUID fields in patch metadata
|
|
487
|
+
if ((line.includes('# Source-Block-UUID:') || line.includes('# Target-Block-UUID:')) &&
|
|
488
|
+
!line.includes('{{GS-UUID}')) {
|
|
489
|
+
|
|
490
|
+
const [fieldPart, valuePart] = line.split(':').map(p => p.trim());
|
|
491
|
+
|
|
492
|
+
// Validate UUID
|
|
493
|
+
const validation = validateUUID(valuePart);
|
|
494
|
+
if (validation["Block-UUID"] === "INVALID UUID") {
|
|
495
|
+
blockModified = true;
|
|
496
|
+
modified = true;
|
|
497
|
+
return `${fieldPart}: ${validation["Correct Block-UUID"]}`;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return line;
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// If this block was modified, replace it in the text
|
|
504
|
+
if (blockModified) {
|
|
505
|
+
const newBlock = `\`\`\`${language}\n${newLines.join('\n')}\n\`\`\``;
|
|
506
|
+
fixedText = fixedText.substring(0, match.index) +
|
|
507
|
+
newBlock +
|
|
508
|
+
fixedText.substring(match.index + fullMatch.length);
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
// Process regular code block or gitsense-search-flow block for UUIDs
|
|
512
|
+
// Split block content into lines to check for header/UUIDs
|
|
513
|
+
const lines = blockContent.split('\n');
|
|
514
|
+
let blockModified = false;
|
|
515
|
+
|
|
516
|
+
// Process each line
|
|
517
|
+
const newLines = lines.map(line => {
|
|
518
|
+
// Check for UUID fields
|
|
519
|
+
if (line.includes(' Block-UUID: ') || line.includes(' Parent-UUID: ')) {
|
|
520
|
+
const [fieldPart, valuePart] = line.split(':').map(p => p.trim());
|
|
521
|
+
|
|
522
|
+
// Skip if N/A or contains unexpected characters
|
|
523
|
+
if (valuePart === 'N/A' || valuePart.match(/\(/)) {
|
|
524
|
+
return line;
|
|
525
|
+
}
|
|
526
|
+
// Validate UUID
|
|
527
|
+
const validation = validateUUID(valuePart);
|
|
528
|
+
|
|
529
|
+
if (validation["Block-UUID"] === "INVALID UUID") {
|
|
530
|
+
blockModified = true;
|
|
531
|
+
modified = true;
|
|
532
|
+
return `${fieldPart}: ${validation["Correct Block-UUID"]}`;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return line;
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// If this block was modified, replace it in the text
|
|
539
|
+
if (blockModified) {
|
|
540
|
+
const newBlock = `\`\`\`${language}\n${newLines.join('\n')}\n\`\`\``;
|
|
541
|
+
fixedText = fixedText.substring(0, match.index) +
|
|
542
|
+
newBlock +
|
|
543
|
+
fixedText.substring(match.index + fullMatch.length);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
text: fixedText,
|
|
550
|
+
modified: modified
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
module.exports = {
|
|
556
|
+
processCodeBlocks,
|
|
557
|
+
extractCodeBlocks,
|
|
558
|
+
fixTextCodeBlocks,
|
|
559
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
@@ -262,6 +283,7 @@
|
|
2
|
+
function processCodeBlocks(text, options = { silent: false, validatePatches: false }) {
|
|
3
|
+
if (typeof text !== "string") { // Allow empty strings
|
|
4
|
+
console.warn("Warning: Input must be a string.");
|
|
5
|
+
+ return { blocks: [], warnings: [{ type: 'invalid_input', message: 'Input must be a string.' }], hasIncompleteBlocks: false, lastIncompleteBlock: null };
|
|
6
|
+
}
|
|
7
|
+
if (text.trim() === "") {
|
|
8
|
+
return { blocks: [], warnings: [], hasIncompleteBlocks: false, lastIncompleteBlock: null };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: CodeBlockUtils Constants
|
|
3
|
+
* Block-UUID: 01b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d
|
|
4
|
+
* Parent-UUID: 144d19a5-139a-4e80-8abe-84965583e35e
|
|
5
|
+
* Version: 1.0.1
|
|
6
|
+
* Description: Defines constants used across the CodeBlockUtils modules, such as comment styles for various languages.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-04-15T15:51:16.973Z
|
|
9
|
+
* Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Flash Thinking (v1.0.1)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Defines comment styles for various programming languages and block types.
|
|
14
|
+
const COMMENT_STYLES = {
|
|
15
|
+
// C-style comments
|
|
16
|
+
'javascript': { type: 'c-style' },
|
|
17
|
+
'typescript': { type: 'c-style' },
|
|
18
|
+
'java': { type: 'c-style' },
|
|
19
|
+
'c': { type: 'c-style' },
|
|
20
|
+
'cpp': { type: 'c-style' },
|
|
21
|
+
'csharp': { type: 'c-style' },
|
|
22
|
+
'php': { type: 'c-style' },
|
|
23
|
+
'swift': { type: 'c-style' },
|
|
24
|
+
'kotlin': { type: 'c-style' },
|
|
25
|
+
'scala': { type: 'c-style' },
|
|
26
|
+
'rust': { type: 'c-style' },
|
|
27
|
+
'go': { type: 'c-style' },
|
|
28
|
+
|
|
29
|
+
// Custom GitSense block types
|
|
30
|
+
'gitsense-search-flow': { type: 'none' },
|
|
31
|
+
|
|
32
|
+
// Hash-style comments
|
|
33
|
+
'python': { type: 'hash' },
|
|
34
|
+
'ruby': { type: 'ruby-block' },
|
|
35
|
+
'perl': { type: 'hash' },
|
|
36
|
+
'bash': { type: 'hash' },
|
|
37
|
+
'shell': { type: 'hash' },
|
|
38
|
+
'yaml': { type: 'hash' },
|
|
39
|
+
'powershell': { type: 'hash' },
|
|
40
|
+
'r': { type: 'hash' },
|
|
41
|
+
'julia': { type: 'hash' },
|
|
42
|
+
|
|
43
|
+
// Other language styles
|
|
44
|
+
'lisp': { type: 'semicolon' },
|
|
45
|
+
'clojure': { type: 'semicolon' },
|
|
46
|
+
'scheme': { type: 'semicolon' },
|
|
47
|
+
'ada': { type: 'ada' },
|
|
48
|
+
'lua': { type: 'lua' },
|
|
49
|
+
'matlab': { type: 'percent' },
|
|
50
|
+
'octave': { type: 'percent' },
|
|
51
|
+
'haskell': { type: 'haskell' },
|
|
52
|
+
'sql': { type: 'sql' },
|
|
53
|
+
'fortran': { type: 'exclamation' },
|
|
54
|
+
'vb': { type: 'apostrophe' },
|
|
55
|
+
'vbscript': { type: 'apostrophe' }
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
COMMENT_STYLES
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
|