sol2uml 2.2.5 → 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 +3 -1
- package/lib/converterClass2Dot.d.ts +1 -0
- package/lib/converterClass2Dot.js +10 -4
- package/lib/filterClasses.d.ts +2 -0
- package/lib/filterClasses.js +22 -2
- package/lib/parserEtherscan.d.ts +1 -1
- package/lib/parserEtherscan.js +7 -1
- package/lib/sol2uml.js +19 -5
- package/lib/squashClasses.d.ts +2 -0
- package/lib/squashClasses.js +143 -0
- package/lib/umlClass.d.ts +5 -1
- package/lib/umlClass.js +2 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ Options:
|
|
|
58
58
|
-f, --outputFormat <value> output file format. (choices: "svg", "png", "dot", "all", default: "svg")
|
|
59
59
|
-o, --outputFileName <value> output file name
|
|
60
60
|
-i, --ignoreFilesOrFolders <filesOrFolders> comma separated list of files or folders to ignore
|
|
61
|
-
-n, --network <network> Ethereum network (choices: "mainnet", "ropsten", "kovan", "rinkeby", "goerli", "sepolia", "polygon", "testnet.polygon", "arbitrum", "testnet.arbitrum", "avalanche", "testnet.avalanche", "bsc", "testnet.bsc", "crono", "fantom", "testnet.fantom", "moonbeam", "optimistic", "kovan-optimistic", default: "mainnet", env: ETH_NETWORK)
|
|
61
|
+
-n, --network <network> Ethereum network (choices: "mainnet", "ropsten", "kovan", "rinkeby", "goerli", "sepolia", "polygon", "testnet.polygon", "arbitrum", "testnet.arbitrum", "avalanche", "testnet.avalanche", "bsc", "testnet.bsc", "crono", "fantom", "testnet.fantom", "moonbeam", "optimistic", "kovan-optimistic", "gnosisscan", default: "mainnet", env: ETH_NETWORK)
|
|
62
62
|
-k, --apiKey <key> Blockchain explorer API key. eg Etherscan, Arbiscan, BscScan, CronoScan, FTMScan, PolygonScan or SnowTrace API key (env: SCAN_API_KEY)
|
|
63
63
|
-v, --verbose run with debugging statements (default: false)
|
|
64
64
|
-h, --help display help for command
|
|
@@ -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
|
|
|
@@ -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 +
|
|
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
|
-
|
|
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;
|
package/lib/filterClasses.d.ts
CHANGED
|
@@ -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;
|
package/lib/filterClasses.js
CHANGED
|
@@ -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(
|
|
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/parserEtherscan.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ASTNode } from '@solidity-parser/parser/dist/src/ast-types';
|
|
2
2
|
import { UmlClass } from './umlClass';
|
|
3
|
-
export declare const networks: readonly ["mainnet", "ropsten", "kovan", "rinkeby", "goerli", "sepolia", "polygon", "testnet.polygon", "arbitrum", "testnet.arbitrum", "avalanche", "testnet.avalanche", "bsc", "testnet.bsc", "crono", "fantom", "testnet.fantom", "moonbeam", "optimistic", "kovan-optimistic"];
|
|
3
|
+
export declare const networks: readonly ["mainnet", "ropsten", "kovan", "rinkeby", "goerli", "sepolia", "polygon", "testnet.polygon", "arbitrum", "testnet.arbitrum", "avalanche", "testnet.avalanche", "bsc", "testnet.bsc", "crono", "fantom", "testnet.fantom", "moonbeam", "optimistic", "kovan-optimistic", "gnosisscan"];
|
|
4
4
|
declare type Network = typeof networks[number];
|
|
5
5
|
export declare class EtherscanParser {
|
|
6
6
|
protected apikey: string;
|
package/lib/parserEtherscan.js
CHANGED
|
@@ -32,6 +32,7 @@ exports.networks = [
|
|
|
32
32
|
'moonbeam',
|
|
33
33
|
'optimistic',
|
|
34
34
|
'kovan-optimistic',
|
|
35
|
+
'gnosisscan',
|
|
35
36
|
];
|
|
36
37
|
class EtherscanParser {
|
|
37
38
|
constructor(apikey = 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1', network = 'mainnet') {
|
|
@@ -69,14 +70,15 @@ class EtherscanParser {
|
|
|
69
70
|
}
|
|
70
71
|
else if (network === 'bsc') {
|
|
71
72
|
this.url = 'https://api.bscscan.com/api';
|
|
73
|
+
this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN';
|
|
72
74
|
}
|
|
73
75
|
else if (network === 'testnet.bsc') {
|
|
74
76
|
this.url = 'https://api-testnet.bscscan.com/api';
|
|
77
|
+
this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN';
|
|
75
78
|
}
|
|
76
79
|
else if (network === 'crono') {
|
|
77
80
|
this.url = 'https://api.cronoscan.com/api';
|
|
78
81
|
this.apikey = '76A3RG5WHTPMMR66E9SFI2EIDT6MP976W2';
|
|
79
|
-
this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN';
|
|
80
82
|
}
|
|
81
83
|
else if (network === 'fantom') {
|
|
82
84
|
this.url = 'https://api.ftmscan.com/api';
|
|
@@ -94,6 +96,10 @@ class EtherscanParser {
|
|
|
94
96
|
this.url = 'https://api-moonbeam.moonscan.io/api';
|
|
95
97
|
this.apikey = '5EUFXW6TDC16VERF3D9SCWRRU6AEMTBHNJ';
|
|
96
98
|
}
|
|
99
|
+
else if (network === 'gnosisscan') {
|
|
100
|
+
this.url = 'https://api.gnosisscan.io/api';
|
|
101
|
+
this.apikey = '2RWGXIWK538EJ8XSP9DE2JUINSCG7UCSJB';
|
|
102
|
+
}
|
|
97
103
|
else {
|
|
98
104
|
this.url = `https://api-${network}.etherscan.io/api`;
|
|
99
105
|
}
|
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
|
|
@@ -37,7 +38,7 @@ The Solidity code can be pulled from verified source code on Blockchain explorer
|
|
|
37
38
|
.choices(parserEtherscan_1.networks)
|
|
38
39
|
.default('mainnet')
|
|
39
40
|
.env('ETH_NETWORK'))
|
|
40
|
-
.addOption(new commander_1.Option('-k, --apiKey <key>', 'Blockchain explorer API key. eg Etherscan, Arbiscan, BscScan, CronoScan, FTMScan, PolygonScan or SnowTrace API key').env('SCAN_API_KEY'))
|
|
41
|
+
.addOption(new commander_1.Option('-k, --apiKey <key>', 'Blockchain explorer API key. eg Etherscan, Arbiscan, Optimism, BscScan, CronoScan, FTMScan, PolygonScan or SnowTrace API key').env('SCAN_API_KEY'))
|
|
41
42
|
.option('-v, --verbose', 'run with debugging statements', false);
|
|
42
43
|
program
|
|
43
44
|
.command('class', { isDefault: true })
|
|
@@ -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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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,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;
|