@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.
- package/LICENSE +21 -0
- package/README.md +383 -0
- package/dist/analyzers/analyzer.d.ts +10 -0
- package/dist/analyzers/analyzer.d.ts.map +1 -0
- package/dist/analyzers/analyzer.js +131 -0
- package/dist/analyzers/analyzer.js.map +1 -0
- package/dist/analyzers/go-analyzer.d.ts +3 -0
- package/dist/analyzers/go-analyzer.d.ts.map +1 -0
- package/dist/analyzers/go-analyzer.js +244 -0
- package/dist/analyzers/go-analyzer.js.map +1 -0
- package/dist/analyzers/index.d.ts +5 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +15 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/analyzers/js-ts-analyzer.d.ts +3 -0
- package/dist/analyzers/js-ts-analyzer.d.ts.map +1 -0
- package/dist/analyzers/js-ts-analyzer.js +299 -0
- package/dist/analyzers/js-ts-analyzer.js.map +1 -0
- package/dist/analyzers/python-analyzer.d.ts +3 -0
- package/dist/analyzers/python-analyzer.d.ts.map +1 -0
- package/dist/analyzers/python-analyzer.js +306 -0
- package/dist/analyzers/python-analyzer.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +381 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +80 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +14 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +6 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +126 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/coverage.d.ts +4 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +108 -0
- package/dist/coverage.js.map +1 -0
- package/dist/generators/ai-generator.d.ts +4 -0
- package/dist/generators/ai-generator.d.ts.map +1 -0
- package/dist/generators/ai-generator.js +175 -0
- package/dist/generators/ai-generator.js.map +1 -0
- package/dist/generators/generator.d.ts +4 -0
- package/dist/generators/generator.d.ts.map +1 -0
- package/dist/generators/generator.js +121 -0
- package/dist/generators/generator.js.map +1 -0
- package/dist/generators/go-generator.d.ts +3 -0
- package/dist/generators/go-generator.d.ts.map +1 -0
- package/dist/generators/go-generator.js +175 -0
- package/dist/generators/go-generator.js.map +1 -0
- package/dist/generators/index.d.ts +6 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +16 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/js-ts-generator.d.ts +3 -0
- package/dist/generators/js-ts-generator.d.ts.map +1 -0
- package/dist/generators/js-ts-generator.js +331 -0
- package/dist/generators/js-ts-generator.js.map +1 -0
- package/dist/generators/python-generator.d.ts +3 -0
- package/dist/generators/python-generator.d.ts.map +1 -0
- package/dist/generators/python-generator.js +180 -0
- package/dist/generators/python-generator.js.map +1 -0
- package/dist/incremental.d.ts +16 -0
- package/dist/incremental.d.ts.map +1 -0
- package/dist/incremental.js +146 -0
- package/dist/incremental.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/mutation/index.d.ts +2 -0
- package/dist/mutation/index.d.ts.map +1 -0
- package/dist/mutation/index.js +9 -0
- package/dist/mutation/index.js.map +1 -0
- package/dist/mutation/mutator.d.ts +6 -0
- package/dist/mutation/mutator.d.ts.map +1 -0
- package/dist/mutation/mutator.js +237 -0
- package/dist/mutation/mutator.js.map +1 -0
- package/dist/types.d.ts +199 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/file.d.ts +10 -0
- package/dist/utils/file.d.ts.map +1 -0
- package/dist/utils/file.js +108 -0
- package/dist/utils/file.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +24 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/language.d.ts +8 -0
- package/dist/utils/language.d.ts.map +1 -0
- package/dist/utils/language.js +137 -0
- package/dist/utils/language.js.map +1 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +57 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/watcher/index.d.ts +2 -0
- package/dist/watcher/index.d.ts.map +1 -0
- package/dist/watcher/index.js +6 -0
- package/dist/watcher/index.js.map +1 -0
- package/dist/watcher/watcher.d.ts +19 -0
- package/dist/watcher/watcher.d.ts.map +1 -0
- package/dist/watcher/watcher.js +122 -0
- package/dist/watcher/watcher.js.map +1 -0
- package/package.json +63 -0
- package/src/analyzers/analyzer.ts +180 -0
- package/src/analyzers/go-analyzer.ts +235 -0
- package/src/analyzers/index.ts +4 -0
- package/src/analyzers/js-ts-analyzer.ts +324 -0
- package/src/analyzers/python-analyzer.ts +306 -0
- package/src/cli.ts +416 -0
- package/src/config/defaults.ts +81 -0
- package/src/config/index.ts +2 -0
- package/src/config/loader.ts +114 -0
- package/src/coverage.ts +128 -0
- package/src/generators/ai-generator.ts +170 -0
- package/src/generators/generator.ts +117 -0
- package/src/generators/go-generator.ts +183 -0
- package/src/generators/index.ts +5 -0
- package/src/generators/js-ts-generator.ts +379 -0
- package/src/generators/python-generator.ts +201 -0
- package/src/incremental.ts +131 -0
- package/src/index.ts +8 -0
- package/src/mutation/index.ts +1 -0
- package/src/mutation/mutator.ts +314 -0
- package/src/types.ts +240 -0
- package/src/utils/file.ts +73 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/language.ts +114 -0
- package/src/utils/logger.ts +61 -0
- package/src/watcher/index.ts +1 -0
- package/src/watcher/watcher.ts +103 -0
- package/tests/analyzer.test.ts +429 -0
- package/tests/config.test.ts +171 -0
- package/tests/coverage.test.ts +197 -0
- package/tests/file-utils.test.ts +121 -0
- package/tests/generators.test.ts +383 -0
- package/tests/incremental.test.ts +108 -0
- package/tests/language.test.ts +90 -0
- package/tests/mutation.test.ts +286 -0
- package/tests/watcher.test.ts +35 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +25 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.detectLanguage = detectLanguage;
|
|
37
|
+
exports.detectFramework = detectFramework;
|
|
38
|
+
exports.getTestFileExtension = getTestFileExtension;
|
|
39
|
+
exports.buildTestFilePath = buildTestFilePath;
|
|
40
|
+
exports.isTestFile = isTestFile;
|
|
41
|
+
exports.shouldAnalyze = shouldAnalyze;
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const defaults_js_1 = require("../config/defaults.js");
|
|
45
|
+
function detectLanguage(filePath) {
|
|
46
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
47
|
+
const language = defaults_js_1.LANGUAGE_EXTENSIONS[ext];
|
|
48
|
+
if (!language) {
|
|
49
|
+
throw new Error(`Unsupported file extension: ${ext}. Supported: ${Object.keys(defaults_js_1.LANGUAGE_EXTENSIONS).join(', ')}`);
|
|
50
|
+
}
|
|
51
|
+
return language;
|
|
52
|
+
}
|
|
53
|
+
function detectFramework(language, projectDir) {
|
|
54
|
+
const frameworks = defaults_js_1.LANGUAGE_FRAMEWORKS[language];
|
|
55
|
+
if (frameworks.length === 0) {
|
|
56
|
+
throw new Error(`No test framework support for language: ${language}`);
|
|
57
|
+
}
|
|
58
|
+
// Check for framework-specific config files
|
|
59
|
+
if (language === 'typescript' || language === 'javascript') {
|
|
60
|
+
const vitestConfig = ['vitest.config.ts', 'vitest.config.js', 'vite.config.ts'].find((f) => fs.existsSync(path.join(projectDir, f)));
|
|
61
|
+
if (vitestConfig)
|
|
62
|
+
return 'vitest';
|
|
63
|
+
const jestConfig = ['jest.config.ts', 'jest.config.js', 'jest.config.mjs'].find((f) => fs.existsSync(path.join(projectDir, f)));
|
|
64
|
+
if (jestConfig)
|
|
65
|
+
return 'jest';
|
|
66
|
+
// Check package.json for dependencies
|
|
67
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
68
|
+
if (fs.existsSync(pkgPath)) {
|
|
69
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
70
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
71
|
+
if (allDeps['vitest'])
|
|
72
|
+
return 'vitest';
|
|
73
|
+
if (allDeps['jest'])
|
|
74
|
+
return 'jest';
|
|
75
|
+
}
|
|
76
|
+
return 'vitest'; // default for JS/TS
|
|
77
|
+
}
|
|
78
|
+
if (language === 'python')
|
|
79
|
+
return 'pytest';
|
|
80
|
+
if (language === 'go')
|
|
81
|
+
return 'go-test';
|
|
82
|
+
return frameworks[0];
|
|
83
|
+
}
|
|
84
|
+
function getTestFileExtension(language) {
|
|
85
|
+
const map = {
|
|
86
|
+
typescript: 'ts',
|
|
87
|
+
javascript: 'js',
|
|
88
|
+
python: 'py',
|
|
89
|
+
go: 'go',
|
|
90
|
+
rust: 'rs',
|
|
91
|
+
};
|
|
92
|
+
return map[language];
|
|
93
|
+
}
|
|
94
|
+
function buildTestFilePath(sourceFilePath, outputDir, language, framework) {
|
|
95
|
+
const dir = path.dirname(sourceFilePath);
|
|
96
|
+
const ext = path.extname(sourceFilePath);
|
|
97
|
+
const baseName = path.basename(sourceFilePath, ext);
|
|
98
|
+
const testExt = getTestFileExtension(language);
|
|
99
|
+
let testFileName;
|
|
100
|
+
if (framework === 'pytest') {
|
|
101
|
+
testFileName = `test_${baseName}.py`;
|
|
102
|
+
}
|
|
103
|
+
else if (framework === 'go-test') {
|
|
104
|
+
testFileName = `${baseName}_test.go`;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
testFileName = `${baseName}.test.${testExt}`;
|
|
108
|
+
}
|
|
109
|
+
if (outputDir === '__tests__' || outputDir.startsWith('./') || outputDir.startsWith('../')) {
|
|
110
|
+
const testDir = path.resolve(path.dirname(sourceFilePath), outputDir);
|
|
111
|
+
return path.join(testDir, testFileName);
|
|
112
|
+
}
|
|
113
|
+
return path.join(dir, testFileName);
|
|
114
|
+
}
|
|
115
|
+
function isTestFile(filePath) {
|
|
116
|
+
const base = path.basename(filePath);
|
|
117
|
+
return (base.includes('.test.') ||
|
|
118
|
+
base.includes('.spec.') ||
|
|
119
|
+
base.startsWith('test_') ||
|
|
120
|
+
base.endsWith('_test.go'));
|
|
121
|
+
}
|
|
122
|
+
function shouldAnalyze(filePath, excludePatterns) {
|
|
123
|
+
if (isTestFile(filePath))
|
|
124
|
+
return false;
|
|
125
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
126
|
+
for (const pattern of excludePatterns) {
|
|
127
|
+
const regex = pattern
|
|
128
|
+
.replace(/\*\*/g, '.*')
|
|
129
|
+
.replace(/\*/g, '[^/]*')
|
|
130
|
+
.replace(/\?/g, '[^/]');
|
|
131
|
+
if (new RegExp(regex).test(normalizedPath)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=language.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"language.js","sourceRoot":"","sources":["../../src/utils/language.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'success';
|
|
2
|
+
export declare function setLogLevel(level: LogLevel): void;
|
|
3
|
+
export declare const logger: {
|
|
4
|
+
debug(message: string, ...args: unknown[]): void;
|
|
5
|
+
info(message: string, ...args: unknown[]): void;
|
|
6
|
+
warn(message: string, ...args: unknown[]): void;
|
|
7
|
+
error(message: string, ...args: unknown[]): void;
|
|
8
|
+
success(message: string, ...args: unknown[]): void;
|
|
9
|
+
plain(message: string, ...args: unknown[]): void;
|
|
10
|
+
heading(message: string): void;
|
|
11
|
+
subheading(message: string): void;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAMvE,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAQD,eAAO,MAAM,MAAM;mBACF,MAAM,WAAW,OAAO,EAAE,GAAG,IAAI;kBAMlC,MAAM,WAAW,OAAO,EAAE,GAAG,IAAI;kBAMjC,MAAM,WAAW,OAAO,EAAE,GAAG,IAAI;mBAMhC,MAAM,WAAW,OAAO,EAAE,GAAG,IAAI;qBAM/B,MAAM,WAAW,OAAO,EAAE,GAAG,IAAI;mBAMnC,MAAM,WAAW,OAAO,EAAE,GAAG,IAAI;qBAI/B,MAAM,GAAG,IAAI;wBAIV,MAAM,GAAG,IAAI;CAGlC,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.logger = void 0;
|
|
7
|
+
exports.setLogLevel = setLogLevel;
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
let currentLogLevel = 'info';
|
|
10
|
+
const LOG_ORDER = ['debug', 'info', 'warn', 'error', 'success'];
|
|
11
|
+
function setLogLevel(level) {
|
|
12
|
+
currentLogLevel = level;
|
|
13
|
+
}
|
|
14
|
+
function shouldLog(level) {
|
|
15
|
+
if (level === 'success')
|
|
16
|
+
return true;
|
|
17
|
+
if (level === 'debug' && currentLogLevel !== 'debug')
|
|
18
|
+
return false;
|
|
19
|
+
return LOG_ORDER.indexOf(level) >= LOG_ORDER.indexOf(currentLogLevel);
|
|
20
|
+
}
|
|
21
|
+
exports.logger = {
|
|
22
|
+
debug(message, ...args) {
|
|
23
|
+
if (shouldLog('debug')) {
|
|
24
|
+
console.log(chalk_1.default.gray(`[debug] ${message}`), ...args);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
info(message, ...args) {
|
|
28
|
+
if (shouldLog('info')) {
|
|
29
|
+
console.log(chalk_1.default.cyan(`[info] ${message}`), ...args);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
warn(message, ...args) {
|
|
33
|
+
if (shouldLog('warn')) {
|
|
34
|
+
console.warn(chalk_1.default.yellow(`[warn] ${message}`), ...args);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
error(message, ...args) {
|
|
38
|
+
if (shouldLog('error')) {
|
|
39
|
+
console.error(chalk_1.default.red(`[error] ${message}`), ...args);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
success(message, ...args) {
|
|
43
|
+
if (shouldLog('success')) {
|
|
44
|
+
console.log(chalk_1.default.green(` ${message}`), ...args);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
plain(message, ...args) {
|
|
48
|
+
console.log(message, ...args);
|
|
49
|
+
},
|
|
50
|
+
heading(message) {
|
|
51
|
+
console.log('\n' + chalk_1.default.bold.blue(` ${message}`));
|
|
52
|
+
},
|
|
53
|
+
subheading(message) {
|
|
54
|
+
console.log(chalk_1.default.bold.white(` ${message}`));
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":";;;;;;AAQA,kCAEC;AAVD,kDAA0B;AAI1B,IAAI,eAAe,GAAa,MAAM,CAAC;AAEvC,MAAM,SAAS,GAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;AAE5E,SAAgB,WAAW,CAAC,KAAe;IACzC,eAAe,GAAG,KAAK,CAAC;AAC1B,CAAC;AAED,SAAS,SAAS,CAAC,KAAe;IAChC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,KAAK,KAAK,OAAO,IAAI,eAAe,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IACnE,OAAO,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;AACxE,CAAC;AAEY,QAAA,MAAM,GAAG;IACpB,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;QACvC,IAAI,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,WAAW,OAAO,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAe;QACtC,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,UAAU,OAAO,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAe;QACtC,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,eAAK,CAAC,MAAM,CAAC,UAAU,OAAO,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;QACvC,IAAI,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,CAAC,eAAK,CAAC,GAAG,CAAC,WAAW,OAAO,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,OAAO,CAAC,OAAe,EAAE,GAAG,IAAe;QACzC,IAAI,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,KAAK,CAAC,KAAK,OAAO,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;QACvC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,CAAC,OAAe;QACrB,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,eAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,UAAU,CAAC,OAAe;QACxB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC;IAChD,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/watcher/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FileWatcher = void 0;
|
|
4
|
+
var watcher_js_1 = require("./watcher.js");
|
|
5
|
+
Object.defineProperty(exports, "FileWatcher", { enumerable: true, get: function () { return watcher_js_1.FileWatcher; } });
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/watcher/index.ts"],"names":[],"mappings":";;;AAAA,2CAA2C;AAAlC,yGAAA,WAAW,OAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FileChangeEvent, TestGenConfig } from '../types.js';
|
|
2
|
+
type FileWatcherCallback = (events: FileChangeEvent[]) => Promise<void>;
|
|
3
|
+
export declare class FileWatcher {
|
|
4
|
+
private watchers;
|
|
5
|
+
private pendingEvents;
|
|
6
|
+
private debounceTimer;
|
|
7
|
+
private debounceMs;
|
|
8
|
+
private ignorePatterns;
|
|
9
|
+
private callback;
|
|
10
|
+
private running;
|
|
11
|
+
constructor(config: TestGenConfig, callback: FileWatcherCallback);
|
|
12
|
+
start(directories: string[]): void;
|
|
13
|
+
stop(): void;
|
|
14
|
+
isRunning(): boolean;
|
|
15
|
+
private shouldIgnore;
|
|
16
|
+
private debouncedNotify;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../src/watcher/watcher.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG7D,KAAK,mBAAmB,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAExE,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,cAAc,CAAW;IACjC,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,mBAAmB;IAMhE,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,IAAI;IAuClC,IAAI,IAAI,IAAI;IAaZ,SAAS,IAAI,OAAO;IAIpB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,eAAe;CAexB"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FileWatcher = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
40
|
+
class FileWatcher {
|
|
41
|
+
watchers = new Map();
|
|
42
|
+
pendingEvents = [];
|
|
43
|
+
debounceTimer = null;
|
|
44
|
+
debounceMs;
|
|
45
|
+
ignorePatterns;
|
|
46
|
+
callback;
|
|
47
|
+
running = false;
|
|
48
|
+
constructor(config, callback) {
|
|
49
|
+
this.debounceMs = config.watch.debounceMs;
|
|
50
|
+
this.ignorePatterns = config.watch.ignorePatterns;
|
|
51
|
+
this.callback = callback;
|
|
52
|
+
}
|
|
53
|
+
start(directories) {
|
|
54
|
+
if (this.running)
|
|
55
|
+
return;
|
|
56
|
+
this.running = true;
|
|
57
|
+
for (const dir of directories) {
|
|
58
|
+
const absoluteDir = path.resolve(dir);
|
|
59
|
+
if (!fs.existsSync(absoluteDir)) {
|
|
60
|
+
logger_js_1.logger.warn(`Watch directory does not exist: ${absoluteDir}`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const watcher = fs.watch(absoluteDir, { recursive: true }, (eventType, filename) => {
|
|
65
|
+
if (!filename)
|
|
66
|
+
return;
|
|
67
|
+
if (this.shouldIgnore(filename))
|
|
68
|
+
return;
|
|
69
|
+
const filePath = path.join(absoluteDir, filename);
|
|
70
|
+
this.pendingEvents.push({
|
|
71
|
+
filePath,
|
|
72
|
+
eventType: eventType,
|
|
73
|
+
timestamp: Date.now(),
|
|
74
|
+
});
|
|
75
|
+
this.debouncedNotify();
|
|
76
|
+
});
|
|
77
|
+
this.watchers.set(absoluteDir, watcher);
|
|
78
|
+
logger_js_1.logger.info(`Watching: ${absoluteDir}`);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
logger_js_1.logger.error(`Failed to watch ${absoluteDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
stop() {
|
|
86
|
+
this.running = false;
|
|
87
|
+
for (const [dir, watcher] of this.watchers) {
|
|
88
|
+
watcher.close();
|
|
89
|
+
logger_js_1.logger.debug(`Stopped watching: ${dir}`);
|
|
90
|
+
}
|
|
91
|
+
this.watchers.clear();
|
|
92
|
+
if (this.debounceTimer) {
|
|
93
|
+
clearTimeout(this.debounceTimer);
|
|
94
|
+
this.debounceTimer = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
isRunning() {
|
|
98
|
+
return this.running;
|
|
99
|
+
}
|
|
100
|
+
shouldIgnore(filename) {
|
|
101
|
+
const normalized = filename.replace(/\\/g, '/');
|
|
102
|
+
return this.ignorePatterns.some((pattern) => {
|
|
103
|
+
return normalized.includes(pattern) || normalized.match(new RegExp(pattern.replace(/\*/g, '.*'))) !== null;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
debouncedNotify() {
|
|
107
|
+
if (this.debounceTimer) {
|
|
108
|
+
clearTimeout(this.debounceTimer);
|
|
109
|
+
}
|
|
110
|
+
this.debounceTimer = setTimeout(() => {
|
|
111
|
+
const events = [...this.pendingEvents];
|
|
112
|
+
this.pendingEvents = [];
|
|
113
|
+
if (events.length > 0) {
|
|
114
|
+
this.callback(events).catch((err) => {
|
|
115
|
+
logger_js_1.logger.error(`Watch callback error: ${err instanceof Error ? err.message : String(err)}`);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}, this.debounceMs);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.FileWatcher = FileWatcher;
|
|
122
|
+
//# sourceMappingURL=watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/watcher/watcher.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@theihtisham/ai-testgen",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-powered test generator that creates comprehensive test suites from source code",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai-testgen": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/cli.js",
|
|
12
|
+
"dev": "ts-node src/cli.ts",
|
|
13
|
+
"test": "vitest run tests/config.test.ts tests/language.test.ts tests/file-utils.test.ts tests/generators.test.ts tests/coverage.test.ts tests/watcher.test.ts tests/incremental.test.ts tests/mutation.test.ts && vitest run tests/analyzer.test.ts",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"test:coverage": "vitest run --coverage",
|
|
16
|
+
"lint": "tsc --noEmit",
|
|
17
|
+
"clean": "rimraf dist",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"testing",
|
|
22
|
+
"ai",
|
|
23
|
+
"test-generator",
|
|
24
|
+
"unit-tests",
|
|
25
|
+
"integration-tests",
|
|
26
|
+
"mutation-testing",
|
|
27
|
+
"code-coverage",
|
|
28
|
+
"jest",
|
|
29
|
+
"vitest",
|
|
30
|
+
"pytest",
|
|
31
|
+
"go-test"
|
|
32
|
+
],
|
|
33
|
+
"author": "",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"chalk": "^4.1.2",
|
|
37
|
+
"commander": "^12.1.0",
|
|
38
|
+
"fast-glob": "^3.3.2",
|
|
39
|
+
"js-yaml": "^4.1.0",
|
|
40
|
+
"openai": "^4.67.3",
|
|
41
|
+
"ora": "^5.4.1",
|
|
42
|
+
"ts-morph": "^21.0.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/js-yaml": "^4.0.9",
|
|
46
|
+
"@types/node": "^22.10.0",
|
|
47
|
+
"cross-env": "^10.1.0",
|
|
48
|
+
"rimraf": "^6.0.1",
|
|
49
|
+
"ts-node": "^10.9.2",
|
|
50
|
+
"typescript": "^5.7.2",
|
|
51
|
+
"vitest": "^2.1.8"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.0.0"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
},
|
|
59
|
+
"repository": {
|
|
60
|
+
"type": "git",
|
|
61
|
+
"url": "https://github.com/theihtisham/ai-testgen"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SourceAnalysis,
|
|
3
|
+
SupportedLanguage,
|
|
4
|
+
AnalyzedFunction,
|
|
5
|
+
TestCase,
|
|
6
|
+
MockDefinition,
|
|
7
|
+
} from '../types.js';
|
|
8
|
+
import { analyzeTsSource } from './js-ts-analyzer.js';
|
|
9
|
+
import { analyzePythonSource } from './python-analyzer.js';
|
|
10
|
+
import { analyzeGoSource } from './go-analyzer.js';
|
|
11
|
+
|
|
12
|
+
export function analyzeSource(filePath: string, language: SupportedLanguage): SourceAnalysis {
|
|
13
|
+
switch (language) {
|
|
14
|
+
case 'typescript':
|
|
15
|
+
case 'javascript':
|
|
16
|
+
return analyzeTsSource(filePath, language);
|
|
17
|
+
case 'python':
|
|
18
|
+
return analyzePythonSource(filePath);
|
|
19
|
+
case 'go':
|
|
20
|
+
return analyzeGoSource(filePath);
|
|
21
|
+
case 'rust':
|
|
22
|
+
throw new Error('Rust analysis is not yet supported. Use AI mode for Rust files.');
|
|
23
|
+
default:
|
|
24
|
+
throw new Error(`Unsupported language: ${language}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function detectEdgeCases(fn: AnalyzedFunction): TestCase[] {
|
|
29
|
+
const cases: TestCase[] = [];
|
|
30
|
+
|
|
31
|
+
for (const param of fn.params) {
|
|
32
|
+
if (param.optional) {
|
|
33
|
+
cases.push({
|
|
34
|
+
name: `${fn.name} handles undefined ${param.name}`,
|
|
35
|
+
type: 'edge-case',
|
|
36
|
+
description: `Test that ${fn.name} gracefully handles undefined ${param.name}`,
|
|
37
|
+
code: '',
|
|
38
|
+
expectedBehavior: 'Should handle undefined parameter without throwing',
|
|
39
|
+
inputDescription: `${param.name} is undefined`,
|
|
40
|
+
tags: ['edge-case', 'undefined', param.name],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (param.type && (param.type.includes('string') || param.type.includes('String'))) {
|
|
45
|
+
cases.push(
|
|
46
|
+
createEdgeCase(fn.name, param.name, 'empty string', '""', 'Should handle empty string input'),
|
|
47
|
+
createEdgeCase(fn.name, param.name, 'very long string', '"a".repeat(10000)', 'Should handle very long string'),
|
|
48
|
+
createEdgeCase(fn.name, param.name, 'string with special characters', '"\\n\\t\\0\\r"', 'Should handle special characters'),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (param.type && (param.type.includes('number') || param.type.includes('Number'))) {
|
|
53
|
+
cases.push(
|
|
54
|
+
createEdgeCase(fn.name, param.name, 'zero', '0', 'Should handle zero value'),
|
|
55
|
+
createEdgeCase(fn.name, param.name, 'negative number', '-1', 'Should handle negative numbers'),
|
|
56
|
+
createEdgeCase(fn.name, param.name, 'very large number', 'Number.MAX_SAFE_INTEGER', 'Should handle large numbers'),
|
|
57
|
+
createEdgeCase(fn.name, param.name, 'NaN', 'NaN', 'Should handle NaN'),
|
|
58
|
+
createEdgeCase(fn.name, param.name, 'Infinity', 'Infinity', 'Should handle Infinity'),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (param.type && (param.type.includes('Array') || param.type.includes('[]'))) {
|
|
63
|
+
cases.push(
|
|
64
|
+
createEdgeCase(fn.name, param.name, 'empty array', '[]', 'Should handle empty array'),
|
|
65
|
+
createEdgeCase(fn.name, param.name, 'array with single element', '[item]', 'Should handle single-element array'),
|
|
66
|
+
createEdgeCase(fn.name, param.name, 'very large array', 'Array(10000).fill(item)', 'Should handle large arrays'),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (param.type && (param.type.includes('object') || param.type.includes('Object') || param.type.includes('{'))) {
|
|
71
|
+
cases.push(
|
|
72
|
+
createEdgeCase(fn.name, param.name, 'empty object', '{}', 'Should handle empty object'),
|
|
73
|
+
createEdgeCase(fn.name, param.name, 'null', 'null', 'Should handle null'),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (fn.throws.length > 0) {
|
|
79
|
+
for (const throws of fn.throws) {
|
|
80
|
+
cases.push({
|
|
81
|
+
name: `${fn.name} throws for invalid input`,
|
|
82
|
+
type: 'edge-case',
|
|
83
|
+
description: `Test that ${fn.name} throws when given invalid input`,
|
|
84
|
+
code: '',
|
|
85
|
+
expectedBehavior: `Should throw ${throws}`,
|
|
86
|
+
inputDescription: 'Invalid input that triggers error',
|
|
87
|
+
tags: ['edge-case', 'error-path'],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (fn.isAsync) {
|
|
93
|
+
cases.push({
|
|
94
|
+
name: `${fn.name} handles rejection`,
|
|
95
|
+
type: 'edge-case',
|
|
96
|
+
description: `Test that ${fn.name} properly handles promise rejection`,
|
|
97
|
+
code: '',
|
|
98
|
+
expectedBehavior: 'Should handle promise rejection gracefully',
|
|
99
|
+
inputDescription: 'Input that causes promise rejection',
|
|
100
|
+
tags: ['edge-case', 'async'],
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return cases;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createEdgeCase(
|
|
108
|
+
fnName: string,
|
|
109
|
+
paramName: string,
|
|
110
|
+
caseName: string,
|
|
111
|
+
value: string,
|
|
112
|
+
expected: string,
|
|
113
|
+
): TestCase {
|
|
114
|
+
return {
|
|
115
|
+
name: `${fnName} handles ${caseName} for ${paramName}`,
|
|
116
|
+
type: 'edge-case',
|
|
117
|
+
description: `Test ${fnName} with ${caseName} for parameter ${paramName}`,
|
|
118
|
+
code: '',
|
|
119
|
+
expectedBehavior: expected,
|
|
120
|
+
inputDescription: `${paramName} = ${value}`,
|
|
121
|
+
tags: ['edge-case', caseName.replace(/\s+/g, '-')],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function detectMocks(analysis: SourceAnalysis): MockDefinition[] {
|
|
126
|
+
const mocks: MockDefinition[] = [];
|
|
127
|
+
|
|
128
|
+
for (const imp of analysis.imports) {
|
|
129
|
+
if (imp.isTypeOnly) continue;
|
|
130
|
+
|
|
131
|
+
if (!imp.modulePath.startsWith('.') && imp.namedImports.length > 0) {
|
|
132
|
+
for (const named of imp.namedImports) {
|
|
133
|
+
const isLikelyDependency =
|
|
134
|
+
!imp.modulePath.startsWith('node:') &&
|
|
135
|
+
!['path', 'fs', 'os', 'util', 'events', 'stream', 'http', 'https', 'crypto'].includes(
|
|
136
|
+
imp.modulePath,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (isLikelyDependency) {
|
|
140
|
+
mocks.push({
|
|
141
|
+
moduleName: imp.modulePath,
|
|
142
|
+
mockName: `mock${named.charAt(0).toUpperCase() + named.slice(1)}`,
|
|
143
|
+
setup: `jest.mock('${imp.modulePath}');`,
|
|
144
|
+
teardown: null,
|
|
145
|
+
implementations: {
|
|
146
|
+
[named]: `jest.fn()`,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return mocks;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface DependencyGraph {
|
|
158
|
+
files: Map<string, string[]>;
|
|
159
|
+
reverse: Map<string, string[]>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function buildDependencyGraph(analyses: SourceAnalysis[]): DependencyGraph {
|
|
163
|
+
const files = new Map<string, string[]>();
|
|
164
|
+
const reverse = new Map<string, string[]>();
|
|
165
|
+
|
|
166
|
+
for (const analysis of analyses) {
|
|
167
|
+
const deps: string[] = [];
|
|
168
|
+
for (const dep of analysis.dependencies) {
|
|
169
|
+
if (dep.startsWith('.')) {
|
|
170
|
+
deps.push(dep);
|
|
171
|
+
const rev = reverse.get(dep) ?? [];
|
|
172
|
+
rev.push(analysis.filePath);
|
|
173
|
+
reverse.set(dep, rev);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
files.set(analysis.filePath, deps);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return { files, reverse };
|
|
180
|
+
}
|