codinsight 0.2.0__tar.gz → 0.3.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.
- {codinsight-0.2.0 → codinsight-0.3.0}/PKG-INFO +1 -1
- {codinsight-0.2.0 → codinsight-0.3.0}/pyproject.toml +17 -1
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/code_analysis/redundancy.py +1 -2
- codinsight-0.3.0/src/code_insight/code_analysis/security.py +327 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/core.py +14 -4
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/multi_analysis.py +9 -10
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/trend_analysis/trend_analysis.py +1 -1
- {codinsight-0.2.0 → codinsight-0.3.0}/src/codinsight.egg-info/PKG-INFO +1 -1
- {codinsight-0.2.0 → codinsight-0.3.0}/src/codinsight.egg-info/SOURCES.txt +1 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/README.md +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/setup.cfg +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/__init__.py +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/code_analysis/__init__.py +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/code_analysis/abstract.py +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/code_analysis/algorithm.py +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/code_analysis/complexity.py +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/code_analysis/quality.py +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/code_analysis/readability.py +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/code_analysis/struct.py +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/code_analysis/style.py +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/py.typed +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/code_insight/trend_analysis/__init__.py +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/codinsight.egg-info/dependency_links.txt +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/codinsight.egg-info/requires.txt +0 -0
- {codinsight-0.2.0 → codinsight-0.3.0}/src/codinsight.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codinsight"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Add your description here"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -35,6 +35,22 @@ addopts = "--exclude=tests"
|
|
|
35
35
|
packages = ["src/code_insight"]
|
|
36
36
|
include = ["src/code_insight/py.typed"]
|
|
37
37
|
|
|
38
|
+
[tool.ruff]
|
|
39
|
+
line-length = 88
|
|
40
|
+
include = ["codinsight/**/*.py"]
|
|
41
|
+
|
|
42
|
+
[tool.ruff.lint]
|
|
43
|
+
select = ["F", "E", "I", "B"]
|
|
44
|
+
|
|
45
|
+
[tool.ruff.lint.isort]
|
|
46
|
+
# black profileと同等の設定
|
|
47
|
+
split-on-trailing-comma = true
|
|
48
|
+
combine-as-imports = true
|
|
49
|
+
|
|
50
|
+
[tool.ruff.format]
|
|
51
|
+
quote-style = "double"
|
|
52
|
+
indent-style = "space"
|
|
53
|
+
|
|
38
54
|
[tool.pytest.ini_options]
|
|
39
55
|
testpaths = ["tests"]
|
|
40
56
|
pythonpath = ["src"]
|
|
@@ -286,8 +286,7 @@ class Redundancy(AbstractAnalysis[RedundancyAnalysisResult, RedundancyAnalysisCo
|
|
|
286
286
|
if isinstance(node_value, bytes):
|
|
287
287
|
node_value = node_value.decode()
|
|
288
288
|
structure_elements.append(
|
|
289
|
-
f"return_const_{type(node.value.value).__name__}_"
|
|
290
|
-
f"{node_value}"
|
|
289
|
+
f"return_const_{type(node.value.value).__name__}_{node_value}"
|
|
291
290
|
)
|
|
292
291
|
elif isinstance(node.value, ast.BinOp):
|
|
293
292
|
structure_elements.append(
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import re
|
|
3
|
+
from typing import List, Set
|
|
4
|
+
|
|
5
|
+
from code_insight.code_analysis.abstract import (
|
|
6
|
+
AbstractAnalysis,
|
|
7
|
+
BaseAnalysisConfig,
|
|
8
|
+
BaseAnalysisResult,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SecurityAnalysisConfig(BaseAnalysisConfig):
|
|
13
|
+
"""
|
|
14
|
+
セキュリティ解析設定
|
|
15
|
+
|
|
16
|
+
Attributes
|
|
17
|
+
----------
|
|
18
|
+
check_hardcoded_secrets : bool
|
|
19
|
+
ハードコードされた秘密情報をチェックするか, by default True
|
|
20
|
+
check_dangerous_functions : bool
|
|
21
|
+
危険な関数の使用をチェックするか, by default True
|
|
22
|
+
check_sql_injection : bool
|
|
23
|
+
SQLインジェクション脆弱性をチェックするか, by default True
|
|
24
|
+
secret_patterns : List[str]
|
|
25
|
+
秘密情報検出パターン, by default 標準パターン
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
check_hardcoded_secrets: bool = True
|
|
29
|
+
check_dangerous_functions: bool = True
|
|
30
|
+
check_sql_injection: bool = True
|
|
31
|
+
secret_patterns: List[str] = [
|
|
32
|
+
r"password\s*=\s*['\"][^'\"]{3,}['\"]",
|
|
33
|
+
r"api_key\s*=\s*['\"][^'\"]{10,}['\"]",
|
|
34
|
+
r"secret\s*=\s*['\"][^'\"]{8,}['\"]",
|
|
35
|
+
r"token\s*=\s*['\"][^'\"]{10,}['\"]",
|
|
36
|
+
r"key\s*=\s*['\"][^'\"]{8,}['\"]",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SecurityAnalysisResult(BaseAnalysisResult):
|
|
41
|
+
"""
|
|
42
|
+
解析結果(セキュリティ)
|
|
43
|
+
|
|
44
|
+
Attributes
|
|
45
|
+
----------
|
|
46
|
+
hardcoded_secrets_count : int
|
|
47
|
+
ハードコードされた秘密情報の数
|
|
48
|
+
dangerous_function_count : int
|
|
49
|
+
危険な関数の使用数
|
|
50
|
+
sql_injection_risk_count : int
|
|
51
|
+
SQLインジェクション脆弱性の可能性がある箇所の数
|
|
52
|
+
input_validation_missing_count : int
|
|
53
|
+
入力検証不備の数
|
|
54
|
+
security_score : float
|
|
55
|
+
セキュリティスコア(0.0-1.0、高いほど安全)
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
hardcoded_secrets_count: int
|
|
59
|
+
dangerous_function_count: int
|
|
60
|
+
sql_injection_risk_count: int
|
|
61
|
+
input_validation_missing_count: int
|
|
62
|
+
security_score: float
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Security(AbstractAnalysis[SecurityAnalysisResult, SecurityAnalysisConfig]):
|
|
66
|
+
"""
|
|
67
|
+
解析クラス(セキュリティ)
|
|
68
|
+
|
|
69
|
+
Notes
|
|
70
|
+
-----
|
|
71
|
+
コードのセキュリティ脆弱性を多角的に解析するクラス
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, config: SecurityAnalysisConfig | None = None) -> None:
|
|
75
|
+
"""
|
|
76
|
+
コンストラクタ
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
config : SecurityAnalysisConfig | None, optional
|
|
81
|
+
セキュリティ解析設定, by default None
|
|
82
|
+
"""
|
|
83
|
+
super().__init__(config)
|
|
84
|
+
|
|
85
|
+
def get_default_config(self) -> SecurityAnalysisConfig:
|
|
86
|
+
"""
|
|
87
|
+
デフォルト設定を取得
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
SecurityAnalysisConfig
|
|
92
|
+
デフォルトのセキュリティ解析設定
|
|
93
|
+
"""
|
|
94
|
+
return SecurityAnalysisConfig()
|
|
95
|
+
|
|
96
|
+
def analyze(self, source_code: str) -> SecurityAnalysisResult:
|
|
97
|
+
"""
|
|
98
|
+
コード解析
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
source_code : str
|
|
103
|
+
解析対象のソースコード
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
SecurityAnalysisResult
|
|
108
|
+
セキュリティ解析結果
|
|
109
|
+
"""
|
|
110
|
+
if not self.config.enabled:
|
|
111
|
+
return SecurityAnalysisResult(
|
|
112
|
+
hardcoded_secrets_count=0,
|
|
113
|
+
dangerous_function_count=0,
|
|
114
|
+
sql_injection_risk_count=0,
|
|
115
|
+
input_validation_missing_count=0,
|
|
116
|
+
security_score=1.0,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
tree = self.parse_source_code(source_code)
|
|
120
|
+
|
|
121
|
+
hardcoded_secrets = self.get_hardcoded_secrets_count(source_code, tree)
|
|
122
|
+
dangerous_functions = self.get_dangerous_function_count(source_code, tree)
|
|
123
|
+
sql_injection_risks = self.get_sql_injection_risk_count(source_code, tree)
|
|
124
|
+
input_validation_missing = self.get_input_validation_missing_count(source_code, tree)
|
|
125
|
+
|
|
126
|
+
security_score = self.calculate_security_score(
|
|
127
|
+
hardcoded_secrets, dangerous_functions, sql_injection_risks, input_validation_missing
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return SecurityAnalysisResult(
|
|
131
|
+
hardcoded_secrets_count=hardcoded_secrets,
|
|
132
|
+
dangerous_function_count=dangerous_functions,
|
|
133
|
+
sql_injection_risk_count=sql_injection_risks,
|
|
134
|
+
input_validation_missing_count=input_validation_missing,
|
|
135
|
+
security_score=security_score,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def parse_source_code(self, source_code: str) -> ast.AST:
|
|
139
|
+
"""
|
|
140
|
+
ソースコードを解析
|
|
141
|
+
|
|
142
|
+
Parameters
|
|
143
|
+
----------
|
|
144
|
+
source_code : str
|
|
145
|
+
解析対象のソースコード
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
ast.AST
|
|
150
|
+
解析済みのAST
|
|
151
|
+
"""
|
|
152
|
+
return ast.parse(source_code)
|
|
153
|
+
|
|
154
|
+
def get_hardcoded_secrets_count(
|
|
155
|
+
self, source_code: str, tree: ast.AST | None = None
|
|
156
|
+
) -> int:
|
|
157
|
+
"""
|
|
158
|
+
ハードコードされた秘密情報の数を取得
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
source_code : str
|
|
163
|
+
解析対象のソースコード
|
|
164
|
+
tree : ast.AST | None, optional
|
|
165
|
+
解析済みのAST, by default None
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
int
|
|
170
|
+
ハードコードされた秘密情報の数
|
|
171
|
+
"""
|
|
172
|
+
if not self.config.check_hardcoded_secrets:
|
|
173
|
+
return 0
|
|
174
|
+
|
|
175
|
+
count = 0
|
|
176
|
+
source_lower = source_code.lower()
|
|
177
|
+
|
|
178
|
+
for pattern in self.config.secret_patterns:
|
|
179
|
+
matches = re.findall(pattern, source_lower, re.IGNORECASE)
|
|
180
|
+
count += len(matches)
|
|
181
|
+
|
|
182
|
+
return count
|
|
183
|
+
|
|
184
|
+
def get_dangerous_function_count(
|
|
185
|
+
self, source_code: str, tree: ast.AST | None = None
|
|
186
|
+
) -> int:
|
|
187
|
+
"""
|
|
188
|
+
危険な関数の使用数を取得
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
source_code : str
|
|
193
|
+
解析対象のソースコード
|
|
194
|
+
tree : ast.AST | None, optional
|
|
195
|
+
解析済みのAST, by default None
|
|
196
|
+
|
|
197
|
+
Returns
|
|
198
|
+
-------
|
|
199
|
+
int
|
|
200
|
+
危険な関数の使用数
|
|
201
|
+
"""
|
|
202
|
+
if not self.config.check_dangerous_functions:
|
|
203
|
+
return 0
|
|
204
|
+
|
|
205
|
+
tree = tree or self.parse_source_code(source_code)
|
|
206
|
+
dangerous_functions = {"eval", "exec", "compile", "__import__"}
|
|
207
|
+
count = 0
|
|
208
|
+
|
|
209
|
+
for node in ast.walk(tree):
|
|
210
|
+
if isinstance(node, ast.Call):
|
|
211
|
+
if isinstance(node.func, ast.Name) and node.func.id in dangerous_functions:
|
|
212
|
+
count += 1
|
|
213
|
+
|
|
214
|
+
return count
|
|
215
|
+
|
|
216
|
+
def get_sql_injection_risk_count(
|
|
217
|
+
self, source_code: str, tree: ast.AST | None = None
|
|
218
|
+
) -> int:
|
|
219
|
+
"""
|
|
220
|
+
SQLインジェクション脆弱性の可能性がある箇所の数を取得
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
source_code : str
|
|
225
|
+
解析対象のソースコード
|
|
226
|
+
tree : ast.AST | None, optional
|
|
227
|
+
解析済みのAST, by default None
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
int
|
|
232
|
+
SQLインジェクション脆弱性の可能性がある箇所の数
|
|
233
|
+
"""
|
|
234
|
+
if not self.config.check_sql_injection:
|
|
235
|
+
return 0
|
|
236
|
+
|
|
237
|
+
tree = tree or self.parse_source_code(source_code)
|
|
238
|
+
count = 0
|
|
239
|
+
|
|
240
|
+
sql_patterns = [
|
|
241
|
+
r"select\s+.*\s+from\s+.*\+",
|
|
242
|
+
r"insert\s+into\s+.*\+",
|
|
243
|
+
r"update\s+.*\s+set\s+.*\+",
|
|
244
|
+
r"delete\s+from\s+.*\+",
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
for node in ast.walk(tree):
|
|
248
|
+
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
|
|
249
|
+
if isinstance(node.left, ast.Constant) and isinstance(node.left.value, str):
|
|
250
|
+
sql_text = node.left.value.lower()
|
|
251
|
+
for pattern in sql_patterns:
|
|
252
|
+
if re.search(pattern, sql_text):
|
|
253
|
+
count += 1
|
|
254
|
+
break
|
|
255
|
+
|
|
256
|
+
return count
|
|
257
|
+
|
|
258
|
+
def get_input_validation_missing_count(
|
|
259
|
+
self, source_code: str, tree: ast.AST | None = None
|
|
260
|
+
) -> int:
|
|
261
|
+
"""
|
|
262
|
+
入力検証不備の数を取得
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
source_code : str
|
|
267
|
+
解析対象のソースコード
|
|
268
|
+
tree : ast.AST | None, optional
|
|
269
|
+
解析済みのAST, by default None
|
|
270
|
+
|
|
271
|
+
Returns
|
|
272
|
+
-------
|
|
273
|
+
int
|
|
274
|
+
入力検証不備の数
|
|
275
|
+
"""
|
|
276
|
+
tree = tree or self.parse_source_code(source_code)
|
|
277
|
+
count = 0
|
|
278
|
+
input_functions = {"input", "raw_input"}
|
|
279
|
+
|
|
280
|
+
for node in ast.walk(tree):
|
|
281
|
+
if isinstance(node, ast.Call):
|
|
282
|
+
if isinstance(node.func, ast.Name) and node.func.id in input_functions:
|
|
283
|
+
parent_found = False
|
|
284
|
+
for parent in ast.walk(tree):
|
|
285
|
+
if isinstance(parent, ast.If):
|
|
286
|
+
for child in ast.walk(parent):
|
|
287
|
+
if child is node:
|
|
288
|
+
parent_found = True
|
|
289
|
+
break
|
|
290
|
+
if not parent_found:
|
|
291
|
+
count += 1
|
|
292
|
+
|
|
293
|
+
return count
|
|
294
|
+
|
|
295
|
+
def calculate_security_score(
|
|
296
|
+
self,
|
|
297
|
+
hardcoded_secrets: int,
|
|
298
|
+
dangerous_functions: int,
|
|
299
|
+
sql_injection_risks: int,
|
|
300
|
+
input_validation_missing: int,
|
|
301
|
+
) -> float:
|
|
302
|
+
"""
|
|
303
|
+
セキュリティスコアを計算
|
|
304
|
+
|
|
305
|
+
Parameters
|
|
306
|
+
----------
|
|
307
|
+
hardcoded_secrets : int
|
|
308
|
+
ハードコードされた秘密情報の数
|
|
309
|
+
dangerous_functions : int
|
|
310
|
+
危険な関数の使用数
|
|
311
|
+
sql_injection_risks : int
|
|
312
|
+
SQLインジェクション脆弱性の可能性がある箇所の数
|
|
313
|
+
input_validation_missing : int
|
|
314
|
+
入力検証不備の数
|
|
315
|
+
|
|
316
|
+
Returns
|
|
317
|
+
-------
|
|
318
|
+
float
|
|
319
|
+
セキュリティスコア(0.0-1.0、高いほど安全)
|
|
320
|
+
"""
|
|
321
|
+
total_issues = hardcoded_secrets + dangerous_functions + sql_injection_risks + input_validation_missing
|
|
322
|
+
|
|
323
|
+
if total_issues == 0:
|
|
324
|
+
return 1.0
|
|
325
|
+
|
|
326
|
+
penalty = min(total_issues * 0.1, 1.0)
|
|
327
|
+
return max(1.0 - penalty, 0.0)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from enum import StrEnum, auto
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel
|
|
5
5
|
|
|
@@ -16,6 +16,7 @@ from code_insight.code_analysis.readability import (
|
|
|
16
16
|
ReadabilityAnalysisConfig,
|
|
17
17
|
)
|
|
18
18
|
from code_insight.code_analysis.redundancy import Redundancy, RedundancyAnalysisConfig
|
|
19
|
+
from code_insight.code_analysis.security import Security, SecurityAnalysisConfig
|
|
19
20
|
from code_insight.code_analysis.struct import Struct, StructAnalysisConfig
|
|
20
21
|
from code_insight.code_analysis.style import Style, StyleAnalysisConfig
|
|
21
22
|
|
|
@@ -40,6 +41,8 @@ class AnalysisConfigs(BaseModel):
|
|
|
40
41
|
複雑度解析設定, by default None
|
|
41
42
|
quality : QualityAnalysisConfig | None
|
|
42
43
|
品質解析設定, by default None
|
|
44
|
+
security : SecurityAnalysisConfig | None
|
|
45
|
+
セキュリティ解析設定, by default None
|
|
43
46
|
"""
|
|
44
47
|
|
|
45
48
|
style: StyleAnalysisConfig | None = None
|
|
@@ -49,6 +52,7 @@ class AnalysisConfigs(BaseModel):
|
|
|
49
52
|
algorithm: AlgorithmAnalysisConfig | None = None
|
|
50
53
|
complexity: ComplexityAnalysisConfig | None = None
|
|
51
54
|
quality: QualityAnalysisConfig | None = None
|
|
55
|
+
security: SecurityAnalysisConfig | None = None
|
|
52
56
|
|
|
53
57
|
|
|
54
58
|
class CodeAnalysisType(StrEnum):
|
|
@@ -71,6 +75,8 @@ class CodeAnalysisType(StrEnum):
|
|
|
71
75
|
複雑度解析
|
|
72
76
|
QUALITY : str
|
|
73
77
|
品質解析
|
|
78
|
+
SECURITY : str
|
|
79
|
+
セキュリティ解析
|
|
74
80
|
"""
|
|
75
81
|
|
|
76
82
|
STYLE = auto()
|
|
@@ -80,6 +86,7 @@ class CodeAnalysisType(StrEnum):
|
|
|
80
86
|
ALGORITHM = auto()
|
|
81
87
|
COMPLEXITY = auto()
|
|
82
88
|
QUALITY = auto()
|
|
89
|
+
SECURITY = auto()
|
|
83
90
|
|
|
84
91
|
@staticmethod
|
|
85
92
|
def get_code_analysis_class(
|
|
@@ -119,6 +126,8 @@ class CodeAnalysisType(StrEnum):
|
|
|
119
126
|
return Complexity(config) # type: ignore
|
|
120
127
|
elif type == CodeAnalysisType.QUALITY:
|
|
121
128
|
return Quality(config) # type: ignore
|
|
129
|
+
elif type == CodeAnalysisType.SECURITY:
|
|
130
|
+
return Security(config) # type: ignore
|
|
122
131
|
else:
|
|
123
132
|
raise ValueError(f"Invalid code analysis type: {type}")
|
|
124
133
|
|
|
@@ -156,7 +165,7 @@ class CodeAnalysis:
|
|
|
156
165
|
|
|
157
166
|
def analyze(
|
|
158
167
|
self, types: list[CodeAnalysisType]
|
|
159
|
-
) -> dict[CodeAnalysisType,
|
|
168
|
+
) -> dict[CodeAnalysisType, BaseAnalysisResult]:
|
|
160
169
|
"""
|
|
161
170
|
コード解析
|
|
162
171
|
|
|
@@ -167,10 +176,10 @@ class CodeAnalysis:
|
|
|
167
176
|
|
|
168
177
|
Returns
|
|
169
178
|
-------
|
|
170
|
-
dict[CodeAnalysisType,
|
|
179
|
+
dict[CodeAnalysisType, BaseAnalysisResult]
|
|
171
180
|
解析結果の辞書
|
|
172
181
|
"""
|
|
173
|
-
result: dict[CodeAnalysisType,
|
|
182
|
+
result: dict[CodeAnalysisType, BaseAnalysisResult] = {}
|
|
174
183
|
for type in types:
|
|
175
184
|
config = self._get_config_for_type(type)
|
|
176
185
|
result[type] = CodeAnalysisType.get_code_analysis_class(
|
|
@@ -205,5 +214,6 @@ class CodeAnalysis:
|
|
|
205
214
|
CodeAnalysisType.ALGORITHM: self.configs.algorithm,
|
|
206
215
|
CodeAnalysisType.COMPLEXITY: self.configs.complexity,
|
|
207
216
|
CodeAnalysisType.QUALITY: self.configs.quality,
|
|
217
|
+
CodeAnalysisType.SECURITY: self.configs.security,
|
|
208
218
|
}
|
|
209
219
|
return config_map.get(analysis_type)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Iterable
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel
|
|
6
6
|
|
|
@@ -24,7 +24,7 @@ class FileAnalysisResult(BaseModel):
|
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
path: str
|
|
27
|
-
results: dict[
|
|
27
|
+
results: dict[CodeAnalysisType, BaseAnalysisResult]
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class AggregateStats(BaseModel):
|
|
@@ -46,7 +46,7 @@ class AggregateStats(BaseModel):
|
|
|
46
46
|
total_files: int
|
|
47
47
|
analyzed_files: int
|
|
48
48
|
errors: list[str]
|
|
49
|
-
by_type_avg: dict[
|
|
49
|
+
by_type_avg: dict[CodeAnalysisType, dict[str, float]]
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
class MultiAnalysisResult(BaseModel):
|
|
@@ -169,16 +169,15 @@ def analyze_file(
|
|
|
169
169
|
source_code = path.read_text(encoding="utf-8", errors="ignore")
|
|
170
170
|
analysis = CodeAnalysis(source_code=source_code, configs=configs)
|
|
171
171
|
result_map = analysis.analyze(types)
|
|
172
|
-
as_dict: dict[
|
|
172
|
+
as_dict: dict[CodeAnalysisType, BaseAnalysisResult] = {}
|
|
173
173
|
for t, model in result_map.items():
|
|
174
|
-
|
|
175
|
-
as_dict[t.name] = m.model_dump()
|
|
174
|
+
as_dict[t] = model
|
|
176
175
|
return FileAnalysisResult(path=str(path), results=as_dict)
|
|
177
176
|
|
|
178
177
|
|
|
179
178
|
def _aggregate_numeric_means(
|
|
180
179
|
files: list[FileAnalysisResult],
|
|
181
|
-
) -> dict[
|
|
180
|
+
) -> dict[CodeAnalysisType, dict[str, float]]:
|
|
182
181
|
"""
|
|
183
182
|
数値メトリクスの平均値を集約
|
|
184
183
|
|
|
@@ -192,17 +191,17 @@ def _aggregate_numeric_means(
|
|
|
192
191
|
dict[str, dict[str, float]]
|
|
193
192
|
解析タイプ別の平均値辞書
|
|
194
193
|
"""
|
|
195
|
-
by_type: dict[
|
|
194
|
+
by_type: dict[CodeAnalysisType, dict[str, list[float]]] = {}
|
|
196
195
|
|
|
197
196
|
for fa in files:
|
|
198
197
|
for tname, metrics in fa.results.items():
|
|
199
198
|
if tname not in by_type:
|
|
200
199
|
by_type[tname] = {}
|
|
201
|
-
for key, val in metrics.items():
|
|
200
|
+
for key, val in metrics.model_dump().items():
|
|
202
201
|
if isinstance(val, (int, float)):
|
|
203
202
|
by_type[tname].setdefault(key, []).append(float(val))
|
|
204
203
|
|
|
205
|
-
avg: dict[
|
|
204
|
+
avg: dict[CodeAnalysisType, dict[str, float]] = {}
|
|
206
205
|
for tname, metrics_map in by_type.items():
|
|
207
206
|
avg[tname] = {}
|
|
208
207
|
for key, values in metrics_map.items():
|
|
@@ -11,6 +11,7 @@ src/code_insight/code_analysis/complexity.py
|
|
|
11
11
|
src/code_insight/code_analysis/quality.py
|
|
12
12
|
src/code_insight/code_analysis/readability.py
|
|
13
13
|
src/code_insight/code_analysis/redundancy.py
|
|
14
|
+
src/code_insight/code_analysis/security.py
|
|
14
15
|
src/code_insight/code_analysis/struct.py
|
|
15
16
|
src/code_insight/code_analysis/style.py
|
|
16
17
|
src/code_insight/trend_analysis/__init__.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|