cast-code 1.0.6 → 1.0.7

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 (29) hide show
  1. package/dist/common/services/multi-llm.service.js +19 -0
  2. package/dist/common/services/multi-llm.service.js.map +1 -1
  3. package/dist/modules/config/services/config-commands.service.js +86 -6
  4. package/dist/modules/config/services/config-commands.service.js.map +1 -1
  5. package/dist/modules/config/types/config.types.js +5 -0
  6. package/dist/modules/config/types/config.types.js.map +1 -1
  7. package/dist/modules/config/types/config.types.spec.js +60 -0
  8. package/dist/modules/config/types/config.types.spec.js.map +1 -0
  9. package/dist/modules/core/services/deep-agent.service.js +40 -4
  10. package/dist/modules/core/services/deep-agent.service.js.map +1 -1
  11. package/dist/modules/git/git.module.js +5 -2
  12. package/dist/modules/git/git.module.js.map +1 -1
  13. package/dist/modules/git/git.module.spec.js +54 -0
  14. package/dist/modules/git/git.module.spec.js.map +1 -0
  15. package/dist/modules/git/services/unit-test-generator.service.js +557 -0
  16. package/dist/modules/git/services/unit-test-generator.service.js.map +1 -0
  17. package/dist/modules/git/services/unit-test-generator.service.spec.js +119 -0
  18. package/dist/modules/git/services/unit-test-generator.service.spec.js.map +1 -0
  19. package/dist/modules/repl/services/commands/git-commands.service.js +97 -2
  20. package/dist/modules/repl/services/commands/git-commands.service.js.map +1 -1
  21. package/dist/modules/repl/services/commands/repl-commands.service.js +1 -0
  22. package/dist/modules/repl/services/commands/repl-commands.service.js.map +1 -1
  23. package/dist/modules/repl/services/commands/repl-commands.service.spec.js +69 -0
  24. package/dist/modules/repl/services/commands/repl-commands.service.spec.js.map +1 -0
  25. package/dist/modules/repl/services/repl.service.js +11 -1
  26. package/dist/modules/repl/services/repl.service.js.map +1 -1
  27. package/dist/modules/repl/services/repl.service.spec.js +137 -0
  28. package/dist/modules/repl/services/repl.service.spec.js.map +1 -0
  29. package/package.json +1 -1
