1+ """Class analyzer for Python class definitions."""
2+
3+ from typing import Dict , List , Any , Optional
4+ import ast
5+ import logging
6+ from dataclasses import dataclass , field
7+
8+ logger = logging .getLogger ("ClassAnalyzer" )
9+
10+ @dataclass
11+ class ClassInfo :
12+ """Information about a class definition."""
13+ name : str
14+ docstring : Optional [str ] = None
15+ bases : List [str ] = field (default_factory = list )
16+ attributes : Dict [str , str ] = field (default_factory = dict ) # attr_name -> type
17+ methods : Dict [str , Dict [str , Any ]] = field (default_factory = dict ) # method_name -> info
18+
19+ class ClassAnalyzer :
20+ """Specialized analyzer for class definitions."""
21+
22+ def __init__ (self ):
23+ self .class_info : Dict [str , ClassInfo ] = {}
24+ self .current_class : Optional [str ] = None
25+
26+ def analyze_classes (self , tree : ast .AST ) -> Dict [str , ClassInfo ]:
27+ """Analyze class definitions in the AST."""
28+ self .class_info .clear ()
29+
30+ # First pass: collect all class names and inheritance
31+ for node in ast .walk (tree ):
32+ if isinstance (node , ast .ClassDef ):
33+ self ._analyze_class_definition (node )
34+
35+ # Second pass: analyze methods and attributes within classes
36+ for node in ast .walk (tree ):
37+ if isinstance (node , ast .ClassDef ):
38+ self ._analyze_class_members (node )
39+
40+ return self .class_info .copy ()
41+
42+ def _analyze_class_definition (self , node : ast .ClassDef ) -> None :
43+ """Analyze a single class definition."""
44+ # Get class docstring
45+ docstring = ast .get_docstring (node )
46+
47+ # Get base classes
48+ bases = []
49+ for base in node .bases :
50+ if isinstance (base , ast .Name ):
51+ bases .append (base .id )
52+ # Handle more complex base expressions if needed
53+
54+ # Create ClassInfo
55+ class_info = ClassInfo (
56+ name = node .name ,
57+ docstring = docstring ,
58+ bases = bases
59+ )
60+
61+ # Store class info
62+ self .class_info [node .name ] = class_info
63+ logger .debug (f"Found class: { node .name } with bases: { bases } " )
64+
65+ def _analyze_class_members (self , node : ast .ClassDef ) -> None :
66+ """Analyze methods and attributes within a class."""
67+ class_info = self .class_info [node .name ]
68+ self .current_class = node .name
69+
70+ try :
71+ for item in node .body :
72+ if isinstance (item , ast .FunctionDef ):
73+ self ._analyze_method (item , class_info )
74+ elif isinstance (item , ast .AnnAssign ):
75+ self ._analyze_class_attribute (item , class_info )
76+ elif isinstance (item , ast .Assign ):
77+ self ._analyze_class_assignment (item , class_info )
78+ finally :
79+ self .current_class = None
80+
81+ def _analyze_method (self , node : ast .FunctionDef , class_info : ClassInfo ) -> None :
82+ """Analyze a method definition."""
83+ method_info = {
84+ 'name' : node .name ,
85+ 'is_constructor' : node .name == '__init__' ,
86+ 'is_static' : any (isinstance (dec , ast .Name ) and dec .id == 'staticmethod'
87+ for dec in node .decorator_list ),
88+ 'is_class_method' : any (isinstance (dec , ast .Name ) and dec .id == 'classmethod'
89+ for dec in node .decorator_list ),
90+ 'parameters' : [],
91+ 'return_type' : 'void' ,
92+ 'docstring' : ast .get_docstring (node )
93+ }
94+
95+ # Analyze parameters
96+ for arg in node .args .args :
97+ if arg .arg == 'self' : # Skip self parameter
98+ continue
99+
100+ param_info = {
101+ 'name' : arg .arg ,
102+ 'type' : 'auto' , # Default type
103+ 'has_default' : False
104+ }
105+
106+ # Check for type annotation
107+ if arg .annotation :
108+ param_info ['type' ] = self ._annotation_to_string (arg .annotation )
109+
110+ method_info ['parameters' ].append (param_info )
111+
112+ # Check for return type annotation
113+ if node .returns :
114+ method_info ['return_type' ] = self ._annotation_to_string (node .returns )
115+
116+ class_info .methods [node .name ] = method_info
117+ logger .debug (f"Found method: { class_info .name } .{ node .name } " )
118+
119+ def _analyze_class_attribute (self , node : ast .AnnAssign , class_info : ClassInfo ) -> None :
120+ """Analyze a type-annotated class attribute."""
121+ if isinstance (node .target , ast .Name ):
122+ attr_name = node .target .id
123+ attr_type = self ._annotation_to_string (node .annotation )
124+ class_info .attributes [attr_name ] = attr_type
125+ logger .debug (f"Found attribute: { class_info .name } .{ attr_name } : { attr_type } " )
126+
127+ def _analyze_class_assignment (self , node : ast .Assign , class_info : ClassInfo ) -> None :
128+ """Analyze a class-level assignment."""
129+ for target in node .targets :
130+ if isinstance (target , ast .Name ):
131+ attr_name = target .id
132+ # Try to infer type from value
133+ attr_type = self ._infer_value_type (node .value )
134+ class_info .attributes [attr_name ] = attr_type
135+ logger .debug (f"Found attribute: { class_info .name } .{ attr_name } : { attr_type } " )
136+
137+ def _annotation_to_string (self , annotation : ast .AST ) -> str :
138+ """Convert an AST annotation to a string representation."""
139+ if isinstance (annotation , ast .Name ):
140+ return annotation .id
141+ elif isinstance (annotation , ast .Constant ):
142+ return str (annotation .value )
143+ elif isinstance (annotation , ast .Subscript ):
144+ if isinstance (annotation .value , ast .Name ):
145+ base = annotation .value .id
146+ if isinstance (annotation .slice , ast .Name ):
147+ param = annotation .slice .id
148+ return f"{ base } [{ param } ]"
149+ elif isinstance (annotation .slice , ast .Tuple ):
150+ params = [self ._annotation_to_string (elt ) for elt in annotation .slice .elts ]
151+ return f"{ base } [{ ', ' .join (params )} ]"
152+ elif isinstance (annotation , ast .Attribute ):
153+ if isinstance (annotation .value , ast .Name ):
154+ return f"{ annotation .value .id } .{ annotation .attr } "
155+
156+ # Fallback: return a generic string representation
157+ return 'auto'
158+
159+ def _infer_value_type (self , value : ast .AST ) -> str :
160+ """Infer type from a value expression."""
161+ if isinstance (value , ast .Constant ):
162+ if isinstance (value .value , int ):
163+ return 'int'
164+ elif isinstance (value .value , float ):
165+ return 'double'
166+ elif isinstance (value .value , str ):
167+ return 'std::string'
168+ elif isinstance (value .value , bool ):
169+ return 'bool'
170+ elif isinstance (value , ast .List ):
171+ return 'std::vector'
172+ elif isinstance (value , ast .Dict ):
173+ return 'std::map'
174+ elif isinstance (value , ast .Set ):
175+ return 'std::set'
176+
177+ return 'auto'
0 commit comments