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 33 import dextool.type; 34 import cpptooling.analyzer.clang.ast : Visitor; 35 import cpptooling.data : TypeKind, TypeAttr, resolveCanonicalType, USRType, 36 TypeKindAttr, CxParam, CxReturnType, TypeKindVariable; 37 import cpptooling.data.symbol.types : FullyQualifiedNameType; 38 import cpptooling.analyzer.clang.analyze_helper : RecordResult; 39 import dextool.plugin.utility : MarkArray; 40 41 static import cpptooling.data.class_classification; 42 43 version (unittest) { 44 import unit_threaded : Name, shouldEqual; 45 } else { 46 private struct Name { 47 string name_; 48 } 49 } 50 51 /** Control various aspects of the analyze and generation like what nodes to 52 * process. 53 */ 54 @safe interface Controller { 55 /// Query the controller with the filename of the AST node for a decision 56 /// if it shall be processed. 57 bool doFile(in string filename, in string info); 58 59 /** Determine by checking the filesystem if a templated PREFIX_style file shall be created. 60 * 61 * Create it with a minimal style. 62 * Currently just the direction but may change in the future. 63 */ 64 Flag!"genStyleInclFile" genStyleInclFile(); 65 66 /// Strip the filename according to user regex. 67 FileName doComponentNameStrip(FileName fname); 68 } 69 70 /// Parameters used during generation. 71 /// Important aspact that they do NOT change, therefore it is pure. 72 @safe pure const interface Parameters { 73 import std.typecons : Flag; 74 75 static struct Files { 76 FileName classes; 77 FileName components; 78 FileName styleIncl; 79 FileName styleOutput; 80 } 81 82 /// Output directory to store files in. 83 DirName getOutputDirectory(); 84 85 /// Files to write generated diagram data to. 86 Files getFiles(); 87 88 /// Name affecting filenames. 89 FilePrefix getFilePrefix(); 90 91 /** In all diagrams generate an "!include" of the style file. 92 * 93 * If the file PREFIX_style do not exist, create it with a minimal style. 94 * Currently just the direction but may change in the future. 95 */ 96 Flag!"doStyleIncl" doStyleIncl(); 97 98 /// Generate a dot graph in the plantuml file 99 Flag!"doGenDot" doGenDot(); 100 101 /// If class methods should be part of the generated class diagrams. 102 Flag!"genClassMethod" genClassMethod(); 103 104 /// If the parameters of methods should result in directed association. 105 Flag!"genClassParamDependency" genClassParamDependency(); 106 107 /// If the inheritance hierarchy between classes is generated. 108 Flag!"genClassInheritDependency" genClassInheritDependency(); 109 110 /// If the class members result in dependency on those members. 111 Flag!"genClassMemberDependency" genClassMemberDependency(); 112 } 113 114 /// Data produced by the generator like files. 115 @safe interface Products { 116 /** Data pushed from the generator to be written to files. 117 * 118 * The put value is the code generation tree. It allows the caller of 119 * Generator to inject more data in the tree before writing. For example a 120 * custom header. 121 * 122 * Params: 123 * fname = file the content is intended to be written to. 124 * data = data to write to the file. 125 */ 126 void putFile(FileName fname, PlantumlRootModule data); 127 128 /// ditto. 129 void putFile(FileName fname, PlantumlModule data); 130 } 131 132 /** The supported "kind"s of relations between entities. Related to the UML 133 * standard. 134 */ 135 enum RelateKind { 136 None, 137 Extend, 138 Compose, 139 Aggregate, 140 Associate, 141 Relate 142 } 143 144 /** Relations to targets with count and kind. 145 * 146 * Intented to be used in a hashmap with the key as the "from". 147 */ 148 private struct Relate { 149 @safe: 150 alias Key = USRType; 151 alias Kind = RelateKind; 152 153 private static struct Inner { 154 uint count; 155 Kind kind; 156 } 157 158 private Inner[][Key] to; 159 160 /// Returns: number of outgoing connections 161 size_t fanOut() pure nothrow const { 162 return to.length; 163 } 164 165 void put(Key to_, Kind kind) 166 out { 167 assert(to_ in to); 168 } 169 body { 170 auto v = to_ in to; 171 if (v is null) { 172 to[to_] = Inner[].init; 173 v = to_ in to; 174 } 175 176 // ugly algorithm, use an inner hashmap instead 177 bool is_new = true; 178 foreach (ref r; *v) { 179 if (r.kind == kind) { 180 r.count++; 181 is_new = false; 182 break; 183 } 184 } 185 186 if (is_new) { 187 *v ~= Inner(1, kind); 188 } 189 } 190 191 /** A range of the form FROM-TO with metadata. 192 * 193 * count is the total number of outgoing connections to the target. 194 * For example would 2 Relation and 4 Extend result in the sum of 6. 195 */ 196 auto toRange(const Relate.Key from) pure const @trusted { 197 import std.algorithm : map; 198 import std.array : array; 199 200 static struct RelateTuple { 201 Relate.Key from; 202 Relate.Key to; 203 ulong count; 204 } 205 206 static ulong sumFanOut(const(Inner)[] inner) pure { 207 import std.algorithm : sum; 208 209 return inner.map!(a => a.count).sum; 210 } 211 212 // dfmt off 213 return to.byKeyValue.map!(a => RelateTuple(from, a.key, sumFanOut(a.value))) 214 .array(); 215 // dfmt on 216 } 217 218 /// Convert the TO/value store to a FROM-KIND-TO-COUNT array. 219 auto toFlatArray(const Relate.Key from) pure const @trusted { 220 import std.algorithm : filter, map, joiner; 221 import std.array : array; 222 223 static struct RelateTuple { 224 Relate.Key from; 225 Kind kind; 226 Relate.Key to; 227 ulong count; 228 } 229 230 // dfmt off 231 return to.byKeyValue.map!(a => a.value 232 .filter!(b => b.kind != Kind.None) 233 .map!(b => RelateTuple(from, b.kind, a.key, b.count)) 234 .array()) 235 .joiner() 236 .array(); 237 // dfmt on 238 } 239 240 auto toStringArray(const Relate.Key from) pure const @trusted { 241 import std.algorithm : map; 242 import std.conv : text; 243 import std.format : format; 244 import std.array : array; 245 246 // dfmt off 247 return this.toFlatArray(from) 248 .map!(b => format("%s -%s- [%d]%s", cast(string) b.from, text(b.kind), b.count, cast(string) b.to)) 249 .array(); 250 // dfmt on 251 } 252 } 253 254 private size_t[] nameIndexSortedRange(T, alias sortNameBy)(T arr) pure { 255 import std.algorithm : makeIndex; 256 257 auto index = new size_t[arr.length]; 258 259 makeIndex!((a, b) => sortNameBy(a) < sortNameBy(b))(arr, index); 260 return index; 261 } 262 263 private auto nameSortedRange(T, alias sortNameBy)(const T t) pure { 264 import std.algorithm : map; 265 import std.array : array; 266 267 auto arr = t.asArray(); 268 auto index = nameIndexSortedRange!(typeof(arr), sortNameBy)(arr); 269 270 return index.map!(i => arr[i]).array(); 271 } 272 273 private auto fanOutSorted(T)(T t) pure { 274 import std.algorithm : makeIndex, map; 275 import std.array : array; 276 277 //TODO how to avoid doing this allocation? 278 279 auto arr = t.nameSortedRange(); 280 auto fanout_i = new size_t[arr.length]; 281 282 // dfmt off 283 makeIndex!((a, b) => t.relate_to[cast(Relate.Key) a.key].fanOut > t.relate_to[cast(Relate.Key) b.key].fanOut)(arr, fanout_i); 284 // dfmt on 285 286 return fanout_i.map!(i => arr[i]).array(); 287 } 288 289 /** UML Class Diagram. 290 * 291 * Not designed for the general case. 292 * The design is what the plantuml plugin needs when analyzing more than one 293 * file. This is the container that is then passed between the analyze stages. 294 * 295 * All classes must exist in "classes". 296 * It is common that during data gathering a class is found to be related to 297 * another class by a USR. The relation is added before the class represented 298 * by the USR is added. 299 * 300 * A --> B 301 * Directed relation. 302 * A can have many connections to B. 303 * 304 * Store of R[A.B]. 305 * When analyzing the structural data it is this kind of relations that are 306 * found. From a class to many X, where X is other classes. 307 * The key used must be unique, thus the choice of using USR. 308 * 309 * Example of relations. 310 * A --> B (member) 311 * A --> B (member) 312 * A --> B (inherit) 313 * B --> A (member) 314 * 315 * relate[A].put(B, Compose) 316 * relate[A].put(B, Compose) 317 * relate[A].put(B, Extend) 318 * relate[B].put(A, Compose) 319 * 320 * The relations are of the kind Fan-out, one-to-many. 321 * They can be sorted in descending fan-out-count order. 322 */ 323 class UMLClassDiagram { 324 @safe: 325 import std.typecons : NullableRef; 326 import std.format : FormatSpec; 327 328 alias ClassClassificationState = cpptooling.data.class_classification.State; 329 330 alias Key = USRType; 331 struct DisplayName { 332 string payload; 333 alias payload this; 334 } 335 336 struct Content { 337 string payload; 338 alias payload this; 339 } 340 341 private struct Class { 342 DisplayName displayName; 343 ClassClassificationState classification; 344 string[] content; 345 } 346 347 /// The class is only added if it doesn't already exist in the store. 348 void put(Key key, DisplayName display_name) { 349 if (key !in classes) { 350 classes[key] = Class(display_name); 351 relate_to[cast(Relate.Key) key] = Relate.init; 352 } 353 } 354 355 /** Store parameter content with the key. 356 * 357 * It is the body of the class in a class diagram. 358 */ 359 void put(Key key, Content content) 360 in { 361 assert(key in classes); 362 } 363 body { 364 classes[key].content ~= cast(string) content; 365 } 366 367 /** Set the classification of a class. 368 * 369 * Example would be a pure virtual, which in java would be an interface. 370 */ 371 void set(Key key, ClassClassificationState classification) 372 in { 373 assert(key in classes); 374 } 375 body { 376 classes[key].classification = classification; 377 } 378 379 /** Add a relation between two classes and increase the count on the class 380 * related TO. 381 */ 382 void relate(Key from, Key to, DisplayName display_name, Relate.Kind kind) 383 out { 384 assert(from in classes); 385 assert(to in classes); 386 assert(kind != Relate.Kind.None); 387 } 388 body { 389 put(to, display_name); 390 relate_to[cast(Relate.Key) from].put(cast(Relate.Key) to, kind); 391 } 392 393 /** Use to retrieve the relation struct for the key. 394 * 395 * Example: 396 * --- 397 * diagram.relateTo(Key("foo")).put(Key("bar"), Relate.Kind.Associate); 398 * --- 399 */ 400 const(Relate) relateTo(Key k) pure const 401 in { 402 assert(k in classes); 403 assert((cast(Relate.Key) k) in relate_to); 404 } 405 body { 406 return relate_to[cast(Relate.Key) k]; 407 } 408 409 /// Returns: Flat array of all relations of type FROM-KIND-TO-COUNT. 410 auto relateToFlatArray() pure const @trusted { 411 import std.algorithm : map, joiner; 412 import std.array : array; 413 414 return relate_to.byKeyValue.map!(a => a.value.toFlatArray(a.key)).joiner().array(); 415 } 416 417 private static struct KeyClass { 418 Key key; 419 const(Class) value; 420 } 421 422 /// Returns: An array of the key/values. 423 KeyClass[] asArray() const pure nothrow @trusted { 424 import std.array : array; 425 import std.algorithm : map; 426 427 //TODO how to do this without so much generated GC 428 429 // dfmt off 430 return classes.byKeyValue 431 .map!(a => KeyClass(a.key, a.value)) 432 .array(); 433 // dfmt on 434 } 435 436 /// Returns: An array of the key/values sorted on key. 437 auto nameSortedRange() const pure @trusted { 438 static string sortClassNameBy(T)(ref T a) { 439 return a.value.displayName; 440 } 441 442 return .nameSortedRange!(typeof(this), sortClassNameBy)(this); 443 } 444 445 private string[] classesToStringArray() const pure @trusted { 446 import std.algorithm : map, joiner; 447 import std.array : array; 448 import std.ascii : newline; 449 import std.conv : text; 450 import std.format : format; 451 import std.range : only, chain, takeOne; 452 453 // dfmt off 454 return classes 455 .byKeyValue 456 .map!(a => chain(only(format("%s -> %s%s", 457 a.value.displayName, 458 a.key, 459 a.value.content.length == 0 ? "" : " {")), 460 a.value.content.dup.map!(b => " " ~ b), 461 a.value.content.takeOne.map!(b => "} // " ~ a.value.displayName)) 462 .joiner(newline) 463 .text) 464 .array(); 465 // dfmt on 466 } 467 468 private string[] relateToStringArray() const pure @trusted { 469 import std.algorithm : map, joiner; 470 import std.array : array; 471 472 return relate_to.byKeyValue.map!(a => a.value.toStringArray(a.key)).joiner().array(); 473 } 474 475 void toString(Writer, Char)(scope Writer w, FormatSpec!Char) const { 476 import std.ascii : newline; 477 import std.format : formattedWrite; 478 import std.range.primitives : put; 479 import std.range : zip, repeat; 480 481 formattedWrite(w, "UML Class Diagram (Total %d) {", classes.length); 482 put(w, newline); 483 foreach (a; zip(classesToStringArray, repeat(newline))) { 484 put(w, a[0]); 485 put(w, a[1]); 486 } 487 foreach (a; zip(relateToStringArray, repeat(newline))) { 488 put(w, a[0]); 489 put(w, a[1]); 490 } 491 put(w, "} // UML Class Diagram"); 492 } 493 494 override string toString() @safe pure const { 495 import std.exception : assumeUnique; 496 import std.format : FormatSpec; 497 498 char[] buf; 499 buf.reserve(100); 500 auto fmt = FormatSpec!char("%s"); 501 toString((const(char)[] s) { buf ~= s; }, fmt); 502 auto trustedUnique(T)(T t) @trusted { 503 return assumeUnique(t); 504 } 505 506 return trustedUnique(buf); 507 } 508 509 private Relate[Relate.Key] relate_to; 510 private Class[Key] classes; 511 } 512 513 /** UML Component Diagram. 514 * 515 * Not designed for the general case. 516 * The design is what the plantuml plugin needs when analyzing more than one 517 * file. This is the container that is then passed between the analyze stages. 518 * 519 * The relations are of the kind Fan-out. 520 * 521 * See_Also: UMLClassDiagram 522 */ 523 class UMLComponentDiagram { 524 import std.container.rbtree : RedBlackTree; 525 526 struct Key { 527 string payload; 528 alias payload this; 529 } 530 531 struct Location { 532 string payload; 533 alias payload this; 534 } 535 536 struct DisplayName { 537 string payload; 538 alias payload this; 539 } 540 541 private struct Component { 542 DisplayName displayName; 543 string[] toFile; 544 RedBlackTree!Location contains; 545 } 546 547 /// The component is only added if it doesn't already exist in the store. 548 void put(Key key, DisplayName display_name) @safe { 549 if (key !in components) { 550 components[key] = Component(display_name, null, new RedBlackTree!Location); 551 relate_to[cast(Relate.Key) key] = Relate.init; 552 } 553 } 554 555 /// Add a location that the component encapsulate 556 void put(Key key, Location loc) @trusted 557 out { 558 assert(key in components); 559 } 560 body { 561 components[key].contains.insert(loc); 562 } 563 564 /** Add a relation between two components and increase the count on the class 565 * related TO. 566 */ 567 void relate(Key from, Key to, DisplayName toDisplayName, Relate.Kind kind) @safe 568 out { 569 assert(from in components); 570 assert(to in components); 571 assert(kind != Relate.Kind.None); 572 } 573 body { 574 put(to, toDisplayName); 575 576 auto rel = cast(Relate.Key) from in relate_to; 577 if (rel is null) { 578 relate_to[cast(Relate.Key) from] = Relate(); 579 rel = cast(Relate.Key) from in relate_to; 580 } 581 rel.put(cast(Relate.Key) to, kind); 582 583 auto comp = from in components; 584 if (comp is null) { 585 // TODO this is a hack. The display name should be the froms name. 586 components[from] = Component(cast(DisplayName) from, null, new RedBlackTree!Location); 587 comp = from in components; 588 } 589 comp.toFile ~= cast(string) to; 590 } 591 592 /** Use to retrieve the relation struct for the key. 593 * 594 * Example: 595 * --- 596 * diagram.relateTo(Key("foo")).put(Key("bar"), Relate.Kind.Associate); 597 * --- 598 */ 599 const(Relate) relateTo(Key k) pure const @safe 600 in { 601 assert(k in components); 602 assert((cast(Relate.Key) k) in relate_to); 603 } 604 body { 605 return relate_to[cast(Relate.Key) k]; 606 } 607 608 /// Return: Flat array of all relations of type FROM-KIND-TO-COUNT. 609 auto relateToFlatArray() pure const @trusted { 610 import std.algorithm : map, joiner; 611 import std.array : array; 612 613 return relate_to.byKeyValue.map!(a => a.value.toFlatArray(a.key)).joiner().array(); 614 } 615 616 private static struct KeyComponent { 617 Key key; 618 const(Component) value; 619 } 620 621 /// Returns: Flat array of all relations of type FROM-KIND-TO-COUNT. 622 KeyComponent[] asArray() const pure nothrow @trusted { 623 import std.array : array; 624 import std.algorithm : map; 625 626 //TODO how to do this without so much generated GC 627 628 // dfmt off 629 return components.byKeyValue 630 .map!(a => KeyComponent(a.key, a.value)) 631 .array(); 632 // dfmt on 633 } 634 635 /// Returns: An array of the key/values sorted on key. 636 auto nameSortedRange() const pure @trusted { 637 static string sortComponentNameBy(T)(ref T a) { 638 return cast(string) a.value.displayName; 639 } 640 641 return .nameSortedRange!(typeof(this), sortComponentNameBy)(this); 642 } 643 644 private string[] componentsToStringArray() const pure @trusted { 645 import std.algorithm : map, joiner; 646 import std.ascii : newline; 647 import std.array : array; 648 import std.format : format; 649 import std.typecons : tuple; 650 651 // dfmt off 652 return nameSortedRange 653 .map!(a => tuple(a.key, a.value.displayName, a.value.contains[].map!(a => newline ~ " " ~ cast(string) a).joiner)) 654 .map!(a => format("%s as %s%s", a[0], 655 a[1], 656 a[2])).array(); 657 // dfmt on 658 } 659 660 private string[] relateToStringArray() const pure @trusted { 661 import std.algorithm : map, joiner; 662 import std.array : array; 663 664 return relate_to.byKeyValue.map!(a => a.value.toStringArray(a.key)).joiner().array(); 665 } 666 667 /// String representation of the Component Diagram. 668 void toString(Writer)(scope Writer w) @safe const { 669 import std.ascii : newline; 670 import std.format : formattedWrite; 671 import std.range.primitives : put; 672 import std.range : zip, repeat; 673 674 formattedWrite(w, "UML Component Diagram (Total %d) {", components.length); 675 put(w, newline); 676 foreach (a; zip(componentsToStringArray, repeat(newline))) { 677 put(w, a[0]); 678 put(w, a[1]); 679 } 680 foreach (a; zip(relateToStringArray, repeat(newline))) { 681 put(w, a[0]); 682 put(w, a[1]); 683 } 684 put(w, "} // UML Component Diagram"); 685 } 686 687 /// 688 override string toString() @safe const { 689 import std.exception : assumeUnique; 690 691 char[] buf; 692 buf.reserve(100); 693 this.toString((const(char)[] s) { buf ~= s; }); 694 auto trustedUnique(T)(T t) @trusted { 695 return assumeUnique(t); 696 } 697 698 return trustedUnique(buf); 699 } 700 701 private: 702 Relate[Relate.Key] relate_to; 703 Component[Key] components; 704 } 705 706 @Name("Should be a None relate not shown and an extended relate") 707 unittest { 708 Relate r; 709 r.put(Relate.Key("B"), Relate.Kind.None); 710 r.put(Relate.Key("B"), Relate.Kind.Extend); 711 712 r.toStringArray(Relate.Key("A")).shouldEqual(["A -Extend- [1]B"]); 713 } 714 715 @Name("Should be all types of relates") 716 unittest { 717 Relate r; 718 r.put(Relate.Key("B"), Relate.Kind.None); 719 r.put(Relate.Key("B"), Relate.Kind.Extend); 720 r.put(Relate.Key("B"), Relate.Kind.Compose); 721 r.put(Relate.Key("B"), Relate.Kind.Aggregate); 722 r.put(Relate.Key("B"), Relate.Kind.Associate); 723 724 r.toStringArray(Relate.Key("A")).shouldEqual([ 725 "A -Extend- [1]B", "A -Compose- [1]B", "A -Aggregate- [1]B", 726 "A -Associate- [1]B" 727 ]); 728 } 729 730 @Name("Should be two relates to the same target") 731 unittest { 732 Relate r; 733 r.put(Relate.Key("B"), Relate.Kind.Compose); 734 r.put(Relate.Key("B"), Relate.Kind.Compose); 735 736 r.toStringArray(Relate.Key("A")).shouldEqual(["A -Compose- [2]B"]); 737 } 738 739 @Name("Should be a UML diagram with one class") 740 unittest { 741 auto uml = new UMLClassDiagram; 742 uml.put(UMLClassDiagram.Key("A"), UMLClassDiagram.DisplayName("A")); 743 744 uml.toString.shouldEqual("UML Class Diagram (Total 1) { 745 A -> A 746 } // UML Class Diagram"); 747 } 748 749 @Name("Should be a UML diagram with two classes related") 750 unittest { 751 auto uml = new UMLClassDiagram; 752 auto ka = UMLClassDiagram.Key("A"); 753 auto kb = UMLClassDiagram.Key("B"); 754 uml.put(ka, UMLClassDiagram.DisplayName("A")); 755 uml.put(kb, UMLClassDiagram.DisplayName("B")); 756 757 uml.relate(ka, kb, UMLClassDiagram.DisplayName("B"), Relate.Kind.Extend); 758 759 uml.toString.shouldEqual("UML Class Diagram (Total 2) { 760 A -> A 761 B -> B 762 A -Extend- [1]B 763 } // UML Class Diagram"); 764 } 765 766 @Name("Should be a UML Component diagram with two components related") 767 unittest { 768 auto uml = new UMLComponentDiagram; 769 auto ka = UMLComponentDiagram.Key("a"); 770 auto kb = UMLComponentDiagram.Key("b"); 771 uml.put(ka, cast(UMLComponentDiagram.DisplayName) "A"); 772 // shall be dedupliated 773 uml.put(ka, cast(UMLComponentDiagram.Location) "file.h"); 774 uml.put(ka, cast(UMLComponentDiagram.Location) "file.h"); 775 776 uml.relate(ka, kb, cast(UMLComponentDiagram.DisplayName) "B", Relate.Kind.Relate); 777 778 uml.toString.shouldEqual("UML Component Diagram (Total 2) { 779 a as A 780 file.h 781 b as B 782 a -Relate- [1]b 783 } // UML Component Diagram"); 784 } 785 786 /** Context for the UML diagram generator from internal representation to the 787 * concrete files. 788 */ 789 struct Generator { 790 import cpptooling.data : CppRoot; 791 import cpptooling.data.symbol : Container; 792 793 private static struct Modules { 794 private static void postInit(ref typeof(this) m) { 795 m.classes_dot.suppressIndent(1); 796 m.components_dot.suppressIndent(1); 797 } 798 799 import dextool.plugin.utility : MakerInitializingClassMembers; 800 801 mixin MakerInitializingClassMembers!(Modules, postInit); 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 FileName 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, FileName 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, FileName 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 = FileName(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 FileName makeDotFileName(FileName 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 FileName((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 cpptooling.analyzer.clang.ast : ClassDecl, CxxBaseSpecifier, Constructor, 1032 Destructor, CxxMethod, FieldDecl, CxxAccessSpecifier, generateIndentIncrDecr; 1033 import cpptooling.analyzer.clang.analyze_helper : analyzeRecord, analyzeConstructor, analyzeDestructor, 1034 analyzeCxxMethod, analyzeFieldDecl, analyzeCxxBaseSpecified, toAccessType; 1035 import cpptooling.analyzer.clang.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, const(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(const(ClassDecl) v) @trusted { 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 auto visitor = scoped!(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(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(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(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(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(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(const(CxxAccessSpecifier) v) @trusted { 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 cpptooling.analyzer.clang.ast : TranslationUnit, UnexposedDecl, 1203 VarDecl, FunctionDecl, ClassDecl, Namespace, generateIndentIncrDecr; 1204 import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl, 1205 analyzeVarDecl, analyzeRecord, analyzeTranslationUnit; 1206 import cpptooling.analyzer.clang.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(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(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(const(VarDecl) v) { 1248 mixin(mixinNodeLog!()); 1249 1250 auto result = () @trusted { return 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(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(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(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 const(TypeKindAttr) src, ref const(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 const(TypeKindAttr) src, ref const(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 const(TypeKindAttr) src, ref const(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 const(TypeKindAttr) src, ref const(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 const(TypeKindAttr) src, ref const(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 const(ClassClassificationResult) result) { 1462 auto key = makeClassKey(result.type.kind.usr); 1463 uml.set(key, result.classification); 1464 } 1465 1466 void put(ref const(RecordResult) src, const(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) @safe 1579 if (is(ElementType!Range == TypeKindAttr) 1580 || is(ElementType!Range == const(TypeKindAttr))) { 1581 import std.algorithm : filter; 1582 1583 // dfmt off 1584 foreach(a; range 1585 // remove primitive types 1586 .filter!(a => a.kind.info.kind != TypeKind.Info.Kind.primitive) 1587 .map!(a => resolveCanonicalType(a.kind, a.attr, lookup)) 1588 .joiner 1589 .map!(a => a.kind.usr) 1590 // create the relations of type src-to-kind 1591 .map!(to_ => USRRelation(src, to_, Relate.Kind.Associate))) { 1592 target.put(a); 1593 } 1594 // dfmt on 1595 } 1596 1597 /// ditto 1598 static void putParamsToCache(T)(ref const(TypeKindAttr) src, 1599 const(CxParam)[] params, ref T target, LookupT lookup) @safe { 1600 // dfmt off 1601 auto range = params 1602 // returns a bunch of ranges of the unpacked parameters 1603 .map!(a => unpackParam(a)) 1604 .joiner; 1605 // dfmt on 1606 1607 putToCache(src.kind.usr, range, target, lookup); 1608 } 1609 1610 static void finalizeSrcCache(LookupT, TargetT)(USRType[] cache, LookupT lookup, TargetT target) { 1611 import std.algorithm : map, joiner; 1612 1613 // dfmt off 1614 foreach (loc; cache 1615 .map!(usr => lookup.location(usr)) 1616 .joiner 1617 .map!(a => a.any) 1618 .joiner) { 1619 target.putSrc(loc); 1620 } 1621 // dfmt on 1622 } 1623 1624 /// Process the last bits left in the cache. 1625 void finalize() { 1626 import std.algorithm : map, filter, cache; 1627 import std.range : enumerate, only; 1628 import std.typecons : tuple; 1629 1630 finalizeSrcCache(src_cache[], lookup, this); 1631 if (src_cache.length > 0) { 1632 logger.tracef("%d relations left in src cache", src_cache.length); 1633 } 1634 src_cache.length = 0; 1635 1636 if (dcache.data.length > 0) { 1637 logger.tracef("%d relations left. Activating fallback strategy", dcache.data.length); 1638 } 1639 1640 // dfmt off 1641 foreach (e; dcache.data 1642 // keep track of the index to allow marking of the cache for removal 1643 .enumerate 1644 // find the types 1645 .map!(a => tuple(a.index, lookup.location(a.value.from), lookup.location(a.value.to))) 1646 .cache 1647 // a zero range means a failed lookup, a broken relation 1648 .filter!(a => a[1].length != 0 && a[2].length != 0) 1649 // unpack with fallback 1650 .map!(a => tuple(a[0], a[1].front.any, a[2].front.any)) 1651 // ensure that both both resulted in valid ranges 1652 .filter!(a => a[1].length != 0 && a[2].length != 0) 1653 // unpack 1654 .map!(a => tuple(a[0], a[1].front, a[2].front)) 1655 // check via ctrl (the user) if the destination is "ok" 1656 .filter!(a => ctrl.doFile(cast(string) a[2].file, cast(string) a[2].file)) 1657 ) { 1658 //TODO warn when a declaration has been used? 1659 1660 putDest(e[1], e[2], Relate.Kind.Associate); 1661 dcache.markForRemoval(e[0]); 1662 } 1663 // dfmt on 1664 1665 dcache.doRemoval; 1666 1667 if (dcache.data.length > 0) { 1668 logger.errorf("Fallback strategy failed for %d USRs. They are:", dcache.data.length); 1669 } 1670 1671 foreach (e; dcache.data) { 1672 logger.tracef(" %s -> %s", cast(string) e.from, cast(string) e.to); 1673 } 1674 } 1675 1676 void put(ref const(TranslationUnitResult) result) { 1677 import std.algorithm : map, filter, cache; 1678 import std.range : enumerate, only; 1679 import std.typecons : tuple; 1680 1681 finalizeSrcCache(src_cache[], lookup, this); 1682 if (src_cache.length > 0) { 1683 logger.tracef("%d relations left in src cache", src_cache.length); 1684 } 1685 src_cache.length = 0; 1686 1687 // dfmt off 1688 foreach (e; dcache.data 1689 // keep track of the index to allow marking of the cache for removal 1690 .enumerate 1691 // find the types 1692 .map!(a => tuple(a.index, lookup.location(a.value.from), lookup.location(a.value.to))) 1693 .cache 1694 // a zero range means a failed lookup, a broken relation 1695 .filter!(a => a[1].length != 0 && a[2].length != 0) 1696 // unpack 1697 .map!(a => tuple(a[0], a[1].front, a[2].front)) 1698 // only okey with a relatioin TO something that is a definition 1699 .filter!(a => a[1].hasDefinition && a[2].hasDefinition) 1700 // check via ctrl (the user) if the destination is "ok" 1701 .filter!(a => ctrl.doFile(cast(string) a[2].definition.file, cast(string) a[2].definition.file)) 1702 ) { 1703 putDest(e[1].definition, e[2].definition, Relate.Kind.Associate); 1704 dcache.markForRemoval(e[0]); 1705 } 1706 // dfmt on 1707 1708 dcache.doRemoval; 1709 } 1710 1711 void put(ref const(RecordResult) result) { 1712 src_cache ~= result.type.kind.usr; 1713 } 1714 1715 void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) { 1716 putParamsToCache(src, result.params, dcache, lookup); 1717 } 1718 1719 void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) { 1720 import std.range : only; 1721 1722 putParamsToCache(src, result.params, dcache, lookup); 1723 putToCache(src.kind.usr, only((cast(const TypeKindAttr) result.returnType)), dcache, lookup); 1724 } 1725 1726 void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) { 1727 import std.range : only; 1728 1729 putToCache(src.kind.usr, only(result.type), dcache, lookup); 1730 } 1731 1732 void put(ref const(TypeKindAttr) src, ref const(ClassClassificationResult) result) { 1733 import std.range : only; 1734 1735 // called when creating a relation for a nested class 1736 putToCache(src.kind.usr, only(result.type), dcache, lookup); 1737 } 1738 1739 void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) { 1740 auto r0 = lookup.kind(result.canonicalUSR).map!(a => TypeKindAttr(a.get, TypeAttr.init)); 1741 1742 putToCache(src.kind.usr, r0, dcache, lookup); 1743 } 1744 1745 void put(ref const(VarDeclResult) result) { 1746 import std.range : only; 1747 1748 // primitive types do not have a location 1749 if (result.location.kind == LocationTag.Kind.loc) { 1750 putSrc(result.location); 1751 1752 putToCache(result.instanceUSR, only(result.type), dcache, lookup); 1753 } 1754 } 1755 1756 void put(ref const(FunctionDeclResult) result) { 1757 import std.range : only; 1758 1759 src_cache ~= result.type.kind.usr; 1760 1761 putParamsToCache(result.type, result.params, dcache, lookup); 1762 putToCache(result.type.kind.usr, 1763 only(cast(const TypeKindAttr) result.returnType), dcache, lookup); 1764 } 1765 1766 void putSrc(ref const(LocationTag) src) @safe { 1767 string location = src.file; 1768 1769 if (src.kind == LocationTag.Kind.noloc || !ctrl.doFile(location, location)) { 1770 return; 1771 } 1772 1773 auto key = makeComponentKey(location, ctrl); 1774 diagram.put(key.key, cast(UMLComponentDiagram.DisplayName) key.display); 1775 diagram.put(key.key, cast(UMLComponentDiagram.Location) location); 1776 } 1777 1778 void putDest(ref const(LocationTag) src, ref const(LocationTag) dest, Relate.Kind kind) { 1779 auto src_ = makeComponentKey(src.file, ctrl); 1780 auto dest_ = makeComponentKey(dest.file, ctrl); 1781 1782 // Ignoring self referencing relations. 1783 if (src_.key == dest_.key) { 1784 return; 1785 } 1786 1787 diagram.relate(src_.key, dest_.key, 1788 cast(UMLComponentDiagram.DisplayName) dest_.display, kind); 1789 } 1790 } 1791 1792 /** Route information to specific transformers. 1793 * 1794 * No manipulation of data is to be done in this struct. Only routing to 1795 * appropriate functions. 1796 */ 1797 class TransformToDiagram(ControllerT, ParametersT, LookupT) { 1798 import std.range : only; 1799 1800 import cpptooling.analyzer.clang.analyze_helper : CxxBaseSpecifierResult, 1801 RecordResult, FieldDeclResult, CxxMethodResult, 1802 ConstructorResult, DestructorResult, VarDeclResult, FunctionDeclResult, 1803 TranslationUnitResult; 1804 import cpptooling.data.symbol.types : USRType; 1805 import cpptooling.data : TypeKind, CppNs, CppAccess; 1806 1807 private { 1808 TransformToComponentDiagram!(ControllerT, LookupT) to_component; 1809 TransformToClassDiagram!(ControllerT, LookupT) to_class; 1810 } 1811 1812 this(ControllerT ctrl, ParametersT params, LookupT lookup, 1813 UMLComponentDiagram comp_dia, UMLClassDiagram class_dia) { 1814 to_component = typeof(to_component)(comp_dia, ctrl, lookup); 1815 to_class = typeof(to_class)(class_dia, ctrl, lookup, params.genClassMethod, 1816 params.genClassParamDependency, params.genClassInheritDependency, 1817 params.genClassMemberDependency); 1818 } 1819 1820 @safe: 1821 1822 /** Signal that diagrams to perform a finalization of cached data. 1823 */ 1824 void finalize() { 1825 to_component.finalize(); 1826 } 1827 1828 void put(ref const(TranslationUnitResult) result) { 1829 to_component.put(result); 1830 } 1831 1832 void put(ref const(RecordResult) result, const(CppNs)[] reside_in) { 1833 to_class.put(result, reside_in); 1834 to_component.put(result); 1835 } 1836 1837 void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) { 1838 to_class.put(src, result); 1839 to_component.put(src, result); 1840 } 1841 1842 void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) { 1843 to_class.put(src, result, access); 1844 to_component.put(src, result, access); 1845 } 1846 1847 void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) { 1848 to_class.put(src, result, access); 1849 to_component.put(src, result, access); 1850 } 1851 1852 void put(ref const(TypeKindAttr) src, ref const(DestructorResult) result, in CppAccess access) { 1853 to_class.put(src, result, access); 1854 } 1855 1856 void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) { 1857 to_class.put(src, result, access); 1858 to_component.put(src, result, access); 1859 } 1860 1861 void put(ref const(ClassClassificationResult) result) { 1862 to_class.put(result); 1863 } 1864 1865 /** A nested class. 1866 * 1867 * Propagate the classification and relation of the root->nested. 1868 */ 1869 void put(ref const(TypeKindAttr) src, ref const(ClassClassificationResult) result) { 1870 to_component.put(src, result); 1871 // only needs result 1872 to_class.put(result); 1873 } 1874 1875 void put(ref const(VarDeclResult) result) { 1876 to_component.put(result); 1877 } 1878 1879 void put(ref const(FunctionDeclResult) result) { 1880 to_component.put(result); 1881 } 1882 } 1883 1884 // visualize where the module private starts 1885 private: // ****************************************************************** 1886 1887 import cpptooling.data.representation : CppRoot, CppClass, CppMethod, CppCtor, 1888 CppDtor, CppNamespace, CFunction, CxGlobalVariable, LocationTag, Location; 1889 import cpptooling.data.symbol : Container; 1890 import dsrcgen.plantuml; 1891 1892 struct KeyValue { 1893 UMLComponentDiagram.Key key; 1894 string display; 1895 string absFilePath; 1896 } 1897 1898 struct KeyRelate { 1899 string file; 1900 KeyValue key; 1901 Relate.Kind kind; 1902 } 1903 1904 /** 1905 * Params: 1906 * file = filename of the relation. 1907 * kind = kind of relation such as associaiton, composition etc. 1908 */ 1909 struct PathKind { 1910 string file; 1911 Relate.Kind kind; 1912 } 1913 1914 /** Calculate the key based on the directory the file that declares the symbol exist in. 1915 * 1916 * Additional metadata as to make it possible to backtrack. 1917 */ 1918 KeyValue makeComponentKey(in string location_file, Controller ctrl) @trusted { 1919 import std.array : appender; 1920 import std.base64 : Base64Impl, Base64; 1921 import std.path : buildNormalizedPath, absolutePath, relativePath, baseName; 1922 import std.typecons : tuple; 1923 1924 // TODO consider using hash murmur2/3 to shorten the length of the encoded 1925 // path 1926 1927 alias SafeBase64 = Base64Impl!('-', '_', Base64.NoPadding); 1928 1929 string file_path = buildNormalizedPath(location_file.absolutePath); 1930 string strip_path = cast(string) ctrl.doComponentNameStrip(FileName(file_path)); 1931 string rel_path = relativePath(strip_path); 1932 string display_name = strip_path.baseName; 1933 1934 auto enc = appender!(char[])(); 1935 SafeBase64.encode(cast(ubyte[]) rel_path, enc); 1936 1937 auto k = KeyValue(UMLComponentDiagram.Key(enc.data.idup), display_name, strip_path); 1938 1939 debug { 1940 logger.tracef("Component:%s stripped:%s file:%s base64:%s", k.display, 1941 strip_path, file_path, cast(string) k.key); 1942 } 1943 1944 return k; 1945 } 1946 1947 UMLClassDiagram.Key makeClassKey(in USRType key) @trusted { 1948 import std.base64 : Base64Impl, Base64; 1949 import std.array : appender; 1950 1951 // TODO consider using hash murmur2/3 function to shorten the length of the 1952 // encoded path 1953 1954 alias SafeBase64 = Base64Impl!('-', '_', Base64.NoPadding); 1955 1956 auto enc = appender!(char[])(); 1957 SafeBase64.encode(cast(ubyte[])(cast(string) key), enc); 1958 1959 auto k = UMLClassDiagram.Key(enc.data.idup); 1960 return k; 1961 } 1962 1963 private auto unpackParam(CxParam p) @trusted { 1964 import std.range : only, dropOne; 1965 import std.variant : visit; 1966 import cpptooling.data : TypeKindVariable, VariadicType; 1967 1968 // dfmt off 1969 return p.visit!( 1970 (TypeKindVariable v) => only(v.type), 1971 (TypeKindAttr v) => only(v), 1972 (VariadicType v) { 1973 logger.error( 1974 "Variadic function not supported. Would require runtime information to relate."); 1975 return only(TypeKindAttr.init).dropOne; 1976 }); 1977 // dfmt on 1978 } 1979 1980 struct ClassRelate { 1981 Relate.Kind kind; 1982 Relate.Key key; 1983 UMLClassDiagram.DisplayName display; 1984 } 1985 1986 auto getClassMemberRelation(LookupT)(TypeKindAttr type, LookupT lookup) { 1987 //TODO code duplication with getMethodRelation 1988 // .. fix it. This function is ugly. 1989 import std.algorithm : each, map, filter, joiner; 1990 import std.array : array; 1991 import std.typecons : tuple; 1992 1993 // TODO this is a mega include. Reduce it. 1994 import cpptooling.data; 1995 1996 auto r = ClassRelate(Relate.Kind.None, Relate.Key(""), UMLClassDiagram.DisplayName("")); 1997 1998 final switch (type.kind.info.kind) with (TypeKind.Info) { 1999 case Kind.typeRef: 2000 auto tref = lookup.kind(type.kind.info.canonicalRef); 2001 foreach (t; tref.filter!(a => a.info.kind == Kind.record)) { 2002 auto rel_type = Relate.Kind.Aggregate; 2003 if (type.attr.isPtr || type.attr.isRef) { 2004 rel_type = Relate.Kind.Compose; 2005 } 2006 r = ClassRelate(rel_type, t.usr, 2007 cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init)); 2008 } 2009 break; 2010 case Kind.record: 2011 r = ClassRelate(Relate.Kind.Aggregate, type.kind.usr, 2012 cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init)); 2013 break; 2014 case Kind.array: 2015 auto element = lookup.kind(type.kind.info.element); 2016 foreach (e; element.filter!(a => a.info.kind == Kind.record)) { 2017 auto rel_type = Relate.Kind.Aggregate; 2018 if (type.attr.isPtr || type.attr.isRef) { 2019 rel_type = Relate.Kind.Compose; 2020 } 2021 r = ClassRelate(rel_type, e.usr, 2022 cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init)); 2023 } 2024 break; 2025 case Kind.pointer: 2026 auto pointee = lookup.kind(type.kind.info.pointee); 2027 foreach (p; pointee.filter!(a => a.info.kind == Kind.record)) { 2028 string display = p.toStringDecl(TypeAttr.init); 2029 r = ClassRelate(Relate.Kind.Compose, p.usr, cast(UMLClassDiagram.DisplayName) display); 2030 } 2031 break; 2032 case Kind.primitive: 2033 case Kind.simple: 2034 case Kind.func: 2035 case Kind.funcPtr: 2036 case Kind.funcSignature: 2037 case Kind.ctor: 2038 case Kind.dtor: 2039 case Kind.null_: 2040 break; 2041 } 2042 2043 return r; 2044 } 2045 2046 private ClassRelate getTypeRelation(LookupT)(TypeKindAttr tk, LookupT lookup) { 2047 import std.algorithm : filter; 2048 import cpptooling.data : TypeKind, TypeAttr, toStringDecl; 2049 2050 auto r = ClassRelate(Relate.Kind.None, Relate.Key(""), UMLClassDiagram.DisplayName("")); 2051 2052 final switch (tk.kind.info.kind) with (TypeKind.Info) { 2053 case Kind.typeRef: 2054 auto tref = lookup.kind(tk.kind.info.canonicalRef); 2055 foreach (t; tref.filter!(a => a.info.kind == Kind.record)) { 2056 r = ClassRelate(Relate.Kind.Associate, Relate.Key(t.usr), 2057 cast(UMLClassDiagram.DisplayName) t.toStringDecl(TypeAttr.init)); 2058 } 2059 break; 2060 case Kind.record: 2061 r = ClassRelate(Relate.Kind.Associate, tk.kind.usr, 2062 cast(UMLClassDiagram.DisplayName) tk.kind.toStringDecl(TypeAttr.init)); 2063 break; 2064 case Kind.array: 2065 auto element = lookup.kind(tk.kind.info.element); 2066 foreach (e; element.filter!(a => a.info.kind == Kind.record)) { 2067 r = ClassRelate(Relate.Kind.Associate, e.usr, 2068 cast(UMLClassDiagram.DisplayName) e.toStringDecl(TypeAttr.init)); 2069 } 2070 break; 2071 case Kind.pointer: 2072 auto pointee = lookup.kind(tk.kind.info.pointee); 2073 foreach (p; pointee.filter!(a => a.info.kind == Kind.record)) { 2074 string display = p.toStringDecl(TypeAttr.init); 2075 r = ClassRelate(Relate.Kind.Associate, Relate.Key(p.usr), 2076 cast(UMLClassDiagram.DisplayName) display); 2077 } 2078 break; 2079 case Kind.primitive: 2080 case Kind.simple: 2081 case Kind.func: 2082 case Kind.funcPtr: 2083 case Kind.funcSignature: 2084 case Kind.ctor: 2085 case Kind.dtor: 2086 case Kind.null_: 2087 } 2088 2089 return r; 2090 } 2091 2092 private auto getClassMethodRelation(LookupT)(const(CxParam)[] params, LookupT lookup) { 2093 import std.array : array; 2094 import std.algorithm : among, map, filter; 2095 import std.variant : visit; 2096 import cpptooling.data : TypeKind, TypeAttr, TypeKindAttr, toStringDecl, VariadicType; 2097 2098 static ClassRelate genParam(CxParam p, LookupT lookup) @trusted { 2099 // dfmt off 2100 return p.visit!( 2101 (TypeKindVariable tkv) => getTypeRelation(tkv.type, lookup), 2102 (TypeKindAttr tk) => getTypeRelation(tk, lookup), 2103 (VariadicType vk) 2104 { 2105 logger.error("Variadic function not supported."); 2106 // Because what types is either discovered first at runtime 2107 // or would require deeper inspection of the implementation 2108 // where the variadic is used. 2109 return ClassRelate.init; 2110 } 2111 ); 2112 // dfmt on 2113 } 2114 2115 // dfmt off 2116 return params.map!(a => genParam(a, lookup)).array(); 2117 // dfmt on 2118 } 2119 2120 void generate(UMLClassDiagram uml_class, UMLComponentDiagram uml_comp, 2121 Flag!"doGenDot" doGenDot, Generator.Modules modules) @safe { 2122 import std.algorithm : each; 2123 import std.format : format; 2124 import std.range : enumerate; 2125 2126 // TODO code duplicaton with class and component. 2127 // Generalize, reduce. 2128 2129 auto classes_preamble = modules.classes.base; 2130 classes_preamble.suppressIndent(1); 2131 foreach (idx, kv; uml_class.fanOutSorted.enumerate) { 2132 generate(kv.key, kv.value, classes_preamble); 2133 generateClassRelate(uml_class.relateTo(kv.key) 2134 .toFlatArray(cast(Relate.Key) kv.key), modules.classes); 2135 if (doGenDot) { 2136 auto nodes = modules.classes_dot.base; 2137 nodes.suppressIndent(1); 2138 nodes.stmt(format(`"%s" [label="%s"]`, kv.key, kv.value.displayName)); 2139 2140 // make a range of all relations from THIS to other components 2141 auto r = uml_class.relateTo(kv.key).toRange(cast(Relate.Key) kv.key); 2142 2143 generateDotRelate(r, idx, modules.classes_dot); 2144 } 2145 } 2146 2147 foreach (idx, kv; uml_comp.fanOutSorted.enumerate) { 2148 generate(kv.key, kv.value, modules.components); 2149 if (doGenDot) { 2150 auto nodes = modules.components_dot.base; 2151 nodes.suppressIndent(1); 2152 nodes.stmt(format(`"%s" [label="%s"]`, kv.key, kv.value.displayName)); 2153 2154 // make a range of all relations from THIS to other components 2155 auto r = uml_comp.relateTo(kv.key).toRange(cast(Relate.Key) kv.key); 2156 2157 generateDotRelate(r, idx, modules.components_dot); 2158 } 2159 } 2160 generateComponentRelate(uml_comp.relateToFlatArray, modules.components); 2161 } 2162 2163 /** Generate PlantUML class and relations from the class. 2164 * 2165 * By generating the relations out of the class directly after the class 2166 * definitions it makes it easier for GraphViz to generate a not-so-muddy 2167 * image. 2168 */ 2169 private void generate(UMLClassDiagram.Key key, const UMLClassDiagram.Class c, PlantumlModule m) @safe { 2170 import std.algorithm : each; 2171 import dsrcgen.plantuml : addSpot; 2172 2173 ClassType pc; 2174 2175 if (c.content.length == 0) { 2176 pc = m.class_(cast(string) c.displayName); 2177 } else { 2178 pc = m.classBody(cast(string) c.displayName); 2179 c.content.each!(a => pc.method(a)); 2180 } 2181 pc.addAs.text(cast(string) key); 2182 2183 //TODO add a plantuml macro and use that as color for interface 2184 // Allows the user to control the color via the PREFIX_style.iuml 2185 switch (c.classification) with (cpptooling.data.class_classification.State) { 2186 case Abstract: 2187 pc.addSpot("<< (A, Pink) >>"); 2188 break; 2189 case VirtualDtor: 2190 case Pure: 2191 pc.addSpot("<< (I, LightBlue) >>"); 2192 break; 2193 default: 2194 break; 2195 } 2196 } 2197 2198 private void generateClassRelate(T)(T relate_range, PlantumlModule m) @safe { 2199 static auto convKind(Relate.Kind kind) { 2200 static import dsrcgen.plantuml; 2201 2202 final switch (kind) with (Relate.Kind) { 2203 case None: 2204 assert(0); 2205 case Extend: 2206 return dsrcgen.plantuml.Relate.Extend; 2207 case Compose: 2208 return dsrcgen.plantuml.Relate.Compose; 2209 case Aggregate: 2210 return dsrcgen.plantuml.Relate.Aggregate; 2211 case Associate: 2212 return dsrcgen.plantuml.Relate.ArrowTo; 2213 case Relate: 2214 return dsrcgen.plantuml.Relate.Relate; 2215 } 2216 } 2217 2218 foreach (r; relate_range) { 2219 m.relate(cast(ClassNameType) r.from, cast(ClassNameType) r.to, convKind(r.kind)); 2220 } 2221 } 2222 2223 private void generateDotRelate(T)(T relate_range, ulong color_idx, PlantumlModule m) @safe { 2224 import std.format : format; 2225 2226 static import dsrcgen.plantuml; 2227 2228 static string getColor(ulong idx) { 2229 static string[] colors = [ 2230 "red", "mediumpurple", "darkorange", "deeppink", "green", "coral", 2231 "orangered", "plum", "deepskyblue", "slategray", "cadetblue", 2232 "olive", "silver", "indianred", "black" 2233 ]; 2234 return colors[idx % colors.length]; 2235 } 2236 2237 if (relate_range.length > 0) { 2238 m.stmt(format("edge [color=%s]", getColor(color_idx))); 2239 } 2240 2241 foreach (r; relate_range) { 2242 auto l = m.relate(cast(ClassNameType) r.from, cast(ClassNameType) r.to, 2243 dsrcgen.plantuml.Relate.DotArrowTo); 2244 //TODO this is ugly, fix dsrcgen relate to support graphviz/DOT 2245 auto w = new dsrcgen.plantuml.Text!PlantumlModule(format("[weight=%d] ", r.count)); 2246 l.block.prepend(w); 2247 } 2248 } 2249 2250 private void generate(UMLComponentDiagram.Key key, 2251 const UMLComponentDiagram.Component component, PlantumlModule m) @safe { 2252 import std.algorithm : map; 2253 import std.conv : text; 2254 import std.path : buildNormalizedPath, relativePath; 2255 2256 auto comp = m.classBody(cast(string) component.displayName); 2257 comp.addAs.text(cast(string) key); 2258 2259 // early exit because the slice of contains segfaults otherwise. 2260 if (component.contains.length == 0) 2261 return; 2262 2263 // dfmt off 2264 foreach (fname; component.contains[] 2265 .map!(a => cast(string) a) 2266 .map!(a => () @trusted { return buildNormalizedPath(a).relativePath; }())) { 2267 comp.m.stmt(text(fname)); 2268 } 2269 // dfmt on 2270 } 2271 2272 private void generateComponentRelate(T)(T relate_range, PlantumlModule m) @safe { 2273 static auto convKind(Relate.Kind kind) { 2274 static import dsrcgen.plantuml; 2275 2276 final switch (kind) with (Relate.Kind) { 2277 case Relate: 2278 return dsrcgen.plantuml.Relate.Relate; 2279 case Extend: 2280 assert(0); 2281 case Compose: 2282 assert(0); 2283 case Aggregate: 2284 assert(0); 2285 case Associate: 2286 return dsrcgen.plantuml.Relate.ArrowTo; 2287 case None: 2288 assert(0); 2289 } 2290 } 2291 2292 foreach (r; relate_range) { 2293 m.relate(cast(ComponentNameType) r.from, cast(ComponentNameType) r.to, convKind(r.kind)); 2294 } 2295 }