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,178 @@
|
|
|
1
|
+
// typeload.go — go/types type-checking, fully self-contained.
|
|
2
|
+
//
|
|
3
|
+
// REVISED from the plan: go/packages is NOT stdlib (it lives in
|
|
4
|
+
// golang.org/x/tools) and would drag in a build-time network dep + a runtime
|
|
5
|
+
// dependency on the `go` command. Instead we type-check the module's own
|
|
6
|
+
// packages in-process:
|
|
7
|
+
//
|
|
8
|
+
// - local packages: checked in dependency order (Go import graphs are
|
|
9
|
+
// acyclic) from the already-parsed ASTs
|
|
10
|
+
// - std packages: importer.Default() (compiled-in export data; degrades to
|
|
11
|
+
// import errors on machines without a GOROOT — harmless, errors ignored)
|
|
12
|
+
// - external packages: import error → unresolved (degraded) — calls to them
|
|
13
|
+
// resolve via name-based [mod] lazy edges anyway
|
|
14
|
+
//
|
|
15
|
+
// All checker errors are swallowed (we're type-RESOLVING, not verifying).
|
|
16
|
+
// The result is per-file *types.Info: Selections/Uses give exact local call
|
|
17
|
+
// targets, and package scopes give exact type objects for IMPLEMENTS.
|
|
18
|
+
//
|
|
19
|
+
// The binary stays static and offline — no `go` command needed at runtime.
|
|
20
|
+
|
|
21
|
+
package main
|
|
22
|
+
|
|
23
|
+
import (
|
|
24
|
+
"go/ast"
|
|
25
|
+
"go/importer"
|
|
26
|
+
"go/token"
|
|
27
|
+
"go/types"
|
|
28
|
+
"sort"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
type TypeInfo struct {
|
|
32
|
+
Mode string // "typed" — informational
|
|
33
|
+
Infos map[string]*types.Info // rel file path → type info for that file
|
|
34
|
+
Pkgs map[string]*types.Package // import path → checked package
|
|
35
|
+
Checks int
|
|
36
|
+
Failures int
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// loadTypes — type-check every package in dependency order.
|
|
40
|
+
func loadTypes(pr *parsedRepo, addError errFn) *TypeInfo {
|
|
41
|
+
ti := &TypeInfo{
|
|
42
|
+
Mode: "typed",
|
|
43
|
+
Infos: map[string]*types.Info{},
|
|
44
|
+
Pkgs: map[string]*types.Package{},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check units: a package's own files (non-test + same-package test files)
|
|
48
|
+
// and — for *_test.go files declaring a DIFFERENT package name
|
|
49
|
+
// (package foo_test) — a synthetic package with import path <dir>_test.
|
|
50
|
+
type unit struct {
|
|
51
|
+
path string
|
|
52
|
+
files []*ParsedFile
|
|
53
|
+
fset *token.FileSet
|
|
54
|
+
}
|
|
55
|
+
units := []*unit{}
|
|
56
|
+
byPath := map[string]*unit{}
|
|
57
|
+
for _, pkg := range pr.packages {
|
|
58
|
+
own := &unit{path: pkg.ImportPath, fset: pkg.fset}
|
|
59
|
+
ext := &unit{path: pkg.ImportPath + "_test", fset: pkg.fset}
|
|
60
|
+
byPath[own.path] = own
|
|
61
|
+
byPath[ext.path] = ext
|
|
62
|
+
for _, pf := range pkg.Files {
|
|
63
|
+
if pf.IsTest && pf.PkgName != pkg.Name {
|
|
64
|
+
ext.files = append(ext.files, pf)
|
|
65
|
+
} else {
|
|
66
|
+
own.files = append(own.files, pf)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
units = append(units, own, ext)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// internal deps per unit (imports that resolve to local packages)
|
|
73
|
+
depPaths := func(u *unit) []string {
|
|
74
|
+
seen := map[string]bool{}
|
|
75
|
+
var deps []string
|
|
76
|
+
for _, pf := range u.files {
|
|
77
|
+
for _, imp := range pf.Imports {
|
|
78
|
+
if _, ok := byPath[imp.Path]; ok && !seen[imp.Path] {
|
|
79
|
+
seen[imp.Path] = true
|
|
80
|
+
deps = append(deps, imp.Path)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
sort.Strings(deps)
|
|
85
|
+
return deps
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// importer: local packages in-memory; std via importer.Default(); external
|
|
89
|
+
// → error (degraded, fine).
|
|
90
|
+
stdImp := importer.Default()
|
|
91
|
+
imp := importerFunc(func(path string) (*types.Package, error) {
|
|
92
|
+
if p, ok := ti.Pkgs[path]; ok {
|
|
93
|
+
return p, nil
|
|
94
|
+
}
|
|
95
|
+
if _, local := byPath[path]; local {
|
|
96
|
+
return nil, errNotCheckedYet
|
|
97
|
+
}
|
|
98
|
+
return stdImp.Import(path)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// topological order over the internal import graph
|
|
102
|
+
done := map[string]bool{}
|
|
103
|
+
var order []*unit
|
|
104
|
+
for len(order) < len(units) {
|
|
105
|
+
progress := false
|
|
106
|
+
for _, u := range units {
|
|
107
|
+
if done[u.path] || len(u.files) == 0 {
|
|
108
|
+
done[u.path] = true
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
ready := true
|
|
112
|
+
for _, d := range depPaths(u) {
|
|
113
|
+
if !done[d] {
|
|
114
|
+
ready = false
|
|
115
|
+
break
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if ready {
|
|
119
|
+
done[u.path] = true
|
|
120
|
+
order = append(order, u)
|
|
121
|
+
progress = true
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if !progress {
|
|
125
|
+
// cycle / unresolvable internal dep — check what we can
|
|
126
|
+
for _, u := range units {
|
|
127
|
+
if !done[u.path] && len(u.files) > 0 {
|
|
128
|
+
done[u.path] = true
|
|
129
|
+
order = append(order, u)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
break
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for _, u := range order {
|
|
137
|
+
if len(u.files) == 0 {
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
files := make([]*ast.File, 0, len(u.files))
|
|
141
|
+
for _, pf := range u.files {
|
|
142
|
+
files = append(files, pf.AST)
|
|
143
|
+
}
|
|
144
|
+
info := &types.Info{
|
|
145
|
+
Types: map[ast.Expr]types.TypeAndValue{},
|
|
146
|
+
Uses: map[*ast.Ident]types.Object{},
|
|
147
|
+
Selections: map[*ast.SelectorExpr]*types.Selection{},
|
|
148
|
+
Defs: map[*ast.Ident]types.Object{},
|
|
149
|
+
Implicits: map[ast.Node]types.Object{},
|
|
150
|
+
}
|
|
151
|
+
conf := types.Config{
|
|
152
|
+
Importer: imp,
|
|
153
|
+
Error: func(error) {}, // swallow — resolution, not verification
|
|
154
|
+
}
|
|
155
|
+
pkg, _ := conf.Check(u.path, u.fset, files, info)
|
|
156
|
+
if pkg == nil {
|
|
157
|
+
ti.Failures++
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
ti.Pkgs[u.path] = pkg
|
|
161
|
+
ti.Checks++
|
|
162
|
+
for _, pf := range u.files {
|
|
163
|
+
ti.Infos[pf.RelPath] = info
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return ti
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
var errNotCheckedYet = &errNotChecked{}
|
|
170
|
+
|
|
171
|
+
type errNotChecked struct{}
|
|
172
|
+
|
|
173
|
+
func (*errNotChecked) Error() string { return "package not checked yet" }
|
|
174
|
+
|
|
175
|
+
// importerFunc adapts a function to types.Importer.
|
|
176
|
+
type importerFunc func(path string) (*types.Package, error)
|
|
177
|
+
|
|
178
|
+
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// walker.go — file discovery: prune IGNORE dirs at the frontier, collect
|
|
2
|
+
// .go files, group them into packages (one package = one directory).
|
|
3
|
+
|
|
4
|
+
package main
|
|
5
|
+
|
|
6
|
+
import (
|
|
7
|
+
"io/fs"
|
|
8
|
+
"os"
|
|
9
|
+
"path/filepath"
|
|
10
|
+
"sort"
|
|
11
|
+
"strings"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// IGNORE_DIRS — pruned at the frontier (never descended into). vendor/ holds
|
|
15
|
+
// third-party copies (go list resolves types there; we never walk it for
|
|
16
|
+
// nodes). bin/dist/build/out/target are build outputs.
|
|
17
|
+
var ignoreDirs = map[string]bool{
|
|
18
|
+
".git": true, "node_modules": true, "vendor": true, "bin": true,
|
|
19
|
+
"dist": true, "build": true, "out": true, "target": true,
|
|
20
|
+
"coverage": true, ".idea": true, ".vscode": true, ".cache": true,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// goFiles walks repoPath and returns relative paths of all .go files,
|
|
24
|
+
// pruned at the frontier (never descend into pruned dirs).
|
|
25
|
+
func goFiles(repoPath string) []string {
|
|
26
|
+
var files []string
|
|
27
|
+
filepath.WalkDir(repoPath, func(p string, d fs.DirEntry, err error) error {
|
|
28
|
+
if err != nil {
|
|
29
|
+
return nil // unreadable entry — skip silently
|
|
30
|
+
}
|
|
31
|
+
if d.IsDir() {
|
|
32
|
+
base := d.Name()
|
|
33
|
+
if p != repoPath && (ignoreDirs[base] || strings.HasPrefix(base, ".")) {
|
|
34
|
+
return filepath.SkipDir
|
|
35
|
+
}
|
|
36
|
+
return nil
|
|
37
|
+
}
|
|
38
|
+
if strings.HasSuffix(d.Name(), ".go") {
|
|
39
|
+
rel, _ := filepath.Rel(repoPath, p)
|
|
40
|
+
files = append(files, filepath.ToSlash(rel))
|
|
41
|
+
}
|
|
42
|
+
return nil
|
|
43
|
+
})
|
|
44
|
+
sort.Strings(files)
|
|
45
|
+
return files
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// groupByDir → rel dir → []rel file paths (deterministic order).
|
|
49
|
+
func groupByDir(files []string) map[string][]string {
|
|
50
|
+
groups := map[string][]string{}
|
|
51
|
+
for _, f := range files {
|
|
52
|
+
dir := "."
|
|
53
|
+
if i := strings.LastIndex(f, "/"); i >= 0 {
|
|
54
|
+
dir = f[:i]
|
|
55
|
+
}
|
|
56
|
+
groups[dir] = append(groups[dir], f)
|
|
57
|
+
}
|
|
58
|
+
for _, list := range groups {
|
|
59
|
+
sort.Strings(list)
|
|
60
|
+
}
|
|
61
|
+
return groups
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func dirExists(repoPath, rel string) bool {
|
|
65
|
+
info, err := os.Stat(filepath.Join(repoPath, rel))
|
|
66
|
+
return err == nil && info.IsDir()
|
|
67
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// DevLens Java extractor — fat-jar build (cross-platform: Linux/macOS/Windows).
|
|
3
|
+
//
|
|
4
|
+
// Node is guaranteed on every machine that installs an npm package, so this
|
|
5
|
+
// replaces the bash build.sh: downloads JavaParser + Gson from Maven Central,
|
|
6
|
+
// compiles src/, and merges EVERYTHING into one self-contained jar so the
|
|
7
|
+
// engine can run `java -jar devlens_java_extractor.jar` with no classpath.
|
|
8
|
+
//
|
|
9
|
+
// Usage: node build.mjs (idempotent — run from this directory)
|
|
10
|
+
|
|
11
|
+
import { execFileSync } from "node:child_process";
|
|
12
|
+
import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync, readFileSync } from "node:fs";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const VERSIONS = {
|
|
18
|
+
"javaparser-core.jar": "com/github/javaparser/javaparser-core/3.28.2/javaparser-core-3.28.2.jar",
|
|
19
|
+
"javaparser-symbol-solver-core.jar": "com/github/javaparser/javaparser-symbol-solver-core/3.28.2/javaparser-symbol-solver-core-3.28.2.jar",
|
|
20
|
+
"gson.jar": "com/google/code/gson/gson/2.14.0/gson-2.14.0.jar",
|
|
21
|
+
"javassist.jar": "org/javassist/javassist/3.32.0-GA/javassist-3.32.0-GA.jar",
|
|
22
|
+
"guava.jar": "com/google/guava/guava/33.6.0-jre/guava-33.6.0-jre.jar",
|
|
23
|
+
"checker-qual.jar": "org/checkerframework/checker-qual/4.2.2/checker-qual-4.2.2.jar",
|
|
24
|
+
};
|
|
25
|
+
const MAVEN = "https://repo1.maven.org/maven2/";
|
|
26
|
+
|
|
27
|
+
const libDir = join(HERE, "lib");
|
|
28
|
+
const classesDir = join(HERE, "classes");
|
|
29
|
+
mkdirSync(libDir, { recursive: true });
|
|
30
|
+
mkdirSync(classesDir, { recursive: true });
|
|
31
|
+
|
|
32
|
+
function run(cmd, args, opts = {}) {
|
|
33
|
+
execFileSync(cmd, args, { stdio: "inherit", ...opts });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── 1. fetch dependencies (cache on disk; idempotent) ────────────────
|
|
37
|
+
console.log("==> Fetching dependencies (Maven Central)");
|
|
38
|
+
for (const [jar, path] of Object.entries(VERSIONS)) {
|
|
39
|
+
const dest = join(libDir, jar);
|
|
40
|
+
if (existsSync(dest) && readFileSync(dest).length > 0) {
|
|
41
|
+
console.log(` cached ${jar}`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
console.log(` downloading ${jar}`);
|
|
45
|
+
const res = await fetch(MAVEN + path);
|
|
46
|
+
if (!res.ok) throw new Error(`download failed (${res.status}): ${MAVEN + path}`);
|
|
47
|
+
writeFileSync(dest, Buffer.from(await res.arrayBuffer()));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── 2. compile ───────────────────────────────────────────────────────
|
|
51
|
+
console.log("==> Compiling");
|
|
52
|
+
const sources = [];
|
|
53
|
+
function collect(dir) {
|
|
54
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
55
|
+
const p = join(dir, entry.name);
|
|
56
|
+
if (entry.isDirectory()) collect(p);
|
|
57
|
+
else if (entry.name.endsWith(".java")) sources.push(p);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
collect(join(HERE, "src"));
|
|
61
|
+
run(process.env.JAVAC || "javac",
|
|
62
|
+
["--release", "17", "-cp", join(libDir, "*"), "-d", classesDir, ...sources]);
|
|
63
|
+
|
|
64
|
+
// ── 3. assemble fat jar ──────────────────────────────────────────────
|
|
65
|
+
console.log("==> Assembling fat jar");
|
|
66
|
+
const fatDir = join(HERE, "fat");
|
|
67
|
+
rmSync(fatDir, { recursive: true, force: true });
|
|
68
|
+
mkdirSync(fatDir, { recursive: true });
|
|
69
|
+
for (const jar of readdirSync(libDir).filter((f) => f.endsWith(".jar"))) {
|
|
70
|
+
run("jar", ["xf", join(libDir, jar)], { cwd: fatDir });
|
|
71
|
+
}
|
|
72
|
+
// strip other jars' META-INF signatures + multi-release leftovers are fine
|
|
73
|
+
for (const sig of [".SF", ".DSA", ".RSA"]) {
|
|
74
|
+
for (const f of readdirSync(join(fatDir, "META-INF"))) {
|
|
75
|
+
if (f.endsWith(sig)) rmSync(join(fatDir, "META-INF", f));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const entry of readdirSync(classesDir)) {
|
|
79
|
+
run(process.platform === "win32" ? "xcopy" : "cp",
|
|
80
|
+
process.platform === "win32"
|
|
81
|
+
? [join(classesDir, entry), join(fatDir, entry), "/E", "/Y"]
|
|
82
|
+
: ["-r", join(classesDir, entry), fatDir],
|
|
83
|
+
process.platform === "win32" ? { shell: true } : {});
|
|
84
|
+
}
|
|
85
|
+
run("jar", ["cfe", join(HERE, "devlens_java_extractor.jar"),
|
|
86
|
+
"devlens.extractor.Main", "-C", fatDir, "."]);
|
|
87
|
+
rmSync(fatDir, { recursive: true, force: true });
|
|
88
|
+
|
|
89
|
+
const size = (readFileSync(join(HERE, "devlens_java_extractor.jar")).length / 1e6).toFixed(1);
|
|
90
|
+
console.log(`==> Done: ${size}M devlens_java_extractor.jar`);
|
|
Binary file
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
package devlens.extractor;
|
|
2
|
+
|
|
3
|
+
import java.nio.charset.StandardCharsets;
|
|
4
|
+
import java.security.MessageDigest;
|
|
5
|
+
import java.util.ArrayList;
|
|
6
|
+
import java.util.LinkedHashMap;
|
|
7
|
+
import java.util.List;
|
|
8
|
+
import java.util.Map;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Contract — the exact JSON shape DevLens Engine expects (mirrors
|
|
12
|
+
* contract.py from the Python extractor). Every key here is camelCase to
|
|
13
|
+
* match the TypeScript interfaces (CodeNode / CodeEdge / BackendRouteNode).
|
|
14
|
+
* This class is the single source of truth for node/edge shapes; other
|
|
15
|
+
* modules never spell keys by hand.
|
|
16
|
+
*/
|
|
17
|
+
public final class Contract {
|
|
18
|
+
|
|
19
|
+
private Contract() {}
|
|
20
|
+
|
|
21
|
+
/** SHA-256 of the raw code, first 16 hex chars (mirrors contract.py). */
|
|
22
|
+
public static String codeHash(String rawCode) {
|
|
23
|
+
try {
|
|
24
|
+
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
|
25
|
+
byte[] digest = md.digest(rawCode.getBytes(StandardCharsets.UTF_8));
|
|
26
|
+
StringBuilder sb = new StringBuilder(16);
|
|
27
|
+
for (int i = 0; i < 8; i++) {
|
|
28
|
+
sb.append(String.format("%02x", digest[i]));
|
|
29
|
+
}
|
|
30
|
+
return sb.toString();
|
|
31
|
+
} catch (Exception e) {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** FILE / TEST node — id format: file::rel/path/File.java. */
|
|
37
|
+
public static Map<String, Object> fileNode(String relPath, int endLine,
|
|
38
|
+
String nodeType, String language) {
|
|
39
|
+
Map<String, Object> metadata = new LinkedHashMap<>();
|
|
40
|
+
metadata.put("nodeCount", 0);
|
|
41
|
+
metadata.put("childNodeIds", new ArrayList<String>());
|
|
42
|
+
metadata.put("language", language);
|
|
43
|
+
|
|
44
|
+
Map<String, Object> node = new LinkedHashMap<>();
|
|
45
|
+
node.put("id", "file::" + relPath);
|
|
46
|
+
node.put("name", relPath.substring(relPath.lastIndexOf('/') + 1));
|
|
47
|
+
node.put("type", nodeType);
|
|
48
|
+
node.put("filePath", relPath);
|
|
49
|
+
node.put("startLine", 1);
|
|
50
|
+
node.put("endLine", endLine);
|
|
51
|
+
node.put("parentFile", "file::" + relPath);
|
|
52
|
+
node.put("metadata", metadata);
|
|
53
|
+
return node;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* CLASS / INTERFACE / ENUM / METHOD node — id format:
|
|
58
|
+
* rel/path/File.java::TypeName or rel/path/File.java::Type.method
|
|
59
|
+
*/
|
|
60
|
+
public static Map<String, Object> codeNode(String relPath, String name, String nodeType,
|
|
61
|
+
int startLine, int endLine, String rawCode,
|
|
62
|
+
Map<String, Object> metadata, String language) {
|
|
63
|
+
Map<String, Object> node = new LinkedHashMap<>();
|
|
64
|
+
node.put("id", relPath + "::" + name);
|
|
65
|
+
node.put("name", name);
|
|
66
|
+
node.put("type", nodeType);
|
|
67
|
+
node.put("filePath", relPath);
|
|
68
|
+
node.put("startLine", startLine);
|
|
69
|
+
node.put("endLine", endLine);
|
|
70
|
+
node.put("rawCode", rawCode);
|
|
71
|
+
node.put("codeHash", codeHash(rawCode));
|
|
72
|
+
node.put("parentFile", "file::" + relPath);
|
|
73
|
+
|
|
74
|
+
Map<String, Object> meta = new LinkedHashMap<>();
|
|
75
|
+
meta.put("language", language);
|
|
76
|
+
if (metadata != null) {
|
|
77
|
+
meta.putAll(metadata);
|
|
78
|
+
}
|
|
79
|
+
node.put("metadata", meta);
|
|
80
|
+
return node;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** THIRD_PARTY node — id format: [mvn]/prefix or [mvn]/prefix::Member. */
|
|
84
|
+
public static Map<String, Object> thirdPartyNode(String id, String name,
|
|
85
|
+
Map<String, Object> metadata) {
|
|
86
|
+
Map<String, Object> node = new LinkedHashMap<>();
|
|
87
|
+
node.put("id", id);
|
|
88
|
+
node.put("name", name);
|
|
89
|
+
node.put("type", "THIRD_PARTY");
|
|
90
|
+
node.put("filePath", id);
|
|
91
|
+
node.put("startLine", 0);
|
|
92
|
+
node.put("endLine", 0);
|
|
93
|
+
node.put("metadata", metadata == null ? new LinkedHashMap<String, Object>() : metadata);
|
|
94
|
+
return node;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** ROUTE code node — id format: rel::METHOD /path (mirrors python common.py). */
|
|
98
|
+
public static Map<String, Object> routeNode(String relPath, String httpMethod,
|
|
99
|
+
String urlPath, boolean isDynamic,
|
|
100
|
+
List<String> params, String framework,
|
|
101
|
+
String handlerName) {
|
|
102
|
+
Map<String, Object> metadata = new LinkedHashMap<>();
|
|
103
|
+
metadata.put("urlPath", urlPath);
|
|
104
|
+
metadata.put("httpMethod", httpMethod);
|
|
105
|
+
metadata.put("isDynamic", isDynamic);
|
|
106
|
+
metadata.put("params", params == null ? new ArrayList<String>() : params);
|
|
107
|
+
metadata.put("framework", framework);
|
|
108
|
+
metadata.put("handlerName", handlerName);
|
|
109
|
+
metadata.put("routeKind", "backend");
|
|
110
|
+
|
|
111
|
+
Map<String, Object> node = new LinkedHashMap<>();
|
|
112
|
+
node.put("id", relPath + "::" + httpMethod + " " + urlPath);
|
|
113
|
+
node.put("name", httpMethod + " " + urlPath);
|
|
114
|
+
node.put("type", "ROUTE");
|
|
115
|
+
node.put("filePath", relPath);
|
|
116
|
+
node.put("startLine", 0);
|
|
117
|
+
node.put("endLine", 0);
|
|
118
|
+
node.put("parentFile", "file::" + relPath);
|
|
119
|
+
node.put("metadata", metadata);
|
|
120
|
+
return node;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** BackendRouteNode dict for the `routes` array (engine BackendRouteNode). */
|
|
124
|
+
public static Map<String, Object> backendRoute(String urlPath, String filePath,
|
|
125
|
+
String httpMethod, String framework,
|
|
126
|
+
boolean isDynamic, List<String> params,
|
|
127
|
+
String handlerName, String nodeId) {
|
|
128
|
+
Map<String, Object> route = new LinkedHashMap<>();
|
|
129
|
+
route.put("type", "BACKEND_ROUTE");
|
|
130
|
+
route.put("urlPath", urlPath);
|
|
131
|
+
route.put("filePath", filePath);
|
|
132
|
+
route.put("httpMethod", httpMethod);
|
|
133
|
+
route.put("framework", framework);
|
|
134
|
+
route.put("isDynamic", isDynamic);
|
|
135
|
+
if (params != null && !params.isEmpty()) {
|
|
136
|
+
route.put("params", params);
|
|
137
|
+
}
|
|
138
|
+
if (handlerName != null) {
|
|
139
|
+
route.put("handlerName", handlerName);
|
|
140
|
+
}
|
|
141
|
+
if (nodeId != null) {
|
|
142
|
+
route.put("nodeId", nodeId);
|
|
143
|
+
}
|
|
144
|
+
return route;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Edge — {type, from, to, metadata?} (engine CodeEdge). */
|
|
148
|
+
public static Map<String, Object> edge(String type, String from, String to,
|
|
149
|
+
Map<String, Object> metadata) {
|
|
150
|
+
Map<String, Object> e = new LinkedHashMap<>();
|
|
151
|
+
e.put("type", type);
|
|
152
|
+
e.put("from", from);
|
|
153
|
+
e.put("to", to);
|
|
154
|
+
if (metadata != null && !metadata.isEmpty()) {
|
|
155
|
+
e.put("metadata", metadata);
|
|
156
|
+
}
|
|
157
|
+
return e;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Deterministic edge-key for dedupe: from|type|to. */
|
|
161
|
+
public static String edgeKey(Map<String, Object> edge) {
|
|
162
|
+
return edge.get("from") + "|" + edge.get("type") + "|" + edge.get("to");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public static Map<String, Object> error(String file, String message) {
|
|
166
|
+
Map<String, Object> err = new LinkedHashMap<>();
|
|
167
|
+
err.put("file", file);
|
|
168
|
+
err.put("error", message);
|
|
169
|
+
return err;
|
|
170
|
+
}
|
|
171
|
+
}
|