UNPKG

sol2uml

Version:

Solidity contract visualisation tool.

177 lines 7.98 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.convertUmlClasses2Dot = convertUmlClasses2Dot; exports.addAssociationsToDot = addAssociationsToDot; const path_1 = require("path"); const converterClass2Dot_1 = require("./converterClass2Dot"); const umlClass_1 = require("./umlClass"); const associations_1 = require("./associations"); const debug = require('debug')('sol2uml'); /** * Converts UML classes to Graphviz's DOT format. * The DOT grammar defines Graphviz nodes, edges, graphs, subgraphs, and clusters http://www.graphviz.org/doc/info/lang.html * @param umlClasses array of UML classes of type `UMLClass` * @param clusterFolders flag if UML classes are to be clustered into folders their source code was in * @param classOptions command line options for the `class` command * @return dotString Graphviz's DOT format for defining nodes, edges and clusters. */ function convertUmlClasses2Dot(umlClasses, clusterFolders = false, classOptions = {}) { let dotString = ` digraph UmlClassDiagram { rankdir=BT arrowhead=open bgcolor="${classOptions.backColor}" edge [color="${classOptions.shapeColor}"] node [shape=record, style=filled, color="${classOptions.shapeColor}", fillcolor="${classOptions.fillColor}", fontcolor="${classOptions.textColor}"]`; // Sort UML Classes by folder of source file const umlClassesSortedByCodePath = sortUmlClassesByCodePath(umlClasses); let currentCodeFolder = ''; for (const umlClass of umlClassesSortedByCodePath) { const codeFolder = (0, path_1.dirname)(umlClass.relativePath); if (currentCodeFolder !== codeFolder) { // Need to close off the last subgraph if not the first if (currentCodeFolder != '') { dotString += '\n}'; } dotString += ` subgraph ${getSubGraphName(clusterFolders)} { label="${codeFolder}"`; currentCodeFolder = codeFolder; } dotString += (0, converterClass2Dot_1.convertClass2Dot)(umlClass, classOptions); } // Need to close off the last subgraph if not the first if (currentCodeFolder != '') { dotString += '\n}'; } dotString += addAssociationsToDot(umlClasses, classOptions); // Need to close off the last the digraph dotString += '\n}'; debug(dotString); return dotString; } let subGraphCount = 0; function getSubGraphName(clusterFolders = false) { if (clusterFolders) { return ` cluster_${subGraphCount++}`; } return ` graph_${subGraphCount++}`; } function sortUmlClassesByCodePath(umlClasses) { return umlClasses.sort((a, b) => { if (a.relativePath < b.relativePath) { return -1; } if (a.relativePath > b.relativePath) { return 1; } return 0; }); } function addAssociationsToDot(umlClasses, classOptions = {}) { let dotString = ''; // for each class for (const sourceUmlClass of umlClasses) { if (!classOptions.hideEnums) { // for each enum in the class sourceUmlClass.enums.forEach((enumId) => { // Has the enum been filtered out? eg depth limited const targetUmlClass = umlClasses.find((c) => c.id === enumId); if (targetUmlClass) { // Draw aggregated link from contract to contract level Enum dotString += `\n${enumId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]`; } }); } if (!classOptions.hideStructs) { // for each struct in the class sourceUmlClass.structs.forEach((structId) => { // Has the struct been filtered out? eg depth limited const targetUmlClass = umlClasses.find((c) => c.id === structId); if (targetUmlClass) { // Draw aggregated link from contract to contract level Struct dotString += `\n${structId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]`; } }); } // for each association in that class for (const association of Object.values(sourceUmlClass.associations)) { const targetUmlClass = (0, associations_1.findAssociatedClass)(association, sourceUmlClass, umlClasses); if (targetUmlClass) { dotString += addAssociationToDot(sourceUmlClass, targetUmlClass, association, classOptions); } } } return dotString; } function addAssociationToDot(sourceUmlClass, targetUmlClass, association, classOptions = {}) { // do not include library or interface associations if hidden // Or associations to Structs, Enums or Constants if they are hidden if ((classOptions.hideLibraries && (sourceUmlClass.stereotype === umlClass_1.ClassStereotype.Library || targetUmlClass.stereotype === umlClass_1.ClassStereotype.Library)) || (classOptions.hideInterfaces && (targetUmlClass.stereotype === umlClass_1.ClassStereotype.Interface || sourceUmlClass.stereotype === umlClass_1.ClassStereotype.Interface)) || (classOptions.hideAbstracts && (targetUmlClass.stereotype === umlClass_1.ClassStereotype.Abstract || sourceUmlClass.stereotype === umlClass_1.ClassStereotype.Abstract)) || (classOptions.hideStructs && targetUmlClass.stereotype === umlClass_1.ClassStereotype.Struct) || (classOptions.hideEnums && targetUmlClass.stereotype === umlClass_1.ClassStereotype.Enum) || (classOptions.hideConstants && targetUmlClass.stereotype === umlClass_1.ClassStereotype.Constant)) { return ''; } let dotString = `\n${sourceUmlClass.id} -> ${targetUmlClass.id} [`; if (association.referenceType == umlClass_1.ReferenceType.Memory || (association.realization && targetUmlClass.stereotype === umlClass_1.ClassStereotype.Interface)) { dotString += 'style=dashed, '; } if (association.realization) { dotString += 'arrowhead=empty, arrowsize=3, '; if (!targetUmlClass.stereotype) { dotString += 'weight=4, '; } else { dotString += 'weight=3, '; } } // Add function name labels on dependency arrows (not on realization/inheritance) if (!association.realization && !classOptions.hideDepFunctions) { const functionNames = getAssociationFunctionNames(sourceUmlClass, targetUmlClass, association); if (functionNames.length > 0) { const label = functionNames.map((fn) => fn + '\\l').join(''); dotString += `label="${label}", fontsize=10, `; } } return dotString + ']'; } function getAssociationFunctionNames(sourceUmlClass, targetUmlClass, association) { const names = new Set(); // Add explicitly captured function names if (association.functionsCalled) { for (const fn of association.functionsCalled) { names.add(fn); } } // Cross-reference: if no explicit calls, check if source class's member // access calls match any of the target class's operator names. // Applies to libraries (using...for) and interfaces (e.g. IERC20(token).transfer()) if (names.size === 0 && (targetUmlClass.stereotype === umlClass_1.ClassStereotype.Library || targetUmlClass.stereotype === umlClass_1.ClassStereotype.Interface) && sourceUmlClass.memberAccessCalls?.size > 0 && targetUmlClass.operators?.length > 0) { const targetOperatorNames = new Set(targetUmlClass.operators.map((op) => op.name)); for (const callName of sourceUmlClass.memberAccessCalls) { if (targetOperatorNames.has(callName)) { names.add(callName); } } } return [...names].sort(); } //# sourceMappingURL=converterClasses2Dot.js.map