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 }