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].overloadIndex.should.equal(0);
315   result.methods[0].isStatic.should.equal(false);
316   result.methods[0].protection.should.equal(Protection.public_);
317   result.methods[0].type.name.should.equal("void(int)");
318   result.methods[0].parameters[0].name.should.equal("_param_0");
319   result.methods[0].parameters[0].type.name.should.equal("int");
320 
321   result.methods[1].name.should.equal("a");
322   result.methods[1].overloadIndex.should.equal(1);
323   result.methods[1].isStatic.should.equal(false);
324   result.methods[1].protection.should.equal(Protection.public_);
325   result.methods[1].type.name.should.equal("void(string)");
326   result.methods[1].parameters[0].name.should.equal("_param_0");
327   result.methods[1].parameters[0].type.name.should.equal("string");
328 }
329 
330 /// It should describe struct method attributes
331 unittest {
332   int attr(int) { return 0; }
333 
334   struct Test {
335     @("attribute1") @attr(1)
336     string name();
337   }
338 
339   auto result = describeAggregate!Test;
340 
341   result.name.should.equal("Test");
342   result.methods[0].attributes.length.should.equal(3);
343   result.methods[0].attributes[0].name.should.equal(`"attribute1"`);
344   result.methods[0].attributes[0].type.name.should.equal(`string`);
345 }
346 
347 /// It should describe a struct @property
348 unittest {
349   int attr(int) { return 0; }
350 
351   struct Test {
352     @property string name();
353   }
354 
355   auto result = describeAggregate!Test;
356 
357   result.name.should.equal("Test");
358   result.methods[0].attributes.length.should.equal(2);
359   result.methods[0].attributes[0].name.should.equal(`"@property"`);
360   result.methods[0].attributes[0].type.name.should.equal(`string`);
361   result.methods[0].attributes[1].name.should.equal(`"@system"`);
362   result.methods[0].attributes[1].type.name.should.equal(`string`);
363 }
364 
365 /// It should describe static struct method
366 unittest {
367   struct Test {
368     static string name();
369     string name(int);
370   }
371 
372   auto result = describeAggregate!Test;
373 
374   result.name.should.equal("Test");
375   result.methods[0].name.should.equal("name");
376   result.methods[0].isStatic.should.equal(true);
377   result.methods[0].parameters.length.should.equal(0);
378 
379   result.methods[1].name.should.equal("name");
380   result.methods[1].isStatic.should.equal(false);
381   result.methods[1].parameters.length.should.equal(1);
382 }
383 
384 /// It should describe enums defined in classes
385 unittest {
386   class Test {
387     enum Other : int {
388       a, b, c, d
389     }
390   }
391 
392   auto result = describeAggregate!Test;
393 
394   result.enums.length.should.equal(1);
395   result.enums[0].name.should.equal("Other");
396 }
397 
398 /// It should describe a manifest constant
399 unittest {
400   class Test {
401     enum constant = 4;
402   }
403 
404   auto result = describeAggregate!Test;
405 
406   result.manifestConstants.length.should.equal(1);
407   result.manifestConstants[0].name.should.equal("constant");
408 }
409 
410 /// It should describe a struct type defined inside a class
411 unittest {
412   class Test {
413     struct Other {}
414   }
415 
416   auto result = describeAggregate!Test;
417 
418   result.nested.length.should.equal(1);
419   result.nested[0].name.should.equal("Other");
420 }
421 
422 /// It should describe a template defined inside a class
423 unittest {
424   class Test {
425     struct Other(T) {}
426   }
427 
428   auto result = describeAggregate!Test;
429 
430   result.nested.length.should.equal(0);
431 
432   result.templates.length.should.equal(1);
433   result.templates[0].name.should.equal("Other");
434 }
435 
436 /// It should describe an instantiated template
437 unittest {
438   class Test(T) {
439     T bar(T val) { return val; }
440   }
441 
442   alias TestInstantiated = Test!string;
443 
444   auto result = describeAggregate!TestInstantiated;
445 
446   result.name.should.equal("Test!string");
447   result.nested.length.should.equal(0);
448   result.templates.length.should.equal(0);
449 
450   result.methods.length.should.equal(6);
451   result.methods[0].name.should.equal("bar");
452   result.methods[0].returns.name.should.equal("string");
453 }
454 
455 /// It should describe unittests defined in a struct
456 unittest {
457   struct Test { unittest { } }
458 
459   auto result = describeAggregate!(Test, true);
460   result.unitTests.length.should.equal(1);
461 }
462 
463 /// It should describe attributes that return structs
464 unittest {
465   struct Mapper { }
466   Mapper mapper() {
467     return Mapper();
468   }
469 
470   class MockClass {
471     @mapper @Mapper @("mapper")
472     void mapper() @trusted nothrow { }
473   }
474 
475   enum result = describeAggregate!(MockClass, true);
476   result.methods[0].attributes.length.should.equal(5);
477   result.methods[0].attributes[0].name.should.equal("mapper");
478   result.methods[0].attributes[0].type.name.should.equal("nothrow @trusted void()");
479   result.methods[0].attributes[1].name.should.equal("Mapper");
480   result.methods[0].attributes[2].name.should.equal(`"mapper"`);
481 }