kotlin_poet_rs/spec/
class.rs

1use crate::io::RenderKotlin;
2use crate::spec::{VisibilityModifier, Argument, ClassInheritanceModifier, CodeBlock, CompanionObject, Function, GenericParameter, Name, PrimaryConstructor, Property, SecondaryConstructor, Type, Annotation};
3use crate::spec::annotation::{mixin_annotation_mutators, AnnotationSlot};
4use crate::spec::kdoc::{KdocSlot, mixin_kdoc_mutators};
5use crate::tokens;
6
7#[derive(Debug, Clone)]
8pub(crate) enum ClassMemberNode {
9    Property(Property),
10    Function(Function),
11    Subclass(Class),
12    SecondaryConstructor(SecondaryConstructor),
13    InitBlock(CodeBlock),
14}
15
16impl RenderKotlin for ClassMemberNode {
17    fn render_into(&self, block: &mut CodeBlock) {
18        match self {
19            ClassMemberNode::Property(property) => {
20                block.push_renderable(property);
21            }
22            ClassMemberNode::Function(function) => {
23                block.push_renderable(function);
24            }
25            ClassMemberNode::Subclass(subclass) => {
26                block.push_renderable(subclass);
27            }
28            ClassMemberNode::SecondaryConstructor(secondary_constructor) => {
29                block.push_renderable(secondary_constructor);
30            }
31            ClassMemberNode::InitBlock(code) => {
32                block.push_static_atom(tokens::keyword::INIT);
33                block.push_curly_brackets(|block| {
34                    block.push_renderable(code);
35                });
36            }
37        }
38    }
39}
40
41#[derive(Debug, Clone)]
42struct EnumInstance {
43    name: Name,
44    arguments: Vec<Argument>,
45}
46
47/// Defines Kotlin's class like entity. This could represent any 'flavour' of class: enum, interface, e.t.c.
48/// To change type of class please use [Class::inheritance_modifier].
49///
50/// #Example
51///
52/// ## Simple class
53/// ```
54/// use kotlin_poet_rs::io::RenderKotlin;
55/// use kotlin_poet_rs::spec::{Class, Name};
56///
57/// let class = Class::new(Name::from("Person"));
58///
59///  assert_eq!(class.render_string(), "public final class Person {\n\n}");
60/// ```
61///
62/// ## Interface
63/// ```
64/// use kotlin_poet_rs::io::RenderKotlin;
65/// use kotlin_poet_rs::spec::{Class, ClassInheritanceModifier, Name};
66///
67/// let class = Class::new(Name::from("Person"))
68///     .inheritance_modifier(ClassInheritanceModifier::Interface);
69///
70///  assert_eq!(class.render_string(), "public interface Person {\n\n}");
71/// ```
72#[derive(Debug, Clone)]
73pub struct Class {
74    name: Name,
75    visibility_modifier: VisibilityModifier,
76    inheritance_modifier: ClassInheritanceModifier,
77    member_nodes: Vec<ClassMemberNode>,
78    enum_instances: Vec<EnumInstance>,
79    primary_constructor: Option<PrimaryConstructor>,
80    companion_object: Option<CompanionObject>,
81    generic_parameters: Vec<GenericParameter>,
82    parent_classes: Vec<Type>,
83    is_inner: bool,
84    annotation_slot: AnnotationSlot,
85    kdoc: KdocSlot,
86}
87
88impl Class {
89    /// Creates new plain final class.
90    pub fn new<NameLike: Into<Name>>(name: NameLike) -> Self {
91        Class {
92            name: name.into(),
93            visibility_modifier: VisibilityModifier::default(),
94            inheritance_modifier: ClassInheritanceModifier::default(),
95            member_nodes: Vec::default(),
96            enum_instances: Vec::default(),
97            primary_constructor: None,
98            companion_object: None,
99            generic_parameters: Vec::default(),
100            parent_classes: Vec::default(),
101            is_inner: false,
102            annotation_slot: AnnotationSlot::vertical(),
103            kdoc: KdocSlot::default(),
104        }
105    }
106
107    /// Marks class as inner
108    pub fn inner(mut self, flag: bool) -> Self {
109        self.is_inner = flag;
110        self
111    }
112
113    /// Set's class visibility modifier
114    pub fn visibility_modifier(mut self, visibility_modifier: VisibilityModifier) -> Self {
115        self.visibility_modifier = visibility_modifier;
116        self
117    }
118
119    /// Changes class type
120    pub fn inheritance_modifier(mut self, inheritance_modifier: ClassInheritanceModifier) -> Self {
121        self.inheritance_modifier = inheritance_modifier;
122        self
123    }
124
125    /// Adds property to this class. Properties in body will appear in order this method is called.
126    pub fn property(mut self, property: Property) -> Self {
127        self.member_nodes.push(ClassMemberNode::Property(property));
128        self
129    }
130
131    /// Adds function to this class. Functions in body will appear in order this method is called.
132    pub fn function(mut self, function: Function) -> Self {
133        self.member_nodes.push(ClassMemberNode::Function(function));
134        self
135    }
136
137    /// Adds subclass to this class. Subclasses in body will appear in order this method is called.
138    pub fn subclass(mut self, subclass: Class) -> Self {
139        self.member_nodes.push(ClassMemberNode::Subclass(subclass));
140        self
141    }
142
143    /// Adds enum instance to this class. Enum instances in body will appear in order this method is called.
144    /// This method is only valid for enum classes. To change class type to enum please use [Class::inheritance_modifier].
145    pub fn enum_instance<NameLike: Into<Name>>(mut self, name: NameLike, arguments: Vec<Argument>) -> Self {
146        self.enum_instances.push(EnumInstance {
147            name: name.into(),
148            arguments,
149        });
150        self
151    }
152
153    /// Adds primary constructor to this class.
154    pub fn primary_constructor(mut self, primary_constructor: PrimaryConstructor) -> Self {
155        self.primary_constructor = Some(primary_constructor);
156        self
157    }
158
159    /// Adds secondary constructor to this class. Secondary constructors in body will appear in order this method is called.
160    pub fn secondary_constructor(mut self, secondary_constructor: SecondaryConstructor) -> Self {
161        self.member_nodes.push(ClassMemberNode::SecondaryConstructor(secondary_constructor));
162        self
163    }
164
165    /// Adds init block to this class. Init blocks in body will appear in order this method is called.
166    pub fn init<CodeBlockLike: Into<CodeBlock>>(mut self, block: CodeBlockLike) -> Self {
167        self.member_nodes.push(ClassMemberNode::InitBlock(block.into()));
168        self
169    }
170
171    /// Adds companion object to this class.
172    pub fn companion_object(mut self, companion_object: CompanionObject) -> Self {
173        self.companion_object = Some(companion_object);
174        self
175    }
176
177    /// Adds [GenericParameter] to this class.
178    /// Could be called multiple times to have multiple generic parameters.
179    pub fn generic_parameter(mut self, generic_parameter: GenericParameter) -> Self {
180        self.generic_parameters.push(generic_parameter);
181        self
182    }
183
184    /// Adds parent class / interface to this class.
185    pub fn inherits<TypeLike: Into<Type>>(mut self, parent_type: TypeLike) -> Self {
186        self.parent_classes.push(parent_type.into());
187        self
188    }
189
190    mixin_annotation_mutators!();
191    mixin_kdoc_mutators!();
192}
193
194impl RenderKotlin for Class {
195    fn render_into(&self, block: &mut CodeBlock) {
196        block.push_renderable(&self.kdoc);
197        block.push_renderable(&self.annotation_slot);
198
199        block.push_renderable(&self.visibility_modifier);
200        block.push_space();
201        if self.is_inner {
202            block.push_static_atom(tokens::keyword::INNER);
203            block.push_space();
204        }
205        block.push_renderable(&self.inheritance_modifier);
206        block.push_space();
207        if !matches!(
208            self.inheritance_modifier,
209            ClassInheritanceModifier::Interface |
210            ClassInheritanceModifier::Object
211        ) {
212            block.push_static_atom(tokens::keyword::CLASS);
213            block.push_space();
214        }
215        block.push_renderable(&self.name);
216        if !self.generic_parameters.is_empty() {
217            block.push_angle_brackets(|code| {
218                code.push_comma_separated(
219                    &self.generic_parameters.iter().map(|it| it.render_definition())
220                        .collect::<Vec<CodeBlock>>()
221                );
222            });
223        }
224        block.push_space();
225
226        if let Some(primary_constructor) = &self.primary_constructor {
227            block.push_renderable(primary_constructor);
228            block.push_space();
229        }
230
231        if !self.parent_classes.is_empty() {
232            block.pop_space();
233            block.push_static_atom(tokens::COLON);
234            block.push_space();
235            block.push_comma_separated(
236                &self.parent_classes
237            );
238            block.push_space();
239        }
240
241        block.push_renderable(
242            &GenericParameter::render_type_boundaries_vec_if_required(
243                &self.generic_parameters
244            )
245        );
246
247        block.push_curly_brackets(|class_body_code| {
248            class_body_code.push_new_line();
249
250            if !self.enum_instances.is_empty() {
251                for (inst_idx, instance) in self.enum_instances.iter().enumerate() {
252                    class_body_code.push_renderable(&instance.name);
253                    class_body_code.push_round_brackets(|arg_code| {
254                        arg_code.push_comma_separated(&instance.arguments);
255                    });
256
257                    if inst_idx != self.enum_instances.len() - 1 {
258                        class_body_code.push_static_atom(tokens::COMMA);
259                        class_body_code.push_new_line();
260                    }
261                }
262
263                class_body_code.push_static_atom(tokens::SEMICOLON);
264            }
265
266            for node in &self.member_nodes {
267                class_body_code.push_renderable(node);
268                class_body_code.push_new_line();
269            }
270
271            if let Some(companion_object) = &self.companion_object {
272                class_body_code.push_renderable(companion_object);
273                class_body_code.push_new_line();
274            }
275        });
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use crate::spec::{Parameter, GenericInvariance, PropertyGetter, PropertySetter, Type, ClassLikeTypeName, Package, KDoc};
282    use super::*;
283
284    #[test]
285    fn test_class() {
286        let class = Class::new(Name::from("Person"));
287        let code = class.render_string();
288
289        assert_eq!(code, "public final class Person {\n\n}");
290    }
291
292    #[test]
293    fn test_class_with_kdoc() {
294        let class = Class::new(Name::from("Person"))
295            .kdoc(
296                KDoc::from("hello world")
297                    .merge(KDoc::from("at here"))
298            );
299        let code = class.render_string();
300
301        assert_eq!(
302            code,
303            "/**\n * hello world\n * at here\n */\npublic final class Person {\n\n}"
304        );
305    }
306
307    #[test]
308    fn test_class_with_property() {
309        let property = Property::new(
310            Name::from("name"),
311            Type::string(),
312        ).initializer(
313            CodeBlock::statement("\"\"")
314        ).getter(
315            PropertyGetter::new(
316                CodeBlock::statement("return field")
317            )
318        ).setter(
319            PropertySetter::new(
320                CodeBlock::statement("field = value")
321            )
322        );
323
324        let class = Class::new(Name::from("Person"))
325            .property(property.clone());
326
327        let code = class.render_string();
328
329        assert_eq!(
330            code,
331            "public final class Person {\n\n    public final var name: kotlin.String = \"\"\n        set(value) {\n            field = value\n        }\n        get() {\n            return field\n        }\n\n}"
332        );
333    }
334
335    #[test]
336    fn test_enum() {
337        let class = Class::new(Name::from("Person"))
338            .inheritance_modifier(ClassInheritanceModifier::Enum)
339            .enum_instance(Name::from("Alex"), vec![
340                Argument::new_positional(CodeBlock::atom("23"))
341            ])
342            .enum_instance(Name::from("Vova"), vec![
343                Argument::new_positional(CodeBlock::atom("23"))
344            ])
345            ;
346        let code = class.render_string();
347
348        assert_eq!(
349            code,
350            "public enum class Person {\n\n    Alex(23),\n    Vova(23);}"
351        );
352    }
353
354    #[test]
355    fn test_with_constructor() {
356        let class = Class::new(Name::from("Person"))
357            .primary_constructor(
358                PrimaryConstructor::new()
359                    .property(
360                        Property::new(
361                            Name::from("name"),
362                            Type::string(),
363                        )
364                    )
365                    .parameter(
366                        Parameter::new(
367                            Name::from("age"),
368                            Type::int(),
369                        )
370                    )
371            );
372
373        assert_eq!(
374            class.render_string(),
375            "public final class Person public constructor(public final val name: kotlin.String, age: kotlin.Int) {\n\n}"
376        );
377    }
378
379    #[test]
380    fn test_with_empty_constructor() {
381        let class = Class::new(Name::from("Person"))
382            .primary_constructor(
383                PrimaryConstructor::new()
384            );
385
386        assert_eq!(
387            class.render_string(),
388            "public final class Person public constructor() {\n\n}"
389        );
390    }
391
392    #[test]
393    fn test_with_init_block() {
394        let class = Class::new(Name::from("Person"))
395            .init(
396                CodeBlock::statement("println(42)")
397            );
398
399        assert_eq!(
400            class.render_string(),
401            "public final class Person {\n\n    init{\n        println(42)\n    }\n}"
402        );
403    }
404
405    #[test]
406    fn test_data_class() {
407        let class = Class::new(Name::from("Person"))
408            .inheritance_modifier(ClassInheritanceModifier::Data)
409            .primary_constructor(
410                PrimaryConstructor::new()
411                    .property(
412                        Property::new(
413                            Name::from("name"),
414                            Type::string(),
415                        ).initializer(
416                            CodeBlock::atom("\"\"")
417                        )
418                    )
419            );
420
421        assert_eq!(
422            class.render_string(),
423            "public data class Person public constructor(public final val name: kotlin.String = \"\") {\n\n}"
424        );
425    }
426
427    #[test]
428    fn test_data_class_with_secondary_constructor() {
429        let class = Class::new(Name::from("Person"))
430            .inheritance_modifier(ClassInheritanceModifier::Data)
431            .primary_constructor(
432                PrimaryConstructor::new()
433                    .property(
434                        Property::new(
435                            Name::from("name"),
436                            Type::string(),
437                        )
438                    )
439                    .property(
440                        Property::new(
441                            Name::from("age"),
442                            Type::int(),
443                        )
444                    )
445            )
446            .secondary_constructor(
447                SecondaryConstructor::new()
448                    .parameter(
449                        Parameter::new(
450                            Name::from("name"),
451                            Type::string(),
452                        )
453                    )
454                    .delegate_argument(
455                        Argument::new_positional(
456                            CodeBlock::atom("name")
457                        )
458                    )
459                    .delegate_argument(
460                        Argument::new_positional(
461                            CodeBlock::atom("23")
462                        )
463                    )
464                    .body(
465                        CodeBlock::statement("println(42)")
466                    )
467            );
468
469        assert_eq!(
470            class.render_string(),
471            "public data class Person public constructor(public final val name: kotlin.String, public final val age: kotlin.Int) {\n\n    public constructor(name: kotlin.String) : this(name, 23) {\n        println(42)\n    }\n}"
472        );
473    }
474
475    #[test]
476    fn test_interface() {
477        let class = Class::new(Name::from("Person"))
478            .inheritance_modifier(ClassInheritanceModifier::Interface);
479
480        assert_eq!(class.render_string(), "public interface Person {\n\n}");
481    }
482
483    #[test]
484    fn test_abstract() {
485        let class = Class::new(Name::from("Person"))
486            .inheritance_modifier(ClassInheritanceModifier::Abstract);
487
488        assert_eq!(class.render_string(), "public abstract class Person {\n\n}");
489    }
490
491    #[test]
492    fn test_object() {
493        let class = Class::new(Name::from("Person"))
494            .inheritance_modifier(ClassInheritanceModifier::Object);
495
496        assert_eq!(class.render_string(), "public object Person {\n\n}");
497    }
498
499    #[test]
500    fn test_class_with_inner() {
501        let class = Class::new(Name::from("Person"))
502            .subclass(
503                Class::new("InnerPerson")
504                    .inheritance_modifier(ClassInheritanceModifier::Abstract)
505                    .inner(true)
506            );
507
508        assert_eq!(
509            class.render_string(),
510            "public final class Person {\n\n    public inner abstract class InnerPerson {\n\n    }\n}"
511        );
512    }
513
514    #[test]
515    fn test_sealed() {
516        let class = Class::new(Name::from("Person"))
517            .inheritance_modifier(ClassInheritanceModifier::Sealed);
518
519        assert_eq!(class.render_string(), "public sealed class Person {\n\n}");
520    }
521
522    #[test]
523    fn test_generic_class() {
524        let class = Class::new(Name::from("Box"))
525            .generic_parameter(
526                GenericParameter::new(Name::from("A"))
527            )
528            .generic_parameter(
529                GenericParameter::new(Name::from("B"))
530                    .invariance(GenericInvariance::In)
531            )
532            .generic_parameter(
533                GenericParameter::new(Name::from("C"))
534                    .invariance(GenericInvariance::Out)
535            );
536
537        assert_eq!(class.render_string(), "public final class Box<A, in B, out C> {\n\n}");
538    }
539
540    #[test]
541    fn test_generic_with_parent() {
542        let class = Class::new(Name::from("Box"))
543            .generic_parameter(
544                GenericParameter::new(Name::from("A"))
545                    .invariance(GenericInvariance::In)
546                    .type_boundary(Type::string())
547            )
548            .inherits(
549                Type::int()
550            );
551
552        assert_eq!(
553            class.render_string(),
554            "public final class Box<in A>: kotlin.Int where A: kotlin.String {\n\n}"
555        );
556    }
557
558    #[test]
559    fn test_generic_class_with_boundaries() {
560        let class = Class::new(Name::from("Box"))
561            .generic_parameter(
562                GenericParameter::new(Name::from("A"))
563            )
564            .generic_parameter(
565                GenericParameter::new(Name::from("B"))
566                    .invariance(GenericInvariance::In)
567                    .type_boundary(Type::string())
568                    .type_boundary(Type::int())
569            )
570            .generic_parameter(
571                GenericParameter::new(Name::from("C"))
572                    .invariance(GenericInvariance::Out)
573            );
574
575        assert_eq!(class.render_string(), "public final class Box<A, in B, out C> where B: kotlin.String, B: kotlin.Int {\n\n}");
576    }
577
578    #[test]
579    fn test_with_annotation() {
580        let class = Class::new(Name::from("Person"))
581            .annotation(
582                Annotation::new(ClassLikeTypeName::top_level(
583                    Package::from(Vec::new()),
584                    Name::from("Deprecated"),
585                ))
586            );
587
588        assert_eq!(class.render_string(), "@Deprecated()\npublic final class Person {\n\n}");
589    }
590}