devlensio 0.4.2 → 0.4.4
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 +6 -4
- package/dist/filesystem/index.js +6 -0
- package/dist/filesystem/index.test.js +170 -0
- package/dist/filesystem/reactRouterRoutes.d.ts +2 -0
- package/dist/filesystem/reactRouterRoutes.js +243 -0
- package/dist/graph/edges/apiFetchEdges.js +68 -137
- package/dist/graph/edges/helpers/routeMatching.d.ts +8 -0
- package/dist/graph/edges/helpers/routeMatching.js +122 -0
- package/dist/graph/edges/navigationEdges.d.ts +5 -0
- package/dist/graph/edges/navigationEdges.js +339 -0
- package/dist/graph/edges/routeEdge.js +25 -0
- package/dist/graph/index.js +12 -1
- package/dist/graph/index.test.js +263 -8
- package/dist/parser/extractors/functions.d.ts +5 -0
- package/dist/parser/extractors/functions.js +5 -5
- package/dist/parser/extractors/objectMethods.d.ts +4 -0
- package/dist/parser/extractors/objectMethods.js +141 -0
- package/dist/parser/index.js +3 -1
- package/dist/parser/index.test.js +129 -9
- package/dist/pipeline/index.js +26 -0
- package/dist/types.d.ts +3 -2
- package/package.json +1 -1
|
@@ -296,24 +296,144 @@ describe("parseRepo", () => {
|
|
|
296
296
|
expect(nodeModuleNode).toBeUndefined();
|
|
297
297
|
deleteFakeRepo(repoPath);
|
|
298
298
|
});
|
|
299
|
-
it("should ignore
|
|
299
|
+
it("should ignore config files", () => {
|
|
300
300
|
const repoPath = createFakeRepo({
|
|
301
301
|
"src/Button.tsx": `export function Button() { return <button />; }`,
|
|
302
|
-
"
|
|
302
|
+
"next.config.ts": `export default {};`,
|
|
303
303
|
});
|
|
304
304
|
const result = parseRepo(repoPath);
|
|
305
|
-
const
|
|
306
|
-
expect(
|
|
305
|
+
const configNode = result.nodes.find((n) => n.filePath.includes(".config."));
|
|
306
|
+
expect(configNode).toBeUndefined();
|
|
307
307
|
deleteFakeRepo(repoPath);
|
|
308
308
|
});
|
|
309
|
-
|
|
309
|
+
// ─── Object-literal Method Extraction ──────────────────────────────────────
|
|
310
|
+
it("should detect an arrow-function value in an object literal", () => {
|
|
310
311
|
const repoPath = createFakeRepo({
|
|
311
|
-
"src/
|
|
312
|
-
|
|
312
|
+
"src/cfg.ts": `
|
|
313
|
+
export const cfg = {
|
|
314
|
+
load: () => doThing(),
|
|
315
|
+
};
|
|
316
|
+
`,
|
|
313
317
|
});
|
|
314
318
|
const result = parseRepo(repoPath);
|
|
315
|
-
const
|
|
316
|
-
expect(
|
|
319
|
+
const fn = result.nodes.find((n) => n.name === "cfg.load");
|
|
320
|
+
expect(fn).toBeDefined();
|
|
321
|
+
expect(fn?.type).toBe("FUNCTION");
|
|
322
|
+
expect(fn?.startLine).toBeGreaterThanOrEqual(1);
|
|
323
|
+
expect(fn?.endLine).toBeGreaterThanOrEqual(fn.startLine);
|
|
324
|
+
expect(fn?.metadata.calls).toContain("doThing");
|
|
325
|
+
deleteFakeRepo(repoPath);
|
|
326
|
+
});
|
|
327
|
+
it("should detect an async arrow value calling axios (nested object)", () => {
|
|
328
|
+
const repoPath = createFakeRepo({
|
|
329
|
+
"src/providersConfig.js": `
|
|
330
|
+
export const providersConfig = {
|
|
331
|
+
zoro: {
|
|
332
|
+
streamingData: async (episodeId) => {
|
|
333
|
+
const response = await axios.get('/api/v2/zoro/watch/' + episodeId);
|
|
334
|
+
return response.data;
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
`,
|
|
339
|
+
});
|
|
340
|
+
const result = parseRepo(repoPath);
|
|
341
|
+
const fn = result.nodes.find((n) => n.name === "providersConfig.zoro.streamingData");
|
|
342
|
+
expect(fn).toBeDefined();
|
|
343
|
+
expect(fn?.type).toBe("FUNCTION");
|
|
344
|
+
expect(fn?.metadata.isAsync).toBe(true);
|
|
345
|
+
expect((fn?.metadata.apiCalls).length).toBeGreaterThan(0);
|
|
346
|
+
deleteFakeRepo(repoPath);
|
|
347
|
+
});
|
|
348
|
+
it("should detect a function-expression value in an object literal", () => {
|
|
349
|
+
const repoPath = createFakeRepo({
|
|
350
|
+
"src/cfg.ts": `
|
|
351
|
+
export const cfg = {
|
|
352
|
+
run: function (x) { return helper(x); },
|
|
353
|
+
};
|
|
354
|
+
`,
|
|
355
|
+
});
|
|
356
|
+
const result = parseRepo(repoPath);
|
|
357
|
+
const fn = result.nodes.find((n) => n.name === "cfg.run");
|
|
358
|
+
expect(fn).toBeDefined();
|
|
359
|
+
expect(fn?.type).toBe("FUNCTION");
|
|
360
|
+
expect(fn?.metadata.params).toContain("x");
|
|
361
|
+
deleteFakeRepo(repoPath);
|
|
362
|
+
});
|
|
363
|
+
it("should detect a shorthand method in an object literal", () => {
|
|
364
|
+
const repoPath = createFakeRepo({
|
|
365
|
+
"src/cfg.ts": `
|
|
366
|
+
export const cfg = {
|
|
367
|
+
fetchData(url) { return fetch(url); },
|
|
368
|
+
};
|
|
369
|
+
`,
|
|
370
|
+
});
|
|
371
|
+
const result = parseRepo(repoPath);
|
|
372
|
+
const fn = result.nodes.find((n) => n.name === "cfg.fetchData");
|
|
373
|
+
expect(fn).toBeDefined();
|
|
374
|
+
expect(fn?.type).toBe("FUNCTION");
|
|
375
|
+
deleteFakeRepo(repoPath);
|
|
376
|
+
});
|
|
377
|
+
it("should detect a concise-body arrow value in an object literal", () => {
|
|
378
|
+
const repoPath = createFakeRepo({
|
|
379
|
+
"src/cfg.ts": `
|
|
380
|
+
export const cfg = {
|
|
381
|
+
pick: (u) => u.id,
|
|
382
|
+
};
|
|
383
|
+
`,
|
|
384
|
+
});
|
|
385
|
+
const result = parseRepo(repoPath);
|
|
386
|
+
const fn = result.nodes.find((n) => n.name === "cfg.pick");
|
|
387
|
+
expect(fn).toBeDefined();
|
|
388
|
+
expect(fn?.type).toBe("FUNCTION");
|
|
389
|
+
deleteFakeRepo(repoPath);
|
|
390
|
+
});
|
|
391
|
+
it("should detect a function in a two-level nested object", () => {
|
|
392
|
+
const repoPath = createFakeRepo({
|
|
393
|
+
"src/api.ts": `
|
|
394
|
+
export const api = {
|
|
395
|
+
v2: {
|
|
396
|
+
users: {
|
|
397
|
+
list: async () => fetch('/api/users'),
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
`,
|
|
402
|
+
});
|
|
403
|
+
const result = parseRepo(repoPath);
|
|
404
|
+
const fn = result.nodes.find((n) => n.name === "api.v2.users.list");
|
|
405
|
+
expect(fn).toBeDefined();
|
|
406
|
+
expect(fn?.type).toBe("FUNCTION");
|
|
407
|
+
deleteFakeRepo(repoPath);
|
|
408
|
+
});
|
|
409
|
+
it("should detect a function in an export-default object", () => {
|
|
410
|
+
const repoPath = createFakeRepo({
|
|
411
|
+
"src/handler.ts": `
|
|
412
|
+
export default {
|
|
413
|
+
handler: async () => fetch('/api/x'),
|
|
414
|
+
};
|
|
415
|
+
`,
|
|
416
|
+
});
|
|
417
|
+
const result = parseRepo(repoPath);
|
|
418
|
+
const fn = result.nodes.find((n) => n.name === "default.handler");
|
|
419
|
+
expect(fn).toBeDefined();
|
|
420
|
+
expect(fn?.type).toBe("FUNCTION");
|
|
421
|
+
deleteFakeRepo(repoPath);
|
|
422
|
+
});
|
|
423
|
+
it("should not double-extract or collide with top-level hooks", () => {
|
|
424
|
+
const repoPath = createFakeRepo({
|
|
425
|
+
"src/mix.ts": `
|
|
426
|
+
export const useThing = () => { return 1; };
|
|
427
|
+
export const cfg = {
|
|
428
|
+
a: () => doA(),
|
|
429
|
+
};
|
|
430
|
+
`,
|
|
431
|
+
});
|
|
432
|
+
const result = parseRepo(repoPath);
|
|
433
|
+
const objMethods = result.nodes.filter((n) => n.name === "cfg.a");
|
|
434
|
+
expect(objMethods.length).toBe(1);
|
|
435
|
+
const hook = result.nodes.find((n) => n.name === "useThing");
|
|
436
|
+
expect(hook?.type).toBe("HOOK");
|
|
317
437
|
deleteFakeRepo(repoPath);
|
|
318
438
|
});
|
|
319
439
|
});
|
package/dist/pipeline/index.js
CHANGED
|
@@ -123,6 +123,32 @@ function routesToCodeNodes(routes, repoPath) {
|
|
|
123
123
|
},
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
|
+
else if (route.type === "REACT_ROUTER_ROUTE") {
|
|
127
|
+
// React Router / TanStack / wouter route — a single ROUTE node, no method.
|
|
128
|
+
const name = route.urlPath;
|
|
129
|
+
const id = `${relativeFilePath}::${name}`;
|
|
130
|
+
nodes.push({
|
|
131
|
+
id,
|
|
132
|
+
name,
|
|
133
|
+
type: "ROUTE",
|
|
134
|
+
filePath: relativeFilePath,
|
|
135
|
+
startLine: 1,
|
|
136
|
+
endLine: 1,
|
|
137
|
+
parentFile: `file::${relativeFilePath}`,
|
|
138
|
+
metadata: {
|
|
139
|
+
urlPath: route.urlPath,
|
|
140
|
+
httpMethod: null,
|
|
141
|
+
isDynamic: route.isDynamic,
|
|
142
|
+
isCatchAll: route.isCatchAll,
|
|
143
|
+
isGroupRoute: route.isGroupRoute,
|
|
144
|
+
params: route.params ?? [],
|
|
145
|
+
routeNodeType: "REACT_ROUTER_ROUTE",
|
|
146
|
+
framework: "react-router",
|
|
147
|
+
routeKind: "react-router",
|
|
148
|
+
rendersComponent: route.rendersComponent, // resolved to a HANDLES edge in routeEdge.ts
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
126
152
|
else {
|
|
127
153
|
// Next.js RouteNode — one node per HTTP method for API routes,
|
|
128
154
|
// one node for page/layout/etc.
|
package/dist/types.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface ProjectFingerprint {
|
|
|
17
17
|
databases: DatabaseLibrary[];
|
|
18
18
|
rawDependencies: Record<string, string>;
|
|
19
19
|
}
|
|
20
|
-
export type RouteNodeType = "PAGE" | "LAYOUT" | "API_ROUTE" | "LOADING" | "ERROR" | "MIDDLEWARE" | "NOT_FOUND";
|
|
20
|
+
export type RouteNodeType = "PAGE" | "LAYOUT" | "API_ROUTE" | "LOADING" | "ERROR" | "MIDDLEWARE" | "NOT_FOUND" | "REACT_ROUTER_ROUTE";
|
|
21
21
|
export interface RouteNode {
|
|
22
22
|
type: RouteNodeType;
|
|
23
23
|
nodeId?: string;
|
|
@@ -29,6 +29,7 @@ export interface RouteNode {
|
|
|
29
29
|
layoutPath?: string;
|
|
30
30
|
params?: string[];
|
|
31
31
|
httpMethods?: string[];
|
|
32
|
+
rendersComponent?: string;
|
|
32
33
|
}
|
|
33
34
|
export interface BackendRouteNode {
|
|
34
35
|
type: "BACKEND_ROUTE";
|
|
@@ -69,7 +70,7 @@ export interface CodeNode {
|
|
|
69
70
|
score?: Number;
|
|
70
71
|
metadata: Record<string, unknown>;
|
|
71
72
|
}
|
|
72
|
-
export type EdgeType = "CALLS" | "IMPORTS" | "READS_FROM" | "WRITES_TO" | "PROP_PASS" | "EMITS" | "LISTENS" | "WRAPPED_BY" | "GUARDS" | "HANDLES" | "TESTS" | "USES" | "NEXTJS_API_CALL";
|
|
73
|
+
export type EdgeType = "CALLS" | "IMPORTS" | "READS_FROM" | "WRITES_TO" | "PROP_PASS" | "EMITS" | "LISTENS" | "WRAPPED_BY" | "GUARDS" | "HANDLES" | "TESTS" | "USES" | "NEXTJS_API_CALL" | "NAVIGATES_TO";
|
|
73
74
|
export interface CodeEdge {
|
|
74
75
|
from: string;
|
|
75
76
|
to: string;
|