55import time
66from concurrent .futures import ProcessPoolExecutor , as_completed
77from pathlib import Path
8+ from typing import Any
89
910from slopometry .core .code_analyzer import CodeAnalyzer , _analyze_single_file
1011from slopometry .core .models .complexity import (
1314 ExtendedComplexityMetrics ,
1415 FileAnalysisResult ,
1516)
17+ from slopometry .core .models .smell import SMELL_REGISTRY
1618from slopometry .core .python_feature_analyzer import PythonFeatureAnalyzer , _count_loc
1719from slopometry .core .settings import settings
1820
@@ -214,33 +216,14 @@ def _calculate_delta(
214216 current_metrics .str_type_percentage - baseline_metrics .str_type_percentage
215217 )
216218
217- delta .orphan_comment_change = current_metrics .orphan_comment_count - baseline_metrics .orphan_comment_count
218- delta .untracked_todo_change = current_metrics .untracked_todo_count - baseline_metrics .untracked_todo_count
219- delta .inline_import_change = current_metrics .inline_import_count - baseline_metrics .inline_import_count
220- delta .dict_get_with_default_change = (
221- current_metrics .dict_get_with_default_count - baseline_metrics .dict_get_with_default_count
222- )
223- delta .hasattr_getattr_change = (
224- current_metrics .hasattr_getattr_count - baseline_metrics .hasattr_getattr_count
225- )
226- delta .nonempty_init_change = current_metrics .nonempty_init_count - baseline_metrics .nonempty_init_count
227- delta .test_skip_change = current_metrics .test_skip_count - baseline_metrics .test_skip_count
228- delta .swallowed_exception_change = (
229- current_metrics .swallowed_exception_count - baseline_metrics .swallowed_exception_count
230- )
231- delta .type_ignore_change = current_metrics .type_ignore_count - baseline_metrics .type_ignore_count
232- delta .dynamic_execution_change = (
233- current_metrics .dynamic_execution_count - baseline_metrics .dynamic_execution_count
234- )
235- delta .single_method_class_change = (
236- current_metrics .single_method_class_count - baseline_metrics .single_method_class_count
237- )
238- delta .deep_inheritance_change = (
239- current_metrics .deep_inheritance_count - baseline_metrics .deep_inheritance_count
240- )
241- delta .passthrough_wrapper_change = (
242- current_metrics .passthrough_wrapper_count - baseline_metrics .passthrough_wrapper_count
243- )
219+ for name in SMELL_REGISTRY :
220+ count_field = f"{ name } _count"
221+ change_field = f"{ name } _change"
222+ setattr (
223+ delta ,
224+ change_field ,
225+ getattr (current_metrics , count_field ) - getattr (baseline_metrics , count_field ),
226+ )
244227
245228 return delta
246229
@@ -261,7 +244,8 @@ def _build_files_by_loc(self, python_files: list[Path], target_dir: Path) -> dic
261244 _ , code_loc = _count_loc (content )
262245 relative_path = self ._get_relative_path (file_path , target_dir )
263246 files_by_loc [relative_path ] = code_loc
264- except (OSError , UnicodeDecodeError ):
247+ except (OSError , UnicodeDecodeError ) as e :
248+ logger .warning (f"Skipping unreadable file { file_path } : { e } " )
265249 continue
266250 return files_by_loc
267251
@@ -433,6 +417,13 @@ def analyze_extended_complexity(self, directory: Path | None = None) -> Extended
433417 any_type_percentage = (feature_stats .any_type_count / total_type_refs * 100.0 ) if total_type_refs > 0 else 0.0
434418 str_type_percentage = (feature_stats .str_type_count / total_type_refs * 100.0 ) if total_type_refs > 0 else 0.0
435419
420+ smell_kwargs : dict [str , Any ] = {}
421+ for defn in SMELL_REGISTRY .values ():
422+ smell_kwargs [defn .count_field ] = getattr (feature_stats , defn .count_field )
423+ smell_kwargs [defn .files_field ] = sorted (
424+ [self ._get_relative_path (p , target_dir ) for p in getattr (feature_stats , defn .files_field )]
425+ )
426+
436427 return ExtendedComplexityMetrics (
437428 total_complexity = total_complexity ,
438429 average_complexity = average_complexity ,
@@ -461,58 +452,11 @@ def analyze_extended_complexity(self, directory: Path | None = None) -> Extended
461452 files_by_complexity = files_by_complexity ,
462453 files_by_effort = files_by_effort ,
463454 files_with_parse_errors = files_with_parse_errors ,
464- orphan_comment_count = feature_stats .orphan_comment_count ,
465- untracked_todo_count = feature_stats .untracked_todo_count ,
466- inline_import_count = feature_stats .inline_import_count ,
467- dict_get_with_default_count = feature_stats .dict_get_with_default_count ,
468- hasattr_getattr_count = feature_stats .hasattr_getattr_count ,
469- nonempty_init_count = feature_stats .nonempty_init_count ,
470- test_skip_count = feature_stats .test_skip_count ,
471- swallowed_exception_count = feature_stats .swallowed_exception_count ,
472- type_ignore_count = feature_stats .type_ignore_count ,
473- dynamic_execution_count = feature_stats .dynamic_execution_count ,
474- orphan_comment_files = sorted (
475- [self ._get_relative_path (p , target_dir ) for p in feature_stats .orphan_comment_files ]
476- ),
477- untracked_todo_files = sorted (
478- [self ._get_relative_path (p , target_dir ) for p in feature_stats .untracked_todo_files ]
479- ),
480- inline_import_files = sorted (
481- [self ._get_relative_path (p , target_dir ) for p in feature_stats .inline_import_files ]
482- ),
483- dict_get_with_default_files = sorted (
484- [self ._get_relative_path (p , target_dir ) for p in feature_stats .dict_get_with_default_files ]
485- ),
486- hasattr_getattr_files = sorted (
487- [self ._get_relative_path (p , target_dir ) for p in feature_stats .hasattr_getattr_files ]
488- ),
489- nonempty_init_files = sorted (
490- [self ._get_relative_path (p , target_dir ) for p in feature_stats .nonempty_init_files ]
491- ),
492- test_skip_files = sorted ([self ._get_relative_path (p , target_dir ) for p in feature_stats .test_skip_files ]),
493- swallowed_exception_files = sorted (
494- [self ._get_relative_path (p , target_dir ) for p in feature_stats .swallowed_exception_files ]
495- ),
496- type_ignore_files = sorted ([self ._get_relative_path (p , target_dir ) for p in feature_stats .type_ignore_files ]),
497- dynamic_execution_files = sorted (
498- [self ._get_relative_path (p , target_dir ) for p in feature_stats .dynamic_execution_files ]
499- ),
500- single_method_class_count = feature_stats .single_method_class_count ,
501- deep_inheritance_count = feature_stats .deep_inheritance_count ,
502- passthrough_wrapper_count = feature_stats .passthrough_wrapper_count ,
503- single_method_class_files = sorted (
504- [self ._get_relative_path (p , target_dir ) for p in feature_stats .single_method_class_files ]
505- ),
506- deep_inheritance_files = sorted (
507- [self ._get_relative_path (p , target_dir ) for p in feature_stats .deep_inheritance_files ]
508- ),
509- passthrough_wrapper_files = sorted (
510- [self ._get_relative_path (p , target_dir ) for p in feature_stats .passthrough_wrapper_files ]
511- ),
512455 total_loc = feature_stats .total_loc ,
513456 code_loc = feature_stats .code_loc ,
514457 files_by_loc = {
515458 self ._get_relative_path (p , target_dir ): loc
516459 for p , loc in self ._build_files_by_loc (python_files , target_dir ).items ()
517460 },
461+ ** smell_kwargs ,
518462 )
0 commit comments