@ai-dev-tools/csharp-copilot-core 0.0.37 → 0.0.38
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/out/gen/autoFix.d.ts +1 -1
- package/out/gen/autoFix.js +8 -3
- package/out/gen/autoFix.js.map +1 -1
- package/out/gen/ensureValidLLMResponse.d.ts +1 -1
- package/out/gen/ensureValidLLMResponse.js +6 -2
- package/out/gen/ensureValidLLMResponse.js.map +1 -1
- package/out/gen/postGen/postGenMoreUTAutofixProcess.d.ts +17 -0
- package/out/gen/postGen/postGenMoreUTAutofixProcess.js +475 -0
- package/out/gen/postGen/postGenMoreUTAutofixProcess.js.map +1 -0
- package/out/gen/postGen/postGenMoreUTProcess.d.ts +41 -0
- package/out/gen/postGen/postGenMoreUTProcess.js +14 -2
- package/out/gen/postGen/postGenMoreUTProcess.js.map +1 -1
- package/out/llm/prompt/moreUT/generateMoreUtAutoFix.liquid +1 -0
- package/out/utils/getTestFile.js +1 -1
- package/out/utils/getTestFile.js.map +1 -1
- package/out/utils/moreUTAutoFixUtil.d.ts +7 -0
- package/out/utils/moreUTAutoFixUtil.js +51 -0
- package/out/utils/moreUTAutoFixUtil.js.map +1 -0
- package/out/utils/removeFailedTestMethods.d.ts +12 -0
- package/out/utils/removeFailedTestMethods.js +18 -16
- package/out/utils/removeFailedTestMethods.js.map +1 -1
- package/out/utils/writeGenCode.d.ts +5 -0
- package/out/utils/writeGenCode.js +15 -0
- package/out/utils/writeGenCode.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.postGenMoreUTAutofixProcess = postGenMoreUTAutofixProcess;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const extractCodeFromResponse_1 = require("./extractCodeFromResponse");
|
|
39
|
+
const removeSingleLines_1 = require("./removeSingleLines");
|
|
40
|
+
const repairRequiredNameSpaces_1 = require("./repairRequiredNameSpaces");
|
|
41
|
+
const postGenProcess_1 = require("./postGenProcess");
|
|
42
|
+
const postGenMoreUTProcess_1 = require("./postGenMoreUTProcess");
|
|
43
|
+
const removeFailedTestMethods_1 = require("../../utils/removeFailedTestMethods");
|
|
44
|
+
const writeGenCode_1 = require("../../utils/writeGenCode");
|
|
45
|
+
// ─── Public entry point ─────────────────────────────────────────────────────────
|
|
46
|
+
/**
|
|
47
|
+
* Post-process LLM response for the "more UT auto-fix" scenario.
|
|
48
|
+
*
|
|
49
|
+
* The LLM is asked to return the **complete** fixed test file, but it sometimes
|
|
50
|
+
* omits unchanged test methods. This function performs a *surgical merge*:
|
|
51
|
+
*
|
|
52
|
+
* 1. Extract & clean the AI code from the markdown response.
|
|
53
|
+
* 2. Replace **only** the error test methods in the original file with the
|
|
54
|
+
* AI-fixed versions.
|
|
55
|
+
* 3. Append any **new** test methods or helper methods that the AI added.
|
|
56
|
+
* 4. Replace the `using` block wholesale with the AI's version.
|
|
57
|
+
* 5. Apply standard namespace / Xap repairs.
|
|
58
|
+
*
|
|
59
|
+
* If `errorMethodNames` is empty or `testFilePath` is unavailable the function
|
|
60
|
+
* falls back to standard `postGenProcess`-style processing.
|
|
61
|
+
*/
|
|
62
|
+
function postGenMoreUTAutofixProcess(generatedCode, testFramework, isXapCode, sourceCode, testFilePath, errorMethodNames) {
|
|
63
|
+
// ── Step 1 : Extract code from the AI response ──────────────────────────
|
|
64
|
+
let aiCode = (0, extractCodeFromResponse_1.extractCodeFromResponse)(generatedCode);
|
|
65
|
+
if (!aiCode) {
|
|
66
|
+
console.error('postGenMoreUTAutofixProcess: failed to extract code from AI response');
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
aiCode = (0, removeSingleLines_1.removeCSharpOnlyLines)(aiCode);
|
|
70
|
+
// Write AI raw response to atmpCache for debugging / auditing
|
|
71
|
+
(0, writeGenCode_1.writeAIRawCode)(aiCode, testFilePath);
|
|
72
|
+
// ── Step 2 : Fallback when surgical merge is not possible ───────────────
|
|
73
|
+
if (!errorMethodNames || errorMethodNames.length === 0) {
|
|
74
|
+
console.log('postGenMoreUTAutofixProcess: no error method names, returning undefined');
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
if (!testFilePath) {
|
|
78
|
+
console.log('postGenMoreUTAutofixProcess: no test file path, returning undefined');
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
if (!fs.existsSync(testFilePath)) {
|
|
82
|
+
console.log(`postGenMoreUTAutofixProcess: test file not found (${testFilePath}), returning undefined`);
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
// ── Step 3 : Read and normalise the original test file ──────────────────
|
|
86
|
+
const originalCode = fs.readFileSync(testFilePath, 'utf-8');
|
|
87
|
+
const lineEnding = (0, postGenMoreUTProcess_1.detectLineEnding)(originalCode);
|
|
88
|
+
const normalizedAi = aiCode.replace(/\r\n/g, '\n');
|
|
89
|
+
const normalizedOriginal = originalCode.replace(/\r\n/g, '\n');
|
|
90
|
+
let resultLines = normalizedOriginal.split('\n');
|
|
91
|
+
const aiLines = normalizedAi.split('\n');
|
|
92
|
+
// ── Step 4 : Enumerate test methods in both files ───────────────────────
|
|
93
|
+
const originalMethods = enumerateTestMethods(resultLines);
|
|
94
|
+
const aiMethods = enumerateTestMethods(aiLines);
|
|
95
|
+
console.log(`postGenMoreUTAutofixProcess: original has ${originalMethods.length} test method(s), AI has ${aiMethods.length} test method(s)`);
|
|
96
|
+
console.log(`postGenMoreUTAutofixProcess: error methods: [${errorMethodNames.join(', ')}]`);
|
|
97
|
+
// ── Step 5 : Replace error methods (bottom-to-top for index stability) ──
|
|
98
|
+
// NOTE: This must happen BEFORE replaceUsingBlock, because originalMethods
|
|
99
|
+
// indices were computed on the current resultLines. If the using block
|
|
100
|
+
// has a different line count the indices would become stale.
|
|
101
|
+
const baseIndent = (0, postGenMoreUTProcess_1.detectTestMethodIndent)(resultLines);
|
|
102
|
+
const replacements = errorMethodNames
|
|
103
|
+
.map(name => ({
|
|
104
|
+
name,
|
|
105
|
+
originalRange: originalMethods.find(m => m.name === name),
|
|
106
|
+
aiRange: aiMethods.find(m => m.name === name),
|
|
107
|
+
}))
|
|
108
|
+
.filter(r => r.originalRange && r.aiRange)
|
|
109
|
+
.sort((a, b) => b.originalRange.start - a.originalRange.start); // descending
|
|
110
|
+
// If the AI didn't produce any of the error methods, the response is useless — signal retry
|
|
111
|
+
if (replacements.length === 0) {
|
|
112
|
+
const missingInAi = errorMethodNames.filter(n => !aiMethods.some(m => m.name === n));
|
|
113
|
+
console.error(`postGenMoreUTAutofixProcess: AI response contains none of the ${errorMethodNames.length} ` +
|
|
114
|
+
`error method(s), missing: [${missingInAi.join(', ')}]. Returning undefined to trigger retry.`);
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
for (const { name, originalRange, aiRange } of replacements) {
|
|
118
|
+
const aiMethodText = aiLines.slice(aiRange.start, aiRange.end + 1);
|
|
119
|
+
const reindented = (0, postGenMoreUTProcess_1.applyIndentation)(aiMethodText.join('\n'), baseIndent).split('\n');
|
|
120
|
+
resultLines.splice(originalRange.start, originalRange.end - originalRange.start + 1, ...reindented);
|
|
121
|
+
console.log(` replaced error method '${name}' (orig ${originalRange.start}-${originalRange.end}) ` +
|
|
122
|
+
`with AI fix (${reindented.length} lines)`);
|
|
123
|
+
}
|
|
124
|
+
// Log any error methods that couldn't be matched
|
|
125
|
+
for (const name of errorMethodNames) {
|
|
126
|
+
const inOrig = originalMethods.some(m => m.name === name);
|
|
127
|
+
const inAi = aiMethods.some(m => m.name === name);
|
|
128
|
+
if (!inOrig)
|
|
129
|
+
console.warn(` error method '${name}' not found in original file`);
|
|
130
|
+
if (!inAi)
|
|
131
|
+
console.warn(` error method '${name}' not found in AI response`);
|
|
132
|
+
}
|
|
133
|
+
// ── Step 6 : Replace using block ────────────────────────────────────────
|
|
134
|
+
// Done after method replacements so that originalMethods indices stay valid.
|
|
135
|
+
resultLines = replaceUsingBlock(resultLines, aiLines);
|
|
136
|
+
// ── Step 7 : Append new test methods from AI ────────────────────────────
|
|
137
|
+
const originalMethodNames = new Set(originalMethods.map(m => m.name));
|
|
138
|
+
const newTestMethods = aiMethods.filter(m => !originalMethodNames.has(m.name));
|
|
139
|
+
if (newTestMethods.length > 0) {
|
|
140
|
+
resultLines = appendMethodBlocks(resultLines, aiLines, newTestMethods, baseIndent, 'new test method');
|
|
141
|
+
}
|
|
142
|
+
// ── Step 8 : Append new helper (non-test) methods from AI ───────────────
|
|
143
|
+
const newHelpers = findNewHelperMethods(resultLines, aiLines, aiMethods);
|
|
144
|
+
if (newHelpers.length > 0) {
|
|
145
|
+
resultLines = appendMethodBlocks(resultLines, aiLines, newHelpers, baseIndent, 'new helper method');
|
|
146
|
+
}
|
|
147
|
+
// ── Step 9 : Standard namespace / Xap / alias repairs ───────────────────
|
|
148
|
+
let resultCode = resultLines.join('\n');
|
|
149
|
+
resultCode = (0, repairRequiredNameSpaces_1.repairRequiredNameSpaces)(resultCode, testFramework);
|
|
150
|
+
if (isXapCode) {
|
|
151
|
+
resultCode = (0, repairRequiredNameSpaces_1.repairXapRequiredNameSpaces)(resultCode);
|
|
152
|
+
}
|
|
153
|
+
if (sourceCode) {
|
|
154
|
+
resultCode = (0, repairRequiredNameSpaces_1.repairUsingStatements)(resultCode, sourceCode);
|
|
155
|
+
}
|
|
156
|
+
// ── Step 10 : Restore original line-ending style ────────────────────────
|
|
157
|
+
resultCode = resultCode.replace(/\n/g, lineEnding);
|
|
158
|
+
console.log(`postGenMoreUTAutofixProcess: completed, code length: ${resultCode.length}`);
|
|
159
|
+
return resultCode;
|
|
160
|
+
}
|
|
161
|
+
// ─── Internal helpers ───────────────────────────────────────────────────────────
|
|
162
|
+
/**
|
|
163
|
+
* Standard fallback: apply the same processing as `postGenProcess`.
|
|
164
|
+
*/
|
|
165
|
+
function fallbackPostProcess(aiCode, testFramework, isXapCode, sourceCode) {
|
|
166
|
+
return (0, postGenProcess_1.postGenProcess)(aiCode, testFramework, isXapCode, sourceCode);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Enumerate all test methods in the file by scanning for test-attribute lines
|
|
170
|
+
* and using `findTestMethodRangeByBrace` to determine their full extent
|
|
171
|
+
* (including all attributes above the method).
|
|
172
|
+
*/
|
|
173
|
+
function enumerateTestMethods(lines) {
|
|
174
|
+
const testPatterns = [
|
|
175
|
+
/\[TestMethod/i,
|
|
176
|
+
/\[Test\]/i,
|
|
177
|
+
/\[Test\(/i,
|
|
178
|
+
/\[Fact/i,
|
|
179
|
+
/\[Theory/i,
|
|
180
|
+
];
|
|
181
|
+
const methods = [];
|
|
182
|
+
const visitedStarts = new Set();
|
|
183
|
+
for (let i = 0; i < lines.length; i++) {
|
|
184
|
+
const trimmed = lines[i].trim();
|
|
185
|
+
const isTestAttr = testPatterns.some(p => p.test(trimmed));
|
|
186
|
+
if (!isTestAttr)
|
|
187
|
+
continue;
|
|
188
|
+
const range = (0, removeFailedTestMethods_1.findTestMethodRangeByBrace)(lines, i);
|
|
189
|
+
if (!range || visitedStarts.has(range.start))
|
|
190
|
+
continue;
|
|
191
|
+
visitedStarts.add(range.start);
|
|
192
|
+
const name = findMethodNameInRange(lines, range.start, range.end);
|
|
193
|
+
if (name) {
|
|
194
|
+
methods.push({ name, start: range.start, end: range.end });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return methods;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Find the method name within a known method range by skipping attribute /
|
|
201
|
+
* empty lines and reading the first real code line.
|
|
202
|
+
*/
|
|
203
|
+
function findMethodNameInRange(lines, start, end) {
|
|
204
|
+
for (let i = start; i <= end; i++) {
|
|
205
|
+
const trimmed = lines[i].trim();
|
|
206
|
+
if (trimmed === '' || trimmed.startsWith('['))
|
|
207
|
+
continue;
|
|
208
|
+
// First non-attribute, non-empty line is the method signature
|
|
209
|
+
const name = (0, removeFailedTestMethods_1.extractMethodName)(trimmed);
|
|
210
|
+
if (name)
|
|
211
|
+
return name;
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
// ─── Using-block replacement ────────────────────────────────────────────────────
|
|
216
|
+
/**
|
|
217
|
+
* Find the contiguous `using` directive block at the top of the file.
|
|
218
|
+
* Returns inclusive 0-based indices, or `null` when no usings are found.
|
|
219
|
+
*
|
|
220
|
+
* Skips blank lines and single-line comments (`//`) that appear between using
|
|
221
|
+
* directives so that groups like:
|
|
222
|
+
*
|
|
223
|
+
* using System;
|
|
224
|
+
* // collections
|
|
225
|
+
* using System.Collections.Generic;
|
|
226
|
+
*
|
|
227
|
+
* are treated as one block.
|
|
228
|
+
*/
|
|
229
|
+
function findUsingBlockRange(lines) {
|
|
230
|
+
let start = -1;
|
|
231
|
+
let end = -1;
|
|
232
|
+
for (let i = 0; i < lines.length; i++) {
|
|
233
|
+
const trimmed = lines[i].trim();
|
|
234
|
+
if (isUsingDirective(trimmed)) {
|
|
235
|
+
if (start === -1)
|
|
236
|
+
start = i;
|
|
237
|
+
end = i;
|
|
238
|
+
}
|
|
239
|
+
else if (start !== -1) {
|
|
240
|
+
// Allow blank lines and single-line comments inside the using block
|
|
241
|
+
if (trimmed === '' || trimmed.startsWith('//'))
|
|
242
|
+
continue;
|
|
243
|
+
// Any other content means the using block has ended
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return start === -1 ? null : { start, end };
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Check whether a trimmed line is a `using` directive (not a `using` statement
|
|
251
|
+
* such as `using (var x = ...)`).
|
|
252
|
+
*/
|
|
253
|
+
function isUsingDirective(trimmed) {
|
|
254
|
+
return (trimmed.startsWith('using ') || trimmed.startsWith('using\t'))
|
|
255
|
+
&& !trimmed.includes('(')
|
|
256
|
+
&& trimmed.endsWith(';');
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Replace the original file's using block with the AI response's using block.
|
|
260
|
+
* If the AI has no usings the original is returned unchanged.
|
|
261
|
+
*/
|
|
262
|
+
function replaceUsingBlock(originalLines, aiLines) {
|
|
263
|
+
const aiRange = findUsingBlockRange(aiLines);
|
|
264
|
+
if (!aiRange)
|
|
265
|
+
return [...originalLines];
|
|
266
|
+
const aiUsings = aiLines.slice(aiRange.start, aiRange.end + 1);
|
|
267
|
+
const origRange = findUsingBlockRange(originalLines);
|
|
268
|
+
const result = [...originalLines];
|
|
269
|
+
if (origRange) {
|
|
270
|
+
result.splice(origRange.start, origRange.end - origRange.start + 1, ...aiUsings);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
// No usings in original – prepend
|
|
274
|
+
result.splice(0, 0, ...aiUsings, '');
|
|
275
|
+
}
|
|
276
|
+
console.log(` replaced using block (${aiUsings.length} lines from AI)`);
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
// ─── Appending new method blocks ────────────────────────────────────────────────
|
|
280
|
+
/**
|
|
281
|
+
* Append method blocks (described by `methods` referencing `aiLines`) into
|
|
282
|
+
* `resultLines` right after the last test method.
|
|
283
|
+
*/
|
|
284
|
+
function appendMethodBlocks(resultLines, aiLines, methods, baseIndent, label) {
|
|
285
|
+
let insertLine = (0, postGenMoreUTProcess_1.findLastTestMethodEndLine)(resultLines);
|
|
286
|
+
if (insertLine === 0) {
|
|
287
|
+
insertLine = (0, postGenMoreUTProcess_1.findTestClassInsertPosition)(resultLines);
|
|
288
|
+
}
|
|
289
|
+
if (insertLine === 0) {
|
|
290
|
+
console.warn(` could not find insert position for ${label}(s), skipping`);
|
|
291
|
+
return resultLines;
|
|
292
|
+
}
|
|
293
|
+
const insertIndex = insertLine - 1; // 1-based → 0-based
|
|
294
|
+
const newLines = [];
|
|
295
|
+
for (const m of methods) {
|
|
296
|
+
const raw = aiLines.slice(m.start, m.end + 1).join('\n');
|
|
297
|
+
const reindented = (0, postGenMoreUTProcess_1.applyIndentation)(raw, baseIndent);
|
|
298
|
+
newLines.push('', ...reindented.split('\n'));
|
|
299
|
+
console.log(` appended ${label} '${m.name}' (${m.end - m.start + 1} lines)`);
|
|
300
|
+
}
|
|
301
|
+
const result = [...resultLines];
|
|
302
|
+
result.splice(insertIndex, 0, ...newLines);
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
// ─── New helper method detection ────────────────────────────────────────────────
|
|
306
|
+
/**
|
|
307
|
+
* Find non-test methods in the AI response that do **not** exist in the
|
|
308
|
+
* original / result file. These are typically helper or factory methods that
|
|
309
|
+
* the AI introduced to support the fixed test code.
|
|
310
|
+
*
|
|
311
|
+
* Detection strategy:
|
|
312
|
+
* 1. Mark every line in `aiLines` that belongs to a known test method.
|
|
313
|
+
* 2. Inside the class body, scan the unclaimed lines for method signatures.
|
|
314
|
+
* 3. Brace-match to determine the full method extent.
|
|
315
|
+
* 4. Keep only methods whose name does not appear in `resultLines`.
|
|
316
|
+
*/
|
|
317
|
+
function findNewHelperMethods(resultLines, aiLines, aiTestMethods) {
|
|
318
|
+
// Lines already claimed by test methods
|
|
319
|
+
const claimed = new Set();
|
|
320
|
+
for (const m of aiTestMethods) {
|
|
321
|
+
for (let i = m.start; i <= m.end; i++)
|
|
322
|
+
claimed.add(i);
|
|
323
|
+
}
|
|
324
|
+
// Approximate class body range in AI: from first `{` after a `class` keyword
|
|
325
|
+
// to the matching `}`.
|
|
326
|
+
const classBody = findClassBodyRange(aiLines);
|
|
327
|
+
if (!classBody)
|
|
328
|
+
return [];
|
|
329
|
+
// Collect existing method names from the result file for comparison
|
|
330
|
+
const existingNames = collectAllMethodNames(resultLines);
|
|
331
|
+
const methodSigPattern = /^\s*(public|private|protected|internal)\s+/;
|
|
332
|
+
const helpers = [];
|
|
333
|
+
const attributePattern = /^\s*\[.+\]\s*$/;
|
|
334
|
+
for (let i = classBody.start; i <= classBody.end; i++) {
|
|
335
|
+
if (claimed.has(i))
|
|
336
|
+
continue;
|
|
337
|
+
const line = aiLines[i];
|
|
338
|
+
if (!methodSigPattern.test(line))
|
|
339
|
+
continue;
|
|
340
|
+
// Must look like a method: name followed by `(`
|
|
341
|
+
const name = (0, removeFailedTestMethods_1.extractMethodName)(line.trim());
|
|
342
|
+
if (!name || existingNames.has(name))
|
|
343
|
+
continue;
|
|
344
|
+
// Check this isn't a property (contains `{ get` or `{ set` on same/next line)
|
|
345
|
+
if (isPropertyDeclaration(aiLines, i))
|
|
346
|
+
continue;
|
|
347
|
+
// Brace-match to find method end
|
|
348
|
+
const end = braceMatchForward(aiLines, i, classBody.end);
|
|
349
|
+
if (end === -1)
|
|
350
|
+
continue;
|
|
351
|
+
// Search upward for preceding attributes
|
|
352
|
+
let start = i;
|
|
353
|
+
for (let k = i - 1; k >= classBody.start; k--) {
|
|
354
|
+
const t = aiLines[k].trim();
|
|
355
|
+
if (attributePattern.test(t)) {
|
|
356
|
+
start = k;
|
|
357
|
+
}
|
|
358
|
+
else if (t === '') { /* skip blank lines between attrs */ }
|
|
359
|
+
else
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
helpers.push({ name, start, end });
|
|
363
|
+
// Mark as claimed so we don't double-count
|
|
364
|
+
for (let k = start; k <= end; k++)
|
|
365
|
+
claimed.add(k);
|
|
366
|
+
}
|
|
367
|
+
return helpers;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Find the class body range (the lines between the opening `{` of the test
|
|
371
|
+
* class and its matching `}`). Returns 0-based inclusive indices.
|
|
372
|
+
*/
|
|
373
|
+
function findClassBodyRange(lines) {
|
|
374
|
+
const classPattern = /^\s*(public\s+)?(partial\s+)?(static\s+)?class\s+/i;
|
|
375
|
+
let classLineIdx = -1;
|
|
376
|
+
for (let i = 0; i < lines.length; i++) {
|
|
377
|
+
if (classPattern.test(lines[i])) {
|
|
378
|
+
classLineIdx = i;
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (classLineIdx === -1)
|
|
383
|
+
return null;
|
|
384
|
+
// Find opening brace
|
|
385
|
+
let openIdx = -1;
|
|
386
|
+
for (let i = classLineIdx; i < lines.length; i++) {
|
|
387
|
+
if (lines[i].includes('{')) {
|
|
388
|
+
openIdx = i;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (openIdx === -1)
|
|
393
|
+
return null;
|
|
394
|
+
// Brace-match to find the class closing brace
|
|
395
|
+
let depth = 0;
|
|
396
|
+
let closeIdx = -1;
|
|
397
|
+
for (let i = openIdx; i < lines.length; i++) {
|
|
398
|
+
for (const ch of lines[i]) {
|
|
399
|
+
if (ch === '{')
|
|
400
|
+
depth++;
|
|
401
|
+
if (ch === '}') {
|
|
402
|
+
depth--;
|
|
403
|
+
if (depth === 0) {
|
|
404
|
+
closeIdx = i;
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (closeIdx !== -1)
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
if (closeIdx === -1)
|
|
413
|
+
return null;
|
|
414
|
+
return { start: openIdx + 1, end: closeIdx - 1 };
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Collect all method-like names from a file (test + non-test) for de-duplication.
|
|
418
|
+
*/
|
|
419
|
+
function collectAllMethodNames(lines) {
|
|
420
|
+
const names = new Set();
|
|
421
|
+
const methodSigPattern = /^\s*(public|private|protected|internal)\s+/;
|
|
422
|
+
for (const line of lines) {
|
|
423
|
+
if (!methodSigPattern.test(line))
|
|
424
|
+
continue;
|
|
425
|
+
if (isLikelyFieldOrProperty(line))
|
|
426
|
+
continue;
|
|
427
|
+
const name = (0, removeFailedTestMethods_1.extractMethodName)(line.trim());
|
|
428
|
+
if (name)
|
|
429
|
+
names.add(name);
|
|
430
|
+
}
|
|
431
|
+
return names;
|
|
432
|
+
}
|
|
433
|
+
/** Simple heuristic to skip field or property declarations. */
|
|
434
|
+
function isLikelyFieldOrProperty(line) {
|
|
435
|
+
const trimmed = line.trim();
|
|
436
|
+
// Properties: `public int Foo { get; set; }`
|
|
437
|
+
if (/\{\s*(get|set)/.test(trimmed))
|
|
438
|
+
return true;
|
|
439
|
+
// Fields: `private readonly int _foo;` or `private int foo = ...;`
|
|
440
|
+
if (trimmed.endsWith(';') && !trimmed.includes('('))
|
|
441
|
+
return true;
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
/** Check whether line `i` starts a property (has `{ get` or `{ set`). */
|
|
445
|
+
function isPropertyDeclaration(lines, i) {
|
|
446
|
+
// Check current line and next line for property-style braces
|
|
447
|
+
for (let k = i; k < Math.min(i + 3, lines.length); k++) {
|
|
448
|
+
if (/\{\s*(get|set)/.test(lines[k]))
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Forward brace-match starting from line `start`. Returns the 0-based line
|
|
455
|
+
* index of the closing `}`, or -1 on failure.
|
|
456
|
+
*/
|
|
457
|
+
function braceMatchForward(lines, start, maxLine) {
|
|
458
|
+
let depth = 0;
|
|
459
|
+
let foundOpen = false;
|
|
460
|
+
for (let i = start; i <= maxLine; i++) {
|
|
461
|
+
for (const ch of lines[i]) {
|
|
462
|
+
if (ch === '{') {
|
|
463
|
+
depth++;
|
|
464
|
+
foundOpen = true;
|
|
465
|
+
}
|
|
466
|
+
if (ch === '}') {
|
|
467
|
+
depth--;
|
|
468
|
+
if (foundOpen && depth === 0)
|
|
469
|
+
return i;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return -1;
|
|
474
|
+
}
|
|
475
|
+
//# sourceMappingURL=postGenMoreUTAutofixProcess.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postGenMoreUTAutofixProcess.js","sourceRoot":"","sources":["../../../src/gen/postGen/postGenMoreUTAutofixProcess.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\r\n\r\nimport { extractCodeFromResponse } from './extractCodeFromResponse';\r\nimport { removeCSharpOnlyLines } from './removeSingleLines';\r\nimport { repairRequiredNameSpaces, repairXapRequiredNameSpaces, repairUsingStatements } from './repairRequiredNameSpaces';\r\nimport { postGenProcess } from './postGenProcess';\r\nimport {\r\n detectLineEnding,\r\n detectTestMethodIndent,\r\n applyIndentation,\r\n findLastTestMethodEndLine,\r\n findTestClassInsertPosition,\r\n} from './postGenMoreUTProcess';\r\nimport { findTestMethodRangeByBrace, extractMethodName } from '../../utils/removeFailedTestMethods';\r\nimport { writeAIRawCode } from '../../utils/writeGenCode';\r\n\r\n// ─── Types ──────────────────────────────────────────────────────────────────────\r\n\r\ninterface MethodInfo {\r\n name: string;\r\n start: number; // 0-based inclusive\r\n end: number; // 0-based inclusive\r\n}\r\n\r\n// ─── Public entry point ─────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Post-process LLM response for the \"more UT auto-fix\" scenario.\r\n *\r\n * The LLM is asked to return the **complete** fixed test file, but it sometimes\r\n * omits unchanged test methods. This function performs a *surgical merge*:\r\n *\r\n * 1. Extract & clean the AI code from the markdown response.\r\n * 2. Replace **only** the error test methods in the original file with the\r\n * AI-fixed versions.\r\n * 3. Append any **new** test methods or helper methods that the AI added.\r\n * 4. Replace the `using` block wholesale with the AI's version.\r\n * 5. Apply standard namespace / Xap repairs.\r\n *\r\n * If `errorMethodNames` is empty or `testFilePath` is unavailable the function\r\n * falls back to standard `postGenProcess`-style processing.\r\n */\r\nexport function postGenMoreUTAutofixProcess(\r\n generatedCode: string,\r\n testFramework: string,\r\n isXapCode: boolean,\r\n sourceCode?: string,\r\n testFilePath?: string,\r\n errorMethodNames?: string[],\r\n): string {\r\n // ── Step 1 : Extract code from the AI response ──────────────────────────\r\n let aiCode = extractCodeFromResponse(generatedCode);\r\n if (!aiCode) {\r\n console.error('postGenMoreUTAutofixProcess: failed to extract code from AI response');\r\n return undefined;\r\n }\r\n aiCode = removeCSharpOnlyLines(aiCode);\r\n\r\n // Write AI raw response to atmpCache for debugging / auditing\r\n writeAIRawCode(aiCode, testFilePath);\r\n\r\n // ── Step 2 : Fallback when surgical merge is not possible ───────────────\r\n if (!errorMethodNames || errorMethodNames.length === 0) {\r\n console.log('postGenMoreUTAutofixProcess: no error method names, returning undefined');\r\n return undefined;\r\n }\r\n if (!testFilePath) {\r\n console.log('postGenMoreUTAutofixProcess: no test file path, returning undefined');\r\n return undefined;\r\n }\r\n if (!fs.existsSync(testFilePath)) {\r\n console.log(`postGenMoreUTAutofixProcess: test file not found (${testFilePath}), returning undefined`);\r\n return undefined;\r\n }\r\n\r\n // ── Step 3 : Read and normalise the original test file ──────────────────\r\n const originalCode = fs.readFileSync(testFilePath, 'utf-8');\r\n const lineEnding = detectLineEnding(originalCode);\r\n\r\n const normalizedAi = aiCode.replace(/\\r\\n/g, '\\n');\r\n const normalizedOriginal = originalCode.replace(/\\r\\n/g, '\\n');\r\n let resultLines = normalizedOriginal.split('\\n');\r\n const aiLines = normalizedAi.split('\\n');\r\n\r\n // ── Step 4 : Enumerate test methods in both files ───────────────────────\r\n const originalMethods = enumerateTestMethods(resultLines);\r\n const aiMethods = enumerateTestMethods(aiLines);\r\n\r\n console.log(`postGenMoreUTAutofixProcess: original has ${originalMethods.length} test method(s), AI has ${aiMethods.length} test method(s)`);\r\n console.log(`postGenMoreUTAutofixProcess: error methods: [${errorMethodNames.join(', ')}]`);\r\n\r\n // ── Step 5 : Replace error methods (bottom-to-top for index stability) ──\r\n // NOTE: This must happen BEFORE replaceUsingBlock, because originalMethods\r\n // indices were computed on the current resultLines. If the using block\r\n // has a different line count the indices would become stale.\r\n const baseIndent = detectTestMethodIndent(resultLines);\r\n\r\n const replacements = errorMethodNames\r\n .map(name => ({\r\n name,\r\n originalRange: originalMethods.find(m => m.name === name),\r\n aiRange: aiMethods.find(m => m.name === name),\r\n }))\r\n .filter(r => r.originalRange && r.aiRange)\r\n .sort((a, b) => b.originalRange.start - a.originalRange.start); // descending\r\n\r\n // If the AI didn't produce any of the error methods, the response is useless — signal retry\r\n if (replacements.length === 0) {\r\n const missingInAi = errorMethodNames.filter(n => !aiMethods.some(m => m.name === n));\r\n console.error(\r\n `postGenMoreUTAutofixProcess: AI response contains none of the ${errorMethodNames.length} ` +\r\n `error method(s), missing: [${missingInAi.join(', ')}]. Returning undefined to trigger retry.`,\r\n );\r\n return undefined;\r\n }\r\n\r\n for (const { name, originalRange, aiRange } of replacements) {\r\n const aiMethodText = aiLines.slice(aiRange.start, aiRange.end + 1);\r\n const reindented = applyIndentation(aiMethodText.join('\\n'), baseIndent).split('\\n');\r\n resultLines.splice(\r\n originalRange.start,\r\n originalRange.end - originalRange.start + 1,\r\n ...reindented,\r\n );\r\n console.log(\r\n ` replaced error method '${name}' (orig ${originalRange.start}-${originalRange.end}) ` +\r\n `with AI fix (${reindented.length} lines)`,\r\n );\r\n }\r\n\r\n // Log any error methods that couldn't be matched\r\n for (const name of errorMethodNames) {\r\n const inOrig = originalMethods.some(m => m.name === name);\r\n const inAi = aiMethods.some(m => m.name === name);\r\n if (!inOrig) console.warn(` error method '${name}' not found in original file`);\r\n if (!inAi) console.warn(` error method '${name}' not found in AI response`);\r\n }\r\n\r\n // ── Step 6 : Replace using block ────────────────────────────────────────\r\n // Done after method replacements so that originalMethods indices stay valid.\r\n resultLines = replaceUsingBlock(resultLines, aiLines);\r\n\r\n // ── Step 7 : Append new test methods from AI ────────────────────────────\r\n const originalMethodNames = new Set(originalMethods.map(m => m.name));\r\n const newTestMethods = aiMethods.filter(m => !originalMethodNames.has(m.name));\r\n\r\n if (newTestMethods.length > 0) {\r\n resultLines = appendMethodBlocks(\r\n resultLines,\r\n aiLines,\r\n newTestMethods,\r\n baseIndent,\r\n 'new test method',\r\n );\r\n }\r\n\r\n // ── Step 8 : Append new helper (non-test) methods from AI ───────────────\r\n const newHelpers = findNewHelperMethods(resultLines, aiLines, aiMethods);\r\n if (newHelpers.length > 0) {\r\n resultLines = appendMethodBlocks(\r\n resultLines,\r\n aiLines,\r\n newHelpers,\r\n baseIndent,\r\n 'new helper method',\r\n );\r\n }\r\n\r\n // ── Step 9 : Standard namespace / Xap / alias repairs ───────────────────\r\n let resultCode = resultLines.join('\\n');\r\n resultCode = repairRequiredNameSpaces(resultCode, testFramework);\r\n if (isXapCode) {\r\n resultCode = repairXapRequiredNameSpaces(resultCode);\r\n }\r\n if (sourceCode) {\r\n resultCode = repairUsingStatements(resultCode, sourceCode);\r\n }\r\n\r\n // ── Step 10 : Restore original line-ending style ────────────────────────\r\n resultCode = resultCode.replace(/\\n/g, lineEnding);\r\n console.log(`postGenMoreUTAutofixProcess: completed, code length: ${resultCode.length}`);\r\n return resultCode;\r\n}\r\n\r\n// ─── Internal helpers ───────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Standard fallback: apply the same processing as `postGenProcess`.\r\n */\r\nfunction fallbackPostProcess(\r\n aiCode: string,\r\n testFramework: string,\r\n isXapCode: boolean,\r\n sourceCode?: string,\r\n): string {\r\n return postGenProcess(aiCode, testFramework, isXapCode, sourceCode);\r\n}\r\n\r\n/**\r\n * Enumerate all test methods in the file by scanning for test-attribute lines\r\n * and using `findTestMethodRangeByBrace` to determine their full extent\r\n * (including all attributes above the method).\r\n */\r\nfunction enumerateTestMethods(lines: string[]): MethodInfo[] {\r\n const testPatterns = [\r\n /\\[TestMethod/i,\r\n /\\[Test\\]/i,\r\n /\\[Test\\(/i,\r\n /\\[Fact/i,\r\n /\\[Theory/i,\r\n ];\r\n\r\n const methods: MethodInfo[] = [];\r\n const visitedStarts = new Set<number>();\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n const trimmed = lines[i].trim();\r\n const isTestAttr = testPatterns.some(p => p.test(trimmed));\r\n if (!isTestAttr) continue;\r\n\r\n const range = findTestMethodRangeByBrace(lines, i);\r\n if (!range || visitedStarts.has(range.start)) continue;\r\n visitedStarts.add(range.start);\r\n\r\n const name = findMethodNameInRange(lines, range.start, range.end);\r\n if (name) {\r\n methods.push({ name, start: range.start, end: range.end });\r\n }\r\n }\r\n\r\n return methods;\r\n}\r\n\r\n/**\r\n * Find the method name within a known method range by skipping attribute /\r\n * empty lines and reading the first real code line.\r\n */\r\nfunction findMethodNameInRange(lines: string[], start: number, end: number): string | null {\r\n for (let i = start; i <= end; i++) {\r\n const trimmed = lines[i].trim();\r\n if (trimmed === '' || trimmed.startsWith('[')) continue;\r\n // First non-attribute, non-empty line is the method signature\r\n const name = extractMethodName(trimmed);\r\n if (name) return name;\r\n }\r\n return null;\r\n}\r\n\r\n// ─── Using-block replacement ────────────────────────────────────────────────────\r\n\r\n/**\r\n * Find the contiguous `using` directive block at the top of the file.\r\n * Returns inclusive 0-based indices, or `null` when no usings are found.\r\n *\r\n * Skips blank lines and single-line comments (`//`) that appear between using\r\n * directives so that groups like:\r\n *\r\n * using System;\r\n * // collections\r\n * using System.Collections.Generic;\r\n *\r\n * are treated as one block.\r\n */\r\nfunction findUsingBlockRange(lines: string[]): { start: number; end: number } | null {\r\n let start = -1;\r\n let end = -1;\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n const trimmed = lines[i].trim();\r\n\r\n if (isUsingDirective(trimmed)) {\r\n if (start === -1) start = i;\r\n end = i;\r\n } else if (start !== -1) {\r\n // Allow blank lines and single-line comments inside the using block\r\n if (trimmed === '' || trimmed.startsWith('//')) continue;\r\n // Any other content means the using block has ended\r\n break;\r\n }\r\n }\r\n\r\n return start === -1 ? null : { start, end };\r\n}\r\n\r\n/**\r\n * Check whether a trimmed line is a `using` directive (not a `using` statement\r\n * such as `using (var x = ...)`).\r\n */\r\nfunction isUsingDirective(trimmed: string): boolean {\r\n return (trimmed.startsWith('using ') || trimmed.startsWith('using\\t'))\r\n && !trimmed.includes('(')\r\n && trimmed.endsWith(';');\r\n}\r\n\r\n/**\r\n * Replace the original file's using block with the AI response's using block.\r\n * If the AI has no usings the original is returned unchanged.\r\n */\r\nfunction replaceUsingBlock(originalLines: string[], aiLines: string[]): string[] {\r\n const aiRange = findUsingBlockRange(aiLines);\r\n if (!aiRange) return [...originalLines];\r\n\r\n const aiUsings = aiLines.slice(aiRange.start, aiRange.end + 1);\r\n const origRange = findUsingBlockRange(originalLines);\r\n\r\n const result = [...originalLines];\r\n if (origRange) {\r\n result.splice(origRange.start, origRange.end - origRange.start + 1, ...aiUsings);\r\n } else {\r\n // No usings in original – prepend\r\n result.splice(0, 0, ...aiUsings, '');\r\n }\r\n console.log(` replaced using block (${aiUsings.length} lines from AI)`);\r\n return result;\r\n}\r\n\r\n// ─── Appending new method blocks ────────────────────────────────────────────────\r\n\r\n/**\r\n * Append method blocks (described by `methods` referencing `aiLines`) into\r\n * `resultLines` right after the last test method.\r\n */\r\nfunction appendMethodBlocks(\r\n resultLines: string[],\r\n aiLines: string[],\r\n methods: MethodInfo[],\r\n baseIndent: string,\r\n label: string,\r\n): string[] {\r\n let insertLine = findLastTestMethodEndLine(resultLines);\r\n if (insertLine === 0) {\r\n insertLine = findTestClassInsertPosition(resultLines);\r\n }\r\n if (insertLine === 0) {\r\n console.warn(` could not find insert position for ${label}(s), skipping`);\r\n return resultLines;\r\n }\r\n\r\n const insertIndex = insertLine - 1; // 1-based → 0-based\r\n const newLines: string[] = [];\r\n\r\n for (const m of methods) {\r\n const raw = aiLines.slice(m.start, m.end + 1).join('\\n');\r\n const reindented = applyIndentation(raw, baseIndent);\r\n newLines.push('', ...reindented.split('\\n'));\r\n console.log(` appended ${label} '${m.name}' (${m.end - m.start + 1} lines)`);\r\n }\r\n\r\n const result = [...resultLines];\r\n result.splice(insertIndex, 0, ...newLines);\r\n return result;\r\n}\r\n\r\n// ─── New helper method detection ────────────────────────────────────────────────\r\n\r\n/**\r\n * Find non-test methods in the AI response that do **not** exist in the\r\n * original / result file. These are typically helper or factory methods that\r\n * the AI introduced to support the fixed test code.\r\n *\r\n * Detection strategy:\r\n * 1. Mark every line in `aiLines` that belongs to a known test method.\r\n * 2. Inside the class body, scan the unclaimed lines for method signatures.\r\n * 3. Brace-match to determine the full method extent.\r\n * 4. Keep only methods whose name does not appear in `resultLines`.\r\n */\r\nfunction findNewHelperMethods(\r\n resultLines: string[],\r\n aiLines: string[],\r\n aiTestMethods: MethodInfo[],\r\n): MethodInfo[] {\r\n // Lines already claimed by test methods\r\n const claimed = new Set<number>();\r\n for (const m of aiTestMethods) {\r\n for (let i = m.start; i <= m.end; i++) claimed.add(i);\r\n }\r\n\r\n // Approximate class body range in AI: from first `{` after a `class` keyword\r\n // to the matching `}`.\r\n const classBody = findClassBodyRange(aiLines);\r\n if (!classBody) return [];\r\n\r\n // Collect existing method names from the result file for comparison\r\n const existingNames = collectAllMethodNames(resultLines);\r\n\r\n const methodSigPattern = /^\\s*(public|private|protected|internal)\\s+/;\r\n const helpers: MethodInfo[] = [];\r\n const attributePattern = /^\\s*\\[.+\\]\\s*$/;\r\n\r\n for (let i = classBody.start; i <= classBody.end; i++) {\r\n if (claimed.has(i)) continue;\r\n const line = aiLines[i];\r\n if (!methodSigPattern.test(line)) continue;\r\n\r\n // Must look like a method: name followed by `(`\r\n const name = extractMethodName(line.trim());\r\n if (!name || existingNames.has(name)) continue;\r\n\r\n // Check this isn't a property (contains `{ get` or `{ set` on same/next line)\r\n if (isPropertyDeclaration(aiLines, i)) continue;\r\n\r\n // Brace-match to find method end\r\n const end = braceMatchForward(aiLines, i, classBody.end);\r\n if (end === -1) continue;\r\n\r\n // Search upward for preceding attributes\r\n let start = i;\r\n for (let k = i - 1; k >= classBody.start; k--) {\r\n const t = aiLines[k].trim();\r\n if (attributePattern.test(t)) { start = k; }\r\n else if (t === '') { /* skip blank lines between attrs */ }\r\n else break;\r\n }\r\n\r\n helpers.push({ name, start, end });\r\n\r\n // Mark as claimed so we don't double-count\r\n for (let k = start; k <= end; k++) claimed.add(k);\r\n }\r\n\r\n return helpers;\r\n}\r\n\r\n/**\r\n * Find the class body range (the lines between the opening `{` of the test\r\n * class and its matching `}`). Returns 0-based inclusive indices.\r\n */\r\nfunction findClassBodyRange(lines: string[]): { start: number; end: number } | null {\r\n const classPattern = /^\\s*(public\\s+)?(partial\\s+)?(static\\s+)?class\\s+/i;\r\n\r\n let classLineIdx = -1;\r\n for (let i = 0; i < lines.length; i++) {\r\n if (classPattern.test(lines[i])) { classLineIdx = i; break; }\r\n }\r\n if (classLineIdx === -1) return null;\r\n\r\n // Find opening brace\r\n let openIdx = -1;\r\n for (let i = classLineIdx; i < lines.length; i++) {\r\n if (lines[i].includes('{')) { openIdx = i; break; }\r\n }\r\n if (openIdx === -1) return null;\r\n\r\n // Brace-match to find the class closing brace\r\n let depth = 0;\r\n let closeIdx = -1;\r\n for (let i = openIdx; i < lines.length; i++) {\r\n for (const ch of lines[i]) {\r\n if (ch === '{') depth++;\r\n if (ch === '}') { depth--; if (depth === 0) { closeIdx = i; break; } }\r\n }\r\n if (closeIdx !== -1) break;\r\n }\r\n if (closeIdx === -1) return null;\r\n\r\n return { start: openIdx + 1, end: closeIdx - 1 };\r\n}\r\n\r\n/**\r\n * Collect all method-like names from a file (test + non-test) for de-duplication.\r\n */\r\nfunction collectAllMethodNames(lines: string[]): Set<string> {\r\n const names = new Set<string>();\r\n const methodSigPattern = /^\\s*(public|private|protected|internal)\\s+/;\r\n\r\n for (const line of lines) {\r\n if (!methodSigPattern.test(line)) continue;\r\n if (isLikelyFieldOrProperty(line)) continue;\r\n const name = extractMethodName(line.trim());\r\n if (name) names.add(name);\r\n }\r\n return names;\r\n}\r\n\r\n/** Simple heuristic to skip field or property declarations. */\r\nfunction isLikelyFieldOrProperty(line: string): boolean {\r\n const trimmed = line.trim();\r\n // Properties: `public int Foo { get; set; }`\r\n if (/\\{\\s*(get|set)/.test(trimmed)) return true;\r\n // Fields: `private readonly int _foo;` or `private int foo = ...;`\r\n if (trimmed.endsWith(';') && !trimmed.includes('(')) return true;\r\n return false;\r\n}\r\n\r\n/** Check whether line `i` starts a property (has `{ get` or `{ set`). */\r\nfunction isPropertyDeclaration(lines: string[], i: number): boolean {\r\n // Check current line and next line for property-style braces\r\n for (let k = i; k < Math.min(i + 3, lines.length); k++) {\r\n if (/\\{\\s*(get|set)/.test(lines[k])) return true;\r\n }\r\n return false;\r\n}\r\n\r\n/**\r\n * Forward brace-match starting from line `start`. Returns the 0-based line\r\n * index of the closing `}`, or -1 on failure.\r\n */\r\nfunction braceMatchForward(lines: string[], start: number, maxLine: number): number {\r\n let depth = 0;\r\n let foundOpen = false;\r\n\r\n for (let i = start; i <= maxLine; i++) {\r\n for (const ch of lines[i]) {\r\n if (ch === '{') { depth++; foundOpen = true; }\r\n if (ch === '}') {\r\n depth--;\r\n if (foundOpen && depth === 0) return i;\r\n }\r\n }\r\n }\r\n return -1;\r\n}\r\n"]}
|
|
@@ -22,4 +22,45 @@ interface MoreUTResult {
|
|
|
22
22
|
* or `undefined` if the generated content could not be parsed.
|
|
23
23
|
*/
|
|
24
24
|
export declare function postGenMoreUTProcess(generatedContent: string, testFilePath: string, isXapCode: boolean): MoreUTResult | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Detect the line-ending convention used in the file.
|
|
27
|
+
* @returns `'\r\n'` for Windows (CRLF) or `'\n'` for Unix (LF).
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectLineEnding(content: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Collect all `using` directives already present in the file.
|
|
32
|
+
* @returns A `Set` of normalised using strings for O(1) de-duplication.
|
|
33
|
+
*/
|
|
34
|
+
export declare function extractExistingUsingStatements(lines: string[]): Set<string>;
|
|
35
|
+
/**
|
|
36
|
+
* Normalise a `using` directive for comparison.
|
|
37
|
+
* Strips whitespace and trailing semicolons so that
|
|
38
|
+
* `" using System.Linq ; "` and `"using System.Linq"` match.
|
|
39
|
+
*
|
|
40
|
+
* @returns `null` when the line is not a using directive.
|
|
41
|
+
*/
|
|
42
|
+
export declare function normalizeUsingStatement(line: string): string | null;
|
|
43
|
+
/**
|
|
44
|
+
* Detect the indentation level of test methods in the file
|
|
45
|
+
* by looking for `[TestMethod]`, `[Test]`, `[Fact]` or `[Theory]` attributes.
|
|
46
|
+
*
|
|
47
|
+
* Falls back to the first non-empty line inside a class body,
|
|
48
|
+
* or 8 spaces if nothing useful is found.
|
|
49
|
+
*/
|
|
50
|
+
export declare function detectTestMethodIndent(lines: string[]): string;
|
|
51
|
+
/**
|
|
52
|
+
* Re-indent a block of code so its minimum indentation matches `baseIndent`,
|
|
53
|
+
* while preserving the *relative* indentation of nested lines.
|
|
54
|
+
*/
|
|
55
|
+
export declare function applyIndentation(code: string, baseIndent: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Find the 1-based line number immediately after the last test method's
|
|
58
|
+
* closing brace. Returns 0 when no test method is found.
|
|
59
|
+
*/
|
|
60
|
+
export declare function findLastTestMethodEndLine(lines: string[]): number;
|
|
61
|
+
/**
|
|
62
|
+
* Find the 1-based line number right after the test class's opening brace.
|
|
63
|
+
* Used as a fallback when there are no existing test methods.
|
|
64
|
+
*/
|
|
65
|
+
export declare function findTestClassInsertPosition(lines: string[]): number;
|
|
25
66
|
export {};
|
|
@@ -34,6 +34,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.postGenMoreUTProcess = postGenMoreUTProcess;
|
|
37
|
+
exports.detectLineEnding = detectLineEnding;
|
|
38
|
+
exports.extractExistingUsingStatements = extractExistingUsingStatements;
|
|
39
|
+
exports.normalizeUsingStatement = normalizeUsingStatement;
|
|
40
|
+
exports.detectTestMethodIndent = detectTestMethodIndent;
|
|
41
|
+
exports.applyIndentation = applyIndentation;
|
|
42
|
+
exports.findLastTestMethodEndLine = findLastTestMethodEndLine;
|
|
43
|
+
exports.findTestClassInsertPosition = findTestClassInsertPosition;
|
|
37
44
|
const fs = __importStar(require("fs"));
|
|
38
45
|
const repairRequiredNameSpaces_1 = require("./repairRequiredNameSpaces");
|
|
39
46
|
const constants_1 = require("../../types/constants");
|
|
@@ -122,12 +129,17 @@ function insertUsingStatements(resultLines, usingStatements, isXapCode, testMeth
|
|
|
122
129
|
const xapUsingsStr = xapUsings.join('\n');
|
|
123
130
|
allUsingStatements = allUsingStatements ? `${allUsingStatements}\n${xapUsingsStr}` : xapUsingsStr;
|
|
124
131
|
}
|
|
125
|
-
// De-duplicate against the original file
|
|
132
|
+
// De-duplicate against the original file AND within the merged list itself
|
|
126
133
|
const existingUsings = extractExistingUsingStatements(resultLines);
|
|
127
134
|
const newUsingLines = allUsingStatements
|
|
128
135
|
.split('\n')
|
|
129
136
|
.filter(line => line.trim() !== '')
|
|
130
|
-
.filter(line =>
|
|
137
|
+
.filter(line => {
|
|
138
|
+
const normalized = normalizeUsingStatement(line);
|
|
139
|
+
if (!normalized || existingUsings.has(normalized))
|
|
140
|
+
return false;
|
|
141
|
+
return existingUsings.add(normalized); // Set.add() returns the Set (truthy)
|
|
142
|
+
});
|
|
131
143
|
if (newUsingLines.length === 0) {
|
|
132
144
|
console.log('All using statements already exist in the original file, skipping insertion.');
|
|
133
145
|
return { lines: resultLines, range: null, linesInserted: 0 };
|