devlensio 0.6.2 → 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/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 +116 -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
package/README.md
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/devlensio)
|
|
4
4
|
[](https://www.gnu.org/licenses/agpl-3.0)
|
|
5
5
|
|
|
6
|
-
The core engine behind [DevLens](https://github.com/devlensio/devlensOSS) — the codebase visualizer.
|
|
6
|
+
The core engine behind [DevLens](https://github.com/devlensio/devlensOSS) — the codebase visualizer.
|
|
7
|
+
|
|
8
|
+
Point it at any repository and it builds a **typed code graph**: every file, function, class, route, and data model becomes a *node*, connected by *edges* that show what imports what, what calls what, and how data flows. Each node gets an importance score and — optionally — an AI-generated functional, technical, and security summary.
|
|
9
|
+
|
|
10
|
+
It understands **6 languages out of the box** — TypeScript, JavaScript, Python, Java, Go, and Rust — with framework-aware parsing (React, Next.js, Express, Django, FastAPI, Spring Boot, Gin, Axum, and more). No tree-sitter, no generic AST guesswork: every language uses its own mature, native parser.
|
|
7
11
|
|
|
8
12
|
The user-facing tools — CLI, MCP server, Agent Skill, and Web UI — all live in [DevLens OSS](https://github.com/devlensio/devlensOSS) and consume this package.
|
|
9
13
|
|
|
@@ -11,34 +15,52 @@ The user-facing tools — CLI, MCP server, Agent Skill, and Web UI — all live
|
|
|
11
15
|
|
|
12
16
|
## What it does
|
|
13
17
|
|
|
18
|
+
Analyzing a repo runs through a simple 7-step pipeline:
|
|
19
|
+
|
|
14
20
|
```
|
|
15
21
|
Repo path
|
|
16
22
|
│
|
|
17
|
-
[1] Fingerprint →
|
|
18
|
-
[2] Route detection →
|
|
19
|
-
[3] AST parsing →
|
|
20
|
-
[4] Edge detection →
|
|
21
|
-
[5] Scoring →
|
|
22
|
-
[6] Clustering →
|
|
23
|
-
[7] Summarize (opt) →
|
|
23
|
+
[1] Fingerprint → which language & framework? (React, Express, Django, Spring, Gin, Axum …)
|
|
24
|
+
[2] Route detection → every URL the app serves, with the handler behind each route
|
|
25
|
+
[3] AST parsing → per-language parser walks the source → typed nodes (files, functions, classes, structs, traits …)
|
|
26
|
+
[4] Edge detection → links nodes: CALLS, IMPORTS, HANDLES, IMPLEMENTS, EXTENDS, READS_FROM, WRITES_TO + more
|
|
27
|
+
[5] Scoring → deterministic importance scoring (no AI)
|
|
28
|
+
[6] Clustering → groups related nodes into cohesive clusters
|
|
29
|
+
[7] Summarize (opt) → LLM summaries — functional + technical + security
|
|
24
30
|
│
|
|
25
31
|
▼
|
|
26
|
-
Graph saved to ~/.devlens
|
|
32
|
+
Graph saved to ~/.devlens → queried via the traversal API / CLI / MCP / UI
|
|
27
33
|
```
|
|
28
34
|
|
|
29
35
|
Structural analysis is fast and deterministic. Summarization is the only step that calls an LLM — and unchanged nodes are reused across commits (90%+ free on re-runs).
|
|
30
36
|
|
|
31
37
|
---
|
|
32
38
|
|
|
39
|
+
## Supported languages & frameworks
|
|
40
|
+
|
|
41
|
+
Every language uses a **native parser** — no tree-sitter. TypeScript/JavaScript is parsed inline by the engine; Python, Java, Go, and Rust each run a small language-native extractor that the engine orchestrates over JSON.
|
|
42
|
+
|
|
43
|
+
| Language | Extractor | Runtime needed on the analyzing machine | Frameworks detected (routes + data layer) |
|
|
44
|
+
| :-- | :-- | :-- | :-- |
|
|
45
|
+
| **TypeScript / JavaScript** | Inline (`ts-morph`) | none | React (incl. React Router), Next.js, Express, Fastify, Koa, Hono, Elysia, Bun |
|
|
46
|
+
| **Python** | Native (stdlib `ast`) | Python 3.11+ (private venv, auto-created) | Django, Flask, FastAPI, DRF · SQLAlchemy / Django ORM |
|
|
47
|
+
| **Java** | Native (JavaParser) | JVM 17+ | Spring Boot, Quarkus · JPA |
|
|
48
|
+
| **Go** | Native (prebuilt static binary) | none | net/http, Gin, Echo, Fiber · GORM, database/sql |
|
|
49
|
+
| **Rust** | Native (prebuilt static binary) | none | Axum, Actix-web, Rocket, utoipa-axum · Diesel |
|
|
50
|
+
|
|
51
|
+
**Zero-toolchain languages.** Go and Rust ship as prebuilt static binaries for Linux / macOS / Windows (amd64 + arm64), and the Java extractor ships as a prebuilt fat jar — no compiler, Maven, or Gradle needed where the analysis runs. Python creates a private `.venv` on install (idempotent, skipped if `python3` isn't found).
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
33
55
|
## Install
|
|
34
56
|
|
|
35
57
|
```bash
|
|
36
|
-
npm install devlensio
|
|
37
|
-
# or
|
|
38
58
|
bun add devlensio
|
|
59
|
+
# or
|
|
60
|
+
npm install devlensio
|
|
39
61
|
```
|
|
40
62
|
|
|
41
|
-
|
|
63
|
+
The engine runs on **Bun** — its published entry imports from `bun` (used by the job queue), so plain-Node loading isn't supported. No LLM key is needed for structural analysis — it's fully offline and deterministic. Keys are only needed for AI summaries. See the language table above for per-language runtime requirements.
|
|
42
64
|
|
|
43
65
|
---
|
|
44
66
|
|
|
@@ -105,23 +127,52 @@ Also exported: all core types (`CodeNode`, `CodeEdge`, `NodeType`, `EdgeType`, `
|
|
|
105
127
|
|
|
106
128
|
## Node & edge types
|
|
107
129
|
|
|
108
|
-
**Node types**
|
|
109
|
-
|
|
110
|
-
| Type | What it represents |
|
|
111
|
-
| :-- | :-- |
|
|
112
|
-
| `COMPONENT` | React / UI component |
|
|
113
|
-
| `HOOK` | React custom hook |
|
|
114
|
-
| `FUNCTION` | Plain function |
|
|
115
|
-
| `STATE_STORE` | State management (Zustand, Redux, etc.) |
|
|
116
|
-
| `UTILITY` | Utility / helper module |
|
|
117
|
-
| `FILE` | File-level node |
|
|
118
|
-
| `ROUTE` | Application route |
|
|
119
|
-
| `TEST` | Test file |
|
|
120
|
-
| `THIRD_PARTY` | External dependency |
|
|
130
|
+
**Node types** — everything the graph knows about
|
|
121
131
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
132
|
+
| Type | What it means | Where you'll see it |
|
|
133
|
+
| :-- | :-- | :-- |
|
|
134
|
+
| `COMPONENT` | A React / UI component — something that renders UI | TS/JS |
|
|
135
|
+
| `HOOK` | A React custom hook (`useX`), including the state/functions it returns | TS/JS |
|
|
136
|
+
| `FUNCTION` | Any plain function — helper, callback, utility, serverless handler | TS/JS, Python, Go, Rust |
|
|
137
|
+
| `STATE_STORE` | A central state container (Zustand, Redux, …) | TS/JS |
|
|
138
|
+
| `UTILITY` | A helper module — code that isn't UI and isn't a component | TS/JS |
|
|
139
|
+
| `CLASS` | A class — a blueprint for objects (`class User {}`). JS ships props/state types & decorators for React class components | TS/JS, Python, Java |
|
|
140
|
+
| `METHOD` | A function attached to a class — shown as `ClassName.method` | TS/JS, Python, Java, Go, Rust |
|
|
141
|
+
| `INTERFACE` | A type contract — the shape implementing types must satisfy | Java, Go |
|
|
142
|
+
| `ENUM` | A fixed set of named values (e.g. `Status.Active`) | Java, Rust |
|
|
143
|
+
| `STRUCT` | A plain data structure with fields (record-like) | Go, Rust |
|
|
144
|
+
| `TRAIT` | Rust's version of an interface — a set of behaviors a type can implement | Rust |
|
|
145
|
+
| `IMPL_BLOCK` | A Rust `impl` block that adds methods & behavior to a type | Rust |
|
|
146
|
+
| `FILE` | One source file — the root node the rest of that file attaches to | every language |
|
|
147
|
+
| `ROUTE` | An application route — a URL the app serves, with the handler behind it | every language |
|
|
148
|
+
| `TEST` | A test file (`.test.tsx`, `_test.go`, `test_*.py`, …) | every language |
|
|
149
|
+
| `STORY` | A Storybook story file | TS/JS |
|
|
150
|
+
| `GHOST` | An invisible "event" node that ties event emitters to listeners via `EMITS` / `LISTENS` | TS/JS event graph |
|
|
151
|
+
| `THIRD_PARTY` | An external package/dependency — shown but not parsed | every language |
|
|
152
|
+
| `MODULE` / `PACKAGE` | Namespace / package node | reserved — not emitted yet |
|
|
153
|
+
|
|
154
|
+
**Edge types** — the arrows between nodes
|
|
155
|
+
|
|
156
|
+
| Edge | What it means | Where you'll see it |
|
|
157
|
+
| :-- | :-- | :-- |
|
|
158
|
+
| `CALLS` | A calls B — one function/method invokes another | all languages |
|
|
159
|
+
| `IMPORTS` | A file imports another file or package | all languages |
|
|
160
|
+
| `READS_FROM` | A reads data from B — a state store, model, or DB query (`User.objects.get`, `session.query(User)`, `db.Find`) | state + data layers (TS/JS, Python, Java, Go ORM) |
|
|
161
|
+
| `WRITES_TO` | A writes/updates data in B (`store.set(...)`, `user.save()`, `db.Create(...)`) | state + data layers (same as above) |
|
|
162
|
+
| `PROP_PASS` | A React prop flows from a parent component to a child | TS/JS |
|
|
163
|
+
| `EMITS` | A emits an event | TS/JS event graph |
|
|
164
|
+
| `LISTENS` | A subscribes to an event | TS/JS event graph |
|
|
165
|
+
| `WRAPPED_BY` | A is wrapped by B (e.g. a context provider wraps its consumers) | TS/JS |
|
|
166
|
+
| `GUARDS` | A guards B — a route guard / middleware protecting a route or handler | route layers |
|
|
167
|
+
| `HANDLES` | A route is handled by a handler — the controller/viewset/function behind a URL | all languages' routes |
|
|
168
|
+
| `TESTS` | A test file verifies the code it points to | all languages |
|
|
169
|
+
| `USES` | A JSX component uses an external function/hook internally | TS/JS |
|
|
170
|
+
| `NEXTJS_API_CALL` | A component fetches a Next.js API route | TS/JS Next.js |
|
|
171
|
+
| `NAVIGATES_TO` | Client-side navigation points to a route | TS/JS |
|
|
172
|
+
| `IMPLEMENTS` | A type implements a contract — class implements an interface, Go struct implements an interface, Rust type implements a trait | Java, Go, Rust, Python (ABC/Protocol) |
|
|
173
|
+
| `EXTENDS` | A inherits / embeds B — class extends a base class, Go struct embeds another, Rust supertrait | class-based languages |
|
|
174
|
+
| `EXPORTS` | reserved — declared, not yet emitted | — |
|
|
175
|
+
| `THROWS` | reserved — declared, not yet emitted | — |
|
|
125
176
|
|
|
126
177
|
Each node carries: **importance score** + **functional summary** + **technical summary** + **security assessment** (severity + notes).
|
|
127
178
|
|
|
@@ -236,9 +287,10 @@ A custom-model entry is always available — essential for OpenRouter's huge cat
|
|
|
236
287
|
src/
|
|
237
288
|
├── fingerprint/ # Detect framework, language, router, state, data layer
|
|
238
289
|
├── filesystem/ # Route detection (Next.js, React Router, Express, etc.)
|
|
239
|
-
├── parser/ # AST extraction → nodes (ts-morph)
|
|
290
|
+
├── parser/ # AST extraction → nodes (ts-morph) — the inline JS/TS extractor
|
|
291
|
+
├── extractors/ # Extractor registry, language detection, subprocess runner
|
|
240
292
|
├── graph/ # Edge detectors, traversal API, lookup maps
|
|
241
|
-
├── scoring/ # Multi-pass importance scoring + noise filtering
|
|
293
|
+
├── scoring/ # Multi-pass importance scoring + noise filtering + pruning
|
|
242
294
|
├── clustering/ # Cohesive cluster computation
|
|
243
295
|
├── summarizer/ # LLM summarization pipeline, prompts, checkpoints
|
|
244
296
|
│ └── providers/ # Generic OpenAI & Anthropic clients, model discovery
|
|
@@ -247,7 +299,13 @@ src/
|
|
|
247
299
|
├── storage/ # File-based graph persistence (~/.devlens)
|
|
248
300
|
├── config/ # Provider config resolution (types, catalog, writer, env)
|
|
249
301
|
├── server/ # HTTP API server (consumed by Web UI)
|
|
250
|
-
└── debug/ # Export and
|
|
302
|
+
└── debug/ # Export and validation utilities
|
|
303
|
+
|
|
304
|
+
extractors/ # Native subprocess extractors (not part of the TS build)
|
|
305
|
+
├── python/ # Python extractor (stdlib `ast`, pip package, auto-venv)
|
|
306
|
+
├── java/ # Java extractor (JavaParser, prebuilt fat jar)
|
|
307
|
+
├── go/ # Go extractor (go/ast + go/types, prebuilt static binary)
|
|
308
|
+
└── rust/ # Rust extractor (syn, prebuilt static binary)
|
|
251
309
|
```
|
|
252
310
|
|
|
253
311
|
---
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Detects the primary langauge of a repo by checking for the manifest files
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export function detectLanguage(repoPath) {
|
|
5
|
+
// first for JS/TS, check for package.json first then use tsconfig.json to distinguish between JS and TS
|
|
6
|
+
if (fs.existsSync(`${repoPath}/package.json`)) {
|
|
7
|
+
if (fs.existsSync(`${repoPath}/tsconfig.json`)) {
|
|
8
|
+
return "typescript";
|
|
9
|
+
}
|
|
10
|
+
return "javascript";
|
|
11
|
+
}
|
|
12
|
+
// secondly lets check for python.
|
|
13
|
+
if (fs.existsSync(path.join(repoPath, "requirements.txt")) ||
|
|
14
|
+
fs.existsSync(path.join(repoPath, "pyproject.toml")) ||
|
|
15
|
+
fs.existsSync(path.join(repoPath, "setup.py"))) {
|
|
16
|
+
return "python";
|
|
17
|
+
}
|
|
18
|
+
// Go
|
|
19
|
+
if (fs.existsSync(path.join(repoPath, "go.mod"))) {
|
|
20
|
+
return "go";
|
|
21
|
+
}
|
|
22
|
+
// Rust
|
|
23
|
+
if (fs.existsSync(path.join(repoPath, "Cargo.toml"))) {
|
|
24
|
+
return "rust";
|
|
25
|
+
}
|
|
26
|
+
// Java
|
|
27
|
+
if (fs.existsSync(path.join(repoPath, "pom.xml")) ||
|
|
28
|
+
fs.existsSync(path.join(repoPath, "build.gradle")) ||
|
|
29
|
+
fs.existsSync(path.join(repoPath, "build.gradle.kts"))) {
|
|
30
|
+
return "java";
|
|
31
|
+
}
|
|
32
|
+
return "unknown";
|
|
33
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Language } from "../types.js";
|
|
2
|
+
import { ExtractorResult, LanguageExtractor } from "./types.js";
|
|
3
|
+
export declare function defaultParseResult(stdout: string): ExtractorResult;
|
|
4
|
+
export declare function getExtractor(language: Language): LanguageExtractor | undefined;
|
|
5
|
+
export declare const INLINE_LANGUAGES: Set<Language>;
|
|
6
|
+
export declare function commandExists(command: string): boolean;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/extractors/index.ts
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
// runner.ts is the main file entry point for the extractors. It contains the runExtractor() function which is called by the pipeline.
|
|
6
|
+
// Extractor registry - maps langauge to their extractor config.
|
|
7
|
+
// JS/TS is NOT here (it will be handled inline by the runner.ts)
|
|
8
|
+
// All subprocesses extractors must return the JSON (ExtractorResults) format
|
|
9
|
+
export function defaultParseResult(stdout) {
|
|
10
|
+
return JSON.parse(stdout);
|
|
11
|
+
}
|
|
12
|
+
// Subprocess Extractor Registry
|
|
13
|
+
// These extractors are spawned as child processes.
|
|
14
|
+
const SUBPROCESS_EXTRACTORS = {
|
|
15
|
+
python: {
|
|
16
|
+
language: "python",
|
|
17
|
+
// Absolute venv python when the postinstall created one (works from
|
|
18
|
+
// node_modules); fall back to PATH python3 in dev/other setups.
|
|
19
|
+
command: resolvePythonCommand(),
|
|
20
|
+
args: ["-m", "devlens_extractors_python"],
|
|
21
|
+
parseResult: defaultParseResult,
|
|
22
|
+
},
|
|
23
|
+
java: {
|
|
24
|
+
language: "java",
|
|
25
|
+
command: "java",
|
|
26
|
+
// Absolute path — the runner spawns with cwd=repoPath, so a bare jar
|
|
27
|
+
// name would be looked up inside the analyzed repo. fileURLToPath
|
|
28
|
+
// also decodes percent-escapes (spaces in the path).
|
|
29
|
+
args: ["-jar", fileURLToPath(new URL("../../extractors/java/devlens_java_extractor.jar", import.meta.url))],
|
|
30
|
+
parseResult: defaultParseResult,
|
|
31
|
+
},
|
|
32
|
+
go: {
|
|
33
|
+
language: "go",
|
|
34
|
+
// Absolute per-platform static binary path — the runner spawns with
|
|
35
|
+
// cwd=repoPath, so a bare name would be looked up inside the analyzed
|
|
36
|
+
// repo. Cross-compiled at publish time by prepack → build.mjs.
|
|
37
|
+
command: resolveGoBinaryPath(),
|
|
38
|
+
args: [],
|
|
39
|
+
parseResult: defaultParseResult,
|
|
40
|
+
},
|
|
41
|
+
rust: {
|
|
42
|
+
language: "rust",
|
|
43
|
+
// Absolute per-platform static binary path — the runner spawns with
|
|
44
|
+
// cwd=repoPath, so a bare name would be looked up inside the analyzed
|
|
45
|
+
// repo. Cross-compiled at publish time by prepack → build.mjs.
|
|
46
|
+
command: resolveRustBinaryPath(),
|
|
47
|
+
args: [],
|
|
48
|
+
parseResult: defaultParseResult,
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
// Public API to get the extractor config
|
|
52
|
+
export function getExtractor(language) {
|
|
53
|
+
return SUBPROCESS_EXTRACTORS[language];
|
|
54
|
+
}
|
|
55
|
+
// The python extractor is a pip package installed into a venv that the
|
|
56
|
+
// postinstall script creates (extractors/python/setup.mjs). Resolve that
|
|
57
|
+
// venv's interpreter absolutely — same pattern as the java jar path —
|
|
58
|
+
// because the runner spawns with cwd=repoPath. Platform-aware: Windows
|
|
59
|
+
// venvs put the interpreter at Scripts\python.exe, Unix at bin/python.
|
|
60
|
+
function resolvePythonCommand() {
|
|
61
|
+
const venvBin = process.platform === "win32" ? "Scripts/python.exe" : "bin/python";
|
|
62
|
+
const venvPython = fileURLToPath(new URL(`../../extractors/python/.venv/${venvBin}`, import.meta.url));
|
|
63
|
+
return fs.existsSync(venvPython) ? venvPython : "python3";
|
|
64
|
+
}
|
|
65
|
+
// The Go extractor ships as a static binary per platform, cross-compiled at
|
|
66
|
+
// publish time (prepack → extractors/go/build.mjs). Resolve the CURRENT
|
|
67
|
+
// platform's binary absolutely — same reason as the java jar path (the runner
|
|
68
|
+
// spawns with cwd=repoPath). fileURLToPath also decodes percent-escapes
|
|
69
|
+
// (spaces in the path).
|
|
70
|
+
function resolveGoBinaryPath() {
|
|
71
|
+
const platformDir = process.platform === "win32"
|
|
72
|
+
? "windows-amd64"
|
|
73
|
+
: process.platform === "darwin"
|
|
74
|
+
? `darwin-${process.arch === "arm64" ? "arm64" : "amd64"}`
|
|
75
|
+
: `linux-${process.arch === "arm64" ? "arm64" : "amd64"}`;
|
|
76
|
+
const exe = process.platform === "win32" ? ".exe" : "";
|
|
77
|
+
return fileURLToPath(new URL(`../../extractors/go/bin/${platformDir}/devlens_go_extractor${exe}`, import.meta.url));
|
|
78
|
+
}
|
|
79
|
+
// The Rust extractor ships as a static binary per platform, cross-compiled at
|
|
80
|
+
// publish time (prepack → extractors/rust/build.mjs). Resolve the CURRENT
|
|
81
|
+
// platform's binary absolutely — same reason as the go binary path (the runner
|
|
82
|
+
// spawns with cwd=repoPath). fileURLToPath also decodes percent-escapes
|
|
83
|
+
// (spaces in the path).
|
|
84
|
+
function resolveRustBinaryPath() {
|
|
85
|
+
const platformDir = process.platform === "win32"
|
|
86
|
+
? "windows-amd64"
|
|
87
|
+
: process.platform === "darwin"
|
|
88
|
+
? `darwin-${process.arch === "arm64" ? "arm64" : "amd64"}`
|
|
89
|
+
: `linux-${process.arch === "arm64" ? "arm64" : "amd64"}`;
|
|
90
|
+
const exe = process.platform === "win32" ? ".exe" : "";
|
|
91
|
+
return fileURLToPath(new URL(`../../extractors/rust/bin/${platformDir}/devlens_rust_extractor${exe}`, import.meta.url));
|
|
92
|
+
}
|
|
93
|
+
// langauges handled inline meaning JS/TS
|
|
94
|
+
export const INLINE_LANGUAGES = new Set(["javascript", "typescript"]);
|
|
95
|
+
// Does `command` resolve? Absolute path → existsSync; bare name → PATH scan
|
|
96
|
+
// (cross-platform: PATHEXT on win32).
|
|
97
|
+
export function commandExists(command) {
|
|
98
|
+
if (command.includes("/") || command.includes("\\")) {
|
|
99
|
+
return fs.existsSync(command);
|
|
100
|
+
}
|
|
101
|
+
const pathEnv = process.env.PATH || "";
|
|
102
|
+
const exts = process.platform === "win32"
|
|
103
|
+
? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").toLowerCase().split(";")
|
|
104
|
+
: [""];
|
|
105
|
+
for (const dir of pathEnv.split(process.platform === "win32" ? ";" : ":")) {
|
|
106
|
+
if (!dir)
|
|
107
|
+
continue;
|
|
108
|
+
for (const ext of exts) {
|
|
109
|
+
if (fs.existsSync(path.join(dir, command + ext))
|
|
110
|
+
|| fs.existsSync(path.join(dir, command.toUpperCase() + ext))) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ExtractorInput, ExtractorResult, LanguageExtractor } from "./types.js";
|
|
2
|
+
export declare function runSubprocessExtractor(extractor: LanguageExtractor, input: ExtractorInput, timeoutMs?: number): Promise<ExtractorResult>;
|
|
3
|
+
export declare function runInlineExtractor(input: ExtractorInput, onStep?: (step: "fingerprint" | "filesystem" | "parse" | "edges" | "scoring") => void): Promise<ExtractorResult>;
|
|
4
|
+
export declare function runExtractor(repoPath: string, options?: {
|
|
5
|
+
includeThirdPartyLibs?: string[];
|
|
6
|
+
onStep?: (step: "fingerprint" | "filesystem" | "parse" | "edges" | "scoring") => void;
|
|
7
|
+
}): Promise<ExtractorResult>;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// src/extractors/runner.ts
|
|
2
|
+
//
|
|
3
|
+
// The Runner — two execution modes:
|
|
4
|
+
// 1. Subprocess: spawns Python/Java/Go/Rust extractor as child process
|
|
5
|
+
// 2. Inline: calls existing ts-morph code directly (no subprocess)
|
|
6
|
+
//
|
|
7
|
+
// The pipeline calls runExtractor() which auto-detects language and routes.
|
|
8
|
+
// This is basically the entry point for the extractor execution.
|
|
9
|
+
/*
|
|
10
|
+
What Actually Happens (Timeline)
|
|
11
|
+
spawn() creates the child process and returns immediately. The child is now running but waiting for input on stdin. Meanwhile, your Node.js code continues executing line by line.
|
|
12
|
+
|
|
13
|
+
Here's the real execution order:
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
TIME 0ms: spawn("python3", ["-m", "devlens_extractors_python"])
|
|
17
|
+
→ child process starts, waits for stdin input
|
|
18
|
+
|
|
19
|
+
TIME 1ms: child.stdout.on("data", ...) ← registers callback (doesn't run yet)
|
|
20
|
+
TIME 2ms: child.stderr.on("data", ...) ← registers callback (doesn't run yet)
|
|
21
|
+
TIME 3ms: setTimeout(...) ← registers timer (doesn't fire yet)
|
|
22
|
+
TIME 4ms: child.on("error", ...) ← registers callback
|
|
23
|
+
TIME 5ms: child.on("close", ...) ← registers callback
|
|
24
|
+
|
|
25
|
+
TIME 6ms: child.stdin.write(JSON.stringify(input)) ← NOW we send the repoPath
|
|
26
|
+
TIME 7ms: child.stdin.end() ← tells extractor "done sending"
|
|
27
|
+
|
|
28
|
+
← runSubprocessExtractor returns the Promise here
|
|
29
|
+
← Node.js moves on to other work
|
|
30
|
+
|
|
31
|
+
... child process is running, parsing the repo ...
|
|
32
|
+
|
|
33
|
+
TIME 5000ms: child finishes, writes JSON to stdout
|
|
34
|
+
→ "data" handler fires, collects stdout
|
|
35
|
+
|
|
36
|
+
TIME 5001ms: child exits with code 0
|
|
37
|
+
→ "close" handler fires
|
|
38
|
+
→ parseResult(stdout) runs
|
|
39
|
+
→ resolve(result) — the Promise resolves
|
|
40
|
+
*/
|
|
41
|
+
import { spawn } from "child_process";
|
|
42
|
+
import fs from "fs";
|
|
43
|
+
import path from "path";
|
|
44
|
+
import { analyzeFingerprint } from "../fingerprint/index.js";
|
|
45
|
+
import { analyzeFilesystem } from "../filesystem/index.js";
|
|
46
|
+
import { routesToCodeNodes } from "../pipeline/index.js";
|
|
47
|
+
import { parseRepo } from "../parser/index.js";
|
|
48
|
+
import { buildThirdPartyNodes } from "../graph/thirdPartyLibs.js";
|
|
49
|
+
import { detectEdges } from "../graph/index.js";
|
|
50
|
+
import { detectLanguage } from "./detectLanguage.js";
|
|
51
|
+
import { getExtractor, INLINE_LANGUAGES, commandExists } from "./index.js";
|
|
52
|
+
// 1. Subprocess Extractor
|
|
53
|
+
// What it does: It will start a process for the given extractor, send it the input json, and wait for the output json. if the process times out, it will kill the process and return error result. If the process exits with non-zero code, it will return error result. If the process exits with zero code, it will parse the output json and return the result.
|
|
54
|
+
export async function runSubprocessExtractor(extractor, input, timeoutMs = 10 * 60 * 1000) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
// Friendly guard for artifact-based extractors (java -jar ...):
|
|
57
|
+
// the jar path is resolved from the package location, not repoPath.
|
|
58
|
+
const jarArg = extractor.args.find((a) => a.endsWith(".jar"));
|
|
59
|
+
if (jarArg && !fs.existsSync(jarArg)) {
|
|
60
|
+
reject(new Error(`${extractor.language} extractor artifact not found: ${jarArg}. ` +
|
|
61
|
+
`Build it first: node extractors/${extractor.language}/build.mjs`));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Runtime prerequisites on the installing machine — friendly errors
|
|
65
|
+
// instead of a raw spawn ENOENT.
|
|
66
|
+
if (extractor.language === "java" && !commandExists("java")) {
|
|
67
|
+
reject(new Error("java extractor requires a Java 17+ runtime (JVM) on PATH — " +
|
|
68
|
+
"install a JDK (e.g. Adoptium Temurin) and retry."));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (extractor.language === "python" && !commandExists(extractor.command)) {
|
|
72
|
+
reject(new Error("python extractor unavailable: no Python 3.11+ found. Install Python, " +
|
|
73
|
+
"or bootstrap the extractor venv: node extractors/python/setup.mjs"));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (extractor.language === "go" && !fs.existsSync(extractor.command)) {
|
|
77
|
+
reject(new Error(`go extractor binary not found: ${extractor.command}. ` +
|
|
78
|
+
`Build it first: node extractors/go/build.mjs (requires the Go toolchain)`));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (extractor.language === "rust" && !fs.existsSync(extractor.command)) {
|
|
82
|
+
reject(new Error(`rust extractor binary not found: ${extractor.command}. ` +
|
|
83
|
+
`Build it first: node extractors/rust/build.mjs (requires the Rust toolchain)`));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const child = spawn(extractor.command, extractor.args, { stdio: ["pipe", "pipe", "pipe"], cwd: input.repoPath });
|
|
87
|
+
let stdout = "";
|
|
88
|
+
let stderr = "";
|
|
89
|
+
child.stdout.on("data", (data) => {
|
|
90
|
+
stdout += data.toString();
|
|
91
|
+
});
|
|
92
|
+
child.stderr.on("data", (data) => {
|
|
93
|
+
stderr += data.toString();
|
|
94
|
+
});
|
|
95
|
+
const timer = setTimeout(() => {
|
|
96
|
+
child.kill("SIGTERM");
|
|
97
|
+
reject(new Error(`${extractor.language} extractor timed out after ${timeoutMs}ms`));
|
|
98
|
+
}, timeoutMs);
|
|
99
|
+
child.on("close", (code) => {
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
if (code !== 0) {
|
|
102
|
+
// Log stderr for debugging but don't crash — return error result
|
|
103
|
+
reject(new Error(`${extractor.language} extractor exited with code ${code}.\n${stderr}`));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const result = extractor.parseResult(stdout);
|
|
108
|
+
resolve(result);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
reject(new Error(`Failed to parse ${extractor.language} extractor output: ` +
|
|
112
|
+
`${err instanceof Error ? err.message : String(err)}`));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
// Send input as JSON on stdin, then close stdin to signal "done sending"
|
|
116
|
+
child.stdin.write(JSON.stringify(input));
|
|
117
|
+
child.stdin.end();
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
;
|
|
121
|
+
// 2. Inline Extractor
|
|
122
|
+
// calls existing ts-morph code directly (no subprocess). This is used for TypeScript/JavaScript projects. It returns the result directly without spawning a child process.
|
|
123
|
+
// This replicates steps 1-6 of the current analyzePipeline and wraps them
|
|
124
|
+
// into an ExtractorResult.
|
|
125
|
+
export async function runInlineExtractor(input, onStep) {
|
|
126
|
+
const { repoPath, options } = input;
|
|
127
|
+
const absoluteRepoPath = path.resolve(repoPath);
|
|
128
|
+
// Step 1: fingerprint
|
|
129
|
+
onStep?.("fingerprint");
|
|
130
|
+
const fingerprint = analyzeFingerprint(absoluteRepoPath);
|
|
131
|
+
// Step 2: Filesystem / routes
|
|
132
|
+
onStep?.("filesystem");
|
|
133
|
+
const routes = analyzeFilesystem(absoluteRepoPath, fingerprint);
|
|
134
|
+
// Step 3: Convert routes -> CodeNodes (so that they can join the graph)
|
|
135
|
+
// (no onStep here — it's part of the filesystem step)
|
|
136
|
+
let routeNodes = routesToCodeNodes(routes, absoluteRepoPath);
|
|
137
|
+
// Step 4: Parse source files into nodes
|
|
138
|
+
onStep?.("parse");
|
|
139
|
+
const parserResult = parseRepo(absoluteRepoPath);
|
|
140
|
+
// Step 5: Build Third party nodes (if options is provided)
|
|
141
|
+
const thirdPartyNodes = buildThirdPartyNodes(absoluteRepoPath, options.includeThirdPartyLibs || []);
|
|
142
|
+
//Step 6: We have all the Nodes, now build the edges.
|
|
143
|
+
onStep?.("edges");
|
|
144
|
+
const edgeResult = detectEdges([...parserResult.nodes, ...routeNodes, ...thirdPartyNodes], routes, absoluteRepoPath, fingerprint);
|
|
145
|
+
// Step 7: Filter API route nodes without handlers (JS-specific cleanup)
|
|
146
|
+
routeNodes = routeNodes.filter(routeNode => {
|
|
147
|
+
if (routeNode.metadata.routeNodeType === "API_ROUTE") {
|
|
148
|
+
return edgeResult.edges.some((edge) => edge.type === "HANDLES" && edge.from === routeNode.id);
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
});
|
|
152
|
+
// Step 8: Assemble final nodes and edges
|
|
153
|
+
const allNodes = [
|
|
154
|
+
...parserResult.nodes,
|
|
155
|
+
...routeNodes,
|
|
156
|
+
...thirdPartyNodes,
|
|
157
|
+
...edgeResult.ghostNodes
|
|
158
|
+
];
|
|
159
|
+
const allEdges = edgeResult.edges;
|
|
160
|
+
const stats = {
|
|
161
|
+
totalFiles: parserResult.stats.totalFiles,
|
|
162
|
+
totalNodes: allNodes.length,
|
|
163
|
+
skippedFiles: parserResult.stats.skippedFiles,
|
|
164
|
+
};
|
|
165
|
+
return {
|
|
166
|
+
fingerprint,
|
|
167
|
+
nodes: allNodes,
|
|
168
|
+
edges: allEdges,
|
|
169
|
+
routes,
|
|
170
|
+
stats,
|
|
171
|
+
errors: [],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// 3. Dispatch - auto detect other langauges (apart from JS/TS) and routes
|
|
175
|
+
export async function runExtractor(repoPath, options) {
|
|
176
|
+
const language = detectLanguage(repoPath);
|
|
177
|
+
const input = {
|
|
178
|
+
repoPath,
|
|
179
|
+
options: {
|
|
180
|
+
includeThirdPartyLibs: options?.includeThirdPartyLibs || []
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
if (INLINE_LANGUAGES.has(language)) {
|
|
184
|
+
return runInlineExtractor(input, options?.onStep);
|
|
185
|
+
}
|
|
186
|
+
// For other languages (python, Go, Rust, Java), we need to spawn a subprocess
|
|
187
|
+
const extractor = getExtractor(language);
|
|
188
|
+
if (!extractor) {
|
|
189
|
+
throw new Error(`No extractor registered for language: "${language}". ` +
|
|
190
|
+
`This language may not be supported yet.`);
|
|
191
|
+
}
|
|
192
|
+
;
|
|
193
|
+
return runSubprocessExtractor(extractor, input);
|
|
194
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Language, BackendRouteNode, CodeEdge, CodeNode, ProjectFingerprint, RouteNode } from "../types.js";
|
|
2
|
+
export interface ExtractorOptions {
|
|
3
|
+
includeThirdPartyLibs?: string[];
|
|
4
|
+
}
|
|
5
|
+
export interface ExtractorInput {
|
|
6
|
+
repoPath: string;
|
|
7
|
+
options: ExtractorOptions;
|
|
8
|
+
}
|
|
9
|
+
export interface ExtractorStats {
|
|
10
|
+
totalFiles: number;
|
|
11
|
+
totalNodes: number;
|
|
12
|
+
skippedFiles: number;
|
|
13
|
+
}
|
|
14
|
+
export interface ExtractorError {
|
|
15
|
+
file: string;
|
|
16
|
+
error: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ExtractorResult {
|
|
19
|
+
fingerprint: ProjectFingerprint;
|
|
20
|
+
nodes: CodeNode[];
|
|
21
|
+
edges: CodeEdge[];
|
|
22
|
+
routes?: (RouteNode | BackendRouteNode)[];
|
|
23
|
+
stats: ExtractorStats;
|
|
24
|
+
errors: ExtractorError[];
|
|
25
|
+
}
|
|
26
|
+
export interface LanguageExtractor {
|
|
27
|
+
language: Language;
|
|
28
|
+
command: string;
|
|
29
|
+
args: string[];
|
|
30
|
+
/**
|
|
31
|
+
* Parse the raw stdout from the extractor process into ExtractorResult.
|
|
32
|
+
* The orchestrator calls this after the process exits successfully.
|
|
33
|
+
*
|
|
34
|
+
* Throwing here marks the extraction as failed.
|
|
35
|
+
*/
|
|
36
|
+
parseResult: (stdout: string) => ExtractorResult;
|
|
37
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// src/extractors/types.ts
|
|
2
|
+
//
|
|
3
|
+
// The Extractor Contract — defines the interface between the Node.js
|
|
4
|
+
// orchestrator and the native-language extractors (Python, Java, Go, Rust).
|
|
5
|
+
//
|
|
6
|
+
// Communication is JSON over stdin/stdout. See expansion-tracker/contract.html
|
|
7
|
+
// for the full spec.
|
|
8
|
+
export {};
|
|
@@ -6,5 +6,6 @@ export interface LookupMaps {
|
|
|
6
6
|
storeNodes: CodeNode[];
|
|
7
7
|
thirdPartyNodesByName: Map<string, CodeNode>;
|
|
8
8
|
thirdPartyImportAliases: Map<string, Map<string, string>>;
|
|
9
|
+
localImportSymbols: Map<string, Map<string, string>>;
|
|
9
10
|
}
|
|
10
11
|
export declare function buildLookupMaps(codeNodes: CodeNode[]): LookupMaps;
|
|
@@ -5,6 +5,7 @@ export function buildLookupMaps(codeNodes) {
|
|
|
5
5
|
const storeNodes = [];
|
|
6
6
|
const thirdPartyNodesByName = new Map();
|
|
7
7
|
const thirdPartyImportAliases = new Map();
|
|
8
|
+
const localImportSymbols = new Map();
|
|
8
9
|
for (const node of codeNodes) {
|
|
9
10
|
if (node.type === "THIRD_PARTY") {
|
|
10
11
|
thirdPartyNodesByName.set(node.name, node);
|
|
@@ -28,5 +29,5 @@ export function buildLookupMaps(codeNodes) {
|
|
|
28
29
|
storeNodes.push(node);
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
|
-
return { nodesByName, nodesByFile, fileNodesByPath, storeNodes, thirdPartyNodesByName, thirdPartyImportAliases };
|
|
32
|
+
return { nodesByName, nodesByFile, fileNodesByPath, storeNodes, thirdPartyNodesByName, thirdPartyImportAliases, localImportSymbols };
|
|
32
33
|
}
|