Coverage for src\llm_code_lens\processors\summary.py: 4%

76 statements  

« prev     ^ index     » next       coverage.py v7.7.0, created at 2025-05-25 12:07 +0300

1# File: R:\Projects\codelens\src\codelens\processor\summary.py 

2""" 

3LLM Code Lens - Summary Processor 

4Generates project summaries from analysis results. 

5""" 

6 

7from typing import Dict, List 

8from pathlib import Path 

9from ..utils import estimate_todo_priority, is_potential_entry_point, is_core_file 

10 

11# src/llm_code_lens/processors/summary.py 

12 

13def generate_summary(analysis: Dict[str, dict]) -> dict: 

14 """Generate summary with improved metrics handling.""" 

15 summary = { 

16 'project_stats': { 

17 'total_files': len(analysis), 

18 'by_type': {}, 

19 'lines_of_code': 0, 

20 'avg_file_size': 0 

21 }, 

22 'code_metrics': { 

23 'functions': {'count': 0, 'with_docs': 0, 'complex': 0}, 

24 'classes': {'count': 0, 'with_docs': 0}, 

25 'imports': {'count': 0, 'unique': set()}, # Changed to set() 

26 'sql_objects': {'procedures': 0, 'views': 0, 'functions': 0} 

27 }, 

28 'maintenance': { 

29 'todos': [], 

30 'comments_ratio': 0, 

31 'doc_coverage': 0 

32 }, 

33 'structure': { 

34 'directories': set(), 

35 'entry_points': [], 

36 'core_files': [], # Added missing key 

37 'sql_dependencies': [] 

38 } 

39 } 

40 

41 # Process each file 

42 for file_path, file_analysis in analysis.items(): 

43 _process_file_stats(file_path, file_analysis, summary) 

44 _process_code_metrics(file_analysis, summary) 

45 _process_maintenance_info(file_path, file_analysis, summary) 

46 _process_structure_info(file_path, file_analysis, summary) 

47 

48 # Calculate final metrics 

49 _calculate_final_metrics(summary) 

50 

51 return summary 

52 

53 

54 

55def _process_file_stats(file_path: str, analysis: dict, summary: dict) -> None: 

56 """Process basic file statistics.""" 

57 # Track file types 

58 ext = Path(file_path).suffix 

59 summary['project_stats']['by_type'][ext] = \ 

60 summary['project_stats']['by_type'].get(ext, 0) + 1 

61 

62 # Track lines of code 

63 metrics = analysis.get('metrics', {}) 

64 loc = metrics.get('loc', 0) 

65 summary['project_stats']['lines_of_code'] += loc 

66 

67def _process_code_metrics(file_analysis: dict, summary: dict) -> None: 

68 """Process code metrics from analysis.""" 

69 if not isinstance(file_analysis, dict): 

70 return 

71 

72 # Process functions 

73 for func in file_analysis.get('functions', []): 

74 if not isinstance(func, dict): 

75 continue 

76 

77 summary['code_metrics']['functions']['count'] += 1 

78 

79 if func.get('docstring'): 

80 summary['code_metrics']['functions']['with_docs'] += 1 

81 

82 # Safely handle complexity and loc values that might be None 

83 complexity = func.get('complexity') 

84 loc = func.get('loc') 

85 

86 if (complexity is not None and complexity > 5) or \ 

87 (loc is not None and loc > 50): 

88 summary['code_metrics']['functions']['complex'] += 1 

89 

90 # Process classes 

91 for cls in file_analysis.get('classes', []): 

92 if not isinstance(cls, dict): 

93 continue 

94 

95 summary['code_metrics']['classes']['count'] += 1 

96 if cls.get('docstring'): 

97 summary['code_metrics']['classes']['with_docs'] += 1 

98 

99 # Process imports 

100 imports = file_analysis.get('imports', []) 

101 if isinstance(imports, list): 

102 summary['code_metrics']['imports']['count'] += len(imports) 

103 summary['code_metrics']['imports']['unique'].update( 

104 set(imp for imp in imports if isinstance(imp, str)) 

105 ) 

106 

107 

108 

109 

110def _process_maintenance_info(file_path: str, analysis: dict, summary: dict) -> None: 

111 """Process maintenance-related information.""" 

112 # Track TODOs 

113 for todo in analysis.get('todos', []): 

114 summary['maintenance']['todos'].append({ 

115 'file': file_path, 

116 'line': todo['line'], 

117 'text': todo['text'], 

118 'priority': _estimate_todo_priority(todo['text']) 

119 }) 

120 

121 # Track comments 

122 comments = len(analysis.get('comments', [])) 

123 lines = analysis.get('metrics', {}).get('loc', 0) 

124 if lines > 0: 

125 summary['maintenance']['comments_ratio'] += comments / lines 

126 

127def _process_structure_info(file_path: str, analysis: dict, summary: dict) -> None: 

128 """Process project structure information.""" 

129 # Track directories 

130 dir_path = str(Path(file_path).parent) 

131 summary['structure']['directories'].add(dir_path) 

132 

133 # Identify potential entry points 

134 if _is_potential_entry_point(file_path, analysis): 

135 summary['structure']['entry_points'].append(file_path) 

136 

137 # Identify core files based on imports 

138 if _is_core_file(analysis): 

139 summary['structure']['core_files'].append(file_path) 

140 

141def _calculate_final_metrics(summary: dict) -> None: 

142 """Calculate final averages and percentages.""" 

143 total_files = summary['project_stats']['total_files'] 

144 if total_files > 0: 

145 # Calculate average file size 

146 summary['project_stats']['avg_file_size'] = \ 

147 summary['project_stats']['lines_of_code'] / total_files 

148 

149 # Calculate documentation coverage 

150 funcs = summary['code_metrics']['functions'] 

151 classes = summary['code_metrics']['classes'] 

152 total_elements = funcs['count'] + classes['count'] 

153 if total_elements > 0: 

154 documented = funcs['with_docs'] + classes['with_docs'] 

155 summary['maintenance']['doc_coverage'] = \ 

156 (documented / total_elements) * 100 

157 

158 # Convert sets to lists for JSON serialization 

159 summary['code_metrics']['imports']['unique'] = \ 

160 list(summary['code_metrics']['imports']['unique']) 

161 summary['structure']['directories'] = \ 

162 list(summary['structure']['directories']) 

163 

164def _estimate_todo_priority(text: str) -> str: 

165 """Estimate TODO priority based on content.""" 

166 return estimate_todo_priority(text) 

167 

168def _is_potential_entry_point(file_path: str, analysis: dict) -> bool: 

169 """Identify if a file is a potential entry point.""" 

170 return is_potential_entry_point(file_path, analysis) 

171 

172def _is_core_file(analysis: dict) -> bool: 

173 """Identify if a file is likely a core component.""" 

174 return is_core_file(analysis) 

175 

176def generate_insights(analysis: Dict[str, dict]) -> List[str]: 

177 """Generate insights with improved handling of file analysis.""" 

178 from .insights import generate_insights as gen_insights 

179 return gen_insights(analysis) 

180 

181