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