@ai-dev-tools/csharp-copilot-core 0.0.33 → 0.0.35

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.
Files changed (69) hide show
  1. package/out/batch/generateCodeTests.js +22 -6
  2. package/out/batch/generateCodeTests.js.map +1 -1
  3. package/out/changedFilesProcessor/processChangedFiles.d.ts +9 -0
  4. package/out/changedFilesProcessor/processChangedFiles.js +156 -0
  5. package/out/changedFilesProcessor/processChangedFiles.js.map +1 -0
  6. package/out/codebk/prompts/buildAfGuidelines_20251226.liquid +15 -0
  7. package/out/codebk/prompts/general/generalUtGuidelines_20251226.liquid +16 -0
  8. package/out/codebk/prompts/xap/xapUtGuideline_20251225.liquid +37 -0
  9. package/out/codebk/prompts/xap/xapUtGuideline_20251226.liquid +49 -0
  10. package/out/command/index.js +32 -0
  11. package/out/command/index.js.map +1 -1
  12. package/out/command/utGenWrapper.js +85 -4
  13. package/out/command/utGenWrapper.js.map +1 -1
  14. package/out/gen/autoFix.d.ts +2 -2
  15. package/out/gen/autoFix.js +88 -31
  16. package/out/gen/autoFix.js.map +1 -1
  17. package/out/gen/csharpUtGen.d.ts +1 -1
  18. package/out/gen/csharpUtGen.js +16 -13
  19. package/out/gen/csharpUtGen.js.map +1 -1
  20. package/out/gen/ensureValidLLMResponse.d.ts +5 -1
  21. package/out/gen/ensureValidLLMResponse.js +15 -3
  22. package/out/gen/ensureValidLLMResponse.js.map +1 -1
  23. package/out/gen/postGen/postGenMoreUTProcess.d.ts +25 -0
  24. package/out/gen/postGen/postGenMoreUTProcess.js +502 -0
  25. package/out/gen/postGen/postGenMoreUTProcess.js.map +1 -0
  26. package/out/gen/postGen/postGenProcess.d.ts +1 -1
  27. package/out/gen/postGen/postGenProcess.js +7 -7
  28. package/out/gen/postGen/postGenProcess.js.map +1 -1
  29. package/out/gen/postGen/repairRequiredNameSpaces.d.ts +17 -0
  30. package/out/gen/postGen/repairRequiredNameSpaces.js +87 -20
  31. package/out/gen/postGen/repairRequiredNameSpaces.js.map +1 -1
  32. package/out/llm/preparePrompt.d.ts +2 -1
  33. package/out/llm/preparePrompt.js +38 -4
  34. package/out/llm/preparePrompt.js.map +1 -1
  35. package/out/llm/prompt/buildAfGuidelines.liquid +8 -8
  36. package/out/llm/prompt/general/generalUtGuidelines.liquid +3 -0
  37. package/out/llm/prompt/moreUT/generateMoreUTSourceCode.liquid +4 -0
  38. package/out/llm/prompt/moreUT/generateMoreUtAutoFix.liquid +45 -0
  39. package/out/llm/prompt/moreUT/generateMoreUtTemplate.liquid +115 -0
  40. package/out/llm/prompt/moreUT/generateMoreUtTestCode.liquid +5 -0
  41. package/out/llm/prompt/moreUT/utGenerationGuidelines.liquid +15 -0
  42. package/out/llm/prompt/xap/xapUtGuideline.liquid +66 -15
  43. package/out/types/changedFilesResult.d.ts +37 -0
  44. package/out/types/changedFilesResult.js +3 -0
  45. package/out/types/changedFilesResult.js.map +1 -0
  46. package/out/types/constants.d.ts +1 -0
  47. package/out/types/constants.js +2 -1
  48. package/out/types/constants.js.map +1 -1
  49. package/out/types/genResult.d.ts +1 -1
  50. package/out/types/genResult.js.map +1 -1
  51. package/out/utils/checkXapCode.d.ts +1 -1
  52. package/out/utils/checkXapCode.js +14 -2
  53. package/out/utils/checkXapCode.js.map +1 -1
  54. package/out/utils/fileUtils.d.ts +1 -0
  55. package/out/utils/fileUtils.js +13 -0
  56. package/out/utils/fileUtils.js.map +1 -1
  57. package/out/utils/getCodeStructurePath.d.ts +4 -0
  58. package/out/utils/getCodeStructurePath.js +30 -0
  59. package/out/utils/getCodeStructurePath.js.map +1 -1
  60. package/out/utils/getTestFile.d.ts +18 -8
  61. package/out/utils/getTestFile.js +735 -62
  62. package/out/utils/getTestFile.js.map +1 -1
  63. package/out/utils/removeFailedTestMethods.d.ts +8 -0
  64. package/out/utils/removeFailedTestMethods.js +363 -0
  65. package/out/utils/removeFailedTestMethods.js.map +1 -1
  66. package/out/utils/verifyGeneratedCode.d.ts +2 -0
  67. package/out/utils/verifyGeneratedCode.js +17 -0
  68. package/out/utils/verifyGeneratedCode.js.map +1 -0
  69. package/package.json +4 -3
@@ -33,15 +33,120 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.getExistingTestFile = getExistingTestFile;
37
- exports.createTestFile = createTestFile;
36
+ exports.isTestFile = isTestFile;
37
+ exports.determineTestFilePath = determineTestFilePath;
38
38
  exports.getOrCreatTestFile = getOrCreatTestFile;
39
39
  const fs = __importStar(require("fs"));
40
40
  const path = __importStar(require("path"));
41
41
  const getCodeStructurePath_1 = require("./getCodeStructurePath");
42
+ const getOrCreateDir_1 = require("./getOrCreateDir");
42
43
  const fast_xml_parser_1 = require("fast-xml-parser");
43
44
  const TEST_NAME_REGEX = /(Test|Tests)/i;
44
45
  const TEST_ATTR_REGEX = /\[(Fact|Theory|TestClass|TestMethod|Test)\]/;
