codinsight 0.0.6__tar.gz → 0.0.8__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 (31) hide show
  1. codinsight-0.0.8/PKG-INFO +108 -0
  2. codinsight-0.0.8/README.md +91 -0
  3. {codinsight-0.0.6 → codinsight-0.0.8}/pyproject.toml +1 -1
  4. codinsight-0.0.8/src/code_insight/__init__.py +56 -0
  5. codinsight-0.0.8/src/code_insight/code_analysis/abstract.py +38 -0
  6. codinsight-0.0.8/src/code_insight/code_analysis/algorithm.py +233 -0
  7. codinsight-0.0.8/src/code_insight/code_analysis/complexity.py +250 -0
  8. codinsight-0.0.8/src/code_insight/code_analysis/quality.py +233 -0
  9. codinsight-0.0.8/src/code_insight/code_analysis/readability.py +328 -0
  10. codinsight-0.0.8/src/code_insight/code_analysis/redundancy.py +216 -0
  11. {codinsight-0.0.6 → codinsight-0.0.8}/src/code_insight/code_analysis/struct.py +87 -35
  12. {codinsight-0.0.6 → codinsight-0.0.8}/src/code_insight/code_analysis/style.py +42 -12
  13. codinsight-0.0.8/src/code_insight/core.py +119 -0
  14. codinsight-0.0.8/src/code_insight/multi_analysis.py +170 -0
  15. codinsight-0.0.8/src/code_insight/trend_analysis/trend_analysis.py +104 -0
  16. codinsight-0.0.8/src/codinsight.egg-info/PKG-INFO +108 -0
  17. {codinsight-0.0.6 → codinsight-0.0.8}/src/codinsight.egg-info/SOURCES.txt +6 -0
  18. codinsight-0.0.6/PKG-INFO +0 -16
  19. codinsight-0.0.6/README.md +0 -0
  20. codinsight-0.0.6/src/code_insight/__init__.py +0 -13
  21. codinsight-0.0.6/src/code_insight/code_analysis/abstract.py +0 -20
  22. codinsight-0.0.6/src/code_insight/core.py +0 -48
  23. codinsight-0.0.6/src/code_insight/trend_analysis/trend_analysis.py +0 -42
  24. codinsight-0.0.6/src/codinsight.egg-info/PKG-INFO +0 -16
  25. {codinsight-0.0.6 → codinsight-0.0.8}/setup.cfg +0 -0
  26. {codinsight-0.0.6 → codinsight-0.0.8}/src/code_insight/code_analysis/__init__.py +0 -0
  27. {codinsight-0.0.6 → codinsight-0.0.8}/src/code_insight/py.typed +0 -0
  28. {codinsight-0.0.6 → codinsight-0.0.8}/src/code_insight/trend_analysis/__init__.py +0 -0
  29. {codinsight-0.0.6 → codinsight-0.0.8}/src/codinsight.egg-info/dependency_links.txt +0 -0
  30. {codinsight-0.0.6 → codinsight-0.0.8}/src/codinsight.egg-info/requires.txt +0 -0
  31. {codinsight-0.0.6 → codinsight-0.0.8}/src/codinsight.egg-info/top_level.txt +0 -0
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: codinsight
3
+ Version: 0.0.8
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
17
+
18
+ Codinsight - 複数ファイル解析の利用方法
19
+
20
+ 概要
21
+ - 複数のファイルやディレクトリを再帰的に走査し、既存の解析エンジン(Style/Struct/Readability/Redundancy/Algorithm/Complexity/Quality)で一括解析を実行
22
+ - 拡張子のフィルタや除外パターン(node_modules, .git など)に対応
23
+ - 結果をファイル単位と集約統計(平均など)で取得可能
24
+ - Pydantic BaseModel で JSON 化が容易
25
+
26
+ ## API 使用例
27
+
28
+ ```python
29
+ from code_insight.core import CodeAnalysisType
30
+ from code_insight.multi_analysis import MultiFileAnalyzer
31
+
32
+ analyzer = MultiFileAnalyzer(
33
+ exts={".py"},
34
+ excludes={"node_modules", "target", ".git", ".venv", "__pycache__"},
35
+ )
36
+ result = analyzer.analyze(
37
+ ["src", "tests"],
38
+ [CodeAnalysisType.STYLE, CodeAnalysisType.STRUCT],
39
+ )
40
+ print(result.model_dump_json())
41
+ ```
42
+
43
+ ## 設定のカスタマイズ
44
+
45
+ ```python
46
+ from code_insight.core import CodeAnalysis, AnalysisConfigs, CodeAnalysisType
47
+ from code_insight.code_analysis.quality import QualityAnalysisConfig
48
+ from code_insight.code_analysis.redundancy import RedundancyAnalysisConfig
49
+ from code_insight.code_analysis.style import StyleAnalysisConfig
50
+
51
+ # カスタム設定の作成
52
+ configs = AnalysisConfigs(
53
+ quality=QualityAnalysisConfig(
54
+ long_param_threshold=3, # デフォルト: 5
55
+ enabled=True
56
+ ),
57
+ redundancy=RedundancyAnalysisConfig(
58
+ long_function_lines_threshold=30, # デフォルト: 50
59
+ long_function_complexity_threshold=8, # デフォルト: 10
60
+ ignored_function_names={"main", "__init__", "setup"}
61
+ ),
62
+ style=StyleAnalysisConfig(
63
+ function_name_pattern=r"^[a-z_][a-z0-9_]*$", # snake_case
64
+ class_name_pattern=r"^[A-Z][a-zA-Z0-9]*$" # PascalCase
65
+ )
66
+ )
67
+
68
+ # 設定を使用した解析
69
+ analysis = CodeAnalysis(source_code, configs)
70
+ result = analysis.analyze([CodeAnalysisType.QUALITY, CodeAnalysisType.REDUNDANCY])
71
+
72
+ # 複数ファイル解析での設定使用
73
+ analyzer = MultiFileAnalyzer(configs=configs)
74
+ result = analyzer.analyze(["src"], [CodeAnalysisType.STYLE])
75
+ ```
76
+
77
+ ### 設定可能な項目
78
+
79
+ #### Quality解析
80
+ - `long_param_threshold`: 長引数関数の閾値(デフォルト: 5)
81
+ - `enabled`: 解析の有効/無効(デフォルト: True)
82
+
83
+ #### Redundancy解析
84
+ - `long_function_lines_threshold`: 長大関数の行数閾値(デフォルト: 50)
85
+ - `long_function_complexity_threshold`: 長大関数の複雑度閾値(デフォルト: 10)
86
+ - `ignored_function_names`: 未使用コード検出で無視する関数名(デフォルト: {"main", "__init__", "__main__"})
87
+
88
+ #### Style解析
89
+ - `function_name_pattern`: 関数名の正規表現パターン(デフォルト: snake_case)
90
+ - `class_name_pattern`: クラス名の正規表現パターン(デフォルト: PascalCase)
91
+
92
+ #### その他の解析エンジン
93
+ - Algorithm, Complexity, Readability, Structの各解析エンジンにも同様の設定項目があります
94
+
95
+ 主なオプション
96
+ - exts: 対象拡張子のセット(デフォルト: {".py"})
97
+ - excludes: 除外ディレクトリ名のセット(デフォルト: {"node_modules", "target", ".git", ".venv", "__pycache__"})
98
+
99
+ 返却データの構造
100
+ - files: 各ファイルの解析結果(解析タイプ名 → メトリクス辞書)
101
+ - aggregate:
102
+ - total_files: 解析対象に収集されたファイル数
103
+ - analyzed_files: 実際に解析に成功したファイル数
104
+ - errors: 解析時にエラーとなったファイルパスの一覧
105
+ - by_type_avg: 解析タイプごとの各数値メトリクスの平均値
106
+
107
+ 注意
108
+ - 現時点では直列処理のみ対応。大規模データや並列化は将来的な拡張予定
@@ -0,0 +1,91 @@
1
+ Codinsight - 複数ファイル解析の利用方法
2
+
3
+ 概要
4
+ - 複数のファイルやディレクトリを再帰的に走査し、既存の解析エンジン(Style/Struct/Readability/Redundancy/Algorithm/Complexity/Quality)で一括解析を実行
5
+ - 拡張子のフィルタや除外パターン(node_modules, .git など)に対応
6
+ - 結果をファイル単位と集約統計(平均など)で取得可能
7
+ - Pydantic BaseModel で JSON 化が容易
8
+
9
+ ## API 使用例
10
+
11
+ ```python
12
+ from code_insight.core import CodeAnalysisType
13
+ from code_insight.multi_analysis import MultiFileAnalyzer
14
+
15
+ analyzer = MultiFileAnalyzer(
16
+ exts={".py"},
17
+ excludes={"node_modules", "target", ".git", ".venv", "__pycache__"},
18
+ )
19
+ result = analyzer.analyze(
20
+ ["src", "tests"],
21
+ [CodeAnalysisType.STYLE, CodeAnalysisType.STRUCT],
22
+ )
23
+ print(result.model_dump_json())
24
+ ```
25
+
26
+ ## 設定のカスタマイズ
27
+
28
+ ```python
29
+ from code_insight.core import CodeAnalysis, AnalysisConfigs, CodeAnalysisType
30
+ from code_insight.code_analysis.quality import QualityAnalysisConfig
31
+ from code_insight.code_analysis.redundancy import RedundancyAnalysisConfig
32
+ from code_insight.code_analysis.style import StyleAnalysisConfig
33
+
34
+ # カスタム設定の作成
35
+ configs = AnalysisConfigs(
36
+ quality=QualityAnalysisConfig(
37
+ long_param_threshold=3, # デフォルト: 5
38
+ enabled=True
39
+ ),
40
+ redundancy=RedundancyAnalysisConfig(
41
+ long_function_lines_threshold=30, # デフォルト: 50
42
+ long_function_complexity_threshold=8, # デフォルト: 10
43
+ ignored_function_names={"main", "__init__", "setup"}
44
+ ),
45
+ style=StyleAnalysisConfig(
46
+ function_name_pattern=r"^[a-z_][a-z0-9_]*$", # snake_case
47
+ class_name_pattern=r"^[A-Z][a-zA-Z0-9]*$" # PascalCase
48
+ )
49
+ )
50
+
51
+ # 設定を使用した解析
52
+ analysis = CodeAnalysis(source_code, configs)
53
+ result = analysis.analyze([CodeAnalysisType.QUALITY, CodeAnalysisType.REDUNDANCY])
54
+
55
+ # 複数ファイル解析での設定使用
56
+ analyzer = MultiFileAnalyzer(configs=configs)
57
+ result = analyzer.analyze(["src"], [CodeAnalysisType.STYLE])
58
+ ```
59
+
60
+ ### 設定可能な項目
61
+
62
+ #### Quality解析
63
+ - `long_param_threshold`: 長引数関数の閾値(デフォルト: 5)
64
+ - `enabled`: 解析の有効/無効(デフォルト: True)
65
+
66
+ #### Redundancy解析
67
+ - `long_function_lines_threshold`: 長大関数の行数閾値(デフォルト: 50)
68
+ - `long_function_complexity_threshold`: 長大関数の複雑度閾値(デフォルト: 10)
69
+ - `ignored_function_names`: 未使用コード検出で無視する関数名(デフォルト: {"main", "__init__", "__main__"})
70
+
71
+ #### Style解析
72
+ - `function_name_pattern`: 関数名の正規表現パターン(デフォルト: snake_case)
73
+ - `class_name_pattern`: クラス名の正規表現パターン(デフォルト: PascalCase)
74
+
75
+ #### その他の解析エンジン
76
+ - Algorithm, Complexity, Readability, Structの各解析エンジンにも同様の設定項目があります
77
+
78
+ 主なオプション
79
+ - exts: 対象拡張子のセット(デフォルト: {".py"})
80
+ - excludes: 除外ディレクトリ名のセット(デフォルト: {"node_modules", "target", ".git", ".venv", "__pycache__"})
81
+
82
+ 返却データの構造
83
+ - files: 各ファイルの解析結果(解析タイプ名 → メトリクス辞書)
84
+ - aggregate:
85
+ - total_files: 解析対象に収集されたファイル数
86
+ - analyzed_files: 実際に解析に成功したファイル数
87
+ - errors: 解析時にエラーとなったファイルパスの一覧
88
+ - by_type_avg: 解析タイプごとの各数値メトリクスの平均値
89
+
90
+ 注意
91
+ - 現時点では直列処理のみ対応。大規模データや並列化は将来的な拡張予定
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codinsight"
7
- version = "0.0.6"
7
+ version = "0.0.8"
8
8
  description = "Add your description here"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -0,0 +1,56 @@
