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