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,232 @@
1
+ """
2
+ PERF002: N+1 Database and I/O in Loop Rule.
3
+ Detects database queries (query, execute, find, select) and network/HTTP calls
4
+ (fetch, get, post) inside loop bodies across Python, TypeScript, Go, and Rust.
5
+ """
6
+
7
+ import re
8
+ from typing import List, Optional, Set, Tuple
9
+ from tree_sitter import Node
10
+
11
+ from code_oracle.perf_lint.models import PerfDiagnostic, PerfRule, Severity
12
+
13
+ DB_IO_METHODS: Set[str] = {
14
+ "query",
15
+ "execute",
16
+ "exec",
17
+ "find",
18
+ "find_one",
19
+ "find_many",
20
+ "find_first",
21
+ "find_unique",
22
+ "find_by",
23
+ "select",
24
+ "select_all",
25
+ "fetch",
26
+ "fetchall",
27
+ "fetchone",
28
+ "fetchmany",
29
+ "raw_query",
30
+ "queryrow",
31
+ "query_row",
32
+ "post",
33
+ "upsert",
34
+ "save",
35
+ # Go context query and exec methods
36
+ "query_context",
37
+ "exec_context",
38
+ "query_row_context",
39
+ "querycontext",
40
+ "execcontext",
41
+ "queryrowcontext",
42
+ # Prisma / MongoDB camelCase variants (normalized and raw)
43
+ "findone",
44
+ "findmany",
45
+ "findunique",
46
+ "findfirst",
47
+ "findby",
48
+ }
49
+
50
+ HTTP_IO_PREFIXES: Tuple[str, ...] = (
51
+ "requests.",
52
+ "http.get",
53
+ "http.post",
54
+ "http.head",
55
+ "http.postform",
56
+ "http.do",
57
+ "client.get",
58
+ "client.post",
59
+ "client.do",
60
+ "axios.",
61
+ "reqwest::",
62
+ "urllib.request.",
63
+ "aiohttp.",
64
+ "httpx.",
65
+ "api.",
66
+ "session.",
67
+ "httpclient.",
68
+ "http_client.",
69
+ "service.",
70
+ "c.post",
71
+ "hc.post",
72
+ )
73
+
74
+ DIRECT_IO_FUNCS: Set[str] = {
75
+ "fetch",
76
+ "query",
77
+ "execute",
78
+ "select",
79
+ "urlopen",
80
+ }
81
+
82
+
83
+ DB_RECEIVER_KEYWORDS: Set[str] = {
84
+ "db",
85
+ "database",
86
+ "repo",
87
+ "repository",
88
+ "table",
89
+ "tables",
90
+ "model",
91
+ "models",
92
+ "conn",
93
+ "connection",
94
+ "cursor",
95
+ "collection",
96
+ "collections",
97
+ "dao",
98
+ "entity",
99
+ "entities",
100
+ "sql",
101
+ }
102
+
103
+ NETWORK_GET_RECEIVER_KEYWORDS: Set[str] = {
104
+ "http",
105
+ "client",
106
+ "request",
107
+ "requests",
108
+ "api",
109
+ "session",
110
+ "service",
111
+ "db",
112
+ "fetch",
113
+ "conn",
114
+ "connection",
115
+ "rest",
116
+ "remote",
117
+ }
118
+
119
+
120
+ def _camel_to_snake(s: str) -> str:
121
+ """Convert camelCase/PascalCase to snake_case."""
122
+ return re.sub(r"(?<=[a-z0-9])([A-Z])", r"_\1", s).lower()
123
+
124
+
125
+ def _receiver_has_keyword(receiver: str, keywords: Set[str]) -> bool:
126
+ """Check if receiver identifier contains any target keywords as distinct tokens."""
127
+ snake = _camel_to_snake(receiver)
128
+ tokens = set(re.split(r"[^a-z0-9]+", snake))
129
+ return bool(tokens & keywords)
130
+
131
+
132
+ def extract_method_name(callee_text: str) -> str:
133
+ """Extract the last identifier (method name) from a callee expression with camelCase normalization."""
134
+ clean = callee_text.strip().replace("\n", "")
135
+ parts = re.split(r"\.|::", clean)
136
+ if not parts:
137
+ return ""
138
+ last_part = parts[-1].strip()
139
+ match = re.search(r"^[a-zA-Z_][a-zA-Z0-9_]*", last_part)
140
+ raw = match.group(0) if match else last_part
141
+ return _camel_to_snake(raw)
142
+
143
+
144
+ class NPlusOneRule:
145
+ """Evaluates PERF002: N+1 I/O in Loop Bodies."""
146
+
147
+ RULE_ID = PerfRule.PERF002.value
148
+
149
+ @staticmethod
150
+ def is_io_call(callee_text: str) -> bool:
151
+ """Check if callee represents database query or network I/O."""
152
+ clean = callee_text.strip().replace("\n", "")
153
+ clean_lower = clean.lower()
154
+ method = extract_method_name(clean)
155
+ method_raw = ""
156
+ parts = re.split(r"\.|::", clean)
157
+ if parts:
158
+ m = re.search(r"^[a-zA-Z_][a-zA-Z0-9_]*", parts[-1].strip())
159
+ if m:
160
+ method_raw = m.group(0).lower()
161
+
162
+ if method in DB_IO_METHODS or method_raw in DB_IO_METHODS:
163
+ return True
164
+
165
+ if clean_lower in DIRECT_IO_FUNCS or method in DIRECT_IO_FUNCS:
166
+ return True
167
+
168
+ for prefix in HTTP_IO_PREFIXES:
169
+ if clean_lower.startswith(prefix) or f".{prefix}" in clean_lower:
170
+ return True
171
+
172
+ # Check for database write calls (update/insert/delete) with DB-related receiver
173
+ # to prevent false positives on set.update(), dict.update(), list.insert()
174
+ if method in ("update", "insert", "delete"):
175
+ if len(parts) >= 2:
176
+ receiver = parts[-2]
177
+ if _receiver_has_keyword(receiver, DB_RECEIVER_KEYWORDS):
178
+ return True
179
+
180
+ # Check for network/database .get(...) calls while preventing dict.get() false positives
181
+ if method == "get":
182
+ if len(parts) >= 2:
183
+ receiver = parts[-2]
184
+ if _receiver_has_keyword(receiver, NETWORK_GET_RECEIVER_KEYWORDS):
185
+ return True
186
+
187
+ return False
188
+
189
+ @classmethod
190
+ def check(
191
+ cls,
192
+ call_node: Node,
193
+ callee_text: str,
194
+ in_loop: bool,
195
+ file_path: str,
196
+ lines: List[str],
197
+ ) -> Optional[PerfDiagnostic]:
198
+ """
199
+ Evaluate if call_node represents an N+1 query inside a loop body.
200
+ """
201
+ if not in_loop or not cls.is_io_call(callee_text):
202
+ return None
203
+
204
+ lineno = call_node.start_point.row + 1
205
+ end_lineno = call_node.end_point.row + 1
206
+ col = call_node.start_point.column
207
+ end_col = call_node.end_point.column
208
+ ctx = lines[lineno - 1].strip() if 1 <= lineno <= len(lines) else None
209
+
210
+ msg = f"Possible N+1 query: I/O or database call '{callee_text}' detected inside loop"
211
+ return PerfDiagnostic(
212
+ rule_id=cls.RULE_ID,
213
+ message=msg,
214
+ severity=Severity.WARN,
215
+ file_path=file_path,
216
+ lineno=lineno,
217
+ end_lineno=end_lineno,
218
+ col_offset=col,
219
+ end_col_offset=end_col,
220
+ context_line=ctx,
221
+ )
222
+
223
+
224
+ def check_n_plus_one(
225
+ call_node: Node,
226
+ callee_text: str,
227
+ in_loop: bool,
228
+ file_path: str,
229
+ lines: List[str],
230
+ ) -> Optional[PerfDiagnostic]:
231
+ """Convenience helper for PERF002 evaluation."""
232
+ return NPlusOneRule.check(call_node, callee_text, in_loop, file_path, lines)
@@ -0,0 +1,137 @@
1
+ """
2
+ PERF001: Nested Loops Complexity Rule.
3
+ Detects nested loop depth >= 2 for O(N^2) warning, >= 3 for O(N^3) error
4
+ across Python, TypeScript/JavaScript, Go, and Rust.
5
+ """
6
+
7
+ from typing import Dict, List, Optional, Set
8
+ from tree_sitter import Node
9
+
10
+ from code_oracle.perf_lint.models import PerfDiagnostic, PerfRule, Severity
11
+
12
+ COMPREHENSION_NODE_TYPES: Set[str] = {
13
+ "list_comprehension",
14
+ "dictionary_comprehension",
15
+ "set_comprehension",
16
+ "generator_expression",
17
+ }
18
+
19
+ LOOP_NODE_TYPES: Dict[str, Set[str]] = {
20
+ "python": {
21
+ "for_statement",
22
+ "while_statement",
23
+ "list_comprehension",
24
+ "dictionary_comprehension",
25
+ "set_comprehension",
26
+ "generator_expression",
27
+ },
28
+ "typescript": {
29
+ "for_statement",
30
+ "for_in_statement",
31
+ "for_of_statement",
32
+ "while_statement",
33
+ "do_statement",
34
+ },
35
+ "javascript": {
36
+ "for_statement",
37
+ "for_in_statement",
38
+ "for_of_statement",
39
+ "while_statement",
40
+ "do_statement",
41
+ },
42
+ "go": {"for_statement"},
43
+ "rust": {"for_expression", "while_expression", "loop_expression"},
44
+ }
45
+
46
+ FUNCTION_NODE_TYPES: Dict[str, Set[str]] = {
47
+ "python": {"function_definition"},
48
+ "typescript": {
49
+ "function_declaration",
50
+ "function_expression",
51
+ "arrow_function",
52
+ "method_definition",
53
+ "generator_function_declaration",
54
+ },
55
+ "javascript": {
56
+ "function_declaration",
57
+ "function_expression",
58
+ "arrow_function",
59
+ "method_definition",
60
+ "generator_function_declaration",
61
+ },
62
+ "go": {"function_declaration", "method_declaration", "func_literal"},
63
+ "rust": {"function_item", "closure_expression"},
64
+ }
65
+
66
+
67
+ class NestedLoopsRule:
68
+ """Evaluates PERF001: Loop Complexity Escalation."""
69
+
70
+ RULE_ID = PerfRule.PERF001.value
71
+
72
+ @staticmethod
73
+ def is_loop_node(node: Node, language: str) -> bool:
74
+ """Check if AST node is a loop in target language."""
75
+ return node.type in LOOP_NODE_TYPES.get(language, set())
76
+
77
+ @staticmethod
78
+ def get_loop_clauses(node: Node, language: str) -> List[Node]:
79
+ """Return loop clauses for compound loops like Python comprehensions."""
80
+ if language == "python" and node.type in COMPREHENSION_NODE_TYPES:
81
+ clauses = [c for c in node.children if c.type == "for_in_clause"]
82
+ return clauses if clauses else [node]
83
+ return [node]
84
+
85
+ @staticmethod
86
+ def is_function_boundary(node: Node, language: str) -> bool:
87
+ """Check if AST node defines a new function boundary that resets loop depth."""
88
+ return node.type in FUNCTION_NODE_TYPES.get(language, set())
89
+
90
+ @classmethod
91
+ def check(
92
+ cls,
93
+ node: Node,
94
+ depth: int,
95
+ max_depth: int,
96
+ file_path: str,
97
+ lines: List[str],
98
+ ) -> Optional[PerfDiagnostic]:
99
+ """
100
+ Evaluate node at given loop depth against max_depth threshold.
101
+ Returns PerfDiagnostic if depth >= 2 and depth >= max_depth.
102
+ """
103
+ if depth < 2 or depth < max_depth:
104
+ return None
105
+
106
+ lineno = node.start_point.row + 1
107
+ end_lineno = node.end_point.row + 1
108
+ col = node.start_point.column
109
+ end_col = node.end_point.column
110
+
111
+ severity = Severity.WARN if depth == 2 else Severity.ERROR
112
+ complexity = f"O(N^{depth})"
113
+ msg = f"Nested loop complexity {complexity} detected at depth {depth}"
114
+ ctx = lines[lineno - 1].strip() if 1 <= lineno <= len(lines) else None
115
+
116
+ return PerfDiagnostic(
117
+ rule_id=cls.RULE_ID,
118
+ message=msg,
119
+ severity=severity,
120
+ file_path=file_path,
121
+ lineno=lineno,
122
+ end_lineno=end_lineno,
123
+ col_offset=col,
124
+ end_col_offset=end_col,
125
+ context_line=ctx,
126
+ )
127
+
128
+
129
+ def check_nested_loop(
130
+ node: Node,
131
+ depth: int,
132
+ max_depth: int,
133
+ file_path: str,
134
+ lines: List[str],
135
+ ) -> Optional[PerfDiagnostic]:
136
+ """Convenience helper for PERF001 evaluation."""
137
+ return NestedLoopsRule.check(node, depth, max_depth, file_path, lines)