1
+ from .code_analysis.algorithm import (
2
+ Algorithm,
3
+ AlgorithmAnalysisConfig,
4
+ AlgorithmAnalysisResult,
5
+ )
6
+ from .code_analysis.complexity import (
7
+ Complexity,
8
+ ComplexityAnalysisConfig,
9
+ ComplexityAnalysisResult,
10
+ )
11
+ from .code_analysis.quality import Quality, QualityAnalysisConfig, QualityAnalysisResult
12
+ from .code_analysis.readability import (
13
+ Readability,
14
+ ReadabilityAnalysisConfig,
15
+ ReadabilityAnalysisResult,
16
+ )
17
+ from .code_analysis.redundancy import (
18
+ Redundancy,
19
+ RedundancyAnalysisConfig,
20
+ RedundancyAnalysisResult,
21
+ )
22
+ from .code_analysis.struct import Struct, StructAnalysisConfig, StructAnalysisResult
23
+ from .code_analysis.style import Style, StyleAnalysisConfig, StyleAnalysisResult
24
+ from .core import AnalysisConfigs, CodeAnalysis, CodeAnalysisType
25
+ from .multi_analysis import MultiAnalysisResult, MultiFileAnalyzer
26
+ from .trend_analysis.trend_analysis import TrendAnalysis
27
+
28
+ __all__ = [
29
+ "CodeAnalysis",
30
+ "CodeAnalysisType",
31
+ "AnalysisConfigs",
32
+ "MultiFileAnalyzer",
33
+ "MultiAnalysisResult",
34
+ "Readability",
35
+ "ReadabilityAnalysisResult",
36
+ "ReadabilityAnalysisConfig",
37
+ "Algorithm",
38
+ "AlgorithmAnalysisResult",
39
+ "AlgorithmAnalysisConfig",
40
+ "Complexity",
41
+ "ComplexityAnalysisResult",
42
+ "ComplexityAnalysisConfig",
43
+ "Quality",
44
+ "QualityAnalysisResult",
45
+ "QualityAnalysisConfig",
46
+ "Redundancy",
47
+ "RedundancyAnalysisResult",
48
+ "RedundancyAnalysisConfig",
49
+ "Struct",
50
+ "StructAnalysisResult",
51
+ "StructAnalysisConfig",
52
+ "Style",
53
+ "StyleAnalysisResult",
54
+ "StyleAnalysisConfig",
55
+ "TrendAnalysis",
56
+ ]
@@ -0,0 +1,38 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Generic, TypeVar
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class BaseAnalysisConfig(BaseModel):
8
+ """解析設定のベースクラス"""
9
+
10
+ enabled: bool = True
11
+
12
+
13
+ class BaseAnalysisResult(BaseModel):
14
+ """解析結果のベースモデル"""
15
+
16
+
17
+ R = TypeVar("R", bound=BaseAnalysisResult)
18
+ C = TypeVar("C", bound=BaseAnalysisConfig)
19
+
20
+
21
+ class AbstractAnalysis(ABC, Generic[R, C]):
22
+ """解析抽象クラス"""
23
+
24
+ config: C
25
+
26
+ def __init__(self, config: C | None = None) -> None:
27
+ """コンストラクタ"""
28
+ self.config = config or self.get_default_config()
29
+
30
+ @abstractmethod
31
+ def get_default_config(self) -> C:
32
+ """デフォルト設定を取得"""
33
+ raise NotImplementedError("get_default_config method must be implemented")
34
+
35
+ @abstractmethod
36
+ def analyze(self, source_code: str) -> R:
37
+ """コードを解析する"""
38
+ raise NotImplementedError("analyze method must be implemented")
@@ -0,0 +1,233 @@
1
+ import ast
2
+ from typing import Set
3
+
4
+ from code_insight.code_analysis.abstract import (
5
+ AbstractAnalysis,
6
+ BaseAnalysisConfig,
7
+ BaseAnalysisResult,
8
+ )
9
+
10
+
11
+ class AlgorithmAnalysisConfig(BaseAnalysisConfig):
12
+ """
13
+ アルゴリズム解析設定
14
+ * 最大ネスト深度閾値
15
+ * サイクロマティック複雑度閾値
16
+ """
17
+
18
+ max_nesting_depth_threshold: int = 4
19
+ cyclomatic_complexity_threshold: float = 5.0
20
+
21
+
22
+ class AlgorithmAnalysisResult(BaseAnalysisResult):
23
+ """
24
+ 解析結果(アルゴリズム)
25
+ * 制御構文
26
+ * if文の数
27
+ * for文の数
28
+ * while文の数
29
+ * try-except文の数
30
+ * 再帰構造
31
+ * 再帰関数の割合
32
+ * FP的要素
33
+ * lambda式の数
34
+ * リスト内包表記の数
35
+ * map/filter/reduce呼び出しの数
36
+ * ネスト深度
37
+ * 制御構文の最大ネスト深度
38
+ """
39
+
40
+ if_count: int
41
+ for_count: int
42
+ while_count: int
43
+ try_count: int
44
+ recursion_rate: float
45
+ lambda_count: int
46
+ comprehension_count: int
47
+ functional_call_count: int
48
+ max_nesting_depth: int
49
+
50
+
51
+ class Algorithm(AbstractAnalysis[AlgorithmAnalysisResult, AlgorithmAnalysisConfig]):
52
+ """解析クラス(アルゴリズム)"""
53
+
54
+ def __init__(self, config: AlgorithmAnalysisConfig | None = None) -> None:
55
+ """コンストラクタ"""
56
+ super().__init__(config)
57
+
58
+ def get_default_config(self) -> AlgorithmAnalysisConfig:
59
+ """デフォルト設定を取得"""
60
+ return AlgorithmAnalysisConfig()
61
+
62
+ def analyze(self, source_code: str) -> AlgorithmAnalysisResult:
63
+ """コード解析"""
64
+ if not self.config.enabled:
65
+ return AlgorithmAnalysisResult(
66
+ if_count=0,
67
+ for_count=0,
68
+ while_count=0,
69
+ try_count=0,
70
+ recursion_rate=0.0,
71
+ lambda_count=0,
72
+ comprehension_count=0,
73
+ functional_call_count=0,
74
+ max_nesting_depth=0,
75
+ )
76
+
77
+ tree = self.parse_source_code(source_code)
78
+
79
+ return AlgorithmAnalysisResult(
80
+ if_count=self.get_if_count(source_code, tree),
81
+ for_count=self.get_for_count(source_code, tree),
82
+ while_count=self.get_while_count(source_code, tree),
83
+ try_count=self.get_try_count(source_code, tree),
84
+ recursion_rate=self.get_recursion_rate(source_code, tree),
85
+ lambda_count=self.get_lambda_count(source_code, tree),
86
+ comprehension_count=self.get_comprehension_count(source_code, tree),
87
+ functional_call_count=self.get_functional_call_count(source_code, tree),
88
+ max_nesting_depth=self.get_max_nesting_depth(source_code, tree),
89
+ )
90
+
91
+ def parse_source_code(self, source_code: str) -> ast.AST:
92
+ """ソースコードを解析"""
93
+ return ast.parse(source_code)
94
+
95
+ def get_if_count(self, source_code: str, tree: ast.AST | None = None) -> int:
96
+ """if文の数を取得"""
97
+ tree = tree or self.parse_source_code(source_code)
98
+ return sum(isinstance(node, ast.If) for node in ast.walk(tree))
99
+
100
+ def get_for_count(self, source_code: str, tree: ast.AST | None = None) -> int:
101
+ """for文の数を取得"""
102
+ tree = tree or self.parse_source_code(source_code)
103
+ return sum(isinstance(node, (ast.For, ast.AsyncFor)) for node in ast.walk(tree))
104
+
105
+ def get_while_count(self, source_code: str, tree: ast.AST | None = None) -> int:
106
+ """while文の数を取得"""
107
+ tree = tree or self.parse_source_code(source_code)
108
+ return sum(isinstance(node, ast.While) for node in ast.walk(tree))
109
+
110
+ def get_try_count(self, source_code: str, tree: ast.AST | None = None) -> int:
111
+ """try-except文の数を取得"""
112
+ tree = tree or self.parse_source_code(source_code)
113
+ return sum(isinstance(node, ast.Try) for node in ast.walk(tree))
114
+
115
+ def get_recursion_rate(
116
+ self, source_code: str, tree: ast.AST | None = None
117
+ ) -> float:
118
+ """再帰関数の割合を取得"""
119
+ tree = tree or self.parse_source_code(source_code)
120
+ function_names: Set[str] = set()
121
+ recursive_functions: Set[str] = set()
122
+
123
+ for node in ast.walk(tree):
124
+ if isinstance(node, ast.FunctionDef):
125
+ function_names.add(node.name)
126
+
127
+ for node in ast.walk(tree):
128
+ if isinstance(node, ast.FunctionDef):
129
+ for call_node in ast.walk(node):
130
+ if (
131
+ isinstance(call_node, ast.Call)
132
+ and isinstance(call_node.func, ast.Name)
133
+ and call_node.func.id == node.name
134
+ ):
135
+ recursive_functions.add(node.name)
136
+
137
+ if function_names:
138
+ return len(recursive_functions) / len(function_names)
139
+ return 0.0
140
+
141
+ def get_lambda_count(self, source_code: str, tree: ast.AST | None = None) -> int:
142
+ """lambda式の数を取得"""
143
+ tree = tree or self.parse_source_code(source_code)
144
+ return sum(isinstance(node, ast.Lambda) for node in ast.walk(tree))
145
+
146
+ def get_comprehension_count(
147
+ self, source_code: str, tree: ast.AST | None = None
148
+ ) -> int:
149
+ """内包表記の数を取得"""
150
+ tree = tree or self.parse_source_code(source_code)
151
+ return sum(
152
+ isinstance(
153
+ node, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)
154
+ )
155
+ for node in ast.walk(tree)
156
+ )
157
+
158
+ def get_functional_call_count(
159
+ self, source_code: str, tree: ast.AST | None = None
160
+ ) -> int:
161
+ """map/filter/reduce呼び出しの数を取得"""
162
+ tree = tree or self.parse_source_code(source_code)
163
+ functional_names = {"map", "filter", "reduce"}
164
+ count = 0
165
+
166
+ for node in ast.walk(tree):
167
+ if (
168
+ isinstance(node, ast.Call)
169
+ and isinstance(node.func, ast.Name)
170
+ and node.func.id in functional_names
171
+ ):
172
+ count += 1
173
+
174
+ return count
175
+
176
+ def get_cyclomatic_complexity(
177
+ self, source_code: str, tree: ast.AST | None = None
178
+ ) -> float:
179
+ """循環的複雑度の平均を取得"""
180
+ tree = tree or self.parse_source_code(source_code)
181
+ complexities = []
182
+
183
+ for node in ast.walk(tree):
184
+ if isinstance(node, ast.FunctionDef):
185
+ complexity = self._calculate_function_complexity(node)
186
+ complexities.append(complexity)
187
+
188
+ return sum(complexities) / len(complexities) if complexities else 0.0
189
+
190
+ def _calculate_function_complexity(self, func_node: ast.FunctionDef) -> int:
191
+ """関数の循環的複雑度を計算"""
192
+ complexity = 1
193
+
194
+ for node in ast.walk(func_node):
195
+ if isinstance(node, (ast.If, ast.While, ast.For, ast.AsyncFor)):
196
+ complexity += 1
197
+ elif isinstance(node, ast.Try):
198
+ complexity += len(node.handlers)
199
+ elif isinstance(node, ast.BoolOp):
200
+ complexity += len(node.values) - 1
201
+
202
+ return complexity
203
+
204
+ def get_max_nesting_depth(
205
+ self, source_code: str, tree: ast.AST | None = None
206
+ ) -> int:
207
+ """制御構文の最大ネスト深度を取得"""
208
+ tree = tree or self.parse_source_code(source_code)
209
+ max_depth = 0
210
+
211
+ def calculate_depth(node: ast.AST, current_depth: int = 0) -> None:
212
+ nonlocal max_depth
213
+
214
+ if isinstance(
215
+ node,
216
+ (
217
+ ast.If,
218
+ ast.For,
219
+ ast.AsyncFor,
220
+ ast.While,
221
+ ast.Try,
222
+ ast.With,
223
+ ast.AsyncWith,
224
+ ),
225
+ ):
226
+ current_depth += 1
227
+ max_depth = max(max_depth, current_depth)
228
+
229
+ for child in ast.iter_child_nodes(node):
230
+ calculate_depth(child, current_depth)
231
+
232
+ calculate_depth(tree)
233
+ return max_depth