@ophan/core 0.0.1
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/README.md +107 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +715 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +492 -0
- package/dist/parsers/__fixtures__/arrow-functions.d.ts +5 -0
- package/dist/parsers/__fixtures__/arrow-functions.d.ts.map +1 -0
- package/dist/parsers/__fixtures__/arrow-functions.js +16 -0
- package/dist/parsers/__fixtures__/class-methods.d.ts +6 -0
- package/dist/parsers/__fixtures__/class-methods.d.ts.map +1 -0
- package/dist/parsers/__fixtures__/class-methods.js +12 -0
- package/dist/parsers/__fixtures__/no-functions.d.ts +9 -0
- package/dist/parsers/__fixtures__/no-functions.d.ts.map +1 -0
- package/dist/parsers/__fixtures__/no-functions.js +4 -0
- package/dist/parsers/index.d.ts +3 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +18 -0
- package/dist/parsers/python.d.ts +8 -0
- package/dist/parsers/python.d.ts.map +1 -0
- package/dist/parsers/python.js +137 -0
- package/dist/parsers/python.test.d.ts +2 -0
- package/dist/parsers/python.test.d.ts.map +1 -0
- package/dist/parsers/python.test.js +96 -0
- package/dist/parsers/registry.d.ts +8 -0
- package/dist/parsers/registry.d.ts.map +1 -0
- package/dist/parsers/registry.js +68 -0
- package/dist/parsers/types.d.ts +10 -0
- package/dist/parsers/types.d.ts.map +1 -0
- package/dist/parsers/types.js +18 -0
- package/dist/parsers/typescript.d.ts +8 -0
- package/dist/parsers/typescript.d.ts.map +1 -0
- package/dist/parsers/typescript.js +110 -0
- package/dist/parsers/typescript.test.d.ts +2 -0
- package/dist/parsers/typescript.test.d.ts.map +1 -0
- package/dist/parsers/typescript.test.js +106 -0
- package/dist/schemas.d.ts +100 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +128 -0
- package/dist/shared.d.ts +12 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +10 -0
- package/dist/test-utils.d.ts +46 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +141 -0
- package/package.json +37 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,492 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const vitest_1 = require("vitest");
|
|
40
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const index_1 = require("./index");
|
|
45
|
+
const test_utils_1 = require("./test-utils");
|
|
46
|
+
// ============ mergeAnalysisRows ============
|
|
47
|
+
(0, vitest_1.describe)("mergeAnalysisRows", () => {
|
|
48
|
+
(0, vitest_1.it)("merges documentation + security rows into unified shape", () => {
|
|
49
|
+
const result = (0, index_1.mergeAnalysisRows)([
|
|
50
|
+
{
|
|
51
|
+
analysis_type: "documentation",
|
|
52
|
+
analysis: JSON.stringify({
|
|
53
|
+
description: "Fetches user",
|
|
54
|
+
params: [{ name: "id", type: "string", description: "user id" }],
|
|
55
|
+
returns: { type: "User", description: "the user" },
|
|
56
|
+
}),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
analysis_type: "security",
|
|
60
|
+
analysis: JSON.stringify({
|
|
61
|
+
dataTags: ["database", "pii"],
|
|
62
|
+
securityFlags: ["sql_injection"],
|
|
63
|
+
}),
|
|
64
|
+
},
|
|
65
|
+
]);
|
|
66
|
+
(0, vitest_1.expect)(result.description).toBe("Fetches user");
|
|
67
|
+
(0, vitest_1.expect)(result.params).toEqual([
|
|
68
|
+
{ name: "id", type: "string", description: "user id" },
|
|
69
|
+
]);
|
|
70
|
+
(0, vitest_1.expect)(result.returns).toEqual({ type: "User", description: "the user" });
|
|
71
|
+
(0, vitest_1.expect)(result.dataTags).toEqual(["database", "pii"]);
|
|
72
|
+
(0, vitest_1.expect)(result.securityFlags).toEqual(["sql_injection"]);
|
|
73
|
+
});
|
|
74
|
+
(0, vitest_1.it)("handles documentation-only (missing security row)", () => {
|
|
75
|
+
const result = (0, index_1.mergeAnalysisRows)([
|
|
76
|
+
{
|
|
77
|
+
analysis_type: "documentation",
|
|
78
|
+
analysis: JSON.stringify({
|
|
79
|
+
description: "Test",
|
|
80
|
+
params: [],
|
|
81
|
+
returns: { type: "void", description: "" },
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
(0, vitest_1.expect)(result.description).toBe("Test");
|
|
86
|
+
(0, vitest_1.expect)(result.dataTags).toEqual([]);
|
|
87
|
+
(0, vitest_1.expect)(result.securityFlags).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
(0, vitest_1.it)("handles security-only (missing documentation row)", () => {
|
|
90
|
+
const result = (0, index_1.mergeAnalysisRows)([
|
|
91
|
+
{
|
|
92
|
+
analysis_type: "security",
|
|
93
|
+
analysis: JSON.stringify({
|
|
94
|
+
dataTags: ["internal"],
|
|
95
|
+
securityFlags: [],
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
(0, vitest_1.expect)(result.description).toBe("");
|
|
100
|
+
(0, vitest_1.expect)(result.params).toEqual([]);
|
|
101
|
+
(0, vitest_1.expect)(result.returns).toEqual({ type: "unknown", description: "" });
|
|
102
|
+
(0, vitest_1.expect)(result.dataTags).toEqual(["internal"]);
|
|
103
|
+
});
|
|
104
|
+
(0, vitest_1.it)("handles empty rows array", () => {
|
|
105
|
+
const result = (0, index_1.mergeAnalysisRows)([]);
|
|
106
|
+
(0, vitest_1.expect)(result.description).toBe("");
|
|
107
|
+
(0, vitest_1.expect)(result.params).toEqual([]);
|
|
108
|
+
(0, vitest_1.expect)(result.returns).toEqual({ type: "unknown", description: "" });
|
|
109
|
+
(0, vitest_1.expect)(result.dataTags).toEqual([]);
|
|
110
|
+
(0, vitest_1.expect)(result.securityFlags).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
(0, vitest_1.it)("ignores unknown analysis_type", () => {
|
|
113
|
+
const result = (0, index_1.mergeAnalysisRows)([
|
|
114
|
+
{
|
|
115
|
+
analysis_type: "future_type",
|
|
116
|
+
analysis: JSON.stringify({ foo: "bar" }),
|
|
117
|
+
},
|
|
118
|
+
]);
|
|
119
|
+
(0, vitest_1.expect)(result.description).toBe("");
|
|
120
|
+
(0, vitest_1.expect)(result.securityFlags).toEqual([]);
|
|
121
|
+
});
|
|
122
|
+
(0, vitest_1.it)("last row wins when duplicate analysis_type", () => {
|
|
123
|
+
const result = (0, index_1.mergeAnalysisRows)([
|
|
124
|
+
{
|
|
125
|
+
analysis_type: "documentation",
|
|
126
|
+
analysis: JSON.stringify({
|
|
127
|
+
description: "First",
|
|
128
|
+
params: [],
|
|
129
|
+
returns: { type: "void", description: "" },
|
|
130
|
+
}),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
analysis_type: "documentation",
|
|
134
|
+
analysis: JSON.stringify({
|
|
135
|
+
description: "Second",
|
|
136
|
+
params: [],
|
|
137
|
+
returns: { type: "string", description: "overwritten" },
|
|
138
|
+
}),
|
|
139
|
+
},
|
|
140
|
+
]);
|
|
141
|
+
(0, vitest_1.expect)(result.description).toBe("Second");
|
|
142
|
+
(0, vitest_1.expect)(result.returns.type).toBe("string");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
// ============ migrateToAnalysisTypes ============
|
|
146
|
+
(0, vitest_1.describe)("migrateToAnalysisTypes", () => {
|
|
147
|
+
(0, vitest_1.it)("splits single legacy row into documentation + security rows", () => {
|
|
148
|
+
const db = (0, test_utils_1.createLegacyTestDb)();
|
|
149
|
+
db.prepare(`INSERT INTO function_analysis
|
|
150
|
+
(content_hash, analysis, model_version, created_at, last_seen_at, language, entity_type, synced_at)
|
|
151
|
+
VALUES (?, ?, 'claude-3', 1000, 2000, 'typescript', 'function', 5000)`).run("abc123", JSON.stringify({
|
|
152
|
+
description: "Test fn",
|
|
153
|
+
params: [{ name: "x", type: "number", description: "input" }],
|
|
154
|
+
returns: { type: "number", description: "output" },
|
|
155
|
+
dataTags: ["internal"],
|
|
156
|
+
securityFlags: ["xss"],
|
|
157
|
+
}));
|
|
158
|
+
(0, index_1.migrateToAnalysisTypes)(db);
|
|
159
|
+
const rows = db
|
|
160
|
+
.prepare("SELECT content_hash, analysis_type, analysis, synced_at FROM function_analysis ORDER BY analysis_type")
|
|
161
|
+
.all();
|
|
162
|
+
(0, vitest_1.expect)(rows).toHaveLength(2);
|
|
163
|
+
const docRow = rows.find((r) => r.analysis_type === "documentation");
|
|
164
|
+
const secRow = rows.find((r) => r.analysis_type === "security");
|
|
165
|
+
(0, vitest_1.expect)(docRow).toBeDefined();
|
|
166
|
+
(0, vitest_1.expect)(secRow).toBeDefined();
|
|
167
|
+
const doc = JSON.parse(docRow.analysis);
|
|
168
|
+
(0, vitest_1.expect)(doc.description).toBe("Test fn");
|
|
169
|
+
(0, vitest_1.expect)(doc.params).toEqual([
|
|
170
|
+
{ name: "x", type: "number", description: "input" },
|
|
171
|
+
]);
|
|
172
|
+
const sec = JSON.parse(secRow.analysis);
|
|
173
|
+
(0, vitest_1.expect)(sec.dataTags).toEqual(["internal"]);
|
|
174
|
+
(0, vitest_1.expect)(sec.securityFlags).toEqual(["xss"]);
|
|
175
|
+
// synced_at should be NULL (forced re-sync)
|
|
176
|
+
(0, vitest_1.expect)(docRow.synced_at).toBeNull();
|
|
177
|
+
(0, vitest_1.expect)(secRow.synced_at).toBeNull();
|
|
178
|
+
db.close();
|
|
179
|
+
});
|
|
180
|
+
(0, vitest_1.it)("handles NULL language/entity_type with COALESCE defaults", () => {
|
|
181
|
+
const db = (0, test_utils_1.createLegacyTestDb)();
|
|
182
|
+
db.prepare(`INSERT INTO function_analysis
|
|
183
|
+
(content_hash, analysis, model_version, created_at, last_seen_at, language, entity_type)
|
|
184
|
+
VALUES (?, ?, 'claude-3', 1000, 2000, NULL, NULL)`).run("hash1", JSON.stringify({ description: "Test" }));
|
|
185
|
+
(0, index_1.migrateToAnalysisTypes)(db);
|
|
186
|
+
const rows = db
|
|
187
|
+
.prepare("SELECT language, entity_type FROM function_analysis WHERE content_hash = 'hash1'")
|
|
188
|
+
.all();
|
|
189
|
+
for (const row of rows) {
|
|
190
|
+
(0, vitest_1.expect)(row.language).toBe("typescript");
|
|
191
|
+
(0, vitest_1.expect)(row.entity_type).toBe("function");
|
|
192
|
+
}
|
|
193
|
+
db.close();
|
|
194
|
+
});
|
|
195
|
+
(0, vitest_1.it)("splits multiple legacy rows", () => {
|
|
196
|
+
const db = (0, test_utils_1.createLegacyTestDb)();
|
|
197
|
+
for (let i = 1; i <= 3; i++) {
|
|
198
|
+
db.prepare(`INSERT INTO function_analysis
|
|
199
|
+
(content_hash, analysis, model_version, created_at, last_seen_at)
|
|
200
|
+
VALUES (?, ?, 'claude-3', 1000, 2000)`).run(`hash${i}`, JSON.stringify({ description: `Fn ${i}` }));
|
|
201
|
+
}
|
|
202
|
+
(0, index_1.migrateToAnalysisTypes)(db);
|
|
203
|
+
const count = db
|
|
204
|
+
.prepare("SELECT COUNT(*) as c FROM function_analysis")
|
|
205
|
+
.get();
|
|
206
|
+
(0, vitest_1.expect)(count.c).toBe(6); // 3 hashes x 2 types
|
|
207
|
+
db.close();
|
|
208
|
+
});
|
|
209
|
+
(0, vitest_1.it)("new table has correct composite PK (content_hash, analysis_type)", () => {
|
|
210
|
+
const db = (0, test_utils_1.createLegacyTestDb)();
|
|
211
|
+
db.prepare(`INSERT INTO function_analysis
|
|
212
|
+
(content_hash, analysis, model_version, created_at, last_seen_at)
|
|
213
|
+
VALUES (?, ?, 'claude-3', 1000, 2000)`).run("hash1", JSON.stringify({}));
|
|
214
|
+
(0, index_1.migrateToAnalysisTypes)(db);
|
|
215
|
+
// Inserting duplicate (same hash + type) should fail
|
|
216
|
+
(0, vitest_1.expect)(() => db
|
|
217
|
+
.prepare(`INSERT INTO function_analysis
|
|
218
|
+
(content_hash, analysis_type, analysis, model_version, schema_version, created_at, last_seen_at, language, entity_type)
|
|
219
|
+
VALUES ('hash1', 'documentation', '{}', 'test', 1, 1000, 2000, 'typescript', 'function')`)
|
|
220
|
+
.run()).toThrow();
|
|
221
|
+
db.close();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
// ============ gcAnalysis ============
|
|
225
|
+
(0, vitest_1.describe)("gcAnalysis", () => {
|
|
226
|
+
let tmpDir;
|
|
227
|
+
let dbPath;
|
|
228
|
+
function setupDb(setup) {
|
|
229
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
230
|
+
// Create schema
|
|
231
|
+
const memDb = (0, test_utils_1.createTestDb)();
|
|
232
|
+
const tables = memDb
|
|
233
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type='table'")
|
|
234
|
+
.all();
|
|
235
|
+
for (const t of tables) {
|
|
236
|
+
db.exec(t.sql + ";");
|
|
237
|
+
}
|
|
238
|
+
const indexes = memDb
|
|
239
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type='index' AND sql IS NOT NULL")
|
|
240
|
+
.all();
|
|
241
|
+
for (const idx of indexes) {
|
|
242
|
+
db.exec(idx.sql + ";");
|
|
243
|
+
}
|
|
244
|
+
memDb.close();
|
|
245
|
+
setup(db);
|
|
246
|
+
db.close();
|
|
247
|
+
}
|
|
248
|
+
(0, vitest_1.beforeEach)(() => {
|
|
249
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ophan-gc-test-"));
|
|
250
|
+
dbPath = path.join(tmpDir, "index.db");
|
|
251
|
+
});
|
|
252
|
+
(0, vitest_1.afterEach)(() => {
|
|
253
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
254
|
+
});
|
|
255
|
+
(0, vitest_1.it)("deletes orphaned analysis older than grace period", () => {
|
|
256
|
+
const ninetyDaysAgo = Math.floor(Date.now() / 1000) - 90 * 24 * 60 * 60;
|
|
257
|
+
setupDb((db) => {
|
|
258
|
+
(0, test_utils_1.insertAnalysisPair)(db, "orphan1", { lastSeenAt: ninetyDaysAgo });
|
|
259
|
+
// No file_functions entry → orphaned
|
|
260
|
+
});
|
|
261
|
+
const result = (0, index_1.gcAnalysis)(dbPath, 30);
|
|
262
|
+
(0, vitest_1.expect)(result.deleted).toBe(2); // doc + sec rows
|
|
263
|
+
const db = new better_sqlite3_1.default(dbPath, { readonly: true });
|
|
264
|
+
const remaining = db
|
|
265
|
+
.prepare("SELECT COUNT(*) as c FROM function_analysis")
|
|
266
|
+
.get();
|
|
267
|
+
(0, vitest_1.expect)(remaining.c).toBe(0);
|
|
268
|
+
db.close();
|
|
269
|
+
});
|
|
270
|
+
(0, vitest_1.it)("preserves analysis still referenced by file_functions", () => {
|
|
271
|
+
const ninetyDaysAgo = Math.floor(Date.now() / 1000) - 90 * 24 * 60 * 60;
|
|
272
|
+
setupDb((db) => {
|
|
273
|
+
(0, test_utils_1.insertAnalysisPair)(db, "active1", { lastSeenAt: ninetyDaysAgo });
|
|
274
|
+
(0, test_utils_1.insertFileFunction)(db, "/src/foo.ts", "foo", "active1");
|
|
275
|
+
});
|
|
276
|
+
const result = (0, index_1.gcAnalysis)(dbPath, 30);
|
|
277
|
+
(0, vitest_1.expect)(result.deleted).toBe(0);
|
|
278
|
+
});
|
|
279
|
+
(0, vitest_1.it)("preserves orphaned analysis within grace period", () => {
|
|
280
|
+
const fiveDaysAgo = Math.floor(Date.now() / 1000) - 5 * 24 * 60 * 60;
|
|
281
|
+
setupDb((db) => {
|
|
282
|
+
(0, test_utils_1.insertAnalysisPair)(db, "recent_orphan", { lastSeenAt: fiveDaysAgo });
|
|
283
|
+
});
|
|
284
|
+
const result = (0, index_1.gcAnalysis)(dbPath, 30);
|
|
285
|
+
(0, vitest_1.expect)(result.deleted).toBe(0);
|
|
286
|
+
});
|
|
287
|
+
(0, vitest_1.it)("creates tombstones in function_gc before deleting", () => {
|
|
288
|
+
const ninetyDaysAgo = Math.floor(Date.now() / 1000) - 90 * 24 * 60 * 60;
|
|
289
|
+
setupDb((db) => {
|
|
290
|
+
(0, test_utils_1.insertAnalysisPair)(db, "doomed", { lastSeenAt: ninetyDaysAgo });
|
|
291
|
+
});
|
|
292
|
+
(0, index_1.gcAnalysis)(dbPath, 30);
|
|
293
|
+
const db = new better_sqlite3_1.default(dbPath, { readonly: true });
|
|
294
|
+
const tombstones = db
|
|
295
|
+
.prepare("SELECT content_hash, analysis_type FROM function_gc ORDER BY analysis_type")
|
|
296
|
+
.all();
|
|
297
|
+
(0, vitest_1.expect)(tombstones).toHaveLength(2);
|
|
298
|
+
(0, vitest_1.expect)(tombstones.map((t) => t.analysis_type).sort()).toEqual([
|
|
299
|
+
"documentation",
|
|
300
|
+
"security",
|
|
301
|
+
]);
|
|
302
|
+
db.close();
|
|
303
|
+
});
|
|
304
|
+
(0, vitest_1.it)("handles empty database", () => {
|
|
305
|
+
setupDb(() => { });
|
|
306
|
+
const result = (0, index_1.gcAnalysis)(dbPath, 30);
|
|
307
|
+
(0, vitest_1.expect)(result.deleted).toBe(0);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
// ============ importAnalysis ============
|
|
311
|
+
(0, vitest_1.describe)("importAnalysis", () => {
|
|
312
|
+
let tmpDir;
|
|
313
|
+
let dbPath;
|
|
314
|
+
function setupDb(setup) {
|
|
315
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
316
|
+
const memDb = (0, test_utils_1.createTestDb)();
|
|
317
|
+
const tables = memDb
|
|
318
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type='table'")
|
|
319
|
+
.all();
|
|
320
|
+
for (const t of tables) {
|
|
321
|
+
db.exec(t.sql + ";");
|
|
322
|
+
}
|
|
323
|
+
memDb.close();
|
|
324
|
+
if (setup)
|
|
325
|
+
setup(db);
|
|
326
|
+
db.close();
|
|
327
|
+
}
|
|
328
|
+
(0, vitest_1.beforeEach)(() => {
|
|
329
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ophan-import-test-"));
|
|
330
|
+
dbPath = path.join(tmpDir, "index.db");
|
|
331
|
+
});
|
|
332
|
+
(0, vitest_1.afterEach)(() => {
|
|
333
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
334
|
+
});
|
|
335
|
+
(0, vitest_1.it)("imports new analysis rows", () => {
|
|
336
|
+
setupDb();
|
|
337
|
+
const count = (0, index_1.importAnalysis)(dbPath, [
|
|
338
|
+
{
|
|
339
|
+
content_hash: "abc",
|
|
340
|
+
analysis_type: "documentation",
|
|
341
|
+
analysis: (0, test_utils_1.makeDocAnalysisJson)(),
|
|
342
|
+
model_version: "test",
|
|
343
|
+
schema_version: 1,
|
|
344
|
+
language: "typescript",
|
|
345
|
+
entity_type: "function",
|
|
346
|
+
},
|
|
347
|
+
]);
|
|
348
|
+
(0, vitest_1.expect)(count).toBe(1);
|
|
349
|
+
const db = new better_sqlite3_1.default(dbPath, { readonly: true });
|
|
350
|
+
const row = db
|
|
351
|
+
.prepare("SELECT * FROM function_analysis WHERE content_hash = 'abc'")
|
|
352
|
+
.get();
|
|
353
|
+
(0, vitest_1.expect)(row).toBeDefined();
|
|
354
|
+
(0, vitest_1.expect)(row.analysis_type).toBe("documentation");
|
|
355
|
+
db.close();
|
|
356
|
+
});
|
|
357
|
+
(0, vitest_1.it)("skips existing rows (INSERT OR IGNORE)", () => {
|
|
358
|
+
setupDb((db) => {
|
|
359
|
+
(0, test_utils_1.insertAnalysisPair)(db, "exists");
|
|
360
|
+
});
|
|
361
|
+
const count = (0, index_1.importAnalysis)(dbPath, [
|
|
362
|
+
{
|
|
363
|
+
content_hash: "exists",
|
|
364
|
+
analysis_type: "documentation",
|
|
365
|
+
analysis: (0, test_utils_1.makeDocAnalysisJson)({ description: "SHOULD NOT OVERWRITE" }),
|
|
366
|
+
model_version: "test",
|
|
367
|
+
schema_version: 1,
|
|
368
|
+
language: "typescript",
|
|
369
|
+
entity_type: "function",
|
|
370
|
+
},
|
|
371
|
+
]);
|
|
372
|
+
(0, vitest_1.expect)(count).toBe(0);
|
|
373
|
+
// Verify original data unchanged
|
|
374
|
+
const db = new better_sqlite3_1.default(dbPath, { readonly: true });
|
|
375
|
+
const row = db
|
|
376
|
+
.prepare("SELECT analysis FROM function_analysis WHERE content_hash = 'exists' AND analysis_type = 'documentation'")
|
|
377
|
+
.get();
|
|
378
|
+
const parsed = JSON.parse(row.analysis);
|
|
379
|
+
(0, vitest_1.expect)(parsed.description).toBe("Test function description");
|
|
380
|
+
db.close();
|
|
381
|
+
});
|
|
382
|
+
(0, vitest_1.it)("sets synced_at on imported rows", () => {
|
|
383
|
+
setupDb();
|
|
384
|
+
(0, index_1.importAnalysis)(dbPath, [
|
|
385
|
+
{
|
|
386
|
+
content_hash: "new1",
|
|
387
|
+
analysis_type: "security",
|
|
388
|
+
analysis: (0, test_utils_1.makeSecAnalysisJson)(),
|
|
389
|
+
model_version: "test",
|
|
390
|
+
schema_version: 1,
|
|
391
|
+
language: "typescript",
|
|
392
|
+
entity_type: "function",
|
|
393
|
+
},
|
|
394
|
+
]);
|
|
395
|
+
const db = new better_sqlite3_1.default(dbPath, { readonly: true });
|
|
396
|
+
const row = db
|
|
397
|
+
.prepare("SELECT synced_at FROM function_analysis WHERE content_hash = 'new1'")
|
|
398
|
+
.get();
|
|
399
|
+
(0, vitest_1.expect)(row.synced_at).not.toBeNull();
|
|
400
|
+
db.close();
|
|
401
|
+
});
|
|
402
|
+
(0, vitest_1.it)("returns 0 for empty rows array", () => {
|
|
403
|
+
setupDb();
|
|
404
|
+
(0, vitest_1.expect)((0, index_1.importAnalysis)(dbPath, [])).toBe(0);
|
|
405
|
+
});
|
|
406
|
+
(0, vitest_1.it)("handles mixed new and existing rows", () => {
|
|
407
|
+
setupDb((db) => {
|
|
408
|
+
(0, test_utils_1.insertAnalysisPair)(db, "exists");
|
|
409
|
+
});
|
|
410
|
+
const count = (0, index_1.importAnalysis)(dbPath, [
|
|
411
|
+
{
|
|
412
|
+
content_hash: "exists",
|
|
413
|
+
analysis_type: "documentation",
|
|
414
|
+
analysis: (0, test_utils_1.makeDocAnalysisJson)(),
|
|
415
|
+
model_version: "test",
|
|
416
|
+
schema_version: 1,
|
|
417
|
+
language: "typescript",
|
|
418
|
+
entity_type: "function",
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
content_hash: "new1",
|
|
422
|
+
analysis_type: "documentation",
|
|
423
|
+
analysis: (0, test_utils_1.makeDocAnalysisJson)(),
|
|
424
|
+
model_version: "test",
|
|
425
|
+
schema_version: 1,
|
|
426
|
+
language: "typescript",
|
|
427
|
+
entity_type: "function",
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
content_hash: "new2",
|
|
431
|
+
analysis_type: "security",
|
|
432
|
+
analysis: (0, test_utils_1.makeSecAnalysisJson)(),
|
|
433
|
+
model_version: "test",
|
|
434
|
+
schema_version: 1,
|
|
435
|
+
language: "python",
|
|
436
|
+
entity_type: "method",
|
|
437
|
+
},
|
|
438
|
+
]);
|
|
439
|
+
(0, vitest_1.expect)(count).toBe(2);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
// ============ findMissingHashes ============
|
|
443
|
+
(0, vitest_1.describe)("findMissingHashes", () => {
|
|
444
|
+
let tmpDir;
|
|
445
|
+
let dbPath;
|
|
446
|
+
function setupDb(setup) {
|
|
447
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
448
|
+
const memDb = (0, test_utils_1.createTestDb)();
|
|
449
|
+
const tables = memDb
|
|
450
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type='table'")
|
|
451
|
+
.all();
|
|
452
|
+
for (const t of tables) {
|
|
453
|
+
db.exec(t.sql + ";");
|
|
454
|
+
}
|
|
455
|
+
memDb.close();
|
|
456
|
+
setup(db);
|
|
457
|
+
db.close();
|
|
458
|
+
}
|
|
459
|
+
(0, vitest_1.beforeEach)(() => {
|
|
460
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ophan-missing-test-"));
|
|
461
|
+
dbPath = path.join(tmpDir, "index.db");
|
|
462
|
+
});
|
|
463
|
+
(0, vitest_1.afterEach)(() => {
|
|
464
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
465
|
+
});
|
|
466
|
+
(0, vitest_1.it)("returns hashes in file_functions but not in function_analysis", () => {
|
|
467
|
+
setupDb((db) => {
|
|
468
|
+
(0, test_utils_1.insertFileFunction)(db, "/src/foo.ts", "foo", "hash1");
|
|
469
|
+
(0, test_utils_1.insertFileFunction)(db, "/src/bar.ts", "bar", "hash2");
|
|
470
|
+
// Only hash1 has analysis
|
|
471
|
+
(0, test_utils_1.insertAnalysisPair)(db, "hash1");
|
|
472
|
+
});
|
|
473
|
+
const missing = (0, index_1.findMissingHashes)(dbPath);
|
|
474
|
+
(0, vitest_1.expect)(missing).toEqual(["hash2"]);
|
|
475
|
+
});
|
|
476
|
+
(0, vitest_1.it)("returns empty when all hashes are analyzed", () => {
|
|
477
|
+
setupDb((db) => {
|
|
478
|
+
(0, test_utils_1.insertFileFunction)(db, "/src/foo.ts", "foo", "hash1");
|
|
479
|
+
(0, test_utils_1.insertAnalysisPair)(db, "hash1");
|
|
480
|
+
});
|
|
481
|
+
const missing = (0, index_1.findMissingHashes)(dbPath);
|
|
482
|
+
(0, vitest_1.expect)(missing).toEqual([]);
|
|
483
|
+
});
|
|
484
|
+
(0, vitest_1.it)("deduplicates — same hash in multiple files returned once", () => {
|
|
485
|
+
setupDb((db) => {
|
|
486
|
+
(0, test_utils_1.insertFileFunction)(db, "/src/a.ts", "fn", "shared_hash");
|
|
487
|
+
(0, test_utils_1.insertFileFunction)(db, "/src/b.ts", "fn", "shared_hash");
|
|
488
|
+
});
|
|
489
|
+
const missing = (0, index_1.findMissingHashes)(dbPath);
|
|
490
|
+
(0, vitest_1.expect)(missing).toEqual(["shared_hash"]);
|
|
491
|
+
});
|
|
492
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arrow-functions.d.ts","sourceRoot":"","sources":["../../../src/parsers/__fixtures__/arrow-functions.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,KAAG,MAEpC,CAAC;AAEF,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,WAAU,CAAC;yBAOnC,GAAG,MAAM;AAAzB,wBAAoC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.add = exports.greet = void 0;
|
|
4
|
+
const greet = (name) => {
|
|
5
|
+
return `Hello, ${name}`;
|
|
6
|
+
};
|
|
7
|
+
exports.greet = greet;
|
|
8
|
+
const add = (a, b) => a + b;
|
|
9
|
+
exports.add = add;
|
|
10
|
+
let handler = (req) => {
|
|
11
|
+
return req.toUpperCase();
|
|
12
|
+
};
|
|
13
|
+
// Unnamed default export arrow — should be skipped
|
|
14
|
+
exports.default = (x) => x * 2;
|
|
15
|
+
// Callback arrow — should be skipped (no variable declaration parent)
|
|
16
|
+
const results = [1, 2, 3].map((n) => n * 2);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"class-methods.d.ts","sourceRoot":"","sources":["../../../src/parsers/__fixtures__/class-methods.ts"],"names":[],"mappings":"AAAA,cAAM,WAAW;IACf,OAAO,CAAC,EAAE,EAAE,MAAM;IAIZ,UAAU,CAAC,IAAI,EAAE,GAAG;CAG3B;AAED,iBAAS,UAAU,YAElB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-functions.d.ts","sourceRoot":"","sources":["../../../src/parsers/__fixtures__/no-functions.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,MAAM,GAAG;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,OAAO,CAAC;CAChB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parsers/index.ts"],"names":[],"mappings":"AAcA,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Parser barrel export — auto-registers all language parsers
|
|
3
|
+
//
|
|
4
|
+
// Importing this module registers all available parsers. To add a new language:
|
|
5
|
+
// 1. Create a parser file in this directory implementing LanguageParser
|
|
6
|
+
// 2. Import and register it below
|
|
7
|
+
// That's it — analyzeRepository() and refreshFileIndex() pick it up automatically.
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getRegisteredParsers = exports.getSupportedExtensions = exports.getParserForFile = void 0;
|
|
10
|
+
const registry_1 = require("./registry");
|
|
11
|
+
const typescript_1 = require("./typescript");
|
|
12
|
+
const python_1 = require("./python");
|
|
13
|
+
(0, registry_1.registerParser)(new typescript_1.TypeScriptParser());
|
|
14
|
+
(0, registry_1.registerParser)(new python_1.PythonParser());
|
|
15
|
+
var registry_2 = require("./registry");
|
|
16
|
+
Object.defineProperty(exports, "getParserForFile", { enumerable: true, get: function () { return registry_2.getParserForFile; } });
|
|
17
|
+
Object.defineProperty(exports, "getSupportedExtensions", { enumerable: true, get: function () { return registry_2.getSupportedExtensions; } });
|
|
18
|
+
Object.defineProperty(exports, "getRegisteredParsers", { enumerable: true, get: function () { return registry_2.getRegisteredParsers; } });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FunctionInfo } from "../shared";
|
|
2
|
+
import type { LanguageParser } from "./types";
|
|
3
|
+
export declare class PythonParser implements LanguageParser {
|
|
4
|
+
readonly language = "python";
|
|
5
|
+
readonly extensions: string[];
|
|
6
|
+
extractFunctions(filePath: string): FunctionInfo[];
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=python.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python.d.ts","sourceRoot":"","sources":["../../src/parsers/python.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA0D9C,qBAAa,YAAa,YAAW,cAAc;IACjD,QAAQ,CAAC,QAAQ,YAAY;IAC7B,QAAQ,CAAC,UAAU,WAAW;IAE9B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,EAAE;CA+BnD"}
|