1111import difflib
1212import datetime
1313import sqlite3
14+ import threading
1415import warnings
1516from typing import Set , Dict , List , Tuple , Any , Optional
1617from dataclasses import dataclass , field
@@ -220,6 +221,7 @@ class AnalysisResult:
220221 name_matches : List [Tuple [str , str ]] = field (default_factory = list )
221222 typehints : List [str ] = field (default_factory = list )
222223 module_attribute_usage : Dict [str , List [str ]] = field (default_factory = dict ) # NEU: Modul → Attribute Mapping
224+ todo_comments : List [Tuple [int , str , str ]] = field (default_factory = list ) # (Zeile, Typ, Text)
223225
224226
225227# ============================================================================
@@ -440,6 +442,15 @@ def build_stdlib_whitelist() -> Set[str]:
440442 return wl
441443
442444
445+ # Pre-warm Whitelist im Hintergrund (pkgutil.iter_modules ist langsam auf Python < 3.10)
446+ _whitelist_prewarm = threading .Thread (
447+ target = build_stdlib_whitelist ,
448+ daemon = True ,
449+ name = "stdlib-whitelist-prewarm"
450+ )
451+ _whitelist_prewarm .start ()
452+
453+
443454def is_valid_missing_def (name : str ) -> bool :
444455 """
445456 Prüft, ob ein Name als fehlende Definition gemeldet werden sollte.
@@ -454,11 +465,15 @@ def is_valid_missing_def(name: str) -> bool:
454465 # Private/Protected Namen überspringen
455466 if name .startswith ("_" ):
456467 return False
457-
458- # Callback-Handler überspringen
459- if name .endswith (CALLBACK_SUFFIXES ):
468+
469+ # Technische Suffixe generell überspringen (eindeutig interne Patterns)
470+ if name .endswith (( "_fetch" , "_stage" , "_emit" , "_process" , "_task" , "_async" ) ):
460471 return False
461-
472+
473+ # _callback/_handler nur in Framework-Kontext überspringen, nicht pauschal
474+ if name .endswith (("_callback" , "_handler" )) and name in FRAMEWORK_MAP :
475+ return False
476+
462477 # Framework-Methoden überspringen
463478 if name in FRAMEWORK_MAP :
464479 return False
@@ -504,6 +519,34 @@ def filter_missing_defs(
504519 return sorted (filtered )
505520
506521
522+ _TODO_PATTERN = re .compile (
523+ r"#\s*(TODO|FIXME|HACK|NOTE|XXX)[:\s]+(.*)" , re .IGNORECASE
524+ )
525+
526+
527+ def scan_todo_comments (code : str ) -> List [Tuple [int , str , str ]]:
528+ """
529+ Scannt Quellcode nach TODO/FIXME/HACK/NOTE/XXX Kommentaren.
530+
531+ Args:
532+ code: Python-Quellcode als String
533+
534+ Returns:
535+ Liste von Tupeln (Zeilennummer, Typ, Text).
536+ Text wird nur gekuerzt wenn er laenger als 50 Zeichen ist.
537+ """
538+ results = []
539+ for lineno , line in enumerate (code .splitlines (), start = 1 ):
540+ match = _TODO_PATTERN .search (line )
541+ if match :
542+ tag = match .group (1 ).upper ()
543+ text = match .group (2 ).strip ()
544+ if len (text ) > 50 :
545+ text = text [:50 ] + "..."
546+ results .append ((lineno , tag , text ))
547+ return results
548+
549+
507550def get_available_module_attributes (analyzer : 'CodeAnalyzer' ) -> Set [str ]:
508551 """
509552 Ermittelt alle Attribute die durch importierte Module verfügbar sind.
@@ -600,6 +643,9 @@ def analyze_file(path: str) -> AnalysisResult:
600643 # Dynamische Aufrufe scannen
601644 dynamic_hits , dynamic_methods = scan_dynamic_usage (code )
602645
646+ # TODO-Kommentare scannen
647+ todo_comments = scan_todo_comments (code )
648+
603649 # TypeHints extrahieren (verwendet bereits geparsten Tree!)
604650 typehints = _extract_typehints (tree )
605651
@@ -681,6 +727,7 @@ def analyze_file(path: str) -> AnalysisResult:
681727 mod : sorted (attrs ) for mod , attrs in analyzer .module_attribute_calls .items ()
682728 if mod in analyzer .imported_modules or mod in analyzer .import_names
683729 },
730+ todo_comments = todo_comments ,
684731 )
685732
686733
@@ -904,6 +951,13 @@ def generate_report(result: AnalysisResult) -> str:
904951 if len (result .module_attribute_usage ) > 10 :
905952 report .append (f" ... und { len (result .module_attribute_usage ) - 10 } weitere Module\n " )
906953
954+ # TODO-Kommentare
955+ if result .todo_comments :
956+ report .append (f"\n [TODO] TODO-KOMMENTARE ({ len (result .todo_comments )} )\n " )
957+ report .append ("-" * 70 + "\n " )
958+ for lineno , tag , text in result .todo_comments :
959+ report .append (f" Zeile { lineno :4d} : [{ tag } ] { text } \n " )
960+
907961 report .append ("\n " + "=" * 70 + "\n " )
908962
909963 return "" .join (report )
@@ -1138,7 +1192,7 @@ def analyze_project(folder_path: str, progress_callback=None) -> ProjectAnalysis
11381192 try :
11391193 with open (file_path , 'r' , encoding = 'utf-8' ) as f :
11401194 total_lines += len (f .readlines ())
1141- except :
1195+ except ( IOError , OSError ) :
11421196 pass
11431197 rel_path = os .path .relpath (file_path , folder_path )
11441198 if result .unused_imports :
0 commit comments