amd-gaia 0.14.3__py3-none-any.whl → 0.15.1__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.
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/METADATA +223 -223
- amd_gaia-0.15.1.dist-info/RECORD +178 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/entry_points.txt +1 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/licenses/LICENSE.md +20 -20
- gaia/__init__.py +29 -29
- gaia/agents/__init__.py +19 -19
- gaia/agents/base/__init__.py +9 -9
- gaia/agents/base/agent.py +2177 -2177
- gaia/agents/base/api_agent.py +120 -120
- gaia/agents/base/console.py +1841 -1841
- gaia/agents/base/errors.py +237 -237
- gaia/agents/base/mcp_agent.py +86 -86
- gaia/agents/base/tools.py +83 -83
- gaia/agents/blender/agent.py +556 -556
- gaia/agents/blender/agent_simple.py +133 -135
- gaia/agents/blender/app.py +211 -211
- gaia/agents/blender/app_simple.py +41 -41
- gaia/agents/blender/core/__init__.py +16 -16
- gaia/agents/blender/core/materials.py +506 -506
- gaia/agents/blender/core/objects.py +316 -316
- gaia/agents/blender/core/rendering.py +225 -225
- gaia/agents/blender/core/scene.py +220 -220
- gaia/agents/blender/core/view.py +146 -146
- gaia/agents/chat/__init__.py +9 -9
- gaia/agents/chat/agent.py +835 -835
- gaia/agents/chat/app.py +1058 -1058
- gaia/agents/chat/session.py +508 -508
- gaia/agents/chat/tools/__init__.py +15 -15
- gaia/agents/chat/tools/file_tools.py +96 -96
- gaia/agents/chat/tools/rag_tools.py +1729 -1729
- gaia/agents/chat/tools/shell_tools.py +436 -436
- gaia/agents/code/__init__.py +7 -7
- gaia/agents/code/agent.py +549 -549
- gaia/agents/code/cli.py +377 -0
- gaia/agents/code/models.py +135 -135
- gaia/agents/code/orchestration/__init__.py +24 -24
- gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
- gaia/agents/code/orchestration/checklist_generator.py +713 -713
- gaia/agents/code/orchestration/factories/__init__.py +9 -9
- gaia/agents/code/orchestration/factories/base.py +63 -63
- gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
- gaia/agents/code/orchestration/factories/python_factory.py +106 -106
- gaia/agents/code/orchestration/orchestrator.py +841 -841
- gaia/agents/code/orchestration/project_analyzer.py +391 -391
- gaia/agents/code/orchestration/steps/__init__.py +67 -67
- gaia/agents/code/orchestration/steps/base.py +188 -188
- gaia/agents/code/orchestration/steps/error_handler.py +314 -314
- gaia/agents/code/orchestration/steps/nextjs.py +828 -828
- gaia/agents/code/orchestration/steps/python.py +307 -307
- gaia/agents/code/orchestration/template_catalog.py +469 -469
- gaia/agents/code/orchestration/workflows/__init__.py +14 -14
- gaia/agents/code/orchestration/workflows/base.py +80 -80
- gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
- gaia/agents/code/orchestration/workflows/python.py +94 -94
- gaia/agents/code/prompts/__init__.py +11 -11
- gaia/agents/code/prompts/base_prompt.py +77 -77
- gaia/agents/code/prompts/code_patterns.py +2036 -2036
- gaia/agents/code/prompts/nextjs_prompt.py +40 -40
- gaia/agents/code/prompts/python_prompt.py +109 -109
- gaia/agents/code/schema_inference.py +365 -365
- gaia/agents/code/system_prompt.py +41 -41
- gaia/agents/code/tools/__init__.py +42 -42
- gaia/agents/code/tools/cli_tools.py +1138 -1138
- gaia/agents/code/tools/code_formatting.py +319 -319
- gaia/agents/code/tools/code_tools.py +769 -769
- gaia/agents/code/tools/error_fixing.py +1347 -1347
- gaia/agents/code/tools/external_tools.py +180 -180
- gaia/agents/code/tools/file_io.py +845 -845
- gaia/agents/code/tools/prisma_tools.py +190 -190
- gaia/agents/code/tools/project_management.py +1016 -1016
- gaia/agents/code/tools/testing.py +321 -321
- gaia/agents/code/tools/typescript_tools.py +122 -122
- gaia/agents/code/tools/validation_parsing.py +461 -461
- gaia/agents/code/tools/validation_tools.py +806 -806
- gaia/agents/code/tools/web_dev_tools.py +1758 -1758
- gaia/agents/code/validators/__init__.py +16 -16
- gaia/agents/code/validators/antipattern_checker.py +241 -241
- gaia/agents/code/validators/ast_analyzer.py +197 -197
- gaia/agents/code/validators/requirements_validator.py +145 -145
- gaia/agents/code/validators/syntax_validator.py +171 -171
- gaia/agents/docker/__init__.py +7 -7
- gaia/agents/docker/agent.py +642 -642
- gaia/agents/emr/__init__.py +8 -8
- gaia/agents/emr/agent.py +1506 -1506
- gaia/agents/emr/cli.py +1322 -1322
- gaia/agents/emr/constants.py +475 -475
- gaia/agents/emr/dashboard/__init__.py +4 -4
- gaia/agents/emr/dashboard/server.py +1974 -1974
- gaia/agents/jira/__init__.py +11 -11
- gaia/agents/jira/agent.py +894 -894
- gaia/agents/jira/jql_templates.py +299 -299
- gaia/agents/routing/__init__.py +7 -7
- gaia/agents/routing/agent.py +567 -570
- gaia/agents/routing/system_prompt.py +75 -75
- gaia/agents/summarize/__init__.py +11 -0
- gaia/agents/summarize/agent.py +885 -0
- gaia/agents/summarize/prompts.py +129 -0
- gaia/api/__init__.py +23 -23
- gaia/api/agent_registry.py +238 -238
- gaia/api/app.py +305 -305
- gaia/api/openai_server.py +575 -575
- gaia/api/schemas.py +186 -186
- gaia/api/sse_handler.py +373 -373
- gaia/apps/__init__.py +4 -4
- gaia/apps/llm/__init__.py +6 -6
- gaia/apps/llm/app.py +173 -169
- gaia/apps/summarize/app.py +116 -633
- gaia/apps/summarize/html_viewer.py +133 -133
- gaia/apps/summarize/pdf_formatter.py +284 -284
- gaia/audio/__init__.py +2 -2
- gaia/audio/audio_client.py +439 -439
- gaia/audio/audio_recorder.py +269 -269
- gaia/audio/kokoro_tts.py +599 -599
- gaia/audio/whisper_asr.py +432 -432
- gaia/chat/__init__.py +16 -16
- gaia/chat/app.py +430 -430
- gaia/chat/prompts.py +522 -522
- gaia/chat/sdk.py +1228 -1225
- gaia/cli.py +5481 -5621
- gaia/database/__init__.py +10 -10
- gaia/database/agent.py +176 -176
- gaia/database/mixin.py +290 -290
- gaia/database/testing.py +64 -64
- gaia/eval/batch_experiment.py +2332 -2332
- gaia/eval/claude.py +542 -542
- gaia/eval/config.py +37 -37
- gaia/eval/email_generator.py +512 -512
- gaia/eval/eval.py +3179 -3179
- gaia/eval/groundtruth.py +1130 -1130
- gaia/eval/transcript_generator.py +582 -582
- gaia/eval/webapp/README.md +167 -167
- gaia/eval/webapp/package-lock.json +875 -875
- gaia/eval/webapp/package.json +20 -20
- gaia/eval/webapp/public/app.js +3402 -3402
- gaia/eval/webapp/public/index.html +87 -87
- gaia/eval/webapp/public/styles.css +3661 -3661
- gaia/eval/webapp/server.js +415 -415
- gaia/eval/webapp/test-setup.js +72 -72
- gaia/llm/__init__.py +9 -2
- gaia/llm/base_client.py +60 -0
- gaia/llm/exceptions.py +12 -0
- gaia/llm/factory.py +70 -0
- gaia/llm/lemonade_client.py +3236 -3221
- gaia/llm/lemonade_manager.py +294 -294
- gaia/llm/providers/__init__.py +9 -0
- gaia/llm/providers/claude.py +108 -0
- gaia/llm/providers/lemonade.py +120 -0
- gaia/llm/providers/openai_provider.py +79 -0
- gaia/llm/vlm_client.py +382 -382
- gaia/logger.py +189 -189
- gaia/mcp/agent_mcp_server.py +245 -245
- gaia/mcp/blender_mcp_client.py +138 -138
- gaia/mcp/blender_mcp_server.py +648 -648
- gaia/mcp/context7_cache.py +332 -332
- gaia/mcp/external_services.py +518 -518
- gaia/mcp/mcp_bridge.py +811 -550
- gaia/mcp/servers/__init__.py +6 -6
- gaia/mcp/servers/docker_mcp.py +83 -83
- gaia/perf_analysis.py +361 -0
- gaia/rag/__init__.py +10 -10
- gaia/rag/app.py +293 -293
- gaia/rag/demo.py +304 -304
- gaia/rag/pdf_utils.py +235 -235
- gaia/rag/sdk.py +2194 -2194
- gaia/security.py +163 -163
- gaia/talk/app.py +289 -289
- gaia/talk/sdk.py +538 -538
- gaia/testing/__init__.py +87 -87
- gaia/testing/assertions.py +330 -330
- gaia/testing/fixtures.py +333 -333
- gaia/testing/mocks.py +493 -493
- gaia/util.py +46 -46
- gaia/utils/__init__.py +33 -33
- gaia/utils/file_watcher.py +675 -675
- gaia/utils/parsing.py +223 -223
- gaia/version.py +100 -100
- amd_gaia-0.14.3.dist-info/RECORD +0 -168
- gaia/agents/code/app.py +0 -266
- gaia/llm/llm_client.py +0 -729
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/WHEEL +0 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/top_level.txt +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# Copyright(C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
|
|
3
|
-
# SPDX-License-Identifier: MIT
|
|
4
|
-
"""Validation and analysis modules for the Code Agent."""
|
|
5
|
-
|
|
6
|
-
from .antipattern_checker import AntipatternChecker
|
|
7
|
-
from .ast_analyzer import ASTAnalyzer
|
|
8
|
-
from .requirements_validator import RequirementsValidator
|
|
9
|
-
from .syntax_validator import SyntaxValidator
|
|
10
|
-
|
|
11
|
-
__all__ = [
|
|
12
|
-
"SyntaxValidator",
|
|
13
|
-
"AntipatternChecker",
|
|
14
|
-
"RequirementsValidator",
|
|
15
|
-
"ASTAnalyzer",
|
|
16
|
-
]
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# Copyright(C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""Validation and analysis modules for the Code Agent."""
|
|
5
|
+
|
|
6
|
+
from .antipattern_checker import AntipatternChecker
|
|
7
|
+
from .ast_analyzer import ASTAnalyzer
|
|
8
|
+
from .requirements_validator import RequirementsValidator
|
|
9
|
+
from .syntax_validator import SyntaxValidator
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"SyntaxValidator",
|
|
13
|
+
"AntipatternChecker",
|
|
14
|
+
"RequirementsValidator",
|
|
15
|
+
"ASTAnalyzer",
|
|
16
|
+
]
|
|
@@ -1,241 +1,241 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# Copyright(C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
|
|
3
|
-
# SPDX-License-Identifier: MIT
|
|
4
|
-
"""Anti-pattern detection for Python code."""
|
|
5
|
-
|
|
6
|
-
import ast
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import Any, Dict, List
|
|
9
|
-
|
|
10
|
-
# Code quality thresholds for anti-pattern detection
|
|
11
|
-
MAX_FUNCTION_NAME_LENGTH = 80
|
|
12
|
-
MAX_FUNCTION_NAME_LENGTH_WARNING = 40
|
|
13
|
-
MAX_CLASS_NAME_LENGTH = 30
|
|
14
|
-
MAX_COMBINATORIAL_NAMING_THRESHOLD = 3
|
|
15
|
-
MAX_FUNCTION_PARAMETERS = 6
|
|
16
|
-
MAX_FUNCTION_LINES = 50
|
|
17
|
-
MAX_FILE_LINES = 1000
|
|
18
|
-
MAX_UNDERSCORES_IN_NAME = 5
|
|
19
|
-
MAX_NESTING_DEPTH = 4
|
|
20
|
-
MAX_BRANCHES = 10
|
|
21
|
-
MAX_LOOPS = 3
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class AntipatternChecker:
|
|
25
|
-
"""Checks for combinatorial anti-patterns and code smells."""
|
|
26
|
-
|
|
27
|
-
def check(self, _file_path: Path, content: str) -> Dict[str, Any]:
|
|
28
|
-
"""Check for combinatorial anti-patterns.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
_file_path: Path to the file being checked (unused currently)
|
|
32
|
-
content: File content to analyze
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
Dictionary with errors and warnings found
|
|
36
|
-
"""
|
|
37
|
-
errors = []
|
|
38
|
-
warnings = []
|
|
39
|
-
|
|
40
|
-
try:
|
|
41
|
-
tree = ast.parse(content)
|
|
42
|
-
|
|
43
|
-
for node in ast.walk(tree):
|
|
44
|
-
if not isinstance(node, ast.FunctionDef):
|
|
45
|
-
continue
|
|
46
|
-
|
|
47
|
-
func_name = node.name
|
|
48
|
-
params = [
|
|
49
|
-
arg.arg for arg in node.args.args if arg.arg not in ("self", "cls")
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
# Check for excessive function name length
|
|
53
|
-
if len(func_name) > MAX_FUNCTION_NAME_LENGTH:
|
|
54
|
-
errors.append(
|
|
55
|
-
f"Line {node.lineno}: Function name {len(func_name)} chars: {func_name[:60]}..."
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
# Check for combinatorial naming
|
|
59
|
-
and_count = func_name.count("_and_")
|
|
60
|
-
by_count = func_name.count("_by_")
|
|
61
|
-
if (
|
|
62
|
-
and_count >= MAX_COMBINATORIAL_NAMING_THRESHOLD
|
|
63
|
-
or by_count >= MAX_COMBINATORIAL_NAMING_THRESHOLD
|
|
64
|
-
):
|
|
65
|
-
errors.append(
|
|
66
|
-
f"Line {node.lineno}: Combinatorial function with {and_count} 'and' and {by_count} 'by'"
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
# Check parameter count
|
|
70
|
-
if len(params) > MAX_FUNCTION_PARAMETERS:
|
|
71
|
-
warnings.append(
|
|
72
|
-
f"Line {node.lineno}: Function has {len(params)} parameters"
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
# Check function complexity (simple heuristic based on body size)
|
|
76
|
-
function_lines = 0
|
|
77
|
-
if hasattr(node, "end_lineno") and node.end_lineno is not None:
|
|
78
|
-
function_lines = node.end_lineno - node.lineno
|
|
79
|
-
if function_lines > MAX_FUNCTION_LINES:
|
|
80
|
-
warnings.append(
|
|
81
|
-
f"Line {node.lineno}: Function '{func_name}' is {function_lines} lines long (consider breaking it up)"
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
# Check for duplicate class definitions
|
|
85
|
-
class_names = [
|
|
86
|
-
node.name for node in ast.walk(tree) if isinstance(node, ast.ClassDef)
|
|
87
|
-
]
|
|
88
|
-
duplicates = [name for name in class_names if class_names.count(name) > 1]
|
|
89
|
-
if duplicates:
|
|
90
|
-
errors.append(
|
|
91
|
-
f"Duplicate class definitions: {', '.join(set(duplicates))}"
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
# Check for excessive file length
|
|
95
|
-
lines = content.split("\n")
|
|
96
|
-
if len(lines) > MAX_FILE_LINES:
|
|
97
|
-
warnings.append(
|
|
98
|
-
f"File has {len(lines)} lines (consider splitting into multiple modules)"
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
except SyntaxError:
|
|
102
|
-
pass # Let pylint handle syntax errors
|
|
103
|
-
|
|
104
|
-
return {"errors": errors, "warnings": warnings}
|
|
105
|
-
|
|
106
|
-
def check_dict(self, content: str) -> Dict[str, Any]:
|
|
107
|
-
"""Check for anti-patterns in code content (without file path).
|
|
108
|
-
|
|
109
|
-
Args:
|
|
110
|
-
content: Python code content to check
|
|
111
|
-
|
|
112
|
-
Returns:
|
|
113
|
-
Dictionary with errors and warnings found
|
|
114
|
-
"""
|
|
115
|
-
# Use a dummy path for checking content directly
|
|
116
|
-
return self.check(Path("dummy.py"), content)
|
|
117
|
-
|
|
118
|
-
def check_naming_patterns(self, tree: ast.Module) -> List[str]:
|
|
119
|
-
"""Check for problematic naming patterns.
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
tree: AST tree to analyze
|
|
123
|
-
|
|
124
|
-
Returns:
|
|
125
|
-
List of naming issues found
|
|
126
|
-
"""
|
|
127
|
-
issues = []
|
|
128
|
-
|
|
129
|
-
for node in ast.walk(tree):
|
|
130
|
-
if isinstance(node, ast.FunctionDef):
|
|
131
|
-
# Check for overly long names
|
|
132
|
-
if len(node.name) > MAX_FUNCTION_NAME_LENGTH_WARNING:
|
|
133
|
-
issues.append(
|
|
134
|
-
f"Function '{node.name[:MAX_FUNCTION_NAME_LENGTH_WARNING]}...' has excessively long name ({len(node.name)} chars)"
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
# Check for too many underscores (indicates poor design)
|
|
138
|
-
if node.name.count("_") > MAX_UNDERSCORES_IN_NAME:
|
|
139
|
-
issues.append(
|
|
140
|
-
f"Function '{node.name}' has too many underscores ({node.name.count('_')})"
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
elif isinstance(node, ast.ClassDef):
|
|
144
|
-
# Check class naming conventions
|
|
145
|
-
if not node.name[0].isupper():
|
|
146
|
-
issues.append(
|
|
147
|
-
f"Class '{node.name}' should start with uppercase letter"
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
# Check for overly long class names
|
|
151
|
-
if len(node.name) > MAX_CLASS_NAME_LENGTH:
|
|
152
|
-
issues.append(
|
|
153
|
-
f"Class '{node.name[:MAX_CLASS_NAME_LENGTH]}...' has excessively long name ({len(node.name)} chars)"
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
return issues
|
|
157
|
-
|
|
158
|
-
def check_function_complexity(self, node: ast.FunctionDef) -> List[str]:
|
|
159
|
-
"""Check function complexity metrics.
|
|
160
|
-
|
|
161
|
-
Args:
|
|
162
|
-
node: Function AST node to analyze
|
|
163
|
-
|
|
164
|
-
Returns:
|
|
165
|
-
List of complexity issues
|
|
166
|
-
"""
|
|
167
|
-
issues = []
|
|
168
|
-
|
|
169
|
-
# Count nested levels
|
|
170
|
-
max_depth = self._get_max_nesting_depth(node)
|
|
171
|
-
if max_depth > MAX_NESTING_DEPTH:
|
|
172
|
-
issues.append(f"Function has excessive nesting depth: {max_depth}")
|
|
173
|
-
|
|
174
|
-
# Count number of branches
|
|
175
|
-
branches = self._count_branches(node)
|
|
176
|
-
if branches > MAX_BRANCHES:
|
|
177
|
-
issues.append(f"Function has too many branches: {branches}")
|
|
178
|
-
|
|
179
|
-
# Count number of loops
|
|
180
|
-
loops = self._count_loops(node)
|
|
181
|
-
if loops > MAX_LOOPS:
|
|
182
|
-
issues.append(f"Function has too many loops: {loops}")
|
|
183
|
-
|
|
184
|
-
return issues
|
|
185
|
-
|
|
186
|
-
def _get_max_nesting_depth(self, node: ast.AST, current_depth: int = 0) -> int:
|
|
187
|
-
"""Calculate maximum nesting depth in a function.
|
|
188
|
-
|
|
189
|
-
Args:
|
|
190
|
-
node: AST node to analyze
|
|
191
|
-
current_depth: Current nesting level
|
|
192
|
-
|
|
193
|
-
Returns:
|
|
194
|
-
Maximum nesting depth found
|
|
195
|
-
"""
|
|
196
|
-
max_depth = current_depth
|
|
197
|
-
|
|
198
|
-
for child in ast.iter_child_nodes(node):
|
|
199
|
-
if isinstance(child, (ast.If, ast.For, ast.While, ast.With, ast.Try)):
|
|
200
|
-
child_depth = self._get_max_nesting_depth(child, current_depth + 1)
|
|
201
|
-
max_depth = max(max_depth, child_depth)
|
|
202
|
-
else:
|
|
203
|
-
child_depth = self._get_max_nesting_depth(child, current_depth)
|
|
204
|
-
max_depth = max(max_depth, child_depth)
|
|
205
|
-
|
|
206
|
-
return max_depth
|
|
207
|
-
|
|
208
|
-
def _count_branches(self, node: ast.AST) -> int:
|
|
209
|
-
"""Count number of branches in a function.
|
|
210
|
-
|
|
211
|
-
Args:
|
|
212
|
-
node: AST node to analyze
|
|
213
|
-
|
|
214
|
-
Returns:
|
|
215
|
-
Number of branches (if/elif/else)
|
|
216
|
-
"""
|
|
217
|
-
count = 0
|
|
218
|
-
for child in ast.walk(node):
|
|
219
|
-
if isinstance(child, (ast.If, ast.IfExp)):
|
|
220
|
-
count += 1
|
|
221
|
-
# Count elif branches
|
|
222
|
-
if isinstance(child, ast.If):
|
|
223
|
-
count += len(child.orelse) if isinstance(child.orelse, list) else 0
|
|
224
|
-
|
|
225
|
-
return count
|
|
226
|
-
|
|
227
|
-
def _count_loops(self, node: ast.AST) -> int:
|
|
228
|
-
"""Count number of loops in a function.
|
|
229
|
-
|
|
230
|
-
Args:
|
|
231
|
-
node: AST node to analyze
|
|
232
|
-
|
|
233
|
-
Returns:
|
|
234
|
-
Number of loops (for/while)
|
|
235
|
-
"""
|
|
236
|
-
count = 0
|
|
237
|
-
for child in ast.walk(node):
|
|
238
|
-
if isinstance(child, (ast.For, ast.While, ast.AsyncFor)):
|
|
239
|
-
count += 1
|
|
240
|
-
|
|
241
|
-
return count
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# Copyright(C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""Anti-pattern detection for Python code."""
|
|
5
|
+
|
|
6
|
+
import ast
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List
|
|
9
|
+
|
|
10
|
+
# Code quality thresholds for anti-pattern detection
|
|
11
|
+
MAX_FUNCTION_NAME_LENGTH = 80
|
|
12
|
+
MAX_FUNCTION_NAME_LENGTH_WARNING = 40
|
|
13
|
+
MAX_CLASS_NAME_LENGTH = 30
|
|
14
|
+
MAX_COMBINATORIAL_NAMING_THRESHOLD = 3
|
|
15
|
+
MAX_FUNCTION_PARAMETERS = 6
|
|
16
|
+
MAX_FUNCTION_LINES = 50
|
|
17
|
+
MAX_FILE_LINES = 1000
|
|
18
|
+
MAX_UNDERSCORES_IN_NAME = 5
|
|
19
|
+
MAX_NESTING_DEPTH = 4
|
|
20
|
+
MAX_BRANCHES = 10
|
|
21
|
+
MAX_LOOPS = 3
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AntipatternChecker:
|
|
25
|
+
"""Checks for combinatorial anti-patterns and code smells."""
|
|
26
|
+
|
|
27
|
+
def check(self, _file_path: Path, content: str) -> Dict[str, Any]:
|
|
28
|
+
"""Check for combinatorial anti-patterns.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
_file_path: Path to the file being checked (unused currently)
|
|
32
|
+
content: File content to analyze
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Dictionary with errors and warnings found
|
|
36
|
+
"""
|
|
37
|
+
errors = []
|
|
38
|
+
warnings = []
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
tree = ast.parse(content)
|
|
42
|
+
|
|
43
|
+
for node in ast.walk(tree):
|
|
44
|
+
if not isinstance(node, ast.FunctionDef):
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
func_name = node.name
|
|
48
|
+
params = [
|
|
49
|
+
arg.arg for arg in node.args.args if arg.arg not in ("self", "cls")
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
# Check for excessive function name length
|
|
53
|
+
if len(func_name) > MAX_FUNCTION_NAME_LENGTH:
|
|
54
|
+
errors.append(
|
|
55
|
+
f"Line {node.lineno}: Function name {len(func_name)} chars: {func_name[:60]}..."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Check for combinatorial naming
|
|
59
|
+
and_count = func_name.count("_and_")
|
|
60
|
+
by_count = func_name.count("_by_")
|
|
61
|
+
if (
|
|
62
|
+
and_count >= MAX_COMBINATORIAL_NAMING_THRESHOLD
|
|
63
|
+
or by_count >= MAX_COMBINATORIAL_NAMING_THRESHOLD
|
|
64
|
+
):
|
|
65
|
+
errors.append(
|
|
66
|
+
f"Line {node.lineno}: Combinatorial function with {and_count} 'and' and {by_count} 'by'"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Check parameter count
|
|
70
|
+
if len(params) > MAX_FUNCTION_PARAMETERS:
|
|
71
|
+
warnings.append(
|
|
72
|
+
f"Line {node.lineno}: Function has {len(params)} parameters"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Check function complexity (simple heuristic based on body size)
|
|
76
|
+
function_lines = 0
|
|
77
|
+
if hasattr(node, "end_lineno") and node.end_lineno is not None:
|
|
78
|
+
function_lines = node.end_lineno - node.lineno
|
|
79
|
+
if function_lines > MAX_FUNCTION_LINES:
|
|
80
|
+
warnings.append(
|
|
81
|
+
f"Line {node.lineno}: Function '{func_name}' is {function_lines} lines long (consider breaking it up)"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Check for duplicate class definitions
|
|
85
|
+
class_names = [
|
|
86
|
+
node.name for node in ast.walk(tree) if isinstance(node, ast.ClassDef)
|
|
87
|
+
]
|
|
88
|
+
duplicates = [name for name in class_names if class_names.count(name) > 1]
|
|
89
|
+
if duplicates:
|
|
90
|
+
errors.append(
|
|
91
|
+
f"Duplicate class definitions: {', '.join(set(duplicates))}"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Check for excessive file length
|
|
95
|
+
lines = content.split("\n")
|
|
96
|
+
if len(lines) > MAX_FILE_LINES:
|
|
97
|
+
warnings.append(
|
|
98
|
+
f"File has {len(lines)} lines (consider splitting into multiple modules)"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
except SyntaxError:
|
|
102
|
+
pass # Let pylint handle syntax errors
|
|
103
|
+
|
|
104
|
+
return {"errors": errors, "warnings": warnings}
|
|
105
|
+
|
|
106
|
+
def check_dict(self, content: str) -> Dict[str, Any]:
|
|
107
|
+
"""Check for anti-patterns in code content (without file path).
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
content: Python code content to check
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Dictionary with errors and warnings found
|
|
114
|
+
"""
|
|
115
|
+
# Use a dummy path for checking content directly
|
|
116
|
+
return self.check(Path("dummy.py"), content)
|
|
117
|
+
|
|
118
|
+
def check_naming_patterns(self, tree: ast.Module) -> List[str]:
|
|
119
|
+
"""Check for problematic naming patterns.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
tree: AST tree to analyze
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
List of naming issues found
|
|
126
|
+
"""
|
|
127
|
+
issues = []
|
|
128
|
+
|
|
129
|
+
for node in ast.walk(tree):
|
|
130
|
+
if isinstance(node, ast.FunctionDef):
|
|
131
|
+
# Check for overly long names
|
|
132
|
+
if len(node.name) > MAX_FUNCTION_NAME_LENGTH_WARNING:
|
|
133
|
+
issues.append(
|
|
134
|
+
f"Function '{node.name[:MAX_FUNCTION_NAME_LENGTH_WARNING]}...' has excessively long name ({len(node.name)} chars)"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Check for too many underscores (indicates poor design)
|
|
138
|
+
if node.name.count("_") > MAX_UNDERSCORES_IN_NAME:
|
|
139
|
+
issues.append(
|
|
140
|
+
f"Function '{node.name}' has too many underscores ({node.name.count('_')})"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
elif isinstance(node, ast.ClassDef):
|
|
144
|
+
# Check class naming conventions
|
|
145
|
+
if not node.name[0].isupper():
|
|
146
|
+
issues.append(
|
|
147
|
+
f"Class '{node.name}' should start with uppercase letter"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Check for overly long class names
|
|
151
|
+
if len(node.name) > MAX_CLASS_NAME_LENGTH:
|
|
152
|
+
issues.append(
|
|
153
|
+
f"Class '{node.name[:MAX_CLASS_NAME_LENGTH]}...' has excessively long name ({len(node.name)} chars)"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return issues
|
|
157
|
+
|
|
158
|
+
def check_function_complexity(self, node: ast.FunctionDef) -> List[str]:
|
|
159
|
+
"""Check function complexity metrics.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
node: Function AST node to analyze
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
List of complexity issues
|
|
166
|
+
"""
|
|
167
|
+
issues = []
|
|
168
|
+
|
|
169
|
+
# Count nested levels
|
|
170
|
+
max_depth = self._get_max_nesting_depth(node)
|
|
171
|
+
if max_depth > MAX_NESTING_DEPTH:
|
|
172
|
+
issues.append(f"Function has excessive nesting depth: {max_depth}")
|
|
173
|
+
|
|
174
|
+
# Count number of branches
|
|
175
|
+
branches = self._count_branches(node)
|
|
176
|
+
if branches > MAX_BRANCHES:
|
|
177
|
+
issues.append(f"Function has too many branches: {branches}")
|
|
178
|
+
|
|
179
|
+
# Count number of loops
|
|
180
|
+
loops = self._count_loops(node)
|
|
181
|
+
if loops > MAX_LOOPS:
|
|
182
|
+
issues.append(f"Function has too many loops: {loops}")
|
|
183
|
+
|
|
184
|
+
return issues
|
|
185
|
+
|
|
186
|
+
def _get_max_nesting_depth(self, node: ast.AST, current_depth: int = 0) -> int:
|
|
187
|
+
"""Calculate maximum nesting depth in a function.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
node: AST node to analyze
|
|
191
|
+
current_depth: Current nesting level
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Maximum nesting depth found
|
|
195
|
+
"""
|
|
196
|
+
max_depth = current_depth
|
|
197
|
+
|
|
198
|
+
for child in ast.iter_child_nodes(node):
|
|
199
|
+
if isinstance(child, (ast.If, ast.For, ast.While, ast.With, ast.Try)):
|
|
200
|
+
child_depth = self._get_max_nesting_depth(child, current_depth + 1)
|
|
201
|
+
max_depth = max(max_depth, child_depth)
|
|
202
|
+
else:
|
|
203
|
+
child_depth = self._get_max_nesting_depth(child, current_depth)
|
|
204
|
+
max_depth = max(max_depth, child_depth)
|
|
205
|
+
|
|
206
|
+
return max_depth
|
|
207
|
+
|
|
208
|
+
def _count_branches(self, node: ast.AST) -> int:
|
|
209
|
+
"""Count number of branches in a function.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
node: AST node to analyze
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Number of branches (if/elif/else)
|
|
216
|
+
"""
|
|
217
|
+
count = 0
|
|
218
|
+
for child in ast.walk(node):
|
|
219
|
+
if isinstance(child, (ast.If, ast.IfExp)):
|
|
220
|
+
count += 1
|
|
221
|
+
# Count elif branches
|
|
222
|
+
if isinstance(child, ast.If):
|
|
223
|
+
count += len(child.orelse) if isinstance(child.orelse, list) else 0
|
|
224
|
+
|
|
225
|
+
return count
|
|
226
|
+
|
|
227
|
+
def _count_loops(self, node: ast.AST) -> int:
|
|
228
|
+
"""Count number of loops in a function.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
node: AST node to analyze
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Number of loops (for/while)
|
|
235
|
+
"""
|
|
236
|
+
count = 0
|
|
237
|
+
for child in ast.walk(node):
|
|
238
|
+
if isinstance(child, (ast.For, ast.While, ast.AsyncFor)):
|
|
239
|
+
count += 1
|
|
240
|
+
|
|
241
|
+
return count
|