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 module dextool.plugin.graphml.backend.base;
11 
12 import std.format : FormatSpec;
13 import std.range : isOutputRange;
14 import std.traits : isSomeString, Unqual;
15 import std.typecons : scoped, Nullable, Flag, Yes;
16 import logger = std.experimental.logger;
17 
18 import cpptooling.analyzer.clang.analyze_helper : VarDeclResult, FieldDeclResult;
19 import cpptooling.analyzer.clang.ast : Visitor;
20 import cpptooling.data : resolveCanonicalType, resolvePointeeType, TypeKindAttr, TypeKind,
21     TypeAttr, toStringDecl, CppAccess, LocationTag, Location, USRType, AccessType;
22 import cpptooling.data.symbol : Container;
23 
24 import dextool.plugin.graphml.backend.xml;
25 import dextool.plugin.graphml.backend.interface_;
26 
27 version (unittest) {
28     import unit_threaded;
29     import std.array : appender;
30 
31     private struct DummyRecv {
32         import std.array : Appender;
33 
34         Appender!(string)* buf;
35 
36         void put(const(char)[] s) {
37             buf.put(s);
38         }
39     }
40 }
41 
42 static import cpptooling.data.class_classification;
43 
44 final class GraphMLAnalyzer(ReceiveT) : Visitor {
45     import cpptooling.analyzer.clang.ast : TranslationUnit, ClassDecl, VarDecl,
46         FunctionDecl, Namespace, UnexposedDecl, StructDecl,
47         CompoundStmt, Constructor, Destructor, CxxMethod, Declaration, ClassTemplate,
48         ClassTemplatePartialSpecialization, FunctionTemplate, UnionDecl;
49     import cpptooling.analyzer.clang.ast.visitor : generateIndentIncrDecr;
50     import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl,
51         analyzeVarDecl, analyzeRecord, analyzeTranslationUnit;
52     import cpptooling.data : CppRoot, CppNs, CFunction, CxReturnType, LocationTag, Location;
53     import cpptooling.data.symbol : Container;
54     import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
55 
56     alias visit = Visitor.visit;
57     mixin generateIndentIncrDecr;
58 
59     private {
60         ReceiveT recv;
61         Controller ctrl;
62         Parameters params;
63         Products prod;
64         Container* container;
65 
66         CppNs[] scope_stack;
67     }
68 
69     this(ReceiveT recv, Controller ctrl, Parameters params, Products prod, ref Container container) {
70         this.recv = recv;
71         this.ctrl = ctrl;
72         this.params = params;
73         this.prod = prod;
74         this.container = &container;
75     }
76 
77     void toString(Writer, Char)(scope Writer w, FormatSpec!Char formatSpec) const {
78         container.toString(w, formatSpec);
79     }
80 
81     override string toString() @safe const {
82         import std.exception : assumeUnique;
83 
84         char[] buf;
85         buf.reserve(100);
86         auto fmt = FormatSpec!char("%s");
87         toString((const(char)[] s) { buf ~= s; }, fmt);
88         auto trustedUnique(T)(T t) @trusted {
89             return assumeUnique(t);
90         }
91 
92         return trustedUnique(buf);
93     }
94 
95     override void visit(const(TranslationUnit) v) {
96         mixin(mixinNodeLog!());
97 
98         if (!ctrl.doFile(v.cursor.spelling))
99             return;
100 
101         auto result = analyzeTranslationUnit(v, *container, indent);
102         recv.put(result);
103 
104         v.accept(this);
105     }
106 
107     override void visit(const(Declaration) v) {
108         mixin(mixinNodeLog!());
109         import cpptooling.analyzer.clang.type : retrieveType;
110         import cpptooling.analyzer.clang.store : put;
111 
112         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
113             return;
114 
115         auto type = () @trusted {
116             return retrieveType(v.cursor, *container, indent);
117         }();
118         put(type, *container, indent);
119 
120         if (!type.isNull) {
121             recv.put(type.get.primary.type);
122         }
123 
124         v.accept(this);
125     }
126 
127     override void visit(const(UnexposedDecl) v) {
128         mixin(mixinNodeLog!());
129 
130         // An unexposed may be:
131 
132         // extern "C" void func_c_linkage();
133         // UnexposedDecl "" extern "C" {...
134         //   FunctionDecl "fun_c_linkage" void func_c_linkage
135         v.accept(this);
136     }
137 
138     override void visit(const(VarDecl) v) {
139         mixin(mixinNodeLog!());
140 
141         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
142             return;
143 
144         auto result = analyzeVarDecl(v, *container, indent);
145 
146         if (scope_stack.length == 0) {
147             recv.put(result);
148         } else {
149             recv.put(result, scope_stack);
150         }
151     }
152 
153     override void visit(const(FunctionDecl) v) {
154         mixin(mixinNodeLog!());
155 
156         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
157             return;
158 
159         auto result = analyzeFunctionDecl(v, *container, indent);
160         recv.put(result);
161         assert(result.isValid);
162 
163         () @trusted {
164             auto visitor = scoped!(BodyVisitor!(ReceiveT))(result.type, ctrl,
165                     recv, *container, indent + 1);
166             v.accept(visitor);
167         }();
168     }
169 
170     override void visit(const(Namespace) v) {
171         mixin(mixinNodeLog!());
172 
173         () @trusted { scope_stack ~= CppNs(v.cursor.spelling); }();
174         // pop the stack when done
175         scope (exit)
176             scope_stack = scope_stack[0 .. $ - 1];
177 
178         // fill the namespace with content from the analyse
179         v.accept(this);
180     }
181 
182     // === Class and Struct ===
183     override void visit(const(FunctionTemplate) v) {
184     }
185 
186     /** Implicit promise that THIS method will output the class node after the
187      * class has been classified.
188      */
189     override void visit(const(ClassDecl) v) {
190         mixin(mixinNodeLog!());
191 
192         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
193             return;
194 
195         auto result = analyzeRecord(v, *container, indent + 1);
196         auto node = visitRecord(v, result);
197         recv.put(result, scope_stack, node);
198     }
199 
200     override void visit(const(ClassTemplate) v) {
201         mixin(mixinNodeLog!());
202 
203         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
204             return;
205 
206         auto result = analyzeRecord(v, *container, indent + 1);
207         auto node = visitRecord(v, result);
208         recv.put(result, scope_stack, node);
209     }
210 
211     override void visit(const(ClassTemplatePartialSpecialization) v) {
212         mixin(mixinNodeLog!());
213 
214         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
215             return;
216 
217         auto result = analyzeRecord(v, *container, indent + 1);
218         auto node = visitRecord(v, result);
219         recv.put(result, scope_stack, node);
220     }
221 
222     override void visit(const(StructDecl) v) {
223         mixin(mixinNodeLog!());
224 
225         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
226             return;
227 
228         auto result = analyzeRecord(v, *container, indent + 1);
229         auto node = visitRecord(v, result);
230         recv.put(result, scope_stack, node);
231     }
232 
233     override void visit(const(UnionDecl) v) {
234         mixin(mixinNodeLog!());
235 
236         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
237             return;
238 
239         auto result = analyzeRecord(v, *container, indent + 1);
240         auto node = visitRecord(v, result);
241         recv.put(result, scope_stack, node);
242     }
243 
244     private auto visitRecord(T, ResultT)(const(T) v, ref ResultT result) @trusted {
245         import std.meta : staticIndexOf;
246         import cpptooling.data.type : AccessType;
247 
248         static if (staticIndexOf!(Unqual!T, ClassDecl, ClassTemplate,
249                 ClassTemplatePartialSpecialization) != -1) {
250             auto access_init = AccessType.Private;
251         } else {
252             auto access_init = AccessType.Public;
253         }
254 
255         auto visitor = scoped!(ClassVisitor!(ReceiveT))(result.type, scope_stack,
256                 access_init, result.name, ctrl, recv, *container, indent + 1);
257         v.accept(visitor);
258 
259         return visitor.node;
260     }
261 
262     // BEGIN. Needed for when the methods are defined outside of the class declaration.
263     // These functions may ONLY ever create relations. Never new nodes.
264     override void visit(const(Constructor) v) {
265         mixin(mixinNodeLog!());
266 
267         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
268             return;
269 
270         visitClassStructMethod(v);
271     }
272 
273     override void visit(const(Destructor) v) {
274         mixin(mixinNodeLog!());
275 
276         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
277             return;
278 
279         visitClassStructMethod(v);
280     }
281 
282     override void visit(const(CxxMethod) v) {
283         mixin(mixinNodeLog!());
284 
285         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
286             return;
287 
288         visitClassStructMethod(v);
289     }
290 
291     private auto visitClassStructMethod(T)(const(T) v) {
292         import std.algorithm : among;
293         import clang.c.Index : CXCursorKind;
294 
295         auto parent = v.cursor.semanticParent;
296 
297         // can't handle ClassTemplates etc yet
298         if (!parent.kind.among(CXCursorKind.classDecl, CXCursorKind.structDecl)) {
299             return;
300         }
301 
302         auto result = analyzeRecord(parent, *container, indent);
303 
304         () @trusted {
305             auto visitor = scoped!(BodyVisitor!(ReceiveT))(result.type, ctrl,
306                     recv, *container, indent + 1);
307             v.accept(visitor);
308         }();
309     }
310     // END.
311 }
312 
313 /**
314  *
315  * The $(D ClassVisitor) do not know when the analyze is finished.
316  * Therefore from the viewpoint of $(D ClassVisitor) classification is an
317  * ongoing process. It is the responsibility of the caller of $(D
318  * ClassVisitor) to use the final result of the classification together with
319  * the style.
320  */
321 private final class ClassVisitor(ReceiveT) : Visitor {
322     import std.algorithm : map, copy, each, joiner;
323     import std.array : Appender;
324     import std.conv : to;
325     import std.typecons : scoped, TypedefType, NullableRef;
326 
327     import cpptooling.analyzer.clang.ast : ClassDecl, ClassTemplate,
328         ClassTemplatePartialSpecialization, StructDecl, CxxBaseSpecifier,
329         Constructor, Destructor, CxxMethod, FieldDecl, CxxAccessSpecifier, TypedefDecl, UnionDecl;
330     import cpptooling.analyzer.clang.ast.visitor : generateIndentIncrDecr;
331     import cpptooling.analyzer.clang.analyze_helper : analyzeRecord, analyzeConstructor, analyzeDestructor,
332         analyzeCxxMethod, analyzeFieldDecl, analyzeCxxBaseSpecified, toAccessType;
333     import cpptooling.data : CppNsStack, CppNs, AccessType, CppAccess, CppDtor,
334         CppCtor, CppMethod, CppClassName, MemberVirtualType;
335     import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
336 
337     import cpptooling.data.class_classification : ClassificationState = State;
338     import cpptooling.data.class_classification : classifyClass, MethodKind;
339 
340     alias visit = Visitor.visit;
341 
342     mixin generateIndentIncrDecr;
343 
344     /** Type representation of this class.
345      * Used as the source of the outgoing relations from this class.
346      */
347     TypeKindAttr this_;
348 
349     NodeRecord node;
350 
351     private {
352         Controller ctrl;
353         NullableRef!ReceiveT recv;
354 
355         Container* container;
356         CppNsStack scope_stack;
357         CppAccess access;
358 
359         /// If the class has any members.
360         Flag!"hasMember" hasMember;
361 
362         /** Classification of the class.
363          * Affected by methods.
364          */
365         ClassificationState classification;
366     }
367 
368     this(ref const(TypeKindAttr) this_, const(CppNs)[] reside_in_ns, AccessType init_access,
369             CppClassName name, Controller ctrl, ref ReceiveT recv,
370             ref Container container, in uint indent) {
371         this.ctrl = ctrl;
372         this.recv = &recv;
373         this.container = &container;
374         this.indent = indent;
375         this.scope_stack = CppNsStack(reside_in_ns.dup);
376 
377         this.access = CppAccess(init_access);
378         this.classification = ClassificationState.Unknown;
379 
380         this.this_ = this_;
381 
382         node.usr = this_.kind.usr;
383         node.identifier = name;
384     }
385 
386     /**
387      * Has hidden data dependencies on:
388      *  - hasMember.
389      *  - current state of classification.
390      *
391      * Will update:
392      *  - the internal state classification
393      *  - the style stereotype
394      */
395     private void updateClassification(MethodKind kind, MemberVirtualType virtual_kind) {
396         this.classification = classifyClass(this.classification, kind,
397                 virtual_kind, this.hasMember);
398         this.node.stereotype = this.classification.toInternal!StereoType;
399     }
400 
401     /// Nested class definitions.
402     override void visit(const(ClassDecl) v) {
403         mixin(mixinNodeLog!());
404 
405         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
406             return;
407 
408         auto result = analyzeRecord(v, *container, indent + 1);
409 
410         auto node = visitRecord(v, result);
411         recv.put(result, scope_stack, node);
412     }
413 
414     override void visit(const(ClassTemplate) v) {
415         mixin(mixinNodeLog!());
416 
417         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
418             return;
419 
420         auto result = analyzeRecord(v, *container, indent + 1);
421 
422         auto node = visitRecord(v, result);
423         recv.put(result, scope_stack, node);
424     }
425 
426     override void visit(const(ClassTemplatePartialSpecialization) v) {
427         mixin(mixinNodeLog!());
428 
429         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
430             return;
431 
432         auto result = analyzeRecord(v, *container, indent + 1);
433 
434         auto node = visitRecord(v, result);
435         recv.put(result, scope_stack, node);
436     }
437 
438     override void visit(const(StructDecl) v) {
439         mixin(mixinNodeLog!());
440 
441         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
442             return;
443 
444         auto result = analyzeRecord(v, *container, indent + 1);
445 
446         auto node = visitRecord(v, result);
447         recv.put(result, scope_stack, node);
448     }
449 
450     override void visit(const(UnionDecl) v) {
451         mixin(mixinNodeLog!());
452 
453         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
454             return;
455 
456         auto result = analyzeRecord(v, *container, indent + 1);
457 
458         auto node = visitRecord(v, result);
459         recv.put(result, scope_stack, node);
460     }
461 
462     private auto visitRecord(T, ResultT)(const(T) v, ref ResultT result) @trusted {
463         import std.meta : staticIndexOf;
464         import cpptooling.data.type : AccessType;
465 
466         scope_stack ~= CppNs(cast(string) result.name);
467         scope (exit)
468             scope_stack = scope_stack[0 .. $ - 1];
469 
470         static if (staticIndexOf!(Unqual!T, ClassDecl, ClassTemplate,
471                 ClassTemplatePartialSpecialization) != -1) {
472             auto access_init = AccessType.Private;
473         } else {
474             auto access_init = AccessType.Public;
475         }
476 
477         auto visitor = scoped!(ClassVisitor!(ReceiveT))(result.type, scope_stack,
478                 access_init, result.name, ctrl, recv, *container, indent + 1);
479         v.accept(visitor);
480 
481         return visitor.node;
482     }
483 
484     /// Analyze the inheritance(s).
485     override void visit(const(CxxBaseSpecifier) v) {
486         mixin(mixinNodeLog!());
487 
488         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
489             return;
490 
491         auto result = analyzeCxxBaseSpecified(v, *container, indent);
492 
493         recv.put(this_, result);
494     }
495 
496     override void visit(const(Constructor) v) {
497         mixin(mixinNodeLog!());
498 
499         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
500             return;
501 
502         auto result = analyzeConstructor(v, *container, indent);
503         updateClassification(MethodKind.Ctor, MemberVirtualType.Unknown);
504 
505         auto tor = CppCtor(result.type.kind.usr, result.name, result.params, access);
506         auto func = NodeFunction(result.type.kind.usr, tor.toString, result.name, result.location);
507         node.methods.put(func);
508 
509         recv.put(this_, result, access);
510 
511         () @trusted {
512             auto visitor = scoped!(BodyVisitor!(ReceiveT))(result.type, ctrl,
513                     recv, *container, indent + 1);
514             v.accept(visitor);
515         }();
516     }
517 
518     override void visit(const(Destructor) v) {
519         mixin(mixinNodeLog!());
520 
521         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
522             return;
523 
524         auto result = analyzeDestructor(v, *container, indent);
525         updateClassification(MethodKind.Dtor, cast(MemberVirtualType) result.virtualKind);
526 
527         auto tor = CppDtor(result.type.kind.usr, result.name, access, result.virtualKind);
528         auto func = NodeFunction(result.type.kind.usr, tor.toString, result.name, result.location);
529         node.methods.put(func);
530 
531         recv.put(this_, result, access);
532 
533         () @trusted {
534             auto visitor = scoped!(BodyVisitor!(ReceiveT))(result.type, ctrl,
535                     recv, *container, indent + 1);
536             v.accept(visitor);
537         }();
538     }
539 
540     override void visit(const(CxxMethod) v) {
541         mixin(mixinNodeLog!());
542         import cpptooling.data : CppMethod, CppConstMethod;
543 
544         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
545             return;
546 
547         auto result = analyzeCxxMethod(v, *container, indent);
548         updateClassification(MethodKind.Method, cast(MemberVirtualType) result.virtualKind);
549 
550         auto method = CppMethod(result.type.kind.usr, result.name, result.params,
551                 result.returnType, access, CppConstMethod(result.isConst), result.virtualKind);
552         auto func = NodeFunction(result.type.kind.usr, method.toString,
553                 result.name, result.location);
554         node.methods.put(func);
555 
556         recv.put(this_, result, access);
557 
558         () @trusted {
559             auto visitor = scoped!(BodyVisitor!(ReceiveT))(result.type, ctrl,
560                     recv, *container, indent + 1);
561             v.accept(visitor);
562         }();
563     }
564 
565     override void visit(const(FieldDecl) v) {
566         mixin(mixinNodeLog!());
567 
568         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
569             return;
570 
571         auto result = analyzeFieldDecl(v, *container, indent);
572 
573         auto field = NodeField(result.instanceUSR, result.name, result.type,
574                 access, decideColor(result), result.location);
575         node.attributes.put(field);
576 
577         // TODO probably not necessary for classification to store it as a
578         // member. Instead extend MethodKind to having a "Member".
579         hasMember = Yes.hasMember;
580         updateClassification(MethodKind.Unknown, MemberVirtualType.Unknown);
581 
582         recv.put(this_, result, access);
583     }
584 
585     override void visit(const(TypedefDecl) v) {
586         mixin(mixinNodeLog!());
587         import cpptooling.analyzer.clang.type : retrieveType;
588         import cpptooling.analyzer.clang.store : put;
589 
590         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
591             return;
592 
593         auto result = () @trusted {
594             return retrieveType(v.cursor, *container, indent + 1);
595         }();
596         put(result, *container, indent + 1);
597 
598         if (!result.isNull) {
599             auto tnode = NodeType(result.get.primary.type);
600             node.types.put(tnode);
601         }
602     }
603 
604     override void visit(const(CxxAccessSpecifier) v) @trusted {
605         mixin(mixinNodeLog!());
606         access = CppAccess(toAccessType(v.cursor.access.accessSpecifier));
607     }
608 }
609 
610 /** Visit a function or method body.
611  *
612  */
613 private final class BodyVisitor(ReceiveT) : Visitor {
614     import std.algorithm;
615     import std.array;
616     import std.conv;
617     import std.typecons;
618 
619     import cpptooling.analyzer.clang.ast;
620     import cpptooling.analyzer.clang.ast.visitor : generateIndentIncrDecr;
621     import cpptooling.analyzer.clang.analyze_helper;
622     import cpptooling.data.representation;
623     import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
624 
625     alias visit = Visitor.visit;
626 
627     mixin generateIndentIncrDecr;
628 
629     /** Type representation of parent.
630      * Used as the source of the outgoing relations from this class.
631      */
632     TypeKindAttr parent;
633 
634     private {
635         Controller ctrl;
636         NullableRef!ReceiveT recv;
637 
638         Container* container;
639     }
640 
641     this(const(TypeKindAttr) parent, Controller ctrl, ref ReceiveT recv,
642             ref Container container, const uint indent) {
643         this.parent = parent;
644         this.ctrl = ctrl;
645         this.recv = &recv;
646         this.container = &container;
647         this.indent = indent;
648     }
649 
650     override void visit(const(Declaration) v) {
651         mixin(mixinNodeLog!());
652 
653         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
654             return;
655 
656         v.accept(this);
657     }
658 
659     override void visit(const(Expression) v) {
660         mixin(mixinNodeLog!());
661 
662         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
663             return;
664 
665         v.accept(this);
666     }
667 
668     override void visit(const(Statement) v) {
669         mixin(mixinNodeLog!());
670 
671         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
672             return;
673 
674         v.accept(this);
675     }
676 
677     override void visit(const(VarDecl) v) {
678         mixin(mixinNodeLog!());
679         import cpptooling.analyzer.clang.cursor_backtrack : isGlobalOrNamespaceScope;
680 
681         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
682             return;
683 
684         // accessing a global
685         if (v.cursor.isGlobalOrNamespaceScope) {
686             auto result = analyzeVarDecl(v, *container, indent);
687             recv.put(parent, result.type);
688         }
689 
690         v.accept(this);
691     }
692 
693     override void visit(const(CallExpr) v) @trusted {
694         mixin(mixinNodeLog!());
695         // assuming the needed reference information of the node is found by traversing the AST
696 
697         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
698             return;
699 
700         auto visitor = scoped!(RefVisitor)(ctrl, *container, indent + 1);
701         v.accept(visitor);
702 
703         processRef(visitor);
704     }
705 
706     override void visit(const(DeclRefExpr) v) @trusted {
707         mixin(mixinNodeLog!());
708 
709         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
710             return;
711 
712         visitRef(v);
713     }
714 
715     override void visit(const(MemberRefExpr) v) @trusted {
716         mixin(mixinNodeLog!());
717 
718         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
719             return;
720 
721         visitRef(v);
722     }
723 
724 private:
725     void visitRef(T)(const(T) v) @trusted {
726         // assuming there are no sub nodes so therefor not calling v.accept(visitor)
727 
728         auto visitor = scoped!(RefVisitor)(ctrl, *container, indent + 1);
729         visitor.visitReferenced(v.cursor);
730         processRef(visitor);
731     }
732 
733     void processRef(T)(ref const(T) r) {
734         foreach (usr; r.destinations.data) {
735             recv.put(parent.kind.usr, usr);
736         }
737 
738         foreach (type; r.extraTypes.data) {
739             recv.putBodyNode(type);
740         }
741     }
742 }
743 
744 /** Analyze a reference node.
745  * The data gathered is:
746  *     - types
747  *     - accessing global variables
748  *     - function calls
749  * The gathered targets are stored in $(D destinations).
750  */
751 private final class RefVisitor : Visitor {
752     import std.algorithm;
753     import std.array;
754     import std.conv;
755     import std.typecons;
756 
757     import clang.Cursor : Cursor;
758     import clang.c.Index : CXCursorKind;
759 
760     import cpptooling.analyzer.clang.ast;
761     import cpptooling.analyzer.clang.ast.visitor : generateIndentIncrDecr;
762     import cpptooling.analyzer.clang.analyze_helper;
763     import cpptooling.data.representation;
764     import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog;
765 
766     alias visit = Visitor.visit;
767 
768     mixin generateIndentIncrDecr;
769 
770     // It is assumed that the nodes the edges connect are analysed and
771     // retrieved from elsewhere.
772     Appender!(USRType[]) destinations;
773 
774     // The assumtion fall apart for functions.
775     // For example __builtins will trigger cases where an edge is created but no node.
776     // To handle this extra dummy "type node" are created for functions
777     Appender!(TypeKindAttr[]) extraTypes;
778 
779     private {
780         Controller ctrl;
781         Container* container;
782         bool[USRType] visited;
783     }
784 
785     this(Controller ctrl, ref Container container, const uint indent) {
786         this.ctrl = ctrl;
787         this.container = &container;
788         this.indent = indent;
789     }
790 
791     void visitReferenced(const(Cursor) cursor) @trusted {
792         import cpptooling.analyzer.clang.type : makeEnsuredUSR;
793 
794         auto ref_ = cursor.referenced;
795 
796         // avoid cycles
797         auto usr = makeEnsuredUSR(ref_, indent + 1);
798         if (usr in visited) {
799             return;
800         }
801         visited[usr] = true;
802 
803         logNode(ref_, indent);
804 
805         import cpptooling.analyzer.clang.ast.tree : dispatch;
806 
807         dispatch(ref_, this);
808     }
809 
810     // Begin: Referencing
811     override void visit(const(MemberRefExpr) v) {
812         mixin(mixinNodeLog!());
813 
814         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
815             return;
816 
817         visitReferenced(v.cursor);
818         v.accept(this);
819     }
820 
821     override void visit(const(DeclRefExpr) v) {
822         mixin(mixinNodeLog!());
823 
824         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
825             return;
826 
827         visitReferenced(v.cursor);
828         v.accept(this);
829     }
830     // End: Referencing
831 
832     override void visit(const(FunctionDecl) v) {
833         mixin(mixinNodeLog!());
834 
835         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
836             return;
837 
838         auto result = analyzeFunctionDecl(v, *container, indent);
839         if (result.isValid) {
840             destinations.put(result.type.kind.usr);
841             extraTypes.put(result.type);
842         }
843     }
844 
845     override void visit(const(VarDecl) v) {
846         mixin(mixinNodeLog!());
847         import cpptooling.analyzer.clang.cursor_backtrack : isGlobalOrNamespaceScope;
848 
849         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
850             return;
851 
852         // the root node for the visitor is a reference.
853         // it may therefor be an access to a global variable.
854 
855         // accessing a global
856         if (v.cursor.isGlobalOrNamespaceScope) {
857             auto result = analyzeVarDecl(v, *container, indent);
858             destinations.put(result.instanceUSR);
859         }
860     }
861 
862     // Begin: Class struct
863     override void visit(const(FieldDecl) v) {
864         mixin(mixinNodeLog!());
865 
866         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
867             return;
868 
869         auto result = analyzeFieldDecl(v, *container, indent);
870         destinations.put(result.instanceUSR);
871         // a template may result in extra nodes. e.g std::string's .c_str()
872         extraTypes.put(result.type);
873     }
874 
875     override void visit(const(CxxMethod) v) {
876         mixin(mixinNodeLog!());
877 
878         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
879             return;
880 
881         auto result = analyzeCxxMethod(v, *container, indent);
882         destinations.put(result.type.kind.usr);
883         // a template may result in extra nodes. e.g std::string's .c_str()
884         extraTypes.put(result.type);
885     }
886 
887     override void visit(const(Constructor) v) {
888         mixin(mixinNodeLog!());
889 
890         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
891             return;
892 
893         auto result = analyzeConstructor(v, *container, indent);
894         destinations.put(result.type.kind.usr);
895         // a template may result in extra nodes. e.g std::string's .c_str()
896         extraTypes.put(result.type);
897     }
898 
899     override void visit(const(Destructor) v) {
900         mixin(mixinNodeLog!());
901 
902         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
903             return;
904 
905         auto result = analyzeDestructor(v, *container, indent);
906         destinations.put(result.type.kind.usr);
907         // a template may result in extra nodes. e.g std::string's .c_str()
908         extraTypes.put(result.type);
909     }
910     // End: Class struct
911 
912     // Begin: Generic
913     override void visit(const(Declaration) v) {
914         mixin(mixinNodeLog!());
915 
916         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
917             return;
918 
919         v.accept(this);
920     }
921 
922     override void visit(const(Expression) v) {
923         mixin(mixinNodeLog!());
924 
925         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
926             return;
927 
928         v.accept(this);
929     }
930 
931     override void visit(const(Statement) v) {
932         mixin(mixinNodeLog!());
933 
934         if (!ctrl.doFile(v.cursor.location.spelling.file.name))
935             return;
936 
937         v.accept(this);
938     }
939     // End: Generic
940 }
941 
942 private T toInternal(T, S)(S value) @safe pure nothrow @nogc
943         if (isSomeString!T && (is(S == CppAccess) || is(S == AccessType))) {
944     import cpptooling.data : AccessType;
945 
946     final switch (value) {
947     case AccessType.Private:
948         return "-";
949     case AccessType.Protected:
950         return "#";
951     case AccessType.Public:
952         return "+";
953     }
954 }
955 
956 private T toInternal(T, S)(S value) @safe pure nothrow @nogc
957         if (is(T == StereoType) && is(S == cpptooling.data.class_classification.State)) {
958     final switch (value) with (cpptooling.data.class_classification.State) {
959     case Unknown:
960     case Normal:
961     case Virtual:
962         return StereoType.None;
963     case Abstract:
964         return StereoType.Abstract;
965     case VirtualDtor: // only one method, a d'tor and it is virtual
966     case Pure:
967         return StereoType.Interface;
968     }
969 }
970 
971 /** Deduct if the type is primitive from the point of view of TransformToXmlStream.
972  *
973  * Filtering out arrays or ptrs of primitive types as to not result in too much
974  * noise.
975  */
976 private bool isPrimitive(T, LookupT)(const T data, LookupT lookup) @safe nothrow {
977     static if (is(T == TypeKind)) {
978         switch (data.info.kind) with (TypeKind.Info) {
979         case Kind.primitive:
980             return true;
981         case Kind.array:
982             foreach (ele; lookup.kind(data.info.element)) {
983                 return ele.info.kind == Kind.primitive;
984             }
985             return false;
986         case Kind.pointer:
987             foreach (ele; lookup.kind(data.info.pointee)) {
988                 return ele.info.kind == Kind.primitive;
989             }
990             return false;
991         default:
992             return false;
993         }
994     } else static if (is(T == NodeData)) {
995         switch (data.tag.kind) with (NodeData.Tag) {
996         case Kind.type:
997             auto node = cast(NodeType) data.tag;
998             return node.type.kind.isPrimitive(lookup);
999         default:
1000             return false;
1001         }
1002     } else {
1003         static assert(0, "unsupported type: " ~ T.stringof);
1004     }
1005 }
1006 
1007 package struct NodeData {
1008     import taggedalgebraic : TaggedAlgebraic, Void;
1009 
1010     alias Tag = TaggedAlgebraic!TagUnion;
1011 
1012     Tag tag;
1013     alias tag this;
1014 
1015     static union TagUnion {
1016         Void null_;
1017         NodeFunction func;
1018         NodeType type;
1019         NodeVariable variable;
1020         NodeRecord record;
1021         NodeFile file;
1022         NodeNamespace namespace;
1023         NodeField field;
1024         NodeFallback fallback;
1025     }
1026 
1027     void toString(Writer, Char)(scope Writer w, FormatSpec!Char fmt) {
1028         static import std.format;
1029         import std.traits : FieldNameTuple;
1030 
1031         enum case_ = "case Tag.Kind.%s: auto n = cast(%s) tag; nodeToXml(n, w); break;\n";
1032 
1033         final switch (tag.kind) {
1034             foreach (tag_field; FieldNameTuple!TagUnion) {
1035                 static if (tag_field == "null_") {
1036                     mixin("case Kind.null_: break;\n");
1037                 } else {
1038                     alias tag_field_t = typeof(__traits(getMember, TagUnion, tag_field));
1039                     mixin(std.format.format(case_, tag_field, tag_field_t.stringof));
1040                 }
1041             }
1042         }
1043     }
1044 }
1045 
1046 private ColorKind decideColor(ref const(VarDeclResult) result) @safe pure nothrow @nogc {
1047     import cpptooling.data.type : StorageClass;
1048 
1049     auto color = ColorKind.global;
1050     if (result.type.attr.isConst) {
1051         color = ColorKind.globalConst;
1052     } else if (result.storageClass == StorageClass.Static) {
1053         color = ColorKind.globalStatic;
1054     }
1055 
1056     return color;
1057 }
1058 
1059 private ColorKind decideColor(ref const(FieldDeclResult) result) @safe pure nothrow @nogc {
1060     // TODO extend to differentiate between const and mutable fields.
1061     return ColorKind.field;
1062 }
1063 
1064 /** Transform analyze data to a xml stream.
1065  *
1066  * XML nodes must never be duplicated.
1067  * An edge source or target must be to nodes that exist.
1068  *
1069  * # Strategy `class`
1070  * The generation of a `node` is delayed as long as possible for a class
1071  * declaration in the hope of finding the definition.
1072  * The delay is implemented with a cache.
1073  * When finalize is called the cache is forcefully transformed to `nodes`.
1074  * Even those symbols that only have a declaration as location.
1075  */
1076 class TransformToXmlStream(RecvXmlT, LookupT) if (isOutputRange!(RecvXmlT, char)) {
1077     import std.range : only;
1078     import std.typecons : NullableRef;
1079 
1080     import cpptooling.analyzer.clang.analyze_helper : CxxBaseSpecifierResult,
1081         RecordResult, FieldDeclResult, CxxMethodResult,
1082         ConstructorResult, DestructorResult, VarDeclResult, FunctionDeclResult,
1083         TranslationUnitResult;
1084     import cpptooling.data.type : USRType, LocationTag, Location, CppNs,
1085         TypeKindAttr, TypeKind, TypeAttr, toStringDecl;
1086     import dextool.plugin.utility : MarkArray;
1087 
1088     private {
1089         /// Nodes cached during an analyze phase
1090         MarkArray!NodeData node_cache;
1091 
1092         /// nodes may never be duplicated. If they are it is a violation of the
1093         /// data format.
1094         bool[USRType] streamed_nodes;
1095         /// Ensure that there are only ever one relation between two entities.
1096         /// It avoids the scenario (which is common) of thick patches of
1097         /// relations to common nodes.
1098         bool[USRType] streamed_edges;
1099 
1100         NullableRef!RecvXmlT recv;
1101         LookupT lookup;
1102     }
1103 
1104     this(ref RecvXmlT recv, LookupT lookup) {
1105         this.recv = &recv;
1106         this.lookup = lookup;
1107     }
1108 
1109 @safe:
1110 
1111     ///
1112     void finalize() {
1113         if (node_cache.data.length == 0) {
1114             return;
1115         }
1116 
1117         debug {
1118             import std.range : enumerate;
1119 
1120             logger.tracef("%d nodes left in cache", node_cache.data.length);
1121             foreach (idx, ref n; node_cache.data.enumerate) {
1122                 logger.tracef("  %d: %s", idx + 1, n.usr);
1123             }
1124         }
1125 
1126         void anyLocation(ref const(NodeData) type, ref const(LocationTag) loc) {
1127             if (type.isPrimitive(lookup)) {
1128                 return;
1129             }
1130 
1131             import std.conv : to;
1132 
1133             debug logger.tracef("creating node %s (%s)", cast(string) type.usr,
1134                     type.tag.kind.to!string());
1135 
1136             NodeData node = type;
1137             node.location = loc;
1138             nodeIfMissing(streamed_nodes, recv, type.usr, node);
1139         }
1140 
1141         LocationCallback cb;
1142         cb.unknown = &anyLocation;
1143         cb.declaration = &anyLocation;
1144         cb.definition = &anyLocation;
1145 
1146         resolveLocation(cb, node_cache.data, lookup);
1147         node_cache.clear;
1148     }
1149 
1150     ///
1151     void put(ref const(TranslationUnitResult) result) {
1152         xmlComment(recv, result.fileName);
1153 
1154         if (node_cache.data.length == 0) {
1155             return;
1156         }
1157 
1158         // empty the cache if anything is left in it
1159 
1160         debug logger.tracef("%d nodes left in cache", node_cache.data.length);
1161 
1162         // ugly hack.
1163         // used by putDefinition
1164         // incremented in the foreach
1165         size_t idx = 0;
1166 
1167         void putDeclaration(ref const(NodeData) type, ref const(LocationTag) loc) {
1168             // hoping for the best that a definition is found later on.
1169             if (type.isPrimitive(lookup)) {
1170                 node_cache.markForRemoval(idx);
1171             }
1172         }
1173 
1174         void putDefinition(ref const(NodeData) type, ref const(LocationTag) loc) {
1175             import std.algorithm : among;
1176 
1177             if (type.tag.kind.among(NodeData.Tag.Kind.record)) {
1178                 // do nothing. delaying until finalize.
1179                 // a struct/class (record) bypasses the cache.
1180             } else if (!type.isPrimitive(lookup)) {
1181                 NodeData node = type;
1182                 node.location = loc;
1183                 nodeIfMissing(streamed_nodes, recv, type.usr, node);
1184             }
1185 
1186             node_cache.markForRemoval(idx);
1187         }
1188 
1189         LocationCallback cb;
1190         cb.unknown = &putDeclaration;
1191         cb.declaration = &putDeclaration;
1192         cb.definition = &putDefinition;
1193 
1194         foreach (ref item; node_cache.data) {
1195             if (item.tag.kind == NodeData.Tag.Kind.fallback) {
1196                 // a fallback node is most likely replaced by the correct node
1197                 if (item.tag.usr in streamed_nodes) {
1198                     node_cache.markForRemoval(idx);
1199                 }
1200             } else {
1201                 resolveLocation(cb, only(item), lookup);
1202             }
1203 
1204             ++idx;
1205         }
1206 
1207         debug logger.tracef("%d nodes left in cache", node_cache.data.length);
1208 
1209         node_cache.doRemoval;
1210     }
1211 
1212     /** Create a raw relation between two identifiers.
1213      */
1214     void put(const(USRType) src, const(USRType) dst) {
1215         addEdge(streamed_nodes, node_cache, streamed_edges, recv, src, dst);
1216     }
1217 
1218     /** Create a raw relation between two types.
1219      */
1220     void put(const(TypeKindAttr) src, const(TypeKindAttr) dst) {
1221         edgeIfNotPrimitive(streamed_nodes, node_cache, streamed_edges, recv, src, dst, lookup);
1222     }
1223 
1224     /** Create a raw node for a type.
1225      */
1226     void put(const(TypeKindAttr) type) {
1227         if (!type.kind.isPrimitive(lookup)) {
1228             auto node = NodeType(type, LocationTag(null));
1229             nodeIfMissing(streamed_nodes, recv, type.kind.usr, NodeData(NodeData.Tag(node)));
1230         }
1231     }
1232 
1233     /** Create a _possible_ node from a body inspection.
1234      * It delays the creation of the node until the translation unit is fully
1235      * analyzed.
1236      */
1237     void putBodyNode(const(TypeKindAttr) type) {
1238         if (!type.kind.isPrimitive(lookup)) {
1239             auto node = NodeType(type, LocationTag(null));
1240             putToCache(NodeData(NodeData.Tag(node)));
1241         }
1242     }
1243 
1244     /** A free variable declaration.
1245      *
1246      * This method do NOT handle those inside a function/method/namespace.
1247      */
1248     void put(ref const(VarDeclResult) result) {
1249         Nullable!USRType file_usr = addFileNode(streamed_nodes, recv, result.location);
1250         addVarDecl(file_usr, result);
1251     }
1252 
1253     /** A free variable declaration in a namespace.
1254      *
1255      * TODO maybe connect the namespace to the file?
1256      */
1257     void put(ref const(VarDeclResult) result, CppNs[] ns)
1258     in {
1259         assert(ns.length > 0);
1260     }
1261     body {
1262         auto ns_usr = addNamespaceNode(streamed_nodes, recv, ns);
1263         addVarDecl(ns_usr, result);
1264     }
1265 
1266     private void addVarDecl(Nullable!USRType parent, ref const(VarDeclResult) result) {
1267         { // instance node
1268             auto node = NodeVariable(result.instanceUSR, result.name,
1269                     result.type, decideColor(result), result.location);
1270             nodeIfMissing(streamed_nodes, recv, result.instanceUSR, NodeData(NodeData.Tag(node)));
1271         }
1272 
1273         if (!parent.isNull) {
1274             // connect namespace to instance
1275             addEdge(streamed_nodes, node_cache, streamed_edges, recv,
1276                     parent.get, result.instanceUSR);
1277         }
1278 
1279         // type node
1280         if (!result.type.kind.isPrimitive(lookup)) {
1281             auto node = NodeType(result.type, result.location);
1282             putToCache(NodeData(NodeData.Tag(node)));
1283             addEdge(streamed_nodes, node_cache, streamed_edges, recv,
1284                     result.instanceUSR, result.type.kind.usr);
1285         }
1286     }
1287 
1288     /** Accessing a global.
1289      *
1290      * Assuming that src is already put in the cache.
1291      * Assuming that target is already in cache or will be in the future when
1292      * traversing the AST.
1293      * */
1294     void put(ref const(TypeKindAttr) src, ref const(VarDeclResult) result) {
1295         addEdge(streamed_nodes, node_cache, streamed_edges, recv,
1296                 src.kind.usr, result.instanceUSR);
1297     }
1298 
1299     ///
1300     void put(ref const(FunctionDeclResult) result) {
1301         import std.algorithm : map, filter, joiner;
1302         import std.range : only, chain;
1303         import cpptooling.data : unpackParam;
1304 
1305         auto src = result.type;
1306 
1307         {
1308             auto node = NodeFunction(src.kind.usr,
1309                     result.type.toStringDecl(result.name), result.name, result.location);
1310             putToCache(NodeData(NodeData.Tag(node)));
1311         }
1312 
1313         // dfmt off
1314         foreach (target; chain(only(cast(TypeKindAttr) result.returnType),
1315                                result.params
1316                                 .map!(a => a.unpackParam)
1317                                 .filter!(a => !a.isVariadic)
1318                                 .map!(a => a.type)
1319                                 .map!(a => resolvePointeeType(a.kind, a.attr, lookup))
1320                                 .joiner
1321                                 .map!(a => TypeKindAttr(a.kind, TypeAttr.init)))) {
1322             putToCache(NodeData(NodeData.Tag(NodeType(target))));
1323             edgeIfNotPrimitive(streamed_nodes, node_cache, streamed_edges, recv, src, target, lookup);
1324         }
1325         // dfmt on
1326     }
1327 
1328     /** Calls from src to result.
1329      *
1330      * Assuming that src is already put in the cache.
1331      *
1332      * Only interested in the relation from src to the function.
1333      */
1334     void put(ref const(TypeKindAttr) src, ref const(FunctionDeclResult) result) {
1335         // TODO investigate if the resolve is needed. I don't think so.
1336         auto target = resolveCanonicalType(result.type.kind, result.type.attr, lookup).front;
1337 
1338         auto node = NodeFunction(result.type.kind.usr,
1339                 result.type.toStringDecl(result.name), result.name, result.location);
1340         putToCache(NodeData(NodeData.Tag(node)));
1341 
1342         edgeIfNotPrimitive(streamed_nodes, node_cache, streamed_edges, recv, src, target, lookup);
1343     }
1344 
1345     /** The node_cache may contain class/struct that have been put there by a parameter item.
1346      * Therefor bypass the cache when a definition is found.
1347      */
1348     void put(ref const(RecordResult) result, CppNs[] ns, NodeRecord in_node) {
1349         auto node = in_node;
1350 
1351         if (node.identifier.length == 0) {
1352             // a C typedef.
1353             // e.g. typedef struct { .. } Foo;
1354             auto type = resolveCanonicalType(result.type.kind, result.type.attr, lookup).front;
1355             node.identifier = type.kind.toStringDecl(TypeAttr.init);
1356         }
1357 
1358         if (result.type.attr.isDefinition) {
1359             node.location = result.location;
1360             nodeIfMissing(streamed_nodes, recv, result.type.kind.usr,
1361                     NodeData(NodeData.Tag(node)));
1362         } else {
1363             putToCache(NodeData(NodeData.Tag(node)));
1364         }
1365 
1366         auto ns_usr = addNamespaceNode(streamed_nodes, recv, ns);
1367         if (!ns_usr.isNull) {
1368             addEdge(streamed_nodes, node_cache, streamed_edges, recv,
1369                     ns_usr.get, result.type.kind.usr);
1370         }
1371     }
1372 
1373     /// Create relations to the parameters of a constructor.
1374     void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) {
1375         import std.algorithm : map, filter, joiner;
1376         import cpptooling.data : unpackParam;
1377 
1378         // dfmt off
1379         foreach (target; result.params
1380             .map!(a => a.unpackParam)
1381             .filter!(a => !a.isVariadic)
1382             .map!(a => resolvePointeeType(a.type.kind, a.type.attr, lookup))
1383             .joiner
1384             .map!(a => TypeKindAttr(a.kind, TypeAttr.init))) {
1385             if (!target.kind.isPrimitive(lookup)) {
1386                 putToCache(NodeData(NodeData.Tag(NodeType(target))));
1387                 addEdge(streamed_nodes, node_cache, streamed_edges, recv, result.type.kind.usr, target.kind.usr);
1388             }
1389         }
1390         // dfmt on
1391     }
1392 
1393     /// No parameters in a destructor so skipping.
1394     void put(ref const(TypeKindAttr) src, ref const(DestructorResult) result, in CppAccess access) {
1395         // do nothing
1396     }
1397 
1398     /// Create relations to the parameters of a method.
1399     void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) {
1400         import std.algorithm : map, filter, joiner;
1401         import std.range : only, chain;
1402         import cpptooling.data : unpackParam;
1403 
1404         // dfmt off
1405         foreach (target; chain(only(cast(TypeKindAttr) result.returnType),
1406                                    result.params
1407                                     .map!(a => a.unpackParam)
1408                                     .filter!(a => !a.isVariadic)
1409                                     .map!(a => resolvePointeeType(a.type.kind, a.type.attr, lookup))
1410                                     .joiner)
1411                         .map!(a => TypeKindAttr(a.kind, TypeAttr.init))) {
1412             if (!target.kind.isPrimitive(lookup)) {
1413                 putToCache(NodeData(NodeData.Tag(NodeType(target))));
1414                 addEdge(streamed_nodes, node_cache, streamed_edges, recv, result.type.kind.usr, target.kind.usr);
1415             }
1416         }
1417         // dfmt on
1418     }
1419 
1420     /** Relation of a class/struct's field to the it is an instance of type.
1421      *
1422      * The field node is aggregated, and thus handled, in the NodeRecord.
1423      */
1424     void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) {
1425         if (result.type.kind.isPrimitive(lookup)) {
1426             return;
1427         }
1428 
1429         auto target = resolvePointeeType(result.type.kind, result.type.attr, lookup).front;
1430 
1431         if (!target.kind.isPrimitive(lookup)) {
1432             putToCache(NodeData(NodeData.Tag(NodeType(target))));
1433             addEdge(streamed_nodes, node_cache, streamed_edges, recv,
1434                     result.instanceUSR, target.kind.usr);
1435         }
1436     }
1437 
1438     /// Avoid code duplication by creating nodes via the node_cache.
1439     void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) {
1440         putToCache(NodeData(NodeData.Tag(NodeType(result.type))));
1441         // by definition it can never be a primitive type so no check needed.
1442         addEdge(streamed_nodes, node_cache, streamed_edges, recv, src.kind.usr,
1443                 result.canonicalUSR, EdgeKind.Generalization);
1444     }
1445 
1446 private:
1447 
1448     import std.range : ElementType;
1449 
1450     /** Used for callback to distinguish the type of location that has been
1451      * resolved.
1452      */
1453     struct LocationCallback {
1454         void delegate(ref const(NodeData) type, ref const(LocationTag) loc) @safe unknown;
1455         void delegate(ref const(NodeData) type, ref const(LocationTag) loc) @safe declaration;
1456         void delegate(ref const(NodeData) type, ref const(LocationTag) loc) @safe definition;
1457     }
1458 
1459     /** Resolve a type and its location.
1460      *
1461      * Performs a callback to either:
1462      *  - callback_def with the resolved type:s TypeKindAttr for the type and
1463      *    location of the definition.
1464      *  - callback_decl with the resolved type:s TypeKindAttr.
1465      * */
1466     static void resolveLocation(Range, LookupT)(LocationCallback callback,
1467             Range range, LookupT lookup)
1468             if (is(Unqual!(ElementType!Range) == NodeData) && __traits(hasMember,
1469                 LookupT, "kind") && __traits(hasMember, LookupT, "location")) {
1470         import std.algorithm : map;
1471         import std.typecons : tuple;
1472 
1473         // dfmt off
1474         foreach (ref a; range
1475                  // a tuple of (NodeData, DeclLocation)
1476                  .map!(a => tuple(a, lookup.location(a.usr)))) {
1477             // no location?
1478             if (a[1].length == 0) {
1479                 LocationTag noloc;
1480                 callback.unknown(a[0], noloc);
1481             }
1482 
1483             auto loc = a[1].front;
1484 
1485             if (loc.hasDefinition) {
1486                 callback.definition(a[0], loc.definition);
1487             } else if (loc.hasDeclaration) {
1488                 callback.declaration(a[0], loc.declaration);
1489             } else {
1490                 // no location?
1491                 LocationTag noloc;
1492                 callback.unknown(a[0], noloc);
1493             }
1494         }
1495         // dfmt on
1496     }
1497 
1498     static auto toRelativePath(const LocationTag loc) {
1499         import std.path : relativePath;
1500 
1501         if (loc.kind == LocationTag.Kind.noloc) {
1502             return loc;
1503         }
1504 
1505         string rel;
1506         () @trusted { rel = relativePath(loc.file); }();
1507 
1508         return LocationTag(Location(rel, loc.line, loc.column));
1509     }
1510 
1511     void putToCache(const NodeData data) {
1512         // lower the number of allocations by checking in the hash table.
1513         if (data.usr in streamed_nodes) {
1514             return;
1515         }
1516 
1517         node_cache.put(data);
1518     }
1519 
1520     // The following functions result in xml data being written.
1521 
1522     static Nullable!USRType addNamespaceNode(NodeStoreT, RecvT)(ref NodeStoreT nodes,
1523             ref RecvT recv, CppNs[] ns) {
1524         if (ns.length == 0) {
1525             return Nullable!USRType();
1526         }
1527 
1528         import cpptooling.data.type : toStringNs;
1529 
1530         auto ns_usr = USRType(ns.toStringNs);
1531         auto node = NodeData(NodeData.Tag(NodeNamespace(ns_usr)));
1532         nodeIfMissing(nodes, recv, ns_usr, node);
1533 
1534         return Nullable!USRType(ns_usr);
1535     }
1536 
1537     static USRType addFileNode(NodeStoreT, RecvT, LocationT)(ref NodeStoreT nodes,
1538             ref RecvT recv, LocationT location) {
1539         auto file_usr = cast(USRType) location.file;
1540 
1541         if (file_usr !in nodes) {
1542             auto node = NodeData(NodeData.Tag(NodeFile(file_usr)));
1543             nodeIfMissing(nodes, recv, file_usr, node);
1544         }
1545 
1546         return file_usr;
1547     }
1548 
1549     /**
1550      * Params:
1551      *   nodes = a AA with USRType as key
1552      *   recv = the receiver of the xml data
1553      *   node_usr = the unique USR for the node
1554      *   node = either the TypeKindAttr of the node or a type supporting
1555      *          `.toString` taking a generic writer as argument.
1556      */
1557     static void nodeIfMissing(NodeStoreT, RecvT, NodeT)(ref NodeStoreT nodes,
1558             ref RecvT recv, USRType node_usr, NodeT node) {
1559         if (node_usr in nodes) {
1560             return;
1561         }
1562 
1563         node.toString(recv, FormatSpec!char("%s"));
1564         nodes[node_usr] = true;
1565     }
1566 
1567     static bool isEitherPrimitive(LookupT)(TypeKindAttr t0, TypeKindAttr t1, LookupT lookup) {
1568         if (t0.kind.info.kind == TypeKind.Info.Kind.null_
1569                 || t1.kind.info.kind == TypeKind.Info.Kind.null_
1570                 || t0.kind.isPrimitive(lookup) || t1.kind.isPrimitive(lookup)) {
1571             return true;
1572         }
1573 
1574         return false;
1575     }
1576 
1577     static void edgeIfNotPrimitive(NoDupNodesT, NodeStoreT, EdgeStoreT, RecvT, LookupT)(
1578             ref NoDupNodesT nodup_nodes, ref NodeStoreT node_store,
1579             ref EdgeStoreT edges, ref RecvT recv, TypeKindAttr src,
1580             TypeKindAttr target, LookupT lookup) {
1581         if (isEitherPrimitive(src, target, lookup)) {
1582             return;
1583         }
1584 
1585         addEdge(nodup_nodes, node_store, edges, recv, src.kind.usr, target.kind.usr);
1586     }
1587 
1588     static void addEdge(NoDupNodesT, NodeStoreT, EdgeStoreT, RecvT)(
1589             ref NoDupNodesT nodup_nodes, ref NodeStoreT node_store, ref EdgeStoreT edges,
1590             ref RecvT recv, string src_usr, string target_usr, EdgeKind kind = EdgeKind.Directed) {
1591         // skip self edges
1592         if (target_usr == src_usr) {
1593             return;
1594         }
1595 
1596         // naive approach
1597         USRType edge_key = USRType(src_usr ~ "_" ~ target_usr);
1598         if (edge_key in edges) {
1599             return;
1600         }
1601 
1602         xmlEdge(recv, src_usr, target_usr, kind);
1603         edges[edge_key] = true;
1604         addFallbackNodes(nodup_nodes, node_store, [
1605                 USRType(src_usr), USRType(target_usr)
1606                 ]);
1607     }
1608 
1609     static void addFallbackNodes(NoDupNodesT, NodeStoreT, NodeT)(
1610             ref NoDupNodesT nodup_nodes, ref NodeStoreT node_store, NodeT[] nodes) {
1611         foreach (ref n; nodes) {
1612             NodeData data;
1613 
1614             static if (is(Unqual!NodeT == TypeKindAttr)) {
1615                 if (n.kind.info.kind == TypeKind.Info.Kind.null_) {
1616                     return;
1617                 }
1618 
1619                 data = NodeData(NodeData.Tag(NodeFallback(n.kind.usr)));
1620             } else {
1621                 data = NodeData(NodeData.Tag(NodeFallback(n)));
1622             }
1623 
1624             if (data.usr in nodup_nodes) {
1625                 continue;
1626             }
1627 
1628             node_store.put(data);
1629         }
1630     }
1631 }
1632 
1633 private mixin template NodeLocationMixin() {
1634     LocationTag location;
1635 
1636     @Attr(IdT.url) void url(scope StreamChar stream) {
1637         if (location.kind == LocationTag.Kind.loc) {
1638             ccdataWrap(stream, location.file);
1639         }
1640     }
1641 
1642     @Attr(IdT.position) void position(scope StreamChar stream) {
1643         import std.conv : to;
1644 
1645         if (location.kind == LocationTag.Kind.loc) {
1646             ccdataWrap(stream, "Line:", location.line.to!string, " Column:",
1647                     location.column.to!string);
1648         }
1649     }
1650 }
1651 
1652 /// Helper to generate a unique ID for the node.
1653 private mixin template NodeIdMixin() {
1654     @NodeId void putId(scope StreamChar stream) {
1655         auto id = ValidNodeId(usr);
1656         id.toString(stream, FormatSpec!char("%s"));
1657     }
1658 
1659     debug {
1660         @NodeExtra void putIdDebug(scope StreamChar stream) {
1661             import std.format : formattedWrite;
1662             import std.string : replace;
1663 
1664             // printing the raw identifiers to make it easier to debug
1665             formattedWrite(stream, "<!-- id: %s -->", usr.replace("-", "_"));
1666         }
1667     }
1668 }
1669 
1670 /// A node for a free function or a class/struct method.
1671 private @safe struct NodeFunction {
1672     import std.array : Appender;
1673 
1674     USRType usr;
1675     @Attr(IdT.signature) string signature;
1676     string identifier;
1677 
1678     mixin NodeLocationMixin;
1679 
1680     @Attr(IdT.kind) enum kind = "function";
1681 
1682     @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) {
1683         auto style = makeShapeNode(identifier, ColorKind.func);
1684         style.toString(stream, FormatSpec!char("%s"));
1685     }
1686 
1687     mixin NodeIdMixin;
1688 }
1689 
1690 @("Should be a xml node of a function")
1691 unittest {
1692     auto func = NodeFunction(USRType("123"), "void foo(int)", "foo", LocationTag("fun.h", 1, 2));
1693 
1694     auto buf = appender!string();
1695     auto recv = DummyRecv(&buf);
1696 
1697     nodeToXml(func, recv);
1698     buf.data.shouldEqual(`<node id="18446744072944306312"><data key="d11"><![CDATA[void foo(int)]]></data><data key="d3"><![CDATA[fun.h]]></data><data key="d8"><![CDATA[Line:1 Column:2]]></data><data key="d9"><![CDATA[function]]></data><data key="d5"><y:ShapeNode><y:Geometry height="20" width="140"/><y:Fill color="#FF6600" transparent="false"/><y:NodeLabel autoSizePolicy="node_size" configuration="CroppingLabel"><![CDATA[foo]]></y:NodeLabel></y:ShapeNode></data><!-- id: 123 --></node>
1699 `);
1700 }
1701 
1702 /// Represents either a class or struct.
1703 private @safe struct NodeRecord {
1704     import std.array : Appender;
1705 
1706     USRType usr;
1707     string identifier;
1708     StereoType stereotype;
1709     Appender!(NodeField[]) attributes;
1710     Appender!(NodeFunction[]) methods;
1711     Appender!(NodeType[]) types;
1712 
1713     mixin NodeLocationMixin;
1714 
1715     @Attr(IdT.kind) enum kind = "record";
1716 
1717     @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) {
1718         //TODO express stereotype in some way
1719         auto folder = FolderNode(identifier);
1720         folder.toString(stream, FormatSpec!char("%s"));
1721     }
1722 
1723     @NodeAttribute string yfile = `yfiles.foldertype="folder"`;
1724 
1725     @NodeExtra void graph(scope StreamChar stream) {
1726         import std.format : formattedWrite;
1727         import std.range.primitives : put;
1728 
1729         formattedWrite(stream, "\n" ~ `<graph edgedefault="directed" id="G%s:">` ~ "\n",
1730                 nextGraphId);
1731 
1732         foreach (type; types.data) {
1733             nodeToXml(type, stream);
1734         }
1735 
1736         foreach (attr; attributes.data) {
1737             nodeToXml(attr, stream);
1738         }
1739 
1740         foreach (func; methods.data) {
1741             nodeToXml(func, stream);
1742         }
1743 
1744         put(stream, `</graph>`);
1745     }
1746 
1747     mixin NodeIdMixin;
1748 }
1749 
1750 /// Node for a C++ type that has no other suitable node to represent it.
1751 private @safe struct NodeType {
1752     USRType usr;
1753     TypeKindAttr type;
1754 
1755     mixin NodeLocationMixin;
1756 
1757     this(TypeKindAttr type, LocationTag location) {
1758         this.type = type;
1759         this.location = location;
1760 
1761         this.usr = type.kind.usr;
1762     }
1763 
1764     this(TypeKindAttr type) {
1765         this(type, LocationTag(null));
1766     }
1767 
1768     @Attr(IdT.kind) enum kind = "type";
1769 
1770     @Attr(IdT.typeAttr) void typeAttr(scope StreamChar stream) {
1771         ccdataWrap(stream, type.attr);
1772     }
1773 
1774     @Attr(IdT.signature) void signature(scope StreamChar stream) {
1775         ccdataWrap(stream, type.toStringDecl);
1776     }
1777 
1778     @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) {
1779         auto style = makeShapeNode(type.kind.toStringDecl(TypeAttr.init));
1780         style.toString(stream, FormatSpec!char("%s"));
1781     }
1782 
1783     @NodeId void putId(scope StreamChar stream) {
1784         auto id = ValidNodeId(type.kind.usr);
1785         id.toString(stream, FormatSpec!char("%s"));
1786     }
1787 }
1788 
1789 /// A variable definition.
1790 private @safe struct NodeVariable {
1791     USRType usr;
1792     string identifier;
1793     TypeKindAttr type;
1794     ColorKind color;
1795 
1796     mixin NodeLocationMixin;
1797 
1798     @Attr(IdT.kind) enum kind = "variable";
1799 
1800     @Attr(IdT.signature) void signature(scope StreamChar stream) {
1801         ccdataWrap(stream, type.toStringDecl(identifier));
1802     }
1803 
1804     @Attr(IdT.typeAttr) void typeAttr(scope StreamChar stream) {
1805         import std.algorithm : joiner, filter, copy;
1806         import std.range : chain, only;
1807         import std.array : appender;
1808 
1809         auto app = appender!string();
1810 
1811         // dfmt off
1812         chain(type.attr.stringRange, only(color == ColorKind.globalStatic ? "static" : null))
1813             .filter!(a => a !is null)
1814             .joiner(";")
1815             .copy(app);
1816         // dfmt on
1817 
1818         ccdataWrap(stream, app.data);
1819     }
1820 
1821     @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) {
1822         auto style = makeShapeNode(identifier, color);
1823         style.toString(stream, FormatSpec!char("%s"));
1824     }
1825 
1826     mixin NodeIdMixin;
1827 }
1828 
1829 @("Static attribute is derived from the color and represented in the typeAttr field")
1830 unittest {
1831     import std.array : appender;
1832     import cpptooling.data : makeSimple;
1833     import unit_threaded : shouldEqual;
1834 
1835     auto tk = makeSimple("int");
1836     auto node = NodeVariable(USRType("usr"), "x", tk, ColorKind.globalStatic);
1837     auto app = appender!string();
1838 
1839     node.typeAttr((const(char)[] s) { app.put(s); });
1840 
1841     app.data.shouldEqual("static");
1842 }
1843 
1844 /// A node for a field of a class/struct.
1845 private @safe struct NodeField {
1846     import cpptooling.data.type : AccessType;
1847 
1848     USRType usr;
1849     string identifier;
1850     TypeKindAttr type;
1851     AccessType access;
1852     ColorKind color;
1853 
1854     mixin NodeLocationMixin;
1855 
1856     @Attr(IdT.kind) enum kind = "field";
1857 
1858     @Attr(IdT.signature) void signature(scope StreamChar stream) {
1859         ccdataWrap(stream, type.toStringDecl(identifier));
1860     }
1861 
1862     @Attr(IdT.typeAttr) void typeAttr(scope StreamChar stream) {
1863         ccdataWrap(stream, type.attr);
1864     }
1865 
1866     @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) {
1867         auto style = makeShapeNode(access.toInternal!string ~ identifier, color);
1868         style.toString(stream, FormatSpec!char("%s"));
1869     }
1870 
1871     mixin NodeIdMixin;
1872 }
1873 
1874 /// A node for a file.
1875 private @safe struct NodeFile {
1876     USRType usr;
1877 
1878     @Attr(IdT.kind) enum kind = "file";
1879 
1880     @Attr(IdT.url) void url(scope StreamChar stream) {
1881         ccdataWrap(stream, cast(string) usr);
1882     }
1883 
1884     @Attr(IdT.signature) void signature(scope StreamChar stream) {
1885         ccdataWrap(stream, cast(string) usr);
1886     }
1887 
1888     @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) {
1889         import std.path : baseName;
1890 
1891         auto style = makeShapeNode((cast(string) usr).baseName, ColorKind.file);
1892         style.toString(stream, FormatSpec!char("%s"));
1893     }
1894 
1895     mixin NodeIdMixin;
1896 }
1897 
1898 /** A node for a namespace.
1899  *
1900  * Intended to enable edges of types and globals to relate to the namespace
1901  * that contain them.
1902  *
1903  * It is not intended to "contain" anything, which would be hard in for a
1904  * language as C++. Hard because any translation unit can add anything to a
1905  * namespace.
1906  */
1907 private @safe struct NodeNamespace {
1908     USRType usr;
1909 
1910     @Attr(IdT.kind) enum kind = "namespace";
1911 
1912     @Attr(IdT.signature) void signature(scope StreamChar stream) {
1913         ccdataWrap(stream, cast(string) usr);
1914     }
1915 
1916     @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) {
1917         auto style = makeShapeNode(cast(string) usr, ColorKind.namespace);
1918         style.toString(stream, FormatSpec!char("%s"));
1919     }
1920 
1921     mixin NodeIdMixin;
1922 }
1923 
1924 private @safe struct NodeFallback {
1925     USRType usr;
1926 
1927     mixin NodeLocationMixin;
1928 
1929     @Attr(IdT.kind) enum kind = "fallback";
1930 
1931     @Attr(IdT.nodegraphics) void graphics(scope StreamChar stream) {
1932         auto style = makeShapeNode(usr, ColorKind.fallback);
1933         style.toString(stream, FormatSpec!char("%s"));
1934     }
1935 
1936     mixin NodeIdMixin;
1937 }