1 /**
2 Date: 2015-2017, Joakim Brännström
3 License: MPL-2, Mozilla Public License 2.0
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 Generate a C test double implementation from data about the structural
7 representation.
8 */
9 module dextool.plugin.ctestdouble.backend.cvariant;
10 
11 import std.algorithm : filter, each, map, joiner;
12 import std.typecons : Flag, Yes, No;
13 import logger = std.experimental.logger;
14 import std.array : array, empty;
15 
16 import dsrcgen.cpp : CppModule, CppHModule;
17 import my.sumtype;
18 
19 import dextool.type : Path, DextoolVersion;
20 import cpptooling.data.symbol;
21 import libclang_ast.ast : Visitor;
22 import cpptooling.testdouble.header_filter : LocationType;
23 import cpptooling.type : MainName, StubPrefix, CustomHeader, MainNs, MainInterface;
24 
25 /// Control various aspects of the analyze and generation like what nodes to
26 /// process.
27 @safe interface Controller {
28     /// Query the controller with the filename of the AST node for a decision
29     /// if it shall be processed.
30     bool doFile(in string filename, in string info);
31 
32     /** Query the controller for a decision if it shall be processed. */
33     bool doSymbol(string symbol);
34 
35     /** A list of includes for the test double header.
36      *
37      * Part of the controller because they are dynamic, may change depending on
38      * for example calls to doFile.
39      */
40     Path[] getIncludes();
41 
42     /// Controls generation of google mock.
43     bool doGoogleMock();
44 
45     /// Generate a pre_include header file from internal template?
46     bool doPreIncludes();
47 
48     /// Generate a #include of the pre include header
49     bool doIncludeOfPreIncludes();
50 
51     /// Generate a post_include header file from internal template?
52     bool doPostIncludes();
53 
54     /// Generate a #include of the post include header
55     bool doIncludeOfPostIncludes();
56 
57     /// Generate location of symbols as comments
58     bool doLocationAsComment();
59 }
60 
61 /// Parameters used during generation.
62 /// Important aspect that they do NOT change, therefore it is pure.
63 @safe pure interface Parameters {
64     static struct Files {
65         Path hdr;
66         Path impl;
67         Path globals;
68         Path gmock;
69         Path gmock_impl;
70         Path pre_incl;
71         Path post_incl;
72     }
73 
74     /// Source files used to generate the test double.
75     Path[] getIncludes();
76 
77     /// Output directory to store files in.
78     Path getOutputDirectory();
79 
80     /// Files to write generated test double data to.
81     Files getFiles();
82 
83     /// Name affecting interface, namespace and output file.
84     MainName getMainName();
85 
86     /** Namespace for the generated test double.
87      *
88      * Contains the adapter, C++ interface, gmock etc.
89      */
90     MainNs getMainNs();
91 
92     /** Name of the interface of the test double.
93      *
94      * Used in Adapter.
95      */
96     MainInterface getMainInterface();
97 
98     /** Prefix to use for the generated files.
99      *
100      * Affects both the filename and the preprocessor #include.
101      */
102     StubPrefix getFilePrefix();
103 
104     /// Prefix used for test artifacts.
105     StubPrefix getArtifactPrefix();
106 
107     /// Dextool Tool version.
108     DextoolVersion getToolVersion();
109 
110     /// Custom header to prepend generated files with.
111     CustomHeader getCustomHeader();
112 
113     /** If an implementation of the interface for globals that zeroes them
114      * shall be generated.
115      */
116     Flag!"generateZeroGlobals" generateZeroGlobals();
117 }
118 
119 /// Data produced by the generator like files.
120 @safe interface Products {
121     /** Data pushed from the test double generator to be written to files.
122      *
123      * The put value is the code generation tree. It allows the caller of
124      * Generator to inject more data in the tree before writing. For
125      * example a custom header.
126      *
127      * Params:
128      *   fname = file the content is intended to be written to.
129      *   hdr_data = data to write to the file.
130      */
131     void putFile(Path fname, CppHModule hdr_data);
132 
133     /// ditto.
134     void putFile(Path fname, CppModule impl_data);
135 
136     /** During the translation phase the location of symbols that aren't
137      * filtered out are pushed to the variant.
138      *
139      * It is intended that the variant control the #include directive strategy.
140      * Just the files that was input?
141      * Deduplicated list of files where the symbols was found?
142      */
143     void putLocation(Path loc, LocationType type);
144 }
145 
146 /** Generator of test doubles for C code.
147  */
148 struct Generator {
149     import cpptooling.data : CppRoot;
150 
151     private static struct Modules {
152         static Modules make() @safe {
153             return Modules(new CppModule, new CppModule, new CppModule, new CppModule, new CppModule);
154         }
155 
156         CppModule hdr;
157         CppModule impl;
158         CppModule globals;
159         CppModule gmock;
160         CppModule gmock_impl;
161     }
162 
163     ///
164     this(Controller ctrl, Parameters params, Products products) {
165         this.ctrl = ctrl;
166         this.params = params;
167         this.products = products;
168     }
169 
170     /** Filter and aggregate data for future processing.
171      */
172     void aggregate(ref CppRoot root, ref Container container) {
173         import std.typecons : Nullable;
174         import cpptooling.data.symbol.types : USRType;
175 
176         rawFilter(root, ctrl, products, filtered,
177                 (Nullable!USRType usr) => container.find!LocationTag(usr.get));
178     }
179 
180     /** Process structural data to a test double.
181      *
182      * aggregated -> translate -> code generation.
183      *
184      * Translate analyzes what is left after filtering.
185      * On demand extra data is created. An example of on demand is --gmock.
186      *
187      * Code generation is a straight up translation.
188      * Logical decisions should have been handled in earlier stages.
189      */
190     void process(ref Container container) {
191         logger.tracef("Filtered:\n%s\n", filtered.toString());
192 
193         auto implementation = makeImplementation(filtered, ctrl, params, container);
194         logger.trace("Post processed:\n", implementation.toString());
195         logger.tracef("kind: %s\nglobals: %s\nadapterKind: %s\n",
196                 implementation.kind, implementation.globals, implementation.adapterKind);
197 
198         auto m = Modules.make();
199         generate(implementation, ctrl, params, container, m.hdr, m.impl,
200                 m.globals, m.gmock, m.gmock_impl);
201 
202         postProcess(m, ctrl, params, products);
203     }
204 
205 private:
206     CppRoot filtered;
207     Controller ctrl;
208     Parameters params;
209     Products products;
210 
211     static void postProcess(Modules modules, Controller ctrl, Parameters params, Products prod) {
212         import cpptooling.generator.includes : convToIncludeGuard,
213             generatePreInclude, generatePostInclude, makeHeader;
214 
215         /** Generate the C++ header file of the test double.
216          * Params:
217          *  fname = intended output filename, used for ifndef guard.
218          */
219         static auto outputHdr(CppModule hdr, Path fname, DextoolVersion ver,
220                 CustomHeader custom_hdr) {
221             auto o = CppHModule(convToIncludeGuard(fname));
222             o.header.append(makeHeader(fname, ver, custom_hdr));
223             o.content.append(hdr);
224 
225             return o;
226         }
227 
228         static auto output(CppModule code, Path incl_fname, Path dest,
229                 DextoolVersion ver, CustomHeader custom_hdr) {
230             import std.path : baseName;
231 
232             auto o = new CppModule;
233             o.suppressIndent(1);
234             o.append(makeHeader(dest, ver, custom_hdr));
235             o.include(incl_fname.baseName);
236             o.sep(2);
237             o.append(code);
238 
239             return o;
240         }
241 
242         prod.putFile(params.getFiles.hdr, outputHdr(modules.hdr,
243                 params.getFiles.hdr, params.getToolVersion, params.getCustomHeader));
244         prod.putFile(params.getFiles.impl, output(modules.impl,
245                 params.getFiles.hdr, params.getFiles.impl,
246                 params.getToolVersion, params.getCustomHeader));
247         prod.putFile(params.getFiles.globals, output(modules.globals,
248                 params.getFiles.hdr, params.getFiles.globals,
249                 params.getToolVersion, params.getCustomHeader));
250 
251         if (ctrl.doPreIncludes) {
252             prod.putFile(params.getFiles.pre_incl, generatePreInclude(params.getFiles.pre_incl));
253         }
254         if (ctrl.doPostIncludes) {
255             prod.putFile(params.getFiles.post_incl, generatePostInclude(params.getFiles.post_incl));
256         }
257 
258         //TODO refactor. should never reach this stage.
259         if (ctrl.doGoogleMock) {
260             import cpptooling.generator.gmock : generateGmockHdr, generateGmockImpl;
261 
262             prod.putFile(params.getFiles.gmock, generateGmockHdr(params.getFiles.hdr,
263                     params.getFiles.gmock, params.getToolVersion,
264                     params.getCustomHeader, modules.gmock));
265             prod.putFile(params.getFiles.gmock_impl, generateGmockImpl(params.getFiles.gmock,
266                     params.getFiles.gmock_impl, params.getToolVersion,
267                     params.getCustomHeader, modules.gmock_impl));
268         }
269     }
270 }
271 
272 final class CVisitor : Visitor {
273     import std.typecons : scoped;
274 
275     import libclang_ast.ast : VarDecl, FunctionDecl, TranslationUnit, generateIndentIncrDecr;
276     import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl, analyzeVarDecl;
277     import cpptooling.data : CppRoot;
278     import cpptooling.data.symbol : Container;
279     import libclang_ast.cursor_logger : logNode, mixinNodeLog;
280 
281     alias visit = Visitor.visit;
282     mixin generateIndentIncrDecr;
283 
284     CppRoot root;
285     Container container;
286 
287     private {
288         Controller ctrl;
289         Products prod;
290     }
291 
292     this(Controller ctrl, Products prod) {
293         this.ctrl = ctrl;
294         this.prod = prod;
295     }
296 
297     void clearRoot() @safe {
298         this.root = CppRoot.init;
299     }
300 
301     override void visit(scope const VarDecl v) {
302         import cpptooling.data : TypeKindVariable;
303         import clang.c.Index : CX_StorageClass;
304 
305         mixin(mixinNodeLog!());
306 
307         //TODO ugly hack. Move this information to the representation. But for
308         //now skipping all definitions
309         if (v.cursor.storageClass() == CX_StorageClass.extern_) {
310             auto result = analyzeVarDecl(v, container, indent);
311             auto var = CxGlobalVariable(result.instanceUSR,
312                     TypeKindVariable(result.type, result.name));
313             root.put(var);
314         }
315     }
316 
317     override void visit(scope const FunctionDecl v) {
318         import cpptooling.data.type : CxReturnType;
319 
320         mixin(mixinNodeLog!());
321 
322         auto result = analyzeFunctionDecl(v, container, indent);
323         if (result.isValid) {
324             auto func = CFunction(result.type.kind.usr, result.name, result.params,
325                     CxReturnType(result.returnType), result.isVariadic, result.storageClass);
326             root.put(func);
327         }
328     }
329 
330     override void visit(scope const TranslationUnit v) {
331         import cpptooling.analyzer.clang.type : makeLocation;
332 
333         mixin(mixinNodeLog!());
334 
335         LocationTag tu_loc;
336         () @trusted { tu_loc = LocationTag(Location(v.cursor.spelling, 0, 0)); }();
337 
338         if (tu_loc.kind != LocationTag.Kind.noloc && ctrl.doFile(tu_loc.file,
339                 "root " ~ tu_loc.toString)) {
340             prod.putLocation(Path(tu_loc.file), LocationType.Root);
341         }
342 
343         v.accept(this);
344     }
345 
346     void toString(Writer)(scope Writer w) @safe {
347         import std.format : FormatSpec;
348         import std.range.primitives : put;
349 
350         auto fmt = FormatSpec!char("%u");
351         fmt.writeUpToNextSpec(w);
352 
353         root.toString(w, fmt);
354         put(w, "\n");
355         container.toString(w, FormatSpec!char("%s"));
356     }
357 
358     override string toString() {
359         import std.exception : assumeUnique;
360 
361         char[] buf;
362         buf.reserve(100);
363         toString((const(char)[] s) { buf ~= s; });
364         auto trustedUnique(T)(T t) @trusted {
365             return assumeUnique(t);
366         }
367 
368         return trustedUnique(buf);
369     }
370 }
371 
372 private:
373 @safe:
374 
375 import cpptooling.data : CppRoot, CppClass, CppMethod, CppCtor, CppDtor,
376     CFunction, CppNamespace, CxGlobalVariable, USRType, LocationTag, Location;
377 import dsrcgen.cpp : E, noIndent;
378 
379 /** Contain data for code generation.
380  */
381 struct ImplData {
382     import cpptooling.data.type : CppMethodName;
383     import dextool.plugin.ctestdouble.backend.adapter : AdapterKind;
384     import dextool.plugin.ctestdouble.backend.global : MutableGlobal;
385 
386     CppRoot root;
387     alias root this;
388 
389     /// Tagging of nodes in the root
390     Kind[size_t] kind;
391     /// Global, mutable variables
392     MutableGlobal[] globals;
393     /// Constructor kinds for ctors in an adapter
394     AdapterKind[USRType] adapterKind;
395 
396     void tag(size_t id, Kind kind_) {
397         kind[id] = kind_;
398     }
399 
400     Kind lookup(size_t id) {
401         if (auto k = id in kind) {
402             return *k;
403         }
404 
405         return Kind.none;
406     }
407 
408     MutableGlobal lookupGlobal(CppMethodName name) {
409         foreach (item; globals) {
410             if (item.name == name) {
411                 return item;
412             }
413         }
414 
415         // Methods shall always be 1:1 mapped with the globals list.
416         assert(0);
417     }
418 }
419 
420 enum Kind {
421     none,
422     /// Adapter class
423     adapter,
424     /// gmock class
425     gmock,
426     /// interface for globals
427     initGlobalInterface,
428     initGlobalsToZero,
429     testDoubleNamespace,
430     testDoubleSingleton,
431     testDoubleInterface,
432 }
433 
434 /** Structurally transformed the input to a test double implementation.
435  *
436  * This stage:
437  *  - removes C++ code.
438  *  - removes according to directives via ctrl.
439  *
440  * Params:
441  *  input = structural representation of the source code
442  *  ctrl = controll what nodes to keep
443  *  prod = push location data of the nodes that are kept
444  *  filtered = output structural representation
445  *  lookup = callback function supporting lookup of locations
446  */
447 void rawFilter(LookupT)(ref CppRoot input, Controller ctrl, Products prod,
448         ref CppRoot filtered, LookupT lookup) {
449     import std.range : tee;
450     import cpptooling.data : StorageClass;
451     import cpptooling.generator.utility : filterAnyLocation;
452 
453     // dfmt off
454     input.funcRange
455         .filter!(a => !a.usr.isNull)
456         // by definition static functions can't be replaced by test doubles
457         .filter!(a => a.storageClass != StorageClass.Static)
458         // ask controller if the user wants to generate a test double function for the symbol.
459         // note: using the fact that C do NOT have name mangling.
460         .filter!(a => ctrl.doSymbol(a.name))
461         // ask controller if to generate a test double for the function
462         .filterAnyLocation!(a => ctrl.doFile(a.location.file, cast(string) a.value.name))(lookup)
463         // pass on location as a product to be used to calculate #include
464         .tee!(a => prod.putLocation(Path(a.location.file), LocationType.Leaf))
465         .each!(a => filtered.put(a.value));
466 
467     input.globalRange()
468         .filter!(a => !a.usr.isNull)
469         // ask controller if the user wants to generate a global for the symbol.
470         // note: using the fact that C do NOT have name mangling.
471         .filter!(a => ctrl.doSymbol(a.name))
472         // ask controller if to generate a test double for the function
473         .filterAnyLocation!(a => ctrl.doFile(a.location.file, cast(string) a.value.name))(lookup)
474         // pass on location as a product to be used to calculate #include
475         .tee!(a => prod.putLocation(Path(a.location.file), LocationType.Leaf))
476         .each!(a => filtered.put(a.value));
477     // dfmt on
478 }
479 
480 /** Transform the content of the root to a test double implementation root.
481  *
482  * Make an adapter.
483  * Make a namespace holding the test double.
484  * Make an interface for initialization of globals.
485  * Make a google mock if asked by the user.
486  */
487 auto makeImplementation(ref CppRoot root, Controller ctrl, Parameters params,
488         ref Container container) @trusted {
489     import cpptooling.data : CppNamespace, CppNs, CppClassName, CppInherit,
490         CppAccess, AccessType, makeUniqueUSR, nextUniqueID, MergeMode;
491     import cpptooling.generator.func : makeFuncInterface;
492     import cpptooling.generator.gmock : makeGmock;
493     import dextool.plugin.ctestdouble.backend.adapter : makeSingleton, makeAdapter;
494     import dextool.plugin.ctestdouble.backend.global : makeGlobalInterface,
495         makeZeroGlobal, filterMutable;
496 
497     ImplData impl;
498     impl.root.merge(root, MergeMode.shallow);
499 
500     impl.globals = impl.globalRange.filterMutable(container).array;
501 
502     const has_mutable_globals = impl.globals.length != 0;
503     const has_functions = !root.funcRange.empty;
504 
505     if (!has_functions && !has_mutable_globals) {
506         return impl;
507     }
508 
509     auto test_double_ns = CppNamespace.make(CppNs(params.getMainNs));
510 
511     if (has_functions) {
512         auto singleton = makeSingleton(CppNs(params.getMainNs),
513                 CppClassName(params.getMainInterface), "test_double_inst");
514         impl.kind[singleton.id] = Kind.testDoubleSingleton;
515         impl.put(singleton); // (1)
516 
517         auto c_if = makeFuncInterface(impl.funcRange, CppClassName(params.getMainInterface));
518         impl.tag(c_if.id, Kind.testDoubleInterface);
519         test_double_ns.put(c_if);
520 
521         if (ctrl.doGoogleMock) {
522             auto mock = makeGmock(c_if);
523             impl.tag(mock.id, Kind.gmock);
524             test_double_ns.put(mock);
525         }
526     }
527 
528     if (has_mutable_globals) {
529         auto if_name = CppClassName(params.getMainInterface ~ "_InitGlobals");
530         auto global_if = makeGlobalInterface(impl.globals[], if_name);
531         impl.tag(global_if.id, Kind.initGlobalInterface);
532         test_double_ns.put(global_if);
533 
534         if (params.generateZeroGlobals) {
535             auto global_init_zero = makeZeroGlobal(impl.globals[],
536                     CppClassName(params.getArtifactPrefix ~ "ZeroGlobals"),
537                     params.getArtifactPrefix, CppInherit(if_name, CppAccess(AccessType.Public)));
538             impl.tag(global_init_zero.id, Kind.initGlobalsToZero);
539             test_double_ns.put(global_init_zero);
540         }
541     }
542 
543     // MUST be added after the singleton (1)
544     impl.tag(test_double_ns.id, Kind.testDoubleNamespace);
545 
546     {
547         // dfmt off
548         auto adapter = makeAdapter(params.getMainInterface)
549             .makeTestDouble(has_functions)
550             .makeInitGlobals(has_mutable_globals)
551             .makeZeroGlobals(params.generateZeroGlobals)
552             .finalize(impl);
553         impl.tag(adapter.id, Kind.adapter);
554         test_double_ns.put(adapter);
555         // dfmt on
556     }
557 
558     impl.put(test_double_ns);
559     return impl;
560 }
561 
562 void generate(ref ImplData data, Controller ctrl, Parameters params, ref Container container,
563         CppModule hdr, CppModule impl, CppModule globals, CppModule gmock, CppModule gmock_impl) {
564     import cpptooling.data.symbol.types : USRType;
565     import cpptooling.generator.func : generateFuncImpl;
566     import cpptooling.generator.includes : generateWrapIncludeInExternC;
567     import dextool.plugin.ctestdouble.backend.adapter : generateSingleton;
568 
569     generateWrapIncludeInExternC(ctrl, params, hdr);
570     generateGlobal(data.globalRange, ctrl, params, container, globals);
571 
572     auto mutable_extern_hook = impl.base;
573     mutable_extern_hook.suppressIndent(1);
574 
575     foreach (ns; data.namespaceRange) {
576         switch (data.lookup(ns.id)) {
577         case Kind.testDoubleSingleton:
578             generateSingleton(ns, impl);
579             break;
580         case Kind.testDoubleNamespace:
581             generateNsTestDoubleHdr(ns,
582                     cast(Flag!"locationAsComment") ctrl.doLocationAsComment, params, hdr, gmock,
583                     (USRType usr) => container.find!LocationTag(usr), (size_t id) => data.lookup(
584                         id));
585             generateNsTestDoubleImpl(ns, params, impl, gmock_impl,
586                     mutable_extern_hook, data, params.getArtifactPrefix, container);
587             break;
588 
589         default:
590         }
591     }
592 
593     // The generated functions must be extern C declared.
594     auto extern_c = impl.suite("extern \"C\"");
595     extern_c.suppressIndent(1);
596     foreach (a; data.funcRange) {
597         generateFuncImpl(a, extern_c);
598     }
599 }
600 
601 /// Generate the global definitions and macros for initialization.
602 void generateGlobal(RangeT)(RangeT r, Controller ctrl, Parameters params,
603         ref Container container, CppModule globals) {
604     import cpptooling.data : TypeKind, Void;
605 
606     void generateDefinitions(ref CxGlobalVariable global, Flag!"locationAsComment" loc_as_comment,
607             string prefix, ref Container container, CppModule code)
608     in {
609         global.type.kind.info.match!(restrictTo!(TypeKind.CtorInfo, TypeKind.DtorInfo, (_) {
610                 assert(0, "wrong type");
611             }), (_) {});
612     }
613     do {
614         import std.format : format;
615         import std.string : toUpper;
616 
617         string d_name = (prefix ~ "Init_").toUpper ~ global.name;
618 
619         if (loc_as_comment) {
620             // dfmt off
621             foreach (loc; container.find!LocationTag(global.usr.get)
622                 // both declaration and definition is OK
623                 .map!(a => a.any)
624                 .joiner) {
625                 code.comment("Origin " ~ loc.toString)[$.begin = "/// "];
626             }
627             // dfmt on
628         }
629         code.stmt(d_name);
630     }
631 
632     void generatePreProcessor(ref CxGlobalVariable global, string prefix, CppModule code) {
633         import std.string : toUpper;
634         import cpptooling.data : toStringDecl;
635 
636         auto d_name = E((prefix ~ "Init_").toUpper ~ global.name);
637         auto ifndef = code.IFNDEF(d_name);
638 
639         void handler() {
640             ifndef.define(d_name ~ E(global.type.toStringDecl(global.name)));
641         }
642 
643         // example: #define TEST_INIT_extern_a int extern_a[4]
644         global.type.kind.info.match!(restrictTo!(TypeKind.ArrayInfo, TypeKind.FuncInfo,
645                 TypeKind.FuncPtrInfo, TypeKind.FuncSignatureInfo, TypeKind.PointerInfo, TypeKind.PrimitiveInfo,
646                 TypeKind.RecordInfo, TypeKind.SimpleInfo, TypeKind.TypeRefInfo, (a) {
647                 handler;
648             }), restrictTo!(TypeKind.CtorInfo, TypeKind.DtorInfo, (a) {
649                 assert(0, "unexpected c++ code in preprocessor macros");
650             }), (Void a) {
651             logger.error("Type of global definition is null. Identifier ", global.name);
652         });
653     }
654 
655     auto global_macros = globals.base;
656     global_macros.suppressIndent(1);
657     globals.sep;
658     auto global_definitions = globals.base;
659     global_definitions.suppressIndent(1);
660 
661     foreach (a; r) {
662         generatePreProcessor(a, params.getArtifactPrefix, global_macros);
663         generateDefinitions(a, cast(Flag!"locationAsComment") ctrl.doLocationAsComment,
664                 params.getArtifactPrefix, container, global_definitions);
665     }
666 }
667 
668 void generateNsTestDoubleHdr(LookupT, KindLookupT)(ref CppNamespace ns, Flag!"locationAsComment" loc_as_comment,
669         Parameters params, CppModule hdr, CppModule gmock, LookupT lookup, KindLookupT kind_lookup) {
670     import cpptooling.generator.classes : generateHdr;
671     import cpptooling.generator.gmock : generateGmock;
672 
673     auto test_double_ns = hdr.namespace(ns.name);
674     test_double_ns.suppressIndent(1);
675     hdr.sep(2);
676 
677     foreach (class_; ns.classRange()) {
678         switch (kind_lookup(class_.id)) {
679         case Kind.none:
680         case Kind.initGlobalsToZero:
681         case Kind.adapter:
682             generateHdr(class_, test_double_ns, loc_as_comment, lookup);
683             break;
684         case Kind.initGlobalInterface:
685         case Kind.testDoubleInterface:
686             generateHdr(class_, test_double_ns, loc_as_comment, lookup, Yes.inlineDtor);
687             break;
688         case Kind.gmock:
689             auto mock_ns = gmock.namespace(params.getMainNs).noIndent;
690             generateGmock(class_, mock_ns, No.inlineCtorDtor);
691             break;
692         default:
693         }
694     }
695 }
696 
697 void generateNsTestDoubleImpl(ref CppNamespace ns, Parameters params, CppModule impl, CppModule gmock_impl,
698         CppModule mutable_extern_hook, ref ImplData data, StubPrefix prefix, ref Container container) {
699     import dextool.plugin.ctestdouble.backend.global : generateGlobalExterns,
700         generateInitGlobalsToZero;
701     import dextool.plugin.ctestdouble.backend.adapter : generateClassImplAdapter = generateImpl;
702     import cpptooling.generator.gmock : generateGmockImpl;
703 
704     auto test_double_ns = impl.namespace(ns.name);
705     test_double_ns.suppressIndent(1);
706     impl.sep(2);
707 
708     auto gmock_ns = gmock_impl.namespace(ns.name);
709     gmock_ns.suppressIndent(1);
710     gmock_ns.sep(2);
711 
712     auto lookup(USRType usr) {
713         return usr in data.adapterKind;
714     }
715 
716     foreach (class_; ns.classRange) {
717         switch (data.lookup(class_.id)) {
718         case Kind.adapter:
719             generateClassImplAdapter(class_, data.globals,
720                     prefix, test_double_ns, &lookup);
721             break;
722 
723         case Kind.initGlobalsToZero:
724             generateGlobalExterns(data.globals[],
725                     mutable_extern_hook, container);
726             generateInitGlobalsToZero(class_, test_double_ns, prefix, &data.lookupGlobal);
727             break;
728 
729         case Kind.gmock:
730             generateGmockImpl(class_, gmock_ns);
731             break;
732 
733         default:
734         }
735     }
736 }