Skip to content

Commit a43ae46

Browse files
committedJul 24, 2022
fix: class linkages with explicit imports and aliases
1 parent 58600ae commit a43ae46

21 files changed

+195
-72
lines changed
 

‎.github/workflows/nodejs.yml

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ jobs:
88
matrix:
99
platform: [ubuntu-latest, macos-latest]
1010
node-version: [14.x, 16.x, 18.x]
11-
fail-fast: false
1211
runs-on: ${{ matrix.platform }}
1312
steps:
1413
- uses: actions/checkout@v1

‎lib/associations.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { Association, UmlClass } from './umlClass';
2+
export declare const findAssociatedClass: (association: Association, sourceUmlClass: UmlClass, umlClasses: UmlClass[]) => UmlClass;

‎lib/associations.js

+31
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/converterAST2Classes.js

+24-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/converterClasses2Dot.js

+2-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/filterClasses.js

+2-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/parserEtherscan.js

-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/umlClass.d.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ export declare enum AttributeType {
2929
Array = 3,
3030
Mapping = 4
3131
}
32+
export interface Import {
33+
absolutePath: string;
34+
classNames: {
35+
className: string;
36+
alias?: string;
37+
}[];
38+
}
3239
export interface Attribute {
3340
visibility?: Visibility;
3441
name: string;
@@ -76,7 +83,7 @@ export declare class UmlClass implements ClassProperties {
7683
name: string;
7784
absolutePath: string;
7885
relativePath: string;
79-
importedPaths?: string[];
86+
imports?: Import[];
8087
stereotype?: ClassStereotype;
8188
attributes: Attribute[];
8289
operators: Operator[];

‎lib/umlClass.js

+6-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/contracts/inheritance/common.sol

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// SPDX-License-Identifier: AGPL-3.0-or-later
22
pragma solidity ^0.8.15;
33

4-
import {A as parentA} from "./parent/A.sol";
4+
import { A as parentA, B as parentB, C2 } from "./parent/A.sol";
5+
import "./parent/E.sol";
6+
import { F } from "./parent/F.sol";
57

68
contract A {
79
bool public flag;
@@ -10,13 +12,12 @@ contract A {
1012

1113
contract B is A {
1214
bool public flagB;
13-
1415
}
1516

1617
contract C is A {
1718
bool public flagC;
1819
}
1920

20-
contract D is B, C {
21+
contract D is B, C, parentA, parentB, C2, E, F {
2122
bool public flagD;
2223
}

‎src/contracts/inheritance/parent/A.sol

+12
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,15 @@ pragma solidity ^0.8.15;
44
contract A {
55
uint256 private _count;
66
}
7+
8+
contract B {
9+
uint256 private _total;
10+
}
11+
12+
contract C2 {
13+
uint256 public max;
14+
}
15+
16+
contract D is A {
17+
uint256 public notIncluded;
18+
}
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
pragma solidity ^0.8.15;
3+
4+
contract E {
5+
address internal owner;
6+
}
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
pragma solidity ^0.8.15;
3+
4+
contract F {
5+
uint8 public index;
6+
}

‎src/ts/__tests__/fileParser.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ describe('Parser', () => {
66
const files = await getSolidityFilesFromFolderOrFile(
77
'./src/contracts'
88
)
9-
expect(files).toHaveLength(22)
9+
expect(files).toHaveLength(24)
1010
})
1111

1212
test('get Solidity files from folder with no sol files', async () => {
@@ -23,7 +23,7 @@ describe('Parser', () => {
2323

2424
test('get Solidity files including Open Zeppelin', async () => {
2525
const files = await getSolidityFilesFromFolderOrFile('.')
26-
expect(files).toHaveLength(107)
26+
expect(files).toHaveLength(109)
2727
}, 10000)
2828

2929
describe('Failures', () => {

‎src/ts/associations.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Association, UmlClass } from './umlClass'
2+
3+
// Find the UML class linked to the association
4+
export const findAssociatedClass = (
5+
association: Association,
6+
sourceUmlClass: UmlClass,
7+
umlClasses: UmlClass[]
8+
) => {
9+
return umlClasses.find((targetUmlClass) => {
10+
return (
11+
// class is in the same source file
12+
(association.targetUmlClassName === targetUmlClass.name &&
13+
sourceUmlClass.absolutePath === targetUmlClass.absolutePath) ||
14+
// imported classes with no explicit import names
15+
(association.targetUmlClassName === targetUmlClass.name &&
16+
sourceUmlClass.imports.find(
17+
(i) =>
18+
i.absolutePath === targetUmlClass.absolutePath &&
19+
i.classNames.length === 0
20+
)) ||
21+
// imported classes with explicit import names or import aliases
22+
sourceUmlClass.imports.find(
23+
(i) =>
24+
i.absolutePath === targetUmlClass.absolutePath &&
25+
i.classNames.find(
26+
(importedClass) =>
27+
// no import alias
28+
(association.targetUmlClassName ===
29+
importedClass.className &&
30+
importedClass.className ===
31+
targetUmlClass.name &&
32+
importedClass.alias == undefined) ||
33+
// import alias
34+
(association.targetUmlClassName ===
35+
importedClass.alias &&
36+
importedClass.className === targetUmlClass.name)
37+
)
38+
)
39+
)
40+
})
41+
}

‎src/ts/converterAST2Classes.ts

+25-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import * as path from 'path'
1717
import {
1818
AttributeType,
1919
ClassStereotype,
20+
Import,
2021
OperatorStereotype,
2122
Parameter,
2223
ReferenceType,
@@ -42,7 +43,7 @@ export function convertAST2UmlClasses(
4243
relativePath: string,
4344
filesystem: boolean = false
4445
): UmlClass[] {
45-
const importedPaths: string[] = []
46+
const imports: Import[] = []
4647
umlClasses = []
4748

4849
if (node.type === 'SourceUnit') {
@@ -99,7 +100,17 @@ export function convertAST2UmlClasses(
99100
const importPath = require.resolve(childNode.path, {
100101
paths: [codeFolder],
101102
})
102-
importedPaths.push(importPath)
103+
imports.push({
104+
absolutePath: importPath,
105+
classNames: childNode.symbolAliases
106+
? childNode.symbolAliases.map((alias) => {
107+
return {
108+
className: alias[0],
109+
alias: alias[1],
110+
}
111+
})
112+
: [],
113+
})
103114
} catch (err) {
104115
debug(
105116
`Failed to resolve import ${childNode.path} from file ${relativePath}`
@@ -108,7 +119,17 @@ export function convertAST2UmlClasses(
108119
} else {
109120
// this has come from Etherscan
110121
const importPath = path.join(codeFolder, childNode.path)
111-
importedPaths.push(importPath)
122+
imports.push({
123+
absolutePath: importPath,
124+
classNames: childNode.symbolAliases
125+
? childNode.symbolAliases.map((alias) => {
126+
return {
127+
className: alias[0],
128+
alias: alias[1],
129+
}
130+
})
131+
: [],
132+
})
112133
}
113134
}
114135
})
@@ -117,7 +138,7 @@ export function convertAST2UmlClasses(
117138
}
118139

119140
umlClasses.forEach((umlClass) => {
120-
umlClass.importedPaths = importedPaths
141+
umlClass.imports = imports
121142
})
122143

123144
return umlClasses

‎src/ts/converterClasses2Dot.ts

+6-13
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
ReferenceType,
77
UmlClass,
88
} from './umlClass'
9+
import { findAssociatedClass } from './associations'
910

1011
const debug = require('debug')('sol2uml')
1112

@@ -102,19 +103,11 @@ export function addAssociationsToDot(
102103

103104
// for each association in that class
104105
for (const association of Object.values(sourceUmlClass.associations)) {
105-
// find the target class with the same class name and
106-
// codePath of the target in the importedPaths of the source class OR
107-
// the codePath of the target is the same as the codePath pf the source class
108-
const targetUmlClass = umlClasses.find((targetUmlClass) => {
109-
return (
110-
targetUmlClass.name === association.targetUmlClassName &&
111-
(sourceUmlClass.importedPaths.includes(
112-
targetUmlClass.absolutePath
113-
) ||
114-
sourceUmlClass.absolutePath ===
115-
targetUmlClass.absolutePath)
116-
)
117-
})
106+
const targetUmlClass = findAssociatedClass(
107+
association,
108+
sourceUmlClass,
109+
umlClasses
110+
)
118111
if (targetUmlClass) {
119112
dotString += addAssociationToDot(
120113
sourceUmlClass,

‎src/ts/filterClasses.ts

+6-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Dijkstra, Edge, WeightedDiGraph } from 'js-graph-algorithms'
22
import { UmlClass } from './umlClass'
3+
import { findAssociatedClass } from './associations'
34

45
export const classesConnectedToBaseContracts = (
56
umlClasses: UmlClass[],
@@ -61,16 +62,11 @@ function loadGraph(umlClasses: UmlClass[]): WeightedDiGraph {
6162
for (const sourceUmlClass of umlClasses) {
6263
for (const association of Object.values(sourceUmlClass.associations)) {
6364
// Find the first UML Class that matches the target class name
64-
const targetUmlClass = umlClasses.find((targetUmlClass) => {
65-
return (
66-
targetUmlClass.name === association.targetUmlClassName &&
67-
(sourceUmlClass.importedPaths.includes(
68-
targetUmlClass.absolutePath
69-
) ||
70-
sourceUmlClass.absolutePath ===
71-
targetUmlClass.absolutePath)
72-
)
73-
})
65+
const targetUmlClass = findAssociatedClass(
66+
association,
67+
sourceUmlClass,
68+
umlClasses
69+
)
7470

7571
if (!targetUmlClass) {
7672
continue

‎src/ts/umlClass.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ export enum AttributeType {
3333
Mapping,
3434
}
3535

36+
export interface Import {
37+
absolutePath: string
38+
classNames: {
39+
className: string
40+
alias?: string
41+
}[]
42+
}
43+
3644
export interface Attribute {
3745
visibility?: Visibility
3846
name: string
@@ -87,7 +95,7 @@ export class UmlClass implements ClassProperties {
8795
name: string
8896
absolutePath: string
8997
relativePath: string
90-
importedPaths?: string[]
98+
imports?: Import[]
9199
stereotype?: ClassStereotype
92100

93101
attributes: Attribute[] = []

‎tsconfig.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
"composite": true,
44

55
"esModuleInterop": true,
6-
"lib": [ "esnext", "dom" ],
6+
"lib": [ "ES2022" ],
77
"module": "commonjs",
8-
"target": "ES2022",
8+
"target": "ES2020",
99
"moduleResolution": "node",
1010

1111
"declaration": true,

‎version2.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@
2525

2626
# Fixes
2727

28-
* Improved linkages of classes when there are duplicate contract names.
28+
* Improved linkages of classes when there are duplicate contract names, explicit imports and import aliases.
2929
* If a contract or library contains a struct, it is marked as an aggregate association. A separate dependency line is used to link a contract to a struct. This can be a storage (solid line) or memory (dashed line) dependency.
3030
* Structs in libraries are now linked to their dependent contract.

0 commit comments

Comments
 (0)
Please sign in to comment.