code-oracle 0.1.0__py3-none-any.whl

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 (40) hide show
  1. code_oracle/__init__.py +30 -0
  2. code_oracle/cli.py +795 -0
  3. code_oracle/config.py +145 -0
  4. code_oracle/dataset.py +5325 -0
  5. code_oracle/dead_code/__init__.py +32 -0
  6. code_oracle/dead_code/detector.py +379 -0
  7. code_oracle/dead_code/entrypoints.py +333 -0
  8. code_oracle/dead_code/models.py +255 -0
  9. code_oracle/dead_code/semantics.py +416 -0
  10. code_oracle/decision.py +906 -0
  11. code_oracle/engine.py +430 -0
  12. code_oracle/export_onnx.py +436 -0
  13. code_oracle/hook.py +531 -0
  14. code_oracle/indexer.py +894 -0
  15. code_oracle/languages/__init__.py +114 -0
  16. code_oracle/languages/common.py +127 -0
  17. code_oracle/languages/go.py +395 -0
  18. code_oracle/languages/python.py +336 -0
  19. code_oracle/languages/rust.py +474 -0
  20. code_oracle/languages/typescript.py +775 -0
  21. code_oracle/linearizer.py +166 -0
  22. code_oracle/locator.py +301 -0
  23. code_oracle/models.py +237 -0
  24. code_oracle/perf_lint/__init__.py +38 -0
  25. code_oracle/perf_lint/engine.py +234 -0
  26. code_oracle/perf_lint/models.py +229 -0
  27. code_oracle/perf_lint/rules/__init__.py +31 -0
  28. code_oracle/perf_lint/rules/async_blocking.py +143 -0
  29. code_oracle/perf_lint/rules/n_plus_one.py +232 -0
  30. code_oracle/perf_lint/rules/nested_loops.py +137 -0
  31. code_oracle/perf_lint/rules/unclosed_res.py +494 -0
  32. code_oracle/perf_lint/visitor.py +299 -0
  33. code_oracle/server.py +184 -0
  34. code_oracle/slicer.py +225 -0
  35. code_oracle/symbolic.py +459 -0
  36. code_oracle-0.1.0.dist-info/METADATA +225 -0
  37. code_oracle-0.1.0.dist-info/RECORD +40 -0
  38. code_oracle-0.1.0.dist-info/WHEEL +4 -0
  39. code_oracle-0.1.0.dist-info/entry_points.txt +2 -0
  40. code_oracle-0.1.0.dist-info/licenses/LICENSE +190 -0
