codinsight 0.0.5__tar.gz → 0.0.7__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 (28) hide show
  1. codinsight-0.0.7/PKG-INFO +16 -0
  2. codinsight-0.0.7/pyproject.toml +40 -0
  3. codinsight-0.0.7/src/code_insight/__init__.py +26 -0
  4. codinsight-0.0.7/src/code_insight/code_analysis/__init__.py +0 -0
  5. codinsight-0.0.7/src/code_insight/code_analysis/abstract.py +20 -0
  6. codinsight-0.0.7/src/code_insight/code_analysis/algorithm.py +189 -0
  7. codinsight-0.0.7/src/code_insight/code_analysis/complexity.py +212 -0
  8. codinsight-0.0.7/src/code_insight/code_analysis/readability.py +272 -0
  9. codinsight-0.0.7/src/code_insight/code_analysis/redundancy.py +220 -0
  10. codinsight-0.0.7/src/code_insight/code_analysis/struct.py +316 -0
  11. codinsight-0.0.7/src/code_insight/code_analysis/style.py +99 -0
  12. codinsight-0.0.7/src/code_insight/core.py +69 -0
  13. codinsight-0.0.7/src/code_insight/py.typed +0 -0
  14. codinsight-0.0.7/src/code_insight/trend_analysis/__init__.py +0 -0
  15. codinsight-0.0.7/src/code_insight/trend_analysis/trend_analysis.py +104 -0
  16. codinsight-0.0.7/src/codinsight.egg-info/PKG-INFO +16 -0
  17. codinsight-0.0.7/src/codinsight.egg-info/SOURCES.txt +20 -0
  18. codinsight-0.0.7/src/codinsight.egg-info/requires.txt +10 -0
  19. codinsight-0.0.7/src/codinsight.egg-info/top_level.txt +1 -0
  20. codinsight-0.0.5/PKG-INFO +0 -6
  21. codinsight-0.0.5/codinsight.egg-info/PKG-INFO +0 -6
  22. codinsight-0.0.5/codinsight.egg-info/SOURCES.txt +0 -7
  23. codinsight-0.0.5/codinsight.egg-info/top_level.txt +0 -1
  24. codinsight-0.0.5/main.py +0 -6
  25. codinsight-0.0.5/pyproject.toml +0 -7
  26. {codinsight-0.0.5 → codinsight-0.0.7}/README.md +0 -0
  27. {codinsight-0.0.5 → codinsight-0.0.7}/setup.cfg +0 -0
  28. {codinsight-0.0.5 → codinsight-0.0.7/src}/codinsight.egg-info/dependency_links.txt +0 -0
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: codinsight
3
+ Version: 0.0.7
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: matplotlib>=3.10.5
8
+ Requires-Dist: pandas>=2.3.1
9
+ Requires-Dist: pycodestyle>=2.14.0
10
+ Requires-Dist: pydantic>=2.11.7
11
+ Requires-Dist: pydocstyle>=6.3.0
12
+ Requires-Dist: pytest>=8.4.1
13
+ Requires-Dist: pytest-cov>=6.2.1
14
+ Requires-Dist: radon>=6.0.1
15
+ Requires-Dist: reportlab>=4.4.3
16
+ Requires-Dist: scikit-learn>=1.7.1
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "codinsight"
7
+ version = "0.0.7"
8
+ description = "Add your description here"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ dependencies = [
12
+ "matplotlib>=3.10.5",
13
+ "pandas>=2.3.1",
14
+ "pycodestyle>=2.14.0",
15
+ "pydantic>=2.11.7",
16
+ "pydocstyle>=6.3.0",
17
+ "pytest>=8.4.1",
18
+ "pytest-cov>=6.2.1",
19
+ "radon>=6.0.1",
20
+ "reportlab>=4.4.3",
21
+ "scikit-learn>=1.7.1",
22
+ ]
23
+
24
+ [tool.mypy]
25
+ plugins = ["pydantic.mypy"]
26
+
27
+ [tool.isort]
28
+ profile = "black"
29
+
30
+ [tool.pydocstyle]
31
+ ignore = ["D100", "D104", "D203", "D205", "D212", "D400", "D415"]
32
+ addopts = "--exclude=tests"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["src/code_insight"]
36
+ include = ["src/code_insight/py.typed"]
37
+
38
+ [tool.pytest.ini_options]
39
+ testpaths = ["tests"]
40
+ pythonpath = ["src"]
@@ -0,0 +1,26 @@
1
+ from .code_analysis.algorithm import Algorithm, AlgorithmAnalysisResult
2
+ from .code_analysis.complexity import Complexity, ComplexityAnalysisResult
3
+ from .code_analysis.readability import Readability, ReadabilityAnalysisResult
4
+ from .code_analysis.redundancy import Redundancy, RedundancyAnalysisResult
5
+ from .code_analysis.struct import Struct, StructAnalysisResult
6
+ from .code_analysis.style import Style, StyleAnalysisResult
7
+ from .core import CodeAnalysis, CodeAnalysisType
8
+ from .trend_analysis.trend_analysis import TrendAnalysis
9
+
10
+ __all__ = [
11
+ "CodeAnalysis",
12
+ "Readability",
13
+ "ReadabilityAnalysisResult",
14
+ "CodeAnalysisType",
15
+ "Style",
16
+ "StyleAnalysisResult",
17
+ "Struct",
18
+ "StructAnalysisResult",
19
+ "Redundancy",
20
+ "RedundancyAnalysisResult",
21
+ "Algorithm",
22
+ "AlgorithmAnalysisResult",
23
+ "Complexity",
24
+ "ComplexityAnalysisResult",
25
+ "TrendAnalysis",
26
+ ]
@@ -0,0 +1,20 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Generic, TypeVar
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class BaseAnalysisResult(BaseModel):
8
+ """解析結果のベースモデル"""
9
+
10
+
11
+ T = TypeVar("T", bound=BaseAnalysisResult)
12
+
13
+
14
+ class AbstractAnalysis(ABC, Generic[T]):
15
+ """解析抽象クラス"""
16
+
17
+ @abstractmethod
18
+ def analyze(self, source_code: str) -> T:
19
+ """コードを解析する"""
20
+ raise NotImplementedError("analyze method must be implemented")
@@ -0,0 +1,189 @@
1
+ import ast
2
+ from typing import Set
3
+
4
+ from code_insight.code_analysis.abstract import AbstractAnalysis, BaseAnalysisResult
5
+
6
+
7
+ class AlgorithmAnalysisResult(BaseAnalysisResult):
8
+ """
9
+ 解析結果(アルゴリズム)
10
+ * 制御構文
11
+ * if文の数
12
+ * for文の数
13
+ * while文の数
14
+ * try-except文の数
15
+ * 再帰構造
16
+ * 再帰関数の割合
17
+ * FP的要素
18
+ * lambda式の数
19
+ * リスト内包表記の数
20
+ * map/filter/reduce呼び出しの数
21
+ * 循環的複雑度
22
+ * McCabe複雑度の平均
23
+ * ネスト深度
24
+ * 制御構文の最大ネスト深度
25
+ """
26
+
27
+ if_count: int
28
+ for_count: int
29
+ while_count: int
30
+ try_count: int
31
+ recursion_rate: float
32
+ lambda_count: int
33
+ comprehension_count: int
34
+ functional_call_count: int
35
+ cyclomatic_complexity: float
36
+ max_nesting_depth: int
37
+
38
+
39
+ class Algorithm(AbstractAnalysis[AlgorithmAnalysisResult]):
40
+ """解析クラス(アルゴリズム)"""
41
+
42
+ def analyze(self, source_code: str) -> AlgorithmAnalysisResult:
43
+ """コード解析"""
44
+ return AlgorithmAnalysisResult(
45
+ if_count=self.get_if_count(source_code),
46
+ for_count=self.get_for_count(source_code),
47
+ while_count=self.get_while_count(source_code),
48
+ try_count=self.get_try_count(source_code),
49
+ recursion_rate=self.get_recursion_rate(source_code),
50
+ lambda_count=self.get_lambda_count(source_code),
51
+ comprehension_count=self.get_comprehension_count(source_code),
52
+ functional_call_count=self.get_functional_call_count(source_code),
53
+ cyclomatic_complexity=self.get_cyclomatic_complexity(source_code),
54
+ max_nesting_depth=self.get_max_nesting_depth(source_code),
55
+ )
56
+
57
+ def parse_source_code(self, source_code: str) -> ast.AST:
58
+ """ソースコードを解析"""
59
+ return ast.parse(source_code)
60
+
61
+ def get_if_count(self, source_code: str) -> int:
62
+ """if文の数を取得"""
63
+ tree = self.parse_source_code(source_code)
64
+ return sum(isinstance(node, ast.If) for node in ast.walk(tree))
65
+
66
+ def get_for_count(self, source_code: str) -> int:
67
+ """for文の数を取得"""
68
+ tree = self.parse_source_code(source_code)
69
+ return sum(isinstance(node, (ast.For, ast.AsyncFor)) for node in ast.walk(tree))
70
+
71
+ def get_while_count(self, source_code: str) -> int:
72
+ """while文の数を取得"""
73
+ tree = self.parse_source_code(source_code)
74
+ return sum(isinstance(node, ast.While) for node in ast.walk(tree))
75
+
76
+ def get_try_count(self, source_code: str) -> int:
77
+ """try-except文の数を取得"""
78
+ tree = self.parse_source_code(source_code)
79
+ return sum(isinstance(node, ast.Try) for node in ast.walk(tree))
80
+
81
+ def get_recursion_rate(self, source_code: str) -> float:
82
+ """再帰関数の割合を取得"""
83
+ tree = self.parse_source_code(source_code)
84
+ function_names: Set[str] = set()
85
+ recursive_functions: Set[str] = set()
86
+
87
+ for node in ast.walk(tree):
88
+ if isinstance(node, ast.FunctionDef):
89
+ function_names.add(node.name)
90
+
91
+ for node in ast.walk(tree):
92
+ if isinstance(node, ast.FunctionDef):
93
+ for call_node in ast.walk(node):
94
+ if (
95
+ isinstance(call_node, ast.Call)
96
+ and isinstance(call_node.func, ast.Name)
97
+ and call_node.func.id == node.name
98
+ ):
99
+ recursive_functions.add(node.name)
100
+
101
+ if function_names:
102
+ return len(recursive_functions) / len(function_names)
103
+ return 0.0
104
+
105
+ def get_lambda_count(self, source_code: str) -> int:
106
+ """lambda式の数を取得"""
107
+ tree = self.parse_source_code(source_code)
108
+ return sum(isinstance(node, ast.Lambda) for node in ast.walk(tree))
109
+
110
+ def get_comprehension_count(self, source_code: str) -> int:
111
+ """内包表記の数を取得"""
112
+ tree = self.parse_source_code(source_code)
113
+ return sum(
114
+ isinstance(
115
+ node, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)
116
+ )
117
+ for node in ast.walk(tree)
118
+ )
119
+
120
+ def get_functional_call_count(self, source_code: str) -> int:
121
+ """map/filter/reduce呼び出しの数を取得"""
122
+ tree = self.parse_source_code(source_code)
123
+ functional_names = {"map", "filter", "reduce"}
124
+ count = 0
125
+
126
+ for node in ast.walk(tree):
127
+ if (
128
+ isinstance(node, ast.Call)
129
+ and isinstance(node.func, ast.Name)
130
+ and node.func.id in functional_names
131
+ ):
132
+ count += 1
133
+
134
+ return count
135
+
136
+ def get_cyclomatic_complexity(self, source_code: str) -> float:
137
+ """循環的複雑度の平均を取得"""
138
+ tree = self.parse_source_code(source_code)
139
+ complexities = []
140
+
141
+ for node in ast.walk(tree):
142
+ if isinstance(node, ast.FunctionDef):
143
+ complexity = self._calculate_function_complexity(node)
144
+ complexities.append(complexity)
145
+
146
+ return sum(complexities) / len(complexities) if complexities else 0.0
147
+
148
+ def _calculate_function_complexity(self, func_node: ast.FunctionDef) -> int:
149
+ """関数の循環的複雑度を計算"""
150
+ complexity = 1
151
+
152
+ for node in ast.walk(func_node):
153
+ if isinstance(node, (ast.If, ast.While, ast.For, ast.AsyncFor)):
154
+ complexity += 1
155
+ elif isinstance(node, ast.Try):
156
+ complexity += len(node.handlers)
157
+ elif isinstance(node, ast.BoolOp):
158
+ complexity += len(node.values) - 1
159
+
160
+ return complexity
161
+
162
+ def get_max_nesting_depth(self, source_code: str) -> int:
163
+ """制御構文の最大ネスト深度を取得"""
164
+ tree = self.parse_source_code(source_code)
165
+ max_depth = 0
166
+
167
+ def calculate_depth(node: ast.AST, current_depth: int = 0) -> None:
168
+ nonlocal max_depth
169
+
170
+ if isinstance(
171
+ node,
172
+ (
173
+ ast.If,
174
+ ast.For,
175
+ ast.AsyncFor,
176
+ ast.While,
177
+ ast.Try,
178
+ ast.With,
179
+ ast.AsyncWith,
180
+ ),
181
+ ):
182
+ current_depth += 1
183
+ max_depth = max(max_depth, current_depth)
184
+
185
+ for child in ast.iter_child_nodes(node):
186
+ calculate_depth(child, current_depth)
187
+
188
+ calculate_depth(tree)
189
+ return max_depth
@@ -0,0 +1,212 @@
1
+ import ast
2
+
3
+ import radon.complexity as cc
4
+ import radon.metrics as metrics
5
+
6
+ from code_insight.code_analysis.abstract import AbstractAnalysis, BaseAnalysisResult
7
+
8
+
9
+ class ComplexityAnalysisResult(BaseAnalysisResult):
10
+ """
11
+ 解析結果(複雑度)
12
+ * サイクロマティック複雑度
13
+ * 関数・メソッドの平均サイクロマティック複雑度
14
+ * Halstead複雑度
15
+ * Halstead Volume, Difficulty, Effort
16
+ * ネストの深さ
17
+ * 最大ネスト深度と平均ネスト深度
18
+ * 認知的複雑度
19
+ * 制御構造の複雑さを測定
20
+ * 保守性指数
21
+ * Maintainability Index
22
+ """
23
+
24
+ cyclomatic_complexity: float
25
+ halstead_volume: float
26
+ halstead_difficulty: float
27
+ halstead_effort: float
28
+ max_nesting_depth: int
29
+ avg_nesting_depth: float
30
+ cognitive_complexity: float
31
+ maintainability_index: float
32
+
33
+
34
+ class Complexity(AbstractAnalysis[ComplexityAnalysisResult]):
35
+ """解析クラス(複雑度)"""
36
+
37
+ def analyze(self, source_code: str) -> ComplexityAnalysisResult:
38
+ """コード解析"""
39
+ return ComplexityAnalysisResult(
40
+ cyclomatic_complexity=self.get_cyclomatic_complexity(source_code),
41
+ halstead_volume=self.get_halstead_volume(source_code),
42
+ halstead_difficulty=self.get_halstead_difficulty(source_code),
43
+ halstead_effort=self.get_halstead_effort(source_code),
44
+ max_nesting_depth=self.get_max_nesting_depth(source_code),
45
+ avg_nesting_depth=self.get_avg_nesting_depth(source_code),
46
+ cognitive_complexity=self.get_cognitive_complexity(source_code),
47
+ maintainability_index=self.get_maintainability_index(source_code),
48
+ )
49
+
50
+ def get_cyclomatic_complexity(self, source_code: str) -> float:
51
+ """サイクロマティック複雑度の平均を取得"""
52
+ if not source_code.strip():
53
+ return 0.0
54
+
55
+ try:
56
+ cc_result = cc.cc_visit(source_code)
57
+ if not cc_result:
58
+ return 0.0
59
+
60
+ total_complexity = sum(item.complexity for item in cc_result)
61
+ return total_complexity / len(cc_result)
62
+ except Exception:
63
+ return 0.0
64
+
65
+ def get_halstead_volume(self, source_code: str) -> float:
66
+ """Halstead Volumeを取得"""
67
+ if not source_code.strip():
68
+ return 0.0
69
+
70
+ try:
71
+ h_result = metrics.h_visit(source_code)
72
+ return h_result.total.volume if h_result.total else 0.0
73
+ except Exception:
74
+ return 0.0
75
+
76
+ def get_halstead_difficulty(self, source_code: str) -> float:
77
+ """Halstead Difficultyを取得"""
78
+ if not source_code.strip():
79
+ return 0.0
80
+
81
+ try:
82
+ h_result = metrics.h_visit(source_code)
83
+ return h_result.total.difficulty if h_result.total else 0.0
84
+ except Exception:
85
+ return 0.0
86
+
87
+ def get_halstead_effort(self, source_code: str) -> float:
88
+ """Halstead Effortを取得"""
89
+ if not source_code.strip():
90
+ return 0.0
91
+
92
+ try:
93
+ h_result = metrics.h_visit(source_code)
94
+ return h_result.total.effort if h_result.total else 0.0
95
+ except Exception:
96
+ return 0.0
97
+
98
+ def get_maintainability_index(self, source_code: str) -> float:
99
+ """保守性指数を取得"""
100
+ if not source_code.strip():
101
+ return 0.0
102
+
103
+ try:
104
+ return metrics.mi_visit(source_code, multi=True)
105
+ except Exception:
106
+ return 0.0
107
+
108
+ def get_max_nesting_depth(self, source_code: str) -> int:
109
+ """最大ネスト深度を取得"""
110
+ if not source_code.strip():
111
+ return 0
112
+
113
+ try:
114
+ tree = ast.parse(source_code)
115
+ max_depth = 0
116
+
117
+ def calculate_depth(node: ast.AST, current_depth: int = 0) -> int:
118
+ nonlocal max_depth
119
+
120
+ nesting_nodes = (
121
+ ast.If,
122
+ ast.For,
123
+ ast.While,
124
+ ast.With,
125
+ ast.Try,
126
+ ast.FunctionDef,
127
+ ast.ClassDef,
128
+ ast.AsyncFor,
129
+ ast.AsyncWith,
130
+ )
131
+
132
+ if isinstance(node, nesting_nodes):
133
+ current_depth += 1
134
+ max_depth = max(max_depth, current_depth)
135
+
136
+ for child in ast.iter_child_nodes(node):
137
+ calculate_depth(child, current_depth)
138
+
139
+ return max_depth
140
+
141
+ return calculate_depth(tree)
142
+ except Exception:
143
+ return 0
144
+
145
+ def get_avg_nesting_depth(self, source_code: str) -> float:
146
+ """平均ネスト深度を取得"""
147
+ if not source_code.strip():
148
+ return 0.0
149
+
150
+ try:
151
+ tree = ast.parse(source_code)
152
+ depths = []
153
+
154
+ def collect_depths(node: ast.AST, current_depth: int = 0) -> None:
155
+ nesting_nodes = (ast.If, ast.For, ast.While, ast.With, ast.Try)
156
+
157
+ if isinstance(node, nesting_nodes):
158
+ current_depth += 1
159
+ depths.append(current_depth)
160
+
161
+ for child in ast.iter_child_nodes(node):
162
+ collect_depths(child, current_depth)
163
+
164
+ collect_depths(tree)
165
+ return sum(depths) / len(depths) if depths else 0.0
166
+ except Exception:
167
+ return 0.0
168
+
169
+ def get_cognitive_complexity(self, source_code: str) -> float:
170
+ """認知的複雑度を取得"""
171
+ if not source_code.strip():
172
+ return 0.0
173
+
174
+ try:
175
+ tree = ast.parse(source_code)
176
+ complexity = 0
177
+
178
+ def calculate_cognitive_complexity(
179
+ node: ast.AST, nesting_level: int = 0
180
+ ) -> int:
181
+ nonlocal complexity
182
+
183
+ if isinstance(node, (ast.If, ast.While, ast.For)):
184
+ complexity += 1 + nesting_level
185
+ elif isinstance(node, ast.Try):
186
+ complexity += 1 + nesting_level
187
+ elif isinstance(node, ast.ExceptHandler):
188
+ complexity += 1 + nesting_level
189
+ elif isinstance(node, ast.BoolOp):
190
+ complexity += len(node.values) - 1
191
+
192
+ nesting_increment_nodes = (
193
+ ast.If,
194
+ ast.For,
195
+ ast.While,
196
+ ast.Try,
197
+ ast.FunctionDef,
198
+ ast.AsyncFunctionDef,
199
+ )
200
+
201
+ new_nesting_level = nesting_level
202
+ if isinstance(node, nesting_increment_nodes):
203
+ new_nesting_level += 1
204
+
205
+ for child in ast.iter_child_nodes(node):
206
+ calculate_cognitive_complexity(child, new_nesting_level)
207
+
208
+ return complexity
209
+
210
+ return float(calculate_cognitive_complexity(tree))
211
+ except Exception:
212
+ return 0.0