code-compass-cli 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.
@@ -0,0 +1 @@
1
+ """Code quality analysis module."""
@@ -0,0 +1,300 @@
1
+ """Code quality analyzer for detecting security, performance, and code smell issues."""
2
+
3
+ import os
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Dict, List, Any, Optional
7
+ from dataclasses import dataclass
8
+
9
+
10
+ @dataclass
11
+ class Issue:
12
+ """Represents a detected code issue."""
13
+
14
+ file: str
15
+ line: int
16
+ severity: str # "critical", "warning", "info"
17
+ category: str # "security", "performance", "code_smell"
18
+ message: str
19
+ suggestion: str
20
+ code_snippet: str
21
+
22
+
23
+ class CodeAnalyzer:
24
+ """Analyzes code for security issues, performance problems, and code smells."""
25
+
26
+ def __init__(self, repo_path: str = "."):
27
+ """Initialize the code analyzer.
28
+
29
+ Args:
30
+ repo_path: Path to repository to analyze
31
+ """
32
+ self.repo_path = Path(repo_path)
33
+ self.issues: List[Issue] = []
34
+
35
+ def analyze(self) -> Dict[str, Any]:
36
+ """Analyze the codebase for issues.
37
+
38
+ Returns:
39
+ Dictionary with analysis results organized by severity
40
+ """
41
+ self.issues = []
42
+
43
+ # Scan all Python files
44
+ for root, _, files in os.walk(self.repo_path):
45
+ # Skip venv and hidden directories
46
+ if "venv" in root or "/.git" in root:
47
+ continue
48
+
49
+ for file in files:
50
+ if file.endswith(".py"):
51
+ filepath = Path(root) / file
52
+ rel_path = filepath.relative_to(self.repo_path)
53
+
54
+ try:
55
+ with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
56
+ content = f.read()
57
+ self._analyze_file(content, str(rel_path))
58
+ except (IOError, OSError):
59
+ continue
60
+
61
+ # Organize by severity
62
+ critical = [i for i in self.issues if i.severity == "critical"]
63
+ warnings = [i for i in self.issues if i.severity == "warning"]
64
+ info = [i for i in self.issues if i.severity == "info"]
65
+
66
+ return {
67
+ "total": len(self.issues),
68
+ "critical": critical,
69
+ "warning": warnings,
70
+ "info": info,
71
+ "by_category": self._group_by_category(),
72
+ }
73
+
74
+ def _analyze_file(self, content: str, filepath: str) -> None:
75
+ """Analyze a single file for issues.
76
+
77
+ Args:
78
+ content: File content
79
+ filepath: Path to the file
80
+ """
81
+ self._check_security(content, filepath)
82
+ self._check_performance(content, filepath)
83
+ self._check_code_smells(content, filepath)
84
+
85
+ def _check_security(self, content: str, filepath: str) -> None:
86
+ """Check for security issues.
87
+
88
+ Args:
89
+ content: File content
90
+ filepath: Path to the file
91
+ """
92
+ lines = content.split("\n")
93
+
94
+ # Check for SQL injection patterns
95
+ sql_patterns = [
96
+ (r'(?:query|execute|sql)\s*\(\s*["\'].*%s', "Potential SQL injection - use parameterized queries"),
97
+ (r'(?:query|execute|sql)\s*\(\s*f["\'].*{', "Potential SQL injection - f-strings are vulnerable"),
98
+ (r'\.format\s*\(.*\)\s*for\s+(?:query|execute)', "SQL string formatting detected"),
99
+ ]
100
+
101
+ for i, line in enumerate(lines, 1):
102
+ for pattern, msg in sql_patterns:
103
+ if re.search(pattern, line, re.IGNORECASE):
104
+ self.issues.append(Issue(
105
+ file=filepath,
106
+ line=i,
107
+ severity="critical",
108
+ category="security",
109
+ message=f"SQL Injection Risk: {msg}",
110
+ suggestion="Use parameterized queries/prepared statements",
111
+ code_snippet=line.strip()[:80],
112
+ ))
113
+
114
+ # Check for exposed secrets/API keys
115
+ secret_patterns = [
116
+ (r'(?:api[_-]?key|secret|password|token|auth)\s*=\s*["\'][^"\']*["\']', "Hardcoded secret/API key"),
117
+ (r'(?:sk_|pk_|Bearer\s+)[A-Za-z0-9\-_]+', "Exposed API key or token"),
118
+ (r'https?://\w+:\w+@', "Credentials in URL"),
119
+ ]
120
+
121
+ for i, line in enumerate(lines, 1):
122
+ # Skip if line is commented
123
+ if line.strip().startswith("#"):
124
+ continue
125
+
126
+ for pattern, msg in secret_patterns:
127
+ if re.search(pattern, line, re.IGNORECASE):
128
+ self.issues.append(Issue(
129
+ file=filepath,
130
+ line=i,
131
+ severity="critical",
132
+ category="security",
133
+ message=f"Exposed Secret: {msg}",
134
+ suggestion="Move secrets to environment variables or configuration files",
135
+ code_snippet=line.strip()[:80],
136
+ ))
137
+
138
+ # Check for XSS vulnerabilities
139
+ xss_patterns = [
140
+ (r'\.innerHTML\s*=\s*(?!.*escape)', "Direct HTML assignment - XSS risk"),
141
+ (r'(?:html|render)\s*\(.*\+.*(?:user_input|request\.args|request\.form)', "User input in HTML - XSS risk"),
142
+ ]
143
+
144
+ for i, line in enumerate(lines, 1):
145
+ for pattern, msg in xss_patterns:
146
+ if re.search(pattern, line, re.IGNORECASE):
147
+ self.issues.append(Issue(
148
+ file=filepath,
149
+ line=i,
150
+ severity="warning",
151
+ category="security",
152
+ message=f"XSS Vulnerability: {msg}",
153
+ suggestion="Escape/sanitize all user input before rendering",
154
+ code_snippet=line.strip()[:80],
155
+ ))
156
+
157
+ def _check_performance(self, content: str, filepath: str) -> None:
158
+ """Check for performance issues.
159
+
160
+ Args:
161
+ content: File content
162
+ filepath: Path to the file
163
+ """
164
+ lines = content.split("\n")
165
+
166
+ # Check for N+1 query patterns
167
+ for i in range(len(lines) - 1):
168
+ line = lines[i]
169
+
170
+ # Look for loops with potential queries inside
171
+ if re.match(r'^\s*for\s+\w+\s+in\s+', line):
172
+ # Check next few lines for query patterns
173
+ for j in range(i + 1, min(i + 5, len(lines))):
174
+ if re.search(r'(?:query|execute|filter|get|fetch)\s*\(', lines[j]):
175
+ self.issues.append(Issue(
176
+ file=filepath,
177
+ line=i + 1,
178
+ severity="warning",
179
+ category="performance",
180
+ message="Potential N+1 Query Problem",
181
+ suggestion="Consider using batch queries or joins instead of looping",
182
+ code_snippet=line.strip()[:80],
183
+ ))
184
+ break
185
+
186
+ # Check for inefficient loop patterns
187
+ for i, line in enumerate(lines, 1):
188
+ # Nested loops
189
+ if re.match(r'^\s{8,}for\s+', line): # Deeply indented for loop
190
+ for j in range(max(0, i - 5), i):
191
+ if re.match(r'^\s{4,8}for\s+', lines[j - 1]):
192
+ self.issues.append(Issue(
193
+ file=filepath,
194
+ line=i,
195
+ severity="info",
196
+ category="performance",
197
+ message="Nested Loop Detected",
198
+ suggestion="Consider using list comprehension or optimization",
199
+ code_snippet=line.strip()[:80],
200
+ ))
201
+ break
202
+
203
+ def _check_code_smells(self, content: str, filepath: str) -> None:
204
+ """Check for code smells.
205
+
206
+ Args:
207
+ content: File content
208
+ filepath: Path to the file
209
+ """
210
+ lines = content.split("\n")
211
+
212
+ # Check for long functions (more than 30 lines)
213
+ current_func = None
214
+ func_start = 0
215
+
216
+ for i, line in enumerate(lines, 1):
217
+ # Find function definitions
218
+ func_match = re.match(r"^\s*def\s+(\w+)\s*\(", line)
219
+ if func_match:
220
+ if current_func and i - func_start > 30:
221
+ self.issues.append(Issue(
222
+ file=filepath,
223
+ line=func_start,
224
+ severity="info",
225
+ category="code_smell",
226
+ message=f"Function '{current_func}' is too long ({i - func_start} lines)",
227
+ suggestion="Consider breaking this function into smaller, more focused functions",
228
+ code_snippet=f"def {current_func}(...)",
229
+ ))
230
+
231
+ current_func = func_match.group(1)
232
+ func_start = i
233
+
234
+ # Check final function
235
+ if current_func and len(lines) - func_start > 30:
236
+ self.issues.append(Issue(
237
+ file=filepath,
238
+ line=func_start,
239
+ severity="info",
240
+ category="code_smell",
241
+ message=f"Function '{current_func}' is too long ({len(lines) - func_start} lines)",
242
+ suggestion="Consider breaking this function into smaller, more focused functions",
243
+ code_snippet=f"def {current_func}(...)",
244
+ ))
245
+
246
+ # Check for duplicate code patterns (simple heuristic)
247
+ code_lines = [line.strip() for line in lines if line.strip() and not line.strip().startswith("#")]
248
+ seen = {}
249
+
250
+ for i, line in enumerate(code_lines):
251
+ # Only check substantial lines
252
+ if len(line) > 20:
253
+ if line in seen:
254
+ # Found duplicate code
255
+ if i - seen[line] > 5: # Not too close
256
+ self.issues.append(Issue(
257
+ file=filepath,
258
+ line=i + 1,
259
+ severity="info",
260
+ category="code_smell",
261
+ message="Duplicate Code Detected",
262
+ suggestion="Extract this logic into a reusable function",
263
+ code_snippet=line[:80],
264
+ ))
265
+ else:
266
+ seen[line] = i
267
+
268
+ # Check for large classes (more than 50 lines)
269
+ current_class = None
270
+ class_start = 0
271
+
272
+ for i, line in enumerate(lines, 1):
273
+ class_match = re.match(r"^\s*class\s+(\w+)\s*[\(:]", line)
274
+ if class_match:
275
+ if current_class and i - class_start > 50:
276
+ self.issues.append(Issue(
277
+ file=filepath,
278
+ line=class_start,
279
+ severity="info",
280
+ category="code_smell",
281
+ message=f"Class '{current_class}' is too large ({i - class_start} lines)",
282
+ suggestion="Consider breaking this class using composition or inheritance",
283
+ code_snippet=f"class {current_class}:",
284
+ ))
285
+
286
+ current_class = class_match.group(1)
287
+ class_start = i
288
+
289
+ def _group_by_category(self) -> Dict[str, List[Issue]]:
290
+ """Group issues by category.
291
+
292
+ Returns:
293
+ Dictionary of issues grouped by category
294
+ """
295
+ grouped = {}
296
+ for issue in self.issues:
297
+ if issue.category not in grouped:
298
+ grouped[issue.category] = []
299
+ grouped[issue.category].append(issue)
300
+ return grouped
src/query/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Query module for code analysis."""