diff --git a/.clang-uml b/.clang-uml index ff87813c..6e6d2e75 100644 --- a/.clang-uml +++ b/.clang-uml @@ -16,6 +16,10 @@ diagrams: include!: uml/class_model_class_diagram.yml sequence_model_class: include!: uml/sequence_model_class_diagram.yml + sequence_diagram_visitor_sequence: + include!: uml/sequence_diagram_visitor_sequence_diagram.yml + class_diagram_generator_sequence: + include!: uml/class_diagram_generator_sequence_diagram.yml package_model_class: include!: uml/package_model_class_diagram.yml include_graph: diff --git a/.gitignore b/.gitignore index 2b3f25ec..d82c22e2 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ packaging/conda/meta.yaml .idea/ cmake-build- cmake-build-* +.run/ diff --git a/Makefile b/Makefile index fa2ebe55..95400d84 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,7 @@ document_test_cases: test_plantuml clanguml_diagrams: debug mkdir -p docs/diagrams debug/clang-uml - plantuml -tsvg docs/diagrams/*.puml + plantuml -tsvg -nometadata docs/diagrams/*.puml python3 util/format_svg.py docs/diagrams/*.svg .PHONY: submodules diff --git a/docs/test_cases.md b/docs/test_cases.md index 93c8f5cd..cc9f1167 100644 --- a/docs/test_cases.md +++ b/docs/test_cases.md @@ -52,6 +52,26 @@ ## Sequence diagrams * [t20001](./test_cases/t20001.md) - Basic sequence diagram test case * [t20002](./test_cases/t20002.md) - Free function sequence diagram test case + * [t20003](./test_cases/t20003.md) - Function template sequence diagram test case + * [t20004](./test_cases/t20004.md) - Function template instantiation sequence diagram test case + * [t20005](./test_cases/t20005.md) - Class template basic sequence diagram + * [t20006](./test_cases/t20006.md) - Class template specialization basic sequence diagram + * [t20007](./test_cases/t20007.md) - Class template variadic argument list sequence diagram + * [t20008](./test_cases/t20008.md) - Constexpr if sequence diagram test case + * [t20009](./test_cases/t20009.md) - Smart pointer dereference call expression test case + * [t20010](./test_cases/t20010.md) - Container sequence diagram test case + * [t20011](./test_cases/t20011.md) - Recursive calls sequence diagram test case + * [t20012](./test_cases/t20012.md) - Lambda expression call sequence diagram test case + * [t20013](./test_cases/t20013.md) - Function and method arguments in sequence diagrams test case + * [t20014](./test_cases/t20014.md) - Multiple translation units sequence diagram test case + * [t20015](./test_cases/t20015.md) - Class exclusion by namespace in sequence diagram test case + * [t20016](./test_cases/t20016.md) - Template method specialization sequence diagram test case + * [t20017](./test_cases/t20017.md) - Test case for combine_free_functions_into_file_participants option + * [t20018](./test_cases/t20018.md) - Recursive template sequence diagram test case + * [t20019](./test_cases/t20019.md) - Curiously Recurring Template Pattern sequence diagram test case + * [t20020](./test_cases/t20020.md) - If statement sequence diagram test case + * [t20021](./test_cases/t20021.md) - Loop statements sequence diagram test case + * [t20022](./test_cases/t20022.md) - Forward class declaration sequence diagram test case ## Package diagrams * [t30001](./test_cases/t30001.md) - Basic package diagram test case * [t30002](./test_cases/t30002.md) - Package dependency test case diff --git a/docs/test_cases/t00002_class.svg b/docs/test_cases/t00002_class.svg index 7fcfadba..9c2ce178 100644 --- a/docs/test_cases/t00002_class.svg +++ b/docs/test_cases/t00002_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + A @@ -21,8 +21,8 @@ foo_c() = 0 : void - - + + B @@ -31,8 +31,8 @@ foo_a() : void - - + + C @@ -41,18 +41,18 @@ foo_c() : void - - + + D - + - + as : std::vector<A *> @@ -60,18 +60,18 @@ foo_a() : void foo_c() : void - - + + E - + - + as : std::vector<A *> @@ -79,13 +79,13 @@ foo_a() : void foo_c() : void - + This is class A - + This is class B - + This is class D diff --git a/docs/test_cases/t00003_class.svg b/docs/test_cases/t00003_class.svg index 3e938744..ab9f861a 100644 --- a/docs/test_cases/t00003_class.svg +++ b/docs/test_cases/t00003_class.svg @@ -1,6 +1,6 @@ - + @@ -9,74 +9,74 @@ - - + + A - + - + public_member : int - + - + protected_member : int - + - + private_member : int - + - + a_ : int - + - + b_ : int - + - + c_ : int - + - + static_int : int - + - + static_const_int : const int - + - + auto_member : const unsigned long @@ -112,11 +112,11 @@ protected_method() : void private_method() : void - + - + compare : std::function<bool (const int)> diff --git a/docs/test_cases/t00004_class.svg b/docs/test_cases/t00004_class.svg index 62a7dbf8..3402db3a 100644 --- a/docs/test_cases/t00004_class.svg +++ b/docs/test_cases/t00004_class.svg @@ -1,6 +1,6 @@ - + @@ -9,16 +9,16 @@ - - + + B - - + + B::AA @@ -28,8 +28,8 @@ AA_3 - - + + A @@ -40,16 +40,16 @@ foo2() const : void - - + + A::AA - - + + A::AA::Lights @@ -59,15 +59,15 @@ Red - - + + A::AA::AAA - + C::B @@ -75,8 +75,8 @@ int - - + + C @@ -84,39 +84,39 @@ T - + - + t : T - + - + b_int : B<int> - - + + C::AA - - + + C::AA::AAA - - + + C::AA::CCC @@ -125,8 +125,8 @@ CCC_2 - - + + C::B @@ -134,16 +134,16 @@ V - + - + b : V - - + + C::CC @@ -152,16 +152,16 @@ CC_2 - - + + detail::D - - + + detail::D::AA @@ -171,8 +171,8 @@ AA_3 - - + + detail::D::DD diff --git a/docs/test_cases/t00005_class.svg b/docs/test_cases/t00005_class.svg index 437cc137..90580ecc 100644 --- a/docs/test_cases/t00005_class.svg +++ b/docs/test_cases/t00005_class.svg @@ -1,6 +1,6 @@ - + @@ -9,204 +9,204 @@ - - + + A - - + + B - - + + C - - + + D - - + + E - - + + F - - + + G - - + + H - - + + I - - + + J - - + + K - - + + R - + - + some_int : int - + - + some_int_pointer : int * - + - + some_int_pointer_pointer : int ** - + - + some_int_reference : int & - + - + a : A - + - + b : B * - + - + c : C & - + - + d : const D * - + - + e : const E & - + - + f : F && - + - + g : G ** - + - + h : H *** - + - + i : I *& - + - + j : volatile J * - + - + k : K * diff --git a/docs/test_cases/t00006_class.svg b/docs/test_cases/t00006_class.svg index cc98f1b1..34d305dd 100644 --- a/docs/test_cases/t00006_class.svg +++ b/docs/test_cases/t00006_class.svg @@ -1,6 +1,6 @@ - + @@ -9,136 +9,136 @@ - - + + A - - + + B - - + + C - - + + D - - + + E - - + + F - - + + G - - + + H - - + + I - - + + J - - + + K - - + + L - - + + M - - + + N - - + + NN - - + + NNN - - + + custom_container @@ -146,15 +146,15 @@ T - + - + data : std::vector<T> - + custom_container @@ -162,102 +162,102 @@ E - - + + R - + - + a : std::vector<A> - + - + b : std::vector<B *> - + - + c : std::map<int,C> - + - + d : std::map<int,D *> - + - + e : custom_container<E> - + - + f : std::vector<std::vector<F>> - + - + g : std::map<int,std::vector<G *>> - + - + h : std::array<H,10> - + - + i : std::array<I *,5> - + - + j : J[10] - + - + k : K *[20] - + - + lm : std::vector<std::pair<L,M>> - + - + ns : std::tuple<N,NN,NNN> diff --git a/docs/test_cases/t00007_class.svg b/docs/test_cases/t00007_class.svg index 8efaa576..2e8e3413 100644 --- a/docs/test_cases/t00007_class.svg +++ b/docs/test_cases/t00007_class.svg @@ -1,6 +1,6 @@ - + @@ -9,56 +9,56 @@ - - + + A - - + + B - - + + C - - + + R - + - + a : std::unique_ptr<A> - + - + b : std::shared_ptr<B> - + - + c : std::weak_ptr<C> diff --git a/docs/test_cases/t00008_class.svg b/docs/test_cases/t00008_class.svg index 3d37d07e..127c6642 100644 --- a/docs/test_cases/t00008_class.svg +++ b/docs/test_cases/t00008_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + A @@ -18,51 +18,51 @@ T,P,CMP,int N - + - + value : T - + - + pointer : T * - + - + reference : T & - + - + values : std::vector<P> - + - + ints : std::array<int,N> - + - + comparator : CMP - - + + Vector @@ -70,16 +70,16 @@ T - + - + values : std::vector<T> - - + + B @@ -87,15 +87,15 @@ T,C<> - + - + template_template : C<T> - + B @@ -103,18 +103,18 @@ int,Vector - - + + D - + - + ints : B<int,Vector> diff --git a/docs/test_cases/t00009_class.svg b/docs/test_cases/t00009_class.svg index cbf22efa..97c3cf94 100644 --- a/docs/test_cases/t00009_class.svg +++ b/docs/test_cases/t00009_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + A @@ -18,15 +18,15 @@ T - + - + value : T - + A @@ -34,7 +34,7 @@ int - + A @@ -42,7 +42,7 @@ std::string - + A @@ -50,32 +50,32 @@ std::vector<std::string> - - + + B - + - + aint : A<int> - + - + astring : A<std::string> * - + - + avector : A<std::vector<std::string>> & diff --git a/docs/test_cases/t00010_class.svg b/docs/test_cases/t00010_class.svg index 65b8872c..56fe165f 100644 --- a/docs/test_cases/t00010_class.svg +++ b/docs/test_cases/t00010_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + A @@ -18,22 +18,22 @@ T,P - + - + first : T - + - + second : P - + A @@ -41,8 +41,8 @@ T,std::string - - + + B @@ -50,15 +50,15 @@ T - + - + astring : A<T,std::string> - + B @@ -66,18 +66,18 @@ int - - + + C - + - + aintstring : B<int> diff --git a/docs/test_cases/t00011_class.svg b/docs/test_cases/t00011_class.svg index 1cd512a7..9e510a1d 100644 --- a/docs/test_cases/t00011_class.svg +++ b/docs/test_cases/t00011_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + D @@ -18,16 +18,16 @@ T - + - + value : T - - + + A @@ -36,18 +36,18 @@ foo() : void - - + + B - + - + m_a : A * diff --git a/docs/test_cases/t00012_class.svg b/docs/test_cases/t00012_class.svg index c6d06850..fa0424c4 100644 --- a/docs/test_cases/t00012_class.svg +++ b/docs/test_cases/t00012_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + A @@ -18,23 +18,23 @@ T,Ts... - + - + value : T - + - + values : std::variant<Ts...> - - + + B @@ -43,15 +43,15 @@ - + - + ints : std::array<int,sizeof...(Is)> - - + + C @@ -60,14 +60,14 @@ - + - + ints : std::array<T,sizeof...(Is)> - + A @@ -75,7 +75,7 @@ int,std::string,float - + A @@ -83,7 +83,7 @@ int,std::string,bool - + B @@ -91,7 +91,7 @@ 3,2,1 - + B @@ -99,7 +99,7 @@ 1,1,1,1 - + C @@ -107,50 +107,50 @@ std::map<int,std::vector<std::vector<std::vector<std::string>>>>,3,3,3 - - + + R - + - + a1 : A<int,std::string,float> - + - + a2 : A<int,std::string,bool> - + - + b1 : B<3,2,1> - + - + b2 : B<1,1,1,1> - + - + c1 : C<std::map<int,std::vector<std::vector<std::vector<std::string>>>>,3,3,3> - + Long template annotation diff --git a/docs/test_cases/t00013_class.svg b/docs/test_cases/t00013_class.svg index 9888703e..b36f70ac 100644 --- a/docs/test_cases/t00013_class.svg +++ b/docs/test_cases/t00013_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + ABCD::F @@ -18,15 +18,15 @@ T - + - + f : T - + ABCD::F @@ -34,70 +34,70 @@ int - - + + A - + - + a : int - - + + B - + - + b : int - - + + C - + - + c : int - - + + D - + - + d : int print(R * r) : void - - + + E @@ -105,16 +105,16 @@ T - + - + e : T - - + + G @@ -122,22 +122,22 @@ T,Args... - + - + g : T - + - + args : std::tuple<Args...> - + E @@ -145,7 +145,7 @@ int - + G @@ -153,7 +153,7 @@ int,float,std::string - + E @@ -161,25 +161,25 @@ std::string - - + + R - + - + gintstring : G<int,float,std::string> - + - + estring : E<std::string> diff --git a/docs/test_cases/t00014_class.svg b/docs/test_cases/t00014_class.svg index 9b919155..53800b54 100644 --- a/docs/test_cases/t00014_class.svg +++ b/docs/test_cases/t00014_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + A @@ -18,37 +18,37 @@ T,P - + - + t : T - + - + p : P - - + + B - + - + value : std::string - + A @@ -56,7 +56,7 @@ T,std::string - + A @@ -64,7 +64,7 @@ T,std::unique_ptr<std::string> - + A @@ -72,7 +72,7 @@ long,T - + A @@ -80,7 +80,7 @@ double,T - + A @@ -88,7 +88,7 @@ long,U - + A @@ -96,7 +96,7 @@ long,bool - + A @@ -104,7 +104,7 @@ double,bool - + A @@ -112,7 +112,7 @@ long,float - + A @@ -120,7 +120,7 @@ double,float - + A @@ -128,7 +128,7 @@ bool,std::string - + A @@ -136,7 +136,7 @@ float,std::unique_ptr<std::string> - + A @@ -144,7 +144,7 @@ int,std::string - + A @@ -152,7 +152,7 @@ std::string,std::string - + A @@ -160,7 +160,7 @@ char,std::string - + A @@ -168,116 +168,116 @@ wchar_t,std::string - - + + R - + - + bapair : PairPairBA<bool> - + - + abool : APtr<bool> - + - + aboolfloat : AAPtr<bool,float> - + - + afloat : ASharedPtr<float> - + - + boolstring : A<bool,std::string> - + - + floatstring : AStringPtr<float> - + - + intstring : AIntString - + - + stringstring : AStringString - + - + bstringstring : BStringString - + - + bs : BVector - + - + bs2 : BVector2 - + - + cb : SimpleCallback<ACharString> - + - + gcb : GenericCallback<R::AWCharString> - + - + vcb : VoidCallback - + - + vps : VectorPtr<B> @@ -307,9 +307,9 @@ - + - + @@ -375,9 +375,9 @@ bstringstring - + - + diff --git a/docs/test_cases/t00015_class.svg b/docs/test_cases/t00015_class.svg index d99fb8c3..d16e1ce3 100644 --- a/docs/test_cases/t00015_class.svg +++ b/docs/test_cases/t00015_class.svg @@ -1,6 +1,6 @@ - + @@ -9,40 +9,40 @@ - - + + ns1::A - - + + ns1::ns2_v0_9_0::A - - + + ns1::Anon - - + + ns3::ns1::ns2::Anon - - + + ns3::B diff --git a/docs/test_cases/t00016_class.svg b/docs/test_cases/t00016_class.svg index b8343025..fdaeb1cd 100644 --- a/docs/test_cases/t00016_class.svg +++ b/docs/test_cases/t00016_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + is_numeric<> @@ -19,8 +19,8 @@ value : enum - - + + is_numeric @@ -29,8 +29,8 @@ value : enum - - + + is_numeric @@ -41,8 +41,8 @@ value : enum - - + + is_numeric @@ -53,8 +53,8 @@ value : enum - - + + is_numeric @@ -65,8 +65,8 @@ value : enum - - + + is_numeric @@ -77,8 +77,8 @@ value : enum - - + + is_numeric diff --git a/docs/test_cases/t00017_class.svg b/docs/test_cases/t00017_class.svg index d5c5d30e..a0a92932 100644 --- a/docs/test_cases/t00017_class.svg +++ b/docs/test_cases/t00017_class.svg @@ -1,6 +1,6 @@ - + @@ -9,127 +9,127 @@ - - + + A - - + + B - - + + C - - + + D - - + + E - - + + F - - + + G - - + + H - - + + I - - + + J - - + + K - - + + R - + - + some_int : int - + - + some_int_pointer : int * - + - + some_int_pointer_pointer : int ** - + - + some_int_reference : int & diff --git a/docs/test_cases/t00018_class.svg b/docs/test_cases/t00018_class.svg index 78d0ad81..a0ebe9bc 100644 --- a/docs/test_cases/t00018_class.svg +++ b/docs/test_cases/t00018_class.svg @@ -1,6 +1,6 @@ - + @@ -9,18 +9,18 @@ - - + + impl::widget - + - + n : int @@ -30,18 +30,18 @@ draw(const widget & w) : void widget(int n) : void - - + + widget - + - + pImpl : std::unique_ptr<impl::widget> diff --git a/docs/test_cases/t00019_class.svg b/docs/test_cases/t00019_class.svg index e0519624..928330d1 100644 --- a/docs/test_cases/t00019_class.svg +++ b/docs/test_cases/t00019_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + Base @@ -25,8 +25,8 @@ m2() : std::string - - + + Layer1 @@ -39,8 +39,8 @@ m2() : std::string - - + + Layer2 @@ -51,8 +51,8 @@ all_calls_count() const : int - - + + Layer3 @@ -60,18 +60,18 @@ LowerLayer - + - + m_m1_calls : int - + - + m_m2_calls : int @@ -83,7 +83,7 @@ m1_calls() const : int m2_calls() const : int - + Layer3 @@ -91,7 +91,7 @@ Base - + Layer2 @@ -99,7 +99,7 @@ Layer3<Base> - + Layer1 @@ -107,18 +107,18 @@ Layer2<Layer3<Base>> - - + + A - + - + layers : std::unique_ptr<Layer1<Layer2<Layer3<Base>>>> diff --git a/docs/test_cases/t00020_class.svg b/docs/test_cases/t00020_class.svg index deb0cf94..1807f957 100644 --- a/docs/test_cases/t00020_class.svg +++ b/docs/test_cases/t00020_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + ProductA @@ -21,8 +21,8 @@ sell(int price) const = 0 : bool - - + + ProductA1 @@ -31,8 +31,8 @@ sell(int price) const : bool - - + + ProductA2 @@ -41,8 +41,8 @@ sell(int price) const : bool - - + + ProductB @@ -53,8 +53,8 @@ buy(int price) const = 0 : bool - - + + ProductB1 @@ -63,8 +63,8 @@ buy(int price) const : bool - - + + ProductB2 @@ -73,8 +73,8 @@ buy(int price) const : bool - - + + AbstractFactory @@ -85,8 +85,8 @@ make_b() const = 0 : std::unique_ptr<ProductB> - - + + Factory1 @@ -97,8 +97,8 @@ make_b() const : std::unique_ptr<ProductB> - - + + Factory2 diff --git a/docs/test_cases/t00021_class.svg b/docs/test_cases/t00021_class.svg index f81368af..20ad9ec6 100644 --- a/docs/test_cases/t00021_class.svg +++ b/docs/test_cases/t00021_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + Visitor @@ -23,8 +23,8 @@ visit_B(const B & item) const = 0 : void - - + + Visitor1 @@ -35,8 +35,8 @@ visit_B(const B & item) const : void - - + + Visitor2 @@ -47,8 +47,8 @@ visit_B(const B & item) const : void - - + + Visitor3 @@ -59,8 +59,8 @@ visit_B(const B & item) const : void - - + + Item @@ -71,8 +71,8 @@ accept(const Visitor & visitor) const = 0 : void - - + + A @@ -81,8 +81,8 @@ accept(const Visitor & visitor) const : void - - + + B diff --git a/docs/test_cases/t00022_class.svg b/docs/test_cases/t00022_class.svg index 7a233cc7..cf9fe7d9 100644 --- a/docs/test_cases/t00022_class.svg +++ b/docs/test_cases/t00022_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + A @@ -23,8 +23,8 @@ method2() = 0 : void - - + + A1 @@ -35,8 +35,8 @@ method2() : void - - + + A2 diff --git a/docs/test_cases/t00023_class.svg b/docs/test_cases/t00023_class.svg index 16874b6b..e7a45e26 100644 --- a/docs/test_cases/t00023_class.svg +++ b/docs/test_cases/t00023_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + Strategy @@ -21,8 +21,8 @@ algorithm() = 0 : void - - + + StrategyA @@ -31,8 +31,8 @@ algorithm() : void - - + + StrategyB @@ -41,8 +41,8 @@ algorithm() : void - - + + StrategyC @@ -51,18 +51,18 @@ algorithm() : void - - + + Context - + - + m_strategy : std::unique_ptr<Strategy> diff --git a/docs/test_cases/t00024_class.svg b/docs/test_cases/t00024_class.svg index 843656e9..fb73378e 100644 --- a/docs/test_cases/t00024_class.svg +++ b/docs/test_cases/t00024_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + Target @@ -23,8 +23,8 @@ m2() = 0 : void - - + + Target1 @@ -35,8 +35,8 @@ m2() : void - - + + Target2 @@ -47,18 +47,18 @@ m2() : void - - + + Proxy - + - + m_target : std::shared_ptr<Target> diff --git a/docs/test_cases/t00025_class.svg b/docs/test_cases/t00025_class.svg index 3dde1987..d19e7f69 100644 --- a/docs/test_cases/t00025_class.svg +++ b/docs/test_cases/t00025_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + Target1 @@ -21,8 +21,8 @@ m2() : void - - + + Target2 @@ -33,8 +33,8 @@ m2() : void - - + + Proxy @@ -42,11 +42,11 @@ T - + - + m_target : std::shared_ptr<T> @@ -56,7 +56,7 @@ m1() : void m2() : void - + Proxy @@ -64,7 +64,7 @@ Target1 - + Proxy @@ -72,25 +72,25 @@ Target2 - - + + ProxyHolder - + - + proxy1 : Proxy<Target1> - + - + proxy2 : Proxy<Target2> diff --git a/docs/test_cases/t00026_class.svg b/docs/test_cases/t00026_class.svg index 70337c3f..066745e3 100644 --- a/docs/test_cases/t00026_class.svg +++ b/docs/test_cases/t00026_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + Memento @@ -18,11 +18,11 @@ T - + - + m_value : T @@ -30,8 +30,8 @@ Memento<T>(T && v) : void value() const : T - - + + Originator @@ -39,11 +39,11 @@ T - + - + m_value : T @@ -57,8 +57,8 @@ print() const : void set(T && v) : void - - + + Caretaker @@ -66,11 +66,11 @@ T - + - + m_mementos : std::unordered_map<std::string,Memento<T>> @@ -78,7 +78,7 @@ state(const std::string & n) : Memento<T> & set_state(const std::string & s, Memento<T> && m) : void - + Caretaker @@ -86,7 +86,7 @@ std::string - + Originator @@ -94,25 +94,25 @@ std::string - - + + StringMemento - + - + caretaker : Caretaker<std::string> - + - + originator : Originator<std::string> diff --git a/docs/test_cases/t00027_class.svg b/docs/test_cases/t00027_class.svg index 64cb30e3..0fdb1f8c 100644 --- a/docs/test_cases/t00027_class.svg +++ b/docs/test_cases/t00027_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + Shape @@ -21,14 +21,14 @@ ~Shape() = default : void - + Line - - + + Line @@ -39,14 +39,14 @@ display() : void - + Text - - + + Text @@ -57,8 +57,8 @@ display() : void - - + + ShapeDecorator @@ -67,8 +67,8 @@ display() = 0 : void - - + + Color @@ -79,8 +79,8 @@ display() : void - - + + Weight @@ -91,7 +91,7 @@ display() : void - + Line @@ -99,7 +99,7 @@ Color,Weight - + Line @@ -107,7 +107,7 @@ Color - + Text @@ -115,7 +115,7 @@ Color,Weight - + Text @@ -123,39 +123,39 @@ Color - - + + Window - + - + border : Line<Color,Weight> - + - + divider : Line<Color> - + - + title : Text<Color,Weight> - + - + description : Text<Color> diff --git a/docs/test_cases/t00028_class.svg b/docs/test_cases/t00028_class.svg index 325830a0..208355dd 100644 --- a/docs/test_cases/t00028_class.svg +++ b/docs/test_cases/t00028_class.svg @@ -1,6 +1,6 @@ - + @@ -9,54 +9,54 @@ - - + + A - + A class note. - - + + B - + B class note. - - + + C - + C class note. - - + + D - + D class note. - - + + E @@ -64,27 +64,27 @@ T - + - + param : T - + E template class note. - - + + G - - + + F @@ -94,10 +94,10 @@ three - + F enum note. - + E @@ -105,59 +105,59 @@ int - - + + R - + - + aaa : A - + - + bbb : B * - + - + ccc : C & - + - + ddd : std::vector<std::shared_ptr<D>> - + - + eee : E<int> - + - + ggg : G ** R(C & c) : void - + R class note. diff --git a/docs/test_cases/t00029_class.svg b/docs/test_cases/t00029_class.svg index 75e09846..b2dbc496 100644 --- a/docs/test_cases/t00029_class.svg +++ b/docs/test_cases/t00029_class.svg @@ -1,6 +1,6 @@ - + @@ -9,16 +9,16 @@ - - + + A - - + + C @@ -26,16 +26,16 @@ T - + - + param : T - - + + E @@ -45,64 +45,64 @@ three - - + + G1 - - + + G2 - - + + G3 - - + + G4 - - + + R - + - + g1 : G1 - + - + g3 : G3 & - + - + g4 : std::shared_ptr<G4> diff --git a/docs/test_cases/t00030_class.svg b/docs/test_cases/t00030_class.svg index a362c03b..4b355df3 100644 --- a/docs/test_cases/t00030_class.svg +++ b/docs/test_cases/t00030_class.svg @@ -1,6 +1,6 @@ - + @@ -9,86 +9,86 @@ - - + + A - - + + B - - + + C - - + + D - - + + E - - + + R - + - + aaa : A - + - + bbb : std::vector<B> - + - + ccc : std::vector<C> - + - + ddd : D - + - + eee : E * diff --git a/docs/test_cases/t00031_class.svg b/docs/test_cases/t00031_class.svg index 097c1dc3..f52e2137 100644 --- a/docs/test_cases/t00031_class.svg +++ b/docs/test_cases/t00031_class.svg @@ -1,33 +1,33 @@ - + - + - + - - - + + + A - - + + B @@ -37,8 +37,8 @@ three - - + + @@ -47,23 +47,23 @@ T - + - + ttt : T - - + + D - + C @@ -71,39 +71,39 @@ int - - + + R - + - + aaa : A * - + - + bbb : std::vector<B> - + - + ccc : C<int> - + - + ddd : D * diff --git a/docs/test_cases/t00032_class.svg b/docs/test_cases/t00032_class.svg index fb0a0275..4e3e60b0 100644 --- a/docs/test_cases/t00032_class.svg +++ b/docs/test_cases/t00032_class.svg @@ -1,6 +1,6 @@ - + @@ -9,24 +9,24 @@ - - + + Base - - + + TBase - - + + A @@ -35,8 +35,8 @@ operator()() : void - - + + B @@ -45,8 +45,8 @@ operator()() : void - - + + C @@ -55,8 +55,8 @@ operator()() : void - - + + Overload @@ -64,15 +64,15 @@ T,L,Ts... - + - + counter : L - + Overload @@ -80,18 +80,18 @@ TBase,int,A,B,C - - + + R - + - + overload : Overload<TBase,int,A,B,C> diff --git a/docs/test_cases/t00033_class.svg b/docs/test_cases/t00033_class.svg index ecd19d28..9f91eeab 100644 --- a/docs/test_cases/t00033_class.svg +++ b/docs/test_cases/t00033_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + A @@ -18,16 +18,16 @@ T - + - + aaa : T - - + + B @@ -35,16 +35,16 @@ T - + - + bbb : T - - + + C @@ -52,30 +52,30 @@ T - + - + ccc : T - - + + D - + - + ddd : int - + C @@ -83,7 +83,7 @@ D - + B @@ -91,7 +91,7 @@ std::unique_ptr<C<D>> - + A @@ -99,18 +99,18 @@ B<std::unique_ptr<C<D>>> - - + + R - + - + abc : A<B<std::unique_ptr<C<D>>>> diff --git a/docs/test_cases/t00034_class.svg b/docs/test_cases/t00034_class.svg index 00317383..8456b303 100644 --- a/docs/test_cases/t00034_class.svg +++ b/docs/test_cases/t00034_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + Void @@ -21,8 +21,8 @@ operator!=(const Void & ) const : bool - - + + lift_void @@ -31,16 +31,16 @@ - - + + lift_void - - + + lift_void @@ -49,8 +49,8 @@ - - + + drop_void @@ -59,16 +59,16 @@ - - + + drop_void - - + + drop_void @@ -77,33 +77,33 @@ - - + + A - - + + R - + - + la : lift_void_t<A> * - + - + lv : lift_void_t<void> * diff --git a/docs/test_cases/t00035_class.svg b/docs/test_cases/t00035_class.svg index fab9db13..9aa89966 100644 --- a/docs/test_cases/t00035_class.svg +++ b/docs/test_cases/t00035_class.svg @@ -1,6 +1,6 @@ - + @@ -9,40 +9,40 @@ - - + + Top - - + + Left - - + + Center - - + + Bottom - - + + Right diff --git a/docs/test_cases/t00036_class.svg b/docs/test_cases/t00036_class.svg index ed26cec5..94611d59 100644 --- a/docs/test_cases/t00036_class.svg +++ b/docs/test_cases/t00036_class.svg @@ -1,6 +1,6 @@ - + @@ -9,23 +9,23 @@ - + ns1 - + ns11 - + ns111 - + ns2 - + ns22 - - + + E @@ -34,8 +34,8 @@ yellow - - + + A @@ -43,15 +43,15 @@ T - + - + a : T - + A @@ -59,23 +59,23 @@ int - - + + B - + - + a_int : A<int> - - + + C diff --git a/docs/test_cases/t00037_class.svg b/docs/test_cases/t00037_class.svg index 072c6497..15145200 100644 --- a/docs/test_cases/t00037_class.svg +++ b/docs/test_cases/t00037_class.svg @@ -1,6 +1,6 @@ - + @@ -9,98 +9,98 @@ - - + + ST - + - + dimensions : ST::(anonymous_620) - + - + units : ST::(anonymous_739) - - + + ST::(dimensions) - + - + t : double - + - + x : double - + - + y : double - + - + z : double - - + + ST::(units) - + - + c : double - + - + h : double - - + + A - + - + st : ST diff --git a/docs/test_cases/t00038_class.svg b/docs/test_cases/t00038_class.svg index d037a570..a3645f4e 100644 --- a/docs/test_cases/t00038_class.svg +++ b/docs/test_cases/t00038_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + thirdparty::ns1::color_t @@ -20,16 +20,16 @@ blue - - + + thirdparty::ns1::E - - + + property_t @@ -39,47 +39,47 @@ property_c - - + + A - - + + B - - + + C - - + + key_t - + - + key : std::string - - + + map @@ -88,16 +88,16 @@ - - + + map - - + + map @@ -106,8 +106,8 @@ - - + + map @@ -116,8 +116,8 @@ - - + + map @@ -126,8 +126,8 @@ - - + + map diff --git a/docs/test_cases/t00039_class.svg b/docs/test_cases/t00039_class.svg index 0bc3a9ef..f7aaa5cc 100644 --- a/docs/test_cases/t00039_class.svg +++ b/docs/test_cases/t00039_class.svg @@ -1,6 +1,6 @@ - + @@ -9,95 +9,95 @@ - - + + C - - + + D - - + + E - - + + CD - - + + DE - - + + CDE - - + + A - - + + AA - - + + AAA - + - + b : B * - - + + ns2::AAAA - - + + ns3::F @@ -105,16 +105,16 @@ T - + - + t : T * - - + + ns3::FF @@ -122,16 +122,16 @@ T,M - + - + m : M * - - + + ns3::FE @@ -139,16 +139,16 @@ T,M - + - + m : M * - - + + ns3::FFF @@ -156,11 +156,11 @@ T,M,N - + - + n : N * diff --git a/docs/test_cases/t00040_class.svg b/docs/test_cases/t00040_class.svg index f34d2c66..49a51f25 100644 --- a/docs/test_cases/t00040_class.svg +++ b/docs/test_cases/t00040_class.svg @@ -1,6 +1,6 @@ - + @@ -9,50 +9,50 @@ - - + + A - + - + ii_ : int get_a() : int - - + + AA - - + + AAA - + - + b : B * get_aaa() : int - - + + R diff --git a/docs/test_cases/t00041_class.svg b/docs/test_cases/t00041_class.svg index 0aa402d1..335ec522 100644 --- a/docs/test_cases/t00041_class.svg +++ b/docs/test_cases/t00041_class.svg @@ -1,6 +1,6 @@ - + @@ -9,100 +9,100 @@ - - + + R - - + + D - + - + rr : RR * - - + + E - - + + F - - + + RR - + - + e : E * - + - + f : F * - + - + g : detail::G * - - + + RRR - - + + ns1::N - - + + ns1::NN - - + + ns1::NM diff --git a/docs/test_cases/t00042_class.svg b/docs/test_cases/t00042_class.svg index f7253a91..cbdbcd54 100644 --- a/docs/test_cases/t00042_class.svg +++ b/docs/test_cases/t00042_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + A @@ -18,16 +18,16 @@ T - + - + a : T - - + + A @@ -35,16 +35,16 @@ void - + - + a : void * - - + + B @@ -52,22 +52,22 @@ T,K - + - + b : T - + - + bb : K - + A @@ -75,7 +75,7 @@ double - + A @@ -83,7 +83,7 @@ std::string - + B diff --git a/docs/test_cases/t00043_class.svg b/docs/test_cases/t00043_class.svg index fed96a77..13141926 100644 --- a/docs/test_cases/t00043_class.svg +++ b/docs/test_cases/t00043_class.svg @@ -1,6 +1,6 @@ - + @@ -9,22 +9,22 @@ - + dependants - + dependencies - - + + A - - + + B @@ -33,8 +33,8 @@ b(dependants::A * a) : void - - + + BB @@ -43,8 +43,8 @@ bb(dependants::A * a) : void - - + + C @@ -53,8 +53,8 @@ c(dependants::B * b) : void - - + + D @@ -65,8 +65,8 @@ dd(dependants::BB * bb) : void - - + + E @@ -75,24 +75,24 @@ e(dependants::D * d) : void - - + + G - - + + GG - - + + H @@ -103,8 +103,8 @@ hh(dependencies::GG * gg) : void - - + + I @@ -113,8 +113,8 @@ i(dependencies::H * h) : void - - + + J diff --git a/docs/test_cases/t00044_class.svg b/docs/test_cases/t00044_class.svg index 39c74bc8..b6ba8e41 100644 --- a/docs/test_cases/t00044_class.svg +++ b/docs/test_cases/t00044_class.svg @@ -1,6 +1,6 @@ - + @@ -9,7 +9,7 @@ - + signal_handler @@ -17,8 +17,8 @@ ,A - - + + sink @@ -27,15 +27,15 @@ sink<signal_handler<type-parameter-0-0 (type-parameter-0-1...), type-parameter-0-2> >(sink<signal_handler<type-parameter-0-0 (type-parameter-0-1...),type-parameter-0-2>>::signal_t & sh) : void - + - + signal : sink<signal_handler<type-parameter-0-0 (type-parameter-0-1...),type-parameter-0-2>>::signal_t * - - + + sink @@ -46,23 +46,23 @@ sink<signal_handler<type-parameter-0-0 (type-parameter-0-1...), type-parameter-0-2> >(sink<signal_handler<type-parameter-0-0 (type-parameter-0-1...),type-parameter-0-2>>::signal_t & sh) : void - + - + signal : sink<signal_handler<type-parameter-0-0 (type-parameter-0-1...),type-parameter-0-2>>::signal_t * - - + + signal_handler - - + + signal_handler @@ -71,8 +71,8 @@ - - + + signal_handler @@ -81,8 +81,8 @@ - - + + sink diff --git a/docs/test_cases/t00045_class.svg b/docs/test_cases/t00045_class.svg index d531d1b8..82f48d40 100644 --- a/docs/test_cases/t00045_class.svg +++ b/docs/test_cases/t00045_class.svg @@ -1,6 +1,6 @@ - + @@ -9,32 +9,32 @@ - - + + A - - + + AA - - + + AAA - - + + AAAA @@ -42,103 +42,103 @@ T - + - + t : T - - + + ns1::A - - + + ns1::ns2::A - - + + ns1::ns2::B - - + + ns1::ns2::C - - + + ns1::ns2::D - - + + ns1::ns2::E - - + + ns1::ns2::AAA - - + + ns1::ns2::R - + - + a : ns1::ns2::A * - + - + ns1_a : ns1::A * - + - + ns1_ns2_a : ns1::ns2::A * - + - + root_a : ::A * diff --git a/docs/test_cases/t00046_class.svg b/docs/test_cases/t00046_class.svg index 78345893..c327b0fb 100644 --- a/docs/test_cases/t00046_class.svg +++ b/docs/test_cases/t00046_class.svg @@ -1,6 +1,6 @@ - + @@ -9,118 +9,118 @@ - + ns1 - + ns2 - + __gnu_cxx - - + + A - - + + A - - + + B - - + + C - - + + D - - + + E - - + + R - + - + a : ns1::ns2::A * - + - + ns1_a : ns1::A * - + - + ns1_ns2_a : ns1::ns2::A * - + - + root_a : ::A * - + - + i : std::vector<std::uint8_t> foo(AA & aa) : void - - + + A - - + + AA diff --git a/docs/test_cases/t00047_class.svg b/docs/test_cases/t00047_class.svg index 8019990e..34a19237 100644 --- a/docs/test_cases/t00047_class.svg +++ b/docs/test_cases/t00047_class.svg @@ -1,6 +1,6 @@ - + @@ -9,16 +9,16 @@ - - + + conditional_t - - + + conditional_t @@ -27,8 +27,8 @@ - - + + conditional_t @@ -37,8 +37,8 @@ - - + + conditional_t @@ -47,8 +47,8 @@ - - + + conditional_t diff --git a/docs/test_cases/t00048.md b/docs/test_cases/t00048.md index 587b2f39..a055889e 100644 --- a/docs/test_cases/t00048.md +++ b/docs/test_cases/t00048.md @@ -9,6 +9,7 @@ diagrams: glob: - ../../tests/t00048/b_t00048.cc - ../../tests/t00048/a_t00048.cc + - ../../tests/t00048/t00048.cc using_namespace: clanguml::t00048 parse_includes: true include: diff --git a/docs/test_cases/t00048_class.svg b/docs/test_cases/t00048_class.svg index 4831786f..48b3d53d 100644 --- a/docs/test_cases/t00048_class.svg +++ b/docs/test_cases/t00048_class.svg @@ -1,6 +1,6 @@ - + @@ -9,25 +9,25 @@ - - + + Base - + - + base : int foo() = 0 : void - - + + BaseTemplate @@ -35,35 +35,35 @@ T - + - + base : T foo() = 0 : void - - + + B - + - + b : int foo() : void - - + + BTemplate @@ -71,35 +71,35 @@ T - + - + b : T foo() : void - - + + A - + - + a : int foo() : void - - + + ATemplate @@ -107,11 +107,11 @@ T - + - + a : T diff --git a/docs/test_cases/t00049_class.svg b/docs/test_cases/t00049_class.svg index b26e3f4e..1467c05b 100644 --- a/docs/test_cases/t00049_class.svg +++ b/docs/test_cases/t00049_class.svg @@ -1,6 +1,6 @@ - + @@ -9,8 +9,8 @@ - - + + A @@ -18,17 +18,17 @@ T - + - + a : T get_a() : T & - + A @@ -36,7 +36,7 @@ intmap - + A @@ -44,7 +44,7 @@ thestring - + A @@ -52,32 +52,32 @@ std::vector<thestring> - - + + R - + - + a_string : A<thestring> - + - + a_vector_string : A<string_vector> - + - + a_int_map : A<intmap> diff --git a/docs/test_cases/t00050_class.svg b/docs/test_cases/t00050_class.svg index f1f999f0..09c91c9a 100644 --- a/docs/test_cases/t00050_class.svg +++ b/docs/test_cases/t00050_class.svg @@ -1,6 +1,6 @@ - + @@ -9,40 +9,40 @@ - - + + A - - + + B - - + + C - - + + utils::D - - + + E @@ -52,8 +52,8 @@ E3 - - + + F @@ -61,44 +61,44 @@ T,V,int N - + - + t : T[N] - + - + v : V - - + + G - - + + NoComment - + Lorem ipsum dolor sit - + Lorem ipsum dolor sit - + Lorem ipsum dolor sit amet consectetur adipiscing elit, urna consequat felis vehicula class ultricies mollis dictumst, aenean non a in donec nulla. @@ -125,50 +125,50 @@ imperdiet praesent magnis ridiculus congue gravida curabitur dictum sagittis, enim et magna sit inceptos sodales parturient pharetra mollis, aenean vel nostra tellus commodo pretium sapien sociosqu. - + This is a short description of class G. - + This is an intermediate description of class G. - + This is a long description of class G. - + Lorem ipsum - + TODO 1. Write meaningful comment - + TODO 2. Write tests - + TODO 3. Implement - + Long comment example - + TODO Implement... - + Simple array wrapper. - + Template parameters diff --git a/docs/test_cases/t20001.md b/docs/test_cases/t20001.md index 0a522af6..2636828b 100644 --- a/docs/test_cases/t20001.md +++ b/docs/test_cases/t20001.md @@ -22,7 +22,7 @@ diagrams: before: - "' t20001 test sequence diagram" after: - - 'note over "tmain()": Main test function' + - '{% set e=element("clanguml::t20001::tmain()") %} note over {{ e.alias) }}: Main test function' ``` ## Source code @@ -58,7 +58,7 @@ public: return res; } - void log_result(int r) { } + static void log_result(int r) { } private: detail::C m_c{}; @@ -94,7 +94,9 @@ int tmain() A a; B b(a); - return b.wrap_add3(1, 2, 3); + auto tmp = a.add(1, 2); + + return b.wrap_add3(tmp, 2, 3); } } } diff --git a/docs/test_cases/t20001_sequence.svg b/docs/test_cases/t20001_sequence.svg index 470e2953..f28a6cf3 100644 --- a/docs/test_cases/t20001_sequence.svg +++ b/docs/test_cases/t20001_sequence.svg @@ -1,6 +1,6 @@ - + - + @@ -9,56 +9,87 @@ - - - - - - - - - - tmain() - - tmain() - - B - - B - - A - - A - - - - - - - - wrap_add3() - - - add3() - - - - - add() - - - - - log_result() - - - - - log_result() - - - - - Main test function + + + + + + + + + + + + + tmain() + + tmain() + + + + A + + A + + + + B + + B + + + + + + + + + + + + add(int,int) + + + + + + + wrap_add3(int,int,int) + + + + + add3(int,int,int) + + + + + + + add(int,int) + + + + + + + + + + + log_result(int) + + + + + + + log_result(int) + + + + + + Main test function diff --git a/docs/test_cases/t20002_sequence.svg b/docs/test_cases/t20002_sequence.svg index 568a2b80..3e43826b 100644 --- a/docs/test_cases/t20002_sequence.svg +++ b/docs/test_cases/t20002_sequence.svg @@ -1,6 +1,6 @@ - + - + @@ -9,40 +9,53 @@ - - - - - - - - - m1() - - m1() - - m2() - - m2() - - m3() - - m3() - - m4() - - m4() - - - - - - m2() - - - m3() - - - m4() + + + + + + + + + + + m1() + + m1() + + + + m2() + + m2() + + + + m3() + + m3() + + + + m4() + + m4() + + + + + + + + + + + + + + + + + diff --git a/docs/test_cases/t20003.md b/docs/test_cases/t20003.md new file mode 100644 index 00000000..f38f01e6 --- /dev/null +++ b/docs/test_cases/t20003.md @@ -0,0 +1,38 @@ +# t20003 - Function template sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20003_sequence: + type: sequence + glob: + - ../../tests/t20003/t20003.cc + include: + namespaces: + - clanguml::t20003 + using_namespace: + - clanguml::t20003 + start_from: + - function: "clanguml::t20003::m1(T)" + +``` +## Source code +File t20003.cc +```cpp +namespace clanguml { +namespace t20003 { + +template void m4(T p) { } + +template void m3(T p) { m4(p); } + +template void m2(T p) { m3(p); } + +template void m1(T p) { m2(p); } +} +} + +``` +## Generated UML diagrams +![t20003_sequence](./t20003_sequence.svg "Function template sequence diagram test case") diff --git a/docs/test_cases/t20003_sequence.svg b/docs/test_cases/t20003_sequence.svg new file mode 100644 index 00000000..d0dbe2ec --- /dev/null +++ b/docs/test_cases/t20003_sequence.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + m1<T>(T) + + m1<T>(T) + + + + m2<T>(T) + + m2<T>(T) + + + + m3<T>(T) + + m3<T>(T) + + + + m4<T>(T) + + m4<T>(T) + + + + + + + + + + + + + + + + + + + diff --git a/docs/test_cases/t20004.md b/docs/test_cases/t20004.md new file mode 100644 index 00000000..6d9fdd8d --- /dev/null +++ b/docs/test_cases/t20004.md @@ -0,0 +1,74 @@ +# t20004 - Function template instantiation sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20004_sequence: + type: sequence + glob: + - ../../tests/t20004/t20004.cc + include: + namespaces: + - clanguml::t20004 + using_namespace: + - clanguml::t20004 + start_from: + - function: "clanguml::t20004::main()" +``` +## Source code +File t20004.cc +```cpp +#include + +namespace clanguml { +namespace t20004 { + +template T m4(T p); + +template <> [[maybe_unused]] int m4(int p) { return p + 2; } + +template <> [[maybe_unused]] unsigned long m4(unsigned long p) +{ + return 1000 * p; +} + +template T m3(T p) { return m4(p); } + +template T m2(T p) { return m3(p); } + +template <> [[maybe_unused]] std::string m2(std::string p) +{ + return std::string{"m2"} + p; +} + +template T m1(T p) { return m2(p); } + +template <> [[maybe_unused]] float m1(float p) { return 2 * p; } + +template <> [[maybe_unused]] unsigned long m1(unsigned long p) +{ + return m4(p); +} + +template <> [[maybe_unused]] std::string m1(std::string p) +{ + return m2(p); +} + +int main() +{ + m1(2.2); + + m1(100); + + m1("main"); + + return m1(0); +} +} +} + +``` +## Generated UML diagrams +![t20004_sequence](./t20004_sequence.svg "Function template instantiation sequence diagram test case") diff --git a/docs/test_cases/t20004_sequence.svg b/docs/test_cases/t20004_sequence.svg new file mode 100644 index 00000000..c1c43de4 --- /dev/null +++ b/docs/test_cases/t20004_sequence.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + main() + + main() + + + + m1<float>(float) + + m1<float>(float) + + + + m1<unsigned long>(unsigned long) + + m1<unsigned long>(unsigned long) + + + + m4<unsigned long>(unsigned long) + + m4<unsigned long>(unsigned long) + + + + m1<std::string>(std::string) + + m1<std::string>(std::string) + + + + m2<std::string>(std::string) + + m2<std::string>(std::string) + + + + m1<int>(int) + + m1<int>(int) + + + + m2<int>(int) + + m2<int>(int) + + + + m3<int>(int) + + m3<int>(int) + + + + m4<int>(int) + + m4<int>(int) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/test_cases/t20005.md b/docs/test_cases/t20005.md new file mode 100644 index 00000000..d3d4e8c9 --- /dev/null +++ b/docs/test_cases/t20005.md @@ -0,0 +1,45 @@ +# t20005 - Class template basic sequence diagram +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20005_sequence: + type: sequence + glob: + - ../../tests/t20005/t20005.cc + include: + namespaces: + - clanguml::t20005 + using_namespace: + - clanguml::t20005 + start_from: + - function: "clanguml::t20005::C::c(T)" +``` +## Source code +File t20005.cc +```cpp +namespace clanguml { +namespace t20005 { + +template struct A { + T a(T arg) { return arg; } +}; + +template struct B { + T b(T arg) { return a_.a(arg); } + + A a_; +}; + +template struct C { + T c(T arg) { return b_.b(arg); } + + B b_; +}; + +} +} +``` +## Generated UML diagrams +![t20005_sequence](./t20005_sequence.svg "Class template basic sequence diagram") diff --git a/docs/test_cases/t20005_sequence.svg b/docs/test_cases/t20005_sequence.svg new file mode 100644 index 00000000..bc341a89 --- /dev/null +++ b/docs/test_cases/t20005_sequence.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + C<T> + + C<T> + + + + B<T> + + B<T> + + + + A<T> + + A<T> + + + + + + + c(T) + + + + b(T) + + + + + a(T) + + + + + + + + + diff --git a/docs/test_cases/t20006.md b/docs/test_cases/t20006.md new file mode 100644 index 00000000..3c16a989 --- /dev/null +++ b/docs/test_cases/t20006.md @@ -0,0 +1,103 @@ +# t20006 - Class template specialization basic sequence diagram +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20006_sequence: + type: sequence + glob: + - ../../tests/t20006/t20006.cc + include: + namespaces: + - clanguml::t20006 + using_namespace: + - clanguml::t20006 + start_from: + - function: "clanguml::t20006::tmain()" +``` +## Source code +File t20006.cc +```cpp +#include + +namespace clanguml { +namespace t20006 { + +template struct A { + T a1(T arg) { return arg; } + T a2(T arg) { return arg + arg; } +}; + +template struct B { + T b(T arg) { return a_.a1(arg); } + A a_; +}; + +template <> struct B { + std::string b(std::string arg) { return a_.a2(arg); } + A a_; +}; + +template struct AA { + void aa1(T t) { } + void aa2(T t) { } +}; + +template struct BB { + void bb1(T t, F f) { aa_.aa1(t); } + void bb2(T t, F f) { aa_.aa2(t); } + + AA aa_; +}; + +template struct BB { + void bb1(T t, std::string f) { aa_->aa2(t); } + void bb2(T t, std::string f) { aa_->aa1(t); } + + BB(AA *aa) + : aa_{aa} + { + } + + AA *aa_; +}; + +template struct BB { + void bb1(T t, float f) { bb2(t, f); } + void bb2(T t, float f) { aa_.aa2(t); } + + BB(AA &aa) + : aa_{aa} + { + } + + AA &aa_; +}; + +void tmain() +{ + B bint; + B bstring; + + bint.b(1); + bstring.b("bstring"); + + BB bbint; + AA aaint; + BB bbstring{&aaint}; + BB bbfloat{aaint}; + + bbint.bb1(1, 1); + bbint.bb2(2, 2); + + bbstring.bb1(1, "calling aa2"); + bbstring.bb2(1, "calling aa1"); + + bbfloat.bb1(1, 1.0f); +} +} +} +``` +## Generated UML diagrams +![t20006_sequence](./t20006_sequence.svg "Class template specialization basic sequence diagram") diff --git a/docs/test_cases/t20006_sequence.svg b/docs/test_cases/t20006_sequence.svg new file mode 100644 index 00000000..b6a69bb2 --- /dev/null +++ b/docs/test_cases/t20006_sequence.svg @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + B<int> + + B<int> + + + + A<int> + + A<int> + + + + B<std::string> + + B<std::string> + + + + A<std::string> + + A<std::string> + + + + BB<int,int> + + BB<int,int> + + + + AA<int> + + AA<int> + + + + BB<int,std::string> + + BB<int,std::string> + + + + BB<int,float> + + BB<int,float> + + + + + + + + + + + + + + + + + + + + + b(int) + + + + + a1(int) + + + + + + + + + b(std::string) + + + + + a2(std::string) + + + + + + + + + bb1(int,int) + + + + + aa1(int) + + + + + bb2(int,int) + + + + + aa2(int) + + + + + bb1(int,std::string) + + + + + aa2(int) + + + + + bb2(int,std::string) + + + + + aa1(int) + + + + + bb1(int,float) + + + + + + + bb2(int,float) + + + + + aa2(int) + + + diff --git a/docs/test_cases/t20007.md b/docs/test_cases/t20007.md new file mode 100644 index 00000000..37f55b34 --- /dev/null +++ b/docs/test_cases/t20007.md @@ -0,0 +1,49 @@ +# t20007 - Class template variadic argument list sequence diagram +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20007_sequence: + type: sequence + glob: + - ../../tests/t20007/t20007.cc + include: + namespaces: + - clanguml::t20007 + using_namespace: + - clanguml::t20007 + start_from: + - function: "clanguml::t20007::tmain()" +``` +## Source code +File t20007.cc +```cpp +#include +#include + +namespace clanguml { +namespace t20007 { + +template struct Adder { + First add(First &&arg, Args &&...args) { return (arg + ... + args); } +}; + +void tmain() +{ + using namespace std::string_literals; + + Adder adder1; + Adder adder2; + Adder adder3; + + [[maybe_unused]] auto res1 = adder1.add(2, 2); + [[maybe_unused]] auto res2 = adder2.add(1, 2.0, 3.0); + [[maybe_unused]] auto res3 = adder3.add("one"s, "two"s, "three"s); +} + +} +} +``` +## Generated UML diagrams +![t20007_sequence](./t20007_sequence.svg "Class template variadic argument list sequence diagram") diff --git a/docs/test_cases/t20007_sequence.svg b/docs/test_cases/t20007_sequence.svg new file mode 100644 index 00000000..30adc106 --- /dev/null +++ b/docs/test_cases/t20007_sequence.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + Adder<int,int> + + Adder<int,int> + + + + Adder<int,float,double> + + Adder<int,float,double> + + + + Adder<std::string,std::string,std::string> + + Adder<std::string,std::string,std::string> + + + + + + + + + add(int &&,int &&) + + + + + + + add(int &&,float &&,double &&) + + + + + + + add(std::string &&,std::string &&,std::string &&) + + + + + diff --git a/docs/test_cases/t20008.md b/docs/test_cases/t20008.md new file mode 100644 index 00000000..e2ca37a0 --- /dev/null +++ b/docs/test_cases/t20008.md @@ -0,0 +1,67 @@ +# t20008 - Constexpr if sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20008_sequence: + type: sequence + glob: + - ../../tests/t20008/t20008.cc + include: + namespaces: + - clanguml::t20008 + using_namespace: + - clanguml::t20008 + start_from: + - function: "clanguml::t20008::tmain()" +``` +## Source code +File t20008.cc +```cpp +#include +#include + +namespace clanguml { +namespace t20008 { + +template struct A { + void a1(T arg) { } + void a2(T arg) { } + void a3(T arg) { } +}; + +template struct B { + A a; + + void b(T arg) + { + if constexpr (std::is_integral_v) { + a.a1(arg); + } + else if constexpr (std::is_pointer_v) { + a.a2(arg); + } + else { + a.a3(arg); + } + } +}; + +void tmain() +{ + using namespace std::string_literals; + + B bint; + B bcharp; + B bstring; + + bint.b(1); + bcharp.b("1"); + bstring.b("1"s); +} +} +} +``` +## Generated UML diagrams +![t20008_sequence](./t20008_sequence.svg "Constexpr if sequence diagram test case") diff --git a/docs/test_cases/t20008_sequence.svg b/docs/test_cases/t20008_sequence.svg new file mode 100644 index 00000000..107697aa --- /dev/null +++ b/docs/test_cases/t20008_sequence.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + B<int> + + B<int> + + + + A<int> + + A<int> + + + + B<const char *> + + B<const char *> + + + + A<const char *> + + A<const char *> + + + + B<std::string> + + B<std::string> + + + + A<std::string> + + A<std::string> + + + + + + + + + + + + b(int) + + + + + a1(int) + + + + + b(const char *) + + + + + a2(const char *) + + + + + b(std::string) + + + + + a3(std::string) + + + diff --git a/docs/test_cases/t20009.md b/docs/test_cases/t20009.md new file mode 100644 index 00000000..e73dcf56 --- /dev/null +++ b/docs/test_cases/t20009.md @@ -0,0 +1,53 @@ +# t20009 - Smart pointer dereference call expression test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20009_sequence: + type: sequence + glob: + - ../../tests/t20009/t20009.cc + include: + namespaces: + - clanguml::t20009 + using_namespace: + - clanguml::t20009 + start_from: + - function: "clanguml::t20009::tmain()" +``` +## Source code +File t20009.cc +```cpp +#include +#include + +namespace clanguml { +namespace t20009 { +template struct A { + void a(T arg) { } +}; + +template struct B { + void b(T arg) { a->a(arg); } + + std::unique_ptr> a; +}; + +using BFloatPtr = std::shared_ptr>; + +void tmain() +{ + std::shared_ptr> bstring; + auto bint = std::make_unique>(); + BFloatPtr bfloat; + + bstring->b("b"); + bint.get()->b(42); + bfloat->b(1.0); +} +} +} +``` +## Generated UML diagrams +![t20009_sequence](./t20009_sequence.svg "Smart pointer dereference call expression test case") diff --git a/docs/test_cases/t20009_sequence.svg b/docs/test_cases/t20009_sequence.svg new file mode 100644 index 00000000..f2ec6655 --- /dev/null +++ b/docs/test_cases/t20009_sequence.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + B<std::string> + + B<std::string> + + + + A<std::string> + + A<std::string> + + + + B<int> + + B<int> + + + + A<int> + + A<int> + + + + B<float> + + B<float> + + + + A<float> + + A<float> + + + + + + + + + + + + b(std::string) + + + + + a(std::string) + + + + + b(int) + + + + + a(int) + + + + + b(float) + + + + + a(float) + + + diff --git a/docs/test_cases/t20010.md b/docs/test_cases/t20010.md new file mode 100644 index 00000000..0a067b96 --- /dev/null +++ b/docs/test_cases/t20010.md @@ -0,0 +1,63 @@ +# t20010 - Container sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20010_sequence: + type: sequence + glob: + - ../../tests/t20010/t20010.cc + include: + namespaces: + - clanguml::t20010 + using_namespace: + - clanguml::t20010 + start_from: + - function: "clanguml::t20010::tmain()" +``` +## Source code +File t20010.cc +```cpp +#include +#include +#include +#include + +namespace clanguml { +namespace t20010 { + +struct A { + void a1() { } + void a2() { } + void a3() { } + void a4() { } +}; + +template struct B { + void b1() { a_.a1(); } + void b2() { avector_.front().a2(); } + void b3() { aptrvector_.front()->a3(); } + void b4() { amap_.at(0).a4(); } + + A a_; + std::vector avector_; + std::vector> aptrvector_; + std::map amap_; +}; + +void tmain() +{ + B b; + + b.b1(); + b.b2(); + b.b3(); + b.b4(); +} + +} +} +``` +## Generated UML diagrams +![t20010_sequence](./t20010_sequence.svg "Container sequence diagram test case") diff --git a/docs/test_cases/t20010_sequence.svg b/docs/test_cases/t20010_sequence.svg new file mode 100644 index 00000000..9f17be94 --- /dev/null +++ b/docs/test_cases/t20010_sequence.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + B<int> + + B<int> + + + + A + + A + + + + + + + + + + + + + + b1() + + + + + a1() + + + + + b2() + + + + + a2() + + + + + b3() + + + + + a3() + + + + + b4() + + + + + a4() + + + diff --git a/docs/test_cases/t20011.md b/docs/test_cases/t20011.md new file mode 100644 index 00000000..3c9f43ef --- /dev/null +++ b/docs/test_cases/t20011.md @@ -0,0 +1,55 @@ +# t20011 - Recursive calls sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20011_sequence: + type: sequence + glob: + - ../../tests/t20011/t20011.cc + include: + namespaces: + - clanguml::t20011 + using_namespace: + - clanguml::t20011 + start_from: + - function: "clanguml::t20011::tmain()" +``` +## Source code +File t20011.cc +```cpp +namespace clanguml { +namespace t20011 { + +struct A { + void a(int i = 10) + { + if (i > 0) + a(i - 1); + } + + void b(int i = 10) { c(i); } + void c(int i) { d(i); } + void d(int i) + { + if (i > 0) + b(i - 1); + else + a(); + } +}; + +void tmain() +{ + A a; + + a.a(); + + a.b(); +} +} +} +``` +## Generated UML diagrams +![t20011_sequence](./t20011_sequence.svg "Recursive calls sequence diagram test case") diff --git a/docs/test_cases/t20011_sequence.svg b/docs/test_cases/t20011_sequence.svg new file mode 100644 index 00000000..0358d213 --- /dev/null +++ b/docs/test_cases/t20011_sequence.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + A + + A + + + + + + + + + + + + + + a(int) + + + + alt + + + + + + a(int) + + + + + b(int) + + + + + + + c(int) + + + + + + + d(int) + + + + alt + + + + + + b(int) + + + + + + + a(int) + + + + alt + + + + + + a(int) + + + diff --git a/docs/test_cases/t20012.md b/docs/test_cases/t20012.md new file mode 100644 index 00000000..a874e7a7 --- /dev/null +++ b/docs/test_cases/t20012.md @@ -0,0 +1,128 @@ +# t20012 - Lambda expression call sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20012_sequence: + type: sequence + glob: + - ../../tests/t20012/t20012.cc + include: + namespaces: + - clanguml::t20012 + using_namespace: + - clanguml::t20012 + start_from: + - function: "clanguml::t20012::tmain()" +``` +## Source code +File t20012.cc +```cpp +#include +#include +#include +#include +#include + +namespace clanguml { +namespace t20012 { +struct A { + void a() { aa(); } + + void aa() { aaa(); } + + void aaa() { } +}; + +struct B { + void b() { bb(); } + + void bb() { bbb(); } + + void bbb() { } + + void eb() { } +}; + +struct C { + void c() { cc(); } + + void cc() { ccc(); } + + void ccc() { } +}; + +struct D { + int add5(int arg) const { return arg + 5; } +}; + +class E { + std::optional> maybe_b; + std::shared_ptr a; + +public: + template void setup(F &&f) { f(maybe_b); } +}; + +template struct R { + R(F &&f) + : f_{std::move(f)} + { + } + + void r() { f_(); } + + F f_; +}; + +void tmain() +{ + A a; + B b; + C c; + + // The activity shouldn't be marked at the lambda definition, but + // wherever it is actually called... + auto alambda = [&a, &b]() { + a.a(); + b.b(); + }; + + // ...like here + alambda(); + + // There should be no call to B in the sequence diagram as the blambda + // is never called + [[maybe_unused]] auto blambda = [&b]() { b.b(); }; + + // Nested lambdas should also work + auto clambda = [alambda, &c]() { + c.c(); + alambda(); + }; + clambda(); + + R r{[&c]() { c.c(); }}; + + r.r(); + + D d; + + std::vector ints{0, 1, 2, 3, 4}; + std::transform(ints.begin(), ints.end(), ints.begin(), + [&d](auto i) { return d.add5(i); }); + + // TODO: Fix naming function call arguments which are lambdas + // E e; + // + // e.setup([](auto &&arg) mutable { + // // We cannot know here what 'arg' might be + // arg.value()->eb(); + // }); +} +} +} +``` +## Generated UML diagrams +![t20012_sequence](./t20012_sequence.svg "Lambda expression call sequence diagram test case") diff --git a/docs/test_cases/t20012_sequence.svg b/docs/test_cases/t20012_sequence.svg new file mode 100644 index 00000000..07d7aed2 --- /dev/null +++ b/docs/test_cases/t20012_sequence.svg @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + tmain()::(lambda t20012.cc:66:20) + + tmain()::(lambda t20012.cc:66:20) + + + + A + + A + + + + B + + B + + + + tmain()::(lambda t20012.cc:79:20) + + tmain()::(lambda t20012.cc:79:20) + + + + C + + C + + + + R<R::(lambda t20012.cc:85:9)> + + R<R::(lambda t20012.cc:85:9)> + + + + tmain()::(lambda t20012.cc:85:9) + + tmain()::(lambda t20012.cc:85:9) + + + + D + + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + operator()() + + + + + a() + + + + + + + aa() + + + + + + + aaa() + + + + + b() + + + + + + + bb() + + + + + + + bbb() + + + + + + + operator()() + + + + + c() + + + + + + + cc() + + + + + + + ccc() + + + + + operator()() + + + + + a() + + + + + + + aa() + + + + + + + aaa() + + + + + b() + + + + + + + bb() + + + + + + + bbb() + + + + + + + + + r() + + + + + operator()() + + + + + c() + + + + + + + cc() + + + + + + + ccc() + + + + + + + add5(int) + + + + + diff --git a/docs/test_cases/t20013.md b/docs/test_cases/t20013.md new file mode 100644 index 00000000..3b282d26 --- /dev/null +++ b/docs/test_cases/t20013.md @@ -0,0 +1,51 @@ +# t20013 - Function and method arguments in sequence diagrams test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20013_sequence: + type: sequence + glob: + - ../../tests/t20013/t20013.cc + include: + namespaces: + - clanguml::t20013 + using_namespace: + - clanguml::t20013 + start_from: + - function: "clanguml::t20013::tmain(int,char **)" +``` +## Source code +File t20013.cc +```cpp +namespace clanguml { +namespace t20013 { + +struct A { + int a1(int i) { return i; } + double a2(double d) { return d; } + const char *a3(const char *s) { return s; } +}; + +struct B { + int b(int i) { return a.a1(i); } + double b(double d) { return a.a2(d); } + const char *b(const char *s) { return a.a3(s); } + + A a; +}; + +void tmain(int argc, char **argv) +{ + B b; + + b.b(1); + b.b(2.0); + b.b("three"); +} +} +} +``` +## Generated UML diagrams +![t20013_sequence](./t20013_sequence.svg "Function and method arguments in sequence diagrams test case") diff --git a/docs/test_cases/t20013_sequence.svg b/docs/test_cases/t20013_sequence.svg new file mode 100644 index 00000000..354c6cdb --- /dev/null +++ b/docs/test_cases/t20013_sequence.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + tmain(int,char **) + + tmain(int,char **) + + + + B + + B + + + + A + + A + + + + + + + + + + + + b(int) + + + + + a1(int) + + + + + + + + + b(double) + + + + + a2(double) + + + + + + + + + b(const char *) + + + + + a3(const char *) + + + + + + + diff --git a/docs/test_cases/t20014.md b/docs/test_cases/t20014.md new file mode 100644 index 00000000..9582938b --- /dev/null +++ b/docs/test_cases/t20014.md @@ -0,0 +1,86 @@ +# t20014 - Multiple translation units sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20014_sequence: + type: sequence + glob: + - ../../tests/t20014/t20014.cc + - ../../tests/t20014/t20014_c.cc + - ../../tests/t20014/t20014_b.cc + - ../../tests/t20014/t20014_a.cc + include: + namespaces: + - clanguml::t20014 + using_namespace: + - clanguml::t20014 + start_from: + - function: "clanguml::t20014::tmain()" +``` +## Source code +File t20014_b.cc +```cpp +#include "include/t20014_b.h" +namespace clanguml { +namespace t20014 { + +int B::b1(int i, int j) { return a_.a1(i, j); } + +int B::b2(int i, int j) { return a_.a2(i, j); } + +} +} +``` +File t20014_c.cc +```cpp +#include "include/t20014_c.h" + +namespace clanguml { +namespace t20014 { + +} +} +``` +File t20014.cc +```cpp +#include "include/t20014.h" +#include "include/t20014_b.h" +#include "include/t20014_c.h" + +namespace clanguml { +namespace t20014 { + +void log(const char *msg) { } + +int tmain() +{ + B b; + C c; + + b.b1(0, 1); + b.b2(1, 2); + + c.c1(2, 3); + + return 0; +} +} +} +``` +File t20014_a.cc +```cpp +#include "include/t20014_a.h" +namespace clanguml { +namespace t20014 { + +int A::a1(int i, int j) { return i + j; } + +int A::a2(int i, int j) { return i - j; } + +} +} +``` +## Generated UML diagrams +![t20014_sequence](./t20014_sequence.svg "Multiple translation units sequence diagram test case") diff --git a/docs/test_cases/t20014_sequence.svg b/docs/test_cases/t20014_sequence.svg new file mode 100644 index 00000000..98e6b209 --- /dev/null +++ b/docs/test_cases/t20014_sequence.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + B + + B + + + + A + + A + + + + C<B,int> + + C<B,int> + + + + + + + + + + + + + b1(int,int) + + + + + a1(int,int) + + + + + + + + + b2(int,int) + + + + + a2(int,int) + + + + + + + + + c1(int,int) + + + + + b1(int,int) + + + + + a1(int,int) + + + + + + + + + diff --git a/docs/test_cases/t20015.md b/docs/test_cases/t20015.md new file mode 100644 index 00000000..b8f6ebaa --- /dev/null +++ b/docs/test_cases/t20015.md @@ -0,0 +1,67 @@ +# t20015 - Class exclusion by namespace in sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20015_sequence: + type: sequence + glob: + - ../../tests/t20015/t20015.cc + include: + namespaces: + - clanguml::t20015 + exclude: + namespaces: + - clanguml::t20015::detail + using_namespace: + - clanguml::t20015 + start_from: + - function: "clanguml::t20015::tmain()" +``` +## Source code +File t20015.cc +```cpp +#include +#include + +namespace clanguml { +namespace t20015 { + +namespace detail { +class A { +public: + void set_x(int x) { x_ = x; } + void set_y(int y) { y_ = y; } + void set_z(int z) { z_ = z; } + +private: + int x_; + int y_; + int z_; +}; +} + +class B { +public: + void setup_a(std::shared_ptr &a) + { + a->set_x(1); + a.get()->set_y(2); + (*a).set_z(3); + } +}; + +void tmain() +{ + auto a = std::make_shared(); + + B b; + + b.setup_a(a); +} +} +} +``` +## Generated UML diagrams +![t20015_sequence](./t20015_sequence.svg "Class exclusion by namespace in sequence diagram test case") diff --git a/docs/test_cases/t20015_sequence.svg b/docs/test_cases/t20015_sequence.svg new file mode 100644 index 00000000..0c16fb7b --- /dev/null +++ b/docs/test_cases/t20015_sequence.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + B + + B + + + + + + + setup_a(std::shared_ptr<detail::A> &) + + + diff --git a/docs/test_cases/t20016.md b/docs/test_cases/t20016.md new file mode 100644 index 00000000..a9dcb528 --- /dev/null +++ b/docs/test_cases/t20016.md @@ -0,0 +1,49 @@ +# t20016 - Template method specialization sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20016_sequence: + type: sequence + glob: + - ../../tests/t20016/t20016.cc + include: + namespaces: + - clanguml::t20016 + using_namespace: + - clanguml::t20016 + start_from: + - function: "clanguml::t20016::tmain()" +``` +## Source code +File t20016.cc +```cpp +namespace clanguml { +namespace t20016 { +struct A { + void a1(int a) { } + template T a2(const T &a) { return a; } +}; + +template struct B { + void b1(T b) { a_.a1(1); } + + template F b2(T t) { return a_.a2(t); } + + A a_; +}; + +void tmain() +{ + B b; + + b.b1(1); + + b.b2(2); +} +} +} +``` +## Generated UML diagrams +![t20016_sequence](./t20016_sequence.svg "Template method specialization sequence diagram test case") diff --git a/docs/test_cases/t20016_sequence.svg b/docs/test_cases/t20016_sequence.svg new file mode 100644 index 00000000..eca94d50 --- /dev/null +++ b/docs/test_cases/t20016_sequence.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + B<long> + + B<long> + + + + A + + A + + + + + + + + + + b1(long) + + + + + a1(int) + + + + + b2(long) + + + + + a2(const long &) + + + + + + + diff --git a/docs/test_cases/t20017.md b/docs/test_cases/t20017.md new file mode 100644 index 00000000..f4e0fb32 --- /dev/null +++ b/docs/test_cases/t20017.md @@ -0,0 +1,44 @@ +# t20017 - Test case for combine_free_functions_into_file_participants option +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20017_sequence: + type: sequence + combine_free_functions_into_file_participants: true + relative_to: ../../tests/t20017 + glob: + - ../../tests/t20017/t20017.cc + include: + namespaces: + - clanguml::t20017 + using_namespace: + - clanguml::t20017 + start_from: + - function: "clanguml::t20017::tmain()" +``` +## Source code +File t20017.cc +```cpp +#include "include/t20017_a.h" +#include "include/t20017_b.h" + +namespace clanguml { +namespace t20017 { +int tmain() { return b2(a1(a2(a3(1, 2), b1(3, 4)), 5), 6); } +} +} +``` +File t20017_b.cc +```cpp +#include "include/t20017_b.h" + +namespace clanguml { +namespace t20017 { +int b1(int x, int y) { return x - y; } +} +} +``` +## Generated UML diagrams +![t20017_sequence](./t20017_sequence.svg "Test case for combine_free_functions_into_file_participants option") diff --git a/docs/test_cases/t20017_sequence.svg b/docs/test_cases/t20017_sequence.svg new file mode 100644 index 00000000..589d6843 --- /dev/null +++ b/docs/test_cases/t20017_sequence.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + t20017.cc + + t20017.cc + + include/t20017_b.h + + include/t20017_b.h + + include/t20017_a.h + + include/t20017_a.h + + + + + + + + + tmain() + + + + b2<int>(int,int) + + + + + + + a1(int,int) + + + + + + + a2(int,int) + + + + + + + a3(int,int) + + + + + + + b1(int,int) + + + + + + + diff --git a/docs/test_cases/t20018.md b/docs/test_cases/t20018.md new file mode 100644 index 00000000..4caa67e9 --- /dev/null +++ b/docs/test_cases/t20018.md @@ -0,0 +1,51 @@ +# t20018 - Recursive template sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20018_sequence: + type: sequence + glob: + - ../../tests/t20018/t20018.cc + include: + namespaces: + - clanguml::t20018 + using_namespace: + - clanguml::t20018 + start_from: + - function: "clanguml::t20018::tmain()" +``` +## Source code +File t20018.cc +```cpp +#include + +namespace clanguml { +namespace t20018 { + +template struct Factorial { + static const int value = N * Factorial::value; + + static void print(int answer) { Factorial::print(answer); } +}; + +template <> struct Factorial<0> { + static const int value = 1; + + static void print(int answer) + { + std::cout << "The answer is " << answer << "\n"; + } +}; + +template struct Answer { + static void print() { T::print(N); } +}; + +void tmain() { Answer>::print(); } +} +} +``` +## Generated UML diagrams +![t20018_sequence](./t20018_sequence.svg "Recursive template sequence diagram test case") diff --git a/docs/test_cases/t20018_sequence.svg b/docs/test_cases/t20018_sequence.svg new file mode 100644 index 00000000..7b159bbd --- /dev/null +++ b/docs/test_cases/t20018_sequence.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + Answer<Factorial<5>,120> + + Answer<Factorial<5>,120> + + + + Factorial<5> + + Factorial<5> + + + + Factorial<4> + + Factorial<4> + + + + Factorial<3> + + Factorial<3> + + + + Factorial<2> + + Factorial<2> + + + + Factorial<1> + + Factorial<1> + + + + Factorial<0> + + Factorial<0> + + + + + + + + + + + + + print() + + + + + print(int) + + + + + print(int) + + + + + print(int) + + + + + print(int) + + + + + print(int) + + + + + print(int) + + + diff --git a/docs/test_cases/t20019.md b/docs/test_cases/t20019.md new file mode 100644 index 00000000..8188a2b0 --- /dev/null +++ b/docs/test_cases/t20019.md @@ -0,0 +1,58 @@ +# t20019 - Curiously Recurring Template Pattern sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20019_sequence: + type: sequence + glob: + - ../../tests/t20019/t20019.cc + include: + namespaces: + - clanguml::t20019 + using_namespace: + - clanguml::t20019 + start_from: + - function: "clanguml::t20019::tmain()" +``` +## Source code +File t20019.cc +```cpp +#include + +namespace clanguml { +namespace t20019 { + +// From https://en.cppreference.com/w/cpp/language/crtp + +template struct Base { + void name() { (static_cast(this))->impl(); } +}; + +struct D1 : public Base { + void impl() { std::puts("D1::impl()"); } +}; + +struct D2 : public Base { + void impl() { std::puts("D2::impl()"); } +}; + +void tmain() +{ + Base b1; + b1.name(); + Base b2; + b2.name(); + + D1 d1; + d1.name(); + D2 d2; + d2.name(); +} + +} +} +``` +## Generated UML diagrams +![t20019_sequence](./t20019_sequence.svg "Curiously Recurring Template Pattern sequence diagram test case") diff --git a/docs/test_cases/t20019_sequence.svg b/docs/test_cases/t20019_sequence.svg new file mode 100644 index 00000000..854edb38 --- /dev/null +++ b/docs/test_cases/t20019_sequence.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + Base<D1> + + Base<D1> + + + + D1 + + D1 + + + + Base<D2> + + Base<D2> + + + + D2 + + D2 + + + + + + + + + + + + + + name() + + + + + impl() + + + + + name() + + + + + impl() + + + + + name() + + + + + impl() + + + + + name() + + + + + impl() + + + diff --git a/docs/test_cases/t20020.md b/docs/test_cases/t20020.md new file mode 100644 index 00000000..6ba3e37e --- /dev/null +++ b/docs/test_cases/t20020.md @@ -0,0 +1,105 @@ +# t20020 - If statement sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20020_sequence: + type: sequence + glob: + - ../../tests/t20020/t20020.cc + include: + namespaces: + - clanguml::t20020 + using_namespace: + - clanguml::t20020 + start_from: + - function: "clanguml::t20020::tmain()" +``` +## Source code +File t20020.cc +```cpp +#include +#include + +namespace clanguml { +namespace t20020 { +struct A { + int a1() { return 0; } + int a2() { return 1; } + int a3() { return 2; } +}; + +struct B { + void log() { } + + int b1() { return 3; } + int b2() { return 4; } +}; + +struct C { + void log() const { } + + void c1() const + { + if (c2()) + log(); + } + + bool c2() const { return true; } +}; + +template struct D { + + T d1(T x, T y) { return x + y; } +}; + +int tmain() +{ + A a; + B b; + C c; + D d; + + int result{0}; + + if (reinterpret_cast(&a) % 100 == 0ULL) { + result = a.a1(); + } + else if (reinterpret_cast(&a) % 64 == 0ULL) { + if (a.a2() > 2) + result = b.b1(); + else + result = b.b2(); + } + else { + result = a.a3(); + } + + b.log(); + + if (true) + c.c1(); + + if (true) + d.d1(1, 1); + + // This if/else should not be included in the diagram at all + // as the calls to std will be excluded by the diagram filters + if (result != 2) { + result = std::exp(result); + } + else if (result == 3) { + result = 4; + } + else { + result = std::exp(result + 1); + } + + return result; +} +} +} +``` +## Generated UML diagrams +![t20020_sequence](./t20020_sequence.svg "If statement sequence diagram test case") diff --git a/docs/test_cases/t20020_sequence.svg b/docs/test_cases/t20020_sequence.svg new file mode 100644 index 00000000..48225848 --- /dev/null +++ b/docs/test_cases/t20020_sequence.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + A + + A + + + + B + + B + + + + C + + C + + + + D<int> + + D<int> + + + + + + + + + + + + + + + alt + + + + a1() + + + + + + + alt + + + + a2() + + + + + + + b1() + + + + + + + b2() + + + + + + + + a3() + + + + + + + log() + + + + alt + + + + c1() + + + + alt + + + + + + c2() + + + + + + + + + + + log() + + + + alt + + + + d1(int,int) + + + + + diff --git a/docs/test_cases/t20021.md b/docs/test_cases/t20021.md new file mode 100644 index 00000000..448d852b --- /dev/null +++ b/docs/test_cases/t20021.md @@ -0,0 +1,64 @@ +# t20021 - Loop statements sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20021_sequence: + type: sequence + glob: + - ../../tests/t20021/t20021.cc + include: + namespaces: + - clanguml::t20021 + using_namespace: + - clanguml::t20021 + start_from: + - function: "clanguml::t20021::tmain()" +``` +## Source code +File t20021.cc +```cpp +#include + +namespace clanguml { +namespace t20021 { +struct A { + int a1() { return 0; } + int a2() { return 1; } + int a3() { return 2; } +}; + +struct B { + void log() { } + + int b1() const { return 3; } + int b2() const { return 4; } +}; + +int tmain() +{ + A a; + std::vector b; + + int i = 10; + while (i--) { + int j = a.a3(); + do { + for (int l = a.a2(); l > 0; l--) + a.a1(); + } while (j--); + } + + int result = 0; + for (const auto &bi : b) { + result += bi.b2(); + } + + return b.front().b2() + result; +} +} +} +``` +## Generated UML diagrams +![t20021_sequence](./t20021_sequence.svg "Loop statements sequence diagram test case") diff --git a/docs/test_cases/t20021_sequence.svg b/docs/test_cases/t20021_sequence.svg new file mode 100644 index 00000000..f3d766f2 --- /dev/null +++ b/docs/test_cases/t20021_sequence.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + A + + A + + + + B + + B + + + + + + + + + + loop + + + + a3() + + + + + + loop + + + loop + + + + a2() + + + + + + + a1() + + + + + + loop + + + + b2() + + + + + + + b2() + + + + + diff --git a/docs/test_cases/t20022.md b/docs/test_cases/t20022.md new file mode 100644 index 00000000..526f2c12 --- /dev/null +++ b/docs/test_cases/t20022.md @@ -0,0 +1,61 @@ +# t20022 - Forward class declaration sequence diagram test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20022_sequence: + type: sequence + glob: + - ../../tests/t20022/t20022.cc + include: + namespaces: + - clanguml::t20022 + using_namespace: + - clanguml::t20022 + start_from: + - function: "clanguml::t20022::tmain()" +``` +## Source code +File t20022.cc +```cpp +#include + +namespace clanguml { +namespace t20022 { +class B; + +class A { +public: + A(std::unique_ptr b); + + void a(); + + std::unique_ptr b_; +}; + +class B { +public: + void b() { } +}; + +A::A(std::unique_ptr b) + : b_{std::move(b)} +{ +} + +void A::a() { b_->b(); } + +int tmain() +{ + A a{std::make_unique()}; + + a.a(); + + return 0; +} +} +} +``` +## Generated UML diagrams +![t20022_sequence](./t20022_sequence.svg "Forward class declaration sequence diagram test case") diff --git a/docs/test_cases/t20022_sequence.svg b/docs/test_cases/t20022_sequence.svg new file mode 100644 index 00000000..a2f1c265 --- /dev/null +++ b/docs/test_cases/t20022_sequence.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + tmain() + + tmain() + + + + A + + A + + + + B + + B + + + + + + + + a() + + + + + b() + + + diff --git a/docs/test_cases/t30001_package.svg b/docs/test_cases/t30001_package.svg index 728dfe54..bec22d11 100644 --- a/docs/test_cases/t30001_package.svg +++ b/docs/test_cases/t30001_package.svg @@ -1,6 +1,6 @@ - + @@ -9,63 +9,63 @@ - - + + A - - + + AA - - + + B - - + + AA - - + + AAA - - + + BBB - - + + BB - - + + AAA - - + + BBB - - + + BB - + A AAA note... - + This is namespace AA in namespace A - + This is namespace AA in namespace B diff --git a/docs/test_cases/t30002_package.svg b/docs/test_cases/t30002_package.svg index 10ae6b88..0cbefe24 100644 --- a/docs/test_cases/t30002_package.svg +++ b/docs/test_cases/t30002_package.svg @@ -1,6 +1,6 @@ - + @@ -9,113 +9,113 @@ - - + + A - - + + AA - - + + B - - + + BB - - + + A1 - - + + A2 - - + + A3 - - + + A4 - - + + A5 - - + + A6 - - + + A7 - - + + A8 - - + + A9 - - + + A10 - - + + A11 - - + + A12 - - + + A13 - - + + A14 - - + + A15 - - + + A16 - - + + A17 - - + + BBB diff --git a/docs/test_cases/t30003_package.svg b/docs/test_cases/t30003_package.svg index 09cb7b37..0da4fff2 100644 --- a/docs/test_cases/t30003_package.svg +++ b/docs/test_cases/t30003_package.svg @@ -1,6 +1,6 @@ - + @@ -9,35 +9,35 @@ - - + + ns1 - - + + ns3 «deprecated» - - + + ns1 - - + + ns2_v1_0_0 - - + + ns2_v0_9_0 «deprecated» - - + + ns2 diff --git a/docs/test_cases/t30004_package.svg b/docs/test_cases/t30004_package.svg index e55119ff..74aee223 100644 --- a/docs/test_cases/t30004_package.svg +++ b/docs/test_cases/t30004_package.svg @@ -1,6 +1,6 @@ - + @@ -9,40 +9,40 @@ - - + + A - + Package AAA. - + Package BBB. - + CCCC package note. - + We skipped DDD. - - + + AAA - - + + BBB - - + + CCC - - + + EEE diff --git a/docs/test_cases/t30005_package.svg b/docs/test_cases/t30005_package.svg index 7c8ac344..a0b57541 100644 --- a/docs/test_cases/t30005_package.svg +++ b/docs/test_cases/t30005_package.svg @@ -1,6 +1,6 @@ - + @@ -9,48 +9,48 @@ - - + + A - - + + AA - - + + B - - + + BB - - + + C - - + + CC - - + + AAA - - + + BBB - - + + CCC diff --git a/docs/test_cases/t30006_package.svg b/docs/test_cases/t30006_package.svg index dc852ff0..a8dfe383 100644 --- a/docs/test_cases/t30006_package.svg +++ b/docs/test_cases/t30006_package.svg @@ -1,6 +1,6 @@ - + @@ -9,22 +9,22 @@ - - + + B - - + + A - - + + C - + Top A note. diff --git a/docs/test_cases/t30007_package.svg b/docs/test_cases/t30007_package.svg index fe2d9213..b7f81ee3 100644 --- a/docs/test_cases/t30007_package.svg +++ b/docs/test_cases/t30007_package.svg @@ -1,6 +1,6 @@ - + @@ -9,27 +9,27 @@ - - + + A - - + + B - - + + AA - - + + C - + Compare layout with t30006. diff --git a/docs/test_cases/t30008_package.svg b/docs/test_cases/t30008_package.svg index f6a010dc..d31552cb 100644 --- a/docs/test_cases/t30008_package.svg +++ b/docs/test_cases/t30008_package.svg @@ -1,6 +1,6 @@ - + @@ -9,43 +9,43 @@ - - + + dependants - - + + dependencies - - + + A - - + + B - - + + C - - + + D - - + + E - - + + F diff --git a/docs/test_cases/t40001_include.svg b/docs/test_cases/t40001_include.svg index 4669f393..9e5f1dff 100644 --- a/docs/test_cases/t40001_include.svg +++ b/docs/test_cases/t40001_include.svg @@ -1,6 +1,6 @@ - + @@ -9,43 +9,43 @@ - + src - + include - + lib1 - - + + t40001.cc - - + + t40001_include1.h - - + + lib1.h - + string - + vector - + yaml-cpp/yaml.h - + This is a lib1 include dir - + This is a t40001_include1.h include file diff --git a/docs/test_cases/t40002_include.svg b/docs/test_cases/t40002_include.svg index ca57f280..2d466344 100644 --- a/docs/test_cases/t40002_include.svg +++ b/docs/test_cases/t40002_include.svg @@ -1,6 +1,6 @@ - + @@ -9,46 +9,46 @@ - + src - + lib1 - + lib2 - + include - + lib1 - + lib2 - - + + t40002.cc - - + + lib1.cc - - + + lib2.cc - - + + lib1.h - - + + lib2.h diff --git a/docs/test_cases/t40003_include.svg b/docs/test_cases/t40003_include.svg index 6db84dd2..70e34409 100644 --- a/docs/test_cases/t40003_include.svg +++ b/docs/test_cases/t40003_include.svg @@ -1,6 +1,6 @@ - + @@ -9,66 +9,66 @@ - + include - + dependants - + dependencies - + src - + dependants - + dependencies - - + + t3.h - - + + t2.h - - + + t1.h - - + + t3.h - - + + t2.h - - + + t1.h - - + + t5.h - - + + t1.cc - - + + t2.cc diff --git a/src/class_diagram/visitor/translation_unit_visitor.cc b/src/class_diagram/visitor/translation_unit_visitor.cc index c85cc445..3aff1883 100644 --- a/src/class_diagram/visitor/translation_unit_visitor.cc +++ b/src/class_diagram/visitor/translation_unit_visitor.cc @@ -171,7 +171,10 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm) } } - assert(id_opt); + if (!id_opt) { + LOG_WARN("Unknown parent for enum {}", qualified_name); + return true; + } auto parent_class = diagram_.get_class(*id_opt); diff --git a/src/common/model/diagram_element.h b/src/common/model/diagram_element.h index 084b09e2..e303edbc 100644 --- a/src/common/model/diagram_element.h +++ b/src/common/model/diagram_element.h @@ -43,7 +43,7 @@ public: void set_id(id_t id); - std::string alias() const; + virtual std::string alias() const; void set_name(const std::string &name) { name_ = name; } diff --git a/src/common/model/enums.cc b/src/common/model/enums.cc index 3cbf85d2..ea40af08 100644 --- a/src/common/model/enums.cc +++ b/src/common/model/enums.cc @@ -74,6 +74,26 @@ std::string to_string(message_t r) return "call"; case message_t::kReturn: return "return"; + case message_t::kIf: + return "if"; + case message_t::kElse: + return "else"; + case message_t::kElseIf: + return "else if"; + case message_t::kIfEnd: + return "end if"; + case message_t::kWhile: + return "while"; + case message_t::kWhileEnd: + return "end while"; + case message_t::kDo: + return "do"; + case message_t::kDoEnd: + return "end do"; + case message_t::kFor: + return "for"; + case message_t::kForEnd: + return "end for"; default: assert(false); return ""; diff --git a/src/common/model/enums.h b/src/common/model/enums.h index e9c3cd2e..fbec4a55 100644 --- a/src/common/model/enums.h +++ b/src/common/model/enums.h @@ -39,13 +39,27 @@ enum class relationship_t { kDependency }; -enum class message_t { kCall, kReturn }; +enum class message_t { + kCall, + kReturn, + kIf, + kElse, + kElseIf, + kIfEnd, + kWhile, + kWhileEnd, + kDo, + kDoEnd, + kFor, + kForEnd, + kNone +}; std::string to_string(relationship_t r); std::string to_string(access_t r); -std::string to_string(message_t r); +std::string to_string(message_t m); std::string to_string(diagram_t r); diff --git a/src/common/model/source_location.h b/src/common/model/source_location.h index 6e2c4a91..38e880ef 100644 --- a/src/common/model/source_location.h +++ b/src/common/model/source_location.h @@ -39,8 +39,13 @@ public: void set_line(const unsigned line) { line_ = line; } + unsigned int location_id() const { return hash_; } + + void set_location_id(unsigned int h) { hash_ = h; } + private: std::string file_; unsigned int line_{0}; + unsigned int hash_; }; } \ No newline at end of file diff --git a/src/common/types.h b/src/common/types.h index 9a6b6ef0..0cb38440 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -36,6 +36,10 @@ public: { } + optional_ref(T *value) { value_ = value; } + + optional_ref(const T *value) { value_ = value; } + optional_ref(T &value) { value_ = &value; } optional_ref(const T &value) { value_ = &value; } diff --git a/src/common/visitor/translation_unit_visitor.cc b/src/common/visitor/translation_unit_visitor.cc index 6c42e29f..3fc3ac2a 100644 --- a/src/common/visitor/translation_unit_visitor.cc +++ b/src/common/visitor/translation_unit_visitor.cc @@ -66,10 +66,23 @@ void translation_unit_visitor::process_comment( void translation_unit_visitor::set_source_location( const clang::Decl &decl, clanguml::common::model::source_location &element) { - if (decl.getLocation().isValid()) { - element.set_file(source_manager_.getFilename(decl.getLocation()).str()); - element.set_line( - source_manager_.getSpellingLineNumber(decl.getLocation())); + set_source_location(decl.getLocation(), element); +} + +void translation_unit_visitor::set_source_location( + const clang::Expr &expr, clanguml::common::model::source_location &element) +{ + set_source_location(expr.getBeginLoc(), element); +} + +void translation_unit_visitor::set_source_location( + const clang::SourceLocation &location, + clanguml::common::model::source_location &element) +{ + if (location.isValid()) { + element.set_file(source_manager_.getFilename(location).str()); + element.set_line(source_manager_.getSpellingLineNumber(location)); + element.set_location_id(location.getHashValue()); } } diff --git a/src/common/visitor/translation_unit_visitor.h b/src/common/visitor/translation_unit_visitor.h index bb82075f..31a35a63 100644 --- a/src/common/visitor/translation_unit_visitor.h +++ b/src/common/visitor/translation_unit_visitor.h @@ -42,12 +42,16 @@ public: clang::SourceManager &source_manager() const; - void finalize(); - protected: void set_source_location(const clang::Decl &decl, clanguml::common::model::source_location &element); + void set_source_location(const clang::Expr &expr, + clanguml::common::model::source_location &element); + + void set_source_location(const clang::SourceLocation &location, + clanguml::common::model::source_location &element); + void process_comment(const clang::NamedDecl &decl, clanguml::common::model::decorated_element &e); diff --git a/src/config/config.cc b/src/config/config.cc index d8e0ffff..8349746b 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -101,6 +101,8 @@ void inheritable_diagram_options::inherit( base_directory.override(parent.base_directory); relative_to.override(parent.relative_to); comment_parser.override(parent.comment_parser); + combine_free_functions_into_file_participants.override( + combine_free_functions_into_file_participants); } std::string inheritable_diagram_options::simplify_template_type( @@ -131,6 +133,38 @@ std::vector diagram::get_translation_units( return translation_units; } +void diagram::initialize_type_aliases() +{ + if (!type_aliases().count("std::basic_string")) { + type_aliases().insert({"std::basic_string", "std::string"}); + } + if (!type_aliases().count("std::basic_string,std::allocator>")) { + type_aliases().insert({"std::basic_string,std::allocator>", + "std::string"}); + } + if (!type_aliases().count("std::basic_string")) { + type_aliases().insert({"std::basic_string", "std::wstring"}); + } + if (!type_aliases().count("std::basic_string")) { + type_aliases().insert( + {"std::basic_string", "std::u16string"}); + } + if (!type_aliases().count("std::basic_string")) { + type_aliases().insert( + {"std::basic_string", "std::u32string"}); + } + if (!type_aliases().count("std::integral_constant")) { + type_aliases().insert( + {"std::integral_constant", "std::true_type"}); + } + if (!type_aliases().count("std::integral_constant")) { + type_aliases().insert( + {"std::integral_constant", "std::false_type"}); + } +} + common::model::diagram_t class_diagram::type() const { return common::model::diagram_t::kClass; @@ -179,38 +213,6 @@ void class_diagram::initialize_relationship_hints() } } -void class_diagram::initialize_type_aliases() -{ - if (!type_aliases().count("std::basic_string")) { - type_aliases().insert({"std::basic_string", "std::string"}); - } - if (!type_aliases().count("std::basic_string,std::allocator>")) { - type_aliases().insert({"std::basic_string,std::allocator>", - "std::string"}); - } - if (!type_aliases().count("std::basic_string")) { - type_aliases().insert({"std::basic_string", "std::wstring"}); - } - if (!type_aliases().count("std::basic_string")) { - type_aliases().insert( - {"std::basic_string", "std::u16string"}); - } - if (!type_aliases().count("std::basic_string")) { - type_aliases().insert( - {"std::basic_string", "std::u32string"}); - } - if (!type_aliases().count("std::integral_constant")) { - type_aliases().insert( - {"std::integral_constant", "std::true_type"}); - } - if (!type_aliases().count("std::integral_constant")) { - type_aliases().insert( - {"std::integral_constant", "std::false_type"}); - } -} - template <> void append_value(plantuml &l, const plantuml &r) { l.append(r); @@ -589,6 +591,10 @@ template <> struct convert { return false; get_option(node, rhs.start_from); + get_option(node, rhs.combine_free_functions_into_file_participants); + get_option(node, rhs.relative_to); + + rhs.initialize_type_aliases(); return true; } diff --git a/src/config/config.h b/src/config/config.h index 7d659b6d..77e18787 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -142,6 +142,8 @@ struct inheritable_diagram_options { option type_aliases{"type_aliases"}; option comment_parser{ "comment_parser", comment_parser_t::plain}; + option combine_free_functions_into_file_participants{ + "combine_free_functions_into_file_participants", false}; void inherit(const inheritable_diagram_options &parent); @@ -156,6 +158,8 @@ struct diagram : public inheritable_diagram_options { std::vector get_translation_units( const std::filesystem::path &root_directory) const; + void initialize_type_aliases(); + std::string name; }; @@ -174,8 +178,6 @@ struct class_diagram : public diagram { option layout{"layout"}; void initialize_relationship_hints(); - - void initialize_type_aliases(); }; struct sequence_diagram : public diagram { diff --git a/src/main.cc b/src/main.cc index b816aff6..d08d0f82 100644 --- a/src/main.cc +++ b/src/main.cc @@ -60,7 +60,7 @@ int main(int argc, const char *argv[]) std::optional output_directory; unsigned int thread_count{0}; bool show_version{false}; - bool verbose{false}; + int verbose{0}; bool list_diagrams{false}; app.add_option( diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index fb45c5d2..2c6668ef 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -36,79 +36,311 @@ generator::generator( { } +std::string generator::render_name(std::string name) const +{ + util::replace_all(name, "##", "::"); + + return name; +} + void generator::generate_call(const message &m, std::ostream &ostr) const { - const auto from = m_config.using_namespace().relative(m.from); - const auto to = m_config.using_namespace().relative(m.to); + const auto &from = m_model.get_participant(m.from()); + const auto &to = m_model.get_participant(m.to()); - if (from.empty() || to.empty()) { - LOG_DBG("Skipping empty call from '{}' to '{}'", from, to); + if (!from || !to) { + LOG_DBG("Skipping empty call from '{}' to '{}'", m.from(), m.to()); return; } - auto message = m.message; - if (!message.empty()) - message += "()"; + generate_participant(ostr, m.from()); + generate_participant(ostr, m.to()); - ostr << '"' << from << "\" " - << common::generators::plantuml::to_plantuml(message_t::kCall) << " \"" - << to << "\" : " << message << std::endl; + std::string message; + + if (to.value().type_name() == "method") { + message = dynamic_cast(to.value()) + .message_name(model::function::message_render_mode::full); + } + else if (m_config.combine_free_functions_into_file_participants()) { + if (to.value().type_name() == "function") { + message = + dynamic_cast(to.value()) + .message_name(model::function::message_render_mode::full); + } + else if (to.value().type_name() == "function_template") { + message = + dynamic_cast(to.value()) + .message_name(model::function::message_render_mode::full); + } + } + + const std::string from_alias = generate_alias(from.value()); + const std::string to_alias = generate_alias(to.value()); + + ostr << from_alias << " " + << common::generators::plantuml::to_plantuml(message_t::kCall) << " " + << to_alias; + + if (m_config.generate_links) { + common_generator::generate_link(ostr, m); + } + + ostr << " : " << message << std::endl; LOG_DBG("Generated call '{}' from {} [{}] to {} [{}]", message, from, - m.from_usr, to, m.to_usr); + m.from(), to, m.to()); } void generator::generate_return(const message &m, std::ostream &ostr) const { // Add return activity only for messages between different actors and // only if the return type is different than void - if ((m.from != m.to) && (m.return_type != "void")) { - const auto from = m_config.using_namespace().relative(m.from); - const auto to = m_config.using_namespace().relative(m.to); + const auto &from = m_model.get_participant(m.from()); + const auto &to = m_model.get_participant(m.to()); + if ((m.from() != m.to()) && !to.value().is_void()) { + const std::string from_alias = generate_alias(from.value()); - ostr << '"' << to << "\" " + const std::string to_alias = generate_alias(to.value()); + + ostr << to_alias << " " << common::generators::plantuml::to_plantuml(message_t::kReturn) - << " \"" << from << "\"" << std::endl; + << " " << from_alias << '\n'; } } -void generator::generate_activity(const activity &a, std::ostream &ostr) const +void generator::generate_activity(const activity &a, std::ostream &ostr, + std::vector &visited) const { - for (const auto &m : a.messages) { - const auto to = m_config.using_namespace().relative(m.to); + for (const auto &m : a.messages()) { + if (m.type() == message_t::kCall) { + const auto &to = + m_model.get_participant(m.to()); + if (!to) + continue; - if (to.empty()) + visited.push_back(m.from()); + + LOG_DBG("Generating message {} --> {}", m.from(), m.to()); + + generate_call(m, ostr); + + std::string to_alias = generate_alias(to.value()); + + ostr << "activate " << to_alias << std::endl; + + if (m_model.sequences().find(m.to()) != m_model.sequences().end()) { + if (std::find(visited.begin(), visited.end(), m.to()) == + visited + .end()) { // break infinite recursion on recursive calls + LOG_DBG("Creating activity {} --> {} - missing sequence {}", + m.from(), m.to(), m.to()); + generate_activity( + m_model.get_activity(m.to()), ostr, visited); + } + } + else + LOG_DBG("Skipping activity {} --> {} - missing sequence {}", + m.from(), m.to(), m.to()); + + generate_return(m, ostr); + + ostr << "deactivate " << to_alias << std::endl; + + visited.pop_back(); + } + else if (m.type() == message_t::kIf) { + ostr << "alt\n"; + } + else if (m.type() == message_t::kElseIf) { + ostr << "else\n"; + } + else if (m.type() == message_t::kElse) { + ostr << "else\n"; + } + else if (m.type() == message_t::kIfEnd) { + ostr << "end\n"; + } + else if (m.type() == message_t::kWhile) { + ostr << "loop\n"; + } + else if (m.type() == message_t::kWhileEnd) { + ostr << "end\n"; + } + else if (m.type() == message_t::kFor) { + ostr << "loop\n"; + } + else if (m.type() == message_t::kForEnd) { + ostr << "end\n"; + } + else if (m.type() == message_t::kDo) { + ostr << "loop\n"; + } + else if (m.type() == message_t::kDoEnd) { + ostr << "end\n"; + } + } +} + +void generator::generate_participant(std::ostream &ostr, common::id_t id) const +{ + for (const auto participant_id : m_model.active_participants()) { + if (participant_id != id) continue; - generate_call(m, ostr); + if (is_participant_generated(participant_id)) + return; - ostr << "activate " << '"' << to << '"' << std::endl; + const auto &participant = + m_model.get_participant(participant_id).value(); - if (m_model.sequences.find(m.to_usr) != m_model.sequences.end()) - generate_activity(m_model.sequences[m.to_usr], ostr); + if (participant.type_name() == "method") { + const auto class_id = + m_model.get_participant(participant_id) + .value() + .class_id(); - generate_return(m, ostr); + if (is_participant_generated(class_id)) + return; - ostr << "deactivate " << '"' << to << '"' << std::endl; + const auto &class_participant = + m_model.get_participant(class_id).value(); + + ostr << "participant \"" + << render_name(m_config.using_namespace().relative( + class_participant.full_name(false))) + << "\" as " << class_participant.alias(); + + if (m_config.generate_links) { + common_generator::generate_link( + ostr, class_participant); + } + + ostr << '\n'; + + generated_participants_.emplace(class_id); + } + else if ((participant.type_name() == "function" || + participant.type_name() == "function_template") && + m_config.combine_free_functions_into_file_participants()) { + // Create a single participant for all functions declared in a + // single file + const auto &file_path = + m_model.get_participant(participant_id) + .value() + .file(); + + assert(!file_path.empty()); + + const auto file_id = common::to_id(file_path); + + if (is_participant_generated(file_id)) + return; + + [[maybe_unused]] const auto &relative_to = + std::filesystem::canonical(m_config.relative_to()); + + auto participant_name = std::filesystem::relative( + std::filesystem::path{file_path}, relative_to) + .string(); + + ostr << "participant \"" << render_name(participant_name) + << "\" as " << fmt::format("C_{:022}", file_id); + + ostr << '\n'; + + generated_participants_.emplace(file_id); + } + else { + ostr << "participant \"" + << m_config.using_namespace().relative( + participant.full_name(false)) + << "\" as " << participant.alias(); + + if (m_config.generate_links) { + common_generator::generate_link( + ostr, participant); + } + + ostr << '\n'; + + generated_participants_.emplace(participant_id); + } + + return; } } +bool generator::is_participant_generated(common::id_t id) const +{ + return std::find(generated_participants_.begin(), + generated_participants_.end(), + id) != generated_participants_.end(); +} + void generator::generate(std::ostream &ostr) const { + m_model.print(); + ostr << "@startuml" << std::endl; generate_plantuml_directives(ostr, m_config.puml().before); for (const auto &sf : m_config.start_from()) { if (sf.location_type == source_location::location_t::function) { - std::int64_t start_from; - for (const auto &[k, v] : m_model.sequences) { - if (v.from == sf.location) { + common::model::diagram_element::id_t start_from; + for (const auto &[k, v] : m_model.sequences()) { + const auto &caller = *m_model.participants().at(v.from()); + std::string vfrom = caller.full_name(false); + if (vfrom == sf.location) { + LOG_DBG("Found sequence diagram start point: {}", k); start_from = k; break; } } - generate_activity(m_model.sequences[start_from], ostr); + + // Use this to break out of recurrent loops + std::vector + visited_participants; + + const auto &from = + m_model.get_participant(start_from); + + if (!from.has_value()) { + LOG_WARN("Failed to find participant {} for start_from " + "condition", + sf.location); + continue; + } + + generate_participant(ostr, start_from); + + std::string from_alias = generate_alias(from.value()); + + if (from.value().type_name() == "method" || + m_config.combine_free_functions_into_file_participants()) { + ostr << "[->" + << " " << from_alias << " : " + << from.value().message_name( + model::function::message_render_mode::full) + << std::endl; + } + + ostr << "activate " << from_alias << std::endl; + + generate_activity( + m_model.get_activity(start_from), ostr, visited_participants); + + if (from.value().type_name() == "method" || + m_config.combine_free_functions_into_file_participants()) { + + if (!from.value().is_void()) { + ostr << "[<--" + << " " << from_alias << std::endl; + } + } + + ostr << "deactivate " << from_alias << std::endl; } else { // TODO: Add support for other sequence start location types @@ -121,4 +353,18 @@ void generator::generate(std::ostream &ostr) const ostr << "@enduml" << std::endl; } +std::string generator::generate_alias( + const model::participant &participant) const +{ + if ((participant.type_name() == "function" || + participant.type_name() == "function_template") && + m_config.combine_free_functions_into_file_participants()) { + const auto file_id = common::to_id(participant.file()); + + return fmt::format("C_{:022}", file_id); + } + + return participant.alias(); +} + } diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h index 30e9ef63..23a4cbe7 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h @@ -52,10 +52,21 @@ public: void generate_return(const clanguml::sequence_diagram::model::message &m, std::ostream &ostr) const; + void generate_participant(std::ostream &ostr, common::id_t id) const; + void generate_activity(const clanguml::sequence_diagram::model::activity &a, - std::ostream &ostr) const; + std::ostream &ostr, + std::vector &visited) const; void generate(std::ostream &ostr) const; + +private: + bool is_participant_generated(common::id_t id) const; + + std::string render_name(std::string name) const; + + mutable std::set generated_participants_; + std::string generate_alias(const model::participant &participant) const; }; } diff --git a/src/sequence_diagram/model/activity.cc b/src/sequence_diagram/model/activity.cc index c9e3d226..e2c921ca 100644 --- a/src/sequence_diagram/model/activity.cc +++ b/src/sequence_diagram/model/activity.cc @@ -20,4 +20,19 @@ namespace clanguml::sequence_diagram::model { +activity::activity(common::model::diagram_element::id_t id) + : from_{id} +{ +} + +void activity::add_message(message m) { messages_.emplace_back(std::move(m)); } + +std::vector &activity::messages() { return messages_; } + +const std::vector &activity::messages() const { return messages_; } + +void activity::set_from(common::model::diagram_element::id_t f) { from_ = f; } + +common::model::diagram_element::id_t activity::from() const { return from_; } + } diff --git a/src/sequence_diagram/model/activity.h b/src/sequence_diagram/model/activity.h index d60cb225..2622ad4c 100644 --- a/src/sequence_diagram/model/activity.h +++ b/src/sequence_diagram/model/activity.h @@ -18,16 +18,30 @@ #pragma once #include "message.h" +#include "participant.h" #include #include namespace clanguml::sequence_diagram::model { -struct activity { - std::uint_least64_t usr; - std::string from; - std::vector messages; +class activity { +public: + activity(common::model::diagram_element::id_t id); + + void add_message(message m); + + std::vector &messages(); + + const std::vector &messages() const; + + void set_from(common::model::diagram_element::id_t f); + + common::model::diagram_element::id_t from() const; + +private: + common::model::diagram_element::id_t from_; + std::vector messages_; }; } diff --git a/src/sequence_diagram/model/diagram.cc b/src/sequence_diagram/model/diagram.cc index 510e7d63..99091e9d 100644 --- a/src/sequence_diagram/model/diagram.cc +++ b/src/sequence_diagram/model/diagram.cc @@ -29,14 +29,22 @@ common::model::diagram_t diagram::type() const } common::optional_ref diagram::get( - const std::string & /*full_name*/) const + const std::string &full_name) const { + for (const auto &[id, participant] : participants_) { + if (participant->full_name(false) == full_name) + return {*participant}; + } + return {}; } common::optional_ref diagram::get( - const common::model::diagram_element::id_t /*id*/) const + const common::model::diagram_element::id_t id) const { + if (participants_.find(id) != participants_.end()) + return {*participants_.at(id)}; + return {}; } @@ -53,11 +61,251 @@ inja::json diagram::context() const inja::json::array_t elements{}; + // Add classes + for (const auto &[id, p] : participants_) { + elements.emplace_back(p->context()); + } + ctx["elements"] = elements; return ctx; } +void diagram::add_participant(std::unique_ptr p) +{ + const auto participant_id = p->id(); + + if (participants_.find(participant_id) == participants_.end()) { + LOG_DBG("Adding '{}' participant: {}, {} [{}]", p->type_name(), + p->full_name(false), p->id(), + p->type_name() == "method" + ? dynamic_cast(p.get())->method_name() + : ""); + + participants_.emplace(participant_id, std::move(p)); + } +} + +void diagram::add_active_participant(common::model::diagram_element::id_t id) +{ + active_participants_.emplace(id); +} + +activity &diagram::get_activity(common::model::diagram_element::id_t id) +{ + return sequences_.at(id); +} + +void diagram::add_for_stmt( + const common::model::diagram_element::id_t current_caller_id) +{ + add_loop_stmt(current_caller_id, common::model::message_t::kFor); +} + +void diagram::end_for_stmt( + const common::model::diagram_element::id_t current_caller_id) +{ + end_loop_stmt(current_caller_id, common::model::message_t::kForEnd); +} + +void diagram::add_while_stmt( + const common::model::diagram_element::id_t current_caller_id) +{ + add_loop_stmt(current_caller_id, common::model::message_t::kWhile); +} + +void diagram::end_while_stmt( + const common::model::diagram_element::id_t current_caller_id) +{ + end_loop_stmt(current_caller_id, common::model::message_t::kWhileEnd); +} + +void diagram::add_do_stmt( + const common::model::diagram_element::id_t current_caller_id) +{ + add_loop_stmt(current_caller_id, common::model::message_t::kDo); +} + +void diagram::end_do_stmt( + const common::model::diagram_element::id_t current_caller_id) +{ + end_loop_stmt(current_caller_id, common::model::message_t::kDoEnd); +} + +void diagram::add_loop_stmt( + const common::model::diagram_element::id_t current_caller_id, + common::model::message_t type) +{ + using clanguml::common::model::message_t; + + if (current_caller_id == 0) + return; + + if (sequences_.find(current_caller_id) == sequences_.end()) { + activity a{current_caller_id}; + sequences_.insert({current_caller_id, std::move(a)}); + } + + get_activity(current_caller_id).add_message({type, current_caller_id}); +} + +void diagram::end_loop_stmt( + const common::model::diagram_element::id_t current_caller_id, + common::model::message_t type) +{ + using clanguml::common::model::message_t; + + if (current_caller_id == 0) + return; + + message m{type, current_caller_id}; + + message_t loop_type = message_t::kWhile; + + if (type == message_t::kForEnd) + loop_type = message_t::kFor; + else if (type == message_t::kDoEnd) + loop_type = message_t::kDo; + + if (sequences_.find(current_caller_id) != sequences_.end()) { + auto ¤t_messages = get_activity(current_caller_id).messages(); + + if (current_messages.back().type() == loop_type) { + current_messages.pop_back(); + } + else { + current_messages.emplace_back(std::move(m)); + } + } +} + +void diagram::add_if_stmt( + const common::model::diagram_element::id_t current_caller_id, + common::model::message_t type) +{ + using clanguml::common::model::message_t; + + if (sequences_.find(current_caller_id) == sequences_.end()) { + activity a{current_caller_id}; + sequences_.insert({current_caller_id, std::move(a)}); + } + + get_activity(current_caller_id).add_message({type, current_caller_id}); +} + +void diagram::end_if_stmt( + const common::model::diagram_element::id_t current_caller_id, + common::model::message_t type) +{ + using clanguml::common::model::message_t; + + message m{message_t::kIfEnd, current_caller_id}; + + if (sequences_.find(current_caller_id) != sequences_.end()) { + + auto ¤t_messages = get_activity(current_caller_id).messages(); + // Remove the if/else messages if there were no calls + // added to the diagram between them + auto last_if_it = + std::find_if(current_messages.rbegin(), current_messages.rend(), + [](const message &m) { return m.type() == message_t::kIf; }); + + bool last_if_block_is_empty = + std::none_of(current_messages.rbegin(), last_if_it, + [](const message &m) { return m.type() == message_t::kCall; }); + + if (!last_if_block_is_empty) { + current_messages.emplace_back(std::move(m)); + } + else { + current_messages.erase( + (last_if_it + 1).base(), current_messages.end()); + } + } +} + +bool diagram::started() const { return started_; } + +void diagram::started(bool s) { started_ = s; } + +std::map &diagram::sequences() +{ + return sequences_; +} + +const std::map & +diagram::sequences() const +{ + return sequences_; +} + +std::map> & +diagram::participants() +{ + return participants_; +} + +const std::map> & +diagram::participants() const +{ + return participants_; +} + +std::set &diagram::active_participants() +{ + return active_participants_; +}; + +const std::set & +diagram::active_participants() const +{ + return active_participants_; +}; + +void diagram::print() const +{ + LOG_TRACE(" --- Participants ---"); + for (const auto &[id, participant] : participants_) { + LOG_DBG("{} - {}", id, participant->to_string()); + } + + LOG_TRACE(" --- Activities ---"); + for (const auto &[from_id, act] : sequences_) { + + LOG_TRACE("Sequence id={}:", from_id); + + const auto &from_activity = *(participants_.at(from_id)); + + LOG_TRACE(" Activity id={}, from={}:", act.from(), + from_activity.full_name(false)); + + for (const auto &message : act.messages()) { + if (participants_.find(message.from()) == participants_.end()) + continue; + + const auto &from_participant = *participants_.at(message.from()); + + if (participants_.find(message.to()) == participants_.end()) { + LOG_TRACE(" Message from={}, from_id={}, " + "to={}, to_id={}, name={}, type={}", + from_participant.full_name(false), from_participant.id(), + "__UNRESOLVABLE_ID__", message.to(), message.message_name(), + to_string(message.type())); + } + else { + const auto &to_participant = *participants_.at(message.to()); + + LOG_TRACE(" Message from={}, from_id={}, " + "to={}, to_id={}, name={}, type={}", + from_participant.full_name(false), from_participant.id(), + to_participant.full_name(false), to_participant.id(), + message.message_name(), to_string(message.type())); + } + } + } +} + } namespace clanguml::common::model { diff --git a/src/sequence_diagram/model/diagram.h b/src/sequence_diagram/model/diagram.h index 9190b12e..2b530f49 100644 --- a/src/sequence_diagram/model/diagram.h +++ b/src/sequence_diagram/model/diagram.h @@ -20,6 +20,7 @@ #include "activity.h" #include "common/model/diagram.h" #include "common/types.h" +#include "participant.h" #include #include @@ -47,9 +48,86 @@ public: inja::json context() const override; - bool started{false}; + void print() const; - std::map sequences; + template + common::optional_ref get_participant( + common::model::diagram_element::id_t id) + { + if (participants_.find(id) == participants_.end()) { + return {}; + } + + return common::optional_ref( + static_cast(participants_.at(id).get())); + } + + template + const common::optional_ref get_participant( + common::model::diagram_element::id_t id) const + { + if (participants_.find(id) == participants_.end()) { + return {}; + } + + return common::optional_ref( + static_cast(participants_.at(id).get())); + } + + void add_participant(std::unique_ptr p); + + void add_active_participant(common::model::diagram_element::id_t id); + + activity &get_activity(common::model::diagram_element::id_t id); + + void add_if_stmt(common::model::diagram_element::id_t current_caller_id, + common::model::message_t type); + void end_if_stmt(common::model::diagram_element::id_t current_caller_id, + common::model::message_t type); + + void add_loop_stmt(common::model::diagram_element::id_t current_caller_id, + common::model::message_t type); + void end_loop_stmt(common::model::diagram_element::id_t current_caller_id, + common::model::message_t type); + + void add_while_stmt(common::model::diagram_element::id_t i); + void end_while_stmt(common::model::diagram_element::id_t i); + + void add_do_stmt(common::model::diagram_element::id_t i); + void end_do_stmt(common::model::diagram_element::id_t i); + + void add_for_stmt(common::model::diagram_element::id_t i); + void end_for_stmt(common::model::diagram_element::id_t i); + + bool started() const; + void started(bool s); + + std::map &sequences(); + + const std::map & + sequences() const; + + std::map> + &participants(); + + const std::map> & + participants() const; + + std::set &active_participants(); + + const std::set & + active_participants() const; + +private: + bool started_{false}; + + std::map sequences_; + + std::map> + participants_; + + std::set active_participants_; }; } diff --git a/src/sequence_diagram/model/message.cc b/src/sequence_diagram/model/message.cc index ef92071b..93f23937 100644 --- a/src/sequence_diagram/model/message.cc +++ b/src/sequence_diagram/model/message.cc @@ -20,4 +20,34 @@ namespace clanguml::sequence_diagram::model { +message::message( + common::model::message_t type, common::model::diagram_element::id_t from) + : type_{type} + , from_{from} +{ +} + +void message::set_type(common::model::message_t t) { type_ = t; } + +common::model::message_t message::type() const { return type_; } + +void message::set_from(common::model::diagram_element::id_t f) { from_ = f; } + +common::model::diagram_element::id_t message::from() const { return from_; } + +void message::set_to(common::model::diagram_element::id_t t) { to_ = t; } + +common::model::diagram_element::id_t message::to() const { return to_; } + +void message::set_message_name(std::string name) +{ + message_name_ = std::move(name); +} + +const std::string &message::message_name() const { return message_name_; } + +void message::set_return_type(std::string t) { return_type_ = std::move(t); } + +const std::string &message::return_type() const { return return_type_; } + } diff --git a/src/sequence_diagram/model/message.h b/src/sequence_diagram/model/message.h index bb0b3212..48c6ca0a 100644 --- a/src/sequence_diagram/model/message.h +++ b/src/sequence_diagram/model/message.h @@ -18,21 +18,47 @@ #pragma once #include "common/model/enums.h" +#include "participant.h" #include #include namespace clanguml::sequence_diagram::model { -struct message { - common::model::message_t type; - std::string from; - std::uint_least64_t from_usr; - std::string to; - std::int64_t to_usr; - std::string message; - std::string return_type; - unsigned int line; +class message : public common::model::diagram_element { +public: + message() = default; + + message(common::model::message_t type, + common::model::diagram_element::id_t from); + + void set_type(common::model::message_t t); + common::model::message_t type() const; + + void set_from(common::model::diagram_element::id_t f); + common::model::diagram_element::id_t from() const; + + void set_to(common::model::diagram_element::id_t t); + common::model::diagram_element::id_t to() const; + + void set_message_name(std::string name); + const std::string &message_name() const; + + void set_return_type(std::string t); + const std::string &return_type() const; + +private: + common::model::message_t type_{common::model::message_t::kNone}; + + common::model::diagram_element::id_t from_{}; + + common::model::diagram_element::id_t to_{}; + + // This is only for better verbose messages, we cannot rely on this + // always + std::string message_name_{}; + + std::string return_type_{}; }; } diff --git a/src/sequence_diagram/model/participant.cc b/src/sequence_diagram/model/participant.cc new file mode 100644 index 00000000..fdf61a28 --- /dev/null +++ b/src/sequence_diagram/model/participant.cc @@ -0,0 +1,338 @@ +/** + * src/sequence_diagram/model/participant.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "participant.h" + +namespace clanguml::sequence_diagram::model { + +std::ostringstream &template_trait::render_template_params( + std::ostringstream &ostr, const common::model::namespace_ &using_namespace, + bool relative) const +{ + using clanguml::common::model::namespace_; + + if (!templates_.empty()) { + std::vector tnames; + std::vector tnames_simplified; + + std::transform(templates_.cbegin(), templates_.cend(), + std::back_inserter(tnames), + [ns = using_namespace, relative]( + const auto &tmplt) { return tmplt.to_string(ns, relative); }); + + ostr << fmt::format("<{}>", fmt::join(tnames, ",")); + } + + return ostr; +} + +void template_trait::set_base_template(const std::string &full_name) +{ + base_template_full_name_ = full_name; +} + +std::string template_trait::base_template() const +{ + return base_template_full_name_; +} + +void template_trait::add_template( + class_diagram::model::template_parameter tmplt) +{ + templates_.push_back(std::move(tmplt)); +} + +bool template_trait::is_implicit() const { return is_implicit_; } + +void template_trait::set_implicit(bool implicit) { is_implicit_ = implicit; } + +const std::vector & +template_trait::templates() const +{ + return templates_; +} + +int template_trait::calculate_template_specialization_match( + const template_trait &other, const std::string &full_name) const +{ + int res{}; + + // std::string left = name_and_ns(); + // // TODO: handle variadic templates + // if ((name_and_ns() != full_name) || + // (templates().size() != other.templates().size())) { + // return res; + // } + + // Iterate over all template arguments + for (auto i = 0U; i < other.templates().size(); i++) { + const auto &template_arg = templates().at(i); + const auto &other_template_arg = other.templates().at(i); + + if (template_arg == other_template_arg) { + res++; + } + else if (other_template_arg.is_specialization_of(template_arg)) { + continue; + } + else { + res = 0; + break; + } + } + + return res; +} + +std::string participant::to_string() const +{ + return fmt::format( + "Participant '{}': id={} name={}", type_name(), id(), full_name(false)); +} + +class_::class_(const common::model::namespace_ &using_namespace) + : participant{using_namespace} +{ +} + +bool class_::is_struct() const { return is_struct_; } + +void class_::is_struct(bool is_struct) { is_struct_ = is_struct; } + +bool class_::is_template() const { return is_template_; } + +void class_::is_template(bool is_template) { is_template_ = is_template; } + +bool class_::is_template_instantiation() const +{ + return is_template_instantiation_; +} + +void class_::is_template_instantiation(bool is_template_instantiation) +{ + is_template_instantiation_ = is_template_instantiation; +} + +std::string class_::full_name_no_ns() const +{ + using namespace clanguml::util; + + std::ostringstream ostr; + + ostr << name(); + + render_template_params(ostr, using_namespace(), false); + + return ostr.str(); +} + +std::string class_::full_name(bool relative) const +{ + using namespace clanguml::util; + using clanguml::common::model::namespace_; + + std::ostringstream ostr; + + ostr << name_and_ns(); + render_template_params(ostr, using_namespace(), relative); + + std::string res; + + if (relative) + res = using_namespace().relative(ostr.str()); + else + res = ostr.str(); + + if (res.empty()) + return "<>"; + + return res; +} + +bool class_::is_alias() const { return is_alias_; } + +void class_::is_alias(bool alias) { is_alias_ = alias; } + +bool class_::is_lambda() const { return is_lambda_; } + +void class_::is_lambda(bool is_lambda) { is_lambda_ = is_lambda; } + +bool operator==(const class_ &l, const class_ &r) { return l.id() == r.id(); } + +function::function(const common::model::namespace_ &using_namespace) + : participant{using_namespace} +{ +} + +std::string function::full_name(bool relative) const +{ + return fmt::format("{}({}){}", element::full_name(relative), + fmt::join(parameters_, ","), is_const() ? " const" : ""); +} + +std::string function::full_name_no_ns() const +{ + return fmt::format("{}({}){}", element::full_name_no_ns(), + fmt::join(parameters_, ","), is_const() ? " const" : ""); +} + +std::string function::message_name(message_render_mode mode) const +{ + if (mode == message_render_mode::no_arguments) { + return fmt::format("{}(){}", name(), is_const() ? " const" : ""); + } + + return fmt::format("{}({}){}", name(), fmt::join(parameters_, ","), + is_const() ? " const" : ""); +} + +bool function::is_const() const { return is_const_; } + +void function::is_const(bool c) { is_const_ = c; } + +bool function::is_void() const { return is_void_; } + +void function::is_void(bool v) { is_void_ = v; } + +bool function::is_static() const { return is_static_; } + +void function::is_static(bool s) { is_static_ = s; } + +void function::add_parameter(const std::string &a) { parameters_.push_back(a); } + +const std::vector &function::parameters() const +{ + return parameters_; +} + +method::method(const common::model::namespace_ &using_namespace) + : function{using_namespace} +{ +} + +const std::string method::method_name() const { return method_name_; } + +std::string method::alias() const +{ + assert(class_id_ >= 0); + + return fmt::format("C_{:022}", class_id_); +} + +void method::set_method_name(const std::string &name) { method_name_ = name; } + +void method::set_class_id(diagram_element::id_t id) { class_id_ = id; } + +void method::set_class_full_name(const std::string &name) +{ + class_full_name_ = name; +} + +const auto &method::class_full_name() const { return class_full_name_; } + +std::string method::full_name(bool /*relative*/) const +{ + return fmt::format("{}::{}({}){}", class_full_name(), method_name(), + fmt::join(parameters(), ","), is_const() ? " const" : ""); +} + +std::string method::message_name(message_render_mode mode) const +{ + const std::string style = is_static() ? "__" : ""; + + if (mode == message_render_mode::no_arguments) { + return fmt::format("{}{}(){}{}", style, method_name(), + is_const() ? " const" : "", style); + } + + return fmt::format("{}{}({}){}{}", style, method_name(), + fmt::join(parameters(), ","), is_const() ? " const" : "", style); +} + +class_::diagram_element::id_t method::class_id() const { return class_id_; } + +std::string method::to_string() const +{ + return fmt::format("Participant '{}': id={}, name={}, class_id={}", + type_name(), id(), full_name(false), class_id()); +} + +function_template::function_template( + const common::model::namespace_ &using_namespace) + : function{using_namespace} +{ +} + +std::string function_template::full_name(bool relative) const +{ + using namespace clanguml::util; + using clanguml::common::model::namespace_; + + std::ostringstream ostr; + + ostr << name_and_ns(); + render_template_params(ostr, using_namespace(), relative); + + ostr << fmt::format( + "({}){}", fmt::join(parameters(), ","), is_const() ? " const" : ""); + + std::string res; + + if (relative) + res = using_namespace().relative(ostr.str()); + else + res = ostr.str(); + + if (res.empty()) + return "<>"; + + return res; +} + +std::string function_template::full_name_no_ns() const +{ + using namespace clanguml::util; + + std::ostringstream ostr; + + ostr << name(); + + render_template_params(ostr, using_namespace(), false); + + ostr << fmt::format( + "({}){}", fmt::join(parameters(), ","), is_const() ? " const" : ""); + + return ostr.str(); +} + +std::string function_template::message_name(message_render_mode mode) const +{ + std::ostringstream s; + render_template_params(s, using_namespace(), true); + std::string template_params = s.str(); + + if (mode == message_render_mode::no_arguments) { + return fmt::format( + "{}{}(){}", name(), template_params, is_const() ? " const" : ""); + } + + return fmt::format("{}{}({}){}", name(), template_params, + fmt::join(parameters(), ","), is_const() ? " const" : ""); +} + +} \ No newline at end of file diff --git a/src/sequence_diagram/model/participant.h b/src/sequence_diagram/model/participant.h new file mode 100644 index 00000000..dce84dec --- /dev/null +++ b/src/sequence_diagram/model/participant.h @@ -0,0 +1,230 @@ +/** + * src/sequence_diagram/model/participant.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "class_diagram/model/template_parameter.h" +#include "class_diagram/model/type_alias.h" +#include "common/model/element.h" + +#include +#include + +namespace clanguml::sequence_diagram::model { + +struct template_trait { + std::ostringstream &render_template_params(std::ostringstream &ostr, + const common::model::namespace_ &using_namespace, bool relative) const; + + void set_base_template(const std::string &full_name); + + std::string base_template() const; + + void add_template(class_diagram::model::template_parameter tmplt); + + const std::vector & + templates() const; + + int calculate_template_specialization_match( + const template_trait &other, const std::string &full_name) const; + + bool is_implicit() const; + + void set_implicit(bool implicit); + +private: + std::vector templates_; + std::string base_template_full_name_; + bool is_implicit_{false}; +}; + +struct participant : public common::model::element, + public common::model::stylable_element { + enum class stereotype_t { + participant = 0, + actor, + boundary, + control, + entity, + database, + collections, + queue + }; + + using common::model::element::element; + + participant(const participant &) = delete; + participant(participant &&) noexcept = delete; + participant &operator=(const participant &) = delete; + participant &operator=(participant &&) = delete; + + std::string type_name() const override { return "participant"; } + + virtual std::string to_string() const; + + stereotype_t stereotype_{stereotype_t::participant}; +}; + +struct class_ : public participant, public template_trait { +public: + class_(const common::model::namespace_ &using_namespace); + + class_(const class_ &) = delete; + class_(class_ &&) noexcept = delete; + class_ &operator=(const class_ &) = delete; + class_ &operator=(class_ &&) = delete; + + std::string type_name() const override { return "class"; } + + bool is_struct() const; + void is_struct(bool is_struct); + + bool is_template() const; + void is_template(bool is_template); + + bool is_template_instantiation() const; + void is_template_instantiation(bool is_template_instantiation); + + friend bool operator==(const class_ &l, const class_ &r); + + std::string full_name(bool relative = true) const override; + + std::string full_name_no_ns() const override; + + bool is_abstract() const; + + bool is_alias() const; + + void is_alias(bool alias); + + bool is_lambda() const; + + void is_lambda(bool is_lambda); + +private: + bool is_struct_{false}; + bool is_template_{false}; + bool is_template_instantiation_{false}; + bool is_alias_{false}; + bool is_lambda_{false}; + + std::map + type_aliases_; + + std::string full_name_; +}; + +struct lambda : public class_ { + using class_::class_; + + std::string type_name() const override { return "lambda"; } +}; + +struct function : public participant { + enum class message_render_mode { full, no_arguments }; + + function(const common::model::namespace_ &using_namespace); + + function(const function &) = delete; + function(function &&) noexcept = delete; + function &operator=(const function &) = delete; + function &operator=(function &&) = delete; + + std::string type_name() const override { return "function"; } + + std::string full_name(bool relative = true) const override; + + std::string full_name_no_ns() const override; + + virtual std::string message_name(message_render_mode mode) const; + + bool is_const() const; + + void is_const(bool c); + + bool is_void() const; + + void is_void(bool v); + + bool is_static() const; + + void is_static(bool s); + + void add_parameter(const std::string &a); + + const std::vector ¶meters() const; + +private: + bool is_const_{false}; + bool is_void_{false}; + bool is_static_{false}; + std::vector parameters_; +}; + +struct method : public function { + method(const common::model::namespace_ &using_namespace); + + method(const function &) = delete; + method(method &&) noexcept = delete; + method &operator=(const method &) = delete; + method &operator=(method &&) = delete; + + std::string type_name() const override { return "method"; } + + const std::string method_name() const; + + std::string alias() const override; + + void set_method_name(const std::string &name); + + void set_class_id(diagram_element::id_t id); + + void set_class_full_name(const std::string &name); + + const auto &class_full_name() const; + + std::string full_name(bool /*relative*/) const override; + + std::string message_name(message_render_mode mode) const override; + + diagram_element::id_t class_id() const; + + std::string to_string() const override; + +private: + diagram_element::id_t class_id_; + std::string method_name_; + std::string class_full_name_; +}; + +struct function_template : public function, public template_trait { + function_template(const common::model::namespace_ &using_namespace); + + function_template(const function_template &) = delete; + function_template(function_template &&) noexcept = delete; + function_template &operator=(const function_template &) = delete; + function_template &operator=(function_template &&) = delete; + + std::string type_name() const override { return "function_template"; } + + std::string full_name(bool relative = true) const override; + + std::string full_name_no_ns() const override; + + std::string message_name(message_render_mode mode) const override; +}; +} diff --git a/src/sequence_diagram/visitor/call_expression_context.cc b/src/sequence_diagram/visitor/call_expression_context.cc new file mode 100644 index 00000000..cd75d268 --- /dev/null +++ b/src/sequence_diagram/visitor/call_expression_context.cc @@ -0,0 +1,189 @@ +/** + * src/sequence_diagram/visitor/call_expression_context.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "call_expression_context.h" + +namespace clanguml::sequence_diagram::visitor { + +call_expression_context::call_expression_context() + : current_class_decl_{nullptr} + , current_class_template_decl_{nullptr} + , current_class_template_specialization_decl_{nullptr} + , current_method_decl_{nullptr} + , current_function_decl_{nullptr} + , current_function_template_decl_{nullptr} + , current_caller_id_{0} +{ +} + +void call_expression_context::reset() +{ + current_caller_id_ = 0; + current_class_decl_ = nullptr; + current_class_template_decl_ = nullptr; + current_class_template_specialization_decl_ = nullptr; + current_method_decl_ = nullptr; + current_function_decl_ = nullptr; + current_function_template_decl_ = nullptr; +} + +void call_expression_context::dump() +{ + LOG_DBG("current_caller_id_ = {}", current_caller_id_); + LOG_DBG("current_class_decl_ = {}", (void *)current_class_decl_); + LOG_DBG("current_class_template_decl_ = {}", + (void *)current_class_template_decl_); + LOG_DBG("current_class_template_specialization_decl_ = {}", + (void *)current_class_template_specialization_decl_); + LOG_DBG("current_method_decl_ = {}", (void *)current_method_decl_); + LOG_DBG("current_function_decl_ = {}", (void *)current_function_decl_); + LOG_DBG("current_function_template_decl_ = {}", + (void *)current_function_template_decl_); +} + +bool call_expression_context::valid() const +{ + return (current_class_decl_ != nullptr) || + (current_class_template_decl_ != nullptr) || + (current_class_template_specialization_decl_ != nullptr) || + (current_method_decl_ != nullptr) || + (current_function_decl_ != nullptr) || + (current_function_template_decl_ != nullptr); +} + +clang::ASTContext *call_expression_context::get_ast_context() +{ + if (current_class_template_specialization_decl_) + return ¤t_class_template_specialization_decl_->getASTContext(); + + if (current_class_template_decl_) + return ¤t_class_template_decl_->getASTContext(); + + if (current_class_decl_) + return ¤t_class_decl_->getASTContext(); + + if (current_function_template_decl_) + return ¤t_function_template_decl_->getASTContext(); + + return ¤t_function_decl_->getASTContext(); +} + +void call_expression_context::update(clang::CXXRecordDecl *cls) +{ + current_class_decl_ = cls; +} + +void call_expression_context::update( + clang::ClassTemplateSpecializationDecl *clst) +{ + current_class_template_specialization_decl_ = clst; +} + +void call_expression_context::update(clang::ClassTemplateDecl *clst) +{ + current_class_template_decl_ = clst; +} + +void call_expression_context::update(clang::CXXMethodDecl *method) +{ + current_method_decl_ = method; +} + +void call_expression_context::update(clang::FunctionDecl *function) +{ + if (!function->isCXXClassMember()) + reset(); + + current_function_decl_ = function; + + // Check if this function is a part of template function declaration, + // If no - reset the current_function_template_decl_ + if (current_function_template_decl_ && + current_function_template_decl_->getQualifiedNameAsString() != + function->getQualifiedNameAsString()) { + current_function_template_decl_ = nullptr; + } +} + +void call_expression_context::update( + clang::FunctionTemplateDecl *function_template) +{ + current_function_template_decl_ = function_template; + + if (!function_template->isCXXClassMember()) + current_class_decl_ = nullptr; + + current_function_template_decl_ = function_template; +} + +bool call_expression_context::in_class_method() const +{ + return current_class_decl_ != nullptr; +} + +bool call_expression_context::in_function() const +{ + return current_class_decl_ == nullptr && current_function_decl_ != nullptr; +} + +bool call_expression_context::in_function_template() const +{ + return current_function_decl_ != nullptr && + current_function_template_decl_ != nullptr; +} + +std::int64_t call_expression_context::caller_id() const +{ + return current_caller_id_; +} + +std::int64_t call_expression_context::lambda_caller_id() const +{ + if (current_lambda_caller_id_.empty()) + return 0; + + return current_lambda_caller_id_.top(); +} + +void call_expression_context::set_caller_id(std::int64_t id) +{ + LOG_DBG("Setting current caller id to {}", id); + current_caller_id_ = id; +} + +void call_expression_context::enter_lambda_expression(std::int64_t id) +{ + LOG_DBG("Setting current lambda caller id to {}", id); + + assert(id != 0); + + current_lambda_caller_id_.push(id); +} + +void call_expression_context::leave_lambda_expression() +{ + if (current_lambda_caller_id_.empty()) + return; + + LOG_DBG("Leaving current lambda expression id to {}", + current_lambda_caller_id_.top()); + + current_lambda_caller_id_.pop(); +} + +} \ No newline at end of file diff --git a/src/sequence_diagram/visitor/call_expression_context.h b/src/sequence_diagram/visitor/call_expression_context.h new file mode 100644 index 00000000..cab68e9d --- /dev/null +++ b/src/sequence_diagram/visitor/call_expression_context.h @@ -0,0 +1,148 @@ +/** + * src/sequence_diagram/visitor/call_expression_context.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +// +//#include "common/visitor/translation_unit_visitor.h" +//#include "config/config.h" +//#include "sequence_diagram/model/diagram.h" + +#include "util/util.h" + +#include +#include +#include + +#include + +namespace clanguml::sequence_diagram::visitor { + +struct call_expression_context { + call_expression_context(); + + void reset(); + + void dump(); + + bool valid() const; + + clang::ASTContext *get_ast_context(); + + void update(clang::CXXRecordDecl *cls); + + void update(clang::ClassTemplateSpecializationDecl *clst); + + void update(clang::ClassTemplateDecl *clst); + + void update(clang::CXXMethodDecl *method); + + void update(clang::FunctionDecl *function); + + void update(clang::FunctionTemplateDecl *function_template); + + bool in_class_method() const; + + bool in_function() const; + + bool in_function_template() const; + + std::int64_t caller_id() const; + + std::int64_t lambda_caller_id() const; + + void set_caller_id(std::int64_t id); + + void enter_lambda_expression(std::int64_t id); + + void leave_lambda_expression(); + + clang::IfStmt *current_ifstmt() const + { + if (if_stmt_stack_.empty()) + return nullptr; + + return if_stmt_stack_.top(); + } + + void enter_ifstmt(clang::IfStmt *stmt) { return if_stmt_stack_.push(stmt); } + + void leave_ifstmt() + { + if (!if_stmt_stack_.empty()) { + if_stmt_stack_.pop(); + std::stack{}.swap(elseif_stmt_stack_); + } + } + + void enter_elseifstmt(clang::IfStmt *stmt) + { + return elseif_stmt_stack_.push(stmt); + } + + void leave_elseifstmt() + { + if (elseif_stmt_stack_.empty()) + return elseif_stmt_stack_.pop(); + } + + clang::IfStmt *current_elseifstmt() const + { + if (elseif_stmt_stack_.empty()) + return nullptr; + + return elseif_stmt_stack_.top(); + } + + clang::Stmt *current_loopstmt() const + { + if (loop_stmt_stack_.empty()) + return nullptr; + + return loop_stmt_stack_.top(); + } + + void enter_loopstmt(clang::Stmt *stmt) + { + return loop_stmt_stack_.push(stmt); + } + + void leave_loopstmt() + { + if (loop_stmt_stack_.empty()) + return loop_stmt_stack_.pop(); + } + + clang::CXXRecordDecl *current_class_decl_; + clang::ClassTemplateDecl *current_class_template_decl_; + clang::ClassTemplateSpecializationDecl + *current_class_template_specialization_decl_; + clang::CXXMethodDecl *current_method_decl_; + clang::FunctionDecl *current_function_decl_; + clang::FunctionTemplateDecl *current_function_template_decl_; + + clang::CallExpr *current_function_call_expr_{nullptr}; + +private: + std::int64_t current_caller_id_; + std::stack current_lambda_caller_id_; + std::stack if_stmt_stack_; + std::stack elseif_stmt_stack_; + + std::stack loop_stmt_stack_; +}; + +} \ No newline at end of file diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index a6db6454..79f7eaf0 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -23,23 +23,67 @@ namespace clanguml::sequence_diagram::visitor { +std::string to_string(const clang::FunctionTemplateDecl *decl) +{ + std::vector template_parameters; + // Handle template function + for (const auto *parameter : *decl->getTemplateParameters()) { + if (clang::dyn_cast_or_null(parameter)) { + const auto *template_type_parameter = + clang::dyn_cast_or_null(parameter); + + std::string template_parameter{ + template_type_parameter->getNameAsString()}; + + if (template_type_parameter->isParameterPack()) + template_parameter += "..."; + + template_parameters.emplace_back(std::move(template_parameter)); + } + else { + // TODO + } + } + return fmt::format("{}<{}>({})", decl->getQualifiedNameAsString(), + fmt::join(template_parameters, ","), ""); +} + translation_unit_visitor::translation_unit_visitor(clang::SourceManager &sm, clanguml::sequence_diagram::model::diagram &diagram, const clanguml::config::sequence_diagram &config) - : source_manager_{sm} + : common::visitor::translation_unit_visitor{sm, config} , diagram_{diagram} , config_{config} - , current_class_decl_{nullptr} - , current_method_decl_{nullptr} - , current_function_decl_{nullptr} + , call_expression_context_{} { } +bool translation_unit_visitor::shouldVisitTemplateInstantiations() +{ + return true; +} + +call_expression_context &translation_unit_visitor::context() +{ + return call_expression_context_; +} + +const call_expression_context &translation_unit_visitor::context() const +{ + return call_expression_context_; +} + clanguml::sequence_diagram::model::diagram &translation_unit_visitor::diagram() { return diagram_; } +const clanguml::sequence_diagram::model::diagram & +translation_unit_visitor::diagram() const +{ + return diagram_; +} + const clanguml::config::sequence_diagram & translation_unit_visitor::config() const { @@ -48,25 +92,640 @@ translation_unit_visitor::config() const bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) { - current_class_decl_ = cls; + // Skip system headers + if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin())) + return true; + + if (!diagram().should_include(cls->getQualifiedNameAsString())) + return true; + + if (cls->isTemplated() && cls->getDescribedTemplate()) { + // If the described templated of this class is already in the model + // skip it: + auto local_id = cls->getDescribedTemplate()->getID(); + if (get_unique_id(local_id)) + return true; + } + + // TODO: Add support for classes defined in function/method bodies + if (cls->isLocalClass()) + return true; + + LOG_TRACE("Visiting class declaration at {}", + cls->getBeginLoc().printToString(source_manager())); + + // Build the class declaration and store it in the diagram, even + // if we don't need it for any of the participants of this diagram + auto c_ptr = this->create_class_declaration(cls); + + if (!c_ptr) + return true; + + context().reset(); + + const auto cls_id = c_ptr->id(); + + set_unique_id(cls->getID(), cls_id); + + auto &class_model = + diagram() + .get_participant(cls_id) + .has_value() + ? *diagram() + .get_participant(cls_id) + .get() + : *c_ptr; + + auto id = class_model.id(); + if (!cls->isCompleteDefinition()) { + forward_declarations_.emplace(id, std::move(c_ptr)); + return true; + } + else { + forward_declarations_.erase(id); + } + + if (diagram().should_include(class_model)) { + LOG_DBG("Adding class {} with id {}", class_model.full_name(false), + class_model.id()); + + assert(class_model.id() == cls_id); + + context().set_caller_id(cls_id); + context().update(cls); + + diagram().add_participant(std::move(c_ptr)); + } + else { + LOG_DBG("Skipping class {} with id {}", class_model.full_name(), + class_model.id()); + } return true; } -bool translation_unit_visitor::VisitCXXMethodDecl(clang::CXXMethodDecl *method) +bool translation_unit_visitor::VisitClassTemplateDecl( + clang::ClassTemplateDecl *cls) { - current_method_decl_ = method; + if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin())) + return true; + + if (!diagram().should_include(cls->getQualifiedNameAsString())) + return true; + + LOG_TRACE("Visiting class template declaration {} at {} [{}]", + cls->getQualifiedNameAsString(), + cls->getLocation().printToString(source_manager()), (void *)cls); + + auto c_ptr = create_class_declaration(cls->getTemplatedDecl()); + + if (!c_ptr) + return true; + + // Override the id with the template id, for now we don't care about the + // underlying templated class id + process_template_parameters(*cls, *c_ptr); + + const auto cls_full_name = c_ptr->full_name(false); + const auto id = common::to_id(cls_full_name); + + c_ptr->set_id(id); + + set_unique_id(cls->getID(), id); + + if (!cls->getTemplatedDecl()->isCompleteDefinition()) { + forward_declarations_.emplace(id, std::move(c_ptr)); + return true; + } + else { + forward_declarations_.erase(id); + } + + if (diagram().should_include(*c_ptr)) { + LOG_DBG("Adding class template {} with id {}", cls_full_name, id); + + context().set_caller_id(id); + context().update(cls); + + diagram().add_participant(std::move(c_ptr)); + } return true; } -bool translation_unit_visitor::VisitFunctionDecl( - clang::FunctionDecl *function_declaration) +bool translation_unit_visitor::VisitClassTemplateSpecializationDecl( + clang::ClassTemplateSpecializationDecl *cls) { - if (!function_declaration->isCXXClassMember()) - current_class_decl_ = nullptr; + if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin())) + return true; - current_function_decl_ = function_declaration; + if (!diagram().should_include(cls->getQualifiedNameAsString())) + return true; + + LOG_TRACE("Visiting template specialization declaration {} at {}", + cls->getQualifiedNameAsString(), + cls->getLocation().printToString(source_manager())); + + // TODO: Add support for classes defined in function/method bodies + if (cls->isLocalClass()) + return true; + + auto template_specialization_ptr = process_template_specialization(cls); + + if (!template_specialization_ptr) + return true; + + const auto cls_full_name = template_specialization_ptr->full_name(false); + const auto id = common::to_id(cls_full_name); + + template_specialization_ptr->set_id(id); + + set_unique_id(cls->getID(), id); + + if (!cls->isCompleteDefinition()) { + forward_declarations_.emplace( + id, std::move(template_specialization_ptr)); + return true; + } + else { + forward_declarations_.erase(id); + } + + if (diagram().should_include(*template_specialization_ptr)) { + LOG_DBG("Adding class template specialization {} with id {}", + cls_full_name, id); + + context().set_caller_id(id); + context().update(cls); + + diagram().add_participant(std::move(template_specialization_ptr)); + } + + return true; +} + +bool translation_unit_visitor::VisitCXXMethodDecl(clang::CXXMethodDecl *m) +{ + if (!diagram().should_include(m->getParent()->getQualifiedNameAsString())) + return true; + + if (!m->isThisDeclarationADefinition()) { + if (m->getDefinition()) + return VisitCXXMethodDecl( + static_cast(m->getDefinition())); + } + + LOG_TRACE("Visiting method {} in class {} [{}]", + m->getQualifiedNameAsString(), + m->getParent()->getQualifiedNameAsString(), (void *)m->getParent()); + + context().update(m); + + auto m_ptr = std::make_unique( + config().using_namespace()); + + common::model::namespace_ ns{m->getQualifiedNameAsString()}; + auto method_name = ns.name(); + m_ptr->set_method_name(method_name); + ns.pop_back(); + m_ptr->set_name(ns.name()); + ns.pop_back(); + + clang::Decl *parent_decl = m->getParent(); + + if (context().current_class_template_decl_) + parent_decl = context().current_class_template_decl_; + + LOG_DBG("Getting method's class with local id {}", parent_decl->getID()); + + if (!get_participant(parent_decl)) { + LOG_INFO("Cannot find parent class_ for method {} in class {}", + m->getQualifiedNameAsString(), + m->getParent()->getQualifiedNameAsString()); + return true; + } + + const auto &method_class = + get_participant(parent_decl).value(); + + m_ptr->is_void(m->getReturnType()->isVoidType()); + + m_ptr->set_class_id(method_class.id()); + m_ptr->set_class_full_name(method_class.full_name(false)); + m_ptr->set_name( + get_participant(m_ptr->class_id()).value().full_name_no_ns() + + "::" + m->getNameAsString()); + m_ptr->is_static(m->isStatic()); + + for (const auto *param : m->parameters()) { + m_ptr->add_parameter(simplify_system_template( + common::to_string(param->getType(), m->getASTContext(), false))); + } + + set_source_location(*m, *m_ptr); + + const auto method_full_name = m_ptr->full_name(false); + + m_ptr->set_id(common::to_id(method_full_name)); + + // Callee methods in call expressions are referred to by first declaration + // id + if (m->isThisDeclarationADefinition()) { + set_unique_id(m->getFirstDecl()->getID(), m_ptr->id()); + } + + set_unique_id(m->getID(), m_ptr->id()); // This is probably not necessary? + + LOG_TRACE("Set id {} --> {} for method name {} [{}]", m->getID(), + m_ptr->id(), method_full_name, m->isThisDeclarationADefinition()); + + context().update(m); + + context().set_caller_id(m_ptr->id()); + + diagram().add_participant(std::move(m_ptr)); + + return true; +} + +bool translation_unit_visitor::VisitFunctionDecl(clang::FunctionDecl *f) +{ + if (f->isCXXClassMember()) + return true; + + const auto function_name = f->getQualifiedNameAsString(); + + if (!diagram().should_include(function_name)) + return true; + + if (!f->isThisDeclarationADefinition()) { + if (f->getDefinition()) + return VisitFunctionDecl( + static_cast(f->getDefinition())); + } + + LOG_TRACE("Visiting function declaration {} at {}", function_name, + f->getLocation().printToString(source_manager())); + + if (f->isTemplated()) { + if (f->getDescribedTemplate()) { + // If the described templated of this function is already in the + // model skip it: + if (get_unique_id(f->getDescribedTemplate()->getID())) + return true; + } + } + + if (f->isFunctionTemplateSpecialization()) { + auto f_ptr = build_function_template_instantiation(*f); + + f_ptr->set_id(common::to_id(f_ptr->full_name(false))); + + f_ptr->is_void(f->getReturnType()->isVoidType()); + + context().update(f); + + context().set_caller_id(f_ptr->id()); + + if (f->isThisDeclarationADefinition()) { + set_unique_id(f->getFirstDecl()->getID(), f_ptr->id()); + } + set_unique_id(f->getID(), f_ptr->id()); + + set_source_location(*f, *f_ptr); + + // TODO: Handle overloaded functions with different arguments + diagram().add_participant(std::move(f_ptr)); + } + else { + auto f_ptr = std::make_unique( + config().using_namespace()); + + common::model::namespace_ ns{function_name}; + f_ptr->set_name(ns.name()); + ns.pop_back(); + f_ptr->set_namespace(ns); + + for (const auto *param : f->parameters()) { + f_ptr->add_parameter(simplify_system_template(common::to_string( + param->getType(), f->getASTContext(), false))); + } + + f_ptr->set_id(common::to_id(f_ptr->full_name(false))); + + f_ptr->is_void(f->getReturnType()->isVoidType()); + + context().update(f); + + context().set_caller_id(f_ptr->id()); + + if (f->isThisDeclarationADefinition()) { + set_unique_id(f->getFirstDecl()->getID(), f_ptr->id()); + } + set_unique_id(f->getID(), f_ptr->id()); + + set_source_location(*f, *f_ptr); + + // TODO: Handle overloaded functions with different arguments + diagram().add_participant(std::move(f_ptr)); + } + + return true; +} + +bool translation_unit_visitor::VisitFunctionTemplateDecl( + clang::FunctionTemplateDecl *function_template) +{ + const auto function_name = function_template->getQualifiedNameAsString(); + + if (!diagram().should_include(function_name)) + return true; + + LOG_TRACE("Visiting function template declaration {} at {}", function_name, + function_template->getLocation().printToString(source_manager())); + + auto f_ptr = std::make_unique( + config().using_namespace()); + + common::model::namespace_ ns{function_name}; + f_ptr->set_name(ns.name()); + ns.pop_back(); + f_ptr->set_namespace(ns); + + process_template_parameters(*function_template, *f_ptr); + + for (const auto *param : + function_template->getTemplatedDecl()->parameters()) { + f_ptr->add_parameter(simplify_system_template(common::to_string( + param->getType(), function_template->getASTContext(), false))); + } + + f_ptr->set_id(common::to_id(f_ptr->full_name(false))); + + f_ptr->is_void( + function_template->getAsFunction()->getReturnType()->isVoidType()); + + set_source_location(*function_template, *f_ptr); + + context().update(function_template); + + context().set_caller_id(f_ptr->id()); + + set_unique_id(function_template->getID(), f_ptr->id()); + + diagram().add_participant(std::move(f_ptr)); + + return true; +} + +bool translation_unit_visitor::VisitLambdaExpr(clang::LambdaExpr *expr) +{ + const auto lambda_full_name = + expr->getLambdaClass()->getCanonicalDecl()->getNameAsString(); + + LOG_TRACE("Visiting lambda expression {} at {}", lambda_full_name, + expr->getBeginLoc().printToString(source_manager())); + + LOG_TRACE("Lambda call operator ID {} - lambda class ID {}, class call " + "operator ID {}", + expr->getCallOperator()->getID(), expr->getLambdaClass()->getID(), + expr->getLambdaClass()->getLambdaCallOperator()->getID()); + + // Create lambda class participant + auto *cls = expr->getLambdaClass(); + auto c_ptr = create_class_declaration(cls); + + if (!c_ptr) + return true; + + const auto cls_id = c_ptr->id(); + + set_unique_id(cls->getID(), cls_id); + + // Create lambda class operator() participant + auto m_ptr = std::make_unique( + config().using_namespace()); + + common::model::namespace_ ns{c_ptr->get_namespace()}; + auto method_name = "operator()"; + m_ptr->set_method_name(method_name); + ns.pop_back(); + + m_ptr->set_class_id(cls_id); + m_ptr->set_class_full_name(c_ptr->full_name(false)); + + diagram().add_participant(std::move(c_ptr)); + + m_ptr->set_id(common::to_id( + get_participant(cls_id).value().full_name(false) + "::" + method_name)); + + context().enter_lambda_expression(m_ptr->id()); + + set_unique_id(expr->getCallOperator()->getID(), m_ptr->id()); + + diagram().add_participant(std::move(m_ptr)); + + [[maybe_unused]] const auto is_generic_lambda = expr->isGenericLambda(); + + return true; +} + +bool translation_unit_visitor::TraverseLambdaExpr(clang::LambdaExpr *expr) +{ + const auto lambda_full_name = + expr->getLambdaClass()->getCanonicalDecl()->getNameAsString(); + + RecursiveASTVisitor::TraverseLambdaExpr(expr); + + context().leave_lambda_expression(); + + return true; +} + +bool translation_unit_visitor::TraverseCallExpr(clang::CallExpr *expr) +{ + context().current_function_call_expr_ = expr; + + RecursiveASTVisitor::TraverseCallExpr(expr); + + context().current_function_call_expr_ = nullptr; + + return true; +} + +bool translation_unit_visitor::TraverseCompoundStmt(clang::CompoundStmt *stmt) +{ + using clanguml::common::model::message_t; + using clanguml::common::model::namespace_; + using clanguml::sequence_diagram::model::activity; + using clanguml::sequence_diagram::model::message; + + const auto *current_ifstmt = context().current_ifstmt(); + const auto *current_elseifstmt = context().current_elseifstmt(); + + // + // Add final else block (not else if) + // + if (current_elseifstmt != nullptr) { + if (current_elseifstmt->getElse() == stmt) { + const auto current_caller_id = context().caller_id(); + + if (current_caller_id) { + diagram() + .get_activity(current_caller_id) + .add_message({message_t::kElse, current_caller_id}); + } + } + } + else if (current_ifstmt != nullptr) { + if (current_ifstmt->getElse() == stmt) { + const auto current_caller_id = context().caller_id(); + + if (current_caller_id) { + diagram() + .get_activity(current_caller_id) + .add_message({message_t::kElse, current_caller_id}); + } + } + } + + RecursiveASTVisitor::TraverseCompoundStmt(stmt); + + return true; +} + +bool translation_unit_visitor::TraverseIfStmt(clang::IfStmt *stmt) +{ + using clanguml::common::model::message_t; + using clanguml::common::model::namespace_; + using clanguml::sequence_diagram::model::activity; + using clanguml::sequence_diagram::model::message; + + bool elseif_block{false}; + + const auto current_caller_id = context().caller_id(); + const auto *current_ifstmt = context().current_ifstmt(); + + // Check if this is a beginning of a new if statement, or an + // else if condition of the current if statement + if (current_ifstmt != nullptr) { + for (const auto *child_stmt : current_ifstmt->children()) { + if (child_stmt == stmt) { + elseif_block = true; + break; + } + } + } + + if (current_caller_id && !stmt->isConstexpr()) { + context().enter_ifstmt(stmt); + if (elseif_block) { + context().enter_elseifstmt(stmt); + diagram().add_if_stmt(current_caller_id, message_t::kElseIf); + } + else { + diagram().add_if_stmt(current_caller_id, message_t::kIf); + } + } + + RecursiveASTVisitor::TraverseIfStmt(stmt); + + if (current_caller_id && !stmt->isConstexpr() && !elseif_block) { + diagram().end_if_stmt(current_caller_id, message_t::kIfEnd); + } + + return true; +} + +bool translation_unit_visitor::TraverseWhileStmt(clang::WhileStmt *stmt) +{ + using clanguml::common::model::message_t; + using clanguml::sequence_diagram::model::activity; + using clanguml::sequence_diagram::model::message; + + const auto current_caller_id = context().caller_id(); + + if (current_caller_id) { + context().enter_loopstmt(stmt); + diagram().add_while_stmt(current_caller_id); + } + RecursiveASTVisitor::TraverseWhileStmt(stmt); + + if (current_caller_id) { + diagram().end_while_stmt(current_caller_id); + context().leave_loopstmt(); + } + + return true; +} + +bool translation_unit_visitor::TraverseDoStmt(clang::DoStmt *stmt) +{ + using clanguml::common::model::message_t; + using clanguml::sequence_diagram::model::activity; + using clanguml::sequence_diagram::model::message; + + const auto current_caller_id = context().caller_id(); + + if (current_caller_id) { + context().enter_loopstmt(stmt); + diagram().add_do_stmt(current_caller_id); + } + + RecursiveASTVisitor::TraverseDoStmt(stmt); + + if (current_caller_id) { + context().leave_loopstmt(); + diagram().end_do_stmt(current_caller_id); + } + + return true; +} + +bool translation_unit_visitor::TraverseForStmt(clang::ForStmt *stmt) +{ + using clanguml::common::model::message_t; + using clanguml::sequence_diagram::model::activity; + using clanguml::sequence_diagram::model::message; + + const auto current_caller_id = context().caller_id(); + + if (current_caller_id) { + context().enter_loopstmt(stmt); + diagram().add_for_stmt(current_caller_id); + } + + RecursiveASTVisitor::TraverseForStmt(stmt); + + if (current_caller_id) { + context().leave_loopstmt(); + diagram().end_for_stmt(current_caller_id); + } + + return true; +} + +bool translation_unit_visitor::TraverseCXXForRangeStmt( + clang::CXXForRangeStmt *stmt) +{ + using clanguml::common::model::message_t; + using clanguml::sequence_diagram::model::activity; + using clanguml::sequence_diagram::model::message; + + const auto current_caller_id = context().caller_id(); + + if (current_caller_id) { + context().enter_loopstmt(stmt); + diagram().add_for_stmt(current_caller_id); + } + + RecursiveASTVisitor::TraverseCXXForRangeStmt( + stmt); + + if (current_caller_id) { + context().leave_loopstmt(); + diagram().end_for_stmt(current_caller_id); + } return true; } @@ -78,6 +737,9 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) using clanguml::sequence_diagram::model::activity; using clanguml::sequence_diagram::model::message; + if (context().caller_id() == 0) + return true; + // Skip casts, moves and such if (expr->isCallToStdMove()) return true; @@ -88,101 +750,1247 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) if (clang::dyn_cast_or_null(expr)) return true; - // Skip if current class was excluded in the config - if (current_class_decl_ && - !diagram().should_include( - current_class_decl_->getQualifiedNameAsString())) + if (!context().valid()) return true; - // Skip if current function was excluded in the config - if (current_function_decl_ && - !diagram().should_include( - current_function_decl_->getQualifiedNameAsString())) - return true; + LOG_TRACE("Visiting call expression at {} [caller_id = {}]", + expr->getBeginLoc().printToString(source_manager()), + context().caller_id()); - message m; - m.type = message_t::kCall; + message m{message_t::kCall, context().caller_id()}; - if (current_class_decl_ != nullptr) { - // Handle call expression within some class method - assert(current_method_decl_ != nullptr); - m.from = current_class_decl_->getQualifiedNameAsString(); - m.from_usr = current_method_decl_->getID(); + set_source_location(*expr, m); + + // If we're currently inside a lambda expression, set it's id as + // message source rather then enclosing context + // Unless the lambda is declared in a function or method call + if (context().lambda_caller_id() != 0) { + if (context().current_function_call_expr_ == nullptr) { + m.set_from(context().lambda_caller_id()); + } + else { + LOG_DBG("Current lambda declaration is passed to a method or " + "function - keep the original caller id"); + } } - else { - // Handle call expression within free function - m.from = current_function_decl_->getQualifiedNameAsString() + "()"; - m.from_usr = current_function_decl_->getID(); - } - - const auto ¤t_ast_context = current_class_decl_ - ? current_class_decl_->getASTContext() - : current_function_decl_->getASTContext(); if (const auto *operator_call_expr = clang::dyn_cast_or_null(expr); operator_call_expr != nullptr) { - // TODO: Handle C++ operator calls + + if (!process_operator_call_expression(m, operator_call_expr)) + return true; } + // + // Call to a class method + // else if (const auto *method_call_expr = clang::dyn_cast_or_null(expr); method_call_expr != nullptr) { - // Get callee declaration as methods parent - const auto *method_decl = method_call_expr->getMethodDecl(); - const auto *callee_decl = - method_decl ? method_decl->getParent() : nullptr; - - if (!(callee_decl && - diagram().should_include( - callee_decl->getQualifiedNameAsString()))) + if (!process_class_method_call_expression(m, method_call_expr)) return true; - - m.to = callee_decl->getQualifiedNameAsString(); - m.to_usr = method_decl->getID(); - m.message = method_decl->getNameAsString(); - m.return_type = method_call_expr->getCallReturnType(current_ast_context) - .getAsString(); - } - else if (const auto *function_call_expr = - clang::dyn_cast_or_null(expr); - function_call_expr != nullptr) { - - const auto *callee_decl = function_call_expr->getCalleeDecl(); - - if (!callee_decl) - return true; - - const auto *callee_function = callee_decl->getAsFunction(); - - if (!callee_function) - return true; - - m.to = callee_function->getQualifiedNameAsString() + "()"; - m.message = callee_function->getNameAsString(); - m.to_usr = callee_function->getID(); - m.return_type = - function_call_expr->getCallReturnType(current_ast_context) - .getAsString(); } + // + // Call to function or template + // else { - return true; + auto *callee_decl = expr->getCalleeDecl(); + + if (callee_decl == nullptr) { + LOG_DBG("Cannot get callee declaration - trying direct callee..."); + callee_decl = expr->getDirectCallee(); + } + + if (!callee_decl) { + // + // Call to a method of a class template + // + if (clang::dyn_cast_or_null( + expr->getCallee())) { + if (!process_class_template_method_call_expression(m, expr)) { + return true; + } + } + // + // Unresolved lookup expression are sometimes calls to template + // functions + // + else if (clang::dyn_cast_or_null( + expr->getCallee())) { + if (!process_unresolved_lookup_call_expression(m, expr)) + return true; + } + } + else { + if (!process_function_call_expression(m, expr)) { + LOG_DBG("Skipping call to unsupported type of call expression " + "at: {}", + expr->getBeginLoc().printToString(source_manager())); + + return true; + } + } } - if (diagram().sequences.find(m.from_usr) == diagram().sequences.end()) { - activity a; - a.usr = m.from_usr; - a.from = m.from; - diagram().sequences.insert({m.from_usr, std::move(a)}); + // + // This crashes on LLVM <= 12, for now just return empty type + // + // const auto &return_type = + // function_call_expr->getCallReturnType(current_ast_context); + // m.return_type = return_type.getAsString(); + + if (m.from() > 0 && m.to() > 0) { + if (diagram().sequences().find(m.from()) == + diagram().sequences().end()) { + activity a{m.from()}; + diagram().sequences().insert({m.from(), std::move(a)}); + } + + diagram().add_active_participant(m.from()); + diagram().add_active_participant(m.to()); + + LOG_DBG("Found call {} from {} [{}] to {} [{}] ", m.message_name(), + m.from(), m.from(), m.to(), m.to()); + + diagram().get_activity(m.from()).add_message(std::move(m)); } - LOG_DBG("Found call {} from {} [{}] to {} [{}] ", m.message, m.from, - m.from_usr, m.to, m.to_usr); - - diagram().sequences[m.from_usr].messages.emplace_back(std::move(m)); - - assert(!diagram().sequences.empty()); - return true; } + +bool translation_unit_visitor::process_operator_call_expression( + model::message &m, const clang::CXXOperatorCallExpr *operator_call_expr) +{ + if (operator_call_expr->getCalleeDecl() == nullptr) + return false; + + // For now we only handle call overloaded operators + if (operator_call_expr->getOperator() != + clang::OverloadedOperatorKind::OO_Call) + return false; + + LOG_DBG("Operator '{}' call expression to {} at {}", + getOperatorSpelling(operator_call_expr->getOperator()), + operator_call_expr->getCalleeDecl()->getID(), + operator_call_expr->getBeginLoc().printToString(source_manager())); + + auto maybe_id = get_unique_id(operator_call_expr->getCalleeDecl()->getID()); + if (maybe_id.has_value()) { + m.set_to(maybe_id.value()); + } + else { + m.set_to(operator_call_expr->getCalleeDecl()->getID()); + } + + m.set_message_name(fmt::format( + "operator{}", getOperatorSpelling(operator_call_expr->getOperator()))); + + return true; +} + +bool translation_unit_visitor::process_class_method_call_expression( + model::message &m, const clang::CXXMemberCallExpr *method_call_expr) +{ + // Get callee declaration as methods parent + const auto *method_decl = method_call_expr->getMethodDecl(); + + if (method_decl == nullptr) + return false; + + std::string method_name = method_decl->getQualifiedNameAsString(); + + auto *callee_decl = method_decl ? method_decl->getParent() : nullptr; + + if (!(callee_decl && + diagram().should_include(callee_decl->getQualifiedNameAsString()))) + return false; + + m.set_to(method_decl->getID()); + m.set_message_name(method_decl->getNameAsString()); + m.set_return_type( + method_call_expr->getCallReturnType(*context().get_ast_context()) + .getAsString()); + + LOG_TRACE("Set callee method id {} for method name {}", m.to(), + method_decl->getQualifiedNameAsString()); + + diagram().add_active_participant(method_decl->getID()); + + return true; +} + +bool translation_unit_visitor::process_class_template_method_call_expression( + model::message &m, const clang::CallExpr *expr) +{ + auto *dependent_member_callee = + clang::dyn_cast_or_null( + expr->getCallee()); + + if (is_callee_valid_template_specialization(dependent_member_callee)) { + const auto *template_declaration = + dependent_member_callee->getBaseType() + ->getAs() + ->getTemplateName() + .getAsTemplateDecl(); + + std::string callee_method_full_name; + + // First check if the primary template is already in the + // participants map + if (get_participant(template_declaration).has_value()) { + callee_method_full_name = + get_participant(template_declaration).value().full_name(false) + + "::" + dependent_member_callee->getMember().getAsString(); + + for (const auto &[id, p] : diagram().participants()) { + const auto p_full_name = p->full_name(false); + + if (p_full_name.find(callee_method_full_name + "(") == 0) { + // TODO: This selects the first matching template method + // without considering arguments!!! + m.set_to(id); + break; + } + } + } + // Otherwise check if it is a smart pointer + else if (is_smart_pointer(template_declaration)) { + const auto *argument_template = + template_declaration->getTemplateParameters() + ->asArray() + .front(); + + if (get_participant(argument_template).has_value()) { + callee_method_full_name = get_participant(argument_template) + .value() + .full_name(false) + + "::" + dependent_member_callee->getMember().getAsString(); + + for (const auto &[id, p] : diagram().participants()) { + const auto p_full_name = p->full_name(false); + if (p_full_name.find(callee_method_full_name + "(") == 0) { + // TODO: This selects the first matching template method + // without considering arguments!!! + m.set_to(id); + break; + } + } + } + else + return false; + } + + m.set_message_name(dependent_member_callee->getMember().getAsString()); + + if (get_unique_id(template_declaration->getID())) + diagram().add_active_participant( + get_unique_id(template_declaration->getID()).value()); + } + else { + LOG_DBG("Skipping call due to unresolvable " + "CXXDependentScopeMemberExpr at {}", + expr->getBeginLoc().printToString(source_manager())); + } + + return true; +} + +bool translation_unit_visitor::process_function_call_expression( + model::message &m, const clang::CallExpr *expr) +{ + const auto *callee_decl = expr->getCalleeDecl(); + + if (callee_decl == nullptr) + return false; + + const auto *callee_function = callee_decl->getAsFunction(); + + if (!callee_function) + return false; + + auto callee_name = callee_function->getQualifiedNameAsString() + "()"; + + if (!diagram().should_include(callee_name)) + return false; + + std::unique_ptr f_ptr; + + if (!get_unique_id(callee_function->getID()).has_value()) { + // This is hopefully not an interesting call... + m.set_to(callee_function->getID()); + } + else { + m.set_to(get_unique_id(callee_function->getID()).value()); + } + + auto message_name = callee_name; + m.set_message_name(message_name.substr(0, message_name.size() - 2)); + + if (f_ptr) + diagram().add_participant(std::move(f_ptr)); + + return true; +} + +bool translation_unit_visitor::process_unresolved_lookup_call_expression( + model::message &m, const clang::CallExpr *expr) +{ + // This is probably a template + auto *unresolved_expr = + clang::dyn_cast_or_null(expr->getCallee()); + + if (unresolved_expr) { + for (const auto *decl : unresolved_expr->decls()) { + if (clang::dyn_cast_or_null(decl)) { + // Yes, it's a template + auto *ftd = + clang::dyn_cast_or_null(decl); + + if (!get_unique_id(ftd->getID()).has_value()) + m.set_to(ftd->getID()); + else { + m.set_to(get_unique_id(ftd->getID()).value()); + } + + break; + } + } + } + + return true; +} + +bool translation_unit_visitor::is_callee_valid_template_specialization( + const clang::CXXDependentScopeMemberExpr *dependent_member_expr) const +{ + const bool base_type_is_not_null = + !dependent_member_expr->getBaseType().isNull(); + + const bool base_type_is_specialization_type = + dependent_member_expr->getBaseType() + ->getAs() != nullptr; + + const bool base_type_is_not_pointer_type = + base_type_is_specialization_type && + !dependent_member_expr->getBaseType() + ->getAs() + ->isPointerType(); + + return (base_type_is_not_null && base_type_is_specialization_type && + base_type_is_not_pointer_type); +} + +bool translation_unit_visitor::is_smart_pointer( + const clang::TemplateDecl *primary_template) const +{ + return primary_template->getQualifiedNameAsString().find( + "std::unique_ptr") == 0 || + primary_template->getQualifiedNameAsString().find("std::shared_ptr") == + 0 || + primary_template->getQualifiedNameAsString().find("std::weak_ptr") == 0; +} + +std::unique_ptr +translation_unit_visitor::create_class_declaration(clang::CXXRecordDecl *cls) +{ + assert(cls != nullptr); + + auto c_ptr{std::make_unique( + config().using_namespace())}; + auto &c = *c_ptr; + + // TODO: refactor to method get_qualified_name() + auto qualified_name = + cls->getQualifiedNameAsString(); // common::get_qualified_name(*cls); + + if (!cls->isLambda()) + if (!diagram().should_include(qualified_name)) + return {}; + + auto ns = common::get_tag_namespace(*cls); + + if (cls->isLambda() && + !diagram().should_include(ns.to_string() + "::lambda")) + return {}; + + const auto *parent = cls->getParent(); + + if (parent && parent->isRecord()) { + // Here we have 2 options, either: + // - the parent is a regular C++ class/struct + // - the parent is a class template declaration/specialization + std::optional id_opt; + int64_t local_id = + static_cast(parent)->getID(); + + // First check if the parent has been added to the diagram as + // regular class + id_opt = get_unique_id(local_id); + + // If not, check if the parent template declaration is in the model + if (!id_opt) { + local_id = static_cast(parent) + ->getDescribedTemplate() + ->getID(); + if (static_cast(parent) + ->getDescribedTemplate()) + id_opt = get_unique_id(local_id); + } + + assert(id_opt); + + auto parent_class = + diagram_.get_participant( + *id_opt); + + assert(parent_class); + + c.set_namespace(ns); + if (cls->getNameAsString().empty()) { + // Nested structs can be anonymous + if (anonymous_struct_relationships_.count(cls->getID()) > 0) { + const auto &[label, hint, access] = + anonymous_struct_relationships_[cls->getID()]; + + c.set_name(parent_class.value().name() + "##" + + fmt::format("({})", label)); + + parent_class.value().add_relationship( + {hint, common::to_id(c.full_name(false)), access, label}); + } + else + c.set_name(parent_class.value().name() + "##" + + fmt::format( + "(anonymous_{})", std::to_string(cls->getID()))); + } + else { + c.set_name( + parent_class.value().name() + "##" + cls->getNameAsString()); + } + + c.set_id(common::to_id(c.full_name(false))); + + c.nested(true); + } + else if (cls->isLambda()) { + c.is_lambda(true); + if (cls->getParent()) { + const auto type_name = make_lambda_name(cls); + + c.set_name(type_name); + c.set_namespace(ns); + c.set_id(common::to_id(c.full_name(false))); + + // Check if lambda is declared as an argument passed to a + // function/method call + } + else { + LOG_WARN("Cannot find parent declaration for lambda {}", + cls->getQualifiedNameAsString()); + return {}; + } + } + else { + c.set_name(common::get_tag_name(*cls)); + c.set_namespace(ns); + c.set_id(common::to_id(c.full_name(false))); + } + + c.is_struct(cls->isStruct()); + + process_comment(*cls, c); + set_source_location(*cls, c); + + if (c.skip()) + return {}; + + c.set_style(c.style_spec()); + + return c_ptr; +} + +bool translation_unit_visitor::process_template_parameters( + const clang::TemplateDecl &template_declaration, + sequence_diagram::model::template_trait &c) +{ + using class_diagram::model::template_parameter; + + LOG_TRACE("Processing class {} template parameters...", + common::get_qualified_name(template_declaration)); + + if (template_declaration.getTemplateParameters() == nullptr) + return false; + + for (const auto *parameter : + *template_declaration.getTemplateParameters()) { + if (clang::dyn_cast_or_null(parameter)) { + const auto *template_type_parameter = + clang::dyn_cast_or_null(parameter); + template_parameter ct; + ct.set_type(""); + ct.is_template_parameter(true); + ct.set_name(template_type_parameter->getNameAsString()); + ct.set_default_value(""); + ct.is_variadic(template_type_parameter->isParameterPack()); + + c.add_template(std::move(ct)); + } + else if (clang::dyn_cast_or_null( + parameter)) { + const auto *template_nontype_parameter = + clang::dyn_cast_or_null( + parameter); + template_parameter ct; + ct.set_type(template_nontype_parameter->getType().getAsString()); + ct.set_name(template_nontype_parameter->getNameAsString()); + ct.is_template_parameter(false); + ct.set_default_value(""); + ct.is_variadic(template_nontype_parameter->isParameterPack()); + + c.add_template(std::move(ct)); + } + else if (clang::dyn_cast_or_null( + parameter)) { + const auto *template_template_parameter = + clang::dyn_cast_or_null( + parameter); + template_parameter ct; + ct.set_type(""); + ct.set_name(template_template_parameter->getNameAsString() + "<>"); + ct.is_template_parameter(true); + ct.set_default_value(""); + ct.is_variadic(template_template_parameter->isParameterPack()); + + c.add_template(std::move(ct)); + } + else { + // pass + } + } + + return false; +} + +void translation_unit_visitor::set_unique_id( + int64_t local_id, common::model::diagram_element::id_t global_id) +{ + LOG_TRACE("Setting local element mapping {} --> {}", local_id, global_id); + + local_ast_id_map_[local_id] = global_id; +} + +std::optional +translation_unit_visitor::get_unique_id(int64_t local_id) const +{ + if (local_ast_id_map_.find(local_id) == local_ast_id_map_.end()) + return {}; + + return local_ast_id_map_.at(local_id); +} + +std::unique_ptr +translation_unit_visitor::build_function_template_instantiation( + const clang::FunctionDecl &decl) +{ + // + // Here we'll hold the template base params to replace with the + // instantiated values + // + std::deque> + template_base_params{}; + + auto template_instantiation_ptr = + std::make_unique(config_.using_namespace()); + auto &template_instantiation = *template_instantiation_ptr; + + // + // Set function template instantiation name + // + auto template_decl_qualified_name = decl.getQualifiedNameAsString(); + common::model::namespace_ ns{template_decl_qualified_name}; + ns.pop_back(); + template_instantiation.set_name(decl.getNameAsString()); + template_instantiation.set_namespace(ns); + + // + // Instantiate the template arguments + // + model::template_trait *parent{nullptr}; + build_template_instantiation_process_template_arguments(parent, + template_base_params, decl.getTemplateSpecializationArgs()->asArray(), + template_instantiation, "", decl.getPrimaryTemplate()); + + for (const auto *param : decl.parameters()) { + template_instantiation_ptr->add_parameter( + common::to_string(param->getType(), decl.getASTContext())); + } + + return template_instantiation_ptr; +} + +void translation_unit_visitor:: + build_template_instantiation_process_template_arguments( + model::template_trait *parent, + std::deque> &template_base_params, + const clang::ArrayRef &template_args, + model::template_trait &template_instantiation, + const std::string &full_template_specialization_name, + const clang::TemplateDecl *template_decl) +{ + auto arg_index = 0U; + for (const auto &arg : template_args) { + const auto argument_kind = arg.getKind(); + class_diagram::model::template_parameter argument; + if (argument_kind == clang::TemplateArgument::Template) { + build_template_instantiation_process_template_argument( + arg, argument); + } + else if (argument_kind == clang::TemplateArgument::Type) { + build_template_instantiation_process_type_argument(parent, + full_template_specialization_name, template_decl, arg, + template_instantiation, argument); + } + else if (argument_kind == clang::TemplateArgument::Integral) { + build_template_instantiation_process_integral_argument( + arg, argument); + } + else if (argument_kind == clang::TemplateArgument::Expression) { + build_template_instantiation_process_expression_argument( + arg, argument); + } + else { + LOG_INFO("Unsupported argument type {}", arg.getKind()); + } + + simplify_system_template( + argument, argument.to_string(config().using_namespace(), false)); + + template_instantiation.add_template(std::move(argument)); + + arg_index++; + } +} + +void translation_unit_visitor:: + build_template_instantiation_process_template_argument( + const clang::TemplateArgument &arg, + class_diagram::model::template_parameter &argument) const +{ + argument.is_template_parameter(true); + auto arg_name = + arg.getAsTemplate().getAsTemplateDecl()->getQualifiedNameAsString(); + argument.set_type(arg_name); +} + +void translation_unit_visitor:: + build_template_instantiation_process_integral_argument( + const clang::TemplateArgument &arg, + class_diagram::model::template_parameter &argument) const +{ + assert(arg.getKind() == clang::TemplateArgument::Integral); + + argument.is_template_parameter(false); + argument.set_type(std::to_string(arg.getAsIntegral().getExtValue())); +} + +void translation_unit_visitor:: + build_template_instantiation_process_expression_argument( + const clang::TemplateArgument &arg, + class_diagram::model::template_parameter &argument) const +{ + assert(arg.getKind() == clang::TemplateArgument::Expression); + + argument.is_template_parameter(false); + argument.set_type(common::get_source_text( + arg.getAsExpr()->getSourceRange(), source_manager())); +} + +void translation_unit_visitor:: + build_template_instantiation_process_tag_argument( + model::template_trait &template_instantiation, + const std::string &full_template_specialization_name, + const clang::TemplateDecl *template_decl, + const clang::TemplateArgument &arg, + class_diagram::model::template_parameter &argument) const +{ + assert(arg.getKind() == clang::TemplateArgument::Type); + + argument.is_template_parameter(false); + + argument.set_name( + common::to_string(arg.getAsType(), template_decl->getASTContext())); +} + +void translation_unit_visitor:: + build_template_instantiation_process_type_argument( + model::template_trait *parent, + const std::string &full_template_specialization_name, + const clang::TemplateDecl *template_decl, + const clang::TemplateArgument &arg, + model::template_trait &template_instantiation, + class_diagram::model::template_parameter &argument) +{ + assert(arg.getKind() == clang::TemplateArgument::Type); + + argument.is_template_parameter(false); + + // If this is a nested template type - add nested templates as + // template arguments + if (arg.getAsType()->getAs()) { + + // for (const auto ¶m_type : + // arg.getAsType()->getAs()->param_types()) + // { + // + // if (!param_type->getAs()) + // continue; + // + // auto classTemplateSpecialization = + // llvm::dyn_cast( + // param_type->getAsRecordDecl()); + // + // if (classTemplateSpecialization) { + // // Read arg info as needed. + // auto nested_template_instantiation = + // build_template_instantiation_from_class_template_specialization( + // *classTemplateSpecialization, + // *param_type->getAs(), + // diagram().should_include( + // full_template_specialization_name) + // ? + // std::make_optional(&template_instantiation) + // : parent); + // } + // } + } + else if (arg.getAsType()->getAs()) { + const auto *nested_template_type = + arg.getAsType()->getAs(); + + const auto nested_template_name = + nested_template_type->getTemplateName() + .getAsTemplateDecl() + ->getQualifiedNameAsString(); + + auto [tinst_ns, tinst_name] = cx::util::split_ns(nested_template_name); + + argument.set_name(nested_template_name); + + // auto nested_template_instantiation = + // build_template_instantiation( + // *arg.getAsType()->getAs(), + // diagram().should_include(full_template_specialization_name) + // ? std::make_optional(&template_instantiation) + // : parent); + // + // argument.set_id(nested_template_instantiation->id()); + // + // for (const auto &t : + // nested_template_instantiation->templates()) + // argument.add_template_param(t); + + // Check if this template should be simplified (e.g. system + // template aliases such as 'std:basic_string' should + // be simply 'std::string') + simplify_system_template( + argument, argument.to_string(config().using_namespace(), false)); + } + else if (arg.getAsType()->getAs()) { + argument.is_template_parameter(true); + argument.set_name( + common::to_string(arg.getAsType(), template_decl->getASTContext())); + } + else { + // This is just a regular record type + build_template_instantiation_process_tag_argument( + template_instantiation, full_template_specialization_name, + template_decl, arg, argument); + } +} + +std::unique_ptr +translation_unit_visitor::process_template_specialization( + clang::ClassTemplateSpecializationDecl *cls) +{ + auto c_ptr{std::make_unique(config_.using_namespace())}; + auto &template_instantiation = *c_ptr; + + // TODO: refactor to method get_qualified_name() + auto qualified_name = cls->getQualifiedNameAsString(); + util::replace_all(qualified_name, "(anonymous namespace)", ""); + util::replace_all(qualified_name, "::::", "::"); + + common::model::namespace_ ns{qualified_name}; + ns.pop_back(); + template_instantiation.set_name(cls->getNameAsString()); + template_instantiation.set_namespace(ns); + + template_instantiation.is_struct(cls->isStruct()); + + process_comment(*cls, template_instantiation); + set_source_location(*cls, template_instantiation); + + if (template_instantiation.skip()) + return {}; + + const auto template_args_count = cls->getTemplateArgs().size(); + for (auto arg_it = 0U; arg_it < template_args_count; arg_it++) { + const auto arg = cls->getTemplateArgs().get(arg_it); + process_template_specialization_argument( + cls, template_instantiation, arg, arg_it); + } + + template_instantiation.set_id( + common::to_id(template_instantiation.full_name(false))); + + set_unique_id(cls->getID(), template_instantiation.id()); + + return c_ptr; +} + +void translation_unit_visitor::process_template_specialization_argument( + const clang::ClassTemplateSpecializationDecl *cls, + model::class_ &template_instantiation, const clang::TemplateArgument &arg, + size_t argument_index, bool in_parameter_pack) +{ + const auto argument_kind = arg.getKind(); + + if (argument_kind == clang::TemplateArgument::Type) { + class_diagram::model::template_parameter argument; + argument.is_template_parameter(false); + + // If this is a nested template type - add nested templates as + // template arguments + if (arg.getAsType()->getAs()) { + const auto *nested_template_type = + arg.getAsType()->getAs(); + + const auto nested_template_name = + nested_template_type->getTemplateName() + .getAsTemplateDecl() + ->getQualifiedNameAsString(); + + argument.set_name(nested_template_name); + + auto nested_template_instantiation = build_template_instantiation( + *arg.getAsType()->getAs(), + &template_instantiation); + + argument.set_id(nested_template_instantiation->id()); + + for (const auto &t : nested_template_instantiation->templates()) + argument.add_template_param(t); + + // Check if this template should be simplified (e.g. system + // template aliases such as 'std:basic_string' should be + // simply 'std::string') + simplify_system_template(argument, + argument.to_string(config().using_namespace(), false)); + } + else if (arg.getAsType()->getAs()) { + auto type_name = + common::to_string(arg.getAsType(), cls->getASTContext()); + + // clang does not provide declared template parameter/argument + // names in template specializations - so we have to extract + // them from raw source code... + if (type_name.find("type-parameter-") == 0) { + auto declaration_text = common::get_source_text_raw( + cls->getSourceRange(), source_manager()); + + declaration_text = declaration_text.substr( + declaration_text.find(cls->getNameAsString()) + + cls->getNameAsString().size() + 1); + + auto template_params = + cx::util::parse_unexposed_template_params( + declaration_text, [](const auto &t) { return t; }); + + if (template_params.size() > argument_index) + type_name = template_params[argument_index].to_string( + config().using_namespace(), false); + else { + LOG_DBG("Failed to find type specialization for argument " + "{} at index {} in declaration \n===\n{}\n===\n", + type_name, argument_index, declaration_text); + } + } + + argument.set_name(type_name); + } + else if (arg.getAsType()->getAsCXXRecordDecl() && + arg.getAsType()->getAsCXXRecordDecl()->isLambda()) { + if (get_unique_id(arg.getAsType()->getAsCXXRecordDecl()->getID()) + .has_value()) { + argument.set_name(get_participant( + get_unique_id( + arg.getAsType()->getAsCXXRecordDecl()->getID()) + .value()) + .value() + .full_name(false)); + } + else { + const auto type_name = + make_lambda_name(arg.getAsType()->getAsCXXRecordDecl()); + argument.set_name(type_name); + } + } + else { + auto type_name = + common::to_string(arg.getAsType(), cls->getASTContext()); + if (type_name.find('<') != std::string::npos) { + // Sometimes template instantiation is reported as + // RecordType in the AST and getAs to + // TemplateSpecializationType returns null pointer so we + // have to at least make sure it's properly formatted + // (e.g. std:integral_constant, or any template + // specialization which contains it - see t00038) + process_unexposed_template_specialization_parameters( + type_name.substr(type_name.find('<') + 1, + type_name.size() - (type_name.find('<') + 2)), + argument, template_instantiation); + + argument.set_name(type_name.substr(0, type_name.find('<'))); + } + else if (type_name.find("type-parameter-") == 0) { + auto declaration_text = common::get_source_text_raw( + cls->getSourceRange(), source_manager()); + + declaration_text = declaration_text.substr( + declaration_text.find(cls->getNameAsString()) + + cls->getNameAsString().size() + 1); + + auto template_params = + cx::util::parse_unexposed_template_params( + declaration_text, [](const auto &t) { return t; }); + + if (template_params.size() > argument_index) + type_name = template_params[argument_index].to_string( + config().using_namespace(), false); + else { + LOG_DBG("Failed to find type specialization for argument " + "{} at index {} in declaration \n===\n{}\n===\n", + type_name, argument_index, declaration_text); + } + + // Otherwise just set the name for the template argument to + // whatever clang says + argument.set_name(type_name); + } + else + argument.set_name(type_name); + } + + LOG_TRACE("Adding template instantiation argument {}", + argument.to_string(config().using_namespace(), false)); + + simplify_system_template( + argument, argument.to_string(config().using_namespace(), false)); + + template_instantiation.add_template(std::move(argument)); + } + else if (argument_kind == clang::TemplateArgument::Integral) { + class_diagram::model::template_parameter argument; + argument.is_template_parameter(false); + argument.set_type(std::to_string(arg.getAsIntegral().getExtValue())); + template_instantiation.add_template(std::move(argument)); + } + else if (argument_kind == clang::TemplateArgument::Expression) { + class_diagram::model::template_parameter argument; + argument.is_template_parameter(false); + argument.set_type(common::get_source_text( + arg.getAsExpr()->getSourceRange(), source_manager())); + template_instantiation.add_template(std::move(argument)); + } + else if (argument_kind == clang::TemplateArgument::TemplateExpansion) { + class_diagram::model::template_parameter argument; + argument.is_template_parameter(true); + + cls->getLocation().dump(source_manager()); + } + else if (argument_kind == clang::TemplateArgument::Pack) { + // This will only work for now if pack is at the end + size_t argument_pack_index{argument_index}; + for (const auto &template_argument : arg.getPackAsArray()) { + process_template_specialization_argument(cls, + template_instantiation, template_argument, + argument_pack_index++, true); + } + } + else { + LOG_INFO("Unsupported template argument kind {} [{}]", arg.getKind(), + cls->getLocation().printToString(source_manager())); + } +} + +std::unique_ptr +translation_unit_visitor::build_template_instantiation( + const clang::TemplateSpecializationType &template_type_decl, + model::class_ *parent) +{ + // TODO: Make sure we only build instantiation once + + // + // Here we'll hold the template base params to replace with the + // instantiated values + // + std::deque> + template_base_params{}; + + auto *template_type_ptr = &template_type_decl; + if (template_type_decl.isTypeAlias() && + template_type_decl.getAliasedType() + ->getAs()) + template_type_ptr = template_type_decl.getAliasedType() + ->getAs(); + + auto &template_type = *template_type_ptr; + + // + // Create class_ instance to hold the template instantiation + // + auto template_instantiation_ptr = + std::make_unique(config_.using_namespace()); + auto &template_instantiation = *template_instantiation_ptr; + std::string full_template_specialization_name = common::to_string( + template_type.desugar(), + template_type.getTemplateName().getAsTemplateDecl()->getASTContext()); + + auto *template_decl{template_type.getTemplateName().getAsTemplateDecl()}; + + auto template_decl_qualified_name = + template_decl->getQualifiedNameAsString(); + + auto *class_template_decl{ + clang::dyn_cast(template_decl)}; + + if (class_template_decl && class_template_decl->getTemplatedDecl() && + class_template_decl->getTemplatedDecl()->getParent() && + class_template_decl->getTemplatedDecl()->getParent()->isRecord()) { + + common::model::namespace_ ns{ + common::get_tag_namespace(*class_template_decl->getTemplatedDecl() + ->getParent() + ->getOuterLexicalRecordContext())}; + + std::string ns_str = ns.to_string(); + std::string name = template_decl->getQualifiedNameAsString(); + if (!ns_str.empty()) { + name = name.substr(ns_str.size() + 2); + } + + util::replace_all(name, "::", "##"); + template_instantiation.set_name(name); + + template_instantiation.set_namespace(ns); + } + else { + common::model::namespace_ ns{template_decl_qualified_name}; + ns.pop_back(); + template_instantiation.set_name(template_decl->getNameAsString()); + template_instantiation.set_namespace(ns); + } + + // TODO: Refactor handling of base parameters to a separate method + + // We need this to match any possible base classes coming from template + // arguments + std::vector< + std::pair> + template_parameter_names{}; + + for (const auto *parameter : *template_decl->getTemplateParameters()) { + if (parameter->isTemplateParameter() && + (parameter->isTemplateParameterPack() || + parameter->isParameterPack())) { + template_parameter_names.emplace_back( + parameter->getNameAsString(), true); + } + else + template_parameter_names.emplace_back( + parameter->getNameAsString(), false); + } + + // Check if the primary template has any base classes + int base_index = 0; + + const auto *templated_class_decl = + clang::dyn_cast_or_null( + template_decl->getTemplatedDecl()); + + if (templated_class_decl && templated_class_decl->hasDefinition()) + for (const auto &base : templated_class_decl->bases()) { + const auto base_class_name = common::to_string( + base.getType(), templated_class_decl->getASTContext(), false); + + LOG_DBG("Found template instantiation base: {}, {}", + base_class_name, base_index); + + // Check if any of the primary template arguments has a + // parameter equal to this type + auto it = std::find_if(template_parameter_names.begin(), + template_parameter_names.end(), + [&base_class_name]( + const auto &p) { return p.first == base_class_name; }); + + if (it != template_parameter_names.end()) { + const auto ¶meter_name = it->first; + const bool is_variadic = it->second; + // Found base class which is a template parameter + LOG_DBG("Found base class which is a template parameter " + "{}, {}, {}", + parameter_name, is_variadic, + std::distance(template_parameter_names.begin(), it)); + + template_base_params.emplace_back(parameter_name, + std::distance(template_parameter_names.begin(), it), + is_variadic); + } + else { + // This is a regular base class - it is handled by + // process_template + } + base_index++; + } + + build_template_instantiation_process_template_arguments(parent, + template_base_params, template_type.template_arguments(), + template_instantiation, full_template_specialization_name, + template_decl); + + // First try to find the best match for this template in partially + // specialized templates + std::string destination{}; + std::string best_match_full_name{}; + auto full_template_name = template_instantiation.full_name(false); + int best_match{}; + common::model::diagram_element::id_t best_match_id{0}; + + for (const auto &[id, c] : diagram().participants()) { + const auto *participant_as_class = + dynamic_cast(c.get()); + if ((participant_as_class != nullptr) && + (*participant_as_class == template_instantiation)) + continue; + + auto c_full_name = participant_as_class->full_name(false); + auto match = + participant_as_class->calculate_template_specialization_match( + template_instantiation, template_instantiation.name_and_ns()); + + if (match > best_match) { + best_match = match; + best_match_full_name = c_full_name; + best_match_id = participant_as_class->id(); + } + } + + auto templated_decl_id = + template_type.getTemplateName().getAsTemplateDecl()->getID(); + // auto templated_decl_local_id = + // get_unique_id(templated_decl_id).value_or(0); + + if (best_match_id > 0) { + destination = best_match_full_name; + } + else { + LOG_DBG("Cannot determine global id for specialization template {} " + "- delaying until the translation unit is complete ", + templated_decl_id); + } + + template_instantiation.set_id( + common::to_id(template_instantiation_ptr->full_name(false))); + + return template_instantiation_ptr; +} + +void translation_unit_visitor:: + process_unexposed_template_specialization_parameters( + const std::string &type_name, + class_diagram::model::template_parameter &tp, model::class_ &c) const +{ + auto template_params = cx::util::parse_unexposed_template_params( + type_name, [](const std::string &t) { return t; }); + + for (auto ¶m : template_params) { + tp.add_template_param(param); + } +} + +bool translation_unit_visitor::simplify_system_template( + class_diagram::model::template_parameter &ct, const std::string &full_name) +{ + if (config().type_aliases().count(full_name) > 0) { + ct.set_name(config().type_aliases().at(full_name)); + ct.clear_params(); + return true; + } + else + return false; +} + +std::string translation_unit_visitor::simplify_system_template( + const std::string &full_name) const +{ + std::string result{full_name}; + for (const auto &[k, v] : config().type_aliases()) { + util::replace_all(result, k, v); + } + + return result; +} + +std::string translation_unit_visitor::make_lambda_name( + const clang::CXXRecordDecl *cls) const +{ + std::string result; + const auto location = cls->getLocation(); + const auto file_line = source_manager().getSpellingLineNumber(location); + const auto file_column = source_manager().getSpellingColumnNumber(location); + const std::string file_name = + util::split(source_manager().getFilename(location).str(), "/").back(); + + if (context().caller_id() != 0 && + get_participant(context().caller_id()).has_value()) { + auto parent_full_name = + get_participant(context().caller_id()).value().full_name_no_ns(); + + result = fmt::format("{}##(lambda {}:{}:{})", parent_full_name, + file_name, file_line, file_column); + } + else { + result = + fmt::format("(lambda {}:{}:{})", file_name, file_line, file_column); + } + + return result; +} + +void translation_unit_visitor::finalize() +{ + std::set active_participants_unique; + + for (auto id : diagram().active_participants()) { + if (local_ast_id_map_.find(id) != local_ast_id_map_.end()) { + active_participants_unique.emplace(local_ast_id_map_.at(id)); + } + else { + active_participants_unique.emplace(id); + } + } + + diagram().active_participants() = std::move(active_participants_unique); + + for (auto &[id, activity] : diagram().sequences()) { + for (auto &m : activity.messages()) { + if (local_ast_id_map_.find(m.to()) != local_ast_id_map_.end()) { + m.set_to(local_ast_id_map_.at(m.to())); + } + } + } +} } diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.h b/src/sequence_diagram/visitor/translation_unit_visitor.h index a5e3fbf5..83eb4dbc 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.h +++ b/src/sequence_diagram/visitor/translation_unit_visitor.h @@ -17,6 +17,8 @@ */ #pragma once +#include "call_expression_context.h" +#include "common/visitor/translation_unit_visitor.h" #include "config/config.h" #include "sequence_diagram/model/diagram.h" @@ -24,31 +26,212 @@ #include #include +#include + namespace clanguml::sequence_diagram::visitor { +std::string to_string(const clang::FunctionTemplateDecl *decl); + class translation_unit_visitor - : public clang::RecursiveASTVisitor { + : public clang::RecursiveASTVisitor, + public common::visitor::translation_unit_visitor { public: translation_unit_visitor(clang::SourceManager &sm, clanguml::sequence_diagram::model::diagram &diagram, const clanguml::config::sequence_diagram &config); - virtual bool VisitCallExpr(clang::CallExpr *expr); + bool shouldVisitTemplateInstantiations(); - virtual bool VisitCXXMethodDecl(clang::CXXMethodDecl *method); + bool VisitCallExpr(clang::CallExpr *expr); - virtual bool VisitCXXRecordDecl(clang::CXXRecordDecl *cls); + bool TraverseCallExpr(clang::CallExpr *expr); - virtual bool VisitFunctionDecl(clang::FunctionDecl *function_declaration); + bool VisitLambdaExpr(clang::LambdaExpr *expr); + + bool TraverseLambdaExpr(clang::LambdaExpr *expr); + + bool VisitCXXMethodDecl(clang::CXXMethodDecl *method); + + bool VisitCXXRecordDecl(clang::CXXRecordDecl *cls); + + bool VisitClassTemplateDecl(clang::ClassTemplateDecl *cls); + + bool VisitClassTemplateSpecializationDecl( + clang::ClassTemplateSpecializationDecl *cls); + + bool VisitFunctionDecl(clang::FunctionDecl *function_declaration); + + bool VisitFunctionTemplateDecl( + clang::FunctionTemplateDecl *function_declaration); + + bool TraverseCompoundStmt(clang::CompoundStmt *stmt); + + bool TraverseIfStmt(clang::IfStmt *stmt); + + bool TraverseWhileStmt(clang::WhileStmt *stmt); + + bool TraverseDoStmt(clang::DoStmt *stmt); + + bool TraverseForStmt(clang::ForStmt *stmt); + + bool TraverseCXXForRangeStmt(clang::CXXForRangeStmt *stmt); clanguml::sequence_diagram::model::diagram &diagram(); + const clanguml::sequence_diagram::model::diagram &diagram() const; + const clanguml::config::sequence_diagram &config() const; - void finalize() { } + call_expression_context &context(); + + const call_expression_context &context() const; + + void finalize(); + + template + common::optional_ref get_participant(const clang::Decl *decl) + { + assert(decl != nullptr); + + auto unique_participant_id = get_unique_id(decl->getID()); + if (!unique_participant_id.has_value()) + return {}; + + return get_participant(unique_participant_id.value()); + } + + template + const common::optional_ref get_participant(const clang::Decl *decl) const + { + assert(decl != nullptr); + + auto unique_participant_id = get_unique_id(decl->getID()); + if (!unique_participant_id.has_value()) + return {}; + + return get_participant(unique_participant_id.value()); + } + + template + common::optional_ref get_participant( + const common::model::diagram_element::id_t id) + { + if (diagram().participants().find(id) == diagram().participants().end()) + return {}; + + return common::optional_ref( + *(static_cast(diagram().participants().at(id).get()))); + } + + template + const common::optional_ref get_participant( + const common::model::diagram_element::id_t id) const + { + if (diagram().participants().find(id) == diagram().participants().end()) + return {}; + + return common::optional_ref( + *(static_cast(diagram().participants().at(id).get()))); + } + + /// Store the mapping from local clang entity id (obtained using + /// getID()) method to clang-uml global id + void set_unique_id( + int64_t local_id, common::model::diagram_element::id_t global_id); + + /// Retrieve the global clang-uml entity id based on the clang local id + std::optional get_unique_id( + int64_t local_id) const; private: - clang::SourceManager &source_manager_; + std::unique_ptr + create_class_declaration(clang::CXXRecordDecl *cls); + + bool process_template_parameters( + const clang::TemplateDecl &template_declaration, + sequence_diagram::model::template_trait &c); + + std::unique_ptr + build_function_template_instantiation(const clang::FunctionDecl &pDecl); + + void build_template_instantiation_process_template_arguments( + model::template_trait *parent, + std::deque> &template_base_params, + const clang::ArrayRef &template_args, + model::template_trait &template_instantiation, + const std::string &full_template_specialization_name, + const clang::TemplateDecl *template_decl); + + void build_template_instantiation_process_template_argument( + const clang::TemplateArgument &arg, + class_diagram::model::template_parameter &argument) const; + + void build_template_instantiation_process_integral_argument( + const clang::TemplateArgument &arg, + class_diagram::model::template_parameter &argument) const; + + void build_template_instantiation_process_expression_argument( + const clang::TemplateArgument &arg, + class_diagram::model::template_parameter &argument) const; + + void build_template_instantiation_process_tag_argument( + model::template_trait &template_instantiation, + const std::string &full_template_specialization_name, + const clang::TemplateDecl *template_decl, + const clang::TemplateArgument &arg, + class_diagram::model::template_parameter &argument) const; + + void build_template_instantiation_process_type_argument( + model::template_trait *parent, + const std::string &full_template_specialization_name, + const clang::TemplateDecl *template_decl, + const clang::TemplateArgument &arg, + model::template_trait &template_instantiation, + class_diagram::model::template_parameter &argument); + + std::unique_ptr process_template_specialization( + clang::ClassTemplateSpecializationDecl *cls); + + void process_template_specialization_argument( + const clang::ClassTemplateSpecializationDecl *cls, + model::class_ &template_instantiation, + const clang::TemplateArgument &arg, size_t argument_index, + bool in_parameter_pack = false); + + void process_unexposed_template_specialization_parameters( + const std::string &type_name, + class_diagram::model::template_parameter &tp, model::class_ &c) const; + + std::unique_ptr build_template_instantiation( + const clang::TemplateSpecializationType &template_type_decl, + model::class_ *parent); + + bool simplify_system_template(class_diagram::model::template_parameter &ct, + const std::string &full_name); + + std::string simplify_system_template(const std::string &full_name) const; + + std::string make_lambda_name(const clang::CXXRecordDecl *cls) const; + + bool is_smart_pointer(const clang::TemplateDecl *primary_template) const; + + bool is_callee_valid_template_specialization( + const clang::CXXDependentScopeMemberExpr *dependent_member_expr) const; + + bool process_operator_call_expression(model::message &m, + const clang::CXXOperatorCallExpr *operator_call_expr); + + bool process_class_method_call_expression( + model::message &m, const clang::CXXMemberCallExpr *operator_call_expr); + + bool process_class_template_method_call_expression( + model::message &m, const clang::CallExpr *expr); + + bool process_function_call_expression( + model::message &m, const clang::CallExpr *expr); + + bool process_unresolved_lookup_call_expression( + model::message &m, const clang::CallExpr *expr); // Reference to the output diagram model clanguml::sequence_diagram::model::diagram &diagram_; @@ -56,9 +239,19 @@ private: // Reference to class diagram config const clanguml::config::sequence_diagram &config_; - clang::CXXRecordDecl *current_class_decl_; - clang::CXXMethodDecl *current_method_decl_; - clang::FunctionDecl *current_function_decl_; -}; + call_expression_context call_expression_context_; + std::map> + forward_declarations_; + + std::mapgetID() */ int64_t, + /* global ID based on full name */ common::model::diagram_element::id_t> + local_ast_id_map_; + + std::map> + anonymous_struct_relationships_; +}; } diff --git a/src/util/util.cc b/src/util/util.cc index b6c9eecf..bda4497b 100644 --- a/src/util/util.cc +++ b/src/util/util.cc @@ -26,16 +26,25 @@ namespace util { const std::string WHITESPACE = " \n\r\t\f\v"; -void setup_logging(bool verbose) +void setup_logging(int verbose) { auto console = spdlog::stdout_color_mt("console", spdlog::color_mode::automatic); console->set_pattern("[%^%l%^] [tid %t] %v"); - if (verbose) { + if (verbose == 0) { + console->set_level(spdlog::level::err); + } + else if (verbose == 1) { + console->set_level(spdlog::level::info); + } + else if (verbose == 2) { console->set_level(spdlog::level::debug); } + else { + console->set_level(spdlog::level::trace); + } } std::string get_process_output(const std::string &command) @@ -201,7 +210,6 @@ std::string abbreviate(const std::string &s, const unsigned int max_length) bool find_element_alias( const std::string &input, std::tuple &result) { - std::regex alias_regex("(@A\\([^\\).]+\\))"); auto alias_it = diff --git a/src/util/util.h b/src/util/util.h index a922a2c8..a5b5b696 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -53,12 +53,16 @@ std::string trim(const std::string &s); spdlog::get("console")->debug(std::string("[{}:{}] ") + fmt__, \ __FILENAME__, __LINE__, ##__VA_ARGS__) +#define LOG_TRACE(fmt__, ...) \ + spdlog::get("console")->trace(std::string("[{}:{}] ") + fmt__, \ + __FILENAME__, __LINE__, ##__VA_ARGS__) + /** * @brief Setup spdlog logger. * * @param verbose Whether the logging should be verbose or not. */ -void setup_logging(bool verbose); +void setup_logging(int verbose); std::string get_process_output(const std::string &command); diff --git a/tests/t00037/test_case.h b/tests/t00037/test_case.h index b6e0baa9..5347e4de 100644 --- a/tests/t00037/test_case.h +++ b/tests/t00037/test_case.h @@ -37,12 +37,11 @@ TEST_CASE("t00037", "[test-case][class]") REQUIRE_THAT(puml, IsClass(_A("ST"))); REQUIRE_THAT(puml, IsClass(_A("A"))); - REQUIRE_THAT(puml, IsClass(_A("ST::\\(units\\)"))); - REQUIRE_THAT(puml, IsClass(_A("ST::\\(dimensions\\)"))); - REQUIRE_THAT(puml, - IsAggregation(_A("ST"), _A("ST::\\(dimensions\\)"), "+dimensions")); + REQUIRE_THAT(puml, IsClass(_A("ST::(units)"))); + REQUIRE_THAT(puml, IsClass(_A("ST::(dimensions)"))); REQUIRE_THAT( - puml, IsAggregation(_A("ST"), _A("ST::\\(units\\)"), "-units")); + puml, IsAggregation(_A("ST"), _A("ST::(dimensions)"), "+dimensions")); + REQUIRE_THAT(puml, IsAggregation(_A("ST"), _A("ST::(units)"), "-units")); save_puml( "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); diff --git a/tests/t00048/.clang-uml b/tests/t00048/.clang-uml index 2c8a4cec..7236ae46 100644 --- a/tests/t00048/.clang-uml +++ b/tests/t00048/.clang-uml @@ -6,6 +6,7 @@ diagrams: glob: - ../../tests/t00048/b_t00048.cc - ../../tests/t00048/a_t00048.cc + - ../../tests/t00048/t00048.cc using_namespace: clanguml::t00048 parse_includes: true include: diff --git a/tests/t20001/.clang-uml b/tests/t20001/.clang-uml index 5a2b9c8c..0e443a01 100644 --- a/tests/t20001/.clang-uml +++ b/tests/t20001/.clang-uml @@ -19,4 +19,4 @@ diagrams: before: - "' t20001 test sequence diagram" after: - - 'note over "tmain()": Main test function' + - '{% set e=element("clanguml::t20001::tmain()") %} note over {{ e.alias) }}: Main test function' diff --git a/tests/t20001/t20001.cc b/tests/t20001/t20001.cc index 18878c11..488c19fe 100644 --- a/tests/t20001/t20001.cc +++ b/tests/t20001/t20001.cc @@ -28,7 +28,7 @@ public: return res; } - void log_result(int r) { } + static void log_result(int r) { } private: detail::C m_c{}; @@ -64,7 +64,9 @@ int tmain() A a; B b(a); - return b.wrap_add3(1, 2, 3); + auto tmp = a.add(1, 2); + + return b.wrap_add3(tmp, 2, 3); } } } diff --git a/tests/t20001/test_case.h b/tests/t20001/test_case.h index 82e44243..3b3cf30f 100644 --- a/tests/t20001/test_case.h +++ b/tests/t20001/test_case.h @@ -33,15 +33,16 @@ TEST_CASE("t20001", "[test-case][sequence]") REQUIRE(!model->should_include("std::vector")); auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); REQUIRE_THAT(puml, StartsWith("@startuml")); REQUIRE_THAT(puml, EndsWith("@enduml\n")); - REQUIRE_THAT(puml, HasCall("A", "log_result")); - REQUIRE_THAT(puml, HasCall("B", "A", "log_result")); - REQUIRE_THAT(puml, HasCallWithResponse("B", "A", "add3")); - REQUIRE_THAT(puml, HasCall("A", "add")); - REQUIRE_THAT(puml, !HasCall("A", "detail::C", "add")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "add3(int,int,int)")); + REQUIRE_THAT(puml, HasCall(_A("A"), "add(int,int)")); + REQUIRE_THAT(puml, !HasCall(_A("A"), _A("detail::C"), "add(int,int)")); + REQUIRE_THAT(puml, HasCall(_A("A"), "__log_result(int)__")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "__log_result(int)__")); save_puml( "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); diff --git a/tests/t20002/test_case.h b/tests/t20002/test_case.h index ebefda0f..d053c8ac 100644 --- a/tests/t20002/test_case.h +++ b/tests/t20002/test_case.h @@ -29,13 +29,14 @@ TEST_CASE("t20002", "[test-case][sequence]") REQUIRE(model->name() == "t20002_sequence"); auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); REQUIRE_THAT(puml, StartsWith("@startuml")); REQUIRE_THAT(puml, EndsWith("@enduml\n")); - REQUIRE_THAT(puml, HasFunctionCall("m1", "m2")); - REQUIRE_THAT(puml, HasFunctionCall("m2", "m3")); - REQUIRE_THAT(puml, HasFunctionCall("m3", "m4")); + REQUIRE_THAT(puml, HasCall(_A("m1()"), _A("m2()"), "")); + REQUIRE_THAT(puml, HasCall(_A("m2()"), _A("m3()"), "")); + REQUIRE_THAT(puml, HasCall(_A("m3()"), _A("m4()"), "")); save_puml( "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); diff --git a/tests/t20003/.clang-uml b/tests/t20003/.clang-uml new file mode 100644 index 00000000..9aae6b72 --- /dev/null +++ b/tests/t20003/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20003_sequence: + type: sequence + glob: + - ../../tests/t20003/t20003.cc + include: + namespaces: + - clanguml::t20003 + using_namespace: + - clanguml::t20003 + start_from: + - function: "clanguml::t20003::m1(T)" diff --git a/tests/t20003/t20003.cc b/tests/t20003/t20003.cc new file mode 100644 index 00000000..00c33395 --- /dev/null +++ b/tests/t20003/t20003.cc @@ -0,0 +1,12 @@ +namespace clanguml { +namespace t20003 { + +template void m4(T p) { } + +template void m3(T p) { m4(p); } + +template void m2(T p) { m3(p); } + +template void m1(T p) { m2(p); } +} +} diff --git a/tests/t20003/test_case.h b/tests/t20003/test_case.h new file mode 100644 index 00000000..7a17c539 --- /dev/null +++ b/tests/t20003/test_case.h @@ -0,0 +1,43 @@ +/** + * tests/t20003/test_case.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20003", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20003"); + + auto diagram = config.diagrams["t20003_sequence"]; + + REQUIRE(diagram->name == "t20003_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20003_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + REQUIRE_THAT(puml, HasCall(_A("m1(T)"), _A("m2(T)"), "")); + REQUIRE_THAT(puml, HasCall(_A("m2(T)"), _A("m3(T)"), "")); + REQUIRE_THAT(puml, HasCall(_A("m3(T)"), _A("m4(T)"), "")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/t20004/.clang-uml b/tests/t20004/.clang-uml new file mode 100644 index 00000000..de348abf --- /dev/null +++ b/tests/t20004/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20004_sequence: + type: sequence + glob: + - ../../tests/t20004/t20004.cc + include: + namespaces: + - clanguml::t20004 + using_namespace: + - clanguml::t20004 + start_from: + - function: "clanguml::t20004::main()" \ No newline at end of file diff --git a/tests/t20004/t20004.cc b/tests/t20004/t20004.cc new file mode 100644 index 00000000..6290ffaf --- /dev/null +++ b/tests/t20004/t20004.cc @@ -0,0 +1,49 @@ +#include + +namespace clanguml { +namespace t20004 { + +template T m4(T p); + +template <> [[maybe_unused]] int m4(int p) { return p + 2; } + +template <> [[maybe_unused]] unsigned long m4(unsigned long p) +{ + return 1000 * p; +} + +template T m3(T p) { return m4(p); } + +template T m2(T p) { return m3(p); } + +template <> [[maybe_unused]] std::string m2(std::string p) +{ + return std::string{"m2"} + p; +} + +template T m1(T p) { return m2(p); } + +template <> [[maybe_unused]] float m1(float p) { return 2 * p; } + +template <> [[maybe_unused]] unsigned long m1(unsigned long p) +{ + return m4(p); +} + +template <> [[maybe_unused]] std::string m1(std::string p) +{ + return m2(p); +} + +int main() +{ + m1(2.2); + + m1(100); + + m1("main"); + + return m1(0); +} +} +} diff --git a/tests/t20004/test_case.h b/tests/t20004/test_case.h new file mode 100644 index 00000000..fc827d68 --- /dev/null +++ b/tests/t20004/test_case.h @@ -0,0 +1,61 @@ +/** + * tests/t20004/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20004", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20004"); + + auto diagram = config.diagrams["t20004_sequence"]; + + REQUIRE(diagram->name == "t20004_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20004_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, HasCall(_A("main()"), _A("m1(float)"), "")); + REQUIRE_THAT( + puml, !HasCall(_A("m1(float)"), _A("m1(float)"), "")); + REQUIRE_THAT( + puml, !HasCall(_A("m1(float)"), _A("m1(float)"), "")); + + REQUIRE_THAT(puml, + HasCall(_A("main()"), _A("m1(unsigned long)"), "")); + REQUIRE_THAT(puml, + HasCall(_A("m1(unsigned long)"), + _A("m4(unsigned long)"), "")); + + REQUIRE_THAT( + puml, HasCall(_A("main()"), _A("m1(std::string)"), "")); + REQUIRE_THAT(puml, + HasCall(_A("m1(std::string)"), + _A("m2(std::string)"), "")); + + REQUIRE_THAT(puml, HasCall(_A("main()"), _A("m1(int)"), "")); + REQUIRE_THAT(puml, HasCall(_A("m1(int)"), _A("m2(int)"), "")); + REQUIRE_THAT(puml, HasCall(_A("m2(int)"), _A("m3(int)"), "")); + REQUIRE_THAT(puml, HasCall(_A("m3(int)"), _A("m4(int)"), "")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20005/.clang-uml b/tests/t20005/.clang-uml new file mode 100644 index 00000000..7fbd937a --- /dev/null +++ b/tests/t20005/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20005_sequence: + type: sequence + glob: + - ../../tests/t20005/t20005.cc + include: + namespaces: + - clanguml::t20005 + using_namespace: + - clanguml::t20005 + start_from: + - function: "clanguml::t20005::C::c(T)" \ No newline at end of file diff --git a/tests/t20005/t20005.cc b/tests/t20005/t20005.cc new file mode 100644 index 00000000..de066173 --- /dev/null +++ b/tests/t20005/t20005.cc @@ -0,0 +1,21 @@ +namespace clanguml { +namespace t20005 { + +template struct A { + T a(T arg) { return arg; } +}; + +template struct B { + T b(T arg) { return a_.a(arg); } + + A a_; +}; + +template struct C { + T c(T arg) { return b_.b(arg); } + + B b_; +}; + +} +} \ No newline at end of file diff --git a/tests/t20005/test_case.h b/tests/t20005/test_case.h new file mode 100644 index 00000000..add8b058 --- /dev/null +++ b/tests/t20005/test_case.h @@ -0,0 +1,44 @@ +/** + * tests/t20005/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20005", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20005"); + + auto diagram = config.diagrams["t20005_sequence"]; + + REQUIRE(diagram->name == "t20005_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20005_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasEntrypoint(_A("C"), "c(T)")); + REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b(T)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a(T)")); + REQUIRE_THAT(puml, HasExitpoint(_A("C"))); + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20006/.clang-uml b/tests/t20006/.clang-uml new file mode 100644 index 00000000..dedfb8f7 --- /dev/null +++ b/tests/t20006/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20006_sequence: + type: sequence + glob: + - ../../tests/t20006/t20006.cc + include: + namespaces: + - clanguml::t20006 + using_namespace: + - clanguml::t20006 + start_from: + - function: "clanguml::t20006::tmain()" \ No newline at end of file diff --git a/tests/t20006/t20006.cc b/tests/t20006/t20006.cc new file mode 100644 index 00000000..f76e9d54 --- /dev/null +++ b/tests/t20006/t20006.cc @@ -0,0 +1,79 @@ +#include + +namespace clanguml { +namespace t20006 { + +template struct A { + T a1(T arg) { return arg; } + T a2(T arg) { return arg + arg; } +}; + +template struct B { + T b(T arg) { return a_.a1(arg); } + A a_; +}; + +template <> struct B { + std::string b(std::string arg) { return a_.a2(arg); } + A a_; +}; + +template struct AA { + void aa1(T t) { } + void aa2(T t) { } +}; + +template struct BB { + void bb1(T t, F f) { aa_.aa1(t); } + void bb2(T t, F f) { aa_.aa2(t); } + + AA aa_; +}; + +template struct BB { + void bb1(T t, std::string f) { aa_->aa2(t); } + void bb2(T t, std::string f) { aa_->aa1(t); } + + BB(AA *aa) + : aa_{aa} + { + } + + AA *aa_; +}; + +template struct BB { + void bb1(T t, float f) { bb2(t, f); } + void bb2(T t, float f) { aa_.aa2(t); } + + BB(AA &aa) + : aa_{aa} + { + } + + AA &aa_; +}; + +void tmain() +{ + B bint; + B bstring; + + bint.b(1); + bstring.b("bstring"); + + BB bbint; + AA aaint; + BB bbstring{&aaint}; + BB bbfloat{aaint}; + + bbint.bb1(1, 1); + bbint.bb2(2, 2); + + bbstring.bb1(1, "calling aa2"); + bbstring.bb2(1, "calling aa1"); + + bbfloat.bb1(1, 1.0f); +} +} +} \ No newline at end of file diff --git a/tests/t20006/test_case.h b/tests/t20006/test_case.h new file mode 100644 index 00000000..dcedefb0 --- /dev/null +++ b/tests/t20006/test_case.h @@ -0,0 +1,74 @@ +/** + * tests/t20006/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20006", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20006"); + + auto diagram = config.diagrams["t20006_sequence"]; + + REQUIRE(diagram->name == "t20006_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20006_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b(int)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a1(int)")); + + REQUIRE_THAT( + puml, HasCall(_A("tmain()"), _A("B"), "b(std::string)")); + REQUIRE_THAT(puml, + HasCall(_A("B"), _A("A"), "a2(std::string)")); + + REQUIRE_THAT( + puml, HasCall(_A("tmain()"), _A("BB"), "bb1(int,int)")); + REQUIRE_THAT(puml, HasCall(_A("BB"), _A("AA"), "aa1(int)")); + + REQUIRE_THAT( + puml, HasCall(_A("tmain()"), _A("BB"), "bb2(int,int)")); + REQUIRE_THAT(puml, HasCall(_A("BB"), _A("AA"), "aa2(int)")); + + REQUIRE_THAT(puml, + HasCall( + _A("tmain()"), _A("BB"), "bb1(int,std::string)")); + REQUIRE_THAT( + puml, HasCall(_A("BB"), _A("AA"), "aa2(int)")); + + REQUIRE_THAT(puml, + HasCall( + _A("tmain()"), _A("BB"), "bb2(int,std::string)")); + REQUIRE_THAT( + puml, HasCall(_A("BB"), _A("AA"), "aa1(int)")); + + REQUIRE_THAT( + puml, HasCall(_A("tmain()"), _A("BB"), "bb1(int,float)")); + REQUIRE_THAT(puml, + HasCall(_A("BB"), _A("BB"), "bb2(int,float)")); + REQUIRE_THAT(puml, HasCall(_A("BB"), _A("AA"), "aa2(int)")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20007/.clang-uml b/tests/t20007/.clang-uml new file mode 100644 index 00000000..40a6a2f7 --- /dev/null +++ b/tests/t20007/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20007_sequence: + type: sequence + glob: + - ../../tests/t20007/t20007.cc + include: + namespaces: + - clanguml::t20007 + using_namespace: + - clanguml::t20007 + start_from: + - function: "clanguml::t20007::tmain()" \ No newline at end of file diff --git a/tests/t20007/t20007.cc b/tests/t20007/t20007.cc new file mode 100644 index 00000000..b7c3ec44 --- /dev/null +++ b/tests/t20007/t20007.cc @@ -0,0 +1,25 @@ +#include +#include + +namespace clanguml { +namespace t20007 { + +template struct Adder { + First add(First &&arg, Args &&...args) { return (arg + ... + args); } +}; + +void tmain() +{ + using namespace std::string_literals; + + Adder adder1; + Adder adder2; + Adder adder3; + + [[maybe_unused]] auto res1 = adder1.add(2, 2); + [[maybe_unused]] auto res2 = adder2.add(1, 2.0, 3.0); + [[maybe_unused]] auto res3 = adder3.add("one"s, "two"s, "three"s); +} + +} +} \ No newline at end of file diff --git a/tests/t20007/test_case.h b/tests/t20007/test_case.h new file mode 100644 index 00000000..5f639b51 --- /dev/null +++ b/tests/t20007/test_case.h @@ -0,0 +1,49 @@ +/** + * tests/t20007/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20007", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20007"); + + auto diagram = config.diagrams["t20007_sequence"]; + + REQUIRE(diagram->name == "t20007_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20007_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, + HasCall(_A("tmain()"), _A("Adder"), "add(int &&,int &&)")); + REQUIRE_THAT(puml, + HasCall(_A("tmain()"), _A("Adder"), + "add(int &&,float &&,double &&)")); + REQUIRE_THAT(puml, + HasCall(_A("tmain()"), _A("Adder"), + "add(std::string &&,std::string &&,std::string &&)")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20008/.clang-uml b/tests/t20008/.clang-uml new file mode 100644 index 00000000..f266305c --- /dev/null +++ b/tests/t20008/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20008_sequence: + type: sequence + glob: + - ../../tests/t20008/t20008.cc + include: + namespaces: + - clanguml::t20008 + using_namespace: + - clanguml::t20008 + start_from: + - function: "clanguml::t20008::tmain()" \ No newline at end of file diff --git a/tests/t20008/t20008.cc b/tests/t20008/t20008.cc new file mode 100644 index 00000000..46f9173c --- /dev/null +++ b/tests/t20008/t20008.cc @@ -0,0 +1,43 @@ +#include +#include + +namespace clanguml { +namespace t20008 { + +template struct A { + void a1(T arg) { } + void a2(T arg) { } + void a3(T arg) { } +}; + +template struct B { + A a; + + void b(T arg) + { + if constexpr (std::is_integral_v) { + a.a1(arg); + } + else if constexpr (std::is_pointer_v) { + a.a2(arg); + } + else { + a.a3(arg); + } + } +}; + +void tmain() +{ + using namespace std::string_literals; + + B bint; + B bcharp; + B bstring; + + bint.b(1); + bcharp.b("1"); + bstring.b("1"s); +} +} +} \ No newline at end of file diff --git a/tests/t20008/test_case.h b/tests/t20008/test_case.h new file mode 100644 index 00000000..e5f7703b --- /dev/null +++ b/tests/t20008/test_case.h @@ -0,0 +1,56 @@ +/** + * tests/t20008/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20008", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20008"); + + auto diagram = config.diagrams["t20008_sequence"]; + + REQUIRE(diagram->name == "t20008_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20008_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b(int)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a1(int)")); + // REQUIRE_THAT(puml, !HasCall(_A("B"), _A("A"), "a2(int)")); + // REQUIRE_THAT(puml, !HasCall(_A("B"), _A("A"), "a3(int)")); + + REQUIRE_THAT( + puml, HasCall(_A("tmain()"), _A("B"), "b(const char *)")); + REQUIRE_THAT(puml, + HasCall( + _A("B"), _A("A"), "a2(const char *)")); + + REQUIRE_THAT( + puml, HasCall(_A("tmain()"), _A("B"), "b(std::string)")); + REQUIRE_THAT(puml, + HasCall(_A("B"), _A("A"), "a3(std::string)")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20009/.clang-uml b/tests/t20009/.clang-uml new file mode 100644 index 00000000..6f8b97ea --- /dev/null +++ b/tests/t20009/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20009_sequence: + type: sequence + glob: + - ../../tests/t20009/t20009.cc + include: + namespaces: + - clanguml::t20009 + using_namespace: + - clanguml::t20009 + start_from: + - function: "clanguml::t20009::tmain()" \ No newline at end of file diff --git a/tests/t20009/t20009.cc b/tests/t20009/t20009.cc new file mode 100644 index 00000000..50d9fa3f --- /dev/null +++ b/tests/t20009/t20009.cc @@ -0,0 +1,29 @@ +#include +#include + +namespace clanguml { +namespace t20009 { +template struct A { + void a(T arg) { } +}; + +template struct B { + void b(T arg) { a->a(arg); } + + std::unique_ptr> a; +}; + +using BFloatPtr = std::shared_ptr>; + +void tmain() +{ + std::shared_ptr> bstring; + auto bint = std::make_unique>(); + BFloatPtr bfloat; + + bstring->b("b"); + bint.get()->b(42); + bfloat->b(1.0); +} +} +} \ No newline at end of file diff --git a/tests/t20009/test_case.h b/tests/t20009/test_case.h new file mode 100644 index 00000000..bddc823e --- /dev/null +++ b/tests/t20009/test_case.h @@ -0,0 +1,50 @@ +/** + * tests/t20009/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20009", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20009"); + + auto diagram = config.diagrams["t20009_sequence"]; + + REQUIRE(diagram->name == "t20009_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20009_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT( + puml, HasCall(_A("tmain()"), _A("B"), "b(std::string)")); + REQUIRE_THAT(puml, + HasCall(_A("B"), _A("A"), "a(std::string)")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b(int)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a(int)")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b(float)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a(float)")); + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20010/.clang-uml b/tests/t20010/.clang-uml new file mode 100644 index 00000000..55f2dd81 --- /dev/null +++ b/tests/t20010/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20010_sequence: + type: sequence + glob: + - ../../tests/t20010/t20010.cc + include: + namespaces: + - clanguml::t20010 + using_namespace: + - clanguml::t20010 + start_from: + - function: "clanguml::t20010::tmain()" \ No newline at end of file diff --git a/tests/t20010/t20010.cc b/tests/t20010/t20010.cc new file mode 100644 index 00000000..3b8745cd --- /dev/null +++ b/tests/t20010/t20010.cc @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +namespace clanguml { +namespace t20010 { + +struct A { + void a1() { } + void a2() { } + void a3() { } + void a4() { } +}; + +template struct B { + void b1() { a_.a1(); } + void b2() { avector_.front().a2(); } + void b3() { aptrvector_.front()->a3(); } + void b4() { amap_.at(0).a4(); } + + A a_; + std::vector avector_; + std::vector> aptrvector_; + std::map amap_; +}; + +void tmain() +{ + B b; + + b.b1(); + b.b2(); + b.b3(); + b.b4(); +} + +} +} \ No newline at end of file diff --git a/tests/t20010/test_case.h b/tests/t20010/test_case.h new file mode 100644 index 00000000..9e2c6594 --- /dev/null +++ b/tests/t20010/test_case.h @@ -0,0 +1,52 @@ +/** + * tests/t20010/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20010", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20010"); + + auto diagram = config.diagrams["t20010_sequence"]; + + REQUIRE(diagram->name == "t20010_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20010_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b1()")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a1()")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b2()")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2()")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b3()")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a3()")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b4()")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a4()")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20011/.clang-uml b/tests/t20011/.clang-uml new file mode 100644 index 00000000..9a2a29be --- /dev/null +++ b/tests/t20011/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20011_sequence: + type: sequence + glob: + - ../../tests/t20011/t20011.cc + include: + namespaces: + - clanguml::t20011 + using_namespace: + - clanguml::t20011 + start_from: + - function: "clanguml::t20011::tmain()" \ No newline at end of file diff --git a/tests/t20011/t20011.cc b/tests/t20011/t20011.cc new file mode 100644 index 00000000..e4ba19c7 --- /dev/null +++ b/tests/t20011/t20011.cc @@ -0,0 +1,31 @@ +namespace clanguml { +namespace t20011 { + +struct A { + void a(int i = 10) + { + if (i > 0) + a(i - 1); + } + + void b(int i = 10) { c(i); } + void c(int i) { d(i); } + void d(int i) + { + if (i > 0) + b(i - 1); + else + a(); + } +}; + +void tmain() +{ + A a; + + a.a(); + + a.b(); +} +} +} \ No newline at end of file diff --git a/tests/t20011/test_case.h b/tests/t20011/test_case.h new file mode 100644 index 00000000..f0640636 --- /dev/null +++ b/tests/t20011/test_case.h @@ -0,0 +1,48 @@ +/** + * tests/t20011/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20011", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20011"); + + auto diagram = config.diagrams["t20011_sequence"]; + + REQUIRE(diagram->name == "t20011_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20011_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a(int)")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "a(int)")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "b(int)")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "c(int)")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "d(int)")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "b(int)")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20012/.clang-uml b/tests/t20012/.clang-uml new file mode 100644 index 00000000..bd288238 --- /dev/null +++ b/tests/t20012/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20012_sequence: + type: sequence + glob: + - ../../tests/t20012/t20012.cc + include: + namespaces: + - clanguml::t20012 + using_namespace: + - clanguml::t20012 + start_from: + - function: "clanguml::t20012::tmain()" \ No newline at end of file diff --git a/tests/t20012/t20012.cc b/tests/t20012/t20012.cc new file mode 100644 index 00000000..49e58a84 --- /dev/null +++ b/tests/t20012/t20012.cc @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include + +namespace clanguml { +namespace t20012 { +struct A { + void a() { aa(); } + + void aa() { aaa(); } + + void aaa() { } +}; + +struct B { + void b() { bb(); } + + void bb() { bbb(); } + + void bbb() { } + + void eb() { } +}; + +struct C { + void c() { cc(); } + + void cc() { ccc(); } + + void ccc() { } +}; + +struct D { + int add5(int arg) const { return arg + 5; } +}; + +class E { + std::optional> maybe_b; + std::shared_ptr a; + +public: + template void setup(F &&f) { f(maybe_b); } +}; + +template struct R { + R(F &&f) + : f_{std::move(f)} + { + } + + void r() { f_(); } + + F f_; +}; + +void tmain() +{ + A a; + B b; + C c; + + // The activity shouldn't be marked at the lambda definition, but + // wherever it is actually called... + auto alambda = [&a, &b]() { + a.a(); + b.b(); + }; + + // ...like here + alambda(); + + // There should be no call to B in the sequence diagram as the blambda + // is never called + [[maybe_unused]] auto blambda = [&b]() { b.b(); }; + + // Nested lambdas should also work + auto clambda = [alambda, &c]() { + c.c(); + alambda(); + }; + clambda(); + + R r{[&c]() { c.c(); }}; + + r.r(); + + D d; + + std::vector ints{0, 1, 2, 3, 4}; + std::transform(ints.begin(), ints.end(), ints.begin(), + [&d](auto i) { return d.add5(i); }); + + // TODO: Fix naming function call arguments which are lambdas + // E e; + // + // e.setup([](auto &&arg) mutable { + // // We cannot know here what 'arg' might be + // arg.value()->eb(); + // }); +} +} +} \ No newline at end of file diff --git a/tests/t20012/test_case.h b/tests/t20012/test_case.h new file mode 100644 index 00000000..e639dd23 --- /dev/null +++ b/tests/t20012/test_case.h @@ -0,0 +1,73 @@ +/** + * tests/t20012/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20012", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20012"); + + auto diagram = config.diagrams["t20012_sequence"]; + + REQUIRE(diagram->name == "t20012_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20012_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, + HasCall(_A("tmain()"), _A("tmain()::(lambda t20012.cc:66:20)"), + "operator()()")); + REQUIRE_THAT( + puml, HasCall(_A("tmain()::(lambda t20012.cc:66:20)"), _A("A"), "a()")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "aa()")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "aaa()")); + + REQUIRE_THAT( + puml, HasCall(_A("tmain()::(lambda t20012.cc:66:20)"), _A("B"), "b()")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("B"), "bb()")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("B"), "bbb()")); + + REQUIRE_THAT( + puml, HasCall(_A("tmain()::(lambda t20012.cc:79:20)"), _A("C"), "c()")); + REQUIRE_THAT(puml, HasCall(_A("C"), _A("C"), "cc()")); + REQUIRE_THAT(puml, HasCall(_A("C"), _A("C"), "ccc()")); + REQUIRE_THAT(puml, + HasCall(_A("tmain()::(lambda t20012.cc:79:20)"), + _A("tmain()::(lambda t20012.cc:66:20)"), "operator()()")); + + REQUIRE_THAT(puml, HasCall(_A("C"), _A("C"), "ccc()")); + + REQUIRE_THAT(puml, + HasCall(_A("tmain()"), _A("R"), "r()")); + REQUIRE_THAT(puml, + HasCall(_A("R"), + _A("tmain()::(lambda t20012.cc:85:9)"), "operator()()")); + REQUIRE_THAT( + puml, HasCall(_A("tmain()::(lambda t20012.cc:85:9)"), _A("C"), "c()")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("D"), "add5(int)")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20013/.clang-uml b/tests/t20013/.clang-uml new file mode 100644 index 00000000..e37d69ee --- /dev/null +++ b/tests/t20013/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20013_sequence: + type: sequence + glob: + - ../../tests/t20013/t20013.cc + include: + namespaces: + - clanguml::t20013 + using_namespace: + - clanguml::t20013 + start_from: + - function: "clanguml::t20013::tmain(int,char **)" \ No newline at end of file diff --git a/tests/t20013/t20013.cc b/tests/t20013/t20013.cc new file mode 100644 index 00000000..616c09a2 --- /dev/null +++ b/tests/t20013/t20013.cc @@ -0,0 +1,27 @@ +namespace clanguml { +namespace t20013 { + +struct A { + int a1(int i) { return i; } + double a2(double d) { return d; } + const char *a3(const char *s) { return s; } +}; + +struct B { + int b(int i) { return a.a1(i); } + double b(double d) { return a.a2(d); } + const char *b(const char *s) { return a.a3(s); } + + A a; +}; + +void tmain(int argc, char **argv) +{ + B b; + + b.b(1); + b.b(2.0); + b.b("three"); +} +} +} \ No newline at end of file diff --git a/tests/t20013/test_case.h b/tests/t20013/test_case.h new file mode 100644 index 00000000..0b100356 --- /dev/null +++ b/tests/t20013/test_case.h @@ -0,0 +1,50 @@ +/** + * tests/t20013/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20013", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20013"); + + auto diagram = config.diagrams["t20013_sequence"]; + + REQUIRE(diagram->name == "t20013_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20013_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("tmain(int,char **)"), _A("B"), "b(int)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a1(int)")); + + REQUIRE_THAT(puml, HasCall(_A("tmain(int,char **)"), _A("B"), "b(double)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2(double)")); + + REQUIRE_THAT( + puml, HasCall(_A("tmain(int,char **)"), _A("B"), "b(const char *)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a3(const char *)")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20014/.clang-uml b/tests/t20014/.clang-uml new file mode 100644 index 00000000..9de444e7 --- /dev/null +++ b/tests/t20014/.clang-uml @@ -0,0 +1,17 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20014_sequence: + type: sequence + glob: + - ../../tests/t20014/t20014.cc + - ../../tests/t20014/t20014_c.cc + - ../../tests/t20014/t20014_b.cc + - ../../tests/t20014/t20014_a.cc + include: + namespaces: + - clanguml::t20014 + using_namespace: + - clanguml::t20014 + start_from: + - function: "clanguml::t20014::tmain()" \ No newline at end of file diff --git a/tests/t20014/include/t20014.h b/tests/t20014/include/t20014.h new file mode 100644 index 00000000..48b974d5 --- /dev/null +++ b/tests/t20014/include/t20014.h @@ -0,0 +1,9 @@ +#pragma once + +namespace clanguml { +namespace t20014 { + +int tmain(); + +} +} \ No newline at end of file diff --git a/tests/t20014/include/t20014_a.h b/tests/t20014/include/t20014_a.h new file mode 100644 index 00000000..44865287 --- /dev/null +++ b/tests/t20014/include/t20014_a.h @@ -0,0 +1,12 @@ +#pragma once + +namespace clanguml { +namespace t20014 { + +struct A { + int a1(int i, int j); + int a2(int i, int j); +}; + +} +} \ No newline at end of file diff --git a/tests/t20014/include/t20014_b.h b/tests/t20014/include/t20014_b.h new file mode 100644 index 00000000..e41e43c5 --- /dev/null +++ b/tests/t20014/include/t20014_b.h @@ -0,0 +1,16 @@ +#pragma once + +#include "t20014_a.h" + +namespace clanguml { +namespace t20014 { + +struct B { + int b1(int i, int); + int b2(int i, int); + + A a_; +}; + +} +} \ No newline at end of file diff --git a/tests/t20014/include/t20014_c.h b/tests/t20014/include/t20014_c.h new file mode 100644 index 00000000..463cfa8b --- /dev/null +++ b/tests/t20014/include/t20014_c.h @@ -0,0 +1,15 @@ +#pragma once + +namespace clanguml { +namespace t20014 { + +template struct C { + F c1(F i, F j) { return c_.b1(i, j); } + + F c2(F i, F j) { return c_.b2(i, j); } + + T c_; +}; + +} +} \ No newline at end of file diff --git a/tests/t20014/t20014.cc b/tests/t20014/t20014.cc new file mode 100644 index 00000000..c22f030d --- /dev/null +++ b/tests/t20014/t20014.cc @@ -0,0 +1,23 @@ +#include "include/t20014.h" +#include "include/t20014_b.h" +#include "include/t20014_c.h" + +namespace clanguml { +namespace t20014 { + +void log(const char *msg) { } + +int tmain() +{ + B b; + C c; + + b.b1(0, 1); + b.b2(1, 2); + + c.c1(2, 3); + + return 0; +} +} +} \ No newline at end of file diff --git a/tests/t20014/t20014_a.cc b/tests/t20014/t20014_a.cc new file mode 100644 index 00000000..58d2b6bf --- /dev/null +++ b/tests/t20014/t20014_a.cc @@ -0,0 +1,10 @@ +#include "include/t20014_a.h" +namespace clanguml { +namespace t20014 { + +int A::a1(int i, int j) { return i + j; } + +int A::a2(int i, int j) { return i - j; } + +} +} \ No newline at end of file diff --git a/tests/t20014/t20014_b.cc b/tests/t20014/t20014_b.cc new file mode 100644 index 00000000..71fe96e6 --- /dev/null +++ b/tests/t20014/t20014_b.cc @@ -0,0 +1,10 @@ +#include "include/t20014_b.h" +namespace clanguml { +namespace t20014 { + +int B::b1(int i, int j) { return a_.a1(i, j); } + +int B::b2(int i, int j) { return a_.a2(i, j); } + +} +} \ No newline at end of file diff --git a/tests/t20014/t20014_c.cc b/tests/t20014/t20014_c.cc new file mode 100644 index 00000000..3a4a45ff --- /dev/null +++ b/tests/t20014/t20014_c.cc @@ -0,0 +1,7 @@ +#include "include/t20014_c.h" + +namespace clanguml { +namespace t20014 { + +} +} \ No newline at end of file diff --git a/tests/t20014/test_case.h b/tests/t20014/test_case.h new file mode 100644 index 00000000..f92ed6bb --- /dev/null +++ b/tests/t20014/test_case.h @@ -0,0 +1,49 @@ +/** + * tests/t20014/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20014", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20014"); + + auto diagram = config.diagrams["t20014_sequence"]; + + REQUIRE(diagram->name == "t20014_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20014_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b1(int,int)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a1(int,int)")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b2(int,int)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2(int,int)")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("C"), "c1(int,int)")); + REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b1(int,int)")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20015/.clang-uml b/tests/t20015/.clang-uml new file mode 100644 index 00000000..231e9543 --- /dev/null +++ b/tests/t20015/.clang-uml @@ -0,0 +1,17 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20015_sequence: + type: sequence + glob: + - ../../tests/t20015/t20015.cc + include: + namespaces: + - clanguml::t20015 + exclude: + namespaces: + - clanguml::t20015::detail + using_namespace: + - clanguml::t20015 + start_from: + - function: "clanguml::t20015::tmain()" \ No newline at end of file diff --git a/tests/t20015/t20015.cc b/tests/t20015/t20015.cc new file mode 100644 index 00000000..d0d643a7 --- /dev/null +++ b/tests/t20015/t20015.cc @@ -0,0 +1,40 @@ +#include +#include + +namespace clanguml { +namespace t20015 { + +namespace detail { +class A { +public: + void set_x(int x) { x_ = x; } + void set_y(int y) { y_ = y; } + void set_z(int z) { z_ = z; } + +private: + int x_; + int y_; + int z_; +}; +} + +class B { +public: + void setup_a(std::shared_ptr &a) + { + a->set_x(1); + a.get()->set_y(2); + (*a).set_z(3); + } +}; + +void tmain() +{ + auto a = std::make_shared(); + + B b; + + b.setup_a(a); +} +} +} \ No newline at end of file diff --git a/tests/t20015/test_case.h b/tests/t20015/test_case.h new file mode 100644 index 00000000..54186b73 --- /dev/null +++ b/tests/t20015/test_case.h @@ -0,0 +1,51 @@ +/** + * tests/t20015/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20015", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20015"); + + auto diagram = config.diagrams["t20015_sequence"]; + + REQUIRE(diagram->name == "t20015_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20015_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, + HasCall( + _A("tmain()"), _A("B"), "setup_a(std::shared_ptr &)")); + REQUIRE_THAT(puml, !HasCall(_A("B"), _A("detail::A"), "set_x(int)")); + REQUIRE_THAT(puml, !HasCall(_A("B"), _A("detail::A"), "set_y(int)")); + REQUIRE_THAT(puml, !HasCall(_A("B"), _A("detail::A"), "set_z(int)")); + + REQUIRE_THAT(puml, !HasCall(_A("B"), _A("B"), "set_x(int)")); + REQUIRE_THAT(puml, !HasCall(_A("B"), _A("B"), "set_y(int)")); + REQUIRE_THAT(puml, !HasCall(_A("B"), _A("B"), "set_z(int)")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20016/.clang-uml b/tests/t20016/.clang-uml new file mode 100644 index 00000000..1cf1f97f --- /dev/null +++ b/tests/t20016/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20016_sequence: + type: sequence + glob: + - ../../tests/t20016/t20016.cc + include: + namespaces: + - clanguml::t20016 + using_namespace: + - clanguml::t20016 + start_from: + - function: "clanguml::t20016::tmain()" \ No newline at end of file diff --git a/tests/t20016/t20016.cc b/tests/t20016/t20016.cc new file mode 100644 index 00000000..77bf22cb --- /dev/null +++ b/tests/t20016/t20016.cc @@ -0,0 +1,25 @@ +namespace clanguml { +namespace t20016 { +struct A { + void a1(int a) { } + template T a2(const T &a) { return a; } +}; + +template struct B { + void b1(T b) { a_.a1(1); } + + template F b2(T t) { return a_.a2(t); } + + A a_; +}; + +void tmain() +{ + B b; + + b.b1(1); + + b.b2(2); +} +} +} \ No newline at end of file diff --git a/tests/t20016/test_case.h b/tests/t20016/test_case.h new file mode 100644 index 00000000..537e94c7 --- /dev/null +++ b/tests/t20016/test_case.h @@ -0,0 +1,46 @@ +/** + * tests/t20016/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20016", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20016"); + + auto diagram = config.diagrams["t20016_sequence"]; + + REQUIRE(diagram->name == "t20016_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20016_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b1(long)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a1(int)")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b2(long)")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2(const long &)")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20017/.clang-uml b/tests/t20017/.clang-uml new file mode 100644 index 00000000..0aa577ba --- /dev/null +++ b/tests/t20017/.clang-uml @@ -0,0 +1,16 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20017_sequence: + type: sequence + combine_free_functions_into_file_participants: true + relative_to: ../../tests/t20017 + glob: + - ../../tests/t20017/t20017.cc + include: + namespaces: + - clanguml::t20017 + using_namespace: + - clanguml::t20017 + start_from: + - function: "clanguml::t20017::tmain()" \ No newline at end of file diff --git a/tests/t20017/include/t20017_a.h b/tests/t20017/include/t20017_a.h new file mode 100644 index 00000000..49ed884b --- /dev/null +++ b/tests/t20017/include/t20017_a.h @@ -0,0 +1,9 @@ +#pragma once + +namespace clanguml { +namespace t20017 { +int a1(int x, int y) { return x + y; } +int a2(int x, int y) { return x - y; } +int a3(int x, int y) { return x * y; } +} +} \ No newline at end of file diff --git a/tests/t20017/include/t20017_b.h b/tests/t20017/include/t20017_b.h new file mode 100644 index 00000000..86c67f72 --- /dev/null +++ b/tests/t20017/include/t20017_b.h @@ -0,0 +1,9 @@ +#pragma once + +namespace clanguml { +namespace t20017 { +int b1(int x, int y); + +template T b2(T x, T y) { return x / y; } +} +} \ No newline at end of file diff --git a/tests/t20017/t20017.cc b/tests/t20017/t20017.cc new file mode 100644 index 00000000..ec6227de --- /dev/null +++ b/tests/t20017/t20017.cc @@ -0,0 +1,8 @@ +#include "include/t20017_a.h" +#include "include/t20017_b.h" + +namespace clanguml { +namespace t20017 { +int tmain() { return b2(a1(a2(a3(1, 2), b1(3, 4)), 5), 6); } +} +} \ No newline at end of file diff --git a/tests/t20017/t20017_b.cc b/tests/t20017/t20017_b.cc new file mode 100644 index 00000000..d0b80bb7 --- /dev/null +++ b/tests/t20017/t20017_b.cc @@ -0,0 +1,7 @@ +#include "include/t20017_b.h" + +namespace clanguml { +namespace t20017 { +int b1(int x, int y) { return x - y; } +} +} \ No newline at end of file diff --git a/tests/t20017/test_case.h b/tests/t20017/test_case.h new file mode 100644 index 00000000..ee5ede2f --- /dev/null +++ b/tests/t20017/test_case.h @@ -0,0 +1,53 @@ +/** + * tests/t20017/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20017", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20017"); + + auto diagram = config.diagrams["t20017_sequence"]; + + REQUIRE(diagram->name == "t20017_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20017_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasEntrypoint(_A("t20017.cc"), "tmain()")); + REQUIRE_THAT(puml, + HasCall(_A("t20017.cc"), _A("include/t20017_a.h"), "a1(int,int)")); + REQUIRE_THAT(puml, + HasCall(_A("t20017.cc"), _A("include/t20017_a.h"), "a2(int,int)")); + REQUIRE_THAT(puml, + HasCall(_A("t20017.cc"), _A("include/t20017_a.h"), "a3(int,int)")); + REQUIRE_THAT(puml, + HasCall(_A("t20017.cc"), _A("include/t20017_b.h"), "b1(int,int)")); + REQUIRE_THAT(puml, + HasCall(_A("t20017.cc"), _A("include/t20017_b.h"), "b2(int,int)")); + REQUIRE_THAT(puml, HasExitpoint(_A("t20017.cc"))); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20018/.clang-uml b/tests/t20018/.clang-uml new file mode 100644 index 00000000..a3f634da --- /dev/null +++ b/tests/t20018/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20018_sequence: + type: sequence + glob: + - ../../tests/t20018/t20018.cc + include: + namespaces: + - clanguml::t20018 + using_namespace: + - clanguml::t20018 + start_from: + - function: "clanguml::t20018::tmain()" \ No newline at end of file diff --git a/tests/t20018/t20018.cc b/tests/t20018/t20018.cc new file mode 100644 index 00000000..d2f7a7d2 --- /dev/null +++ b/tests/t20018/t20018.cc @@ -0,0 +1,27 @@ +#include + +namespace clanguml { +namespace t20018 { + +template struct Factorial { + static const int value = N * Factorial::value; + + static void print(int answer) { Factorial::print(answer); } +}; + +template <> struct Factorial<0> { + static const int value = 1; + + static void print(int answer) + { + std::cout << "The answer is " << answer << "\n"; + } +}; + +template struct Answer { + static void print() { T::print(N); } +}; + +void tmain() { Answer>::print(); } +} +} \ No newline at end of file diff --git a/tests/t20018/test_case.h b/tests/t20018/test_case.h new file mode 100644 index 00000000..d48116cb --- /dev/null +++ b/tests/t20018/test_case.h @@ -0,0 +1,56 @@ +/** + * tests/t20018/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20018", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20018"); + + auto diagram = config.diagrams["t20018_sequence"]; + + REQUIRE(diagram->name == "t20018_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20018_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, + HasCall(_A("tmain()"), _A("Answer,120>"), "__print()__")); + REQUIRE_THAT(puml, + HasCall(_A("Answer,120>"), _A("Factorial<5>"), + "__print(int)__")); + REQUIRE_THAT(puml, + HasCall(_A("Factorial<5>"), _A("Factorial<4>"), "__print(int)__")); + REQUIRE_THAT(puml, + HasCall(_A("Factorial<4>"), _A("Factorial<3>"), "__print(int)__")); + REQUIRE_THAT(puml, + HasCall(_A("Factorial<3>"), _A("Factorial<2>"), "__print(int)__")); + REQUIRE_THAT(puml, + HasCall(_A("Factorial<2>"), _A("Factorial<1>"), "__print(int)__")); + REQUIRE_THAT(puml, + HasCall(_A("Factorial<1>"), _A("Factorial<0>"), "__print(int)__")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20019/.clang-uml b/tests/t20019/.clang-uml new file mode 100644 index 00000000..77631b8c --- /dev/null +++ b/tests/t20019/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20019_sequence: + type: sequence + glob: + - ../../tests/t20019/t20019.cc + include: + namespaces: + - clanguml::t20019 + using_namespace: + - clanguml::t20019 + start_from: + - function: "clanguml::t20019::tmain()" \ No newline at end of file diff --git a/tests/t20019/t20019.cc b/tests/t20019/t20019.cc new file mode 100644 index 00000000..907e2066 --- /dev/null +++ b/tests/t20019/t20019.cc @@ -0,0 +1,34 @@ +#include + +namespace clanguml { +namespace t20019 { + +// From https://en.cppreference.com/w/cpp/language/crtp + +template struct Base { + void name() { (static_cast(this))->impl(); } +}; + +struct D1 : public Base { + void impl() { std::puts("D1::impl()"); } +}; + +struct D2 : public Base { + void impl() { std::puts("D2::impl()"); } +}; + +void tmain() +{ + Base b1; + b1.name(); + Base b2; + b2.name(); + + D1 d1; + d1.name(); + D2 d2; + d2.name(); +} + +} +} \ No newline at end of file diff --git a/tests/t20019/test_case.h b/tests/t20019/test_case.h new file mode 100644 index 00000000..3e7bb3f7 --- /dev/null +++ b/tests/t20019/test_case.h @@ -0,0 +1,45 @@ +/** + * tests/t20019/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20019", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20019"); + + auto diagram = config.diagrams["t20019_sequence"]; + + REQUIRE(diagram->name == "t20019_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20019_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("Base"), "name()")); + REQUIRE_THAT(puml, HasCall(_A("Base"), _A("D1"), "impl()")); + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("Base"), "name()")); + REQUIRE_THAT(puml, HasCall(_A("Base"), _A("D2"), "impl()")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20020/.clang-uml b/tests/t20020/.clang-uml new file mode 100644 index 00000000..9f58d838 --- /dev/null +++ b/tests/t20020/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20020_sequence: + type: sequence + glob: + - ../../tests/t20020/t20020.cc + include: + namespaces: + - clanguml::t20020 + using_namespace: + - clanguml::t20020 + start_from: + - function: "clanguml::t20020::tmain()" \ No newline at end of file diff --git a/tests/t20020/t20020.cc b/tests/t20020/t20020.cc new file mode 100644 index 00000000..498ded67 --- /dev/null +++ b/tests/t20020/t20020.cc @@ -0,0 +1,81 @@ +#include +#include + +namespace clanguml { +namespace t20020 { +struct A { + int a1() { return 0; } + int a2() { return 1; } + int a3() { return 2; } +}; + +struct B { + void log() { } + + int b1() { return 3; } + int b2() { return 4; } +}; + +struct C { + void log() const { } + + void c1() const + { + if (c2()) + log(); + } + + bool c2() const { return true; } +}; + +template struct D { + + T d1(T x, T y) { return x + y; } +}; + +int tmain() +{ + A a; + B b; + C c; + D d; + + int result{0}; + + if (reinterpret_cast(&a) % 100 == 0ULL) { + result = a.a1(); + } + else if (reinterpret_cast(&a) % 64 == 0ULL) { + if (a.a2() > 2) + result = b.b1(); + else + result = b.b2(); + } + else { + result = a.a3(); + } + + b.log(); + + if (true) + c.c1(); + + if (true) + d.d1(1, 1); + + // This if/else should not be included in the diagram at all + // as the calls to std will be excluded by the diagram filters + if (result != 2) { + result = std::exp(result); + } + else if (result == 3) { + result = 4; + } + else { + result = std::exp(result + 1); + } + + return result; +} +} +} \ No newline at end of file diff --git a/tests/t20020/test_case.h b/tests/t20020/test_case.h new file mode 100644 index 00000000..8bcb57d4 --- /dev/null +++ b/tests/t20020/test_case.h @@ -0,0 +1,50 @@ +/** + * tests/t20020/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20020", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20020"); + + auto diagram = config.diagrams["t20020_sequence"]; + + REQUIRE(diagram->name == "t20020_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20020_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a1()")); + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a2()")); + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a3()")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b1()")); + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b2()")); + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "log()")); + + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("C"), "c1()")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20021/.clang-uml b/tests/t20021/.clang-uml new file mode 100644 index 00000000..4c80b1bf --- /dev/null +++ b/tests/t20021/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20021_sequence: + type: sequence + glob: + - ../../tests/t20021/t20021.cc + include: + namespaces: + - clanguml::t20021 + using_namespace: + - clanguml::t20021 + start_from: + - function: "clanguml::t20021::tmain()" \ No newline at end of file diff --git a/tests/t20021/t20021.cc b/tests/t20021/t20021.cc new file mode 100644 index 00000000..5fe5e19f --- /dev/null +++ b/tests/t20021/t20021.cc @@ -0,0 +1,40 @@ +#include + +namespace clanguml { +namespace t20021 { +struct A { + int a1() { return 0; } + int a2() { return 1; } + int a3() { return 2; } +}; + +struct B { + void log() { } + + int b1() const { return 3; } + int b2() const { return 4; } +}; + +int tmain() +{ + A a; + std::vector b; + + int i = 10; + while (i--) { + int j = a.a3(); + do { + for (int l = a.a2(); l > 0; l--) + a.a1(); + } while (j--); + } + + int result = 0; + for (const auto &bi : b) { + result += bi.b2(); + } + + return b.front().b2() + result; +} +} +} \ No newline at end of file diff --git a/tests/t20021/test_case.h b/tests/t20021/test_case.h new file mode 100644 index 00000000..5868931e --- /dev/null +++ b/tests/t20021/test_case.h @@ -0,0 +1,47 @@ +/** + * tests/t20021/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20021", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20021"); + + auto diagram = config.diagrams["t20021_sequence"]; + + REQUIRE(diagram->name == "t20021_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20021_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a1()")); + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a2()")); + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a3()")); + + REQUIRE_THAT(puml, !HasCall(_A("tmain()"), _A("B"), "b1()")); + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b2()")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/t20022/.clang-uml b/tests/t20022/.clang-uml new file mode 100644 index 00000000..0fa68fa9 --- /dev/null +++ b/tests/t20022/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20022_sequence: + type: sequence + glob: + - ../../tests/t20022/t20022.cc + include: + namespaces: + - clanguml::t20022 + using_namespace: + - clanguml::t20022 + start_from: + - function: "clanguml::t20022::tmain()" \ No newline at end of file diff --git a/tests/t20022/t20022.cc b/tests/t20022/t20022.cc new file mode 100644 index 00000000..d9aee6ae --- /dev/null +++ b/tests/t20022/t20022.cc @@ -0,0 +1,37 @@ +#include + +namespace clanguml { +namespace t20022 { +class B; + +class A { +public: + A(std::unique_ptr b); + + void a(); + + std::unique_ptr b_; +}; + +class B { +public: + void b() { } +}; + +A::A(std::unique_ptr b) + : b_{std::move(b)} +{ +} + +void A::a() { b_->b(); } + +int tmain() +{ + A a{std::make_unique()}; + + a.a(); + + return 0; +} +} +} \ No newline at end of file diff --git a/tests/t20022/test_case.h b/tests/t20022/test_case.h new file mode 100644 index 00000000..9ffa41ed --- /dev/null +++ b/tests/t20022/test_case.h @@ -0,0 +1,43 @@ +/** + * tests/t20022/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20022", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20022"); + + auto diagram = config.diagrams["t20022_sequence"]; + + REQUIRE(diagram->name == "t20022_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20022_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a()")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("B"), "b()")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 5f5381b1..3a9b7b6a 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -249,6 +249,26 @@ using namespace clanguml::test::matchers; /// #include "t20001/test_case.h" #include "t20002/test_case.h" +#include "t20003/test_case.h" +#include "t20004/test_case.h" +#include "t20005/test_case.h" +#include "t20006/test_case.h" +#include "t20007/test_case.h" +#include "t20008/test_case.h" +#include "t20009/test_case.h" +#include "t20010/test_case.h" +#include "t20011/test_case.h" +#include "t20012/test_case.h" +#include "t20013/test_case.h" +#include "t20014/test_case.h" +#include "t20015/test_case.h" +#include "t20016/test_case.h" +#include "t20017/test_case.h" +#include "t20018/test_case.h" +#include "t20019/test_case.h" +#include "t20020/test_case.h" +#include "t20021/test_case.h" +#include "t20022/test_case.h" /// /// Package diagram tests @@ -295,4 +315,4 @@ int main(int argc, char *argv[]) clanguml::util::setup_logging(debug_log); return session.run(); -} +} \ No newline at end of file diff --git a/tests/test_cases.h b/tests/test_cases.h index 9fe8e489..1bfe9dd8 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -48,8 +48,10 @@ using Catch::Matchers::Contains; using Catch::Matchers::EndsWith; using Catch::Matchers::Equals; +using Catch::Matchers::Matches; using Catch::Matchers::StartsWith; using Catch::Matchers::VectorContains; + using namespace clanguml::util; std::pair constexpr bool has_type() noexcept { @@ -119,41 +122,80 @@ struct HasCallWithResultMatcher : ContainsMatcher { CasedString m_resultComparator; }; -ContainsMatcher HasCall(std::string const &from, std::string const &message, - CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) -{ - return ContainsMatcher( - CasedString(fmt::format("\"{}\" -> \"{}\" : {}()", from, from, message), - caseSensitivity)); -} +template class HasCallMatcher : public Catch::MatcherBase { + T m_from, m_to, m_message; -ContainsMatcher HasFunctionCall(std::string const &from, +public: + HasCallMatcher(T from, T to, T message) + : m_from(from) + , m_to{to} + , m_message{message} + { + util::replace_all(m_message, "(", "\\("); + util::replace_all(m_message, "*", "\\*"); + util::replace_all(m_message, ")", "\\)"); + } + + bool match(T const &in) const override + { + std::istringstream fin(in); + std::string line; + std::regex r{fmt::format("{} -> {} " + "(\\[\\[.*\\]\\] )?: {}", + m_from, m_to, m_message)}; + while (std::getline(fin, line)) { + std::smatch base_match; + std::regex_search(in, base_match, r); + if (base_match.size() > 0) + return true; + } + + return false; + } + + std::string describe() const override + { + std::ostringstream ss; + ss << "has call " + << fmt::format("{} -> {} : {}", m_from, m_to, m_message); + return ss.str(); + } +}; + +auto HasCall(std::string const &from, std::string const &to, std::string const &message, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) { - return ContainsMatcher(CasedString( - fmt::format("\"{}()\" -> \"{}()\" : {}()", from, message, message), - caseSensitivity)); + return HasCallMatcher(from, to, message); } -ContainsMatcher HasCall(std::string const &from, std::string const &to, - std::string const &message, +auto HasCall(std::string const &from, std::string const &message, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) { - return ContainsMatcher( - CasedString(fmt::format("\"{}\" -> \"{}\" : {}()", from, to, message), - caseSensitivity)); + return HasCall(from, from, message, caseSensitivity); } auto HasCallWithResponse(std::string const &from, std::string const &to, std::string const &message, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) { - return HasCallWithResultMatcher( - CasedString(fmt::format("\"{}\" -> \"{}\" : {}()", from, to, message), - caseSensitivity), - CasedString( - fmt::format("\"{}\" --> \"{}\"", to, from), caseSensitivity)); + return ContainsMatcher(CasedString( + fmt::format("{} --> {}", to, from), caseSensitivity)) && + HasCallMatcher(from, to, message); +} + +ContainsMatcher HasEntrypoint(std::string const &to, std::string const &message, + CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) +{ + return ContainsMatcher( + CasedString(fmt::format("[-> {} : {}", to, message), caseSensitivity)); +} + +ContainsMatcher HasExitpoint(std::string const &to, + CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) +{ + return ContainsMatcher( + CasedString(fmt::format("[<-- {}", to), caseSensitivity)); } struct AliasMatcher { @@ -162,12 +204,17 @@ struct AliasMatcher { { } - std::string operator()(const std::string &name) + std::string operator()(std::string name) { std::vector patterns; const std::string alias_regex("([A-Z]_[0-9]+)"); + util::replace_all(name, "(", "\\("); + util::replace_all(name, ")", "\\)"); + util::replace_all(name, " ", "\\s"); + util::replace_all(name, "*", "\\*"); + patterns.push_back( std::regex{"class\\s\"" + name + "\"\\sas\\s" + alias_regex}); patterns.push_back( @@ -182,17 +229,18 @@ struct AliasMatcher { std::regex{"file\\s\"" + name + "\"\\sas\\s" + alias_regex}); patterns.push_back( std::regex{"folder\\s\"" + name + "\"\\sas\\s" + alias_regex}); + patterns.push_back( + std::regex{"participant\\s\"" + name + "\"\\sas\\s" + alias_regex}); std::smatch base_match; for (const auto &line : puml) { for (const auto &pattern : patterns) { - if (std::regex_search(line, base_match, pattern)) { - if (base_match.size() == 2) { - std::ssub_match base_sub_match = base_match[1]; - std::string alias = base_sub_match.str(); - return trim(alias); - } + if (std::regex_search(line, base_match, pattern) && + base_match.size() == 2) { + std::ssub_match base_sub_match = base_match[1]; + std::string alias = base_sub_match.str(); + return trim(alias); } } } diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 12be8585..37fd33cb 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -154,6 +154,66 @@ test_cases: - name: t20002 title: Free function sequence diagram test case description: + - name: t20003 + title: Function template sequence diagram test case + description: + - name: t20004 + title: Function template instantiation sequence diagram test case + description: + - name: t20005 + title: Class template basic sequence diagram + description: + - name: t20006 + title: Class template specialization basic sequence diagram + description: + - name: t20007 + title: Class template variadic argument list sequence diagram + description: + - name: t20008 + title: Constexpr if sequence diagram test case + description: + - name: t20009 + title: Smart pointer dereference call expression test case + description: + - name: t20010 + title: Container sequence diagram test case + description: + - name: t20011 + title: Recursive calls sequence diagram test case + description: + - name: t20012 + title: Lambda expression call sequence diagram test case + description: + - name: t20013 + title: Function and method arguments in sequence diagrams test case + description: + - name: t20014 + title: Multiple translation units sequence diagram test case + description: + - name: t20015 + title: Class exclusion by namespace in sequence diagram test case + description: + - name: t20016 + title: Template method specialization sequence diagram test case + description: + - name: t20017 + title: Test case for combine_free_functions_into_file_participants option + description: + - name: t20018 + title: Recursive template sequence diagram test case + description: + - name: t20019 + title: Curiously Recurring Template Pattern sequence diagram test case + description: + - name: t20020 + title: If statement sequence diagram test case + description: + - name: t20021 + title: Loop statements sequence diagram test case + description: + - name: t20022 + title: Forward class declaration sequence diagram test case + description: Package diagrams: - name: t30001 title: Basic package diagram test case diff --git a/uml/class_diagram_generator_sequence_diagram.yml b/uml/class_diagram_generator_sequence_diagram.yml new file mode 100644 index 00000000..ffad3a03 --- /dev/null +++ b/uml/class_diagram_generator_sequence_diagram.yml @@ -0,0 +1,13 @@ +type: sequence +glob: + - src/class_diagram/generators/plantuml/*.cc +include: + namespaces: + - clanguml +using_namespace: + - clanguml::class_diagram::generators::plantuml +plantuml: + before: + - 'title clang-uml clanguml::class_diagram::generators::plantuml::generator sequence diagram' +start_from: + - function: "clanguml::class_diagram::generators::plantuml::generator::generate(std::ostream &)" \ No newline at end of file diff --git a/uml/sequence_diagram_visitor_sequence_diagram.yml b/uml/sequence_diagram_visitor_sequence_diagram.yml new file mode 100644 index 00000000..bcbbe923 --- /dev/null +++ b/uml/sequence_diagram_visitor_sequence_diagram.yml @@ -0,0 +1,15 @@ +type: sequence +glob: + - src/sequence_diagram/visitor/*.cc + - src/sequence_diagram/model/*.cc +include: + namespaces: + - clanguml::sequence_diagram::visitor + - clanguml::sequence_diagram::model +using_namespace: + - clanguml::sequence_diagram::visitor +plantuml: + before: + - 'title clang-uml sequence_diagram::visitor::translation_unit_visitor::VisitCXXRecordDecl sequence diagram' +start_from: + - function: "clanguml::sequence_diagram::visitor::translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *)" diff --git a/util/generate_test_case.py b/util/generate_test_case.py index 933ad835..ba2e1bc3 100755 --- a/util/generate_test_case.py +++ b/util/generate_test_case.py @@ -56,8 +56,8 @@ CLASS_DIAGRAM_TEST_CASE_EXAMPLES = """ SEQUENCE_DIAGRAM_TEST_CASE_EXAMPLES = """ // Check if all calls exist - //REQUIRE_THAT(puml, HasCall("A", "log_result")); - //REQUIRE_THAT(puml, HasCallWithResponse("B", "A", "add3")); + //REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a()")); + //REQUIRE_THAT(puml, HasCall(_A("A"), "a()")); """ PACKAGE_DIAGRAM_TEST_CASE_EXAMPLES = """ diff --git a/util/templates/test_cases/.clang-uml b/util/templates/test_cases/.clang-uml index 4e1e2749..63e89fab 100644 --- a/util/templates/test_cases/.clang-uml +++ b/util/templates/test_cases/.clang-uml @@ -7,4 +7,6 @@ diagrams: - ../../tests/{{ name }}/{{ name }}.cc include: namespaces: - - clanguml::{{ name }} \ No newline at end of file + - clanguml::{{ name }} + using_namespace: + - clanguml::{{ name }} \ No newline at end of file