@theihtisham/ai-testgen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +383 -0
  3. package/dist/analyzers/analyzer.d.ts +10 -0
  4. package/dist/analyzers/analyzer.d.ts.map +1 -0
  5. package/dist/analyzers/analyzer.js +131 -0
  6. package/dist/analyzers/analyzer.js.map +1 -0
  7. package/dist/analyzers/go-analyzer.d.ts +3 -0
  8. package/dist/analyzers/go-analyzer.d.ts.map +1 -0
  9. package/dist/analyzers/go-analyzer.js +244 -0
  10. package/dist/analyzers/go-analyzer.js.map +1 -0
  11. package/dist/analyzers/index.d.ts +5 -0
  12. package/dist/analyzers/index.d.ts.map +1 -0
  13. package/dist/analyzers/index.js +15 -0
  14. package/dist/analyzers/index.js.map +1 -0
  15. package/dist/analyzers/js-ts-analyzer.d.ts +3 -0
  16. package/dist/analyzers/js-ts-analyzer.d.ts.map +1 -0
  17. package/dist/analyzers/js-ts-analyzer.js +299 -0
  18. package/dist/analyzers/js-ts-analyzer.js.map +1 -0
  19. package/dist/analyzers/python-analyzer.d.ts +3 -0
  20. package/dist/analyzers/python-analyzer.d.ts.map +1 -0
  21. package/dist/analyzers/python-analyzer.js +306 -0
  22. package/dist/analyzers/python-analyzer.js.map +1 -0
  23. package/dist/cli.d.ts +3 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +381 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/config/defaults.d.ts +6 -0
  28. package/dist/config/defaults.d.ts.map +1 -0
  29. package/dist/config/defaults.js +80 -0
  30. package/dist/config/defaults.js.map +1 -0
  31. package/dist/config/index.d.ts +3 -0
  32. package/dist/config/index.d.ts.map +1 -0
  33. package/dist/config/index.js +14 -0
  34. package/dist/config/index.js.map +1 -0
  35. package/dist/config/loader.d.ts +6 -0
  36. package/dist/config/loader.d.ts.map +1 -0
  37. package/dist/config/loader.js +126 -0
  38. package/dist/config/loader.js.map +1 -0
  39. package/dist/coverage.d.ts +4 -0
  40. package/dist/coverage.d.ts.map +1 -0
  41. package/dist/coverage.js +108 -0
  42. package/dist/coverage.js.map +1 -0
  43. package/dist/generators/ai-generator.d.ts +4 -0
  44. package/dist/generators/ai-generator.d.ts.map +1 -0
  45. package/dist/generators/ai-generator.js +175 -0
  46. package/dist/generators/ai-generator.js.map +1 -0
  47. package/dist/generators/generator.d.ts +4 -0
  48. package/dist/generators/generator.d.ts.map +1 -0
  49. package/dist/generators/generator.js +121 -0
  50. package/dist/generators/generator.js.map +1 -0
  51. package/dist/generators/go-generator.d.ts +3 -0
  52. package/dist/generators/go-generator.d.ts.map +1 -0
  53. package/dist/generators/go-generator.js +175 -0
  54. package/dist/generators/go-generator.js.map +1 -0
  55. package/dist/generators/index.d.ts +6 -0
  56. package/dist/generators/index.d.ts.map +1 -0
  57. package/dist/generators/index.js +16 -0
  58. package/dist/generators/index.js.map +1 -0
  59. package/dist/generators/js-ts-generator.d.ts +3 -0
  60. package/dist/generators/js-ts-generator.d.ts.map +1 -0
  61. package/dist/generators/js-ts-generator.js +331 -0
  62. package/dist/generators/js-ts-generator.js.map +1 -0
  63. package/dist/generators/python-generator.d.ts +3 -0
  64. package/dist/generators/python-generator.d.ts.map +1 -0
  65. package/dist/generators/python-generator.js +180 -0
  66. package/dist/generators/python-generator.js.map +1 -0
  67. package/dist/incremental.d.ts +16 -0
  68. package/dist/incremental.d.ts.map +1 -0
  69. package/dist/incremental.js +146 -0
  70. package/dist/incremental.js.map +1 -0
  71. package/dist/index.d.ts +9 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +44 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/mutation/index.d.ts +2 -0
  76. package/dist/mutation/index.d.ts.map +1 -0
  77. package/dist/mutation/index.js +9 -0
  78. package/dist/mutation/index.js.map +1 -0
  79. package/dist/mutation/mutator.d.ts +6 -0
  80. package/dist/mutation/mutator.d.ts.map +1 -0
  81. package/dist/mutation/mutator.js +237 -0
  82. package/dist/mutation/mutator.js.map +1 -0
  83. package/dist/types.d.ts +199 -0
  84. package/dist/types.d.ts.map +1 -0
  85. package/dist/types.js +4 -0
  86. package/dist/types.js.map +1 -0
  87. package/dist/utils/file.d.ts +10 -0
  88. package/dist/utils/file.d.ts.map +1 -0
  89. package/dist/utils/file.js +108 -0
  90. package/dist/utils/file.js.map +1 -0
  91. package/dist/utils/index.d.ts +4 -0
  92. package/dist/utils/index.d.ts.map +1 -0
  93. package/dist/utils/index.js +24 -0
  94. package/dist/utils/index.js.map +1 -0
  95. package/dist/utils/language.d.ts +8 -0
  96. package/dist/utils/language.d.ts.map +1 -0
  97. package/dist/utils/language.js +137 -0
  98. package/dist/utils/language.js.map +1 -0
  99. package/dist/utils/logger.d.ts +13 -0
  100. package/dist/utils/logger.d.ts.map +1 -0
  101. package/dist/utils/logger.js +57 -0
  102. package/dist/utils/logger.js.map +1 -0
  103. package/dist/watcher/index.d.ts +2 -0
  104. package/dist/watcher/index.d.ts.map +1 -0
  105. package/dist/watcher/index.js +6 -0
  106. package/dist/watcher/index.js.map +1 -0
  107. package/dist/watcher/watcher.d.ts +19 -0
  108. package/dist/watcher/watcher.d.ts.map +1 -0
  109. package/dist/watcher/watcher.js +122 -0
  110. package/dist/watcher/watcher.js.map +1 -0
  111. package/package.json +63 -0
  112. package/src/analyzers/analyzer.ts +180 -0
  113. package/src/analyzers/go-analyzer.ts +235 -0
  114. package/src/analyzers/index.ts +4 -0
  115. package/src/analyzers/js-ts-analyzer.ts +324 -0
  116. package/src/analyzers/python-analyzer.ts +306 -0
  117. package/src/cli.ts +416 -0
  118. package/src/config/defaults.ts +81 -0
  119. package/src/config/index.ts +2 -0
  120. package/src/config/loader.ts +114 -0
  121. package/src/coverage.ts +128 -0
  122. package/src/generators/ai-generator.ts +170 -0
  123. package/src/generators/generator.ts +117 -0
  124. package/src/generators/go-generator.ts +183 -0
  125. package/src/generators/index.ts +5 -0
  126. package/src/generators/js-ts-generator.ts +379 -0
  127. package/src/generators/python-generator.ts +201 -0
  128. package/src/incremental.ts +131 -0
  129. package/src/index.ts +8 -0
  130. package/src/mutation/index.ts +1 -0
  131. package/src/mutation/mutator.ts +314 -0
  132. package/src/types.ts +240 -0
  133. package/src/utils/file.ts +73 -0
  134. package/src/utils/index.ts +3 -0
  135. package/src/utils/language.ts +114 -0
  136. package/src/utils/logger.ts +61 -0
  137. package/src/watcher/index.ts +1 -0
  138. package/src/watcher/watcher.ts +103 -0
  139. package/tests/analyzer.test.ts +429 -0
  140. package/tests/config.test.ts +171 -0
  141. package/tests/coverage.test.ts +197 -0
  142. package/tests/file-utils.test.ts +121 -0
  143. package/tests/generators.test.ts +383 -0
  144. package/tests/incremental.test.ts +108 -0
  145. package/tests/language.test.ts +90 -0
  146. package/tests/mutation.test.ts +286 -0
  147. package/tests/watcher.test.ts +35 -0
  148. package/tsconfig.json +26 -0
  149. package/vitest.config.ts +25 -0
