codinsight 0.0.7__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.
- codinsight-0.0.8/PKG-INFO +108 -0
- codinsight-0.0.8/README.md +91 -0
- {codinsight-0.0.7 → codinsight-0.0.8}/pyproject.toml +1 -1
- codinsight-0.0.8/src/code_insight/__init__.py +56 -0
- codinsight-0.0.8/src/code_insight/code_analysis/abstract.py +38 -0
- {codinsight-0.0.7 → codinsight-0.0.8}/src/code_insight/code_analysis/algorithm.py +79 -35
- {codinsight-0.0.7 → codinsight-0.0.8}/src/code_insight/code_analysis/complexity.py +52 -14
- codinsight-0.0.8/src/code_insight/code_analysis/quality.py +233 -0
- {codinsight-0.0.7 → codinsight-0.0.8}/src/code_insight/code_analysis/readability.py +88 -32
- {codinsight-0.0.7 → codinsight-0.0.8}/src/code_insight/code_analysis/redundancy.py +51 -55
- {codinsight-0.0.7 → codinsight-0.0.8}/src/code_insight/code_analysis/struct.py +87 -35
- {codinsight-0.0.7 → codinsight-0.0.8}/src/code_insight/code_analysis/style.py +42 -12
- codinsight-0.0.8/src/code_insight/core.py +119 -0
- codinsight-0.0.8/src/code_insight/multi_analysis.py +170 -0
- codinsight-0.0.8/src/codinsight.egg-info/PKG-INFO +108 -0
- {codinsight-0.0.7 → codinsight-0.0.8}/src/codinsight.egg-info/SOURCES.txt +2 -0
- codinsight-0.0.7/PKG-INFO +0 -16
- codinsight-0.0.7/README.md +0 -0
- codinsight-0.0.7/src/code_insight/__init__.py +0 -26
- codinsight-0.0.7/src/code_insight/code_analysis/abstract.py +0 -20
- codinsight-0.0.7/src/code_insight/core.py +0 -69
- codinsight-0.0.7/src/codinsight.egg-info/PKG-INFO +0 -16
- {codinsight-0.0.7 → codinsight-0.0.8}/setup.cfg +0 -0
- {codinsight-0.0.7 → codinsight-0.0.8}/src/code_insight/code_analysis/__init__.py +0 -0
- {codinsight-0.0.7 → codinsight-0.0.8}/src/code_insight/py.typed +0 -0
- {codinsight-0.0.7 → codinsight-0.0.8}/src/code_insight/trend_analysis/__init__.py +0 -0
- {codinsight-0.0.7 → codinsight-0.0.8}/src/code_insight/trend_analysis/trend_analysis.py +0 -0
- {codinsight-0.0.7 → codinsight-0.0.8}/src/codinsight.egg-info/dependency_links.txt +0 -0
- {codinsight-0.0.7 → codinsight-0.0.8}/src/codinsight.egg-info/requires.txt +0 -0
- {codinsight-0.0.7 → 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
|
+
- 現時点では直列処理のみ対応。大規模データや並列化は将来的な拡張予定
|
|
@@ -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")
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
from typing import Set
|
|
3
3
|
|
|
4
|
-
from code_insight.code_analysis.abstract import
|
|
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
|
|
5
20
|
|
|
6
21
|
|
|
7
22
|
class AlgorithmAnalysisResult(BaseAnalysisResult):
|
|
@@ -18,8 +33,6 @@ class AlgorithmAnalysisResult(BaseAnalysisResult):
|
|
|
18
33
|
* lambda式の数
|
|
19
34
|
* リスト内包表記の数
|
|
20
35
|
* map/filter/reduce呼び出しの数
|
|
21
|
-
* 循環的複雑度
|
|
22
|
-
* McCabe複雑度の平均
|
|
23
36
|
* ネスト深度
|
|
24
37
|
* 制御構文の最大ネスト深度
|
|
25
38
|
"""
|
|
@@ -32,55 +45,78 @@ class AlgorithmAnalysisResult(BaseAnalysisResult):
|
|
|
32
45
|
lambda_count: int
|
|
33
46
|
comprehension_count: int
|
|
34
47
|
functional_call_count: int
|
|
35
|
-
cyclomatic_complexity: float
|
|
36
48
|
max_nesting_depth: int
|
|
37
49
|
|
|
38
50
|
|
|
39
|
-
class Algorithm(AbstractAnalysis[AlgorithmAnalysisResult]):
|
|
51
|
+
class Algorithm(AbstractAnalysis[AlgorithmAnalysisResult, AlgorithmAnalysisConfig]):
|
|
40
52
|
"""解析クラス(アルゴリズム)"""
|
|
41
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
|
+
|
|
42
62
|
def analyze(self, source_code: str) -> AlgorithmAnalysisResult:
|
|
43
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
|
+
|
|
44
79
|
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
|
-
|
|
54
|
-
max_nesting_depth=self.get_max_nesting_depth(source_code),
|
|
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),
|
|
55
89
|
)
|
|
56
90
|
|
|
57
91
|
def parse_source_code(self, source_code: str) -> ast.AST:
|
|
58
92
|
"""ソースコードを解析"""
|
|
59
93
|
return ast.parse(source_code)
|
|
60
94
|
|
|
61
|
-
def get_if_count(self, source_code: str) -> int:
|
|
95
|
+
def get_if_count(self, source_code: str, tree: ast.AST | None = None) -> int:
|
|
62
96
|
"""if文の数を取得"""
|
|
63
|
-
tree = self.parse_source_code(source_code)
|
|
97
|
+
tree = tree or self.parse_source_code(source_code)
|
|
64
98
|
return sum(isinstance(node, ast.If) for node in ast.walk(tree))
|
|
65
99
|
|
|
66
|
-
def get_for_count(self, source_code: str) -> int:
|
|
100
|
+
def get_for_count(self, source_code: str, tree: ast.AST | None = None) -> int:
|
|
67
101
|
"""for文の数を取得"""
|
|
68
|
-
tree = self.parse_source_code(source_code)
|
|
102
|
+
tree = tree or self.parse_source_code(source_code)
|
|
69
103
|
return sum(isinstance(node, (ast.For, ast.AsyncFor)) for node in ast.walk(tree))
|
|
70
104
|
|
|
71
|
-
def get_while_count(self, source_code: str) -> int:
|
|
105
|
+
def get_while_count(self, source_code: str, tree: ast.AST | None = None) -> int:
|
|
72
106
|
"""while文の数を取得"""
|
|
73
|
-
tree = self.parse_source_code(source_code)
|
|
107
|
+
tree = tree or self.parse_source_code(source_code)
|
|
74
108
|
return sum(isinstance(node, ast.While) for node in ast.walk(tree))
|
|
75
109
|
|
|
76
|
-
def get_try_count(self, source_code: str) -> int:
|
|
110
|
+
def get_try_count(self, source_code: str, tree: ast.AST | None = None) -> int:
|
|
77
111
|
"""try-except文の数を取得"""
|
|
78
|
-
tree = self.parse_source_code(source_code)
|
|
112
|
+
tree = tree or self.parse_source_code(source_code)
|
|
79
113
|
return sum(isinstance(node, ast.Try) for node in ast.walk(tree))
|
|
80
114
|
|
|
81
|
-
def get_recursion_rate(
|
|
115
|
+
def get_recursion_rate(
|
|
116
|
+
self, source_code: str, tree: ast.AST | None = None
|
|
117
|
+
) -> float:
|
|
82
118
|
"""再帰関数の割合を取得"""
|
|
83
|
-
tree = self.parse_source_code(source_code)
|
|
119
|
+
tree = tree or self.parse_source_code(source_code)
|
|
84
120
|
function_names: Set[str] = set()
|
|
85
121
|
recursive_functions: Set[str] = set()
|
|
86
122
|
|
|
@@ -102,14 +138,16 @@ class Algorithm(AbstractAnalysis[AlgorithmAnalysisResult]):
|
|
|
102
138
|
return len(recursive_functions) / len(function_names)
|
|
103
139
|
return 0.0
|
|
104
140
|
|
|
105
|
-
def get_lambda_count(self, source_code: str) -> int:
|
|
141
|
+
def get_lambda_count(self, source_code: str, tree: ast.AST | None = None) -> int:
|
|
106
142
|
"""lambda式の数を取得"""
|
|
107
|
-
tree = self.parse_source_code(source_code)
|
|
143
|
+
tree = tree or self.parse_source_code(source_code)
|
|
108
144
|
return sum(isinstance(node, ast.Lambda) for node in ast.walk(tree))
|
|
109
145
|
|
|
110
|
-
def get_comprehension_count(
|
|
146
|
+
def get_comprehension_count(
|
|
147
|
+
self, source_code: str, tree: ast.AST | None = None
|
|
148
|
+
) -> int:
|
|
111
149
|
"""内包表記の数を取得"""
|
|
112
|
-
tree = self.parse_source_code(source_code)
|
|
150
|
+
tree = tree or self.parse_source_code(source_code)
|
|
113
151
|
return sum(
|
|
114
152
|
isinstance(
|
|
115
153
|
node, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)
|
|
@@ -117,9 +155,11 @@ class Algorithm(AbstractAnalysis[AlgorithmAnalysisResult]):
|
|
|
117
155
|
for node in ast.walk(tree)
|
|
118
156
|
)
|
|
119
157
|
|
|
120
|
-
def get_functional_call_count(
|
|
158
|
+
def get_functional_call_count(
|
|
159
|
+
self, source_code: str, tree: ast.AST | None = None
|
|
160
|
+
) -> int:
|
|
121
161
|
"""map/filter/reduce呼び出しの数を取得"""
|
|
122
|
-
tree = self.parse_source_code(source_code)
|
|
162
|
+
tree = tree or self.parse_source_code(source_code)
|
|
123
163
|
functional_names = {"map", "filter", "reduce"}
|
|
124
164
|
count = 0
|
|
125
165
|
|
|
@@ -133,9 +173,11 @@ class Algorithm(AbstractAnalysis[AlgorithmAnalysisResult]):
|
|
|
133
173
|
|
|
134
174
|
return count
|
|
135
175
|
|
|
136
|
-
def get_cyclomatic_complexity(
|
|
176
|
+
def get_cyclomatic_complexity(
|
|
177
|
+
self, source_code: str, tree: ast.AST | None = None
|
|
178
|
+
) -> float:
|
|
137
179
|
"""循環的複雑度の平均を取得"""
|
|
138
|
-
tree = self.parse_source_code(source_code)
|
|
180
|
+
tree = tree or self.parse_source_code(source_code)
|
|
139
181
|
complexities = []
|
|
140
182
|
|
|
141
183
|
for node in ast.walk(tree):
|
|
@@ -159,9 +201,11 @@ class Algorithm(AbstractAnalysis[AlgorithmAnalysisResult]):
|
|
|
159
201
|
|
|
160
202
|
return complexity
|
|
161
203
|
|
|
162
|
-
def get_max_nesting_depth(
|
|
204
|
+
def get_max_nesting_depth(
|
|
205
|
+
self, source_code: str, tree: ast.AST | None = None
|
|
206
|
+
) -> int:
|
|
163
207
|
"""制御構文の最大ネスト深度を取得"""
|
|
164
|
-
tree = self.parse_source_code(source_code)
|
|
208
|
+
tree = tree or self.parse_source_code(source_code)
|
|
165
209
|
max_depth = 0
|
|
166
210
|
|
|
167
211
|
def calculate_depth(node: ast.AST, current_depth: int = 0) -> None:
|
|
@@ -3,7 +3,18 @@ import ast
|
|
|
3
3
|
import radon.complexity as cc
|
|
4
4
|
import radon.metrics as metrics
|
|
5
5
|
|
|
6
|
-
from code_insight.code_analysis.abstract import
|
|
6
|
+
from code_insight.code_analysis.abstract import (
|
|
7
|
+
AbstractAnalysis,
|
|
8
|
+
BaseAnalysisConfig,
|
|
9
|
+
BaseAnalysisResult,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ComplexityAnalysisConfig(BaseAnalysisConfig):
|
|
14
|
+
"""複雑度解析設定"""
|
|
15
|
+
|
|
16
|
+
max_nesting_depth_threshold: int = 5
|
|
17
|
+
cognitive_complexity_threshold: float = 10.0
|
|
7
18
|
|
|
8
19
|
|
|
9
20
|
class ComplexityAnalysisResult(BaseAnalysisResult):
|
|
@@ -31,19 +42,40 @@ class ComplexityAnalysisResult(BaseAnalysisResult):
|
|
|
31
42
|
maintainability_index: float
|
|
32
43
|
|
|
33
44
|
|
|
34
|
-
class Complexity(AbstractAnalysis[ComplexityAnalysisResult]):
|
|
45
|
+
class Complexity(AbstractAnalysis[ComplexityAnalysisResult, ComplexityAnalysisConfig]):
|
|
35
46
|
"""解析クラス(複雑度)"""
|
|
36
47
|
|
|
48
|
+
def __init__(self, config: ComplexityAnalysisConfig | None = None) -> None:
|
|
49
|
+
"""コンストラクタ"""
|
|
50
|
+
super().__init__(config)
|
|
51
|
+
|
|
52
|
+
def get_default_config(self) -> ComplexityAnalysisConfig:
|
|
53
|
+
"""デフォルト設定を取得"""
|
|
54
|
+
return ComplexityAnalysisConfig()
|
|
55
|
+
|
|
37
56
|
def analyze(self, source_code: str) -> ComplexityAnalysisResult:
|
|
38
57
|
"""コード解析"""
|
|
58
|
+
tree = ast.parse(source_code) if source_code.strip() else ast.parse("")
|
|
59
|
+
if not self.config.enabled:
|
|
60
|
+
return ComplexityAnalysisResult(
|
|
61
|
+
cyclomatic_complexity=0.0,
|
|
62
|
+
halstead_volume=0.0,
|
|
63
|
+
halstead_difficulty=0.0,
|
|
64
|
+
halstead_effort=0.0,
|
|
65
|
+
max_nesting_depth=0,
|
|
66
|
+
avg_nesting_depth=0.0,
|
|
67
|
+
cognitive_complexity=0.0,
|
|
68
|
+
maintainability_index=0.0,
|
|
69
|
+
)
|
|
70
|
+
|
|
39
71
|
return ComplexityAnalysisResult(
|
|
40
72
|
cyclomatic_complexity=self.get_cyclomatic_complexity(source_code),
|
|
41
73
|
halstead_volume=self.get_halstead_volume(source_code),
|
|
42
74
|
halstead_difficulty=self.get_halstead_difficulty(source_code),
|
|
43
75
|
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),
|
|
76
|
+
max_nesting_depth=self.get_max_nesting_depth(source_code, tree),
|
|
77
|
+
avg_nesting_depth=self.get_avg_nesting_depth(source_code, tree),
|
|
78
|
+
cognitive_complexity=self.get_cognitive_complexity(source_code, tree),
|
|
47
79
|
maintainability_index=self.get_maintainability_index(source_code),
|
|
48
80
|
)
|
|
49
81
|
|
|
@@ -105,13 +137,15 @@ class Complexity(AbstractAnalysis[ComplexityAnalysisResult]):
|
|
|
105
137
|
except Exception:
|
|
106
138
|
return 0.0
|
|
107
139
|
|
|
108
|
-
def get_max_nesting_depth(
|
|
140
|
+
def get_max_nesting_depth(
|
|
141
|
+
self, source_code: str, tree: ast.AST | None = None
|
|
142
|
+
) -> int:
|
|
109
143
|
"""最大ネスト深度を取得"""
|
|
110
|
-
if not source_code.strip():
|
|
144
|
+
if not source_code.strip() and tree is None:
|
|
111
145
|
return 0
|
|
112
146
|
|
|
113
147
|
try:
|
|
114
|
-
tree = ast.parse(source_code)
|
|
148
|
+
tree = tree or ast.parse(source_code)
|
|
115
149
|
max_depth = 0
|
|
116
150
|
|
|
117
151
|
def calculate_depth(node: ast.AST, current_depth: int = 0) -> int:
|
|
@@ -142,13 +176,15 @@ class Complexity(AbstractAnalysis[ComplexityAnalysisResult]):
|
|
|
142
176
|
except Exception:
|
|
143
177
|
return 0
|
|
144
178
|
|
|
145
|
-
def get_avg_nesting_depth(
|
|
179
|
+
def get_avg_nesting_depth(
|
|
180
|
+
self, source_code: str, tree: ast.AST | None = None
|
|
181
|
+
) -> float:
|
|
146
182
|
"""平均ネスト深度を取得"""
|
|
147
|
-
if not source_code.strip():
|
|
183
|
+
if not source_code.strip() and tree is None:
|
|
148
184
|
return 0.0
|
|
149
185
|
|
|
150
186
|
try:
|
|
151
|
-
tree = ast.parse(source_code)
|
|
187
|
+
tree = tree or ast.parse(source_code)
|
|
152
188
|
depths = []
|
|
153
189
|
|
|
154
190
|
def collect_depths(node: ast.AST, current_depth: int = 0) -> None:
|
|
@@ -166,13 +202,15 @@ class Complexity(AbstractAnalysis[ComplexityAnalysisResult]):
|
|
|
166
202
|
except Exception:
|
|
167
203
|
return 0.0
|
|
168
204
|
|
|
169
|
-
def get_cognitive_complexity(
|
|
205
|
+
def get_cognitive_complexity(
|
|
206
|
+
self, source_code: str, tree: ast.AST | None = None
|
|
207
|
+
) -> float:
|
|
170
208
|
"""認知的複雑度を取得"""
|
|
171
|
-
if not source_code.strip():
|
|
209
|
+
if not source_code.strip() and tree is None:
|
|
172
210
|
return 0.0
|
|
173
211
|
|
|
174
212
|
try:
|
|
175
|
-
tree = ast.parse(source_code)
|
|
213
|
+
tree = tree or ast.parse(source_code)
|
|
176
214
|
complexity = 0
|
|
177
215
|
|
|
178
216
|
def calculate_cognitive_complexity(
|