sol2uml 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,6 +49,7 @@ The three subcommands:
49
49
  * class: Generates a UML class diagram from Solidity source code. default
50
50
  * storage: Generates a diagram of a contract's storage slots.
51
51
  * flatten: Merges verified source files from a Blockchain explorer into one local file.
52
+ * diff: Compares the flattened Solidity code from a Blockchain explorer for two contracts.
52
53
 
53
54
  The Solidity code can be pulled from verified source code on Blockchain explorers like Etherscan or from local Solidity files.
54
55
 
@@ -73,7 +74,7 @@ Commands:
73
74
  ### Class usage
74
75
 
75
76
  ```
76
- Usage: sol2uml class <fileFolderAddress> [options]
77
+ Usage: sol2uml class [options] <fileFolderAddress>
77
78
 
78
79
  Generates UML diagrams from Solidity source code.
79
80
 
@@ -114,7 +115,7 @@ Options:
114
115
  ### Storage usage
115
116
 
116
117
  ```
117
- Usage: sol2uml storage <fileFolderAddress> [options]
118
+ Usage: sol2uml storage [options] <fileFolderAddress>
118
119
 
119
120
  WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.
120
121
 
@@ -136,7 +137,7 @@ Options:
136
137
  ### Flatten usage
137
138
 
138
139
  ```
139
- Usage: sol2uml flatten <contractAddress> [options]
140
+ Usage: sol2uml flatten <contractAddress>
140
141
 
141
142
  In order for the merged code to compile, the following is done:
142
143
  1. pragma solidity is set using the compiler of the verified contract.
@@ -154,6 +155,28 @@ Options:
154
155
  -h, --help display help for command
155
156
  ```
156
157
 
158
+ ### Diff usage
159
+
160
+ ```
161
+ Usage: sol2uml diff [options] <addressA> <addressB>
162
+
163
+ The results show the comparision of contract A to B.
164
+ The green sections are additions to contract B that are not in contract A.
165
+ The red sections are removals from contract A that are not in contract B.
166
+ The line numbers are from contract B. There are no line numbers for the red sections as they are not in contract B.
167
+
168
+ Compare verified Solidity code differences between two contracts.
169
+
170
+ Arguments:
171
+ addressA Contract address in hexadecimal format with a 0x prefix.
172
+ addressB Contract address in hexadecimal format with a 0x prefix.
173
+
174
+ Options:
175
+ -l, --lineBuffer <value> Minimum number a lines before and after changes (default: "4")
176
+ -s, --saveFiles Save the flattened contract code to the filesystem. The file names will be the contract address with a .sol extension. (default: false)
177
+ -h, --help display help for command
178
+ ```
179
+
157
180
  ## UML Class diagram examples
158
181
 
159
182
  To generate a diagram of all contracts under the contracts folder and its sub folders
@@ -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
- umlClass = parseContractDefinition(umlClass, childNode);
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
- umlClass = parseStructDefinition(umlClass, childNode);
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
- umlClass = parseEnumDefinition(umlClass, childNode);
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
- function parseStructDefinition(umlClass, node) {
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
- umlClass = addAssociations(node.members, umlClass);
192
- return umlClass;
203
+ addAssociations(node.members, umlClass);
193
204
  }
194
- function parseEnumDefinition(umlClass, node) {
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
- umlClass = addAssociations(node.members, umlClass);
204
- return umlClass;
219
+ addAssociations(node.members, umlClass);
205
220
  }
