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 test.extra_should : shouldEqualPretty;
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     FileName doComponentNameStrip(FileName 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         FileName classes;
78         FileName components;
79         FileName styleIncl;
80         FileName styleOutput;
81     }
82 
83     /// Output directory to store files in.
84     DirName 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(FileName fname, PlantumlRootModule data);
128 
129     /// ditto.
130     void putFile(FileName 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(["A -Extend- [1]B",
726             "A -Compose- [1]B", "A -Aggregate- [1]B", "A -Associate- [1]B"]);
727 }
728 
729 @Name("Should be two relates to the same target")
730 unittest {
731     Relate r;
732     r.put(Relate.Key("B"), Relate.Kind.Compose);
733     r.put(Relate.Key("B"), Relate.Kind.Compose);
734 
735     r.toStringArray(Relate.Key("A")).shouldEqual(["A -Compose- [2]B"]);
736 }
737 
738 @Name("Should be a UML diagram with one class")
739 unittest {
740     auto uml = new UMLClassDiagram;
741     uml.put(UMLClassDiagram.Key("A"), UMLClassDiagram.DisplayName("A"));
742 
743     uml.toString.shouldEqualPretty("UML Class Diagram (Total 1) {
744 A -> A
745 } // UML Class Diagram");
746 }
747 
748 @Name("Should be a UML diagram with two classes related")
749 unittest {
750     auto uml = new UMLClassDiagram;
751     auto ka = UMLClassDiagram.Key("A");
752     auto kb = UMLClassDiagram.Key("B");
753     uml.put(ka, UMLClassDiagram.DisplayName("A"));
754     uml.put(kb, UMLClassDiagram.DisplayName("B"));
755 
756     uml.relate(ka, kb, UMLClassDiagram.DisplayName("B"), Relate.Kind.Extend);
757 
758     uml.toString.shouldEqualPretty("UML Class Diagram (Total 2) {
759 A -> A
760 B -> B
761 A -Extend- [1]B
762 } // UML Class Diagram");
763 }
764 
765 @Name("Should be a UML Component diagram with two components related")
766 unittest {
767     auto uml = new UMLComponentDiagram;
768     auto ka = UMLComponentDiagram.Key("a");
769     auto kb = UMLComponentDiagram.Key("b");
770     uml.put(ka, cast(UMLComponentDiagram.DisplayName) "A");
771     // shall be dedupliated
772     uml.put(ka, cast(UMLComponentDiagram.Location) "file.h");
773     uml.put(ka, cast(UMLComponentDiagram.Location) "file.h");
774 
775     uml.relate(ka, kb, cast(UMLComponentDiagram.DisplayName) "B", Relate.Kind.Relate);
776 
777     uml.toString.shouldEqualPretty("UML Component Diagram (Total 2) {
778 a as A
779   file.h
780 b as B
781 a -Relate- [1]b
782 } // UML Component Diagram");
783 }
784 
785 /** Context for the UML diagram generator from internal representation to the
786  * concrete files.
787  */
788 struct Generator {
789     import cpptooling.data : CppRoot;
790     import cpptooling.data.symbol : Container;
791 
792     private static struct Modules {
793         private static void postInit(ref typeof(this) m) {
794             m.classes_dot.suppressIndent(1);
795             m.components_dot.suppressIndent(1);
796         }
797 
798         import dextool.plugin.utility : MakerInitializingClassMembers;
799 
800         mixin MakerInitializingClassMembers!(Modules, postInit);
801 
802         PlantumlModule classes;
803         PlantumlModule classes_dot;
804         PlantumlModule components;
805         PlantumlModule components_dot;
806     }
807 
808     /** Instansiate.
809      *
810      * Params:
811      *  ctrl = dynamic control of data generation.
812      *  params = static control, may never change during generation.
813      *  products = receiver of UML diagrams.
814      */
815     this(Controller ctrl, Parameters params, Products products) {
816         this.ctrl = ctrl;
817         this.params = params;
818         this.products = products;
819         this.umlClass = new UMLClassDiagram;
820         this.umlComponent = new UMLComponentDiagram;
821     }
822 
823     /** Process the sources to produce UML diagrams in-memory.
824      *
825      * The diagrams are forwarded to the registered Products instance.
826      */
827     auto process() {
828         auto m = Modules.make();
829         generate(umlClass, umlComponent, params.doGenDot, m);
830         postProcess(ctrl, params, products, m);
831     }
832 
833     /// The UML diagram used as source during generation.
834     UMLClassDiagram umlClass;
835 
836     /// ditto
837     UMLComponentDiagram umlComponent;
838 
839 private:
840     Controller ctrl;
841     Parameters params;
842     Products products;
843 
844     static void postProcess(Controller ctrl, Parameters params, Products prods, Modules m) {
845         static PlantumlRootModule makeMinimalStyle(Flag!"genClassMethod" show_methods) {
846             auto proot = PlantumlRootModule.make();
847 
848             auto class_ = proot.makeUml;
849             class_.stmt("left to right direction");
850             class_.stmt("'skinparam linetype polyline");
851             class_.stmt("'skinparam linetype ortho");
852             class_.stmt("set namespaceSeparator none");
853             if (show_methods) {
854                 class_.stmt("'hide members");
855             } else {
856                 class_.stmt("hide members");
857             }
858 
859             auto component = proot.makeUml;
860             component.stmt("left to right direction");
861             component.stmt("skinparam componentStyle uml2");
862             component.stmt("'skinparam linetype polyline");
863             component.stmt("'skinparam linetype ortho");
864             component.stmt("set namespaceSeparator none");
865             component.stmt("hide circle");
866             component.stmt("hide methods");
867             component.stmt("'To hide file location");
868             component.stmt("hide members");
869 
870             return proot;
871         }
872 
873         enum DotLayout {
874             Neato,
875             Dot,
876             DotOrtho
877         }
878 
879         static PlantumlModule makeDotPreamble(DotLayout layout, Flag!"doSmall" doSmall) {
880             auto m = new PlantumlModule;
881             m.suppressIndent(1);
882 
883             //TODO if DotOrtho and Dot don't change consider removing the code
884             // duplication.
885             final switch (layout) with (DotLayout) {
886             case Neato:
887                 m.stmt("layout=neato");
888                 m.stmt("edge [len=3]");
889                 break;
890             case DotOrtho:
891                 m.stmt("layout=dot");
892                 m.stmt("rankdir=LR");
893                 m.stmt("pack=true");
894                 m.stmt("concentrate=true");
895                 // inactivating, can result in a crash as of
896                 // dot 2.38.0 (20140413.2041)
897                 m.stmt("// activate for orthogonal lines, aka straight lines");
898                 m.stmt("// but can result in GraphViz/dot crashing");
899                 m.stmt("//splines=ortho");
900                 break;
901             case Dot:
902                 m.stmt("layout=dot");
903                 m.stmt("rankdir=LR");
904                 m.stmt("pack=true");
905                 m.stmt("concentrate=true");
906                 break;
907             }
908 
909             m.sep(2);
910 
911             m.stmt("colorscheme=svg");
912             if (doSmall) {
913                 m.stmt("node [style=rounded shape=box fontsize=9 width=0.25 height=0.375]");
914             } else {
915                 m.stmt("node [style=rounded shape=box]");
916             }
917             m.sep(2);
918 
919             return m;
920         }
921 
922         enum StyleType {
923             Class,
924             Component
925         }
926 
927         static PlantumlModule makeStyleInclude(Flag!"doStyleIncl" do_style_incl,
928                 FileName style_file, StyleType style_type) {
929             import std.conv : to;
930 
931             auto m = new PlantumlModule;
932             if (!do_style_incl) {
933                 return m;
934             }
935 
936             m.stmt("!include " ~ style_file ~ "!" ~ to!string(cast(int) style_type));
937 
938             return m;
939         }
940 
941         static void makeUml(Products prods, FileName fname, PlantumlModule style,
942                 PlantumlModule content) {
943             import std.algorithm : filter;
944 
945             auto proot = PlantumlRootModule.make();
946             auto c = proot.makeUml();
947             c.suppressIndent(1);
948 
949             foreach (m; [style, content].filter!(a => a !is null)) {
950                 c.append(m);
951             }
952 
953             prods.putFile(fname, proot);
954         }
955 
956         static void makeDot(Products prods, FileName fname, PlantumlModule style,
957                 PlantumlModule content) {
958             import std.algorithm : filter;
959             import std.path : stripExtension, baseName;
960 
961             immutable ext_dot = ".dot";
962 
963             auto fname_dot = FileName(fname.stripExtension ~ ext_dot);
964             auto dot = new PlantumlModule;
965             auto digraph = dot.digraph("g");
966             digraph.suppressThisIndent(1);
967             foreach (m; [style, content].filter!(a => a !is null)) {
968                 digraph.append(m);
969             }
970             prods.putFile(fname_dot, dot);
971 
972             auto proot = PlantumlRootModule.make();
973             auto pu = proot.makeDot;
974             pu.stmt("!include " ~ (cast(string) fname_dot).baseName);
975             prods.putFile(fname, proot);
976         }
977 
978         static FileName makeDotFileName(FileName f, DotLayout layout) {
979             import std.path : extension, stripExtension;
980 
981             auto ext = extension(cast(string) f);
982 
983             string suffix;
984             final switch (layout) with (DotLayout) {
985             case Dot:
986                 goto case;
987             case DotOrtho:
988                 suffix = "_dot";
989                 break;
990             case Neato:
991                 suffix = "_neato";
992                 break;
993             }
994 
995             return FileName((cast(string) f).stripExtension ~ suffix ~ ext);
996         }
997 
998         if (ctrl.genStyleInclFile) {
999             prods.putFile(params.getFiles.styleOutput, makeMinimalStyle(params.genClassMethod));
1000         }
1001 
1002         if (params.doGenDot) {
1003             makeDot(prods, makeDotFileName(params.getFiles.classes, DotLayout.Dot),
1004                     makeDotPreamble(DotLayout.Dot, Yes.doSmall), m.classes_dot);
1005             makeDot(prods, makeDotFileName(params.getFiles.classes, DotLayout.Neato),
1006                     makeDotPreamble(DotLayout.Neato, Yes.doSmall), m.classes_dot);
1007             makeDot(prods, makeDotFileName(params.getFiles.components, DotLayout.Neato),
1008                     makeDotPreamble(DotLayout.Neato, No.doSmall), m.components_dot);
1009             makeDot(prods, makeDotFileName(params.getFiles.components, DotLayout.DotOrtho),
1010                     makeDotPreamble(DotLayout.DotOrtho, No.doSmall), m.components_dot);
1011         }
1012 
1013         makeUml(prods, params.getFiles.classes, makeStyleInclude(params.doStyleIncl,
1014                 params.getFiles.styleIncl, StyleType.Class), m.classes);
1015         makeUml(prods, params.getFiles.components, makeStyleInclude(params.doStyleIncl,
1016                 params.getFiles.styleIncl, StyleType.Component), m.components);
1017     }
1018 }
1019 
1020 private struct ClassClassificationResult {
1021     TypeKindAttr type;
1022     cpptooling.data.class_classification.State classification;
1023 }
1024 
1025 private final class UMLClassVisitor(ControllerT, ReceiveT) : Visitor {
1026     import std.algorithm : map, copy, each, joiner;
1027     import std.array : Appender;
1028     import std.typecons : scoped, NullableRef;
1029 
1030     import cpptooling.analyzer.clang.ast : ClassDecl, CxxBaseSpecifier,
1031         Constructor, Destructor, CxxMethod, FieldDecl, CxxAccessSpecifier,
1032         generateIndentIncrDecr;
1033     import cpptooling.analyzer.clang.analyze_helper : analyzeRecord,
1034         analyzeConstructor, analyzeDestructor, analyzeCxxMethod,
1035         analyzeFieldDecl, analyzeCxxBaseSpecified, toAccessType;
1036     import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
1037     import cpptooling.data : CppNsStack, CppNs, AccessType, CppAccess,
1038         MemberVirtualType;
1039 
1040     import cpptooling.data.class_classification : ClassificationState = State;
1041     import cpptooling.data.class_classification : classifyClass, MethodKind;
1042 
1043     alias visit = Visitor.visit;
1044 
1045     mixin generateIndentIncrDecr;
1046 
1047     /** Type representation of this class.
1048      * Used as the source of the outgoing relations from this class.
1049      */
1050     TypeKindAttr type;
1051 
1052     /** Classification of the class.
1053      * Affected by methods.
1054      */
1055     ClassificationState classification;
1056 
1057     private {
1058         ControllerT ctrl;
1059         NullableRef!ReceiveT recv;
1060 
1061         Container* container;
1062         CppNsStack ns_stack;
1063         CppAccess access;
1064 
1065         /// If the class has any members.
1066         Flag!"hasMember" hasMember;
1067     }
1068 
1069     this(TypeKindAttr type, const(CppNs)[] reside_in_ns, ControllerT ctrl,
1070             ref ReceiveT recv, ref Container container, in uint indent) {
1071         this.ctrl = ctrl;
1072         this.recv = &recv;
1073         this.container = &container;
1074         this.indent = indent;
1075         this.ns_stack = CppNsStack(reside_in_ns.dup);
1076 
1077         this.access = CppAccess(AccessType.Private);
1078         this.classification = ClassificationState.Unknown;
1079 
1080         this.type = type;
1081     }
1082 
1083     /// Nested class definitions.
1084     override void visit(const(ClassDecl) v) @trusted {
1085         mixin(mixinNodeLog!());
1086         logger.info("class: ", v.cursor.spelling);
1087 
1088         auto result = analyzeRecord(v, *container, indent);
1089 
1090         foreach (loc; container.find!LocationTag(result.type.kind.usr).map!(a => a.any).joiner) {
1091             if (!ctrl.doFile(loc.file, loc.file)) {
1092                 return;
1093             }
1094         }
1095 
1096         recv.put(result, ns_stack);
1097 
1098         auto visitor = scoped!(UMLClassVisitor!(ControllerT, ReceiveT))(result.type,
1099                 ns_stack, ctrl, recv, *container, indent + 1);
1100         v.accept(visitor);
1101 
1102         auto result_class = ClassClassificationResult(visitor.type, visitor.classification);
1103         recv.put(this.type, result_class);
1104     }
1105 
1106     /// Analyze the inheritance(s).
1107     override void visit(const(CxxBaseSpecifier) v) {
1108         import cpptooling.data : TypeKind;
1109 
1110         mixin(mixinNodeLog!());
1111 
1112         auto result = analyzeCxxBaseSpecified(v, *container, indent);
1113 
1114         debug {
1115             import std.algorithm : each;
1116             import std.range : retro;
1117             import cpptooling.data : CppInherit;
1118 
1119             auto inherit = CppInherit(result.name, result.access);
1120             retro(result.reverseScope).each!(a => inherit.put(a));
1121 
1122             logger.trace("inherit: ", inherit.toString);
1123         }
1124 
1125         recv.put(this.type, result);
1126     }
1127 
1128     override void visit(const(Constructor) v) {
1129         mixin(mixinNodeLog!());
1130 
1131         auto result = analyzeConstructor(v, *container, indent);
1132 
1133         debug {
1134             auto tor = CppCtor(result.type.kind.usr, result.name, result.params, access);
1135             logger.trace("ctor: ", tor.toString);
1136         }
1137 
1138         recv.put(this.type, result, access);
1139     }
1140 
1141     override void visit(const(Destructor) v) {
1142         mixin(mixinNodeLog!());
1143 
1144         auto result = analyzeDestructor(v, *container, indent);
1145         classification = classifyClass(classification, MethodKind.Dtor,
1146                 cast(MemberVirtualType) result.virtualKind, hasMember);
1147 
1148         debug {
1149             auto tor = CppDtor(result.type.kind.usr, result.name, access, result.virtualKind);
1150             logger.trace("dtor: ", tor.toString);
1151         }
1152 
1153         recv.put(this.type, result, access);
1154     }
1155 
1156     override void visit(const(CxxMethod) v) {
1157         mixin(mixinNodeLog!());
1158 
1159         auto result = analyzeCxxMethod(v, *container, indent);
1160 
1161         classification = classifyClass(classification, MethodKind.Method,
1162                 cast(MemberVirtualType) result.virtualKind, hasMember);
1163 
1164         debug {
1165             import cpptooling.data.type : CppConstMethod;
1166             import cpptooling.data : CppMethod;
1167 
1168             auto method = CppMethod(result.type.kind.usr, result.name, result.params,
1169                     result.returnType, access, CppConstMethod(result.isConst), result.virtualKind);
1170             logger.trace("method: ", method.toString);
1171         }
1172 
1173         recv.put(this.type, result, access);
1174     }
1175 
1176     override void visit(const(FieldDecl) v) {
1177         mixin(mixinNodeLog!());
1178 
1179         auto result = analyzeFieldDecl(v, *container, indent);
1180 
1181         // TODO probably not necessary for classification to store it as a
1182         // member. Instead extend MethodKind to having a "Member".
1183         hasMember = Yes.hasMember;
1184         classification = classifyClass(classification, MethodKind.Unknown,
1185                 MemberVirtualType.Unknown, hasMember);
1186         debug {
1187             logger.trace("member: ", cast(string) result.name);
1188         }
1189 
1190         recv.put(this.type, result, access);
1191     }
1192 
1193     override void visit(const(CxxAccessSpecifier) v) @trusted {
1194         mixin(mixinNodeLog!());
1195         access = CppAccess(toAccessType(v.cursor.access.accessSpecifier));
1196     }
1197 }
1198 
1199 final class UMLVisitor(ControllerT, ReceiveT) : Visitor {
1200     import std.algorithm : map, filter, cache, joiner;
1201     import std.range : chain, only, dropOne, ElementType;
1202     import std.typecons : scoped, NullableRef;
1203 
1204     import cpptooling.analyzer.clang.ast : TranslationUnit, UnexposedDecl,
1205         VarDecl, FunctionDecl, ClassDecl, Namespace, generateIndentIncrDecr;
1206     import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl,
1207         analyzeVarDecl, analyzeRecord, analyzeTranslationUnit;
1208     import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
1209     import cpptooling.data : CppNsStack, CppNs;
1210 
1211     alias visit = Visitor.visit;
1212 
1213     mixin generateIndentIncrDecr;
1214 
1215     private {
1216         ReceiveT recv;
1217         ControllerT ctrl;
1218 
1219         NullableRef!Container container;
1220         CppNs[] ns_stack;
1221 
1222     }
1223 
1224     this(ControllerT ctrl, ref ReceiveT recv, ref Container container) {
1225         this.ctrl = ctrl;
1226         this.recv = recv;
1227         this.container = &container;
1228     }
1229 
1230     override void visit(const(TranslationUnit) v) {
1231         mixin(mixinNodeLog!());
1232         v.accept(this);
1233 
1234         auto result = analyzeTranslationUnit(v, container, indent);
1235         recv.put(result);
1236     }
1237 
1238     override void visit(const(UnexposedDecl) v) {
1239         mixin(mixinNodeLog!());
1240 
1241         // An unexposed may be:
1242 
1243         // an extern "C"
1244         // UnexposedDecl "" extern "C" {...
1245         //   FunctionDecl "fun_c_linkage" void func_c_linkage
1246         v.accept(this);
1247     }
1248 
1249     override void visit(const(VarDecl) v) {
1250         mixin(mixinNodeLog!());
1251 
1252         auto result = () @trusted{ return analyzeVarDecl(v, container, indent); }();
1253 
1254         debug {
1255             logger.info("global variable: ", cast(string) result.name);
1256         }
1257 
1258         recv.put(result);
1259     }
1260 
1261     override void visit(const(FunctionDecl) v) {
1262         mixin(mixinNodeLog!());
1263 
1264         auto result = analyzeFunctionDecl(v, container, indent);
1265 
1266         debug {
1267             auto func = CFunction(result.type.kind.usr, result.name, result.params,
1268                     CxReturnType(result.returnType), result.isVariadic, result.storageClass);
1269             logger.info("function: ", func.toString);
1270         }
1271 
1272         recv.put(result);
1273     }
1274 
1275     override void visit(const(ClassDecl) v) @trusted {
1276         mixin(mixinNodeLog!());
1277         logger.info("class: ", v.cursor.spelling);
1278 
1279         auto result = analyzeRecord(v, container, indent);
1280 
1281         foreach (loc; container.find!LocationTag(result.type.kind.usr).map!(a => a.any).joiner) {
1282             if (!ctrl.doFile(loc.file, loc.file)) {
1283                 return;
1284             }
1285         }
1286 
1287         recv.put(result, ns_stack);
1288 
1289         auto visitor = scoped!(UMLClassVisitor!(ControllerT, ReceiveT))(result.type,
1290                 ns_stack, ctrl, recv, container, indent + 1);
1291         v.accept(visitor);
1292 
1293         auto r_classification = ClassClassificationResult(visitor.type, visitor.classification);
1294         recv.put(r_classification);
1295     }
1296 
1297     override void visit(const(Namespace) v) {
1298         mixin(mixinNodeLog!());
1299 
1300         () @trusted{ ns_stack ~= CppNs(v.cursor.spelling); }();
1301         // pop the stack when done
1302         scope (exit)
1303             ns_stack = ns_stack[0 .. $ - 1];
1304 
1305         // fill the namespace with content from the analyse
1306         v.accept(this);
1307     }
1308 }
1309 
1310 private struct TransformToClassDiagram(ControllerT, LookupT) {
1311 @safe:
1312     import cpptooling.analyzer.clang.analyze_helper : CxxMethodResult,
1313         ConstructorResult, DestructorResult, FieldDeclResult,
1314         CxxBaseSpecifierResult;
1315     import cpptooling.data.type : CppAccess;
1316     import cpptooling.data.type : CppNs;
1317 
1318     invariant {
1319         assert(uml !is null);
1320     }
1321 
1322     private {
1323         UMLClassDiagram uml;
1324         ControllerT ctrl;
1325         LookupT lookup;
1326     }
1327 
1328     /// If class methods should be part of the generated class diagrams.
1329     Flag!"genClassMethod" genClassMethod;
1330 
1331     /// If the parameters of methods should result in directed association.
1332     Flag!"genClassParamDependency" genClassParamDependency;
1333 
1334     /// If the inheritance hierarchy between classes is generated.
1335     Flag!"genClassInheritDependency" genClassInheritDependency;
1336 
1337     /// If the class members result in dependency on those members.
1338     Flag!"genClassMemberDependency" genClassMemberDependency;
1339 
1340     private static string toPrefix(CppAccess access) {
1341         import cpptooling.data.type : CppAccess, AccessType;
1342 
1343         final switch (access) {
1344         case AccessType.Public:
1345             return "+";
1346         case AccessType.Protected:
1347             return "#";
1348         case AccessType.Private:
1349             return "-";
1350         }
1351     }
1352 
1353     void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) {
1354         import std.algorithm : map, joiner;
1355         import std.conv : text;
1356         import std.range : chain, only, retro;
1357         import cpptooling.data : TypeKind, TypeAttr, toStringDecl;
1358 
1359         if (genClassInheritDependency) {
1360             auto src_key = makeClassKey(src.kind.usr);
1361 
1362             auto canonical = lookup.kind(result.canonicalUSR).front;
1363             auto dest_key = makeClassKey(canonical.usr);
1364             auto fqn = canonical.toStringDecl(TypeAttr.init);
1365 
1366             uml.relate(src_key, dest_key,
1367                     cast(UMLClassDiagram.DisplayName) fqn, Relate.Kind.Extend);
1368         }
1369     }
1370 
1371     /// Reconstruct the function signature as a UML comment.
1372     void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) {
1373         import std.algorithm : filter;
1374         import std.traits : ReturnType;
1375         import std.range : chain, only;
1376 
1377         import cpptooling.data : CppMethod, CppConstMethod;
1378 
1379         ReturnType!makeClassKey src_key;
1380 
1381         if (genClassMethod || genClassParamDependency) {
1382             src_key = makeClassKey(src.kind.usr);
1383         }
1384 
1385         if (genClassMethod) {
1386             auto method = CppMethod(USRType("dummy"), result.name, result.params,
1387                     result.returnType, access, CppConstMethod(result.isConst), result.virtualKind);
1388             method.usr.nullify;
1389             uml.put(src_key, UMLClassDiagram.Content(toPrefix(access) ~ method.toString));
1390         }
1391 
1392         if (genClassParamDependency) {
1393             // dfmt off
1394             auto relations =
1395                 chain(getClassMethodRelation(result.params, lookup),
1396                       only(getTypeRelation(cast(TypeKindAttr) result.returnType, lookup)))
1397                 .filter!(a => a.kind != Relate.Kind.None)
1398                 // remove self referencing keys, would result in circles which
1399                 // just clutters the diagrams
1400                 .filter!(a => a.key != src.kind.usr);
1401             // dfmt on
1402             foreach (rel; relations) {
1403                 auto dest_key = makeClassKey(rel.key);
1404                 uml.relate(src_key, dest_key, rel.display, rel.kind);
1405             }
1406         }
1407     }
1408 
1409     void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) {
1410         import std.algorithm : filter;
1411         import std.traits : ReturnType;
1412 
1413         import cpptooling.data : CppCtor;
1414 
1415         ReturnType!makeClassKey src_key;
1416 
1417         if (genClassMethod || genClassParamDependency) {
1418             src_key = makeClassKey(src.kind.usr);
1419         }
1420 
1421         if (genClassMethod) {
1422             auto tor = CppCtor(result.type.kind.usr, result.name, result.params, access);
1423             uml.put(src_key, UMLClassDiagram.Content(toPrefix(access) ~ tor.toString));
1424         }
1425 
1426         if (genClassParamDependency) {
1427             // dfmt off
1428             auto relations = getClassMethodRelation(result.params, lookup)
1429                 .filter!(a => a.kind != Relate.Kind.None)
1430                 // remove self referencing keys, would result in circles which
1431                 // just clutters the diagrams
1432                 .filter!(a => a.key != src.kind.usr);
1433             // dfmt on
1434             foreach (rel; relations) {
1435                 auto dest_key = makeClassKey(rel.key);
1436                 uml.relate(src_key, dest_key, rel.display, rel.kind);
1437             }
1438         }
1439     }
1440 
1441     void put(ref const(TypeKindAttr) src, ref const(DestructorResult) result, in CppAccess access) {
1442         import cpptooling.data : CppDtor;
1443 
1444         if (genClassMethod) {
1445             auto key = makeClassKey(src.kind.usr);
1446             auto tor = CppDtor(result.type.kind.usr, result.name, access, result.virtualKind);
1447             uml.put(key, UMLClassDiagram.Content(toPrefix(access) ~ tor.toString));
1448         }
1449     }
1450 
1451     void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) {
1452         import std.algorithm : filter;
1453 
1454         if (genClassMemberDependency) {
1455             auto rel = getClassMemberRelation(result.type, lookup);
1456             if (rel.kind != Relate.Kind.None) {
1457                 auto src_key = makeClassKey(src.kind.usr);
1458                 auto dest_key = makeClassKey(rel.key);
1459                 uml.relate(src_key, dest_key, rel.display, rel.kind);
1460             }
1461         }
1462     }
1463 
1464     void put(ref const(ClassClassificationResult) result) {
1465         auto key = makeClassKey(result.type.kind.usr);
1466         uml.set(key, result.classification);
1467     }
1468 
1469     void put(ref const(RecordResult) src, const(CppNs)[] reside_in) {
1470         import std.algorithm : map, joiner;
1471         import std.conv : text;
1472         import std.range : chain, only;
1473 
1474         auto key = makeClassKey(src.type.kind.usr);
1475         string fqn = chain(reside_in.map!(a => cast(string) a), only(cast(string) src.name)).joiner("::")
1476             .text;
1477         uml.put(key, cast(UMLClassDiagram.DisplayName) fqn);
1478     }
1479 }
1480 
1481 /** Transform data from a data source (via push) to a UML component diagram.
1482  *
1483  * The component diagram is built upon the assumption that the physical
1484  * location of a declaration/definition has a correlation to the design the
1485  * creator had in mind.
1486  *
1487  * Physical world -> mental model.
1488  *
1489  * Design of relations transform:
1490  * A relation is based on where the identifier is located to the owner of the
1491  * type.
1492  * Identifier-location -> Type-owner-location.
1493  *
1494  * A type-owner-location is where the type is defined.
1495  * This though creates a problem when considering forward declarations in
1496  * combination with pointers, references, parameters.
1497  *
1498  * To handle the above case relations are go through three steps.
1499  *  - Add relations with USR->USR.
1500  *  - First try. Check both USRs location. If both of them are definitions then
1501  *    accept the relation. Otherwise put it into the cache.
1502  *  - Second try. Process the cache at the end of a translation unit. Same
1503  *    criteria as the first try.
1504  *  - Third try. When all translation units have been processed use a fallback
1505  *    strategy for those items left in the cache. At this stage a location
1506  *    corresponding to a declaration is OK. Reason, better than nothing.
1507  *
1508  * In the following example the processing is a.h before b.h.
1509  * If the locatoin of the forward declaration of B had been used the relation
1510  * from a.h to b.h would have been lost.
1511  *
1512  * Example:
1513  * a.h
1514  * ---
1515  * class B;
1516  * class A {
1517  *  B* b;
1518  * };
1519  * ---
1520  *
1521  * b.h
1522  * ---
1523  * class B {};
1524  * ---
1525  */
1526 private @safe struct TransformToComponentDiagram(ControllerT, LookupT) {
1527     import std.algorithm : map, copy, each, joiner;
1528     import std.range : chain;
1529 
1530     import cpptooling.analyzer.clang.analyze_helper : CxxBaseSpecifierResult,
1531         CxxMethodResult, ConstructorResult, DestructorResult, RecordResult,
1532         FieldDeclResult, VarDeclResult, FunctionDeclResult,
1533         TranslationUnitResult;
1534     import cpptooling.data.symbol : Container;
1535     import cpptooling.data : CppAccess, CxReturnType;
1536 
1537     invariant {
1538         assert(diagram !is null);
1539         assert(ctrl !is null);
1540     }
1541 
1542     private {
1543         static struct USRRelation {
1544             USRType from;
1545             USRType to;
1546             Relate.Kind kind;
1547         }
1548 
1549         UMLComponentDiagram diagram;
1550         ControllerT ctrl;
1551         LookupT lookup;
1552         MarkArray!USRRelation dcache;
1553         USRType[] src_cache;
1554     }
1555 
1556     this(UMLComponentDiagram diagram, ControllerT ctrl, LookupT lookup) {
1557         this.diagram = diagram;
1558         this.ctrl = ctrl;
1559         this.lookup = lookup;
1560     }
1561 
1562     /** Store the relations in the cache for later resolution regarding there
1563      * location.
1564      *
1565      * The concept is a source has relations to many destinations.
1566      *
1567      * The relation is hard coded as an Association.
1568      * If the function is generalized to be reused with Class then the hard
1569      * coded must be a lookup table or something to allow differentiating
1570      * depending on "stuff".
1571      *
1572      * It is by design that the src do NOT go via resolveCanonicalType. A free
1573      * variable that is a pointer shall have the "src" still as the pointer
1574      * itself but the destination is the pointed at type.
1575      *
1576      * Params:
1577      *  src = source of the relations
1578      *  range = destinations of the relations
1579      *  target = cache to put the values into
1580      *  lookup = type supporting lookups via USR for the TypeKind
1581      */
1582     static void putToCache(Range, T)(USRType src, Range range, ref T target, LookupT lookup) @safe 
1583             if (is(ElementType!Range == TypeKindAttr)
1584                 || is(ElementType!Range == const(TypeKindAttr))) {
1585         import std.algorithm : filter;
1586 
1587         // dfmt off
1588         foreach(a; range
1589             // remove primitive types
1590             .filter!(a => a.kind.info.kind != TypeKind.Info.Kind.primitive)
1591             .map!(a => resolveCanonicalType(a.kind, a.attr, lookup))
1592             .joiner
1593             .map!(a => a.kind.usr)
1594             // create the relations of type src-to-kind
1595             .map!(to_ => USRRelation(src, to_, Relate.Kind.Associate))) {
1596             target.put(a);
1597         }
1598         // dfmt on
1599     }
1600 
1601     /// ditto
1602     static void putParamsToCache(T)(ref const(TypeKindAttr) src,
1603             const(CxParam)[] params, ref T target, LookupT lookup) @safe {
1604         // dfmt off
1605         auto range = params
1606             // returns a bunch of ranges of the unpacked parameters
1607             .map!(a => unpackParam(a))
1608             .joiner;
1609         // dfmt on
1610 
1611         putToCache(src.kind.usr, range, target, lookup);
1612     }
1613 
1614     static void finalizeSrcCache(LookupT, TargetT)(USRType[] cache, LookupT lookup, TargetT target) {
1615         import std.algorithm : map, joiner;
1616 
1617         // dfmt off
1618         foreach (loc; cache
1619                  .map!(usr => lookup.location(usr))
1620                  .joiner
1621                  .map!(a => a.any)
1622                  .joiner) {
1623             target.putSrc(loc);
1624         }
1625         // dfmt on
1626     }
1627 
1628     /// Process the last bits left in the cache.
1629     void finalize() {
1630         import std.algorithm : map, filter, cache;
1631         import std.range : enumerate, only;
1632         import std.typecons : tuple;
1633 
1634         finalizeSrcCache(src_cache[], lookup, this);
1635         if (src_cache.length > 0) {
1636             logger.tracef("%d relations left in src cache", src_cache.length);
1637         }
1638         src_cache.length = 0;
1639 
1640         if (dcache.data.length > 0) {
1641             logger.tracef("%d relations left. Activating fallback strategy", dcache.data.length);
1642         }
1643 
1644         // dfmt off
1645         foreach (e; dcache.data
1646                  // keep track of the index to allow marking of the cache for removal
1647                  .enumerate
1648                  // find the types
1649                  .map!(a => tuple(a.index, lookup.location(a.value.from), lookup.location(a.value.to)))
1650                  .cache
1651                  // a zero range means a failed lookup, a broken relation
1652                  .filter!(a => a[1].length != 0 && a[2].length != 0)
1653                  // unpack with fallback
1654                  .map!(a => tuple(a[0], a[1].front.any, a[2].front.any))
1655                  // ensure that both both resulted in valid ranges
1656                  .filter!(a => a[1].length != 0 && a[2].length != 0)
1657                  // unpack
1658                  .map!(a => tuple(a[0], a[1].front, a[2].front))
1659                  // check via ctrl (the user) if the destination is "ok"
1660                  .filter!(a => ctrl.doFile(cast(string) a[2].file, cast(string) a[2].file))
1661                  ) {
1662             //TODO warn when a declaration has been used?
1663 
1664             putDest(e[1], e[2], Relate.Kind.Associate);
1665             dcache.markForRemoval(e[0]);
1666         }
1667         // dfmt on
1668 
1669         dcache.doRemoval;
1670 
1671         if (dcache.data.length > 0) {
1672             logger.errorf("Fallback strategy failed for %d USRs. They are:", dcache.data.length);
1673         }
1674 
1675         foreach (e; dcache.data) {
1676             logger.tracef("  %s -> %s", cast(string) e.from, cast(string) e.to);
1677         }
1678     }
1679 
1680     void put(ref const(TranslationUnitResult) result) {
1681         import std.algorithm : map, filter, cache;
1682         import std.range : enumerate, only;
1683         import std.typecons : tuple;
1684 
1685         finalizeSrcCache(src_cache[], lookup, this);
1686         if (src_cache.length > 0) {
1687             logger.tracef("%d relations left in src cache", src_cache.length);
1688         }
1689         src_cache.length = 0;
1690 
1691         // dfmt off
1692         foreach (e; dcache.data
1693                  // keep track of the index to allow marking of the cache for removal
1694                  .enumerate
1695                  // find the types
1696                  .map!(a => tuple(a.index, lookup.location(a.value.from), lookup.location(a.value.to)))
1697                  .cache
1698                  // a zero range means a failed lookup, a broken relation
1699                  .filter!(a => a[1].length != 0 && a[2].length != 0)
1700                  // unpack
1701                  .map!(a => tuple(a[0], a[1].front, a[2].front))
1702                  // only okey with a relatioin TO something that is a definition
1703                  .filter!(a => a[1].hasDefinition && a[2].hasDefinition)
1704                  // check via ctrl (the user) if the destination is "ok"
1705                  .filter!(a => ctrl.doFile(cast(string) a[2].definition.file, cast(string) a[2].definition.file))
1706                  ) {
1707             putDest(e[1].definition, e[2].definition, Relate.Kind.Associate);
1708             dcache.markForRemoval(e[0]);
1709         }
1710         // dfmt on
1711 
1712         dcache.doRemoval;
1713     }
1714 
1715     void put(ref const(RecordResult) result) {
1716         src_cache ~= result.type.kind.usr;
1717     }
1718 
1719     void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) {
1720         putParamsToCache(src, result.params, dcache, lookup);
1721     }
1722 
1723     void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) {
1724         import std.range : only;
1725 
1726         putParamsToCache(src, result.params, dcache, lookup);
1727         putToCache(src.kind.usr, only((cast(const TypeKindAttr) result.returnType)), dcache, lookup);
1728     }
1729 
1730     void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) {
1731         import std.range : only;
1732 
1733         putToCache(src.kind.usr, only(result.type), dcache, lookup);
1734     }
1735 
1736     void put(ref const(TypeKindAttr) src, ref const(ClassClassificationResult) result) {
1737         import std.range : only;
1738 
1739         // called when creating a relation for a nested class
1740         putToCache(src.kind.usr, only(result.type), dcache, lookup);
1741     }
1742 
1743     void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) {
1744         auto r0 = lookup.kind(result.canonicalUSR).map!(a => TypeKindAttr(a.get, TypeAttr.init));
1745 
1746         putToCache(src.kind.usr, r0, dcache, lookup);
1747     }
1748 
1749     void put(ref const(VarDeclResult) result) {
1750         import std.range : only;
1751 
1752         // primitive types do not have a location
1753         if (result.location.kind == LocationTag.Kind.loc) {
1754             putSrc(result.location);
1755 
1756             putToCache(result.instanceUSR, only(result.type), dcache, lookup);
1757         }
1758     }
1759 
1760     void put(ref const(FunctionDeclResult) result) {
1761         import std.range : only;
1762 
1763         src_cache ~= result.type.kind.usr;
1764 
1765         putParamsToCache(result.type, result.params, dcache, lookup);
1766         putToCache(result.type.kind.usr,
1767                 only(cast(const TypeKindAttr) result.returnType), dcache, lookup);
1768     }
1769 
1770     void putSrc(ref const(LocationTag) src) @safe {
1771         string location = src.file;
1772 
1773         if (src.kind == LocationTag.Kind.noloc || !ctrl.doFile(location, location)) {
1774             return;
1775         }
1776 
1777         auto key = makeComponentKey(location, ctrl);
1778         diagram.put(key.key, cast(UMLComponentDiagram.DisplayName) key.display);
1779         diagram.put(key.key, cast(UMLComponentDiagram.Location) location);
1780     }
1781 
1782     void putDest(ref const(LocationTag) src, ref const(LocationTag) dest, Relate.Kind kind) {
1783         auto src_ = makeComponentKey(src.file, ctrl);
1784         auto dest_ = makeComponentKey(dest.file, ctrl);
1785 
1786         // Ignoring self referencing relations.
1787         if (src_.key == dest_.key) {
1788             return;
1789         }
1790 
1791         diagram.relate(src_.key, dest_.key,
1792                 cast(UMLComponentDiagram.DisplayName) dest_.display, kind);
1793     }
1794 }
1795 
1796 /** Route information to specific transformers.
1797  *
1798  * No manipulation of data is to be done in this struct. Only routing to
1799  * appropriate functions.
1800  */
1801 class TransformToDiagram(ControllerT, ParametersT, LookupT) {
1802     import std.range : only;
1803 
1804     import cpptooling.analyzer.clang.analyze_helper : CxxBaseSpecifierResult,
1805         RecordResult, FieldDeclResult, CxxMethodResult, ConstructorResult,
1806         DestructorResult, VarDeclResult, FunctionDeclResult,
1807         TranslationUnitResult;
1808     import cpptooling.data.symbol.types : USRType;
1809     import cpptooling.data : TypeKind, CppNs, CppAccess;
1810 
1811     private {
1812         TransformToComponentDiagram!(ControllerT, LookupT) to_component;
1813         TransformToClassDiagram!(ControllerT, LookupT) to_class;
1814     }
1815 
1816     this(ControllerT ctrl, ParametersT params, LookupT lookup,
1817             UMLComponentDiagram comp_dia, UMLClassDiagram class_dia) {
1818         to_component = typeof(to_component)(comp_dia, ctrl, lookup);
1819         to_class = typeof(to_class)(class_dia, ctrl, lookup, params.genClassMethod,
1820                 params.genClassParamDependency, params.genClassInheritDependency,
1821                 params.genClassMemberDependency);
1822     }
1823 
1824 @safe:
1825 
1826     /** Signal that diagrams to perform a finalization of cached data.
1827      */
1828     void finalize() {
1829         to_component.finalize();
1830     }
1831 
1832     void put(ref const(TranslationUnitResult) result) {
1833         to_component.put(result);
1834     }
1835 
1836     void put(ref const(RecordResult) result, const(CppNs)[] reside_in) {
1837         to_class.put(result, reside_in);
1838         to_component.put(result);
1839     }
1840 
1841     void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) {
1842         to_class.put(src, result);
1843         to_component.put(src, result);
1844     }
1845 
1846     void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) {
1847         to_class.put(src, result, access);
1848         to_component.put(src, result, access);
1849     }
1850 
1851     void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) {
1852         to_class.put(src, result, access);
1853         to_component.put(src, result, access);
1854     }
1855 
1856     void put(ref const(TypeKindAttr) src, ref const(DestructorResult) result, in CppAccess access) {
1857         to_class.put(src, result, access);
1858     }
1859 
1860     void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) {
1861         to_class.put(src, result, access);
1862         to_component.put(src, result, access);
1863     }
1864 
1865     void put(ref const(ClassClassificationResult) result) {
1866         to_class.put(result);
1867     }
1868 
1869     /** A nested class.
1870      *
1871      * Propagate the classification and relation of the root->nested.
1872      */
1873     void put(ref const(TypeKindAttr) src, ref const(ClassClassificationResult) result) {
1874         to_component.put(src, result);
1875         // only needs result
1876         to_class.put(result);
1877     }
1878 
1879     void put(ref const(VarDeclResult) result) {
1880         to_component.put(result);
1881     }
1882 
1883     void put(ref const(FunctionDeclResult) result) {
1884         to_component.put(result);
1885     }
1886 }
1887 
1888 // visualize where the module private starts
1889 private: // ******************************************************************
1890 
1891 import cpptooling.data.representation : CppRoot, CppClass, CppMethod, CppCtor,
1892     CppDtor, CppNamespace, CFunction, CxGlobalVariable, LocationTag, Location;
1893 import cpptooling.data.symbol : Container;
1894 import dsrcgen.plantuml;
1895 
1896 struct KeyValue {
1897     UMLComponentDiagram.Key key;
1898     string display;
1899     string absFilePath;
1900 }
1901 
1902 struct KeyRelate {
1903     string file;
1904     KeyValue key;
1905     Relate.Kind kind;
1906 }
1907 
1908 /**
1909  * Params:
1910  *  file = filename of the relation.
1911  *  kind = kind of relation such as associaiton, composition etc.
1912  */
1913 struct PathKind {
1914     string file;
1915     Relate.Kind kind;
1916 }
1917 
1918 /** Calculate the key based on the directory the file that declares the symbol exist in.
1919  *
1920  * Additional metadata as to make it possible to backtrack.
1921  */
1922 KeyValue makeComponentKey(in string location_file, Controller ctrl) @trusted {
1923     import std.array : appender;
1924     import std.base64 : Base64Impl, Base64;
1925     import std.path : buildNormalizedPath, absolutePath, relativePath, baseName;
1926     import std.typecons : tuple;
1927 
1928     // TODO consider using hash murmur2/3 to shorten the length of the encoded
1929     // path
1930 
1931     alias SafeBase64 = Base64Impl!('-', '_', Base64.NoPadding);
1932 
1933     string file_path = buildNormalizedPath(location_file.absolutePath);
1934     string strip_path = cast(string) ctrl.doComponentNameStrip(FileName(file_path));
1935     string rel_path = relativePath(strip_path);
1936     string display_name = strip_path.baseName;
1937 
1938     auto enc = appender!(char[])();
1939     SafeBase64.encode(cast(ubyte[]) rel_path, enc);
1940 
1941     auto k = KeyValue(UMLComponentDiagram.Key(enc.data.idup), display_name, strip_path);
1942 
1943     debug {
1944         logger.tracef("Component:%s stripped:%s file:%s base64:%s", k.display,
1945                 strip_path, file_path, cast(string) k.key);
1946     }
1947 
1948     return k;
1949 }
1950 
1951 UMLClassDiagram.Key makeClassKey(in USRType key) @trusted {
1952     import std.base64 : Base64Impl, Base64;
1953     import std.array : appender;
1954 
1955     // TODO consider using hash murmur2/3 function to shorten the length of the
1956     // encoded path
1957 
1958     alias SafeBase64 = Base64Impl!('-', '_', Base64.NoPadding);
1959 
1960     auto enc = appender!(char[])();
1961     SafeBase64.encode(cast(ubyte[])(cast(string) key), enc);
1962 
1963     auto k = UMLClassDiagram.Key(enc.data.idup);
1964     return k;
1965 }
1966 
1967 private auto unpackParam(CxParam p) @trusted {
1968     import std.range : only, dropOne;
1969     import std.variant : visit;
1970     import cpptooling.data : TypeKindVariable, VariadicType;
1971 
1972     // dfmt off
1973     return p.visit!(
1974                     (TypeKindVariable v) => only(v.type),
1975                     (TypeKindAttr v) => only(v),
1976                     (VariadicType v) {
1977                         logger.error(
1978                                      "Variadic function not supported. Would require runtime information to relate.");
1979                         return only(TypeKindAttr.init).dropOne;
1980                     });
1981     // dfmt on
1982 }
1983 
1984 struct ClassRelate {
1985     Relate.Kind kind;
1986     Relate.Key key;
1987     UMLClassDiagram.DisplayName display;
1988 }
1989 
1990 auto getClassMemberRelation(LookupT)(TypeKindAttr type, LookupT lookup) {
1991     //TODO code duplication with getMethodRelation
1992     // .. fix it. This function is ugly.
1993     import std.algorithm : each, map, filter, joiner;
1994     import std.array : array;
1995     import std.typecons : tuple;
1996 
1997     // TODO this is a mega include. Reduce it.
1998     import cpptooling.data;
1999 
2000     auto r = ClassRelate(Relate.Kind.None, Relate.Key(""), UMLClassDiagram.DisplayName(""));
2001 
2002     final switch (type.kind.info.kind) with (TypeKind.Info) {
2003     case Kind.typeRef:
2004         auto tref = lookup.kind(type.kind.info.canonicalRef);
2005         foreach (t; tref.filter!(a => a.info.kind == Kind.record)) {
2006             auto rel_type = Relate.Kind.Aggregate;
2007             if (type.attr.isPtr || type.attr.isRef) {
2008                 rel_type = Relate.Kind.Compose;
2009             }
2010             r = ClassRelate(rel_type, t.usr,
2011                     cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init));
2012         }
2013         break;
2014     case Kind.record:
2015         r = ClassRelate(Relate.Kind.Aggregate, type.kind.usr,
2016                 cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init));
2017         break;
2018     case Kind.array:
2019         auto element = lookup.kind(type.kind.info.element);
2020         foreach (e; element.filter!(a => a.info.kind == Kind.record)) {
2021             auto rel_type = Relate.Kind.Aggregate;
2022             if (type.attr.isPtr || type.attr.isRef) {
2023                 rel_type = Relate.Kind.Compose;
2024             }
2025             r = ClassRelate(rel_type, e.usr,
2026                     cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init));
2027         }
2028         break;
2029     case Kind.pointer:
2030         auto pointee = lookup.kind(type.kind.info.pointee);
2031         foreach (p; pointee.filter!(a => a.info.kind == Kind.record)) {
2032             string display = p.toStringDecl(TypeAttr.init);
2033             r = ClassRelate(Relate.Kind.Compose, p.usr, cast(UMLClassDiagram.DisplayName) display);
2034         }
2035         break;
2036     case Kind.primitive:
2037     case Kind.simple:
2038     case Kind.func:
2039     case Kind.funcPtr:
2040     case Kind.funcSignature:
2041     case Kind.ctor:
2042     case Kind.dtor:
2043     case Kind.null_:
2044         break;
2045     }
2046 
2047     return r;
2048 }
2049 
2050 private ClassRelate getTypeRelation(LookupT)(TypeKindAttr tk, LookupT lookup) {
2051     import std.algorithm : filter;
2052     import cpptooling.data : TypeKind, TypeAttr, toStringDecl;
2053 
2054     auto r = ClassRelate(Relate.Kind.None, Relate.Key(""), UMLClassDiagram.DisplayName(""));
2055 
2056     final switch (tk.kind.info.kind) with (TypeKind.Info) {
2057     case Kind.typeRef:
2058         auto tref = lookup.kind(tk.kind.info.canonicalRef);
2059         foreach (t; tref.filter!(a => a.info.kind == Kind.record)) {
2060             r = ClassRelate(Relate.Kind.Associate, Relate.Key(t.usr),
2061                     cast(UMLClassDiagram.DisplayName) t.toStringDecl(TypeAttr.init));
2062         }
2063         break;
2064     case Kind.record:
2065         r = ClassRelate(Relate.Kind.Associate, tk.kind.usr,
2066                 cast(UMLClassDiagram.DisplayName) tk.kind.toStringDecl(TypeAttr.init));
2067         break;
2068     case Kind.array:
2069         auto element = lookup.kind(tk.kind.info.element);
2070         foreach (e; element.filter!(a => a.info.kind == Kind.record)) {
2071             r = ClassRelate(Relate.Kind.Associate, e.usr,
2072                     cast(UMLClassDiagram.DisplayName) e.toStringDecl(TypeAttr.init));
2073         }
2074         break;
2075     case Kind.pointer:
2076         auto pointee = lookup.kind(tk.kind.info.pointee);
2077         foreach (p; pointee.filter!(a => a.info.kind == Kind.record)) {
2078             string display = p.toStringDecl(TypeAttr.init);
2079             r = ClassRelate(Relate.Kind.Associate, Relate.Key(p.usr),
2080                     cast(UMLClassDiagram.DisplayName) display);
2081         }
2082         break;
2083     case Kind.primitive:
2084     case Kind.simple:
2085     case Kind.func:
2086     case Kind.funcPtr:
2087     case Kind.funcSignature:
2088     case Kind.ctor:
2089     case Kind.dtor:
2090     case Kind.null_:
2091     }
2092 
2093     return r;
2094 }
2095 
2096 private auto getClassMethodRelation(LookupT)(const(CxParam)[] params, LookupT lookup) {
2097     import std.array : array;
2098     import std.algorithm : among, map, filter;
2099     import std.variant : visit;
2100     import cpptooling.data : TypeKind, TypeAttr, TypeKindAttr, toStringDecl,
2101         VariadicType;
2102 
2103     static ClassRelate genParam(CxParam p, LookupT lookup) @trusted {
2104         // dfmt off
2105         return p.visit!(
2106             (TypeKindVariable tkv) => getTypeRelation(tkv.type, lookup),
2107             (TypeKindAttr tk) => getTypeRelation(tk, lookup),
2108             (VariadicType vk)
2109                 {
2110                     logger.error("Variadic function not supported.");
2111                     // Because what types is either discovered first at runtime
2112                     // or would require deeper inspection of the implementation
2113                     // where the variadic is used.
2114                     return ClassRelate.init;
2115                 }
2116             );
2117         // dfmt on
2118     }
2119 
2120     // dfmt off
2121     return params.map!(a => genParam(a, lookup)).array();
2122     // dfmt on
2123 }
2124 
2125 void generate(UMLClassDiagram uml_class, UMLComponentDiagram uml_comp,
2126         Flag!"doGenDot" doGenDot, Generator.Modules modules) @safe {
2127     import std.algorithm : each;
2128     import std.format : format;
2129     import std.range : enumerate;
2130 
2131     // TODO code duplicaton with class and component.
2132     // Generalize, reduce.
2133 
2134     auto classes_preamble = modules.classes.base;
2135     classes_preamble.suppressIndent(1);
2136     foreach (idx, kv; uml_class.fanOutSorted.enumerate) {
2137         generate(kv.key, kv.value, classes_preamble);
2138         generateClassRelate(uml_class.relateTo(kv.key)
2139                 .toFlatArray(cast(Relate.Key) kv.key), modules.classes);
2140         if (doGenDot) {
2141             auto nodes = modules.classes_dot.base;
2142             nodes.suppressIndent(1);
2143             nodes.stmt(format(`"%s" [label="%s"]`, kv.key, kv.value.displayName));
2144 
2145             // make a range of all relations from THIS to other components
2146             auto r = uml_class.relateTo(kv.key).toRange(cast(Relate.Key) kv.key);
2147 
2148             generateDotRelate(r, idx, modules.classes_dot);
2149         }
2150     }
2151 
2152     foreach (idx, kv; uml_comp.fanOutSorted.enumerate) {
2153         generate(kv.key, kv.value, modules.components);
2154         if (doGenDot) {
2155             auto nodes = modules.components_dot.base;
2156             nodes.suppressIndent(1);
2157             nodes.stmt(format(`"%s" [label="%s"]`, kv.key, kv.value.displayName));
2158 
2159             // make a range of all relations from THIS to other components
2160             auto r = uml_comp.relateTo(kv.key).toRange(cast(Relate.Key) kv.key);
2161 
2162             generateDotRelate(r, idx, modules.components_dot);
2163         }
2164     }
2165     generateComponentRelate(uml_comp.relateToFlatArray, modules.components);
2166 }
2167 
2168 /** Generate PlantUML class and relations from the class.
2169  *
2170  * By generating the relations out of the class directly after the class
2171  * definitions it makes it easier for GraphViz to generate a not-so-muddy
2172  * image.
2173  */
2174 private void generate(UMLClassDiagram.Key key, const UMLClassDiagram.Class c, PlantumlModule m) @safe {
2175     import std.algorithm : each;
2176     import dsrcgen.plantuml : addSpot;
2177 
2178     ClassType pc;
2179 
2180     if (c.content.length == 0) {
2181         pc = m.class_(cast(string) c.displayName);
2182     } else {
2183         pc = m.classBody(cast(string) c.displayName);
2184         c.content.each!(a => pc.method(a));
2185     }
2186     pc.addAs.text(cast(string) key);
2187 
2188     //TODO add a plantuml macro and use that as color for interface
2189     // Allows the user to control the color via the PREFIX_style.iuml
2190     switch (c.classification) with (cpptooling.data.class_classification.State) {
2191     case Abstract:
2192         pc.addSpot("<< (A, Pink) >>");
2193         break;
2194     case VirtualDtor:
2195     case Pure:
2196         pc.addSpot("<< (I, LightBlue) >>");
2197         break;
2198     default:
2199         break;
2200     }
2201 }
2202 
2203 private void generateClassRelate(T)(T relate_range, PlantumlModule m) @safe {
2204     static auto convKind(Relate.Kind kind) {
2205         static import dsrcgen.plantuml;
2206 
2207         final switch (kind) with (Relate.Kind) {
2208         case None:
2209             assert(0);
2210         case Extend:
2211             return dsrcgen.plantuml.Relate.Extend;
2212         case Compose:
2213             return dsrcgen.plantuml.Relate.Compose;
2214         case Aggregate:
2215             return dsrcgen.plantuml.Relate.Aggregate;
2216         case Associate:
2217             return dsrcgen.plantuml.Relate.ArrowTo;
2218         case Relate:
2219             return dsrcgen.plantuml.Relate.Relate;
2220         }
2221     }
2222 
2223     foreach (r; relate_range) {
2224         m.relate(cast(ClassNameType) r.from, cast(ClassNameType) r.to, convKind(r.kind));
2225     }
2226 }
2227 
2228 private void generateDotRelate(T)(T relate_range, ulong color_idx, PlantumlModule m) @safe {
2229     import std.format : format;
2230 
2231     static import dsrcgen.plantuml;
2232 
2233     static string getColor(ulong idx) {
2234         static string[] colors = [
2235             "red", "mediumpurple", "darkorange", "deeppink", "green", "coral", "orangered", "plum", "deepskyblue",
2236             "slategray", "cadetblue", "olive", "silver", "indianred", "black"
2237         ];
2238         return colors[idx % colors.length];
2239     }
2240 
2241     if (relate_range.length > 0) {
2242         m.stmt(format("edge [color=%s]", getColor(color_idx)));
2243     }
2244 
2245     foreach (r; relate_range) {
2246         auto l = m.relate(cast(ClassNameType) r.from, cast(ClassNameType) r.to,
2247                 dsrcgen.plantuml.Relate.DotArrowTo);
2248         //TODO this is ugly, fix dsrcgen relate to support graphviz/DOT
2249         auto w = new dsrcgen.plantuml.Text!PlantumlModule(format("[weight=%d] ", r.count));
2250         l.block.prepend(w);
2251     }
2252 }
2253 
2254 private void generate(UMLComponentDiagram.Key key,
2255         const UMLComponentDiagram.Component component, PlantumlModule m) @safe {
2256     import std.algorithm : map;
2257     import std.conv : text;
2258     import std.path : buildNormalizedPath, relativePath;
2259 
2260     auto comp = m.classBody(cast(string) component.displayName);
2261     comp.addAs.text(cast(string) key);
2262 
2263     // early exit because the slice of contains segfaults otherwise.
2264     if (component.contains.length == 0)
2265         return;
2266 
2267     // dfmt off
2268     foreach (fname; component.contains[]
2269         .map!(a => cast(string) a)
2270         .map!(a => () @trusted { return buildNormalizedPath(a).relativePath; }())) {
2271         comp.m.stmt(text(fname));
2272     }
2273     // dfmt on
2274 }
2275 
2276 private void generateComponentRelate(T)(T relate_range, PlantumlModule m) @safe {
2277     static auto convKind(Relate.Kind kind) {
2278         static import dsrcgen.plantuml;
2279 
2280         final switch (kind) with (Relate.Kind) {
2281         case Relate:
2282             return dsrcgen.plantuml.Relate.Relate;
2283         case Extend:
2284             assert(0);
2285         case Compose:
2286             assert(0);
2287         case Aggregate:
2288             assert(0);
2289         case Associate:
2290             return dsrcgen.plantuml.Relate.ArrowTo;
2291         case None:
2292             assert(0);
2293         }
2294     }
2295 
2296     foreach (r; relate_range) {
2297         m.relate(cast(ComponentNameType) r.from, cast(ComponentNameType) r.to, convKind(r.kind));
2298     }
2299 }