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.
Files changed (129) hide show
  1. package/README.md +89 -31
  2. package/dist/extractors/detectLanguage.d.ts +2 -0
  3. package/dist/extractors/detectLanguage.js +33 -0
  4. package/dist/extractors/index.d.ts +6 -0
  5. package/dist/extractors/index.js +116 -0
  6. package/dist/extractors/runner.d.ts +7 -0
  7. package/dist/extractors/runner.js +194 -0
  8. package/dist/extractors/types.d.ts +37 -0
  9. package/dist/extractors/types.js +8 -0
  10. package/dist/graph/buildLookup.d.ts +1 -0
  11. package/dist/graph/buildLookup.js +2 -1
  12. package/dist/graph/edges/callEdges.js +19 -5
  13. package/dist/graph/edges/callEdges.test.d.ts +1 -0
  14. package/dist/graph/edges/callEdges.test.js +200 -0
  15. package/dist/graph/edges/importEdges.js +12 -0
  16. package/dist/graph/edges/inheritanceEdges.d.ts +3 -0
  17. package/dist/graph/edges/inheritanceEdges.js +73 -0
  18. package/dist/graph/edges/inheritanceEdges.test.d.ts +1 -0
  19. package/dist/graph/edges/inheritanceEdges.test.js +140 -0
  20. package/dist/graph/index.js +4 -0
  21. package/dist/parser/classes.test.d.ts +1 -0
  22. package/dist/parser/classes.test.js +360 -0
  23. package/dist/parser/extractors/classes.d.ts +5 -0
  24. package/dist/parser/extractors/classes.js +241 -0
  25. package/dist/parser/index.d.ts +2 -0
  26. package/dist/parser/index.js +7 -1
  27. package/dist/pipeline/index.d.ts +2 -1
  28. package/dist/pipeline/index.js +30 -42
  29. package/dist/scoring/index.js +8 -0
  30. package/dist/scoring/index.test.js +43 -0
  31. package/dist/scoring/nodeScorer.js +2 -0
  32. package/dist/scoring/pruneDisconnected.d.ts +8 -0
  33. package/dist/scoring/pruneDisconnected.js +66 -0
  34. package/dist/scoring/pruneDisconnected.test.d.ts +1 -0
  35. package/dist/scoring/pruneDisconnected.test.js +123 -0
  36. package/dist/summarizer/prompts.d.ts +1 -1
  37. package/dist/types.d.ts +5 -5
  38. package/extractors/go/bin/darwin-amd64/devlens_go_extractor +0 -0
  39. package/extractors/go/bin/darwin-arm64/devlens_go_extractor +0 -0
  40. package/extractors/go/bin/linux-amd64/devlens_go_extractor +0 -0
  41. package/extractors/go/bin/linux-arm64/devlens_go_extractor +0 -0
  42. package/extractors/go/bin/windows-amd64/devlens_go_extractor.exe +0 -0
  43. package/extractors/go/build.mjs +43 -0
  44. package/extractors/go/calls.go +289 -0
  45. package/extractors/go/contract.go +140 -0
  46. package/extractors/go/extractor.go +161 -0
  47. package/extractors/go/fingerprint.go +167 -0
  48. package/extractors/go/go.mod +3 -0
  49. package/extractors/go/imports.go +90 -0
  50. package/extractors/go/inheritance.go +138 -0
  51. package/extractors/go/lookup.go +106 -0
  52. package/extractors/go/main.go +57 -0
  53. package/extractors/go/nodes.go +177 -0
  54. package/extractors/go/orm_edges.go +277 -0
  55. package/extractors/go/parser.go +469 -0
  56. package/extractors/go/routes.go +521 -0
  57. package/extractors/go/tests.go +45 -0
  58. package/extractors/go/thirdparty.go +138 -0
  59. package/extractors/go/typeload.go +178 -0
  60. package/extractors/go/walker.go +67 -0
  61. package/extractors/java/build.mjs +90 -0
  62. package/extractors/java/devlens_java_extractor.jar +0 -0
  63. package/extractors/java/src/devlens/extractor/Contract.java +171 -0
  64. package/extractors/java/src/devlens/extractor/Extractor.java +262 -0
  65. package/extractors/java/src/devlens/extractor/ExtractorResult.java +12 -0
  66. package/extractors/java/src/devlens/extractor/Fingerprint.java +239 -0
  67. package/extractors/java/src/devlens/extractor/LookupMaps.java +123 -0
  68. package/extractors/java/src/devlens/extractor/Main.java +66 -0
  69. package/extractors/java/src/devlens/extractor/Parser.java +522 -0
  70. package/extractors/java/src/devlens/extractor/SourceWalker.java +82 -0
  71. package/extractors/java/src/devlens/extractor/ThirdParty.java +141 -0
  72. package/extractors/java/src/devlens/extractor/TypeSolverFactory.java +43 -0
  73. package/extractors/java/src/devlens/extractor/edges/Calls.java +266 -0
  74. package/extractors/java/src/devlens/extractor/edges/Enrich.java +54 -0
  75. package/extractors/java/src/devlens/extractor/edges/Imports.java +154 -0
  76. package/extractors/java/src/devlens/extractor/edges/Inheritance.java +79 -0
  77. package/extractors/java/src/devlens/extractor/edges/OrmEdges.java +209 -0
  78. package/extractors/java/src/devlens/extractor/edges/Routes.java +143 -0
  79. package/extractors/java/src/devlens/extractor/edges/Tests.java +53 -0
  80. package/extractors/python/devlens_extractors_python/__init__.py +3 -0
  81. package/extractors/python/devlens_extractors_python/__main__.py +33 -0
  82. package/extractors/python/devlens_extractors_python/contract.py +94 -0
  83. package/extractors/python/devlens_extractors_python/edges/__init__.py +28 -0
  84. package/extractors/python/devlens_extractors_python/edges/calls.py +112 -0
  85. package/extractors/python/devlens_extractors_python/edges/enrich.py +44 -0
  86. package/extractors/python/devlens_extractors_python/edges/imports.py +155 -0
  87. package/extractors/python/devlens_extractors_python/edges/inheritance.py +97 -0
  88. package/extractors/python/devlens_extractors_python/edges/orm_edges.py +225 -0
  89. package/extractors/python/devlens_extractors_python/edges/routes/__init__.py +28 -0
  90. package/extractors/python/devlens_extractors_python/edges/routes/common.py +79 -0
  91. package/extractors/python/devlens_extractors_python/edges/routes/decorators.py +212 -0
  92. package/extractors/python/devlens_extractors_python/edges/routes/django_urls.py +213 -0
  93. package/extractors/python/devlens_extractors_python/edges/routes/drf.py +128 -0
  94. package/extractors/python/devlens_extractors_python/edges/tests.py +53 -0
  95. package/extractors/python/devlens_extractors_python/extractor.py +107 -0
  96. package/extractors/python/devlens_extractors_python/fingerprint.py +201 -0
  97. package/extractors/python/devlens_extractors_python/lookup.py +72 -0
  98. package/extractors/python/devlens_extractors_python/parser/__init__.py +72 -0
  99. package/extractors/python/devlens_extractors_python/parser/classes.py +109 -0
  100. package/extractors/python/devlens_extractors_python/parser/functions.py +163 -0
  101. package/extractors/python/devlens_extractors_python/parser/walker.py +30 -0
  102. package/extractors/python/devlens_extractors_python/third_party.py +103 -0
  103. package/extractors/python/pyproject.toml +16 -0
  104. package/extractors/python/setup.mjs +54 -0
  105. package/extractors/rust/Cargo.toml +29 -0
  106. package/extractors/rust/bin/darwin-amd64/devlens_rust_extractor +0 -0
  107. package/extractors/rust/bin/darwin-arm64/devlens_rust_extractor +0 -0
  108. package/extractors/rust/bin/linux-amd64/devlens_rust_extractor +0 -0
  109. package/extractors/rust/bin/linux-arm64/devlens_rust_extractor +0 -0
  110. package/extractors/rust/bin/windows-amd64/devlens_rust_extractor.exe +0 -0
  111. package/extractors/rust/build.mjs +85 -0
  112. package/extractors/rust/src/calls.rs +295 -0
  113. package/extractors/rust/src/contract.rs +236 -0
  114. package/extractors/rust/src/enrich.rs +107 -0
  115. package/extractors/rust/src/extractor.rs +199 -0
  116. package/extractors/rust/src/fingerprint.rs +238 -0
  117. package/extractors/rust/src/imports.rs +101 -0
  118. package/extractors/rust/src/inheritance.rs +233 -0
  119. package/extractors/rust/src/lookup.rs +226 -0
  120. package/extractors/rust/src/main.rs +53 -0
  121. package/extractors/rust/src/module_map.rs +131 -0
  122. package/extractors/rust/src/nodes.rs +174 -0
  123. package/extractors/rust/src/orm_edges.rs +125 -0
  124. package/extractors/rust/src/parser.rs +859 -0
  125. package/extractors/rust/src/routes.rs +1265 -0
  126. package/extractors/rust/src/tests.rs +109 -0
  127. package/extractors/rust/src/thirdparty.rs +114 -0
  128. package/extractors/rust/src/walker.rs +82 -0
  129. package/package.json +22 -4