@@ -0,0 +1,474 @@
1
+ """
2
+ Rust AST extractor using Tree-sitter.
3
+ Supports Rust structs, enums, traits, impl blocks, functions, methods, and use/mod declarations.
4
+ """
5
+
6
+ from typing import List, Optional, Tuple
7
+ from tree_sitter import Language, Node, Parser
8
+ import tree_sitter_rust
9
+
10
+ from code_oracle.languages.common import extract_preceding_docstring, format_syntax_error, get_node_text
11
+ from code_oracle.models import CallReference, ImportReference, Parameter, Symbol
12
+
13
+ _RUST_LANG = Language(tree_sitter_rust.language())
14
+
15
+
16
+ def get_rust_parser() -> Parser:
17
+ """Return a Tree-sitter parser configured for Rust."""
18
+ return Parser(_RUST_LANG)
19
+
20
+
21
+ def validate_rust_syntax(source: str, file_path: str = "") -> Optional[str]:
22
+ """Validate Rust source syntax, returning an error message if invalid."""
23
+ if not source.strip():
24
+ return None
25
+ parser = get_rust_parser()
26
+ tree = parser.parse(source.encode("utf-8"))
27
+ return format_syntax_error(tree.root_node, "Rust")
28
+
29
+
30
+ def _extract_rust_parameters(params_node: Node, source_bytes: bytes) -> Tuple[List[Parameter], bool]:
31
+ """
32
+ Extract parameters from a Rust parameters node.
33
+ Returns: (list_of_parameters, has_self_receiver)
34
+ """
35
+ params: List[Parameter] = []
36
+ has_self = False
37
+ if not params_node:
38
+ return params, has_self
39
+
40
+ for child in params_node.children:
41
+ if child.type in ("(", ")", ","):
42
+ continue
43
+
44
+ if child.type == "self_parameter":
45
+ has_self = True
46
+ params.append(Parameter(name="self", annotation=get_node_text(child, source_bytes)))
47
+
48
+ elif child.type == "parameter":
49
+ name_node = child.child_by_field_name("pattern") or (
50
+ child.children[0] if child.children else None
51
+ )
52
+ param_name = get_node_text(name_node, source_bytes) if name_node else f"arg{len(params)}"
53
+
54
+ type_node = child.child_by_field_name("type") or (
55
+ child.children[-1] if len(child.children) > 1 else None
56
+ )
57
+ type_str = get_node_text(type_node, source_bytes).strip() if type_node else None
58
+
59
+ params.append(
60
+ Parameter(
61
+ name=param_name,
62
+ annotation=type_str,
63
+ )
64
+ )
65
+
66
+ return params, has_self
67
+
68
+
69
+ def _extract_rust_calls(node: Node, source_bytes: bytes, caller_id: Optional[str] = None) -> List[CallReference]:
70
+ """Recursively extract function and method calls inside a Rust AST node."""
71
+ calls: List[CallReference] = []
72
+
73
+ def walk(n: Node):
74
+ if n.type == "call_expression":
75
+ fn_node = n.child_by_field_name("function")
76
+ callee_name = get_node_text(fn_node, source_bytes).strip() if fn_node else ""
77
+
78
+ args_count = 0
79
+ args_node = n.child_by_field_name("arguments")
80
+ if args_node:
81
+ for arg in args_node.children:
82
+ if arg.type in ("(", ")", ",", "comment", "line_comment", "block_comment") or "comment" in arg.type:
83
+ continue
84
+ args_count += 1
85
+
86
+ if callee_name:
87
+ calls.append(
88
+ CallReference(
89
+ callee=callee_name,
90
+ args_count=args_count,
91
+ kwargs=[],
92
+ lineno=n.start_point.row + 1,
93
+ caller=caller_id,
94
+ )
95
+ )
96
+
97
+ for child in n.children:
98
+ walk(child)
99
+
100
+ walk(node)
101
+ return calls
102
+
103
+
104
+ def extract_rust_imports(source: str, file_path: str = "") -> List[ImportReference]:
105
+ """Extract all use declarations and mod declarations from Rust source."""
106
+ if not source.strip():
107
+ return []
108
+
109
+ parser = get_rust_parser()
110
+ source_bytes = source.encode("utf-8")
111
+ tree = parser.parse(source_bytes)
112
+
113
+ imports: List[ImportReference] = []
114
+
115
+ def walk(node: Node):
116
+ if node.type == "use_declaration":
117
+ lineno = node.start_point.row + 1
118
+ for ch in node.children:
119
+ if ch.type in ("scoped_identifier", "identifier"):
120
+ text = get_node_text(ch, source_bytes)
121
+ if "::" in text:
122
+ mod_part, name_part = text.rsplit("::", 1)
123
+ imports.append(
124
+ ImportReference(
125
+ module=mod_part,
126
+ name=name_part,
127
+ lineno=lineno,
128
+ file_path=file_path,
129
+ )
130
+ )
131
+ else:
132
+ imports.append(
133
+ ImportReference(
134
+ module=None,
135
+ name=text,
136
+ lineno=lineno,
137
+ file_path=file_path,
138
+ )
139
+ )
140
+ elif ch.type == "scoped_use_list":
141
+ prefix_node = ch.child_by_field_name("path") or (
142
+ ch.children[0] if ch.children else None
143
+ )
144
+ prefix = get_node_text(prefix_node, source_bytes) if prefix_node else ""
145
+ list_node = ch.child_by_field_name("list") or next(
146
+ (c for c in ch.children if c.type == "use_list"), None
147
+ )
148
+ if list_node:
149
+ for item in list_node.children:
150
+ if item.type == "identifier":
151
+ imports.append(
152
+ ImportReference(
153
+ module=prefix,
154
+ name=get_node_text(item, source_bytes),
155
+ lineno=lineno,
156
+ file_path=file_path,
157
+ )
158
+ )
159
+ elif item.type == "use_as_clause":
160
+ path_child = item.child_by_field_name("path") or item.children[0]
161
+ alias_child = item.child_by_field_name("alias") or item.children[-1]
162
+ imports.append(
163
+ ImportReference(
164
+ module=prefix,
165
+ name=get_node_text(path_child, source_bytes),
166
+ asname=get_node_text(alias_child, source_bytes),
167
+ lineno=lineno,
168
+ file_path=file_path,
169
+ )
170
+ )
171
+ elif ch.type == "use_wildcard":
172
+ text = get_node_text(ch, source_bytes)
173
+ mod_part = text.replace("::*", "").strip()
174
+ imports.append(
175
+ ImportReference(
176
+ module=mod_part,
177
+ name="*",
178
+ lineno=lineno,
179
+ file_path=file_path,
180
+ )
181
+ )
182
+
183
+ elif node.type == "mod_item":
184
+ name_node = node.child_by_field_name("name")
185
+ if name_node:
186
+ mod_name = get_node_text(name_node, source_bytes)
187
+ imports.append(
188
+ ImportReference(
189
+ module=None,
190
+ name=mod_name,
191
+ lineno=node.start_point.row + 1,
192
+ file_path=file_path,
193
+ )
194
+ )
195
+
196
+ for child in node.children:
197
+ walk(child)
198
+
199
+ walk(tree.root_node)
200
+ return imports
201
+
202
+
203
+ def _get_rust_visibility(node: Node, source_bytes: bytes) -> Tuple[bool, str]:
204
+ """Determine if a Rust node has pub visibility."""
205
+ vis_node = node.child_by_field_name("visibility") or next(
206
+ (c for c in node.children if c.type == "visibility_modifier"), None
207
+ )
208
+ if not vis_node:
209
+ return False, "internal"
210
+ vis_text = get_node_text(vis_node, source_bytes)
211
+ if vis_text == "pub":
212
+ return True, "public"
213
+ elif any(k in vis_text for k in ("pub(crate)", "pub(super)", "pub(self)", "pub(in ")):
214
+ return False, "internal"
215
+ return True, "public"
216
+
217
+
218
+ def extract_rust_symbols(source: str, file_path: str = "") -> List[Symbol]:
219
+ """Parse Rust source into AST and extract symbol entities with detailed metadata."""
220
+ if not source.strip():
221
+ return []
222
+
223
+ parser = get_rust_parser()
224
+ source_bytes = source.encode("utf-8")
225
+ tree = parser.parse(source_bytes)
226
+
227
+ symbols: List[Symbol] = []
228
+
229
+ for child in tree.root_node.children:
230
+ docstring = extract_preceding_docstring(child, source_bytes)
231
+ is_exp, vis = _get_rust_visibility(child, source_bytes)
232
+
233
+ if child.type == "function_item":
234
+ name_node = child.child_by_field_name("name")
235
+ if not name_node:
236
+ continue
237
+ fn_name = get_node_text(name_node, source_bytes)
238
+ sym_id = f"{file_path}::{fn_name}"
239
+
240
+ params_node = child.child_by_field_name("parameters")
241
+ params, _ = _extract_rust_parameters(params_node, source_bytes) if params_node else ([], False)
242
+
243
+ ret_node = child.child_by_field_name("return_type")
244
+ ret_type = get_node_text(ret_node, source_bytes).lstrip("->").strip() if ret_node else None
245
+
246
+ min_args = len(params)
247
+ max_args = len(params)
248
+
249
+ body_node = child.child_by_field_name("body")
250
+ calls = _extract_rust_calls(body_node, source_bytes, caller_id=sym_id) if body_node else []
251
+
252
+ param_strs = [p.name + (f": {p.annotation}" if p.annotation else "") for p in params]
253
+ ret_suffix = f" -> {ret_type}" if ret_type else ""
254
+ signature = f"fn {fn_name}({', '.join(param_strs)}){ret_suffix}"
255
+
256
+ symbols.append(
257
+ Symbol(
258
+ name=fn_name,
259
+ qualname=fn_name,
260
+ file_path=file_path,
261
+ kind="function",
262
+ lineno=child.start_point.row + 1,
263
+ end_lineno=child.end_point.row + 1,
264
+ signature=signature,
265
+ params=params,
266
+ min_args=min_args,
267
+ max_args=max_args,
268
+ return_type=ret_type,
269
+ calls=calls,
270
+ is_method=False,
271
+ is_static=False,
272
+ docstring=docstring,
273
+ is_exported=is_exp,
274
+ visibility=vis,
275
+ )
276
+ )
277
+
278
+ elif child.type == "struct_item":
279
+ name_node = child.child_by_field_name("name")
280
+ if name_node:
281
+ s_name = get_node_text(name_node, source_bytes)
282
+ symbols.append(
283
+ Symbol(
284
+ name=s_name,
285
+ qualname=s_name,
286
+ file_path=file_path,
287
+ kind="struct",
288
+ lineno=child.start_point.row + 1,
289
+ end_lineno=child.end_point.row + 1,
290
+ signature=f"struct {s_name}",
291
+ docstring=docstring,
292
+ is_exported=is_exp,
293
+ visibility=vis,
294
+ )
295
+ )
296
+
297
+ elif child.type == "enum_item":
298
+ name_node = child.child_by_field_name("name")
299
+ if name_node:
300
+ e_name = get_node_text(name_node, source_bytes)
301
+ symbols.append(
302
+ Symbol(
303
+ name=e_name,
304
+ qualname=e_name,
305
+ file_path=file_path,
306
+ kind="enum",
307
+ lineno=child.start_point.row + 1,
308
+ end_lineno=child.end_point.row + 1,
309
+ signature=f"enum {e_name}",
310
+ docstring=docstring,
311
+ is_exported=is_exp,
312
+ visibility=vis,
313
+ )
314
+ )
315
+
316
+ elif child.type == "trait_item":
317
+ name_node = child.child_by_field_name("name")
318
+ if name_node:
319
+ t_name = get_node_text(name_node, source_bytes)
320
+ symbols.append(
321
+ Symbol(
322
+ name=t_name,
323
+ qualname=t_name,
324
+ file_path=file_path,
325
+ kind="interface",
326
+ lineno=child.start_point.row + 1,
327
+ end_lineno=child.end_point.row + 1,
328
+ signature=f"trait {t_name}",
329
+ docstring=docstring,
330
+ is_exported=is_exp,
331
+ visibility=vis,
332
+ )
333
+ )
334
+
335
+ elif child.type == "impl_item":
336
+ type_node = child.child_by_field_name("type")
337
+ trait_node = child.child_by_field_name("trait")
338
+ raw_struct = get_node_text(type_node, source_bytes) if type_node else "Unknown"
339
+ raw_trait = get_node_text(trait_node, source_bytes) if trait_node else None
340
+ # Strip generic parameters and lifetime bounds for clean qualname and inheritance resolution
341
+ struct_name = raw_struct.split("<")[0].strip()
342
+ trait_name = raw_trait.split("<")[0].strip() if raw_trait else None
343
+
344
+ body_node = child.child_by_field_name("body")
345
+ if body_node:
346
+ for item in body_node.children:
347
+ if item.type == "function_item":
348
+ fn_name_node = item.child_by_field_name("name")
349
+ if not fn_name_node:
350
+ continue
351
+ fn_name = get_node_text(fn_name_node, source_bytes)
352
+ qualname = f"{struct_name}.{fn_name}"
353
+ sym_id = f"{file_path}::{qualname}"
354
+
355
+ params_node = item.child_by_field_name("parameters")
356
+ params, has_self = (
357
+ _extract_rust_parameters(params_node, source_bytes)
358
+ if params_node
359
+ else ([], False)
360
+ )
361
+
362
+ ret_node = item.child_by_field_name("return_type")
363
+ ret_type = get_node_text(ret_node, source_bytes).lstrip("->").strip() if ret_node else None
364
+
365
+ is_method = has_self
366
+ is_static = not has_self
367
+
368
+ # For method calls, self is receiver (first param)
369
+ formal_count = len(params) - 1 if is_method else len(params)
370
+ min_args = formal_count
371
+ max_args = formal_count
372
+
373
+ item_body = item.child_by_field_name("body")
374
+ calls = _extract_rust_calls(item_body, source_bytes, caller_id=sym_id) if item_body else []
375
+
376
+ param_strs = [p.name + (f": {p.annotation}" if p.annotation else "") for p in params]
377
+ ret_suffix = f" -> {ret_type}" if ret_type else ""
378
+ signature = f"fn {fn_name}({', '.join(param_strs)}){ret_suffix}"
379
+
380
+ m_doc = extract_preceding_docstring(item, source_bytes)
381
+ m_exp, m_vis = _get_rust_visibility(item, source_bytes)
382
+
383
+ symbols.append(
384
+ Symbol(
385
+ name=fn_name,
386
+ qualname=qualname,
387
+ file_path=file_path,
388
+ kind="method" if is_method else "function",
389
+ lineno=item.start_point.row + 1,
390
+ end_lineno=item.end_point.row + 1,
391
+ signature=signature,
392
+ params=params,
393
+ min_args=min_args,
394
+ max_args=max_args,
395
+ return_type=ret_type,
396
+ calls=calls,
397
+ is_method=is_method,
398
+ is_static=is_static,
399
+ bases=[trait_name] if trait_name else [],
400
+ docstring=m_doc,
401
+ is_exported=m_exp,
402
+ visibility=m_vis,
403
+ )
404
+ )
405
+
406
+ elif child.type in ("const_item", "static_item"):
407
+ is_const = child.type == "const_item"
408
+ kind = "constant" if is_const else "variable"
409
+ name_node = child.child_by_field_name("name") or next(
410
+ (c for c in child.children if c.type == "identifier"), None
411
+ )
412
+ if name_node:
413
+ v_name = get_node_text(name_node, source_bytes)
414
+ symbols.append(
415
+ Symbol(
416
+ name=v_name,
417
+ qualname=v_name,
418
+ file_path=file_path,
419
+ kind=kind,
420
+ lineno=child.start_point.row + 1,
421
+ end_lineno=child.end_point.row + 1,
422
+ signature=f"{'const' if is_const else 'static'} {v_name}",
423
+ min_args=0,
424
+ max_args=0,
425
+ docstring=docstring,
426
+ is_exported=is_exp,
427
+ visibility=vis,
428
+ )
429
+ )
430
+
431
+ elif child.type == "type_item":
432
+ name_node = child.child_by_field_name("name") or next(
433
+ (c for c in child.children if c.type == "type_identifier"), None
434
+ )
435
+ if name_node:
436
+ t_name = get_node_text(name_node, source_bytes)
437
+ symbols.append(
438
+ Symbol(
439
+ name=t_name,
440
+ qualname=t_name,
441
+ file_path=file_path,
442
+ kind="type_alias",
443
+ lineno=child.start_point.row + 1,
444
+ end_lineno=child.end_point.row + 1,
445
+ signature=f"type {t_name}",
446
+ docstring=docstring,
447
+ is_exported=is_exp,
448
+ visibility=vis,
449
+ )
450
+ )
451
+
452
+ # Extract module calls
453
+ all_calls = _extract_rust_calls(tree.root_node, source_bytes, caller_id=f"{file_path}::<module>")
454
+ module_calls = [
455
+ c for c in all_calls
456
+ if not any(s.lineno <= c.lineno <= s.end_lineno for s in symbols if s.kind != "module")
457
+ ]
458
+ if module_calls:
459
+ line_count = len(source.splitlines()) or 1
460
+ module_sym = Symbol(
461
+ name="<module>",
462
+ qualname="<module>",
463
+ file_path=file_path,
464
+ kind="module",
465
+ lineno=1,
466
+ end_lineno=line_count,
467
+ signature=f"// module {file_path}",
468
+ calls=module_calls,
469
+ is_exported=True,
470
+ visibility="public",
471
+ )
472
+ symbols.append(module_sym)
473
+
474
+ return symbols