46
+ /**
47
+ * Check if a file path is a test file based on common naming conventions.
48
+ * Checks if the file name (without .cs extension) ends with test, tests, unittest, or unittests.
49
+ */
50
+ function isTestFile(filepath) {
51
+ const fileName = path.basename(filepath).toLowerCase();
52
+ const fileNameWithoutExt = fileName.replace(/\.cs$/, '');
53
+ return fileNameWithoutExt.endsWith('test') ||
54
+ fileNameWithoutExt.endsWith('tests') ||
55
+ fileNameWithoutExt.endsWith('unittest') ||
56
+ fileNameWithoutExt.endsWith('unittests');
57
+ }
58
+ // Modifier pattern for C# type declarations (sealed, abstract, static, partial)
59
+ const MODIFIER_PATTERN = `(?:sealed|abstract|static|partial)?\\s*`;
60
+ // Helper function to create test file name pattern
61
+ function getTestFileNamePattern(sourceFileName) {
62
+ return new RegExp(`^${sourceFileName}(Unit)?(\\.)?(Test|Tests)$`);
63
+ }
64
+ /**
65
+ * Extract words from a name string (e.g., project name, file name)
66
+ * Splits by camelCase and special characters, returns lowercase words
67
+ *
68
+ * @param name - The name string to extract words from
69
+ * @returns Array of lowercase words
70
+ */
71
+ function extractWordsFromName(name) {
72
+ return name
73
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
74
+ .split(/[^a-zA-Z0-9]+/)
75
+ .filter(Boolean)
76
+ .map(w => w.toLowerCase());
77
+ }
78
+ /**
79
+ * Calculate word-level matching score between two names
80
+ * Returns the count of matching words
81
+ *
82
+ * @param name1 - First name to compare
83
+ * @param name2 - Second name to compare
84
+ * @returns Number of matching words
85
+ */
86
+ function calculateWordMatchCount(name1, name2) {
87
+ const words1 = extractWordsFromName(name1);
88
+ const words2 = extractWordsFromName(name2);
89
+ const words1Set = new Set(words1);
90
+ return words2.filter(w => words1Set.has(w)).length;
91
+ }
92
+ /**
93
+ * Get all subfolder names under a directory (recursive)
94
+ * Skips: bin, obj, .vs directories
95
+ *
96
+ * @param dirPath - Path to directory
97
+ * @returns Set of lowercase folder names
98
+ */
99
+ function getSubfolderNamesRecursive(dirPath) {
100
+ const subfolders = new Set();
101
+ try {
102
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
103
+ for (const entry of entries) {
104
+ if (entry.isDirectory() && entry.name !== 'bin' && entry.name !== 'obj' && entry.name !== '.vs') {
105
+ subfolders.add(entry.name.toLowerCase());
106
+ // Recursively get subfolders
107
+ const subPath = path.join(dirPath, entry.name);
108
+ const deepSubfolders = getSubfolderNamesRecursive(subPath);
109
+ deepSubfolders.forEach(f => subfolders.add(f));
110
+ }
111
+ }
112
+ }
113
+ catch (err) {
114
+ // Ignore errors for inaccessible directories
115
+ }
116
+ return subfolders;
117
+ }
118
+ /**
119
+ * Get all .cs file names under a directory (recursive)
120
+ * Skips: bin, obj, .vs directories
121
+ *
122
+ * @param dirPath - Path to directory
123
+ * @returns Set of lowercase file names (without .cs extension)
124
+ */
125
+ function getFileNamesRecursive(dirPath) {
126
+ const fileNames = new Set();
127
+ try {
128
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
129
+ for (const entry of entries) {
130
+ if (entry.name === 'bin' || entry.name === 'obj' || entry.name === '.vs') {
131
+ continue;
132
+ }
133
+ const fullPath = path.join(dirPath, entry.name);
134
+ if (entry.isDirectory()) {
135
+ // Recursively get file names from subdirectories
136
+ const subFileNames = getFileNamesRecursive(fullPath);
137
+ subFileNames.forEach(f => fileNames.add(f));
138
+ }
139
+ else if (entry.isFile() && entry.name.endsWith('.cs')) {
140
+ // Add file name without .cs extension
141
+ fileNames.add(entry.name.slice(0, -3).toLowerCase());
142
+ }
143
+ }
144
+ }
145
+ catch (err) {
146
+ // Ignore errors for inaccessible directories
147
+ }
148
+ return fileNames;
149
+ }
45
150
  function findAllCsprojFiles(dir, excludedPaths) {
46
151
  const results = [];
47
152
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -134,47 +239,197 @@ function isFalconUnitTestProject(csprojPath) {
134
239
  }
135
240
  function isProjectNameMatchedTestProject(csprojPath, projectAssemblyName, projectName) {
136
241
  const testProjectName = path.basename(csprojPath, '.csproj').toLowerCase();
137
- const fileNameEndWithTests = testProjectName.endsWith('.tests') || testProjectName.endsWith('.test') || testProjectName.endsWith('.unittest') || testProjectName.endsWith('.unittests');
138
- if (testProjectName.startsWith(projectName.toLowerCase()) && fileNameEndWithTests) {
242
+ const projectNameLower = projectName.toLowerCase();
243
+ // Check if test project name exactly matches: projectName + suffix
244
+ const suffixes = ['.tests', '.test', '.unittest', '.unittests', 'tests', 'test'];
245
+ const exactMatch = suffixes.some(suffix => testProjectName === projectNameLower + suffix);
246
+ if (exactMatch) {
139
247
  return true;
140
248
  }
249
+ // Fallback: check assembly name with exact match
141
250
  const testProjectAssemblyName = (0, getCodeStructurePath_1.getAssemblyName)(csprojPath);
142
- return testProjectAssemblyName?.toLowerCase()?.startsWith(projectAssemblyName.toLowerCase()) && fileNameEndWithTests;
251
+ if (testProjectAssemblyName) {
252
+ const assemblyNameLower = testProjectAssemblyName.toLowerCase();
253
+ const projectAssemblyNameLower = projectAssemblyName.toLowerCase();
254
+ return suffixes.some(suffix => assemblyNameLower === projectAssemblyNameLower + suffix);
255
+ }
256
+ return false;
143
257
  }
144
- function findTestProjectFromCsFile(csFilePath) {
258
+ /**
259
+ * Search for test projects in repository directories
260
+ * Walks up from source file directory and collects all test projects that reference the source project
261
+ *
262
+ * @param csFilePath - Path to the source .cs file
263
+ * @param repoRoot - Repository root directory
264
+ * @param projectFilePath - Path to source project's .csproj
265
+ * @param normalizedProjectPath - Normalized path to source project
266
+ * @returns Array of test project paths that reference the source project
267
+ */
268
+ function searchTestProjectsInRepositories(csFilePath, repoRoot, projectFilePath, normalizedProjectPath) {
145
269
  let currentDir = path.dirname(csFilePath);
146
- const repoRoot = (0, getCodeStructurePath_1.getCodeRepoRoot)(csFilePath);
147
- const projectFilePath = (0, getCodeStructurePath_1.getSourceFileCsprojFilePath)(csFilePath);
148
- const projectName = path.basename(projectFilePath, '.csproj');
149
- const projectAssemblyName = (0, getCodeStructurePath_1.getAssemblyName)(projectFilePath);
270
+ const possibleTestProjects = [];
150
271
  const excludedPaths = [currentDir];
151
272
  const repoRootParentPath = path.dirname(repoRoot);
152
- // Start from the parent directory of the source file, because test project is usually in the parent directory
153
273
  currentDir = path.dirname(currentDir);
154
274
  while (currentDir !== repoRootParentPath) {
155
275
  const csprojFiles = findAllCsprojFiles(currentDir, excludedPaths);
156
- for (const csproj of csprojFiles) {
157
- if (isProjectNameMatchedTestProject(csproj, projectAssemblyName, projectName)) {
158
- return csproj;
159
- }
160
- else if (isXapTestProject(csproj)) {
161
- return csproj;
276
+ // Filter csproj files by standard test project naming patterns
277
+ const testProjectCandidates = csprojFiles.filter(csproj => {
278
+ const rawName = path.basename(csproj, '.csproj');
279
+ const csprojName = rawName.toLowerCase();
280
+ // Standard test naming: ends with .tests, .test, .unittests, .unittest, Tests, or Test
281
+ if (csprojName.endsWith('.tests') ||
282
+ csprojName.endsWith('.test') ||
283
+ csprojName.endsWith('.unittests') ||
284
+ csprojName.endsWith('.unittest') ||
285
+ csprojName.endsWith('tests') ||
286
+ csprojName.endsWith('test')) {
287
+ return true;
162
288
  }
163
- else if (isFalconUnitTestProject(csproj)) {
164
- const filename = path.basename(csproj, '.csproj').toLowerCase();
165
- if (filename.endsWith('unittest') || filename.endsWith('unittests')) {
166
- return csproj;
167
- }
289
+ // Word-level match: any word ends with "test" or "tests"
290
+ const words = extractWordsFromName(rawName);
291
+ return words.some(w => w.endsWith('test') || w.endsWith('tests'));
292
+ });
293
+ for (const csproj of testProjectCandidates) {
294
+ // Check if this csproj references the source project
295
+ const projectReferences = getProjectReferencesFromCsproj(csproj);
296
+ const referencesSourceProject = checkTestProjectReferencesSourceProject(csproj, normalizedProjectPath, projectReferences);
297
+ if (!referencesSourceProject) {
298
+ continue;
168
299
  }
169
- else if (isGeneralTestProject(csproj)) {
170
- return csproj;
300
+ const projectName = path.basename(projectFilePath, '.csproj');
301
+ const projectAssemblyName = (0, getCodeStructurePath_1.getAssemblyName)(projectFilePath);
302
+ if (isProjectNameMatchedTestProject(csproj, projectAssemblyName, projectName)) {
303
+ return [csproj];
171
304
  }
305
+ // Collect all test projects that reference the source project
306
+ possibleTestProjects.push(csproj);
172
307
  }
173
- // Add current directory to excluded paths to avoid re-checking
174
308
  excludedPaths.push(currentDir);
175
309
  currentDir = path.dirname(currentDir);
176
310
  }
177
- return null;
311
+ return possibleTestProjects;
312
+ }
313
+ /**
314
+ * Filter test projects by subfolder matching
315
+ * Returns projects sorted by match count, or proceeds to file name matching if all zero
316
+ *
317
+ * @param possibleTestProjects - List of test project paths
318
+ * @param projectFilePath - Path to source project's .csproj
319
+ * @returns Filtered projects with scores, or null if needs file name matching
320
+ */
321
+ function filterBySubfolderMatching(possibleTestProjects, projectFilePath) {
322
+ const sourceProjectDir = path.dirname(projectFilePath);
323
+ const sourceSubfolders = getSubfolderNamesRecursive(sourceProjectDir);
324
+ const projectsWithScores = possibleTestProjects.map(csproj => {
325
+ const testProjectDir = path.dirname(csproj);
326
+ const testSubfolders = getSubfolderNamesRecursive(testProjectDir);
327
+ let matchCount = 0;
328
+ testSubfolders.forEach(folderName => {
329
+ if (sourceSubfolders.has(folderName)) {
330
+ matchCount++;
331
+ }
332
+ });
333
+ return { csproj, matchCount };
334
+ });
335
+ projectsWithScores.sort((a, b) => b.matchCount - a.matchCount);
336
+ const allZero = projectsWithScores.every(p => p.matchCount === 0);
337
+ return allZero ? null : projectsWithScores;
338
+ }
339
+ /**
340
+ * Filter test projects by file name matching with test suffixes
341
+ * Returns filtered projects if any matches found
342
+ *
343
+ * @param projectsWithScores - Projects with scores
344
+ * @param projectFilePath - Path to source project's .csproj
345
+ * @returns Filtered projects by file name, or null if no matches
346
+ */
347
+ function filterByFileNameMatching(projectsWithScores, projectFilePath) {
348
+ const sourceProjectDir = path.dirname(projectFilePath);
349
+ const sourceFileNames = getFileNamesRecursive(sourceProjectDir);
350
+ const testSuffixes = ['.tests', '.test', '.unittests', '.unittest', 'tests', 'test'];
351
+ const filteredProjectsWithScores = projectsWithScores.filter(projectScore => {
352
+ const testProjectDir = path.dirname(projectScore.csproj);
353
+ const testFileNames = getFileNamesRecursive(testProjectDir);
354
+ for (const sourceFileName of sourceFileNames) {
355
+ for (const suffix of testSuffixes) {
356
+ const expectedTestFileName = (sourceFileName + suffix).toLowerCase();
357
+ if (testFileNames.has(expectedTestFileName)) {
358
+ return true;
359
+ }
360
+ }
361
+ }
362
+ return false;
363
+ });
364
+ return filteredProjectsWithScores.length > 0 ? filteredProjectsWithScores : null;
365
+ }
366
+ /**
367
+ * Filter test projects by word-level matching
368
+ * Returns top 2 projects with highest word match count
369
+ *
370
+ * @param projectsWithScores - Projects with scores
371
+ * @param projectName - Name of source project
372
+ * @returns Top 2 projects by word match count, or empty array if no matches
373
+ */
374
+ function filterByWordLevelMatching(projectsWithScores, projectName) {
375
+ const projectsWithWordScores = projectsWithScores.map(projectScore => {
376
+ const testProjectName = path.basename(projectScore.csproj, '.csproj');
377
+ const wordMatchCount = calculateWordMatchCount(projectName, testProjectName);
378
+ return { ...projectScore, wordMatchCount };
379
+ });
380
+ const projectsWithMatches = projectsWithWordScores.filter(p => p.wordMatchCount > 0);
381
+ if (projectsWithMatches.length === 0) {
382
+ return [];
383
+ }
384
+ projectsWithMatches.sort((a, b) => b.wordMatchCount - a.wordMatchCount);
385
+ const countToReturn = Math.min(2, projectsWithMatches.length);
386
+ return projectsWithMatches.slice(0, countToReturn).map(p => p.csproj);
387
+ }
388
+ /**
389
+ * Select top projects by score percentage or minimum count
390
+ *
391
+ * @param projectsWithScores - Projects with scores
392
+ * @param minCount - Minimum projects to return (default 2)
393
+ * @returns Array of selected project paths
394
+ */
395
+ function selectTopProjectsByScore(projectsWithScores, minCount = 2) {
396
+ const countToReturn = Math.max(minCount, Math.ceil(projectsWithScores.length * 0.3));
397
+ return projectsWithScores.slice(0, countToReturn).map(p => p.csproj);
398
+ }
399
+ /**
400
+ * Find possible test projects for a C# source file
401
+ * Uses multi-level matching: folder names -> file names -> word-level matching
402
+ *
403
+ * @param csFilePath - Path to the source .cs file
404
+ * @returns Array of possible test project paths
405
+ */
406
+ function findPossibleTestProjectsFromCsFile(csFilePath) {
407
+ const repoRoot = (0, getCodeStructurePath_1.getCodeRepoRoot)(csFilePath);
408
+ const projectFilePath = (0, getCodeStructurePath_1.getSourceFileCsprojFilePath)(csFilePath);
409
+ const projectName = path.basename(projectFilePath, '.csproj');
410
+ const normalizedProjectPath = path.normalize(projectFilePath);
411
+ // Search for test projects in repository
412
+ const possibleTestProjects = searchTestProjectsInRepositories(csFilePath, repoRoot, projectFilePath, normalizedProjectPath);
413
+ if (possibleTestProjects.length === 0) {
414
+ return [];
415
+ }
416
+ if (possibleTestProjects.length === 1) {
417
+ return possibleTestProjects;
418
+ }
419
+ // Try subfolder-level matching
420
+ const subfolderMatchResults = filterBySubfolderMatching(possibleTestProjects, projectFilePath);
421
+ if (subfolderMatchResults !== null) {
422
+ return selectTopProjectsByScore(subfolderMatchResults);
423
+ }
424
+ // If subfolder matching is all zero, try file name matching
425
+ const projectsWithScores = possibleTestProjects.map(csproj => ({ csproj, matchCount: 0 }));
426
+ const fileNameMatchResults = filterByFileNameMatching(projectsWithScores, projectFilePath);
427
+ if (fileNameMatchResults !== null) {
428
+ return selectTopProjectsByScore(fileNameMatchResults);
429
+ }
430
+ // If file name matching fails, try word-level matching
431
+ const wordLevelResults = filterByWordLevelMatching(projectsWithScores, projectName);
432
+ return wordLevelResults.length > 0 ? wordLevelResults : [];
178
433
  }
179
434
  function findPossibleTestFiles(testProjectDir, className) {
180
435
  const results = [];
@@ -201,27 +456,205 @@ function fileContainsTestAttributes(filePath) {
201
456
  const content = fs.readFileSync(filePath, 'utf-8');
202
457
  return TEST_ATTR_REGEX.test(content);
203
458
  }
204
- function getExistingTestFile(sourceCodePath) {
205
- const absPath = path.resolve(sourceCodePath);
206
- const testProjectPath = findTestProjectFromCsFile(absPath);
207
- if (!testProjectPath) {
208
- console.error(`No test project found for ${sourceCodePath}`);
209
- return { testFilePath: null, testProjectPath: null };
459
+ /**
460
+ * Check if a test project references a source project.
461
+ *
462
+ * @param testCsprojPath - Path to the test project's csproj file
463
+ * @param sourceCsprojPath - Path to the source project's csproj file
464
+ * @param projectReferences - Project references from the test csproj
465
+ * @returns True if test project references the source project, false otherwise
466
+ */
467
+ function checkTestProjectReferencesSourceProject(testCsprojPath, sourceCsprojPath, projectReferences) {
468
+ const normalizedSourceCsprojPath = path.normalize(sourceCsprojPath).toLowerCase();
469
+ return projectReferences.some(ref => {
470
+ const resolvedRefPath = path.normalize(path.resolve(path.dirname(testCsprojPath), ref)).toLowerCase();
471
+ return resolvedRefPath === normalizedSourceCsprojPath;
472
+ });
473
+ }
474
+ function getProjectReferencesFromCsproj(csprojPath) {
475
+ const projectReferences = [];
476
+ try {
477
+ const xmlContent = fs.readFileSync(csprojPath, 'utf-8');
478
+ const parser = new fast_xml_parser_1.XMLParser({
479
+ ignoreAttributes: false,
480
+ attributeNamePrefix: ''
481
+ });
482
+ const projectData = parser.parse(xmlContent);
483
+ const project = projectData.Project;
484
+ const itemGroups = Array.isArray(project.ItemGroup) ? project.ItemGroup : [project.ItemGroup];
485
+ for (const group of itemGroups) {
486
+ if (!group)
487
+ continue;
488
+ const projectRefs = group.ProjectReference;
489
+ if (!projectRefs)
490
+ continue;
491
+ const refList = Array.isArray(projectRefs) ? projectRefs : [projectRefs];
492
+ for (const ref of refList) {
493
+ if (ref.Include) {
494
+ projectReferences.push(ref.Include);
495
+ }
496
+ }
497
+ }
498
+ }
499
+ catch (err) {
500
+ console.error(`Failed to parse csproj: ${csprojPath}`, err);
210
501
  }
211
- const testProjectDir = path.dirname(testProjectPath);
212
- const className = path.basename(sourceCodePath, '.cs');
213
- const candidateFiles = findPossibleTestFiles(testProjectDir, className);
214
- for (const testFilePath of candidateFiles) {
215
- if (TEST_NAME_REGEX.test(path.basename(testFilePath))) {
216
- return { testFilePath, testProjectPath };
502
+ return projectReferences;
503
+ }
504
+ /**
505
+ * Calculate matching score between test file path and source file path based on directory structure.
506
+ * Returns two scores: continuous matching folders from end, and total matching folders.
507
+ *
508
+ * @param testRelativePath - Relative path of test file from test project root
509
+ * @param sourceRelativePath - Relative path of source file from source project root
510
+ * @returns Object with 'continuous' (right-to-left consecutive matches) and 'total' (all matches)
511
+ */
512
+ function calculateMatchingScore(testRelativePath, sourceRelativePath) {
513
+ // Extract directory paths (remove file names)
514
+ const testDir = path.dirname(testRelativePath);
515
+ const sourceDir = path.dirname(sourceRelativePath);
516
+ // If both files are in root directory (no subdirectories), give highest priority
517
+ if ((testDir === '.' || testDir === '') && (sourceDir === '.' || sourceDir === '')) {
518
+ return { continuous: 1000, total: 1000 };
519
+ }
520
+ // Split paths into folder arrays (case-insensitive)
521
+ const testFolders = testDir === '.' || testDir === '' ? [] : testDir.split(path.sep).map(f => f.toLowerCase());
522
+ const sourceFolders = sourceDir === '.' || sourceDir === '' ? [] : sourceDir.split(path.sep).map(f => f.toLowerCase());
523
+ // 1. Calculate continuous matching folders from end (right to left)
524
+ let continuous = 0;
525
+ const minLength = Math.min(testFolders.length, sourceFolders.length);
526
+ for (let i = 1; i <= minLength; i++) {
527
+ if (testFolders[testFolders.length - i] === sourceFolders[sourceFolders.length - i]) {
528
+ continuous++;
529
+ }
530
+ else {
531
+ // Stop at first mismatch
532
+ break;
217
533
  }
218
534
  }
219
- return { testFilePath: null, testProjectPath: testProjectPath };
535
+ // 2. Calculate total matching folders (not required to be continuous)
536
+ const sourceSet = new Set(sourceFolders);
537
+ const total = testFolders.filter(f => sourceSet.has(f)).length;
538
+ return { continuous, total };
220
539
  }
221
- function createXapTestProject(sourceCodePath) {
540
+ function isTestFileForSourceFile(testFilePath, sourceCodePath) {
541
+ const sourceFileName = path.basename(sourceCodePath, '.cs');
542
+ const testFileName = path.basename(testFilePath, '.cs');
543
+ // First check: test file name should match common test patterns
544
+ // Patterns: SourceFile + (Unit)? + (.)? + (Test|Tests)
545
+ // Examples: SourceFileTests, SourceFileTest, SourceFile.Tests, SourceFile.Test, SourceFileUnitTests, SourceFileUnit.Tests
546
+ const testPattern = getTestFileNamePattern(sourceFileName);
547
+ const matchesPattern = testPattern.test(testFileName);
548
+ if (!matchesPattern) {
549
+ return false;
550
+ }
551
+ try {
552
+ // Read both files content
553
+ const testContent = fs.readFileSync(testFilePath, 'utf-8');
554
+ const sourceContent = fs.readFileSync(sourceCodePath, 'utf-8');
555
+ // First check: Extract namespace from source file and verify test file references it
556
+ const sourceNamespaceMatch = sourceContent.match(/namespace\s+([\w.]+)/);
557
+ if (!sourceNamespaceMatch || !sourceNamespaceMatch[1]) {
558
+ // No namespace found in source file, fall back to name-based matching
559
+ return true;
560
+ }
561
+ const sourceNamespace = sourceNamespaceMatch[1];
562
+ // Check if test file has using statement for source namespace
563
+ const usingRegex = new RegExp(`using\\s+${sourceNamespace.replace(/\./g, '\\.')};`);
564
+ if (!usingRegex.test(testContent)) {
565
+ // Test file doesn't reference source namespace
566
+ return false;
567
+ }
568
+ // Second check: Extract the main type name from source file (public or internal)
569
+ // Supports: class, interface, struct, record (but not nested private/protected types)
570
+ // Priority: public > internal; match first public, fallback to first internal
571
+ // Handles modifiers: sealed, abstract, static, partial
572
+ const publicTypeMatch = sourceContent.match(new RegExp(`\\bpublic\\s+${MODIFIER_PATTERN}(?:class|interface|struct|record)\\s+(\\w+)`));
573
+ const internalTypeMatch = sourceContent.match(new RegExp(`\\binternal\\s+${MODIFIER_PATTERN}(?:class|interface|struct|record)\\s+(\\w+)`));
574
+ const sourceTypeMatch = publicTypeMatch || internalTypeMatch;
575
+ if (!sourceTypeMatch || !sourceTypeMatch[1]) {
576
+ // No public or internal type found - this file might be a utility/helper file
577
+ // without testable classes, so it shouldn't have a test file
578
+ return false;
579
+ }
580
+ const sourceTypeName = sourceTypeMatch[1];
581
+ // Check if test file references the source type name or its interface
582
+ // If source is an interface (direct interface definition), only match itself
583
+ // If source is a class/struct/record, also match interface (ITypeName)
584
+ const isSourceInterface = new RegExp(`\\b(?:public|internal)\\s+${MODIFIER_PATTERN}interface\\s+(?:\\w+)`).test(sourceContent);
585
+ let sourceCodeTypeReferenceRegex;
586
+ if (isSourceInterface) {
587
+ // Source is an interface, match only itself
588
+ sourceCodeTypeReferenceRegex = new RegExp(`\\b${sourceTypeName}\\b`, 'g');
589
+ }
590
+ else {
591
+ // Source is a class/struct/record, match both TypeName and ITypeName
592
+ sourceCodeTypeReferenceRegex = new RegExp(`\\b(I)?${sourceTypeName}\\b`, 'g');
593
+ }
594
+ if (sourceCodeTypeReferenceRegex.test(testContent)) {
595
+ return true;
596
+ }
597
+ // Namespace matches but class name not found in test file
598
+ return false;
599
+ }
600
+ catch (err) {
601
+ console.error(`Failed to read file content for matching: ${err}`);
602
+ // Strict mode: if we can't verify the content, treat as non-matching
603
+ return false;
604
+ }
222
605
  }
223
- function createTestFile(sourceCodePath) {
224
- const testProjectPath = findTestProjectFromCsFile(sourceCodePath);
606
+ /**
607
+ * Find an existing test file from sibling source files in the same or parent directories.
608
+ * This helps determine the best location for a new test file by looking at where
609
+ * existing test files are located for nearby source files.
610
+ *
611
+ * @param sourceCodePath - Path to the source file
612
+ * @param testCsprojPath - Path to the test project's csproj file
613
+ * @param sourceCsprojPath - Path to the source project's csproj file
614
+ * @returns Object with siblingTestFilePath and sourceRelativeDepth if found, otherwise null
615
+ */
616
+ function findSiblingTestFile(sourceCodePath, testCsprojPath, sourceCsprojPath) {
617
+ const sourceProjectDir = path.dirname(sourceCsprojPath);
618
+ let currentDir = path.dirname(sourceCodePath);
619
+ let depth = 0;
620
+ // Search from current directory up to source project root
621
+ while (currentDir.toLowerCase().startsWith(sourceProjectDir.toLowerCase()) && currentDir.length >= sourceProjectDir.length) {
622
+ try {
623
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
624
+ const csFiles = entries
625
+ .filter(e => e.isFile() && e.name.endsWith('.cs') && path.join(currentDir, e.name) !== sourceCodePath)
626
+ .map(e => path.join(currentDir, e.name));
627
+ for (const csFile of csFiles) {
628
+ // Try to find test file for this sibling source file
629
+ const result = findMatchingTestFileInProject(testCsprojPath, csFile, sourceCsprojPath);
630
+ if (result.testFilePath) {
631
+ return { siblingTestFilePath: result.testFilePath, sourceRelativeDepth: depth };
632
+ }
633
+ }
634
+ }
635
+ catch (err) {
636
+ // Ignore errors for inaccessible directories
637
+ }
638
+ // Move to parent directory
639
+ const parentDir = path.dirname(currentDir);
640
+ if (parentDir === currentDir) {
641
+ break;
642
+ }
643
+ currentDir = parentDir;
644
+ depth++;
645
+ }
646
+ return null;
647
+ }
648
+ /**
649
+ * Determine the best test file path for a source file.
650
+ * This function only calculates the path, it does not create any directories.
651
+ *
652
+ * @param sourceCodePath - Path to the source file
653
+ * @param testCsprojPath - Path to the test project's csproj file (optional)
654
+ * @returns Object with testFilePath, testProjectPath, and testFileExist flag
655
+ */
656
+ function determineTestFilePath(sourceCodePath, testCsprojPath) {
657
+ let testProjectPath = testCsprojPath;
225
658
  if (!testProjectPath) {
226
659
  console.error(`No test project found for ${sourceCodePath}`);
227
660
  return { testFilePath: null, testProjectPath: null };
@@ -229,38 +662,278 @@ function createTestFile(sourceCodePath) {
229
662
  const sourceFileName = path.basename(sourceCodePath, '.cs');
230
663
  const testFileName = `${sourceFileName}Tests.cs`;
231
664
  const sourceFileProjectPath = (0, getCodeStructurePath_1.getSourceFileProjectDir)(sourceCodePath);
232
- const sourceFileDir = path.dirname(sourceCodePath);
233
- const testFileRelativePath = sourceFileDir.replace(sourceFileProjectPath, "");
234
- const testProjecDir = path.dirname(testProjectPath);
235
- const testFilePath = path.join(testProjecDir, testFileRelativePath, testFileName);
236
- const testFileDir = path.dirname(testFilePath);
237
- // Create all missing directories recursively
238
- fs.mkdirSync(testFileDir, { recursive: true });
239
- console.log(`Created test file dir at ${testFilePath}`);
240
- return { testFilePath, testProjectPath };
665
+ const sourceCsprojPath = (0, getCodeStructurePath_1.getSourceFileCsprojFilePath)(sourceCodePath);
666
+ // Try to find sibling test file to determine the best location
667
+ const siblingResult = findSiblingTestFile(sourceCodePath, testProjectPath, sourceCsprojPath);
668
+ let testFilePath;
669
+ if (siblingResult) {
670
+ // Found sibling test file, calculate new test file path based on it
671
+ const siblingTestDir = path.dirname(siblingResult.siblingTestFilePath);
672
+ const sourceFileDir = path.dirname(sourceCodePath);
673
+ if (siblingResult.sourceRelativeDepth === 0) {
674
+ // Sibling is in the same directory, put test file in the same test directory
675
+ testFilePath = path.join(siblingTestDir, testFileName);
676
+ }
677
+ else {
678
+ // Sibling is in a parent directory, calculate the relative path from sibling source to current source
679
+ let relativeParts = [];
680
+ let tempDir = sourceFileDir;
681
+ for (let i = 0; i < siblingResult.sourceRelativeDepth; i++) {
682
+ relativeParts.unshift(path.basename(tempDir));
683
+ tempDir = path.dirname(tempDir);
684
+ }
685
+ // The relative path from the found sibling source dir to our source file
686
+ const relativePath = relativeParts.join(path.sep);
687
+ testFilePath = path.join(siblingTestDir, relativePath, testFileName);
688
+ }
689
+ }
690
+ else {
691
+ // No sibling test file found, use original logic
692
+ const sourceFileDir = path.dirname(sourceCodePath);
693
+ const testFileRelativePath = sourceFileDir.replace(sourceFileProjectPath, "");
694
+ const testProjecDir = path.dirname(testProjectPath);
695
+ testFilePath = path.join(testProjecDir, testFileRelativePath, testFileName);
696
+ }
697
+ // Check if the test file already exists
698
+ const testFileExist = fs.existsSync(testFilePath);
699
+ if (testFileExist) {
700
+ console.log(`Test file already exists at ${testFilePath}`);
701
+ }
702
+ return { testFilePath, testProjectPath, testFileExist };
703
+ }
704
+ /**
705
+ * Search for matching test file in a specific test project.
706
+ * Uses directory structure matching and file content analysis to find the best match.
707
+ *
708
+ * @param testCsprojPath - Path to the test project's csproj file
709
+ * @param sourceCodePath - Path to the source file
710
+ * @param sourceCsprojPath - Path to the source project's csproj file
711
+ * @returns Object with testFilePath and testProjectPath if found, otherwise both null
712
+ */
713
+ function findMatchingTestFileInProject(testCsprojPath, sourceCodePath, sourceCsprojPath) {
714
+ const testProjectDir = path.dirname(testCsprojPath);
715
+ // Get source file's csproj directory
716
+ const sourceCsprojDir = path.dirname(sourceCsprojPath);
717
+ // Calculate relative path of source file from its csproj directory
718
+ const sourceRelativePath = path.relative(sourceCsprojDir, sourceCodePath);
719
+ // Get source file name for filtering
720
+ const sourceFileName = path.basename(sourceCodePath, '.cs');
721
+ // Find .cs files containing source file name (optimization to reduce file count)
722
+ const allTestFiles = findPossibleTestFiles(testProjectDir, sourceFileName);
723
+ // Filter by file name pattern first to reduce unnecessary file reads
724
+ const testPattern = getTestFileNamePattern(sourceFileName);
725
+ // Create list of candidates with their matching scores
726
+ const candidatesWithScores = allTestFiles
727
+ .filter(file => {
728
+ const testFileName = path.basename(file, '.cs');
729
+ return testPattern.test(testFileName);
730
+ })
731
+ .map(file => {
732
+ const testRelativePath = path.relative(testProjectDir, file);
733
+ const score = calculateMatchingScore(testRelativePath, sourceRelativePath);
734
+ return {
735
+ filePath: file,
736
+ relativePath: testRelativePath,
737
+ score: score
738
+ };
739
+ })
740
+ // Sort by continuous match first, then by total match
741
+ .sort((a, b) => {
742
+ if (b.score.continuous !== a.score.continuous) {
743
+ return b.score.continuous - a.score.continuous;
744
+ }
745
+ return b.score.total - a.score.total;
746
+ });
747
+ for (const candidate of candidatesWithScores) {
748
+ if (isTestFileForSourceFile(candidate.filePath, sourceCodePath)) {
749
+ return { testFilePath: candidate.filePath, testProjectPath: testCsprojPath };
750
+ }
751
+ }
752
+ return { testFilePath: null, testProjectPath: null };
753
+ }
754
+ function getTestFileFromChangedFiles(sourceCodePath, changedTestFiles) {
755
+ if (!changedTestFiles || changedTestFiles.length === 0) {
756
+ return { testFilePath: null, testProjectPath: null };
757
+ }
758
+ // Get the csproj of the source code
759
+ const sourceCsprojPath = (0, getCodeStructurePath_1.getSourceFileCsprojFilePath)(sourceCodePath);
760
+ if (!sourceCsprojPath) {
761
+ return { testFilePath: null, testProjectPath: null };
762
+ }
763
+ // Cache to store project references from csproj to avoid re-parsing
764
+ const csprojCache = new Map();
765
+ // Store test projects that reference the source project
766
+ const referencingTestProjects = new Set();
767
+ for (const testFilePath of changedTestFiles) {
768
+ if (!fs.existsSync(testFilePath)) {
769
+ continue;
770
+ }
771
+ // Get the csproj of the test file
772
+ const testCsprojPath = (0, getCodeStructurePath_1.getSourceFileCsprojFilePath)(testFilePath);
773
+ if (!testCsprojPath) {
774
+ continue;
775
+ }
776
+ // Check cache first
777
+ let projectReferences = csprojCache.get(testCsprojPath);
778
+ if (!projectReferences) {
779
+ projectReferences = getProjectReferencesFromCsproj(testCsprojPath);
780
+ csprojCache.set(testCsprojPath, projectReferences);
781
+ }
782
+ // Check if test project references the source project
783
+ const normalizedSourceCsprojPath = path.normalize(sourceCsprojPath);
784
+ if (checkTestProjectReferencesSourceProject(testCsprojPath, normalizedSourceCsprojPath, projectReferences)) {
785
+ referencingTestProjects.add(testCsprojPath);
786
+ // Check if this test file is for the source file
787
+ if (isTestFileForSourceFile(testFilePath, sourceCodePath)) {
788
+ return { testFilePath, testProjectPath: testCsprojPath };
789
+ }
790
+ }
791
+ }
792
+ // Fallback: search all test files in projects that reference the source project
793
+ for (const testCsprojPath of referencingTestProjects) {
794
+ const result = findMatchingTestFileInProject(testCsprojPath, sourceCodePath, sourceCsprojPath);
795
+ if (result.testFilePath) {
796
+ return result;
797
+ }
798
+ }
799
+ return { testFilePath: null, testProjectPath: null };
241
800
  }
242
- function getOrCreatTestFile(sourceCodePath, testFilePath, isXapCode) {
801
+ function getOrCreatTestFile(sourceCodePath, testFilePath, changedTestFiles) {
243
802
  let finalTestFilePath = testFilePath;
244
803
  let finalTestProjectPath;
245
804
  let testFileExist = !!testFilePath && fs.existsSync(testFilePath);
805
+ // If testFilePath is provided, get its project path and return directly
806
+ if (testFilePath && testFilePath.trim() !== '') {
807
+ finalTestProjectPath = (0, getCodeStructurePath_1.getSourceFileCsprojFilePath)(testFilePath);
808
+ return { testFilePath: finalTestFilePath, testProjectPath: finalTestProjectPath, testFileExist };
809
+ }
246
810
  // if outputPath param is not provided, try to find an existing test file
247
811
  // if no existing test file found, create a new one in test project
248
- if (!testFilePath || testFilePath.trim() === '') {
249
- console.log(' Output path is missing. outputPath:', testFilePath, ', start looking for existing test project');
250
- let result = getExistingTestFile(sourceCodePath);
812
+ console.log(' Output path is missing. outputPath:', testFilePath, ', start looking for existing test project');
813
+ // Try to find test file from changed files first
814
+ let resultFromChangedFiles = getTestFileFromChangedFiles(sourceCodePath, changedTestFiles);
815
+ if (resultFromChangedFiles.testFilePath) {
816
+ return { testFilePath: resultFromChangedFiles.testFilePath, testProjectPath: resultFromChangedFiles.testProjectPath, testFileExist: true };
817
+ }
818
+ // Fallback: try to find existing test file
819
+ const possibleCSProjPaths = findPossibleTestProjectsFromCsFile(sourceCodePath);
820
+ if (!possibleCSProjPaths || possibleCSProjPaths.length === 0) {
821
+ console.error(` No test project found for source code: ${sourceCodePath}`);
822
+ return { testFilePath: null, testProjectPath: null, testFileExist: false };
823
+ }
824
+ // Get the source csproj path for matching
825
+ const sourceCsprojPath = (0, getCodeStructurePath_1.getSourceFileCsprojFilePath)(sourceCodePath);
826
+ if (!sourceCsprojPath) {
827
+ console.error(` Cannot find source csproj for: ${sourceCodePath}`);
828
+ return { testFilePath: null, testProjectPath: null, testFileExist: false };
829
+ }
830
+ // Try to find existing test file in each possible test project
831
+ for (const testCsprojPath of possibleCSProjPaths) {
832
+ const result = findMatchingTestFileInProject(testCsprojPath, sourceCodePath, sourceCsprojPath);
251
833
  if (result.testFilePath) {
252
834
  testFileExist = true;
253
- console.log(`Found existing test file: ${result.testFilePath}`);
254
- }
255
- else {
256
- console.log('No existing test file found, creating a new one.');
257
- result = createTestFile(sourceCodePath);
835
+ finalTestFilePath = result.testFilePath;
836
+ finalTestProjectPath = result.testProjectPath;
837
+ return { testFilePath: finalTestFilePath, testProjectPath: finalTestProjectPath, testFileExist };
258
838
  }
259
- finalTestFilePath = result.testFilePath;
260
- finalTestProjectPath = result.testProjectPath;
839
+ }
840
+ // No existing test file found, determine test file path (may find existing)
841
+ const result = determineTestFilePath(sourceCodePath, possibleCSProjPaths[0]);
842
+ if (!result.testFilePath || !result.testProjectPath) {
843
+ console.error(`Failed to determine test file path for ${sourceCodePath}`);
844
+ return { testFilePath: null, testProjectPath: null, testFileExist: false };
845
+ }
846
+ finalTestFilePath = result.testFilePath;
847
+ finalTestProjectPath = result.testProjectPath;
848
+ testFileExist = result.testFileExist ?? false;
849
+ // Create directory if test file doesn't exist
850
+ if (!testFileExist) {
851
+ (0, getOrCreateDir_1.getOrCreateDir)(path.dirname(finalTestFilePath));
261
852
  }
262
853
  return { testFilePath: finalTestFilePath, testProjectPath: finalTestProjectPath, testFileExist };
263
854
  }
855
+ // // Verify test file paths for all projects
856
+ // function verifyTestFilePaths(): void {
857
+ // const baseFolder = 'Q:\\src\\Microsoft.News.OneService';
858
+ // const outputFile = 'q:\\verifyTestFilePaths_results.txt';
859
+ // const results: string[] = [];
860
+ // try {
861
+ // // Recursively find all non test csproj files
862
+ // const getAllCsprojFiles = (dir: string): string[] => {
863
+ // const csprojFiles: string[] = [];
864
+ // try {
865
+ // const entries = fs.readdirSync(dir, { withFileTypes: true });
866
+ // for (const entry of entries) {
867
+ // const fullPath = path.join(dir, entry.name);
868
+ // if (entry.isDirectory() && entry.name !== 'bin' && entry.name !== 'obj' && entry.name !== '.vs') {
869
+ // csprojFiles.push(...getAllCsprojFiles(fullPath));
870
+ // } else if (entry.isFile() && entry.name.endsWith('.csproj')) {
871
+ // // Filter by test project naming patterns
872
+ // const rawName = path.basename(fullPath, '.csproj');
873
+ // const words = rawName
874
+ // .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
875
+ // .split(/[^a-zA-Z0-9]+/)
876
+ // .filter(Boolean)
877
+ // .map(w => w.toLowerCase());
878
+ // if (words.every(w => !w.endsWith('test') && !w.endsWith('tests'))) {
879
+ // csprojFiles.push(fullPath);
880
+ // }
881
+ // }
882
+ // }
883
+ // } catch (err) {
884
+ // // Skip inaccessible directories
885
+ // }
886
+ // return csprojFiles;
887
+ // };
888
+ // // Get first .cs file in a directory (recursive search)
889
+ // const getFirstCsFile = (dir: string): string | null => {
890
+ // try {
891
+ // const entries = fs.readdirSync(dir, { withFileTypes: true });
892
+ // // First pass: look for .cs files in current directory
893
+ // for (const entry of entries) {
894
+ // if (entry.isFile() && entry.name.endsWith('.cs')) {
895
+ // return path.join(dir, entry.name);
896
+ // }
897
+ // }
898
+ // // Second pass: recurse into subdirectories
899
+ // for (const entry of entries) {
900
+ // if (entry.isDirectory() && entry.name !== 'bin' && entry.name !== 'obj' && entry.name !== '.vs') {
901
+ // const fullPath = path.join(dir, entry.name);
902
+ // const result = getFirstCsFile(fullPath);
903
+ // if (result) {
904
+ // return result;
905
+ // }
906
+ // }
907
+ // }
908
+ // } catch (err) {
909
+ // // Skip if directory can't be read
910
+ // }
911
+ // return null;
912
+ // };
913
+ // // Find all csproj files
914
+ // const csprojFiles = getAllCsprojFiles(baseFolder);
915
+ // // Process each csproj
916
+ // for (const csprojPath of csprojFiles) {
917
+ // const csprojDir = path.dirname(csprojPath);
918
+ // const csFile = getFirstCsFile(csprojDir);
919
+ // if (csFile) {
920
+ // const result = getOrCreatTestFile(csFile, undefined);
921
+ // const testFilePath = result.testFilePath || 'NOT_FOUND';
922
+ // const testProjectPath = result.testProjectPath || 'NOT_FOUND';
923
+ // results.push(`${csprojPath}\t${csFile}\t${testFilePath}\t${testProjectPath}\t${result.testFileExist}`);
924
+ // console.log(`Processed: ${csprojPath}`);
925
+ // }
926
+ // }
927
+ // // Write results to file
928
+ // fs.writeFileSync(outputFile, results.join('\n'), 'utf-8');
929
+ // console.log(`Results written to ${outputFile}`);
930
+ // } catch (err) {
931
+ // console.error(`Error during verification: ${err}`);
932
+ // }
933
+ // }
934
+ // // Uncomment to run verification
935
+ // verifyTestFilePaths();
936
+ // getOrCreatTestFile("Q:\\src\\Microsoft.News.OneService\\Libraries\\Tests\\MockStrategies\\MockDataException.cs", undefined);
264
937
  // Example usage
265
938
  // const csFilePath = "D:\\code\\CS.Service.Fundamental\\SharedSegments\\SharedSegments\\SharedSegments.Plugins\\Workflows\\LocationDetection\\AudienceResponseParser.cs";
266
939
  // const csFilePath = "D:\\code\\msnews-experiences\\experiences\\weather-right-rail\\src\\WeatherRightRail.connector.ts";