@@ -0,0 +1,174 @@
1
+ // nodes.rs — CodeNode building from parsed facts (mirrors go/nodes.go).
2
+ //
3
+ // Contract rules enforced here:
4
+ // - FILE id `file::rel/path.rs`; file nodes parent themselves
5
+ // - TEST files (any #[test] fn) = LEAF nodes: nodes=[] for them, test fns
6
+ // ride in metadata.testCases only (the bug that shipped in Python's
7
+ // parser — asserted in the bun suite)
8
+ // - rawCode + codeHash on every FUNCTION/METHOD/STRUCT/ENUM/TRAIT/IMPL_BLOCK
9
+ // - deterministic ids (file-scoped; trait-impl method suffix for the
10
+ // same-name-different-trait collision class)
11
+
12
+ use crate::contract::{base_metadata, code_node, file_node, CodeNode, NODE_FILE, NODE_TEST};
13
+ use crate::parser::{ParsedFile, ParsedItem};
14
+ use serde_json::{Map, Value};
15
+
16
+ /// FILE (or TEST leaf) nodes — always parents of their file's children.
17
+ pub fn collect_file_nodes(files: &[ParsedFile]) -> Vec<CodeNode> {
18
+ let mut out = vec![];
19
+ for pf in files {
20
+ if pf.is_test_file {
21
+ let mut n = file_node(&pf.rel_path, pf.line_count, NODE_TEST);
22
+ let meta = n
23
+ .metadata
24
+ .entry("testCases".to_string())
25
+ .or_insert_with(|| Value::Array(vec![]));
26
+ if let Value::Array(arr) = meta {
27
+ for t in &pf.test_fns {
28
+ arr.push(Value::from(t.clone()));
29
+ }
30
+ }
31
+ out.push(n);
32
+ } else {
33
+ out.push(file_node(&pf.rel_path, pf.line_count, NODE_FILE));
34
+ }
35
+ }
36
+ out
37
+ }
38
+
39
+ /// Code nodes (FUNCTION/METHOD/STRUCT/ENUM/TRAIT/IMPL_BLOCK) — test fns are
40
+ /// NOT emitted (leaf rule); they feed metadata.testCases only.
41
+ pub fn collect_code_nodes(files: &[ParsedFile]) -> Vec<CodeNode> {
42
+ let mut out = vec![];
43
+ for pf in files {
44
+ if pf.is_test_file {
45
+ continue;
46
+ }
47
+ for item in &pf.items {
48
+ if item.is_test {
49
+ continue;
50
+ }
51
+ out.push(item_node(pf, item));
52
+ }
53
+ }
54
+ out
55
+ }
56
+
57
+ fn item_node(pf: &ParsedFile, item: &ParsedItem) -> CodeNode {
58
+ let mut meta = base_metadata();
59
+ meta.insert("isPublic".to_string(), Value::from(item.is_pub));
60
+ if item.is_async {
61
+ meta.insert("isAsync".to_string(), Value::from(true));
62
+ }
63
+ if item.receiver.contains("self") {
64
+ meta.insert("receiver".to_string(), Value::from(item.receiver.clone()));
65
+ }
66
+
67
+ let (id, node_type, name) = match item.kind {
68
+ crate::parser::KIND_FUNCTION => (
69
+ crate::lookup::func_node_id(&pf.rel_path, &item.name),
70
+ "FUNCTION",
71
+ item.name.clone(),
72
+ ),
73
+ crate::parser::KIND_METHOD => {
74
+ let owner = item.impl_target.clone();
75
+ let trait_path = item.impl_trait_path.clone();
76
+ let id = crate::lookup::method_node_id(
77
+ &pf.rel_path,
78
+ &owner,
79
+ &item.name,
80
+ trait_path.as_deref(),
81
+ );
82
+ meta.insert("parentStruct".to_string(), Value::from(owner.clone()));
83
+ if let Some(t) = &trait_path {
84
+ meta.insert("traitPath".to_string(), Value::from(t.clone()));
85
+ }
86
+ (id, "METHOD", item.name.clone())
87
+ }
88
+ crate::parser::KIND_STRUCT => {
89
+ let id = crate::lookup::struct_node_id(&pf.rel_path, &item.name);
90
+ if !item.fields.is_empty() {
91
+ let fields: Vec<Value> = item
92
+ .fields
93
+ .iter()
94
+ .map(|f| Value::from(f.name.clone()))
95
+ .collect();
96
+ meta.insert("fields".to_string(), Value::Array(fields));
97
+ }
98
+ if !item.derives.is_empty() {
99
+ let d: Vec<Value> = item
100
+ .derives
101
+ .iter()
102
+ .map(|d| Value::from(d.clone()))
103
+ .collect();
104
+ meta.insert("derives".to_string(), Value::Array(d));
105
+ }
106
+ (id, "STRUCT", item.name.clone())
107
+ }
108
+ crate::parser::KIND_ENUM => {
109
+ let id = crate::lookup::enum_node_id(&pf.rel_path, &item.name);
110
+ if !item.variants.is_empty() {
111
+ let v: Vec<Value> = item
112
+ .variants
113
+ .iter()
114
+ .map(|v| Value::from(v.clone()))
115
+ .collect();
116
+ meta.insert("variants".to_string(), Value::Array(v));
117
+ }
118
+ (id, "ENUM", item.name.clone())
119
+ }
120
+ crate::parser::KIND_TRAIT => {
121
+ let id = crate::lookup::trait_node_id(&pf.rel_path, &item.name);
122
+ if !item.supertraits.is_empty() {
123
+ let s: Vec<Value> = item
124
+ .supertraits
125
+ .iter()
126
+ .map(|s| Value::from(s.clone()))
127
+ .collect();
128
+ meta.insert("supertraits".to_string(), Value::Array(s));
129
+ }
130
+ (id, "TRAIT", item.name.clone())
131
+ }
132
+ crate::parser::KIND_IMPL_BLOCK => {
133
+ let label = if item.impl_trait.is_some() {
134
+ format!(
135
+ "impl {} for {}",
136
+ item.impl_trait_path.as_deref().unwrap_or(""),
137
+ item.impl_target
138
+ )
139
+ } else {
140
+ format!("impl {}", item.impl_target)
141
+ };
142
+ let id = crate::lookup::impl_block_node_id(&pf.rel_path, &label, item.start_line);
143
+ if let Some(t) = &item.impl_trait_path {
144
+ meta.insert("traitPath".to_string(), Value::from(t.clone()));
145
+ }
146
+ if !item.impl_target.is_empty() {
147
+ meta.insert(
148
+ "implTarget".to_string(),
149
+ Value::from(item.impl_target.clone()),
150
+ );
151
+ }
152
+ (id, "IMPL_BLOCK", label)
153
+ }
154
+ _ => return code_node("", "", "", "", 0, 0, "", Map::new()),
155
+ };
156
+
157
+ let mut n = code_node(
158
+ &id,
159
+ &pf.rel_path,
160
+ &name,
161
+ node_type,
162
+ item.start_line,
163
+ item.end_line,
164
+ &item.raw_code,
165
+ meta,
166
+ );
167
+ // metadata.calls — contract compliance + LLM context (the orchestrator
168
+ // never resolves them for subprocess languages, but they must be present)
169
+ if !item.calls.is_empty() {
170
+ let calls: Vec<Value> = item.calls.iter().map(|c| Value::from(c.clone())).collect();
171
+ n.metadata.insert("calls".to_string(), Value::Array(calls));
172
+ }
173
+ n
174
+ }
@@ -0,0 +1,125 @@
1
+ // orm_edges.rs — Diesel data layer: table! concepts + model structs →
2
+ // READS_FROM / WRITES_TO edges (consumer → store, mirroring stateEdges.ts).
3
+ //
4
+ // Model detection (documented diesel API):
5
+ // - struct with #[derive(Queryable, Insertable, ...)] and/or
6
+ // #[diesel(table_name = users)] (legacy #[table_name = "users"]) → model
7
+ // R/W grammar (diesel query DSL):
8
+ // READ — users::table.filter/.first/.get_result/.get_results/.load/.find
9
+ // WRITE — diesel::insert_into(users::table) / users::table.insert(...) /
10
+ // diesel::update(users::table) / diesel::delete(...)
11
+ // Target: the model STRUCT whose table_name matches; fallback = the FILE
12
+ // containing the table! decl (no TABLE node type in types.ts — documented).
13
+ // sqlx raw SQL → V2 (documented). isModel/tableName metadata enrichment
14
+ // happens in enrich.rs (same facts, single source).
15
+
16
+ use crate::contract::{edge, CodeEdge, EDGE_READS_FROM, EDGE_WRITES_TO};
17
+ use crate::extractor::{Options, ParsedRepo};
18
+ use std::collections::HashMap;
19
+
20
+ /// table name → model struct node id
21
+ pub fn table_to_model_map(repo: &ParsedRepo) -> HashMap<String, String> {
22
+ let mut map = HashMap::new();
23
+ for pf in &repo.files {
24
+ if pf.is_test_file {
25
+ continue;
26
+ }
27
+ for item in &pf.items {
28
+ if item.kind != crate::parser::KIND_STRUCT || item.is_test {
29
+ continue;
30
+ }
31
+ let is_diesel_model = item.derives.iter().any(|d| {
32
+ d == "Queryable"
33
+ || d == "Insertable"
34
+ || d == "QueryableByName"
35
+ || d == "Associations"
36
+ || d == "Identifiable"
37
+ }) || item.attrs.iter().any(|a| {
38
+ (a.path == "diesel" && a.args.contains("table_name")) || a.path == "table_name"
39
+ });
40
+ if !is_diesel_model {
41
+ continue;
42
+ }
43
+ let table_name = diesel_table_name(item);
44
+ if !table_name.is_empty() {
45
+ let id = crate::lookup::struct_node_id(&pf.rel_path, &item.name);
46
+ map.insert(table_name, id);
47
+ }
48
+ }
49
+ }
50
+ map
51
+ }
52
+
53
+ /// table name → file containing the table! decl (fallback store target).
54
+ pub fn table_to_schema_file_map(repo: &ParsedRepo) -> HashMap<String, String> {
55
+ let mut map = HashMap::new();
56
+ for pf in &repo.files {
57
+ for t in &pf.tables {
58
+ map.insert(t.name.clone(), pf.rel_path.clone());
59
+ }
60
+ }
61
+ map
62
+ }
63
+
64
+ /// Parse the table name from #[diesel(table_name = users)] /
65
+ /// #[table_name = "users"] (legacy diesel 1.x).
66
+ pub fn diesel_table_name(item: &crate::parser::ParsedItem) -> String {
67
+ for a in &item.attrs {
68
+ if a.path == "diesel" && a.args.contains("table_name") {
69
+ if let Some((_, v)) = a.args.split_once('=') {
70
+ return v.trim().trim_matches('"').to_string();
71
+ }
72
+ } else if a.path == "table_name" {
73
+ return a.args.trim().trim_matches('"').to_string();
74
+ }
75
+ }
76
+ String::new()
77
+ }
78
+
79
+ pub fn detect_orm_edges(
80
+ repo: &ParsedRepo,
81
+ _lookup: &crate::lookup::LookupMaps,
82
+ _opts: &Options,
83
+ ) -> Vec<CodeEdge> {
84
+ let mut edges = vec![];
85
+ if repo.files.is_empty() {
86
+ return edges;
87
+ }
88
+ let table_to_model = table_to_model_map(repo);
89
+ let table_to_schema_file = table_to_schema_file_map(repo);
90
+
91
+ for pf in &repo.files {
92
+ if pf.is_test_file {
93
+ continue;
94
+ }
95
+ for item in &pf.items {
96
+ if item.is_test || item.orm_calls.is_empty() {
97
+ continue;
98
+ }
99
+ if item.kind != crate::parser::KIND_FUNCTION && item.kind != crate::parser::KIND_METHOD
100
+ {
101
+ continue;
102
+ }
103
+ let from = crate::lookup::node_id_for_item(&pf.rel_path, item);
104
+ for oc in &item.orm_calls {
105
+ // normalize the table ref ("users::table" → "users") — top-
106
+ // level fn args keep the raw path (insert_into(users::table))
107
+ let table = crate::parser::table_name_from_ref(&oc.table_ref);
108
+ let target = table_to_model.get(&table).cloned().or_else(|| {
109
+ table_to_schema_file
110
+ .get(&table)
111
+ .map(|f| format!("file::{}", f))
112
+ });
113
+ if let Some(to) = target {
114
+ let etype = if oc.kind == "read" {
115
+ EDGE_READS_FROM
116
+ } else {
117
+ EDGE_WRITES_TO
118
+ };
119
+ edges.push(edge(&from, &to, etype));
120
+ }
121
+ }
122
+ }
123
+ }
124
+ edges
125
+ }