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