1 module introspection.aggregate;
2 
3 import std.traits;
4 import introspection.type;
5 import introspection.location;
6 import introspection.attribute;
7 import introspection.protection;
8 import introspection.callable;
9 import introspection.enum_;
10 import introspection.manifestConstant;
11 import introspection.template_;
12 import introspection.protection;
13 import introspection.property;
14 import introspection.unittest_;
15 
16 version(unittest) {
17   import fluent.asserts;
18   import std.algorithm;
19   import std.array;
20 }
21 
22 /// Stores information about attributes
23 struct Aggregate {
24   ///
25   string name;
26 
27   ///
28   Type type;
29 
30   ///
31   Type[] baseClasses;
32 
33   ///
34   Type[] interfaces;
35 
36   ///
37   Type[] nested;
38 
39   ///
40   Attribute[] attributes;
41 
42   ///
43   Property[] properties;
44 
45   ///
46   Callable[] methods;
47 
48   ///
49   Enum[] enums;
50 
51   ///
52   ManifestConstant[] manifestConstants;
53 
54   ///
55   Template[] templates;
56 
57   ///
58   UnitTest[] unitTests;
59 
60   ///
61   Location location;
62 
63   ///
64   Protection protection;
65 }
66 
67 /// Describes classes, structs, unions and interfaces
68 Aggregate describeAggregate(T, bool withUnitTests = false)() if(isAggregateType!T) {
69   Aggregate aggregate;
70 
71   aggregate.name = Unqual!T.stringof;
72   aggregate.type = describeType!T;
73 
74   static if(is(T == class)) {
75     static foreach (B; BaseClassesTuple!T) {
76       aggregate.baseClasses ~= describeType!B;
77     }
78 
79     static foreach (I; InterfacesTuple!T) {
80       aggregate.interfaces ~= describeType!I;
81     }
82   }
83 
84   aggregate.attributes = describeAttributeList!(__traits(getAttributes, T));
85 
86   static foreach(member; __traits(allMembers, T)) static if(member != "this" && member != "Monitor") {{
87     alias M = __traits(getMember, T, member);
88 
89     static if(isCallable!M) {
90       static foreach(index, overload; __traits(getOverloads, T, member)) {{
91         aggregate.methods ~= describeCallable!(overload, index);
92       }}
93     }
94     else static if(isManifestConstant!(T, member)) {
95       aggregate.manifestConstants ~= describeManifestConstant!(T, member);
96     }
97     else static if(is(M == enum)) {
98       aggregate.enums ~= describeEnum!M;
99     }
100     else static if(is(M == class) || is(M == struct) || is(M == interface) || is(M == union)) {
101       aggregate.nested ~= describeType!M;
102     }
103     else static if(__traits(isTemplate, M)) {
104       aggregate.templates ~= describeTemplate!M;
105     }
106     else {
107       aggregate.properties ~= describeProperty!(T, member);
108     }
109   }}
110 
111   auto location = __traits(getLocation, T);
112   aggregate.location = Location(location[0], location[1], location[2]);
113 
114   aggregate.protection = __traits(getProtection, T).toProtection;
115 
116   static if(withUnitTests) {
117     aggregate.unitTests = describeUnitTests!T;
118   }
119 
120   return aggregate;
121 }
122 
123 /// It should describe a class with interfaces and base classes
124 unittest {
125   interface I1 { }
126   interface I2 { }
127 
128   class C1 { }
129   class C2 : C1, I1 { }
130   class C3 : C2, I2 { }
131 
132   auto result = describeAggregate!C3;
133 
134   result.name.should.equal("C3");
135 
136   result.baseClasses.length.should.equal(3);
137   result.baseClasses[0].name.should.equal("C2");
138   result.baseClasses[1].name.should.equal("C1");
139   result.baseClasses[2].name.should.equal("Object");
140 
141   result.interfaces.length.should.equal(2);
142   result.interfaces[0].name.should.equal("I1");
143   result.interfaces[1].name.should.equal("I2");
144 
145   result.location.file.should.equal("source/introspection/aggregate.d");
146   result.location.line.should.be.greaterThan(0);
147   result.location.column.should.equal(3);
148 
149   result.protection = Protection.public_;
150 }
151 
152 /// It should describe struct attributes
153 unittest {
154   int attr(int) { return 0; }
155 
156   @("attribute1") @attr(1)
157   struct Test { }
158 
159   auto result = describeAggregate!Test;
160 
161   result.name.should.equal("Test");
162   result.attributes.length.should.equal(2);
163 
164   result.attributes[0].name.should.equal(`"attribute1"`);
165   result.attributes[0].type.name.should.equal(`string`);
166 
167   result.attributes[1].name.should.equal("0");
168   result.attributes[1].type.name.should.equal(`int`);
169 }
170 
171 /// It should find properties from a struct
172 unittest {
173   struct Test { int a; string b; }
174 
175   auto result = describeAggregate!Test;
176 
177   result.properties.length.should.equal(2);
178 
179   result.properties[0].name.should.equal("a");
180   result.properties[0].isStatic.should.equal(false);
181   result.properties[0].protection.should.equal(Protection.public_);
182   result.properties[0].type.name.should.equal("int");
183 
184   result.properties[1].name.should.equal("b");
185   result.properties[0].isStatic.should.equal(false);
186   result.properties[1].protection.should.equal(Protection.public_);
187   result.properties[1].type.name.should.equal("string");
188 }
189 
190 /// It should find a static property
191 unittest {
192   struct Test {
193     static int a;
194     string b;
195   }
196 
197   auto result = describeAggregate!Test;
198 
199   result.properties[0].name.should.equal("a");
200   result.properties[0].isStatic.should.equal(true);
201   result.properties[1].isStatic.should.equal(false);
202 }
203 
204 /// It should find property protection levels
205 unittest {
206   class Test {
207     public int a;
208     protected int b;
209     private int c;
210     export int d;
211   }
212 
213   auto result = describeAggregate!Test;
214 
215   result.properties.length.should.equal(4);
216 
217   result.properties[0].name.should.equal("a");
218   result.properties[0].protection.should.equal(Protection.public_);
219 
220   result.properties[1].name.should.equal("b");
221   result.properties[1].protection.should.equal(Protection.protected_);
222 
223   result.properties[2].name.should.equal("c");
224   result.properties[2].protection.should.equal(Protection.private_);
225 
226   result.properties[3].name.should.equal("d");
227   result.properties[3].protection.should.equal(Protection.export_);
228 }
229 
230 /// It should describe struct property attributes
231 unittest {
232   int attr(int) { return 0; }
233 
234   struct Test {
235     @("attribute1") @attr(1)
236     string name;
237   }
238 
239   auto result = describeAggregate!Test;
240 
241   result.name.should.equal("Test");
242 
243   result.properties[0].attributes.length.should.equal(2);
244   result.properties[0].attributes[0].name.should.equal(`"attribute1"`);
245   result.properties[0].attributes[0].type.name.should.equal(`string`);
246 }
247 
248 /// It should find public methods from a struct
249 unittest {
250   struct Test {
251     void a() {}
252     void b() {}
253   }
254 
255   auto result = describeAggregate!Test;
256 
257   result.methods.length.should.equal(2);
258 
259   result.methods[0].name.should.equal("a");
260   result.methods[0].isStatic.should.equal(false);
261   result.methods[0].protection.should.equal(Protection.public_);
262   result.methods[0].type.name.should.equal("void()");
263 
264   result.methods[1].name.should.equal("b");
265   result.methods[1].isStatic.should.equal(false);
266   result.methods[1].protection.should.equal(Protection.public_);
267   result.methods[1].type.name.should.equal("void()");
268 }
269 
270 /// It should find all methods from a class
271 unittest {
272   class Test {
273     public void a() {}
274     protected void b() {}
275     private void c() {}
276   }
277 
278   auto result = describeAggregate!Test;
279 
280   result.methods.length.should.equal(8);
281 
282   result.methods.map!(a => a.name).array.should.equal(["a", "b", "c", "toString", "toHash", "opCmp", "opEquals", "factory"]);
283 
284   result.methods[0].name.should.equal("a");
285   result.methods[0].isStatic.should.equal(false);
286   result.methods[0].protection.should.equal(Protection.public_);
287   result.methods[0].type.name.should.equal("void()");
288 
289   result.methods[1].name.should.equal("b");
290   result.methods[1].isStatic.should.equal(false);
291   result.methods[1].protection.should.equal(Protection.protected_);
292   result.methods[1].type.name.should.equal("void()");
293 
294   result.methods[2].name.should.equal("c");
295   result.methods[2].isStatic.should.equal(false);
296   result.methods[2].protection.should.equal(Protection.private_);
297   result.methods[2].type.name.should.equal("void()");
298 }
299 
300 /// It should find overloaded methods
301 unittest {
302   class Test {
303     void a(int) {}
304     void a(string) {}
305   }
306 
307   auto result = describeAggregate!Test;
308 
309   result.methods.length.should.equal(7);
310 
311   result.methods.map!(a => a.name).array.should.equal(["a", "a", "toString", "toHash", "opCmp", "opEquals", "factory"]);
312 
313   result.methods[0].name.should.equal("a");
314   result.methods[0].isStatic.should.equal(false);
315   result.methods[0].protection.should.equal(Protection.public_);
316   result.methods[0].type.name.should.equal("void(int)");
317   result.methods[0].parameters[0].name.should.equal("_param_0");
318   result.methods[0].parameters[0].type.name.should.equal("int");
319 
320   result.methods[1].name.should.equal("a");
321   result.methods[1].isStatic.should.equal(false);
322   result.methods[1].protection.should.equal(Protection.public_);
323   result.methods[1].type.name.should.equal("void(string)");
324   result.methods[1].parameters[0].name.should.equal("_param_0");
325   result.methods[1].parameters[0].type.name.should.equal("string");
326 }
327 
328 /// It should describe struct method attributes
329 unittest {
330   int attr(int) { return 0; }
331 
332   struct Test {
333     @("attribute1") @attr(1)
334     string name();
335   }
336 
337   auto result = describeAggregate!Test;
338 
339   result.name.should.equal("Test");
340   result.methods[0].attributes.length.should.equal(2);
341   result.methods[0].attributes[0].name.should.equal(`"attribute1"`);
342   result.methods[0].attributes[0].type.name.should.equal(`string`);
343 }
344 
345 /// It should describe static struct method
346 unittest {
347   struct Test {
348     static string name();
349     string name(int);
350   }
351 
352   auto result = describeAggregate!Test;
353 
354   result.name.should.equal("Test");
355   result.methods[0].name.should.equal("name");
356   result.methods[0].isStatic.should.equal(true);
357   result.methods[0].parameters.length.should.equal(0);
358 
359   result.methods[1].name.should.equal("name");
360   result.methods[1].isStatic.should.equal(false);
361   result.methods[1].parameters.length.should.equal(1);
362 }
363 
364 /// It should describe enums defined in classes
365 unittest {
366   class Test {
367     enum Other : int {
368       a, b, c, d
369     }
370   }
371 
372   auto result = describeAggregate!Test;
373 
374   result.enums.length.should.equal(1);
375   result.enums[0].name.should.equal("Other");
376 }
377 
378 /// It should describe a manifest constant
379 unittest {
380   class Test {
381     enum constant = 4;
382   }
383 
384   auto result = describeAggregate!Test;
385 
386   result.manifestConstants.length.should.equal(1);
387   result.manifestConstants[0].name.should.equal("constant");
388 }
389 
390 /// It should describe a struct type defined inside a class
391 unittest {
392   class Test {
393     struct Other {}
394   }
395 
396   auto result = describeAggregate!Test;
397 
398   result.nested.length.should.equal(1);
399   result.nested[0].name.should.equal("Other");
400 }
401 
402 /// It should describe a template defined inside a class
403 unittest {
404   class Test {
405     struct Other(T) {}
406   }
407 
408   auto result = describeAggregate!Test;
409 
410   result.nested.length.should.equal(0);
411 
412   result.templates.length.should.equal(1);
413   result.templates[0].name.should.equal("Other");
414 }
415 
416 /// It should describe an instantiated template
417 unittest {
418   class Test(T) {
419     T bar(T val) { return val; }
420   }
421 
422   alias TestInstantiated = Test!string;
423 
424   auto result = describeAggregate!TestInstantiated;
425 
426   result.name.should.equal("Test!string");
427   result.nested.length.should.equal(0);
428   result.templates.length.should.equal(0);
429 
430   result.methods.length.should.equal(6);
431   result.methods[0].name.should.equal("bar");
432   result.methods[0].returns.name.should.equal("string");
433 }
434 
435 /// It should describe unittests defined in a struct
436 unittest {
437   struct Test { unittest { } }
438 
439   auto result = describeAggregate!(Test, true);
440   result.unitTests.length.should.equal(1);
441 }
442 
443 /// It should describe attributes that return structs
444 unittest {
445   struct Mapper { }
446   Mapper mapper() {
447     return Mapper();
448   }
449 
450   class MockClass {
451     @mapper @Mapper @("mapper")
452     void mapper() @trusted nothrow { }
453   }
454 
455   enum result = describeAggregate!(MockClass, true);
456   result.methods[0].attributes.length.should.equal(3);
457   result.methods[0].attributes[0].name.should.equal("mapper");
458   result.methods[0].attributes[0].type.name.should.equal("nothrow @trusted void()");
459   result.methods[0].attributes[1].name.should.equal("Mapper");
460   result.methods[0].attributes[2].name.should.equal(`"mapper"`);
461 }