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.type : FilePrefix; 35 import cpptooling.analyzer.clang.ast : Visitor; 36 import cpptooling.data : TypeKind, TypeAttr, resolveCanonicalType, USRType, 37 TypeKindAttr, CxParam, CxReturnType, TypeKindVariable; 38 import cpptooling.data.symbol.types : FullyQualifiedNameType; 39 import cpptooling.analyzer.clang.analyze_helper : RecordResult; 40 import dextool.plugin.utility : MarkArray; 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 const 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 body { 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)(const 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 body { 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 body { 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 body { 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 const(Relate) relateTo(Key k) pure const 402 in { 403 assert(k in classes); 404 assert((cast(Relate.Key) k) in relate_to); 405 } 406 body { 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 const @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 const(Class) value; 421 } 422 423 /// Returns: An array of the key/values. 424 KeyClass[] asArray() const 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() const 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() const 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() const 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) const { 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 const { 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 body { 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 body { 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 const(Relate) relateTo(Key k) pure const @safe 601 in { 602 assert(k in components); 603 assert((cast(Relate.Key) k) in relate_to); 604 } 605 body { 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 const @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 const(Component) value; 620 } 621 622 /// Returns: Flat array of all relations of type FROM-KIND-TO-COUNT. 623 KeyComponent[] asArray() const 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() const 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() const 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() const 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 const { 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 const { 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 private static void postInit(ref typeof(this) m) { 796 m.classes_dot.suppressIndent(1); 797 m.components_dot.suppressIndent(1); 798 } 799 800 import dextool.plugin.utility : MakerInitializingClassMembers; 801 802 mixin MakerInitializingClassMembers!(Modules, postInit); 803 804 PlantumlModule classes; 805 PlantumlModule classes_dot; 806 PlantumlModule components; 807 PlantumlModule components_dot; 808 } 809 810 /** Instansiate. 811 * 812 * Params: 813 * ctrl = dynamic control of data generation. 814 * params = static control, may never change during generation. 815 * products = receiver of UML diagrams. 816 */ 817 this(Controller ctrl, Parameters params, Products products) { 818 this.ctrl = ctrl; 819 this.params = params; 820 this.products = products; 821 this.umlClass = new UMLClassDiagram; 822 this.umlComponent = new UMLComponentDiagram; 823 } 824 825 /** Process the sources to produce UML diagrams in-memory. 826 * 827 * The diagrams are forwarded to the registered Products instance. 828 */ 829 auto process() { 830 auto m = Modules.make(); 831 generate(umlClass, umlComponent, params.doGenDot, m); 832 postProcess(ctrl, params, products, m); 833 } 834 835 /// The UML diagram used as source during generation. 836 UMLClassDiagram umlClass; 837 838 /// ditto 839 UMLComponentDiagram umlComponent; 840 841 private: 842 Controller ctrl; 843 Parameters params; 844 Products products; 845 846 static void postProcess(Controller ctrl, Parameters params, Products prods, Modules m) { 847 static PlantumlRootModule makeMinimalStyle(Flag!"genClassMethod" show_methods) { 848 auto proot = PlantumlRootModule.make(); 849 850 auto class_ = proot.makeUml; 851 class_.stmt("left to right direction"); 852 class_.stmt("'skinparam linetype polyline"); 853 class_.stmt("'skinparam linetype ortho"); 854 class_.stmt("set namespaceSeparator none"); 855 if (show_methods) { 856 class_.stmt("'hide members"); 857 } else { 858 class_.stmt("hide members"); 859 } 860 861 auto component = proot.makeUml; 862 component.stmt("left to right direction"); 863 component.stmt("skinparam componentStyle uml2"); 864 component.stmt("'skinparam linetype polyline"); 865 component.stmt("'skinparam linetype ortho"); 866 component.stmt("set namespaceSeparator none"); 867 component.stmt("hide circle"); 868 component.stmt("hide methods"); 869 component.stmt("'To hide file location"); 870 component.stmt("hide members"); 871 872 return proot; 873 } 874 875 enum DotLayout { 876 Neato, 877 Dot, 878 DotOrtho 879 } 880 881 static PlantumlModule makeDotPreamble(DotLayout layout, Flag!"doSmall" doSmall) { 882 auto m = new PlantumlModule; 883 m.suppressIndent(1); 884 885 //TODO if DotOrtho and Dot don't change consider removing the code 886 // duplication. 887 final switch (layout) with (DotLayout) { 888 case Neato: 889 m.stmt("layout=neato"); 890 m.stmt("edge [len=3]"); 891 break; 892 case DotOrtho: 893 m.stmt("layout=dot"); 894 m.stmt("rankdir=LR"); 895 m.stmt("pack=true"); 896 m.stmt("concentrate=true"); 897 // inactivating, can result in a crash as of 898 // dot 2.38.0 (20140413.2041) 899 m.stmt("// activate for orthogonal lines, aka straight lines"); 900 m.stmt("// but can result in GraphViz/dot crashing"); 901 m.stmt("//splines=ortho"); 902 break; 903 case Dot: 904 m.stmt("layout=dot"); 905 m.stmt("rankdir=LR"); 906 m.stmt("pack=true"); 907 m.stmt("concentrate=true"); 908 break; 909 } 910 911 m.sep(2); 912 913 m.stmt("colorscheme=svg"); 914 if (doSmall) { 915 m.stmt("node [style=rounded shape=box fontsize=9 width=0.25 height=0.375]"); 916 } else { 917 m.stmt("node [style=rounded shape=box]"); 918 } 919 m.sep(2); 920 921 return m; 922 } 923 924 enum StyleType { 925 Class, 926 Component 927 } 928 929 static PlantumlModule makeStyleInclude(Flag!"doStyleIncl" do_style_incl, 930 Path style_file, StyleType style_type) { 931 import std.conv : to; 932 933 auto m = new PlantumlModule; 934 if (!do_style_incl) { 935 return m; 936 } 937 938 m.stmt("!include " ~ style_file ~ "!" ~ to!string(cast(int) style_type)); 939 940 return m; 941 } 942 943 static void makeUml(Products prods, Path fname, PlantumlModule style, 944 PlantumlModule content) { 945 import std.algorithm : filter; 946 947 auto proot = PlantumlRootModule.make(); 948 auto c = proot.makeUml(); 949 c.suppressIndent(1); 950 951 foreach (m; [style, content].filter!(a => a !is null)) { 952 c.append(m); 953 } 954 955 prods.putFile(fname, proot); 956 } 957 958 static void makeDot(Products prods, Path fname, PlantumlModule style, 959 PlantumlModule content) { 960 import std.algorithm : filter; 961 import std.path : stripExtension, baseName; 962 963 immutable ext_dot = ".dot"; 964 965 auto fname_dot = Path(fname.stripExtension ~ ext_dot); 966 auto dot = new PlantumlModule; 967 auto digraph = dot.digraph("g"); 968 digraph.suppressThisIndent(1); 969 foreach (m; [style, content].filter!(a => a !is null)) { 970 digraph.append(m); 971 } 972 prods.putFile(fname_dot, dot); 973 974 auto proot = PlantumlRootModule.make(); 975 auto pu = proot.makeDot; 976 pu.stmt("!include " ~ (cast(string) fname_dot).baseName); 977 prods.putFile(fname, proot); 978 } 979 980 static Path makeDotFileName(Path f, DotLayout layout) { 981 import std.path : extension, stripExtension; 982 983 auto ext = extension(cast(string) f); 984 985 string suffix; 986 final switch (layout) with (DotLayout) { 987 case Dot: 988 goto case; 989 case DotOrtho: 990 suffix = "_dot"; 991 break; 992 case Neato: 993 suffix = "_neato"; 994 break; 995 } 996 997 return Path((cast(string) f).stripExtension ~ suffix ~ ext); 998 } 999 1000 if (ctrl.genStyleInclFile) { 1001 prods.putFile(params.getFiles.styleOutput, makeMinimalStyle(params.genClassMethod)); 1002 } 1003 1004 if (params.doGenDot) { 1005 makeDot(prods, makeDotFileName(params.getFiles.classes, DotLayout.Dot), 1006 makeDotPreamble(DotLayout.Dot, Yes.doSmall), m.classes_dot); 1007 makeDot(prods, makeDotFileName(params.getFiles.classes, DotLayout.Neato), 1008 makeDotPreamble(DotLayout.Neato, Yes.doSmall), m.classes_dot); 1009 makeDot(prods, makeDotFileName(params.getFiles.components, DotLayout.Neato), 1010 makeDotPreamble(DotLayout.Neato, No.doSmall), m.components_dot); 1011 makeDot(prods, makeDotFileName(params.getFiles.components, DotLayout.DotOrtho), 1012 makeDotPreamble(DotLayout.DotOrtho, No.doSmall), m.components_dot); 1013 } 1014 1015 makeUml(prods, params.getFiles.classes, makeStyleInclude(params.doStyleIncl, 1016 params.getFiles.styleIncl, StyleType.Class), m.classes); 1017 makeUml(prods, params.getFiles.components, makeStyleInclude(params.doStyleIncl, 1018 params.getFiles.styleIncl, StyleType.Component), m.components); 1019 } 1020 } 1021 1022 private struct ClassClassificationResult { 1023 TypeKindAttr type; 1024 cpptooling.data.class_classification.State classification; 1025 } 1026 1027 private final class UMLClassVisitor(ControllerT, ReceiveT) : Visitor { 1028 import std.algorithm : map, copy, each, joiner; 1029 import std.array : Appender; 1030 import std.typecons : scoped, NullableRef; 1031 1032 import cpptooling.analyzer.clang.ast : ClassDecl, CxxBaseSpecifier, Constructor, 1033 Destructor, CxxMethod, FieldDecl, CxxAccessSpecifier, generateIndentIncrDecr; 1034 import cpptooling.analyzer.clang.analyze_helper : analyzeRecord, analyzeConstructor, analyzeDestructor, 1035 analyzeCxxMethod, analyzeFieldDecl, analyzeCxxBaseSpecified, toAccessType; 1036 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog; 1037 import cpptooling.data : CppNsStack, CppNs, AccessType, CppAccess, MemberVirtualType; 1038 1039 import cpptooling.data.class_classification : ClassificationState = State; 1040 import cpptooling.data.class_classification : classifyClass, MethodKind; 1041 1042 alias visit = Visitor.visit; 1043 1044 mixin generateIndentIncrDecr; 1045 1046 /** Type representation of this class. 1047 * Used as the source of the outgoing relations from this class. 1048 */ 1049 TypeKindAttr type; 1050 1051 /** Classification of the class. 1052 * Affected by methods. 1053 */ 1054 ClassificationState classification; 1055 1056 private { 1057 ControllerT ctrl; 1058 NullableRef!ReceiveT recv; 1059 1060 Container* container; 1061 CppNsStack ns_stack; 1062 CppAccess access; 1063 1064 /// If the class has any members. 1065 Flag!"hasMember" hasMember; 1066 } 1067 1068 this(TypeKindAttr type, const(CppNs)[] reside_in_ns, ControllerT ctrl, 1069 ref ReceiveT recv, ref Container container, in uint indent) { 1070 this.ctrl = ctrl; 1071 this.recv = &recv; 1072 this.container = &container; 1073 this.indent = indent; 1074 this.ns_stack = CppNsStack(reside_in_ns.dup); 1075 1076 this.access = CppAccess(AccessType.Private); 1077 this.classification = ClassificationState.Unknown; 1078 1079 this.type = type; 1080 } 1081 1082 /// Nested class definitions. 1083 override void visit(const(ClassDecl) v) @trusted { 1084 mixin(mixinNodeLog!()); 1085 logger.info("class: ", v.cursor.spelling); 1086 1087 auto result = analyzeRecord(v, *container, indent); 1088 1089 foreach (loc; container.find!LocationTag(result.type.kind.usr).map!(a => a.any).joiner) { 1090 if (!ctrl.doFile(loc.file, loc.file)) { 1091 return; 1092 } 1093 } 1094 1095 recv.put(result, ns_stack); 1096 1097 auto visitor = scoped!(UMLClassVisitor!(ControllerT, ReceiveT))(result.type, 1098 ns_stack, ctrl, recv, *container, indent + 1); 1099 v.accept(visitor); 1100 1101 auto result_class = ClassClassificationResult(visitor.type, visitor.classification); 1102 recv.put(this.type, result_class); 1103 } 1104 1105 /// Analyze the inheritance(s). 1106 override void visit(const(CxxBaseSpecifier) v) { 1107 import cpptooling.data : TypeKind; 1108 1109 mixin(mixinNodeLog!()); 1110 1111 auto result = analyzeCxxBaseSpecified(v, *container, indent); 1112 1113 debug { 1114 import std.algorithm : each; 1115 import std.range : retro; 1116 import cpptooling.data : CppInherit; 1117 1118 auto inherit = CppInherit(result.name, result.access); 1119 retro(result.reverseScope).each!(a => inherit.put(a)); 1120 1121 logger.trace("inherit: ", inherit.toString); 1122 } 1123 1124 recv.put(this.type, result); 1125 } 1126 1127 override void visit(const(Constructor) v) { 1128 mixin(mixinNodeLog!()); 1129 1130 auto result = analyzeConstructor(v, *container, indent); 1131 1132 debug { 1133 auto tor = CppCtor(result.type.kind.usr, result.name, result.params, access); 1134 logger.trace("ctor: ", tor.toString); 1135 } 1136 1137 recv.put(this.type, result, access); 1138 } 1139 1140 override void visit(const(Destructor) v) { 1141 mixin(mixinNodeLog!()); 1142 1143 auto result = analyzeDestructor(v, *container, indent); 1144 classification = classifyClass(classification, MethodKind.Dtor, 1145 cast(MemberVirtualType) result.virtualKind, hasMember); 1146 1147 debug { 1148 auto tor = CppDtor(result.type.kind.usr, result.name, access, result.virtualKind); 1149 logger.trace("dtor: ", tor.toString); 1150 } 1151 1152 recv.put(this.type, result, access); 1153 } 1154 1155 override void visit(const(CxxMethod) v) { 1156 mixin(mixinNodeLog!()); 1157 1158 auto result = analyzeCxxMethod(v, *container, indent); 1159 1160 classification = classifyClass(classification, MethodKind.Method, 1161 cast(MemberVirtualType) result.virtualKind, hasMember); 1162 1163 debug { 1164 import cpptooling.data.type : CppConstMethod; 1165 import cpptooling.data : CppMethod; 1166 1167 auto method = CppMethod(result.type.kind.usr, result.name, result.params, 1168 result.returnType, access, CppConstMethod(result.isConst), result.virtualKind); 1169 logger.trace("method: ", method.toString); 1170 } 1171 1172 recv.put(this.type, result, access); 1173 } 1174 1175 override void visit(const(FieldDecl) v) { 1176 mixin(mixinNodeLog!()); 1177 1178 auto result = analyzeFieldDecl(v, *container, indent); 1179 1180 // TODO probably not necessary for classification to store it as a 1181 // member. Instead extend MethodKind to having a "Member". 1182 hasMember = Yes.hasMember; 1183 classification = classifyClass(classification, MethodKind.Unknown, 1184 MemberVirtualType.Unknown, hasMember); 1185 debug { 1186 logger.trace("member: ", cast(string) result.name); 1187 } 1188 1189 recv.put(this.type, result, access); 1190 } 1191 1192 override void visit(const(CxxAccessSpecifier) v) @trusted { 1193 mixin(mixinNodeLog!()); 1194 access = CppAccess(toAccessType(v.cursor.access.accessSpecifier)); 1195 } 1196 } 1197 1198 final class UMLVisitor(ControllerT, ReceiveT) : Visitor { 1199 import std.algorithm : map, filter, cache, joiner; 1200 import std.range : chain, only, dropOne, ElementType; 1201 import std.typecons : scoped, NullableRef; 1202 1203 import cpptooling.analyzer.clang.ast : TranslationUnit, UnexposedDecl, 1204 VarDecl, FunctionDecl, ClassDecl, Namespace, generateIndentIncrDecr; 1205 import cpptooling.analyzer.clang.analyze_helper : analyzeFunctionDecl, 1206 analyzeVarDecl, analyzeRecord, analyzeTranslationUnit; 1207 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog; 1208 import cpptooling.data : CppNsStack, CppNs; 1209 1210 alias visit = Visitor.visit; 1211 1212 mixin generateIndentIncrDecr; 1213 1214 private { 1215 ReceiveT recv; 1216 ControllerT ctrl; 1217 1218 NullableRef!Container container; 1219 CppNs[] ns_stack; 1220 1221 } 1222 1223 this(ControllerT ctrl, ref ReceiveT recv, ref Container container) { 1224 this.ctrl = ctrl; 1225 this.recv = recv; 1226 this.container = &container; 1227 } 1228 1229 override void visit(const(TranslationUnit) v) { 1230 mixin(mixinNodeLog!()); 1231 v.accept(this); 1232 1233 auto result = analyzeTranslationUnit(v, container, indent); 1234 recv.put(result); 1235 } 1236 1237 override void visit(const(UnexposedDecl) v) { 1238 mixin(mixinNodeLog!()); 1239 1240 // An unexposed may be: 1241 1242 // an extern "C" 1243 // UnexposedDecl "" extern "C" {... 1244 // FunctionDecl "fun_c_linkage" void func_c_linkage 1245 v.accept(this); 1246 } 1247 1248 override void visit(const(VarDecl) v) { 1249 mixin(mixinNodeLog!()); 1250 1251 auto result = () @trusted { return analyzeVarDecl(v, container, indent); }(); 1252 1253 debug { 1254 logger.info("global variable: ", cast(string) result.name); 1255 } 1256 1257 recv.put(result); 1258 } 1259 1260 override void visit(const(FunctionDecl) v) { 1261 mixin(mixinNodeLog!()); 1262 1263 auto result = analyzeFunctionDecl(v, container, indent); 1264 1265 debug { 1266 auto func = CFunction(result.type.kind.usr, result.name, result.params, 1267 CxReturnType(result.returnType), result.isVariadic, result.storageClass); 1268 logger.info("function: ", func.toString); 1269 } 1270 1271 recv.put(result); 1272 } 1273 1274 override void visit(const(ClassDecl) v) @trusted { 1275 mixin(mixinNodeLog!()); 1276 logger.info("class: ", v.cursor.spelling); 1277 1278 auto result = analyzeRecord(v, container, indent); 1279 1280 foreach (loc; container.find!LocationTag(result.type.kind.usr).map!(a => a.any).joiner) { 1281 if (!ctrl.doFile(loc.file, loc.file)) { 1282 return; 1283 } 1284 } 1285 1286 recv.put(result, ns_stack); 1287 1288 auto visitor = scoped!(UMLClassVisitor!(ControllerT, ReceiveT))(result.type, 1289 ns_stack, ctrl, recv, container, indent + 1); 1290 v.accept(visitor); 1291 1292 auto r_classification = ClassClassificationResult(visitor.type, visitor.classification); 1293 recv.put(r_classification); 1294 } 1295 1296 override void visit(const(Namespace) v) { 1297 mixin(mixinNodeLog!()); 1298 1299 () @trusted { ns_stack ~= CppNs(v.cursor.spelling); }(); 1300 // pop the stack when done 1301 scope (exit) 1302 ns_stack = ns_stack[0 .. $ - 1]; 1303 1304 // fill the namespace with content from the analyse 1305 v.accept(this); 1306 } 1307 } 1308 1309 private struct TransformToClassDiagram(ControllerT, LookupT) { 1310 @safe: 1311 import cpptooling.analyzer.clang.analyze_helper : CxxMethodResult, 1312 ConstructorResult, DestructorResult, FieldDeclResult, CxxBaseSpecifierResult; 1313 import cpptooling.data.type : CppAccess; 1314 import cpptooling.data.type : CppNs; 1315 1316 invariant { 1317 assert(uml !is null); 1318 } 1319 1320 private { 1321 UMLClassDiagram uml; 1322 ControllerT ctrl; 1323 LookupT lookup; 1324 } 1325 1326 /// If class methods should be part of the generated class diagrams. 1327 Flag!"genClassMethod" genClassMethod; 1328 1329 /// If the parameters of methods should result in directed association. 1330 Flag!"genClassParamDependency" genClassParamDependency; 1331 1332 /// If the inheritance hierarchy between classes is generated. 1333 Flag!"genClassInheritDependency" genClassInheritDependency; 1334 1335 /// If the class members result in dependency on those members. 1336 Flag!"genClassMemberDependency" genClassMemberDependency; 1337 1338 private static string toPrefix(CppAccess access) { 1339 import cpptooling.data.type : CppAccess, AccessType; 1340 1341 final switch (access) { 1342 case AccessType.Public: 1343 return "+"; 1344 case AccessType.Protected: 1345 return "#"; 1346 case AccessType.Private: 1347 return "-"; 1348 } 1349 } 1350 1351 void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) { 1352 import std.algorithm : map, joiner; 1353 import std.conv : text; 1354 import std.range : chain, only, retro; 1355 import cpptooling.data : TypeKind, TypeAttr, toStringDecl; 1356 1357 if (genClassInheritDependency) { 1358 auto src_key = makeClassKey(src.kind.usr); 1359 1360 auto canonical = lookup.kind(result.canonicalUSR).front; 1361 auto dest_key = makeClassKey(canonical.usr); 1362 auto fqn = canonical.toStringDecl(TypeAttr.init); 1363 1364 uml.relate(src_key, dest_key, 1365 cast(UMLClassDiagram.DisplayName) fqn, Relate.Kind.Extend); 1366 } 1367 } 1368 1369 /// Reconstruct the function signature as a UML comment. 1370 void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) { 1371 import std.algorithm : filter; 1372 import std.traits : ReturnType; 1373 import std.range : chain, only; 1374 1375 import cpptooling.data : CppMethod, CppConstMethod; 1376 1377 ReturnType!makeClassKey src_key; 1378 1379 if (genClassMethod || genClassParamDependency) { 1380 src_key = makeClassKey(src.kind.usr); 1381 } 1382 1383 if (genClassMethod) { 1384 auto method = CppMethod(USRType("dummy"), result.name, result.params, 1385 result.returnType, access, CppConstMethod(result.isConst), result.virtualKind); 1386 method.usr.nullify; 1387 uml.put(src_key, UMLClassDiagram.Content(toPrefix(access) ~ method.toString)); 1388 } 1389 1390 if (genClassParamDependency) { 1391 // dfmt off 1392 auto relations = 1393 chain(getClassMethodRelation(result.params, lookup), 1394 only(getTypeRelation(cast(TypeKindAttr) result.returnType, lookup))) 1395 .filter!(a => a.kind != Relate.Kind.None) 1396 // remove self referencing keys, would result in circles which 1397 // just clutters the diagrams 1398 .filter!(a => a.key != src.kind.usr); 1399 // dfmt on 1400 foreach (rel; relations) { 1401 auto dest_key = makeClassKey(rel.key); 1402 uml.relate(src_key, dest_key, rel.display, rel.kind); 1403 } 1404 } 1405 } 1406 1407 void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) { 1408 import std.algorithm : filter; 1409 import std.traits : ReturnType; 1410 1411 import cpptooling.data : CppCtor; 1412 1413 ReturnType!makeClassKey src_key; 1414 1415 if (genClassMethod || genClassParamDependency) { 1416 src_key = makeClassKey(src.kind.usr); 1417 } 1418 1419 if (genClassMethod) { 1420 auto tor = CppCtor(result.type.kind.usr, result.name, result.params, access); 1421 uml.put(src_key, UMLClassDiagram.Content(toPrefix(access) ~ tor.toString)); 1422 } 1423 1424 if (genClassParamDependency) { 1425 // dfmt off 1426 auto relations = getClassMethodRelation(result.params, lookup) 1427 .filter!(a => a.kind != Relate.Kind.None) 1428 // remove self referencing keys, would result in circles which 1429 // just clutters the diagrams 1430 .filter!(a => a.key != src.kind.usr); 1431 // dfmt on 1432 foreach (rel; relations) { 1433 auto dest_key = makeClassKey(rel.key); 1434 uml.relate(src_key, dest_key, rel.display, rel.kind); 1435 } 1436 } 1437 } 1438 1439 void put(ref const(TypeKindAttr) src, ref const(DestructorResult) result, in CppAccess access) { 1440 import cpptooling.data : CppDtor; 1441 1442 if (genClassMethod) { 1443 auto key = makeClassKey(src.kind.usr); 1444 auto tor = CppDtor(result.type.kind.usr, result.name, access, result.virtualKind); 1445 uml.put(key, UMLClassDiagram.Content(toPrefix(access) ~ tor.toString)); 1446 } 1447 } 1448 1449 void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) { 1450 import std.algorithm : filter; 1451 1452 if (genClassMemberDependency) { 1453 auto rel = getClassMemberRelation(result.type, lookup); 1454 if (rel.kind != Relate.Kind.None) { 1455 auto src_key = makeClassKey(src.kind.usr); 1456 auto dest_key = makeClassKey(rel.key); 1457 uml.relate(src_key, dest_key, rel.display, rel.kind); 1458 } 1459 } 1460 } 1461 1462 void put(ref const(ClassClassificationResult) result) { 1463 auto key = makeClassKey(result.type.kind.usr); 1464 uml.set(key, result.classification); 1465 } 1466 1467 void put(ref const(RecordResult) src, const(CppNs)[] reside_in) { 1468 import std.algorithm : map, joiner; 1469 import std.conv : text; 1470 import std.range : chain, only; 1471 1472 auto key = makeClassKey(src.type.kind.usr); 1473 string fqn = chain(reside_in.map!(a => cast(string) a), only(cast(string) src.name)).joiner("::") 1474 .text; 1475 uml.put(key, cast(UMLClassDiagram.DisplayName) fqn); 1476 } 1477 } 1478 1479 /** Transform data from a data source (via push) to a UML component diagram. 1480 * 1481 * The component diagram is built upon the assumption that the physical 1482 * location of a declaration/definition has a correlation to the design the 1483 * creator had in mind. 1484 * 1485 * Physical world -> mental model. 1486 * 1487 * Design of relations transform: 1488 * A relation is based on where the identifier is located to the owner of the 1489 * type. 1490 * Identifier-location -> Type-owner-location. 1491 * 1492 * A type-owner-location is where the type is defined. 1493 * This though creates a problem when considering forward declarations in 1494 * combination with pointers, references, parameters. 1495 * 1496 * To handle the above case relations are go through three steps. 1497 * - Add relations with USR->USR. 1498 * - First try. Check both USRs location. If both of them are definitions then 1499 * accept the relation. Otherwise put it into the cache. 1500 * - Second try. Process the cache at the end of a translation unit. Same 1501 * criteria as the first try. 1502 * - Third try. When all translation units have been processed use a fallback 1503 * strategy for those items left in the cache. At this stage a location 1504 * corresponding to a declaration is OK. Reason, better than nothing. 1505 * 1506 * In the following example the processing is a.h before b.h. 1507 * If the locatoin of the forward declaration of B had been used the relation 1508 * from a.h to b.h would have been lost. 1509 * 1510 * Example: 1511 * a.h 1512 * --- 1513 * class B; 1514 * class A { 1515 * B* b; 1516 * }; 1517 * --- 1518 * 1519 * b.h 1520 * --- 1521 * class B {}; 1522 * --- 1523 */ 1524 private @safe struct TransformToComponentDiagram(ControllerT, LookupT) { 1525 import std.algorithm : map, copy, each, joiner; 1526 import std.range : chain; 1527 1528 import cpptooling.analyzer.clang.analyze_helper : CxxBaseSpecifierResult, 1529 CxxMethodResult, ConstructorResult, DestructorResult, 1530 RecordResult, FieldDeclResult, VarDeclResult, FunctionDeclResult, TranslationUnitResult; 1531 import cpptooling.data.symbol : Container; 1532 import cpptooling.data : CppAccess, CxReturnType; 1533 1534 invariant { 1535 assert(diagram !is null); 1536 assert(ctrl !is null); 1537 } 1538 1539 private { 1540 static struct USRRelation { 1541 USRType from; 1542 USRType to; 1543 Relate.Kind kind; 1544 } 1545 1546 UMLComponentDiagram diagram; 1547 ControllerT ctrl; 1548 LookupT lookup; 1549 MarkArray!USRRelation dcache; 1550 USRType[] src_cache; 1551 } 1552 1553 this(UMLComponentDiagram diagram, ControllerT ctrl, LookupT lookup) { 1554 this.diagram = diagram; 1555 this.ctrl = ctrl; 1556 this.lookup = lookup; 1557 } 1558 1559 /** Store the relations in the cache for later resolution regarding there 1560 * location. 1561 * 1562 * The concept is a source has relations to many destinations. 1563 * 1564 * The relation is hard coded as an Association. 1565 * If the function is generalized to be reused with Class then the hard 1566 * coded must be a lookup table or something to allow differentiating 1567 * depending on "stuff". 1568 * 1569 * It is by design that the src do NOT go via resolveCanonicalType. A free 1570 * variable that is a pointer shall have the "src" still as the pointer 1571 * itself but the destination is the pointed at type. 1572 * 1573 * Params: 1574 * src = source of the relations 1575 * range = destinations of the relations 1576 * target = cache to put the values into 1577 * lookup = type supporting lookups via USR for the TypeKind 1578 */ 1579 static void putToCache(Range, T)(USRType src, Range range, ref T target, LookupT lookup) @safe 1580 if (is(ElementType!Range == TypeKindAttr) 1581 || is(ElementType!Range == const(TypeKindAttr))) { 1582 import std.algorithm : filter; 1583 1584 // dfmt off 1585 foreach(a; range 1586 // remove primitive types 1587 .filter!(a => a.kind.info.kind != TypeKind.Info.Kind.primitive) 1588 .map!(a => resolveCanonicalType(a.kind, a.attr, lookup)) 1589 .joiner 1590 .map!(a => a.kind.usr) 1591 // create the relations of type src-to-kind 1592 .map!(to_ => USRRelation(src, to_, Relate.Kind.Associate))) { 1593 target.put(a); 1594 } 1595 // dfmt on 1596 } 1597 1598 /// ditto 1599 static void putParamsToCache(T)(ref const(TypeKindAttr) src, 1600 const(CxParam)[] params, ref T target, LookupT lookup) @safe { 1601 // dfmt off 1602 auto range = params 1603 // returns a bunch of ranges of the unpacked parameters 1604 .map!(a => unpackParam(a)) 1605 .joiner; 1606 // dfmt on 1607 1608 putToCache(src.kind.usr, range, target, lookup); 1609 } 1610 1611 static void finalizeSrcCache(LookupT, TargetT)(USRType[] cache, LookupT lookup, TargetT target) { 1612 import std.algorithm : map, joiner; 1613 1614 // dfmt off 1615 foreach (loc; cache 1616 .map!(usr => lookup.location(usr)) 1617 .joiner 1618 .map!(a => a.any) 1619 .joiner) { 1620 target.putSrc(loc); 1621 } 1622 // dfmt on 1623 } 1624 1625 /// Process the last bits left in the cache. 1626 void finalize() { 1627 import std.algorithm : map, filter, cache; 1628 import std.range : enumerate, only; 1629 import std.typecons : tuple; 1630 1631 finalizeSrcCache(src_cache[], lookup, this); 1632 if (src_cache.length > 0) { 1633 logger.tracef("%d relations left in src cache", src_cache.length); 1634 } 1635 src_cache.length = 0; 1636 1637 if (dcache.data.length > 0) { 1638 logger.tracef("%d relations left. Activating fallback strategy", dcache.data.length); 1639 } 1640 1641 // dfmt off 1642 foreach (e; dcache.data 1643 // keep track of the index to allow marking of the cache for removal 1644 .enumerate 1645 // find the types 1646 .map!(a => tuple(a.index, lookup.location(a.value.from), lookup.location(a.value.to))) 1647 .cache 1648 // a zero range means a failed lookup, a broken relation 1649 .filter!(a => a[1].length != 0 && a[2].length != 0) 1650 // unpack with fallback 1651 .map!(a => tuple(a[0], a[1].front.any, a[2].front.any)) 1652 // ensure that both both resulted in valid ranges 1653 .filter!(a => a[1].length != 0 && a[2].length != 0) 1654 // unpack 1655 .map!(a => tuple(a[0], a[1].front, a[2].front)) 1656 // check via ctrl (the user) if the destination is "ok" 1657 .filter!(a => ctrl.doFile(cast(string) a[2].file, cast(string) a[2].file)) 1658 ) { 1659 //TODO warn when a declaration has been used? 1660 1661 putDest(e[1], e[2], Relate.Kind.Associate); 1662 dcache.markForRemoval(e[0]); 1663 } 1664 // dfmt on 1665 1666 dcache.doRemoval; 1667 1668 if (dcache.data.length > 0) { 1669 logger.errorf("Fallback strategy failed for %d USRs. They are:", dcache.data.length); 1670 } 1671 1672 foreach (e; dcache.data) { 1673 logger.tracef(" %s -> %s", cast(string) e.from, cast(string) e.to); 1674 } 1675 } 1676 1677 void put(ref const(TranslationUnitResult) result) { 1678 import std.algorithm : map, filter, cache; 1679 import std.range : enumerate, only; 1680 import std.typecons : tuple; 1681 1682 finalizeSrcCache(src_cache[], lookup, this); 1683 if (src_cache.length > 0) { 1684 logger.tracef("%d relations left in src cache", src_cache.length); 1685 } 1686 src_cache.length = 0; 1687 1688 // dfmt off 1689 foreach (e; dcache.data 1690 // keep track of the index to allow marking of the cache for removal 1691 .enumerate 1692 // find the types 1693 .map!(a => tuple(a.index, lookup.location(a.value.from), lookup.location(a.value.to))) 1694 .cache 1695 // a zero range means a failed lookup, a broken relation 1696 .filter!(a => a[1].length != 0 && a[2].length != 0) 1697 // unpack 1698 .map!(a => tuple(a[0], a[1].front, a[2].front)) 1699 // only okey with a relatioin TO something that is a definition 1700 .filter!(a => a[1].hasDefinition && a[2].hasDefinition) 1701 // check via ctrl (the user) if the destination is "ok" 1702 .filter!(a => ctrl.doFile(cast(string) a[2].definition.file, cast(string) a[2].definition.file)) 1703 ) { 1704 putDest(e[1].definition, e[2].definition, Relate.Kind.Associate); 1705 dcache.markForRemoval(e[0]); 1706 } 1707 // dfmt on 1708 1709 dcache.doRemoval; 1710 } 1711 1712 void put(ref const(RecordResult) result) { 1713 src_cache ~= result.type.kind.usr; 1714 } 1715 1716 void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) { 1717 putParamsToCache(src, result.params, dcache, lookup); 1718 } 1719 1720 void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) { 1721 import std.range : only; 1722 1723 putParamsToCache(src, result.params, dcache, lookup); 1724 putToCache(src.kind.usr, only((cast(const TypeKindAttr) result.returnType)), dcache, lookup); 1725 } 1726 1727 void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) { 1728 import std.range : only; 1729 1730 putToCache(src.kind.usr, only(result.type), dcache, lookup); 1731 } 1732 1733 void put(ref const(TypeKindAttr) src, ref const(ClassClassificationResult) result) { 1734 import std.range : only; 1735 1736 // called when creating a relation for a nested class 1737 putToCache(src.kind.usr, only(result.type), dcache, lookup); 1738 } 1739 1740 void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) { 1741 auto r0 = lookup.kind(result.canonicalUSR).map!(a => TypeKindAttr(a.get, TypeAttr.init)); 1742 1743 putToCache(src.kind.usr, r0, dcache, lookup); 1744 } 1745 1746 void put(ref const(VarDeclResult) result) { 1747 import std.range : only; 1748 1749 // primitive types do not have a location 1750 if (result.location.kind == LocationTag.Kind.loc) { 1751 putSrc(result.location); 1752 1753 putToCache(result.instanceUSR, only(result.type), dcache, lookup); 1754 } 1755 } 1756 1757 void put(ref const(FunctionDeclResult) result) { 1758 import std.range : only; 1759 1760 src_cache ~= result.type.kind.usr; 1761 1762 putParamsToCache(result.type, result.params, dcache, lookup); 1763 putToCache(result.type.kind.usr, 1764 only(cast(const TypeKindAttr) result.returnType), dcache, lookup); 1765 } 1766 1767 void putSrc(ref const(LocationTag) src) @safe { 1768 string location = src.file; 1769 1770 if (src.kind == LocationTag.Kind.noloc || !ctrl.doFile(location, location)) { 1771 return; 1772 } 1773 1774 auto key = makeComponentKey(location, ctrl); 1775 diagram.put(key.key, cast(UMLComponentDiagram.DisplayName) key.display); 1776 diagram.put(key.key, cast(UMLComponentDiagram.Location) location); 1777 } 1778 1779 void putDest(ref const(LocationTag) src, ref const(LocationTag) dest, Relate.Kind kind) { 1780 auto src_ = makeComponentKey(src.file, ctrl); 1781 auto dest_ = makeComponentKey(dest.file, ctrl); 1782 1783 // Ignoring self referencing relations. 1784 if (src_.key == dest_.key) { 1785 return; 1786 } 1787 1788 diagram.relate(src_.key, dest_.key, 1789 cast(UMLComponentDiagram.DisplayName) dest_.display, kind); 1790 } 1791 } 1792 1793 /** Route information to specific transformers. 1794 * 1795 * No manipulation of data is to be done in this struct. Only routing to 1796 * appropriate functions. 1797 */ 1798 class TransformToDiagram(ControllerT, ParametersT, LookupT) { 1799 import std.range : only; 1800 1801 import cpptooling.analyzer.clang.analyze_helper : CxxBaseSpecifierResult, 1802 RecordResult, FieldDeclResult, CxxMethodResult, 1803 ConstructorResult, DestructorResult, VarDeclResult, FunctionDeclResult, 1804 TranslationUnitResult; 1805 import cpptooling.data.symbol.types : USRType; 1806 import cpptooling.data : TypeKind, CppNs, CppAccess; 1807 1808 private { 1809 TransformToComponentDiagram!(ControllerT, LookupT) to_component; 1810 TransformToClassDiagram!(ControllerT, LookupT) to_class; 1811 } 1812 1813 this(ControllerT ctrl, ParametersT params, LookupT lookup, 1814 UMLComponentDiagram comp_dia, UMLClassDiagram class_dia) { 1815 to_component = typeof(to_component)(comp_dia, ctrl, lookup); 1816 to_class = typeof(to_class)(class_dia, ctrl, lookup, params.genClassMethod, 1817 params.genClassParamDependency, params.genClassInheritDependency, 1818 params.genClassMemberDependency); 1819 } 1820 1821 @safe: 1822 1823 /** Signal that diagrams to perform a finalization of cached data. 1824 */ 1825 void finalize() { 1826 to_component.finalize(); 1827 } 1828 1829 void put(ref const(TranslationUnitResult) result) { 1830 to_component.put(result); 1831 } 1832 1833 void put(ref const(RecordResult) result, const(CppNs)[] reside_in) { 1834 to_class.put(result, reside_in); 1835 to_component.put(result); 1836 } 1837 1838 void put(ref const(TypeKindAttr) src, ref const(CxxBaseSpecifierResult) result) { 1839 to_class.put(src, result); 1840 to_component.put(src, result); 1841 } 1842 1843 void put(ref const(TypeKindAttr) src, ref const(CxxMethodResult) result, in CppAccess access) { 1844 to_class.put(src, result, access); 1845 to_component.put(src, result, access); 1846 } 1847 1848 void put(ref const(TypeKindAttr) src, ref const(ConstructorResult) result, in CppAccess access) { 1849 to_class.put(src, result, access); 1850 to_component.put(src, result, access); 1851 } 1852 1853 void put(ref const(TypeKindAttr) src, ref const(DestructorResult) result, in CppAccess access) { 1854 to_class.put(src, result, access); 1855 } 1856 1857 void put(ref const(TypeKindAttr) src, ref const(FieldDeclResult) result, in CppAccess access) { 1858 to_class.put(src, result, access); 1859 to_component.put(src, result, access); 1860 } 1861 1862 void put(ref const(ClassClassificationResult) result) { 1863 to_class.put(result); 1864 } 1865 1866 /** A nested class. 1867 * 1868 * Propagate the classification and relation of the root->nested. 1869 */ 1870 void put(ref const(TypeKindAttr) src, ref const(ClassClassificationResult) result) { 1871 to_component.put(src, result); 1872 // only needs result 1873 to_class.put(result); 1874 } 1875 1876 void put(ref const(VarDeclResult) result) { 1877 to_component.put(result); 1878 } 1879 1880 void put(ref const(FunctionDeclResult) result) { 1881 to_component.put(result); 1882 } 1883 } 1884 1885 // visualize where the module private starts 1886 private: // ****************************************************************** 1887 1888 import cpptooling.data.representation : CppRoot, CppClass, CppMethod, CppCtor, 1889 CppDtor, CppNamespace, CFunction, CxGlobalVariable, LocationTag, Location; 1890 import cpptooling.data.symbol : Container; 1891 import dsrcgen.plantuml; 1892 1893 struct KeyValue { 1894 UMLComponentDiagram.Key key; 1895 string display; 1896 string absFilePath; 1897 } 1898 1899 struct KeyRelate { 1900 string file; 1901 KeyValue key; 1902 Relate.Kind kind; 1903 } 1904 1905 /** 1906 * Params: 1907 * file = filename of the relation. 1908 * kind = kind of relation such as associaiton, composition etc. 1909 */ 1910 struct PathKind { 1911 string file; 1912 Relate.Kind kind; 1913 } 1914 1915 /** Calculate the key based on the directory the file that declares the symbol exist in. 1916 * 1917 * Additional metadata as to make it possible to backtrack. 1918 */ 1919 KeyValue makeComponentKey(in string location_file, Controller ctrl) @trusted { 1920 import std.array : appender; 1921 import std.base64 : Base64Impl, Base64; 1922 import std.path : buildNormalizedPath, absolutePath, relativePath, baseName; 1923 import std.typecons : tuple; 1924 1925 // TODO consider using hash murmur2/3 to shorten the length of the encoded 1926 // path 1927 1928 alias SafeBase64 = Base64Impl!('-', '_', Base64.NoPadding); 1929 1930 string file_path = buildNormalizedPath(location_file.absolutePath); 1931 string strip_path = cast(string) ctrl.doComponentNameStrip(Path(file_path)); 1932 string rel_path = relativePath(strip_path); 1933 string display_name = strip_path.baseName; 1934 1935 auto enc = appender!(char[])(); 1936 SafeBase64.encode(cast(ubyte[]) rel_path, enc); 1937 1938 auto k = KeyValue(UMLComponentDiagram.Key(enc.data.idup), display_name, strip_path); 1939 1940 debug { 1941 logger.tracef("Component:%s stripped:%s file:%s base64:%s", k.display, 1942 strip_path, file_path, cast(string) k.key); 1943 } 1944 1945 return k; 1946 } 1947 1948 UMLClassDiagram.Key makeClassKey(in USRType key) @trusted { 1949 import std.base64 : Base64Impl, Base64; 1950 import std.array : appender; 1951 1952 // TODO consider using hash murmur2/3 function to shorten the length of the 1953 // encoded path 1954 1955 alias SafeBase64 = Base64Impl!('-', '_', Base64.NoPadding); 1956 1957 auto enc = appender!(char[])(); 1958 SafeBase64.encode(cast(ubyte[])(cast(string) key), enc); 1959 1960 auto k = UMLClassDiagram.Key(enc.data.idup); 1961 return k; 1962 } 1963 1964 private auto unpackParam(CxParam p) @trusted { 1965 import std.range : only, dropOne; 1966 import std.variant : visit; 1967 import cpptooling.data : TypeKindVariable, VariadicType; 1968 1969 // dfmt off 1970 return p.visit!( 1971 (TypeKindVariable v) => only(v.type), 1972 (TypeKindAttr v) => only(v), 1973 (VariadicType v) { 1974 logger.error( 1975 "Variadic function not supported. Would require runtime information to relate."); 1976 return only(TypeKindAttr.init).dropOne; 1977 }); 1978 // dfmt on 1979 } 1980 1981 struct ClassRelate { 1982 Relate.Kind kind; 1983 Relate.Key key; 1984 UMLClassDiagram.DisplayName display; 1985 } 1986 1987 auto getClassMemberRelation(LookupT)(TypeKindAttr type, LookupT lookup) { 1988 //TODO code duplication with getMethodRelation 1989 // .. fix it. This function is ugly. 1990 import std.algorithm : each, map, filter, joiner; 1991 import std.array : array; 1992 import std.typecons : tuple; 1993 1994 // TODO this is a mega include. Reduce it. 1995 import cpptooling.data; 1996 1997 auto r = ClassRelate(Relate.Kind.None, Relate.Key(""), UMLClassDiagram.DisplayName("")); 1998 1999 final switch (type.kind.info.kind) with (TypeKind.Info) { 2000 case Kind.typeRef: 2001 auto tref = lookup.kind(type.kind.info.canonicalRef); 2002 foreach (t; tref.filter!(a => a.info.kind == Kind.record)) { 2003 auto rel_type = Relate.Kind.Aggregate; 2004 if (type.attr.isPtr || type.attr.isRef) { 2005 rel_type = Relate.Kind.Compose; 2006 } 2007 r = ClassRelate(rel_type, t.usr, 2008 cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init)); 2009 } 2010 break; 2011 case Kind.record: 2012 r = ClassRelate(Relate.Kind.Aggregate, type.kind.usr, 2013 cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init)); 2014 break; 2015 case Kind.array: 2016 auto element = lookup.kind(type.kind.info.element); 2017 foreach (e; element.filter!(a => a.info.kind == Kind.record)) { 2018 auto rel_type = Relate.Kind.Aggregate; 2019 if (type.attr.isPtr || type.attr.isRef) { 2020 rel_type = Relate.Kind.Compose; 2021 } 2022 r = ClassRelate(rel_type, e.usr, 2023 cast(UMLClassDiagram.DisplayName) type.kind.toStringDecl(TypeAttr.init)); 2024 } 2025 break; 2026 case Kind.pointer: 2027 auto pointee = lookup.kind(type.kind.info.pointee); 2028 foreach (p; pointee.filter!(a => a.info.kind == Kind.record)) { 2029 string display = p.toStringDecl(TypeAttr.init); 2030 r = ClassRelate(Relate.Kind.Compose, p.usr, cast(UMLClassDiagram.DisplayName) display); 2031 } 2032 break; 2033 case Kind.primitive: 2034 case Kind.simple: 2035 case Kind.func: 2036 case Kind.funcPtr: 2037 case Kind.funcSignature: 2038 case Kind.ctor: 2039 case Kind.dtor: 2040 case Kind.null_: 2041 break; 2042 } 2043 2044 return r; 2045 } 2046 2047 private ClassRelate getTypeRelation(LookupT)(TypeKindAttr tk, LookupT lookup) { 2048 import std.algorithm : filter; 2049 import cpptooling.data : TypeKind, TypeAttr, toStringDecl; 2050 2051 auto r = ClassRelate(Relate.Kind.None, Relate.Key(""), UMLClassDiagram.DisplayName("")); 2052 2053 final switch (tk.kind.info.kind) with (TypeKind.Info) { 2054 case Kind.typeRef: 2055 auto tref = lookup.kind(tk.kind.info.canonicalRef); 2056 foreach (t; tref.filter!(a => a.info.kind == Kind.record)) { 2057 r = ClassRelate(Relate.Kind.Associate, Relate.Key(t.usr), 2058 cast(UMLClassDiagram.DisplayName) t.toStringDecl(TypeAttr.init)); 2059 } 2060 break; 2061 case Kind.record: 2062 r = ClassRelate(Relate.Kind.Associate, tk.kind.usr, 2063 cast(UMLClassDiagram.DisplayName) tk.kind.toStringDecl(TypeAttr.init)); 2064 break; 2065 case Kind.array: 2066 auto element = lookup.kind(tk.kind.info.element); 2067 foreach (e; element.filter!(a => a.info.kind == Kind.record)) { 2068 r = ClassRelate(Relate.Kind.Associate, e.usr, 2069 cast(UMLClassDiagram.DisplayName) e.toStringDecl(TypeAttr.init)); 2070 } 2071 break; 2072 case Kind.pointer: 2073 auto pointee = lookup.kind(tk.kind.info.pointee); 2074 foreach (p; pointee.filter!(a => a.info.kind == Kind.record)) { 2075 string display = p.toStringDecl(TypeAttr.init); 2076 r = ClassRelate(Relate.Kind.Associate, Relate.Key(p.usr), 2077 cast(UMLClassDiagram.DisplayName) display); 2078 } 2079 break; 2080 case Kind.primitive: 2081 case Kind.simple: 2082 case Kind.func: 2083 case Kind.funcPtr: 2084 case Kind.funcSignature: 2085 case Kind.ctor: 2086 case Kind.dtor: 2087 case Kind.null_: 2088 } 2089 2090 return r; 2091 } 2092 2093 private auto getClassMethodRelation(LookupT)(const(CxParam)[] params, LookupT lookup) { 2094 import std.array : array; 2095 import std.algorithm : among, map, filter; 2096 import std.variant : visit; 2097 import cpptooling.data : TypeKind, TypeAttr, TypeKindAttr, toStringDecl, VariadicType; 2098 2099 static ClassRelate genParam(CxParam p, LookupT lookup) @trusted { 2100 // dfmt off 2101 return p.visit!( 2102 (TypeKindVariable tkv) => getTypeRelation(tkv.type, lookup), 2103 (TypeKindAttr tk) => getTypeRelation(tk, lookup), 2104 (VariadicType vk) 2105 { 2106 logger.error("Variadic function not supported."); 2107 // Because what types is either discovered first at runtime 2108 // or would require deeper inspection of the implementation 2109 // where the variadic is used. 2110 return ClassRelate.init; 2111 } 2112 ); 2113 // dfmt on 2114 } 2115 2116 // dfmt off 2117 return params.map!(a => genParam(a, lookup)).array(); 2118 // dfmt on 2119 } 2120 2121 void generate(UMLClassDiagram uml_class, UMLComponentDiagram uml_comp, 2122 Flag!"doGenDot" doGenDot, Generator.Modules modules) @safe { 2123 import std.algorithm : each; 2124 import std.format : format; 2125 import std.range : enumerate; 2126 2127 // TODO code duplicaton with class and component. 2128 // Generalize, reduce. 2129 2130 auto classes_preamble = modules.classes.base; 2131 classes_preamble.suppressIndent(1); 2132 foreach (idx, kv; uml_class.fanOutSorted.enumerate) { 2133 generate(kv.key, kv.value, classes_preamble); 2134 generateClassRelate(uml_class.relateTo(kv.key) 2135 .toFlatArray(cast(Relate.Key) kv.key), modules.classes); 2136 if (doGenDot) { 2137 auto nodes = modules.classes_dot.base; 2138 nodes.suppressIndent(1); 2139 nodes.stmt(format(`"%s" [label="%s"]`, kv.key, kv.value.displayName)); 2140 2141 // make a range of all relations from THIS to other components 2142 auto r = uml_class.relateTo(kv.key).toRange(cast(Relate.Key) kv.key); 2143 2144 generateDotRelate(r, idx, modules.classes_dot); 2145 } 2146 } 2147 2148 foreach (idx, kv; uml_comp.fanOutSorted.enumerate) { 2149 generate(kv.key, kv.value, modules.components); 2150 if (doGenDot) { 2151 auto nodes = modules.components_dot.base; 2152 nodes.suppressIndent(1); 2153 nodes.stmt(format(`"%s" [label="%s"]`, kv.key, kv.value.displayName)); 2154 2155 // make a range of all relations from THIS to other components 2156 auto r = uml_comp.relateTo(kv.key).toRange(cast(Relate.Key) kv.key); 2157 2158 generateDotRelate(r, idx, modules.components_dot); 2159 } 2160 } 2161 generateComponentRelate(uml_comp.relateToFlatArray, modules.components); 2162 } 2163 2164 /** Generate PlantUML class and relations from the class. 2165 * 2166 * By generating the relations out of the class directly after the class 2167 * definitions it makes it easier for GraphViz to generate a not-so-muddy 2168 * image. 2169 */ 2170 private void generate(UMLClassDiagram.Key key, const UMLClassDiagram.Class c, PlantumlModule m) @safe { 2171 import std.algorithm : each; 2172 import dsrcgen.plantuml : addSpot; 2173 2174 ClassType pc; 2175 2176 if (c.content.length == 0) { 2177 pc = m.class_(cast(string) c.displayName); 2178 } else { 2179 pc = m.classBody(cast(string) c.displayName); 2180 c.content.each!(a => pc.method(a)); 2181 } 2182 pc.addAs.text(cast(string) key); 2183 2184 //TODO add a plantuml macro and use that as color for interface 2185 // Allows the user to control the color via the PREFIX_style.iuml 2186 switch (c.classification) with (cpptooling.data.class_classification.State) { 2187 case Abstract: 2188 pc.addSpot("<< (A, Pink) >>"); 2189 break; 2190 case VirtualDtor: 2191 case Pure: 2192 pc.addSpot("<< (I, LightBlue) >>"); 2193 break; 2194 default: 2195 break; 2196 } 2197 } 2198 2199 private void generateClassRelate(T)(T relate_range, PlantumlModule m) @safe { 2200 static auto convKind(Relate.Kind kind) { 2201 static import dsrcgen.plantuml; 2202 2203 final switch (kind) with (Relate.Kind) { 2204 case None: 2205 assert(0); 2206 case Extend: 2207 return dsrcgen.plantuml.Relate.Extend; 2208 case Compose: 2209 return dsrcgen.plantuml.Relate.Compose; 2210 case Aggregate: 2211 return dsrcgen.plantuml.Relate.Aggregate; 2212 case Associate: 2213 return dsrcgen.plantuml.Relate.ArrowTo; 2214 case Relate: 2215 return dsrcgen.plantuml.Relate.Relate; 2216 } 2217 } 2218 2219 foreach (r; relate_range) { 2220 m.relate(cast(ClassNameType) r.from, cast(ClassNameType) r.to, convKind(r.kind)); 2221 } 2222 } 2223 2224 private void generateDotRelate(T)(T relate_range, ulong color_idx, PlantumlModule m) @safe { 2225 import std.format : format; 2226 2227 static import dsrcgen.plantuml; 2228 2229 static string getColor(ulong idx) { 2230 static string[] colors = [ 2231 "red", "mediumpurple", "darkorange", "deeppink", "green", "coral", 2232 "orangered", "plum", "deepskyblue", "slategray", "cadetblue", 2233 "olive", "silver", "indianred", "black" 2234 ]; 2235 return colors[idx % colors.length]; 2236 } 2237 2238 if (relate_range.length > 0) { 2239 m.stmt(format("edge [color=%s]", getColor(color_idx))); 2240 } 2241 2242 foreach (r; relate_range) { 2243 auto l = m.relate(cast(ClassNameType) r.from, cast(ClassNameType) r.to, 2244 dsrcgen.plantuml.Relate.DotArrowTo); 2245 //TODO this is ugly, fix dsrcgen relate to support graphviz/DOT 2246 auto w = new dsrcgen.plantuml.Text!PlantumlModule(format("[weight=%d] ", r.count)); 2247 l.block.prepend(w); 2248 } 2249 } 2250 2251 private void generate(UMLComponentDiagram.Key key, 2252 const UMLComponentDiagram.Component component, PlantumlModule m) @safe { 2253 import std.algorithm : map; 2254 import std.conv : text; 2255 import std.path : buildNormalizedPath, relativePath; 2256 2257 auto comp = m.classBody(cast(string) component.displayName); 2258 comp.addAs.text(cast(string) key); 2259 2260 // early exit because the slice of contains segfaults otherwise. 2261 if (component.contains.length == 0) 2262 return; 2263 2264 // dfmt off 2265 foreach (fname; component.contains[] 2266 .map!(a => cast(string) a) 2267 .map!(a => () @trusted { return buildNormalizedPath(a).relativePath; }())) { 2268 comp.m.stmt(text(fname)); 2269 } 2270 // dfmt on 2271 } 2272 2273 private void generateComponentRelate(T)(T relate_range, PlantumlModule m) @safe { 2274 static auto convKind(Relate.Kind kind) { 2275 static import dsrcgen.plantuml; 2276 2277 final switch (kind) with (Relate.Kind) { 2278 case Relate: 2279 return dsrcgen.plantuml.Relate.Relate; 2280 case Extend: 2281 assert(0); 2282 case Compose: 2283 assert(0); 2284 case Aggregate: 2285 assert(0); 2286 case Associate: 2287 return dsrcgen.plantuml.Relate.ArrowTo; 2288 case None: 2289 assert(0); 2290 } 2291 } 2292 2293 foreach (r; relate_range) { 2294 m.relate(cast(ComponentNameType) r.from, cast(ComponentNameType) r.to, convKind(r.kind)); 2295 } 2296 }