ts2famix 1.0.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/.eslintrc.json +24 -0
- package/.github/workflows/node.js.yml +60 -0
- package/LICENSE +23 -0
- package/README.md +111 -0
- package/doc-metamodel/skins.include.puml +2 -0
- package/jest.config.ts +199 -0
- package/package.json +47 -0
- package/src/analyze.ts +90 -0
- package/src/analyze_functions/processAccesses.ts +50 -0
- package/src/analyze_functions/processFiles.ts +656 -0
- package/src/analyze_functions/processImportClauses.ts +77 -0
- package/src/analyze_functions/processInheritances.ts +84 -0
- package/src/analyze_functions/processInvocations.ts +51 -0
- package/src/famix2puml.ts +119 -0
- package/src/famix_functions/famix_functions.ts +552 -0
- package/src/famix_functions/famix_functions_associations.ts +208 -0
- package/src/famix_functions/famix_functions_index.ts +44 -0
- package/src/famix_functions/famix_functions_types.ts +100 -0
- package/src/fqn.ts +127 -0
- package/src/fqp_implementation.ts +66 -0
- package/src/generate_uml.sh +16 -0
- package/src/lib/famix/License.md +23 -0
- package/src/lib/famix/package-lock.json +301 -0
- package/src/lib/famix/package.json +28 -0
- package/src/lib/famix/readme.md +5 -0
- package/src/lib/famix/src/famix_JSON_exporter.ts +56 -0
- package/src/lib/famix/src/famix_base_element.ts +18 -0
- package/src/lib/famix/src/famix_repository.ts +199 -0
- package/src/lib/famix/src/index.ts +8 -0
- package/src/lib/famix/src/model/famix/access.ts +53 -0
- package/src/lib/famix/src/model/famix/accessor.ts +15 -0
- package/src/lib/famix/src/model/famix/alias.ts +41 -0
- package/src/lib/famix/src/model/famix/association.ts +44 -0
- package/src/lib/famix/src/model/famix/behavioral_entity.ts +107 -0
- package/src/lib/famix/src/model/famix/c_source_language.ts +15 -0
- package/src/lib/famix/src/model/famix/class.ts +86 -0
- package/src/lib/famix/src/model/famix/comment.ts +50 -0
- package/src/lib/famix/src/model/famix/container_entity.ts +165 -0
- package/src/lib/famix/src/model/famix/custom_source_language.ts +27 -0
- package/src/lib/famix/src/model/famix/decorator.ts +39 -0
- package/src/lib/famix/src/model/famix/entity.ts +15 -0
- package/src/lib/famix/src/model/famix/enum.ts +31 -0
- package/src/lib/famix/src/model/famix/enum_value.ts +29 -0
- package/src/lib/famix/src/model/famix/function.ts +15 -0
- package/src/lib/famix/src/model/famix/implicit_variable.ts +15 -0
- package/src/lib/famix/src/model/famix/import_clause.ts +53 -0
- package/src/lib/famix/src/model/famix/index.ts +42 -0
- package/src/lib/famix/src/model/famix/indexed_file_anchor.ts +49 -0
- package/src/lib/famix/src/model/famix/inheritance.ts +42 -0
- package/src/lib/famix/src/model/famix/interface.ts +75 -0
- package/src/lib/famix/src/model/famix/invocation.ts +68 -0
- package/src/lib/famix/src/model/famix/method.ts +96 -0
- package/src/lib/famix/src/model/famix/module.ts +31 -0
- package/src/lib/famix/src/model/famix/named_entity.ts +98 -0
- package/src/lib/famix/src/model/famix/namespace.ts +28 -0
- package/src/lib/famix/src/model/famix/parameter.ts +29 -0
- package/src/lib/famix/src/model/famix/parameterizable_class.ts +31 -0
- package/src/lib/famix/src/model/famix/parameterizable_interface.ts +31 -0
- package/src/lib/famix/src/model/famix/parameterized_type.ts +40 -0
- package/src/lib/famix/src/model/famix/primitive_type.ts +15 -0
- package/src/lib/famix/src/model/famix/property.ts +54 -0
- package/src/lib/famix/src/model/famix/reference.ts +42 -0
- package/src/lib/famix/src/model/famix/scoping_entity.ts +31 -0
- package/src/lib/famix/src/model/famix/script_entity.ts +38 -0
- package/src/lib/famix/src/model/famix/source_anchor.ts +31 -0
- package/src/lib/famix/src/model/famix/source_language.ts +31 -0
- package/src/lib/famix/src/model/famix/sourced_entity.ts +70 -0
- package/src/lib/famix/src/model/famix/structural_entity.ts +44 -0
- package/src/lib/famix/src/model/famix/text_anchor.ts +49 -0
- package/src/lib/famix/src/model/famix/type.ts +88 -0
- package/src/lib/famix/src/model/famix/type_parameter.ts +33 -0
- package/src/lib/famix/src/model/famix/variable.ts +28 -0
- package/src/lib/famix/tsconfig.json +27 -0
- package/src/lib/famix/tslint.json +15 -0
- package/src/lib/ts-complex/cyclomatic-service.ts +85 -0
- package/src/ts2famix-cli.ts +26 -0
- package/src/ts2famix-tsconfig.ts +30 -0
- package/test/abstractClassWithComments.test.ts +58 -0
- package/test/abstracts.test.ts +53 -0
- package/test/access.test.ts +62 -0
- package/test/accesses.test.ts +42 -0
- package/test/accessorsWithDecorators.test.ts +98 -0
- package/test/alias.test.ts +39 -0
- package/test/classExtendsUndefinedClass.test.ts +41 -0
- package/test/classImplementsUndefinedInterface.test.ts +45 -0
- package/test/classWithDecorators.test.ts +65 -0
- package/test/entities.test.ts +232 -0
- package/test/entities_json.test.ts +48 -0
- package/test/enum.test.ts +55 -0
- package/test/functionReturnsFunction.test.ts +53 -0
- package/test/functionWithParameters.test.ts +38 -0
- package/test/functionWithVariables.test.ts +64 -0
- package/test/functions.test.ts +23 -0
- package/test/functionsInFunction.test.ts +40 -0
- package/test/functionsInMethod.test.ts +42 -0
- package/test/genericClass.test.ts +42 -0
- package/test/genericClassInheritsInterface.test.ts +47 -0
- package/test/genericInterface.test.ts +38 -0
- package/test/genericMethod.test.ts +65 -0
- package/test/genericWithInvocation.test.ts +71 -0
- package/test/generics.test.ts +68 -0
- package/test/inheritance.test.ts +50 -0
- package/test/interfaceInheritsInterface.test.ts +40 -0
- package/test/interfaceInheritsUndefinedInterface.test.ts +41 -0
- package/test/invocation.test.ts +94 -0
- package/test/invocationWithFunction.test.ts +42 -0
- package/test/invocationWithVariable.test.ts +46 -0
- package/test/invocation_json.test.ts +63 -0
- package/test/invocations.test.ts +131 -0
- package/test/jsDoc.test.ts +31 -0
- package/test/methodWithDecorator.test.ts +44 -0
- package/test/methods.test.ts +42 -0
- package/test/metrics.test.ts +51 -0
- package/test/module.test.ts +71 -0
- package/test/namespaces.test.ts +54 -0
- package/test/namespacesAndClasses.test.ts +66 -0
- package/test/parameterWithDecorators.test.ts +54 -0
- package/test/propertyWithDecorators.test.ts +80 -0
- package/test/sample.test.ts +13 -0
- package/test/simpleFunction.test.ts +32 -0
- package/test/simpleTest.test.ts +18 -0
- package/test/simpleTest2.test.ts +36 -0
- package/test/types.test.ts +58 -0
- package/test_src/sample.ts +103 -0
- package/test_src/sampleForModule.ts +10 -0
- package/test_src/sampleForModule2.ts +7 -0
- package/test_src/sampleForModule3.ts +2 -0
- package/tsconfig.json +70 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ImportDeclaration, SourceFile, ExportedDeclarations } from "ts-morph";
|
|
2
|
+
import { FamixFunctions } from "../famix_functions/famix_functions";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This class is used to build a Famix model for the import clauses
|
|
6
|
+
*/
|
|
7
|
+
export class ProcessImportClauses {
|
|
8
|
+
|
|
9
|
+
private famixFunctions: FamixFunctions; // FamixFunctions object, it contains all the functions needed to create Famix entities
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Initializes the ProcessImportClauses object
|
|
13
|
+
* @param famixFunctions FamixFunctions object, it contains all the functions needed to create Famix entities
|
|
14
|
+
*/
|
|
15
|
+
constructor(famixFunctions: FamixFunctions) {
|
|
16
|
+
this.famixFunctions = famixFunctions;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Builds a Famix model for the import clauses of the source files which are modules
|
|
21
|
+
* @param modules An array of modules
|
|
22
|
+
* @param exports An array of maps of exported declarations
|
|
23
|
+
*/
|
|
24
|
+
public processImportClauses(modules: Array<SourceFile>, exports: Array<ReadonlyMap<string, ExportedDeclarations[]>>): void {
|
|
25
|
+
console.info(`processImportClauses: Creating import clauses:`);
|
|
26
|
+
modules.forEach(f => {
|
|
27
|
+
f.getImportDeclarations().forEach(i => {
|
|
28
|
+
const path = this.getModulePath(i);
|
|
29
|
+
|
|
30
|
+
i.getNamedImports().forEach(ni => {
|
|
31
|
+
console.info(`processImportClauses: Importing (named) ${ni.getName()} from ${i.getModuleSpecifierValue()}`);
|
|
32
|
+
const importedEntityName = ni.getName();
|
|
33
|
+
let bool = false;
|
|
34
|
+
exports.forEach(e => {
|
|
35
|
+
if (e.has(importedEntityName)) {
|
|
36
|
+
bool = true;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
this.famixFunctions.createFamixImportClause(f, i.getModuleSpecifierValue(), path, ni, bool, false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const defaultImport = i.getDefaultImport();
|
|
43
|
+
if (defaultImport !== undefined) {
|
|
44
|
+
console.info(`processImportClauses: Importing (default) ${defaultImport.getText()} from ${i.getModuleSpecifierValue()}`);
|
|
45
|
+
this.famixFunctions.createFamixImportClause(f, i.getModuleSpecifierValue(), path, defaultImport, false, true);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const namespaceImport = i.getNamespaceImport();
|
|
49
|
+
if (namespaceImport !== undefined) {
|
|
50
|
+
console.info(`processImportClauses: Importing (namespace) ${namespaceImport.getText()} from ${i.getModuleSpecifierValue()}`);
|
|
51
|
+
this.famixFunctions.createFamixImportClause(f, i.getModuleSpecifierValue(), path, namespaceImport, false, false);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Gets the path of a module to be imported
|
|
59
|
+
* @param i An import declaration
|
|
60
|
+
* @returns The path of the module to be imported
|
|
61
|
+
*/
|
|
62
|
+
private getModulePath(i: ImportDeclaration): string {
|
|
63
|
+
let path: string;
|
|
64
|
+
if (i.getModuleSpecifierSourceFile() === undefined) {
|
|
65
|
+
if (i.getModuleSpecifierValue().substring(i.getModuleSpecifierValue().length - 3) === ".ts") {
|
|
66
|
+
path = i.getModuleSpecifierValue();
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
path = i.getModuleSpecifierValue() + ".ts";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
path = i.getModuleSpecifierSourceFile().getFilePath();
|
|
74
|
+
}
|
|
75
|
+
return path;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ClassDeclaration, InterfaceDeclaration, ExpressionWithTypeArguments } from "ts-morph";
|
|
2
|
+
import { FamixFunctions } from "../famix_functions/famix_functions";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This class is used to build a Famix model for the inheritances
|
|
6
|
+
*/
|
|
7
|
+
export class ProcessInheritances {
|
|
8
|
+
|
|
9
|
+
private famixFunctions: FamixFunctions; // FamixFunctions object, it contains all the functions needed to create Famix entities
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Initializes the ProcessInheritances object
|
|
13
|
+
* @param famixFunctions FamixFunctions object, it contains all the functions needed to create Famix entities
|
|
14
|
+
*/
|
|
15
|
+
constructor(famixFunctions: FamixFunctions) {
|
|
16
|
+
this.famixFunctions = famixFunctions;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Builds a Famix model for the inheritances of the classes and interfaces of the source files
|
|
21
|
+
* @param classes An array of classes
|
|
22
|
+
* @param interfaces An array of interfaces
|
|
23
|
+
*/
|
|
24
|
+
public processInheritances(classes: Array<ClassDeclaration>, interfaces: Array<InterfaceDeclaration>): void {
|
|
25
|
+
console.info(`processInheritances: Creating inheritances:`);
|
|
26
|
+
classes.forEach(cls => {
|
|
27
|
+
console.info(`processInheritances: Checking class inheritance for ${cls.getName()}`);
|
|
28
|
+
const extClass = cls.getBaseClass();
|
|
29
|
+
if (extClass !== undefined) {
|
|
30
|
+
this.famixFunctions.createFamixInheritance(cls, extClass);
|
|
31
|
+
|
|
32
|
+
console.info(`processInheritances: class: ${cls.getName()}, (${cls.getType().getText()}), extClass: ${extClass.getName()}, (${extClass.getType().getText()})`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.info(`processInheritances: Checking interface inheritance for ${cls.getName()}`);
|
|
36
|
+
const implementedInterfaces = this.getImplementedOrExtendedInterfaces(interfaces, cls);
|
|
37
|
+
implementedInterfaces.forEach(impInter => {
|
|
38
|
+
this.famixFunctions.createFamixInheritance(cls, impInter);
|
|
39
|
+
|
|
40
|
+
console.info(`processInheritances: class: ${cls.getName()}, (${cls.getType().getText()}), impInter: ${(impInter instanceof InterfaceDeclaration) ? impInter.getName() : impInter.getExpression().getText()}, (${(impInter instanceof InterfaceDeclaration) ? impInter.getType().getText() : impInter.getExpression().getText()})`);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
interfaces.forEach(inter => {
|
|
45
|
+
console.info(`processInheritances: Checking interface inheritance for ${inter.getName()}`);
|
|
46
|
+
const extendedInterfaces = this.getImplementedOrExtendedInterfaces(interfaces, inter);
|
|
47
|
+
extendedInterfaces.forEach(extInter => {
|
|
48
|
+
this.famixFunctions.createFamixInheritance(inter, extInter);
|
|
49
|
+
|
|
50
|
+
console.info(`processInheritances: inter: ${inter.getName()}, (${inter.getType().getText()}), extInter: ${(extInter instanceof InterfaceDeclaration) ? extInter.getName() : extInter.getExpression().getText()}, (${(extInter instanceof InterfaceDeclaration) ? extInter.getType().getText() : extInter.getExpression().getText()})`);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Gets the interfaces implemented or extended by a class or an interface
|
|
57
|
+
* @param interfaces An array of interfaces
|
|
58
|
+
* @param subClass A class or an interface
|
|
59
|
+
* @returns An array of InterfaceDeclaration and ExpressionWithTypeArguments containing the interfaces implemented or extended by the subClass
|
|
60
|
+
*/
|
|
61
|
+
private getImplementedOrExtendedInterfaces(interfaces: Array<InterfaceDeclaration>, subClass: ClassDeclaration | InterfaceDeclaration): Array<InterfaceDeclaration | ExpressionWithTypeArguments> {
|
|
62
|
+
let impOrExtInterfaces: Array<ExpressionWithTypeArguments>;
|
|
63
|
+
if (subClass instanceof ClassDeclaration) {
|
|
64
|
+
impOrExtInterfaces = subClass.getImplements();
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
impOrExtInterfaces = subClass.getExtends();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const interfacesNames = interfaces.map(i => i.getName());
|
|
71
|
+
const implementedOrExtendedInterfaces = new Array<InterfaceDeclaration | ExpressionWithTypeArguments>();
|
|
72
|
+
|
|
73
|
+
impOrExtInterfaces.forEach(i => {
|
|
74
|
+
if (interfacesNames.includes(i.getExpression().getText())) {
|
|
75
|
+
implementedOrExtendedInterfaces.push(interfaces[interfacesNames.indexOf(i.getExpression().getText())]);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
implementedOrExtendedInterfaces.push(i);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return implementedOrExtendedInterfaces;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { MethodDeclaration, FunctionDeclaration, Identifier, ConstructorDeclaration, GetAccessorDeclaration, SetAccessorDeclaration, FunctionExpression } from "ts-morph";
|
|
2
|
+
import { FamixFunctions } from "../famix_functions/famix_functions";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This class is used to build a Famix model for the invocations
|
|
6
|
+
*/
|
|
7
|
+
export class ProcessInvocations {
|
|
8
|
+
|
|
9
|
+
private famixFunctions: FamixFunctions; // FamixFunctions object, it contains all the functions needed to create Famix entities
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Initializes the ProcessInvocations object
|
|
13
|
+
* @param famixFunctions FamixFunctions object, it contains all the functions needed to create Famix entities
|
|
14
|
+
*/
|
|
15
|
+
constructor(famixFunctions: FamixFunctions) {
|
|
16
|
+
this.famixFunctions = famixFunctions;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Builds a Famix model for the invocations of the methods and functions of the source files
|
|
21
|
+
* @param methodsAndFunctionsWithId A map of methods and functions with their id
|
|
22
|
+
*/
|
|
23
|
+
public processInvocations(methodsAndFunctionsWithId: Map<number, MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression>): void {
|
|
24
|
+
console.info(`Creating invocations:`);
|
|
25
|
+
methodsAndFunctionsWithId.forEach((m, id) => {
|
|
26
|
+
console.info(`Invocations to ${(m instanceof MethodDeclaration || m instanceof GetAccessorDeclaration || m instanceof SetAccessorDeclaration || m instanceof FunctionDeclaration) ? m.getName() : ((m instanceof ConstructorDeclaration) ? 'constructor' : (m.getName() ? m.getName() : 'anonymous'))}`);
|
|
27
|
+
try {
|
|
28
|
+
const temp_nodes = m.findReferencesAsNodes() as Array<Identifier>;
|
|
29
|
+
temp_nodes.forEach(node => this.processNodeForInvocations(node, m, id));
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(`> WARNING: got exception ${error}. Continuing...`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Builds a Famix model for an invocation of a method or a function
|
|
38
|
+
* @param n A node
|
|
39
|
+
* @param m A method or a function
|
|
40
|
+
* @param id The id of the method or the function
|
|
41
|
+
*/
|
|
42
|
+
private processNodeForInvocations(n: Identifier, m: MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression, id: number): void {
|
|
43
|
+
try {
|
|
44
|
+
this.famixFunctions.createFamixInvocation(n, m, id);
|
|
45
|
+
|
|
46
|
+
console.info(`node: node, (${n.getType().getText()})`);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(`> WARNING: got exception ${error}. ScopeDeclaration invalid for ${n.getSymbol().getFullyQualifiedName()}. Continuing...`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import yargs from 'yargs';
|
|
3
|
+
|
|
4
|
+
const argv = yargs
|
|
5
|
+
.example('ts-node src/famix2puml.ts -i JSONModels/projectName.json -o PUMLModels/projectName.puml', 'creates a PlantUML class diagram from a JSON-format model of a typescript project')
|
|
6
|
+
.alias('i', 'input')
|
|
7
|
+
.nargs('i', 1)
|
|
8
|
+
.alias('o', 'output')
|
|
9
|
+
.nargs('o', 1)
|
|
10
|
+
.demandOption('input').demandOption('output').parseSync();
|
|
11
|
+
|
|
12
|
+
const INHERITANCE_LINK_COLOR = 'blue';
|
|
13
|
+
|
|
14
|
+
// approximation for code completion
|
|
15
|
+
interface FamixTypeScriptElement {
|
|
16
|
+
FM3: string
|
|
17
|
+
name: string
|
|
18
|
+
id?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface Association {
|
|
22
|
+
from: string;
|
|
23
|
+
to: string;
|
|
24
|
+
name: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const jsonFileName = argv.input as string;
|
|
28
|
+
const pumlFileName = (argv.output as string).substring((argv.output as string).indexOf("/")+1, (argv.output as string).lastIndexOf('.'));
|
|
29
|
+
const parsedModel: Array<FamixTypeScriptElement> = JSON.parse(fs.readFileSync(jsonFileName, 'utf-8'));
|
|
30
|
+
const classNameMap = new Map<string, string>();
|
|
31
|
+
const associations = new Array<Association>();
|
|
32
|
+
|
|
33
|
+
// maps all class names to their id
|
|
34
|
+
parsedModel.forEach(element => {
|
|
35
|
+
// map has id as key and unique (plantuml) class name
|
|
36
|
+
classNameMap.set(element.id, uniqueElementName(element));
|
|
37
|
+
const nameWithoutPrefix = element.FM3.split('.')[1];
|
|
38
|
+
// special case association
|
|
39
|
+
if (nameWithoutPrefix.endsWith('Inheritance')) {
|
|
40
|
+
const subclass = element['subclass'].ref;
|
|
41
|
+
const superclass = element['superclass'].ref;
|
|
42
|
+
associations.push({ from: subclass, to: superclass, name: nameWithoutPrefix });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// generates plantuml
|
|
47
|
+
let plantUMLOutString = `@startuml ${pumlFileName}
|
|
48
|
+
skinparam style strictuml
|
|
49
|
+
title Object diagram for ${jsonFileName}
|
|
50
|
+
`;
|
|
51
|
+
parsedModel.forEach(element => {
|
|
52
|
+
plantUMLOutString += `${toPlantUML(element)}\n`;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// creates associations
|
|
56
|
+
associations.forEach(association => {
|
|
57
|
+
// inheritance is a special case, show it in UML even though it doesn't make 100% sense in object diagrams
|
|
58
|
+
const isInheritance = association.name.startsWith('Inheritance');
|
|
59
|
+
if (isInheritance) {
|
|
60
|
+
plantUMLOutString += `${classNameMap.get(association.from)} --|> ${classNameMap.get(association.to)} #line:${INHERITANCE_LINK_COLOR}\n`;
|
|
61
|
+
} else {
|
|
62
|
+
plantUMLOutString += `${classNameMap.get(association.from)} ..> "${association.name}" ${classNameMap.get(association.to)}\n`;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
plantUMLOutString += '@enduml';
|
|
67
|
+
|
|
68
|
+
// writes to output file
|
|
69
|
+
fs.writeFile(argv.output as string, plantUMLOutString, (err) => {
|
|
70
|
+
if (err) { throw err; }
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
function uniqueElementName(element: FamixTypeScriptElement): string {
|
|
74
|
+
return `${element.FM3}${element.id}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function toPlantUML(element: FamixTypeScriptElement) {
|
|
78
|
+
let plantUMLString = '';
|
|
79
|
+
const optionalName = element.name || '';
|
|
80
|
+
const nameWithoutPrefix = element.FM3.split('.')[1];
|
|
81
|
+
plantUMLString += `object "${optionalName}:${nameWithoutPrefix}" as ${uniqueElementName(element)} {\n`;
|
|
82
|
+
plantUMLString += `id = ${element.id}\n`;
|
|
83
|
+
plantUMLString += propertiesToPlantUML(element);
|
|
84
|
+
plantUMLString += '}\n';
|
|
85
|
+
return plantUMLString;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function propertiesToPlantUML(element: FamixTypeScriptElement) {
|
|
89
|
+
let plantUMLString = '';
|
|
90
|
+
for (const property in element) {
|
|
91
|
+
const attribute = element[property];
|
|
92
|
+
const isOneToManyReference = typeof attribute !== 'string' && attribute.length; // array but not a string
|
|
93
|
+
|
|
94
|
+
switch (property) {
|
|
95
|
+
// ignores these properties
|
|
96
|
+
case 'subclass':
|
|
97
|
+
case 'superclass':
|
|
98
|
+
case 'FM3':
|
|
99
|
+
case 'id':
|
|
100
|
+
case 'name':
|
|
101
|
+
break;
|
|
102
|
+
|
|
103
|
+
default:
|
|
104
|
+
if (isOneToManyReference) {
|
|
105
|
+
attribute.forEach((composite, index) => {
|
|
106
|
+
associations.push({ from: element.id, to: composite.ref, name: `${property}[${index}]` });
|
|
107
|
+
});
|
|
108
|
+
} else if (typeof attribute === 'object') {
|
|
109
|
+
associations.push({ from: element.id, to: attribute.ref, name: property });
|
|
110
|
+
} else { // typeof string, boolean, number, etc
|
|
111
|
+
// treats it as a simple attribute
|
|
112
|
+
plantUMLString += `${property} = ${element[property]}\n`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return plantUMLString;
|
|
119
|
+
}
|