1 /**
2 Copyright: Copyright (c) 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.cpptestdouble.backend.visitor;
11 
12 import logger = std.experimental.logger;
13 
14 import dextool.type : Path;
15 
16 import libclang_ast.ast : Visitor;
17 import cpptooling.data : CppRoot, CppClass, CppMethod, CppCtor, CppDtor,
18     CFunction, CppNamespace, LocationTag, Location;
19 import cpptooling.data.symbol : USRType;
20 
21 import dextool.plugin.cpptestdouble.backend.interface_;
22 
23 /// Data derived during analyze
24 struct AnalyzeData {
25     import cpptooling.data.symbol.types : FullyQualifiedNameType;
26 
27     CppRoot root;
28 
29     /// Classes found during src analysis.
30     CppClass[FullyQualifiedNameType] classes;
31 
32     void putForLookup(CppClass c) {
33         classes[c.fullyQualifiedName] = c;
34     }
35 }
36 
37 private enum VisitorKind {
38     root,
39     child
40 }
41 
42 alias CppTUVisitor = CppVisitor!(VisitorKind.root);
43 
44 final class CppVisitor(VisitorKind RootT) : Visitor {
45     import std.typecons : scoped, NullableRef;
46 
47     import libclang_ast.ast : UnexposedDecl, VarDecl, FunctionDecl, ClassDecl,
48         Namespace, TranslationUnit, generateIndentIncrDecr, StructDecl;
49     import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl, analyzeVarDecl;
50     import cpptooling.data : CppRoot, CxGlobalVariable, CppNsStack,
51         CxReturnType, CppNs, TypeKindVariable;
52     import cpptooling.data.symbol : Container;
53     import libclang_ast.cursor_logger : logNode, mixinNodeLog;
54 
55     alias visit = Visitor.visit;
56 
57     mixin generateIndentIncrDecr;
58 
59     NullableRef!Container container;
60     NullableRef!AnalyzeData analyze_data;
61 
62     private {
63         Controller ctrl;
64         Products prod;
65         CppNsStack ns_stack;
66     }
67 
68     static if (RootT == VisitorKind.root) {
69         CppRoot root;
70 
71         this(Controller ctrl, Products prod, NullableRef!AnalyzeData analyze,
72                 NullableRef!Container container) {
73             this.ctrl = ctrl;
74             this.prod = prod;
75             this.analyze_data = analyze;
76             this.container = container;
77         }
78     } else {
79         CppNamespace root;
80 
81         this(Controller ctrl, Products prod, uint indent, CppNsStack ns_stack,
82                 NullableRef!AnalyzeData analyze, NullableRef!Container container) {
83             this.root = CppNamespace(ns_stack);
84             this.ctrl = ctrl;
85             this.prod = prod;
86             this.indent = indent;
87             this.ns_stack = ns_stack;
88             this.analyze_data = analyze;
89             this.container = container;
90         }
91     }
92 
93     override void visit(scope const UnexposedDecl v) {
94         mixin(mixinNodeLog!());
95 
96         // An unexposed may be:
97 
98         // an extern "C"
99         // UnexposedDecl "" extern "C" {...
100         //   FunctionDecl "fun_c_linkage" void func_c_linkage
101         v.accept(this);
102     }
103 
104     override void visit(scope const VarDecl v) {
105         import clang.c.Index : CX_StorageClass;
106 
107         mixin(mixinNodeLog!());
108 
109         // TODO investigate if linkage() == CXLinkage_External should be used
110         // instead.
111         if (v.cursor.storageClass() == CX_StorageClass.extern_) {
112             auto result = analyzeVarDecl(v, container, indent);
113             auto var = CxGlobalVariable(result.instanceUSR,
114                     TypeKindVariable(result.type, result.name));
115             root.put(var);
116         }
117     }
118 
119     override void visit(scope const FunctionDecl v) {
120         mixin(mixinNodeLog!());
121 
122         auto result = analyzeFunctionDecl(v, container, indent);
123         if (result.isValid) {
124             auto func = CFunction(result.type.kind.usr, result.name, result.params,
125                     CxReturnType(result.returnType), result.isVariadic, result.storageClass);
126             root.put(func);
127         }
128     }
129 
130     override void visit(scope const ClassDecl v) {
131         visitRecord(v);
132     }
133 
134     override void visit(scope const StructDecl v) {
135         visitRecord(v);
136     }
137 
138     void visitRecord(T)(scope const T v) @trusted {
139         import std.typecons : scoped;
140         import cpptooling.analyzer.clang.analyze_helper : ClassVisitor, analyzeRecord;
141 
142         ///TODO add information if it is a public/protected/private class.
143         ///TODO add metadata to the class if it is a definition or declaration
144 
145         mixin(mixinNodeLog!());
146 
147         auto result = analyzeRecord(v, container, indent + 1);
148         debug logger.trace("class: ", result.name);
149 
150         // TODO remove the zero length check to add support for typedef'ed structs.
151         // Example:
152         // typedef struct {
153         // } Struct;
154 
155         if (result.name.length != 0 && v.cursor.isDefinition) {
156             auto visitor = scoped!ClassVisitor(v, ns_stack, result, container, indent + 1);
157             v.accept(visitor);
158 
159             root.put(visitor.root);
160             analyze_data.putForLookup(visitor.root);
161         }
162     }
163 
164     override void visit(scope const Namespace v) @trusted {
165         mixin(mixinNodeLog!());
166 
167         () @trusted { ns_stack ~= CppNs(v.cursor.spelling); }();
168         // pop the stack when done
169         scope (exit)
170             ns_stack = ns_stack[0 .. $ - 1];
171 
172         auto ns_visitor = scoped!(CppVisitor!(VisitorKind.child))(ctrl, prod,
173                 indent, ns_stack, analyze_data, container);
174 
175         v.accept(ns_visitor);
176 
177         // fill the namespace with content from the analysis
178         root.put(ns_visitor.root);
179     }
180 
181     override void visit(scope const TranslationUnit v) {
182         import std.algorithm : filter;
183         import cpptooling.analyzer.clang.type : makeLocation;
184         import cpptooling.testdouble.header_filter : LocationType;
185 
186         mixin(mixinNodeLog!());
187 
188         LocationTag tu_loc;
189         () @trusted { tu_loc = LocationTag(Location(v.cursor.spelling, 0, 0)); }();
190 
191         if (tu_loc.kind != LocationTag.Kind.noloc && ctrl.doFile(tu_loc.file,
192                 "root " ~ tu_loc.toString)) {
193             prod.putLocation(Path(tu_loc.file), LocationType.Root);
194         }
195 
196         v.accept(this);
197     }
198 }