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