sol2uml 2.2.6 → 2.3.1
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 +2 -0
- package/lib/converterAST2Classes.d.ts +7 -0
- package/lib/converterAST2Classes.js +116 -58
- package/lib/converterClass2Dot.d.ts +1 -0
- package/lib/converterClass2Dot.js +10 -4
- package/lib/converterClasses2Dot.d.ts +8 -0
- package/lib/converterClasses2Dot.js +8 -0
- package/lib/filterClasses.d.ts +25 -0
- package/lib/filterClasses.js +45 -2
- package/lib/parserEtherscan.d.ts +1 -2
- package/lib/parserGeneral.d.ts +13 -1
- package/lib/parserGeneral.js +5 -0
- package/lib/sol2uml.js +21 -4
- package/lib/squashClasses.d.ts +8 -0
- package/lib/squashClasses.js +149 -0
- package/lib/umlClass.d.ts +6 -2
- package/lib/umlClass.js +2 -3
- package/lib/writerFiles.d.ts +1 -1
- package/package.json +10 -10
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
|
|
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
import { ASTNode } from '@solidity-parser/parser/dist/src/ast-types';
|
|
2
2
|
import { UmlClass } from './umlClass';
|
|
3
|
+
/**
|
|
4
|
+
* Convert solidity parser output of type `ASTNode` to UML classes of type `UMLClass`
|
|
5
|
+
* @param node output of Solidity parser of type `ASTNode`
|
|
6
|
+
* @param relativePath relative path from the working directory to the Solidity source file
|
|
7
|
+
* @param filesystem flag if Solidity source code was parsed from the filesystem or Etherscan
|
|
8
|
+
* @return umlClasses array of UML class definitions of type `UmlClass`
|
|
9
|
+
*/
|
|
3
10
|
export declare function convertAST2UmlClasses(node: ASTNode, relativePath: string, filesystem?: boolean): UmlClass[];
|
|
@@ -29,7 +29,14 @@ const path_1 = require("path");
|
|
|
29
29
|
const umlClass_1 = require("./umlClass");
|
|
30
30
|
const typeGuards_1 = require("./typeGuards");
|
|
31
31
|
const debug = require('debug')('sol2uml');
|
|
32
|
-
let umlClasses
|
|
32
|
+
let umlClasses;
|
|
33
|
+
/**
|
|
34
|
+
* Convert solidity parser output of type `ASTNode` to UML classes of type `UMLClass`
|
|
35
|
+
* @param node output of Solidity parser of type `ASTNode`
|
|
36
|
+
* @param relativePath relative path from the working directory to the Solidity source file
|
|
37
|
+
* @param filesystem flag if Solidity source code was parsed from the filesystem or Etherscan
|
|
38
|
+
* @return umlClasses array of UML class definitions of type `UmlClass`
|
|
39
|
+
*/
|
|
33
40
|
function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
34
41
|
const imports = [];
|
|
35
42
|
umlClasses = [];
|
|
@@ -43,7 +50,7 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
43
50
|
: relativePath,
|
|
44
51
|
relativePath,
|
|
45
52
|
});
|
|
46
|
-
|
|
53
|
+
parseContractDefinition(childNode, umlClass);
|
|
47
54
|
debug(`Added contract ${childNode.name}`);
|
|
48
55
|
umlClasses.push(umlClass);
|
|
49
56
|
}
|
|
@@ -57,7 +64,7 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
57
64
|
: relativePath,
|
|
58
65
|
relativePath,
|
|
59
66
|
});
|
|
60
|
-
|
|
67
|
+
parseStructDefinition(childNode, umlClass);
|
|
61
68
|
debug(`Added struct ${umlClass.name}`);
|
|
62
69
|
umlClasses.push(umlClass);
|
|
63
70
|
}
|
|
@@ -72,7 +79,7 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
72
79
|
relativePath,
|
|
73
80
|
});
|
|
74
81
|
debug(`Added enum ${umlClass.name}`);
|
|
75
|
-
|
|
82
|
+
parseEnumDefinition(childNode, umlClass);
|
|
76
83
|
umlClasses.push(umlClass);
|
|
77
84
|
}
|
|
78
85
|
else if (childNode.type === 'ImportDirective') {
|
|
@@ -178,7 +185,12 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
178
185
|
return umlClasses;
|
|
179
186
|
}
|
|
180
187
|
exports.convertAST2UmlClasses = convertAST2UmlClasses;
|
|
181
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Parse struct definition for UML attributes and associations.
|
|
190
|
+
* @param node defined in ASTNode as `StructDefinition`
|
|
191
|
+
* @param umlClass that has struct attributes and associations added. This parameter is mutated.
|
|
192
|
+
*/
|
|
193
|
+
function parseStructDefinition(node, umlClass) {
|
|
182
194
|
node.members.forEach((member) => {
|
|
183
195
|
const [type, attributeType] = parseTypeName(member.typeName);
|
|
184
196
|
umlClass.attributes.push({
|
|
@@ -188,10 +200,14 @@ function parseStructDefinition(umlClass, node) {
|
|
|
188
200
|
});
|
|
189
201
|
});
|
|
190
202
|
// Recursively parse struct members for associations
|
|
191
|
-
|
|
192
|
-
return umlClass;
|
|
203
|
+
addAssociations(node.members, umlClass);
|
|
193
204
|
}
|
|
194
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Parse enum definition for UML attributes and associations.
|
|
207
|
+
* @param node defined in ASTNode as `EnumDefinition`
|
|
208
|
+
* @param umlClass that has enum attributes and associations added. This parameter is mutated.
|
|
209
|
+
*/
|
|
210
|
+
function parseEnumDefinition(node, umlClass) {
|
|
195
211
|
let index = 0;
|
|
196
212
|
node.members.forEach((member) => {
|
|
197
213
|
umlClass.attributes.push({
|
|
@@ -200,10 +216,14 @@ function parseEnumDefinition(umlClass, node) {
|
|
|
200
216
|
});
|
|
201
217
|
});
|
|
202
218
|
// Recursively parse struct members for associations
|
|
203
|
-
|
|
204
|
-
return umlClass;
|
|
219
|
+
addAssociations(node.members, umlClass);
|
|
205
220
|
}
|
|
206
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Parse contract definition for UML attributes, operations and associations.
|
|
223
|
+
* @param node defined in ASTNode as `ContractDefinition`
|
|
224
|
+
* @param umlClass that has attributes, operations and associations added. This parameter is mutated.
|
|
225
|
+
*/
|
|
226
|
+
function parseContractDefinition(node, umlClass) {
|
|
207
227
|
umlClass.stereotype = parseContractKind(node.kind);
|
|
208
228
|
// For each base contract
|
|
209
229
|
node.baseContracts.forEach((baseClass) => {
|
|
@@ -239,7 +259,7 @@ function parseContractDefinition(umlClass, node) {
|
|
|
239
259
|
}
|
|
240
260
|
});
|
|
241
261
|
// Recursively parse variables for associations
|
|
242
|
-
|
|
262
|
+
addAssociations(subNode.variables, umlClass);
|
|
243
263
|
}
|
|
244
264
|
else if ((0, typeGuards_1.isUsingForDeclaration)(subNode)) {
|
|
245
265
|
// Add association to library contract
|
|
@@ -262,7 +282,7 @@ function parseContractDefinition(umlClass, node) {
|
|
|
262
282
|
name: '',
|
|
263
283
|
stereotype: umlClass_1.OperatorStereotype.Fallback,
|
|
264
284
|
parameters: parseParameters(subNode.parameters),
|
|
265
|
-
|
|
285
|
+
stateMutability: subNode.stateMutability,
|
|
266
286
|
});
|
|
267
287
|
}
|
|
268
288
|
else {
|
|
@@ -283,9 +303,9 @@ function parseContractDefinition(umlClass, node) {
|
|
|
283
303
|
});
|
|
284
304
|
}
|
|
285
305
|
// Recursively parse function parameters for associations
|
|
286
|
-
|
|
306
|
+
addAssociations(subNode.parameters, umlClass);
|
|
287
307
|
if (subNode.returnParameters) {
|
|
288
|
-
|
|
308
|
+
addAssociations(subNode.returnParameters, umlClass);
|
|
289
309
|
}
|
|
290
310
|
// If no body to the function, it must be either an Interface or Abstract
|
|
291
311
|
if (subNode.body === null) {
|
|
@@ -296,7 +316,7 @@ function parseContractDefinition(umlClass, node) {
|
|
|
296
316
|
}
|
|
297
317
|
else {
|
|
298
318
|
// Recursively parse function statements for associations
|
|
299
|
-
|
|
319
|
+
addAssociations(subNode.body.statements, umlClass);
|
|
300
320
|
}
|
|
301
321
|
}
|
|
302
322
|
else if ((0, typeGuards_1.isModifierDefinition)(subNode)) {
|
|
@@ -307,7 +327,7 @@ function parseContractDefinition(umlClass, node) {
|
|
|
307
327
|
});
|
|
308
328
|
if (subNode.body && subNode.body.statements) {
|
|
309
329
|
// Recursively parse modifier statements for associations
|
|
310
|
-
|
|
330
|
+
addAssociations(subNode.body.statements, umlClass);
|
|
311
331
|
}
|
|
312
332
|
}
|
|
313
333
|
else if ((0, typeGuards_1.isEventDefinition)(subNode)) {
|
|
@@ -317,7 +337,7 @@ function parseContractDefinition(umlClass, node) {
|
|
|
317
337
|
parameters: parseParameters(subNode.parameters),
|
|
318
338
|
});
|
|
319
339
|
// Recursively parse event parameters for associations
|
|
320
|
-
|
|
340
|
+
addAssociations(subNode.parameters, umlClass);
|
|
321
341
|
}
|
|
322
342
|
else if ((0, typeGuards_1.isStructDefinition)(subNode)) {
|
|
323
343
|
const structClass = new umlClass_1.UmlClass({
|
|
@@ -326,7 +346,7 @@ function parseContractDefinition(umlClass, node) {
|
|
|
326
346
|
relativePath: umlClass.relativePath,
|
|
327
347
|
stereotype: umlClass_1.ClassStereotype.Struct,
|
|
328
348
|
});
|
|
329
|
-
parseStructDefinition(
|
|
349
|
+
parseStructDefinition(subNode, structClass);
|
|
330
350
|
umlClasses.push(structClass);
|
|
331
351
|
// list as contract level struct
|
|
332
352
|
umlClass.structs.push(structClass.id);
|
|
@@ -338,19 +358,22 @@ function parseContractDefinition(umlClass, node) {
|
|
|
338
358
|
relativePath: umlClass.relativePath,
|
|
339
359
|
stereotype: umlClass_1.ClassStereotype.Enum,
|
|
340
360
|
});
|
|
341
|
-
parseEnumDefinition(
|
|
361
|
+
parseEnumDefinition(subNode, enumClass);
|
|
342
362
|
umlClasses.push(enumClass);
|
|
343
363
|
// list as contract level enum
|
|
344
364
|
umlClass.enums.push(enumClass.id);
|
|
345
365
|
}
|
|
346
366
|
});
|
|
347
|
-
return umlClass;
|
|
348
367
|
}
|
|
349
|
-
|
|
368
|
+
/**
|
|
369
|
+
* Recursively parse a list of ASTNodes for UML associations
|
|
370
|
+
* @param nodes array of parser output of type ASTNode
|
|
371
|
+
* @param umlClass that has associations added of type `Association`. This parameter is mutated.
|
|
372
|
+
*/
|
|
350
373
|
function addAssociations(nodes, umlClass) {
|
|
351
374
|
if (!nodes || !Array.isArray(nodes)) {
|
|
352
375
|
debug('Warning - can not recursively parse AST nodes for associations. Invalid nodes array');
|
|
353
|
-
return
|
|
376
|
+
return;
|
|
354
377
|
}
|
|
355
378
|
for (const node of nodes) {
|
|
356
379
|
// Some variables can be null. eg var (lad,,,) = tub.cups(cup);
|
|
@@ -382,8 +405,8 @@ function addAssociations(nodes, umlClass) {
|
|
|
382
405
|
}
|
|
383
406
|
}
|
|
384
407
|
else if (node.typeName.type === 'Mapping') {
|
|
385
|
-
|
|
386
|
-
|
|
408
|
+
addAssociations([node.typeName.keyType], umlClass);
|
|
409
|
+
addAssociations([
|
|
387
410
|
{
|
|
388
411
|
...node.typeName.valueType,
|
|
389
412
|
isStateVar: node.isStateVar,
|
|
@@ -416,89 +439,101 @@ function addAssociations(nodes, umlClass) {
|
|
|
416
439
|
});
|
|
417
440
|
break;
|
|
418
441
|
case 'Block':
|
|
419
|
-
|
|
442
|
+
addAssociations(node.statements, umlClass);
|
|
420
443
|
break;
|
|
421
444
|
case 'StateVariableDeclaration':
|
|
422
445
|
case 'VariableDeclarationStatement':
|
|
423
|
-
|
|
424
|
-
|
|
446
|
+
addAssociations(node.variables, umlClass);
|
|
447
|
+
parseExpression(node.initialValue, umlClass);
|
|
448
|
+
break;
|
|
449
|
+
case 'EmitStatement':
|
|
450
|
+
addAssociations(node.eventCall.arguments, umlClass);
|
|
451
|
+
parseExpression(node.eventCall.expression, umlClass);
|
|
452
|
+
break;
|
|
453
|
+
case 'FunctionCall':
|
|
454
|
+
addAssociations(node.arguments, umlClass);
|
|
455
|
+
parseExpression(node.expression, umlClass);
|
|
425
456
|
break;
|
|
426
457
|
case 'ForStatement':
|
|
427
458
|
if ('statements' in node.body) {
|
|
428
|
-
|
|
459
|
+
addAssociations(node.body.statements, umlClass);
|
|
429
460
|
}
|
|
430
|
-
|
|
431
|
-
|
|
461
|
+
parseExpression(node.conditionExpression, umlClass);
|
|
462
|
+
parseExpression(node.loopExpression.expression, umlClass);
|
|
432
463
|
break;
|
|
433
464
|
case 'WhileStatement':
|
|
434
465
|
if ('statements' in node.body) {
|
|
435
|
-
|
|
466
|
+
addAssociations(node.body.statements, umlClass);
|
|
436
467
|
}
|
|
437
468
|
break;
|
|
438
469
|
case 'DoWhileStatement':
|
|
439
470
|
if ('statements' in node.body) {
|
|
440
|
-
|
|
471
|
+
addAssociations(node.body.statements, umlClass);
|
|
441
472
|
}
|
|
442
|
-
|
|
473
|
+
parseExpression(node.condition, umlClass);
|
|
443
474
|
break;
|
|
444
475
|
case 'ReturnStatement':
|
|
445
476
|
case 'ExpressionStatement':
|
|
446
|
-
|
|
477
|
+
parseExpression(node.expression, umlClass);
|
|
447
478
|
break;
|
|
448
479
|
case 'IfStatement':
|
|
449
480
|
if (node.trueBody) {
|
|
450
481
|
if ('statements' in node.trueBody) {
|
|
451
|
-
|
|
482
|
+
addAssociations(node.trueBody.statements, umlClass);
|
|
452
483
|
}
|
|
453
484
|
if ('expression' in node.trueBody) {
|
|
454
|
-
|
|
485
|
+
parseExpression(node.trueBody.expression, umlClass);
|
|
455
486
|
}
|
|
456
487
|
}
|
|
457
488
|
if (node.falseBody) {
|
|
458
489
|
if ('statements' in node.falseBody) {
|
|
459
|
-
|
|
490
|
+
addAssociations(node.falseBody.statements, umlClass);
|
|
460
491
|
}
|
|
461
492
|
if ('expression' in node.falseBody) {
|
|
462
|
-
|
|
493
|
+
parseExpression(node.falseBody.expression, umlClass);
|
|
463
494
|
}
|
|
464
495
|
}
|
|
465
|
-
|
|
496
|
+
parseExpression(node.condition, umlClass);
|
|
466
497
|
break;
|
|
467
498
|
default:
|
|
468
499
|
break;
|
|
469
500
|
}
|
|
470
501
|
}
|
|
471
|
-
return umlClass;
|
|
472
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Recursively parse an expression to add UML associations to other contracts, constants, enums or structs.
|
|
505
|
+
* @param expression defined in ASTNode as `Expression`
|
|
506
|
+
* @param umlClass that has associations added of type `Association`. This parameter is mutated.
|
|
507
|
+
*/
|
|
473
508
|
function parseExpression(expression, umlClass) {
|
|
474
509
|
if (!expression || !expression.type) {
|
|
475
|
-
return
|
|
510
|
+
return;
|
|
476
511
|
}
|
|
477
512
|
if (expression.type === 'BinaryOperation') {
|
|
478
|
-
|
|
479
|
-
|
|
513
|
+
parseExpression(expression.left, umlClass);
|
|
514
|
+
parseExpression(expression.right, umlClass);
|
|
480
515
|
}
|
|
481
516
|
else if (expression.type === 'FunctionCall') {
|
|
482
|
-
|
|
517
|
+
parseExpression(expression.expression, umlClass);
|
|
483
518
|
expression.arguments.forEach((arg) => {
|
|
484
|
-
|
|
519
|
+
parseExpression(arg, umlClass);
|
|
485
520
|
});
|
|
486
521
|
}
|
|
487
522
|
else if (expression.type === 'IndexAccess') {
|
|
488
|
-
|
|
489
|
-
|
|
523
|
+
parseExpression(expression.base, umlClass);
|
|
524
|
+
parseExpression(expression.index, umlClass);
|
|
490
525
|
}
|
|
491
526
|
else if (expression.type === 'TupleExpression') {
|
|
492
527
|
expression.components.forEach((component) => {
|
|
493
|
-
|
|
528
|
+
parseExpression(component, umlClass);
|
|
494
529
|
});
|
|
495
530
|
}
|
|
496
531
|
else if (expression.type === 'MemberAccess') {
|
|
497
|
-
|
|
532
|
+
parseExpression(expression.expression, umlClass);
|
|
498
533
|
}
|
|
499
534
|
else if (expression.type === 'Conditional') {
|
|
500
|
-
|
|
501
|
-
|
|
535
|
+
addAssociations([expression.trueExpression], umlClass);
|
|
536
|
+
addAssociations([expression.falseExpression], umlClass);
|
|
502
537
|
}
|
|
503
538
|
else if (expression.type === 'Identifier') {
|
|
504
539
|
umlClass.addAssociation({
|
|
@@ -507,14 +542,19 @@ function parseExpression(expression, umlClass) {
|
|
|
507
542
|
});
|
|
508
543
|
}
|
|
509
544
|
else if (expression.type === 'NewExpression') {
|
|
510
|
-
|
|
545
|
+
addAssociations([expression.typeName], umlClass);
|
|
511
546
|
}
|
|
512
547
|
else if (expression.type === 'UnaryOperation' &&
|
|
513
548
|
expression.subExpression) {
|
|
514
|
-
|
|
549
|
+
parseExpression(expression.subExpression, umlClass);
|
|
515
550
|
}
|
|
516
|
-
return umlClass;
|
|
517
551
|
}
|
|
552
|
+
/**
|
|
553
|
+
* Convert user defined type to class name and struct or enum.
|
|
554
|
+
* eg `Set.Data` has class `Set` and struct or enum of `Data`
|
|
555
|
+
* @param rawClassName can be `TypeName` properties `namePath`, `length.name` or `baseTypeName.namePath` as defined in the ASTNode
|
|
556
|
+
* @return object with `umlClassName` and `structOrEnum` of type string
|
|
557
|
+
*/
|
|
518
558
|
function parseClassName(rawClassName) {
|
|
519
559
|
if (!rawClassName ||
|
|
520
560
|
typeof rawClassName !== 'string' ||
|
|
@@ -531,6 +571,11 @@ function parseClassName(rawClassName) {
|
|
|
531
571
|
structOrEnum: splitUmlClassName[1],
|
|
532
572
|
};
|
|
533
573
|
}
|
|
574
|
+
/**
|
|
575
|
+
* Converts the contract visibility to attribute or operator visibility of type `Visibility`
|
|
576
|
+
* @param params defined in ASTNode as VariableDeclaration.visibility, FunctionDefinition.visibility or FunctionTypeName.visibility
|
|
577
|
+
* @return visibility `Visibility` enum used by the `visibility` property on UML `Attribute` or `Operator`
|
|
578
|
+
*/
|
|
534
579
|
function parseVisibility(visibility) {
|
|
535
580
|
switch (visibility) {
|
|
536
581
|
case 'default':
|
|
@@ -547,6 +592,12 @@ function parseVisibility(visibility) {
|
|
|
547
592
|
throw Error(`Invalid visibility ${visibility}. Was not public, external, internal or private`);
|
|
548
593
|
}
|
|
549
594
|
}
|
|
595
|
+
/**
|
|
596
|
+
* Recursively converts contract variables to UMLClass attribute types.
|
|
597
|
+
* Types can be standard Solidity types, arrays, mappings or user defined types like structs and enums.
|
|
598
|
+
* @param typeName defined in ASTNode as `TypeName`
|
|
599
|
+
* @return attributeTypes array of type string and `AttributeType`
|
|
600
|
+
*/
|
|
550
601
|
function parseTypeName(typeName) {
|
|
551
602
|
switch (typeName.type) {
|
|
552
603
|
case 'ElementaryTypeName':
|
|
@@ -582,6 +633,11 @@ function parseTypeName(typeName) {
|
|
|
582
633
|
throw Error(`Invalid typeName ${typeName}`);
|
|
583
634
|
}
|
|
584
635
|
}
|
|
636
|
+
/**
|
|
637
|
+
* Converts the contract params to `Operator` properties `parameters` or `returnParameters`
|
|
638
|
+
* @param params defined in ASTNode as `VariableDeclaration`
|
|
639
|
+
* @return parameters or `returnParameters` of the `Operator` of type `Parameter`
|
|
640
|
+
*/
|
|
585
641
|
function parseParameters(params) {
|
|
586
642
|
if (!params || !params) {
|
|
587
643
|
return [];
|
|
@@ -596,6 +652,11 @@ function parseParameters(params) {
|
|
|
596
652
|
}
|
|
597
653
|
return parameters;
|
|
598
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* Converts the contract `kind` to `UMLClass` stereotype
|
|
657
|
+
* @param kind defined in ASTNode as ContractDefinition.kind
|
|
658
|
+
* @return stereotype of the `UMLClass` with type `ClassStereotype
|
|
659
|
+
*/
|
|
599
660
|
function parseContractKind(kind) {
|
|
600
661
|
switch (kind) {
|
|
601
662
|
case 'contract':
|
|
@@ -610,7 +671,4 @@ function parseContractKind(kind) {
|
|
|
610
671
|
throw Error(`Invalid kind ${kind}`);
|
|
611
672
|
}
|
|
612
673
|
}
|
|
613
|
-
function parsePayable(stateMutability) {
|
|
614
|
-
return stateMutability === 'payable';
|
|
615
|
-
}
|
|
616
674
|
//# sourceMappingURL=converterAST2Classes.js.map
|
|
@@ -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;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { ClassOptions } from './converterClass2Dot';
|
|
2
2
|
import { UmlClass } from './umlClass';
|
|
3
|
+
/**
|
|
4
|
+
* Converts UML classes to Graphviz's DOT format.
|
|
5
|
+
* The DOT grammar defines Graphviz nodes, edges, graphs, subgraphs, and clusters http://www.graphviz.org/doc/info/lang.html
|
|
6
|
+
* @param umlClasses array of UML classes of type `UMLClass`
|
|
7
|
+
* @param clusterFolders flag if UML classes are to be clustered into folders their source code was in
|
|
8
|
+
* @param classOptions command line options for the `class` command
|
|
9
|
+
* @return dotString Graphviz's DOT format for defining nodes, edges and clusters.
|
|
10
|
+
*/
|
|
3
11
|
export declare function convertUmlClasses2Dot(umlClasses: UmlClass[], clusterFolders?: boolean, classOptions?: ClassOptions): string;
|
|
4
12
|
export declare function addAssociationsToDot(umlClasses: UmlClass[], classOptions?: ClassOptions): string;
|
|
@@ -6,6 +6,14 @@ const converterClass2Dot_1 = require("./converterClass2Dot");
|
|
|
6
6
|
const umlClass_1 = require("./umlClass");
|
|
7
7
|
const associations_1 = require("./associations");
|
|
8
8
|
const debug = require('debug')('sol2uml');
|
|
9
|
+
/**
|
|
10
|
+
* Converts UML classes to Graphviz's DOT format.
|
|
11
|
+
* The DOT grammar defines Graphviz nodes, edges, graphs, subgraphs, and clusters http://www.graphviz.org/doc/info/lang.html
|
|
12
|
+
* @param umlClasses array of UML classes of type `UMLClass`
|
|
13
|
+
* @param clusterFolders flag if UML classes are to be clustered into folders their source code was in
|
|
14
|
+
* @param classOptions command line options for the `class` command
|
|
15
|
+
* @return dotString Graphviz's DOT format for defining nodes, edges and clusters.
|
|
16
|
+
*/
|
|
9
17
|
function convertUmlClasses2Dot(umlClasses, clusterFolders = false, classOptions = {}) {
|
|
10
18
|
let dotString = `
|
|
11
19
|
digraph UmlClassDiagram {
|
package/lib/filterClasses.d.ts
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import { WeightedDiGraph } from 'js-graph-algorithms';
|
|
2
2
|
import { UmlClass } from './umlClass';
|
|
3
|
+
import { ClassOptions } from './converterClass2Dot';
|
|
4
|
+
/**
|
|
5
|
+
* Filter out any UML Class types that are to be hidden.
|
|
6
|
+
* @param umlClasses array of UML classes of type `UMLClass`
|
|
7
|
+
* @param options sol2uml class options
|
|
8
|
+
* @return umlClasses filtered list of UML classes of type `UMLClass`
|
|
9
|
+
*/
|
|
10
|
+
export declare const filterHiddenClasses: (umlClasses: UmlClass[], options: ClassOptions) => UmlClass[];
|
|
11
|
+
/**
|
|
12
|
+
* Finds all the UML classes that have an association with a list of base contract names.
|
|
13
|
+
* The associated classes can be contracts, abstract contracts, interfaces, libraries, enums, structs or constants.
|
|
14
|
+
* @param umlClasses array of UML classes of type `UMLClass`
|
|
15
|
+
* @param baseContractNames array of base contract names
|
|
16
|
+
* @param depth limit the number of associations from the base contract.
|
|
17
|
+
* @return filteredUmlClasses list of UML classes of type `UMLClass`
|
|
18
|
+
*/
|
|
3
19
|
export declare const classesConnectedToBaseContracts: (umlClasses: UmlClass[], baseContractNames: string[], depth?: number) => UmlClass[];
|
|
20
|
+
/**
|
|
21
|
+
* Finds all the UML classes that have an association with a base contract name.
|
|
22
|
+
* The associated classes can be contracts, abstract contracts, interfaces, libraries, enums, structs or constants.
|
|
23
|
+
* @param umlClasses array of UML classes of type `UMLClass`
|
|
24
|
+
* @param baseContractName base contract name
|
|
25
|
+
* @param weightedDirectedGraph graph of type WeightedDiGraph from the `js-graph-algorithms` package
|
|
26
|
+
* @param depth limit the number of associations from the base contract.
|
|
27
|
+
* @return filteredUmlClasses list of UML classes of type `UMLClass`
|
|
28
|
+
*/
|
|
4
29
|
export declare const classesConnectedToBaseContract: (umlClasses: UmlClass[], baseContractName: string, weightedDirectedGraph: WeightedDiGraph, depth?: number) => {
|
|
5
30
|
[contractName: string]: UmlClass;
|
|
6
31
|
};
|
package/lib/filterClasses.js
CHANGED
|
@@ -1,8 +1,38 @@
|
|
|
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
|
+
/**
|
|
8
|
+
* Filter out any UML Class types that are to be hidden.
|
|
9
|
+
* @param umlClasses array of UML classes of type `UMLClass`
|
|
10
|
+
* @param options sol2uml class options
|
|
11
|
+
* @return umlClasses filtered list of UML classes of type `UMLClass`
|
|
12
|
+
*/
|
|
13
|
+
const filterHiddenClasses = (umlClasses, options) => {
|
|
14
|
+
return umlClasses.filter((u) => (u.stereotype === umlClass_1.ClassStereotype.Enum && !options.hideEnums) ||
|
|
15
|
+
(u.stereotype === umlClass_1.ClassStereotype.Struct && !options.hideStructs) ||
|
|
16
|
+
(u.stereotype === umlClass_1.ClassStereotype.Abstract &&
|
|
17
|
+
!options.hideAbstracts) ||
|
|
18
|
+
(u.stereotype === umlClass_1.ClassStereotype.Interface &&
|
|
19
|
+
!options.hideInterfaces) ||
|
|
20
|
+
(u.stereotype === umlClass_1.ClassStereotype.Constant &&
|
|
21
|
+
!options.hideConstants) ||
|
|
22
|
+
(u.stereotype === umlClass_1.ClassStereotype.Library &&
|
|
23
|
+
!options.hideLibraries) ||
|
|
24
|
+
u.stereotype === umlClass_1.ClassStereotype.None ||
|
|
25
|
+
u.stereotype === umlClass_1.ClassStereotype.Contract);
|
|
26
|
+
};
|
|
27
|
+
exports.filterHiddenClasses = filterHiddenClasses;
|
|
28
|
+
/**
|
|
29
|
+
* Finds all the UML classes that have an association with a list of base contract names.
|
|
30
|
+
* The associated classes can be contracts, abstract contracts, interfaces, libraries, enums, structs or constants.
|
|
31
|
+
* @param umlClasses array of UML classes of type `UMLClass`
|
|
32
|
+
* @param baseContractNames array of base contract names
|
|
33
|
+
* @param depth limit the number of associations from the base contract.
|
|
34
|
+
* @return filteredUmlClasses list of UML classes of type `UMLClass`
|
|
35
|
+
*/
|
|
6
36
|
const classesConnectedToBaseContracts = (umlClasses, baseContractNames, depth) => {
|
|
7
37
|
let filteredUmlClasses = {};
|
|
8
38
|
const weightedDirectedGraph = loadWeightedDirectedGraph(umlClasses);
|
|
@@ -15,6 +45,15 @@ const classesConnectedToBaseContracts = (umlClasses, baseContractNames, depth) =
|
|
|
15
45
|
return Object.values(filteredUmlClasses);
|
|
16
46
|
};
|
|
17
47
|
exports.classesConnectedToBaseContracts = classesConnectedToBaseContracts;
|
|
48
|
+
/**
|
|
49
|
+
* Finds all the UML classes that have an association with a base contract name.
|
|
50
|
+
* The associated classes can be contracts, abstract contracts, interfaces, libraries, enums, structs or constants.
|
|
51
|
+
* @param umlClasses array of UML classes of type `UMLClass`
|
|
52
|
+
* @param baseContractName base contract name
|
|
53
|
+
* @param weightedDirectedGraph graph of type WeightedDiGraph from the `js-graph-algorithms` package
|
|
54
|
+
* @param depth limit the number of associations from the base contract.
|
|
55
|
+
* @return filteredUmlClasses list of UML classes of type `UMLClass`
|
|
56
|
+
*/
|
|
18
57
|
const classesConnectedToBaseContract = (umlClasses, baseContractName, weightedDirectedGraph, depth = 1000) => {
|
|
19
58
|
// Find the base UML Class from the base contract name
|
|
20
59
|
const baseUmlClass = umlClasses.find(({ name }) => {
|
|
@@ -35,7 +74,9 @@ const classesConnectedToBaseContract = (umlClasses, baseContractName, weightedDi
|
|
|
35
74
|
};
|
|
36
75
|
exports.classesConnectedToBaseContract = classesConnectedToBaseContract;
|
|
37
76
|
function loadWeightedDirectedGraph(umlClasses) {
|
|
38
|
-
const weightedDirectedGraph = new js_graph_algorithms_1.WeightedDiGraph(
|
|
77
|
+
const weightedDirectedGraph = new js_graph_algorithms_1.WeightedDiGraph(
|
|
78
|
+
// the number vertices in the graph
|
|
79
|
+
umlClass_1.UmlClass.idCounter + 1);
|
|
39
80
|
for (const sourceUmlClass of umlClasses) {
|
|
40
81
|
for (const association of Object.values(sourceUmlClass.associations)) {
|
|
41
82
|
// Find the first UML Class that matches the target class name
|
|
@@ -43,6 +84,8 @@ function loadWeightedDirectedGraph(umlClasses) {
|
|
|
43
84
|
if (!targetUmlClass) {
|
|
44
85
|
continue;
|
|
45
86
|
}
|
|
87
|
+
const isTarget = umlClasses.find((u) => u.id === targetUmlClass.id);
|
|
88
|
+
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
89
|
weightedDirectedGraph.addEdge(new js_graph_algorithms_1.Edge(sourceUmlClass.id, targetUmlClass.id, 1));
|
|
47
90
|
}
|
|
48
91
|
}
|
package/lib/parserEtherscan.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ASTNode } from '@solidity-parser/parser/dist/src/ast-types';
|
|
2
2
|
import { UmlClass } from './umlClass';
|
|
3
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
|
+
export type Network = typeof networks[number];
|
|
5
5
|
export declare class EtherscanParser {
|
|
6
6
|
protected apikey: string;
|
|
7
7
|
network: Network;
|
|
@@ -45,4 +45,3 @@ export declare class EtherscanParser {
|
|
|
45
45
|
compilerVersion: string;
|
|
46
46
|
}>;
|
|
47
47
|
}
|
|
48
|
-
export {};
|
package/lib/parserGeneral.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
import { Network } from './parserEtherscan';
|
|
1
2
|
import { UmlClass } from './umlClass';
|
|
2
|
-
export
|
|
3
|
+
export interface ParserOptions {
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
network?: Network;
|
|
6
|
+
subfolders?: string;
|
|
7
|
+
ignoreFilesOrFolders?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Parses Solidity source code from a local filesystem or verified code on Etherscan
|
|
11
|
+
* @param fileFolderAddress filename, folder name or contract address
|
|
12
|
+
* @param options of type `ParserOptions`
|
|
13
|
+
*/
|
|
14
|
+
export declare const parserUmlClasses: (fileFolderAddress: string, options: ParserOptions) => Promise<{
|
|
3
15
|
umlClasses: UmlClass[];
|
|
4
16
|
contractName?: string;
|
|
5
17
|
}>;
|
package/lib/parserGeneral.js
CHANGED
|
@@ -5,6 +5,11 @@ const parserEtherscan_1 = require("./parserEtherscan");
|
|
|
5
5
|
const parserFiles_1 = require("./parserFiles");
|
|
6
6
|
const regEx_1 = require("./utils/regEx");
|
|
7
7
|
const debug = require('debug')('sol2uml');
|
|
8
|
+
/**
|
|
9
|
+
* Parses Solidity source code from a local filesystem or verified code on Etherscan
|
|
10
|
+
* @param fileFolderAddress filename, folder name or contract address
|
|
11
|
+
* @param options of type `ParserOptions`
|
|
12
|
+
*/
|
|
8
13
|
const parserUmlClasses = async (fileFolderAddress, options) => {
|
|
9
14
|
let result = {
|
|
10
15
|
umlClasses: [],
|
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,20 +70,36 @@ 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 = {
|
|
75
78
|
...command.parent._optionValues,
|
|
76
79
|
...options,
|
|
77
80
|
};
|
|
81
|
+
// Parse Solidity code from local file system or verified source code on Etherscan.
|
|
78
82
|
let { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
if (options.squash &&
|
|
84
|
+
// Must specify base contract(s) or parse from Etherscan to get contractName
|
|
85
|
+
!(options.baseContractNames || contractName)) {
|
|
86
|
+
throw Error('Must specify base contract(s) when using the squash option against local Solidity files.');
|
|
87
|
+
}
|
|
88
|
+
// Filter out any class stereotypes that are to be hidden
|
|
89
|
+
let filteredUmlClasses = (0, filterClasses_1.filterHiddenClasses)(umlClasses, options);
|
|
90
|
+
const baseContractNames = options.baseContractNames?.split(',');
|
|
91
|
+
if (baseContractNames) {
|
|
92
|
+
// Find all the classes connected to the base classes
|
|
93
|
+
filteredUmlClasses = (0, filterClasses_1.classesConnectedToBaseContracts)(filteredUmlClasses, baseContractNames, options.depth);
|
|
83
94
|
contractName = baseContractNames[0];
|
|
84
95
|
}
|
|
96
|
+
// squash contracts
|
|
97
|
+
if (options.squash) {
|
|
98
|
+
filteredUmlClasses = (0, squashClasses_1.squashUmlClasses)(filteredUmlClasses, baseContractNames || [contractName]);
|
|
99
|
+
}
|
|
100
|
+
// Convert UML classes to Graphviz dot format.
|
|
85
101
|
const dotString = (0, converterClasses2Dot_1.convertUmlClasses2Dot)(filteredUmlClasses, combinedOptions.clusterFolders, combinedOptions);
|
|
102
|
+
// Convert Graphviz dot format to file formats. eg svg or png
|
|
86
103
|
await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName || 'classDiagram', combinedOptions.outputFormat, combinedOptions.outputFileName);
|
|
87
104
|
debug(`Finished generating UML`);
|
|
88
105
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { UmlClass } from './umlClass';
|
|
2
|
+
/**
|
|
3
|
+
* Flattens the inheritance hierarchy for each base contract.
|
|
4
|
+
* @param umlClasses array of UML classes of type `UMLClass`
|
|
5
|
+
* @param baseContractNames array of contract names to be rendered in squashed format.
|
|
6
|
+
* @return squashUmlClasses array of UML classes of type `UMLClass`
|
|
7
|
+
*/
|
|
8
|
+
export declare const squashUmlClasses: (umlClasses: UmlClass[], baseContractNames: string[]) => UmlClass[];
|
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
/**
|
|
31
|
+
* Flattens the inheritance hierarchy for each base contract.
|
|
32
|
+
* @param umlClasses array of UML classes of type `UMLClass`
|
|
33
|
+
* @param baseContractNames array of contract names to be rendered in squashed format.
|
|
34
|
+
* @return squashUmlClasses array of UML classes of type `UMLClass`
|
|
35
|
+
*/
|
|
36
|
+
const squashUmlClasses = (umlClasses, baseContractNames) => {
|
|
37
|
+
let removedClassIds = [];
|
|
38
|
+
for (const baseContractName of baseContractNames) {
|
|
39
|
+
// Find the base UML Class to squash
|
|
40
|
+
let baseIndex = umlClasses.findIndex(({ name }) => {
|
|
41
|
+
return name === baseContractName;
|
|
42
|
+
});
|
|
43
|
+
if (baseIndex === undefined) {
|
|
44
|
+
throw Error(`Failed to find contract with name "${baseContractName}" to squash`);
|
|
45
|
+
}
|
|
46
|
+
const baseClass = umlClasses[baseIndex];
|
|
47
|
+
let squashedClass = new umlClass_1.UmlClass({
|
|
48
|
+
name: baseClass.name,
|
|
49
|
+
absolutePath: baseClass.absolutePath,
|
|
50
|
+
relativePath: baseClass.relativePath,
|
|
51
|
+
});
|
|
52
|
+
squashedClass.id = baseClass.id;
|
|
53
|
+
const result = recursiveSquash(squashedClass, [], baseClass, umlClasses, 1);
|
|
54
|
+
removedClassIds = removedClassIds.concat(result.removedClassIds);
|
|
55
|
+
// Remove overridden functions from squashed class
|
|
56
|
+
squashedClass.operators = reduceOperators(squashedClass.operators);
|
|
57
|
+
umlClasses[baseIndex] = squashedClass;
|
|
58
|
+
}
|
|
59
|
+
// filter the list of classes that will be rendered
|
|
60
|
+
return umlClasses.filter((u) =>
|
|
61
|
+
// remove any squashed inherited contracts
|
|
62
|
+
!removedClassIds.includes(u.id) ||
|
|
63
|
+
// Include all base contracts
|
|
64
|
+
baseContractNames.includes(u.name));
|
|
65
|
+
};
|
|
66
|
+
exports.squashUmlClasses = squashUmlClasses;
|
|
67
|
+
const recursiveSquash = (squashedClass, inheritedContractNames, baseClass, umlClasses, startPosition) => {
|
|
68
|
+
let currentPosition = startPosition;
|
|
69
|
+
const removedClassIds = [];
|
|
70
|
+
// For each association from the baseClass
|
|
71
|
+
for (const [targetClassName, association] of Object.entries(baseClass.associations)) {
|
|
72
|
+
// if inheritance and (Abstract or Contract)
|
|
73
|
+
// Libraries and Interfaces will be copied
|
|
74
|
+
if (association.realization) {
|
|
75
|
+
// Find the target UML Class
|
|
76
|
+
const inheritedContract = umlClasses.find(({ name }) => {
|
|
77
|
+
return name === targetClassName;
|
|
78
|
+
});
|
|
79
|
+
if (!inheritedContract) {
|
|
80
|
+
debug(`Warning: failed to find inherited contract with name ${targetClassName}`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Is the associated class a contract or abstract contract?
|
|
84
|
+
if (inheritedContract?.stereotype === umlClass_1.ClassStereotype.Library) {
|
|
85
|
+
squashedClass.addAssociation(association);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// has the contract already been added to the inheritance tree?
|
|
89
|
+
const alreadyInherited = inheritedContractNames.includes(inheritedContract.name);
|
|
90
|
+
// Do not add inherited contract if it has already been added to the inheritance tree
|
|
91
|
+
if (!alreadyInherited) {
|
|
92
|
+
inheritedContractNames.push(inheritedContract.name);
|
|
93
|
+
const squashResult = recursiveSquash(squashedClass, inheritedContractNames, inheritedContract, umlClasses, currentPosition++);
|
|
94
|
+
// Add to list of removed class ids
|
|
95
|
+
removedClassIds.push(...squashResult.removedClassIds, inheritedContract.id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// Copy association but will not duplicate it
|
|
101
|
+
squashedClass.addAssociation(association);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Copy class properties from the baseClass to the squashedClass
|
|
105
|
+
baseClass.constants.forEach((c) => squashedClass.constants.push({ ...c, sourceContract: baseClass.name }));
|
|
106
|
+
baseClass.attributes.forEach((a) => squashedClass.attributes.push({ ...a, sourceContract: baseClass.name }));
|
|
107
|
+
baseClass.enums.forEach((e) => squashedClass.enums.push(e));
|
|
108
|
+
baseClass.structs.forEach((s) => squashedClass.structs.push(s));
|
|
109
|
+
baseClass.imports.forEach((i) => squashedClass.imports.push(i));
|
|
110
|
+
// copy the functions
|
|
111
|
+
baseClass.operators.forEach((f) => squashedClass.operators.push({
|
|
112
|
+
...f,
|
|
113
|
+
hash: hash(f),
|
|
114
|
+
inheritancePosition: currentPosition,
|
|
115
|
+
sourceContract: baseClass.name,
|
|
116
|
+
}));
|
|
117
|
+
return {
|
|
118
|
+
currentPosition,
|
|
119
|
+
removedClassIds,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
const hash = (operator) => {
|
|
123
|
+
const hash = crypto.createHash('sha256');
|
|
124
|
+
let data = operator.name ?? 'fallback';
|
|
125
|
+
operator.parameters?.forEach((p) => {
|
|
126
|
+
data += ',' + p.type;
|
|
127
|
+
});
|
|
128
|
+
operator.returnParameters?.forEach((p) => {
|
|
129
|
+
data += ',' + p.type;
|
|
130
|
+
});
|
|
131
|
+
return hash.update(data).digest('hex');
|
|
132
|
+
};
|
|
133
|
+
const reduceOperators = (operators) => {
|
|
134
|
+
const hashes = new Set(operators.map((o) => o.hash));
|
|
135
|
+
const operatorsWithNoHash = operators.filter((o) => !o.hash);
|
|
136
|
+
const newOperators = [];
|
|
137
|
+
for (const hash of hashes) {
|
|
138
|
+
const operator = operators
|
|
139
|
+
.filter((o) => o.hash === hash)
|
|
140
|
+
// sort operators by inheritance position. smaller to highest
|
|
141
|
+
.sort((o) => o.inheritancePosition)
|
|
142
|
+
// get last operator in the array
|
|
143
|
+
.slice(-1)[0];
|
|
144
|
+
newOperators.push(operator);
|
|
145
|
+
}
|
|
146
|
+
newOperators.push(...operatorsWithNoHash);
|
|
147
|
+
return newOperators;
|
|
148
|
+
};
|
|
149
|
+
//# 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;
|
|
@@ -53,8 +54,11 @@ export interface Operator extends Attribute {
|
|
|
53
54
|
stereotype?: OperatorStereotype;
|
|
54
55
|
parameters?: Parameter[];
|
|
55
56
|
returnParameters?: Parameter[];
|
|
56
|
-
|
|
57
|
+
stateMutability?: string;
|
|
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/lib/writerFiles.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type OutputFormats = 'svg' | 'png' | 'dot' | 'all';
|
|
2
2
|
export declare const writeOutputFiles: (dot: string, fileFolderAddress: string, contractName: string, outputFormat?: OutputFormats, outputFilename?: string) => Promise<void>;
|
|
3
3
|
export declare function convertDot2Svg(dot: string): any;
|
|
4
4
|
export declare function writeSolidity(code: string, filename?: string): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sol2uml",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Solidity contract visualisation tool.",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -20,25 +20,25 @@
|
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@aduh95/viz.js": "^3.7.0",
|
|
23
|
-
"@solidity-parser/parser": "^0.14.
|
|
24
|
-
"axios": "^
|
|
25
|
-
"axios-debug-log": "^0.
|
|
23
|
+
"@solidity-parser/parser": "^0.14.5",
|
|
24
|
+
"axios": "^1.2.1",
|
|
25
|
+
"axios-debug-log": "^1.0.0",
|
|
26
26
|
"commander": "^9.4.1",
|
|
27
27
|
"convert-svg-to-png": "^0.6.4",
|
|
28
28
|
"debug": "^4.3.4",
|
|
29
|
-
"ethers": "^5.7.
|
|
29
|
+
"ethers": "^5.7.2",
|
|
30
30
|
"js-graph-algorithms": "^1.0.18",
|
|
31
31
|
"klaw": "^4.0.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@openzeppelin/contracts": "4.
|
|
35
|
-
"@types/jest": "^29.
|
|
34
|
+
"@openzeppelin/contracts": "4.8.0",
|
|
35
|
+
"@types/jest": "^29.2.4",
|
|
36
36
|
"@types/klaw": "^3.0.3",
|
|
37
|
-
"jest": "^29.1
|
|
38
|
-
"prettier": "^2.
|
|
37
|
+
"jest": "^29.3.1",
|
|
38
|
+
"prettier": "^2.8.1",
|
|
39
39
|
"ts-jest": "^29.0.3",
|
|
40
40
|
"ts-node": "^10.9.1",
|
|
41
|
-
"typescript": "^4.
|
|
41
|
+
"typescript": "^4.9.4"
|
|
42
42
|
},
|
|
43
43
|
"files": [
|
|
44
44
|
"lib/*.js",
|