sol2uml 2.1.4 → 2.1.7

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