1 /**
2 Copyright: Copyright (c) 2016-2017, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 
10 Overall design of the data flow when analyzing.
11  - Visitor pull data from the AST.
12  - Visitor push data to the general Transform.
13  - The Transform splice the data and forwards to the specialized transformers.
14    The top Transform act as a mediator. It do not have any logic or knowledge
15    other than how to forward the data to the specialized transforms.
16  - The specialized transformers finalizes the data, delays, decisions etc.
17    They decide when to do the final forwarding to the diagrams.
18  - The UML diagrams are processed by the Generator.
19    The generator transforms the in-memory representation to content suitable to
20    store in files.
21  - The generator forwards the content to a receiver, the registered Products.
22  - Backend done. See frontend for what happens with the Products.
23 */
24 module dextool.plugin.backend.plantuml;
25 
26 import std.meta : templateAnd, templateOr;
27 import std.range : ElementType;
28 import std.typecons : Flag, Yes, No;
29 import logger = std.experimental.logger;
30 
31 import dsrcgen.plantuml;
32 
33 import dextool.type;
34 import cpptooling.analyzer.clang.ast : Visitor;
35 import cpptooling.data : TypeKind, TypeAttr, resolveCanonicalType, USRType,
36     TypeKindAttr, CxParam, CxReturnType, TypeKindVariable;
37 import cpptooling.data.symbol.types : FullyQualifiedNameType;
38 import cpptooling.analyzer.clang.analyze_helper : RecordResult;
39 import dextool.plugin.utility : MarkArray;
40 
41 static import cpptooling.data.class_classification;
42 
43 version (unittest) {
44     import unit_threaded : Name, shouldEqual;
45 } else {
46     private struct Name {
47         string name_;
48     }
49 }
50 
51 /** Control various aspects of the analyze and generation like what nodes to
52  * process.
53  */
54 @safe interface Controller {
55     /// Query the controller with the filename of the AST node for a decision
56     /// if it shall be processed.
57     bool doFile(in string filename, in string info);
58 
59     /** Determine by checking the filesystem if a templated PREFIX_style file shall be created.
60      *
61      * Create it with a minimal style.
62      * Currently just the direction but may change in the future.
63      */
64     Flag!"genStyleInclFile" genStyleInclFile();
65 
66     /// Strip the filename according to user regex.
67     FileName doComponentNameStrip(FileName fname);
68 }
69 
70 /// Parameters used during generation.
71 /// Important aspact that they do NOT change, therefore it is pure.
72 @safe pure const interface Parameters {
73     import std.typecons : Flag;
74 
75     static struct Files {
76         FileName classes;
77         FileName components;
78         FileName styleIncl;
79         FileName styleOutput;
80     }
81 
82     /// Output directory to store files in.
83     DirName getOutputDirectory();
84 
85     /// Files to write generated diagram data to.
86     Files getFiles();
87 
88     /// Name affecting filenames.
89     FilePrefix getFilePrefix();
90 
91     /** In all diagrams generate an "!include" of the style file.
92      *
93      * If the file PREFIX_style do not exist, create it with a minimal style.
94      * Currently just the direction but may change in the future.
95      */
96     Flag!"doStyleIncl" doStyleIncl();
97 
98     /// Generate a dot graph in the plantuml file
99     Flag!"doGenDot" doGenDot();
100 
101     /// If class methods should be part of the generated class diagrams.
102     Flag!"genClassMethod" genClassMethod();
103 
104     /// If the parameters of methods should result in directed association.
105     Flag!"genClassParamDependency" genClassParamDependency();
106 
107     /// If the inheritance hierarchy between classes is generated.
108     Flag!"genClassInheritDependency" genClassInheritDependency();
109 
110     /// If the class members result in dependency on those members.
111     Flag!"genClassMemberDependency" genClassMemberDependency();
112 }
113 
114 /// Data produced by the generator like files.
115 @safe interface Products {
116     /** Data pushed from the generator to be written to files.
117      *
118      * The put value is the code generation tree. It allows the caller of
119      * Generator to inject more data in the tree before writing. For example a
120      * custom header.
121      *
122      * Params:
123      *   fname = file the content is intended to be written to.
124      *   data = data to write to the file.
125      */
126     void putFile(FileName fname, PlantumlRootModule data);
127 
128     /// ditto.
129     void putFile(FileName fname, PlantumlModule data);
130 }
131 
132 /** The supported "kind"s of relations between entities. Related to the UML
133  * standard.
134  */
135 enum RelateKind {
136     None,
137     Extend,
138     Compose,
139     Aggregate,
140     Associate,
141     Relate
142 }
143 
144 /** Relations to targets with count and kind.
145  *
146  * Intented to be used in a hashmap with the key as the "from".
147  */
148 private struct Relate {
149 @safe:
150     alias Key = USRType;
151     alias Kind = RelateKind;
152 
153     private static struct Inner {
154         uint count;
155         Kind kind;
156     }
157 
158     private Inner[][Key] to;
159 
160     /// Returns: number of outgoing connections
161     size_t fanOut() pure nothrow const {
162         return to.length;
163     }
164 
165     void put(Key to_, Kind kind)
166     out {
167         assert(to_ in to);
168     }
169     body {
170         auto v = to_ in to;
171         if (v is null) {
172             to[to_] = Inner[].init;
173             v = to_ in to;
174         }
175 
176         // ugly algorithm, use an inner hashmap instead
177         bool is_new = true;
178         foreach (ref r; *v) {
179             if (r.kind == kind) {
180                 r.count++;
181                 is_new = false;
182                 break;
183             }
184         }
185 
186         if (is_new) {
187             *v ~= Inner(1, kind);
188         }
189     }
190 
191     /** A range of the form FROM-TO with metadata.
192      *
193      * count is the total number of outgoing connections to the target.
194      * For example would 2 Relation and 4 Extend result in the sum of 6.
195      */
196     auto toRange(const Relate.Key from) pure const @trusted {
197         import std.algorithm : map;
198         import std.array : array;
199 
200         static struct RelateTuple {
201             Relate.Key from;
202             Relate.Key to;
203             ulong count;
204         }
205 
206         static ulong sumFanOut(const(Inner)[] inner) pure {
207             import std.algorithm : sum;
208 
209             return inner.map!(a => a.count).sum;
210         }
211 
212         // dfmt off
213         return to.byKeyValue.map!(a => RelateTuple(from, a.key, sumFanOut(a.value)))
214             .array();
215         // dfmt on
216     }
217 
218     /// Convert the TO/value store to a FROM-KIND-TO-COUNT array.
219     auto toFlatArray(const Relate.Key from) pure const @trusted {
220         import std.algorithm : filter, map, joiner;
221         import std.array : array;
222 
223         static struct RelateTuple {
224             Relate.Key from;
225             Kind kind;
226             Relate.Key to;
227             ulong count;
228         }
229 
230         // dfmt off
231         return to.byKeyValue.map!(a => a.value
232                                     .filter!(b => b.kind != Kind.None)
233                                     .map!(b => RelateTuple(from, b.kind, a.key, b.count))
234                                     .array())
235             .joiner()
236             .array();
237         // dfmt on
238     }
239 
240     auto toStringArray(const Relate.Key from) pure const @trusted {
241         import std.algorithm : map;
242         import std.conv : text;
243         import std.format : format;
244         import std.array : array;
245 
246         // dfmt off
247         return this.toFlatArray(from)
248             .map!(b => format("%s -%s- [%d]%s", cast(string) b.from, text(b.kind), b.count, cast(string) b.to))
249             .array();
250         // dfmt on
251     }
252 }
253 
254 private size_t[] nameIndexSortedRange(T, alias sortNameBy)(T arr) pure {
255     import std.algorithm : makeIndex;
256 
257     auto index = new size_t[arr.length];
258 
259     makeIndex!((a, b) => sortNameBy(a) < sortNameBy(b))(arr, index);
260     return index;
261 }
262 
263 private auto nameSortedRange(T, alias sortNameBy)(const T t) pure {
264     import std.algorithm : map;
265     import std.array : array;
266 
267     auto arr = t.asArray();
268     auto index = nameIndexSortedRange!(typeof(arr), sortNameBy)(arr);
269 
270     return index.map!(i => arr[i]).array();
271 }
272 
273 private auto fanOutSorted(T)(T t) pure {
274     import std.algorithm : makeIndex, map;
275     import std.array : array;
276 
277     //TODO how to avoid doing this allocation?
278 
279     auto arr = t.nameSortedRange();
280     auto fanout_i = new size_t[arr.length];
281 
282     // dfmt off
283     makeIndex!((a, b) => t.relate_to[cast(Relate.Key) a.key].fanOut > t.relate_to[cast(Relate.Key) b.key].fanOut)(arr, fanout_i);
284     // dfmt on
285 
286     return fanout_i.map!(i => arr[i]).array();
287 }
288 
289 /** UML Class Diagram.
290  *
291  * Not designed for the general case.
292  * The design is what the plantuml plugin needs when analyzing more than one
293  * file. This is the container that is then passed between the analyze stages.
294  *
295  * All classes must exist in "classes".
296  * It is common that during data gathering a class is found to be related to
297  * another class by a USR. The relation is added before the class represented
298  * by the USR is added.
299  *
300  * A --> B
301  * Directed relation.
302  * A can have many connections to B.
303  *
304  * Store of R[A.B].
305  * When analyzing the structural data it is this kind of relations that are
306  * found. From a class to many X, where X is other classes.
307  * The key used must be unique, thus the choice of using USR.
308  *
309  * Example of relations.
310  * A --> B (member)
311  * A --> B (member)
312  * A --> B (inherit)
313  * B --> A (member)
314  *
315  * relate[A].put(B, Compose)
316  * relate[A].put(B, Compose)
317  * relate[A].put(B, Extend)
318  * relate[B].put(A, Compose)
319  *
320  * The relations are of the kind Fan-out, one-to-many.
321  * They can be sorted in descending fan-out-count order.
322  */
323 class UMLClassDiagram {
324 @safe:
325     import std.typecons : NullableRef;
326     import std.format : FormatSpec;
327 
328     alias ClassClassificationState = cpptooling.data.class_classification.State;
329 
330     alias Key = USRType;
331     struct DisplayName {
332         string payload;
333         alias payload this;
334     }
335 
336     struct Content {
337         string payload;
338         alias payload this;
339     }
340 
341     private struct Class {
342         DisplayName displayName;
343         ClassClassificationState classification;
344         string[] content;
345     }
346 
347     /// The class is only added if it doesn't already exist in the store.
348     void put(Key key, DisplayName display_name) {
349         if (key !in classes) {
350             classes[key] = Class(display_name);
351             relate_to[cast(Relate.Key) key] = Relate.init;
352         }
353     }
354 
355     /** Store parameter content with the key.
356      *
357      * It is the body of the class in a class diagram.
358      */
359     void put(Key key, Content content)
360     in {
361         assert(key in classes);
362     }
363     body {
364         classes[key].content ~= cast(string) content;
365     }
366 
367     /** Set the classification of a class.
368      *
369      * Example would be a pure virtual, which in java would be an interface.
370      */
371     void set(Key key, ClassClassificationState classification)
372     in {
373         assert(key in classes);
374     }
375     body {
376         classes[key].classification = classification;
377     }
378 
379     /** Add a relation between two classes and increase the count on the class
380      * related TO.
381      */
382     void relate(Key from, Key to, DisplayName display_name, Relate.Kind kind)
383     out {
384         assert(from in classes);
385         assert(to in classes);
386         assert(kind != Relate.Kind.None);
387     }
388     body {
389         put(to, display_name);
390         relate_to[cast(Relate.Key) from].put(cast(Relate.Key) to, kind);
391     }
392 
393     /** Use to retrieve the relation struct for the key.
394      *
395      * Example:
396      * ---
397      * diagram.relateTo(Key("foo")).put(Key("bar"), Relate.Kind.Associate);
398      * ---
399      */
400     const(Relate) relateTo(Key k) pure const
401     in {
402         assert(k in classes);
403         assert((cast(Relate.Key) k) in relate_to);
404     }
405     body {
406         return relate_to[cast(Relate.Key) k];
407     }
408 
409     /// Returns: Flat array of all relations of type FROM-KIND-TO-COUNT.
410     auto relateToFlatArray() pure const @trusted {
411         import std.algorithm : map, joiner;
412         import std.array : array;
413 
414         return relate_to.byKeyValue.map!(a => a.value.toFlatArray(a.key)).joiner().array();
415     }
416 
417     private static struct KeyClass {
418         Key key;
419         const(Class) value;
420     }
421 
422     /// Returns: An array of the key/values.
423     KeyClass[] asArray() const pure nothrow @trusted {
424         import std.array : array;
425         import std.algorithm : map;
426 
427         //TODO how to do this without so much generated GC
428 
429         // dfmt off
430         return classes.byKeyValue
431             .map!(a => KeyClass(a.key, a.value))
432             .array();
433         // dfmt on
434     }
435 
436     /// Returns: An array of the key/values sorted on key.
437     auto nameSortedRange() const pure @trusted {
438         static string sortClassNameBy(T)(ref T a) {
439             return a.value.displayName;
440         }
441 
442         return .nameSortedRange!(typeof(this), sortClassNameBy)(this);
443     }
444 
445     private string[] classesToStringArray() const pure @trusted {
446         import std.algorithm : map, joiner;
447         import std.array : array;
448         import std.ascii : newline;
449         import std.conv : text;
450         import std.format : format;
451         import std.range : only, chain, takeOne;
452 
453         // dfmt off
454         return classes
455             .byKeyValue
456             .map!(a => chain(only(format("%s -> %s%s",
457                                          a.value.displayName,
458                                          a.key,
459                                          a.value.content.length == 0 ? "" : " {")),
460                              a.value.content.dup.map!(b => "  " ~ b),
461                              a.value.content.takeOne.map!(b => "} // " ~ a.value.displayName))
462                   .joiner(newline)
463                   .text)
464             .array();
465         // dfmt on
466     }
467 
468     private string[] relateToStringArray() const pure @trusted {
469         import std.algorithm : map, joiner;
470         import std.array : array;
471 
472         return relate_to.byKeyValue.map!(a => a.value.toStringArray(a.key)).joiner().array();
473     }
474 
475     void toString(Writer, Char)(scope Writer w, FormatSpec!Char) const {
476         import std.ascii : newline;
477         import std.format : formattedWrite;
478         import std.range.primitives : put;
479         import std.range : zip, repeat;
480 
481         formattedWrite(w, "UML Class Diagram (Total %d) {", classes.length);
482         put(w, newline);
483         foreach (a; zip(classesToStringArray, repeat(newline))) {
484             put(w, a[0]);
485             put(w, a[1]);
486         }
487         foreach (a; zip(relateToStringArray, repeat(newline))) {
488             put(w, a[0]);
489             put(w, a[1]);
490         }
491         put(w, "} // UML Class Diagram");
492     }
493 
494     override string toString() @safe pure const {
495         import std.exception : assumeUnique;
496         import std.format : FormatSpec;
497 
498         char[] buf;
499         buf.reserve(100);
500         auto fmt = FormatSpec!char("%s");
501         toString((const(char)[] s) { buf ~= s; }, fmt);
502         auto trustedUnique(T)(T t) @trusted {
503             return assumeUnique(t);
504         }
505 
506         return trustedUnique(buf);
507     }
508 
509     private Relate[Relate.Key] relate_to;
510     private Class[Key] classes;
511 }
512 
513 /** UML Component Diagram.
514  *
515  * Not designed for the general case.
516  * The design is what the plantuml plugin needs when analyzing more than one
517  * file. This is the container that is then passed between the analyze stages.
518  *
519  * The relations are of the kind Fan-out.
520  *
521  * See_Also: UMLClassDiagram
522  */
523 class UMLComponentDiagram {
524     import std.container.rbtree : RedBlackTree;
525 
526     struct Key {
527         string payload;
528         alias payload this;
529     }
530 
531     struct Location {
532         string payload;
533         alias payload this;
534     }
535 
536     struct DisplayName {
537         string payload;
538         alias payload this;
539     }
540 
541     private struct Component {
542         DisplayName displayName;
543         string[] toFile;
544         RedBlackTree!Location contains;
545     }
546 
547     /// The component is only added if it doesn't already exist in the store.
548     void put(Key key, DisplayName display_name) @safe {
549         if (key !in components) {
550             components[key] = Component(display_name, null, new RedBlackTree!Location);
551             relate_to[cast(Relate.Key) key] = Relate.init;
552         }
553     }
554 
555     /// Add a location that the component encapsulate
556     void put(Key key, Location loc) @trusted
557     out {
558         assert(key in components);
559     }
560     body {
561         components[key].contains.insert(loc);
562     }
563 
564     /** Add a relation between two components and increase the count on the class
565      * related TO.
566      */
567     void relate(Key from, Key to, DisplayName toDisplayName, Relate.Kind kind) @safe
568     out {
569         assert(from in components);
570         assert(to in components);
571         assert(kind != Relate.Kind.None);
572     }
573     body {
574         put(to, toDisplayName);
575 
576         auto rel = cast(Relate.Key) from in relate_to;
577         if (rel is null) {
578             relate_to[cast(Relate.Key) from] = Relate();
579             rel = cast(Relate.Key) from in relate_to;
580         }
581         rel.put(cast(Relate.Key) to, kind);
582 
583         auto comp = from in components;
584         if (comp is null) {
585             // TODO this is a hack. The display name should be the froms name.
586             components[from] = Component(cast(DisplayName) from, null, new RedBlackTree!Location);
587             comp = from in components;
588         }
589         comp.toFile ~= cast(string) to;
590     }
591 
592     /** Use to retrieve the relation struct for the key.
593      *
594      * Example:
595      * ---
596      * diagram.relateTo(Key("foo")).put(Key("bar"), Relate.Kind.Associate);
597      * ---
598      */
599     const(Relate) relateTo(Key k) pure const @safe
600     in {
601         assert(k in components);
602         assert((cast(Relate.Key) k) in relate_to);
603     }
604     body {
605         return relate_to[cast(Relate.Key) k];
606     }
607 
608     /// Return: Flat array of all relations of type FROM-KIND-TO-COUNT.
609     auto relateToFlatArray() pure const @trusted {
610         import std.algorithm : map, joiner;
611         import std.array : array;
612 
613         return relate_to.byKeyValue.map!(a => a.value.toFlatArray(a.key)).joiner().array();
614     }
615 
616     private static struct KeyComponent {
617         Key key;
618         const(Component) value;
619     }
620 
621     /// Returns: Flat array of all relations of type FROM-KIND-TO-COUNT.
622     KeyComponent[] asArray() const pure nothrow @trusted {
623         import std.array : array;
624         import std.algorithm : map;
625 
626         //TODO how to do this without so much generated GC
627 
628         // dfmt off
629         return components.byKeyValue
630             .map!(a => KeyComponent(a.key, a.value))
631             .array();
632         // dfmt on
633     }
634 
635     /// Returns: An array of the key/values sorted on key.
636     auto nameSortedRange() const pure @trusted {
637         static string sortComponentNameBy(T)(ref T a) {
638             return cast(string) a.value.displayName;
639         }
640 
641         return .nameSortedRange!(typeof(this), sortComponentNameBy)(this);
642     }
643 
644     private string[] componentsToStringArray() const pure @trusted {
645         import std.algorithm : map, joiner;
646         import std.ascii : newline;
647         import std.array : array;
648         import std.format : format;
649         import std.typecons : tuple;
650 
651         // dfmt off
652         return nameSortedRange
653             .map!(a => tuple(a.key, a.value.displayName, a.value.contains[].map!(a => newline ~ "  " ~ cast(string) a).joiner))
654             .map!(a => format("%s as %s%s", a[0],
655                 a[1],
656                 a[2])).array();
657         // dfmt on
658     }
659 
660     private string[] relateToStringArray() const pure @trusted {
661         import std.algorithm : map, joiner;
662         import std.array : array;
663 
664         return relate_to.byKeyValue.map!(a => a.value.toStringArray(a.key)).joiner().array();
665     }
666 
667     /// String representation of the Component Diagram.
668     void toString(Writer)(scope Writer w) @safe const {
669         import std.ascii : newline;
670         import std.format : formattedWrite;
671         import std.range.primitives : put;
672         import std.range : zip, repeat;
673 
674         formattedWrite(w, "UML Component Diagram (Total %d) {", components.length);
675         put(w, newline);
676         foreach (a; zip(componentsToStringArray, repeat(newline))) {
677             put(w, a[0]);
678             put(w, a[1]);
679         }
680         foreach (a; zip(relateToStringArray, repeat(newline))) {
681             put(w, a[0]);
682             put(w, a[1]);
683         }
684         put(w, "} // UML Component Diagram");
685     }
686 
687     ///
688     override string toString() @safe const {
689         import std.exception : assumeUnique;
690 
691         char[] buf;
692         buf.reserve(100);
693         this.toString((const(char)[] s) { buf ~= s; });
694         auto trustedUnique(T)(T t) @trusted {
695             return assumeUnique(t);
696         }
697 
698         return trustedUnique(buf);
699     }
700 
701 private:
702     Relate[Relate.Key] relate_to;
703     Component[Key] components;
704 }
705 
706 @Name("Should be a None relate not shown and an extended relate")
707 unittest {
708     Relate r;
709     r.put(Relate.Key("B"), Relate.Kind.None);
710     r.put(Relate.Key("B"), Relate.Kind.Extend);
711 
712     r.toStringArray(Relate.Key("A")).shouldEqual(["A -Extend- [1]B"]);
713 }
714 
715 @Name("Should be all types of relates")
716 unittest {
717     Relate r;
718     r.put(Relate.Key("B"), Relate.Kind.None);
719     r.put(Relate.Key("B"), Relate.Kind.Extend);
720     r.put(Relate.Key("B"), Relate.Kind.Compose);
721     r.put(Relate.Key("B"), Relate.Kind.Aggregate);
722     r.put(Relate.Key("B"), Relate.Kind.Associate);
723 
724     r.toStringArray(Relate.Key("A")).shouldEqual([
725             "A -Extend- [1]B", "A -Compose- [1]B", "A -Aggregate- [1]B",
726             "A -Associate- [1]B"
727             ]);
728 }
729 
730 @Name("Should be two relates to the same target")
731 unittest {
732     Relate r;
733     r.put(Relate.Key("B"), Relate.Kind.Compose);
734     r.put(Relate.Key("B"), Relate.Kind.Compose);
735 
736     r.toStringArray(Relate.Key("A")).shouldEqual(["A -Compose- [2]B"]);
737 }
738 
739 @Name("Should be a UML diagram with one class")
740 unittest {
741     auto uml = new UMLClassDiagram;
742     uml.put(UMLClassDiagram.Key("A"), UMLClassDiagram.DisplayName("A"));
743 
744     uml.toString.shouldEqual("UML Class Diagram (Total 1) {
745 A -> A
746 } // UML Class Diagram");
747 }
748 
749 @Name("Should be a UML diagram with two classes related")
750 unittest {
751     auto uml = new UMLClassDiagram;
752     auto ka = UMLClassDiagram.Key("A");
753     auto kb = UMLClassDiagram.Key("B");
754     uml.put(ka, UMLClassDiagram.DisplayName("A"));
755     uml.put(kb, UMLClassDiagram.DisplayName("B"));
756 
757     uml.relate(ka, kb, UMLClassDiagram.DisplayName("B"), Relate.Kind.Extend);
758 
759     uml.toString.shouldEqual("UML Class Diagram (Total 2) {
760 A -> A
761 B -> B
762 A -Extend- [1]B
763 } // UML Class Diagram");
764 }
765 
766 @Name("Should be a UML Component diagram with two components related")
767 unittest {
768     auto uml = new UMLComponentDiagram;
769     auto ka = UMLComponentDiagram.Key("a");
770     auto kb = UMLComponentDiagram.Key("b");
771     uml.put(ka, cast(UMLComponentDiagram.DisplayName) "A");
772     // shall be dedupliated
773     uml.put(ka, cast(UMLComponentDiagram.Location) "file.h");
774     uml.put(ka, cast(UMLComponentDiagram.Location) "file.h");
775 
776     uml.relate(ka, kb, cast(UMLComponentDiagram.DisplayName) "B", Relate.Kind.Relate);
777 
778     uml.toString.shouldEqual("UML Component Diagram (Total 2) {
779 a as A
780   file.h
781 b as B
782 a -Relate- [1]b
783 } // UML Component Diagram");
784 }
785 
786 /** Context for the UML diagram generator from internal representation to the
787  * concrete files.
788  */
789 struct Generator {
790     import cpptooling.data : CppRoot;
791     import cpptooling.data.symbol : Container;
792 
793     private static struct Modules {
794         private static void postInit(ref typeof(this) m) {
795             m.classes_dot.suppressIndent(1);
796             m.components_dot.suppressIndent(1);
797         }
798 
799         import dextool.plugin.utility : MakerInitializingClassMembers;
800 
801         mixin MakerInitializingClassMembers!(Modules, postInit);
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                 FileName 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, FileName 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, FileName 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 = FileName(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 FileName makeDotFileName(FileName 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 FileName((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 cpptooling.analyzer.clang.ast : ClassDecl, CxxBaseSpecifier, Constructor,
1032         Destructor, CxxMethod, FieldDecl, CxxAccessSpecifier, generateIndentIncrDecr;
1033     import cpptooling.analyzer.clang.analyze_helper : analyzeRecord, analyzeConstructor, analyzeDestructor,
1034         analyzeCxxMethod, analyzeFieldDecl, analyzeCxxBaseSpecified, toAccessType;
1035     import cpptooling.analyzer.clang.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, const(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 cpptooling.analyzer.clang.ast : TranslationUnit, UnexposedDecl,
1203         VarDecl, FunctionDecl, ClassDecl, Namespace, generateIndentIncrDecr;
1204     import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl,
1205         analyzeVarDecl, analyzeRecord, analyzeTranslationUnit;
1206     import cpptooling.analyzer.clang.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 const(TypeKindAttr) src, ref const(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 const(TypeKindAttr) src, ref const(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 const(TypeKindAttr) src, ref const(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 const(TypeKindAttr) src, ref const(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 const(TypeKindAttr) src, ref const(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 const(ClassClassificationResult) result) {
1462         auto key = makeClassKey(result.type.kind.usr);
1463         uml.set(key, result.classification);
1464     }
1465 
1466     void put(ref const(RecordResult) src, const(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) @safe
1579             if (is(ElementType!Range == TypeKindAttr)
1580                 || is(ElementType!Range == const(TypeKindAttr))) {
1581         import std.algorithm : filter;
1582 
1583         // dfmt off
1584         foreach(a; range
1585             // remove primitive types
1586             .filter!(a => a.kind.info.kind != TypeKind.Info.Kind.primitive)
1587             .map!(a => resolveCanonicalType(a.kind, a.attr, lookup))
1588             .joiner
1589             .map!(a => a.kind.usr)
1590             // create the relations of type src-to-kind
1591             .map!(to_ => USRRelation(src, to_, Relate.Kind.Associate))) {
1592             target.put(a);
1593         }
1594         // dfmt on
1595     }
1596 
1597     /// ditto
1598     static void putParamsToCache(T)(ref const(TypeKindAttr) src,
1599             const(CxParam)[] params, ref T target, LookupT lookup) @safe {
1600         // dfmt off
1601         auto range = params
1602             // returns a bunch of ranges of the unpacked parameters
1603             .map!(a => unpackParam(a))
1604             .joiner;
1605         // dfmt on
1606 
1607         putToCache(src.kind.usr, range, target, lookup);
1608     }
1609 
1610     static void finalizeSrcCache(LookupT, TargetT)(USRType[] cache, LookupT lookup, TargetT target) {
1611         import std.algorithm : map, joiner;
1612 
1613         // dfmt off
1614         foreach (loc; cache
1615                  .map!(usr => lookup.location(usr))
1616                  .joiner
1617                  .map!(a => a.any)
1618                  .joiner) {
1619             target.putSrc(loc);
1620         }
1621         // dfmt on
1622     }
1623 
1624     /// Process the last bits left in the cache.
1625     void finalize() {
1626         import std.algorithm : map, filter, cache;
1627         import std.range : enumerate, only;
1628         import std.typecons : tuple;
1629 
1630         finalizeSrcCache(src_cache[], lookup, this);
1631         if (src_cache.length > 0) {
1632             logger.tracef("%d relations left in src cache", src_cache.length);
1633         }
1634         src_cache.length = 0;
1635 
1636         if (dcache.data.length > 0) {
1637             logger.tracef("%d relations left. Activating fallback strategy", dcache.data.length);
1638         }
1639 
1640         // dfmt off
1641         foreach (e; dcache.data
1642                  // keep track of the index to allow marking of the cache for removal
1643                  .enumerate
1644                  // find the types
1645                  .map!(a => tuple(a.index, lookup.location(a.value.from), lookup.location(a.value.to)))
1646                  .cache
1647                  // a zero range means a failed lookup, a broken relation
1648                  .filter!(a => a[1].length != 0 && a[2].length != 0)
1649                  // unpack with fallback
1650                  .map!(a => tuple(a[0], a[1].front.any, a[2].front.any))
1651                  // ensure that both both resulted in valid ranges
1652                  .filter!(a => a[1].length != 0 && a[2].length != 0)
1653                  // unpack
1654                  .map!(a => tuple(a[0], a[1].front, a[2].front))
1655                  // check via ctrl (the user) if the destination is "ok"
1656                  .filter!(a => ctrl.doFile(cast(string) a[2].file, cast(string) a[2].file))
1657                  ) {
1658             //TODO warn when a declaration has been used?
1659 
1660             putDest(e[1], e[2], Relate.Kind.Associate);
1661             dcache.markForRemoval(e[0]);
1662         }
1663         // dfmt on
1664 
1665         dcache.doRemoval;
1666 
1667         if (dcache.data.length > 0) {
1668             logger.errorf("Fallback strategy failed for %d USRs. They are:", dcache.data.length);
1669         }
1670 
1671         foreach (e; dcache.data) {
1672             logger.tracef("  %s -> %s", cast(string) e.from, cast(string) e.to);
1673         }
1674     }
1675 
1676     void put(ref const(TranslationUnitResult) result) {
1677         import std.algorithm : map, filter, cache;
1678         import std.range : enumerate, only;
1679         import std.typecons : tuple;
1680 
1681         finalizeSrcCache(src_cache[], lookup, this);
1682         if (src_cache.length > 0) {
1683             logger.tracef("%d relations left in src cache", src_cache.length);
1684         }
1685         src_cache.length = 0;
1686 
1687         // dfmt off
1688         foreach (e; dcache.data
1689                  // keep track of the index to allow marking of the cache for removal
1690                  .enumerate
1691                  // find the types
1692                  .map!(a => tuple(a.index, lookup.location(a.value.from), lookup.location(a.value.to)))
1693                  .cache
1694                  // a zero range means a failed lookup, a broken relation
1695                  .filter!(a => a[1].length != 0 && a[2].length != 0)
1696                  // unpack
1697                  .map!(a => tuple(a[0], a[1].front, a[2].front))
1698                  // only okey with a relatioin TO something that is a definition
1699                  .filter!(a => a[1].hasDefinition && a[2].hasDefinition)
1700                  // check via ctrl (the user) if the destination is "ok"
1701                  .filter!(a => ctrl.doFile(cast(string) a[2].definition.file, cast(string) a[2].definition.file))
1702                  ) {
1703             putDest(e[1].definition, e[2].definition, Relate.Kind.Associate);
1704             dcache.markForRemoval(e[0]);
1705         }
1706         // dfmt on
1707 
1708         dcache.doRemoval;
1709     }
1710 
1711     void put(ref const(RecordResult) result) {
1712         src_cache ~= result.type.kind.usr;
1713     }
1714 
1715     void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) {
1716         putParamsToCache(src, result.params, dcache, lookup);
1717     }
1718 
1719     void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) {
1720         import std.range : only;
1721 
1722         putParamsToCache(src, result.params, dcache, lookup);
1723         putToCache(src.kind.usr, only((cast(const TypeKindAttr) result.returnType)), dcache, lookup);
1724     }
1725 
1726     void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) {
1727         import std.range : only;
1728 
1729         putToCache(src.kind.usr, only(result.type), dcache, lookup);
1730     }
1731 
1732     void put(ref const(TypeKindAttr) src, ref const(ClassClassificationResult) result) {
1733         import std.range : only;
1734 
1735         // called when creating a relation for a nested class
1736         putToCache(src.kind.usr, only(result.type), dcache, lookup);
1737     }
1738 
1739     void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) {
1740         auto r0 = lookup.kind(result.canonicalUSR).map!(a => TypeKindAttr(a.get, TypeAttr.init));
1741 
1742         putToCache(src.kind.usr, r0, dcache, lookup);
1743     }
1744 
1745     void put(ref const(VarDeclResult) result) {
1746         import std.range : only;
1747 
1748         // primitive types do not have a location
1749         if (result.location.kind == LocationTag.Kind.loc) {
1750             putSrc(result.location);
1751 
1752             putToCache(result.instanceUSR, only(result.type), dcache, lookup);
1753         }
1754     }
1755 
1756     void put(ref const(FunctionDeclResult) result) {
1757         import std.range : only;
1758 
1759         src_cache ~= result.type.kind.usr;
1760 
1761         putParamsToCache(result.type, result.params, dcache, lookup);
1762         putToCache(result.type.kind.usr,
1763                 only(cast(const TypeKindAttr) result.returnType), dcache, lookup);
1764     }
1765 
1766     void putSrc(ref const(LocationTag) src) @safe {
1767         string location = src.file;
1768 
1769         if (src.kind == LocationTag.Kind.noloc || !ctrl.doFile(location, location)) {
1770             return;
1771         }
1772 
1773         auto key = makeComponentKey(location, ctrl);
1774         diagram.put(key.key, cast(UMLComponentDiagram.DisplayName) key.display);
1775         diagram.put(key.key, cast(UMLComponentDiagram.Location) location);
1776     }
1777 
1778     void putDest(ref const(LocationTag) src, ref const(LocationTag) dest, Relate.Kind kind) {
1779         auto src_ = makeComponentKey(src.file, ctrl);
1780         auto dest_ = makeComponentKey(dest.file, ctrl);
1781 
1782         // Ignoring self referencing relations.
1783         if (src_.key == dest_.key) {
1784             return;
1785         }
1786 
1787         diagram.relate(src_.key, dest_.key,
1788                 cast(UMLComponentDiagram.DisplayName) dest_.display, kind);
1789     }
1790 }
1791 
1792 /** Route information to specific transformers.
1793  *
1794  * No manipulation of data is to be done in this struct. Only routing to
1795  * appropriate functions.
1796  */
1797 class TransformToDiagram(ControllerT, ParametersT, LookupT) {
1798     import std.range : only;
1799 
1800     import cpptooling.analyzer.clang.analyze_helper : CxxBaseSpecifierResult,
1801         RecordResult, FieldDeclResult, CxxMethodResult,
1802         ConstructorResult, DestructorResult, VarDeclResult, FunctionDeclResult,
1803         TranslationUnitResult;
1804     import cpptooling.data.symbol.types : USRType;
1805     import cpptooling.data : TypeKind, CppNs, CppAccess;
1806 
1807     private {
1808         TransformToComponentDiagram!(ControllerT, LookupT) to_component;
1809         TransformToClassDiagram!(ControllerT, LookupT) to_class;
1810     }
1811 
1812     this(ControllerT ctrl, ParametersT params, LookupT lookup,
1813             UMLComponentDiagram comp_dia, UMLClassDiagram class_dia) {
1814         to_component = typeof(to_component)(comp_dia, ctrl, lookup);
1815         to_class = typeof(to_class)(class_dia, ctrl, lookup, params.genClassMethod,
1816                 params.genClassParamDependency, params.genClassInheritDependency,
1817                 params.genClassMemberDependency);
1818     }
1819 
1820 @safe:
1821 
1822     /** Signal that diagrams to perform a finalization of cached data.
1823      */
1824     void finalize() {
1825         to_component.finalize();
1826     }
1827 
1828     void put(ref const(TranslationUnitResult) result) {
1829         to_component.put(result);
1830     }
1831 
1832     void put(ref const(RecordResult) result, const(CppNs)[] reside_in) {
1833         to_class.put(result, reside_in);
1834         to_component.put(result);
1835     }
1836 
1837     void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) {
1838         to_class.put(src, result);
1839         to_component.put(src, result);
1840     }
1841 
1842     void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) {
1843         to_class.put(src, result, access);
1844         to_component.put(src, result, access);
1845     }
1846 
1847     void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) {
1848         to_class.put(src, result, access);
1849         to_component.put(src, result, access);
1850     }
1851 
1852     void put(ref const(TypeKindAttr) src, ref const(DestructorResult) result, in CppAccess access) {
1853         to_class.put(src, result, access);
1854     }
1855 
1856     void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) {
1857         to_class.put(src, result, access);
1858         to_component.put(src, result, access);
1859     }
1860 
1861     void put(ref const(ClassClassificationResult) result) {
1862         to_class.put(result);
1863     }
1864 
1865     /** A nested class.
1866      *
1867      * Propagate the classification and relation of the root->nested.
1868      */
1869     void put(ref const(TypeKindAttr) src, ref const(ClassClassificationResult) result) {
1870         to_component.put(src, result);
1871         // only needs result
1872         to_class.put(result);
1873     }
1874 
1875     void put(ref const(VarDeclResult) result) {
1876         to_component.put(result);
1877     }
1878 
1879     void put(ref const(FunctionDeclResult) result) {
1880         to_component.put(result);
1881     }
1882 }
1883 
1884 // visualize where the module private starts
1885 private: // ******************************************************************
1886 
1887 import cpptooling.data.representation : CppRoot, CppClass, CppMethod, CppCtor,
1888     CppDtor, CppNamespace, CFunction, CxGlobalVariable, LocationTag, Location;
1889 import cpptooling.data.symbol : Container;
1890 import dsrcgen.plantuml;
1891 
1892 struct KeyValue {
1893     UMLComponentDiagram.Key key;
1894     string display;
1895     string absFilePath;
1896 }
1897 
1898 struct KeyRelate {
1899     string file;
1900     KeyValue key;
1901     Relate.Kind kind;
1902 }
1903 
1904 /**
1905  * Params:
1906  *  file = filename of the relation.
1907  *  kind = kind of relation such as associaiton, composition etc.
1908  */
1909 struct PathKind {
1910     string file;
1911     Relate.Kind kind;
1912 }
1913 
1914 /** Calculate the key based on the directory the file that declares the symbol exist in.
1915  *
1916  * Additional metadata as to make it possible to backtrack.
1917  */
1918 KeyValue makeComponentKey(in string location_file, Controller ctrl) @trusted {
1919     import std.array : appender;
1920     import std.base64 : Base64Impl, Base64;
1921     import std.path : buildNormalizedPath, absolutePath, relativePath, baseName;
1922     import std.typecons : tuple;
1923 
1924     // TODO consider using hash murmur2/3 to shorten the length of the encoded
1925     // path
1926 
1927     alias SafeBase64 = Base64Impl!('-', '_', Base64.NoPadding);
1928 
1929     string file_path = buildNormalizedPath(location_file.absolutePath);
1930     string strip_path = cast(string) ctrl.doComponentNameStrip(FileName(file_path));
1931     string rel_path = relativePath(strip_path);
1932     string display_name = strip_path.baseName;
1933 
1934     auto enc = appender!(char[])();
1935     SafeBase64.encode(cast(ubyte[]) rel_path, enc);
1936 
1937     auto k = KeyValue(UMLComponentDiagram.Key(enc.data.idup), display_name, strip_path);
1938 
1939     debug {
1940         logger.tracef("Component:%s stripped:%s file:%s base64:%s", k.display,
1941                 strip_path, file_path, cast(string) k.key);
1942     }
1943 
1944     return k;
1945 }
1946 
1947 UMLClassDiagram.Key makeClassKey(in USRType key) @trusted {
1948     import std.base64 : Base64Impl, Base64;
1949     import std.array : appender;
1950 
1951     // TODO consider using hash murmur2/3 function to shorten the length of the
1952     // encoded path
1953 
1954     alias SafeBase64 = Base64Impl!('-', '_', Base64.NoPadding);
1955 
1956     auto enc = appender!(char[])();
1957     SafeBase64.encode(cast(ubyte[])(cast(string) key), enc);
1958 
1959     auto k = UMLClassDiagram.Key(enc.data.idup);
1960     return k;
1961 }
1962 
1963 private auto unpackParam(CxParam p) @trusted {
1964     import std.range : only, dropOne;
1965     import std.variant : visit;
1966     import cpptooling.data : TypeKindVariable, VariadicType;
1967 
1968     // dfmt off
1969     return p.visit!(
1970                     (TypeKindVariable v) => only(v.type),
1971                     (TypeKindAttr v) => only(v),
1972                     (VariadicType v) {
1973                         logger.error(
1974                                      "Variadic function not supported. Would require runtime information to relate.");
1975                         return only(TypeKindAttr.init).dropOne;
1976                     });
1977     // dfmt on
1978 }
1979 
1980 struct ClassRelate {
1981     Relate.Kind kind;
1982     Relate.Key key;
1983     UMLClassDiagram.DisplayName display;
1984 }
1985 
1986 auto getClassMemberRelation(LookupT)(TypeKindAttr type, LookupT lookup) {
1987     //TODO code duplication with getMethodRelation
1988     // .. fix it. This function is ugly.
1989     import std.algorithm : each, map, filter, joiner;
1990     import std.array : array;
1991     import std.typecons : tuple;
1992 
1993     // TODO this is a mega include. Reduce it.
1994     import cpptooling.data;
1995 
1996     auto r = ClassRelate(Relate.Kind.None, Relate.Key(""), UMLClassDiagram.DisplayName(""));
1997 
1998     final switch (type.kind.info.kind) with (TypeKind.Info) {
1999     case Kind.typeRef:
2000         auto tref = lookup.kind(type.kind.info.canonicalRef);
2001         foreach (t; tref.filter!(a => a.info.kind == Kind.record)) {
2002             auto rel_type = Relate.Kind.Aggregate;
2003             if (type.attr.isPtr || type.attr.isRef) {
2004                 rel_type = Relate.Kind.Compose;
2005             }
2006             r = ClassRelate(rel_type, t.usr,
2007                     cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init));
2008         }
2009         break;
2010     case Kind.record:
2011         r = ClassRelate(Relate.Kind.Aggregate, type.kind.usr,
2012                 cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init));
2013         break;
2014     case Kind.array:
2015         auto element = lookup.kind(type.kind.info.element);
2016         foreach (e; element.filter!(a => a.info.kind == Kind.record)) {
2017             auto rel_type = Relate.Kind.Aggregate;
2018             if (type.attr.isPtr || type.attr.isRef) {
2019                 rel_type = Relate.Kind.Compose;
2020             }
2021             r = ClassRelate(rel_type, e.usr,
2022                     cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init));
2023         }
2024         break;
2025     case Kind.pointer:
2026         auto pointee = lookup.kind(type.kind.info.pointee);
2027         foreach (p; pointee.filter!(a => a.info.kind == Kind.record)) {
2028             string display = p.toStringDecl(TypeAttr.init);
2029             r = ClassRelate(Relate.Kind.Compose, p.usr, cast(UMLClassDiagram.DisplayName) display);
2030         }
2031         break;
2032     case Kind.primitive:
2033     case Kind.simple:
2034     case Kind.func:
2035     case Kind.funcPtr:
2036     case Kind.funcSignature:
2037     case Kind.ctor:
2038     case Kind.dtor:
2039     case Kind.null_:
2040         break;
2041     }
2042 
2043     return r;
2044 }
2045 
2046 private ClassRelate getTypeRelation(LookupT)(TypeKindAttr tk, LookupT lookup) {
2047     import std.algorithm : filter;
2048     import cpptooling.data : TypeKind, TypeAttr, toStringDecl;
2049 
2050     auto r = ClassRelate(Relate.Kind.None, Relate.Key(""), UMLClassDiagram.DisplayName(""));
2051 
2052     final switch (tk.kind.info.kind) with (TypeKind.Info) {
2053     case Kind.typeRef:
2054         auto tref = lookup.kind(tk.kind.info.canonicalRef);
2055         foreach (t; tref.filter!(a => a.info.kind == Kind.record)) {
2056             r = ClassRelate(Relate.Kind.Associate, Relate.Key(t.usr),
2057                     cast(UMLClassDiagram.DisplayName) t.toStringDecl(TypeAttr.init));
2058         }
2059         break;
2060     case Kind.record:
2061         r = ClassRelate(Relate.Kind.Associate, tk.kind.usr,
2062                 cast(UMLClassDiagram.DisplayName) tk.kind.toStringDecl(TypeAttr.init));
2063         break;
2064     case Kind.array:
2065         auto element = lookup.kind(tk.kind.info.element);
2066         foreach (e; element.filter!(a => a.info.kind == Kind.record)) {
2067             r = ClassRelate(Relate.Kind.Associate, e.usr,
2068                     cast(UMLClassDiagram.DisplayName) e.toStringDecl(TypeAttr.init));
2069         }
2070         break;
2071     case Kind.pointer:
2072         auto pointee = lookup.kind(tk.kind.info.pointee);
2073         foreach (p; pointee.filter!(a => a.info.kind == Kind.record)) {
2074             string display = p.toStringDecl(TypeAttr.init);
2075             r = ClassRelate(Relate.Kind.Associate, Relate.Key(p.usr),
2076                     cast(UMLClassDiagram.DisplayName) display);
2077         }
2078         break;
2079     case Kind.primitive:
2080     case Kind.simple:
2081     case Kind.func:
2082     case Kind.funcPtr:
2083     case Kind.funcSignature:
2084     case Kind.ctor:
2085     case Kind.dtor:
2086     case Kind.null_:
2087     }
2088 
2089     return r;
2090 }
2091 
2092 private auto getClassMethodRelation(LookupT)(const(CxParam)[] params, LookupT lookup) {
2093     import std.array : array;
2094     import std.algorithm : among, map, filter;
2095     import std.variant : visit;
2096     import cpptooling.data : TypeKind, TypeAttr, TypeKindAttr, toStringDecl, VariadicType;
2097 
2098     static ClassRelate genParam(CxParam p, LookupT lookup) @trusted {
2099         // dfmt off
2100         return p.visit!(
2101             (TypeKindVariable tkv) => getTypeRelation(tkv.type, lookup),
2102             (TypeKindAttr tk) => getTypeRelation(tk, lookup),
2103             (VariadicType vk)
2104                 {
2105                     logger.error("Variadic function not supported.");
2106                     // Because what types is either discovered first at runtime
2107                     // or would require deeper inspection of the implementation
2108                     // where the variadic is used.
2109                     return ClassRelate.init;
2110                 }
2111             );
2112         // dfmt on
2113     }
2114 
2115     // dfmt off
2116     return params.map!(a => genParam(a, lookup)).array();
2117     // dfmt on
2118 }
2119 
2120 void generate(UMLClassDiagram uml_class, UMLComponentDiagram uml_comp,
2121         Flag!"doGenDot" doGenDot, Generator.Modules modules) @safe {
2122     import std.algorithm : each;
2123     import std.format : format;
2124     import std.range : enumerate;
2125 
2126     // TODO code duplicaton with class and component.
2127     // Generalize, reduce.
2128 
2129     auto classes_preamble = modules.classes.base;
2130     classes_preamble.suppressIndent(1);
2131     foreach (idx, kv; uml_class.fanOutSorted.enumerate) {
2132         generate(kv.key, kv.value, classes_preamble);
2133         generateClassRelate(uml_class.relateTo(kv.key)
2134                 .toFlatArray(cast(Relate.Key) kv.key), modules.classes);
2135         if (doGenDot) {
2136             auto nodes = modules.classes_dot.base;
2137             nodes.suppressIndent(1);
2138             nodes.stmt(format(`"%s" [label="%s"]`, kv.key, kv.value.displayName));
2139 
2140             // make a range of all relations from THIS to other components
2141             auto r = uml_class.relateTo(kv.key).toRange(cast(Relate.Key) kv.key);
2142 
2143             generateDotRelate(r, idx, modules.classes_dot);
2144         }
2145     }
2146 
2147     foreach (idx, kv; uml_comp.fanOutSorted.enumerate) {
2148         generate(kv.key, kv.value, modules.components);
2149         if (doGenDot) {
2150             auto nodes = modules.components_dot.base;
2151             nodes.suppressIndent(1);
2152             nodes.stmt(format(`"%s" [label="%s"]`, kv.key, kv.value.displayName));
2153 
2154             // make a range of all relations from THIS to other components
2155             auto r = uml_comp.relateTo(kv.key).toRange(cast(Relate.Key) kv.key);
2156 
2157             generateDotRelate(r, idx, modules.components_dot);
2158         }
2159     }
2160     generateComponentRelate(uml_comp.relateToFlatArray, modules.components);
2161 }
2162 
2163 /** Generate PlantUML class and relations from the class.
2164  *
2165  * By generating the relations out of the class directly after the class
2166  * definitions it makes it easier for GraphViz to generate a not-so-muddy
2167  * image.
2168  */
2169 private void generate(UMLClassDiagram.Key key, const UMLClassDiagram.Class c, PlantumlModule m) @safe {
2170     import std.algorithm : each;
2171     import dsrcgen.plantuml : addSpot;
2172 
2173     ClassType pc;
2174 
2175     if (c.content.length == 0) {
2176         pc = m.class_(cast(string) c.displayName);
2177     } else {
2178         pc = m.classBody(cast(string) c.displayName);
2179         c.content.each!(a => pc.method(a));
2180     }
2181     pc.addAs.text(cast(string) key);
2182 
2183     //TODO add a plantuml macro and use that as color for interface
2184     // Allows the user to control the color via the PREFIX_style.iuml
2185     switch (c.classification) with (cpptooling.data.class_classification.State) {
2186     case Abstract:
2187         pc.addSpot("<< (A, Pink) >>");
2188         break;
2189     case VirtualDtor:
2190     case Pure:
2191         pc.addSpot("<< (I, LightBlue) >>");
2192         break;
2193     default:
2194         break;
2195     }
2196 }
2197 
2198 private void generateClassRelate(T)(T relate_range, PlantumlModule m) @safe {
2199     static auto convKind(Relate.Kind kind) {
2200         static import dsrcgen.plantuml;
2201 
2202         final switch (kind) with (Relate.Kind) {
2203         case None:
2204             assert(0);
2205         case Extend:
2206             return dsrcgen.plantuml.Relate.Extend;
2207         case Compose:
2208             return dsrcgen.plantuml.Relate.Compose;
2209         case Aggregate:
2210             return dsrcgen.plantuml.Relate.Aggregate;
2211         case Associate:
2212             return dsrcgen.plantuml.Relate.ArrowTo;
2213         case Relate:
2214             return dsrcgen.plantuml.Relate.Relate;
2215         }
2216     }
2217 
2218     foreach (r; relate_range) {
2219         m.relate(cast(ClassNameType) r.from, cast(ClassNameType) r.to, convKind(r.kind));
2220     }
2221 }
2222 
2223 private void generateDotRelate(T)(T relate_range, ulong color_idx, PlantumlModule m) @safe {
2224     import std.format : format;
2225 
2226     static import dsrcgen.plantuml;
2227 
2228     static string getColor(ulong idx) {
2229         static string[] colors = [
2230             "red", "mediumpurple", "darkorange", "deeppink", "green", "coral",
2231             "orangered", "plum", "deepskyblue", "slategray", "cadetblue",
2232             "olive", "silver", "indianred", "black"
2233         ];
2234         return colors[idx % colors.length];
2235     }
2236 
2237     if (relate_range.length > 0) {
2238         m.stmt(format("edge [color=%s]", getColor(color_idx)));
2239     }
2240 
2241     foreach (r; relate_range) {
2242         auto l = m.relate(cast(ClassNameType) r.from, cast(ClassNameType) r.to,
2243                 dsrcgen.plantuml.Relate.DotArrowTo);
2244         //TODO this is ugly, fix dsrcgen relate to support graphviz/DOT
2245         auto w = new dsrcgen.plantuml.Text!PlantumlModule(format("[weight=%d] ", r.count));
2246         l.block.prepend(w);
2247     }
2248 }
2249 
2250 private void generate(UMLComponentDiagram.Key key,
2251         const UMLComponentDiagram.Component component, PlantumlModule m) @safe {
2252     import std.algorithm : map;
2253     import std.conv : text;
2254     import std.path : buildNormalizedPath, relativePath;
2255 
2256     auto comp = m.classBody(cast(string) component.displayName);
2257     comp.addAs.text(cast(string) key);
2258 
2259     // early exit because the slice of contains segfaults otherwise.
2260     if (component.contains.length == 0)
2261         return;
2262 
2263     // dfmt off
2264     foreach (fname; component.contains[]
2265         .map!(a => cast(string) a)
2266         .map!(a => () @trusted { return buildNormalizedPath(a).relativePath; }())) {
2267         comp.m.stmt(text(fname));
2268     }
2269     // dfmt on
2270 }
2271 
2272 private void generateComponentRelate(T)(T relate_range, PlantumlModule m) @safe {
2273     static auto convKind(Relate.Kind kind) {
2274         static import dsrcgen.plantuml;
2275 
2276         final switch (kind) with (Relate.Kind) {
2277         case Relate:
2278             return dsrcgen.plantuml.Relate.Relate;
2279         case Extend:
2280             assert(0);
2281         case Compose:
2282             assert(0);
2283         case Aggregate:
2284             assert(0);
2285         case Associate:
2286             return dsrcgen.plantuml.Relate.ArrowTo;
2287         case None:
2288             assert(0);
2289         }
2290     }
2291 
2292     foreach (r; relate_range) {
2293         m.relate(cast(ComponentNameType) r.from, cast(ComponentNameType) r.to, convKind(r.kind));
2294     }
2295 }