codeguardian-cli 1.0.0__tar.gz
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.
- codeguardian_cli-1.0.0/CODEGUARDIAN/__init__.py +0 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/__init__.py +0 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/circular_dependency_analyzer.py +101 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/db_access_analyzer.py +55 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/dependency_analyzer.py +259 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/file_analyzer.py +96 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/function_analyzer.py +199 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/source_code_analyzer.py +169 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/cli/__init__.py +0 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/cli/commands.py +619 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/config/__init__.py +0 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/config/config_loader.py +76 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/config/settings.py +2 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/main.py +4 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/reports/__init__.py +0 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/reports/architecture_score.py +68 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/reports/console_reporter.py +487 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/reports/html_reporter.py +293 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/reports/json_reporter.py +69 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/reports/markdown_reporter.py +130 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/reports/severity.py +15 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/reports/statistics_reporter.py +183 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/rules/__init__.py +0 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/rules/architecture_rules.py +5 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/rules/architecture_validator.py +69 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/rules/controllers/user_controller.py +16 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/__init__.py +0 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/discovery/__init__.py +0 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/discovery/finder.py +39 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/__init__.py +0 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/class_extractor.py +32 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/function_extractor.py +17 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/import_extractor.py +23 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/python_class_extractor.py +13 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/python_function_extractor.py +16 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/python_imports_extractor.py +24 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/main.py +65 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/parser/__init__.py +0 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/parser/ts_parser.py +14 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/tree/Walker.py +10 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/src/tree/__init__.py +0 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/utils/__init__.py +0 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/utils/ast_cache.py +47 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/utils/file_cache.py +30 -0
- codeguardian_cli-1.0.0/CODEGUARDIAN/utils/layer_utils.py +19 -0
- codeguardian_cli-1.0.0/PKG-INFO +43 -0
- codeguardian_cli-1.0.0/README.md +34 -0
- codeguardian_cli-1.0.0/codeguardian_cli.egg-info/PKG-INFO +43 -0
- codeguardian_cli-1.0.0/codeguardian_cli.egg-info/SOURCES.txt +66 -0
- codeguardian_cli-1.0.0/codeguardian_cli.egg-info/dependency_links.txt +1 -0
- codeguardian_cli-1.0.0/codeguardian_cli.egg-info/entry_points.txt +2 -0
- codeguardian_cli-1.0.0/codeguardian_cli.egg-info/requires.txt +2 -0
- codeguardian_cli-1.0.0/codeguardian_cli.egg-info/top_level.txt +1 -0
- codeguardian_cli-1.0.0/pyproject.toml +21 -0
- codeguardian_cli-1.0.0/setup.cfg +4 -0
- codeguardian_cli-1.0.0/tests/test_architecture_validator.py +28 -0
- codeguardian_cli-1.0.0/tests/test_ast_cache.py +50 -0
- codeguardian_cli-1.0.0/tests/test_circular_dependency.py +50 -0
- codeguardian_cli-1.0.0/tests/test_class_extractor.py +24 -0
- codeguardian_cli-1.0.0/tests/test_config_loader.py +16 -0
- codeguardian_cli-1.0.0/tests/test_dependencies.py +17 -0
- codeguardian_cli-1.0.0/tests/test_dependency_analyzer.py +17 -0
- codeguardian_cli-1.0.0/tests/test_discovery.py +29 -0
- codeguardian_cli-1.0.0/tests/test_file_cache.py +22 -0
- codeguardian_cli-1.0.0/tests/test_function_analyzer.py +39 -0
- codeguardian_cli-1.0.0/tests/test_function_extractor.py +26 -0
- codeguardian_cli-1.0.0/tests/test_malformed_source.py +25 -0
- codeguardian_cli-1.0.0/tests/test_ts_parser.py +17 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
|
|
2
|
+
class CircularViolation:
|
|
3
|
+
|
|
4
|
+
def __init__(self, cycle):
|
|
5
|
+
|
|
6
|
+
self.cycle = cycle
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CircularDependencyAnalyzer:
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
|
|
13
|
+
self.graph = {}
|
|
14
|
+
|
|
15
|
+
def build_graph(self, dependencies):
|
|
16
|
+
|
|
17
|
+
self.graph = {}
|
|
18
|
+
|
|
19
|
+
for dep in dependencies:
|
|
20
|
+
|
|
21
|
+
source = dep.source_file
|
|
22
|
+
target = dep.target_module
|
|
23
|
+
|
|
24
|
+
if source not in self.graph:
|
|
25
|
+
self.graph[source] = []
|
|
26
|
+
|
|
27
|
+
self.graph[source].append(target)
|
|
28
|
+
|
|
29
|
+
def dfs(
|
|
30
|
+
self,
|
|
31
|
+
node,
|
|
32
|
+
visited,
|
|
33
|
+
rec_stack,
|
|
34
|
+
path,
|
|
35
|
+
cycles
|
|
36
|
+
):
|
|
37
|
+
|
|
38
|
+
visited.add(node)
|
|
39
|
+
rec_stack.add(node)
|
|
40
|
+
path.append(node)
|
|
41
|
+
|
|
42
|
+
for neighbor in self.graph.get(node, []):
|
|
43
|
+
|
|
44
|
+
if neighbor not in visited:
|
|
45
|
+
|
|
46
|
+
self.dfs(
|
|
47
|
+
neighbor,
|
|
48
|
+
visited,
|
|
49
|
+
rec_stack,
|
|
50
|
+
path,
|
|
51
|
+
cycles
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
elif neighbor in rec_stack:
|
|
55
|
+
|
|
56
|
+
cycle_start = path.index(
|
|
57
|
+
neighbor
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
cycle = (
|
|
61
|
+
path[cycle_start:]
|
|
62
|
+
+ [neighbor]
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
cycle_tuple = tuple(cycle)
|
|
66
|
+
|
|
67
|
+
if cycle_tuple not in {
|
|
68
|
+
|
|
69
|
+
tuple(c)
|
|
70
|
+
|
|
71
|
+
for c in cycles
|
|
72
|
+
}:
|
|
73
|
+
|
|
74
|
+
cycles.append(cycle)
|
|
75
|
+
rec_stack.remove(node)
|
|
76
|
+
path.pop()
|
|
77
|
+
|
|
78
|
+
def detect(self, dependencies):
|
|
79
|
+
|
|
80
|
+
self.build_graph(dependencies)
|
|
81
|
+
|
|
82
|
+
visited = set()
|
|
83
|
+
rec_stack = set()
|
|
84
|
+
cycles = []
|
|
85
|
+
|
|
86
|
+
for node in self.graph:
|
|
87
|
+
|
|
88
|
+
if node not in visited:
|
|
89
|
+
|
|
90
|
+
self.dfs(
|
|
91
|
+
node,
|
|
92
|
+
visited,
|
|
93
|
+
rec_stack,
|
|
94
|
+
[],
|
|
95
|
+
cycles
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
CircularViolation(cycle)
|
|
100
|
+
for cycle in cycles
|
|
101
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from CODEGUARDIAN.utils.layer_utils import get_layer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DBAccessViolation:
|
|
6
|
+
|
|
7
|
+
def __init__(self, source_file, database_library):
|
|
8
|
+
|
|
9
|
+
self.source_file = source_file
|
|
10
|
+
self.database_library = database_library
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DBAccessAnalyzer:
|
|
14
|
+
|
|
15
|
+
DB_LIBRARIES = {
|
|
16
|
+
|
|
17
|
+
"mongoose",
|
|
18
|
+
"sequelize",
|
|
19
|
+
"prisma"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def analyze(self, dependencies ):
|
|
25
|
+
|
|
26
|
+
violations = []
|
|
27
|
+
|
|
28
|
+
for dependency in dependencies:
|
|
29
|
+
|
|
30
|
+
source_layer = get_layer(dependency.source_file)
|
|
31
|
+
|
|
32
|
+
if source_layer != "controllers":
|
|
33
|
+
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
for db_library in self.DB_LIBRARIES:
|
|
37
|
+
|
|
38
|
+
if db_library in dependency.target_module:
|
|
39
|
+
|
|
40
|
+
violations.append(
|
|
41
|
+
|
|
42
|
+
DBAccessViolation(
|
|
43
|
+
|
|
44
|
+
dependency.source_file,
|
|
45
|
+
|
|
46
|
+
db_library
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return violations
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from CODEGUARDIAN.config.config_loader import ConfigLoader
|
|
5
|
+
|
|
6
|
+
from CODEGUARDIAN.src.parser.ts_parser import (
|
|
7
|
+
parse_typescript
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from CODEGUARDIAN.src.tree.Walker import walk
|
|
11
|
+
|
|
12
|
+
from CODEGUARDIAN.utils.ast_cache import ASTCache
|
|
13
|
+
class Dependency:
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
source_file,
|
|
18
|
+
target_module
|
|
19
|
+
):
|
|
20
|
+
|
|
21
|
+
self.source_file = source_file
|
|
22
|
+
self.target_module = target_module
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DependencyAnalyzer:
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
|
|
29
|
+
self.config = ConfigLoader.load()
|
|
30
|
+
|
|
31
|
+
self.supported_extensions = set(
|
|
32
|
+
self.config["supported_extensions"]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def analyze_python_file(
|
|
36
|
+
self,
|
|
37
|
+
file_path,
|
|
38
|
+
project_root
|
|
39
|
+
):
|
|
40
|
+
|
|
41
|
+
dependencies = []
|
|
42
|
+
|
|
43
|
+
relative_path = os.path.relpath(
|
|
44
|
+
file_path,
|
|
45
|
+
project_root
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
module_name = (
|
|
49
|
+
relative_path
|
|
50
|
+
.replace(".py", "")
|
|
51
|
+
.replace(os.sep, ".")
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
with open(
|
|
55
|
+
file_path,
|
|
56
|
+
"r",
|
|
57
|
+
encoding="utf-8"
|
|
58
|
+
) as file:
|
|
59
|
+
|
|
60
|
+
source = file.read()
|
|
61
|
+
try:
|
|
62
|
+
tree = ASTCache.get_tree(
|
|
63
|
+
file_path,
|
|
64
|
+
source
|
|
65
|
+
)
|
|
66
|
+
except SyntaxError:
|
|
67
|
+
return []
|
|
68
|
+
|
|
69
|
+
for node in ast.walk(tree):
|
|
70
|
+
|
|
71
|
+
if isinstance(
|
|
72
|
+
node,
|
|
73
|
+
ast.ImportFrom
|
|
74
|
+
):
|
|
75
|
+
|
|
76
|
+
if node.module:
|
|
77
|
+
|
|
78
|
+
dependencies.append(
|
|
79
|
+
|
|
80
|
+
Dependency(
|
|
81
|
+
module_name,
|
|
82
|
+
node.module
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
elif isinstance(
|
|
87
|
+
node,
|
|
88
|
+
ast.Import
|
|
89
|
+
):
|
|
90
|
+
|
|
91
|
+
for imported in node.names:
|
|
92
|
+
|
|
93
|
+
dependencies.append(
|
|
94
|
+
|
|
95
|
+
Dependency(
|
|
96
|
+
module_name,
|
|
97
|
+
imported.name
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return dependencies
|
|
102
|
+
|
|
103
|
+
def analyze_ts_js_file(
|
|
104
|
+
self,
|
|
105
|
+
file_path,
|
|
106
|
+
project_root
|
|
107
|
+
):
|
|
108
|
+
|
|
109
|
+
dependencies = []
|
|
110
|
+
|
|
111
|
+
relative_path = os.path.relpath(
|
|
112
|
+
file_path,
|
|
113
|
+
project_root
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
module_name = (
|
|
117
|
+
relative_path
|
|
118
|
+
.replace(".ts", "")
|
|
119
|
+
.replace(".js", "")
|
|
120
|
+
.replace(os.sep, ".")
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
with open(
|
|
124
|
+
file_path,
|
|
125
|
+
"r",
|
|
126
|
+
encoding="utf-8"
|
|
127
|
+
) as file:
|
|
128
|
+
|
|
129
|
+
source = file.read()
|
|
130
|
+
try:
|
|
131
|
+
tree = ASTCache.get_tree(
|
|
132
|
+
file_path,
|
|
133
|
+
source
|
|
134
|
+
)
|
|
135
|
+
except Exception:
|
|
136
|
+
return []
|
|
137
|
+
|
|
138
|
+
if tree.root_node.has_error:
|
|
139
|
+
return []
|
|
140
|
+
|
|
141
|
+
def visit(node):
|
|
142
|
+
|
|
143
|
+
if node.type != "import_statement":
|
|
144
|
+
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
source_node = node.child_by_field_name(
|
|
148
|
+
"source"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if not source_node:
|
|
152
|
+
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
imported_module = (
|
|
156
|
+
source_node.text
|
|
157
|
+
.decode("utf8")
|
|
158
|
+
.replace('"', "")
|
|
159
|
+
.replace("'", "")
|
|
160
|
+
)
|
|
161
|
+
if imported_module.startswith("./"):
|
|
162
|
+
|
|
163
|
+
current_directory = ".".join(
|
|
164
|
+
module_name.split(".")[:-1]
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
imported_module = (
|
|
168
|
+
current_directory
|
|
169
|
+
+ "."
|
|
170
|
+
+ imported_module[2:]
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
imported_module = imported_module.replace(
|
|
174
|
+
"/",
|
|
175
|
+
"."
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
dependencies.append(
|
|
179
|
+
|
|
180
|
+
Dependency(
|
|
181
|
+
module_name,
|
|
182
|
+
imported_module
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
walk(
|
|
187
|
+
tree.root_node,
|
|
188
|
+
visit
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return dependencies
|
|
192
|
+
|
|
193
|
+
def analyze_project(
|
|
194
|
+
self,
|
|
195
|
+
project_path
|
|
196
|
+
):
|
|
197
|
+
|
|
198
|
+
dependencies = []
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
ignore_dirs = set(
|
|
203
|
+
self.config[
|
|
204
|
+
"ignored_directories"
|
|
205
|
+
]
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
for root, dirs, files in os.walk(project_path):
|
|
209
|
+
|
|
210
|
+
dirs[:] = [
|
|
211
|
+
|
|
212
|
+
d for d in dirs
|
|
213
|
+
|
|
214
|
+
if d not in ignore_dirs
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
for file in files:
|
|
218
|
+
|
|
219
|
+
extension = os.path.splitext(
|
|
220
|
+
file
|
|
221
|
+
)[1]
|
|
222
|
+
|
|
223
|
+
if (
|
|
224
|
+
extension
|
|
225
|
+
not in self.supported_extensions
|
|
226
|
+
):
|
|
227
|
+
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
file_path = os.path.join(
|
|
231
|
+
root,
|
|
232
|
+
file
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if extension == ".py":
|
|
236
|
+
|
|
237
|
+
dependencies.extend(
|
|
238
|
+
|
|
239
|
+
self.analyze_python_file(
|
|
240
|
+
file_path,
|
|
241
|
+
project_path
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
elif extension in {
|
|
246
|
+
|
|
247
|
+
".ts",
|
|
248
|
+
".js"
|
|
249
|
+
}:
|
|
250
|
+
|
|
251
|
+
dependencies.extend(
|
|
252
|
+
|
|
253
|
+
self.analyze_ts_js_file(
|
|
254
|
+
file_path,
|
|
255
|
+
project_path
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
return dependencies
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from CODEGUARDIAN.config.config_loader import ConfigLoader
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from CODEGUARDIAN.utils.file_cache import FileCache
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from CODEGUARDIAN.src.discovery.finder import discover_files
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FileViolation:
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
file_path,
|
|
17
|
+
line_count
|
|
18
|
+
):
|
|
19
|
+
|
|
20
|
+
self.file_path = str(file_path)
|
|
21
|
+
self.line_count = line_count
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FileAnalyzer:
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
max_lines=None
|
|
29
|
+
):
|
|
30
|
+
|
|
31
|
+
config = ConfigLoader.load()
|
|
32
|
+
|
|
33
|
+
if max_lines is None:
|
|
34
|
+
|
|
35
|
+
max_lines = config[
|
|
36
|
+
"max_file_lines"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
self.max_lines = max_lines
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
self.supported_extensions = set(
|
|
43
|
+
|
|
44
|
+
config[
|
|
45
|
+
"supported_extensions"
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def analyze(
|
|
50
|
+
self,
|
|
51
|
+
project_path
|
|
52
|
+
):
|
|
53
|
+
|
|
54
|
+
violations = []
|
|
55
|
+
|
|
56
|
+
files = FileCache.get_files(
|
|
57
|
+
project_path,
|
|
58
|
+
self.supported_extensions
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
for file_path in files:
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
|
|
67
|
+
with open(
|
|
68
|
+
file_path,
|
|
69
|
+
"r",
|
|
70
|
+
encoding="utf-8"
|
|
71
|
+
) as f:
|
|
72
|
+
|
|
73
|
+
line_count = len(
|
|
74
|
+
f.readlines()
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
|
|
79
|
+
line_count
|
|
80
|
+
> self.max_lines
|
|
81
|
+
|
|
82
|
+
):
|
|
83
|
+
|
|
84
|
+
violations.append(
|
|
85
|
+
|
|
86
|
+
FileViolation(
|
|
87
|
+
str(file_path),
|
|
88
|
+
line_count
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
except UnicodeDecodeError:
|
|
93
|
+
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
return violations
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from CODEGUARDIAN.config.config_loader import ConfigLoader
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from CODEGUARDIAN.src.tree.Walker import walk
|
|
9
|
+
from CODEGUARDIAN.utils.file_cache import FileCache
|
|
10
|
+
from CODEGUARDIAN.utils.ast_cache import ASTCache
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FunctionViolation:
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
function_name,
|
|
18
|
+
line_count
|
|
19
|
+
):
|
|
20
|
+
|
|
21
|
+
self.function_name = function_name
|
|
22
|
+
self.line_count = line_count
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FunctionAnalyzer:
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
max_lines=None
|
|
30
|
+
):
|
|
31
|
+
|
|
32
|
+
config = ConfigLoader.load()
|
|
33
|
+
|
|
34
|
+
if max_lines is None:
|
|
35
|
+
|
|
36
|
+
max_lines = config[
|
|
37
|
+
"max_function_lines"
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
self.max_lines = max_lines
|
|
41
|
+
|
|
42
|
+
self.supported_extensions = set(
|
|
43
|
+
config[
|
|
44
|
+
"supported_extensions"
|
|
45
|
+
]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def analyze_python_file(
|
|
49
|
+
self,
|
|
50
|
+
file_path
|
|
51
|
+
):
|
|
52
|
+
|
|
53
|
+
violations = []
|
|
54
|
+
|
|
55
|
+
with open(
|
|
56
|
+
file_path,
|
|
57
|
+
"r",
|
|
58
|
+
encoding="utf-8"
|
|
59
|
+
) as f:
|
|
60
|
+
|
|
61
|
+
source = f.read()
|
|
62
|
+
try:
|
|
63
|
+
|
|
64
|
+
tree = ASTCache.get_tree(
|
|
65
|
+
file_path,
|
|
66
|
+
source
|
|
67
|
+
)
|
|
68
|
+
except SyntaxError:
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
for node in ast.walk(tree):
|
|
72
|
+
|
|
73
|
+
if isinstance(
|
|
74
|
+
node,
|
|
75
|
+
ast.FunctionDef
|
|
76
|
+
):
|
|
77
|
+
|
|
78
|
+
line_count = (
|
|
79
|
+
node.end_lineno
|
|
80
|
+
- node.lineno
|
|
81
|
+
+ 1
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if line_count > self.max_lines:
|
|
85
|
+
|
|
86
|
+
violations.append(
|
|
87
|
+
|
|
88
|
+
FunctionViolation(
|
|
89
|
+
node.name,
|
|
90
|
+
line_count
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return violations
|
|
95
|
+
|
|
96
|
+
def analyze_ts_js_file(
|
|
97
|
+
self,
|
|
98
|
+
file_path
|
|
99
|
+
):
|
|
100
|
+
|
|
101
|
+
violations = []
|
|
102
|
+
|
|
103
|
+
with open(
|
|
104
|
+
file_path,
|
|
105
|
+
"r",
|
|
106
|
+
encoding="utf-8"
|
|
107
|
+
) as file:
|
|
108
|
+
|
|
109
|
+
source = file.read()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
tree = ASTCache.get_tree(
|
|
114
|
+
file_path,
|
|
115
|
+
source
|
|
116
|
+
)
|
|
117
|
+
except Exception:
|
|
118
|
+
return []
|
|
119
|
+
|
|
120
|
+
if tree.root_node.has_error:
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
def visit(node):
|
|
124
|
+
|
|
125
|
+
if node.type != "function_declaration":
|
|
126
|
+
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
name_node = node.child_by_field_name(
|
|
130
|
+
"name"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if not name_node:
|
|
134
|
+
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
function_name = (
|
|
138
|
+
name_node.text.decode("utf8")
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
line_count = (
|
|
142
|
+
node.end_point[0]
|
|
143
|
+
- node.start_point[0]
|
|
144
|
+
+ 1
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if line_count > self.max_lines:
|
|
148
|
+
|
|
149
|
+
violations.append(
|
|
150
|
+
|
|
151
|
+
FunctionViolation(
|
|
152
|
+
function_name,
|
|
153
|
+
line_count
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
walk(
|
|
158
|
+
tree.root_node,
|
|
159
|
+
visit
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return violations
|
|
163
|
+
|
|
164
|
+
def analyze_project(
|
|
165
|
+
self,
|
|
166
|
+
project_path
|
|
167
|
+
):
|
|
168
|
+
|
|
169
|
+
violations = []
|
|
170
|
+
|
|
171
|
+
files = FileCache.get_files(
|
|
172
|
+
project_path,
|
|
173
|
+
self.supported_extensions
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
for file_path in files:
|
|
177
|
+
|
|
178
|
+
extension = file_path.suffix
|
|
179
|
+
|
|
180
|
+
if extension == ".py":
|
|
181
|
+
|
|
182
|
+
violations.extend(
|
|
183
|
+
self.analyze_python_file(
|
|
184
|
+
file_path
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
elif extension in {
|
|
189
|
+
".ts",
|
|
190
|
+
".js"
|
|
191
|
+
}:
|
|
192
|
+
|
|
193
|
+
violations.extend(
|
|
194
|
+
self.analyze_ts_js_file(
|
|
195
|
+
file_path
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return violations
|