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.
Files changed (128) hide show
  1. package/.eslintrc.json +24 -0
  2. package/.github/workflows/node.js.yml +60 -0
  3. package/LICENSE +23 -0
  4. package/README.md +111 -0
  5. package/doc-metamodel/skins.include.puml +2 -0
  6. package/jest.config.ts +199 -0
  7. package/package.json +47 -0
  8. package/src/analyze.ts +90 -0
  9. package/src/analyze_functions/processAccesses.ts +50 -0
  10. package/src/analyze_functions/processFiles.ts +656 -0
  11. package/src/analyze_functions/processImportClauses.ts +77 -0
  12. package/src/analyze_functions/processInheritances.ts +84 -0
  13. package/src/analyze_functions/processInvocations.ts +51 -0
  14. package/src/famix2puml.ts +119 -0
  15. package/src/famix_functions/famix_functions.ts +552 -0
  16. package/src/famix_functions/famix_functions_associations.ts +208 -0
  17. package/src/famix_functions/famix_functions_index.ts +44 -0
  18. package/src/famix_functions/famix_functions_types.ts +100 -0
  19. package/src/fqn.ts +127 -0
  20. package/src/fqp_implementation.ts +66 -0
  21. package/src/generate_uml.sh +16 -0
  22. package/src/lib/famix/License.md +23 -0
  23. package/src/lib/famix/package-lock.json +301 -0
  24. package/src/lib/famix/package.json +28 -0
  25. package/src/lib/famix/readme.md +5 -0
  26. package/src/lib/famix/src/famix_JSON_exporter.ts +56 -0
  27. package/src/lib/famix/src/famix_base_element.ts +18 -0
  28. package/src/lib/famix/src/famix_repository.ts +199 -0
  29. package/src/lib/famix/src/index.ts +8 -0
  30. package/src/lib/famix/src/model/famix/access.ts +53 -0
  31. package/src/lib/famix/src/model/famix/accessor.ts +15 -0
  32. package/src/lib/famix/src/model/famix/alias.ts +41 -0
  33. package/src/lib/famix/src/model/famix/association.ts +44 -0
  34. package/src/lib/famix/src/model/famix/behavioral_entity.ts +107 -0
  35. package/src/lib/famix/src/model/famix/c_source_language.ts +15 -0
  36. package/src/lib/famix/src/model/famix/class.ts +86 -0
  37. package/src/lib/famix/src/model/famix/comment.ts +50 -0
  38. package/src/lib/famix/src/model/famix/container_entity.ts +165 -0
  39. package/src/lib/famix/src/model/famix/custom_source_language.ts +27 -0
  40. package/src/lib/famix/src/model/famix/decorator.ts +39 -0
  41. package/src/lib/famix/src/model/famix/entity.ts +15 -0
  42. package/src/lib/famix/src/model/famix/enum.ts +31 -0
  43. package/src/lib/famix/src/model/famix/enum_value.ts +29 -0
  44. package/src/lib/famix/src/model/famix/function.ts +15 -0
  45. package/src/lib/famix/src/model/famix/implicit_variable.ts +15 -0
  46. package/src/lib/famix/src/model/famix/import_clause.ts +53 -0
  47. package/src/lib/famix/src/model/famix/index.ts +42 -0
  48. package/src/lib/famix/src/model/famix/indexed_file_anchor.ts +49 -0
  49. package/src/lib/famix/src/model/famix/inheritance.ts +42 -0
  50. package/src/lib/famix/src/model/famix/interface.ts +75 -0
  51. package/src/lib/famix/src/model/famix/invocation.ts +68 -0
  52. package/src/lib/famix/src/model/famix/method.ts +96 -0
  53. package/src/lib/famix/src/model/famix/module.ts +31 -0
  54. package/src/lib/famix/src/model/famix/named_entity.ts +98 -0
  55. package/src/lib/famix/src/model/famix/namespace.ts +28 -0
  56. package/src/lib/famix/src/model/famix/parameter.ts +29 -0
  57. package/src/lib/famix/src/model/famix/parameterizable_class.ts +31 -0
  58. package/src/lib/famix/src/model/famix/parameterizable_interface.ts +31 -0
  59. package/src/lib/famix/src/model/famix/parameterized_type.ts +40 -0
  60. package/src/lib/famix/src/model/famix/primitive_type.ts +15 -0
  61. package/src/lib/famix/src/model/famix/property.ts +54 -0
  62. package/src/lib/famix/src/model/famix/reference.ts +42 -0
  63. package/src/lib/famix/src/model/famix/scoping_entity.ts +31 -0
  64. package/src/lib/famix/src/model/famix/script_entity.ts +38 -0
  65. package/src/lib/famix/src/model/famix/source_anchor.ts +31 -0
  66. package/src/lib/famix/src/model/famix/source_language.ts +31 -0
  67. package/src/lib/famix/src/model/famix/sourced_entity.ts +70 -0
  68. package/src/lib/famix/src/model/famix/structural_entity.ts +44 -0
  69. package/src/lib/famix/src/model/famix/text_anchor.ts +49 -0
  70. package/src/lib/famix/src/model/famix/type.ts +88 -0
  71. package/src/lib/famix/src/model/famix/type_parameter.ts +33 -0
  72. package/src/lib/famix/src/model/famix/variable.ts +28 -0
  73. package/src/lib/famix/tsconfig.json +27 -0
  74. package/src/lib/famix/tslint.json +15 -0
  75. package/src/lib/ts-complex/cyclomatic-service.ts +85 -0
  76. package/src/ts2famix-cli.ts +26 -0
  77. package/src/ts2famix-tsconfig.ts +30 -0
  78. package/test/abstractClassWithComments.test.ts +58 -0
  79. package/test/abstracts.test.ts +53 -0
  80. package/test/access.test.ts +62 -0
  81. package/test/accesses.test.ts +42 -0
  82. package/test/accessorsWithDecorators.test.ts +98 -0
  83. package/test/alias.test.ts +39 -0
  84. package/test/classExtendsUndefinedClass.test.ts +41 -0
  85. package/test/classImplementsUndefinedInterface.test.ts +45 -0
  86. package/test/classWithDecorators.test.ts +65 -0
  87. package/test/entities.test.ts +232 -0
  88. package/test/entities_json.test.ts +48 -0
  89. package/test/enum.test.ts +55 -0
  90. package/test/functionReturnsFunction.test.ts +53 -0
  91. package/test/functionWithParameters.test.ts +38 -0
  92. package/test/functionWithVariables.test.ts +64 -0
  93. package/test/functions.test.ts +23 -0
  94. package/test/functionsInFunction.test.ts +40 -0
  95. package/test/functionsInMethod.test.ts +42 -0
  96. package/test/genericClass.test.ts +42 -0
  97. package/test/genericClassInheritsInterface.test.ts +47 -0
  98. package/test/genericInterface.test.ts +38 -0
  99. package/test/genericMethod.test.ts +65 -0
  100. package/test/genericWithInvocation.test.ts +71 -0
  101. package/test/generics.test.ts +68 -0
  102. package/test/inheritance.test.ts +50 -0
  103. package/test/interfaceInheritsInterface.test.ts +40 -0
  104. package/test/interfaceInheritsUndefinedInterface.test.ts +41 -0
  105. package/test/invocation.test.ts +94 -0
  106. package/test/invocationWithFunction.test.ts +42 -0
  107. package/test/invocationWithVariable.test.ts +46 -0
  108. package/test/invocation_json.test.ts +63 -0
  109. package/test/invocations.test.ts +131 -0
  110. package/test/jsDoc.test.ts +31 -0
  111. package/test/methodWithDecorator.test.ts +44 -0
  112. package/test/methods.test.ts +42 -0
  113. package/test/metrics.test.ts +51 -0
  114. package/test/module.test.ts +71 -0
  115. package/test/namespaces.test.ts +54 -0
  116. package/test/namespacesAndClasses.test.ts +66 -0
  117. package/test/parameterWithDecorators.test.ts +54 -0
  118. package/test/propertyWithDecorators.test.ts +80 -0
  119. package/test/sample.test.ts +13 -0
  120. package/test/simpleFunction.test.ts +32 -0
  121. package/test/simpleTest.test.ts +18 -0
  122. package/test/simpleTest2.test.ts +36 -0
  123. package/test/types.test.ts +58 -0
  124. package/test_src/sample.ts +103 -0
  125. package/test_src/sampleForModule.ts +10 -0
  126. package/test_src/sampleForModule2.ts +7 -0
  127. package/test_src/sampleForModule3.ts +2 -0
  128. 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
+ }