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.
Files changed (68) hide show
  1. codeguardian_cli-1.0.0/CODEGUARDIAN/__init__.py +0 -0
  2. codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/__init__.py +0 -0
  3. codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/circular_dependency_analyzer.py +101 -0
  4. codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/db_access_analyzer.py +55 -0
  5. codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/dependency_analyzer.py +259 -0
  6. codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/file_analyzer.py +96 -0
  7. codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/function_analyzer.py +199 -0
  8. codeguardian_cli-1.0.0/CODEGUARDIAN/analyzers/source_code_analyzer.py +169 -0
  9. codeguardian_cli-1.0.0/CODEGUARDIAN/cli/__init__.py +0 -0
  10. codeguardian_cli-1.0.0/CODEGUARDIAN/cli/commands.py +619 -0
  11. codeguardian_cli-1.0.0/CODEGUARDIAN/config/__init__.py +0 -0
  12. codeguardian_cli-1.0.0/CODEGUARDIAN/config/config_loader.py +76 -0
  13. codeguardian_cli-1.0.0/CODEGUARDIAN/config/settings.py +2 -0
  14. codeguardian_cli-1.0.0/CODEGUARDIAN/main.py +4 -0
  15. codeguardian_cli-1.0.0/CODEGUARDIAN/reports/__init__.py +0 -0
  16. codeguardian_cli-1.0.0/CODEGUARDIAN/reports/architecture_score.py +68 -0
  17. codeguardian_cli-1.0.0/CODEGUARDIAN/reports/console_reporter.py +487 -0
  18. codeguardian_cli-1.0.0/CODEGUARDIAN/reports/html_reporter.py +293 -0
  19. codeguardian_cli-1.0.0/CODEGUARDIAN/reports/json_reporter.py +69 -0
  20. codeguardian_cli-1.0.0/CODEGUARDIAN/reports/markdown_reporter.py +130 -0
  21. codeguardian_cli-1.0.0/CODEGUARDIAN/reports/severity.py +15 -0
  22. codeguardian_cli-1.0.0/CODEGUARDIAN/reports/statistics_reporter.py +183 -0
  23. codeguardian_cli-1.0.0/CODEGUARDIAN/rules/__init__.py +0 -0
  24. codeguardian_cli-1.0.0/CODEGUARDIAN/rules/architecture_rules.py +5 -0
  25. codeguardian_cli-1.0.0/CODEGUARDIAN/rules/architecture_validator.py +69 -0
  26. codeguardian_cli-1.0.0/CODEGUARDIAN/rules/controllers/user_controller.py +16 -0
  27. codeguardian_cli-1.0.0/CODEGUARDIAN/src/__init__.py +0 -0
  28. codeguardian_cli-1.0.0/CODEGUARDIAN/src/discovery/__init__.py +0 -0
  29. codeguardian_cli-1.0.0/CODEGUARDIAN/src/discovery/finder.py +39 -0
  30. codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/__init__.py +0 -0
  31. codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/class_extractor.py +32 -0
  32. codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/function_extractor.py +17 -0
  33. codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/import_extractor.py +23 -0
  34. codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/python_class_extractor.py +13 -0
  35. codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/python_function_extractor.py +16 -0
  36. codeguardian_cli-1.0.0/CODEGUARDIAN/src/extractor/python_imports_extractor.py +24 -0
  37. codeguardian_cli-1.0.0/CODEGUARDIAN/src/main.py +65 -0
  38. codeguardian_cli-1.0.0/CODEGUARDIAN/src/parser/__init__.py +0 -0
  39. codeguardian_cli-1.0.0/CODEGUARDIAN/src/parser/ts_parser.py +14 -0
  40. codeguardian_cli-1.0.0/CODEGUARDIAN/src/tree/Walker.py +10 -0
  41. codeguardian_cli-1.0.0/CODEGUARDIAN/src/tree/__init__.py +0 -0
  42. codeguardian_cli-1.0.0/CODEGUARDIAN/utils/__init__.py +0 -0
  43. codeguardian_cli-1.0.0/CODEGUARDIAN/utils/ast_cache.py +47 -0
  44. codeguardian_cli-1.0.0/CODEGUARDIAN/utils/file_cache.py +30 -0
  45. codeguardian_cli-1.0.0/CODEGUARDIAN/utils/layer_utils.py +19 -0
  46. codeguardian_cli-1.0.0/PKG-INFO +43 -0
  47. codeguardian_cli-1.0.0/README.md +34 -0
  48. codeguardian_cli-1.0.0/codeguardian_cli.egg-info/PKG-INFO +43 -0
  49. codeguardian_cli-1.0.0/codeguardian_cli.egg-info/SOURCES.txt +66 -0
  50. codeguardian_cli-1.0.0/codeguardian_cli.egg-info/dependency_links.txt +1 -0
  51. codeguardian_cli-1.0.0/codeguardian_cli.egg-info/entry_points.txt +2 -0
  52. codeguardian_cli-1.0.0/codeguardian_cli.egg-info/requires.txt +2 -0
  53. codeguardian_cli-1.0.0/codeguardian_cli.egg-info/top_level.txt +1 -0
  54. codeguardian_cli-1.0.0/pyproject.toml +21 -0
  55. codeguardian_cli-1.0.0/setup.cfg +4 -0
  56. codeguardian_cli-1.0.0/tests/test_architecture_validator.py +28 -0
  57. codeguardian_cli-1.0.0/tests/test_ast_cache.py +50 -0
  58. codeguardian_cli-1.0.0/tests/test_circular_dependency.py +50 -0
  59. codeguardian_cli-1.0.0/tests/test_class_extractor.py +24 -0
  60. codeguardian_cli-1.0.0/tests/test_config_loader.py +16 -0
  61. codeguardian_cli-1.0.0/tests/test_dependencies.py +17 -0
  62. codeguardian_cli-1.0.0/tests/test_dependency_analyzer.py +17 -0
  63. codeguardian_cli-1.0.0/tests/test_discovery.py +29 -0
  64. codeguardian_cli-1.0.0/tests/test_file_cache.py +22 -0
  65. codeguardian_cli-1.0.0/tests/test_function_analyzer.py +39 -0
  66. codeguardian_cli-1.0.0/tests/test_function_extractor.py +26 -0
  67. codeguardian_cli-1.0.0/tests/test_malformed_source.py +25 -0
  68. codeguardian_cli-1.0.0/tests/test_ts_parser.py +17 -0
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