@@ -0,0 +1,306 @@
1
+ import * as fs from 'fs';
2
+ import {
3
+ SourceAnalysis,
4
+ AnalyzedFunction,
5
+ AnalyzedClass,
6
+ AnalyzedExport,
7
+ ImportInfo,
8
+ FunctionParam,
9
+ ClassProperty,
10
+ } from '../types.js';
11
+
12
+ export function analyzePythonSource(filePath: string): SourceAnalysis {
13
+ const content = fs.readFileSync(filePath, 'utf-8');
14
+ const lines = content.split('\n');
15
+
16
+ return {
17
+ filePath,
18
+ language: 'python',
19
+ functions: extractPythonFunctions(content),
20
+ classes: extractPythonClasses(content),
21
+ interfaces: [],
22
+ exports: extractPythonExports(content),
23
+ imports: extractPythonImports(content),
24
+ dependencies: extractPythonImports(content).map((imp) => imp.modulePath),
25
+ linesOfCode: lines.length,
26
+ cyclomaticComplexity: calculatePythonComplexity(content),
27
+ };
28
+ }
29
+
30
+ function extractPythonFunctions(content: string): AnalyzedFunction[] {
31
+ const functions: AnalyzedFunction[] = [];
32
+ const funcRegex = /^(\s*)(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*(.+?))?\s*:/gm;
33
+ let match: RegExpExecArray | null;
34
+
35
+ while ((match = funcRegex.exec(content)) !== null) {
36
+ const indent = match[1]!.length;
37
+ const name = match[2]!;
38
+ const paramsStr = match[3] ?? '';
39
+ const returnType = match[4]?.trim() ?? null;
40
+ const isAsync = content.substring(match.index, match.index + 20).includes('async');
41
+
42
+ const params = parsePythonParams(paramsStr);
43
+
44
+ const startLine = content.substring(0, match.index).split('\n').length;
45
+ const body = extractPythonBody(content, match.index);
46
+ const endLine = startLine + body.split('\n').length - 1;
47
+
48
+ functions.push({
49
+ name,
50
+ isAsync,
51
+ isExported: indent === 0,
52
+ params,
53
+ returnType,
54
+ throws: extractPythonRaises(body),
55
+ complexity: calculatePythonComplexity(body),
56
+ hasSideEffects: detectPythonSideEffects(body),
57
+ startLine,
58
+ endLine,
59
+ });
60
+ }
61
+
62
+ return functions;
63
+ }
64
+
65
+ function extractPythonClasses(content: string): AnalyzedClass[] {
66
+ const classes: AnalyzedClass[] = [];
67
+ const classRegex = /^class\s+(\w+)(?:\(([^)]+)\))?\s*:/gm;
68
+ let match: RegExpExecArray | null;
69
+
70
+ while ((match = classRegex.exec(content)) !== null) {
71
+ const name = match[1]!;
72
+ const bases = match[2]?.split(',').map((s) => s.trim()) ?? [];
73
+ const startLine = content.substring(0, match.index).split('\n').length;
74
+ const body = extractPythonBody(content, match.index);
75
+
76
+ const methods = extractPythonMethods(body, startLine);
77
+ const constructorParams = methods.find((m) => m.name === '__init__')?.params ?? [];
78
+
79
+ classes.push({
80
+ name,
81
+ isExported: true,
82
+ constructorParams,
83
+ methods,
84
+ properties: extractPythonProperties(body),
85
+ implements: [],
86
+ extends: bases[0] ?? null,
87
+ });
88
+ }
89
+
90
+ return classes;
91
+ }
92
+
93
+ function extractPythonMethods(body: string, baseLine: number): AnalyzedFunction[] {
94
+ const methods: AnalyzedFunction[] = [];
95
+ const methodRegex = /^\s+(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*(.+?))?\s*:/gm;
96
+ let match: RegExpExecArray | null;
97
+
98
+ while ((match = methodRegex.exec(body)) !== null) {
99
+ const name = match[1]!;
100
+ const paramsStr = match[2] ?? '';
101
+ const returnType = match[3]?.trim() ?? null;
102
+ const isAsync = body.substring(match.index, match.index + 30).includes('async');
103
+
104
+ const params = parsePythonParams(paramsStr);
105
+ const methodBody = extractPythonBody(body, match.index);
106
+ const methodStartLine = baseLine + body.substring(0, match.index).split('\n').length;
107
+
108
+ methods.push({
109
+ name,
110
+ isAsync,
111
+ isExported: false,
112
+ params,
113
+ returnType,
114
+ throws: extractPythonRaises(methodBody),
115
+ complexity: calculatePythonComplexity(methodBody),
116
+ hasSideEffects: detectPythonSideEffects(methodBody),
117
+ startLine: methodStartLine,
118
+ endLine: methodStartLine + methodBody.split('\n').length - 1,
119
+ });
120
+ }
121
+
122
+ return methods;
123
+ }
124
+
125
+ function extractPythonProperties(body: string): ClassProperty[] {
126
+ const props: ClassProperty[] = [];
127
+ const selfAssignRegex = /self\.(\w+)\s*=/g;
128
+ let match: RegExpExecArray | null;
129
+
130
+ while ((match = selfAssignRegex.exec(body)) !== null) {
131
+ const name = match[1]!;
132
+ if (!props.some((p) => p.name === name)) {
133
+ props.push({
134
+ name,
135
+ type: null,
136
+ visibility: 'public',
137
+ isStatic: false,
138
+ isReadonly: false,
139
+ });
140
+ }
141
+ }
142
+
143
+ return props;
144
+ }
145
+
146
+ function parsePythonParams(paramsStr: string): FunctionParam[] {
147
+ if (!paramsStr.trim()) return [];
148
+
149
+ return paramsStr.split(',').map((param): FunctionParam => {
150
+ const trimmed = param.trim();
151
+ if (!trimmed || trimmed === 'self' || trimmed === 'cls') {
152
+ return { name: trimmed || 'self', type: null, optional: true, defaultValue: null };
153
+ }
154
+
155
+ const hasDefault = trimmed.includes('=');
156
+ const hasType = trimmed.includes(':');
157
+
158
+ let name = trimmed;
159
+ let type: string | null = null;
160
+ let defaultValue: string | null = null;
161
+
162
+ if (hasType) {
163
+ const parts = trimmed.split(':');
164
+ name = parts[0]!.trim();
165
+ const rest = parts.slice(1).join(':').trim();
166
+ if (rest.includes('=')) {
167
+ const typeParts = rest.split('=');
168
+ type = typeParts[0]!.trim();
169
+ defaultValue = typeParts.slice(1).join('=').trim();
170
+ } else {
171
+ type = rest;
172
+ }
173
+ } else if (hasDefault) {
174
+ const parts = trimmed.split('=');
175
+ name = parts[0]!.trim();
176
+ defaultValue = parts.slice(1).join('=').trim();
177
+ }
178
+
179
+ return {
180
+ name,
181
+ type,
182
+ optional: hasDefault || trimmed.startsWith('*'),
183
+ defaultValue,
184
+ };
185
+ }).filter((p) => p.name !== 'self' && p.name !== 'cls');
186
+ }
187
+
188
+ function extractPythonBody(content: string, startIndex: number): string {
189
+ const afterMatch = content.substring(startIndex);
190
+ const bodyLines = afterMatch.split('\n');
191
+ if (bodyLines.length <= 1) return '';
192
+
193
+ let baseIndent = 0;
194
+ for (let i = 1; i < bodyLines.length; i++) {
195
+ const indentMatch = bodyLines[i]!.match(/^(\s*)\S/);
196
+ if (indentMatch) {
197
+ baseIndent = indentMatch[1]!.length;
198
+ break;
199
+ }
200
+ }
201
+
202
+ const result: string[] = [];
203
+ for (let i = 1; i < bodyLines.length; i++) {
204
+ const line = bodyLines[i]!;
205
+ if (line.trim() === '') {
206
+ result.push(line);
207
+ continue;
208
+ }
209
+ const indentMatch = line.match(/^(\s*)\S/);
210
+ if (indentMatch && indentMatch[1]!.length >= baseIndent && baseIndent > 0) {
211
+ result.push(line);
212
+ } else if (indentMatch && indentMatch[1]!.length < baseIndent && baseIndent > 0) {
213
+ break;
214
+ } else {
215
+ result.push(line);
216
+ }
217
+ }
218
+
219
+ return result.join('\n');
220
+ }
221
+
222
+ function extractPythonRaises(body: string): string[] {
223
+ const raises: string[] = [];
224
+ const raiseRegex = /raise\s+(\w+)/g;
225
+ let match: RegExpExecArray | null;
226
+ while ((match = raiseRegex.exec(body)) !== null) {
227
+ if (!raises.includes(match[1]!)) {
228
+ raises.push(match[1]!);
229
+ }
230
+ }
231
+ return raises;
232
+ }
233
+
234
+ function calculatePythonComplexity(code: string): number {
235
+ let complexity = 1;
236
+ const patterns = [
237
+ /\bif\b/g,
238
+ /\belif\b/g,
239
+ /\belse\b/g,
240
+ /\bfor\b/g,
241
+ /\bwhile\b/g,
242
+ /\bexcept\b/g,
243
+ /\band\b/g,
244
+ /\bor\b/g,
245
+ /\bnot\b/g,
246
+ /\bwith\b/g,
247
+ ];
248
+ for (const pattern of patterns) {
249
+ const matches = code.match(pattern);
250
+ if (matches) complexity += matches.length;
251
+ }
252
+ return complexity;
253
+ }
254
+
255
+ function detectPythonSideEffects(code: string): boolean {
256
+ const patterns = [
257
+ /\bprint\(/,
258
+ /\bopen\(/,
259
+ /\bos\.\w+/,
260
+ /\brequests\.\w+/,
261
+ /\brandom\.\w+/,
262
+ /\bdatetime\.\w+/,
263
+ /\bdb\.\w+/,
264
+ /\.append\(/,
265
+ /\.extend\(/,
266
+ ];
267
+ return patterns.some((p) => p.test(code));
268
+ }
269
+
270
+ function extractPythonExports(content: string): AnalyzedExport[] {
271
+ const exports: AnalyzedExport[] = [];
272
+ const funcRegex = /^(?:async\s+)?def\s+(\w+)/gm;
273
+ let match: RegExpExecArray | null;
274
+ while ((match = funcRegex.exec(content)) !== null) {
275
+ exports.push({ name: match[1]!, type: 'function', filePath: '' });
276
+ }
277
+ const classRegex = /^class\s+(\w+)/gm;
278
+ while ((match = classRegex.exec(content)) !== null) {
279
+ exports.push({ name: match[1]!, type: 'class', filePath: '' });
280
+ }
281
+ return exports;
282
+ }
283
+
284
+ function extractPythonImports(content: string): ImportInfo[] {
285
+ const imports: ImportInfo[] = [];
286
+ const importRegex = /^import\s+(\S+)|^from\s+(\S+)\s+import\s+(.+)/gm;
287
+ let match: RegExpExecArray | null;
288
+ while ((match = importRegex.exec(content)) !== null) {
289
+ if (match[1]) {
290
+ imports.push({
291
+ modulePath: match[1],
292
+ namedImports: [],
293
+ defaultImport: null,
294
+ isTypeOnly: false,
295
+ });
296
+ } else if (match[2] && match[3]) {
297
+ imports.push({
298
+ modulePath: match[2],
299
+ namedImports: match[3].split(',').map((s) => s.trim()),
300
+ defaultImport: null,
301
+ isTypeOnly: false,
302
+ });
303
+ }
304
+ }
305
+ return imports;
306
+ }