1 /** 2 Copyright: Copyright (c) 2016-2017, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 10 Overall design of the data flow when analyzing. 11 - Visitor pull data from the AST. 12 - Visitor push data to the general Transform. 13 - The Transform splice the data and forwards to the specialized transformers. 14 The top Transform act as a mediator. It do not have any logic or knowledge 15 other than how to forward the data to the specialized transforms. 16 - The specialized transformers finalizes the data, delays, decisions etc. 17 They decide when to do the final forwarding to the diagrams. 18 - The UML diagrams are processed by the Generator. 19 The generator transforms the in-memory representation to content suitable to 20 store in files. 21 - The generator forwards the content to a receiver, the registered Products. 22 - Backend done. See frontend for what happens with the Products. 23 */ 24 module dextool.plugin.backend.plantuml; 25 26 import std.meta : templateAnd, templateOr; 27 import std.range : ElementType; 28 import std.typecons : Flag, Yes, No; 29 import logger = std.experimental.logger; 30 31 import dsrcgen.plantuml; 32 import sumtype; 33 34 import dextool.type; 35 import cpptooling.type : FilePrefix; 36 import libclang_ast.ast : Visitor; 37 import cpptooling.data : TypeKind, TypeAttr, resolveCanonicalType, USRType, 38 TypeKindAttr, CxParam, CxReturnType, TypeKindVariable; 39 import cpptooling.data.symbol.types : FullyQualifiedNameType; 40 import cpptooling.analyzer.clang.analyze_helper : RecordResult; 41 42 static import cpptooling.data.class_classification; 43 44 version (unittest) { 45 import unit_threaded : Name, shouldEqual; 46 } else { 47 private struct Name { 48 string name_; 49 } 50 } 51 52 /** Control various aspects of the analyze and generation like what nodes to 53 * process. 54 */ 55 @safe interface Controller { 56 /// Query the controller with the filename of the AST node for a decision 57 /// if it shall be processed. 58 bool doFile(in string filename, in string info); 59 60 /** Determine by checking the filesystem if a templated PREFIX_style file shall be created. 61 * 62 * Create it with a minimal style. 63 * Currently just the direction but may change in the future. 64 */ 65 Flag!"genStyleInclFile" genStyleInclFile(); 66 67 /// Strip the filename according to user regex. 68 Path doComponentNameStrip(Path fname); 69 } 70 71 /// Parameters used during generation. 72 /// Important aspact that they do NOT change, therefore it is pure. 73 @safe pure interface Parameters { 74 import std.typecons : Flag; 75 76 static struct Files { 77 Path classes; 78 Path components; 79 Path styleIncl; 80 Path styleOutput; 81 } 82 83 /// Output directory to store files in. 84 Path getOutputDirectory(); 85 86 /// Files to write generated diagram data to. 87 Files getFiles(); 88 89 /// Name affecting filenames. 90 FilePrefix getFilePrefix(); 91 92 /** In all diagrams generate an "!include" of the style file. 93 * 94 * If the file PREFIX_style do not exist, create it with a minimal style. 95 * Currently just the direction but may change in the future. 96 */ 97 Flag!"doStyleIncl" doStyleIncl(); 98 99 /// Generate a dot graph in the plantuml file 100 Flag!"doGenDot" doGenDot(); 101 102 /// If class methods should be part of the generated class diagrams. 103 Flag!"genClassMethod" genClassMethod(); 104 105 /// If the parameters of methods should result in directed association. 106 Flag!"genClassParamDependency" genClassParamDependency(); 107 108 /// If the inheritance hierarchy between classes is generated. 109 Flag!"genClassInheritDependency" genClassInheritDependency(); 110 111 /// If the class members result in dependency on those members. 112 Flag!"genClassMemberDependency" genClassMemberDependency(); 113 } 114 115 /// Data produced by the generator like files. 116 @safe interface Products { 117 /** Data pushed from the generator to be written to files. 118 * 119 * The put value is the code generation tree. It allows the caller of 120 * Generator to inject more data in the tree before writing. For example a 121 * custom header. 122 * 123 * Params: 124 * fname = file the content is intended to be written to. 125 * data = data to write to the file. 126 */ 127 void putFile(Path fname, PlantumlRootModule data); 128 129 /// ditto. 130 void putFile(Path fname, PlantumlModule data); 131 } 132 133 /** The supported "kind"s of relations between entities. Related to the UML 134 * standard. 135 */ 136 enum RelateKind { 137 None, 138 Extend, 139 Compose, 140 Aggregate, 141 Associate, 142 Relate 143 } 144 145 /** Relations to targets with count and kind. 146 * 147 * Intented to be used in a hashmap with the key as the "from". 148 */ 149 private struct Relate { 150 @safe: 151 alias Key = USRType; 152 alias Kind = RelateKind; 153 154 private static struct Inner { 155 uint count; 156 Kind kind; 157 } 158 159 private Inner[][Key] to; 160 161 /// Returns: number of outgoing connections 162 size_t fanOut() pure nothrow const { 163 return to.length; 164 } 165 166 void put(Key to_, Kind kind) 167 out { 168 assert(to_ in to); 169 } 170 do { 171 auto v = to_ in to; 172 if (v is null) { 173 to[to_] = Inner[].init; 174 v = to_ in to; 175 } 176 177 // ugly algorithm, use an inner hashmap instead 178 bool is_new = true; 179 foreach (ref r; *v) { 180 if (r.kind == kind) { 181 r.count++; 182 is_new = false; 183 break; 184 } 185 } 186 187 if (is_new) { 188 *v ~= Inner(1, kind); 189 } 190 } 191 192 /** A range of the form FROM-TO with metadata. 193 * 194 * count is the total number of outgoing connections to the target. 195 * For example would 2 Relation and 4 Extend result in the sum of 6. 196 */ 197 auto toRange(const Relate.Key from) pure const @trusted { 198 import std.algorithm : map; 199 import std.array : array; 200 201 static struct RelateTuple { 202 Relate.Key from; 203 Relate.Key to; 204 ulong count; 205 } 206 207 static ulong sumFanOut(const(Inner)[] inner) pure { 208 import std.algorithm : sum; 209 210 return inner.map!(a => a.count).sum; 211 } 212 213 // dfmt off 214 return to.byKeyValue.map!(a => RelateTuple(from, a.key, sumFanOut(a.value))) 215 .array(); 216 // dfmt on 217 } 218 219 /// Convert the TO/value store to a FROM-KIND-TO-COUNT array. 220 auto toFlatArray(const Relate.Key from) pure const @trusted { 221 import std.algorithm : filter, map, joiner; 222 import std.array : array; 223 224 static struct RelateTuple { 225 Relate.Key from; 226 Kind kind; 227 Relate.Key to; 228 ulong count; 229 } 230 231 // dfmt off 232 return to.byKeyValue.map!(a => a.value 233 .filter!(b => b.kind != Kind.None) 234 .map!(b => RelateTuple(from, b.kind, a.key, b.count)) 235 .array()) 236 .joiner() 237 .array(); 238 // dfmt on 239 } 240 241 auto toStringArray(const Relate.Key from) pure const @trusted { 242 import std.algorithm : map; 243 import std.conv : text; 244 import std.format : format; 245 import std.array : array; 246 247 // dfmt off 248 return this.toFlatArray(from) 249 .map!(b => format("%s -%s- [%d]%s", cast(string) b.from, text(b.kind), b.count, cast(string) b.to)) 250 .array(); 251 // dfmt on 252 } 253 } 254 255 private size_t[] nameIndexSortedRange(T, alias sortNameBy)(T arr) pure { 256 import std.algorithm : makeIndex; 257 258 auto index = new size_t[arr.length]; 259 260 makeIndex!((a, b) => sortNameBy(a) < sortNameBy(b))(arr, index); 261 return index; 262 } 263 264 private auto nameSortedRange(T, alias sortNameBy)(T t) pure { 265 import std.algorithm : map; 266 import std.array : array; 267 268 auto arr = t.asArray(); 269 auto index = nameIndexSortedRange!(typeof(arr), sortNameBy)(arr); 270 271 return index.map!(i => arr[i]).array(); 272 } 273 274 private auto fanOutSorted(T)(T t) pure { 275 import std.algorithm : makeIndex, map; 276 import std.array : array; 277 278 //TODO how to avoid doing this allocation? 279 280 auto arr = t.nameSortedRange(); 281 auto fanout_i = new size_t[arr.length]; 282 283 // dfmt off 284 makeIndex!((a, b) => t.relate_to[cast(Relate.Key) a.key].fanOut > t.relate_to[cast(Relate.Key) b.key].fanOut)(arr, fanout_i); 285 // dfmt on 286 287 return fanout_i.map!(i => arr[i]).array(); 288 } 289 290 /** UML Class Diagram. 291 * 292 * Not designed for the general case. 293 * The design is what the plantuml plugin needs when analyzing more than one 294 * file. This is the container that is then passed between the analyze stages. 295 * 296 * All classes must exist in "classes". 297 * It is common that during data gathering a class is found to be related to 298 * another class by a USR. The relation is added before the class represented 299 * by the USR is added. 300 * 301 * A --> B 302 * Directed relation. 303 * A can have many connections to B. 304 * 305 * Store of R[A.B]. 306 * When analyzing the structural data it is this kind of relations that are 307 * found. From a class to many X, where X is other classes. 308 * The key used must be unique, thus the choice of using USR. 309 * 310 * Example of relations. 311 * A --> B (member) 312 * A --> B (member) 313 * A --> B (inherit) 314 * B --> A (member) 315 * 316 * relate[A].put(B, Compose) 317 * relate[A].put(B, Compose) 318 * relate[A].put(B, Extend) 319 * relate[B].put(A, Compose) 320 * 321 * The relations are of the kind Fan-out, one-to-many. 322 * They can be sorted in descending fan-out-count order. 323 */ 324 class UMLClassDiagram { 325 @safe: 326 import std.typecons : NullableRef; 327 import std.format : FormatSpec; 328 329 alias ClassClassificationState = cpptooling.data.class_classification.State; 330 331 alias Key = USRType; 332 struct DisplayName { 333 string payload; 334 alias payload this; 335 } 336 337 struct Content { 338 string payload; 339 alias payload this; 340 } 341 342 private struct Class { 343 DisplayName displayName; 344 ClassClassificationState classification; 345 string[] content; 346 } 347 348 /// The class is only added if it doesn't already exist in the store. 349 void put(Key key, DisplayName display_name) { 350 if (key !in classes) { 351 classes[key] = Class(display_name); 352 relate_to[cast(Relate.Key) key] = Relate.init; 353 } 354 } 355 356 /** Store parameter content with the key. 357 * 358 * It is the body of the class in a class diagram. 359 */ 360 void put(Key key, Content content) 361 in { 362 assert(key in classes); 363 } 364 do { 365 classes[key].content ~= cast(string) content; 366 } 367 368 /** Set the classification of a class. 369 * 370 * Example would be a pure virtual, which in java would be an interface. 371 */ 372 void set(Key key, ClassClassificationState classification) 373 in { 374 assert(key in classes); 375 } 376 do { 377 classes[key].classification = classification; 378 } 379 380 /** Add a relation between two classes and increase the count on the class 381 * related TO. 382 */ 383 void relate(Key from, Key to, DisplayName display_name, Relate.Kind kind) 384 out { 385 assert(from in classes); 386 assert(to in classes); 387 assert(kind != Relate.Kind.None); 388 } 389 do { 390 put(to, display_name); 391 relate_to[cast(Relate.Key) from].put(cast(Relate.Key) to, kind); 392 } 393 394 /** Use to retrieve the relation struct for the key. 395 * 396 * Example: 397 * --- 398 * diagram.relateTo(Key("foo")).put(Key("bar"), Relate.Kind.Associate); 399 * --- 400 */ 401 Relate relateTo(Key k) pure 402 in { 403 assert(k in classes); 404 assert((cast(Relate.Key) k) in relate_to); 405 } 406 do { 407 return relate_to[cast(Relate.Key) k]; 408 } 409 410 /// Returns: Flat array of all relations of type FROM-KIND-TO-COUNT. 411 auto relateToFlatArray() pure @trusted { 412 import std.algorithm : map, joiner; 413 import std.array : array; 414 415 return relate_to.byKeyValue.map!(a => a.value.toFlatArray(a.key)).joiner().array(); 416 } 417 418 private static struct KeyClass { 419 Key key; 420 Class value; 421 } 422 423 /// Returns: An array of the key/values. 424 KeyClass[] asArray() pure nothrow @trusted { 425 import std.array : array; 426 import std.algorithm : map; 427 428 //TODO how to do this without so much generated GC 429 430 // dfmt off 431 return classes.byKeyValue 432 .map!(a => KeyClass(a.key, a.value)) 433 .array(); 434 // dfmt on 435 } 436 437 /// Returns: An array of the key/values sorted on key. 438 auto nameSortedRange() pure @trusted { 439 static string sortClassNameBy(T)(ref T a) { 440 return a.value.displayName; 441 } 442 443 return .nameSortedRange!(typeof(this), sortClassNameBy)(this); 444 } 445 446 private string[] classesToStringArray() pure @trusted { 447 import std.algorithm : map, joiner; 448 import std.array : array; 449 import std.ascii : newline; 450 import std.conv : text; 451 import std.format : format; 452 import std.range : only, chain, takeOne; 453 454 // dfmt off 455 return classes 456 .byKeyValue 457 .map!(a => chain(only(format("%s -> %s%s", 458 a.value.displayName, 459 a.key, 460 a.value.content.length == 0 ? "" : " {")), 461 a.value.content.dup.map!(b => " " ~ b), 462 a.value.content.takeOne.map!(b => "} // " ~ a.value.displayName)) 463 .joiner(newline) 464 .text) 465 .array(); 466 // dfmt on 467 } 468 469 private string[] relateToStringArray() pure @trusted { 470 import std.algorithm : map, joiner; 471 import std.array : array; 472 473 return relate_to.byKeyValue.map!(a => a.value.toStringArray(a.key)).joiner().array(); 474 } 475 476 void toString(Writer, Char)(scope Writer w, FormatSpec!Char) { 477 import std.ascii : newline; 478 import std.format : formattedWrite; 479 import std.range.primitives : put; 480 import std.range : zip, repeat; 481 482 formattedWrite(w, "UML Class Diagram (Total %d) {", classes.length); 483 put(w, newline); 484 foreach (a; zip(classesToStringArray, repeat(newline))) { 485 put(w, a[0]); 486 put(w, a[1]); 487 } 488 foreach (a; zip(relateToStringArray, repeat(newline))) { 489 put(w, a[0]); 490 put(w, a[1]); 491 } 492 put(w, "} // UML Class Diagram"); 493 } 494 495 override string toString() @safe pure { 496 import std.exception : assumeUnique; 497 import std.format : FormatSpec; 498 499 char[] buf; 500 buf.reserve(100); 501 auto fmt = FormatSpec!char("%s"); 502 toString((const(char)[] s) { buf ~= s; }, fmt); 503 auto trustedUnique(T)(T t) @trusted { 504 return assumeUnique(t); 505 } 506 507 return trustedUnique(buf); 508 } 509 510 private Relate[Relate.Key] relate_to; 511 private Class[Key] classes; 512 } 513 514 /** UML Component Diagram. 515 * 516 * Not designed for the general case. 517 * The design is what the plantuml plugin needs when analyzing more than one 518 * file. This is the container that is then passed between the analyze stages. 519 * 520 * The relations are of the kind Fan-out. 521 * 522 * See_Also: UMLClassDiagram 523 */ 524 class UMLComponentDiagram { 525 import std.container.rbtree : RedBlackTree; 526 527 struct Key { 528 string payload; 529 alias payload this; 530 } 531 532 struct Location { 533 string payload; 534 alias payload this; 535 } 536 537 struct DisplayName { 538 string payload; 539 alias payload this; 540 } 541 542 private struct Component { 543 DisplayName displayName; 544 string[] toFile; 545 RedBlackTree!Location contains; 546 } 547 548 /// The component is only added if it doesn't already exist in the store. 549 void put(Key key, DisplayName display_name) @safe { 550 if (key !in components) { 551 components[key] = Component(display_name, null, new RedBlackTree!Location); 552 relate_to[cast(Relate.Key) key] = Relate.init; 553 } 554 } 555 556 /// Add a location that the component encapsulate 557 void put(Key key, Location loc) @trusted 558 out { 559 assert(key in components); 560 } 561 do { 562 components[key].contains.insert(loc); 563 } 564 565 /** Add a relation between two components and increase the count on the class 566 * related TO. 567 */ 568 void relate(Key from, Key to, DisplayName toDisplayName, Relate.Kind kind) @safe 569 out { 570 assert(from in components); 571 assert(to in components); 572 assert(kind != Relate.Kind.None); 573 } 574 do { 575 put(to, toDisplayName); 576 577 auto rel = cast(Relate.Key) from in relate_to; 578 if (rel is null) { 579 relate_to[cast(Relate.Key) from] = Relate(); 580 rel = cast(Relate.Key) from in relate_to; 581 } 582 rel.put(cast(Relate.Key) to, kind); 583 584 auto comp = from in components; 585 if (comp is null) { 586 // TODO this is a hack. The display name should be the froms name. 587 components[from] = Component(cast(DisplayName) from, null, new RedBlackTree!Location); 588 comp = from in components; 589 } 590 comp.toFile ~= cast(string) to; 591 } 592 593 /** Use to retrieve the relation struct for the key. 594 * 595 * Example: 596 * --- 597 * diagram.relateTo(Key("foo")).put(Key("bar"), Relate.Kind.Associate); 598 * --- 599 */ 600 Relate relateTo(Key k) pure @safe 601 in { 602 assert(k in components); 603 assert((cast(Relate.Key) k) in relate_to); 604 } 605 do { 606 return relate_to[cast(Relate.Key) k]; 607 } 608 609 /// Return: Flat array of all relations of type FROM-KIND-TO-COUNT. 610 auto relateToFlatArray() pure @trusted { 611 import std.algorithm : map, joiner; 612 import std.array : array; 613 614 return relate_to.byKeyValue.map!(a => a.value.toFlatArray(a.key)).joiner().array(); 615 } 616 617 private static struct KeyComponent { 618 Key key; 619 Component value; 620 } 621 622 /// Returns: Flat array of all relations of type FROM-KIND-TO-COUNT. 623 KeyComponent[] asArray() pure nothrow @trusted { 624 import std.array : array; 625 import std.algorithm : map; 626 627 //TODO how to do this without so much generated GC 628 629 // dfmt off 630 return components.byKeyValue 631 .map!(a => KeyComponent(a.key, a.value)) 632 .array(); 633 // dfmt on 634 } 635 636 /// Returns: An array of the key/values sorted on key. 637 auto nameSortedRange() pure @trusted { 638 static string sortComponentNameBy(T)(ref T a) { 639 return cast(string) a.value.displayName; 640 } 641 642 return .nameSortedRange!(typeof(this), sortComponentNameBy)(this); 643 } 644 645 private string[] componentsToStringArray() pure @trusted { 646 import std.algorithm : map, joiner; 647 import std.ascii : newline; 648 import std.array : array; 649 import std.format : format; 650 import std.typecons : tuple; 651 652 // dfmt off 653 return nameSortedRange 654 .map!(a => tuple(a.key, a.value.displayName, a.value.contains[].map!(a => newline ~ " " ~ cast(string) a).joiner)) 655 .map!(a => format("%s as %s%s", a[0], 656 a[1], 657 a[2])).array(); 658 // dfmt on 659 } 660 661 private string[] relateToStringArray() pure @trusted { 662 import std.algorithm : map, joiner; 663 import std.array : array; 664 665 return relate_to.byKeyValue.map!(a => a.value.toStringArray(a.key)).joiner().array(); 666 } 667 668 /// String representation of the Component Diagram. 669 void toString(Writer)(scope Writer w) @safe { 670 import std.ascii : newline; 671 import std.format : formattedWrite; 672 import std.range.primitives : put; 673 import std.range : zip, repeat; 674 675 formattedWrite(w, "UML Component Diagram (Total %d) {", components.length); 676 put(w, newline); 677 foreach (a; zip(componentsToStringArray, repeat(newline))) { 678 put(w, a[0]); 679 put(w, a[1]); 680 } 681 foreach (a; zip(relateToStringArray, repeat(newline))) { 682 put(w, a[0]); 683 put(w, a[1]); 684 } 685 put(w, "} // UML Component Diagram"); 686 } 687 688 /// 689 override string toString() @safe { 690 import std.exception : assumeUnique; 691 692 char[] buf; 693 buf.reserve(100); 694 this.toString((const(char)[] s) { buf ~= s; }); 695 auto trustedUnique(T)(T t) @trusted { 696 return assumeUnique(t); 697 } 698 699 return trustedUnique(buf); 700 } 701 702 private: 703 Relate[Relate.Key] relate_to; 704 Component[Key] components; 705 } 706 707 @Name("Should be a None relate not shown and an extended relate") 708 unittest { 709 Relate r; 710 r.put(Relate.Key("B"), Relate.Kind.None); 711 r.put(Relate.Key("B"), Relate.Kind.Extend); 712 713 r.toStringArray(Relate.Key("A")).shouldEqual(["A -Extend- [1]B"]); 714 } 715 716 @Name("Should be all types of relates") 717 unittest { 718 Relate r; 719 r.put(Relate.Key("B"), Relate.Kind.None); 720 r.put(Relate.Key("B"), Relate.Kind.Extend); 721 r.put(Relate.Key("B"), Relate.Kind.Compose); 722 r.put(Relate.Key("B"), Relate.Kind.Aggregate); 723 r.put(Relate.Key("B"), Relate.Kind.Associate); 724 725 r.toStringArray(Relate.Key("A")).shouldEqual([ 726 "A -Extend- [1]B", "A -Compose- [1]B", "A -Aggregate- [1]B", 727 "A -Associate- [1]B" 728 ]); 729 } 730 731 @Name("Should be two relates to the same target") 732 unittest { 733 Relate r; 734 r.put(Relate.Key("B"), Relate.Kind.Compose); 735 r.put(Relate.Key("B"), Relate.Kind.Compose); 736 737 r.toStringArray(Relate.Key("A")).shouldEqual(["A -Compose- [2]B"]); 738 } 739 740 @Name("Should be a UML diagram with one class") 741 unittest { 742 auto uml = new UMLClassDiagram; 743 uml.put(UMLClassDiagram.Key("A"), UMLClassDiagram.DisplayName("A")); 744 745 uml.toString.shouldEqual("UML Class Diagram (Total 1) { 746 A -> A 747 } // UML Class Diagram"); 748 } 749 750 @Name("Should be a UML diagram with two classes related") 751 unittest { 752 auto uml = new UMLClassDiagram; 753 auto ka = UMLClassDiagram.Key("A"); 754 auto kb = UMLClassDiagram.Key("B"); 755 uml.put(ka, UMLClassDiagram.DisplayName("A")); 756 uml.put(kb, UMLClassDiagram.DisplayName("B")); 757 758 uml.relate(ka, kb, UMLClassDiagram.DisplayName("B"), Relate.Kind.Extend); 759 760 uml.toString.shouldEqual("UML Class Diagram (Total 2) { 761 A -> A 762 B -> B 763 A -Extend- [1]B 764 } // UML Class Diagram"); 765 } 766 767 @Name("Should be a UML Component diagram with two components related") 768 unittest { 769 auto uml = new UMLComponentDiagram; 770 auto ka = UMLComponentDiagram.Key("a"); 771 auto kb = UMLComponentDiagram.Key("b"); 772 uml.put(ka, cast(UMLComponentDiagram.DisplayName) "A"); 773 // shall be dedupliated 774 uml.put(ka, cast(UMLComponentDiagram.Location) "file.h"); 775 uml.put(ka, cast(UMLComponentDiagram.Location) "file.h"); 776 777 uml.relate(ka, kb, cast(UMLComponentDiagram.DisplayName) "B", Relate.Kind.Relate); 778 779 uml.toString.shouldEqual("UML Component Diagram (Total 2) { 780 a as A 781 file.h 782 b as B 783 a -Relate- [1]b 784 } // UML Component Diagram"); 785 } 786 787 /** Context for the UML diagram generator from internal representation to the 788 * concrete files. 789 */ 790 struct Generator { 791 import cpptooling.data : CppRoot; 792 import cpptooling.data.symbol : Container; 793 794 private static struct Modules { 795 static Modules make() @safe { 796 auto rval = Modules(new PlantumlModule, new PlantumlModule, 797 new PlantumlModule, new PlantumlModule); 798 rval.classes_dot.suppressIndent(1); 799 rval.components_dot.suppressIndent(1); 800 return rval; 801 } 802 803 PlantumlModule classes; 804 PlantumlModule classes_dot; 805 PlantumlModule components; 806 PlantumlModule components_dot; 807 } 808 809 /** Instansiate. 810 * 811 * Params: 812 * ctrl = dynamic control of data generation. 813 * params = static control, may never change during generation. 814 * products = receiver of UML diagrams. 815 */ 816 this(Controller ctrl, Parameters params, Products products) { 817 this.ctrl = ctrl; 818 this.params = params; 819 this.products = products; 820 this.umlClass = new UMLClassDiagram; 821 this.umlComponent = new UMLComponentDiagram; 822 } 823 824 /** Process the sources to produce UML diagrams in-memory. 825 * 826 * The diagrams are forwarded to the registered Products instance. 827 */ 828 auto process() { 829 auto m = Modules.make(); 830 generate(umlClass, umlComponent, params.doGenDot, m); 831 postProcess(ctrl, params, products, m); 832 } 833 834 /// The UML diagram used as source during generation. 835 UMLClassDiagram umlClass; 836 837 /// ditto 838 UMLComponentDiagram umlComponent; 839 840 private: 841 Controller ctrl; 842 Parameters params; 843 Products products; 844 845 static void postProcess(Controller ctrl, Parameters params, Products prods, Modules m) { 846 static PlantumlRootModule makeMinimalStyle(Flag!"genClassMethod" show_methods) { 847 auto proot = PlantumlRootModule.make(); 848 849 auto class_ = proot.makeUml; 850 class_.stmt("left to right direction"); 851 class_.stmt("'skinparam linetype polyline"); 852 class_.stmt("'skinparam linetype ortho"); 853 class_.stmt("set namespaceSeparator none"); 854 if (show_methods) { 855 class_.stmt("'hide members"); 856 } else { 857 class_.stmt("hide members"); 858 } 859 860 auto component = proot.makeUml; 861 component.stmt("left to right direction"); 862 component.stmt("skinparam componentStyle uml2"); 863 component.stmt("'skinparam linetype polyline"); 864 component.stmt("'skinparam linetype ortho"); 865 component.stmt("set namespaceSeparator none"); 866 component.stmt("hide circle"); 867 component.stmt("hide methods"); 868 component.stmt("'To hide file location"); 869 component.stmt("hide members"); 870 871 return proot; 872 } 873 874 enum DotLayout { 875 Neato, 876 Dot, 877 DotOrtho 878 } 879 880 static PlantumlModule makeDotPreamble(DotLayout layout, Flag!"doSmall" doSmall) { 881 auto m = new PlantumlModule; 882 m.suppressIndent(1); 883 884 //TODO if DotOrtho and Dot don't change consider removing the code 885 // duplication. 886 final switch (layout) with (DotLayout) { 887 case Neato: 888 m.stmt("layout=neato"); 889 m.stmt("edge [len=3]"); 890 break; 891 case DotOrtho: 892 m.stmt("layout=dot"); 893 m.stmt("rankdir=LR"); 894 m.stmt("pack=true"); 895 m.stmt("concentrate=true"); 896 // inactivating, can result in a crash as of 897 // dot 2.38.0 (20140413.2041) 898 m.stmt("// activate for orthogonal lines, aka straight lines"); 899 m.stmt("// but can result in GraphViz/dot crashing"); 900 m.stmt("//splines=ortho"); 901 break; 902 case Dot: 903 m.stmt("layout=dot"); 904 m.stmt("rankdir=LR"); 905 m.stmt("pack=true"); 906 m.stmt("concentrate=true"); 907 break; 908 } 909 910 m.sep(2); 911 912 m.stmt("colorscheme=svg"); 913 if (doSmall) { 914 m.stmt("node [style=rounded shape=box fontsize=9 width=0.25 height=0.375]"); 915 } else { 916 m.stmt("node [style=rounded shape=box]"); 917 } 918 m.sep(2); 919 920 return m; 921 } 922 923 enum StyleType { 924 Class, 925 Component 926 } 927 928 static PlantumlModule makeStyleInclude(Flag!"doStyleIncl" do_style_incl, 929 Path style_file, StyleType style_type) { 930 import std.conv : to; 931 932 auto m = new PlantumlModule; 933 if (!do_style_incl) { 934 return m; 935 } 936 937 m.stmt("!include " ~ style_file ~ "!" ~ to!string(cast(int) style_type)); 938 939 return m; 940 } 941 942 static void makeUml(Products prods, Path fname, PlantumlModule style, 943 PlantumlModule content) { 944 import std.algorithm : filter; 945 946 auto proot = PlantumlRootModule.make(); 947 auto c = proot.makeUml(); 948 c.suppressIndent(1); 949 950 foreach (m; [style, content].filter!(a => a !is null)) { 951 c.append(m); 952 } 953 954 prods.putFile(fname, proot); 955 } 956 957 static void makeDot(Products prods, Path fname, PlantumlModule style, 958 PlantumlModule content) { 959 import std.algorithm : filter; 960 import std.path : stripExtension, baseName; 961 962 immutable ext_dot = ".dot"; 963 964 auto fname_dot = Path(fname.stripExtension ~ ext_dot); 965 auto dot = new PlantumlModule; 966 auto digraph = dot.digraph("g"); 967 digraph.suppressThisIndent(1); 968 foreach (m; [style, content].filter!(a => a !is null)) { 969 digraph.append(m); 970 } 971 prods.putFile(fname_dot, dot); 972 973 auto proot = PlantumlRootModule.make(); 974 auto pu = proot.makeDot; 975 pu.stmt("!include " ~ (cast(string) fname_dot).baseName); 976 prods.putFile(fname, proot); 977 } 978 979 static Path makeDotFileName(Path f, DotLayout layout) { 980 import std.path : extension, stripExtension; 981 982 auto ext = extension(cast(string) f); 983 984 string suffix; 985 final switch (layout) with (DotLayout) { 986 case Dot: 987 goto case; 988 case DotOrtho: 989 suffix = "_dot"; 990 break; 991 case Neato: 992 suffix = "_neato"; 993 break; 994 } 995 996 return Path((cast(string) f).stripExtension ~ suffix ~ ext); 997 } 998 999 if (ctrl.genStyleInclFile) { 1000 prods.putFile(params.getFiles.styleOutput, makeMinimalStyle(params.genClassMethod)); 1001 } 1002 1003 if (params.doGenDot) { 1004 makeDot(prods, makeDotFileName(params.getFiles.classes, DotLayout.Dot), 1005 makeDotPreamble(DotLayout.Dot, Yes.doSmall), m.classes_dot); 1006 makeDot(prods, makeDotFileName(params.getFiles.classes, DotLayout.Neato), 1007 makeDotPreamble(DotLayout.Neato, Yes.doSmall), m.classes_dot); 1008 makeDot(prods, makeDotFileName(params.getFiles.components, DotLayout.Neato), 1009 makeDotPreamble(DotLayout.Neato, No.doSmall), m.components_dot); 1010 makeDot(prods, makeDotFileName(params.getFiles.components, DotLayout.DotOrtho), 1011 makeDotPreamble(DotLayout.DotOrtho, No.doSmall), m.components_dot); 1012 } 1013 1014 makeUml(prods, params.getFiles.classes, makeStyleInclude(params.doStyleIncl, 1015 params.getFiles.styleIncl, StyleType.Class), m.classes); 1016 makeUml(prods, params.getFiles.components, makeStyleInclude(params.doStyleIncl, 1017 params.getFiles.styleIncl, StyleType.Component), m.components); 1018 } 1019 } 1020 1021 private struct ClassClassificationResult { 1022 TypeKindAttr type; 1023 cpptooling.data.class_classification.State classification; 1024 } 1025 1026 private final class UMLClassVisitor(ControllerT, ReceiveT) : Visitor { 1027 import std.algorithm : map, copy, each, joiner; 1028 import std.array : Appender; 1029 import std.typecons : scoped, NullableRef; 1030 1031 import libclang_ast.ast : ClassDecl, CxxBaseSpecifier, Constructor, Destructor, 1032 CxxMethod, FieldDecl, CxxAccessSpecifier, generateIndentIncrDecr; 1033 import cpptooling.analyzer.clang.analyze_helper : analyzeRecord, analyzeConstructor, analyzeDestructor, 1034 analyzeCxxMethod, analyzeFieldDecl, analyzeCxxBaseSpecified, toAccessType; 1035 import libclang_ast.cursor_logger : logNode, mixinNodeLog; 1036 import cpptooling.data : CppNsStack, CppNs, AccessType, CppAccess, MemberVirtualType; 1037 1038 import cpptooling.data.class_classification : ClassificationState = State; 1039 import cpptooling.data.class_classification : classifyClass, MethodKind; 1040 1041 alias visit = Visitor.visit; 1042 1043 mixin generateIndentIncrDecr; 1044 1045 /** Type representation of this class. 1046 * Used as the source of the outgoing relations from this class. 1047 */ 1048 TypeKindAttr type; 1049 1050 /** Classification of the class. 1051 * Affected by methods. 1052 */ 1053 ClassificationState classification; 1054 1055 private { 1056 ControllerT ctrl; 1057 NullableRef!ReceiveT recv; 1058 1059 Container* container; 1060 CppNsStack ns_stack; 1061 CppAccess access; 1062 1063 /// If the class has any members. 1064 Flag!"hasMember" hasMember; 1065 } 1066 1067 this(TypeKindAttr type, CppNs[] reside_in_ns, ControllerT ctrl, 1068 ref ReceiveT recv, ref Container container, in uint indent) { 1069 this.ctrl = ctrl; 1070 this.recv = &recv; 1071 this.container = &container; 1072 this.indent = indent; 1073 this.ns_stack = CppNsStack(reside_in_ns.dup); 1074 1075 this.access = CppAccess(AccessType.Private); 1076 this.classification = ClassificationState.Unknown; 1077 1078 this.type = type; 1079 } 1080 1081 /// Nested class definitions. 1082 override void visit(scope const ClassDecl v) { 1083 mixin(mixinNodeLog!()); 1084 logger.info("class: ", v.cursor.spelling); 1085 1086 auto result = analyzeRecord(v, *container, indent); 1087 1088 foreach (loc; container.find!LocationTag(result.type.kind.usr).map!(a => a.any).joiner) { 1089 if (!ctrl.doFile(loc.file, loc.file)) { 1090 return; 1091 } 1092 } 1093 1094 recv.put(result, ns_stack); 1095 1096 scope visitor = new UMLClassVisitor!(ControllerT, ReceiveT)(result.type, 1097 ns_stack, ctrl, recv, *container, indent + 1); 1098 v.accept(visitor); 1099 1100 auto result_class = ClassClassificationResult(visitor.type, visitor.classification); 1101 recv.put(this.type, result_class); 1102 } 1103 1104 /// Analyze the inheritance(s). 1105 override void visit(scope const CxxBaseSpecifier v) { 1106 import cpptooling.data : TypeKind; 1107 1108 mixin(mixinNodeLog!()); 1109 1110 auto result = analyzeCxxBaseSpecified(v, *container, indent); 1111 1112 debug { 1113 import std.algorithm : each; 1114 import std.range : retro; 1115 import cpptooling.data : CppInherit; 1116 1117 auto inherit = CppInherit(result.name, result.access); 1118 retro(result.reverseScope).each!(a => inherit.put(a)); 1119 1120 logger.trace("inherit: ", inherit.toString); 1121 } 1122 1123 recv.put(this.type, result); 1124 } 1125 1126 override void visit(scope const Constructor v) { 1127 mixin(mixinNodeLog!()); 1128 1129 auto result = analyzeConstructor(v, *container, indent); 1130 1131 debug { 1132 auto tor = CppCtor(result.type.kind.usr, result.name, result.params, access); 1133 logger.trace("ctor: ", tor.toString); 1134 } 1135 1136 recv.put(this.type, result, access); 1137 } 1138 1139 override void visit(scope const Destructor v) { 1140 mixin(mixinNodeLog!()); 1141 1142 auto result = analyzeDestructor(v, *container, indent); 1143 classification = classifyClass(classification, MethodKind.Dtor, 1144 cast(MemberVirtualType) result.virtualKind, hasMember); 1145 1146 debug { 1147 auto tor = CppDtor(result.type.kind.usr, result.name, access, result.virtualKind); 1148 logger.trace("dtor: ", tor.toString); 1149 } 1150 1151 recv.put(this.type, result, access); 1152 } 1153 1154 override void visit(scope const CxxMethod v) { 1155 mixin(mixinNodeLog!()); 1156 1157 auto result = analyzeCxxMethod(v, *container, indent); 1158 1159 classification = classifyClass(classification, MethodKind.Method, 1160 cast(MemberVirtualType) result.virtualKind, hasMember); 1161 1162 debug { 1163 import cpptooling.data.type : CppConstMethod; 1164 import cpptooling.data : CppMethod; 1165 1166 auto method = CppMethod(result.type.kind.usr, result.name, result.params, 1167 result.returnType, access, CppConstMethod(result.isConst), result.virtualKind); 1168 logger.trace("method: ", method.toString); 1169 } 1170 1171 recv.put(this.type, result, access); 1172 } 1173 1174 override void visit(scope const FieldDecl v) { 1175 mixin(mixinNodeLog!()); 1176 1177 auto result = analyzeFieldDecl(v, *container, indent); 1178 1179 // TODO probably not necessary for classification to store it as a 1180 // member. Instead extend MethodKind to having a "Member". 1181 hasMember = Yes.hasMember; 1182 classification = classifyClass(classification, MethodKind.Unknown, 1183 MemberVirtualType.Unknown, hasMember); 1184 debug { 1185 logger.trace("member: ", cast(string) result.name); 1186 } 1187 1188 recv.put(this.type, result, access); 1189 } 1190 1191 override void visit(scope const CxxAccessSpecifier v) { 1192 mixin(mixinNodeLog!()); 1193 access = CppAccess(toAccessType(v.cursor.access.accessSpecifier)); 1194 } 1195 } 1196 1197 final class UMLVisitor(ControllerT, ReceiveT) : Visitor { 1198 import std.algorithm : map, filter, cache, joiner; 1199 import std.range : chain, only, dropOne, ElementType; 1200 import std.typecons : scoped, NullableRef; 1201 1202 import libclang_ast.ast : TranslationUnit, UnexposedDecl, VarDecl, 1203 FunctionDecl, ClassDecl, Namespace, generateIndentIncrDecr; 1204 import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl, 1205 analyzeVarDecl, analyzeRecord, analyzeTranslationUnit; 1206 import libclang_ast.cursor_logger : logNode, mixinNodeLog; 1207 import cpptooling.data : CppNsStack, CppNs; 1208 1209 alias visit = Visitor.visit; 1210 1211 mixin generateIndentIncrDecr; 1212 1213 private { 1214 ReceiveT recv; 1215 ControllerT ctrl; 1216 1217 NullableRef!Container container; 1218 CppNs[] ns_stack; 1219 1220 } 1221 1222 this(ControllerT ctrl, ref ReceiveT recv, ref Container container) { 1223 this.ctrl = ctrl; 1224 this.recv = recv; 1225 this.container = &container; 1226 } 1227 1228 override void visit(scope const TranslationUnit v) { 1229 mixin(mixinNodeLog!()); 1230 v.accept(this); 1231 1232 auto result = analyzeTranslationUnit(v, container, indent); 1233 recv.put(result); 1234 } 1235 1236 override void visit(scope const UnexposedDecl v) { 1237 mixin(mixinNodeLog!()); 1238 1239 // An unexposed may be: 1240 1241 // an extern "C" 1242 // UnexposedDecl "" extern "C" {... 1243 // FunctionDecl "fun_c_linkage" void func_c_linkage 1244 v.accept(this); 1245 } 1246 1247 override void visit(scope const VarDecl v) { 1248 mixin(mixinNodeLog!()); 1249 1250 auto result = analyzeVarDecl(v, container, indent); 1251 1252 debug { 1253 logger.info("global variable: ", cast(string) result.name); 1254 } 1255 1256 recv.put(result); 1257 } 1258 1259 override void visit(scope const FunctionDecl v) { 1260 mixin(mixinNodeLog!()); 1261 1262 auto result = analyzeFunctionDecl(v, container, indent); 1263 1264 debug { 1265 auto func = CFunction(result.type.kind.usr, result.name, result.params, 1266 CxReturnType(result.returnType), result.isVariadic, result.storageClass); 1267 logger.info("function: ", func.toString); 1268 } 1269 1270 recv.put(result); 1271 } 1272 1273 override void visit(scope const ClassDecl v) @trusted { 1274 mixin(mixinNodeLog!()); 1275 logger.info("class: ", v.cursor.spelling); 1276 1277 auto result = analyzeRecord(v, container, indent); 1278 1279 foreach (loc; container.find!LocationTag(result.type.kind.usr).map!(a => a.any).joiner) { 1280 if (!ctrl.doFile(loc.file, loc.file)) { 1281 return; 1282 } 1283 } 1284 1285 recv.put(result, ns_stack); 1286 1287 auto visitor = scoped!(UMLClassVisitor!(ControllerT, ReceiveT))(result.type, 1288 ns_stack, ctrl, recv, container, indent + 1); 1289 v.accept(visitor); 1290 1291 auto r_classification = ClassClassificationResult(visitor.type, visitor.classification); 1292 recv.put(r_classification); 1293 } 1294 1295 override void visit(scope const Namespace v) { 1296 mixin(mixinNodeLog!()); 1297 1298 () @trusted { ns_stack ~= CppNs(v.cursor.spelling); }(); 1299 // pop the stack when done 1300 scope (exit) 1301 ns_stack = ns_stack[0 .. $ - 1]; 1302 1303 // fill the namespace with content from the analyse 1304 v.accept(this); 1305 } 1306 } 1307 1308 private struct TransformToClassDiagram(ControllerT, LookupT) { 1309 @safe: 1310 import cpptooling.analyzer.clang.analyze_helper : CxxMethodResult, 1311 ConstructorResult, DestructorResult, FieldDeclResult, CxxBaseSpecifierResult; 1312 import cpptooling.data.type : CppAccess; 1313 import cpptooling.data.type : CppNs; 1314 1315 invariant { 1316 assert(uml !is null); 1317 } 1318 1319 private { 1320 UMLClassDiagram uml; 1321 ControllerT ctrl; 1322 LookupT lookup; 1323 } 1324 1325 /// If class methods should be part of the generated class diagrams. 1326 Flag!"genClassMethod" genClassMethod; 1327 1328 /// If the parameters of methods should result in directed association. 1329 Flag!"genClassParamDependency" genClassParamDependency; 1330 1331 /// If the inheritance hierarchy between classes is generated. 1332 Flag!"genClassInheritDependency" genClassInheritDependency; 1333 1334 /// If the class members result in dependency on those members. 1335 Flag!"genClassMemberDependency" genClassMemberDependency; 1336 1337 private static string toPrefix(CppAccess access) { 1338 import cpptooling.data.type : CppAccess, AccessType; 1339 1340 final switch (access) { 1341 case AccessType.Public: 1342 return "+"; 1343 case AccessType.Protected: 1344 return "#"; 1345 case AccessType.Private: 1346 return "-"; 1347 } 1348 } 1349 1350 void put(ref TypeKindAttr src, ref CxxBaseSpecifierResult result) { 1351 import std.algorithm : map, joiner; 1352 import std.conv : text; 1353 import std.range : chain, only, retro; 1354 import cpptooling.data : TypeKind, TypeAttr, toStringDecl; 1355 1356 if (genClassInheritDependency) { 1357 auto src_key = makeClassKey(src.kind.usr); 1358 1359 auto canonical = lookup.kind(result.canonicalUSR).front; 1360 auto dest_key = makeClassKey(canonical.usr); 1361 auto fqn = canonical.toStringDecl(TypeAttr.init); 1362 1363 uml.relate(src_key, dest_key, 1364 cast(UMLClassDiagram.DisplayName) fqn, Relate.Kind.Extend); 1365 } 1366 } 1367 1368 /// Reconstruct the function signature as a UML comment. 1369 void put(ref TypeKindAttr src, ref CxxMethodResult result, in CppAccess access) { 1370 import std.algorithm : filter; 1371 import std.traits : ReturnType; 1372 import std.range : chain, only; 1373 1374 import cpptooling.data : CppMethod, CppConstMethod; 1375 1376 ReturnType!makeClassKey src_key; 1377 1378 if (genClassMethod || genClassParamDependency) { 1379 src_key = makeClassKey(src.kind.usr); 1380 } 1381 1382 if (genClassMethod) { 1383 auto method = CppMethod(USRType("dummy"), result.name, result.params, 1384 result.returnType, access, CppConstMethod(result.isConst), result.virtualKind); 1385 method.usr.nullify; 1386 uml.put(src_key, UMLClassDiagram.Content(toPrefix(access) ~ method.toString)); 1387 } 1388 1389 if (genClassParamDependency) { 1390 // dfmt off 1391 auto relations = 1392 chain(getClassMethodRelation(result.params, lookup), 1393 only(getTypeRelation(cast(TypeKindAttr) result.returnType, lookup))) 1394 .filter!(a => a.kind != Relate.Kind.None) 1395 // remove self referencing keys, would result in circles which 1396 // just clutters the diagrams 1397 .filter!(a => a.key != src.kind.usr); 1398 // dfmt on 1399 foreach (rel; relations) { 1400 auto dest_key = makeClassKey(rel.key); 1401 uml.relate(src_key, dest_key, rel.display, rel.kind); 1402 } 1403 } 1404 } 1405 1406 void put(ref TypeKindAttr src, ref ConstructorResult result, in CppAccess access) { 1407 import std.algorithm : filter; 1408 import std.traits : ReturnType; 1409 1410 import cpptooling.data : CppCtor; 1411 1412 ReturnType!makeClassKey src_key; 1413 1414 if (genClassMethod || genClassParamDependency) { 1415 src_key = makeClassKey(src.kind.usr); 1416 } 1417 1418 if (genClassMethod) { 1419 auto tor = CppCtor(result.type.kind.usr, result.name, result.params, access); 1420 uml.put(src_key, UMLClassDiagram.Content(toPrefix(access) ~ tor.toString)); 1421 } 1422 1423 if (genClassParamDependency) { 1424 // dfmt off 1425 auto relations = getClassMethodRelation(result.params, lookup) 1426 .filter!(a => a.kind != Relate.Kind.None) 1427 // remove self referencing keys, would result in circles which 1428 // just clutters the diagrams 1429 .filter!(a => a.key != src.kind.usr); 1430 // dfmt on 1431 foreach (rel; relations) { 1432 auto dest_key = makeClassKey(rel.key); 1433 uml.relate(src_key, dest_key, rel.display, rel.kind); 1434 } 1435 } 1436 } 1437 1438 void put(ref TypeKindAttr src, ref DestructorResult result, in CppAccess access) { 1439 import cpptooling.data : CppDtor; 1440 1441 if (genClassMethod) { 1442 auto key = makeClassKey(src.kind.usr); 1443 auto tor = CppDtor(result.type.kind.usr, result.name, access, result.virtualKind); 1444 uml.put(key, UMLClassDiagram.Content(toPrefix(access) ~ tor.toString)); 1445 } 1446 } 1447 1448 void put(ref TypeKindAttr src, ref FieldDeclResult result, in CppAccess access) { 1449 import std.algorithm : filter; 1450 1451 if (genClassMemberDependency) { 1452 auto rel = getClassMemberRelation(result.type, lookup); 1453 if (rel.kind != Relate.Kind.None) { 1454 auto src_key = makeClassKey(src.kind.usr); 1455 auto dest_key = makeClassKey(rel.key); 1456 uml.relate(src_key, dest_key, rel.display, rel.kind); 1457 } 1458 } 1459 } 1460 1461 void put(ref ClassClassificationResult result) { 1462 auto key = makeClassKey(result.type.kind.usr); 1463 uml.set(key, result.classification); 1464 } 1465 1466 void put(ref RecordResult src, CppNs[] reside_in) { 1467 import std.algorithm : map, joiner; 1468 import std.conv : text; 1469 import std.range : chain, only; 1470 1471 auto key = makeClassKey(src.type.kind.usr); 1472 string fqn = chain(reside_in.map!(a => cast(string) a), only(cast(string) src.name)).joiner("::") 1473 .text; 1474 uml.put(key, cast(UMLClassDiagram.DisplayName) fqn); 1475 } 1476 } 1477 1478 /** Transform data from a data source (via push) to a UML component diagram. 1479 * 1480 * The component diagram is built upon the assumption that the physical 1481 * location of a declaration/definition has a correlation to the design the 1482 * creator had in mind. 1483 * 1484 * Physical world -> mental model. 1485 * 1486 * Design of relations transform: 1487 * A relation is based on where the identifier is located to the owner of the 1488 * type. 1489 * Identifier-location -> Type-owner-location. 1490 * 1491 * A type-owner-location is where the type is defined. 1492 * This though creates a problem when considering forward declarations in 1493 * combination with pointers, references, parameters. 1494 * 1495 * To handle the above case relations are go through three steps. 1496 * - Add relations with USR->USR. 1497 * - First try. Check both USRs location. If both of them are definitions then 1498 * accept the relation. Otherwise put it into the cache. 1499 * - Second try. Process the cache at the end of a translation unit. Same 1500 * criteria as the first try. 1501 * - Third try. When all translation units have been processed use a fallback 1502 * strategy for those items left in the cache. At this stage a location 1503 * corresponding to a declaration is OK. Reason, better than nothing. 1504 * 1505 * In the following example the processing is a.h before b.h. 1506 * If the locatoin of the forward declaration of B had been used the relation 1507 * from a.h to b.h would have been lost. 1508 * 1509 * Example: 1510 * a.h 1511 * --- 1512 * class B; 1513 * class A { 1514 * B* b; 1515 * }; 1516 * --- 1517 * 1518 * b.h 1519 * --- 1520 * class B {}; 1521 * --- 1522 */ 1523 private @safe struct TransformToComponentDiagram(ControllerT, LookupT) { 1524 import std.algorithm : map, copy, each, joiner; 1525 import std.range : chain; 1526 1527 import cpptooling.analyzer.clang.analyze_helper : CxxBaseSpecifierResult, 1528 CxxMethodResult, ConstructorResult, DestructorResult, 1529 RecordResult, FieldDeclResult, VarDeclResult, FunctionDeclResult, TranslationUnitResult; 1530 import cpptooling.data.symbol : Container; 1531 import cpptooling.data : CppAccess, CxReturnType; 1532 1533 invariant { 1534 assert(diagram !is null); 1535 assert(ctrl !is null); 1536 } 1537 1538 private { 1539 static struct USRRelation { 1540 USRType from; 1541 USRType to; 1542 Relate.Kind kind; 1543 } 1544 1545 UMLComponentDiagram diagram; 1546 ControllerT ctrl; 1547 LookupT lookup; 1548 MarkArray!USRRelation dcache; 1549 USRType[] src_cache; 1550 } 1551 1552 this(UMLComponentDiagram diagram, ControllerT ctrl, LookupT lookup) { 1553 this.diagram = diagram; 1554 this.ctrl = ctrl; 1555 this.lookup = lookup; 1556 } 1557 1558 /** Store the relations in the cache for later resolution regarding there 1559 * location. 1560 * 1561 * The concept is a source has relations to many destinations. 1562 * 1563 * The relation is hard coded as an Association. 1564 * If the function is generalized to be reused with Class then the hard 1565 * coded must be a lookup table or something to allow differentiating 1566 * depending on "stuff". 1567 * 1568 * It is by design that the src do NOT go via resolveCanonicalType. A free 1569 * variable that is a pointer shall have the "src" still as the pointer 1570 * itself but the destination is the pointed at type. 1571 * 1572 * Params: 1573 * src = source of the relations 1574 * range = destinations of the relations 1575 * target = cache to put the values into 1576 * lookup = type supporting lookups via USR for the TypeKind 1577 */ 1578 static void putToCache(Range, T)(USRType src, Range range, ref T target, LookupT lookup) @trusted { 1579 import std.algorithm : filter; 1580 1581 // dfmt off 1582 foreach(a; range 1583 // remove primitive types 1584 .filter!(a => a.kind.info.match!((TypeKind.PrimitiveInfo) => false, _ => true)) 1585 .map!(a => resolveCanonicalType(a.kind, a.attr, lookup)) 1586 .joiner 1587 .map!(a => a.kind.usr) 1588 // create the relations of type src-to-kind 1589 .map!(to_ => USRRelation(src, to_, Relate.Kind.Associate))) { 1590 target.put(a); 1591 } 1592 // dfmt on 1593 } 1594 1595 /// ditto 1596 static void putParamsToCache(T)(ref TypeKindAttr src, CxParam[] params, 1597 ref T target, LookupT lookup) @trusted { 1598 // dfmt off 1599 auto range = params 1600 // returns a bunch of ranges of the unpacked parameters 1601 .map!(a => unpackParam(a)) 1602 .joiner; 1603 // dfmt on 1604 1605 putToCache(src.kind.usr, range, target, lookup); 1606 } 1607 1608 static void finalizeSrcCache(LookupT, TargetT)(USRType[] cache, LookupT lookup, TargetT target) { 1609 import std.algorithm : map, joiner; 1610 1611 // dfmt off 1612 foreach (loc; cache 1613 .map!(usr => lookup.location(usr)) 1614 .joiner 1615 .map!(a => a.any) 1616 .joiner) { 1617 target.putSrc(loc); 1618 } 1619 // dfmt on 1620 } 1621 1622 /// Process the last bits left in the cache. 1623 void finalize() { 1624 import std.algorithm : map, filter, cache; 1625 import std.range : enumerate, only; 1626 import std.typecons : tuple; 1627 1628 finalizeSrcCache(src_cache[], lookup, this); 1629 if (src_cache.length > 0) { 1630 logger.tracef("%d relations left in src cache", src_cache.length); 1631 } 1632 src_cache.length = 0; 1633 1634 if (dcache.data.length > 0) { 1635 logger.tracef("%d relations left. Activating fallback strategy", dcache.data.length); 1636 } 1637 1638 // dfmt off 1639 foreach (e; dcache.data 1640 // keep track of the index to allow marking of the cache for removal 1641 .enumerate 1642 // find the types 1643 .map!(a => tuple(a.index, lookup.location(a.value.from), lookup.location(a.value.to))) 1644 .cache 1645 // a zero range means a failed lookup, a broken relation 1646 .filter!(a => a[1].length != 0 && a[2].length != 0) 1647 // unpack with fallback 1648 .map!(a => tuple(a[0], a[1].front.any, a[2].front.any)) 1649 // ensure that both both resulted in valid ranges 1650 .filter!(a => a[1].length != 0 && a[2].length != 0) 1651 // unpack 1652 .map!(a => tuple(a[0], a[1].front, a[2].front)) 1653 // check via ctrl (the user) if the destination is "ok" 1654 .filter!(a => ctrl.doFile(cast(string) a[2].file, cast(string) a[2].file)) 1655 ) { 1656 //TODO warn when a declaration has been used? 1657 1658 putDest(e[1], e[2], Relate.Kind.Associate); 1659 dcache.markForRemoval(e[0]); 1660 } 1661 // dfmt on 1662 1663 dcache.doRemoval; 1664 1665 if (dcache.data.length > 0) { 1666 logger.errorf("Fallback strategy failed for %d USRs. They are:", dcache.data.length); 1667 } 1668 1669 foreach (e; dcache.data) { 1670 logger.tracef(" %s -> %s", cast(string) e.from, cast(string) e.to); 1671 } 1672 } 1673 1674 void put(ref TranslationUnitResult result) { 1675 import std.algorithm : map, filter, cache; 1676 import std.range : enumerate, only; 1677 import std.typecons : tuple; 1678 1679 finalizeSrcCache(src_cache[], lookup, this); 1680 if (src_cache.length > 0) { 1681 logger.tracef("%d relations left in src cache", src_cache.length); 1682 } 1683 src_cache.length = 0; 1684 1685 // dfmt off 1686 foreach (e; dcache.data 1687 // keep track of the index to allow marking of the cache for removal 1688 .enumerate 1689 // find the types 1690 .map!(a => tuple(a.index, lookup.location(a.value.from), lookup.location(a.value.to))) 1691 .cache 1692 // a zero range means a failed lookup, a broken relation 1693 .filter!(a => a[1].length != 0 && a[2].length != 0) 1694 // unpack 1695 .map!(a => tuple(a[0], a[1].front, a[2].front)) 1696 // only okey with a relatioin TO something that is a definition 1697 .filter!(a => a[1].hasDefinition && a[2].hasDefinition) 1698 // check via ctrl (the user) if the destination is "ok" 1699 .filter!(a => ctrl.doFile(cast(string) a[2].definition.file, cast(string) a[2].definition.file)) 1700 ) { 1701 putDest(e[1].definition, e[2].definition, Relate.Kind.Associate); 1702 dcache.markForRemoval(e[0]); 1703 } 1704 // dfmt on 1705 1706 dcache.doRemoval; 1707 } 1708 1709 void put(ref RecordResult result) { 1710 src_cache ~= result.type.kind.usr; 1711 } 1712 1713 void put(ref TypeKindAttr src, ref ConstructorResult result, in CppAccess access) { 1714 putParamsToCache(src, result.params, dcache, lookup); 1715 } 1716 1717 void put(ref TypeKindAttr src, ref CxxMethodResult result, in CppAccess access) { 1718 import std.range : only; 1719 1720 putParamsToCache(src, result.params, dcache, lookup); 1721 putToCache(src.kind.usr, only((result.returnType)), dcache, lookup); 1722 } 1723 1724 void put(ref TypeKindAttr src, ref FieldDeclResult result, in CppAccess access) { 1725 import std.range : only; 1726 1727 putToCache(src.kind.usr, only(result.type), dcache, lookup); 1728 } 1729 1730 void put(ref TypeKindAttr src, ref ClassClassificationResult result) { 1731 import std.range : only; 1732 1733 // called when creating a relation for a nested class 1734 putToCache(src.kind.usr, only(result.type), dcache, lookup); 1735 } 1736 1737 void put(ref TypeKindAttr src, ref CxxBaseSpecifierResult result) { 1738 auto r0 = lookup.kind(result.canonicalUSR).map!(a => TypeKindAttr(a.get, TypeAttr.init)); 1739 1740 putToCache(src.kind.usr, r0, dcache, lookup); 1741 } 1742 1743 void put(ref VarDeclResult result) { 1744 import std.range : only; 1745 1746 // primitive types do not have a location 1747 if (result.location.kind == LocationTag.Kind.loc) { 1748 putSrc(result.location); 1749 1750 putToCache(result.instanceUSR, only(result.type), dcache, lookup); 1751 } 1752 } 1753 1754 void put(ref FunctionDeclResult result) { 1755 import std.range : only; 1756 1757 src_cache ~= result.type.kind.usr; 1758 1759 putParamsToCache(result.type, result.params, dcache, lookup); 1760 putToCache(result.type.kind.usr, only(result.returnType), dcache, lookup); 1761 } 1762 1763 void putSrc(ref LocationTag src) @safe { 1764 string location = src.file; 1765 1766 if (src.kind == LocationTag.Kind.noloc || !ctrl.doFile(location, location)) { 1767 return; 1768 } 1769 1770 auto key = makeComponentKey(location, ctrl); 1771 diagram.put(key.key, cast(UMLComponentDiagram.DisplayName) key.display); 1772 diagram.put(key.key, cast(UMLComponentDiagram.Location) location); 1773 } 1774 1775 void putDest(LocationTag src, LocationTag dest, Relate.Kind kind) { 1776 auto src_ = makeComponentKey(src.file, ctrl); 1777 auto dest_ = makeComponentKey(dest.file, ctrl); 1778 1779 // Ignoring self referencing relations. 1780 if (src_.key == dest_.key) { 1781 return; 1782 } 1783 1784 diagram.relate(src_.key, dest_.key, 1785 cast(UMLComponentDiagram.DisplayName) dest_.display, kind); 1786 } 1787 } 1788 1789 /** Route information to specific transformers. 1790 * 1791 * No manipulation of data is to be done in this struct. Only routing to 1792 * appropriate functions. 1793 */ 1794 class TransformToDiagram(ControllerT, ParametersT, LookupT) { 1795 import std.range : only; 1796 1797 import cpptooling.analyzer.clang.analyze_helper : CxxBaseSpecifierResult, 1798 RecordResult, FieldDeclResult, CxxMethodResult, 1799 ConstructorResult, DestructorResult, VarDeclResult, FunctionDeclResult, 1800 TranslationUnitResult; 1801 import cpptooling.data.symbol.types : USRType; 1802 import cpptooling.data : TypeKind, CppNs, CppAccess; 1803 1804 private { 1805 TransformToComponentDiagram!(ControllerT, LookupT) to_component; 1806 TransformToClassDiagram!(ControllerT, LookupT) to_class; 1807 } 1808 1809 this(ControllerT ctrl, ParametersT params, LookupT lookup, 1810 UMLComponentDiagram comp_dia, UMLClassDiagram class_dia) { 1811 to_component = typeof(to_component)(comp_dia, ctrl, lookup); 1812 to_class = typeof(to_class)(class_dia, ctrl, lookup, params.genClassMethod, 1813 params.genClassParamDependency, params.genClassInheritDependency, 1814 params.genClassMemberDependency); 1815 } 1816 1817 @safe: 1818 1819 /** Signal that diagrams to perform a finalization of cached data. 1820 */ 1821 void finalize() { 1822 to_component.finalize(); 1823 } 1824 1825 void put(ref TranslationUnitResult result) { 1826 to_component.put(result); 1827 } 1828 1829 void put(ref RecordResult result, CppNs[] reside_in) { 1830 to_class.put(result, reside_in); 1831 to_component.put(result); 1832 } 1833 1834 void put(ref TypeKindAttr src, ref CxxBaseSpecifierResult result) { 1835 to_class.put(src, result); 1836 to_component.put(src, result); 1837 } 1838 1839 void put(ref TypeKindAttr src, ref CxxMethodResult result, in CppAccess access) { 1840 to_class.put(src, result, access); 1841 to_component.put(src, result, access); 1842 } 1843 1844 void put(ref TypeKindAttr src, ref ConstructorResult result, in CppAccess access) { 1845 to_class.put(src, result, access); 1846 to_component.put(src, result, access); 1847 } 1848 1849 void put(ref TypeKindAttr src, ref DestructorResult result, in CppAccess access) { 1850 to_class.put(src, result, access); 1851 } 1852 1853 void put(ref TypeKindAttr src, ref FieldDeclResult result, in CppAccess access) { 1854 to_class.put(src, result, access); 1855 to_component.put(src, result, access); 1856 } 1857 1858 void put(ref ClassClassificationResult result) { 1859 to_class.put(result); 1860 } 1861 1862 /** A nested class. 1863 * 1864 * Propagate the classification and relation of the root->nested. 1865 */ 1866 void put(ref TypeKindAttr src, ref ClassClassificationResult result) { 1867 to_component.put(src, result); 1868 // only needs result 1869 to_class.put(result); 1870 } 1871 1872 void put(ref VarDeclResult result) { 1873 to_component.put(result); 1874 } 1875 1876 void put(ref FunctionDeclResult result) { 1877 to_component.put(result); 1878 } 1879 } 1880 1881 // visualize where the module private starts 1882 private: // ****************************************************************** 1883 1884 import cpptooling.data.representation : CppRoot, CppClass, CppMethod, CppCtor, 1885 CppDtor, CppNamespace, CFunction, CxGlobalVariable, LocationTag, Location; 1886 import cpptooling.data.symbol : Container; 1887 import dsrcgen.plantuml; 1888 1889 struct KeyValue { 1890 UMLComponentDiagram.Key key; 1891 string display; 1892 string absFilePath; 1893 } 1894 1895 struct KeyRelate { 1896 string file; 1897 KeyValue key; 1898 Relate.Kind kind; 1899 } 1900 1901 /** 1902 * Params: 1903 * file = filename of the relation. 1904 * kind = kind of relation such as associaiton, composition etc. 1905 */ 1906 struct PathKind { 1907 string file; 1908 Relate.Kind kind; 1909 } 1910 1911 /** Calculate the key based on the directory the file that declares the symbol exist in. 1912 * 1913 * Additional metadata as to make it possible to backtrack. 1914 */ 1915 KeyValue makeComponentKey(in string location_file, Controller ctrl) @trusted { 1916 import std.array : appender; 1917 import std.base64 : Base64Impl, Base64; 1918 import std.path : buildNormalizedPath, absolutePath, relativePath, baseName; 1919 import std.typecons : tuple; 1920 1921 // TODO consider using hash murmur2/3 to shorten the length of the encoded 1922 // path 1923 1924 alias SafeBase64 = Base64Impl!('-', '_', Base64.NoPadding); 1925 1926 string file_path = buildNormalizedPath(location_file.absolutePath); 1927 string strip_path = cast(string) ctrl.doComponentNameStrip(Path(file_path)); 1928 string rel_path = relativePath(strip_path); 1929 string display_name = strip_path.baseName; 1930 1931 auto enc = appender!(char[])(); 1932 SafeBase64.encode(cast(ubyte[]) rel_path, enc); 1933 1934 auto k = KeyValue(UMLComponentDiagram.Key(enc.data.idup), display_name, strip_path); 1935 1936 debug { 1937 logger.tracef("Component:%s stripped:%s file:%s base64:%s", k.display, 1938 strip_path, file_path, cast(string) k.key); 1939 } 1940 1941 return k; 1942 } 1943 1944 UMLClassDiagram.Key makeClassKey(in USRType key) @trusted { 1945 import std.base64 : Base64Impl, Base64; 1946 import std.array : appender; 1947 1948 // TODO consider using hash murmur2/3 function to shorten the length of the 1949 // encoded path 1950 1951 alias SafeBase64 = Base64Impl!('-', '_', Base64.NoPadding); 1952 1953 auto enc = appender!(char[])(); 1954 SafeBase64.encode(cast(ubyte[])(cast(string) key), enc); 1955 1956 auto k = UMLClassDiagram.Key(enc.data.idup); 1957 return k; 1958 } 1959 1960 private auto unpackParam(CxParam p) @trusted { 1961 import std.range : only, dropOne; 1962 import std.variant : visit; 1963 import cpptooling.data : TypeKindVariable, VariadicType; 1964 1965 // dfmt off 1966 return p.visit!( 1967 (TypeKindVariable v) => only(v.type), 1968 (TypeKindAttr v) => only(v), 1969 (VariadicType v) { 1970 logger.error( 1971 "Variadic function not supported. Would require runtime information to relate."); 1972 return only(TypeKindAttr.init).dropOne; 1973 }); 1974 // dfmt on 1975 } 1976 1977 struct ClassRelate { 1978 Relate.Kind kind; 1979 Relate.Key key; 1980 UMLClassDiagram.DisplayName display; 1981 } 1982 1983 auto getClassMemberRelation(LookupT)(TypeKindAttr type, LookupT lookup) { 1984 //TODO code duplication with getMethodRelation 1985 // .. fix it. This function is ugly. 1986 import std.algorithm : each, map, filter, joiner; 1987 import std.array : array; 1988 import std.typecons : tuple; 1989 1990 // TODO this is a mega include. Reduce it. 1991 import cpptooling.data; 1992 1993 auto r = ClassRelate(Relate.Kind.None, Relate.Key(""), UMLClassDiagram.DisplayName("")); 1994 1995 type.kind.info.match!((TypeKind.TypeRefInfo info) { 1996 auto tref = lookup.kind(info.canonicalRef); 1997 foreach (t; tref.filter!(a => a.info.match!((TypeKind.RecordInfo a) => true, _ => false))) { 1998 auto rel_type = Relate.Kind.Aggregate; 1999 if (type.attr.isPtr || type.attr.isRef) { 2000 rel_type = Relate.Kind.Compose; 2001 } 2002 r = ClassRelate(rel_type, t.usr, 2003 cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init)); 2004 } 2005 }, (TypeKind.RecordInfo info) { 2006 r = ClassRelate(Relate.Kind.Aggregate, type.kind.usr, 2007 cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init)); 2008 }, (TypeKind.ArrayInfo info) { 2009 auto element = lookup.kind(info.element); 2010 foreach (e; element.filter!(a => a.info.match!((TypeKind.RecordInfo a) => true, _ => false))) { 2011 auto rel_type = Relate.Kind.Aggregate; 2012 if (type.attr.isPtr || type.attr.isRef) { 2013 rel_type = Relate.Kind.Compose; 2014 } 2015 r = ClassRelate(rel_type, e.usr, 2016 cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init)); 2017 } 2018 }, (TypeKind.PointerInfo info) { 2019 auto pointee = lookup.kind(info.pointee); 2020 foreach (p; pointee.filter!(a => a.info.match!((TypeKind.RecordInfo a) => true, _ => false))) { 2021 string display = p.toStringDecl(TypeAttr.init); 2022 r = ClassRelate(Relate.Kind.Compose, p.usr, cast(UMLClassDiagram.DisplayName) display); 2023 } 2024 }, (_) {}); 2025 2026 return r; 2027 } 2028 2029 private ClassRelate getTypeRelation(LookupT)(TypeKindAttr tk, LookupT lookup) { 2030 import std.algorithm : filter; 2031 import cpptooling.data : TypeKind, TypeAttr, toStringDecl, Void; 2032 2033 auto r = ClassRelate(Relate.Kind.None, Relate.Key(""), UMLClassDiagram.DisplayName("")); 2034 2035 tk.kind.info.match!((TypeKind.TypeRefInfo info) { 2036 auto tref = lookup.kind(info.canonicalRef); 2037 foreach (t; tref.filter!(a => a.info.match!((TypeKind.RecordInfo a) => true, _ => false))) { 2038 r = ClassRelate(Relate.Kind.Associate, Relate.Key(t.usr), 2039 cast(UMLClassDiagram.DisplayName) t.toStringDecl(TypeAttr.init)); 2040 } 2041 }, (TypeKind.RecordInfo t) { 2042 r = ClassRelate(Relate.Kind.Associate, tk.kind.usr, 2043 cast(UMLClassDiagram.DisplayName) tk.kind.toStringDecl(TypeAttr.init)); 2044 }, (TypeKind.ArrayInfo t) { 2045 auto element = lookup.kind(t.element); 2046 foreach (e; element.filter!(a => a.info.match!((TypeKind.RecordInfo a) => true, _ => false))) { 2047 r = ClassRelate(Relate.Kind.Associate, e.usr, 2048 cast(UMLClassDiagram.DisplayName) e.toStringDecl(TypeAttr.init)); 2049 } 2050 }, (TypeKind.PointerInfo t) { 2051 auto pointee = lookup.kind(t.pointee); 2052 foreach (p; pointee.filter!(a => a.info.match!((TypeKind.RecordInfo a) => true, _ => false))) { 2053 string display = p.toStringDecl(TypeAttr.init); 2054 r = ClassRelate(Relate.Kind.Associate, Relate.Key(p.usr), 2055 cast(UMLClassDiagram.DisplayName) display); 2056 } 2057 }, (_) {}); 2058 2059 return r; 2060 } 2061 2062 private auto getClassMethodRelation(LookupT)(CxParam[] params, LookupT lookup) { 2063 import std.array : array; 2064 import std.algorithm : among, map, filter; 2065 import std.variant : visit; 2066 import cpptooling.data : TypeKind, TypeAttr, TypeKindAttr, toStringDecl, VariadicType; 2067 2068 static ClassRelate genParam(CxParam p, LookupT lookup) @trusted { 2069 // dfmt off 2070 return p.visit!( 2071 (TypeKindVariable tkv) => getTypeRelation(tkv.type, lookup), 2072 (TypeKindAttr tk) => getTypeRelation(tk, lookup), 2073 (VariadicType vk) 2074 { 2075 logger.error("Variadic function not supported."); 2076 // Because what types is either discovered first at runtime 2077 // or would require deeper inspection of the implementation 2078 // where the variadic is used. 2079 return ClassRelate.init; 2080 } 2081 ); 2082 // dfmt on 2083 } 2084 2085 // dfmt off 2086 return params.map!(a => genParam(a, lookup)).array(); 2087 // dfmt on 2088 } 2089 2090 void generate(UMLClassDiagram uml_class, UMLComponentDiagram uml_comp, 2091 Flag!"doGenDot" doGenDot, Generator.Modules modules) @safe { 2092 import std.algorithm : each; 2093 import std.format : format; 2094 import std.range : enumerate; 2095 2096 // TODO code duplicaton with class and component. 2097 // Generalize, reduce. 2098 2099 auto classes_preamble = modules.classes.base; 2100 classes_preamble.suppressIndent(1); 2101 foreach (idx, kv; uml_class.fanOutSorted.enumerate) { 2102 generate(kv.key, kv.value, classes_preamble); 2103 generateClassRelate(uml_class.relateTo(kv.key) 2104 .toFlatArray(cast(Relate.Key) kv.key), modules.classes); 2105 if (doGenDot) { 2106 auto nodes = modules.classes_dot.base; 2107 nodes.suppressIndent(1); 2108 nodes.stmt(format(`"%s" [label="%s"]`, kv.key, kv.value.displayName)); 2109 2110 // make a range of all relations from THIS to other components 2111 auto r = uml_class.relateTo(kv.key).toRange(cast(Relate.Key) kv.key); 2112 2113 generateDotRelate(r, idx, modules.classes_dot); 2114 } 2115 } 2116 2117 foreach (idx, kv; uml_comp.fanOutSorted.enumerate) { 2118 generate(kv.key, kv.value, modules.components); 2119 if (doGenDot) { 2120 auto nodes = modules.components_dot.base; 2121 nodes.suppressIndent(1); 2122 nodes.stmt(format(`"%s" [label="%s"]`, kv.key, kv.value.displayName)); 2123 2124 // make a range of all relations from THIS to other components 2125 auto r = uml_comp.relateTo(kv.key).toRange(cast(Relate.Key) kv.key); 2126 2127 generateDotRelate(r, idx, modules.components_dot); 2128 } 2129 } 2130 generateComponentRelate(uml_comp.relateToFlatArray, modules.components); 2131 } 2132 2133 /** Generate PlantUML class and relations from the class. 2134 * 2135 * By generating the relations out of the class directly after the class 2136 * definitions it makes it easier for GraphViz to generate a not-so-muddy 2137 * image. 2138 */ 2139 private void generate(UMLClassDiagram.Key key, UMLClassDiagram.Class c, PlantumlModule m) @safe { 2140 import std.algorithm : each; 2141 import dsrcgen.plantuml : addSpot; 2142 2143 ClassType pc; 2144 2145 if (c.content.length == 0) { 2146 pc = m.class_(cast(string) c.displayName); 2147 } else { 2148 pc = m.classBody(cast(string) c.displayName); 2149 c.content.each!(a => pc.method(a)); 2150 } 2151 pc.addAs.text(cast(string) key); 2152 2153 //TODO add a plantuml macro and use that as color for interface 2154 // Allows the user to control the color via the PREFIX_style.iuml 2155 switch (c.classification) with (cpptooling.data.class_classification.State) { 2156 case Abstract: 2157 pc.addSpot("<< (A, Pink) >>"); 2158 break; 2159 case VirtualDtor: 2160 case Pure: 2161 pc.addSpot("<< (I, LightBlue) >>"); 2162 break; 2163 default: 2164 break; 2165 } 2166 } 2167 2168 private void generateClassRelate(T)(T relate_range, PlantumlModule m) @safe { 2169 static auto convKind(Relate.Kind kind) { 2170 static import dsrcgen.plantuml; 2171 2172 final switch (kind) with (Relate.Kind) { 2173 case None: 2174 assert(0); 2175 case Extend: 2176 return dsrcgen.plantuml.Relate.Extend; 2177 case Compose: 2178 return dsrcgen.plantuml.Relate.Compose; 2179 case Aggregate: 2180 return dsrcgen.plantuml.Relate.Aggregate; 2181 case Associate: 2182 return dsrcgen.plantuml.Relate.ArrowTo; 2183 case Relate: 2184 return dsrcgen.plantuml.Relate.Relate; 2185 } 2186 } 2187 2188 foreach (r; relate_range) { 2189 m.relate(cast(ClassNameType) r.from, cast(ClassNameType) r.to, convKind(r.kind)); 2190 } 2191 } 2192 2193 private void generateDotRelate(T)(T relate_range, ulong color_idx, PlantumlModule m) @safe { 2194 import std.format : format; 2195 2196 static import dsrcgen.plantuml; 2197 2198 static string getColor(ulong idx) { 2199 static string[] colors = [ 2200 "red", "mediumpurple", "darkorange", "deeppink", "green", "coral", 2201 "orangered", "plum", "deepskyblue", "slategray", "cadetblue", 2202 "olive", "silver", "indianred", "black" 2203 ]; 2204 return colors[idx % colors.length]; 2205 } 2206 2207 if (relate_range.length > 0) { 2208 m.stmt(format("edge [color=%s]", getColor(color_idx))); 2209 } 2210 2211 foreach (r; relate_range) { 2212 auto l = m.relate(cast(ClassNameType) r.from, cast(ClassNameType) r.to, 2213 dsrcgen.plantuml.Relate.DotArrowTo); 2214 //TODO this is ugly, fix dsrcgen relate to support graphviz/DOT 2215 auto w = new dsrcgen.plantuml.Text!PlantumlModule(format("[weight=%d] ", r.count)); 2216 l.block.prepend(w); 2217 } 2218 } 2219 2220 private void generate(UMLComponentDiagram.Key key, 2221 UMLComponentDiagram.Component component, PlantumlModule m) @safe { 2222 import std.algorithm : map; 2223 import std.conv : text; 2224 import std.path : buildNormalizedPath, relativePath; 2225 2226 auto comp = m.classBody(cast(string) component.displayName); 2227 comp.addAs.text(cast(string) key); 2228 2229 // early exit because the slice of contains segfaults otherwise. 2230 if (component.contains.length == 0) 2231 return; 2232 2233 // dfmt off 2234 foreach (fname; component.contains[] 2235 .map!(a => cast(string) a) 2236 .map!(a => () @trusted { return buildNormalizedPath(a).relativePath; }())) { 2237 comp.m.stmt(text(fname)); 2238 } 2239 // dfmt on 2240 } 2241 2242 private void generateComponentRelate(T)(T relate_range, PlantumlModule m) @safe { 2243 static auto convKind(Relate.Kind kind) { 2244 static import dsrcgen.plantuml; 2245 2246 final switch (kind) with (Relate.Kind) { 2247 case Relate: 2248 return dsrcgen.plantuml.Relate.Relate; 2249 case Extend: 2250 assert(0); 2251 case Compose: 2252 assert(0); 2253 case Aggregate: 2254 assert(0); 2255 case Associate: 2256 return dsrcgen.plantuml.Relate.ArrowTo; 2257 case None: 2258 assert(0); 2259 } 2260 } 2261 2262 foreach (r; relate_range) { 2263 m.relate(cast(ComponentNameType) r.from, cast(ComponentNameType) r.to, convKind(r.kind)); 2264 } 2265 } 2266 2267 private: 2268 2269 /** Convenient array with support for marking of elements for later removal. 2270 */ 2271 struct MarkArray(T) { 2272 import std.array : Appender; 2273 2274 alias Range = T[]; 2275 private Appender!(size_t[]) remove_; 2276 private Appender!(T*[]) arr; 2277 2278 /// Store e in the cache. 2279 void put(T e) { 2280 auto item = new T; 2281 *item = e; 2282 arr.put(item); 2283 } 2284 2285 /// ditto 2286 void put(T[] e) { 2287 import std.algorithm : map; 2288 2289 foreach (b; e.map!((a) { auto item = new T; *item = a; return item; })) { 2290 arr.put(b); 2291 } 2292 } 2293 2294 /// Retrieve a slice of the stored data. 2295 auto data() { 2296 import std.algorithm : map; 2297 2298 return arr.data.map!(a => *a); 2299 } 2300 2301 /** Mark index `idx` for removal. 2302 * 2303 * Later as in calling $(D doRemoval). 2304 */ 2305 void markForRemoval(size_t idx) @safe pure { 2306 remove_.put(idx); 2307 } 2308 2309 /// Remove all items that has been marked. 2310 void doRemoval() { 2311 import std.algorithm : canFind, filter, map; 2312 import std.range : enumerate; 2313 2314 // naive implementation. Should use swapping instead. 2315 typeof(arr) new_; 2316 new_.put(arr.data 2317 .enumerate 2318 .filter!(a => !canFind(remove_.data, a.index)) 2319 .map!(a => a.value)); 2320 arr.clear; 2321 remove_.clear; 2322 2323 arr = new_; 2324 } 2325 2326 /// Clear the $(D MarkArray). 2327 void clear() { 2328 arr.clear; 2329 remove_.clear; 2330 } 2331 } 2332 2333 @("Should store item") 2334 unittest { 2335 MarkArray!int arr; 2336 2337 arr.put(10); 2338 2339 arr.data.length.shouldEqual(1); 2340 arr.data[0].shouldEqual(10); 2341 } 2342 2343 @("Should mark and remove items") 2344 unittest { 2345 MarkArray!int arr; 2346 arr.put([10, 20, 30]); 2347 2348 arr.markForRemoval(1); 2349 arr.data.length.shouldEqual(3); 2350 2351 arr.doRemoval; 2352 2353 arr.data.length.shouldEqual(2); 2354 arr.data[0].shouldEqual(10); 2355 arr.data[1].shouldEqual(30); 2356 }