devlensio 0.6.2 → 1.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 +89 -31
- package/dist/extractors/detectLanguage.d.ts +2 -0
- package/dist/extractors/detectLanguage.js +33 -0
- package/dist/extractors/index.d.ts +6 -0
- package/dist/extractors/index.js +155 -0
- package/dist/extractors/runner.d.ts +7 -0
- package/dist/extractors/runner.js +194 -0
- package/dist/extractors/types.d.ts +37 -0
- package/dist/extractors/types.js +8 -0
- package/dist/graph/buildLookup.d.ts +1 -0
- package/dist/graph/buildLookup.js +2 -1
- package/dist/graph/edges/callEdges.js +19 -5
- package/dist/graph/edges/callEdges.test.d.ts +1 -0
- package/dist/graph/edges/callEdges.test.js +200 -0
- package/dist/graph/edges/importEdges.js +12 -0
- package/dist/graph/edges/inheritanceEdges.d.ts +3 -0
- package/dist/graph/edges/inheritanceEdges.js +73 -0
- package/dist/graph/edges/inheritanceEdges.test.d.ts +1 -0
- package/dist/graph/edges/inheritanceEdges.test.js +140 -0
- package/dist/graph/index.js +4 -0
- package/dist/parser/classes.test.d.ts +1 -0
- package/dist/parser/classes.test.js +360 -0
- package/dist/parser/extractors/classes.d.ts +5 -0
- package/dist/parser/extractors/classes.js +241 -0
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +7 -1
- package/dist/pipeline/index.d.ts +2 -1
- package/dist/pipeline/index.js +30 -42
- package/dist/scoring/index.js +8 -0
- package/dist/scoring/index.test.js +43 -0
- package/dist/scoring/nodeScorer.js +2 -0
- package/dist/scoring/pruneDisconnected.d.ts +8 -0
- package/dist/scoring/pruneDisconnected.js +66 -0
- package/dist/scoring/pruneDisconnected.test.d.ts +1 -0
- package/dist/scoring/pruneDisconnected.test.js +123 -0
- package/dist/summarizer/prompts.d.ts +1 -1
- package/dist/types.d.ts +5 -5
- package/extractors/go/bin/darwin-amd64/devlens_go_extractor +0 -0
- package/extractors/go/bin/darwin-arm64/devlens_go_extractor +0 -0
- package/extractors/go/bin/linux-amd64/devlens_go_extractor +0 -0
- package/extractors/go/bin/linux-arm64/devlens_go_extractor +0 -0
- package/extractors/go/bin/windows-amd64/devlens_go_extractor.exe +0 -0
- package/extractors/go/build.mjs +43 -0
- package/extractors/go/calls.go +289 -0
- package/extractors/go/contract.go +140 -0
- package/extractors/go/extractor.go +161 -0
- package/extractors/go/fingerprint.go +167 -0
- package/extractors/go/go.mod +3 -0
- package/extractors/go/imports.go +90 -0
- package/extractors/go/inheritance.go +138 -0
- package/extractors/go/lookup.go +106 -0
- package/extractors/go/main.go +57 -0
- package/extractors/go/nodes.go +177 -0
- package/extractors/go/orm_edges.go +277 -0
- package/extractors/go/parser.go +469 -0
- package/extractors/go/routes.go +521 -0
- package/extractors/go/tests.go +45 -0
- package/extractors/go/thirdparty.go +138 -0
- package/extractors/go/typeload.go +178 -0
- package/extractors/go/walker.go +67 -0
- package/extractors/java/build.mjs +90 -0
- package/extractors/java/devlens_java_extractor.jar +0 -0
- package/extractors/java/src/devlens/extractor/Contract.java +171 -0
- package/extractors/java/src/devlens/extractor/Extractor.java +262 -0
- package/extractors/java/src/devlens/extractor/ExtractorResult.java +12 -0
- package/extractors/java/src/devlens/extractor/Fingerprint.java +239 -0
- package/extractors/java/src/devlens/extractor/LookupMaps.java +123 -0
- package/extractors/java/src/devlens/extractor/Main.java +66 -0
- package/extractors/java/src/devlens/extractor/Parser.java +522 -0
- package/extractors/java/src/devlens/extractor/SourceWalker.java +82 -0
- package/extractors/java/src/devlens/extractor/ThirdParty.java +141 -0
- package/extractors/java/src/devlens/extractor/TypeSolverFactory.java +43 -0
- package/extractors/java/src/devlens/extractor/edges/Calls.java +266 -0
- package/extractors/java/src/devlens/extractor/edges/Enrich.java +54 -0
- package/extractors/java/src/devlens/extractor/edges/Imports.java +154 -0
- package/extractors/java/src/devlens/extractor/edges/Inheritance.java +79 -0
- package/extractors/java/src/devlens/extractor/edges/OrmEdges.java +209 -0
- package/extractors/java/src/devlens/extractor/edges/Routes.java +143 -0
- package/extractors/java/src/devlens/extractor/edges/Tests.java +53 -0
- package/extractors/python/devlens_extractors_python/__init__.py +3 -0
- package/extractors/python/devlens_extractors_python/__main__.py +33 -0
- package/extractors/python/devlens_extractors_python/contract.py +94 -0
- package/extractors/python/devlens_extractors_python/edges/__init__.py +28 -0
- package/extractors/python/devlens_extractors_python/edges/calls.py +112 -0
- package/extractors/python/devlens_extractors_python/edges/enrich.py +44 -0
- package/extractors/python/devlens_extractors_python/edges/imports.py +155 -0
- package/extractors/python/devlens_extractors_python/edges/inheritance.py +97 -0
- package/extractors/python/devlens_extractors_python/edges/orm_edges.py +225 -0
- package/extractors/python/devlens_extractors_python/edges/routes/__init__.py +28 -0
- package/extractors/python/devlens_extractors_python/edges/routes/common.py +79 -0
- package/extractors/python/devlens_extractors_python/edges/routes/decorators.py +212 -0
- package/extractors/python/devlens_extractors_python/edges/routes/django_urls.py +213 -0
- package/extractors/python/devlens_extractors_python/edges/routes/drf.py +128 -0
- package/extractors/python/devlens_extractors_python/edges/tests.py +53 -0
- package/extractors/python/devlens_extractors_python/extractor.py +107 -0
- package/extractors/python/devlens_extractors_python/fingerprint.py +201 -0
- package/extractors/python/devlens_extractors_python/lookup.py +72 -0
- package/extractors/python/devlens_extractors_python/parser/__init__.py +72 -0
- package/extractors/python/devlens_extractors_python/parser/classes.py +109 -0
- package/extractors/python/devlens_extractors_python/parser/functions.py +163 -0
- package/extractors/python/devlens_extractors_python/parser/walker.py +30 -0
- package/extractors/python/devlens_extractors_python/third_party.py +103 -0
- package/extractors/python/pyproject.toml +16 -0
- package/extractors/python/setup.mjs +54 -0
- package/extractors/rust/Cargo.toml +29 -0
- package/extractors/rust/bin/darwin-amd64/devlens_rust_extractor +0 -0
- package/extractors/rust/bin/darwin-arm64/devlens_rust_extractor +0 -0
- package/extractors/rust/bin/linux-amd64/devlens_rust_extractor +0 -0
- package/extractors/rust/bin/linux-arm64/devlens_rust_extractor +0 -0
- package/extractors/rust/bin/windows-amd64/devlens_rust_extractor.exe +0 -0
- package/extractors/rust/build.mjs +85 -0
- package/extractors/rust/src/calls.rs +295 -0
- package/extractors/rust/src/contract.rs +236 -0
- package/extractors/rust/src/enrich.rs +107 -0
- package/extractors/rust/src/extractor.rs +199 -0
- package/extractors/rust/src/fingerprint.rs +238 -0
- package/extractors/rust/src/imports.rs +101 -0
- package/extractors/rust/src/inheritance.rs +233 -0
- package/extractors/rust/src/lookup.rs +226 -0
- package/extractors/rust/src/main.rs +53 -0
- package/extractors/rust/src/module_map.rs +131 -0
- package/extractors/rust/src/nodes.rs +174 -0
- package/extractors/rust/src/orm_edges.rs +125 -0
- package/extractors/rust/src/parser.rs +859 -0
- package/extractors/rust/src/routes.rs +1265 -0
- package/extractors/rust/src/tests.rs +109 -0
- package/extractors/rust/src/thirdparty.rs +114 -0
- package/extractors/rust/src/walker.rs +82 -0
- package/package.json +22 -4
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { parseRepo } from "./index.js";
|
|
5
|
+
// ─── Helpers (mirror index.test.ts) ─────────────────────────────────────────
|
|
6
|
+
function createFakeRepo(files) {
|
|
7
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "devlens-classes-test-"));
|
|
8
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
9
|
+
const fullPath = path.join(tmpDir, filePath);
|
|
10
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
11
|
+
fs.writeFileSync(fullPath, content);
|
|
12
|
+
}
|
|
13
|
+
return tmpDir;
|
|
14
|
+
}
|
|
15
|
+
function deleteFakeRepo(repoPath) {
|
|
16
|
+
fs.rmSync(repoPath, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
function nodesOf(repoPath, type) {
|
|
19
|
+
return parseRepo(repoPath).nodes.filter((n) => n.type === type);
|
|
20
|
+
}
|
|
21
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
22
|
+
describe("class extraction", () => {
|
|
23
|
+
// 1. Plain class, no methods → CLASS node only
|
|
24
|
+
it("emits a CLASS node for a plain class with no methods", () => {
|
|
25
|
+
const repo = createFakeRepo({
|
|
26
|
+
"src/empty.ts": `export class Marker {}`,
|
|
27
|
+
});
|
|
28
|
+
const cls = nodesOf(repo, "CLASS").find((n) => n.name === "Marker");
|
|
29
|
+
expect(cls).toBeDefined();
|
|
30
|
+
expect(cls?.metadata.methodNames).toEqual([]);
|
|
31
|
+
expect(cls?.metadata.extendsType).toBeUndefined();
|
|
32
|
+
expect(cls?.metadata.implementsTypes).toEqual([]);
|
|
33
|
+
deleteFakeRepo(repo);
|
|
34
|
+
});
|
|
35
|
+
// 2. Constructor-only class → Class.constructor METHOD
|
|
36
|
+
it("emits a constructor as a METHOD node", () => {
|
|
37
|
+
const repo = createFakeRepo({
|
|
38
|
+
"src/svc.ts": `
|
|
39
|
+
export class Service {
|
|
40
|
+
constructor(private api: string) {}
|
|
41
|
+
}
|
|
42
|
+
`,
|
|
43
|
+
});
|
|
44
|
+
const ctor = nodesOf(repo, "METHOD").find((n) => n.name === "Service.constructor");
|
|
45
|
+
expect(ctor).toBeDefined();
|
|
46
|
+
expect(ctor?.metadata.isConstructor).toBe(true);
|
|
47
|
+
expect(ctor?.metadata.className).toBe("Service");
|
|
48
|
+
const cls = nodesOf(repo, "CLASS").find((n) => n.name === "Service");
|
|
49
|
+
expect(cls?.metadata.constructorParams).toEqual(["api"]);
|
|
50
|
+
deleteFakeRepo(repo);
|
|
51
|
+
});
|
|
52
|
+
// 3. Private methods (private foo, #bar) → isPrivate
|
|
53
|
+
it("flags private and #-private methods", () => {
|
|
54
|
+
const repo = createFakeRepo({
|
|
55
|
+
"src/priv.ts": `
|
|
56
|
+
export class Secrets {
|
|
57
|
+
private hidden() { return 1; }
|
|
58
|
+
#trulyHidden() { return 2; }
|
|
59
|
+
visible() { return this.hidden() + this.#trulyHidden(); }
|
|
60
|
+
}
|
|
61
|
+
`,
|
|
62
|
+
});
|
|
63
|
+
const methods = nodesOf(repo, "METHOD");
|
|
64
|
+
const hidden = methods.find((n) => n.name === "Secrets.hidden");
|
|
65
|
+
const truly = methods.find((n) => n.name === "Secrets.trulyHidden");
|
|
66
|
+
const visible = methods.find((n) => n.name === "Secrets.visible");
|
|
67
|
+
expect(hidden?.metadata.isPrivate).toBe(true);
|
|
68
|
+
expect(truly?.metadata.isPrivate).toBe(true);
|
|
69
|
+
expect(visible?.metadata.isPrivate).toBe(false);
|
|
70
|
+
// #-name is stripped for the node name, and this.#trulyHidden() rewrites
|
|
71
|
+
// to the stripped name
|
|
72
|
+
expect(visible?.metadata.calls).toContain("Secrets.hidden");
|
|
73
|
+
expect(visible?.metadata.calls).toContain("Secrets.trulyHidden");
|
|
74
|
+
deleteFakeRepo(repo);
|
|
75
|
+
});
|
|
76
|
+
// 4. Static methods → isStatic
|
|
77
|
+
it("flags static methods", () => {
|
|
78
|
+
const repo = createFakeRepo({
|
|
79
|
+
"src/static.ts": `
|
|
80
|
+
export class MathUtils {
|
|
81
|
+
static add(a: number, b: number) { return a + b; }
|
|
82
|
+
instance() { return MathUtils.add(1, 2); }
|
|
83
|
+
}
|
|
84
|
+
`,
|
|
85
|
+
});
|
|
86
|
+
const methods = nodesOf(repo, "METHOD");
|
|
87
|
+
expect(methods.find((n) => n.name === "MathUtils.add")?.metadata.isStatic).toBe(true);
|
|
88
|
+
expect(methods.find((n) => n.name === "MathUtils.instance")?.metadata.isStatic).toBe(false);
|
|
89
|
+
// Static self-reference stays as the dotted class name → resolvable later
|
|
90
|
+
expect(methods.find((n) => n.name === "MathUtils.instance")?.metadata.calls).toContain("MathUtils.add");
|
|
91
|
+
deleteFakeRepo(repo);
|
|
92
|
+
});
|
|
93
|
+
// 5. Abstract class + abstract method
|
|
94
|
+
it("flags abstract classes and abstract methods", () => {
|
|
95
|
+
const repo = createFakeRepo({
|
|
96
|
+
"src/abs.ts": `
|
|
97
|
+
export abstract class Shape {
|
|
98
|
+
abstract area(): number;
|
|
99
|
+
label() { return "shape"; }
|
|
100
|
+
}
|
|
101
|
+
`,
|
|
102
|
+
});
|
|
103
|
+
const cls = nodesOf(repo, "CLASS").find((n) => n.name === "Shape");
|
|
104
|
+
expect(cls?.metadata.isAbstract).toBe(true);
|
|
105
|
+
const methods = nodesOf(repo, "METHOD");
|
|
106
|
+
expect(methods.find((n) => n.name === "Shape.area")?.metadata.isAbstract).toBe(true);
|
|
107
|
+
expect(methods.find((n) => n.name === "Shape.label")?.metadata.isAbstract).toBe(false);
|
|
108
|
+
deleteFakeRepo(repo);
|
|
109
|
+
});
|
|
110
|
+
// 6. Getter/setter → isAccessor
|
|
111
|
+
it("emits getters and setters as METHOD nodes with isAccessor", () => {
|
|
112
|
+
const repo = createFakeRepo({
|
|
113
|
+
"src/acc.ts": `
|
|
114
|
+
export class Temperature {
|
|
115
|
+
private celsius = 0;
|
|
116
|
+
get fahrenheit() { return this.celsius * 9 / 5 + 32; }
|
|
117
|
+
set fahrenheit(v: number) { this.celsius = (v - 32) * 5 / 9; }
|
|
118
|
+
}
|
|
119
|
+
`,
|
|
120
|
+
});
|
|
121
|
+
const methods = nodesOf(repo, "METHOD");
|
|
122
|
+
const getter = methods.find((n) => n.name === "Temperature.fahrenheit" && n.metadata.isAccessor === "get");
|
|
123
|
+
const setter = methods.find((n) => n.name === "Temperature.fahrenheit" && n.metadata.isAccessor === "set");
|
|
124
|
+
expect(getter).toBeDefined();
|
|
125
|
+
expect(setter).toBeDefined();
|
|
126
|
+
deleteFakeRepo(repo);
|
|
127
|
+
});
|
|
128
|
+
// 7. Generic class → typeParams
|
|
129
|
+
it("captures type parameters on generic classes", () => {
|
|
130
|
+
const repo = createFakeRepo({
|
|
131
|
+
"src/gen.ts": `export class Box<T> { value?: T; }`,
|
|
132
|
+
});
|
|
133
|
+
const cls = nodesOf(repo, "CLASS").find((n) => n.name === "Box");
|
|
134
|
+
expect(cls?.metadata.typeParams).toEqual(["T"]);
|
|
135
|
+
deleteFakeRepo(repo);
|
|
136
|
+
});
|
|
137
|
+
// 8. Decorated class (NestJS-style)
|
|
138
|
+
it("captures class decorators", () => {
|
|
139
|
+
const repo = createFakeRepo({
|
|
140
|
+
"src/dec.ts": `
|
|
141
|
+
function Injectable() { return (t: any) => t; }
|
|
142
|
+
function Controller(p: string) { return (t: any) => t; }
|
|
143
|
+
@Controller("cats")
|
|
144
|
+
@Injectable()
|
|
145
|
+
export class CatsService {}
|
|
146
|
+
`,
|
|
147
|
+
});
|
|
148
|
+
const cls = nodesOf(repo, "CLASS").find((n) => n.name === "CatsService");
|
|
149
|
+
expect(cls?.metadata.decorators).toContain("Injectable");
|
|
150
|
+
expect(cls?.metadata.decorators).toContain("Controller");
|
|
151
|
+
deleteFakeRepo(repo);
|
|
152
|
+
});
|
|
153
|
+
// 9. React class component → CLASS (not COMPONENT) + propsType/stateType
|
|
154
|
+
it("emits React class components as CLASS nodes with props/state types", () => {
|
|
155
|
+
const repo = createFakeRepo({
|
|
156
|
+
"src/Dashboard.tsx": `
|
|
157
|
+
import React from "react";
|
|
158
|
+
interface DashboardProps { title: string; }
|
|
159
|
+
interface DashboardState { open: boolean; }
|
|
160
|
+
export class Dashboard extends React.Component<DashboardProps, DashboardState> {
|
|
161
|
+
render() { return <div>{this.props.title}</div>; }
|
|
162
|
+
}
|
|
163
|
+
`,
|
|
164
|
+
});
|
|
165
|
+
const result = parseRepo(repo);
|
|
166
|
+
const cls = result.nodes.find((n) => n.name === "Dashboard");
|
|
167
|
+
expect(cls).toBeDefined();
|
|
168
|
+
expect(cls?.type).toBe("CLASS"); // NOT COMPONENT — one node per declaration
|
|
169
|
+
expect(cls?.metadata.isReactComponent).toBe(true);
|
|
170
|
+
expect(cls?.metadata.propsType).toBe("DashboardProps");
|
|
171
|
+
expect(cls?.metadata.stateType).toBe("DashboardState");
|
|
172
|
+
expect(result.nodes.some((n) => n.type === "COMPONENT" && n.name === "Dashboard")).toBe(false);
|
|
173
|
+
const render = result.nodes.find((n) => n.name === "Dashboard.render");
|
|
174
|
+
expect(render?.metadata.isReactLifecycle).toBe(true);
|
|
175
|
+
deleteFakeRepo(repo);
|
|
176
|
+
});
|
|
177
|
+
// 10. Multiple implements → implementsTypes
|
|
178
|
+
it("captures multiple implements clauses", () => {
|
|
179
|
+
const repo = createFakeRepo({
|
|
180
|
+
"src/multi.ts": `
|
|
181
|
+
interface A {}
|
|
182
|
+
interface B {}
|
|
183
|
+
export class Combined implements A, B {}
|
|
184
|
+
`,
|
|
185
|
+
});
|
|
186
|
+
const cls = nodesOf(repo, "CLASS").find((n) => n.name === "Combined");
|
|
187
|
+
expect(cls?.metadata.implementsTypes).toEqual(["A", "B"]);
|
|
188
|
+
deleteFakeRepo(repo);
|
|
189
|
+
});
|
|
190
|
+
// 11. extends local base → extendsType (raw text, generics kept)
|
|
191
|
+
it("captures the raw extends expression", () => {
|
|
192
|
+
const repo = createFakeRepo({
|
|
193
|
+
"src/inh.ts": `
|
|
194
|
+
export class Base {}
|
|
195
|
+
export class Child extends Base {}
|
|
196
|
+
export class GenericChild extends Base<string> {}
|
|
197
|
+
`,
|
|
198
|
+
});
|
|
199
|
+
const child = nodesOf(repo, "CLASS").find((n) => n.name === "Child");
|
|
200
|
+
expect(child?.metadata.extendsType).toBe("Base");
|
|
201
|
+
const generic = nodesOf(repo, "CLASS").find((n) => n.name === "GenericChild");
|
|
202
|
+
expect(generic?.metadata.extendsType).toBe("Base<string>");
|
|
203
|
+
deleteFakeRepo(repo);
|
|
204
|
+
});
|
|
205
|
+
// 12. Class inside a test file → testCases metadata only, zero child nodes
|
|
206
|
+
it("does not emit class/method nodes for test files", () => {
|
|
207
|
+
const repo = createFakeRepo({
|
|
208
|
+
"src/thing.test.ts": `
|
|
209
|
+
export class TestHelper { run() { return 1; } }
|
|
210
|
+
export function helper() { return new TestHelper().run(); }
|
|
211
|
+
`,
|
|
212
|
+
});
|
|
213
|
+
const result = parseRepo(repo);
|
|
214
|
+
expect(result.nodes.some((n) => n.type === "CLASS" || n.type === "METHOD")).toBe(false);
|
|
215
|
+
const testFile = result.nodes.find((n) => n.type === "TEST");
|
|
216
|
+
expect(testFile).toBeDefined();
|
|
217
|
+
expect(testFile?.metadata.testCases).toContain("TestHelper");
|
|
218
|
+
deleteFakeRepo(repo);
|
|
219
|
+
});
|
|
220
|
+
// 13. Class expression (const Foo = class {}) → skipped
|
|
221
|
+
it("skips class expressions", () => {
|
|
222
|
+
const repo = createFakeRepo({
|
|
223
|
+
"src/expr.ts": `
|
|
224
|
+
export const Foo = class {
|
|
225
|
+
bar() { return 1; }
|
|
226
|
+
};
|
|
227
|
+
`,
|
|
228
|
+
});
|
|
229
|
+
const result = parseRepo(repo);
|
|
230
|
+
expect(result.nodes.some((n) => n.type === "CLASS" && n.name === "Foo")).toBe(false);
|
|
231
|
+
expect(result.nodes.some((n) => n.type === "METHOD" && n.name === "Foo.bar")).toBe(false);
|
|
232
|
+
deleteFakeRepo(repo);
|
|
233
|
+
});
|
|
234
|
+
// 14-20. this-rewrite behavior
|
|
235
|
+
it("rewrites this.method() calls to Class.method", () => {
|
|
236
|
+
const repo = createFakeRepo({
|
|
237
|
+
"src/rewrite.ts": `
|
|
238
|
+
export class UserService {
|
|
239
|
+
save() { return 1; }
|
|
240
|
+
run() { return this.save(); }
|
|
241
|
+
}
|
|
242
|
+
`,
|
|
243
|
+
});
|
|
244
|
+
const run = nodesOf(repo, "METHOD").find((n) => n.name === "UserService.run");
|
|
245
|
+
expect(run?.metadata.calls).toContain("UserService.save");
|
|
246
|
+
expect(run?.metadata.calls).not.toContain("this.save");
|
|
247
|
+
deleteFakeRepo(repo);
|
|
248
|
+
});
|
|
249
|
+
it("does not rewrite this.obj.bar() when obj is a property", () => {
|
|
250
|
+
const repo = createFakeRepo({
|
|
251
|
+
"src/prop.ts": `
|
|
252
|
+
export class Adapter {
|
|
253
|
+
private nested = { emit: () => 1 };
|
|
254
|
+
go() { return this.nested.emit(); }
|
|
255
|
+
}
|
|
256
|
+
`,
|
|
257
|
+
});
|
|
258
|
+
const go = nodesOf(repo, "METHOD").find((n) => n.name === "Adapter.go");
|
|
259
|
+
expect(go?.metadata.calls).toContain("this.nested.emit"); // untouched
|
|
260
|
+
expect(go?.metadata.calls).not.toContain("Adapter.nested.emit");
|
|
261
|
+
deleteFakeRepo(repo);
|
|
262
|
+
});
|
|
263
|
+
it("leaves super.method() calls untouched", () => {
|
|
264
|
+
const repo = createFakeRepo({
|
|
265
|
+
"src/super.ts": `
|
|
266
|
+
export class Base { ping() { return 1; } }
|
|
267
|
+
export class Child extends Base {
|
|
268
|
+
call() { return super.ping(); }
|
|
269
|
+
}
|
|
270
|
+
`,
|
|
271
|
+
});
|
|
272
|
+
const call = nodesOf(repo, "METHOD").find((n) => n.name === "Child.call");
|
|
273
|
+
expect(call?.metadata.calls).toContain("super.ping");
|
|
274
|
+
expect(call?.metadata.calls).not.toContain("Child.ping");
|
|
275
|
+
deleteFakeRepo(repo);
|
|
276
|
+
});
|
|
277
|
+
it("rewrites this.method.bind(this) member expressions cleanly", () => {
|
|
278
|
+
const repo = createFakeRepo({
|
|
279
|
+
"src/bind.ts": `
|
|
280
|
+
export class Handler {
|
|
281
|
+
work() { return 1; }
|
|
282
|
+
setup() { return this.work.bind(this); }
|
|
283
|
+
}
|
|
284
|
+
`,
|
|
285
|
+
});
|
|
286
|
+
const setup = nodesOf(repo, "METHOD").find((n) => n.name === "Handler.setup");
|
|
287
|
+
expect(setup?.metadata.calls).toContain("Handler.work.bind");
|
|
288
|
+
deleteFakeRepo(repo);
|
|
289
|
+
});
|
|
290
|
+
it("rewrites this.foo() inside nested arrow functions", () => {
|
|
291
|
+
const repo = createFakeRepo({
|
|
292
|
+
"src/arrow.ts": `
|
|
293
|
+
export class Runner {
|
|
294
|
+
step() { return 1; }
|
|
295
|
+
all() { return [1].map(() => this.step()); }
|
|
296
|
+
}
|
|
297
|
+
`,
|
|
298
|
+
});
|
|
299
|
+
const all = nodesOf(repo, "METHOD").find((n) => n.name === "Runner.all");
|
|
300
|
+
expect(all?.metadata.calls).toContain("Runner.step");
|
|
301
|
+
deleteFakeRepo(repo);
|
|
302
|
+
});
|
|
303
|
+
it("rewrites this.foo() inside nested regular functions (heuristic)", () => {
|
|
304
|
+
const repo = createFakeRepo({
|
|
305
|
+
"src/fn.ts": `
|
|
306
|
+
export class Legacy {
|
|
307
|
+
step() { return 1; }
|
|
308
|
+
run() { return function () { return this.step(); }; }
|
|
309
|
+
}
|
|
310
|
+
`,
|
|
311
|
+
});
|
|
312
|
+
const run = nodesOf(repo, "METHOD").find((n) => n.name === "Legacy.run");
|
|
313
|
+
expect(run?.metadata.calls).toContain("Legacy.step"); // documented heuristic
|
|
314
|
+
deleteFakeRepo(repo);
|
|
315
|
+
});
|
|
316
|
+
it("rewrites this.bar() in static methods to Class.bar", () => {
|
|
317
|
+
const repo = createFakeRepo({
|
|
318
|
+
"src/stat.ts": `
|
|
319
|
+
export class Registry {
|
|
320
|
+
static lookup() { return 1; }
|
|
321
|
+
static init() { return this.lookup(); }
|
|
322
|
+
}
|
|
323
|
+
`,
|
|
324
|
+
});
|
|
325
|
+
const init = nodesOf(repo, "METHOD").find((n) => n.name === "Registry.init");
|
|
326
|
+
expect(init?.metadata.calls).toContain("Registry.lookup");
|
|
327
|
+
deleteFakeRepo(repo);
|
|
328
|
+
});
|
|
329
|
+
// Stats
|
|
330
|
+
it("reports classCount and methodCount in stats", () => {
|
|
331
|
+
const repo = createFakeRepo({
|
|
332
|
+
"src/stats.ts": `
|
|
333
|
+
export class Alpha { one() {} two() {} }
|
|
334
|
+
export class Beta { three() {} }
|
|
335
|
+
`,
|
|
336
|
+
});
|
|
337
|
+
const stats = parseRepo(repo).stats;
|
|
338
|
+
expect(stats.classCount).toBe(2);
|
|
339
|
+
expect(stats.methodCount).toBe(3);
|
|
340
|
+
deleteFakeRepo(repo);
|
|
341
|
+
});
|
|
342
|
+
// Node identity / wiring
|
|
343
|
+
it("normalizes class and method ids to relative paths with parentFile", () => {
|
|
344
|
+
const repo = createFakeRepo({
|
|
345
|
+
"src/ids.ts": `export class Widget { paint() {} }`,
|
|
346
|
+
});
|
|
347
|
+
const result = parseRepo(repo);
|
|
348
|
+
const cls = result.nodes.find((n) => n.name === "Widget");
|
|
349
|
+
const method = result.nodes.find((n) => n.name === "Widget.paint");
|
|
350
|
+
expect(cls?.id).toBe("src/ids.ts::Widget");
|
|
351
|
+
expect(cls?.parentFile).toBe("file::src/ids.ts");
|
|
352
|
+
expect(method?.id).toBe("src/ids.ts::Widget.paint");
|
|
353
|
+
expect(method?.parentFile).toBe("file::src/ids.ts");
|
|
354
|
+
expect(method?.filePath).toBe("src/ids.ts");
|
|
355
|
+
const fileNode = result.nodes.find((n) => n.type === "FILE" && n.filePath === "src/ids.ts");
|
|
356
|
+
expect(fileNode?.metadata.childNodeIds).toContain("src/ids.ts::Widget");
|
|
357
|
+
expect(fileNode?.metadata.childNodeIds).toContain("src/ids.ts::Widget.paint");
|
|
358
|
+
deleteFakeRepo(repo);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { SourceFile } from "ts-morph";
|
|
2
|
+
import type { CodeNode } from "../../types.js";
|
|
3
|
+
import { type RenderingBoundary } from "../directives.js";
|
|
4
|
+
export declare function stripGenerics(typeText: string): string;
|
|
5
|
+
export declare function extractClasses(file: SourceFile, fileDirective?: RenderingBoundary): CodeNode[];
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// Extracts classes and their methods as CLASS / METHOD nodes.
|
|
2
|
+
//
|
|
3
|
+
// Previously, `class Foo {}` declarations (and React class components like
|
|
4
|
+
// `class Dashboard extends React.Component`) were completely invisible to the
|
|
5
|
+
// graph — the components extractor only sees functions, and the functions
|
|
6
|
+
// extractor skips anything that isn't a function declaration or arrow.
|
|
7
|
+
//
|
|
8
|
+
// Conventions:
|
|
9
|
+
// - CLASS node id/name: `Foo` (file-scoped, like functions)
|
|
10
|
+
// - METHOD node id/name: `Foo.bar` (dotted — matches the Go
|
|
11
|
+
// extractor's `methodNodeID` and the objectMethods extractor, so
|
|
12
|
+
// `nodesByName` resolves `Foo.bar(...)` calls with zero extra plumbing)
|
|
13
|
+
// - `this.bar()` inside a method is rewritten to `Foo.bar(...)` at extraction
|
|
14
|
+
// time ONLY when `bar` is a method of the class (property access like
|
|
15
|
+
// `this.obj.bar()` is left alone). `super.x()` is never rewritten.
|
|
16
|
+
//
|
|
17
|
+
// Out of scope (deliberate): INTERFACE / ENUM / STRUCT nodes for JS/TS.
|
|
18
|
+
import { SyntaxKind } from "ts-morph";
|
|
19
|
+
import { detectFunctionDirective } from "../directives.js";
|
|
20
|
+
import { extractFunctionCalls, extractHookCalls, extractApiCalls, hasErrorHandling, extractThrowStatements, } from "./functions.js";
|
|
21
|
+
import { extractParams, extractReturnTypeAnnotation, extractBareTypeNames, extractReferencedInterfaces, } from "../typeUtils.js";
|
|
22
|
+
// React lifecycle methods (incl. render) — flagged so consumers can tell
|
|
23
|
+
// framework-registered callbacks apart from ordinary methods.
|
|
24
|
+
const REACT_LIFECYCLE = new Set([
|
|
25
|
+
"render",
|
|
26
|
+
"componentDidMount",
|
|
27
|
+
"componentDidUpdate",
|
|
28
|
+
"componentWillUnmount",
|
|
29
|
+
"shouldComponentUpdate",
|
|
30
|
+
"getDerivedStateFromProps",
|
|
31
|
+
"getSnapshotBeforeUpdate",
|
|
32
|
+
"componentWillMount",
|
|
33
|
+
"componentWillReceiveProps",
|
|
34
|
+
]);
|
|
35
|
+
function makeId(filePath, name) {
|
|
36
|
+
return `${filePath}::${name}`;
|
|
37
|
+
}
|
|
38
|
+
// `React.Component<Props, State>` → `React.Component`; `Base<T>` → `Base`.
|
|
39
|
+
// Used for extends-resolution (the detector only cares about the base name).
|
|
40
|
+
export function stripGenerics(typeText) {
|
|
41
|
+
const lt = typeText.indexOf("<");
|
|
42
|
+
return (lt === -1 ? typeText : typeText.slice(0, lt)).trim();
|
|
43
|
+
}
|
|
44
|
+
// True when the extends expression is a React component base
|
|
45
|
+
// (React.Component / React.PureComponent / Component / PureComponent).
|
|
46
|
+
function isReactBase(extendsType) {
|
|
47
|
+
const base = stripGenerics(extendsType);
|
|
48
|
+
return /^(?:React\.)?(?:Pure)?Component$/.test(base);
|
|
49
|
+
}
|
|
50
|
+
// Pulls `Props` and `State` out of `React.Component<Props, State>` /
|
|
51
|
+
// `Component<Props>` (top-level comma split — nested generics tolerated).
|
|
52
|
+
function parseComponentGenerics(extendsType) {
|
|
53
|
+
const lt = extendsType.indexOf("<");
|
|
54
|
+
const gt = extendsType.lastIndexOf(">");
|
|
55
|
+
if (lt === -1 || gt === -1 || gt < lt)
|
|
56
|
+
return {};
|
|
57
|
+
const args = extendsType.slice(lt + 1, gt).split(",").map((a) => a.trim()).filter(Boolean);
|
|
58
|
+
if (args.length === 0)
|
|
59
|
+
return {};
|
|
60
|
+
const out = { propsType: args[0] };
|
|
61
|
+
if (args.length > 1)
|
|
62
|
+
out.stateType = args[1];
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
// Rewrites `this.foo(...)` call texts to `<ClassName>.foo(...)` so they match
|
|
66
|
+
// the dotted METHOD node names in nodesByName. Only rewrites when the first
|
|
67
|
+
// segment after `this.` is an actual method of the class — `this.obj.bar()`
|
|
68
|
+
// (property access) and `super.x()` stay untouched. A leading `#` on the
|
|
69
|
+
// method segment (private identifier `this.#foo()`) is stripped to match the
|
|
70
|
+
// node name.
|
|
71
|
+
function rewriteThisCalls(calls, className, methodNames) {
|
|
72
|
+
return calls.map((call) => {
|
|
73
|
+
if (!call.startsWith("this."))
|
|
74
|
+
return call;
|
|
75
|
+
const rest = call.slice("this.".length);
|
|
76
|
+
const firstSegment = rest.split(".")[0].replace(/^#/, "");
|
|
77
|
+
if (!methodNames.has(firstSegment))
|
|
78
|
+
return call;
|
|
79
|
+
return `${className}.${rest.replace(/^#/, "")}`;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function extractDecoratorNames(cls) {
|
|
83
|
+
if (typeof cls.getDecorators !== "function")
|
|
84
|
+
return [];
|
|
85
|
+
return cls.getDecorators().map((d) => (typeof d.getName === "function" ? d.getName() : d.getText()));
|
|
86
|
+
}
|
|
87
|
+
function isPrivateMethod(method, name) {
|
|
88
|
+
if (name.startsWith("#"))
|
|
89
|
+
return true;
|
|
90
|
+
return method.getModifiers().some((m) => m.getKind() === SyntaxKind.PrivateKeyword);
|
|
91
|
+
}
|
|
92
|
+
// Builds a METHOD node from any function-shaped class member.
|
|
93
|
+
// methodNameSet — all method-ish names of the owning class, used by the
|
|
94
|
+
// `this.` rewrite to distinguish methods from field/property access.
|
|
95
|
+
function buildMethodNode(file, className, methodName, method, methodNameSet, fileDirective) {
|
|
96
|
+
const filePath = file.getFilePath();
|
|
97
|
+
const dotted = `${className}.${methodName}`;
|
|
98
|
+
const typedParams = extractParams(method);
|
|
99
|
+
const calls = extractFunctionCalls(method);
|
|
100
|
+
const hookCalls = extractHookCalls(method);
|
|
101
|
+
const apiCalls = extractApiCalls(method);
|
|
102
|
+
const isAsync = typeof method.isAsync === "function" && method.isAsync();
|
|
103
|
+
const hasErrors = hasErrorHandling(method);
|
|
104
|
+
const throws = extractThrowStatements(method);
|
|
105
|
+
const renderingBoundary = detectFunctionDirective(method.getBody?.()) ?? fileDirective;
|
|
106
|
+
const returnType = extractReturnTypeAnnotation(method);
|
|
107
|
+
const bareTypeNames = extractBareTypeNames([...typedParams.map((p) => p.type), returnType]);
|
|
108
|
+
const referencedTypes = extractReferencedInterfaces(file, bareTypeNames);
|
|
109
|
+
return {
|
|
110
|
+
id: makeId(filePath, dotted),
|
|
111
|
+
name: dotted,
|
|
112
|
+
type: "METHOD",
|
|
113
|
+
filePath,
|
|
114
|
+
startLine: method.getStartLineNumber(),
|
|
115
|
+
endLine: method.getEndLineNumber(),
|
|
116
|
+
rawCode: method.getText(),
|
|
117
|
+
metadata: {
|
|
118
|
+
className,
|
|
119
|
+
params: typedParams.map((p) => p.name),
|
|
120
|
+
parameters: typedParams,
|
|
121
|
+
returnType,
|
|
122
|
+
referencedTypes,
|
|
123
|
+
calls: rewriteThisCalls(calls, className, methodNameSet),
|
|
124
|
+
hookCalls,
|
|
125
|
+
apiCalls,
|
|
126
|
+
isAsync,
|
|
127
|
+
isStatic: typeof method.isStatic === "function" ? method.isStatic() : false,
|
|
128
|
+
isPrivate: isPrivateMethod(method, methodName),
|
|
129
|
+
isAbstract: typeof method.isAbstract === "function" ? method.isAbstract() : false,
|
|
130
|
+
isConstructor: methodName === "constructor",
|
|
131
|
+
isAccessor: undefined,
|
|
132
|
+
isReactLifecycle: REACT_LIFECYCLE.has(methodName),
|
|
133
|
+
hasErrorHandling: hasErrors,
|
|
134
|
+
throws,
|
|
135
|
+
lineCount: method.getEndLineNumber() - method.getStartLineNumber(),
|
|
136
|
+
...(renderingBoundary !== null && { renderingBoundary }),
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// All method-ish names of a class declaration (methods + constructor +
|
|
141
|
+
// accessors) — used by the `this.` rewrite to distinguish methods from fields.
|
|
142
|
+
function collectMethodNames(cls) {
|
|
143
|
+
const names = [];
|
|
144
|
+
for (const m of cls.getMethods?.() ?? []) {
|
|
145
|
+
const n = m.getName?.();
|
|
146
|
+
if (typeof n === "string")
|
|
147
|
+
names.push(n.startsWith("#") ? n.slice(1) : n);
|
|
148
|
+
}
|
|
149
|
+
for (const c of cls.getConstructors?.() ?? [])
|
|
150
|
+
names.push("constructor");
|
|
151
|
+
for (const a of [...(cls.getGetAccessors?.() ?? []), ...(cls.getSetAccessors?.() ?? [])]) {
|
|
152
|
+
const n = a.getName?.();
|
|
153
|
+
if (typeof n === "string")
|
|
154
|
+
names.push(n);
|
|
155
|
+
}
|
|
156
|
+
return names;
|
|
157
|
+
}
|
|
158
|
+
export function extractClasses(file, fileDirective = null) {
|
|
159
|
+
const nodes = [];
|
|
160
|
+
const filePath = file.getFilePath();
|
|
161
|
+
for (const cls of file.getClasses()) {
|
|
162
|
+
const name = cls.getName();
|
|
163
|
+
// Anonymous default-export classes (`export default class {}`) have no
|
|
164
|
+
// name to build an id from — skipped (documented).
|
|
165
|
+
if (!name)
|
|
166
|
+
continue;
|
|
167
|
+
const extendsClause = cls.getExtends();
|
|
168
|
+
const extendsType = extendsClause ? extendsClause.getText() : undefined;
|
|
169
|
+
const implementsTypes = cls.getImplements().map((t) => t.getExpression().getText());
|
|
170
|
+
const decorators = extractDecoratorNames(cls);
|
|
171
|
+
const isReactComponent = extendsType ? isReactBase(extendsType) : false;
|
|
172
|
+
const componentGenerics = isReactComponent && extendsType ? parseComponentGenerics(extendsType) : {};
|
|
173
|
+
const methodNames = collectMethodNames(cls);
|
|
174
|
+
const propertyNames = [...(cls.getProperties?.() ?? []), ...(cls.getStaticProperties?.() ?? [])]
|
|
175
|
+
.map((p) => p.getName?.())
|
|
176
|
+
.filter((n) => typeof n === "string");
|
|
177
|
+
const constructors = cls.getConstructors?.() ?? [];
|
|
178
|
+
const constructorParams = constructors[0]
|
|
179
|
+
? constructors[0].getParameters().map((p) => p.getName())
|
|
180
|
+
: [];
|
|
181
|
+
const isExported = typeof cls.isExported === "function" ? cls.isExported() : false;
|
|
182
|
+
nodes.push({
|
|
183
|
+
id: makeId(filePath, name),
|
|
184
|
+
name,
|
|
185
|
+
type: "CLASS",
|
|
186
|
+
filePath,
|
|
187
|
+
startLine: cls.getStartLineNumber(),
|
|
188
|
+
endLine: cls.getEndLineNumber(),
|
|
189
|
+
rawCode: cls.getText(),
|
|
190
|
+
metadata: {
|
|
191
|
+
extendsType,
|
|
192
|
+
implementsTypes,
|
|
193
|
+
decorators,
|
|
194
|
+
isAbstract: typeof cls.isAbstract === "function" ? cls.isAbstract() : false,
|
|
195
|
+
isExported,
|
|
196
|
+
exportType: cls.isDefaultExport() ? "default" : isExported ? "named" : "none",
|
|
197
|
+
isReactComponent,
|
|
198
|
+
...componentGenerics,
|
|
199
|
+
typeParams: cls.getTypeParameters().map((tp) => tp.getName()),
|
|
200
|
+
methodNames,
|
|
201
|
+
propertyNames,
|
|
202
|
+
constructorParams,
|
|
203
|
+
lineCount: cls.getEndLineNumber() - cls.getStartLineNumber(),
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
// ─── METHOD nodes ────────────────────────────────────────────────────
|
|
207
|
+
const methodNameSet = new Set(methodNames);
|
|
208
|
+
for (const m of cls.getMethods()) {
|
|
209
|
+
let methodName = m.getName();
|
|
210
|
+
if (typeof methodName !== "string" || methodName === "")
|
|
211
|
+
continue; // computed names
|
|
212
|
+
const isHashPrivate = methodName.startsWith("#");
|
|
213
|
+
if (isHashPrivate)
|
|
214
|
+
methodName = methodName.slice(1);
|
|
215
|
+
const node = buildMethodNode(file, name, methodName, m, methodNameSet, fileDirective);
|
|
216
|
+
if (isHashPrivate)
|
|
217
|
+
node.metadata.isPrivate = true;
|
|
218
|
+
nodes.push(node);
|
|
219
|
+
}
|
|
220
|
+
for (const c of cls.getConstructors()) {
|
|
221
|
+
nodes.push(buildMethodNode(file, name, "constructor", c, methodNameSet, fileDirective));
|
|
222
|
+
}
|
|
223
|
+
for (const acc of cls.getGetAccessors()) {
|
|
224
|
+
const accName = acc.getName();
|
|
225
|
+
if (typeof accName !== "string")
|
|
226
|
+
continue;
|
|
227
|
+
const node = buildMethodNode(file, name, accName, acc, methodNameSet, fileDirective);
|
|
228
|
+
node.metadata.isAccessor = "get";
|
|
229
|
+
nodes.push(node);
|
|
230
|
+
}
|
|
231
|
+
for (const acc of cls.getSetAccessors()) {
|
|
232
|
+
const accName = acc.getName();
|
|
233
|
+
if (typeof accName !== "string")
|
|
234
|
+
continue;
|
|
235
|
+
const node = buildMethodNode(file, name, accName, acc, methodNameSet, fileDirective);
|
|
236
|
+
node.metadata.isAccessor = "set";
|
|
237
|
+
nodes.push(node);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return nodes;
|
|
241
|
+
}
|
package/dist/parser/index.d.ts
CHANGED
package/dist/parser/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { extractHooks } from "./extractors/hooks.js";
|
|
|
6
6
|
import { extractFunctions } from "./extractors/functions.js";
|
|
7
7
|
import { extractStores } from "./extractors/stores.js";
|
|
8
8
|
import { extractObjectMethods } from "./extractors/objectMethods.js";
|
|
9
|
+
import { extractClasses } from "./extractors/classes.js";
|
|
9
10
|
import { detectFileDirective } from "./directives.js";
|
|
10
11
|
import { createHash } from "crypto";
|
|
11
12
|
// Directories to skip entirely while walking
|
|
@@ -109,7 +110,8 @@ export function parseRepo(repoPath) {
|
|
|
109
110
|
const functions = extractFunctions(file, fileDirective);
|
|
110
111
|
const stores = extractStores(file);
|
|
111
112
|
const objectMethods = extractObjectMethods(file, fileDirective);
|
|
112
|
-
const
|
|
113
|
+
const classes = extractClasses(file, fileDirective);
|
|
114
|
+
const extracted = [...components, ...hooks, ...functions, ...stores, ...objectMethods, ...classes];
|
|
113
115
|
for (const node of extracted) {
|
|
114
116
|
// Normalize all extracted nodes to relative paths so every node in the
|
|
115
117
|
// graph uses the same coordinate system as the FILE nodes.
|
|
@@ -155,6 +157,8 @@ export function parseRepo(repoPath) {
|
|
|
155
157
|
const hookCount = allNodes.filter((n) => n.type === "HOOK").length;
|
|
156
158
|
const functionCount = allNodes.filter((n) => n.type === "FUNCTION").length;
|
|
157
159
|
const storeCount = allNodes.filter((n) => n.type === "STATE_STORE").length;
|
|
160
|
+
const classCount = allNodes.filter((n) => n.type === "CLASS").length;
|
|
161
|
+
const methodCount = allNodes.filter((n) => n.type === "METHOD").length;
|
|
158
162
|
return {
|
|
159
163
|
nodes: allNodes,
|
|
160
164
|
stats: {
|
|
@@ -164,6 +168,8 @@ export function parseRepo(repoPath) {
|
|
|
164
168
|
hookCount,
|
|
165
169
|
functionCount,
|
|
166
170
|
storeCount,
|
|
171
|
+
classCount,
|
|
172
|
+
methodCount,
|
|
167
173
|
skippedFiles,
|
|
168
174
|
},
|
|
169
175
|
};
|
package/dist/pipeline/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface PipelineResult {
|
|
|
36
36
|
repoPath: string;
|
|
37
37
|
analyzedAt: string;
|
|
38
38
|
fingerprint: ProjectFingerprint;
|
|
39
|
-
routes: RouteNode
|
|
39
|
+
routes: (RouteNode | BackendRouteNode)[];
|
|
40
40
|
nodes: CodeNode[];
|
|
41
41
|
edges: CodeEdge[];
|
|
42
42
|
allNodes: CodeNode[];
|
|
@@ -46,5 +46,6 @@ export interface PipelineResult {
|
|
|
46
46
|
isGithubRepo: boolean;
|
|
47
47
|
gitInfo: GitInfo;
|
|
48
48
|
}
|
|
49
|
+
export declare function routesToCodeNodes(routes: (RouteNode | BackendRouteNode)[], repoPath: string): CodeNode[];
|
|
49
50
|
export declare function analyzePipeline(repoPath: string, isGithubRepo: boolean, options?: PipelineOptions): Promise<PipelineResult>;
|
|
50
51
|
export declare function refilterPipeline(stored: PipelineResult, thresholds: FilterThresholds): Pick<PipelineResult, "nodes" | "edges" | "stats">;
|