sol2uml 2.1.5 → 2.1.8
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 -2
- package/lib/converterAST2Classes.js +64 -16
- package/lib/converterClass2Dot.d.ts +1 -0
- package/lib/converterClass2Dot.js +9 -3
- package/lib/converterClasses2Dot.js +4 -2
- package/lib/converterClasses2Storage.js +17 -12
- package/lib/parserEtherscan.js +1 -0
- package/lib/sol2uml.js +7 -2
- package/lib/umlClass.d.ts +2 -1
- package/lib/umlClass.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,9 +77,9 @@ Usage: sol2uml class <fileFolderAddress> [options]
|
|
|
77
77
|
|
|
78
78
|
Generates UML diagrams from Solidity source code.
|
|
79
79
|
|
|
80
|
-
If no file, folder or address is
|
|
80
|
+
If no file, folder or address is passed as the first argument, the working folder is used.
|
|
81
81
|
When a folder is used, all *.sol files are found in that folder and all sub folders.
|
|
82
|
-
A comma separated list of files and folders can also used. For example
|
|
82
|
+
A comma separated list of files and folders can also be used. For example
|
|
83
83
|
sol2uml contracts,node_modules/openzeppelin-solidity
|
|
84
84
|
|
|
85
85
|
If an Ethereum address with a 0x prefix is passed, the verified source code from Etherscan will be used. For example
|
|
@@ -97,6 +97,7 @@ Options:
|
|
|
97
97
|
-hv, --hideVariables hide variables from contracts, interfaces, structs and enums (default: false)
|
|
98
98
|
-hf, --hideFunctions hide functions from contracts, interfaces and libraries (default: false)
|
|
99
99
|
-hp, --hidePrivates hide private and internal attributes and operators (default: false)
|
|
100
|
+
-hc, --hideConstants hide file level constants (default: false)
|
|
100
101
|
-he, --hideEnums hide enum types (default: false)
|
|
101
102
|
-hs, --hideStructs hide data structures (default: false)
|
|
102
103
|
-hl, --hideLibraries hide libraries (default: false)
|
|
@@ -25,6 +25,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
exports.convertAST2UmlClasses = void 0;
|
|
27
27
|
const path = __importStar(require("path"));
|
|
28
|
+
const path_1 = require("path");
|
|
28
29
|
const umlClass_1 = require("./umlClass");
|
|
29
30
|
const typeGuards_1 = require("./typeGuards");
|
|
30
31
|
const debug = require('debug')('sol2uml');
|
|
@@ -35,7 +36,6 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
35
36
|
if (node.type === 'SourceUnit') {
|
|
36
37
|
node.children.forEach((childNode) => {
|
|
37
38
|
if (childNode.type === 'ContractDefinition') {
|
|
38
|
-
debug(`Adding contract ${childNode.name}`);
|
|
39
39
|
let umlClass = new umlClass_1.UmlClass({
|
|
40
40
|
name: childNode.name,
|
|
41
41
|
absolutePath: filesystem
|
|
@@ -44,10 +44,11 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
44
44
|
relativePath,
|
|
45
45
|
});
|
|
46
46
|
umlClass = parseContractDefinition(umlClass, childNode);
|
|
47
|
+
debug(`Added contract ${childNode.name}`);
|
|
47
48
|
umlClasses.push(umlClass);
|
|
48
49
|
}
|
|
49
50
|
else if (childNode.type === 'StructDefinition') {
|
|
50
|
-
debug(`Adding struct ${childNode.name}`);
|
|
51
|
+
debug(`Adding file level struct ${childNode.name}`);
|
|
51
52
|
let umlClass = new umlClass_1.UmlClass({
|
|
52
53
|
name: childNode.name,
|
|
53
54
|
stereotype: umlClass_1.ClassStereotype.Struct,
|
|
@@ -57,10 +58,11 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
57
58
|
relativePath,
|
|
58
59
|
});
|
|
59
60
|
umlClass = parseStructDefinition(umlClass, childNode);
|
|
61
|
+
debug(`Added struct ${umlClass.name}`);
|
|
60
62
|
umlClasses.push(umlClass);
|
|
61
63
|
}
|
|
62
64
|
else if (childNode.type === 'EnumDefinition') {
|
|
63
|
-
debug(`Adding enum ${childNode.name}`);
|
|
65
|
+
debug(`Adding file level enum ${childNode.name}`);
|
|
64
66
|
let umlClass = new umlClass_1.UmlClass({
|
|
65
67
|
name: childNode.name,
|
|
66
68
|
stereotype: umlClass_1.ClassStereotype.Enum,
|
|
@@ -69,6 +71,7 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
69
71
|
: relativePath,
|
|
70
72
|
relativePath,
|
|
71
73
|
});
|
|
74
|
+
debug(`Added enum ${umlClass.name}`);
|
|
72
75
|
umlClass = parseEnumDefinition(umlClass, childNode);
|
|
73
76
|
umlClasses.push(umlClass);
|
|
74
77
|
}
|
|
@@ -80,7 +83,7 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
80
83
|
const importPath = require.resolve(childNode.path, {
|
|
81
84
|
paths: [codeFolder],
|
|
82
85
|
});
|
|
83
|
-
|
|
86
|
+
const newImport = {
|
|
84
87
|
absolutePath: importPath,
|
|
85
88
|
classNames: childNode.symbolAliases
|
|
86
89
|
? childNode.symbolAliases.map((alias) => {
|
|
@@ -90,7 +93,9 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
90
93
|
};
|
|
91
94
|
})
|
|
92
95
|
: [],
|
|
93
|
-
}
|
|
96
|
+
};
|
|
97
|
+
debug(`Added filesystem import ${newImport.absolutePath} with class names ${newImport.classNames}`);
|
|
98
|
+
imports.push(newImport);
|
|
94
99
|
}
|
|
95
100
|
catch (err) {
|
|
96
101
|
debug(`Failed to resolve import ${childNode.path} from file ${relativePath}`);
|
|
@@ -98,10 +103,12 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
98
103
|
}
|
|
99
104
|
else {
|
|
100
105
|
// this has come from Etherscan
|
|
101
|
-
const importPath = childNode.path[0] === '
|
|
102
|
-
?
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
const importPath = childNode.path[0] === '.'
|
|
107
|
+
? // Use Linux paths, not Windows paths, to resolve Etherscan files
|
|
108
|
+
path_1.posix.join(codeFolder.toString(), childNode.path)
|
|
109
|
+
: childNode.path;
|
|
110
|
+
debug(`codeFolder ${codeFolder} childNode.path ${childNode.path}`);
|
|
111
|
+
const newImport = {
|
|
105
112
|
absolutePath: importPath,
|
|
106
113
|
classNames: childNode.symbolAliases
|
|
107
114
|
? childNode.symbolAliases.map((alias) => {
|
|
@@ -111,8 +118,40 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
111
118
|
};
|
|
112
119
|
})
|
|
113
120
|
: [],
|
|
121
|
+
};
|
|
122
|
+
debug(`Added Etherscan import ${newImport.absolutePath} with class names: ${newImport.classNames}`);
|
|
123
|
+
imports.push(newImport);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else if (childNode.type === 'FileLevelConstant') {
|
|
127
|
+
debug(`Adding file level constant ${childNode.name}`);
|
|
128
|
+
const [type, attributeType] = parseTypeName(childNode.typeName);
|
|
129
|
+
const umlClass = new umlClass_1.UmlClass({
|
|
130
|
+
name: childNode.name,
|
|
131
|
+
stereotype: umlClass_1.ClassStereotype.Constant,
|
|
132
|
+
absolutePath: filesystem
|
|
133
|
+
? path.resolve(relativePath) // resolve the absolute path
|
|
134
|
+
: relativePath,
|
|
135
|
+
relativePath,
|
|
136
|
+
attributes: [
|
|
137
|
+
{
|
|
138
|
+
name: childNode.name,
|
|
139
|
+
type,
|
|
140
|
+
attributeType,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
});
|
|
144
|
+
if (childNode?.initialValue?.type === 'NumberLiteral') {
|
|
145
|
+
umlClass.constants.push({
|
|
146
|
+
name: childNode.name,
|
|
147
|
+
value: parseInt(childNode.initialValue.number),
|
|
114
148
|
});
|
|
115
149
|
}
|
|
150
|
+
// TODO handle expressions. eg N_COINS * 2
|
|
151
|
+
umlClasses.push(umlClass);
|
|
152
|
+
}
|
|
153
|
+
else if (childNode.type !== 'PragmaDirective') {
|
|
154
|
+
debug(`node type "${childNode.type}" not parsed in ${relativePath}`);
|
|
116
155
|
}
|
|
117
156
|
});
|
|
118
157
|
}
|
|
@@ -337,13 +376,22 @@ function addAssociations(nodes, umlClass) {
|
|
|
337
376
|
], umlClass);
|
|
338
377
|
// Array of user defined types
|
|
339
378
|
}
|
|
340
|
-
else if (node.typeName.type == 'ArrayTypeName'
|
|
341
|
-
node.typeName.baseTypeName.type ===
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
379
|
+
else if (node.typeName.type == 'ArrayTypeName') {
|
|
380
|
+
if (node.typeName.baseTypeName.type ===
|
|
381
|
+
'UserDefinedTypeName') {
|
|
382
|
+
const { umlClassName } = parseClassName(node.typeName.baseTypeName.namePath);
|
|
383
|
+
umlClass.addAssociation({
|
|
384
|
+
referenceType,
|
|
385
|
+
targetUmlClassName: umlClassName,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
else if (node.typeName.length?.type === 'Identifier') {
|
|
389
|
+
const { umlClassName } = parseClassName(node.typeName.length.name);
|
|
390
|
+
umlClass.addAssociation({
|
|
391
|
+
referenceType,
|
|
392
|
+
targetUmlClassName: umlClassName,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
347
395
|
}
|
|
348
396
|
break;
|
|
349
397
|
case 'UserDefinedTypeName':
|
|
@@ -14,7 +14,9 @@ const convertClass2Dot = (umlClass, options = {}) => {
|
|
|
14
14
|
umlClass.stereotype === umlClass_1.ClassStereotype.Abstract) ||
|
|
15
15
|
(options.hideStructs &&
|
|
16
16
|
umlClass.stereotype === umlClass_1.ClassStereotype.Struct) ||
|
|
17
|
-
(options.hideEnums && umlClass.stereotype === umlClass_1.ClassStereotype.Enum)
|
|
17
|
+
(options.hideEnums && umlClass.stereotype === umlClass_1.ClassStereotype.Enum) ||
|
|
18
|
+
(options.hideConstants &&
|
|
19
|
+
umlClass.stereotype === umlClass_1.ClassStereotype.Constant)) {
|
|
18
20
|
return '';
|
|
19
21
|
}
|
|
20
22
|
let dotString = `\n${umlClass.id} [label="{${dotClassTitle(umlClass, options)}`;
|
|
@@ -51,6 +53,9 @@ const dotClassTitle = (umlClass, options = {}) => {
|
|
|
51
53
|
case umlClass_1.ClassStereotype.Enum:
|
|
52
54
|
stereoName = 'Enum';
|
|
53
55
|
break;
|
|
56
|
+
case umlClass_1.ClassStereotype.Constant:
|
|
57
|
+
stereoName = 'Constant';
|
|
58
|
+
break;
|
|
54
59
|
default:
|
|
55
60
|
// Contract or undefined stereotype will just return the UmlClass name
|
|
56
61
|
return `${umlClass.name}${relativePath}`;
|
|
@@ -61,9 +66,10 @@ const dotAttributeVisibilities = (umlClass, options = {}) => {
|
|
|
61
66
|
if (umlClass.attributes.length === 0)
|
|
62
67
|
return '';
|
|
63
68
|
let dotString = '| ';
|
|
64
|
-
// if a struct or
|
|
69
|
+
// if a struct, enum or constant then no visibility group
|
|
65
70
|
if (umlClass.stereotype === umlClass_1.ClassStereotype.Struct ||
|
|
66
|
-
umlClass.stereotype === umlClass_1.ClassStereotype.Enum
|
|
71
|
+
umlClass.stereotype === umlClass_1.ClassStereotype.Enum ||
|
|
72
|
+
umlClass.stereotype === umlClass_1.ClassStereotype.Constant) {
|
|
67
73
|
return dotString + dotAttributes(umlClass.attributes, undefined, false);
|
|
68
74
|
}
|
|
69
75
|
// For each visibility group
|
|
@@ -90,7 +90,7 @@ function addAssociationsToDot(umlClasses, classOptions = {}) {
|
|
|
90
90
|
exports.addAssociationsToDot = addAssociationsToDot;
|
|
91
91
|
function addAssociationToDot(sourceUmlClass, targetUmlClass, association, classOptions = {}) {
|
|
92
92
|
// do not include library or interface associations if hidden
|
|
93
|
-
// Or associations to Structs or
|
|
93
|
+
// Or associations to Structs, Enums or Constants if they are hidden
|
|
94
94
|
if ((classOptions.hideLibraries &&
|
|
95
95
|
(sourceUmlClass.stereotype === umlClass_1.ClassStereotype.Library ||
|
|
96
96
|
targetUmlClass.stereotype === umlClass_1.ClassStereotype.Library)) ||
|
|
@@ -103,7 +103,9 @@ function addAssociationToDot(sourceUmlClass, targetUmlClass, association, classO
|
|
|
103
103
|
(classOptions.hideStructs &&
|
|
104
104
|
targetUmlClass.stereotype === umlClass_1.ClassStereotype.Struct) ||
|
|
105
105
|
(classOptions.hideEnums &&
|
|
106
|
-
targetUmlClass.stereotype === umlClass_1.ClassStereotype.Enum)
|
|
106
|
+
targetUmlClass.stereotype === umlClass_1.ClassStereotype.Enum) ||
|
|
107
|
+
(classOptions.hideConstants &&
|
|
108
|
+
targetUmlClass.stereotype === umlClass_1.ClassStereotype.Constant)) {
|
|
107
109
|
return '';
|
|
108
110
|
}
|
|
109
111
|
let dotString = `\n${sourceUmlClass.id} -> ${targetUmlClass.id} [`;
|
|
@@ -29,8 +29,8 @@ const addStorageValues = async (url, contractAddress, storage, blockTag) => {
|
|
|
29
29
|
const valueVariables = storage.variables.filter((s) => !s.noValue);
|
|
30
30
|
const slots = valueVariables.map((s) => s.fromSlot);
|
|
31
31
|
const values = await (0, slotValues_1.getStorageValues)(url, contractAddress, slots, blockTag);
|
|
32
|
-
valueVariables.forEach((
|
|
33
|
-
|
|
32
|
+
valueVariables.forEach((valueVariable, i) => {
|
|
33
|
+
valueVariable.value = values[i];
|
|
34
34
|
});
|
|
35
35
|
};
|
|
36
36
|
exports.addStorageValues = addStorageValues;
|
|
@@ -41,14 +41,15 @@ const convertClasses2Storages = (contractName, umlClasses, contractFilename) =>
|
|
|
41
41
|
return name === contractName;
|
|
42
42
|
}
|
|
43
43
|
return (name === contractName &&
|
|
44
|
-
(relativePath == contractFilename ||
|
|
45
|
-
path_1.default.basename(relativePath) ===
|
|
44
|
+
(relativePath == path_1.default.normalize(contractFilename) ||
|
|
45
|
+
path_1.default.basename(relativePath) ===
|
|
46
|
+
path_1.default.normalize(contractFilename)));
|
|
46
47
|
});
|
|
47
48
|
if (!umlClass) {
|
|
48
49
|
const contractFilenameError = contractFilename
|
|
49
50
|
? ` in filename "${contractFilename}"`
|
|
50
51
|
: '';
|
|
51
|
-
throw Error(`Failed to find contract with name "${contractName}"${contractFilenameError}
|
|
52
|
+
throw Error(`Failed to find contract with name "${contractName}"${contractFilenameError}.\nIs the \`-c --contract <name>\` option correct?`);
|
|
52
53
|
}
|
|
53
54
|
debug(`Found contract "${contractName}" in ${umlClass.absolutePath}`);
|
|
54
55
|
const storages = [];
|
|
@@ -176,25 +177,29 @@ const parseReferenceStorage = (attribute, umlClass, otherClasses, storages) => {
|
|
|
176
177
|
attributeType: baseAttributeType,
|
|
177
178
|
};
|
|
178
179
|
const { size: arrayItemSize } = (0, exports.calcStorageByteSize)(baseAttribute, umlClass, otherClasses);
|
|
179
|
-
const
|
|
180
|
+
const arraySlotSize = arrayItemSize > 16
|
|
181
|
+
? 32 * Math.ceil(arrayItemSize / 32)
|
|
182
|
+
: arrayItemSize;
|
|
183
|
+
const variables = [];
|
|
184
|
+
variables[0] = {
|
|
180
185
|
id: variableId++,
|
|
181
186
|
fromSlot: 0,
|
|
182
|
-
toSlot: Math.floor((
|
|
187
|
+
toSlot: Math.floor((arraySlotSize - 1) / 32),
|
|
183
188
|
byteSize: arrayItemSize,
|
|
184
189
|
byteOffset: 0,
|
|
185
190
|
type: baseType,
|
|
186
191
|
dynamic,
|
|
187
192
|
noValue: false,
|
|
188
193
|
};
|
|
189
|
-
const variables = [firstVariable];
|
|
190
194
|
if (arrayLength > 1) {
|
|
195
|
+
// For fixed length arrays. Dynamic arrays will have undefined arrayLength
|
|
191
196
|
for (let i = 1; i < arrayLength; i++) {
|
|
192
197
|
variables.push({
|
|
193
198
|
id: variableId++,
|
|
194
|
-
fromSlot: Math.floor((i *
|
|
195
|
-
toSlot: Math.floor(((i + 1) *
|
|
199
|
+
fromSlot: Math.floor((i * arraySlotSize) / 32),
|
|
200
|
+
toSlot: Math.floor(((i + 1) * arraySlotSize - 1) / 32),
|
|
196
201
|
byteSize: arrayItemSize,
|
|
197
|
-
byteOffset: (i *
|
|
202
|
+
byteOffset: (i * arraySlotSize) % 32,
|
|
198
203
|
type: baseType,
|
|
199
204
|
dynamic,
|
|
200
205
|
noValue: false,
|
|
@@ -204,7 +209,7 @@ const parseReferenceStorage = (attribute, umlClass, otherClasses, storages) => {
|
|
|
204
209
|
// recursively add storage
|
|
205
210
|
if (baseAttributeType !== umlClass_1.AttributeType.Elementary) {
|
|
206
211
|
const referenceStorage = (0, exports.parseReferenceStorage)(baseAttribute, umlClass, otherClasses, storages);
|
|
207
|
-
|
|
212
|
+
variables[0].referenceStorageId = referenceStorage?.id;
|
|
208
213
|
}
|
|
209
214
|
const newStorage = {
|
|
210
215
|
id: storageId++,
|
package/lib/parserEtherscan.js
CHANGED
|
@@ -105,6 +105,7 @@ class EtherscanParser {
|
|
|
105
105
|
const { files, contractName } = await this.getSourceCode(contractAddress);
|
|
106
106
|
let umlClasses = [];
|
|
107
107
|
for (const file of files) {
|
|
108
|
+
debug(`Parsing source file ${file.filename}`);
|
|
108
109
|
const node = await this.parseSourceCode(file.code);
|
|
109
110
|
const umlClass = (0, converterAST2Classes_1.convertAST2UmlClasses)(node, file.filename);
|
|
110
111
|
umlClasses = umlClasses.concat(umlClass);
|
package/lib/sol2uml.js
CHANGED
|
@@ -46,9 +46,9 @@ program
|
|
|
46
46
|
|
|
47
47
|
Generates UML diagrams from Solidity source code.
|
|
48
48
|
|
|
49
|
-
If no file, folder or address is
|
|
49
|
+
If no file, folder or address is passed as the first argument, the working folder is used.
|
|
50
50
|
When a folder is used, all *.sol files are found in that folder and all sub folders.
|
|
51
|
-
A comma separated list of files and folders can also used. For example
|
|
51
|
+
A comma separated list of files and folders can also be used. For example
|
|
52
52
|
sol2uml contracts,node_modules/openzeppelin-solidity
|
|
53
53
|
|
|
54
54
|
If an Ethereum address with a 0x prefix is passed, the verified source code from Etherscan will be used. For example
|
|
@@ -60,6 +60,7 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
|
|
|
60
60
|
.option('-hv, --hideVariables', 'hide variables from contracts, interfaces, structs and enums', false)
|
|
61
61
|
.option('-hf, --hideFunctions', 'hide functions from contracts, interfaces and libraries', false)
|
|
62
62
|
.option('-hp, --hidePrivates', 'hide private and internal attributes and operators', false)
|
|
63
|
+
.option('-hc, --hideConstants', 'hide file level constants', false)
|
|
63
64
|
.option('-he, --hideEnums', 'hide enum types', false)
|
|
64
65
|
.option('-hs, --hideStructs', 'hide data structures', false)
|
|
65
66
|
.option('-hl, --hideLibraries', 'hide libraries', false)
|
|
@@ -109,6 +110,10 @@ WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A k
|
|
|
109
110
|
...command.parent._optionValues,
|
|
110
111
|
...options,
|
|
111
112
|
};
|
|
113
|
+
// If not an address and the contractName option has not been specified
|
|
114
|
+
if (!(0, regEx_1.isAddress)(fileFolderAddress) && !combinedOptions.contract) {
|
|
115
|
+
throw Error(`Must use the \`-c, --contract <name>\` option to specify the contract to draw the storage diagram for when sourcing from local files.\nThis option is not needed when sourcing from a blockchain explorer with a contract address.`);
|
|
116
|
+
}
|
|
112
117
|
let { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);
|
|
113
118
|
contractName = combinedOptions.contract || contractName;
|
|
114
119
|
const storages = (0, converterClasses2Storage_1.convertClasses2Storages)(contractName, umlClasses, combinedOptions.contractFile);
|
package/lib/umlClass.d.ts
CHANGED
package/lib/umlClass.js
CHANGED
|
@@ -18,6 +18,7 @@ var ClassStereotype;
|
|
|
18
18
|
ClassStereotype[ClassStereotype["Contract"] = 4] = "Contract";
|
|
19
19
|
ClassStereotype[ClassStereotype["Struct"] = 5] = "Struct";
|
|
20
20
|
ClassStereotype[ClassStereotype["Enum"] = 6] = "Enum";
|
|
21
|
+
ClassStereotype[ClassStereotype["Constant"] = 7] = "Constant";
|
|
21
22
|
})(ClassStereotype = exports.ClassStereotype || (exports.ClassStereotype = {}));
|
|
22
23
|
var OperatorStereotype;
|
|
23
24
|
(function (OperatorStereotype) {
|