206
- function parseContractDefinition(umlClass, node) {
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
- umlClass = addAssociations(subNode.variables, umlClass);
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
- isPayable: parsePayable(subNode.stateMutability),
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
- umlClass = addAssociations(subNode.parameters, umlClass);
306
+ addAssociations(subNode.parameters, umlClass);
287
307
  if (subNode.returnParameters) {
288
- umlClass = addAssociations(subNode.returnParameters, umlClass);
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
- umlClass = addAssociations(subNode.body.statements, umlClass);
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
- umlClass = addAssociations(subNode.body.statements, umlClass);
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
- umlClass = addAssociations(subNode.parameters, umlClass);
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(structClass, subNode);
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(enumClass, subNode);
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
- // Recursively parse AST nodes for associations
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 umlClass;
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
- umlClass = addAssociations([node.typeName.keyType], umlClass);
386
- umlClass = addAssociations([
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
- umlClass = addAssociations(node.statements, umlClass);
442
+ addAssociations(node.statements, umlClass);
420
443
  break;
421
444
  case 'StateVariableDeclaration':
422
445
  case 'VariableDeclarationStatement':
423
- umlClass = addAssociations(node.variables, umlClass);
424
- umlClass = parseExpression(node.initialValue, umlClass);
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
- umlClass = addAssociations(node.body.statements, umlClass);
459
+ addAssociations(node.body.statements, umlClass);
429
460
  }
430
- umlClass = parseExpression(node.conditionExpression, umlClass);
431
- umlClass = parseExpression(node.loopExpression.expression, umlClass);
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
- umlClass = addAssociations(node.body.statements, umlClass);
466
+ addAssociations(node.body.statements, umlClass);
436
467
  }
437
468
  break;
438
469
  case 'DoWhileStatement':
439
470
  if ('statements' in node.body) {
440
- umlClass = addAssociations(node.body.statements, umlClass);
471
+ addAssociations(node.body.statements, umlClass);
441
472
  }
442
- umlClass = parseExpression(node.condition, umlClass);
473
+ parseExpression(node.condition, umlClass);
443
474
  break;
444
475
  case 'ReturnStatement':
445
476
  case 'ExpressionStatement':
446
- umlClass = parseExpression(node.expression, umlClass);
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
- umlClass = addAssociations(node.trueBody.statements, umlClass);
482
+ addAssociations(node.trueBody.statements, umlClass);
452
483
  }
453
484
  if ('expression' in node.trueBody) {
454
- umlClass = parseExpression(node.trueBody.expression, umlClass);
485
+ parseExpression(node.trueBody.expression, umlClass);
455
486
  }
456
487
  }
457
488
  if (node.falseBody) {
458
489
  if ('statements' in node.falseBody) {
459
- umlClass = addAssociations(node.falseBody.statements, umlClass);
490
+ addAssociations(node.falseBody.statements, umlClass);
460
491
  }
461
492
  if ('expression' in node.falseBody) {
462
- umlClass = parseExpression(node.falseBody.expression, umlClass);
493
+ parseExpression(node.falseBody.expression, umlClass);
463
494
  }
464
495
  }
465
- umlClass = parseExpression(node.condition, umlClass);
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 umlClass;
510
+ return;
476
511
  }
477
512
  if (expression.type === 'BinaryOperation') {
478
- umlClass = parseExpression(expression.left, umlClass);
479
- umlClass = parseExpression(expression.right, umlClass);
513
+ parseExpression(expression.left, umlClass);
514
+ parseExpression(expression.right, umlClass);
480
515
  }
481
516
  else if (expression.type === 'FunctionCall') {
482
- umlClass = parseExpression(expression.expression, umlClass);
517
+ parseExpression(expression.expression, umlClass);
483
518
  expression.arguments.forEach((arg) => {
484
- umlClass = parseExpression(arg, umlClass);
519
+ parseExpression(arg, umlClass);
485
520
  });
486
521
  }
487
522
  else if (expression.type === 'IndexAccess') {
488
- umlClass = parseExpression(expression.base, umlClass);
489
- umlClass = parseExpression(expression.index, umlClass);
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
- umlClass = parseExpression(component, umlClass);
528
+ parseExpression(component, umlClass);
494
529
  });
495
530
  }
