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.
@@ -296,24 +296,144 @@ describe("parseRepo", () => {
296
296
  expect(nodeModuleNode).toBeUndefined();
297
297
  deleteFakeRepo(repoPath);
298
298
  });
299
- it("should ignore test files", () => {
299
+ it("should ignore config files", () => {
300
300
  const repoPath = createFakeRepo({
301
301
  "src/Button.tsx": `export function Button() { return <button />; }`,
302
- "src/Button.test.tsx": `export function FakeButton() { return <button />; }`,
302
+ "next.config.ts": `export default {};`,
303
303
  });
304
304
  const result = parseRepo(repoPath);
305
- const testNode = result.nodes.find((n) => n.filePath.includes(".test."));
306
- expect(testNode).toBeUndefined();
305
+ const configNode = result.nodes.find((n) => n.filePath.includes(".config."));
306
+ expect(configNode).toBeUndefined();
307
307
  deleteFakeRepo(repoPath);
308
308
  });
309
- it("should ignore config files", () => {
309
+ // ─── Object-literal Method Extraction ──────────────────────────────────────
310
+ it("should detect an arrow-function value in an object literal", () => {
310
311
  const repoPath = createFakeRepo({
311
- "src/Button.tsx": `export function Button() { return <button />; }`,
312
- "next.config.ts": `export default {};`,
312
+ "src/cfg.ts": `
313
+ export const cfg = {
314
+ load: () => doThing(),
315
+ };
316
+ `,
313
317
  });
314
318
  const result = parseRepo(repoPath);
315
- const configNode = result.nodes.find((n) => n.filePath.includes(".config."));
316
- expect(configNode).toBeUndefined();
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
  });
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devlensio",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Codebase intelligence engine for TypeScript/JavaScript/Reactjs/Nextjs repositories.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",