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,336 @@
1
+ """
2
+ Python AST extractor using Python's native ast module.
3
+ Preserves 100% fidelity and backward compatibility for Python verification.
4
+ """
5
+
6
+ import ast
7
+ from typing import List, Optional, Set
8
+
9
+ from code_oracle.models import CallReference, ImportReference, Parameter, Symbol
10
+
11
+
12
+ def _extract_calls_from_node(node: ast.AST, caller_id: Optional[str] = None) -> List[CallReference]:
13
+ """Extract all function/method calls inside a Python AST node."""
14
+ calls: List[CallReference] = []
15
+ for child in ast.walk(node):
16
+ if isinstance(child, ast.Call):
17
+ try:
18
+ callee_name = ast.unparse(child.func)
19
+ except Exception:
20
+ callee_name = "<unknown>"
21
+ kwargs = [kw.arg for kw in child.keywords if kw.arg is not None]
22
+ has_vararg = any(isinstance(a, ast.Starred) for a in child.args)
23
+ has_kwarg = any(kw.arg is None for kw in child.keywords)
24
+ calls.append(
25
+ CallReference(
26
+ callee=callee_name,
27
+ args_count=len(child.args),
28
+ kwargs=kwargs,
29
+ lineno=getattr(child, "lineno", 0),
30
+ caller=caller_id,
31
+ has_vararg=has_vararg,
32
+ has_kwarg=has_kwarg,
33
+ )
34
+ )
35
+ return calls
36
+
37
+
38
+ def extract_python_imports(source: str, file_path: str = "") -> List[ImportReference]:
39
+ """Extract all import statements from Python source."""
40
+ if not source.strip():
41
+ return []
42
+
43
+ try:
44
+ tree = ast.parse(source)
45
+ except SyntaxError:
46
+ return []
47
+
48
+ imports: List[ImportReference] = []
49
+ for node in ast.walk(tree):
50
+ if isinstance(node, ast.Import):
51
+ for alias in node.names:
52
+ imports.append(
53
+ ImportReference(
54
+ module=None,
55
+ name=alias.name,
56
+ asname=alias.asname,
57
+ lineno=getattr(node, "lineno", 0),
58
+ file_path=file_path,
59
+ level=0,
60
+ )
61
+ )
62
+ elif isinstance(node, ast.ImportFrom):
63
+ for alias in node.names:
64
+ imports.append(
65
+ ImportReference(
66
+ module=node.module,
67
+ name=alias.name,
68
+ asname=alias.asname,
69
+ lineno=getattr(node, "lineno", 0),
70
+ file_path=file_path,
71
+ level=node.level,
72
+ )
73
+ )
74
+ return imports
75
+
76
+
77
+ def extract_python_symbols(source: str, file_path: str = "") -> List[Symbol]:
78
+ """Parse Python source into AST and extract symbol entities with detailed metadata."""
79
+ if not source.strip():
80
+ return []
81
+
82
+ try:
83
+ tree = ast.parse(source)
84
+ except SyntaxError:
85
+ return []
86
+
87
+ symbols: List[Symbol] = []
88
+
89
+ # Check for module-level __all__
90
+ all_names: Optional[Set[str]] = None
91
+
92
+ def _extract_all_elts(val_node: ast.AST) -> Set[str]:
93
+ if isinstance(val_node, (ast.List, ast.Tuple, ast.Set)):
94
+ return {
95
+ elt.value for elt in val_node.elts
96
+ if isinstance(elt, ast.Constant) and isinstance(elt.value, str)
97
+ }
98
+ return set()
99
+
100
+ for stmt in tree.body:
101
+ if isinstance(stmt, ast.Assign):
102
+ for target in stmt.targets:
103
+ if isinstance(target, ast.Name) and target.id == "__all__":
104
+ elts = _extract_all_elts(stmt.value)
105
+ all_names = elts if all_names is None else (all_names | elts)
106
+ elif isinstance(stmt, ast.AnnAssign):
107
+ if isinstance(stmt.target, ast.Name) and stmt.target.id == "__all__" and stmt.value:
108
+ elts = _extract_all_elts(stmt.value)
109
+ all_names = elts if all_names is None else (all_names | elts)
110
+ elif isinstance(stmt, ast.AugAssign):
111
+ if isinstance(stmt.target, ast.Name) and stmt.target.id == "__all__":
112
+ elts = _extract_all_elts(stmt.value)
113
+ all_names = elts if all_names is None else (all_names | elts)
114
+
115
+ def process_body(nodes: List[ast.stmt], parent_qualname: Optional[str] = None, is_parent_class: bool = False):
116
+ for node in nodes:
117
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
118
+ qualname = f"{parent_qualname}.{node.name}" if parent_qualname else node.name
119
+ is_static = any(
120
+ (isinstance(d, ast.Name) and d.id == "staticmethod")
121
+ or (isinstance(d, ast.Attribute) and d.attr == "staticmethod")
122
+ for d in node.decorator_list
123
+ )
124
+ is_method = is_parent_class and not is_static
125
+ kind = "method" if is_parent_class else ("async_function" if isinstance(node, ast.AsyncFunctionDef) else "function")
126
+
127
+ # Parameters extraction
128
+ args_node = node.args
129
+ posonly_names = {a.arg for a in args_node.posonlyargs}
130
+ pos_args_nodes = args_node.posonlyargs + args_node.args
131
+ num_defaults = len(args_node.defaults)
132
+ defaults_offset = len(pos_args_nodes) - num_defaults
133
+
134
+ params: List[Parameter] = []
135
+ for i, arg in enumerate(pos_args_nodes):
136
+ default_str = None
137
+ has_default = False
138
+ if i >= defaults_offset:
139
+ default_node = args_node.defaults[i - defaults_offset]
140
+ default_str = ast.unparse(default_node)
141
+ has_default = True
142
+ annotation_str = ast.unparse(arg.annotation) if arg.annotation else None
143
+ params.append(
144
+ Parameter(
145
+ name=arg.arg,
146
+ annotation=annotation_str,
147
+ default=default_str,
148
+ has_default=has_default,
149
+ is_posonly=(arg.arg in posonly_names),
150
+ )
151
+ )
152
+
153
+ # Keyword-only parameters
154
+ for i, kwarg in enumerate(args_node.kwonlyargs):
155
+ kw_default = args_node.kw_defaults[i]
156
+ has_kw_default = kw_default is not None
157
+ kw_default_str = ast.unparse(kw_default) if has_kw_default else None
158
+ annotation_str = ast.unparse(kwarg.annotation) if kwarg.annotation else None
159
+ params.append(
160
+ Parameter(
161
+ name=kwarg.arg,
162
+ annotation=annotation_str,
163
+ default=kw_default_str,
164
+ has_default=has_kw_default,
165
+ is_kwonly=True,
166
+ )
167
+ )
168
+
169
+ # Varargs & kwargs
170
+ if args_node.vararg:
171
+ params.append(Parameter(name=args_node.vararg.arg, is_vararg=True))
172
+ if args_node.kwarg:
173
+ params.append(Parameter(name=args_node.kwarg.arg, is_kwarg=True))
174
+
175
+ min_args = len(pos_args_nodes) - num_defaults
176
+ max_args = None if args_node.vararg is not None else len(pos_args_nodes)
177
+
178
+ accepted_kwargs = None if args_node.kwarg is not None else {
179
+ p.name for p in params if not p.is_vararg and not p.is_kwarg and not p.is_posonly
180
+ }
181
+ required_kwargs = {
182
+ kwarg.arg for i, kwarg in enumerate(args_node.kwonlyargs)
183
+ if args_node.kw_defaults[i] is None
184
+ }
185
+
186
+ # Return annotation
187
+ return_type = ast.unparse(node.returns) if node.returns else None
188
+
189
+ # Signature representation
190
+ sig_params = []
191
+ for p in params:
192
+ p_str = p.name
193
+ if p.is_vararg:
194
+ p_str = f"*{p.name}"
195
+ elif p.is_kwarg:
196
+ p_str = f"**{p.name}"
197
+ if p.annotation:
198
+ p_str += f": {p.annotation}"
199
+ if p.has_default and p.default is not None:
200
+ p_str += f" = {p.default}"
201
+ sig_params.append(p_str)
202
+ ret_suffix = f" -> {return_type}" if return_type else ""
203
+ prefix = "async def" if isinstance(node, ast.AsyncFunctionDef) else "def"
204
+ signature = f"{prefix} {node.name}({', '.join(sig_params)}){ret_suffix}"
205
+
206
+ sym_id = f"{file_path}::{qualname}"
207
+ calls = _extract_calls_from_node(node, caller_id=sym_id)
208
+
209
+ # Docstring and export visibility
210
+ docstring = ast.get_docstring(node)
211
+ if parent_qualname and not is_parent_class:
212
+ is_exported = False
213
+ visibility = "private"
214
+ elif is_parent_class:
215
+ if node.name.startswith("__") and not node.name.endswith("__"):
216
+ visibility = "private"
217
+ is_exported = False
218
+ elif node.name.startswith("_"):
219
+ visibility = "internal"
220
+ is_exported = False
221
+ else:
222
+ visibility = "public"
223
+ is_exported = True
224
+ else:
225
+ if all_names is not None:
226
+ is_exported = node.name in all_names
227
+ visibility = "public" if is_exported else ("private" if node.name.startswith("__") else "internal")
228
+ else:
229
+ if node.name.startswith("__") and not node.name.endswith("__"):
230
+ visibility = "private"
231
+ is_exported = False
232
+ elif node.name.startswith("_"):
233
+ visibility = "internal"
234
+ is_exported = False
235
+ else:
236
+ visibility = "public"
237
+ is_exported = True
238
+
239
+ symbol = Symbol(
240
+ name=node.name,
241
+ qualname=qualname,
242
+ file_path=file_path,
243
+ kind=kind,
244
+ lineno=node.lineno,
245
+ end_lineno=node.end_lineno or node.lineno,
246
+ signature=signature,
247
+ params=params,
248
+ min_args=min_args,
249
+ max_args=max_args,
250
+ accepted_kwargs=accepted_kwargs,
251
+ required_kwargs=required_kwargs,
252
+ return_type=return_type,
253
+ calls=calls,
254
+ is_method=is_method,
255
+ is_static=is_static,
256
+ docstring=docstring,
257
+ is_exported=is_exported,
258
+ visibility=visibility,
259
+ )
260
+ symbols.append(symbol)
261
+ process_body(node.body, parent_qualname=qualname, is_parent_class=False)
262
+
263
+ elif isinstance(node, ast.ClassDef):
264
+ qualname = f"{parent_qualname}.{node.name}" if parent_qualname else node.name
265
+ bases_list = [ast.unparse(b) for b in node.bases]
266
+ bases_str = ", ".join(bases_list)
267
+ signature = f"class {node.name}({bases_str})" if bases_str else f"class {node.name}"
268
+ sym_id = f"{file_path}::{qualname}"
269
+ calls = _extract_calls_from_node(node, caller_id=sym_id)
270
+
271
+ docstring = ast.get_docstring(node)
272
+ if all_names is not None:
273
+ is_exported = node.name in all_names
274
+ visibility = "public" if is_exported else ("private" if node.name.startswith("__") else "internal")
275
+ else:
276
+ if node.name.startswith("__") and not node.name.endswith("__"):
277
+ visibility = "private"
278
+ is_exported = False
279
+ elif node.name.startswith("_"):
280
+ visibility = "internal"
281
+ is_exported = False
282
+ else:
283
+ visibility = "public"
284
+ is_exported = True
285
+
286
+ symbol = Symbol(
287
+ name=node.name,
288
+ qualname=qualname,
289
+ file_path=file_path,
290
+ kind="class",
291
+ lineno=node.lineno,
292
+ end_lineno=node.end_lineno or node.lineno,
293
+ signature=signature,
294
+ calls=calls,
295
+ bases=bases_list,
296
+ docstring=docstring,
297
+ is_exported=is_exported,
298
+ visibility=visibility,
299
+ )
300
+ symbols.append(symbol)
301
+ process_body(node.body, parent_qualname=qualname, is_parent_class=True)
302
+
303
+ process_body(tree.body)
304
+
305
+ module_calls = [
306
+ c for c in _extract_calls_from_node(tree, caller_id=f"{file_path}::<module>")
307
+ if not any(s.lineno <= c.lineno <= s.end_lineno for s in symbols)
308
+ ]
309
+ if module_calls:
310
+ line_count = len(source.splitlines()) or 1
311
+ module_doc = ast.get_docstring(tree)
312
+ module_sym = Symbol(
313
+ name="<module>",
314
+ qualname="<module>",
315
+ file_path=file_path,
316
+ kind="module",
317
+ lineno=1,
318
+ end_lineno=line_count,
319
+ signature=f"# module {file_path}",
320
+ calls=module_calls,
321
+ docstring=module_doc,
322
+ is_exported=True,
323
+ visibility="public",
324
+ )
325
+ symbols.append(module_sym)
326
+
327
+ return symbols
328
+
329
+
330
+ def validate_python_syntax(source: str, file_path: str = "") -> Optional[str]:
331
+ """Validate Python source syntax, returning an error message if invalid."""
332
+ try:
333
+ ast.parse(source)
334
+ return None
335
+ except SyntaxError as e:
336
+ return f"SyntaxError at line {e.lineno}:{e.offset}: {e.msg}"