sol2uml 2.2.6 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -106,6 +106,8 @@ Options:
106
106
  -hi, --hideInterfaces hide interfaces (default: false)
107
107
  -ha, --hideAbstracts hide abstract contracts (default: false)
108
108
  -hn, --hideFilename hide relative path and file name (default: false)
109
+ -s, --squash squash inherited contracts to the base contract(s) (default: false)
110
+ -hsc, --hideSourceContract hide the source contract when using squash (default: false)
109
111
  -h, --help display help for command
110
112
  ```
111
113
 
@@ -12,5 +12,6 @@ export interface ClassOptions {
12
12
  hidePrivates?: boolean;
13
13
  hideAbstracts?: boolean;
14
14
  hideFilename?: boolean;
15
+ hideSourceContract?: boolean;
15
16
  }
16
17
  export declare const convertClass2Dot: (umlClass: UmlClass, options?: ClassOptions) => string;
@@ -71,7 +71,8 @@ const dotAttributeVisibilities = (umlClass, options) => {
71
71
  if (umlClass.stereotype === umlClass_1.ClassStereotype.Struct ||
72
72
  umlClass.stereotype === umlClass_1.ClassStereotype.Enum ||
73
73
  umlClass.stereotype === umlClass_1.ClassStereotype.Constant) {
74
- return dotString + dotAttributes(umlClass.attributes, undefined, false);
74
+ return (dotString +
75
+ dotAttributes(umlClass.attributes, options, undefined, false));
75
76
  }
76
77
  // For each visibility group
77
78
  for (const vizGroup of ['Private', 'Internal', 'External', 'Public']) {
@@ -100,11 +101,11 @@ const dotAttributeVisibilities = (umlClass, options) => {
100
101
  attributes.push(attribute);
101
102
  }
102
103
  }
103
- dotString += dotAttributes(attributes, vizGroup);
104
+ dotString += dotAttributes(attributes, options, vizGroup);
104
105
  }
105
106
  return dotString;
106
107
  };
107
- const dotAttributes = (attributes, vizGroup, indent = true) => {
108
+ const dotAttributes = (attributes, options, vizGroup, indent = true) => {
108
109
  if (!attributes || attributes.length === 0) {
109
110
  return '';
110
111
  }
@@ -112,7 +113,10 @@ const dotAttributes = (attributes, vizGroup, indent = true) => {
112
113
  let dotString = vizGroup ? vizGroup + ':\\l' : '';
113
114
  // for each attribute
114
115
  attributes.forEach((attribute) => {
115
- dotString += `${indentString}${attribute.name}: ${attribute.type}\\l`;
116
+ const sourceContract = attribute.sourceContract && !options.hideSourceContract
117
+ ? ` \\<\\<${attribute.sourceContract}\\>\\>`
118
+ : '';
119
+ dotString += `${indentString}${attribute.name}: ${attribute.type}${sourceContract}\\l`;
116
120
  });
117
121
  return dotString;
118
122
  };
@@ -179,6 +183,8 @@ const dotOperators = (umlClass, vizGroup, operators, options) => {
179
183
  if (options.hideModifiers === false && operator.modifiers?.length > 0) {
180
184
  dotString += ` \\<\\<${operator.modifiers.join(', ')}\\>\\>`;
181
185
  }
186
+ if (operator.sourceContract && !options.hideSourceContract)
187
+ dotString += ` \\<\\<${operator.sourceContract}\\>\\>`;
182
188
  dotString += '\\l';
183
189
  }
184
190
  return dotString;
@@ -1,5 +1,7 @@
1
1
  import { WeightedDiGraph } from 'js-graph-algorithms';
2
2
  import { UmlClass } from './umlClass';
3
+ import { ClassOptions } from './converterClass2Dot';
4
+ export declare const filterHiddenClasses: (umlClasses: UmlClass[], options: ClassOptions) => UmlClass[];
3
5
  export declare const classesConnectedToBaseContracts: (umlClasses: UmlClass[], baseContractNames: string[], depth?: number) => UmlClass[];
4
6
  export declare const classesConnectedToBaseContract: (umlClasses: UmlClass[], baseContractName: string, weightedDirectedGraph: WeightedDiGraph, depth?: number) => {
5
7
  [contractName: string]: UmlClass;
@@ -1,8 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.topologicalSortClasses = exports.classesConnectedToBaseContract = exports.classesConnectedToBaseContracts = void 0;
3
+ exports.topologicalSortClasses = exports.classesConnectedToBaseContract = exports.classesConnectedToBaseContracts = exports.filterHiddenClasses = void 0;
4
4
  const js_graph_algorithms_1 = require("js-graph-algorithms");
5
+ const umlClass_1 = require("./umlClass");
5
6
  const associations_1 = require("./associations");
7
+ const filterHiddenClasses = (umlClasses, options) => {
8
+ return umlClasses.filter((u) => (u.stereotype === umlClass_1.ClassStereotype.Enum && !options.hideEnums) ||
9
+ (u.stereotype === umlClass_1.ClassStereotype.Struct && !options.hideStructs) ||
10
+ (u.stereotype === umlClass_1.ClassStereotype.Abstract &&
11
+ !options.hideAbstracts) ||
12
+ (u.stereotype === umlClass_1.ClassStereotype.Interface &&
13
+ !options.hideInterfaces) ||
14
+ (u.stereotype === umlClass_1.ClassStereotype.Constant &&
15
+ !options.hideConstants) ||
16
+ (u.stereotype === umlClass_1.ClassStereotype.Library &&
17
+ !options.hideLibraries) ||
18
+ u.stereotype === umlClass_1.ClassStereotype.None ||
19
+ u.stereotype === umlClass_1.ClassStereotype.Contract);
20
+ };
21
+ exports.filterHiddenClasses = filterHiddenClasses;
6
22
  const classesConnectedToBaseContracts = (umlClasses, baseContractNames, depth) => {
7
23
  let filteredUmlClasses = {};
8
24
  const weightedDirectedGraph = loadWeightedDirectedGraph(umlClasses);
@@ -35,7 +51,9 @@ const classesConnectedToBaseContract = (umlClasses, baseContractName, weightedDi
35
51
  };
36
52
  exports.classesConnectedToBaseContract = classesConnectedToBaseContract;
37
53
  function loadWeightedDirectedGraph(umlClasses) {
38
- const weightedDirectedGraph = new js_graph_algorithms_1.WeightedDiGraph(umlClasses.length); // the number vertices in the graph
54
+ const weightedDirectedGraph = new js_graph_algorithms_1.WeightedDiGraph(
55
+ // the number vertices in the graph
56
+ umlClass_1.UmlClass.idCounter + 1);
39
57
  for (const sourceUmlClass of umlClasses) {
40
58
  for (const association of Object.values(sourceUmlClass.associations)) {
41
59
  // Find the first UML Class that matches the target class name
@@ -43,6 +61,8 @@ function loadWeightedDirectedGraph(umlClasses) {
43
61
  if (!targetUmlClass) {
44
62
  continue;
45
63
  }
64
+ const isTarget = umlClasses.find((u) => u.id === targetUmlClass.id);
65
+ console.log(`isTarget ${isTarget} Adding edge from ${sourceUmlClass.name} with id ${sourceUmlClass.id} to ${targetUmlClass.name} with id ${targetUmlClass.id} and type ${targetUmlClass.stereotype}`);
46
66
  weightedDirectedGraph.addEdge(new js_graph_algorithms_1.Edge(sourceUmlClass.id, targetUmlClass.id, 1));
47
67
  }
48
68
  }
package/lib/sol2uml.js CHANGED
@@ -11,6 +11,7 @@ const converterStorage2Dot_1 = require("./converterStorage2Dot");
11
11
  const regEx_1 = require("./utils/regEx");
12
12
  const writerFiles_1 = require("./writerFiles");
13
13
  const path_1 = require("path");
14
+ const squashClasses_1 = require("./squashClasses");
14
15
  const program = new commander_1.Command();
15
16
  const version = (0, path_1.basename)(__dirname) === 'lib'
16
17
  ? require('../package.json').version // used when run from compile js in /lib
@@ -69,6 +70,8 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
69
70
  .option('-hi, --hideInterfaces', 'hide interfaces', false)
70
71
  .option('-ha, --hideAbstracts', 'hide abstract contracts', false)
71
72
  .option('-hn, --hideFilename', 'hide relative path and file name', false)
73
+ .option('-s, --squash', 'squash inherited contracts to the base contract(s)', false)
74
+ .option('-hsc, --hideSourceContract', 'hide the source contract when using squash', false)
72
75
  .action(async (fileFolderAddress, options, command) => {
73
76
  try {
74
77
  const combinedOptions = {
@@ -76,12 +79,23 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
76
79
  ...options,
77
80
  };
78
81
  let { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);
79
- let filteredUmlClasses = umlClasses;
80
- if (options.baseContractNames) {
81
- const baseContractNames = options.baseContractNames.split(',');
82
- filteredUmlClasses = (0, filterClasses_1.classesConnectedToBaseContracts)(umlClasses, baseContractNames, options.depth);
82
+ if (options.squash &&
83
+ // Must specify base contract(s) or parse from Etherscan to get contractName
84
+ !(options.baseContractNames || contractName)) {
85
+ throw Error('Must specify base contract(s) when using the squash option against local Solidity files.');
86
+ }
87
+ // Filter out any class stereotypes that are to be hidden
88
+ let filteredUmlClasses = (0, filterClasses_1.filterHiddenClasses)(umlClasses, options);
89
+ const baseContractNames = options.baseContractNames?.split(',');
90
+ if (baseContractNames) {
91
+ // Find all the classes connected to the base classes
92
+ filteredUmlClasses = (0, filterClasses_1.classesConnectedToBaseContracts)(filteredUmlClasses, baseContractNames, options.depth);
83
93
  contractName = baseContractNames[0];
84
94
  }
95
+ // squash contracts
96
+ if (options.squash) {
97
+ filteredUmlClasses = (0, squashClasses_1.squashUmlClasses)(filteredUmlClasses, baseContractNames || [contractName]);
98
+ }
85
99
  const dotString = (0, converterClasses2Dot_1.convertUmlClasses2Dot)(filteredUmlClasses, combinedOptions.clusterFolders, combinedOptions);
86
100
  await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName || 'classDiagram', combinedOptions.outputFormat, combinedOptions.outputFileName);
87
101
  debug(`Finished generating UML`);
@@ -0,0 +1,2 @@
1
+ import { UmlClass } from './umlClass';
2
+ export declare const squashUmlClasses: (umlClasses: UmlClass[], squashContractNames: string[]) => UmlClass[];
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.squashUmlClasses = void 0;
27
+ const umlClass_1 = require("./umlClass");
28
+ const crypto = __importStar(require("crypto"));
29
+ const debug = require('debug')('sol2uml');
30
+ const squashUmlClasses = (umlClasses, squashContractNames) => {
31
+ let removedClassIds = [];
32
+ for (const squashContractName of squashContractNames) {
33
+ // Find the base UML Class to squash
34
+ let baseIndex = umlClasses.findIndex(({ name }) => {
35
+ return name === squashContractName;
36
+ });
37
+ if (baseIndex === undefined) {
38
+ throw Error(`Failed to find contract with name "${squashContractName}" to squash`);
39
+ }
40
+ const baseClass = umlClasses[baseIndex];
41
+ let squashedClass = new umlClass_1.UmlClass({
42
+ name: baseClass.name,
43
+ absolutePath: baseClass.absolutePath,
44
+ relativePath: baseClass.relativePath,
45
+ });
46
+ squashedClass.id = baseClass.id;
47
+ const result = recursiveSquash(squashedClass, [], baseClass, umlClasses, 1);
48
+ removedClassIds = removedClassIds.concat(result.removedClassIds);
49
+ // Remove overridden functions from squashed class
50
+ squashedClass.operators = reduceOperators(squashedClass.operators);
51
+ umlClasses[baseIndex] = squashedClass;
52
+ }
53
+ // filter the list of classes that will be rendered
54
+ return umlClasses.filter((u) =>
55
+ // remove any squashed inherited contracts
56
+ !removedClassIds.includes(u.id) ||
57
+ // Include all base contracts
58
+ squashContractNames.includes(u.name));
59
+ };
60
+ exports.squashUmlClasses = squashUmlClasses;
61
+ const recursiveSquash = (squashedClass, inheritedContractNames, baseClass, umlClasses, startPosition) => {
62
+ let currentPosition = startPosition;
63
+ const removedClassIds = [];
64
+ // For each association from the baseClass
65
+ for (const [targetClassName, association] of Object.entries(baseClass.associations)) {
66
+ // if inheritance and (Abstract or Contract)
67
+ // Libraries and Interfaces will be copied
68
+ if (association.realization) {
69
+ // Find the target UML Class
70
+ const inheritedContract = umlClasses.find(({ name }) => {
71
+ return name === targetClassName;
72
+ });
73
+ if (!inheritedContract) {
74
+ debug(`Warning: failed to find inherited contract with name ${targetClassName}`);
75
+ continue;
76
+ }
77
+ // Is the associated class a contract or abstract contract?
78
+ if (inheritedContract?.stereotype === umlClass_1.ClassStereotype.Library) {
79
+ squashedClass.addAssociation(association);
80
+ }
81
+ else {
82
+ // has the contract already been added to the inheritance tree?
83
+ const alreadyInherited = inheritedContractNames.includes(inheritedContract.name);
84
+ // Do not add inherited contract if it has already been added to the inheritance tree
85
+ if (!alreadyInherited) {
86
+ inheritedContractNames.push(inheritedContract.name);
87
+ const squashResult = recursiveSquash(squashedClass, inheritedContractNames, inheritedContract, umlClasses, currentPosition++);
88
+ // Add to list of removed class ids
89
+ removedClassIds.push(...squashResult.removedClassIds, inheritedContract.id);
90
+ }
91
+ }
92
+ }
93
+ else {
94
+ // Copy association but will not duplicate it
95
+ squashedClass.addAssociation(association);
96
+ }
97
+ }
98
+ // Copy class properties from the baseClass to the squashedClass
99
+ baseClass.constants.forEach((c) => squashedClass.constants.push({ ...c, sourceContract: baseClass.name }));
100
+ baseClass.attributes.forEach((a) => squashedClass.attributes.push({ ...a, sourceContract: baseClass.name }));
101
+ baseClass.enums.forEach((e) => squashedClass.enums.push(e));
102
+ baseClass.structs.forEach((s) => squashedClass.structs.push(s));
103
+ baseClass.imports.forEach((i) => squashedClass.imports.push(i));
104
+ // copy the functions
105
+ baseClass.operators.forEach((f) => squashedClass.operators.push({
106
+ ...f,
107
+ hash: hash(f),
108
+ inheritancePosition: currentPosition,
109
+ sourceContract: baseClass.name,
110
+ }));
111
+ return {
112
+ currentPosition,
113
+ removedClassIds,
114
+ };
115
+ };
116
+ const hash = (operator) => {
117
+ const hash = crypto.createHash('sha256');
118
+ let data = operator.name ?? 'fallback';
119
+ operator.parameters?.forEach((p) => {
120
+ data += ',' + p.type;
121
+ });
122
+ operator.returnParameters?.forEach((p) => {
123
+ data += ',' + p.type;
124
+ });
125
+ return hash.update(data).digest('hex');
126
+ };
127
+ const reduceOperators = (operators) => {
128
+ const hashes = new Set(operators.map((o) => o.hash));
129
+ const operatorsWithNoHash = operators.filter((o) => !o.hash);
130
+ const newOperators = [];
131
+ for (const hash of hashes) {
132
+ const operator = operators
133
+ .filter((o) => o.hash === hash)
134
+ // sort operators by inheritance position. smaller to highest
135
+ .sort((o) => o.inheritancePosition)
136
+ // get last operator in the array
137
+ .slice(-1)[0];
138
+ newOperators.push(operator);
139
+ }
140
+ newOperators.push(...operatorsWithNoHash);
141
+ return newOperators;
142
+ };
143
+ //# sourceMappingURL=squashClasses.js.map
package/lib/umlClass.d.ts CHANGED
@@ -44,6 +44,7 @@ export interface Attribute {
44
44
  type?: string;
45
45
  attributeType?: AttributeType;
46
46
  compiled?: boolean;
47
+ sourceContract?: string;
47
48
  }
48
49
  export interface Parameter {
49
50
  name?: string;
@@ -55,6 +56,9 @@ export interface Operator extends Attribute {
55
56
  returnParameters?: Parameter[];
56
57
  isPayable?: boolean;
57
58
  modifiers?: string[];
59
+ hash?: string;
60
+ inheritancePosition?: number;
61
+ sourceContract?: string;
58
62
  }
59
63
  export declare enum ReferenceType {
60
64
  Memory = 0,
@@ -63,12 +67,12 @@ export declare enum ReferenceType {
63
67
  export interface Association {
64
68
  referenceType: ReferenceType;
65
69
  targetUmlClassName: string;
66
- targetUmlClassStereotype?: ClassStereotype;
67
70
  realization?: boolean;
68
71
  }
69
72
  export interface Constants {
70
73
  name: string;
71
74
  value: number;
75
+ sourceContract?: string;
72
76
  }
73
77
  export interface ClassProperties {
74
78
  name: string;
package/lib/umlClass.js CHANGED
@@ -45,6 +45,7 @@ var ReferenceType;
45
45
  })(ReferenceType = exports.ReferenceType || (exports.ReferenceType = {}));
46
46
  class UmlClass {
47
47
  constructor(properties) {
48
+ this.imports = [];
48
49
  this.constants = [];
49
50
  this.attributes = [];
50
51
  this.operators = [];
@@ -83,9 +84,7 @@ class UmlClass {
83
84
  * Does not include any grand parent associations. That has to be done recursively.
84
85
  */
85
86
  getParentContracts() {
86
- return Object.values(this.associations).filter((association) => association.realization &&
87
- association.targetUmlClassStereotype !==
88
- ClassStereotype.Interface);
87
+ return Object.values(this.associations).filter((association) => association.realization);
89
88
  }
90
89
  }
91
90
  exports.UmlClass = UmlClass;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sol2uml",
3
- "version": "2.2.6",
3
+ "version": "2.3.0",
4
4
  "description": "Solidity contract visualisation tool.",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",