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