-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
114 lines (103 loc) · 3.32 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
const fs = require('fs');
const path = require('path');
const commander = require('commander');
const parser = require('@solidity-parser/parser');
commander
.createCommand('solidity-graph-puml')
.description('Generates PlantUML class diagrams from Solidity source code')
.option(
'-i, --input-path <path>',
'Path to the folder containing .sol files',
'./contracts'
)
.option(
'-o, --output-file <file>',
'Name of the output .puml file (default: diagram.puml)',
'diagram.puml'
)
.action((opts) => {
const inputPath = opts.inputPath;
const outputFile = opts.outputFile;
generatePlantUMLMetadata(inputPath)
.then(async (metadata) => {
await fs.promises.writeFile(outputFile, metadata);
})
.catch((error) => {
console.error('Error generating PlantUML diagram:', error);
});
})
.parse(process.argv);
async function generatePlantUMLMetadata(inputPath) {
const pumlMetadata = [];
// Recursively read .sol files
const readFiles = async (dirPath) => {
const files = await fs.promises.readdir(dirPath);
for (const file of files) {
const filePath = path.join(dirPath, file);
const stats = await fs.promises.stat(filePath);
if (stats.isDirectory()) {
await readFiles(filePath);
} else if (file.endsWith('.sol')) {
try {
const parsedAST = await parser.parse(
fs.readFileSync(filePath, 'utf-8')
);
const classMetadata = extractClassMetadata(parsedAST);
pumlMetadata.push(classMetadata);
} catch (error) {
console.error(`Error parsing ${filePath}:`, error);
}
}
}
};
await readFiles(inputPath);
return `@startuml
${pumlMetadata.join('\n')}
@enduml`;
}
function extractClassMetadata(parsedAST) {
// Filter out the ContractDefinition
const contractDefinitions = parsedAST.children.filter(
(child) => child.type === 'ContractDefinition'
);
// map contract imports
const imports = parsedAST.children
.filter((child) => child.type === 'ImportDirective')
.map((importDirective) => {
return `import ${importDirective.path}`;
});
// Map the contract definitions to PlantUML class format
const classes = contractDefinitions.map((contract) => {
const className = contract.name;
const classMembers = contract.subNodes
.filter((node) => node.type === 'StateVariableDeclaration')
.map(
(variable) =>
`${variable.variables[0].typeName.name} ${variable.variables[0].name}`
);
const classMethods = contract.subNodes
.filter((node) => node.type === 'FunctionDefinition')
.map(
(func) =>
`${func.name === null ? 'constructor' : func.name}(${func.parameters
.map((param) => param.typeName.name)
.join(', ')}) : ${
func.returnParameters
? func.returnParameters
.map((param) => param.typeName.name)
.join(', ')
: 'void'
}`
);
return `class ${className} {
${imports.join('\n')}
${classMembers.join('\n')}
${classMethods.join('\n')}
}`;
});
// Join the classes into a single PlantUML diagram
// const puml = `@startuml
// ${classes.join('\n')}
// @enduml`;
return classes.join('\n');
}