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 }