cxx_qt_gen/writer/cpp/
mod.rs

1// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5
6pub mod header;
7pub mod source;
8
9use crate::generator::cpp::{fragment::CppFragment, GeneratedCppBlocks};
10use clang_format::{clang_format_with_style, ClangFormatStyle};
11use header::write_cpp_header;
12use indoc::formatdoc;
13use source::write_cpp_source;
14
15/// Surround the given C++ code with the namespace if it is not empty
16pub fn namespaced(namespace: &str, cpp_code: &str) -> String {
17    if namespace.is_empty() {
18        cpp_code.to_owned()
19    } else {
20        formatdoc! {r#"
21            namespace {namespace} {{
22            {cpp_code}
23            }} // namespace {namespace}
24            "# }
25    }
26}
27
28/// For a given GeneratedCppBlocks write this into a C++ header and source pair
29pub fn write_cpp(generated: &GeneratedCppBlocks, include_path: &str) -> CppFragment {
30    let header = write_cpp_header(generated, include_path);
31    let source = write_cpp_source(generated, include_path);
32
33    CppFragment::Pair {
34        header: clang_format_with_style(&header, &ClangFormatStyle::File).unwrap_or(header),
35        source: clang_format_with_style(&source, &ClangFormatStyle::File).unwrap_or(source),
36    }
37}
38/// Extract the header from a given CppFragment
39pub fn pair_as_header(pair: &CppFragment) -> Option<String> {
40    match pair {
41        CppFragment::Pair { header, source: _ } => Some(header.clone()),
42        CppFragment::Header(header) => Some(header.clone()),
43        CppFragment::Source(_) => None,
44    }
45}
46
47/// Extract the source from a given CppFragment
48pub fn pair_as_source(pair: &CppFragment) -> Option<String> {
49    match pair {
50        CppFragment::Pair { header: _, source } => Some(source.clone()),
51        CppFragment::Header(_) => None,
52        CppFragment::Source(source) => Some(source.clone()),
53    }
54}
55
56pub fn extract_extern_qt(
57    generated: &GeneratedCppBlocks,
58    mut filter_fn: impl FnMut(&CppFragment) -> Option<String>,
59) -> String {
60    generated
61        .extern_cxx_qt
62        .iter()
63        .flat_map(|block| {
64            block
65                .fragments
66                .iter()
67                .filter_map(&mut filter_fn)
68                .collect::<Vec<String>>()
69        })
70        .collect::<Vec<String>>()
71        .join("\n")
72}
73
74#[cfg(test)]
75mod tests {
76    use std::collections::BTreeSet;
77
78    use super::*;
79
80    use crate::generator::cpp::property::tests::require_pair;
81    use crate::{
82        generator::cpp::qobject::{GeneratedCppQObject, GeneratedCppQObjectBlocks},
83        naming::Name,
84        tests::format_cpp,
85    };
86    use indoc::indoc;
87    use pretty_assertions::assert_str_eq;
88
89    pub fn create_generated_cpp() -> GeneratedCppBlocks {
90        create_generated_cpp_with_namespace(Some("cxx_qt::my_object"))
91    }
92
93    /// Helper to create a GeneratedCppBlocks for testing
94    pub fn create_generated_cpp_with_namespace(namespace: Option<&str>) -> GeneratedCppBlocks {
95        GeneratedCppBlocks {
96            forward_declares: vec![],
97            includes: BTreeSet::default(),
98            extern_cxx_qt: vec![],
99            qobjects: vec![
100                GeneratedCppQObject {
101                    name: if let Some(namespace) = namespace {
102                        Name::mock_namespaced("MyObject", namespace)
103                    } else {
104                        Name::mock("MyObject")
105                    },
106                    rust_struct: Name::mock("MyObjectRust"),
107                    namespace_internals: if let Some(namespace) = namespace {
108                        format!("{namespace}::cxx_qt_my_object")
109                    } else {
110                        "cxx_qt_my_object".to_owned()
111                    },
112                    has_qobject_macro: true,
113                    blocks: GeneratedCppQObjectBlocks {
114                        base_classes: vec!["QStringListModel".to_owned()],
115                        includes: {
116                          let mut includes = BTreeSet::<String>::default();
117                          includes.insert("#include <test>".to_owned());
118                          includes
119                        },
120                        metaobjects: vec![
121                            "Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)".to_owned(),
122                            "Q_PROPERTY(bool longPropertyNameThatWrapsInClangFormat READ getToggle WRITE setToggle NOTIFY toggleChanged)"
123                                .to_owned(),
124                        ],
125                        methods: vec![
126                            CppFragment::Pair {
127                                header: "int count() const;".to_owned(),
128                                source: indoc! {r#"
129                                    int
130                                    MyObject::count() const
131                                    {
132                                      // getter
133                                    }
134                                "#}
135                                .to_owned(),
136                            },
137                            CppFragment::Pair {
138                                header: "bool toggle() const;".to_owned(),
139                                source: indoc! {r#"
140                                    bool
141                                    MyObject::toggle() const
142                                    {
143                                      // getter
144                                    }
145                                "#}
146                                .to_owned(),
147                            },
148                            CppFragment::Pair {
149                                header: "Q_INVOKABLE void invokable();".to_owned(),
150                                source: indoc! {r#"
151                                    void
152                                    MyObject::invokable()
153                                    {
154                                      // invokable method
155                                    }
156                                "#}
157                                .to_owned(),
158                            },
159                            CppFragment::Pair {
160                                header: "void cppMethod();".to_owned(),
161                                source: indoc! {r#"
162                                    void
163                                    MyObject::cppMethod()
164                                    {
165                                      // cpp method
166                                    }
167                                "#}
168                                .to_owned(),
169                            },
170                            CppFragment::Pair {
171                                header: "Q_SLOT void setCount(int count);".to_owned(),
172                                source: indoc! {r#"
173                                    void
174                                    MyObject::setCount(int count) const
175                                    {
176                                      // setter
177                                    }
178                                    "#}
179                                .to_owned(),
180                            },
181                            CppFragment::Pair {
182                                header: "Q_SLOT void setToggle(bool toggle);".to_owned(),
183                                source: indoc! {r#"
184                                    void
185                                    MyObject::setToggle(bool toggle) const
186                                    {
187                                      // setter
188                                    }
189                                    "#}
190                                .to_owned(),
191                            },
192                            CppFragment::Header (
193                                "Q_SIGNAL void countChanged();".to_owned(),
194                            ),
195                            CppFragment::Header (
196                                "Q_SIGNAL void toggleChanged();".to_owned(),
197                            ),
198                        ],
199                        private_methods: vec![CppFragment::Pair{
200                                header: "void privateMethod() const;".to_owned(),
201                                source: indoc! {r#"
202                                    void MyObject::privateMethod() const {
203                                        // private method
204                                    }
205                                    "#}.to_owned(),
206                            },
207                        CppFragment::Pair{
208                                header: "void privateMethod();".to_owned(),
209                                source: indoc! {r#"
210                                    void MyObject::privateMethod()
211                                    {
212                                        // non-const private method
213                                    }
214                                    "#}.to_owned(),
215                            }],
216                            ..Default::default()
217                    }
218                }
219            ],
220        }
221    }
222
223    /// Helper to create a GeneratedCppBlocks for testing with multiple qobjects
224    pub fn create_generated_cpp_multi_qobjects() -> GeneratedCppBlocks {
225        GeneratedCppBlocks {
226            forward_declares: vec![],
227            includes: BTreeSet::default(),
228            extern_cxx_qt: vec![],
229            qobjects: vec![
230                GeneratedCppQObject {
231                    name: Name::mock_namespaced("FirstObject", "cxx_qt"),
232                    rust_struct: Name::mock("FirstObjectRust"),
233                    namespace_internals: "cxx_qt::cxx_qt_first_object".to_owned(),
234                    has_qobject_macro: true,
235                    blocks: GeneratedCppQObjectBlocks {
236                        base_classes: vec!["QStringListModel".to_owned()],
237                        includes: {
238                          let mut includes = BTreeSet::<String>::default();
239                          includes.insert("#include <test>".to_owned());
240                          includes
241                        },
242                        metaobjects: vec![
243                            "Q_PROPERTY(int longPropertyNameThatWrapsInClangFormat READ count WRITE setCount NOTIFY countChanged)"
244                                .to_owned(),
245                        ],
246                        methods: vec![CppFragment::Pair {
247                            header: "int count() const;".to_owned(),
248                            source: indoc! {r#"
249                                    int
250                                    FirstObject::count() const
251                                    {
252                                      // getter
253                                    }
254                                "#}
255                            .to_owned(),
256                        },
257                        CppFragment::Pair {
258                            header: "Q_SLOT void setCount(int count);".to_owned(),
259                            source: indoc! {r#"
260                                    void
261                                    FirstObject::setCount(int count) const
262                                    {
263                                      // setter
264                                    }
265                                "#}
266                            .to_owned(),
267                        },
268                        CppFragment::Header("Q_SIGNAL void countChanged();".to_owned()),
269                        ],
270                        ..Default::default()
271                    }
272                },
273                GeneratedCppQObject {
274                    name: Name::mock_namespaced("SecondObject",  "cxx_qt"),
275                    rust_struct: Name::mock("SecondObjectRust"),
276                    namespace_internals: "cxx_qt::cxx_qt_second_object".to_owned(),
277                    has_qobject_macro: true,
278                    blocks: GeneratedCppQObjectBlocks {
279                        base_classes: vec!["QStringListModel".to_owned()],
280                        includes: {
281                          let mut includes = BTreeSet::<String>::default();
282                          includes.insert("#include <test>".to_owned());
283                          includes
284                        },
285                        metaobjects: vec![
286                            "Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)"
287                                .to_owned(),
288                        ],
289                        methods: vec![CppFragment::Pair {
290                            header: "int count() const;".to_owned(),
291                            source: indoc! {r#"
292                                    int
293                                    SecondObject::count() const
294                                    {
295                                      // getter
296                                    }
297                                "#}
298                            .to_owned(),
299                        },
300                        CppFragment::Pair {
301                            header: "Q_SLOT void setCount(int count);".to_owned(),
302                            source: indoc! {r#"
303                                void
304                                SecondObject::setCount(int count) const
305                                {
306                                  // setter
307                                }
308                                "#}
309                            .to_owned(),
310                        },
311                        CppFragment::Header("Q_SIGNAL void countChanged();".to_owned()),
312                        ],
313                        private_methods: vec![
314                            CppFragment::Pair{
315                                header: "void privateMethod() const;".to_owned(),
316                                source: indoc! {r#"
317                                    void SecondObject::privateMethod() const {
318                                        // private method
319                                    }
320                                    "#}.to_owned(),
321                            }],
322                        ..Default::default()
323                    },
324                }
325            ]
326        }
327    }
328
329    /// Helper to create a GeneratedCppBlocks with no namespace for testing
330    pub fn create_generated_cpp_no_namespace() -> GeneratedCppBlocks {
331        create_generated_cpp_with_namespace(None)
332    }
333
334    /// Helper for the expected header
335    pub fn expected_header() -> &'static str {
336        indoc! {r#"
337        #pragma once
338
339        #include <test>
340
341        namespace cxx_qt::my_object {
342        class MyObject;
343
344
345        } // namespace cxx_qt::my_object
346
347
348
349        #include "cxx-qt-gen/cxx_file_stem.cxx.h"
350
351
352
353        namespace cxx_qt::my_object {
354        class MyObject : public QStringListModel
355        {
356          Q_OBJECT
357        public:
358          Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
359          Q_PROPERTY(bool longPropertyNameThatWrapsInClangFormat READ getToggle WRITE setToggle NOTIFY toggleChanged)
360
361          virtual ~MyObject() = default;
362
363        public:
364          int count() const;
365          bool toggle() const;
366          Q_INVOKABLE void invokable();
367          void cppMethod();
368          Q_SLOT void setCount(int count);
369          Q_SLOT void setToggle(bool toggle);
370          Q_SIGNAL void countChanged();
371          Q_SIGNAL void toggleChanged();
372
373        private:
374          void privateMethod() const;
375          void privateMethod();
376
377        };
378
379        static_assert(::std::is_base_of<QObject, MyObject>::value, "MyObject must inherit from QObject");
380        } // namespace cxx_qt::my_object
381
382
383
384        Q_DECLARE_METATYPE(cxx_qt::my_object::MyObject*)
385
386        "#}
387    }
388
389    /// Helper for the expected header with multiple QObjects
390    pub fn expected_header_multi_qobjects() -> &'static str {
391        indoc! {r#"
392        #pragma once
393
394        #include <test>
395
396        namespace cxx_qt {
397        class FirstObject;
398
399
400        } // namespace cxx_qt
401
402
403
404        namespace cxx_qt {
405        class SecondObject;
406
407
408        } // namespace cxx_qt
409
410
411
412        #include "cxx-qt-gen/cxx_file_stem.cxx.h"
413
414
415
416        namespace cxx_qt {
417        class FirstObject : public QStringListModel
418        {
419          Q_OBJECT
420        public:
421          Q_PROPERTY(int longPropertyNameThatWrapsInClangFormat READ count WRITE setCount NOTIFY countChanged)
422
423          virtual ~FirstObject() = default;
424
425        public:
426          int count() const;
427          Q_SLOT void setCount(int count);
428          Q_SIGNAL void countChanged();
429
430
431        };
432
433        static_assert(::std::is_base_of<QObject, FirstObject>::value, "FirstObject must inherit from QObject");
434        } // namespace cxx_qt
435
436
437
438        Q_DECLARE_METATYPE(cxx_qt::FirstObject*)
439
440
441        namespace cxx_qt {
442        class SecondObject : public QStringListModel
443        {
444          Q_OBJECT
445        public:
446          Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
447
448          virtual ~SecondObject() = default;
449
450        public:
451          int count() const;
452          Q_SLOT void setCount(int count);
453          Q_SIGNAL void countChanged();
454
455        private:
456          void privateMethod() const;
457
458        };
459
460        static_assert(::std::is_base_of<QObject, SecondObject>::value, "SecondObject must inherit from QObject");
461        } // namespace cxx_qt
462
463
464
465        Q_DECLARE_METATYPE(cxx_qt::SecondObject*)
466
467        "#}
468    }
469
470    /// Helper for the expected header with no namespace
471    pub fn expected_header_no_namespace() -> &'static str {
472        indoc! {r#"
473        #pragma once
474
475        #include <test>
476
477        class MyObject;
478
479
480
481
482        #include "cxx-qt-gen/cxx_file_stem.cxx.h"
483
484
485
486        class MyObject : public QStringListModel
487        {
488          Q_OBJECT
489        public:
490          Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
491          Q_PROPERTY(bool longPropertyNameThatWrapsInClangFormat READ getToggle WRITE setToggle NOTIFY toggleChanged)
492
493          virtual ~MyObject() = default;
494
495        public:
496          int count() const;
497          bool toggle() const;
498          Q_INVOKABLE void invokable();
499          void cppMethod();
500          Q_SLOT void setCount(int count);
501          Q_SLOT void setToggle(bool toggle);
502          Q_SIGNAL void countChanged();
503          Q_SIGNAL void toggleChanged();
504
505        private:
506          void privateMethod() const;
507          void privateMethod();
508
509        };
510
511        static_assert(::std::is_base_of<QObject, MyObject>::value, "MyObject must inherit from QObject");
512
513
514        Q_DECLARE_METATYPE(MyObject*)
515
516        "#}
517    }
518
519    /// Helper for the expected source
520    pub fn expected_source() -> &'static str {
521        indoc! {r#"
522        #include "cxx-qt-gen/cxx_file_stem.cxxqt.h"
523
524
525        namespace cxx_qt::my_object {
526        int
527        MyObject::count() const
528        {
529          // getter
530        }
531
532        bool
533        MyObject::toggle() const
534        {
535          // getter
536        }
537
538        void
539        MyObject::invokable()
540        {
541          // invokable method
542        }
543
544        void
545        MyObject::cppMethod()
546        {
547          // cpp method
548        }
549
550        void
551        MyObject::setCount(int count) const
552        {
553          // setter
554        }
555
556        void
557        MyObject::setToggle(bool toggle) const
558        {
559          // setter
560        }
561
562        void MyObject::privateMethod() const {
563            // private method
564        }
565
566        void MyObject::privateMethod()
567        {
568            // non-const private method
569        }
570
571        } // namespace cxx_qt::my_object
572
573        "#}
574    }
575
576    /// Helper for the expected source with multiple QObjects
577    pub fn expected_source_multi_qobjects() -> &'static str {
578        indoc! {r#"
579        #include "cxx-qt-gen/cxx_file_stem.cxxqt.h"
580
581
582        namespace cxx_qt {
583        int
584        FirstObject::count() const
585        {
586          // getter
587        }
588
589        void
590        FirstObject::setCount(int count) const
591        {
592          // setter
593        }
594
595        } // namespace cxx_qt
596
597        namespace cxx_qt {
598        int
599        SecondObject::count() const
600        {
601          // getter
602        }
603
604        void
605        SecondObject::setCount(int count) const
606        {
607          // setter
608        }
609
610        void SecondObject::privateMethod() const {
611            // private method
612        }
613
614        } // namespace cxx_qt
615
616        "#}
617    }
618
619    /// Helper for the expected source with no namespace
620    pub fn expected_source_no_namespace() -> &'static str {
621        indoc! {r#"
622        #include "cxx-qt-gen/cxx_file_stem.cxxqt.h"
623
624
625        int
626        MyObject::count() const
627        {
628          // getter
629        }
630
631        bool
632        MyObject::toggle() const
633        {
634          // getter
635        }
636
637        void
638        MyObject::invokable()
639        {
640          // invokable method
641        }
642
643        void
644        MyObject::cppMethod()
645        {
646          // cpp method
647        }
648
649        void
650        MyObject::setCount(int count) const
651        {
652          // setter
653        }
654
655        void
656        MyObject::setToggle(bool toggle) const
657        {
658          // setter
659        }
660
661        void MyObject::privateMethod() const {
662            // private method
663        }
664
665        void MyObject::privateMethod()
666        {
667            // non-const private method
668        }
669
670        "#}
671    }
672
673    #[test]
674    fn namespacing() {
675        let cpp_code = "// My C++ Code\n  // with multi-line";
676
677        let namespaced_code = namespaced("my_namespace", cpp_code);
678
679        assert_str_eq!(
680            indoc! {r#"
681            namespace my_namespace {
682            // My C++ Code
683              // with multi-line
684            } // namespace my_namespace
685            "#
686            },
687            namespaced_code
688        );
689    }
690
691    #[test]
692    fn namespacing_with_empty_namespace() {
693        let cpp_code = indoc! {r#"
694            // My C++ Code
695            "#};
696        let namespaced_code = namespaced("", cpp_code);
697        assert_str_eq!(cpp_code, namespaced_code);
698    }
699
700    #[test]
701    fn test_write_cpp() {
702        let generated = create_generated_cpp();
703        let (header, source) =
704            require_pair(&write_cpp(&generated, "cxx-qt-gen/cxx_file_stem")).unwrap();
705        assert_str_eq!(header, format_cpp(expected_header()));
706        assert_str_eq!(source, format_cpp(expected_source()));
707    }
708
709    #[test]
710    fn test_write_cpp_multi_qobjects() {
711        let generated = create_generated_cpp_multi_qobjects();
712        let (header, source) =
713            require_pair(&write_cpp(&generated, "cxx-qt-gen/cxx_file_stem")).unwrap();
714        assert_str_eq!(header, format_cpp(expected_header_multi_qobjects()));
715        assert_str_eq!(source, format_cpp(expected_source_multi_qobjects()));
716    }
717
718    #[test]
719    fn test_write_cpp_no_namespace() {
720        let generated = create_generated_cpp_no_namespace();
721        let (header, source) =
722            require_pair(&write_cpp(&generated, "cxx-qt-gen/cxx_file_stem")).unwrap();
723        assert_str_eq!(header, format_cpp(expected_header_no_namespace()));
724        assert_str_eq!(source, format_cpp(expected_source_no_namespace()));
725    }
726}