@@ -0,0 +1,557 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "UnitTestGeneratorService", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return UnitTestGeneratorService;
9
+ }
10
+ });
11
+ const _common = require("@nestjs/common");
12
+ const _child_process = require("child_process");
13
+ const _messages = require("@langchain/core/messages");
14
+ const _multillmservice = require("../../../common/services/multi-llm.service");
15
+ function _ts_decorate(decorators, target, key, desc) {
16
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
17
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
18
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
19
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
20
+ }
21
+ function _ts_metadata(k, v) {
22
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
23
+ }
24
+ let UnitTestGeneratorService = class UnitTestGeneratorService {
25
+ detectDefaultBaseBranch() {
26
+ try {
27
+ const cwd = process.cwd();
28
+ const candidates = [
29
+ 'main',
30
+ 'master',
31
+ 'develop'
32
+ ];
33
+ for (const branch of candidates){
34
+ try {
35
+ (0, _child_process.execSync)(`git rev-parse --verify ${branch} 2>/dev/null || git rev-parse --verify origin/${branch} 2>/dev/null`, {
36
+ cwd,
37
+ stdio: 'ignore'
38
+ });
39
+ return branch;
40
+ } catch {}
41
+ }
42
+ return 'main';
43
+ } catch {
44
+ return 'main';
45
+ }
46
+ }
47
+ getChangedFiles(baseBranch) {
48
+ const cwd = process.cwd();
49
+ const files = new Set();
50
+ const outputs = [
51
+ this.execGit(`git diff --name-only ${baseBranch}..HEAD`, cwd),
52
+ this.execGit('git diff --name-only --cached', cwd),
53
+ this.execGit('git diff --name-only', cwd),
54
+ this.execGit('git ls-files --others --exclude-standard', cwd)
55
+ ];
56
+ for (const output of outputs){
57
+ for (const file of output.split('\n').map((f)=>f.trim()).filter((f)=>f)){
58
+ files.add(file);
59
+ }
60
+ }
61
+ return Array.from(files);
62
+ }
63
+ detectTestFramework() {
64
+ try {
65
+ const fs = require('fs');
66
+ const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
67
+ const deps = {
68
+ ...pkg.dependencies || {},
69
+ ...pkg.devDependencies || {}
70
+ };
71
+ if (deps.vitest) return 'vitest';
72
+ if (deps.jest || deps['ts-jest']) return 'jest';
73
+ if (deps.mocha) return 'mocha';
74
+ return 'node:test';
75
+ } catch {
76
+ return 'node:test';
77
+ }
78
+ }
79
+ async generateUnitTests(baseBranch, onProgress) {
80
+ const changedFiles = this.getChangedFiles(baseBranch);
81
+ const relevantFiles = this.filterRelevantFiles(changedFiles);
82
+ if (relevantFiles.length === 0) {
83
+ return {
84
+ files: [],
85
+ framework: this.detectTestFramework(),
86
+ notes: [
87
+ 'No relevant source files changed.'
88
+ ]
89
+ };
90
+ }
91
+ const plans = this.buildTestPlans(baseBranch, relevantFiles);
92
+ if (plans.length === 0) {
93
+ return {
94
+ files: [],
95
+ framework: this.detectTestFramework(),
96
+ notes: [
97
+ 'No valid source files available for test generation.'
98
+ ]
99
+ };
100
+ }
101
+ let selectedPlans = plans;
102
+ const notes = [];
103
+ if (plans.length > this.MAX_FILES_PER_RUN) {
104
+ selectedPlans = plans.slice(0, this.MAX_FILES_PER_RUN);
105
+ notes.push(`Generation limited to ${this.MAX_FILES_PER_RUN} files (from ${plans.length}). Run /unit-test again after staging smaller batches.`);
106
+ }
107
+ const llm = this.createTestModel();
108
+ const generatedFiles = [];
109
+ for(let idx = 0; idx < selectedPlans.length; idx++){
110
+ const plan = selectedPlans[idx];
111
+ onProgress?.({
112
+ current: idx + 1,
113
+ total: selectedPlans.length,
114
+ sourcePath: plan.sourcePath
115
+ });
116
+ try {
117
+ const firstResponse = await this.invokeWithTimeout(llm, [
118
+ new _messages.SystemMessage(this.getSystemPrompt()),
119
+ new _messages.HumanMessage(this.buildFilePrompt(plan))
120
+ ]);
121
+ const firstContent = this.extractContent(firstResponse.content);
122
+ const firstParsed = this.parseJson(firstContent);
123
+ if (!firstParsed || !firstParsed.content) {
124
+ notes.push(`Failed to parse generated tests for ${plan.sourcePath}.`);
125
+ continue;
126
+ }
127
+ let generated = String(firstParsed.content).trimEnd();
128
+ if (!generated) {
129
+ notes.push(`Empty test content generated for ${plan.sourcePath}.`);
130
+ continue;
131
+ }
132
+ let validation = this.validateGeneratedTest(plan, generated);
133
+ if (!validation.valid) {
134
+ const revised = await this.reviseGeneratedTest(llm, plan, generated, validation.issues);
135
+ if (revised) {
136
+ generated = revised;
137
+ validation = this.validateGeneratedTest(plan, generated);
138
+ }
139
+ }
140
+ if (!validation.valid) {
141
+ notes.push(`Low quality tests for ${plan.sourcePath}: ${validation.issues.join(' | ')}`);
142
+ continue;
143
+ }
144
+ generatedFiles.push({
145
+ path: plan.testPath,
146
+ content: generated,
147
+ reason: firstParsed.reason ? String(firstParsed.reason) : undefined
148
+ });
149
+ } catch (error) {
150
+ const message = typeof error?.message === 'string' ? error.message : 'unknown error';
151
+ notes.push(`Failed to generate tests for ${plan.sourcePath}: ${message}`);
152
+ }
153
+ }
154
+ const missingPlans = selectedPlans.filter((plan)=>!generatedFiles.some((file)=>file.path === plan.testPath));
155
+ for (const missing of missingPlans){
156
+ notes.push(`No generated test file for ${missing.sourcePath} (${missing.testPath}).`);
157
+ }
158
+ return {
159
+ files: generatedFiles,
160
+ framework: this.detectTestFramework(),
161
+ notes
162
+ };
163
+ }
164
+ buildTestPlans(baseBranch, sourceFiles) {
165
+ const plans = [];
166
+ const fs = require('fs');
167
+ for (const sourcePath of sourceFiles){
168
+ const language = this.getLanguageForFile(sourcePath);
169
+ if (!language) continue;
170
+ if (!fs.existsSync(sourcePath)) continue;
171
+ const sourceContent = this.readFileSafely(sourcePath, 10000);
172
+ if (!sourceContent.trim()) continue;
173
+ const testPath = this.resolveTestPath(sourcePath, language);
174
+ const existingTest = fs.existsSync(testPath);
175
+ const existingTestContent = existingTest ? this.readFileSafely(testPath, 10000) : '';
176
+ const fileDiff = this.getFileDiff(baseBranch, sourcePath, 8000);
177
+ const changedSymbols = this.extractChangedSymbols(fileDiff, language);
178
+ plans.push({
179
+ sourcePath,
180
+ testPath,
181
+ language,
182
+ framework: this.detectFrameworkForLanguage(language),
183
+ sourceContent,
184
+ existingTestContent,
185
+ existingTest,
186
+ fileDiff,
187
+ changedSymbols
188
+ });
189
+ }
190
+ return plans;
191
+ }
192
+ resolveTestPath(sourcePath, language) {
193
+ if (language === 'java') {
194
+ if (sourcePath.includes('/src/main/java/')) {
195
+ return sourcePath.replace('/src/main/java/', '/src/test/java/').replace(/\.java$/, 'Test.java');
196
+ }
197
+ return sourcePath.replace(/\.java$/, 'Test.java');
198
+ }
199
+ if (language === 'python') {
200
+ const normalized = sourcePath.startsWith('src/') ? sourcePath.slice(4) : sourcePath;
201
+ const fileName = normalized.split('/').pop() || 'module.py';
202
+ return `tests/test_${fileName.replace(/\.py$/, '')}.py`;
203
+ }
204
+ return sourcePath.replace(/\.(ts|tsx|js|jsx)$/, '.spec.ts');
205
+ }
206
+ getFileDiff(baseBranch, filePath, maxChars = 8000) {
207
+ const cwd = process.cwd();
208
+ const parts = [
209
+ this.execGit(`git diff ${baseBranch}..HEAD -- "${filePath}"`, cwd),
210
+ this.execGit(`git diff --cached -- "${filePath}"`, cwd),
211
+ this.execGit(`git diff -- "${filePath}"`, cwd)
212
+ ].filter((p)=>p.trim());
213
+ const combined = parts.join('\n\n');
214
+ if (!combined.trim()) return '';
215
+ return combined.length > maxChars ? combined.slice(0, maxChars) + '\n... (diff truncated)' : combined;
216
+ }
217
+ filterRelevantFiles(files) {
218
+ return files.filter((f)=>{
219
+ if (!/\.(ts|tsx|js|jsx|java|py)$/.test(f)) return false;
220
+ if (/(\.spec\.|\.test\.)/.test(f)) return false;
221
+ if (/src\/test\/|__tests__|tests\/|test_.*\.py$|.*_test\.py$|.*Test\.java$/.test(f)) return false;
222
+ if (f.startsWith('dist/') || f.startsWith('node_modules/')) return false;
223
+ return true;
224
+ });
225
+ }
226
+ getSystemPrompt() {
227
+ return [
228
+ 'You are a senior test engineer.',
229
+ 'Generate deterministic and high-value unit tests only.',
230
+ 'Avoid trivial tests that only assert definition/existence.',
231
+ 'Prefer behavior-oriented assertions and edge cases.',
232
+ 'Never return placeholders or TODOs.'
233
+ ].join(' ');
234
+ }
235
+ buildFilePrompt(plan) {
236
+ const changedSymbols = plan.changedSymbols.length > 0 ? plan.changedSymbols.join(', ') : '(none detected)';
237
+ const existingLabel = plan.existingTest ? 'EXISTING TEST FILE (update this file completely)' : 'NO EXISTING TEST FILE (create new file)';
238
+ const existingContent = plan.existingTestContent || '(empty)';
239
+ const diff = plan.fileDiff || '(no textual diff available, rely on source code)';
240
+ return `Goal:
241
+ Generate or update unit tests for one source file.
242
+
243
+ Language: ${plan.language}
244
+ Framework: ${plan.framework}
245
+ Source file: ${plan.sourcePath}
246
+ Target test file path: ${plan.testPath}
247
+ Status: ${existingLabel}
248
+ Changed symbols that MUST be covered when applicable: ${changedSymbols}
249
+
250
+ Source code (truncated):
251
+ ${plan.sourceContent}
252
+
253
+ Source diff (truncated):
254
+ ${diff}
255
+
256
+ Current test file content (truncated):
257
+ ${existingContent}
258
+
259
+ Rules:
260
+ - Return the full test file content for ${plan.testPath}.
261
+ - If test file exists, preserve useful existing tests and add coverage for new/changed behavior.
262
+ - Create at least one test case for each changed symbol when testable.
263
+ - Add a short comment immediately above each test case explaining what is validated.
264
+ - Use correct comment syntax: // for javascript/java, # for python.
265
+ - Unit tests only, no integration/E2E.
266
+ - Mock external dependencies and I/O.
267
+ - Include at least one happy-path test and one edge/error-path test when applicable.
268
+ - Every test case must contain meaningful assertions.
269
+ - Keep imports and setup minimal, compile-ready, and framework-correct.
270
+ - Do not use placeholders like TODO, FIXME, or "to be implemented".
271
+
272
+ OUTPUT FORMAT (JSON):
273
+ \`\`\`json
274
+ {
275
+ "content": "full file content",
276
+ "reason": "short summary"
277
+ }
278
+ \`\`\``;
279
+ }
280
+ async reviseGeneratedTest(llm, plan, previousContent, issues) {
281
+ try {
282
+ const response = await this.invokeWithTimeout(llm, [
283
+ new _messages.SystemMessage(this.getSystemPrompt()),
284
+ new _messages.HumanMessage(this.buildRevisionPrompt(plan, previousContent, issues))
285
+ ]);
286
+ const content = this.extractContent(response.content);
287
+ const parsed = this.parseJson(content);
288
+ if (!parsed || !parsed.content) return null;
289
+ const revised = String(parsed.content).trimEnd();
290
+ return revised || null;
291
+ } catch {
292
+ return null;
293
+ }
294
+ }
295
+ buildRevisionPrompt(plan, previousContent, issues) {
296
+ return `The previous test output for ${plan.testPath} did not pass quality checks.
297
+
298
+ Issues to fix:
299
+ ${issues.map((issue, idx)=>`${idx + 1}. ${issue}`).join('\n')}
300
+
301
+ Return a corrected full file, keeping the same target path.
302
+
303
+ Previous content:
304
+ ${previousContent}
305
+
306
+ OUTPUT FORMAT (JSON):
307
+ \`\`\`json
308
+ {
309
+ "content": "full corrected file content",
310
+ "reason": "short summary"
311
+ }
312
+ \`\`\``;
313
+ }
314
+ parseJson(content) {
315
+ const match = content.match(/```json\s*([\s\S]*?)\s*```/) || content.match(/\{[\s\S]*\}/);
316
+ if (!match) return null;
317
+ try {
318
+ return JSON.parse(match[1] || match[0]);
319
+ } catch {
320
+ return null;
321
+ }
322
+ }
323
+ extractContent(content) {
324
+ if (typeof content === 'string') return content;
325
+ if (Array.isArray(content) && content.length > 0) {
326
+ const first = content[0];
327
+ if (typeof first === 'object' && first !== null && 'text' in first) {
328
+ return String(first.text);
329
+ }
330
+ }
331
+ return String(content);
332
+ }
333
+ execGit(command, cwd) {
334
+ try {
335
+ return (0, _child_process.execSync)(command, {
336
+ cwd,
337
+ encoding: 'utf-8'
338
+ });
339
+ } catch {
340
+ return '';
341
+ }
342
+ }
343
+ createTestModel() {
344
+ try {
345
+ return this.multiLlmService.createModel('tester');
346
+ } catch {
347
+ return this.multiLlmService.createModel('cheap');
348
+ }
349
+ }
350
+ async invokeWithTimeout(llm, messages) {
351
+ return Promise.race([
352
+ llm.invoke(messages),
353
+ new Promise((_, reject)=>setTimeout(()=>reject(new Error(`LLM timeout after ${this.LLM_TIMEOUT_MS / 1000}s`)), this.LLM_TIMEOUT_MS))
354
+ ]);
355
+ }
356
+ getLanguageForFile(file) {
357
+ if (/\.(ts|tsx|js|jsx)$/.test(file)) return 'javascript';
358
+ if (/\.java$/.test(file)) return 'java';
359
+ if (/\.py$/.test(file)) return 'python';
360
+ return null;
361
+ }
362
+ detectFrameworkForLanguage(language) {
363
+ if (language === 'java') {
364
+ return this.detectJavaFramework();
365
+ }
366
+ if (language === 'python') {
367
+ return this.detectPythonFramework();
368
+ }
369
+ return this.detectTestFramework();
370
+ }
371
+ detectJavaFramework() {
372
+ try {
373
+ const fs = require('fs');
374
+ if (fs.existsSync('build.gradle') || fs.existsSync('build.gradle.kts')) return 'junit5 (gradle)';
375
+ if (fs.existsSync('pom.xml')) return 'junit5 (maven)';
376
+ } catch {}
377
+ return 'junit5';
378
+ }
379
+ detectPythonFramework() {
380
+ try {
381
+ const fs = require('fs');
382
+ if (fs.existsSync('pyproject.toml')) {
383
+ const raw = fs.readFileSync('pyproject.toml', 'utf-8');
384
+ if (raw.includes('pytest')) return 'pytest';
385
+ }
386
+ if (fs.existsSync('requirements.txt')) {
387
+ const raw = fs.readFileSync('requirements.txt', 'utf-8');
388
+ if (raw.toLowerCase().includes('pytest')) return 'pytest';
389
+ }
390
+ } catch {}
391
+ return 'pytest';
392
+ }
393
+ readFileSafely(filePath, maxChars = 10000) {
394
+ try {
395
+ const fs = require('fs');
396
+ const raw = fs.readFileSync(filePath, 'utf-8');
397
+ return raw.length > maxChars ? raw.slice(0, maxChars) + '\n... (file truncated)' : raw;
398
+ } catch {
399
+ return '';
400
+ }
401
+ }
402
+ extractChangedSymbols(diff, language) {
403
+ if (!diff.trim()) return [];
404
+ const lines = diff.split('\n').filter((line)=>line.startsWith('+') && !line.startsWith('+++'));
405
+ const symbols = new Set();
406
+ for (const line of lines){
407
+ const text = line.slice(1);
408
+ const matches = this.extractSymbolsFromLine(text, language);
409
+ for (const symbol of matches){
410
+ symbols.add(symbol);
411
+ }
412
+ }
413
+ return Array.from(symbols).slice(0, 20);
414
+ }
415
+ extractSymbolsFromLine(line, language) {
416
+ const symbols = [];
417
+ if (language === 'python') {
418
+ const fn = line.match(/^\s*def\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
419
+ if (fn?.[1]) symbols.push(fn[1]);
420
+ return symbols;
421
+ }
422
+ const classMatch = line.match(/^\s*(?:export\s+)?class\s+([A-Za-z_][A-Za-z0-9_]*)\b/);
423
+ if (classMatch?.[1]) symbols.push(classMatch[1]);
424
+ const fnDecl = line.match(/^\s*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
425
+ if (fnDecl?.[1]) symbols.push(fnDecl[1]);
426
+ const varFn = line.match(/^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:async\s*)?\(/);
427
+ if (varFn?.[1]) symbols.push(varFn[1]);
428
+ const method = line.match(/^\s*(?:public|private|protected|static|async|\s)*([A-Za-z_][A-Za-z0-9_]*)\s*\([^)]*\)\s*\{/);
429
+ if (method?.[1] && ![
430
+ 'if',
431
+ 'for',
432
+ 'while',
433
+ 'switch',
434
+ 'catch'
435
+ ].includes(method[1])) {
436
+ symbols.push(method[1]);
437
+ }
438
+ return symbols;
439
+ }
440
+ getMissingSymbols(symbols, generatedTestContent) {
441
+ if (symbols.length === 0) return [];
442
+ const lowered = generatedTestContent.toLowerCase();
443
+ return symbols.filter((symbol)=>!lowered.includes(symbol.toLowerCase()));
444
+ }
445
+ validateGeneratedTest(plan, content) {
446
+ const issues = [];
447
+ if (/\bTODO\b|\bFIXME\b|to be implemented/i.test(content)) {
448
+ issues.push('contains placeholders (TODO/FIXME)');
449
+ }
450
+ if (content.trim().length < 120) {
451
+ issues.push('content too short');
452
+ }
453
+ const missingSymbols = this.getMissingSymbols(plan.changedSymbols, content);
454
+ if (missingSymbols.length > 0) {
455
+ issues.push(`missing changed symbols: ${missingSymbols.join(', ')}`);
456
+ }
457
+ const languageChecks = this.validateByLanguage(plan.language, content);
458
+ issues.push(...languageChecks);
459
+ return {
460
+ valid: issues.length === 0,
461
+ issues
462
+ };
463
+ }
464
+ validateByLanguage(language, content) {
465
+ if (language === 'python') {
466
+ return this.validatePythonTest(content);
467
+ }
468
+ if (language === 'java') {
469
+ return this.validateJavaTest(content);
470
+ }
471
+ return this.validateJsTest(content);
472
+ }
473
+ validateJsTest(content) {
474
+ const issues = [];
475
+ const tests = this.getMatchingLines(content, /^\s*(?:it|test)\s*\(/gm);
476
+ if (tests.length === 0) {
477
+ issues.push('no test cases found (it/test)');
478
+ return issues;
479
+ }
480
+ if (!/(expect\s*\(|assert\.)/.test(content)) {
481
+ issues.push('no meaningful assertions found');
482
+ }
483
+ if (!this.hasCommentsAboveTests(content, tests, '//')) {
484
+ issues.push('missing short comment above test case');
485
+ }
486
+ return issues;
487
+ }
488
+ validateJavaTest(content) {
489
+ const issues = [];
490
+ const tests = this.getMatchingLines(content, /^\s*@Test\b/gm);
491
+ if (tests.length === 0) {
492
+ issues.push('no @Test methods found');
493
+ return issues;
494
+ }
495
+ if (!/\bassert[A-Za-z]+\s*\(/.test(content)) {
496
+ issues.push('no JUnit assertions found');
497
+ }
498
+ if (!this.hasCommentsAboveTests(content, tests, '//')) {
499
+ issues.push('missing short comment above test case');
500
+ }
501
+ return issues;
502
+ }
503
+ validatePythonTest(content) {
504
+ const issues = [];
505
+ const tests = this.getMatchingLines(content, /^\s*def\s+test_[A-Za-z0-9_]*\s*\(/gm);
506
+ if (tests.length === 0) {
507
+ issues.push('no pytest test functions found');
508
+ return issues;
509
+ }
510
+ if (!/\bassert\b/.test(content)) {
511
+ issues.push('no assert statements found');
512
+ }
513
+ if (!this.hasCommentsAboveTests(content, tests, '#')) {
514
+ issues.push('missing short comment above test case');
515
+ }
516
+ return issues;
517
+ }
518
+ getMatchingLines(content, pattern) {
519
+ const lines = content.split('\n');
520
+ const matched = [];
521
+ for(let i = 0; i < lines.length; i++){
522
+ if (pattern.test(lines[i])) {
523
+ matched.push(i);
524
+ }
525
+ pattern.lastIndex = 0;
526
+ }
527
+ return matched;
528
+ }
529
+ hasCommentsAboveTests(content, testLineIndexes, commentPrefix) {
530
+ const lines = content.split('\n');
531
+ for (const testIdx of testLineIndexes){
532
+ let prev = testIdx - 1;
533
+ while(prev >= 0 && lines[prev].trim() === ''){
534
+ prev--;
535
+ }
536
+ if (prev < 0) return false;
537
+ if (!lines[prev].trim().startsWith(commentPrefix)) {
538
+ return false;
539
+ }
540
+ }
541
+ return true;
542
+ }
543
+ constructor(multiLlmService){
544
+ this.multiLlmService = multiLlmService;
545
+ this.MAX_FILES_PER_RUN = 12;
546
+ this.LLM_TIMEOUT_MS = 70000;
547
+ }
548
+ };
549
+ UnitTestGeneratorService = _ts_decorate([
550
+ (0, _common.Injectable)(),
551
+ _ts_metadata("design:type", Function),
552
+ _ts_metadata("design:paramtypes", [
553
+ typeof _multillmservice.MultiLlmService === "undefined" ? Object : _multillmservice.MultiLlmService
554
+ ])
555
+ ], UnitTestGeneratorService);
556
+
557
+ //# sourceMappingURL=unit-test-generator.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/modules/git/services/unit-test-generator.service.ts"],"sourcesContent":["import { Injectable } from '@nestjs/common';\nimport { execSync } from 'child_process';\nimport { HumanMessage, SystemMessage } from '@langchain/core/messages';\nimport { MultiLlmService } from '../../../common/services/multi-llm.service';\n\nexport interface GeneratedTestFile {\n path: string;\n content: string;\n reason?: string;\n}\n\nexport interface UnitTestGenerationResult {\n files: GeneratedTestFile[];\n framework: string;\n notes: string[];\n}\n\ninterface UnitTestProgress {\n current: number;\n total: number;\n sourcePath: string;\n}\n\ninterface SourceTestPlan {\n sourcePath: string;\n testPath: string;\n language: 'javascript' | 'java' | 'python';\n framework: string;\n sourceContent: string;\n existingTestContent: string;\n existingTest: boolean;\n fileDiff: string;\n changedSymbols: string[];\n}\n\ninterface TestValidationResult {\n valid: boolean;\n issues: string[];\n}\n\n@Injectable()\nexport class UnitTestGeneratorService {\n private readonly MAX_FILES_PER_RUN = 12;\n private readonly LLM_TIMEOUT_MS = 70000;\n\n constructor(private readonly multiLlmService: MultiLlmService) {}\n\n detectDefaultBaseBranch(): string {\n try {\n const cwd = process.cwd();\n const candidates = ['main', 'master', 'develop'];\n for (const branch of candidates) {\n try {\n execSync(`git rev-parse --verify ${branch} 2>/dev/null || git rev-parse --verify origin/${branch} 2>/dev/null`, {\n cwd,\n stdio: 'ignore',\n });\n return branch;\n } catch {}\n }\n return 'main';\n } catch {\n return 'main';\n }\n }\n\n getChangedFiles(baseBranch: string): string[] {\n const cwd = process.cwd();\n const files = new Set<string>();\n\n const outputs = [\n this.execGit(`git diff --name-only ${baseBranch}..HEAD`, cwd),\n this.execGit('git diff --name-only --cached', cwd),\n this.execGit('git diff --name-only', cwd),\n this.execGit('git ls-files --others --exclude-standard', cwd),\n ];\n\n for (const output of outputs) {\n for (const file of output.split('\\n').map((f) => f.trim()).filter((f) => f)) {\n files.add(file);\n }\n }\n\n return Array.from(files);\n }\n\n detectTestFramework(): string {\n try {\n const fs = require('fs');\n const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));\n const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };\n if (deps.vitest) return 'vitest';\n if (deps.jest || deps['ts-jest']) return 'jest';\n if (deps.mocha) return 'mocha';\n return 'node:test';\n } catch {\n return 'node:test';\n }\n }\n\n async generateUnitTests(\n baseBranch: string,\n onProgress?: (progress: UnitTestProgress) => void,\n ): Promise<UnitTestGenerationResult> {\n const changedFiles = this.getChangedFiles(baseBranch);\n const relevantFiles = this.filterRelevantFiles(changedFiles);\n\n if (relevantFiles.length === 0) {\n return { files: [], framework: this.detectTestFramework(), notes: ['No relevant source files changed.'] };\n }\n\n const plans = this.buildTestPlans(baseBranch, relevantFiles);\n if (plans.length === 0) {\n return { files: [], framework: this.detectTestFramework(), notes: ['No valid source files available for test generation.'] };\n }\n\n let selectedPlans = plans;\n const notes: string[] = [];\n if (plans.length > this.MAX_FILES_PER_RUN) {\n selectedPlans = plans.slice(0, this.MAX_FILES_PER_RUN);\n notes.push(\n `Generation limited to ${this.MAX_FILES_PER_RUN} files (from ${plans.length}). Run /unit-test again after staging smaller batches.`\n );\n }\n\n const llm = this.createTestModel();\n const generatedFiles: GeneratedTestFile[] = [];\n\n for (let idx = 0; idx < selectedPlans.length; idx++) {\n const plan = selectedPlans[idx];\n onProgress?.({\n current: idx + 1,\n total: selectedPlans.length,\n sourcePath: plan.sourcePath,\n });\n try {\n const firstResponse = await this.invokeWithTimeout(llm, [\n new SystemMessage(this.getSystemPrompt()),\n new HumanMessage(this.buildFilePrompt(plan)),\n ]);\n\n const firstContent = this.extractContent(firstResponse.content);\n const firstParsed = this.parseJson(firstContent);\n if (!firstParsed || !firstParsed.content) {\n notes.push(`Failed to parse generated tests for ${plan.sourcePath}.`);\n continue;\n }\n\n let generated = String(firstParsed.content).trimEnd();\n if (!generated) {\n notes.push(`Empty test content generated for ${plan.sourcePath}.`);\n continue;\n }\n\n let validation = this.validateGeneratedTest(plan, generated);\n if (!validation.valid) {\n const revised = await this.reviseGeneratedTest(llm, plan, generated, validation.issues);\n if (revised) {\n generated = revised;\n validation = this.validateGeneratedTest(plan, generated);\n }\n }\n\n if (!validation.valid) {\n notes.push(`Low quality tests for ${plan.sourcePath}: ${validation.issues.join(' | ')}`);\n continue;\n }\n\n generatedFiles.push({\n path: plan.testPath,\n content: generated,\n reason: firstParsed.reason ? String(firstParsed.reason) : undefined,\n });\n } catch (error: any) {\n const message = typeof error?.message === 'string' ? error.message : 'unknown error';\n notes.push(`Failed to generate tests for ${plan.sourcePath}: ${message}`);\n }\n }\n\n const missingPlans = selectedPlans.filter((plan) => !generatedFiles.some((file) => file.path === plan.testPath));\n for (const missing of missingPlans) {\n notes.push(`No generated test file for ${missing.sourcePath} (${missing.testPath}).`);\n }\n\n return {\n files: generatedFiles,\n framework: this.detectTestFramework(),\n notes,\n };\n }\n\n private buildTestPlans(baseBranch: string, sourceFiles: string[]): SourceTestPlan[] {\n const plans: SourceTestPlan[] = [];\n const fs = require('fs');\n\n for (const sourcePath of sourceFiles) {\n const language = this.getLanguageForFile(sourcePath);\n if (!language) continue;\n if (!fs.existsSync(sourcePath)) continue;\n\n const sourceContent = this.readFileSafely(sourcePath, 10000);\n if (!sourceContent.trim()) continue;\n\n const testPath = this.resolveTestPath(sourcePath, language);\n const existingTest = fs.existsSync(testPath);\n const existingTestContent = existingTest ? this.readFileSafely(testPath, 10000) : '';\n const fileDiff = this.getFileDiff(baseBranch, sourcePath, 8000);\n const changedSymbols = this.extractChangedSymbols(fileDiff, language);\n\n plans.push({\n sourcePath,\n testPath,\n language,\n framework: this.detectFrameworkForLanguage(language),\n sourceContent,\n existingTestContent,\n existingTest,\n fileDiff,\n changedSymbols,\n });\n }\n\n return plans;\n }\n\n private resolveTestPath(sourcePath: string, language: 'javascript' | 'java' | 'python'): string {\n if (language === 'java') {\n if (sourcePath.includes('/src/main/java/')) {\n return sourcePath.replace('/src/main/java/', '/src/test/java/').replace(/\\.java$/, 'Test.java');\n }\n return sourcePath.replace(/\\.java$/, 'Test.java');\n }\n\n if (language === 'python') {\n const normalized = sourcePath.startsWith('src/') ? sourcePath.slice(4) : sourcePath;\n const fileName = normalized.split('/').pop() || 'module.py';\n return `tests/test_${fileName.replace(/\\.py$/, '')}.py`;\n }\n\n return sourcePath.replace(/\\.(ts|tsx|js|jsx)$/, '.spec.ts');\n }\n\n private getFileDiff(baseBranch: string, filePath: string, maxChars = 8000): string {\n const cwd = process.cwd();\n const parts = [\n this.execGit(`git diff ${baseBranch}..HEAD -- \"${filePath}\"`, cwd),\n this.execGit(`git diff --cached -- \"${filePath}\"`, cwd),\n this.execGit(`git diff -- \"${filePath}\"`, cwd),\n ].filter((p) => p.trim());\n\n const combined = parts.join('\\n\\n');\n if (!combined.trim()) return '';\n return combined.length > maxChars ? combined.slice(0, maxChars) + '\\n... (diff truncated)' : combined;\n }\n\n private filterRelevantFiles(files: string[]): string[] {\n return files.filter((f) => {\n if (!/\\.(ts|tsx|js|jsx|java|py)$/.test(f)) return false;\n if (/(\\.spec\\.|\\.test\\.)/.test(f)) return false;\n if (/src\\/test\\/|__tests__|tests\\/|test_.*\\.py$|.*_test\\.py$|.*Test\\.java$/.test(f)) return false;\n if (f.startsWith('dist/') || f.startsWith('node_modules/')) return false;\n return true;\n });\n }\n\n private getSystemPrompt(): string {\n return [\n 'You are a senior test engineer.',\n 'Generate deterministic and high-value unit tests only.',\n 'Avoid trivial tests that only assert definition/existence.',\n 'Prefer behavior-oriented assertions and edge cases.',\n 'Never return placeholders or TODOs.',\n ].join(' ');\n }\n\n private buildFilePrompt(plan: SourceTestPlan): string {\n const changedSymbols = plan.changedSymbols.length > 0 ? plan.changedSymbols.join(', ') : '(none detected)';\n const existingLabel = plan.existingTest ? 'EXISTING TEST FILE (update this file completely)' : 'NO EXISTING TEST FILE (create new file)';\n const existingContent = plan.existingTestContent || '(empty)';\n const diff = plan.fileDiff || '(no textual diff available, rely on source code)';\n\n return `Goal:\nGenerate or update unit tests for one source file.\n\nLanguage: ${plan.language}\nFramework: ${plan.framework}\nSource file: ${plan.sourcePath}\nTarget test file path: ${plan.testPath}\nStatus: ${existingLabel}\nChanged symbols that MUST be covered when applicable: ${changedSymbols}\n\nSource code (truncated):\n${plan.sourceContent}\n\nSource diff (truncated):\n${diff}\n\nCurrent test file content (truncated):\n${existingContent}\n\nRules:\n- Return the full test file content for ${plan.testPath}.\n- If test file exists, preserve useful existing tests and add coverage for new/changed behavior.\n- Create at least one test case for each changed symbol when testable.\n- Add a short comment immediately above each test case explaining what is validated.\n- Use correct comment syntax: // for javascript/java, # for python.\n- Unit tests only, no integration/E2E.\n- Mock external dependencies and I/O.\n- Include at least one happy-path test and one edge/error-path test when applicable.\n- Every test case must contain meaningful assertions.\n- Keep imports and setup minimal, compile-ready, and framework-correct.\n- Do not use placeholders like TODO, FIXME, or \"to be implemented\".\n\nOUTPUT FORMAT (JSON):\n\\`\\`\\`json\n{\n \"content\": \"full file content\",\n \"reason\": \"short summary\"\n}\n\\`\\`\\``;\n }\n\n private async reviseGeneratedTest(\n llm: any,\n plan: SourceTestPlan,\n previousContent: string,\n issues: string[],\n ): Promise<string | null> {\n try {\n const response = await this.invokeWithTimeout(llm, [\n new SystemMessage(this.getSystemPrompt()),\n new HumanMessage(this.buildRevisionPrompt(plan, previousContent, issues)),\n ]);\n\n const content = this.extractContent(response.content);\n const parsed = this.parseJson(content);\n if (!parsed || !parsed.content) return null;\n const revised = String(parsed.content).trimEnd();\n return revised || null;\n } catch {\n return null;\n }\n }\n\n private buildRevisionPrompt(plan: SourceTestPlan, previousContent: string, issues: string[]): string {\n return `The previous test output for ${plan.testPath} did not pass quality checks.\n\nIssues to fix:\n${issues.map((issue, idx) => `${idx + 1}. ${issue}`).join('\\n')}\n\nReturn a corrected full file, keeping the same target path.\n\nPrevious content:\n${previousContent}\n\nOUTPUT FORMAT (JSON):\n\\`\\`\\`json\n{\n \"content\": \"full corrected file content\",\n \"reason\": \"short summary\"\n}\n\\`\\`\\``;\n }\n\n private parseJson(content: string): any | null {\n const match = content.match(/```json\\s*([\\s\\S]*?)\\s*```/) || content.match(/\\{[\\s\\S]*\\}/);\n if (!match) return null;\n try {\n return JSON.parse(match[1] || match[0]);\n } catch {\n return null;\n }\n }\n\n private extractContent(content: unknown): string {\n if (typeof content === 'string') return content;\n if (Array.isArray(content) && content.length > 0) {\n const first = content[0];\n if (typeof first === 'object' && first !== null && 'text' in first) {\n return String(first.text);\n }\n }\n return String(content);\n }\n\n private execGit(command: string, cwd: string): string {\n try {\n return execSync(command, { cwd, encoding: 'utf-8' });\n } catch {\n return '';\n }\n }\n\n private createTestModel() {\n try {\n return this.multiLlmService.createModel('tester');\n } catch {\n return this.multiLlmService.createModel('cheap');\n }\n }\n\n private async invokeWithTimeout(llm: any, messages: any[]): Promise<any> {\n return Promise.race([\n llm.invoke(messages),\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error(`LLM timeout after ${this.LLM_TIMEOUT_MS / 1000}s`)), this.LLM_TIMEOUT_MS)\n ),\n ]);\n }\n\n private getLanguageForFile(file: string): 'javascript' | 'java' | 'python' | null {\n if (/\\.(ts|tsx|js|jsx)$/.test(file)) return 'javascript';\n if (/\\.java$/.test(file)) return 'java';\n if (/\\.py$/.test(file)) return 'python';\n return null;\n }\n\n private detectFrameworkForLanguage(language: 'javascript' | 'java' | 'python'): string {\n if (language === 'java') {\n return this.detectJavaFramework();\n }\n if (language === 'python') {\n return this.detectPythonFramework();\n }\n return this.detectTestFramework();\n }\n\n private detectJavaFramework(): string {\n try {\n const fs = require('fs');\n if (fs.existsSync('build.gradle') || fs.existsSync('build.gradle.kts')) return 'junit5 (gradle)';\n if (fs.existsSync('pom.xml')) return 'junit5 (maven)';\n } catch {}\n return 'junit5';\n }\n\n private detectPythonFramework(): string {\n try {\n const fs = require('fs');\n if (fs.existsSync('pyproject.toml')) {\n const raw = fs.readFileSync('pyproject.toml', 'utf-8');\n if (raw.includes('pytest')) return 'pytest';\n }\n if (fs.existsSync('requirements.txt')) {\n const raw = fs.readFileSync('requirements.txt', 'utf-8');\n if (raw.toLowerCase().includes('pytest')) return 'pytest';\n }\n } catch {}\n return 'pytest';\n }\n\n private readFileSafely(filePath: string, maxChars = 10000): string {\n try {\n const fs = require('fs');\n const raw = fs.readFileSync(filePath, 'utf-8');\n return raw.length > maxChars ? raw.slice(0, maxChars) + '\\n... (file truncated)' : raw;\n } catch {\n return '';\n }\n }\n\n private extractChangedSymbols(\n diff: string,\n language: 'javascript' | 'java' | 'python',\n ): string[] {\n if (!diff.trim()) return [];\n const lines = diff.split('\\n').filter((line) => line.startsWith('+') && !line.startsWith('+++'));\n const symbols = new Set<string>();\n\n for (const line of lines) {\n const text = line.slice(1);\n const matches = this.extractSymbolsFromLine(text, language);\n for (const symbol of matches) {\n symbols.add(symbol);\n }\n }\n\n return Array.from(symbols).slice(0, 20);\n }\n\n private extractSymbolsFromLine(\n line: string,\n language: 'javascript' | 'java' | 'python',\n ): string[] {\n const symbols: string[] = [];\n\n if (language === 'python') {\n const fn = line.match(/^\\s*def\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(/);\n if (fn?.[1]) symbols.push(fn[1]);\n return symbols;\n }\n\n const classMatch = line.match(/^\\s*(?:export\\s+)?class\\s+([A-Za-z_][A-Za-z0-9_]*)\\b/);\n if (classMatch?.[1]) symbols.push(classMatch[1]);\n\n const fnDecl = line.match(/^\\s*(?:export\\s+)?(?:async\\s+)?function\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(/);\n if (fnDecl?.[1]) symbols.push(fnDecl[1]);\n\n const varFn = line.match(/^\\s*(?:export\\s+)?(?:const|let|var)\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*(?:async\\s*)?\\(/);\n if (varFn?.[1]) symbols.push(varFn[1]);\n\n const method = line.match(/^\\s*(?:public|private|protected|static|async|\\s)*([A-Za-z_][A-Za-z0-9_]*)\\s*\\([^)]*\\)\\s*\\{/);\n if (method?.[1] && !['if', 'for', 'while', 'switch', 'catch'].includes(method[1])) {\n symbols.push(method[1]);\n }\n\n return symbols;\n }\n\n private getMissingSymbols(symbols: string[], generatedTestContent: string): string[] {\n if (symbols.length === 0) return [];\n const lowered = generatedTestContent.toLowerCase();\n return symbols.filter((symbol) => !lowered.includes(symbol.toLowerCase()));\n }\n\n private validateGeneratedTest(plan: SourceTestPlan, content: string): TestValidationResult {\n const issues: string[] = [];\n\n if (/\\bTODO\\b|\\bFIXME\\b|to be implemented/i.test(content)) {\n issues.push('contains placeholders (TODO/FIXME)');\n }\n\n if (content.trim().length < 120) {\n issues.push('content too short');\n }\n\n const missingSymbols = this.getMissingSymbols(plan.changedSymbols, content);\n if (missingSymbols.length > 0) {\n issues.push(`missing changed symbols: ${missingSymbols.join(', ')}`);\n }\n\n const languageChecks = this.validateByLanguage(plan.language, content);\n issues.push(...languageChecks);\n\n return { valid: issues.length === 0, issues };\n }\n\n private validateByLanguage(language: 'javascript' | 'java' | 'python', content: string): string[] {\n if (language === 'python') {\n return this.validatePythonTest(content);\n }\n if (language === 'java') {\n return this.validateJavaTest(content);\n }\n return this.validateJsTest(content);\n }\n\n private validateJsTest(content: string): string[] {\n const issues: string[] = [];\n const tests = this.getMatchingLines(content, /^\\s*(?:it|test)\\s*\\(/gm);\n if (tests.length === 0) {\n issues.push('no test cases found (it/test)');\n return issues;\n }\n\n if (!/(expect\\s*\\(|assert\\.)/.test(content)) {\n issues.push('no meaningful assertions found');\n }\n\n if (!this.hasCommentsAboveTests(content, tests, '//')) {\n issues.push('missing short comment above test case');\n }\n\n return issues;\n }\n\n private validateJavaTest(content: string): string[] {\n const issues: string[] = [];\n const tests = this.getMatchingLines(content, /^\\s*@Test\\b/gm);\n if (tests.length === 0) {\n issues.push('no @Test methods found');\n return issues;\n }\n\n if (!/\\bassert[A-Za-z]+\\s*\\(/.test(content)) {\n issues.push('no JUnit assertions found');\n }\n\n if (!this.hasCommentsAboveTests(content, tests, '//')) {\n issues.push('missing short comment above test case');\n }\n\n return issues;\n }\n\n private validatePythonTest(content: string): string[] {\n const issues: string[] = [];\n const tests = this.getMatchingLines(content, /^\\s*def\\s+test_[A-Za-z0-9_]*\\s*\\(/gm);\n if (tests.length === 0) {\n issues.push('no pytest test functions found');\n return issues;\n }\n\n if (!/\\bassert\\b/.test(content)) {\n issues.push('no assert statements found');\n }\n\n if (!this.hasCommentsAboveTests(content, tests, '#')) {\n issues.push('missing short comment above test case');\n }\n\n return issues;\n }\n\n private getMatchingLines(content: string, pattern: RegExp): number[] {\n const lines = content.split('\\n');\n const matched: number[] = [];\n for (let i = 0; i < lines.length; i++) {\n if (pattern.test(lines[i])) {\n matched.push(i);\n }\n pattern.lastIndex = 0;\n }\n return matched;\n }\n\n private hasCommentsAboveTests(content: string, testLineIndexes: number[], commentPrefix: string): boolean {\n const lines = content.split('\\n');\n for (const testIdx of testLineIndexes) {\n let prev = testIdx - 1;\n while (prev >= 0 && lines[prev].trim() === '') {\n prev--;\n }\n if (prev < 0) return false;\n if (!lines[prev].trim().startsWith(commentPrefix)) {\n return false;\n }\n }\n return true;\n }\n}\n"],"names":["UnitTestGeneratorService","detectDefaultBaseBranch","cwd","process","candidates","branch","execSync","stdio","getChangedFiles","baseBranch","files","Set","outputs","execGit","output","file","split","map","f","trim","filter","add","Array","from","detectTestFramework","fs","require","pkg","JSON","parse","readFileSync","deps","dependencies","devDependencies","vitest","jest","mocha","generateUnitTests","onProgress","changedFiles","relevantFiles","filterRelevantFiles","length","framework","notes","plans","buildTestPlans","selectedPlans","MAX_FILES_PER_RUN","slice","push","llm","createTestModel","generatedFiles","idx","plan","current","total","sourcePath","firstResponse","invokeWithTimeout","SystemMessage","getSystemPrompt","HumanMessage","buildFilePrompt","firstContent","extractContent","content","firstParsed","parseJson","generated","String","trimEnd","validation","validateGeneratedTest","valid","revised","reviseGeneratedTest","issues","join","path","testPath","reason","undefined","error","message","missingPlans","some","missing","sourceFiles","language","getLanguageForFile","existsSync","sourceContent","readFileSafely","resolveTestPath","existingTest","existingTestContent","fileDiff","getFileDiff","changedSymbols","extractChangedSymbols","detectFrameworkForLanguage","includes","replace","normalized","startsWith","fileName","pop","filePath","maxChars","parts","p","combined","test","existingLabel","existingContent","diff","previousContent","response","buildRevisionPrompt","parsed","issue","match","isArray","first","text","command","encoding","multiLlmService","createModel","messages","Promise","race","invoke","_","reject","setTimeout","Error","LLM_TIMEOUT_MS","detectJavaFramework","detectPythonFramework","raw","toLowerCase","lines","line","symbols","matches","extractSymbolsFromLine","symbol","fn","classMatch","fnDecl","varFn","method","getMissingSymbols","generatedTestContent","lowered","missingSymbols","languageChecks","validateByLanguage","validatePythonTest","validateJavaTest","validateJsTest","tests","getMatchingLines","hasCommentsAboveTests","pattern","matched","i","lastIndex","testLineIndexes","commentPrefix","testIdx","prev"],"mappings":"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"}