496
531
  else if (expression.type === 'MemberAccess') {
497
- umlClass = parseExpression(expression.expression, umlClass);
532
+ parseExpression(expression.expression, umlClass);
498
533
  }
499
534
  else if (expression.type === 'Conditional') {
500
- umlClass = addAssociations([expression.trueExpression], umlClass);
501
- umlClass = addAssociations([expression.falseExpression], umlClass);
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
- umlClass = addAssociations([expression.typeName], umlClass);
545
+ addAssociations([expression.typeName], umlClass);
511
546
  }
512
547
  else if (expression.type === 'UnaryOperation' &&
513
548
  expression.subExpression) {
514
- umlClass = parseExpression(expression.subExpression, umlClass);
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
@@ -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/diff.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Compares code using Google's diff_match_patch and displays the results in the console.
3
+ * @param codeA
4
+ * @param codeB
5
+ * @param lineBuff the number of lines to display before and after each change.
6
+ */
7
+ export declare const diffCode: (codeA: string, codeB: string, lineBuff: number) => void;
package/lib/diff.js ADDED
@@ -0,0 +1,188 @@
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.diffCode = void 0;
27
+ const diff_match_patch_1 = __importStar(require("diff-match-patch"));
28
+ const clc = require('cli-color');
29
+ const SkippedLinesMarker = `\n---`;
30
+ /**
31
+ * Compares code using Google's diff_match_patch and displays the results in the console.
32
+ * @param codeA
33
+ * @param codeB
34
+ * @param lineBuff the number of lines to display before and after each change.
35
+ */
36
+ const diffCode = (codeA, codeB, lineBuff) => {
37
+ // @ts-ignore
38
+ const dmp = new diff_match_patch_1.default();
39
+ const diff = dmp.diff_main(codeA, codeB);
40
+ dmp.diff_cleanupSemantic(diff);
41
+ const linesB = countLines(codeB) + 1;
42
+ diff_pretty(diff, linesB, lineBuff);
43
+ };
44
+ exports.diffCode = diffCode;
45
+ /**
46
+ * Convert a diff array into human readable for the console
47
+ * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
48
+ * @param lines number of a lines in the second contract B
49
+ * @param lineBuff number of a lines to output before and after the change
50
+ */
51
+ const diff_pretty = (diffs, lines, lineBuff = 2) => {
52
+ const linePad = lines.toString().length;
53
+ let output = '';
54
+ let diffIndex = 0;
55
+ let lineCount = 1;
56
+ const firstLineNumber = '1'.padStart(linePad) + ' ';
57
+ for (const diff of diffs) {
58
+ diffIndex++;
59
+ const initialLineNumber = diffIndex <= 1 ? firstLineNumber : '';
60
+ const op = diff[0]; // Operation (insert, delete, equal)
61
+ const text = diff[1]; // Text of change.
62
+ switch (op) {
63
+ case diff_match_patch_1.DIFF_INSERT:
64
+ // If first diff then we need to add the first line number
65
+ const linesInserted = addLineNumbers(text, lineCount, linePad);
66
+ output += initialLineNumber + clc.green(linesInserted);
67
+ lineCount += countLines(text);
68
+ break;
69
+ case diff_match_patch_1.DIFF_DELETE:
70
+ // zero start line means blank line numbers are used
71
+ const linesDeleted = addLineNumbers(text, 0, linePad);
72
+ output += initialLineNumber + clc.red(linesDeleted);
73
+ break;
74
+ case diff_match_patch_1.DIFF_EQUAL:
75
+ const eolPositions = findEOLPositions(text);
76
+ // If no changes yet
77
+ if (diffIndex <= 1) {
78
+ output += lastLines(text, eolPositions, lineBuff, linePad);
79
+ }
80
+ // if no more changes
81
+ else if (diffIndex === diffs.length) {
82
+ output += firstLines(text, eolPositions, lineBuff, lineCount, linePad);
83
+ }
84
+ else {
85
+ // else the first n lines and last n lines
86
+ output += firstAndLastLines(text, eolPositions, lineBuff, lineCount, linePad);
87
+ }
88
+ lineCount += eolPositions.length;
89
+ break;
90
+ }
91
+ }
92
+ output += '\n';
93
+ console.log(output);
94
+ };
95
+ /**
96
+ * Used when there is no more changes left
97
+ */
98
+ const firstLines = (text, eolPositions, lineBuff, lineStart, linePad) => {
99
+ const lines = text.slice(0, eolPositions[lineBuff]);
100
+ return addLineNumbers(lines, lineStart, linePad);
101
+ };
102
+ /**
103
+ * Used before the first change
104
+ */
105
+ const lastLines = (text, eolPositions, lineBuff, linePad) => {
106
+ const eolFrom = eolPositions.length - (lineBuff + 1);
107
+ let lines = text;
108
+ let lineCount = 1;
109
+ if (eolFrom >= 0) {
110
+ lines = eolFrom >= 0 ? text.slice(eolPositions[eolFrom] + 1) : text;
111
+ lineCount = eolFrom + 2;
112
+ }
113
+ const firstLineNumber = lineCount.toString().padStart(linePad) + ' ';
114
+ return firstLineNumber + addLineNumbers(lines, lineCount, linePad);
115
+ };
116
+ /**
117
+ * Used between changes to show the lines after the last change and before the next change.
118
+ * @param text
119
+ * @param eolPositions
120
+ * @param lineBuff
121
+ * @param lineStart
122
+ * @param linePad
123
+ */
124
+ const firstAndLastLines = (text, eolPositions, lineBuff, lineStart, linePad) => {
125
+ if (eolPositions.length <= 2 * lineBuff) {
126
+ return addLineNumbers(text, lineStart, linePad);
127
+ }
128
+ const endFirstLines = eolPositions[lineBuff];
129
+ const eolFrom = eolPositions.length - (lineBuff + 1);
130
+ const startLastLines = eolPositions[eolFrom];
131
+ if (startLastLines <= endFirstLines) {
132
+ return addLineNumbers(text, lineStart, linePad);
133
+ }
134
+ // Lines after the previous change
135
+ let lines = text.slice(0, endFirstLines);
136
+ let output = addLineNumbers(lines, lineStart, linePad);
137
+ output += SkippedLinesMarker;
138
+ // Lines before the next change
139
+ lines = text.slice(startLastLines);
140
+ const lineCount = lineStart + eolFrom;
141
+ output += addLineNumbers(lines, lineCount, linePad);
142
+ return output;
143
+ };
144
+ /**
145
+ * Gets the positions of the end of lines in the string
146
+ * @param text
147
+ */
148
+ const findEOLPositions = (text) => {
149
+ const eolPositions = [];
150
+ text.split('').forEach((c, i) => {
151
+ if (c === '\n') {
152
+ eolPositions.push(i);
153
+ }
154
+ });
155
+ return eolPositions;
156
+ };
157
+ /**
158
+ * Counts the number of carriage returns in a string
159
+ * @param text
160
+ */
161
+ const countLines = (text) => (text.match(/\n/g) || '').length;
162
+ /**
163
+ * Adds left padded line numbers to each line.
164
+ * @param text with the lines of code
165
+ * @param lineStart the line number of the first line in the text. If zero, then no lines numbers are added.
166
+ * @param linePad the width of the largest number which may not be in the text
167
+ */
168
+ const addLineNumbers = (text, lineStart, linePad) => {
169
+ let lineCount = lineStart;
170
+ let textWithLineNumbers = '';
171
+ text.split('').forEach((c, i) => {
172
+ if (c === '\n') {
173
+ if (lineStart > 0) {
174
+ textWithLineNumbers += `\n${(++lineCount)
175
+ .toString()
176
+ .padStart(linePad)} `;
177
+ }
178
+ else {
179
+ textWithLineNumbers += `\n${' '.repeat(linePad)} `;
180
+ }
181
+ }
182
+ else {
183
+ textWithLineNumbers += c;
184
+ }
185
+ });
186
+ return textWithLineNumbers;
187
+ };
188
+ //# sourceMappingURL=diff.js.map
@@ -1,8 +1,31 @@
1
1
  import { WeightedDiGraph } from 'js-graph-algorithms';
2
2
  import { UmlClass } from './umlClass';
3
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
+ */
4
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
+ */
5
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
+ */
6
29
  export declare const classesConnectedToBaseContract: (umlClasses: UmlClass[], baseContractName: string, weightedDirectedGraph: WeightedDiGraph, depth?: number) => {
7
30
  [contractName: string]: UmlClass;
8
31
  };
@@ -4,6 +4,12 @@ exports.topologicalSortClasses = exports.classesConnectedToBaseContract = export
4
4
  const js_graph_algorithms_1 = require("js-graph-algorithms");
5
5
  const umlClass_1 = require("./umlClass");
6
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
+ */
7
13
  const filterHiddenClasses = (umlClasses, options) => {
8
14
  return umlClasses.filter((u) => (u.stereotype === umlClass_1.ClassStereotype.Enum && !options.hideEnums) ||
9
15
  (u.stereotype === umlClass_1.ClassStereotype.Struct && !options.hideStructs) ||
@@ -19,6 +25,14 @@ const filterHiddenClasses = (umlClasses, options) => {
19
25
  u.stereotype === umlClass_1.ClassStereotype.Contract);
20
26
  };
21
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
+ */
22
36
  const classesConnectedToBaseContracts = (umlClasses, baseContractNames, depth) => {
23
37
  let filteredUmlClasses = {};
24
38
  const weightedDirectedGraph = loadWeightedDirectedGraph(umlClasses);
@@ -31,6 +45,15 @@ const classesConnectedToBaseContracts = (umlClasses, baseContractNames, depth) =
31
45
  return Object.values(filteredUmlClasses);
32
46
  };
33
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
+ */
34
57
  const classesConnectedToBaseContract = (umlClasses, baseContractName, weightedDirectedGraph, depth = 1000) => {
35
58
  // Find the base UML Class from the base contract name
36
59
  const baseUmlClass = umlClasses.find(({ name }) => {
@@ -76,11 +99,12 @@ const topologicalSortClasses = (umlClasses) => {
76
99
  const sortedUmlClasses = sortedUmlClassIds.map((umlClassId) =>
77
100
  // Lookup the UmlClass for each class id
78
101
  umlClasses.find((umlClass) => umlClass.id === umlClassId));
79
- return sortedUmlClasses;
102
+ // Filter out any unfound classes. This happens when diff sources the second contract.
103
+ return sortedUmlClasses.filter((umlClass) => umlClass !== undefined);
80
104
  };
81
105
  exports.topologicalSortClasses = topologicalSortClasses;
82
106
  const loadDirectedAcyclicGraph = (umlClasses) => {
83
- const directedAcyclicGraph = new js_graph_algorithms_1.DiGraph(umlClasses.length); // the number vertices in the graph
107
+ const directedAcyclicGraph = new js_graph_algorithms_1.DiGraph(umlClass_1.UmlClass.idCounter); // the number vertices in the graph
84
108
  for (const sourceUmlClass of umlClasses) {
85
109
  for (const association of Object.values(sourceUmlClass.associations)) {
86
110
  // Find the first UML Class that matches the target class name
@@ -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
- declare type Network = typeof networks[number];
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 {};
@@ -147,7 +147,9 @@ class EtherscanParser {
147
147
  // find any files that didn't have dependencies found
148
148
  const nonDependentFiles = files.filter((f) => !dependentFilenames.includes(f.filename));
149
149
  const nonDependentFilenames = nonDependentFiles.map((f) => f.filename);
150
- debug(`Failed to find dependencies to files: ${nonDependentFilenames}`);
150
+ if (nonDependentFilenames.length) {
151
+ debug(`Failed to find dependencies to files: ${nonDependentFilenames}`);
152
+ }
151
153
  const solidityVersion = (0, regEx_1.parseSolidityVersion)(compilerVersion);
152
154
  let solidityCode = `pragma solidity =${solidityVersion};\n`;
153
155
  // output non dependent code before the dependent files just in case sol2uml missed some dependencies
@@ -1,5 +1,17 @@
1
+ import { Network } from './parserEtherscan';
1
2
  import { UmlClass } from './umlClass';
2
- export declare const parserUmlClasses: (fileFolderAddress: string, options: any) => Promise<{
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
  }>;
@@ -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
@@ -12,6 +12,8 @@ const regEx_1 = require("./utils/regEx");
12
12
  const writerFiles_1 = require("./writerFiles");
13
13
  const path_1 = require("path");
14
14
  const squashClasses_1 = require("./squashClasses");
15
+ const diff_1 = require("./diff");
16
+ const clc = require('cli-color');
15
17
  const program = new commander_1.Command();
16
18
  const version = (0, path_1.basename)(__dirname) === 'lib'
17
19
  ? require('../package.json').version // used when run from compile js in /lib
@@ -25,7 +27,8 @@ program
25
27
  sol2uml comes with three subcommands:
26
28
  * class: Generates a UML class diagram from Solidity source code. (default)
27
29
  * storage: Generates a diagram of a contract's storage slots.
28
- * flatten: Merges verified source files from a Blockchain explorer into one local file.
30
+ * flatten: Merges verified Solidity files from a Blockchain explorer into one local file.
31
+ * diff: Compares the flattened Solidity code from a Blockchain explorer for two contracts.
29
32
 
30
33
  The Solidity code can be pulled from verified source code on Blockchain explorers like Etherscan or from local Solidity files.`)
31
34
  .addOption(new commander_1.Option('-sf, --subfolders <value>', 'number of subfolders that will be recursively searched for Solidity files.').default('-1', 'all'))
@@ -43,7 +46,7 @@ The Solidity code can be pulled from verified source code on Blockchain explorer
43
46
  program
44
47
  .command('class', { isDefault: true })
45
48
  .description('Generates a UML class diagram from Solidity source code.')
46
- .usage(`<fileFolderAddress> [options]
49
+ .usage(`sol2uml [options] <fileFolderAddress>
47
50
 
48
51
  Generates UML diagrams from Solidity source code.
49
52
 
@@ -78,6 +81,7 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
78
81
  ...command.parent._optionValues,
79
82
  ...options,
80
83
  };
84
+ // Parse Solidity code from local file system or verified source code on Etherscan.
81
85
  let { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);
82
86
  if (options.squash &&
83
87
  // Must specify base contract(s) or parse from Etherscan to get contractName
@@ -96,7 +100,9 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
96
100
  if (options.squash) {
97
101
  filteredUmlClasses = (0, squashClasses_1.squashUmlClasses)(filteredUmlClasses, baseContractNames || [contractName]);
98
102
  }
103
+ // Convert UML classes to Graphviz dot format.
99
104
  const dotString = (0, converterClasses2Dot_1.convertUmlClasses2Dot)(filteredUmlClasses, combinedOptions.clusterFolders, combinedOptions);
105
+ // Convert Graphviz dot format to file formats. eg svg or png
100
106
  await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName || 'classDiagram', combinedOptions.outputFormat, combinedOptions.outputFileName);
101
107
  debug(`Finished generating UML`);
102
108
  }
@@ -108,7 +114,7 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
108
114
  program
109
115
  .command('storage')
110
116
  .description("Visually display a contract's storage slots.")
111
- .usage(`<fileFolderAddress> [options]
117
+ .usage(`[options] <fileFolderAddress>
112
118
 
113
119
  WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.`)
114
120
  .argument('<fileFolderAddress>', 'file name, base folder or contract address')
@@ -167,7 +173,7 @@ WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A k
167
173
  program
168
174
  .command('flatten')
169
175
  .description('Merges verified source files for a contract from a Blockchain explorer into one local file.')
170
- .usage(`<contractAddress> [options]
176
+ .usage(`<contractAddress>
171
177
 
172
178
  In order for the merged code to compile, the following is done:
173
179
  1. pragma solidity is set using the compiler of the verified contract.
@@ -194,6 +200,48 @@ In order for the merged code to compile, the following is done:
194
200
  process.exit(2);
195
201
  }
196
202
  });
203
+ program
204
+ .command('diff')
205
+ .description('Compare verified Solidity code differences between two contracts.')
206
+ .usage(`[options] <addressA> <addressB>
207
+
208
+ The results show the comparision of contract A to B.
209
+ The ${clc.green('green')} sections are additions to contract B that are not in contract A.
210
+ The ${clc.red('red')} sections are removals from contract A that are not in contract B.
211
+ The line numbers are from contract B. There are no line numbers for the red sections as they are not in contract B.`)
212
+ .argument('<addressA>', 'Contract address in hexadecimal format with a 0x prefix.')
213
+ .argument('<addressB>', 'Contract address in hexadecimal format with a 0x prefix.')
214
+ .addOption(new commander_1.Option('-l, --lineBuffer <value>', 'Minimum number a lines before and after changes').default('4'))
215
+ .option('-s, --saveFiles', 'Save the flattened contract code to the filesystem. The file names will be the contract address with a .sol extension.', false)
216
+ .action(async (addressA, addressB, options, command) => {
217
+ try {
218
+ debug(`About to diff ${addressA} and ${addressB}`);
219
+ const combinedOptions = {
220
+ ...command.parent._optionValues,
221
+ ...options,
222
+ };
223
+ // Diff solidity code
224
+ const lineBuffer = parseInt(options.lineBuffer);
225
+ if (isNaN(lineBuffer))
226
+ throw Error(`Invalid line buffer "${options.lineBuffer}". Must be a number`);
227
+ const etherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.apiKey, combinedOptions.network);
228
+ // Get verified Solidity code from Etherscan and flatten
229
+ const { solidityCode: codeA, contractName: contractNameA } = await etherscanParser.getSolidityCode(addressA);
230
+ const { solidityCode: codeB, contractName: contractNameB } = await etherscanParser.getSolidityCode(addressB);
231
+ console.log(`Difference between`);
232
+ console.log(`A. ${addressA} ${contractNameA} on ${combinedOptions.network}`);
233
+ console.log(`B. ${addressB} ${contractNameB} on ${combinedOptions.network}\n`);
234
+ (0, diff_1.diffCode)(codeA, codeB, lineBuffer);
235
+ if (options.saveFiles) {
236
+ await (0, writerFiles_1.writeSolidity)(codeA, addressA);
237
+ await (0, writerFiles_1.writeSolidity)(codeB, addressB);
238
+ }
239
+ }
240
+ catch (err) {
241
+ console.error(err);
242
+ process.exit(2);
243
+ }
244
+ });
197
245
  program.on('option:verbose', () => {
198
246
  debugControl.enable('sol2uml,axios');
199
247
  debug('verbose on');
@@ -1,2 +1,8 @@
1
1
  import { UmlClass } from './umlClass';
2
- export declare const squashUmlClasses: (umlClasses: UmlClass[], squashContractNames: string[]) => 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[];
@@ -27,15 +27,21 @@ exports.squashUmlClasses = void 0;
27
27
  const umlClass_1 = require("./umlClass");
28
28
  const crypto = __importStar(require("crypto"));
29
29
  const debug = require('debug')('sol2uml');
30
- const squashUmlClasses = (umlClasses, squashContractNames) => {
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) => {
31
37
  let removedClassIds = [];
32
- for (const squashContractName of squashContractNames) {
38
+ for (const baseContractName of baseContractNames) {
33
39
  // Find the base UML Class to squash
34
40
  let baseIndex = umlClasses.findIndex(({ name }) => {
35
- return name === squashContractName;
41
+ return name === baseContractName;
36
42
  });
37
43
  if (baseIndex === undefined) {
38
- throw Error(`Failed to find contract with name "${squashContractName}" to squash`);
44
+ throw Error(`Failed to find contract with name "${baseContractName}" to squash`);
39
45
  }
40
46
  const baseClass = umlClasses[baseIndex];
41
47
  let squashedClass = new umlClass_1.UmlClass({
@@ -55,7 +61,7 @@ const squashUmlClasses = (umlClasses, squashContractNames) => {
55
61
  // remove any squashed inherited contracts
56
62
  !removedClassIds.includes(u.id) ||
57
63
  // Include all base contracts
58
- squashContractNames.includes(u.name));
64
+ baseContractNames.includes(u.name));
59
65
  };
60
66
  exports.squashUmlClasses = squashUmlClasses;
61
67
  const recursiveSquash = (squashedClass, inheritedContractNames, baseClass, umlClasses, startPosition) => {
package/lib/umlClass.d.ts CHANGED
@@ -54,7 +54,7 @@ export interface Operator extends Attribute {
54
54
  stereotype?: OperatorStereotype;
55
55
  parameters?: Parameter[];
56
56
  returnParameters?: Parameter[];
57
- isPayable?: boolean;
57
+ stateMutability?: string;
58
58
  modifiers?: string[];
59
59
  hash?: string;
60
60
  inheritancePosition?: number;
File without changes
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=diff.js.map
@@ -1,4 +1,4 @@
1
- export declare type OutputFormats = 'svg' | 'png' | 'dot' | 'all';
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.0",
3
+ "version": "2.4.0",
4
4
  "description": "Solidity contract visualisation tool.",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -20,25 +20,28 @@
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
22
  "@aduh95/viz.js": "^3.7.0",
23
- "@solidity-parser/parser": "^0.14.3",
24
- "axios": "^0.27.2",
25
- "axios-debug-log": "^0.8.4",
23
+ "@solidity-parser/parser": "^0.14.5",
24
+ "axios": "^1.2.1",
25
+ "axios-debug-log": "^1.0.0",
26
+ "cli-color": "^2.0.3",
26
27
  "commander": "^9.4.1",
27
28
  "convert-svg-to-png": "^0.6.4",
28
29
  "debug": "^4.3.4",
29
- "ethers": "^5.7.1",
30
+ "diff-match-patch": "^1.0.5",
31
+ "ethers": "^5.7.2",
30
32
  "js-graph-algorithms": "^1.0.18",
31
33
  "klaw": "^4.0.1"
32
34
  },
33
35
  "devDependencies": {
34
- "@openzeppelin/contracts": "4.7.3",
35
- "@types/jest": "^29.1.1",
36
+ "@openzeppelin/contracts": "4.8.0",
37
+ "@types/diff-match-patch": "^1.0.32",
38
+ "@types/jest": "^29.2.4",
36
39
  "@types/klaw": "^3.0.3",
37
- "jest": "^29.1.2",
38
- "prettier": "^2.7.1",
40
+ "jest": "^29.3.1",
41
+ "prettier": "^2.8.1",
39
42
  "ts-jest": "^29.0.3",
40
43
  "ts-node": "^10.9.1",
41
- "typescript": "^4.8.4"
44
+ "typescript": "^4.9.4"
42
45
  },
43
46
  "files": [
44
47
  "lib/*.js",