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.
- code_oracle/__init__.py +30 -0
- code_oracle/cli.py +795 -0
- code_oracle/config.py +145 -0
- code_oracle/dataset.py +5325 -0
- code_oracle/dead_code/__init__.py +32 -0
- code_oracle/dead_code/detector.py +379 -0
- code_oracle/dead_code/entrypoints.py +333 -0
- code_oracle/dead_code/models.py +255 -0
- code_oracle/dead_code/semantics.py +416 -0
- code_oracle/decision.py +906 -0
- code_oracle/engine.py +430 -0
- code_oracle/export_onnx.py +436 -0
- code_oracle/hook.py +531 -0
- code_oracle/indexer.py +894 -0
- code_oracle/languages/__init__.py +114 -0
- code_oracle/languages/common.py +127 -0
- code_oracle/languages/go.py +395 -0
- code_oracle/languages/python.py +336 -0
- code_oracle/languages/rust.py +474 -0
- code_oracle/languages/typescript.py +775 -0
- code_oracle/linearizer.py +166 -0
- code_oracle/locator.py +301 -0
- code_oracle/models.py +237 -0
- code_oracle/perf_lint/__init__.py +38 -0
- code_oracle/perf_lint/engine.py +234 -0
- code_oracle/perf_lint/models.py +229 -0
- code_oracle/perf_lint/rules/__init__.py +31 -0
- code_oracle/perf_lint/rules/async_blocking.py +143 -0
- code_oracle/perf_lint/rules/n_plus_one.py +232 -0
- code_oracle/perf_lint/rules/nested_loops.py +137 -0
- code_oracle/perf_lint/rules/unclosed_res.py +494 -0
- code_oracle/perf_lint/visitor.py +299 -0
- code_oracle/server.py +184 -0
- code_oracle/slicer.py +225 -0
- code_oracle/symbolic.py +459 -0
- code_oracle-0.1.0.dist-info/METADATA +225 -0
- code_oracle-0.1.0.dist-info/RECORD +40 -0
- code_oracle-0.1.0.dist-info/WHEEL +4 -0
- code_oracle-0.1.0.dist-info/entry_points.txt +2 -0
- 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)
|