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