codegraph_python/entities/
class.rs

1use super::function::FunctionEntity;
2use serde::{Deserialize, Serialize};
3
4/// Represents a class field/attribute
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6pub struct Field {
7    /// Field name
8    pub name: String,
9
10    /// Type annotation (if present)
11    pub type_annotation: Option<String>,
12
13    /// Visibility: "public", "private", or "protected"
14    pub visibility: String,
15
16    /// Is this a class variable (static) vs instance variable?
17    pub is_static: bool,
18}
19
20impl Field {
21    /// Create a new field
22    pub fn new(name: impl Into<String>) -> Self {
23        let name = name.into();
24        let visibility = if name.starts_with("__") && name.ends_with("__") {
25            "public".to_string() // Dunder names are public
26        } else if name.starts_with("__") {
27            "private".to_string()
28        } else if name.starts_with('_') {
29            "protected".to_string()
30        } else {
31            "public".to_string()
32        };
33
34        Self {
35            name,
36            type_annotation: None,
37            visibility,
38            is_static: false,
39        }
40    }
41
42    /// Set the type annotation
43    pub fn set_type_annotation(mut self, type_ann: Option<String>) -> Self {
44        self.type_annotation = type_ann;
45        self
46    }
47
48    /// Set whether this is a static field
49    pub fn set_static(mut self, is_static: bool) -> Self {
50        self.is_static = is_static;
51        self
52    }
53}
54
55/// Represents a Python class
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub struct ClassEntity {
58    /// Class name
59    pub name: String,
60
61    /// Visibility: "public" or "private"
62    pub visibility: String,
63
64    /// Starting line number (1-indexed)
65    pub line_start: usize,
66
67    /// Ending line number (1-indexed)
68    pub line_end: usize,
69
70    /// Is this an abstract class (has ABCMeta or ABC base)?
71    pub is_abstract: bool,
72
73    /// Inherited classes (base classes)
74    pub base_classes: Vec<String>,
75
76    /// Protocols/traits implemented
77    pub implemented_traits: Vec<String>,
78
79    /// Methods in this class (full FunctionEntity objects)
80    pub methods: Vec<FunctionEntity>,
81
82    /// Class attributes/fields
83    pub fields: Vec<Field>,
84
85    /// Documentation string (docstring)
86    pub doc_comment: Option<String>,
87
88    /// Decorators applied to the class (e.g., ["@dataclass"])
89    pub attributes: Vec<String>,
90}
91
92impl ClassEntity {
93    /// Create a new class entity with required fields
94    pub fn new(name: impl Into<String>, line_start: usize, line_end: usize) -> Self {
95        let name = name.into();
96        let visibility = if name.starts_with('_') {
97            "private".to_string()
98        } else {
99            "public".to_string()
100        };
101
102        Self {
103            name,
104            visibility,
105            line_start,
106            line_end,
107            is_abstract: false,
108            base_classes: Vec::new(),
109            implemented_traits: Vec::new(),
110            methods: Vec::new(),
111            fields: Vec::new(),
112            doc_comment: None,
113            attributes: Vec::new(),
114        }
115    }
116
117    /// Check if this is a dataclass
118    pub fn is_dataclass(&self) -> bool {
119        self.attributes.iter().any(|a| a == "@dataclass")
120    }
121
122    /// Check if this is abstract
123    pub fn is_abstract(&self) -> bool {
124        self.is_abstract
125            || self
126                .base_classes
127                .iter()
128                .any(|b| b == "ABC" || b == "ABCMeta")
129            || self.attributes.iter().any(|a| a.contains("abstractmethod"))
130    }
131
132    /// Check if a method exists
133    pub fn has_method(&self, method_name: &str) -> bool {
134        self.methods.iter().any(|m| m.name == method_name)
135    }
136
137    /// Set abstract flag
138    pub fn set_abstract(mut self, is_abstract: bool) -> Self {
139        self.is_abstract = is_abstract;
140        self
141    }
142
143    /// Add a base class
144    pub fn add_base_class(mut self, base: impl Into<String>) -> Self {
145        self.base_classes.push(base.into());
146        self
147    }
148
149    /// Add an implemented trait/protocol
150    pub fn add_trait(mut self, trait_name: impl Into<String>) -> Self {
151        self.implemented_traits.push(trait_name.into());
152        self
153    }
154
155    /// Add a method
156    pub fn add_method(mut self, method: FunctionEntity) -> Self {
157        self.methods.push(method);
158        self
159    }
160
161    /// Add a field
162    pub fn add_field(mut self, field: Field) -> Self {
163        self.fields.push(field);
164        self
165    }
166
167    /// Set the docstring
168    pub fn set_doc_comment(mut self, doc: Option<String>) -> Self {
169        self.doc_comment = doc;
170        self
171    }
172
173    /// Add a decorator
174    pub fn add_attribute(mut self, attr: impl Into<String>) -> Self {
175        self.attributes.push(attr.into());
176        self
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_field_visibility() {
186        let public_field = Field::new("name");
187        assert_eq!(public_field.visibility, "public");
188
189        let protected_field = Field::new("_name");
190        assert_eq!(protected_field.visibility, "protected");
191
192        let private_field = Field::new("__name");
193        assert_eq!(private_field.visibility, "private");
194
195        let dunder_field = Field::new("__init__");
196        assert_eq!(dunder_field.visibility, "public"); // Dunder methods are public
197    }
198
199    #[test]
200    fn test_class_entity_visibility() {
201        let public_class = ClassEntity::new("MyClass", 1, 10);
202        assert_eq!(public_class.visibility, "public");
203
204        let private_class = ClassEntity::new("_MyClass", 1, 10);
205        assert_eq!(private_class.visibility, "private");
206    }
207
208    #[test]
209    fn test_class_entity_dataclass() {
210        let class = ClassEntity::new("User", 1, 10).add_attribute("@dataclass");
211
212        assert!(class.is_dataclass());
213    }
214
215    #[test]
216    fn test_class_entity_abstract() {
217        let class1 = ClassEntity::new("Base", 1, 10).add_base_class("ABC");
218        assert!(class1.is_abstract());
219
220        let class2 = ClassEntity::new("Base", 1, 10).set_abstract(true);
221        assert!(class2.is_abstract());
222    }
223
224    #[test]
225    fn test_class_entity_methods() {
226        let method1 = FunctionEntity::new("method1", 2, 4);
227        let method2 = FunctionEntity::new("method2", 5, 7);
228
229        let class = ClassEntity::new("MyClass", 1, 10)
230            .add_method(method1)
231            .add_method(method2);
232
233        assert!(class.has_method("method1"));
234        assert!(class.has_method("method2"));
235        assert!(!class.has_method("method3"));
236        assert_eq!(class.methods.len(), 2);
237    }
238}