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