1 /**
2 Copyright: Copyright (c) 2016-2017, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 
10 Overall design of the data flow when analyzing.
11  - Visitor pull data from the AST.
12  - Visitor push data to the general Transform.
13  - The Transform splice the data and forwards to the specialized transformers.
14    The top Transform act as a mediator. It do not have any logic or knowledge
15    other than how to forward the data to the specialized transforms.
16  - The specialized transformers finalizes the data, delays, decisions etc.
17    They decide when to do the final forwarding to the diagrams.
18  - The UML diagrams are processed by the Generator.
19    The generator transforms the in-memory representation to content suitable to
20    store in files.
21  - The generator forwards the content to a receiver, the registered Products.
22  - Backend done. See frontend for what happens with the Products.
23 */
24 module dextool.plugin.backend.plantuml;
25 
26 import std.meta : templateAnd, templateOr;
27 import std.range : ElementType;
28 import std.typecons : Flag, Yes, No;
29 import logger = std.experimental.logger;
30 
31 import dsrcgen.plantuml;
32 
33 import dextool.type;
34 import cpptooling.type : FilePrefix;
35 import cpptooling.analyzer.clang.ast : Visitor;
36 import cpptooling.data : TypeKind, TypeAttr, resolveCanonicalType, USRType,
37     TypeKindAttr, CxParam, CxReturnType, TypeKindVariable;
38 import cpptooling.data.symbol.types : FullyQualifiedNameType;
39 import cpptooling.analyzer.clang.analyze_helper : RecordResult;
40 import dextool.plugin.utility : MarkArray;
41 
42 static import cpptooling.data.class_classification;
43 
44 version (unittest) {
45     import unit_threaded : Name, shouldEqual;
46 } else {
47     private struct Name {
48         string name_;
49     }
50 }
51 
52 /** Control various aspects of the analyze and generation like what nodes to
53  * process.
54  */
55 @safe interface Controller {
56     /// Query the controller with the filename of the AST node for a decision
57     /// if it shall be processed.
58     bool doFile(in string filename, in string info);
59 
60     /** Determine by checking the filesystem if a templated PREFIX_style file shall be created.
61      *
62      * Create it with a minimal style.
63      * Currently just the direction but may change in the future.
64      */
65     Flag!"genStyleInclFile" genStyleInclFile();
66 
67     /// Strip the filename according to user regex.
68     Path doComponentNameStrip(Path fname);
69 }
70 
71 /// Parameters used during generation.
72 /// Important aspact that they do NOT change, therefore it is pure.
73 @safe pure const interface Parameters {
74     import std.typecons : Flag;
75 
76     static struct Files {
77         Path classes;
78         Path components;
79         Path styleIncl;
80         Path styleOutput;
81     }
82 
83     /// Output directory to store files in.
84     Path getOutputDirectory();
85 
86     /// Files to write generated diagram data to.
87     Files getFiles();
88 
89     /// Name affecting filenames.
90     FilePrefix getFilePrefix();
91 
92     /** In all diagrams generate an "!include" of the style file.
93      *
94      * If the file PREFIX_style do not exist, create it with a minimal style.
95      * Currently just the direction but may change in the future.
96      */
97     Flag!"doStyleIncl" doStyleIncl();
98 
99     /// Generate a dot graph in the plantuml file
100     Flag!"doGenDot" doGenDot();
101 
102     /// If class methods should be part of the generated class diagrams.
103     Flag!"genClassMethod" genClassMethod();
104 
105     /// If the parameters of methods should result in directed association.
106     Flag!"genClassParamDependency" genClassParamDependency();
107 
108     /// If the inheritance hierarchy between classes is generated.
109     Flag!"genClassInheritDependency" genClassInheritDependency();
110 
111     /// If the class members result in dependency on those members.
112     Flag!"genClassMemberDependency" genClassMemberDependency();
113 }
114 
115 /// Data produced by the generator like files.
116 @safe interface Products {
117     /** Data pushed from the generator to be written to files.
118      *
119      * The put value is the code generation tree. It allows the caller of
120      * Generator to inject more data in the tree before writing. For example a
121      * custom header.
122      *
123      * Params:
124      *   fname = file the content is intended to be written to.
125      *   data = data to write to the file.
126      */
127     void putFile(Path fname, PlantumlRootModule data);
128 
129     /// ditto.
130     void putFile(Path fname, PlantumlModule data);
131 }
132 
133 /** The supported "kind"s of relations between entities. Related to the UML
134  * standard.
135  */
136 enum RelateKind {
137     None,
138     Extend,
139     Compose,
140     Aggregate,
141     Associate,
142     Relate
143 }
144 
145 /** Relations to targets with count and kind.
146  *
147  * Intented to be used in a hashmap with the key as the "from".
148  */
149 private struct Relate {
150 @safe:
151     alias Key = USRType;
152     alias Kind = RelateKind;
153 
154     private static struct Inner {
155         uint count;
156         Kind kind;
157     }
158 
159     private Inner[][Key] to;
160 
161     /// Returns: number of outgoing connections
162     size_t fanOut() pure nothrow const {
163         return to.length;
164     }
165 
166     void put(Key to_, Kind kind)
167     out {
168         assert(to_ in to);
169     }
170     body {
171         auto v = to_ in to;
172         if (v is null) {
173             to[to_] = Inner[].init;
174             v = to_ in to;
175         }
176 
177         // ugly algorithm, use an inner hashmap instead
178         bool is_new = true;
179         foreach (ref r; *v) {
180             if (r.kind == kind) {
181                 r.count++;
182                 is_new = false;
183                 break;
184             }
185         }
186 
187         if (is_new) {
188             *v ~= Inner(1, kind);
189         }
190     }
191 
192     /** A range of the form FROM-TO with metadata.
193      *
194      * count is the total number of outgoing connections to the target.
195      * For example would 2 Relation and 4 Extend result in the sum of 6.
196      */
197     auto toRange(const Relate.Key from) pure const @trusted {
198         import std.algorithm : map;
199         import std.array : array;
200 
201         static struct RelateTuple {
202             Relate.Key from;
203             Relate.Key to;
204             ulong count;
205         }
206 
207         static ulong sumFanOut(const(Inner)[] inner) pure {
208             import std.algorithm : sum;
209 
210             return inner.map!(a => a.count).sum;
211         }
212 
213         // dfmt off
214         return to.byKeyValue.map!(a => RelateTuple(from, a.key, sumFanOut(a.value)))
215             .array();
216         // dfmt on
217     }
218 
219     /// Convert the TO/value store to a FROM-KIND-TO-COUNT array.
220     auto toFlatArray(const Relate.Key from) pure const @trusted {
221         import std.algorithm : filter, map, joiner;
222         import std.array : array;
223 
224         static struct RelateTuple {
225             Relate.Key from;
226             Kind kind;
227             Relate.Key to;
228             ulong count;
229         }
230 
231         // dfmt off
232         return to.byKeyValue.map!(a => a.value
233                                     .filter!(b => b.kind != Kind.None)
234                                     .map!(b => RelateTuple(from, b.kind, a.key, b.count))
235                                     .array())
236             .joiner()
237             .array();
238         // dfmt on
239     }
240 
241     auto toStringArray(const Relate.Key from) pure const @trusted {
242         import std.algorithm : map;
243         import std.conv : text;
244         import std.format : format;
245         import std.array : array;
246 
247         // dfmt off
248         return this.toFlatArray(from)
249             .map!(b => format("%s -%s- [%d]%s", cast(string) b.from, text(b.kind), b.count, cast(string) b.to))
250             .array();
251         // dfmt on
252     }
253 }
254 
255 private size_t[] nameIndexSortedRange(T, alias sortNameBy)(T arr) pure {
256     import std.algorithm : makeIndex;
257 
258     auto index = new size_t[arr.length];
259 
260     makeIndex!((a, b) => sortNameBy(a) < sortNameBy(b))(arr, index);
261     return index;
262 }
263 
264 private auto nameSortedRange(T, alias sortNameBy)(const T t) pure {
265     import std.algorithm : map;
266     import std.array : array;
267 
268     auto arr = t.asArray();
269     auto index = nameIndexSortedRange!(typeof(arr), sortNameBy)(arr);
270 
271     return index.map!(i => arr[i]).array();
272 }
273 
274 private auto fanOutSorted(T)(T t) pure {
275     import std.algorithm : makeIndex, map;
276     import std.array : array;
277 
278     //TODO how to avoid doing this allocation?
279 
280     auto arr = t.nameSortedRange();
281     auto fanout_i = new size_t[arr.length];
282 
283     // dfmt off
284     makeIndex!((a, b) => t.relate_to[cast(Relate.Key) a.key].fanOut > t.relate_to[cast(Relate.Key) b.key].fanOut)(arr, fanout_i);
285     // dfmt on
286 
287     return fanout_i.map!(i => arr[i]).array();
288 }
289 
290 /** UML Class Diagram.
291  *
292  * Not designed for the general case.
293  * The design is what the plantuml plugin needs when analyzing more than one
294  * file. This is the container that is then passed between the analyze stages.
295  *
296  * All classes must exist in "classes".
297  * It is common that during data gathering a class is found to be related to
298  * another class by a USR. The relation is added before the class represented
299  * by the USR is added.
300  *
301  * A --> B
302  * Directed relation.
303  * A can have many connections to B.
304  *
305  * Store of R[A.B].
306  * When analyzing the structural data it is this kind of relations that are
307  * found. From a class to many X, where X is other classes.
308  * The key used must be unique, thus the choice of using USR.
309  *
310  * Example of relations.
311  * A --> B (member)
312  * A --> B (member)
313  * A --> B (inherit)
314  * B --> A (member)
315  *
316  * relate[A].put(B, Compose)
317  * relate[A].put(B, Compose)
318  * relate[A].put(B, Extend)
319  * relate[B].put(A, Compose)
320  *
321  * The relations are of the kind Fan-out, one-to-many.
322  * They can be sorted in descending fan-out-count order.
323  */
324 class UMLClassDiagram {
325 @safe:
326     import std.typecons : NullableRef;
327     import std.format : FormatSpec;
328 
329     alias ClassClassificationState = cpptooling.data.class_classification.State;
330 
331     alias Key = USRType;
332     struct DisplayName {
333         string payload;
334         alias payload this;
335     }
336 
337     struct Content {
338         string payload;
339         alias payload this;
340     }
341 
342     private struct Class {
343         DisplayName displayName;
344         ClassClassificationState classification;
345         string[] content;
346     }
347 
348     /// The class is only added if it doesn't already exist in the store.
349     void put(Key key, DisplayName display_name) {
350         if (key !in classes) {
351             classes[key] = Class(display_name);
352             relate_to[cast(Relate.Key) key] = Relate.init;
353         }
354     }
355 
356     /** Store parameter content with the key.
357      *
358      * It is the body of the class in a class diagram.
359      */
360     void put(Key key, Content content)
361     in {
362         assert(key in classes);
363     }
364     body {
365         classes[key].content ~= cast(string) content;
366     }
367 
368     /** Set the classification of a class.
369      *
370      * Example would be a pure virtual, which in java would be an interface.
371      */
372     void set(Key key, ClassClassificationState classification)
373     in {
374         assert(key in classes);
375     }
376     body {
377         classes[key].classification = classification;
378     }
379 
380     /** Add a relation between two classes and increase the count on the class
381      * related TO.
382      */
383     void relate(Key from, Key to, DisplayName display_name, Relate.Kind kind)
384     out {
385         assert(from in classes);
386         assert(to in classes);
387         assert(kind != Relate.Kind.None);
388     }
389     body {
390         put(to, display_name);
391         relate_to[cast(Relate.Key) from].put(cast(Relate.Key) to, kind);
392     }
393 
394     /** Use to retrieve the relation struct for the key.
395      *
396      * Example:
397      * ---
398      * diagram.relateTo(Key("foo")).put(Key("bar"), Relate.Kind.Associate);
399      * ---
400      */
401     const(Relate) relateTo(Key k) pure const
402     in {
403         assert(k in classes);
404         assert((cast(Relate.Key) k) in relate_to);
405     }
406     body {
407         return relate_to[cast(Relate.Key) k];
408     }
409 
410     /// Returns: Flat array of all relations of type FROM-KIND-TO-COUNT.
411     auto relateToFlatArray() pure const @trusted {
412         import std.algorithm : map, joiner;
413         import std.array : array;
414 
415         return relate_to.byKeyValue.map!(a => a.value.toFlatArray(a.key)).joiner().array();
416     }
417 
418     private static struct KeyClass {
419         Key key;
420         const(Class) value;
421     }
422 
423     /// Returns: An array of the key/values.
424     KeyClass[] asArray() const pure nothrow @trusted {
425         import std.array : array;
426         import std.algorithm : map;
427 
428         //TODO how to do this without so much generated GC
429 
430         // dfmt off
431         return classes.byKeyValue
432             .map!(a => KeyClass(a.key, a.value))
433             .array();
434         // dfmt on
435     }
436 
437     /// Returns: An array of the key/values sorted on key.
438     auto nameSortedRange() const pure @trusted {
439         static string sortClassNameBy(T)(ref T a) {
440             return a.value.displayName;
441         }
442 
443         return .nameSortedRange!(typeof(this), sortClassNameBy)(this);
444     }
445 
446     private string[] classesToStringArray() const pure @trusted {
447         import std.algorithm : map, joiner;
448         import std.array : array;
449         import std.ascii : newline;
450         import std.conv : text;
451         import std.format : format;
452         import std.range : only, chain, takeOne;
453 
454         // dfmt off
455         return classes
456             .byKeyValue
457             .map!(a => chain(only(format("%s -> %s%s",
458                                          a.value.displayName,
459                                          a.key,
460                                          a.value.content.length == 0 ? "" : " {")),
461                              a.value.content.dup.map!(b => "  " ~ b),
462                              a.value.content.takeOne.map!(b => "} // " ~ a.value.displayName))
463                   .joiner(newline)
464                   .text)
465             .array();
466         // dfmt on
467     }
468 
469     private string[] relateToStringArray() const pure @trusted {
470         import std.algorithm : map, joiner;
471         import std.array : array;
472 
473         return relate_to.byKeyValue.map!(a => a.value.toStringArray(a.key)).joiner().array();
474     }
475 
476     void toString(Writer, Char)(scope Writer w, FormatSpec!Char) const {
477         import std.ascii : newline;
478         import std.format : formattedWrite;
479         import std.range.primitives : put;
480         import std.range : zip, repeat;
481 
482         formattedWrite(w, "UML Class Diagram (Total %d) {", classes.length);
483         put(w, newline);
484         foreach (a; zip(classesToStringArray, repeat(newline))) {
485             put(w, a[0]);
486             put(w, a[1]);
487         }
488         foreach (a; zip(relateToStringArray, repeat(newline))) {
489             put(w, a[0]);
490             put(w, a[1]);
491         }
492         put(w, "} // UML Class Diagram");
493     }
494 
495     override string toString() @safe pure const {
496         import std.exception : assumeUnique;
497         import std.format : FormatSpec;
498 
499         char[] buf;
500         buf.reserve(100);
501         auto fmt = FormatSpec!char("%s");
502         toString((const(char)[] s) { buf ~= s; }, fmt);
503         auto trustedUnique(T)(T t) @trusted {
504             return assumeUnique(t);
505         }
506 
507         return trustedUnique(buf);
508     }
509 
510     private Relate[Relate.Key] relate_to;
511     private Class[Key] classes;
512 }
513 
514 /** UML Component Diagram.
515  *
516  * Not designed for the general case.
517  * The design is what the plantuml plugin needs when analyzing more than one
518  * file. This is the container that is then passed between the analyze stages.
519  *
520  * The relations are of the kind Fan-out.
521  *
522  * See_Also: UMLClassDiagram
523  */
524 class UMLComponentDiagram {
525     import std.container.rbtree : RedBlackTree;
526 
527     struct Key {
528         string payload;
529         alias payload this;
530     }
531 
532     struct Location {
533         string payload;
534         alias payload this;
535     }
536 
537     struct DisplayName {
538         string payload;
539         alias payload this;
540     }
541 
542     private struct Component {
543         DisplayName displayName;
544         string[] toFile;
545         RedBlackTree!Location contains;
546     }
547 
548     /// The component is only added if it doesn't already exist in the store.
549     void put(Key key, DisplayName display_name) @safe {
550         if (key !in components) {
551             components[key] = Component(display_name, null, new RedBlackTree!Location);
552             relate_to[cast(Relate.Key) key] = Relate.init;
553         }
554     }
555 
556     /// Add a location that the component encapsulate
557     void put(Key key, Location loc) @trusted
558     out {
559         assert(key in components);
560     }
561     body {
562         components[key].contains.insert(loc);
563     }
564 
565     /** Add a relation between two components and increase the count on the class
566      * related TO.
567      */
568     void relate(Key from, Key to, DisplayName toDisplayName, Relate.Kind kind) @safe
569     out {
570         assert(from in components);
571         assert(to in components);
572         assert(kind != Relate.Kind.None);
573     }
574     body {
575         put(to, toDisplayName);
576 
577         auto rel = cast(Relate.Key) from in relate_to;
578         if (rel is null) {
579             relate_to[cast(Relate.Key) from] = Relate();
580             rel = cast(Relate.Key) from in relate_to;
581         }
582         rel.put(cast(Relate.Key) to, kind);
583 
584         auto comp = from in components;
585         if (comp is null) {
586             // TODO this is a hack. The display name should be the froms name.
587             components[from] = Component(cast(DisplayName) from, null, new RedBlackTree!Location);
588             comp = from in components;
589         }
590         comp.toFile ~= cast(string) to;
591     }
592 
593     /** Use to retrieve the relation struct for the key.
594      *
595      * Example:
596      * ---
597      * diagram.relateTo(Key("foo")).put(Key("bar"), Relate.Kind.Associate);
598      * ---
599      */
600     const(Relate) relateTo(Key k) pure const @safe
601     in {
602         assert(k in components);
603         assert((cast(Relate.Key) k) in relate_to);
604     }
605     body {
606         return relate_to[cast(Relate.Key) k];
607     }
608 
609     /// Return: Flat array of all relations of type FROM-KIND-TO-COUNT.
610     auto relateToFlatArray() pure const @trusted {
611         import std.algorithm : map, joiner;
612         import std.array : array;
613 
614         return relate_to.byKeyValue.map!(a => a.value.toFlatArray(a.key)).joiner().array();
615     }
616 
617     private static struct KeyComponent {
618         Key key;
619         const(Component) value;
620     }
621 
622     /// Returns: Flat array of all relations of type FROM-KIND-TO-COUNT.
623     KeyComponent[] asArray() const pure nothrow @trusted {
624         import std.array : array;
625         import std.algorithm : map;
626 
627         //TODO how to do this without so much generated GC
628 
629         // dfmt off
630         return components.byKeyValue
631             .map!(a => KeyComponent(a.key, a.value))
632             .array();
633         // dfmt on
634     }
635 
636     /// Returns: An array of the key/values sorted on key.
637     auto nameSortedRange() const pure @trusted {
638         static string sortComponentNameBy(T)(ref T a) {
639             return cast(string) a.value.displayName;
640         }
641 
642         return .nameSortedRange!(typeof(this), sortComponentNameBy)(this);
643     }
644 
645     private string[] componentsToStringArray() const pure @trusted {
646         import std.algorithm : map, joiner;
647         import std.ascii : newline;
648         import std.array : array;
649         import std.format : format;
650         import std.typecons : tuple;
651 
652         // dfmt off
653         return nameSortedRange
654             .map!(a => tuple(a.key, a.value.displayName, a.value.contains[].map!(a => newline ~ "  " ~ cast(string) a).joiner))
655             .map!(a => format("%s as %s%s", a[0],
656                 a[1],
657                 a[2])).array();
658         // dfmt on
659     }
660 
661     private string[] relateToStringArray() const pure @trusted {
662         import std.algorithm : map, joiner;
663         import std.array : array;
664 
665         return relate_to.byKeyValue.map!(a => a.value.toStringArray(a.key)).joiner().array();
666     }
667 
668     /// String representation of the Component Diagram.
669     void toString(Writer)(scope Writer w) @safe const {
670         import std.ascii : newline;
671         import std.format : formattedWrite;
672         import std.range.primitives : put;
673         import std.range : zip, repeat;
674 
675         formattedWrite(w, "UML Component Diagram (Total %d) {", components.length);
676         put(w, newline);
677         foreach (a; zip(componentsToStringArray, repeat(newline))) {
678             put(w, a[0]);
679             put(w, a[1]);
680         }
681         foreach (a; zip(relateToStringArray, repeat(newline))) {
682             put(w, a[0]);
683             put(w, a[1]);
684         }
685         put(w, "} // UML Component Diagram");
686     }
687 
688     ///
689     override string toString() @safe const {
690         import std.exception : assumeUnique;
691 
692         char[] buf;
693         buf.reserve(100);
694         this.toString((const(char)[] s) { buf ~= s; });
695         auto trustedUnique(T)(T t) @trusted {
696             return assumeUnique(t);
697         }
698 
699         return trustedUnique(buf);
700     }
701 
702 private:
703     Relate[Relate.Key] relate_to;
704     Component[Key] components;
705 }
706 
707 @Name("Should be a None relate not shown and an extended relate")
708 unittest {
709     Relate r;
710     r.put(Relate.Key("B"), Relate.Kind.None);
711     r.put(Relate.Key("B"), Relate.Kind.Extend);
712 
713     r.toStringArray(Relate.Key("A")).shouldEqual(["A -Extend- [1]B"]);
714 }
715 
716 @Name("Should be all types of relates")
717 unittest {
718     Relate r;
719     r.put(Relate.Key("B"), Relate.Kind.None);
720     r.put(Relate.Key("B"), Relate.Kind.Extend);
721     r.put(Relate.Key("B"), Relate.Kind.Compose);
722     r.put(Relate.Key("B"), Relate.Kind.Aggregate);
723     r.put(Relate.Key("B"), Relate.Kind.Associate);
724 
725     r.toStringArray(Relate.Key("A")).shouldEqual([
726             "A -Extend- [1]B", "A -Compose- [1]B", "A -Aggregate- [1]B",
727             "A -Associate- [1]B"
728             ]);
729 }
730 
731 @Name("Should be two relates to the same target")
732 unittest {
733     Relate r;
734     r.put(Relate.Key("B"), Relate.Kind.Compose);
735     r.put(Relate.Key("B"), Relate.Kind.Compose);
736 
737     r.toStringArray(Relate.Key("A")).shouldEqual(["A -Compose- [2]B"]);
738 }
739 
740 @Name("Should be a UML diagram with one class")
741 unittest {
742     auto uml = new UMLClassDiagram;
743     uml.put(UMLClassDiagram.Key("A"), UMLClassDiagram.DisplayName("A"));
744 
745     uml.toString.shouldEqual("UML Class Diagram (Total 1) {
746 A -> A
747 } // UML Class Diagram");
748 }
749 
750 @Name("Should be a UML diagram with two classes related")
751 unittest {
752     auto uml = new UMLClassDiagram;
753     auto ka = UMLClassDiagram.Key("A");
754     auto kb = UMLClassDiagram.Key("B");
755     uml.put(ka, UMLClassDiagram.DisplayName("A"));
756     uml.put(kb, UMLClassDiagram.DisplayName("B"));
757 
758     uml.relate(ka, kb, UMLClassDiagram.DisplayName("B"), Relate.Kind.Extend);
759 
760     uml.toString.shouldEqual("UML Class Diagram (Total 2) {
761 A -> A
762 B -> B
763 A -Extend- [1]B
764 } // UML Class Diagram");
765 }
766 
767 @Name("Should be a UML Component diagram with two components related")
768 unittest {
769     auto uml = new UMLComponentDiagram;
770     auto ka = UMLComponentDiagram.Key("a");
771     auto kb = UMLComponentDiagram.Key("b");
772     uml.put(ka, cast(UMLComponentDiagram.DisplayName) "A");
773     // shall be dedupliated
774     uml.put(ka, cast(UMLComponentDiagram.Location) "file.h");
775     uml.put(ka, cast(UMLComponentDiagram.Location) "file.h");
776 
777     uml.relate(ka, kb, cast(UMLComponentDiagram.DisplayName) "B", Relate.Kind.Relate);
778 
779     uml.toString.shouldEqual("UML Component Diagram (Total 2) {
780 a as A
781   file.h
782 b as B
783 a -Relate- [1]b
784 } // UML Component Diagram");
785 }
786 
787 /** Context for the UML diagram generator from internal representation to the
788  * concrete files.
789  */
790 struct Generator {
791     import cpptooling.data : CppRoot;
792     import cpptooling.data.symbol : Container;
793 
794     private static struct Modules {
795         private static void postInit(ref typeof(this) m) {
796             m.classes_dot.suppressIndent(1);
797             m.components_dot.suppressIndent(1);
798         }
799 
800         import dextool.plugin.utility : MakerInitializingClassMembers;
801 
802         mixin MakerInitializingClassMembers!(Modules, postInit);
803 
804         PlantumlModule classes;
805         PlantumlModule classes_dot;
806         PlantumlModule components;
807         PlantumlModule components_dot;
808     }
809 
810     /** Instansiate.
811      *
812      * Params:
813      *  ctrl = dynamic control of data generation.
814      *  params = static control, may never change during generation.
815      *  products = receiver of UML diagrams.
816      */
817     this(Controller ctrl, Parameters params, Products products) {
818         this.ctrl = ctrl;
819         this.params = params;
820         this.products = products;
821         this.umlClass = new UMLClassDiagram;
822         this.umlComponent = new UMLComponentDiagram;
823     }
824 
825     /** Process the sources to produce UML diagrams in-memory.
826      *
827      * The diagrams are forwarded to the registered Products instance.
828      */
829     auto process() {
830         auto m = Modules.make();
831         generate(umlClass, umlComponent, params.doGenDot, m);
832         postProcess(ctrl, params, products, m);
833     }
834 
835     /// The UML diagram used as source during generation.
836     UMLClassDiagram umlClass;
837 
838     /// ditto
839     UMLComponentDiagram umlComponent;
840 
841 private:
842     Controller ctrl;
843     Parameters params;
844     Products products;
845 
846     static void postProcess(Controller ctrl, Parameters params, Products prods, Modules m) {
847         static PlantumlRootModule makeMinimalStyle(Flag!"genClassMethod" show_methods) {
848             auto proot = PlantumlRootModule.make();
849 
850             auto class_ = proot.makeUml;
851             class_.stmt("left to right direction");
852             class_.stmt("'skinparam linetype polyline");
853             class_.stmt("'skinparam linetype ortho");
854             class_.stmt("set namespaceSeparator none");
855             if (show_methods) {
856                 class_.stmt("'hide members");
857             } else {
858                 class_.stmt("hide members");
859             }
860 
861             auto component = proot.makeUml;
862             component.stmt("left to right direction");
863             component.stmt("skinparam componentStyle uml2");
864             component.stmt("'skinparam linetype polyline");
865             component.stmt("'skinparam linetype ortho");
866             component.stmt("set namespaceSeparator none");
867             component.stmt("hide circle");
868             component.stmt("hide methods");
869             component.stmt("'To hide file location");
870             component.stmt("hide members");
871 
872             return proot;
873         }
874 
875         enum DotLayout {
876             Neato,
877             Dot,
878             DotOrtho
879         }
880 
881         static PlantumlModule makeDotPreamble(DotLayout layout, Flag!"doSmall" doSmall) {
882             auto m = new PlantumlModule;
883             m.suppressIndent(1);
884 
885             //TODO if DotOrtho and Dot don't change consider removing the code
886             // duplication.
887             final switch (layout) with (DotLayout) {
888             case Neato:
889                 m.stmt("layout=neato");
890                 m.stmt("edge [len=3]");
891                 break;
892             case DotOrtho:
893                 m.stmt("layout=dot");
894                 m.stmt("rankdir=LR");
895                 m.stmt("pack=true");
896                 m.stmt("concentrate=true");
897                 // inactivating, can result in a crash as of
898                 // dot 2.38.0 (20140413.2041)
899                 m.stmt("// activate for orthogonal lines, aka straight lines");
900                 m.stmt("// but can result in GraphViz/dot crashing");
901                 m.stmt("//splines=ortho");
902                 break;
903             case Dot:
904                 m.stmt("layout=dot");
905                 m.stmt("rankdir=LR");
906                 m.stmt("pack=true");
907                 m.stmt("concentrate=true");
908                 break;
909             }
910 
911             m.sep(2);
912 
913             m.stmt("colorscheme=svg");
914             if (doSmall) {
915                 m.stmt("node [style=rounded shape=box fontsize=9 width=0.25 height=0.375]");
916             } else {
917                 m.stmt("node [style=rounded shape=box]");
918             }
919             m.sep(2);
920 
921             return m;
922         }
923 
924         enum StyleType {
925             Class,
926             Component
927         }
928 
929         static PlantumlModule makeStyleInclude(Flag!"doStyleIncl" do_style_incl,
930                 Path style_file, StyleType style_type) {
931             import std.conv : to;
932 
933             auto m = new PlantumlModule;
934             if (!do_style_incl) {
935                 return m;
936             }
937 
938             m.stmt("!include " ~ style_file ~ "!" ~ to!string(cast(int) style_type));
939 
940             return m;
941         }
942 
943         static void makeUml(Products prods, Path fname, PlantumlModule style,
944                 PlantumlModule content) {
945             import std.algorithm : filter;
946 
947             auto proot = PlantumlRootModule.make();
948             auto c = proot.makeUml();
949             c.suppressIndent(1);
950 
951             foreach (m; [style, content].filter!(a => a !is null)) {
952                 c.append(m);
953             }
954 
955             prods.putFile(fname, proot);
956         }
957 
958         static void makeDot(Products prods, Path fname, PlantumlModule style,
959                 PlantumlModule content) {
960             import std.algorithm : filter;
961             import std.path : stripExtension, baseName;
962 
963             immutable ext_dot = ".dot";
964 
965             auto fname_dot = Path(fname.stripExtension ~ ext_dot);
966             auto dot = new PlantumlModule;
967             auto digraph = dot.digraph("g");
968             digraph.suppressThisIndent(1);
969             foreach (m; [style, content].filter!(a => a !is null)) {
970                 digraph.append(m);
971             }
972             prods.putFile(fname_dot, dot);
973 
974             auto proot = PlantumlRootModule.make();
975             auto pu = proot.makeDot;
976             pu.stmt("!include " ~ (cast(string) fname_dot).baseName);
977             prods.putFile(fname, proot);
978         }
979 
980         static Path makeDotFileName(Path f, DotLayout layout) {
981             import std.path : extension, stripExtension;
982 
983             auto ext = extension(cast(string) f);
984 
985             string suffix;
986             final switch (layout) with (DotLayout) {
987             case Dot:
988                 goto case;
989             case DotOrtho:
990                 suffix = "_dot";
991                 break;
992             case Neato:
993                 suffix = "_neato";
994                 break;
995             }
996 
997             return Path((cast(string) f).stripExtension ~ suffix ~ ext);
998         }
999 
1000         if (ctrl.genStyleInclFile) {
1001             prods.putFile(params.getFiles.styleOutput, makeMinimalStyle(params.genClassMethod));
1002         }
1003 
1004         if (params.doGenDot) {
1005             makeDot(prods, makeDotFileName(params.getFiles.classes, DotLayout.Dot),
1006                     makeDotPreamble(DotLayout.Dot, Yes.doSmall), m.classes_dot);
1007             makeDot(prods, makeDotFileName(params.getFiles.classes, DotLayout.Neato),
1008                     makeDotPreamble(DotLayout.Neato, Yes.doSmall), m.classes_dot);
1009             makeDot(prods, makeDotFileName(params.getFiles.components, DotLayout.Neato),
1010                     makeDotPreamble(DotLayout.Neato, No.doSmall), m.components_dot);
1011             makeDot(prods, makeDotFileName(params.getFiles.components, DotLayout.DotOrtho),
1012                     makeDotPreamble(DotLayout.DotOrtho, No.doSmall), m.components_dot);
1013         }
1014 
1015         makeUml(prods, params.getFiles.classes, makeStyleInclude(params.doStyleIncl,
1016                 params.getFiles.styleIncl, StyleType.Class), m.classes);
1017         makeUml(prods, params.getFiles.components, makeStyleInclude(params.doStyleIncl,
1018                 params.getFiles.styleIncl, StyleType.Component), m.components);
1019     }
1020 }
1021 
1022 private struct ClassClassificationResult {
1023     TypeKindAttr type;
1024     cpptooling.data.class_classification.State classification;
1025 }
1026 
1027 private final class UMLClassVisitor(ControllerT, ReceiveT) : Visitor {
1028     import std.algorithm : map, copy, each, joiner;
1029     import std.array : Appender;
1030     import std.typecons : scoped, NullableRef;
1031 
1032     import cpptooling.analyzer.clang.ast : ClassDecl, CxxBaseSpecifier, Constructor,
1033         Destructor, CxxMethod, FieldDecl, CxxAccessSpecifier, generateIndentIncrDecr;
1034     import cpptooling.analyzer.clang.analyze_helper : analyzeRecord, analyzeConstructor, analyzeDestructor,
1035         analyzeCxxMethod, analyzeFieldDecl, analyzeCxxBaseSpecified, toAccessType;
1036     import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
1037     import cpptooling.data : CppNsStack, CppNs, AccessType, CppAccess, MemberVirtualType;
1038 
1039     import cpptooling.data.class_classification : ClassificationState = State;
1040     import cpptooling.data.class_classification : classifyClass, MethodKind;
1041 
1042     alias visit = Visitor.visit;
1043 
1044     mixin generateIndentIncrDecr;
1045 
1046     /** Type representation of this class.
1047      * Used as the source of the outgoing relations from this class.
1048      */
1049     TypeKindAttr type;
1050 
1051     /** Classification of the class.
1052      * Affected by methods.
1053      */
1054     ClassificationState classification;
1055 
1056     private {
1057         ControllerT ctrl;
1058         NullableRef!ReceiveT recv;
1059 
1060         Container* container;
1061         CppNsStack ns_stack;
1062         CppAccess access;
1063 
1064         /// If the class has any members.
1065         Flag!"hasMember" hasMember;
1066     }
1067 
1068     this(TypeKindAttr type, const(CppNs)[] reside_in_ns, ControllerT ctrl,
1069             ref ReceiveT recv, ref Container container, in uint indent) {
1070         this.ctrl = ctrl;
1071         this.recv = &recv;
1072         this.container = &container;
1073         this.indent = indent;
1074         this.ns_stack = CppNsStack(reside_in_ns.dup);
1075 
1076         this.access = CppAccess(AccessType.Private);
1077         this.classification = ClassificationState.Unknown;
1078 
1079         this.type = type;
1080     }
1081 
1082     /// Nested class definitions.
1083     override void visit(const(ClassDecl) v) @trusted {
1084         mixin(mixinNodeLog!());
1085         logger.info("class: ", v.cursor.spelling);
1086 
1087         auto result = analyzeRecord(v, *container, indent);
1088 
1089         foreach (loc; container.find!LocationTag(result.type.kind.usr).map!(a => a.any).joiner) {
1090             if (!ctrl.doFile(loc.file, loc.file)) {
1091                 return;
1092             }
1093         }
1094 
1095         recv.put(result, ns_stack);
1096 
1097         auto visitor = scoped!(UMLClassVisitor!(ControllerT, ReceiveT))(result.type,
1098                 ns_stack, ctrl, recv, *container, indent + 1);
1099         v.accept(visitor);
1100 
1101         auto result_class = ClassClassificationResult(visitor.type, visitor.classification);
1102         recv.put(this.type, result_class);
1103     }
1104 
1105     /// Analyze the inheritance(s).
1106     override void visit(const(CxxBaseSpecifier) v) {
1107         import cpptooling.data : TypeKind;
1108 
1109         mixin(mixinNodeLog!());
1110 
1111         auto result = analyzeCxxBaseSpecified(v, *container, indent);
1112 
1113         debug {
1114             import std.algorithm : each;
1115             import std.range : retro;
1116             import cpptooling.data : CppInherit;
1117 
1118             auto inherit = CppInherit(result.name, result.access);
1119             retro(result.reverseScope).each!(a => inherit.put(a));
1120 
1121             logger.trace("inherit: ", inherit.toString);
1122         }
1123 
1124         recv.put(this.type, result);
1125     }
1126 
1127     override void visit(const(Constructor) v) {
1128         mixin(mixinNodeLog!());
1129 
1130         auto result = analyzeConstructor(v, *container, indent);
1131 
1132         debug {
1133             auto tor = CppCtor(result.type.kind.usr, result.name, result.params, access);
1134             logger.trace("ctor: ", tor.toString);
1135         }
1136 
1137         recv.put(this.type, result, access);
1138     }
1139 
1140     override void visit(const(Destructor) v) {
1141         mixin(mixinNodeLog!());
1142 
1143         auto result = analyzeDestructor(v, *container, indent);
1144         classification = classifyClass(classification, MethodKind.Dtor,
1145                 cast(MemberVirtualType) result.virtualKind, hasMember);
1146 
1147         debug {
1148             auto tor = CppDtor(result.type.kind.usr, result.name, access, result.virtualKind);
1149             logger.trace("dtor: ", tor.toString);
1150         }
1151 
1152         recv.put(this.type, result, access);
1153     }
1154 
1155     override void visit(const(CxxMethod) v) {
1156         mixin(mixinNodeLog!());
1157 
1158         auto result = analyzeCxxMethod(v, *container, indent);
1159 
1160         classification = classifyClass(classification, MethodKind.Method,
1161                 cast(MemberVirtualType) result.virtualKind, hasMember);
1162 
1163         debug {
1164             import cpptooling.data.type : CppConstMethod;
1165             import cpptooling.data : CppMethod;
1166 
1167             auto method = CppMethod(result.type.kind.usr, result.name, result.params,
1168                     result.returnType, access, CppConstMethod(result.isConst), result.virtualKind);
1169             logger.trace("method: ", method.toString);
1170         }
1171 
1172         recv.put(this.type, result, access);
1173     }
1174 
1175     override void visit(const(FieldDecl) v) {
1176         mixin(mixinNodeLog!());
1177 
1178         auto result = analyzeFieldDecl(v, *container, indent);
1179 
1180         // TODO probably not necessary for classification to store it as a
1181         // member. Instead extend MethodKind to having a "Member".
1182         hasMember = Yes.hasMember;
1183         classification = classifyClass(classification, MethodKind.Unknown,
1184                 MemberVirtualType.Unknown, hasMember);
1185         debug {
1186             logger.trace("member: ", cast(string) result.name);
1187         }
1188 
1189         recv.put(this.type, result, access);
1190     }
1191 
1192     override void visit(const(CxxAccessSpecifier) v) @trusted {
1193         mixin(mixinNodeLog!());
1194         access = CppAccess(toAccessType(v.cursor.access.accessSpecifier));
1195     }
1196 }
1197 
1198 final class UMLVisitor(ControllerT, ReceiveT) : Visitor {
1199     import std.algorithm : map, filter, cache, joiner;
1200     import std.range : chain, only, dropOne, ElementType;
1201     import std.typecons : scoped, NullableRef;
1202 
1203     import cpptooling.analyzer.clang.ast : TranslationUnit, UnexposedDecl,
1204         VarDecl, FunctionDecl, ClassDecl, Namespace, generateIndentIncrDecr;
1205     import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl,
1206         analyzeVarDecl, analyzeRecord, analyzeTranslationUnit;
1207     import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
1208     import cpptooling.data : CppNsStack, CppNs;
1209 
1210     alias visit = Visitor.visit;
1211 
1212     mixin generateIndentIncrDecr;
1213 
1214     private {
1215         ReceiveT recv;
1216         ControllerT ctrl;
1217 
1218         NullableRef!Container container;
1219         CppNs[] ns_stack;
1220 
1221     }
1222 
1223     this(ControllerT ctrl, ref ReceiveT recv, ref Container container) {
1224         this.ctrl = ctrl;
1225         this.recv = recv;
1226         this.container = &container;
1227     }
1228 
1229     override void visit(const(TranslationUnit) v) {
1230         mixin(mixinNodeLog!());
1231         v.accept(this);
1232 
1233         auto result = analyzeTranslationUnit(v, container, indent);
1234         recv.put(result);
1235     }
1236 
1237     override void visit(const(UnexposedDecl) v) {
1238         mixin(mixinNodeLog!());
1239 
1240         // An unexposed may be:
1241 
1242         // an extern "C"
1243         // UnexposedDecl "" extern "C" {...
1244         //   FunctionDecl "fun_c_linkage" void func_c_linkage
1245         v.accept(this);
1246     }
1247 
1248     override void visit(const(VarDecl) v) {
1249         mixin(mixinNodeLog!());
1250 
1251         auto result = () @trusted { return analyzeVarDecl(v, container, indent); }();
1252 
1253         debug {
1254             logger.info("global variable: ", cast(string) result.name);
1255         }
1256 
1257         recv.put(result);
1258     }
1259 
1260     override void visit(const(FunctionDecl) v) {
1261         mixin(mixinNodeLog!());
1262 
1263         auto result = analyzeFunctionDecl(v, container, indent);
1264 
1265         debug {
1266             auto func = CFunction(result.type.kind.usr, result.name, result.params,
1267                     CxReturnType(result.returnType), result.isVariadic, result.storageClass);
1268             logger.info("function: ", func.toString);
1269         }
1270 
1271         recv.put(result);
1272     }
1273 
1274     override void visit(const(ClassDecl) v) @trusted {
1275         mixin(mixinNodeLog!());
1276         logger.info("class: ", v.cursor.spelling);
1277 
1278         auto result = analyzeRecord(v, container, indent);
1279 
1280         foreach (loc; container.find!LocationTag(result.type.kind.usr).map!(a => a.any).joiner) {
1281             if (!ctrl.doFile(loc.file, loc.file)) {
1282                 return;
1283             }
1284         }
1285 
1286         recv.put(result, ns_stack);
1287 
1288         auto visitor = scoped!(UMLClassVisitor!(ControllerT, ReceiveT))(result.type,
1289                 ns_stack, ctrl, recv, container, indent + 1);
1290         v.accept(visitor);
1291 
1292         auto r_classification = ClassClassificationResult(visitor.type, visitor.classification);
1293         recv.put(r_classification);
1294     }
1295 
1296     override void visit(const(Namespace) v) {
1297         mixin(mixinNodeLog!());
1298 
1299         () @trusted { ns_stack ~= CppNs(v.cursor.spelling); }();
1300         // pop the stack when done
1301         scope (exit)
1302             ns_stack = ns_stack[0 .. $ - 1];
1303 
1304         // fill the namespace with content from the analyse
1305         v.accept(this);
1306     }
1307 }
1308 
1309 private struct TransformToClassDiagram(ControllerT, LookupT) {
1310 @safe:
1311     import cpptooling.analyzer.clang.analyze_helper : CxxMethodResult,
1312         ConstructorResult, DestructorResult, FieldDeclResult, CxxBaseSpecifierResult;
1313     import cpptooling.data.type : CppAccess;
1314     import cpptooling.data.type : CppNs;
1315 
1316     invariant {
1317         assert(uml !is null);
1318     }
1319 
1320     private {
1321         UMLClassDiagram uml;
1322         ControllerT ctrl;
1323         LookupT lookup;
1324     }
1325 
1326     /// If class methods should be part of the generated class diagrams.
1327     Flag!"genClassMethod" genClassMethod;
1328 
1329     /// If the parameters of methods should result in directed association.
1330     Flag!"genClassParamDependency" genClassParamDependency;
1331 
1332     /// If the inheritance hierarchy between classes is generated.
1333     Flag!"genClassInheritDependency" genClassInheritDependency;
1334 
1335     /// If the class members result in dependency on those members.
1336     Flag!"genClassMemberDependency" genClassMemberDependency;
1337 
1338     private static string toPrefix(CppAccess access) {
1339         import cpptooling.data.type : CppAccess, AccessType;
1340 
1341         final switch (access) {
1342         case AccessType.Public:
1343             return "+";
1344         case AccessType.Protected:
1345             return "#";
1346         case AccessType.Private:
1347             return "-";
1348         }
1349     }
1350 
1351     void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) {
1352         import std.algorithm : map, joiner;
1353         import std.conv : text;
1354         import std.range : chain, only, retro;
1355         import cpptooling.data : TypeKind, TypeAttr, toStringDecl;
1356 
1357         if (genClassInheritDependency) {
1358             auto src_key = makeClassKey(src.kind.usr);
1359 
1360             auto canonical = lookup.kind(result.canonicalUSR).front;
1361             auto dest_key = makeClassKey(canonical.usr);
1362             auto fqn = canonical.toStringDecl(TypeAttr.init);
1363 
1364             uml.relate(src_key, dest_key,
1365                     cast(UMLClassDiagram.DisplayName) fqn, Relate.Kind.Extend);
1366         }
1367     }
1368 
1369     /// Reconstruct the function signature as a UML comment.
1370     void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) {
1371         import std.algorithm : filter;
1372         import std.traits : ReturnType;
1373         import std.range : chain, only;
1374 
1375         import cpptooling.data : CppMethod, CppConstMethod;
1376 
1377         ReturnType!makeClassKey src_key;
1378 
1379         if (genClassMethod || genClassParamDependency) {
1380             src_key = makeClassKey(src.kind.usr);
1381         }
1382 
1383         if (genClassMethod) {
1384             auto method = CppMethod(USRType("dummy"), result.name, result.params,
1385                     result.returnType, access, CppConstMethod(result.isConst), result.virtualKind);
1386             method.usr.nullify;
1387             uml.put(src_key, UMLClassDiagram.Content(toPrefix(access) ~ method.toString));
1388         }
1389 
1390         if (genClassParamDependency) {
1391             // dfmt off
1392             auto relations =
1393                 chain(getClassMethodRelation(result.params, lookup),
1394                       only(getTypeRelation(cast(TypeKindAttr) result.returnType, lookup)))
1395                 .filter!(a => a.kind != Relate.Kind.None)
1396                 // remove self referencing keys, would result in circles which
1397                 // just clutters the diagrams
1398                 .filter!(a => a.key != src.kind.usr);
1399             // dfmt on
1400             foreach (rel; relations) {
1401                 auto dest_key = makeClassKey(rel.key);
1402                 uml.relate(src_key, dest_key, rel.display, rel.kind);
1403             }
1404         }
1405     }
1406 
1407     void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) {
1408         import std.algorithm : filter;
1409         import std.traits : ReturnType;
1410 
1411         import cpptooling.data : CppCtor;
1412 
1413         ReturnType!makeClassKey src_key;
1414 
1415         if (genClassMethod || genClassParamDependency) {
1416             src_key = makeClassKey(src.kind.usr);
1417         }
1418 
1419         if (genClassMethod) {
1420             auto tor = CppCtor(result.type.kind.usr, result.name, result.params, access);
1421             uml.put(src_key, UMLClassDiagram.Content(toPrefix(access) ~ tor.toString));
1422         }
1423 
1424         if (genClassParamDependency) {
1425             // dfmt off
1426             auto relations = getClassMethodRelation(result.params, lookup)
1427                 .filter!(a => a.kind != Relate.Kind.None)
1428                 // remove self referencing keys, would result in circles which
1429                 // just clutters the diagrams
1430                 .filter!(a => a.key != src.kind.usr);
1431             // dfmt on
1432             foreach (rel; relations) {
1433                 auto dest_key = makeClassKey(rel.key);
1434                 uml.relate(src_key, dest_key, rel.display, rel.kind);
1435             }
1436         }
1437     }
1438 
1439     void put(ref const(TypeKindAttr) src, ref const(DestructorResult) result, in CppAccess access) {
1440         import cpptooling.data : CppDtor;
1441 
1442         if (genClassMethod) {
1443             auto key = makeClassKey(src.kind.usr);
1444             auto tor = CppDtor(result.type.kind.usr, result.name, access, result.virtualKind);
1445             uml.put(key, UMLClassDiagram.Content(toPrefix(access) ~ tor.toString));
1446         }
1447     }
1448 
1449     void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) {
1450         import std.algorithm : filter;
1451 
1452         if (genClassMemberDependency) {
1453             auto rel = getClassMemberRelation(result.type, lookup);
1454             if (rel.kind != Relate.Kind.None) {
1455                 auto src_key = makeClassKey(src.kind.usr);
1456                 auto dest_key = makeClassKey(rel.key);
1457                 uml.relate(src_key, dest_key, rel.display, rel.kind);
1458             }
1459         }
1460     }
1461 
1462     void put(ref const(ClassClassificationResult) result) {
1463         auto key = makeClassKey(result.type.kind.usr);
1464         uml.set(key, result.classification);
1465     }
1466 
1467     void put(ref const(RecordResult) src, const(CppNs)[] reside_in) {
1468         import std.algorithm : map, joiner;
1469         import std.conv : text;
1470         import std.range : chain, only;
1471 
1472         auto key = makeClassKey(src.type.kind.usr);
1473         string fqn = chain(reside_in.map!(a => cast(string) a), only(cast(string) src.name)).joiner("::")
1474             .text;
1475         uml.put(key, cast(UMLClassDiagram.DisplayName) fqn);
1476     }
1477 }
1478 
1479 /** Transform data from a data source (via push) to a UML component diagram.
1480  *
1481  * The component diagram is built upon the assumption that the physical
1482  * location of a declaration/definition has a correlation to the design the
1483  * creator had in mind.
1484  *
1485  * Physical world -> mental model.
1486  *
1487  * Design of relations transform:
1488  * A relation is based on where the identifier is located to the owner of the
1489  * type.
1490  * Identifier-location -> Type-owner-location.
1491  *
1492  * A type-owner-location is where the type is defined.
1493  * This though creates a problem when considering forward declarations in
1494  * combination with pointers, references, parameters.
1495  *
1496  * To handle the above case relations are go through three steps.
1497  *  - Add relations with USR->USR.
1498  *  - First try. Check both USRs location. If both of them are definitions then
1499  *    accept the relation. Otherwise put it into the cache.
1500  *  - Second try. Process the cache at the end of a translation unit. Same
1501  *    criteria as the first try.
1502  *  - Third try. When all translation units have been processed use a fallback
1503  *    strategy for those items left in the cache. At this stage a location
1504  *    corresponding to a declaration is OK. Reason, better than nothing.
1505  *
1506  * In the following example the processing is a.h before b.h.
1507  * If the locatoin of the forward declaration of B had been used the relation
1508  * from a.h to b.h would have been lost.
1509  *
1510  * Example:
1511  * a.h
1512  * ---
1513  * class B;
1514  * class A {
1515  *  B* b;
1516  * };
1517  * ---
1518  *
1519  * b.h
1520  * ---
1521  * class B {};
1522  * ---
1523  */
1524 private @safe struct TransformToComponentDiagram(ControllerT, LookupT) {
1525     import std.algorithm : map, copy, each, joiner;
1526     import std.range : chain;
1527 
1528     import cpptooling.analyzer.clang.analyze_helper : CxxBaseSpecifierResult,
1529         CxxMethodResult, ConstructorResult, DestructorResult,
1530         RecordResult, FieldDeclResult, VarDeclResult, FunctionDeclResult, TranslationUnitResult;
1531     import cpptooling.data.symbol : Container;
1532     import cpptooling.data : CppAccess, CxReturnType;
1533 
1534     invariant {
1535         assert(diagram !is null);
1536         assert(ctrl !is null);
1537     }
1538 
1539     private {
1540         static struct USRRelation {
1541             USRType from;
1542             USRType to;
1543             Relate.Kind kind;
1544         }
1545 
1546         UMLComponentDiagram diagram;
1547         ControllerT ctrl;
1548         LookupT lookup;
1549         MarkArray!USRRelation dcache;
1550         USRType[] src_cache;
1551     }
1552 
1553     this(UMLComponentDiagram diagram, ControllerT ctrl, LookupT lookup) {
1554         this.diagram = diagram;
1555         this.ctrl = ctrl;
1556         this.lookup = lookup;
1557     }
1558 
1559     /** Store the relations in the cache for later resolution regarding there
1560      * location.
1561      *
1562      * The concept is a source has relations to many destinations.
1563      *
1564      * The relation is hard coded as an Association.
1565      * If the function is generalized to be reused with Class then the hard
1566      * coded must be a lookup table or something to allow differentiating
1567      * depending on "stuff".
1568      *
1569      * It is by design that the src do NOT go via resolveCanonicalType. A free
1570      * variable that is a pointer shall have the "src" still as the pointer
1571      * itself but the destination is the pointed at type.
1572      *
1573      * Params:
1574      *  src = source of the relations
1575      *  range = destinations of the relations
1576      *  target = cache to put the values into
1577      *  lookup = type supporting lookups via USR for the TypeKind
1578      */
1579     static void putToCache(Range, T)(USRType src, Range range, ref T target, LookupT lookup) @safe
1580             if (is(ElementType!Range == TypeKindAttr)
1581                 || is(ElementType!Range == const(TypeKindAttr))) {
1582         import std.algorithm : filter;
1583 
1584         // dfmt off
1585         foreach(a; range
1586             // remove primitive types
1587             .filter!(a => a.kind.info.kind != TypeKind.Info.Kind.primitive)
1588             .map!(a => resolveCanonicalType(a.kind, a.attr, lookup))
1589             .joiner
1590             .map!(a => a.kind.usr)
1591             // create the relations of type src-to-kind
1592             .map!(to_ => USRRelation(src, to_, Relate.Kind.Associate))) {
1593             target.put(a);
1594         }
1595         // dfmt on
1596     }
1597 
1598     /// ditto
1599     static void putParamsToCache(T)(ref const(TypeKindAttr) src,
1600             const(CxParam)[] params, ref T target, LookupT lookup) @safe {
1601         // dfmt off
1602         auto range = params
1603             // returns a bunch of ranges of the unpacked parameters
1604             .map!(a => unpackParam(a))
1605             .joiner;
1606         // dfmt on
1607 
1608         putToCache(src.kind.usr, range, target, lookup);
1609     }
1610 
1611     static void finalizeSrcCache(LookupT, TargetT)(USRType[] cache, LookupT lookup, TargetT target) {
1612         import std.algorithm : map, joiner;
1613 
1614         // dfmt off
1615         foreach (loc; cache
1616                  .map!(usr => lookup.location(usr))
1617                  .joiner
1618                  .map!(a => a.any)
1619                  .joiner) {
1620             target.putSrc(loc);
1621         }
1622         // dfmt on
1623     }
1624 
1625     /// Process the last bits left in the cache.
1626     void finalize() {
1627         import std.algorithm : map, filter, cache;
1628         import std.range : enumerate, only;
1629         import std.typecons : tuple;
1630 
1631         finalizeSrcCache(src_cache[], lookup, this);
1632         if (src_cache.length > 0) {
1633             logger.tracef("%d relations left in src cache", src_cache.length);
1634         }
1635         src_cache.length = 0;
1636 
1637         if (dcache.data.length > 0) {
1638             logger.tracef("%d relations left. Activating fallback strategy", dcache.data.length);
1639         }
1640 
1641         // dfmt off
1642         foreach (e; dcache.data
1643                  // keep track of the index to allow marking of the cache for removal
1644                  .enumerate
1645                  // find the types
1646                  .map!(a => tuple(a.index, lookup.location(a.value.from), lookup.location(a.value.to)))
1647                  .cache
1648                  // a zero range means a failed lookup, a broken relation
1649                  .filter!(a => a[1].length != 0 && a[2].length != 0)
1650                  // unpack with fallback
1651                  .map!(a => tuple(a[0], a[1].front.any, a[2].front.any))
1652                  // ensure that both both resulted in valid ranges
1653                  .filter!(a => a[1].length != 0 && a[2].length != 0)
1654                  // unpack
1655                  .map!(a => tuple(a[0], a[1].front, a[2].front))
1656                  // check via ctrl (the user) if the destination is "ok"
1657                  .filter!(a => ctrl.doFile(cast(string) a[2].file, cast(string) a[2].file))
1658                  ) {
1659             //TODO warn when a declaration has been used?
1660 
1661             putDest(e[1], e[2], Relate.Kind.Associate);
1662             dcache.markForRemoval(e[0]);
1663         }
1664         // dfmt on
1665 
1666         dcache.doRemoval;
1667 
1668         if (dcache.data.length > 0) {
1669             logger.errorf("Fallback strategy failed for %d USRs. They are:", dcache.data.length);
1670         }
1671 
1672         foreach (e; dcache.data) {
1673             logger.tracef("  %s -> %s", cast(string) e.from, cast(string) e.to);
1674         }
1675     }
1676 
1677     void put(ref const(TranslationUnitResult) result) {
1678         import std.algorithm : map, filter, cache;
1679         import std.range : enumerate, only;
1680         import std.typecons : tuple;
1681 
1682         finalizeSrcCache(src_cache[], lookup, this);
1683         if (src_cache.length > 0) {
1684             logger.tracef("%d relations left in src cache", src_cache.length);
1685         }
1686         src_cache.length = 0;
1687 
1688         // dfmt off
1689         foreach (e; dcache.data
1690                  // keep track of the index to allow marking of the cache for removal
1691                  .enumerate
1692                  // find the types
1693                  .map!(a => tuple(a.index, lookup.location(a.value.from), lookup.location(a.value.to)))
1694                  .cache
1695                  // a zero range means a failed lookup, a broken relation
1696                  .filter!(a => a[1].length != 0 && a[2].length != 0)
1697                  // unpack
1698                  .map!(a => tuple(a[0], a[1].front, a[2].front))
1699                  // only okey with a relatioin TO something that is a definition
1700                  .filter!(a => a[1].hasDefinition && a[2].hasDefinition)
1701                  // check via ctrl (the user) if the destination is "ok"
1702                  .filter!(a => ctrl.doFile(cast(string) a[2].definition.file, cast(string) a[2].definition.file))
1703                  ) {
1704             putDest(e[1].definition, e[2].definition, Relate.Kind.Associate);
1705             dcache.markForRemoval(e[0]);
1706         }
1707         // dfmt on
1708 
1709         dcache.doRemoval;
1710     }
1711 
1712     void put(ref const(RecordResult) result) {
1713         src_cache ~= result.type.kind.usr;
1714     }
1715 
1716     void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) {
1717         putParamsToCache(src, result.params, dcache, lookup);
1718     }
1719 
1720     void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) {
1721         import std.range : only;
1722 
1723         putParamsToCache(src, result.params, dcache, lookup);
1724         putToCache(src.kind.usr, only((cast(const TypeKindAttr) result.returnType)), dcache, lookup);
1725     }
1726 
1727     void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) {
1728         import std.range : only;
1729 
1730         putToCache(src.kind.usr, only(result.type), dcache, lookup);
1731     }
1732 
1733     void put(ref const(TypeKindAttr) src, ref const(ClassClassificationResult) result) {
1734         import std.range : only;
1735 
1736         // called when creating a relation for a nested class
1737         putToCache(src.kind.usr, only(result.type), dcache, lookup);
1738     }
1739 
1740     void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) {
1741         auto r0 = lookup.kind(result.canonicalUSR).map!(a => TypeKindAttr(a.get, TypeAttr.init));
1742 
1743         putToCache(src.kind.usr, r0, dcache, lookup);
1744     }
1745 
1746     void put(ref const(VarDeclResult) result) {
1747         import std.range : only;
1748 
1749         // primitive types do not have a location
1750         if (result.location.kind == LocationTag.Kind.loc) {
1751             putSrc(result.location);
1752 
1753             putToCache(result.instanceUSR, only(result.type), dcache, lookup);
1754         }
1755     }
1756 
1757     void put(ref const(FunctionDeclResult) result) {
1758         import std.range : only;
1759 
1760         src_cache ~= result.type.kind.usr;
1761 
1762         putParamsToCache(result.type, result.params, dcache, lookup);
1763         putToCache(result.type.kind.usr,
1764                 only(cast(const TypeKindAttr) result.returnType), dcache, lookup);
1765     }
1766 
1767     void putSrc(ref const(LocationTag) src) @safe {
1768         string location = src.file;
1769 
1770         if (src.kind == LocationTag.Kind.noloc || !ctrl.doFile(location, location)) {
1771             return;
1772         }
1773 
1774         auto key = makeComponentKey(location, ctrl);
1775         diagram.put(key.key, cast(UMLComponentDiagram.DisplayName) key.display);
1776         diagram.put(key.key, cast(UMLComponentDiagram.Location) location);
1777     }
1778 
1779     void putDest(ref const(LocationTag) src, ref const(LocationTag) dest, Relate.Kind kind) {
1780         auto src_ = makeComponentKey(src.file, ctrl);
1781         auto dest_ = makeComponentKey(dest.file, ctrl);
1782 
1783         // Ignoring self referencing relations.
1784         if (src_.key == dest_.key) {
1785             return;
1786         }
1787 
1788         diagram.relate(src_.key, dest_.key,
1789                 cast(UMLComponentDiagram.DisplayName) dest_.display, kind);
1790     }
1791 }
1792 
1793 /** Route information to specific transformers.
1794  *
1795  * No manipulation of data is to be done in this struct. Only routing to
1796  * appropriate functions.
1797  */
1798 class TransformToDiagram(ControllerT, ParametersT, LookupT) {
1799     import std.range : only;
1800 
1801     import cpptooling.analyzer.clang.analyze_helper : CxxBaseSpecifierResult,
1802         RecordResult, FieldDeclResult, CxxMethodResult,
1803         ConstructorResult, DestructorResult, VarDeclResult, FunctionDeclResult,
1804         TranslationUnitResult;
1805     import cpptooling.data.symbol.types : USRType;
1806     import cpptooling.data : TypeKind, CppNs, CppAccess;
1807 
1808     private {
1809         TransformToComponentDiagram!(ControllerT, LookupT) to_component;
1810         TransformToClassDiagram!(ControllerT, LookupT) to_class;
1811     }
1812 
1813     this(ControllerT ctrl, ParametersT params, LookupT lookup,
1814             UMLComponentDiagram comp_dia, UMLClassDiagram class_dia) {
1815         to_component = typeof(to_component)(comp_dia, ctrl, lookup);
1816         to_class = typeof(to_class)(class_dia, ctrl, lookup, params.genClassMethod,
1817                 params.genClassParamDependency, params.genClassInheritDependency,
1818                 params.genClassMemberDependency);
1819     }
1820 
1821 @safe:
1822 
1823     /** Signal that diagrams to perform a finalization of cached data.
1824      */
1825     void finalize() {
1826         to_component.finalize();
1827     }
1828 
1829     void put(ref const(TranslationUnitResult) result) {
1830         to_component.put(result);
1831     }
1832 
1833     void put(ref const(RecordResult) result, const(CppNs)[] reside_in) {
1834         to_class.put(result, reside_in);
1835         to_component.put(result);
1836     }
1837 
1838     void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) {
1839         to_class.put(src, result);
1840         to_component.put(src, result);
1841     }
1842 
1843     void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) {
1844         to_class.put(src, result, access);
1845         to_component.put(src, result, access);
1846     }
1847 
1848     void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) {
1849         to_class.put(src, result, access);
1850         to_component.put(src, result, access);
1851     }
1852 
1853     void put(ref const(TypeKindAttr) src, ref const(DestructorResult) result, in CppAccess access) {
1854         to_class.put(src, result, access);
1855     }
1856 
1857     void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) {
1858         to_class.put(src, result, access);
1859         to_component.put(src, result, access);
1860     }
1861 
1862     void put(ref const(ClassClassificationResult) result) {
1863         to_class.put(result);
1864     }
1865 
1866     /** A nested class.
1867      *
1868      * Propagate the classification and relation of the root->nested.
1869      */
1870     void put(ref const(TypeKindAttr) src, ref const(ClassClassificationResult) result) {
1871         to_component.put(src, result);
1872         // only needs result
1873         to_class.put(result);
1874     }
1875 
1876     void put(ref const(VarDeclResult) result) {
1877         to_component.put(result);
1878     }
1879 
1880     void put(ref const(FunctionDeclResult) result) {
1881         to_component.put(result);
1882     }
1883 }
1884 
1885 // visualize where the module private starts
1886 private: // ******************************************************************
1887 
1888 import cpptooling.data.representation : CppRoot, CppClass, CppMethod, CppCtor,
1889     CppDtor, CppNamespace, CFunction, CxGlobalVariable, LocationTag, Location;
1890 import cpptooling.data.symbol : Container;
1891 import dsrcgen.plantuml;
1892 
1893 struct KeyValue {
1894     UMLComponentDiagram.Key key;
1895     string display;
1896     string absFilePath;
1897 }
1898 
1899 struct KeyRelate {
1900     string file;
1901     KeyValue key;
1902     Relate.Kind kind;
1903 }
1904 
1905 /**
1906  * Params:
1907  *  file = filename of the relation.
1908  *  kind = kind of relation such as associaiton, composition etc.
1909  */
1910 struct PathKind {
1911     string file;
1912     Relate.Kind kind;
1913 }
1914 
1915 /** Calculate the key based on the directory the file that declares the symbol exist in.
1916  *
1917  * Additional metadata as to make it possible to backtrack.
1918  */
1919 KeyValue makeComponentKey(in string location_file, Controller ctrl) @trusted {
1920     import std.array : appender;
1921     import std.base64 : Base64Impl, Base64;
1922     import std.path : buildNormalizedPath, absolutePath, relativePath, baseName;
1923     import std.typecons : tuple;
1924 
1925     // TODO consider using hash murmur2/3 to shorten the length of the encoded
1926     // path
1927 
1928     alias SafeBase64 = Base64Impl!('-', '_', Base64.NoPadding);
1929 
1930     string file_path = buildNormalizedPath(location_file.absolutePath);
1931     string strip_path = cast(string) ctrl.doComponentNameStrip(Path(file_path));
1932     string rel_path = relativePath(strip_path);
1933     string display_name = strip_path.baseName;
1934 
1935     auto enc = appender!(char[])();
1936     SafeBase64.encode(cast(ubyte[]) rel_path, enc);
1937 
1938     auto k = KeyValue(UMLComponentDiagram.Key(enc.data.idup), display_name, strip_path);
1939 
1940     debug {
1941         logger.tracef("Component:%s stripped:%s file:%s base64:%s", k.display,
1942                 strip_path, file_path, cast(string) k.key);
1943     }
1944 
1945     return k;
1946 }
1947 
1948 UMLClassDiagram.Key makeClassKey(in USRType key) @trusted {
1949     import std.base64 : Base64Impl, Base64;
1950     import std.array : appender;
1951 
1952     // TODO consider using hash murmur2/3 function to shorten the length of the
1953     // encoded path
1954 
1955     alias SafeBase64 = Base64Impl!('-', '_', Base64.NoPadding);
1956 
1957     auto enc = appender!(char[])();
1958     SafeBase64.encode(cast(ubyte[])(cast(string) key), enc);
1959 
1960     auto k = UMLClassDiagram.Key(enc.data.idup);
1961     return k;
1962 }
1963 
1964 private auto unpackParam(CxParam p) @trusted {
1965     import std.range : only, dropOne;
1966     import std.variant : visit;
1967     import cpptooling.data : TypeKindVariable, VariadicType;
1968 
1969     // dfmt off
1970     return p.visit!(
1971                     (TypeKindVariable v) => only(v.type),
1972                     (TypeKindAttr v) => only(v),
1973                     (VariadicType v) {
1974                         logger.error(
1975                                      "Variadic function not supported. Would require runtime information to relate.");
1976                         return only(TypeKindAttr.init).dropOne;
1977                     });
1978     // dfmt on
1979 }
1980 
1981 struct ClassRelate {
1982     Relate.Kind kind;
1983     Relate.Key key;
1984     UMLClassDiagram.DisplayName display;
1985 }
1986 
1987 auto getClassMemberRelation(LookupT)(TypeKindAttr type, LookupT lookup) {
1988     //TODO code duplication with getMethodRelation
1989     // .. fix it. This function is ugly.
1990     import std.algorithm : each, map, filter, joiner;
1991     import std.array : array;
1992     import std.typecons : tuple;
1993 
1994     // TODO this is a mega include. Reduce it.
1995     import cpptooling.data;
1996 
1997     auto r = ClassRelate(Relate.Kind.None, Relate.Key(""), UMLClassDiagram.DisplayName(""));
1998 
1999     final switch (type.kind.info.kind) with (TypeKind.Info) {
2000     case Kind.typeRef:
2001         auto tref = lookup.kind(type.kind.info.canonicalRef);
2002         foreach (t; tref.filter!(a => a.info.kind == Kind.record)) {
2003             auto rel_type = Relate.Kind.Aggregate;
2004             if (type.attr.isPtr || type.attr.isRef) {
2005                 rel_type = Relate.Kind.Compose;
2006             }
2007             r = ClassRelate(rel_type, t.usr,
2008                     cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init));
2009         }
2010         break;
2011     case Kind.record:
2012         r = ClassRelate(Relate.Kind.Aggregate, type.kind.usr,
2013                 cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init));
2014         break;
2015     case Kind.array:
2016         auto element = lookup.kind(type.kind.info.element);
2017         foreach (e; element.filter!(a => a.info.kind == Kind.record)) {
2018             auto rel_type = Relate.Kind.Aggregate;
2019             if (type.attr.isPtr || type.attr.isRef) {
2020                 rel_type = Relate.Kind.Compose;
2021             }
2022             r = ClassRelate(rel_type, e.usr,
2023                     cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init));
2024         }
2025         break;
2026     case Kind.pointer:
2027         auto pointee = lookup.kind(type.kind.info.pointee);
2028         foreach (p; pointee.filter!(a => a.info.kind == Kind.record)) {
2029             string display = p.toStringDecl(TypeAttr.init);
2030             r = ClassRelate(Relate.Kind.Compose, p.usr, cast(UMLClassDiagram.DisplayName) display);
2031         }
2032         break;
2033     case Kind.primitive:
2034     case Kind.simple:
2035     case Kind.func:
2036     case Kind.funcPtr:
2037     case Kind.funcSignature:
2038     case Kind.ctor:
2039     case Kind.dtor:
2040     case Kind.null_:
2041         break;
2042     }
2043 
2044     return r;
2045 }
2046 
2047 private ClassRelate getTypeRelation(LookupT)(TypeKindAttr tk, LookupT lookup) {
2048     import std.algorithm : filter;
2049     import cpptooling.data : TypeKind, TypeAttr, toStringDecl;
2050 
2051     auto r = ClassRelate(Relate.Kind.None, Relate.Key(""), UMLClassDiagram.DisplayName(""));
2052 
2053     final switch (tk.kind.info.kind) with (TypeKind.Info) {
2054     case Kind.typeRef:
2055         auto tref = lookup.kind(tk.kind.info.canonicalRef);
2056         foreach (t; tref.filter!(a => a.info.kind == Kind.record)) {
2057             r = ClassRelate(Relate.Kind.Associate, Relate.Key(t.usr),
2058                     cast(UMLClassDiagram.DisplayName) t.toStringDecl(TypeAttr.init));
2059         }
2060         break;
2061     case Kind.record:
2062         r = ClassRelate(Relate.Kind.Associate, tk.kind.usr,
2063                 cast(UMLClassDiagram.DisplayName) tk.kind.toStringDecl(TypeAttr.init));
2064         break;
2065     case Kind.array:
2066         auto element = lookup.kind(tk.kind.info.element);
2067         foreach (e; element.filter!(a => a.info.kind == Kind.record)) {
2068             r = ClassRelate(Relate.Kind.Associate, e.usr,
2069                     cast(UMLClassDiagram.DisplayName) e.toStringDecl(TypeAttr.init));
2070         }
2071         break;
2072     case Kind.pointer:
2073         auto pointee = lookup.kind(tk.kind.info.pointee);
2074         foreach (p; pointee.filter!(a => a.info.kind == Kind.record)) {
2075             string display = p.toStringDecl(TypeAttr.init);
2076             r = ClassRelate(Relate.Kind.Associate, Relate.Key(p.usr),
2077                     cast(UMLClassDiagram.DisplayName) display);
2078         }
2079         break;
2080     case Kind.primitive:
2081     case Kind.simple:
2082     case Kind.func:
2083     case Kind.funcPtr:
2084     case Kind.funcSignature:
2085     case Kind.ctor:
2086     case Kind.dtor:
2087     case Kind.null_:
2088     }
2089 
2090     return r;
2091 }
2092 
2093 private auto getClassMethodRelation(LookupT)(const(CxParam)[] params, LookupT lookup) {
2094     import std.array : array;
2095     import std.algorithm : among, map, filter;
2096     import std.variant : visit;
2097     import cpptooling.data : TypeKind, TypeAttr, TypeKindAttr, toStringDecl, VariadicType;
2098 
2099     static ClassRelate genParam(CxParam p, LookupT lookup) @trusted {
2100         // dfmt off
2101         return p.visit!(
2102             (TypeKindVariable tkv) => getTypeRelation(tkv.type, lookup),
2103             (TypeKindAttr tk) => getTypeRelation(tk, lookup),
2104             (VariadicType vk)
2105                 {
2106                     logger.error("Variadic function not supported.");
2107                     // Because what types is either discovered first at runtime
2108                     // or would require deeper inspection of the implementation
2109                     // where the variadic is used.
2110                     return ClassRelate.init;
2111                 }
2112             );
2113         // dfmt on
2114     }
2115 
2116     // dfmt off
2117     return params.map!(a => genParam(a, lookup)).array();
2118     // dfmt on
2119 }
2120 
2121 void generate(UMLClassDiagram uml_class, UMLComponentDiagram uml_comp,
2122         Flag!"doGenDot" doGenDot, Generator.Modules modules) @safe {
2123     import std.algorithm : each;
2124     import std.format : format;
2125     import std.range : enumerate;
2126 
2127     // TODO code duplicaton with class and component.
2128     // Generalize, reduce.
2129 
2130     auto classes_preamble = modules.classes.base;
2131     classes_preamble.suppressIndent(1);
2132     foreach (idx, kv; uml_class.fanOutSorted.enumerate) {
2133         generate(kv.key, kv.value, classes_preamble);
2134         generateClassRelate(uml_class.relateTo(kv.key)
2135                 .toFlatArray(cast(Relate.Key) kv.key), modules.classes);
2136         if (doGenDot) {
2137             auto nodes = modules.classes_dot.base;
2138             nodes.suppressIndent(1);
2139             nodes.stmt(format(`"%s" [label="%s"]`, kv.key, kv.value.displayName));
2140 
2141             // make a range of all relations from THIS to other components
2142             auto r = uml_class.relateTo(kv.key).toRange(cast(Relate.Key) kv.key);
2143 
2144             generateDotRelate(r, idx, modules.classes_dot);
2145         }
2146     }
2147 
2148     foreach (idx, kv; uml_comp.fanOutSorted.enumerate) {
2149         generate(kv.key, kv.value, modules.components);
2150         if (doGenDot) {
2151             auto nodes = modules.components_dot.base;
2152             nodes.suppressIndent(1);
2153             nodes.stmt(format(`"%s" [label="%s"]`, kv.key, kv.value.displayName));
2154 
2155             // make a range of all relations from THIS to other components
2156             auto r = uml_comp.relateTo(kv.key).toRange(cast(Relate.Key) kv.key);
2157 
2158             generateDotRelate(r, idx, modules.components_dot);
2159         }
2160     }
2161     generateComponentRelate(uml_comp.relateToFlatArray, modules.components);
2162 }
2163 
2164 /** Generate PlantUML class and relations from the class.
2165  *
2166  * By generating the relations out of the class directly after the class
2167  * definitions it makes it easier for GraphViz to generate a not-so-muddy
2168  * image.
2169  */
2170 private void generate(UMLClassDiagram.Key key, const UMLClassDiagram.Class c, PlantumlModule m) @safe {
2171     import std.algorithm : each;
2172     import dsrcgen.plantuml : addSpot;
2173 
2174     ClassType pc;
2175 
2176     if (c.content.length == 0) {
2177         pc = m.class_(cast(string) c.displayName);
2178     } else {
2179         pc = m.classBody(cast(string) c.displayName);
2180         c.content.each!(a => pc.method(a));
2181     }
2182     pc.addAs.text(cast(string) key);
2183 
2184     //TODO add a plantuml macro and use that as color for interface
2185     // Allows the user to control the color via the PREFIX_style.iuml
2186     switch (c.classification) with (cpptooling.data.class_classification.State) {
2187     case Abstract:
2188         pc.addSpot("<< (A, Pink) >>");
2189         break;
2190     case VirtualDtor:
2191     case Pure:
2192         pc.addSpot("<< (I, LightBlue) >>");
2193         break;
2194     default:
2195         break;
2196     }
2197 }
2198 
2199 private void generateClassRelate(T)(T relate_range, PlantumlModule m) @safe {
2200     static auto convKind(Relate.Kind kind) {
2201         static import dsrcgen.plantuml;
2202 
2203         final switch (kind) with (Relate.Kind) {
2204         case None:
2205             assert(0);
2206         case Extend:
2207             return dsrcgen.plantuml.Relate.Extend;
2208         case Compose:
2209             return dsrcgen.plantuml.Relate.Compose;
2210         case Aggregate:
2211             return dsrcgen.plantuml.Relate.Aggregate;
2212         case Associate:
2213             return dsrcgen.plantuml.Relate.ArrowTo;
2214         case Relate:
2215             return dsrcgen.plantuml.Relate.Relate;
2216         }
2217     }
2218 
2219     foreach (r; relate_range) {
2220         m.relate(cast(ClassNameType) r.from, cast(ClassNameType) r.to, convKind(r.kind));
2221     }
2222 }
2223 
2224 private void generateDotRelate(T)(T relate_range, ulong color_idx, PlantumlModule m) @safe {
2225     import std.format : format;
2226 
2227     static import dsrcgen.plantuml;
2228 
2229     static string getColor(ulong idx) {
2230         static string[] colors = [
2231             "red", "mediumpurple", "darkorange", "deeppink", "green", "coral",
2232             "orangered", "plum", "deepskyblue", "slategray", "cadetblue",
2233             "olive", "silver", "indianred", "black"
2234         ];
2235         return colors[idx % colors.length];
2236     }
2237 
2238     if (relate_range.length > 0) {
2239         m.stmt(format("edge [color=%s]", getColor(color_idx)));
2240     }
2241 
2242     foreach (r; relate_range) {
2243         auto l = m.relate(cast(ClassNameType) r.from, cast(ClassNameType) r.to,
2244                 dsrcgen.plantuml.Relate.DotArrowTo);
2245         //TODO this is ugly, fix dsrcgen relate to support graphviz/DOT
2246         auto w = new dsrcgen.plantuml.Text!PlantumlModule(format("[weight=%d] ", r.count));
2247         l.block.prepend(w);
2248     }
2249 }
2250 
2251 private void generate(UMLComponentDiagram.Key key,
2252         const UMLComponentDiagram.Component component, PlantumlModule m) @safe {
2253     import std.algorithm : map;
2254     import std.conv : text;
2255     import std.path : buildNormalizedPath, relativePath;
2256 
2257     auto comp = m.classBody(cast(string) component.displayName);
2258     comp.addAs.text(cast(string) key);
2259 
2260     // early exit because the slice of contains segfaults otherwise.
2261     if (component.contains.length == 0)
2262         return;
2263 
2264     // dfmt off
2265     foreach (fname; component.contains[]
2266         .map!(a => cast(string) a)
2267         .map!(a => () @trusted { return buildNormalizedPath(a).relativePath; }())) {
2268         comp.m.stmt(text(fname));
2269     }
2270     // dfmt on
2271 }
2272 
2273 private void generateComponentRelate(T)(T relate_range, PlantumlModule m) @safe {
2274     static auto convKind(Relate.Kind kind) {
2275         static import dsrcgen.plantuml;
2276 
2277         final switch (kind) with (Relate.Kind) {
2278         case Relate:
2279             return dsrcgen.plantuml.Relate.Relate;
2280         case Extend:
2281             assert(0);
2282         case Compose:
2283             assert(0);
2284         case Aggregate:
2285             assert(0);
2286         case Associate:
2287             return dsrcgen.plantuml.Relate.ArrowTo;
2288         case None:
2289             assert(0);
2290         }
2291     }
2292 
2293     foreach (r; relate_range) {
2294         m.relate(cast(ComponentNameType) r.from, cast(ComponentNameType) r.to, convKind(r.kind));
2295     }
2296 }