ts2famix 2.0.3 → 2.1.0-beta.2

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 (191) hide show
  1. package/.eslintrc.json +24 -24
  2. package/.vscode/settings.json +4 -0
  3. package/LICENSE +24 -24
  4. package/README.md +78 -78
  5. package/TODO +1 -0
  6. package/arwea-fix.json +1 -0
  7. package/bogus.ts +3 -0
  8. package/class-diagram.puml +792 -0
  9. package/debug.txt +13332 -0
  10. package/debuglog.txt +12073 -0
  11. package/dist/analyze.js +19 -9
  12. package/dist/analyze_functions/process_functions.js +101 -71
  13. package/dist/famix2puml.js +126 -0
  14. package/dist/famix_functions/EntityDictionary.js +881 -509
  15. package/dist/famix_functions/helpers_creation.js +18 -8
  16. package/dist/fqn.js +44 -11
  17. package/dist/getClasses-arrow-body.js +43 -0
  18. package/dist/lib/famix/famix_JSON_exporter.js +1 -1
  19. package/dist/lib/famix/famix_base_element.js +1 -1
  20. package/dist/lib/famix/famix_repository.js +67 -2
  21. package/dist/lib/famix/index.js +18 -8
  22. package/dist/lib/famix/model/famix/access.js +1 -1
  23. package/dist/lib/famix/model/famix/accessor.js +1 -1
  24. package/dist/lib/famix/model/famix/alias.js +1 -1
  25. package/dist/lib/famix/model/famix/arrow_function.js +1 -1
  26. package/dist/lib/famix/model/famix/behavioral_entity.js +1 -1
  27. package/dist/lib/famix/model/famix/class.js +1 -1
  28. package/dist/lib/famix/model/famix/comment.js +1 -1
  29. package/dist/lib/famix/model/famix/concretisation.js +1 -1
  30. package/dist/lib/famix/model/famix/container_entity.js +1 -1
  31. package/dist/lib/famix/model/famix/decorator.js +1 -1
  32. package/dist/lib/famix/model/famix/entity.js +1 -1
  33. package/dist/lib/famix/model/famix/enum.js +1 -1
  34. package/dist/lib/famix/model/famix/enum_value.js +1 -1
  35. package/dist/lib/famix/model/famix/function.js +1 -1
  36. package/dist/lib/famix/model/famix/import_clause.js +1 -1
  37. package/dist/lib/famix/model/famix/index.js +1 -1
  38. package/dist/lib/famix/model/famix/indexed_file_anchor.js +1 -1
  39. package/dist/lib/famix/model/famix/inheritance.js +1 -1
  40. package/dist/lib/famix/model/famix/interface.js +1 -1
  41. package/dist/lib/famix/model/famix/invocation.js +1 -1
  42. package/dist/lib/famix/model/famix/method.js +1 -1
  43. package/dist/lib/famix/model/famix/module.js +1 -1
  44. package/dist/lib/famix/model/famix/named_entity.js +1 -1
  45. package/dist/lib/famix/model/famix/parameter.js +1 -1
  46. package/dist/lib/famix/model/famix/parameter_concretisation.js +1 -1
  47. package/dist/lib/famix/model/famix/parameter_type.js +1 -1
  48. package/dist/lib/famix/model/famix/parametric_arrow_function.js +1 -1
  49. package/dist/lib/famix/model/famix/parametric_class.js +1 -1
  50. package/dist/lib/famix/model/famix/parametric_function.js +1 -1
  51. package/dist/lib/famix/model/famix/parametric_interface.js +1 -1
  52. package/dist/lib/famix/model/famix/parametric_method.js +1 -1
  53. package/dist/lib/famix/model/famix/primitive_type.js +1 -1
  54. package/dist/lib/famix/model/famix/property.js +1 -1
  55. package/dist/lib/famix/model/famix/reference.js +1 -1
  56. package/dist/lib/famix/model/famix/scoping_entity.js +1 -1
  57. package/dist/lib/famix/model/famix/script_entity.js +1 -1
  58. package/dist/lib/famix/model/famix/source_anchor.js +1 -1
  59. package/dist/lib/famix/model/famix/source_language.js +1 -1
  60. package/dist/lib/famix/model/famix/sourced_entity.js +1 -1
  61. package/dist/lib/famix/model/famix/structural_entity.js +1 -1
  62. package/dist/lib/famix/model/famix/type.js +1 -1
  63. package/dist/lib/famix/model/famix/variable.js +1 -1
  64. package/dist/lib/famix/src/famix_JSON_exporter.js +55 -0
  65. package/dist/lib/famix/src/famix_base_element.js +18 -0
  66. package/dist/lib/famix/src/famix_repository.js +224 -0
  67. package/dist/lib/famix/src/index.js +31 -0
  68. package/dist/lib/famix/src/model/famix/access.js +40 -0
  69. package/dist/lib/famix/src/model/famix/accessor.js +17 -0
  70. package/dist/lib/famix/src/model/famix/alias.js +33 -0
  71. package/dist/lib/famix/src/model/famix/arrowFunction.js +17 -0
  72. package/dist/lib/famix/src/model/famix/arrow_function.js +17 -0
  73. package/dist/lib/famix/src/model/famix/behavioral_entity.js +79 -0
  74. package/dist/lib/famix/src/model/famix/class.js +71 -0
  75. package/dist/lib/famix/src/model/famix/comment.js +39 -0
  76. package/dist/lib/famix/src/model/famix/concretisation.js +31 -0
  77. package/dist/lib/famix/src/model/famix/container_entity.js +126 -0
  78. package/dist/lib/famix/src/model/famix/decorator.js +32 -0
  79. package/dist/lib/famix/src/model/famix/entity.js +17 -0
  80. package/dist/lib/famix/src/model/famix/enum.js +31 -0
  81. package/dist/lib/famix/src/model/famix/enum_value.js +25 -0
  82. package/dist/lib/famix/src/model/famix/function.js +17 -0
  83. package/dist/lib/famix/src/model/famix/implicit_variable.js +17 -0
  84. package/dist/lib/famix/src/model/famix/import_clause.js +41 -0
  85. package/dist/lib/famix/src/model/famix/index.js +86 -0
  86. package/dist/lib/famix/src/model/famix/indexed_file_anchor.js +38 -0
  87. package/dist/lib/famix/src/model/famix/inheritance.js +33 -0
  88. package/dist/lib/famix/src/model/famix/interface.js +64 -0
  89. package/dist/lib/famix/src/model/famix/invocation.js +54 -0
  90. package/dist/lib/famix/src/model/famix/method.js +67 -0
  91. package/dist/lib/famix/src/model/famix/module.js +60 -0
  92. package/dist/lib/famix/src/model/famix/named_entity.js +78 -0
  93. package/dist/lib/famix/src/model/famix/parameter.js +25 -0
  94. package/dist/lib/famix/src/model/famix/parameterConcretisation.js +44 -0
  95. package/dist/lib/famix/src/model/famix/parameter_concretisation.js +44 -0
  96. package/dist/lib/famix/src/model/famix/parameter_type.js +45 -0
  97. package/dist/lib/famix/src/model/famix/parametricArrowFunction.js +29 -0
  98. package/dist/lib/famix/src/model/famix/parametric_arrow_function.js +31 -0
  99. package/dist/lib/famix/src/model/famix/parametric_class.js +44 -0
  100. package/dist/lib/famix/src/model/famix/parametric_function.js +31 -0
  101. package/dist/lib/famix/src/model/famix/parametric_interface.js +44 -0
  102. package/dist/lib/famix/src/model/famix/parametric_method.js +31 -0
  103. package/dist/lib/famix/src/model/famix/primitive_type.js +17 -0
  104. package/dist/lib/famix/src/model/famix/property.js +73 -0
  105. package/dist/lib/famix/src/model/famix/reference.js +33 -0
  106. package/dist/lib/famix/src/model/famix/scoping_entity.js +36 -0
  107. package/dist/lib/famix/src/model/famix/script_entity.js +29 -0
  108. package/dist/lib/famix/src/model/famix/source_anchor.js +27 -0
  109. package/dist/lib/famix/src/model/famix/source_language.js +35 -0
  110. package/dist/lib/famix/src/model/famix/sourced_entity.js +60 -0
  111. package/dist/lib/famix/src/model/famix/structural_entity.js +39 -0
  112. package/dist/lib/famix/src/model/famix/text_anchor.js +38 -0
  113. package/dist/lib/famix/src/model/famix/type.js +73 -0
  114. package/dist/lib/famix/src/model/famix/variable.js +24 -0
  115. package/dist/lib/ts-complex/cyclomatic-service.js +1 -1
  116. package/dist/refactorer/refactor-getter-setter.js +18 -8
  117. package/dist/ts2famix-cli-wrapper.js +18 -8
  118. package/dist/ts2famix-cli.js +18 -8
  119. package/dist/ts2famix-tsconfig.js +18 -8
  120. package/doc-uml/famix-typescript-model.puml +607 -607
  121. package/eslint.config.mjs +28 -0
  122. package/fqn-model.json +1 -0
  123. package/iterateGenericTypes.ts +69 -0
  124. package/out/class-diagram/class-diagram.svg +1 -0
  125. package/package.json +70 -66
  126. package/sample.json +1 -0
  127. package/sample.ts +1 -0
  128. package/src/analyze.ts +120 -120
  129. package/src/analyze_functions/process_functions.ts +1040 -1019
  130. package/src/famix_functions/EntityDictionary.ts +2016 -1593
  131. package/src/famix_functions/helpers_creation.ts +135 -135
  132. package/src/fqn.ts +50 -16
  133. package/src/generate_uml.sh +20 -20
  134. package/src/lib/famix/License.md +22 -22
  135. package/src/lib/famix/famix_JSON_exporter.ts +56 -56
  136. package/src/lib/famix/famix_base_element.ts +22 -22
  137. package/src/lib/famix/famix_repository.ts +278 -243
  138. package/src/lib/famix/index.ts +8 -8
  139. package/src/lib/famix/model/famix/access.ts +50 -50
  140. package/src/lib/famix/model/famix/accessor.ts +15 -15
  141. package/src/lib/famix/model/famix/alias.ts +39 -39
  142. package/src/lib/famix/model/famix/arrow_function.ts +15 -15
  143. package/src/lib/famix/model/famix/behavioral_entity.ts +97 -97
  144. package/src/lib/famix/model/famix/class.ts +85 -85
  145. package/src/lib/famix/model/famix/comment.ts +47 -47
  146. package/src/lib/famix/model/famix/concretisation.ts +40 -40
  147. package/src/lib/famix/model/famix/container_entity.ts +160 -160
  148. package/src/lib/famix/model/famix/decorator.ts +37 -37
  149. package/src/lib/famix/model/famix/entity.ts +15 -15
  150. package/src/lib/famix/model/famix/enum.ts +30 -30
  151. package/src/lib/famix/model/famix/enum_value.ts +28 -28
  152. package/src/lib/famix/model/famix/function.ts +15 -15
  153. package/src/lib/famix/model/famix/import_clause.ts +51 -51
  154. package/src/lib/famix/model/famix/index.ts +41 -41
  155. package/src/lib/famix/model/famix/indexed_file_anchor.ts +46 -46
  156. package/src/lib/famix/model/famix/inheritance.ts +40 -40
  157. package/src/lib/famix/model/famix/interface.ts +75 -75
  158. package/src/lib/famix/model/famix/invocation.ts +65 -65
  159. package/src/lib/famix/model/famix/method.ts +89 -89
  160. package/src/lib/famix/model/famix/module.ts +71 -71
  161. package/src/lib/famix/model/famix/named_entity.ts +95 -95
  162. package/src/lib/famix/model/famix/parameter.ts +28 -28
  163. package/src/lib/famix/model/famix/parameter_concretisation.ts +51 -51
  164. package/src/lib/famix/model/famix/parameter_type.ts +58 -58
  165. package/src/lib/famix/model/famix/parametric_arrow_function.ts +32 -32
  166. package/src/lib/famix/model/famix/parametric_class.ts +49 -49
  167. package/src/lib/famix/model/famix/parametric_function.ts +32 -32
  168. package/src/lib/famix/model/famix/parametric_interface.ts +49 -49
  169. package/src/lib/famix/model/famix/parametric_method.ts +32 -32
  170. package/src/lib/famix/model/famix/primitive_type.ts +15 -15
  171. package/src/lib/famix/model/famix/property.ts +94 -94
  172. package/src/lib/famix/model/famix/reference.ts +40 -40
  173. package/src/lib/famix/model/famix/scoping_entity.ts +35 -35
  174. package/src/lib/famix/model/famix/script_entity.ts +34 -34
  175. package/src/lib/famix/model/famix/source_anchor.ts +30 -30
  176. package/src/lib/famix/model/famix/source_language.ts +35 -35
  177. package/src/lib/famix/model/famix/sourced_entity.ts +70 -70
  178. package/src/lib/famix/model/famix/structural_entity.ts +43 -43
  179. package/src/lib/famix/model/famix/type.ts +87 -87
  180. package/src/lib/famix/model/famix/variable.ts +27 -27
  181. package/src/lib/famix/package.json +28 -28
  182. package/src/lib/ts-complex/cyclomatic-service.ts +83 -83
  183. package/src/refactorer/refactor-getter-setter.ts +140 -140
  184. package/src/ts2famix-cli-wrapper.ts +21 -21
  185. package/src/ts2famix-cli.ts +60 -60
  186. package/stats.txt +3091 -0
  187. package/tabby-debug-output.txt +19433 -0
  188. package/ts2famix.log +22656 -0
  189. package/tsconfig.check-tests.json +14 -14
  190. package/tsconfig.json +72 -72
  191. package/validate-references.js +103 -0
@@ -1,1593 +1,2016 @@
1
- import { ClassDeclaration, ConstructorDeclaration, FunctionDeclaration, Identifier, InterfaceDeclaration, MethodDeclaration, MethodSignature, ModuleDeclaration, PropertyDeclaration, PropertySignature, SourceFile, TypeParameterDeclaration, VariableDeclaration, ParameterDeclaration, Decorator, GetAccessorDeclaration, SetAccessorDeclaration, ImportSpecifier, CommentRange, EnumDeclaration, EnumMember, TypeAliasDeclaration, FunctionExpression, ExpressionWithTypeArguments, ImportDeclaration, ImportEqualsDeclaration, SyntaxKind, Expression, TypeNode, Node, ts, Scope, Type, ArrowFunction } from "ts-morph";
2
- import { isAmbient, isNamespace } from "../analyze_functions/process_functions";
3
- import * as Famix from "../lib/famix/model/famix";
4
- import { FamixRepository } from "../lib/famix/famix_repository";
5
- import { logger, config } from "../analyze";
6
- import GraphemeSplitter from "grapheme-splitter";
7
- import * as Helpers from "./helpers_creation";
8
- import * as FQNFunctions from "../fqn";
9
- import path from "path";
10
- import _ from 'lodash';
11
-
12
- export type TSMorphObjectType = ImportDeclaration | ImportEqualsDeclaration | SourceFile | ModuleDeclaration | ClassDeclaration | InterfaceDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature | FunctionDeclaration | FunctionExpression | ParameterDeclaration | VariableDeclaration | PropertyDeclaration | PropertySignature | TypeParameterDeclaration | Identifier | Decorator | GetAccessorDeclaration | SetAccessorDeclaration | ImportSpecifier | CommentRange | EnumDeclaration | EnumMember | TypeAliasDeclaration | ExpressionWithTypeArguments;
13
-
14
- export type TypeDeclaration = TypeAliasDeclaration | PropertyDeclaration | PropertySignature | MethodDeclaration | ConstructorDeclaration | MethodSignature | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression | ParameterDeclaration | VariableDeclaration | EnumMember | ImportEqualsDeclaration;
15
-
16
- type ParametricVariantType = Famix.ParametricClass | Famix.ParametricInterface | Famix.ParametricFunction | Famix.ParametricMethod;
17
-
18
- type ConcreteElementTSMorphType = ClassDeclaration | InterfaceDeclaration | FunctionDeclaration | MethodDeclaration;
19
-
20
- export class EntityDictionary {
21
-
22
- public famixRep = new FamixRepository();
23
- private fmxAliasMap = new Map<string, Famix.Alias>(); // Maps the alias names to their Famix model
24
- private fmxClassMap = new Map<string, Famix.Class | Famix.ParametricClass>(); // Maps the fully qualified class names to their Famix model
25
- private fmxInterfaceMap = new Map<string, Famix.Interface | Famix.ParametricInterface>(); // Maps the interface names to their Famix model
26
- private fmxModuleMap = new Map<string, Famix.Module>(); // Maps the namespace names to their Famix model
27
- private fmxFileMap = new Map<string, Famix.ScriptEntity | Famix.Module>(); // Maps the source file names to their Famix model
28
- private fmxTypeMap = new Map<string, Famix.Type | Famix.PrimitiveType | Famix.ParameterType>(); // Maps the type names to their Famix model
29
- private fmxFunctionAndMethodMap = new Map<string, Famix.Function | Famix.ParametricFunction | Famix.Method | Famix.ParametricMethod> // Maps the function names to their Famix model
30
- private UNKNOWN_VALUE = '(unknown due to parsing error)'; // The value to use when a name is not usable
31
- public fmxElementObjectMap = new Map<Famix.Entity,TSMorphObjectType>();
32
- public tsMorphElementObjectMap = new Map<TSMorphObjectType,Famix.Entity>();
33
-
34
- constructor() {
35
- this.famixRep.setFmxElementObjectMap(this.fmxElementObjectMap);
36
- }
37
-
38
- public addSourceAnchor(fmx: Famix.SourcedEntity, node: TSMorphObjectType): Famix.IndexedFileAnchor {
39
- const sourceAnchor: Famix.IndexedFileAnchor = new Famix.IndexedFileAnchor();
40
- let sourceStart, sourceEnd: number;
41
- if (fmx && node) {
42
- // find the start and end positions of the source element
43
- if (!(node instanceof CommentRange)) {
44
- sourceStart = node.getStart();
45
- sourceEnd = node.getEnd();
46
- } else {
47
- sourceStart = node.getPos();
48
- sourceEnd = node.getEnd();
49
- }
50
-
51
- if (config.expectGraphemes) {
52
- /**
53
- * The following logic handles the case of multi-code point characters (e.g. emoji) in the source text.
54
- * This is needed because Pharo/Smalltalk treats multi-code point characters as a single character,
55
- * but JavaScript treats them as multiple characters. This means that the start and end positions
56
- * of a source element in Pharo/Smalltalk will be different than the start and end positions of the
57
- * same source element in JavaScript. This logic finds the start and end positions of the source
58
- * element in JavaScript and then uses those positions to set the start and end positions of the
59
- * Famix index file anchor.
60
- * It depends on code in the 'grapheme-splitter' package in npm.
61
- */
62
- const splitter = new GraphemeSplitter();
63
- const sourceFileText = node.getSourceFile().getFullText();
64
- const hasGraphemeClusters = splitter.countGraphemes(sourceFileText) > 1;
65
- if (hasGraphemeClusters) {
66
- const sourceElementText = sourceFileText.substring(sourceStart, sourceEnd);
67
- const sourceElementTextGraphemes = splitter.splitGraphemes(sourceElementText);
68
- const sourceFileTextGraphemes = splitter.splitGraphemes(sourceFileText);
69
- const numberOfGraphemeClustersBeforeStart = splitter.countGraphemes(sourceFileText.substring(0, sourceStart));
70
-
71
- // find the start of the sourceElementTextGraphemes array in the sourceFileTextGraphemes array
72
- sourceStart = Helpers.indexOfSplitArray({searchArray: sourceFileTextGraphemes,
73
- targetArray: sourceElementTextGraphemes,
74
- start: sourceStart - numberOfGraphemeClustersBeforeStart});
75
- sourceEnd = sourceStart + sourceElementTextGraphemes.length;
76
- }
77
- }
78
-
79
- // The +1 is because the source anchor (Pharo) is 1-based, but ts-morph is 0-based
80
- sourceAnchor.startPos = sourceStart + 1;
81
- sourceAnchor.endPos = sourceEnd + 1;
82
-
83
- const fileName = node.getSourceFile().getFilePath();
84
-
85
- sourceAnchor.element = fmx;
86
- sourceAnchor.fileName = fileName;
87
- fmx.sourceAnchor = sourceAnchor;
88
- this.famixRep.addElement(sourceAnchor);
89
-
90
- }
91
- return sourceAnchor;
92
- }
93
-
94
- /**
95
- * Makes a Famix index file anchor
96
- * @param sourceElement A source element
97
- * @param famixElement The Famix model of the source element
98
- */
99
- public makeFamixIndexFileAnchor(sourceElement: TSMorphObjectType, famixElement: Famix.SourcedEntity): void {
100
- // check if famixElement doesn't have a valid fullyQualifiedName
101
- if (typeof (famixElement as any).getFullyQualifiedName === 'function') {
102
- // The method exists
103
- const fullyQualifiedName = (famixElement as any).fullyQualifiedName;
104
- if (!fullyQualifiedName || fullyQualifiedName === this.UNKNOWN_VALUE) {
105
- throw new Error(`Famix element ${famixElement.constructor.name} has no valid fullyQualifiedName.`);
106
- }
107
- }
108
-
109
- logger.debug("making index file anchor for '" + sourceElement?.getText() + "' with famixElement " + famixElement.getJSON());
110
- const fmxIndexFileAnchor = new Famix.IndexedFileAnchor();
111
- fmxIndexFileAnchor.element = famixElement;
112
- this.fmxElementObjectMap.set(famixElement, sourceElement);
113
-
114
- if (sourceElement !== null) {
115
- const absolutePathProject = this.famixRep.getAbsolutePath();
116
-
117
- const absolutePath = path.normalize(sourceElement.getSourceFile().getFilePath());
118
-
119
- const positionNodeModules = absolutePath.indexOf('node_modules');
120
-
121
- let pathInProject: string = "";
122
-
123
- if (positionNodeModules !== -1) {
124
- const pathFromNodeModules = absolutePath.substring(positionNodeModules);
125
- pathInProject = pathFromNodeModules;
126
- } else {
127
- pathInProject = this.convertToRelativePath(absolutePath, absolutePathProject);
128
- }
129
-
130
- // revert any backslashes to forward slashes (path.normalize on windows introduces them)
131
- pathInProject = pathInProject.replace(/\\/g, "/");
132
-
133
- fmxIndexFileAnchor.fileName = pathInProject;
134
- let sourceStart, sourceEnd, sourceLineStart, sourceLineEnd: number;
135
- if (!(sourceElement instanceof CommentRange)) {
136
- sourceStart = sourceElement.getStart();
137
- sourceEnd = sourceElement.getEnd();
138
- sourceLineStart = sourceElement.getStartLineNumber();
139
- sourceLineEnd = sourceElement.getEndLineNumber();
140
- } else {
141
- sourceStart = sourceElement.getPos();
142
- sourceEnd = sourceElement.getEnd();
143
- }
144
- if (config.expectGraphemes) {
145
- /**
146
- * The following logic handles the case of multi-code point characters (e.g. emoji) in the source text.
147
- * This is needed because Pharo/Smalltalk treats multi-code point characters as a single character,
148
- * but JavaScript treats them as multiple characters. This means that the start and end positions
149
- * of a source element in Pharo/Smalltalk will be different than the start and end positions of the
150
- * same source element in JavaScript. This logic finds the start and end positions of the source
151
- * element in JavaScript and then uses those positions to set the start and end positions of the
152
- * Famix index file anchor.
153
- * It depends on code in the 'grapheme-splitter' package in npm.
154
- */
155
- const splitter = new GraphemeSplitter();
156
- const sourceFileText = sourceElement.getSourceFile().getFullText();
157
- const hasGraphemeClusters = splitter.countGraphemes(sourceFileText) > 1;
158
- if (hasGraphemeClusters) {
159
- const sourceElementText = sourceFileText.substring(sourceStart, sourceEnd);
160
- const sourceElementTextGraphemes = splitter.splitGraphemes(sourceElementText);
161
- const sourceFileTextGraphemes = splitter.splitGraphemes(sourceFileText);
162
- const numberOfGraphemeClustersBeforeStart = splitter.countGraphemes(sourceFileText.substring(0, sourceStart));
163
-
164
- // find the start of the sourceElementTextGraphemes array in the sourceFileTextGraphemes array
165
- sourceStart = Helpers.indexOfSplitArray({searchArray: sourceFileTextGraphemes,
166
- targetArray: sourceElementTextGraphemes,
167
- start: sourceStart - numberOfGraphemeClustersBeforeStart});
168
- sourceEnd = sourceStart + sourceElementTextGraphemes.length;
169
- }
170
- }
171
- // note: the +1 is because the source anchor is 1-based, but ts-morph is 0-based
172
- fmxIndexFileAnchor.startPos = sourceStart + 1;
173
- fmxIndexFileAnchor.endPos = sourceEnd + 1;
174
-
175
- // if (!(famixElement instanceof Famix.ImportClause || famixElement instanceof Famix.Access || famixElement instanceof Famix.Reference || famixElement instanceof Famix.Invocation || famixElement instanceof Famix.Inheritance) && !(famixElement instanceof Famix.Comment) && !(sourceElement instanceof CommentRange) && !(sourceElement instanceof Identifier) && !(sourceElement instanceof ImportSpecifier) && !(sourceElement instanceof ExpressionWithTypeArguments)) {
176
- // initFQN(sourceElement, famixElement);
177
- // }
178
- } else {
179
- // sourceElement is null
180
- logger.warn("sourceElement is null for famixElement " + famixElement.getJSON());
181
- fmxIndexFileAnchor.fileName = "unknown";
182
- fmxIndexFileAnchor.startPos = 0;
183
- fmxIndexFileAnchor.endPos = 0;
184
- }
185
-
186
- this.famixRep.addElement(fmxIndexFileAnchor);
187
- }
188
-
189
- /**
190
- * Creates or gets a Famix script entity or module
191
- * @param f A source file
192
- * @param isModule A boolean indicating if the source file is a module
193
- * @returns The Famix model of the source file
194
- */
195
- public createOrGetFamixFile(f: SourceFile, isModule: boolean): Famix.ScriptEntity | Famix.Module {
196
- let fmxFile: Famix.ScriptEntity; // | Famix.Module;
197
-
198
- const fileName = f.getBaseName();
199
- const fullyQualifiedFilename = f.getFilePath();
200
- const foundFileName = this.fmxFileMap.get(fullyQualifiedFilename);
201
- if (!foundFileName) {
202
- if (isModule) {
203
- fmxFile = new Famix.Module();
204
- }
205
- else {
206
- fmxFile = new Famix.ScriptEntity();
207
- }
208
- fmxFile.name = fileName;
209
- fmxFile.numberOfLinesOfText = f.getEndLineNumber() - f.getStartLineNumber();
210
- fmxFile.numberOfCharacters = f.getFullText().length;
211
-
212
- initFQN(f, fmxFile);
213
-
214
- this.makeFamixIndexFileAnchor(f, fmxFile);
215
-
216
- this.fmxFileMap.set(fullyQualifiedFilename, fmxFile);
217
- this.famixRep.addElement(fmxFile);
218
- }
219
- else {
220
- fmxFile = foundFileName;
221
- }
222
-
223
- this.fmxElementObjectMap.set(fmxFile,f);
224
- return fmxFile;
225
- }
226
-
227
- /**
228
- * Creates or gets a Famix Module
229
- * @param m A module
230
- * @returns The Famix model of the module
231
- */
232
- public createOrGetFamixModule(m: ModuleDeclaration): Famix.Module {
233
- let fmxModule: Famix.Module;
234
- const moduleName = m.getName();
235
- const foundModuleName = this.fmxModuleMap.get(moduleName);
236
- if (!foundModuleName) {
237
- fmxModule = new Famix.Module();
238
- fmxModule.name = moduleName;
239
- fmxModule.isAmbient = isAmbient(m);
240
- fmxModule.isNamespace = isNamespace(m);
241
- fmxModule.isModule = !fmxModule.isNamespace && !fmxModule.isAmbient;
242
-
243
- initFQN(m, fmxModule);
244
- this.makeFamixIndexFileAnchor(m, fmxModule);
245
-
246
- this.fmxModuleMap.set(moduleName, fmxModule);
247
-
248
- this.famixRep.addElement(fmxModule);
249
- }
250
- else {
251
- fmxModule = foundModuleName;
252
- }
253
-
254
- this.fmxElementObjectMap.set(fmxModule,m);
255
- return fmxModule;
256
- }
257
-
258
- /**
259
- * Creates a Famix alias
260
- * @param a An alias
261
- * @returns The Famix model of the alias
262
- */
263
- public createFamixAlias(a: TypeAliasDeclaration): Famix.Alias {
264
- let fmxAlias: Famix.Alias;
265
- const aliasName = a.getName();
266
- const aliasFullyQualifiedName = a.getType().getText(); // FQNFunctions.getFQN(a);
267
- const foundAlias = this.fmxAliasMap.get(aliasFullyQualifiedName);
268
- if (!foundAlias) {
269
- fmxAlias = new Famix.Alias();
270
- fmxAlias.name = a.getName();
271
- const aliasNameWithGenerics = aliasName + (a.getTypeParameters().length ? ("<" + a.getTypeParameters().map(tp => tp.getName()).join(", ") + ">") : "");
272
- logger.debug(`> NOTE: alias ${aliasName} has fully qualified name ${aliasFullyQualifiedName} and name with generics ${aliasNameWithGenerics}.`);
273
-
274
- const fmxType = this.createOrGetFamixType(aliasNameWithGenerics, a);
275
- fmxAlias.aliasedEntity = fmxType;
276
- initFQN(a, fmxAlias);
277
- this.makeFamixIndexFileAnchor(a, fmxAlias);
278
-
279
- this.fmxAliasMap.set(aliasFullyQualifiedName, fmxAlias);
280
-
281
- this.famixRep.addElement(fmxAlias);
282
- }
283
- else {
284
- fmxAlias = foundAlias;
285
- }
286
- this.fmxElementObjectMap.set(fmxAlias,a);
287
-
288
- return fmxAlias;
289
- }
290
-
291
- /**
292
- * Creates or gets a Famix class or parameterizable class
293
- * @param cls A class
294
- * @returns The Famix model of the class
295
- */
296
- public createOrGetFamixClass(cls: ClassDeclaration): Famix.Class | Famix.ParametricClass {
297
- let fmxClass: Famix.Class | Famix.ParametricClass;
298
- const isAbstract = cls.isAbstract();
299
- const classFullyQualifiedName = FQNFunctions.getFQN(cls);
300
- const clsName = cls.getName() || this.UNKNOWN_VALUE;
301
- const isGeneric = cls.getTypeParameters().length;
302
- const foundClass = this.fmxClassMap.get(classFullyQualifiedName);
303
- if (!foundClass) {
304
- if (isGeneric) {
305
- fmxClass = new Famix.ParametricClass();
306
- }
307
- else {
308
- fmxClass = new Famix.Class();
309
- }
310
-
311
- fmxClass.name = clsName;
312
- fmxClass.fullyQualifiedName = classFullyQualifiedName;
313
- fmxClass.isAbstract = isAbstract;
314
-
315
- this.makeFamixIndexFileAnchor(cls, fmxClass);
316
-
317
- this.fmxClassMap.set(classFullyQualifiedName, fmxClass);
318
-
319
- this.famixRep.addElement(fmxClass);
320
-
321
- this.fmxElementObjectMap.set(fmxClass,cls);
322
- }
323
- else {
324
- fmxClass = foundClass;
325
- }
326
-
327
- return fmxClass;
328
- }
329
-
330
- /**
331
- * Creates or gets a Famix interface or parameterizable interface
332
- * @param inter An interface
333
- * @returns The Famix model of the interface
334
- */
335
- public createOrGetFamixInterface(inter: InterfaceDeclaration): Famix.Interface | Famix.ParametricInterface {
336
-
337
- let fmxInterface: Famix.Interface | Famix.ParametricInterface;
338
- const interName = inter.getName();
339
- const interFullyQualifiedName = FQNFunctions.getFQN(inter);
340
- const foundInterface = this.fmxInterfaceMap.get(interFullyQualifiedName);
341
- if (!foundInterface) {
342
- const isGeneric = inter.getTypeParameters().length;
343
- if (isGeneric) {
344
- fmxInterface = new Famix.ParametricInterface();
345
- }
346
- else {
347
- fmxInterface = new Famix.Interface();
348
- }
349
-
350
- fmxInterface.name = interName;
351
- initFQN(inter, fmxInterface);
352
- this.makeFamixIndexFileAnchor(inter, fmxInterface);
353
-
354
- this.fmxInterfaceMap.set(interFullyQualifiedName, fmxInterface);
355
-
356
- this.famixRep.addElement(fmxInterface);
357
-
358
- this.fmxElementObjectMap.set(fmxInterface,inter);
359
- }
360
- else {
361
- fmxInterface = foundInterface;
362
- }
363
- return fmxInterface;
364
- }
365
-
366
-
367
- /**
368
- * Creates or gets a Famix concrete element
369
- * @param concreteElement A parametric Element
370
- * @param concreteElementDeclaration the element declaration
371
- * @param concreteArguments concrete arguments
372
- * @returns A parametric Element
373
- */
374
- public createOrGetFamixConcreteElement(concreteElement : ParametricVariantType,
375
- concreteElementDeclaration : ConcreteElementTSMorphType,
376
- concreteArguments: TypeNode[]): ParametricVariantType {
377
-
378
- let fullyQualifiedFilename = concreteElement.fullyQualifiedName;
379
- let params = "";
380
-
381
- concreteArguments.map((param) => {
382
- params = params+param.getText()+','
383
- })
384
-
385
- params = params.substring(0, params.length - 1)
386
-
387
- fullyQualifiedFilename = Helpers.replaceLastBetweenTags(fullyQualifiedFilename,params);
388
-
389
- let concElement: ParametricVariantType;
390
-
391
- if (!this.fmxInterfaceMap.has(fullyQualifiedFilename) &&
392
- !this.fmxClassMap.has(fullyQualifiedFilename) &&
393
- !this.fmxFunctionAndMethodMap.has(fullyQualifiedFilename)){
394
- concElement = _.cloneDeep(concreteElement);
395
- concElement.fullyQualifiedName = fullyQualifiedFilename;
396
- concElement.clearGenericParameters();
397
- concreteArguments.map((param) => {
398
- const parameter = this.createOrGetFamixConcreteType(param);
399
- concElement.addConcreteParameter(parameter);
400
- })
401
-
402
- if (concreteElement instanceof Famix.ParametricClass) {
403
- this.fmxClassMap.set(fullyQualifiedFilename, concElement as Famix.ParametricClass);
404
- } else if (concreteElement instanceof Famix.ParametricInterface) {
405
- this.fmxInterfaceMap.set(fullyQualifiedFilename, concElement as Famix.ParametricInterface);
406
- } else if (concreteElement instanceof Famix.ParametricFunction) {
407
- this.fmxFunctionAndMethodMap.set(fullyQualifiedFilename, concElement as Famix.ParametricFunction);
408
- } else { // if (concreteElement instanceof Famix.ParametricMethod) {
409
- this.fmxFunctionAndMethodMap.set(fullyQualifiedFilename, concElement as Famix.ParametricMethod);
410
- }
411
- this.famixRep.addElement(concElement);
412
- this.fmxElementObjectMap.set(concElement,concreteElementDeclaration);
413
- } else {
414
- if (concreteElement instanceof Famix.ParametricClass) {
415
- concElement = this.fmxClassMap.get(fullyQualifiedFilename) as Famix.ParametricClass;
416
- } else if (concreteElement instanceof Famix.ParametricInterface) {
417
- concElement = this.fmxInterfaceMap.get(fullyQualifiedFilename) as Famix.ParametricInterface;
418
- } else if (concreteElement instanceof Famix.ParametricFunction) {
419
- concElement = this.fmxFunctionAndMethodMap.get(fullyQualifiedFilename) as Famix.ParametricFunction;
420
- } else { // if (concreteElement instanceof Famix.ParametricMethod) {
421
- concElement = this.fmxFunctionAndMethodMap.get(fullyQualifiedFilename) as Famix.ParametricMethod;
422
- }
423
- }
424
- return concElement;
425
- }
426
-
427
- /**
428
- * Creates a Famix property
429
- * @param property A property
430
- * @returns The Famix model of the property
431
- */
432
- public createFamixProperty(property: PropertyDeclaration | PropertySignature): Famix.Property {
433
- const fmxProperty = new Famix.Property();
434
- const isSignature = property instanceof PropertySignature;
435
- fmxProperty.name = property.getName();
436
-
437
- let propTypeName = this.UNKNOWN_VALUE;
438
- try {
439
- propTypeName = property.getType().getText().trim();
440
- } catch (error) {
441
- logger.error(`> WARNING: got exception ${error}. Failed to get usable name for property: ${property.getName()}. Continuing...`);
442
- }
443
-
444
- const fmxType = this.createOrGetFamixType(propTypeName, property);
445
- fmxProperty.declaredType = fmxType;
446
-
447
- // add the visibility (public, private, etc.) to the fmxProperty
448
- fmxProperty.visibility = "";
449
-
450
- property.getModifiers().forEach(m => {
451
- switch (m.getText()) {
452
- case Scope.Public:
453
- fmxProperty.visibility = "public";
454
- break;
455
- case Scope.Protected:
456
- fmxProperty.visibility = "protected";
457
- break;
458
- case Scope.Private:
459
- fmxProperty.visibility = "private";
460
- break;
461
- case "static":
462
- fmxProperty.isClassSide = true;
463
- break;
464
- case "readonly":
465
- fmxProperty.readOnly = true;
466
- break;
467
- default:
468
- break;
469
- }
470
- });
471
-
472
- if (!isSignature && property.getExclamationTokenNode()) {
473
- fmxProperty.isDefinitelyAssigned = true;
474
- }
475
- if (property.getQuestionTokenNode()) {
476
- fmxProperty.isOptional = true;
477
- }
478
- if (property.getName().substring(0, 1) === "#") {
479
- fmxProperty.isJavaScriptPrivate = true;
480
- }
481
-
482
- initFQN(property, fmxProperty);
483
- this.makeFamixIndexFileAnchor(property, fmxProperty);
484
-
485
- this.famixRep.addElement(fmxProperty);
486
-
487
- this.fmxElementObjectMap.set(fmxProperty,property);
488
-
489
- return fmxProperty;
490
- }
491
-
492
- /**
493
- * Creates a Famix method or accessor
494
- * @param method A method or an accessor
495
- * @param currentCC The cyclomatic complexity metrics of the current source file
496
- * @returns The Famix model of the method or the accessor
497
- */
498
- public createOrGetFamixMethod(method: MethodDeclaration | ConstructorDeclaration | MethodSignature | GetAccessorDeclaration | SetAccessorDeclaration, currentCC: { [key: string]: number }): Famix.Method | Famix.Accessor | Famix.ParametricMethod {
499
- let fmxMethod: Famix.Method | Famix.Accessor | Famix.ParametricMethod;
500
- const isGeneric = method.getTypeParameters().length > 0;
501
- const functionFullyQualifiedName = FQNFunctions.getFQN(method);
502
- if (!this.fmxFunctionAndMethodMap.has(functionFullyQualifiedName)) {
503
-
504
- if (method instanceof GetAccessorDeclaration || method instanceof SetAccessorDeclaration) {
505
- fmxMethod = new Famix.Accessor();
506
- const isGetter = method instanceof GetAccessorDeclaration;
507
- const isSetter = method instanceof SetAccessorDeclaration;
508
- if (isGetter) {(fmxMethod as Famix.Accessor).kind = "getter";}
509
- if (isSetter) {(fmxMethod as Famix.Accessor).kind = "setter";}
510
- this.famixRep.addElement(fmxMethod);
511
- }
512
- else {
513
- if (isGeneric) {
514
- fmxMethod = new Famix.ParametricMethod();
515
- }
516
- else {
517
- fmxMethod = new Famix.Method();
518
- }
519
- this.famixRep.addElement(fmxMethod);
520
- }
521
- const isConstructor = method instanceof ConstructorDeclaration;
522
- const isSignature = method instanceof MethodSignature;
523
-
524
- let isAbstract = false;
525
- let isStatic = false;
526
- if (method instanceof MethodDeclaration || method instanceof GetAccessorDeclaration || method instanceof SetAccessorDeclaration) {
527
- isAbstract = method.isAbstract();
528
- isStatic = method.isStatic();
529
- }
530
-
531
- if (isConstructor) {(fmxMethod as Famix.Accessor).kind = "constructor";}
532
- fmxMethod.isAbstract = isAbstract;
533
- fmxMethod.isClassSide = isStatic;
534
- fmxMethod.isPrivate = (method instanceof MethodDeclaration || method instanceof GetAccessorDeclaration || method instanceof SetAccessorDeclaration) ? (method.getModifiers().find(x => x.getText() === 'private')) !== undefined : false;
535
- fmxMethod.isProtected = (method instanceof MethodDeclaration || method instanceof GetAccessorDeclaration || method instanceof SetAccessorDeclaration) ? (method.getModifiers().find(x => x.getText() === 'protected')) !== undefined : false;
536
- fmxMethod.signature = Helpers.computeSignature(method.getText());
537
-
538
- let methodName: string;
539
- if (isConstructor) {
540
- methodName = "constructor";
541
- }
542
- else {
543
- methodName = (method as MethodDeclaration | MethodSignature | GetAccessorDeclaration | SetAccessorDeclaration).getName();
544
- }
545
- fmxMethod.name = methodName;
546
-
547
- if (!isConstructor) {
548
- if (method.getName().substring(0, 1) === "#") {
549
- fmxMethod.isPrivate = true;
550
- }
551
- }
552
-
553
- if (!fmxMethod.isPrivate && !fmxMethod.isProtected) {
554
- fmxMethod.isPublic = true;
555
- }
556
- else {
557
- fmxMethod.isPublic = false;
558
- }
559
-
560
- if (!isSignature) {
561
- fmxMethod.cyclomaticComplexity = currentCC[fmxMethod.name];
562
- }
563
- else {
564
- fmxMethod.cyclomaticComplexity = 0;
565
- }
566
-
567
- let methodTypeName = this.UNKNOWN_VALUE;
568
- try {
569
- methodTypeName = method.getReturnType().getText().trim();
570
- } catch (error) {
571
- logger.error(`> WARNING: got exception ${error}. Failed to get usable name for return type of method: ${fmxMethod.name}. Continuing...`);
572
- }
573
-
574
- const fmxType = this.createOrGetFamixType(methodTypeName, method);
575
- fmxMethod.declaredType = fmxType;
576
- fmxMethod.numberOfLinesOfCode = method.getEndLineNumber() - method.getStartLineNumber();
577
- const parameters = method.getParameters();
578
- fmxMethod.numberOfParameters = parameters.length;
579
-
580
- if (!isSignature) {
581
- fmxMethod.numberOfStatements = method.getStatements().length;
582
- }
583
- else {
584
- fmxMethod.numberOfStatements = 0;
585
- }
586
-
587
- initFQN(method, fmxMethod);
588
- this.makeFamixIndexFileAnchor(method, fmxMethod);
589
-
590
- this.fmxFunctionAndMethodMap.set(functionFullyQualifiedName, fmxMethod);
591
- }
592
- else {
593
- fmxMethod = this.fmxFunctionAndMethodMap.get(functionFullyQualifiedName) as (Famix.Method | Famix.Accessor | Famix.ParametricMethod);
594
- }
595
-
596
- this.fmxElementObjectMap.set(fmxMethod,method);
597
-
598
- return fmxMethod;
599
- }
600
-
601
- /**
602
- * Creates a Famix function
603
- * @param func A function
604
- * @param currentCC The cyclomatic complexity metrics of the current source file
605
- * @returns The Famix model of the function
606
- */
607
- public createOrGetFamixFunction(func: FunctionDeclaration | FunctionExpression, currentCC: { [key: string]: number }): Famix.Function | Famix.ParametricFunction {
608
- let fmxFunction: Famix.Function | Famix.ParametricFunction;
609
- const isGeneric = func.getTypeParameters().length > 0;
610
- const functionFullyQualifiedName = FQNFunctions.getFQN(func);
611
- if (!this.fmxFunctionAndMethodMap.has(functionFullyQualifiedName)) {
612
- if (isGeneric) {
613
- fmxFunction = new Famix.ParametricFunction();
614
- }
615
- else {
616
- fmxFunction = new Famix.Function();
617
- }
618
-
619
- const name = func.getName();
620
- if (name) {
621
- fmxFunction.name = name;
622
- }
623
- else {
624
- fmxFunction.name = "anonymous";
625
- }
626
-
627
- fmxFunction.signature = Helpers.computeSignature(func.getText());
628
- fmxFunction.cyclomaticComplexity = currentCC[fmxFunction.name];
629
- fmxFunction.fullyQualifiedName = functionFullyQualifiedName;
630
-
631
- let functionTypeName = this.UNKNOWN_VALUE;
632
- try {
633
- functionTypeName = func.getReturnType().getText().trim();
634
- } catch (error) {
635
- logger.error(`> WARNING: got exception ${error}. Failed to get usable name for return type of function: ${func.getName()}. Continuing...`);
636
- }
637
-
638
- const fmxType = this.createOrGetFamixType(functionTypeName, func);
639
- fmxFunction.declaredType = fmxType;
640
- fmxFunction.numberOfLinesOfCode = func.getEndLineNumber() - func.getStartLineNumber();
641
- const parameters = func.getParameters();
642
- fmxFunction.numberOfParameters = parameters.length;
643
- fmxFunction.numberOfStatements = func.getStatements().length;
644
- this.makeFamixIndexFileAnchor(func, fmxFunction);
645
-
646
- this.famixRep.addElement(fmxFunction);
647
-
648
- this.fmxElementObjectMap.set(fmxFunction,func);
649
-
650
- this.fmxFunctionAndMethodMap.set(functionFullyQualifiedName, fmxFunction);
651
- }
652
- else {
653
- fmxFunction = this.fmxFunctionAndMethodMap.get(functionFullyQualifiedName) as (Famix.Function | Famix.ParametricFunction);
654
- }
655
-
656
- return fmxFunction;
657
- }
658
-
659
- /**
660
- * Creates a Famix parameter
661
- * @param param A parameter
662
- * @returns The Famix model of the parameter
663
- */
664
- public createFamixParameter(param: ParameterDeclaration): Famix.Parameter {
665
- const fmxParam = new Famix.Parameter();
666
-
667
- let paramTypeName = this.UNKNOWN_VALUE;
668
- try {
669
- paramTypeName = param.getType().getText().trim();
670
- } catch (error) {
671
- logger.error(`> WARNING: got exception ${error}. Failed to get usable name for parameter: ${param.getName()}. Continuing...`);
672
- }
673
-
674
- const fmxType = this.createOrGetFamixType(paramTypeName, param);
675
- fmxParam.declaredType = fmxType;
676
- fmxParam.name = param.getName();
677
-
678
- initFQN(param, fmxParam);
679
- this.makeFamixIndexFileAnchor(param, fmxParam);
680
-
681
- this.famixRep.addElement(fmxParam);
682
-
683
- this.fmxElementObjectMap.set(fmxParam,param);
684
-
685
- return fmxParam;
686
- }
687
-
688
- /**
689
- * Creates a Famix type parameter
690
- * @param tp A type parameter
691
- * @returns The Famix model of the type parameter
692
- */
693
- public createFamixParameterType(tp: TypeParameterDeclaration): Famix.ParameterType {
694
-
695
- const fmxParameterType = new Famix.ParameterType();
696
-
697
- fmxParameterType.name = tp.getName();
698
- initFQN(tp, fmxParameterType);
699
- this.makeFamixIndexFileAnchor(tp, fmxParameterType);
700
-
701
- this.famixRep.addElement(fmxParameterType);
702
-
703
- this.fmxElementObjectMap.set(fmxParameterType,tp);
704
-
705
- return fmxParameterType;
706
- }
707
-
708
- /**
709
- * Creates a Famix type parameter
710
- * @param tp A type parameter
711
- * @returns The Famix model of the type parameter
712
- */
713
- public createOrGetFamixConcreteType(param: TypeNode): Famix.ParameterType | Famix.PrimitiveType | Famix.Class | Famix.Interface {
714
- const typeParameterDeclaration = param.getSymbol()?.getDeclarations()[0] as TypeParameterDeclaration;
715
- const parameterTypeName : string = param.getText();
716
- let fmxParameterType: Famix.Type | Famix.Class | Famix.Interface | undefined = undefined;
717
-
718
- let isClassOrInterface = false;
719
- if (this.fmxClassMap.has(parameterTypeName)){
720
- this.fmxClassMap.forEach((obj, name) => {
721
- if(obj instanceof Famix.ParametricClass){
722
- if (name === param.getText() && obj.genericParameters.size>0) {
723
- fmxParameterType = obj;
724
- isClassOrInterface = true;
725
- }
726
- } else {
727
- if (name === param.getText()) {
728
- fmxParameterType = obj;
729
- isClassOrInterface = true;
730
- }
731
- }
732
- })
733
- }
734
-
735
- if (this.fmxInterfaceMap.has(parameterTypeName)){
736
- this.fmxInterfaceMap.forEach((obj, name) => {
737
- if(obj instanceof Famix.ParametricInterface){
738
- if (name === param.getText() && obj.genericParameters.size>0) {
739
- fmxParameterType = obj;
740
- isClassOrInterface = true;
741
- }
742
- } else {
743
- if (name === param.getText()) {
744
- fmxParameterType = obj;
745
- isClassOrInterface = true;
746
- }
747
- }
748
- })
749
- }
750
-
751
- if(!isClassOrInterface){
752
- if (!this.fmxTypeMap.has(parameterTypeName)) {
753
- if (parameterTypeName === "number" || parameterTypeName === "string" || parameterTypeName === "boolean" || parameterTypeName === "bigint" || parameterTypeName === "symbol" || parameterTypeName === "undefined" || parameterTypeName === "null" || parameterTypeName === "any" || parameterTypeName === "unknown" || parameterTypeName === "never" || parameterTypeName === "void") {
754
- fmxParameterType = new Famix.PrimitiveType();
755
- fmxParameterType.isStub = true;
756
- } else {
757
- fmxParameterType = new Famix.ParameterType();
758
- }
759
-
760
- fmxParameterType.name = parameterTypeName;
761
- this.famixRep.addElement(fmxParameterType);
762
- this.fmxTypeMap.set(parameterTypeName, fmxParameterType);
763
- this.fmxElementObjectMap.set(fmxParameterType,typeParameterDeclaration);
764
- }
765
- else {
766
- const result = this.fmxTypeMap.get(parameterTypeName);
767
- if (result) {
768
- fmxParameterType = result;
769
- } else {
770
- throw new Error(`Famix type ${parameterTypeName} is not found in the Type map.`);
771
- }
772
- }
773
- }
774
- if (!fmxParameterType) {
775
- throw new Error(`fmxParameterType was undefined for parameterTypeName ${parameterTypeName}`);
776
- }
777
- return fmxParameterType;
778
- }
779
-
780
- /**
781
- * Creates a Famix variable
782
- * @param variable A variable
783
- * @returns The Famix model of the variable
784
- */
785
- public createFamixVariable(variable: VariableDeclaration): Famix.Variable {
786
- const fmxVariable = new Famix.Variable();
787
-
788
- let variableTypeName = this.UNKNOWN_VALUE;
789
- try {
790
- variableTypeName = variable.getType().getText().trim();
791
- } catch (error) {
792
- logger.error(`> WARNING: got exception ${error}. Failed to get usable name for variable: ${variable.getName()}. Continuing...`);
793
- }
794
-
795
- const fmxType = this.createOrGetFamixType(variableTypeName, variable);
796
- fmxVariable.declaredType = fmxType;
797
- fmxVariable.name = variable.getName();
798
- initFQN(variable, fmxVariable);
799
- this.makeFamixIndexFileAnchor(variable, fmxVariable);
800
-
801
- this.famixRep.addElement(fmxVariable);
802
-
803
- this.fmxElementObjectMap.set(fmxVariable,variable);
804
-
805
- return fmxVariable;
806
- }
807
-
808
- /**
809
- * Creates a Famix enum
810
- * @param enumEntity An enum
811
- * @returns The Famix model of the enum
812
- */
813
- public createFamixEnum(enumEntity: EnumDeclaration): Famix.Enum {
814
- const fmxEnum = new Famix.Enum();
815
- fmxEnum.name = enumEntity.getName();
816
- initFQN(enumEntity, fmxEnum);
817
- this.makeFamixIndexFileAnchor(enumEntity, fmxEnum);
818
-
819
- this.famixRep.addElement(fmxEnum);
820
-
821
- this.fmxElementObjectMap.set(fmxEnum,enumEntity);
822
-
823
- return fmxEnum;
824
- }
825
-
826
- /**
827
- * Creates a Famix enum value
828
- * @param enumMember An enum member
829
- * @returns The Famix model of the enum member
830
- */
831
- public createFamixEnumValue(enumMember: EnumMember): Famix.EnumValue {
832
- const fmxEnumValue = new Famix.EnumValue();
833
-
834
- let enumValueTypeName = this.UNKNOWN_VALUE;
835
- try {
836
- enumValueTypeName = enumMember.getType().getText().trim();
837
- } catch (error) {
838
- logger.error(`> WARNING: got exception ${error}. Failed to get usable name for enum value: ${enumMember.getName()}. Continuing...`);
839
- }
840
-
841
- const fmxType = this.createOrGetFamixType(enumValueTypeName, enumMember);
842
- fmxEnumValue.declaredType = fmxType;
843
- fmxEnumValue.name = enumMember.getName();
844
- initFQN(enumMember, fmxEnumValue);
845
- this.makeFamixIndexFileAnchor(enumMember, fmxEnumValue);
846
-
847
- this.famixRep.addElement(fmxEnumValue);
848
-
849
- this.fmxElementObjectMap.set(fmxEnumValue,enumMember);
850
-
851
- return fmxEnumValue;
852
- }
853
-
854
- /**
855
- * Creates or gets a Famix decorator
856
- * @param decorator A decorator
857
- * @param decoratedEntity A class, a method, a parameter or a property
858
- * @returns The Famix model of the decorator
859
- */
860
- public createOrGetFamixDecorator(decorator: Decorator, decoratedEntity: ClassDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | ParameterDeclaration | PropertyDeclaration): Famix.Decorator {
861
- const fmxDecorator = new Famix.Decorator();
862
- const decoratorName = "@" + decorator.getName();
863
- const decoratorExpression = decorator.getText().substring(1);
864
-
865
- fmxDecorator.name = decoratorName;
866
- fmxDecorator.decoratorExpression = decoratorExpression;
867
- const decoratedEntityFullyQualifiedName = FQNFunctions.getFQN(decoratedEntity);
868
- const fmxDecoratedEntity = this.famixRep.getFamixEntityByFullyQualifiedName(decoratedEntityFullyQualifiedName) as Famix.NamedEntity;
869
- fmxDecorator.decoratedEntity = fmxDecoratedEntity;
870
- initFQN(decorator, fmxDecorator);
871
- this.makeFamixIndexFileAnchor(decorator, fmxDecorator);
872
-
873
- this.famixRep.addElement(fmxDecorator);
874
-
875
- this.fmxElementObjectMap.set(fmxDecorator,decorator);
876
-
877
- return fmxDecorator;
878
- }
879
-
880
- /**
881
- * Creates a Famix comment
882
- * @param comment A comment
883
- * @param fmxScope The Famix model of the comment's container
884
- * @param isJSDoc A boolean indicating if the comment is a JSDoc
885
- * @returns The Famix model of the comment
886
- */
887
- public createFamixComment(comment: CommentRange, fmxScope: Famix.NamedEntity, isJSDoc: boolean): Famix.Comment {
888
- logger.debug(`> NOTE: creating comment ${comment.getText()} in scope ${fmxScope.name}.`);
889
- const fmxComment = new Famix.Comment();
890
- fmxComment.container = fmxScope; // adds comment to the container's comments collection
891
- fmxComment.isJSDoc = isJSDoc;
892
-
893
- this.makeFamixIndexFileAnchor(comment, fmxComment);
894
-
895
- this.famixRep.addElement(fmxComment);
896
-
897
- this.fmxElementObjectMap.set(fmxComment,comment);
898
-
899
- return fmxComment;
900
- }
901
-
902
- /**
903
- * Creates or gets a Famix type
904
- * @param typeName A type name
905
- * @param element A ts-morph element
906
- * @returns The Famix model of the type
907
- */
908
- public createOrGetFamixType(typeName: string, element: TypeDeclaration): Famix.Type | Famix.PrimitiveType | Famix.ParameterType {
909
- let fmxType: Famix.Type | Famix.PrimitiveType | Famix.ParameterType;
910
- let isPrimitiveType = false;
911
- let isParameterType = false;
912
-
913
- logger.debug("Creating (or getting) type: '" + typeName + "' of element: " + element?.getText() + " of kind: " + element?.getKindName());
914
- let ancestor: Famix.ContainerEntity | undefined = undefined;
915
- if (element !== undefined) {
916
- const typeAncestor = Helpers.findTypeAncestor(element);
917
- if (!typeAncestor) {
918
- throw new Error(`Ancestor not found for element ${element.getText()}.`);
919
- }
920
- const ancestorFullyQualifiedName = FQNFunctions.getFQN(typeAncestor);
921
- ancestor = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName) as Famix.ContainerEntity;
922
- if (!ancestor) {
923
- logger.debug(`Ancestor ${FQNFunctions.getFQN(typeAncestor)} not found. Adding the new type.`);
924
- ancestor = this.createOrGetFamixType(typeAncestor.getText(), typeAncestor as TypeDeclaration);
925
- }
926
- }
927
-
928
- if (typeName === "number" || typeName === "string" || typeName === "boolean" || typeName === "bigint" || typeName === "symbol" || typeName === "undefined" || typeName === "null" || typeName === "any" || typeName === "unknown" || typeName === "never" || typeName === "void") {
929
- isPrimitiveType = true;
930
- }
931
-
932
- if(!isPrimitiveType && typeName.includes("<") && typeName.includes(">") && !(typeName.includes("=>"))) {
933
- isParameterType = true;
934
- }
935
-
936
- if (!this.fmxTypeMap.has(typeName)) {
937
- if (isPrimitiveType) {
938
- fmxType = new Famix.PrimitiveType();
939
- fmxType.isStub = true;
940
- }
941
- else if (isParameterType) {
942
- fmxType = new Famix.ParameterType();
943
- const parameterTypeNames = typeName.substring(typeName.indexOf("<") + 1, typeName.indexOf(">")).split(",").map(s => s.trim());
944
- const baseTypeName = typeName.substring(0, typeName.indexOf("<")).trim();
945
- parameterTypeNames.forEach(parameterTypeName => {
946
- const fmxParameterType = this.createOrGetFamixType(parameterTypeName, element);
947
- (fmxType as Famix.ParameterType).addArgument(fmxParameterType);
948
- });
949
- const fmxBaseType = this.createOrGetFamixType(baseTypeName, element);
950
- (fmxType as Famix.ParameterType).baseType = fmxBaseType;
951
- }
952
- else {
953
- fmxType = new Famix.Type();
954
- }
955
-
956
- fmxType.name = typeName;
957
- if (!ancestor) {
958
- throw new Error(`Ancestor not found for type ${typeName}.`);
959
- }
960
- fmxType.container = ancestor;
961
- initFQN(element, fmxType);
962
- this.makeFamixIndexFileAnchor(element, fmxType);
963
-
964
- this.famixRep.addElement(fmxType);
965
-
966
- this.fmxTypeMap.set(typeName, fmxType);
967
- }
968
- else {
969
- const result = this.fmxTypeMap.get(typeName);
970
- if (result) {
971
- fmxType = result;
972
- } else {
973
- throw new Error(`Famix type ${typeName} is not found in the Type map.`);
974
- }
975
- }
976
-
977
- this.fmxElementObjectMap.set(fmxType,element);
978
-
979
- return fmxType;
980
- }
981
-
982
- /**
983
- * Creates a Famix access
984
- * @param node A node
985
- * @param id An id of a parameter, a variable, a property or an enum member
986
- */
987
- public createFamixAccess(node: Identifier, id: number): void {
988
- const fmxVar = this.famixRep.getFamixEntityById(id) as Famix.StructuralEntity;
989
- if (!fmxVar) {
990
- throw new Error(`Famix entity with id ${id} not found, for node ${node.getText()} in ${node.getSourceFile().getBaseName()} at line ${node.getStartLineNumber()}.`);
991
- }
992
-
993
- logger.debug(`Creating FamixAccess. Node: [${node.getKindName()}] '${node.getText()}' at line ${node.getStartLineNumber()} in ${node.getSourceFile().getBaseName()}, id: ${id} refers to fmxVar '${fmxVar.fullyQualifiedName}'.`);
994
-
995
- const nodeReferenceAncestor = Helpers.findAncestor(node);
996
- const ancestorFullyQualifiedName = FQNFunctions.getFQN(nodeReferenceAncestor);
997
- let accessor = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName) as Famix.ContainerEntity;
998
- if (!accessor) {
999
- logger.error(`Ancestor ${ancestorFullyQualifiedName} of kind ${nodeReferenceAncestor.getKindName()} not found.`);
1000
- // accessor = this.createOrGetFamixType(ancestorFullyQualifiedName, nodeReferenceAncestor as TypeDeclaration);
1001
- }
1002
-
1003
- const fmxAccess = new Famix.Access();
1004
- fmxAccess.accessor = accessor;
1005
- fmxAccess.variable = fmxVar;
1006
-
1007
- this.famixRep.addElement(fmxAccess);
1008
-
1009
- this.fmxElementObjectMap.set(fmxAccess,node);
1010
- }
1011
-
1012
- /**
1013
- * Creates a Famix invocation
1014
- * @param node A node
1015
- * @param m A method or a function
1016
- * @param id The id of the method or the function
1017
- */
1018
- public createFamixInvocation(node: Identifier, m: MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression, id: number): void {
1019
- const fmxMethodOrFunction = this.famixRep.getFamixEntityById(id) as Famix.BehavioralEntity;
1020
- const nodeReferenceAncestor = Helpers.findAncestor(node);
1021
- const ancestorFullyQualifiedName = FQNFunctions.getFQN(nodeReferenceAncestor);
1022
- const sender = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName) as Famix.ContainerEntity;
1023
- const receiverFullyQualifiedName = FQNFunctions.getFQN(m.getParent());
1024
- const receiver = this.famixRep.getFamixEntityByFullyQualifiedName(receiverFullyQualifiedName) as Famix.NamedEntity;
1025
-
1026
- const fmxInvocation = new Famix.Invocation();
1027
- fmxInvocation.sender = sender;
1028
- fmxInvocation.receiver = receiver;
1029
- fmxInvocation.addCandidate(fmxMethodOrFunction);
1030
- fmxInvocation.signature = fmxMethodOrFunction.signature;
1031
-
1032
- this.famixRep.addElement(fmxInvocation);
1033
-
1034
- this.fmxElementObjectMap.set(fmxInvocation,node);
1035
- }
1036
-
1037
- /**
1038
- * Creates a Famix inheritance
1039
- * @param cls A class or an interface (subclass)
1040
- * @param inhClass The inherited class or interface (superclass)
1041
- */
1042
- public createFamixInheritance(cls: ClassDeclaration | InterfaceDeclaration, inhClass: ClassDeclaration | InterfaceDeclaration | ExpressionWithTypeArguments): void {
1043
- const fmxInheritance = new Famix.Inheritance();
1044
- // const clsName = cls.getName();
1045
- const classFullyQualifiedName = FQNFunctions.getFQN(cls);
1046
- logger.debug(`createFamixInheritance: classFullyQualifiedName: class fqn = ${classFullyQualifiedName}`);
1047
- let subClass: Famix.Class | Famix.Interface | undefined;
1048
- if (cls instanceof ClassDeclaration) {
1049
- subClass = this.fmxClassMap.get(classFullyQualifiedName);
1050
- }
1051
- else {
1052
- subClass = this.fmxInterfaceMap.get(classFullyQualifiedName);
1053
- }
1054
- if (!subClass) {
1055
- throw new Error(`Subclass ${classFullyQualifiedName} not found in Class or Interface maps.`);
1056
- }
1057
-
1058
- let inhClassName: string | undefined;
1059
- let inhClassFullyQualifiedName: string;
1060
- let superClass: Famix.Class | Famix.Interface | undefined;
1061
- if (inhClass instanceof ClassDeclaration || inhClass instanceof InterfaceDeclaration) {
1062
- inhClassName = inhClass.getName();
1063
- if (!inhClassName) {
1064
- throw new Error(`Inherited class or interface name not found for ${inhClass.getText()}.`);
1065
- }
1066
- inhClassFullyQualifiedName = FQNFunctions.getFQN(inhClass);
1067
- if (inhClass instanceof ClassDeclaration) {
1068
- superClass = this.fmxClassMap.get(inhClassFullyQualifiedName);
1069
- }
1070
- else {
1071
- superClass = this.fmxInterfaceMap.get(inhClassFullyQualifiedName);
1072
- }
1073
- if (!superClass) {
1074
- throw new Error(`Superclass ${classFullyQualifiedName} not found in Class or Interface maps.`);
1075
- }
1076
- }
1077
- else {
1078
- // inhClass is an ExpressionWithTypeArguments
1079
- inhClassName = inhClass.getExpression().getText();
1080
- // what is inhClassFullyQualifiedName? TODO
1081
- inhClassFullyQualifiedName = 'Undefined_Scope_from_importer.' + inhClassName;
1082
- }
1083
-
1084
- if (superClass === undefined) {
1085
- if (inhClass instanceof ClassDeclaration) {
1086
- superClass = new Famix.Class();
1087
- this.fmxClassMap.set(inhClassFullyQualifiedName, superClass);
1088
- }
1089
- else {
1090
- superClass = new Famix.Interface();
1091
- this.fmxInterfaceMap.set(inhClassFullyQualifiedName, superClass);
1092
- }
1093
-
1094
- this.fmxElementObjectMap.set(superClass,inhClass);
1095
-
1096
- superClass.name = inhClassName;
1097
- superClass.fullyQualifiedName = inhClassFullyQualifiedName;
1098
- superClass.isStub = true;
1099
-
1100
- this.makeFamixIndexFileAnchor(inhClass, superClass);
1101
-
1102
- this.famixRep.addElement(superClass);
1103
- }
1104
-
1105
- fmxInheritance.subclass = subClass;
1106
- fmxInheritance.superclass = superClass;
1107
-
1108
- this.famixRep.addElement(fmxInheritance);
1109
-
1110
- // We don't map inheritance to the source code element because there are two elements (super, sub)
1111
- // this.fmxElementObjectMap.set(fmxInheritance, null);
1112
-
1113
- }
1114
-
1115
- public createFamixImportClause(importedEntity: Famix.NamedEntity, importingEntity: Famix.Module) {
1116
- const fmxImportClause = new Famix.ImportClause();
1117
- fmxImportClause.importedEntity = importedEntity;
1118
- fmxImportClause.importingEntity = importingEntity;
1119
- importingEntity.addOutgoingImport(fmxImportClause);
1120
- this.famixRep.addElement(fmxImportClause);
1121
- }
1122
-
1123
- /**
1124
- * Creates a Famix import clause
1125
- * @param importClauseInfo The information needed to create a Famix import clause
1126
- * @param importDeclaration The import declaration
1127
- * @param importer A source file which is a module
1128
- * @param moduleSpecifierFilePath The path of the module where the export declaration is
1129
- * @param importElement The imported entity
1130
- * @param isInExports A boolean indicating if the imported entity is in the exports
1131
- * @param isDefaultExport A boolean indicating if the imported entity is a default export
1132
- */
1133
- public oldCreateFamixImportClause(importClauseInfo: {importDeclaration?: ImportDeclaration | ImportEqualsDeclaration, importerSourceFile: SourceFile, moduleSpecifierFilePath: string, importElement: ImportSpecifier | Identifier, isInExports: boolean, isDefaultExport: boolean}): void {
1134
- const {importDeclaration, importerSourceFile: importer, moduleSpecifierFilePath, importElement, isInExports, isDefaultExport} = importClauseInfo;
1135
- logger.debug(`createFamixImportClause: Creating import clause:`);
1136
- const fmxImportClause = new Famix.ImportClause();
1137
-
1138
- let importedEntity: Famix.NamedEntity | Famix.StructuralEntity | undefined = undefined;
1139
- let importedEntityName: string;
1140
-
1141
- const absolutePathProject = this.famixRep.getAbsolutePath();
1142
-
1143
- const absolutePath = path.normalize(moduleSpecifierFilePath);
1144
- // convert the path and remove any windows backslashes introduced by path.normalize
1145
- const pathInProject: string = this.convertToRelativePath(absolutePath, absolutePathProject).replace(/\\/g, "/");
1146
- let pathName = "{" + pathInProject + "}.";
1147
-
1148
- // Named imports, e.g. import { ClassW } from "./complexExportModule";
1149
-
1150
- // Start with simple import clause (without referring to the actual variable)
1151
-
1152
- if (importDeclaration instanceof ImportDeclaration
1153
- && importElement instanceof ImportSpecifier) {
1154
- importedEntityName = importElement.getName();
1155
- pathName = pathName + importedEntityName;
1156
- if (isInExports) {
1157
- importedEntity = this.famixRep.getFamixEntityByFullyQualifiedName(pathName) as Famix.NamedEntity;
1158
- }
1159
- if (importedEntity === undefined) {
1160
- importedEntity = new Famix.NamedEntity();
1161
- importedEntity.name = importedEntityName;
1162
- if (!isInExports) {
1163
- importedEntity.isStub = true;
1164
- }
1165
- importedEntity.fullyQualifiedName = pathName;
1166
- this.makeFamixIndexFileAnchor(importElement, importedEntity);
1167
- // must add entity to repository
1168
- this.famixRep.addElement(importedEntity);
1169
- }
1170
- }
1171
- // handle import equals declarations, e.g. import myModule = require("./complexExportModule");
1172
- // TypeScript can't determine the type of the imported module, so we create a Module entity
1173
- else if (importDeclaration instanceof ImportEqualsDeclaration) {
1174
- importedEntityName = importDeclaration?.getName();
1175
- pathName = pathName + importedEntityName;
1176
- importedEntity = new Famix.StructuralEntity();
1177
- importedEntity.name = importedEntityName;
1178
- initFQN(importDeclaration, importedEntity);
1179
- this.makeFamixIndexFileAnchor(importElement, importedEntity);
1180
- importedEntity.fullyQualifiedName = pathName;
1181
- const anyType = this.createOrGetFamixType('any', importDeclaration);
1182
- (importedEntity as Famix.StructuralEntity).declaredType = anyType;
1183
- } else { // default imports, e.g. import ClassW from "./complexExportModule";
1184
- importedEntityName = importElement.getText();
1185
- pathName = pathName + (isDefaultExport ? "defaultExport" : "namespaceExport");
1186
- importedEntity = new Famix.NamedEntity();
1187
- importedEntity.name = importedEntityName;
1188
- importedEntity.fullyQualifiedName = pathName;
1189
- this.makeFamixIndexFileAnchor(importElement, importedEntity);
1190
- }
1191
- // I don't think it should be added to the repository if it exists already
1192
- if (!isInExports) this.famixRep.addElement(importedEntity);
1193
- const importerFullyQualifiedName = FQNFunctions.getFQN(importer);
1194
- const fmxImporter = this.famixRep.getFamixEntityByFullyQualifiedName(importerFullyQualifiedName) as Famix.Module;
1195
- fmxImportClause.importingEntity = fmxImporter;
1196
- fmxImportClause.importedEntity = importedEntity;
1197
- if (importDeclaration instanceof ImportEqualsDeclaration) {
1198
- fmxImportClause.moduleSpecifier = importDeclaration?.getModuleReference().getText() as string;
1199
- } else {
1200
- fmxImportClause.moduleSpecifier = importDeclaration?.getModuleSpecifierValue() as string;
1201
- }
1202
-
1203
- logger.debug(`createFamixImportClause: ${fmxImportClause.importedEntity?.name} (of type ${
1204
- Helpers.getSubTypeName(fmxImportClause.importedEntity)}) is imported by ${fmxImportClause.importingEntity?.name}`);
1205
-
1206
- fmxImporter.addOutgoingImport(fmxImportClause);
1207
-
1208
- this.famixRep.addElement(fmxImportClause);
1209
-
1210
- if (importDeclaration) this.fmxElementObjectMap.set(fmxImportClause, importDeclaration);
1211
- }
1212
-
1213
- /**
1214
- * Creates a Famix Arrow Function
1215
- * @param arrowExpression An Expression
1216
- * @returns The Famix model of the variable
1217
- */
1218
- public createFamixArrowFunction(arrowExpression: Expression, currentCC: { [key: string]: number } ): Famix.ArrowFunction | Famix.ParametricArrowFunction {
1219
-
1220
- let fmxArrowFunction: Famix.ArrowFunction | Famix.ParametricArrowFunction;
1221
-
1222
- const arrowFunction = arrowExpression.asKindOrThrow(SyntaxKind.ArrowFunction);
1223
-
1224
- const isGeneric = arrowFunction.getTypeParameters().length > 0;
1225
-
1226
- if (isGeneric) {
1227
- fmxArrowFunction = new Famix.ParametricArrowFunction();
1228
- }
1229
- else {
1230
- fmxArrowFunction = new Famix.ArrowFunction();
1231
- }
1232
-
1233
- // Get the parent of the arrow function (the variable declaration)
1234
- const parent = arrowFunction.getParentIfKind(SyntaxKind.VariableDeclaration);
1235
- let functionName = '(NO_NAME)';
1236
-
1237
- if (parent && parent instanceof VariableDeclaration) {
1238
- // Get the name of the variable
1239
- functionName = parent.getName();
1240
- }
1241
-
1242
- if (functionName) {
1243
- fmxArrowFunction.name = functionName;
1244
- }
1245
- else {
1246
- fmxArrowFunction.name = "anonymous";
1247
- }
1248
-
1249
- // Signature of an arrow function is (parameters) => return_type
1250
- const parametersSignature = arrowFunction.getParameters().map(p => p.getText()).join(", ");
1251
- const returnTypeSignature = arrowFunction.getReturnType().getText();
1252
- fmxArrowFunction.signature = `(${parametersSignature}) => ${returnTypeSignature}`;
1253
- fmxArrowFunction.cyclomaticComplexity = currentCC[fmxArrowFunction.name];
1254
-
1255
- let functionTypeName = this.UNKNOWN_VALUE;
1256
- try {
1257
- functionTypeName = arrowFunction.getReturnType().getText().trim();
1258
- } catch (error) {
1259
- logger.error(`> WARNING: got exception ${error}. Failed to get usable name for return type of function: ${functionName}. Continuing...`);
1260
- }
1261
-
1262
- const fmxType = this.createOrGetFamixType(functionTypeName, arrowFunction as unknown as FunctionDeclaration);
1263
- fmxArrowFunction.declaredType = fmxType;
1264
- fmxArrowFunction.numberOfLinesOfCode = arrowFunction.getEndLineNumber() - arrowFunction.getStartLineNumber();
1265
- const parameters = arrowFunction.getParameters();
1266
- fmxArrowFunction.numberOfParameters = parameters.length;
1267
- fmxArrowFunction.numberOfStatements = arrowFunction.getStatements().length;
1268
- initFQN(arrowExpression as unknown as TSMorphObjectType, fmxArrowFunction);
1269
- this.makeFamixIndexFileAnchor(arrowExpression as unknown as TSMorphObjectType, fmxArrowFunction);
1270
- this.famixRep.addElement(fmxArrowFunction);
1271
- this.fmxElementObjectMap.set(fmxArrowFunction,arrowFunction as unknown as TSMorphObjectType);
1272
-
1273
- return fmxArrowFunction;
1274
- }
1275
-
1276
- /**
1277
- * Creates a Famix concretisation
1278
- * @param cls A class
1279
- * @returns The Famix model of the concretisation
1280
- */
1281
- public createFamixConcretisation(conEntity : Famix.ParametricClass | Famix.ParametricInterface | Famix.ParametricFunction | Famix.ParametricMethod ,genEntity : Famix.ParametricClass | Famix.ParametricInterface | Famix.ParametricFunction | Famix.ParametricMethod): Famix.Concretisation {
1282
-
1283
- const fmxConcretisation : Famix.Concretisation = new Famix.Concretisation();
1284
-
1285
- fmxConcretisation.concreteEntity = conEntity;
1286
- fmxConcretisation.genericEntity = genEntity;
1287
- // this.fmxElementObjectMap.set(fmxConcretisation,null);
1288
- this.famixRep.addElement(fmxConcretisation);
1289
- const parameterConcretisation = this.createFamixParameterConcretisation(fmxConcretisation);
1290
-
1291
- return fmxConcretisation;
1292
- }
1293
-
1294
- /**
1295
- * Creates a Famix concretisation
1296
- * @param concretisation A FamixConcretisation
1297
- * @returns The Famix model of the ParameterConcrestisation
1298
- */
1299
- public createFamixParameterConcretisation(concretisation: Famix.Concretisation): Famix.ParameterConcretisation | undefined{
1300
- const conClass = concretisation.concreteEntity;
1301
- const genClass = concretisation.genericEntity;
1302
- logger.debug(`Creating parameter concretisation between ${conClass.fullyQualifiedName} and ${genClass.fullyQualifiedName}`);
1303
- const parameterConcretisations = this.famixRep._getAllEntitiesWithType("ParameterConcretisation") as Set<Famix.ParameterConcretisation>;
1304
- const concreteParameters = conClass.concreteParameters;
1305
- const genericParameters = genClass.genericParameters;
1306
-
1307
- let conClassTypeParametersIterator = concreteParameters.values();
1308
- let genClassTypeParametersIterator = genericParameters.values();
1309
- let fmxParameterConcretisation : Famix.ParameterConcretisation | undefined = undefined;
1310
-
1311
- for (let i = 0; i < genericParameters.size; i++) {
1312
- const conClassTypeParameter = conClassTypeParametersIterator.next().value as Famix.ParameterType;
1313
- const genClassTypeParameter = genClassTypeParametersIterator.next().value as Famix.ParameterType;
1314
- let createParameterConcretisation : boolean = true;
1315
- if(conClassTypeParameter && genClassTypeParameter && conClassTypeParameter.name != genClassTypeParameter.name){
1316
- parameterConcretisations.forEach((param : Famix.ParameterConcretisation) => {
1317
- if (conClassTypeParameter.name == param.concreteParameter.name && genClassTypeParameter.name == param.genericParameter.name) {
1318
- createParameterConcretisation = false;
1319
- fmxParameterConcretisation = param;
1320
- }
1321
- })
1322
- if (createParameterConcretisation) {
1323
- fmxParameterConcretisation = new Famix.ParameterConcretisation();
1324
- fmxParameterConcretisation.genericParameter = genClassTypeParameter;
1325
- fmxParameterConcretisation.concreteParameter = conClassTypeParameter;
1326
- fmxParameterConcretisation.addConcretisation(concretisation);
1327
- // this.fmxElementObjectMap.set(fmxParameterConcretisation,null);
1328
- } else {
1329
- if (!fmxParameterConcretisation) {
1330
- throw new Error(`fmxParameterConcretisation was undefined for concretisation with generic parameter ${genClassTypeParameter.name} and concrete parameter ${conClassTypeParameter.name}`);
1331
- }
1332
- fmxParameterConcretisation.addConcretisation(concretisation);
1333
- }
1334
- this.famixRep.addElement(fmxParameterConcretisation);
1335
- }
1336
- }
1337
- if (!fmxParameterConcretisation) {
1338
- logger.error(`fmxParameterConcretisation was undefined for concretisation with concrete entity ${conClass.fullyQualifiedName} and generic entity ${genClass.fullyQualifiedName}`);
1339
- }
1340
- return fmxParameterConcretisation;
1341
-
1342
- }
1343
-
1344
- /**
1345
- * Creates a Famix concretisation between two classes or two interfaces
1346
- * @param element A class or an Interface
1347
- */
1348
- public createFamixConcretisationClassOrInterfaceSpecialisation(element: ClassDeclaration | InterfaceDeclaration){
1349
-
1350
- const superEntity = element.getExtends();
1351
- let superEntityArray;
1352
- if (superEntity){
1353
- superEntityArray = Array.isArray(superEntity) ? superEntity : [superEntity];
1354
- }
1355
- if (superEntityArray && superEntityArray.length > 0) {
1356
- superEntityArray.forEach(entity => {
1357
- let entityIsGeneric;
1358
- const superEntitySymbol = entity.getExpression().getSymbolOrThrow();
1359
- let superEntityDeclaration;
1360
- if (superEntity instanceof ExpressionWithTypeArguments) {
1361
- superEntityDeclaration = superEntitySymbol.getDeclarations()[0].asKind(ts.SyntaxKind.ClassDeclaration);
1362
- } else {
1363
- superEntityDeclaration = superEntitySymbol.getDeclarations()[0].asKind(ts.SyntaxKind.InterfaceDeclaration);
1364
- }
1365
- if (superEntityDeclaration) {
1366
- entityIsGeneric = superEntityDeclaration.getTypeParameters().length > 0;
1367
- }
1368
- if (entityIsGeneric) {
1369
- let EntityDeclaration;
1370
- let genEntity;
1371
- if (superEntity instanceof ExpressionWithTypeArguments) {
1372
- EntityDeclaration = entity.getExpression().getSymbol()?.getDeclarations()[0] as ClassDeclaration;
1373
- genEntity = this.createOrGetFamixClass(EntityDeclaration) as Famix.ParametricClass;
1374
- } else {
1375
- EntityDeclaration = entity.getExpression().getSymbol()?.getDeclarations()[0] as InterfaceDeclaration;
1376
- genEntity = this.createOrGetFamixInterface(EntityDeclaration) as Famix.ParametricInterface;
1377
- }
1378
- const genParams = EntityDeclaration.getTypeParameters().map((param) => param.getText());
1379
- const args = element.getHeritageClauses()[0].getTypeNodes()[0].getTypeArguments()
1380
- const conParams = element.getHeritageClauses()[0].getTypeNodes()[0].getTypeArguments().map((param) => param.getText());
1381
- if (!Helpers.arraysAreEqual(conParams,genParams)) {
1382
- let conEntity;
1383
- conEntity = this.createOrGetFamixConcreteElement(genEntity,EntityDeclaration,args);
1384
- const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1385
- let createConcretisation : boolean = true;
1386
- concretisations.forEach((conc : Famix.Concretisation) => {
1387
- if (genEntity.fullyQualifiedName == conc.genericEntity.fullyQualifiedName && conc.concreteEntity.fullyQualifiedName == conEntity.fullyQualifiedName){
1388
- createConcretisation = false;
1389
- }
1390
- });
1391
-
1392
- if (createConcretisation) {
1393
- const fmxConcretisation : Famix.Concretisation = this.createFamixConcretisation(conEntity,genEntity);
1394
- }
1395
- }
1396
- }
1397
- });
1398
- }
1399
- // TODO: This function seems unfinished
1400
- }
1401
-
1402
-
1403
- /**
1404
- * Creates a Famix concretisation between a class and its instanciations
1405
- * @param cls A class
1406
- */
1407
- public createFamixConcretisationGenericInstantiation(cls: ClassDeclaration){
1408
-
1409
- const isGeneric = cls.getTypeParameters().length > 0;
1410
- if (isGeneric) {
1411
- const instances = cls.getSourceFile().getDescendantsOfKind(ts.SyntaxKind.NewExpression)
1412
- .filter(newExpr => {
1413
- const expression = newExpr.getExpression();
1414
- return expression.getText() === cls.getName();
1415
- });
1416
-
1417
- instances.forEach(instance => {
1418
- const instanceIsGeneric = instance.getTypeArguments().length > 0;
1419
- if (instanceIsGeneric) {
1420
- const conParams = instance.getTypeArguments().map((param) => param.getText());
1421
- const genEntity = this.createOrGetFamixClass(cls) as Famix.ParametricClass;
1422
- const genParams = cls.getTypeParameters().map((param) => param.getText());
1423
- if (!Helpers.arraysAreEqual(conParams,genParams)) {
1424
- let conEntity;
1425
- conEntity = this.createOrGetFamixConcreteElement(genEntity,cls,instance.getTypeArguments());
1426
- const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1427
- let createConcretisation : boolean = true;
1428
- concretisations.forEach((conc : Famix.Concretisation) => {
1429
- if (genEntity.fullyQualifiedName == conc.genericEntity.fullyQualifiedName && conc.concreteEntity.fullyQualifiedName == conEntity.fullyQualifiedName){
1430
- createConcretisation = false;
1431
- }
1432
- });
1433
-
1434
- if (createConcretisation) {
1435
- const fmxConcretisation : Famix.Concretisation = this.createFamixConcretisation(conEntity,genEntity);
1436
- }
1437
- }
1438
- }
1439
- })
1440
- }
1441
- // TODO: This function seems unfinished
1442
- }
1443
-
1444
- /**
1445
- * Creates a Famix concretisation between a class and its instanciations
1446
- * @param func A function
1447
- */
1448
- public createFamixConcretisationFunctionInstantiation(element: FunctionDeclaration | MethodDeclaration){
1449
- const isGeneric = element.getTypeParameters().length > 0;
1450
- if (isGeneric) {
1451
- const genParams = element.getTypeParameters().map(param => param.getText());
1452
- const uses = element.findReferencesAsNodes();
1453
- uses.forEach(usage => {
1454
- let currentNode: Node | undefined = usage;
1455
-
1456
- while (currentNode) {
1457
- if (currentNode.getKind() === SyntaxKind.CallExpression) {
1458
- const callExpression = currentNode.asKind(SyntaxKind.CallExpression);
1459
- if (!callExpression) {
1460
- throw new Error(`CallExpression not found for ${currentNode.getText()}`);
1461
- }
1462
- const instanceIsGeneric = callExpression.getTypeArguments().length > 0;
1463
- if (instanceIsGeneric) {
1464
- const args = callExpression.getTypeArguments();
1465
- const conParams = callExpression.getTypeArguments().map(param => param.getText());
1466
- if (!Helpers.arraysAreEqual(conParams,genParams)) {
1467
- let genElement;
1468
- if(element instanceof FunctionDeclaration){
1469
- genElement = this.createOrGetFamixFunction(element, {}) as Famix.ParametricFunction;
1470
- } else {
1471
- genElement = this.createOrGetFamixMethod(element, {}) as Famix.ParametricMethod;
1472
- }
1473
- let concElement;
1474
- concElement = this.createOrGetFamixConcreteElement(genElement,element,args);
1475
- const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1476
- let createConcretisation : boolean = true;
1477
- concretisations.forEach((conc : Famix.Concretisation) => {
1478
- if (genElement.fullyQualifiedName == conc.genericEntity.fullyQualifiedName && conc.concreteEntity.fullyQualifiedName == concElement.fullyQualifiedName){
1479
- createConcretisation = false;
1480
- }
1481
- });
1482
-
1483
- if (createConcretisation) {
1484
- const fmxConcretisation : Famix.Concretisation = this.createFamixConcretisation(concElement,genElement);
1485
- }
1486
- }
1487
- }
1488
- break;
1489
- }
1490
- // Remonter à l'élément parent (utile si le nœud de référence est un enfant)
1491
- currentNode = currentNode.getParent();
1492
- }
1493
- });
1494
- }
1495
- }
1496
-
1497
- /**
1498
- * Creates a Famix concretisation between a class and an interface
1499
- * @param cls A class
1500
- */
1501
- public createFamixConcretisationInterfaceClass(cls: ClassDeclaration){
1502
-
1503
- const superInterfaces = cls.getImplements();
1504
- superInterfaces.forEach(interfaceType => {
1505
- const interfaceIsGeneric = interfaceType.getTypeArguments().length>0;
1506
- if (interfaceIsGeneric) {
1507
- const interfaceDeclaration = interfaceType.getExpression().getSymbol()?.getDeclarations()[0] as InterfaceDeclaration;
1508
- const genParams = interfaceDeclaration.getTypeParameters().map((param) => param.getText());
1509
- const conParams = cls.getHeritageClauses()[0].getTypeNodes()[0].getTypeArguments().map((param) => param.getText());
1510
- const args = cls.getHeritageClauses()[0].getTypeNodes()[0].getTypeArguments();
1511
- if (!Helpers.arraysAreEqual(conParams,genParams)) {
1512
- const genInterface = this.createOrGetFamixInterface(interfaceDeclaration) as Famix.ParametricInterface;
1513
- const conInterface = this.createOrGetFamixConcreteElement(genInterface,interfaceDeclaration,args);
1514
- const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1515
- let createConcretisation : boolean = true;
1516
- concretisations.forEach((conc : Famix.Concretisation) => {
1517
- if (genInterface.fullyQualifiedName == conc.genericEntity.fullyQualifiedName && conc.concreteEntity.fullyQualifiedName == conInterface.fullyQualifiedName){
1518
- createConcretisation = false;
1519
- }
1520
- });
1521
-
1522
- if (createConcretisation) {
1523
- const fmxConcretisation : Famix.Concretisation = this.createFamixConcretisation(conInterface,genInterface);
1524
- }
1525
- }
1526
- }
1527
- });
1528
- }
1529
-
1530
- /**
1531
- * Creates a Famix concretisation between an interface and a Type
1532
- * @param element A variable or a function
1533
- * @param inter An interface
1534
- */
1535
- public createFamixConcretisationTypeInstanciation(element: InterfaceDeclaration | ClassDeclaration){
1536
-
1537
- const isGeneric = element.getTypeParameters().length > 0;
1538
- if (isGeneric) {
1539
- const genParams = element.getTypeParameters().map(param => param.getText());
1540
- const uses = element.findReferencesAsNodes();
1541
- uses.forEach(use => {
1542
- let parentNode = use.getParent();
1543
- while (parentNode) {
1544
- if (parentNode.getKind() === SyntaxKind.TypeReference) {
1545
- const typeReferenceNode = parentNode.asKind(SyntaxKind.TypeReference);
1546
- if (!typeReferenceNode) {
1547
- throw new Error(`TypeReferenceNode not found for ${parentNode.getText()}`);
1548
- }
1549
- const typeReferenceNodeIsGeneric = typeReferenceNode.getTypeArguments().length > 0;
1550
- if (typeReferenceNodeIsGeneric) {}
1551
- const args = typeReferenceNode.getTypeArguments();
1552
- const conParams = typeReferenceNode.getTypeArguments().map(param => param.getText());
1553
- if (!Helpers.arraysAreEqual(conParams,genParams)) {
1554
- let genElement;
1555
- if(element instanceof ClassDeclaration){
1556
- genElement = this.createOrGetFamixClass(element) as Famix.ParametricClass;
1557
- } else {
1558
- genElement = this.createOrGetFamixInterface(element) as Famix.ParametricInterface;
1559
- }
1560
- let concElement;
1561
- concElement = this.createOrGetFamixConcreteElement(genElement,element,args);
1562
- const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1563
- let createConcretisation : boolean = true;
1564
- concretisations.forEach((conc : Famix.Concretisation) => {
1565
- if (genElement.fullyQualifiedName == conc.genericEntity.fullyQualifiedName && conc.concreteEntity.fullyQualifiedName == concElement.fullyQualifiedName){
1566
- createConcretisation = false;
1567
- }
1568
- });
1569
-
1570
- if (createConcretisation) {
1571
- const fmxConcretisation : Famix.Concretisation = this.createFamixConcretisation(concElement,genElement);
1572
- }
1573
- }
1574
- break;
1575
- }
1576
- parentNode = parentNode.getParent();
1577
- }
1578
- });
1579
- }
1580
- }
1581
-
1582
- public convertToRelativePath(absolutePath: string, absolutePathProject: string) {
1583
- return absolutePath.replace(absolutePathProject, "").slice(1);
1584
- }
1585
- }
1586
- function initFQN(sourceElement: TSMorphObjectType, famixElement: Famix.SourcedEntity) {
1587
- if (!(sourceElement instanceof CommentRange)) {
1588
- const fqn = FQNFunctions.getFQN(sourceElement);
1589
- logger.debug("Setting fully qualified name for " + famixElement.getJSON() + " to " + fqn);
1590
- (famixElement as Famix.NamedEntity).fullyQualifiedName = fqn;
1591
- }
1592
- }
1593
-
1
+ /**
2
+ * a function getOrCreateXType takes arguments name: string and element: ts-morph-type and returns a Famix.Type
3
+ * The goal is to keep track of the types (e.g., a method's definedType), for the model.
4
+ * The name doesn't need to be fully qualified (it's the name used in the source code, or the Famix model).
5
+ */
6
+
7
+
8
+ import { ClassDeclaration, ConstructorDeclaration, FunctionDeclaration, Identifier, InterfaceDeclaration, MethodDeclaration, MethodSignature, ModuleDeclaration, PropertyDeclaration, PropertySignature, SourceFile, TypeParameterDeclaration, VariableDeclaration, ParameterDeclaration, Decorator, GetAccessorDeclaration, SetAccessorDeclaration, ImportSpecifier, CommentRange, EnumDeclaration, EnumMember, TypeAliasDeclaration, FunctionExpression, ExpressionWithTypeArguments, ImportDeclaration, ImportEqualsDeclaration, SyntaxKind, Expression, TypeNode, Scope, ArrowFunction } from "ts-morph";
9
+ import { isAmbient, isNamespace } from "../analyze_functions/process_functions";
10
+ import * as Famix from "../lib/famix/model/famix";
11
+ import { FamixRepository } from "../lib/famix/famix_repository";
12
+ import { logger, config } from "../analyze";
13
+ // import GraphemeSplitter from "grapheme-splitter";
14
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
15
+ import GraphemeSplitter = require('grapheme-splitter');
16
+ import * as Helpers from "./helpers_creation";
17
+ import * as FQNFunctions from "../fqn";
18
+ import path from "path";
19
+ // import _ from 'lodash';
20
+
21
+ export type TSMorphObjectType = ImportDeclaration | ImportEqualsDeclaration | SourceFile | ModuleDeclaration | ClassDeclaration | InterfaceDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature | FunctionDeclaration | FunctionExpression | ParameterDeclaration | VariableDeclaration | PropertyDeclaration | PropertySignature | TypeParameterDeclaration | Identifier | Decorator | GetAccessorDeclaration | SetAccessorDeclaration | ImportSpecifier | CommentRange | EnumDeclaration | EnumMember | TypeAliasDeclaration | ExpressionWithTypeArguments | TSMorphParametricType;
22
+
23
+ export type TSMorphTypeDeclaration = TypeAliasDeclaration | PropertyDeclaration | PropertySignature | ConstructorDeclaration | MethodSignature | GetAccessorDeclaration | SetAccessorDeclaration | FunctionExpression | ParameterDeclaration | VariableDeclaration | EnumMember | ImportEqualsDeclaration | TSMorphParametricType | TypeParameterDeclaration;
24
+
25
+ export type TSMorphParametricType = ClassDeclaration | InterfaceDeclaration | FunctionDeclaration | MethodDeclaration | ArrowFunction;
26
+
27
+ // type ParametricVariantType = Famix.ParametricClass | Famix.ParametricInterface | Famix.ParametricFunction | Famix.ParametricMethod;
28
+
29
+ // type ConcreteElementTSMorphType = ClassDeclaration | InterfaceDeclaration | FunctionDeclaration | MethodDeclaration;
30
+
31
+ export type InvocableType = MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression | ArrowFunction;
32
+
33
+ export class EntityDictionary {
34
+
35
+ public famixRep = new FamixRepository();
36
+ private fmxAliasMap = new Map<string, Famix.Alias>(); // Maps the alias names to their Famix model
37
+ private fmxClassMap = new Map<string, Famix.Class | Famix.ParametricClass>(); // Maps the fully qualified class names to their Famix model
38
+ private fmxInterfaceMap = new Map<string, Famix.Interface | Famix.ParametricInterface>(); // Maps the interface names to their Famix model
39
+ private fmxModuleMap = new Map<ModuleDeclaration, Famix.Module>(); // Maps the namespace names to their Famix model
40
+ private fmxFileMap = new Map<string, Famix.ScriptEntity | Famix.Module>(); // Maps the source file names to their Famix model
41
+ private fmxTypeMap = new Map<TSMorphTypeDeclaration, Famix.Type | Famix.ParameterType>(); // Maps the types declarations to their Famix model
42
+ private fmxPrimitiveTypeMap = new Map<string, Famix.PrimitiveType>(); // Maps the primitive type names to their Famix model
43
+ private fmxFunctionAndMethodMap = new Map<string, Famix.Function | Famix.ParametricFunction | Famix.Method | Famix.ParametricMethod>; // Maps the function names to their Famix model
44
+ private fmxArrowFunctionMap = new Map<string, Famix.ArrowFunction>; // Maps the function names to their Famix model
45
+ private fmxParameterMap = new Map<ParameterDeclaration, Famix.Parameter>(); // Maps the parameters to their Famix model
46
+ private fmxVariableMap = new Map<VariableDeclaration, Famix.Variable>(); // Maps the variables to their Famix model
47
+ private fmxImportClauseMap = new Map<ImportDeclaration | ImportEqualsDeclaration, Famix.ImportClause>(); // Maps the import clauses to their Famix model
48
+ private fmxEnumMap = new Map<EnumDeclaration, Famix.Enum>(); // Maps the enum names to their Famix model
49
+ private fmxInheritanceMap = new Map<string, Famix.Inheritance>(); // Maps the inheritance names to their Famix model
50
+ private UNKNOWN_VALUE = '(unknown due to parsing error)'; // The value to use when a name is not usable
51
+ public fmxElementObjectMap = new Map<Famix.Entity,TSMorphObjectType>();
52
+ public tsMorphElementObjectMap = new Map<TSMorphObjectType,Famix.Entity>();
53
+
54
+ constructor() {
55
+ this.famixRep.setFmxElementObjectMap(this.fmxElementObjectMap);
56
+ }
57
+
58
+ public addSourceAnchor(fmx: Famix.SourcedEntity, node: TSMorphObjectType): Famix.IndexedFileAnchor {
59
+ const sourceAnchor: Famix.IndexedFileAnchor = new Famix.IndexedFileAnchor();
60
+ let sourceStart, sourceEnd: number;
61
+ if (fmx && node) {
62
+ // find the start and end positions of the source element
63
+ if (!(node instanceof CommentRange)) {
64
+ sourceStart = node.getStart();
65
+ sourceEnd = node.getEnd();
66
+ } else {
67
+ sourceStart = node.getPos();
68
+ sourceEnd = node.getEnd();
69
+ }
70
+
71
+ if (config.expectGraphemes) {
72
+ /**
73
+ * The following logic handles the case of multi-code point characters (e.g. emoji) in the source text.
74
+ * This is needed because Pharo/Smalltalk treats multi-code point characters as a single character,
75
+ * but JavaScript treats them as multiple characters. This means that the start and end positions
76
+ * of a source element in Pharo/Smalltalk will be different than the start and end positions of the
77
+ * same source element in JavaScript. This logic finds the start and end positions of the source
78
+ * element in JavaScript and then uses those positions to set the start and end positions of the
79
+ * Famix index file anchor.
80
+ * It depends on code in the 'grapheme-splitter' package in npm.
81
+ */
82
+ const splitter = new GraphemeSplitter();
83
+ const sourceFileText = node.getSourceFile().getFullText();
84
+ const hasGraphemeClusters = splitter.countGraphemes(sourceFileText) > 1;
85
+ if (hasGraphemeClusters) {
86
+ const sourceElementText = sourceFileText.substring(sourceStart, sourceEnd);
87
+ const sourceElementTextGraphemes = splitter.splitGraphemes(sourceElementText);
88
+ const sourceFileTextGraphemes = splitter.splitGraphemes(sourceFileText);
89
+ const numberOfGraphemeClustersBeforeStart = splitter.countGraphemes(sourceFileText.substring(0, sourceStart));
90
+
91
+ // find the start of the sourceElementTextGraphemes array in the sourceFileTextGraphemes array
92
+ sourceStart = Helpers.indexOfSplitArray({searchArray: sourceFileTextGraphemes,
93
+ targetArray: sourceElementTextGraphemes,
94
+ start: sourceStart - numberOfGraphemeClustersBeforeStart});
95
+ sourceEnd = sourceStart + sourceElementTextGraphemes.length;
96
+ }
97
+ }
98
+
99
+ // The +1 is because the source anchor (Pharo) is 1-based, but ts-morph is 0-based
100
+ sourceAnchor.startPos = sourceStart + 1;
101
+ sourceAnchor.endPos = sourceEnd + 1;
102
+
103
+ let fileName = node.getSourceFile().getFilePath() as string;
104
+ if (fileName.startsWith("/")) {
105
+ fileName = fileName.substring(1);
106
+ }
107
+ sourceAnchor.element = fmx;
108
+ sourceAnchor.fileName = fileName;
109
+ fmx.sourceAnchor = sourceAnchor;
110
+ this.famixRep.addElement(sourceAnchor);
111
+
112
+ }
113
+ return sourceAnchor;
114
+ }
115
+
116
+ /**
117
+ * Makes a Famix index file anchor
118
+ * @param sourceElement A source element
119
+ * @param famixElement The Famix model of the source element
120
+ */
121
+ public makeFamixIndexFileAnchor(sourceElement: TSMorphObjectType, famixElement: Famix.SourcedEntity): void {
122
+ // Famix.Comment is not a named entity (does not have a fullyQualifiedName)
123
+ if (!(famixElement instanceof Famix.Comment)) { // must be a named entity
124
+ // insanity check: named entities should have fullyQualifiedName
125
+ const fullyQualifiedName = (famixElement as Famix.NamedEntity).fullyQualifiedName;
126
+ if (!fullyQualifiedName || fullyQualifiedName === this.UNKNOWN_VALUE) {
127
+ throw new Error(`Famix element ${famixElement.constructor.name} has no valid fullyQualifiedName.`);
128
+ }
129
+ }
130
+
131
+ logger.debug("making index file anchor for '" + sourceElement?.getText() + "' with famixElement " + famixElement.getJSON());
132
+ const fmxIndexFileAnchor = new Famix.IndexedFileAnchor();
133
+ fmxIndexFileAnchor.element = famixElement;
134
+ this.fmxElementObjectMap.set(famixElement, sourceElement);
135
+
136
+ if (sourceElement !== null) {
137
+ const absolutePathProject = this.famixRep.getAbsolutePath();
138
+
139
+ const absolutePath = path.normalize(sourceElement.getSourceFile().getFilePath());
140
+
141
+ const positionNodeModules = absolutePath.indexOf('node_modules');
142
+
143
+ let pathInProject: string = "";
144
+
145
+ if (positionNodeModules !== -1) {
146
+ const pathFromNodeModules = absolutePath.substring(positionNodeModules);
147
+ pathInProject = pathFromNodeModules;
148
+ } else {
149
+ pathInProject = this.convertToRelativePath(absolutePath, absolutePathProject);
150
+ }
151
+
152
+ // revert any backslashes to forward slashes (path.normalize on windows introduces them)
153
+ pathInProject = pathInProject.replace(/\\/g, "/");
154
+
155
+ if (pathInProject.startsWith("/")) {
156
+ pathInProject = pathInProject.substring(1);
157
+ }
158
+
159
+ fmxIndexFileAnchor.fileName = pathInProject;
160
+ let sourceStart, sourceEnd
161
+ // ,sourceLineStart, sourceLineEnd
162
+ : number;
163
+ if (!(sourceElement instanceof CommentRange)) {
164
+ sourceStart = sourceElement.getStart();
165
+ sourceEnd = sourceElement.getEnd();
166
+ // sourceLineStart = sourceElement.getStartLineNumber();
167
+ // sourceLineEnd = sourceElement.getEndLineNumber();
168
+ } else {
169
+ sourceStart = sourceElement.getPos();
170
+ sourceEnd = sourceElement.getEnd();
171
+ }
172
+ if (config.expectGraphemes) {
173
+ /**
174
+ * The following logic handles the case of multi-code point characters (e.g. emoji) in the source text.
175
+ * This is needed because Pharo/Smalltalk treats multi-code point characters as a single character,
176
+ * but JavaScript treats them as multiple characters. This means that the start and end positions
177
+ * of a source element in Pharo/Smalltalk will be different than the start and end positions of the
178
+ * same source element in JavaScript. This logic finds the start and end positions of the source
179
+ * element in JavaScript and then uses those positions to set the start and end positions of the
180
+ * Famix index file anchor.
181
+ * It depends on code in the 'grapheme-splitter' package in npm.
182
+ */
183
+ const splitter = new GraphemeSplitter();
184
+ const sourceFileText = sourceElement.getSourceFile().getFullText();
185
+ const hasGraphemeClusters = splitter.countGraphemes(sourceFileText) > 1;
186
+ if (hasGraphemeClusters) {
187
+ const sourceElementText = sourceFileText.substring(sourceStart, sourceEnd);
188
+ const sourceElementTextGraphemes = splitter.splitGraphemes(sourceElementText);
189
+ const sourceFileTextGraphemes = splitter.splitGraphemes(sourceFileText);
190
+ const numberOfGraphemeClustersBeforeStart = splitter.countGraphemes(sourceFileText.substring(0, sourceStart));
191
+
192
+ // find the start of the sourceElementTextGraphemes array in the sourceFileTextGraphemes array
193
+ sourceStart = Helpers.indexOfSplitArray({searchArray: sourceFileTextGraphemes,
194
+ targetArray: sourceElementTextGraphemes,
195
+ start: sourceStart - numberOfGraphemeClustersBeforeStart});
196
+ sourceEnd = sourceStart + sourceElementTextGraphemes.length;
197
+ }
198
+ }
199
+ // note: the +1 is because the source anchor is 1-based, but ts-morph is 0-based
200
+ fmxIndexFileAnchor.startPos = sourceStart + 1;
201
+ fmxIndexFileAnchor.endPos = sourceEnd + 1;
202
+
203
+ // if (!(famixElement instanceof Famix.ImportClause || famixElement instanceof Famix.Access || famixElement instanceof Famix.Reference || famixElement instanceof Famix.Invocation || famixElement instanceof Famix.Inheritance) && !(famixElement instanceof Famix.Comment) && !(sourceElement instanceof CommentRange) && !(sourceElement instanceof Identifier) && !(sourceElement instanceof ImportSpecifier) && !(sourceElement instanceof ExpressionWithTypeArguments)) {
204
+ // initFQN(sourceElement, famixElement);
205
+ // }
206
+ } else {
207
+ // sourceElement is null
208
+ logger.warn("sourceElement is null for famixElement " + famixElement.getJSON());
209
+ fmxIndexFileAnchor.fileName = "unknown";
210
+ fmxIndexFileAnchor.startPos = 0;
211
+ fmxIndexFileAnchor.endPos = 0;
212
+ }
213
+
214
+ this.famixRep.addElement(fmxIndexFileAnchor);
215
+ }
216
+
217
+ /**
218
+ * Creates or gets a Famix script entity or module
219
+ * @param f A source file
220
+ * @param isModule A boolean indicating if the source file is a module
221
+ * @returns The Famix model of the source file
222
+ */
223
+ public createOrGetFamixFile(f: SourceFile, isModule: boolean): Famix.ScriptEntity | Famix.Module {
224
+ let fmxFile: Famix.ScriptEntity; // | Famix.Module;
225
+
226
+ const fileName = f.getBaseName();
227
+ const fullyQualifiedFilename = f.getFilePath();
228
+ const foundFileName = this.fmxFileMap.get(fullyQualifiedFilename);
229
+ if (!foundFileName) {
230
+ if (isModule) {
231
+ fmxFile = new Famix.Module();
232
+ }
233
+ else {
234
+ fmxFile = new Famix.ScriptEntity();
235
+ }
236
+ fmxFile.name = fileName;
237
+ fmxFile.numberOfLinesOfText = f.getEndLineNumber() - f.getStartLineNumber();
238
+ fmxFile.numberOfCharacters = f.getFullText().length;
239
+
240
+ initFQN(f, fmxFile);
241
+
242
+ this.makeFamixIndexFileAnchor(f, fmxFile);
243
+
244
+ this.fmxFileMap.set(fullyQualifiedFilename, fmxFile);
245
+ this.famixRep.addElement(fmxFile);
246
+ }
247
+ else {
248
+ fmxFile = foundFileName;
249
+ }
250
+
251
+ this.fmxElementObjectMap.set(fmxFile,f);
252
+ return fmxFile;
253
+ }
254
+
255
+ /**
256
+ * Creates or gets a Famix Module
257
+ * @param moduleDeclaration A module
258
+ * @returns The Famix model of the module
259
+ */
260
+ public createOrGetFamixModule(moduleDeclaration: ModuleDeclaration): Famix.Module {
261
+ if (this.fmxModuleMap.has(moduleDeclaration)) {
262
+ const rModule = this.fmxModuleMap.get(moduleDeclaration);
263
+ if (rModule) {
264
+ return rModule;
265
+ } else {
266
+ throw new Error(`Famix module ${moduleDeclaration.getName()} is not found in the module map.`);
267
+ }
268
+ }
269
+
270
+ const fmxModule = new Famix.Module();
271
+ const moduleName = moduleDeclaration.getName();
272
+ fmxModule.name = moduleName;
273
+ fmxModule.isAmbient = isAmbient(moduleDeclaration);
274
+ fmxModule.isNamespace = isNamespace(moduleDeclaration);
275
+ fmxModule.isModule = !fmxModule.isNamespace && !fmxModule.isAmbient;
276
+
277
+ initFQN(moduleDeclaration, fmxModule);
278
+ this.makeFamixIndexFileAnchor(moduleDeclaration, fmxModule);
279
+
280
+ this.fmxModuleMap.set(moduleDeclaration, fmxModule);
281
+
282
+ this.famixRep.addElement(fmxModule);
283
+
284
+ this.fmxElementObjectMap.set(fmxModule,moduleDeclaration);
285
+ return fmxModule;
286
+ }
287
+
288
+ /**
289
+ * Creates a Famix alias
290
+ * @param a An alias
291
+ * @returns The Famix model of the alias
292
+ */
293
+ public createFamixAlias(a: TypeAliasDeclaration): Famix.Alias {
294
+ let fmxAlias: Famix.Alias;
295
+ const aliasName = a.getName();
296
+ //const aliasFullyQualifiedName = a.getType().getText(); // FQNFunctions.getFQN(a);
297
+ const aliasFullyQualifiedName = FQNFunctions.getFQN(a);
298
+ const foundAlias = this.fmxAliasMap.get(aliasFullyQualifiedName);
299
+ if (!foundAlias) {
300
+ fmxAlias = new Famix.Alias();
301
+ fmxAlias.name = a.getName();
302
+ const aliasNameWithGenerics = aliasName + (a.getTypeParameters().length ? ("<" + a.getTypeParameters().map(tp => tp.getName()).join(", ") + ">") : "");
303
+ logger.debug(`> NOTE: alias ${aliasName} has fully qualified name ${aliasFullyQualifiedName} and name with generics ${aliasNameWithGenerics}.`);
304
+
305
+ const fmxType = this.createOrGetFamixType(aliasNameWithGenerics, a);
306
+ fmxAlias.aliasedEntity = fmxType;
307
+ initFQN(a, fmxAlias);
308
+ this.makeFamixIndexFileAnchor(a, fmxAlias);
309
+
310
+ this.fmxAliasMap.set(aliasFullyQualifiedName, fmxAlias);
311
+
312
+ this.famixRep.addElement(fmxAlias);
313
+ }
314
+ else {
315
+ fmxAlias = foundAlias;
316
+ }
317
+ this.fmxElementObjectMap.set(fmxAlias,a);
318
+
319
+ return fmxAlias;
320
+ }
321
+
322
+ /**
323
+ * Creates or gets a Famix class or parameterizable class
324
+ * @param cls A class
325
+ * @returns The Famix model of the class
326
+ */
327
+ public createOrGetFamixClass(cls: ClassDeclaration): Famix.Class | Famix.ParametricClass {
328
+ let fmxClass: Famix.Class | Famix.ParametricClass;
329
+ const isAbstract = cls.isAbstract();
330
+ const classFullyQualifiedName = FQNFunctions.getFQN(cls);
331
+ const clsName = cls.getName() || this.UNKNOWN_VALUE;
332
+ const isGeneric = cls.getTypeParameters().length;
333
+ const foundClass = this.fmxClassMap.get(classFullyQualifiedName);
334
+ if (!foundClass) {
335
+ if (isGeneric) {
336
+ fmxClass = new Famix.ParametricClass();
337
+ }
338
+ else {
339
+ fmxClass = new Famix.Class();
340
+ }
341
+
342
+ fmxClass.name = clsName;
343
+ initFQN(cls, fmxClass);
344
+ // fmxClass.fullyQualifiedName = classFullyQualifiedName;
345
+ fmxClass.isAbstract = isAbstract;
346
+
347
+ this.makeFamixIndexFileAnchor(cls, fmxClass);
348
+
349
+ this.fmxClassMap.set(classFullyQualifiedName, fmxClass);
350
+
351
+ this.famixRep.addElement(fmxClass);
352
+
353
+ this.fmxElementObjectMap.set(fmxClass,cls);
354
+ }
355
+ else {
356
+ fmxClass = foundClass;
357
+ }
358
+
359
+ return fmxClass;
360
+ }
361
+
362
+ /**
363
+ * Creates or gets a Famix interface or parameterizable interface
364
+ * @param inter An interface
365
+ * @returns The Famix model of the interface
366
+ */
367
+ public createOrGetFamixInterface(inter: InterfaceDeclaration): Famix.Interface | Famix.ParametricInterface {
368
+
369
+ let fmxInterface: Famix.Interface | Famix.ParametricInterface;
370
+ const interName = inter.getName();
371
+ const interFullyQualifiedName = FQNFunctions.getFQN(inter);
372
+ const foundInterface = this.fmxInterfaceMap.get(interFullyQualifiedName);
373
+ if (!foundInterface) {
374
+ const isGeneric = inter.getTypeParameters().length;
375
+ if (isGeneric) {
376
+ fmxInterface = new Famix.ParametricInterface();
377
+ }
378
+ else {
379
+ fmxInterface = new Famix.Interface();
380
+ }
381
+
382
+ fmxInterface.name = interName;
383
+ initFQN(inter, fmxInterface);
384
+ this.makeFamixIndexFileAnchor(inter, fmxInterface);
385
+
386
+ this.fmxInterfaceMap.set(interFullyQualifiedName, fmxInterface);
387
+
388
+ this.famixRep.addElement(fmxInterface);
389
+
390
+ this.fmxElementObjectMap.set(fmxInterface,inter);
391
+ }
392
+ else {
393
+ fmxInterface = foundInterface;
394
+ }
395
+ return fmxInterface;
396
+ }
397
+
398
+
399
+ /**
400
+ * Creates or gets a Famix concrete element
401
+ * @param concreteElement A parametric Element
402
+ * @param concreteElementDeclaration the element declaration
403
+ * @param concreteArguments concrete arguments
404
+ * @returns A parametric Element
405
+ */
406
+ // public createOrGetFamixConcreteElement(concreteElement : ParametricVariantType,
407
+ // concreteElementDeclaration : ConcreteElementTSMorphType,
408
+ // concreteArguments: TypeNode[]): ParametricVariantType {
409
+
410
+ // let fullyQualifiedFilename = concreteElement.fullyQualifiedName;
411
+ // let params = "";
412
+
413
+ // concreteArguments.map((param) => {
414
+ // params = params+param.getText()+',';
415
+ // });
416
+
417
+ // params = params.substring(0, params.length - 1)
418
+
419
+ // fullyQualifiedFilename = Helpers.replaceLastBetweenTags(fullyQualifiedFilename,params);
420
+
421
+ // let concElement: ParametricVariantType;
422
+
423
+ // if (!this.fmxInterfaceMap.has(fullyQualifiedFilename) &&
424
+ // !this.fmxClassMap.has(fullyQualifiedFilename) &&
425
+ // !this.fmxFunctionAndMethodMap.has(fullyQualifiedFilename)){
426
+ // concElement = _.cloneDeep(concreteElement);
427
+ // concElement.fullyQualifiedName = fullyQualifiedFilename;
428
+ // concElement.clearGenericParameters();
429
+ // concreteArguments.map((param) => {
430
+ // const parameter = this.createOrGetFamixConcreteType(param);
431
+ // concElement.addConcreteParameter(parameter);
432
+ // })
433
+
434
+ // if (concreteElement instanceof Famix.ParametricClass) {
435
+ // this.fmxClassMap.set(fullyQualifiedFilename, concElement as Famix.ParametricClass);
436
+ // } else if (concreteElement instanceof Famix.ParametricInterface) {
437
+ // this.fmxInterfaceMap.set(fullyQualifiedFilename, concElement as Famix.ParametricInterface);
438
+ // } else if (concreteElement instanceof Famix.ParametricFunction) {
439
+ // this.fmxFunctionAndMethodMap.set(fullyQualifiedFilename, concElement as Famix.ParametricFunction);
440
+ // } else { // if (concreteElement instanceof Famix.ParametricMethod) {
441
+ // this.fmxFunctionAndMethodMap.set(fullyQualifiedFilename, concElement as Famix.ParametricMethod);
442
+ // }
443
+ // this.famixRep.addElement(concElement);
444
+ // this.fmxElementObjectMap.set(concElement,concreteElementDeclaration);
445
+ // } else {
446
+ // if (concreteElement instanceof Famix.ParametricClass) {
447
+ // concElement = this.fmxClassMap.get(fullyQualifiedFilename) as Famix.ParametricClass;
448
+ // } else if (concreteElement instanceof Famix.ParametricInterface) {
449
+ // concElement = this.fmxInterfaceMap.get(fullyQualifiedFilename) as Famix.ParametricInterface;
450
+ // } else if (concreteElement instanceof Famix.ParametricFunction) {
451
+ // concElement = this.fmxFunctionAndMethodMap.get(fullyQualifiedFilename) as Famix.ParametricFunction;
452
+ // } else { // if (concreteElement instanceof Famix.ParametricMethod) {
453
+ // concElement = this.fmxFunctionAndMethodMap.get(fullyQualifiedFilename) as Famix.ParametricMethod;
454
+ // }
455
+ // }
456
+ // return concElement;
457
+ // }
458
+
459
+ /**
460
+ * Creates a Famix property
461
+ * @param property A property
462
+ * @returns The Famix model of the property
463
+ */
464
+ public createFamixProperty(property: PropertyDeclaration | PropertySignature): Famix.Property {
465
+ const fmxProperty = new Famix.Property();
466
+ const isSignature = property instanceof PropertySignature;
467
+ fmxProperty.name = property.getName();
468
+
469
+ let propTypeName = this.UNKNOWN_VALUE;
470
+ try {
471
+ propTypeName = property.getType().getText().trim();
472
+ } catch (error) {
473
+ logger.error(`> WARNING: got exception ${error}. Failed to get usable name for property: ${property.getName()}. Continuing...`);
474
+ }
475
+
476
+ const fmxType = this.createOrGetFamixType(propTypeName, property);
477
+ fmxProperty.declaredType = fmxType;
478
+
479
+ // add the visibility (public, private, etc.) to the fmxProperty
480
+ fmxProperty.visibility = "";
481
+
482
+ property.getModifiers().forEach(m => {
483
+ switch (m.getText()) {
484
+ case Scope.Public:
485
+ fmxProperty.visibility = "public";
486
+ break;
487
+ case Scope.Protected:
488
+ fmxProperty.visibility = "protected";
489
+ break;
490
+ case Scope.Private:
491
+ fmxProperty.visibility = "private";
492
+ break;
493
+ case "static":
494
+ fmxProperty.isClassSide = true;
495
+ break;
496
+ case "readonly":
497
+ fmxProperty.readOnly = true;
498
+ break;
499
+ default:
500
+ break;
501
+ }
502
+ });
503
+
504
+ if (!isSignature && property.getExclamationTokenNode()) {
505
+ fmxProperty.isDefinitelyAssigned = true;
506
+ }
507
+ if (property.getQuestionTokenNode()) {
508
+ fmxProperty.isOptional = true;
509
+ }
510
+ if (property.getName().substring(0, 1) === "#") {
511
+ fmxProperty.isJavaScriptPrivate = true;
512
+ }
513
+
514
+ initFQN(property, fmxProperty);
515
+ this.makeFamixIndexFileAnchor(property, fmxProperty);
516
+
517
+ this.famixRep.addElement(fmxProperty);
518
+
519
+ this.fmxElementObjectMap.set(fmxProperty,property);
520
+
521
+ return fmxProperty;
522
+ }
523
+
524
+ /**
525
+ * Creates a Famix method or accessor
526
+ * @param method A method or an accessor
527
+ * @param currentCC The cyclomatic complexity metrics of the current source file
528
+ * @returns The Famix model of the method or the accessor
529
+ */
530
+ public createOrGetFamixMethod(method: MethodDeclaration | ConstructorDeclaration | MethodSignature | GetAccessorDeclaration | SetAccessorDeclaration, currentCC: { [key: string]: number }): Famix.Method | Famix.Accessor | Famix.ParametricMethod {
531
+ let fmxMethod: Famix.Method | Famix.Accessor | Famix.ParametricMethod;
532
+ const isGeneric = method.getTypeParameters().length > 0;
533
+ const functionFullyQualifiedName = FQNFunctions.getFQN(method);
534
+ if (!this.fmxFunctionAndMethodMap.has(functionFullyQualifiedName)) {
535
+
536
+ if (method instanceof GetAccessorDeclaration || method instanceof SetAccessorDeclaration) {
537
+ fmxMethod = new Famix.Accessor();
538
+ const isGetter = method instanceof GetAccessorDeclaration;
539
+ const isSetter = method instanceof SetAccessorDeclaration;
540
+ if (isGetter) {(fmxMethod as Famix.Accessor).kind = "getter";}
541
+ if (isSetter) {(fmxMethod as Famix.Accessor).kind = "setter";}
542
+ this.famixRep.addElement(fmxMethod);
543
+ }
544
+ else {
545
+ if (isGeneric) {
546
+ fmxMethod = new Famix.ParametricMethod();
547
+ }
548
+ else {
549
+ fmxMethod = new Famix.Method();
550
+ }
551
+ }
552
+ const isConstructor = method instanceof ConstructorDeclaration;
553
+ const isSignature = method instanceof MethodSignature;
554
+
555
+ let isAbstract = false;
556
+ let isStatic = false;
557
+ if (method instanceof MethodDeclaration || method instanceof GetAccessorDeclaration || method instanceof SetAccessorDeclaration) {
558
+ isAbstract = method.isAbstract();
559
+ isStatic = method.isStatic();
560
+ }
561
+
562
+ if (isConstructor) {(fmxMethod as Famix.Accessor).kind = "constructor";}
563
+ fmxMethod.isAbstract = isAbstract;
564
+ fmxMethod.isClassSide = isStatic;
565
+ fmxMethod.isPrivate = (method instanceof MethodDeclaration || method instanceof GetAccessorDeclaration || method instanceof SetAccessorDeclaration) ? (method.getModifiers().find(x => x.getText() === 'private')) !== undefined : false;
566
+ fmxMethod.isProtected = (method instanceof MethodDeclaration || method instanceof GetAccessorDeclaration || method instanceof SetAccessorDeclaration) ? (method.getModifiers().find(x => x.getText() === 'protected')) !== undefined : false;
567
+ fmxMethod.signature = Helpers.computeSignature(method.getText());
568
+
569
+ let methodName: string;
570
+ if (isConstructor) {
571
+ methodName = "constructor";
572
+ }
573
+ else {
574
+ methodName = (method as MethodDeclaration | MethodSignature | GetAccessorDeclaration | SetAccessorDeclaration).getName();
575
+ }
576
+ fmxMethod.name = methodName;
577
+
578
+ if (!isConstructor) {
579
+ if (method.getName().substring(0, 1) === "#") {
580
+ fmxMethod.isPrivate = true;
581
+ }
582
+ }
583
+
584
+ if (!fmxMethod.isPrivate && !fmxMethod.isProtected) {
585
+ fmxMethod.isPublic = true;
586
+ }
587
+ else {
588
+ fmxMethod.isPublic = false;
589
+ }
590
+
591
+ if (!isSignature) {
592
+ fmxMethod.cyclomaticComplexity = currentCC[fmxMethod.name];
593
+ }
594
+ else {
595
+ fmxMethod.cyclomaticComplexity = 0;
596
+ }
597
+
598
+ let methodTypeName = this.UNKNOWN_VALUE;
599
+ try {
600
+ methodTypeName = method.getReturnType().getText().trim();
601
+ } catch (error) {
602
+ logger.error(`> WARNING: got exception ${error}. Failed to get usable name for return type of method: ${fmxMethod.name}. Continuing...`);
603
+ }
604
+
605
+ const fmxType = this.createOrGetFamixType(methodTypeName, method);
606
+ fmxMethod.declaredType = fmxType;
607
+ fmxMethod.numberOfLinesOfCode = method.getEndLineNumber() - method.getStartLineNumber();
608
+ const parameters = method.getParameters();
609
+ fmxMethod.numberOfParameters = parameters.length;
610
+
611
+ if (!isSignature) {
612
+ fmxMethod.numberOfStatements = method.getStatements().length;
613
+ }
614
+ else {
615
+ fmxMethod.numberOfStatements = 0;
616
+ }
617
+
618
+ initFQN(method, fmxMethod);
619
+ this.famixRep.addElement(fmxMethod);
620
+ this.makeFamixIndexFileAnchor(method, fmxMethod);
621
+
622
+ this.fmxFunctionAndMethodMap.set(functionFullyQualifiedName, fmxMethod);
623
+ }
624
+ else {
625
+ fmxMethod = this.fmxFunctionAndMethodMap.get(functionFullyQualifiedName) as (Famix.Method | Famix.Accessor | Famix.ParametricMethod);
626
+ }
627
+
628
+ this.fmxElementObjectMap.set(fmxMethod,method);
629
+
630
+ return fmxMethod;
631
+ }
632
+
633
+ /**
634
+ * Creates a Famix function
635
+ * @param func A function
636
+ * @param currentCC The cyclomatic complexity metrics of the current source file
637
+ * @returns The Famix model of the function
638
+ */
639
+ public createOrGetFamixFunction(func: FunctionDeclaration | FunctionExpression, currentCC: { [key: string]: number }): Famix.Function | Famix.ParametricFunction {
640
+ let fmxFunction: Famix.Function | Famix.ParametricFunction;
641
+ const isGeneric = func.getTypeParameters().length > 0;
642
+ const functionFullyQualifiedName = FQNFunctions.getFQN(func);
643
+ if (!this.fmxFunctionAndMethodMap.has(functionFullyQualifiedName)) {
644
+ if (isGeneric) {
645
+ fmxFunction = new Famix.ParametricFunction();
646
+ }
647
+ else {
648
+ fmxFunction = new Famix.Function();
649
+ }
650
+
651
+ const name = func.getName();
652
+ if (name) {
653
+ fmxFunction.name = name;
654
+ }
655
+ else {
656
+ fmxFunction.name = "anonymous";
657
+ }
658
+
659
+ fmxFunction.signature = Helpers.computeSignature(func.getText());
660
+ fmxFunction.cyclomaticComplexity = currentCC[fmxFunction.name];
661
+ initFQN(func, fmxFunction);
662
+ // fmxFunction.fullyQualifiedName = functionFullyQualifiedName;
663
+
664
+ let functionTypeName = this.UNKNOWN_VALUE;
665
+ try {
666
+ functionTypeName = func.getReturnType().getText().trim();
667
+ } catch (error) {
668
+ logger.error(`> WARNING: got exception ${error}. Failed to get usable name for return type of function: ${func.getName()}. Continuing...`);
669
+ }
670
+
671
+ const fmxType = this.createOrGetFamixType(functionTypeName, func);
672
+ fmxFunction.declaredType = fmxType;
673
+ fmxFunction.numberOfLinesOfCode = func.getEndLineNumber() - func.getStartLineNumber();
674
+ const parameters = func.getParameters();
675
+ fmxFunction.numberOfParameters = parameters.length;
676
+ fmxFunction.numberOfStatements = func.getStatements().length;
677
+ this.makeFamixIndexFileAnchor(func, fmxFunction);
678
+
679
+ this.famixRep.addElement(fmxFunction);
680
+
681
+ this.fmxElementObjectMap.set(fmxFunction,func);
682
+
683
+ this.fmxFunctionAndMethodMap.set(functionFullyQualifiedName, fmxFunction);
684
+ }
685
+ else {
686
+ fmxFunction = this.fmxFunctionAndMethodMap.get(functionFullyQualifiedName) as (Famix.Function | Famix.ParametricFunction);
687
+ }
688
+
689
+ return fmxFunction;
690
+ }
691
+
692
+ /**
693
+ * Creates a Famix parameter
694
+ * @param param A parameter
695
+ * @returns The Famix model of the parameter
696
+ */
697
+ public createOrGetFamixParameter(param: ParameterDeclaration): Famix.Parameter {
698
+ if (this.fmxParameterMap.has(param)) {
699
+ const rParameter = this.fmxParameterMap.get(param);
700
+ if (rParameter) {
701
+ return rParameter;
702
+ } else {
703
+ throw new Error(`Famix parameter ${param.getName()} is not found in the parameter map.`);
704
+ }
705
+ }
706
+
707
+ const fmxParam = new Famix.Parameter();
708
+
709
+ let paramTypeName = this.UNKNOWN_VALUE;
710
+ try {
711
+ paramTypeName = param.getType().getText().trim();
712
+ } catch (error) {
713
+ logger.error(`> WARNING: got exception ${error}. Failed to get usable name for parameter: ${param.getName()}. Continuing...`);
714
+ }
715
+
716
+ const fmxType = this.createOrGetFamixType(paramTypeName, param);
717
+ fmxParam.declaredType = fmxType;
718
+ fmxParam.name = param.getName();
719
+
720
+ initFQN(param, fmxParam);
721
+ this.makeFamixIndexFileAnchor(param, fmxParam);
722
+
723
+ this.famixRep.addElement(fmxParam);
724
+
725
+ this.fmxElementObjectMap.set(fmxParam, param);
726
+ this.fmxParameterMap.set(param, fmxParam);
727
+
728
+ return fmxParam;
729
+ }
730
+
731
+ /**
732
+ * Creates a Famix type parameter
733
+ * @param tp A type parameter
734
+ * @returns The Famix model of the type parameter
735
+ */
736
+ public createFamixParameterType(tp: TypeParameterDeclaration): Famix.ParameterType {
737
+
738
+ const fmxParameterType = new Famix.ParameterType();
739
+
740
+ fmxParameterType.name = tp.getName();
741
+ initFQN(tp, fmxParameterType);
742
+ this.makeFamixIndexFileAnchor(tp, fmxParameterType);
743
+
744
+ this.famixRep.addElement(fmxParameterType);
745
+
746
+ this.fmxElementObjectMap.set(fmxParameterType,tp);
747
+
748
+ return fmxParameterType;
749
+ }
750
+
751
+ /**
752
+ * Creates a Famix type in the context of concretizations
753
+ * @param typeName A type name
754
+ * @param element An element
755
+ * @returns The Famix model of the type
756
+ */
757
+ public createOrGetFamixConcreteType(element: TypeNode):
758
+ Famix.ParameterType | Famix.PrimitiveType | Famix.Class | Famix.Interface {
759
+ // TODO - refactor to stop using names as a key in the maps, use ts-morph element instead
760
+ const typeParameterDeclaration = element.getSymbol()?.getDeclarations()[0] as TypeParameterDeclaration;
761
+ const parameterTypeName : string = element.getText();
762
+ let fmxParameterType: Famix.Type | Famix.Class | Famix.Interface | undefined = undefined;
763
+
764
+ // get a TypeReference from a TypeNode
765
+ const typeReference = element.getType();
766
+ // get a TypeDeclaration from a TypeReference
767
+ const typeDeclaration = typeReference.getSymbol()?.getDeclarations()[0] as TSMorphTypeDeclaration;
768
+
769
+ let isClassOrInterface = false;
770
+ if (this.fmxClassMap.has(parameterTypeName)){
771
+ this.fmxClassMap.forEach((obj, name) => {
772
+ if(obj instanceof Famix.ParametricClass){
773
+ if (name === element.getText() && obj.genericParameters.size>0) {
774
+ fmxParameterType = obj;
775
+ isClassOrInterface = true;
776
+ }
777
+ } else {
778
+ if (name === element.getText()) {
779
+ fmxParameterType = obj;
780
+ isClassOrInterface = true;
781
+ }
782
+ }
783
+ });
784
+ }
785
+
786
+ if (this.fmxInterfaceMap.has(parameterTypeName)){
787
+ this.fmxInterfaceMap.forEach((obj, name) => {
788
+ if(obj instanceof Famix.ParametricInterface){
789
+ if (name === element.getText() && obj.genericParameters.size>0) {
790
+ fmxParameterType = obj;
791
+ isClassOrInterface = true;
792
+ }
793
+ } else {
794
+ if (name === element.getText()) {
795
+ fmxParameterType = obj;
796
+ isClassOrInterface = true;
797
+ }
798
+ }
799
+ });
800
+ }
801
+
802
+ if(!isClassOrInterface){
803
+ if (!this.fmxTypeMap.has(typeDeclaration)) {
804
+ // TODO refactor
805
+ if (isPrimitiveType(parameterTypeName)) {
806
+ fmxParameterType = new Famix.PrimitiveType();
807
+ fmxParameterType.isStub = true;
808
+ } else {
809
+ fmxParameterType = new Famix.ParameterType();
810
+ }
811
+
812
+ fmxParameterType.name = parameterTypeName;
813
+ this.famixRep.addElement(fmxParameterType);
814
+ this.fmxTypeMap.set(typeDeclaration, fmxParameterType);
815
+ this.fmxElementObjectMap.set(fmxParameterType,typeParameterDeclaration);
816
+ }
817
+ else {
818
+ const result = this.fmxTypeMap.get(typeDeclaration);
819
+ if (result) {
820
+ fmxParameterType = result;
821
+ } else {
822
+ throw new Error(`Famix type ${typeDeclaration} is not found in the Type map.`);
823
+ }
824
+ }
825
+ }
826
+ if (!fmxParameterType) {
827
+ throw new Error(`fmxParameterType was undefined for parameterTypeName ${parameterTypeName}`);
828
+ }
829
+ return fmxParameterType;
830
+ }
831
+
832
+ /**
833
+ * Creates a Famix variable
834
+ * @param variable A variable
835
+ * @returns The Famix model of the variable
836
+ */
837
+ public createOrGetFamixVariable(variable: VariableDeclaration): Famix.Variable {
838
+ if (this.fmxVariableMap.has(variable)) {
839
+ const rVariable = this.fmxVariableMap.get(variable);
840
+ if (rVariable) {
841
+ return rVariable;
842
+ } else {
843
+ throw new Error(`Famix parameter ${variable.getName()} is not found in the variable map.`);
844
+ }
845
+ }
846
+ const fmxVariable = new Famix.Variable();
847
+
848
+ let variableTypeName = this.UNKNOWN_VALUE;
849
+ try {
850
+ variableTypeName = variable.getType().getText().trim();
851
+ } catch (error) {
852
+ logger.error(`> WARNING: got exception ${error}. Failed to get usable name for variable: ${variable.getName()}. Continuing...`);
853
+ }
854
+
855
+ const fmxType = this.createOrGetFamixType(variableTypeName, variable);
856
+ fmxVariable.declaredType = fmxType;
857
+ fmxVariable.name = variable.getName();
858
+ initFQN(variable, fmxVariable);
859
+ this.makeFamixIndexFileAnchor(variable, fmxVariable);
860
+
861
+ this.famixRep.addElement(fmxVariable);
862
+
863
+ this.fmxElementObjectMap.set(fmxVariable,variable);
864
+ this.fmxVariableMap.set(variable, fmxVariable);
865
+
866
+ return fmxVariable;
867
+ }
868
+
869
+ /**
870
+ * Creates a Famix enum
871
+ * @param enumEntity An enum
872
+ * @returns The Famix model of the enum
873
+ */
874
+ public createOrGetFamixEnum(enumEntity: EnumDeclaration): Famix.Enum {
875
+ if (this.fmxEnumMap.has(enumEntity)) {
876
+ const rEnum = this.fmxEnumMap.get(enumEntity);
877
+ if (rEnum) {
878
+ return rEnum;
879
+ } else {
880
+ throw new Error(`Famix enum ${enumEntity.getName()} is not found in the enum map.`);
881
+ }
882
+ }
883
+ const fmxEnum = new Famix.Enum();
884
+ fmxEnum.name = enumEntity.getName();
885
+ initFQN(enumEntity, fmxEnum);
886
+ this.makeFamixIndexFileAnchor(enumEntity, fmxEnum);
887
+
888
+ this.famixRep.addElement(fmxEnum);
889
+
890
+ this.fmxElementObjectMap.set(fmxEnum,enumEntity);
891
+ this.fmxEnumMap.set(enumEntity, fmxEnum);
892
+
893
+ return fmxEnum;
894
+ }
895
+
896
+ /**
897
+ * Creates a Famix enum value
898
+ * @param enumMember An enum member
899
+ * @returns The Famix model of the enum member
900
+ */
901
+ public createFamixEnumValue(enumMember: EnumMember): Famix.EnumValue {
902
+ const fmxEnumValue = new Famix.EnumValue();
903
+
904
+ let enumValueTypeName = this.UNKNOWN_VALUE;
905
+ try {
906
+ enumValueTypeName = enumMember.getType().getText().trim();
907
+ } catch (error) {
908
+ logger.error(`> WARNING: got exception ${error}. Failed to get usable name for enum value: ${enumMember.getName()}. Continuing...`);
909
+ }
910
+
911
+ const fmxType = this.createOrGetFamixType(enumValueTypeName, enumMember);
912
+ fmxEnumValue.declaredType = fmxType;
913
+ fmxEnumValue.name = enumMember.getName();
914
+ initFQN(enumMember, fmxEnumValue);
915
+ this.makeFamixIndexFileAnchor(enumMember, fmxEnumValue);
916
+
917
+ this.famixRep.addElement(fmxEnumValue);
918
+
919
+ this.fmxElementObjectMap.set(fmxEnumValue,enumMember);
920
+
921
+ return fmxEnumValue;
922
+ }
923
+
924
+ /**
925
+ * Creates or gets a Famix decorator
926
+ * @param decorator A decorator
927
+ * @param decoratedEntity A class, a method, a parameter or a property
928
+ * @returns The Famix model of the decorator
929
+ */
930
+ public createOrGetFamixDecorator(decorator: Decorator, decoratedEntity: ClassDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | ParameterDeclaration | PropertyDeclaration): Famix.Decorator {
931
+ const fmxDecorator = new Famix.Decorator();
932
+ const decoratorName = "@" + decorator.getName();
933
+ const decoratorExpression = decorator.getText().substring(1);
934
+
935
+ fmxDecorator.name = decoratorName;
936
+ fmxDecorator.decoratorExpression = decoratorExpression;
937
+ const decoratedEntityFullyQualifiedName = FQNFunctions.getFQN(decoratedEntity);
938
+ const fmxDecoratedEntity = this.famixRep.getFamixEntityByFullyQualifiedName(decoratedEntityFullyQualifiedName) as Famix.NamedEntity;
939
+ fmxDecorator.decoratedEntity = fmxDecoratedEntity;
940
+ initFQN(decorator, fmxDecorator);
941
+ this.makeFamixIndexFileAnchor(decorator, fmxDecorator);
942
+
943
+ this.famixRep.addElement(fmxDecorator);
944
+
945
+ this.fmxElementObjectMap.set(fmxDecorator,decorator);
946
+
947
+ return fmxDecorator;
948
+ }
949
+
950
+ /**
951
+ * Creates a Famix comment
952
+ * @param comment A comment
953
+ * @param fmxScope The Famix model of the comment's container
954
+ * @param isJSDoc A boolean indicating if the comment is a JSDoc
955
+ * @returns The Famix model of the comment
956
+ */
957
+ public createFamixComment(comment: CommentRange, fmxScope: Famix.NamedEntity, isJSDoc: boolean): Famix.Comment {
958
+ logger.debug(`> NOTE: creating comment ${comment.getText()} in scope ${fmxScope.name}.`);
959
+ const fmxComment = new Famix.Comment();
960
+ fmxComment.container = fmxScope; // adds comment to the container's comments collection
961
+ fmxComment.isJSDoc = isJSDoc;
962
+
963
+ this.makeFamixIndexFileAnchor(comment, fmxComment);
964
+
965
+ this.famixRep.addElement(fmxComment);
966
+
967
+ this.fmxElementObjectMap.set(fmxComment,comment);
968
+
969
+ return fmxComment;
970
+ }
971
+
972
+ /**
973
+ * Creates or gets a Famix type
974
+ * @param typeName A type name
975
+ * @param element A ts-morph element
976
+ * @returns The Famix model of the type
977
+ */
978
+ public createOrGetFamixType(typeName: string, element: TSMorphTypeDeclaration): Famix.Type {
979
+ let fmxType: Famix.Type;
980
+ const isPrimitive = isPrimitiveType(typeName);
981
+ const isParametricType =
982
+ element instanceof ClassDeclaration && element.getTypeParameters().length > 0 ||
983
+ element instanceof InterfaceDeclaration && element.getTypeParameters().length > 0;
984
+
985
+ // Functions and methods aren't types!
986
+ // ||
987
+ // element instanceof FunctionDeclaration && element.getTypeParameters().length > 0 ||
988
+ // element instanceof MethodDeclaration && element.getTypeParameters().length > 0 ||
989
+ // element instanceof ArrowFunction && element.getTypeParameters().length > 0;
990
+
991
+ logger.debug("Creating (or getting) type: '" + typeName + "' of element: " + element?.getText() + " of kind: " + element?.getKindName());
992
+
993
+ if (isPrimitive) {
994
+ return this.createOrGetFamixPrimitiveType(typeName);
995
+ }
996
+
997
+ if (isParametricType) {
998
+ // narrow the type
999
+ const parametricElement = element as TSMorphParametricType;
1000
+ return this.createOrGetFamixParametricType(typeName, parametricElement);
1001
+ }
1002
+
1003
+ if (!this.fmxTypeMap.has(element)) {
1004
+ let ancestor: Famix.ContainerEntity | undefined = undefined;
1005
+
1006
+ if (element !== undefined) {
1007
+ const typeAncestor = Helpers.findTypeAncestor(element);
1008
+ if (!typeAncestor) {
1009
+ throw new Error(`Ancestor not found for element ${element.getText()}.`);
1010
+ }
1011
+ const ancestorFullyQualifiedName = FQNFunctions.getFQN(typeAncestor);
1012
+ ancestor = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName) as Famix.ContainerEntity;
1013
+ if (!ancestor) {
1014
+ logger.debug(`Ancestor ${FQNFunctions.getFQN(typeAncestor)} not found. Adding the new type.`);
1015
+ ancestor = this.createOrGetFamixType(typeAncestor.getText(), typeAncestor as TSMorphTypeDeclaration);
1016
+ }
1017
+ }
1018
+
1019
+ fmxType = new Famix.Type();
1020
+ fmxType.name = typeName;
1021
+
1022
+ if (!ancestor) {
1023
+ throw new Error(`Ancestor not found for type ${typeName}.`);
1024
+ }
1025
+ fmxType.container = ancestor;
1026
+ initFQN(element, fmxType);
1027
+ this.makeFamixIndexFileAnchor(element, fmxType);
1028
+
1029
+ this.famixRep.addElement(fmxType);
1030
+
1031
+ this.fmxTypeMap.set(element, fmxType);
1032
+ }
1033
+ else {
1034
+ const result = this.fmxTypeMap.get(element);
1035
+ if (result) {
1036
+ fmxType = result;
1037
+ } else {
1038
+ throw new Error(`Famix type ${typeName} is not found in the Type map.`);
1039
+ }
1040
+ }
1041
+
1042
+ this.fmxElementObjectMap.set(fmxType,element);
1043
+
1044
+ return fmxType;
1045
+ }
1046
+
1047
+ /**
1048
+ * Creates or gets a Famix type that is parametric
1049
+ * @param typeName A type name
1050
+ * @param element A ts-morph element
1051
+ * @returns The Famix model of the parameter type
1052
+ */
1053
+ createOrGetFamixParametricType(typeName: string, element: TSMorphParametricType): Famix.Type {
1054
+
1055
+ if (this.fmxTypeMap.has(element) === true) {
1056
+ const result = this.fmxTypeMap.get(element);
1057
+ if (result) {
1058
+ return result;
1059
+ } else {
1060
+ throw new Error(`Famix type ${typeName} is not found (undefined) in the Type map.`);
1061
+ }
1062
+ }
1063
+
1064
+ // A parametric type is a type that has type parameters, e.g., List<T>
1065
+ // In TS it can be a class, an interface, a function, an arrow function, or a method
1066
+
1067
+ // create the Famix Parametric Type (maybe it's just an Interface, etc.)
1068
+ let fmxType: Famix.Type;
1069
+
1070
+ if (element instanceof ClassDeclaration) {
1071
+ fmxType = new Famix.ParametricClass();
1072
+ } else if (element instanceof InterfaceDeclaration) {
1073
+ fmxType = new Famix.ParametricInterface();
1074
+ }
1075
+ // functions and methods are not types
1076
+ // else if (element instanceof FunctionDeclaration) {
1077
+ // fmxType = new Famix.ParametricFunction();
1078
+ // } else if (element instanceof ArrowFunction) {
1079
+ // fmxType = new Famix.ParametricArrowFunction();
1080
+ // } else if (element instanceof MethodDeclaration) {
1081
+ // fmxType = new Famix.ParametricMethod();
1082
+ // }
1083
+ else {
1084
+ throw new Error(`Element is not a class, interface, function, arrow function, or method.`);
1085
+ }
1086
+
1087
+ // const parameters = element.getTypeParameters();
1088
+
1089
+ // // for each parameter, getOrCreate the FamixParameterType
1090
+ // for (const parameter of parameters) {
1091
+ // this.createOrGetFamixParameterType(parameter.getName(), parameter);
1092
+ // }
1093
+
1094
+ // // TODO: the following code is not correct, it is just a placeholder
1095
+ // const parameterTypeNames = typeName.substring(typeName.indexOf("<") + 1, typeName.indexOf(">"))
1096
+ // .split(",").map(s => s.trim());
1097
+ // const baseTypeName = typeName.substring(0, typeName.indexOf("<")).trim();
1098
+ // parameterTypeNames.forEach(parameterTypeName => {
1099
+ // const fmxParameterType = this.createOrGetFamixParameterType(parameterTypeName, element);
1100
+ // (fmxType as Famix.ParameterType).addArgument(fmxParameterType);
1101
+ // });
1102
+ // const fmxBaseType = this.createOrGetFamixType(baseTypeName, element);
1103
+
1104
+ // (fmxType as Famix.ParameterType).baseType = fmxBaseType;
1105
+
1106
+ fmxType.name = typeName;
1107
+ initFQN(element, fmxType);
1108
+ this.famixRep.addElement(fmxType);
1109
+ this.fmxTypeMap.set(element, fmxType);
1110
+ return fmxType;
1111
+ }
1112
+
1113
+ /**
1114
+ * Creates a type for a parameter in a parametric type, e.g., T in List<T>
1115
+ * @param parameterTypeName
1116
+ * @param element the TypeScript element (TSMorphParametricType) that the type is associated with
1117
+ * @returns
1118
+ */
1119
+ // createOrGetFamixParameterType(parameterTypeName: string, element: ParameterDeclaration) {
1120
+ // if (this.fmxTypeMap.has(element)) {
1121
+ // return this.fmxTypeMap.get(element) as Famix.ParameterType;
1122
+ // }
1123
+
1124
+ // // determine if element is a
1125
+ // const fmxType = new Famix.ParameterType();
1126
+ // // const parameterTypeNames = typeName.substring(typeName.indexOf("<") + 1, typeName.indexOf(">"))
1127
+ // // .split(",").map(s => s.trim());
1128
+ // // const baseTypeName = typeName.substring(0, typeName.indexOf("<")).trim();
1129
+ // // parameterTypeNames.forEach(parameterTypeName => {
1130
+ // // const fmxParameterType = this.createOrGetFamixParameterType(parameterTypeName, element);
1131
+ // // (fmxType as Famix.ParameterType).addArgument(fmxParameterType);
1132
+ // // });
1133
+ // const fmxBaseType = this.createOrGetFamixType(baseTypeName, element);
1134
+ // (fmxType as Famix.ParameterType).baseType = fmxBaseType;
1135
+ // initFQN(element, fmxType);
1136
+ // this.famixRep.addElement(fmxType);
1137
+ // this.fmxTypeMap.set(element, fmxType);
1138
+ // return fmxType;
1139
+ // }
1140
+
1141
+ /**
1142
+ * Creates or gets a Famix primitive type
1143
+ * @param typeName A type name
1144
+ * @returns The Famix model of the primitive type
1145
+ */
1146
+ createOrGetFamixPrimitiveType(typeName: string): Famix.PrimitiveType {
1147
+ let fmxType: Famix.PrimitiveType = new Famix.PrimitiveType();
1148
+ if (!this.fmxPrimitiveTypeMap.has(typeName)) {
1149
+ fmxType = new Famix.PrimitiveType();
1150
+ fmxType.isStub = true;
1151
+ fmxType.name = typeName;
1152
+ this.fmxPrimitiveTypeMap.set(typeName, fmxType);
1153
+ this.famixRep.addElement(fmxType);
1154
+ } else {
1155
+ fmxType = this.fmxPrimitiveTypeMap.get(typeName) as Famix.PrimitiveType;
1156
+ }
1157
+ return fmxType;
1158
+ }
1159
+
1160
+ /**
1161
+ * Creates a Famix access
1162
+ * @param node A node
1163
+ * @param id An id of a parameter, a variable, a property or an enum member
1164
+ */
1165
+ public createFamixAccess(node: Identifier, id: number): void {
1166
+ const fmxVar = this.famixRep.getFamixEntityById(id) as Famix.StructuralEntity;
1167
+ if (!fmxVar) {
1168
+ throw new Error(`Famix entity with id ${id} not found, for node ${node.getText()} in ${node.getSourceFile().getBaseName()} at line ${node.getStartLineNumber()}.`);
1169
+ }
1170
+
1171
+ logger.debug(`Creating FamixAccess. Node: [${node.getKindName()}] '${node.getText()}' at line ${node.getStartLineNumber()} in ${node.getSourceFile().getBaseName()}, id: ${id} refers to fmxVar '${fmxVar.fullyQualifiedName}'.`);
1172
+
1173
+ const nodeReferenceAncestor = Helpers.findAncestor(node);
1174
+ const ancestorFullyQualifiedName = FQNFunctions.getFQN(nodeReferenceAncestor);
1175
+ const accessor = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName) as Famix.ContainerEntity;
1176
+ if (!accessor) {
1177
+ logger.error(`Ancestor ${ancestorFullyQualifiedName} of kind ${nodeReferenceAncestor.getKindName()} not found.`);
1178
+ // accessor = this.createOrGetFamixType(ancestorFullyQualifiedName, nodeReferenceAncestor as TypeDeclaration);
1179
+ return; // bail out TODO: this is probably wrong
1180
+ } else {
1181
+ logger.debug(`Found accessor to be ${accessor.fullyQualifiedName}.`);
1182
+ }
1183
+
1184
+
1185
+ // make sure accessor is a method, function, script or module
1186
+ if (!(accessor instanceof Famix.Method) && !(accessor instanceof Famix.ArrowFunction) && !(accessor instanceof Famix.Function) && !(accessor instanceof Famix.ScriptEntity) && !(accessor instanceof Famix.Module)) {
1187
+ logger.error(`Accessor ${accessor.fullyQualifiedName} is not a method, function, etc.`);
1188
+ return;
1189
+ }
1190
+
1191
+ // don't create any duplicates (e.g. if the same variable is accessed multiple times by same accessor)
1192
+ const foundAccess = this.famixRep.getFamixAccessByAccessorAndVariable(accessor, fmxVar);
1193
+ if (foundAccess) {
1194
+ logger.debug(`FamixAccess already exists for accessor ${accessor.fullyQualifiedName} and variable ${fmxVar.fullyQualifiedName}.`);
1195
+ return;
1196
+ }
1197
+ const fmxAccess = new Famix.Access();
1198
+ fmxAccess.accessor = accessor;
1199
+ fmxAccess.variable = fmxVar;
1200
+
1201
+ this.famixRep.addElement(fmxAccess);
1202
+
1203
+ this.fmxElementObjectMap.set(fmxAccess,node);
1204
+ }
1205
+
1206
+ /**
1207
+ * Creates a Famix invocation
1208
+ * @param nodeReferringToInvocable A node
1209
+ * @param invocable A method or a function
1210
+ * @param id The id of the method or the function
1211
+ */
1212
+ public createFamixInvocation(nodeReferringToInvocable: Identifier, invocable: InvocableType, id: number): void {
1213
+ const fmxInvocable = this.famixRep.getFamixEntityById(id) as Famix.BehavioralEntity;
1214
+ // since the node is in the AST, we need to find the ancestor that is in the Famix model
1215
+ const containerOfNode = Helpers.findAncestor(nodeReferringToInvocable);
1216
+ logger.debug(`Found container (ancestor) ${containerOfNode.getKindName()} for AST node ${nodeReferringToInvocable.getText()}.`);
1217
+ const containerFQN = FQNFunctions.getFQN(containerOfNode);
1218
+ logger.debug(`Found containerFQN ${containerFQN}.`);
1219
+ let sender = this.famixRep.getFamixEntityByFullyQualifiedName(containerFQN) as Famix.ContainerEntity;
1220
+ logger.debug(`Found a sender that matches ${sender.fullyQualifiedName}.`);
1221
+ if (sender instanceof Famix.Type) {
1222
+ // TODO this might be an error in getFamixEntityByFullyQualifiedName
1223
+ logger.debug(`Oops! Sender is a type, which is not valid for an Invocation. Trying to find a container for ${sender.fullyQualifiedName}.`);
1224
+ const senderContainer = sender.container;
1225
+ if (senderContainer) {
1226
+ sender = senderContainer;
1227
+ }
1228
+ }
1229
+ const receiverFullyQualifiedName = FQNFunctions.getFQN(invocable.getParent());
1230
+ const receiver = this.famixRep.getFamixEntityByFullyQualifiedName(receiverFullyQualifiedName) as Famix.NamedEntity;
1231
+
1232
+ const fmxInvocation = new Famix.Invocation();
1233
+ fmxInvocation.sender = sender;
1234
+ fmxInvocation.receiver = receiver;
1235
+ fmxInvocation.addCandidate(fmxInvocable);
1236
+ fmxInvocation.signature = fmxInvocable.signature;
1237
+
1238
+ this.famixRep.addElement(fmxInvocation);
1239
+
1240
+ this.fmxElementObjectMap.set(fmxInvocation,nodeReferringToInvocable);
1241
+ }
1242
+
1243
+ /**
1244
+ * Creates a Famix inheritance
1245
+ * @param cls A class or an interface (subclass)
1246
+ * @param inhClass The inherited class or interface (superclass)
1247
+ */
1248
+ public createOrGetFamixInheritance(cls: ClassDeclaration | InterfaceDeclaration, inhClass: ClassDeclaration | InterfaceDeclaration | ExpressionWithTypeArguments): void {
1249
+ // // need a key to see if the inheritance already exists
1250
+ // const classFullyQualifiedName = FQNFunctions.getFQN(cls);
1251
+ // let inKeyword: string;
1252
+ // let inhClassFullyQualifiedName: string;
1253
+ // let inhClassName: string | undefined;
1254
+ // // if inhClass is an ExpressionWithTypeArguments, it is an interface
1255
+ // if (inhClass instanceof ExpressionWithTypeArguments) {
1256
+ // inhClassName = inhClass.getExpression().getText();
1257
+ // // what is inhClassFullyQualifiedName? TODO
1258
+ // inhClassFullyQualifiedName = 'Undefined_Scope_from_importer.' + inhClassName;
1259
+ // } else {
1260
+ // inhClassName = inhClass.getName();
1261
+ // if (!inhClassName) {
1262
+ // throw new Error(`Inherited class or interface name not found for ${inhClass.getText()}.`);
1263
+ // }
1264
+ // inhClassFullyQualifiedName = FQNFunctions.getFQN(inhClass);
1265
+ // }
1266
+ // // build the unique key of the inheritance
1267
+ // if ((cls instanceof ClassDeclaration && inhClass instanceof ClassDeclaration)
1268
+ // || (cls instanceof InterfaceDeclaration && inhClass instanceof InterfaceDeclaration)) {
1269
+ // inKeyword = " extends ";
1270
+ // } else if (cls instanceof ClassDeclaration && (inhClass instanceof InterfaceDeclaration || inhClass instanceof ExpressionWithTypeArguments)) {
1271
+ // inKeyword = " implements ";
1272
+ // } else {
1273
+ // throw new Error(`Inheritance ${cls.getText()} and ${inhClass.getText()} is not valid.`);
1274
+ // }
1275
+
1276
+ // const inheritanceFullyQualifiedName = FQNFunctions.getFQN(cls) + inKeyword + inhClassFullyQualifiedName;
1277
+ // if (this.fmxInheritanceMap.has(inheritanceFullyQualifiedName)) {
1278
+ // const rInheritance = this.fmxInheritanceMap.get(inheritanceFullyQualifiedName);
1279
+ // if (rInheritance) {
1280
+ // return; // don't do anything
1281
+ // } else {
1282
+ // throw new Error(`Inheritance ${cls.getText()} is not found in the inheritance map.`);
1283
+ // }
1284
+ // }
1285
+
1286
+ const fmxInheritance = new Famix.Inheritance();
1287
+
1288
+ // logger.debug(`createFamixInheritance: classFullyQualifiedName: class fqn = ${classFullyQualifiedName}`);
1289
+
1290
+ let subClass: Famix.Class | Famix.Interface | undefined;
1291
+ if (cls instanceof ClassDeclaration) {
1292
+ subClass = this.createOrGetFamixClass(cls);
1293
+ }
1294
+ else {
1295
+ subClass = this.createOrGetFamixInterface(cls);
1296
+ }
1297
+
1298
+ if (!subClass) {
1299
+ throw new Error(`Subclass ${cls} not found in Class or Interface maps.`);
1300
+ }
1301
+
1302
+ let superClass: Famix.Class | Famix.Interface | undefined;
1303
+ // if (inhClass instanceof ClassDeclaration || inhClass instanceof InterfaceDeclaration) {
1304
+ // inhClassName = inhClass.getName();
1305
+ // if (!inhClassName) {
1306
+ // throw new Error(`Inherited class or interface name not found for ${inhClass.getText()}.`);
1307
+ // }
1308
+ // inhClassFullyQualifiedName = FQNFunctions.getFQN(inhClass);
1309
+ // if (inhClass instanceof ClassDeclaration) {
1310
+ // superClass = this.fmxClassMap.get(inhClassFullyQualifiedName);
1311
+ // }
1312
+ // else {
1313
+ // superClass = this.fmxInterfaceMap.get(inhClassFullyQualifiedName);
1314
+ // }
1315
+ // if (!superClass) {
1316
+ // throw new Error(`Superclass ${classFullyQualifiedName} not found in Class or Interface maps.`);
1317
+ // }
1318
+ // }
1319
+
1320
+ // // it shouldn't add the class/interface again to the Map, it should use createOrGet (?)
1321
+ // if (superClass === undefined) {
1322
+
1323
+ if (inhClass instanceof ClassDeclaration) {
1324
+ // superClass = new Famix.Class();
1325
+ superClass = this.createOrGetFamixClass(inhClass);
1326
+ // this.fmxClassMap.set(inhClassFullyQualifiedName, superClass);
1327
+ }
1328
+ else if (inhClass instanceof InterfaceDeclaration) {
1329
+ // superClass = new Famix.Interface();
1330
+ superClass = this.createOrGetFamixInterface(inhClass);
1331
+ // this.fmxInterfaceMap.set(inhClassFullyQualifiedName, superClass);
1332
+ } else {
1333
+ // inhClass instanceof ExpressionWithTypeArguments
1334
+ const interfaceDeclaration = getInterfaceDeclarationFromExpression(inhClass);
1335
+ if (interfaceDeclaration !== undefined) {
1336
+ superClass = this.createOrGetFamixInterface(interfaceDeclaration);
1337
+ } else {
1338
+ throw new Error(`Interface declaration not found for ${inhClass.getText()}.`);
1339
+ }
1340
+ }
1341
+
1342
+ this.fmxElementObjectMap.set(superClass, inhClass);
1343
+
1344
+ this.makeFamixIndexFileAnchor(inhClass, superClass);
1345
+
1346
+ this.famixRep.addElement(superClass);
1347
+
1348
+ fmxInheritance.subclass = subClass;
1349
+ fmxInheritance.superclass = superClass;
1350
+
1351
+ this.famixRep.addElement(fmxInheritance);
1352
+ // no FQN for inheritance
1353
+
1354
+ // We don't map inheritance to the source code element because there are two elements (super, sub)
1355
+ // this.fmxElementObjectMap.set(fmxInheritance, null);
1356
+
1357
+ }
1358
+
1359
+ public createFamixImportClause(importedEntity: Famix.NamedEntity, importingEntity: Famix.Module) {
1360
+ const fmxImportClause = new Famix.ImportClause();
1361
+ fmxImportClause.importedEntity = importedEntity;
1362
+ fmxImportClause.importingEntity = importingEntity;
1363
+ importingEntity.addOutgoingImport(fmxImportClause);
1364
+ this.famixRep.addElement(fmxImportClause);
1365
+ }
1366
+
1367
+ /**
1368
+ * Creates a Famix import clause
1369
+ * @param importClauseInfo The information needed to create a Famix import clause
1370
+ * @param importDeclaration The import declaration
1371
+ * @param importer A source file which is a module
1372
+ * @param moduleSpecifierFilePath The path of the module where the export declaration is
1373
+ * @param importElement The imported entity
1374
+ * @param isInExports A boolean indicating if the imported entity is in the exports
1375
+ * @param isDefaultExport A boolean indicating if the imported entity is a default export
1376
+ */
1377
+ public oldCreateOrGetFamixImportClause(importClauseInfo: {importDeclaration?: ImportDeclaration | ImportEqualsDeclaration, importerSourceFile: SourceFile, moduleSpecifierFilePath: string, importElement: ImportSpecifier | Identifier, isInExports: boolean, isDefaultExport: boolean}): void {
1378
+ const {importDeclaration, importerSourceFile: importer, moduleSpecifierFilePath, importElement, isInExports, isDefaultExport} = importClauseInfo;
1379
+ if (importDeclaration && this.fmxImportClauseMap.has(importDeclaration)) {
1380
+ const rImportClause = this.fmxImportClauseMap.get(importDeclaration);
1381
+ if (rImportClause) {
1382
+ return; // don't do anything
1383
+ } else {
1384
+ throw new Error(`Import clause ${importElement.getText()} is not found in the import clause map.`);
1385
+ }
1386
+ }
1387
+
1388
+ logger.info(`creating a new FamixImportClause for ${importDeclaration?.getText()} in ${importer.getBaseName()}.`);
1389
+ const fmxImportClause = new Famix.ImportClause();
1390
+
1391
+ let importedEntity: Famix.NamedEntity | Famix.StructuralEntity | undefined = undefined;
1392
+ let importedEntityName: string;
1393
+
1394
+ const absolutePathProject = this.famixRep.getAbsolutePath();
1395
+
1396
+ const absolutePath = path.normalize(moduleSpecifierFilePath);
1397
+ // convert the path and remove any windows backslashes introduced by path.normalize
1398
+ logger.debug(`createFamixImportClause: absolutePath: ${absolutePath}`);
1399
+ logger.debug(`createFamixImportClause: convertToRelativePath: ${this.convertToRelativePath(absolutePath, absolutePathProject)}`);
1400
+ const pathInProject: string = this.convertToRelativePath(absolutePath, absolutePathProject).replace(/\\/g, "/");
1401
+ logger.debug(`createFamixImportClause: pathInProject: ${pathInProject}`);
1402
+ let pathName = "{" + pathInProject + "}.";
1403
+ logger.debug(`createFamixImportClause: pathName: ${pathName}`);
1404
+
1405
+ // Named imports, e.g. import { ClassW } from "./complexExportModule";
1406
+
1407
+ // Start with simple import clause (without referring to the actual variable)
1408
+
1409
+ if (importDeclaration instanceof ImportDeclaration
1410
+ && importElement instanceof ImportSpecifier) {
1411
+ importedEntityName = importElement.getName();
1412
+ pathName = pathName + importedEntityName;
1413
+ if (isInExports) {
1414
+ importedEntity = this.famixRep.getFamixEntityByFullyQualifiedName(pathName) as Famix.NamedEntity;
1415
+ }
1416
+ if (importedEntity === undefined) {
1417
+ importedEntity = new Famix.NamedEntity();
1418
+ importedEntity.name = importedEntityName;
1419
+ if (!isInExports) {
1420
+ importedEntity.isStub = true;
1421
+ }
1422
+ // logger.debug(`createFamixImportClause: Creating named entity ${importedEntityName} with FQN ${pathName}`);
1423
+ // importedEntity.fullyQualifiedName = pathName;
1424
+ initFQN(importElement, importedEntity);
1425
+
1426
+ this.makeFamixIndexFileAnchor(importElement, importedEntity);
1427
+ // must add entity to repository
1428
+ this.famixRep.addElement(importedEntity);
1429
+ }
1430
+ }
1431
+ // handle import equals declarations, e.g. import myModule = require("./complexExportModule");
1432
+ // TypeScript can't determine the type of the imported module, so we create a Module entity
1433
+ else if (importDeclaration instanceof ImportEqualsDeclaration) {
1434
+ importedEntityName = importDeclaration?.getName();
1435
+ pathName = pathName + importedEntityName;
1436
+ importedEntity = new Famix.StructuralEntity();
1437
+ importedEntity.name = importedEntityName;
1438
+ initFQN(importDeclaration, importedEntity);
1439
+ this.makeFamixIndexFileAnchor(importElement, importedEntity);
1440
+ // importedEntity.fullyQualifiedName = pathName;
1441
+ const anyType = this.createOrGetFamixType('any', importDeclaration);
1442
+ (importedEntity as Famix.StructuralEntity).declaredType = anyType;
1443
+ } else { // default imports, e.g. import ClassW from "./complexExportModule";
1444
+ importedEntityName = importElement.getText();
1445
+ pathName = pathName + (isDefaultExport ? "defaultExport" : "namespaceExport");
1446
+ importedEntity = new Famix.NamedEntity();
1447
+ importedEntity.name = importedEntityName;
1448
+ // importedEntity.fullyQualifiedName = pathName;
1449
+ initFQN(importElement, importedEntity);
1450
+ this.makeFamixIndexFileAnchor(importElement, importedEntity);
1451
+ }
1452
+ // I don't think it should be added to the repository if it exists already
1453
+ if (!isInExports) this.famixRep.addElement(importedEntity);
1454
+ const importerFullyQualifiedName = FQNFunctions.getFQN(importer);
1455
+ const fmxImporter = this.famixRep.getFamixEntityByFullyQualifiedName(importerFullyQualifiedName) as Famix.Module;
1456
+ fmxImportClause.importingEntity = fmxImporter;
1457
+ fmxImportClause.importedEntity = importedEntity;
1458
+ if (importDeclaration instanceof ImportEqualsDeclaration) {
1459
+ fmxImportClause.moduleSpecifier = importDeclaration?.getModuleReference().getText() as string;
1460
+ } else {
1461
+ fmxImportClause.moduleSpecifier = importDeclaration?.getModuleSpecifierValue() as string;
1462
+ }
1463
+
1464
+ logger.debug(`createFamixImportClause: ${fmxImportClause.importedEntity?.name} (of type ${
1465
+ Helpers.getSubTypeName(fmxImportClause.importedEntity)}) is imported by ${fmxImportClause.importingEntity?.name}`);
1466
+
1467
+ fmxImporter.addOutgoingImport(fmxImportClause);
1468
+
1469
+ this.famixRep.addElement(fmxImportClause);
1470
+
1471
+ if (importDeclaration) {
1472
+ this.fmxElementObjectMap.set(fmxImportClause, importDeclaration);
1473
+ this.fmxImportClauseMap.set(importDeclaration, fmxImportClause);
1474
+ }
1475
+ }
1476
+
1477
+ /**
1478
+ * Creates a Famix Arrow Function
1479
+ * @param arrowExpression An Expression
1480
+ * @returns The Famix model of the variable
1481
+ */
1482
+ public createOrGetFamixArrowFunction(arrowExpression: Expression, currentCC: { [key: string]: number } ): Famix.ArrowFunction | Famix.ParametricArrowFunction {
1483
+
1484
+ let fmxArrowFunction: Famix.ArrowFunction | Famix.ParametricArrowFunction;
1485
+ const functionFullyQualifiedName = FQNFunctions.getFQN(arrowExpression);
1486
+
1487
+ if (!this.fmxFunctionAndMethodMap.has(functionFullyQualifiedName)) {
1488
+
1489
+ const arrowFunction = arrowExpression.asKindOrThrow(SyntaxKind.ArrowFunction);
1490
+
1491
+ const isGeneric = arrowFunction.getTypeParameters().length > 0;
1492
+
1493
+ if (isGeneric) {
1494
+ fmxArrowFunction = new Famix.ParametricArrowFunction();
1495
+ }
1496
+ else {
1497
+ fmxArrowFunction = new Famix.ArrowFunction();
1498
+ }
1499
+
1500
+ // Get the parent of the arrow function (the variable declaration)
1501
+ const parent = arrowFunction.getParentIfKind(SyntaxKind.VariableDeclaration);
1502
+ let functionName = '(NO_NAME)';
1503
+
1504
+ if (parent && parent instanceof VariableDeclaration) {
1505
+ // Get the name of the variable
1506
+ functionName = parent.getName();
1507
+ }
1508
+
1509
+ if (functionName) {
1510
+ fmxArrowFunction.name = functionName;
1511
+ }
1512
+ else {
1513
+ fmxArrowFunction.name = "anonymous";
1514
+ }
1515
+
1516
+ // Signature of an arrow function is (parameters) => return_type
1517
+ const parametersSignature = arrowFunction.getParameters().map(p => p.getText()).join(", ");
1518
+ const returnTypeSignature = arrowFunction.getReturnType().getText();
1519
+ fmxArrowFunction.signature = `(${parametersSignature}) => ${returnTypeSignature}`;
1520
+ fmxArrowFunction.cyclomaticComplexity = currentCC[fmxArrowFunction.name];
1521
+
1522
+ let functionTypeName = this.UNKNOWN_VALUE;
1523
+ try {
1524
+ functionTypeName = arrowFunction.getReturnType().getText().trim();
1525
+ } catch (error) {
1526
+ logger.error(`> WARNING: got exception ${error}. Failed to get usable name for return type of function: ${functionName}. Continuing...`);
1527
+ }
1528
+
1529
+ const fmxType = this.createOrGetFamixType(functionTypeName, arrowFunction as unknown as FunctionDeclaration);
1530
+ fmxArrowFunction.declaredType = fmxType;
1531
+ fmxArrowFunction.numberOfLinesOfCode = arrowFunction.getEndLineNumber() - arrowFunction.getStartLineNumber();
1532
+ const parameters = arrowFunction.getParameters();
1533
+ fmxArrowFunction.numberOfParameters = parameters.length;
1534
+ fmxArrowFunction.numberOfStatements = arrowFunction.getStatements().length;
1535
+ initFQN(arrowExpression as unknown as TSMorphObjectType, fmxArrowFunction);
1536
+ this.makeFamixIndexFileAnchor(arrowExpression as unknown as TSMorphObjectType, fmxArrowFunction);
1537
+ this.famixRep.addElement(fmxArrowFunction);
1538
+ this.fmxElementObjectMap.set(fmxArrowFunction,arrowFunction as unknown as TSMorphObjectType);
1539
+ this.fmxFunctionAndMethodMap.set(functionFullyQualifiedName, fmxArrowFunction);
1540
+ } else {
1541
+ fmxArrowFunction = this.fmxFunctionAndMethodMap.get(functionFullyQualifiedName) as Famix.ArrowFunction;
1542
+ }
1543
+
1544
+ return fmxArrowFunction;
1545
+ }
1546
+
1547
+ /**
1548
+ * Creates a Famix concretisation
1549
+ * @param cls A class
1550
+ * @returns The Famix model of the concretisation
1551
+ */
1552
+ // public createFamixConcretisation(conEntity : Famix.ParametricClass | Famix.ParametricInterface | Famix.ParametricFunction | Famix.ParametricMethod ,genEntity : Famix.ParametricClass | Famix.ParametricInterface | Famix.ParametricFunction | Famix.ParametricMethod): Famix.Concretisation {
1553
+
1554
+ // const fmxConcretisation : Famix.Concretisation = new Famix.Concretisation();
1555
+
1556
+ // fmxConcretisation.concreteEntity = conEntity;
1557
+ // fmxConcretisation.genericEntity = genEntity;
1558
+ // // this.fmxElementObjectMap.set(fmxConcretisation,null);
1559
+ // this.famixRep.addElement(fmxConcretisation);
1560
+ // const parameterConcretisation = this.createFamixParameterConcretisation(fmxConcretisation);
1561
+
1562
+ // return fmxConcretisation;
1563
+ // }
1564
+
1565
+ /**
1566
+ * Creates a Famix concretisation
1567
+ * @param concretisation A FamixConcretisation
1568
+ * @returns The Famix model of the ParameterConcrestisation
1569
+ */
1570
+ // public createFamixParameterConcretisation(concretisation: Famix.Concretisation): Famix.ParameterConcretisation | undefined{
1571
+ // const conClass = concretisation.concreteEntity;
1572
+ // const genClass = concretisation.genericEntity;
1573
+ // logger.debug(`Creating parameter concretisation between ${conClass.fullyQualifiedName} and ${genClass.fullyQualifiedName}`);
1574
+ // const parameterConcretisations = this.famixRep._getAllEntitiesWithType("ParameterConcretisation") as Set<Famix.ParameterConcretisation>;
1575
+ // const concreteParameters = conClass.concreteParameters;
1576
+ // const genericParameters = genClass.genericParameters;
1577
+
1578
+ // let conClassTypeParametersIterator = concreteParameters.values();
1579
+ // let genClassTypeParametersIterator = genericParameters.values();
1580
+ // let fmxParameterConcretisation : Famix.ParameterConcretisation | undefined = undefined;
1581
+
1582
+ // for (let i = 0; i < genericParameters.size; i++) {
1583
+ // const conClassTypeParameter = conClassTypeParametersIterator.next().value as Famix.ParameterType;
1584
+ // const genClassTypeParameter = genClassTypeParametersIterator.next().value as Famix.ParameterType;
1585
+ // let createParameterConcretisation : boolean = true;
1586
+ // if(conClassTypeParameter && genClassTypeParameter && conClassTypeParameter.name != genClassTypeParameter.name){
1587
+ // parameterConcretisations.forEach((param : Famix.ParameterConcretisation) => {
1588
+ // if (conClassTypeParameter.name == param.concreteParameter.name && genClassTypeParameter.name == param.genericParameter.name) {
1589
+ // createParameterConcretisation = false;
1590
+ // fmxParameterConcretisation = param;
1591
+ // }
1592
+ // })
1593
+ // if (createParameterConcretisation) {
1594
+ // fmxParameterConcretisation = new Famix.ParameterConcretisation();
1595
+ // fmxParameterConcretisation.genericParameter = genClassTypeParameter;
1596
+ // fmxParameterConcretisation.concreteParameter = conClassTypeParameter;
1597
+ // fmxParameterConcretisation.addConcretisation(concretisation);
1598
+ // // this.fmxElementObjectMap.set(fmxParameterConcretisation,null);
1599
+ // } else {
1600
+ // if (!fmxParameterConcretisation) {
1601
+ // throw new Error(`fmxParameterConcretisation was undefined for concretisation with generic parameter ${genClassTypeParameter.name} and concrete parameter ${conClassTypeParameter.name}`);
1602
+ // }
1603
+ // fmxParameterConcretisation.addConcretisation(concretisation);
1604
+ // }
1605
+ // this.famixRep.addElement(fmxParameterConcretisation);
1606
+ // }
1607
+ // }
1608
+ // if (!fmxParameterConcretisation) {
1609
+ // logger.error(`fmxParameterConcretisation was undefined for concretisation with concrete entity ${conClass.fullyQualifiedName} and generic entity ${genClass.fullyQualifiedName}`);
1610
+ // }
1611
+ // return fmxParameterConcretisation;
1612
+
1613
+ // }
1614
+
1615
+ /**
1616
+ * Creates a Famix concretisation between two classes or two interfaces
1617
+ * @param element A class or an Interface
1618
+ */
1619
+ // public createFamixConcretisationClassOrInterfaceSpecialisation(element: ClassDeclaration | InterfaceDeclaration){
1620
+
1621
+ // const superEntity = element.getExtends();
1622
+ // let superEntityArray;
1623
+ // if (superEntity){
1624
+ // superEntityArray = Array.isArray(superEntity) ? superEntity : [superEntity];
1625
+ // }
1626
+ // if (superEntityArray && superEntityArray.length > 0) {
1627
+ // superEntityArray.forEach(entity => {
1628
+ // let entityIsGeneric;
1629
+ // const superEntitySymbol = entity.getExpression().getSymbolOrThrow();
1630
+ // let superEntityDeclaration;
1631
+ // if (superEntity instanceof ExpressionWithTypeArguments) {
1632
+ // superEntityDeclaration = superEntitySymbol.getDeclarations()[0].asKind(ts.SyntaxKind.ClassDeclaration);
1633
+ // } else {
1634
+ // superEntityDeclaration = superEntitySymbol.getDeclarations()[0].asKind(ts.SyntaxKind.InterfaceDeclaration);
1635
+ // }
1636
+ // if (superEntityDeclaration) {
1637
+ // entityIsGeneric = superEntityDeclaration.getTypeParameters().length > 0;
1638
+ // }
1639
+ // if (entityIsGeneric) {
1640
+ // let EntityDeclaration;
1641
+ // let genEntity;
1642
+ // if (superEntity instanceof ExpressionWithTypeArguments) {
1643
+ // EntityDeclaration = entity.getExpression().getSymbol()?.getDeclarations()[0] as ClassDeclaration;
1644
+ // genEntity = this.createOrGetFamixClass(EntityDeclaration) as Famix.ParametricClass;
1645
+ // } else {
1646
+ // EntityDeclaration = entity.getExpression().getSymbol()?.getDeclarations()[0] as InterfaceDeclaration;
1647
+ // genEntity = this.createOrGetFamixInterface(EntityDeclaration) as Famix.ParametricInterface;
1648
+ // }
1649
+ // const genParams = EntityDeclaration.getTypeParameters().map((param) => param.getText());
1650
+ // const args = element.getHeritageClauses()[0].getTypeNodes()[0].getTypeArguments()
1651
+ // const conParams = element.getHeritageClauses()[0].getTypeNodes()[0].getTypeArguments().map((param) => param.getText());
1652
+ // if (!Helpers.arraysAreEqual(conParams,genParams)) {
1653
+ // let conEntity;
1654
+ // conEntity = this.createOrGetFamixConcreteElement(genEntity,EntityDeclaration,args);
1655
+ // const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1656
+ // let createConcretisation : boolean = true;
1657
+ // concretisations.forEach((conc : Famix.Concretisation) => {
1658
+ // if (genEntity.fullyQualifiedName == conc.genericEntity.fullyQualifiedName && conc.concreteEntity.fullyQualifiedName == conEntity.fullyQualifiedName){
1659
+ // createConcretisation = false;
1660
+ // }
1661
+ // });
1662
+
1663
+ // if (createConcretisation) {
1664
+ // const fmxConcretisation : Famix.Concretisation = this.createFamixConcretisation(conEntity,genEntity);
1665
+ // }
1666
+ // }
1667
+ // }
1668
+ // });
1669
+ // }
1670
+ // // TODO: This function seems unfinished
1671
+ // }
1672
+
1673
+
1674
+ /**
1675
+ * Creates a Famix concretisation between a class and its instanciations
1676
+ * @param cls A class
1677
+ */
1678
+ // public createFamixConcretisationGenericInstantiation(cls: ClassDeclaration){
1679
+
1680
+ // const isGeneric = cls.getTypeParameters().length > 0;
1681
+ // if (isGeneric) {
1682
+ // const instances = cls.getSourceFile().getDescendantsOfKind(ts.SyntaxKind.NewExpression)
1683
+ // .filter(newExpr => {
1684
+ // const expression = newExpr.getExpression();
1685
+ // return expression.getText() === cls.getName();
1686
+ // });
1687
+
1688
+ // instances.forEach(instance => {
1689
+ // const instanceIsGeneric = instance.getTypeArguments().length > 0;
1690
+ // if (instanceIsGeneric) {
1691
+ // const conParams = instance.getTypeArguments().map((param) => param.getText());
1692
+ // const genEntity = this.createOrGetFamixClass(cls) as Famix.ParametricClass;
1693
+ // const genParams = cls.getTypeParameters().map((param) => param.getText());
1694
+ // if (!Helpers.arraysAreEqual(conParams,genParams)) {
1695
+ // let conEntity;
1696
+ // conEntity = this.createOrGetFamixConcreteElement(genEntity,cls,instance.getTypeArguments());
1697
+ // const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1698
+ // let createConcretisation : boolean = true;
1699
+ // concretisations.forEach((conc : Famix.Concretisation) => {
1700
+ // if (genEntity.fullyQualifiedName == conc.genericEntity.fullyQualifiedName && conc.concreteEntity.fullyQualifiedName == conEntity.fullyQualifiedName){
1701
+ // createConcretisation = false;
1702
+ // }
1703
+ // });
1704
+
1705
+ // if (createConcretisation) {
1706
+ // const fmxConcretisation : Famix.Concretisation = this.createFamixConcretisation(conEntity,genEntity);
1707
+ // }
1708
+ // }
1709
+ // }
1710
+ // })
1711
+ // }
1712
+ // // TODO: This function seems unfinished
1713
+ // }
1714
+
1715
+ /**
1716
+ * Creates a Famix concretisation between a class and its instanciations
1717
+ * @param func A function
1718
+ */
1719
+ // public createFamixConcretisationFunctionInstantiation(element: FunctionDeclaration | MethodDeclaration){
1720
+ // const isGeneric = element.getTypeParameters().length > 0;
1721
+ // if (isGeneric) {
1722
+ // const genParams = element.getTypeParameters().map(param => param.getText());
1723
+ // const uses = element.findReferencesAsNodes();
1724
+ // uses.forEach(usage => {
1725
+ // let currentNode: Node | undefined = usage;
1726
+
1727
+ // while (currentNode) {
1728
+ // if (currentNode.getKind() === SyntaxKind.CallExpression) {
1729
+ // const callExpression = currentNode.asKind(SyntaxKind.CallExpression);
1730
+ // if (!callExpression) {
1731
+ // throw new Error(`CallExpression not found for ${currentNode.getText()}`);
1732
+ // }
1733
+ // const instanceIsGeneric = callExpression.getTypeArguments().length > 0;
1734
+ // if (instanceIsGeneric) {
1735
+ // const args = callExpression.getTypeArguments();
1736
+ // const conParams = callExpression.getTypeArguments().map(param => param.getText());
1737
+ // if (!Helpers.arraysAreEqual(conParams,genParams)) {
1738
+ // let genElement;
1739
+ // if(element instanceof FunctionDeclaration){
1740
+ // genElement = this.createOrGetFamixFunction(element, {}) as Famix.ParametricFunction;
1741
+ // } else {
1742
+ // genElement = this.createOrGetFamixMethod(element, {}) as Famix.ParametricMethod;
1743
+ // }
1744
+ // let concElement;
1745
+ // concElement = this.createOrGetFamixConcreteElement(genElement,element,args);
1746
+ // const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1747
+ // let createConcretisation : boolean = true;
1748
+ // concretisations.forEach((conc : Famix.Concretisation) => {
1749
+ // if (genElement.fullyQualifiedName == conc.genericEntity.fullyQualifiedName && conc.concreteEntity.fullyQualifiedName == concElement.fullyQualifiedName){
1750
+ // createConcretisation = false;
1751
+ // }
1752
+ // });
1753
+
1754
+ // if (createConcretisation) {
1755
+ // const fmxConcretisation : Famix.Concretisation = this.createFamixConcretisation(concElement,genElement);
1756
+ // }
1757
+ // }
1758
+ // }
1759
+ // break;
1760
+ // }
1761
+ // // Remonter à l'élément parent (utile si le nœud de référence est un enfant)
1762
+ // currentNode = currentNode.getParent();
1763
+ // }
1764
+ // });
1765
+ // }
1766
+ // }
1767
+
1768
+ /**
1769
+ * Creates a Famix concretisation between a class and an interface
1770
+ * @param cls A class
1771
+ */
1772
+ // public createFamixConcretisationInterfaceClass(cls: ClassDeclaration){
1773
+
1774
+ // const superInterfaces = cls.getImplements();
1775
+ // superInterfaces.forEach(interfaceType => {
1776
+ // const interfaceIsGeneric = interfaceType.getTypeArguments().length>0;
1777
+ // if (interfaceIsGeneric) {
1778
+ // const interfaceDeclaration = interfaceType.getExpression().getSymbol()?.getDeclarations()[0] as InterfaceDeclaration;
1779
+ // const genParams = interfaceDeclaration.getTypeParameters().map((param) => param.getText());
1780
+ // const conParams = cls.getHeritageClauses()[0].getTypeNodes()[0].getTypeArguments().map((param) => param.getText());
1781
+ // const args = cls.getHeritageClauses()[0].getTypeNodes()[0].getTypeArguments();
1782
+ // if (!Helpers.arraysAreEqual(conParams,genParams)) {
1783
+ // const genInterface = this.createOrGetFamixInterface(interfaceDeclaration) as Famix.ParametricInterface;
1784
+ // const conInterface = this.createOrGetFamixConcreteElement(genInterface,interfaceDeclaration,args);
1785
+ // const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1786
+ // let createConcretisation : boolean = true;
1787
+ // concretisations.forEach((conc : Famix.Concretisation) => {
1788
+ // if (genInterface.fullyQualifiedName == conc.genericEntity.fullyQualifiedName && conc.concreteEntity.fullyQualifiedName == conInterface.fullyQualifiedName){
1789
+ // createConcretisation = false;
1790
+ // }
1791
+ // });
1792
+
1793
+ // if (createConcretisation) {
1794
+ // const fmxConcretisation : Famix.Concretisation = this.createFamixConcretisation(conInterface,genInterface);
1795
+ // }
1796
+ // }
1797
+ // }
1798
+ // });
1799
+ // }
1800
+
1801
+ /**
1802
+ * Creates a Famix concretisation between an interface and a Type
1803
+ * @param element A variable or a function
1804
+ * @param inter An interface
1805
+ */
1806
+ // public createFamixConcretisationTypeInstanciation(element: InterfaceDeclaration | ClassDeclaration){
1807
+
1808
+ // const isGeneric = element.getTypeParameters().length > 0;
1809
+ // if (isGeneric) {
1810
+ // const genParams = element.getTypeParameters().map(param => param.getText());
1811
+ // const uses = element.findReferencesAsNodes();
1812
+ // uses.forEach(use => {
1813
+ // let parentNode = use.getParent();
1814
+ // while (parentNode) {
1815
+ // if (parentNode.getKind() === SyntaxKind.TypeReference) {
1816
+ // const typeReferenceNode = parentNode.asKind(SyntaxKind.TypeReference);
1817
+ // if (!typeReferenceNode) {
1818
+ // throw new Error(`TypeReferenceNode not found for ${parentNode.getText()}`);
1819
+ // }
1820
+ // const typeReferenceNodeIsGeneric = typeReferenceNode.getTypeArguments().length > 0;
1821
+ // if (typeReferenceNodeIsGeneric) {}
1822
+ // const args = typeReferenceNode.getTypeArguments();
1823
+ // const conParams = typeReferenceNode.getTypeArguments().map(param => param.getText());
1824
+ // if (!Helpers.arraysAreEqual(conParams,genParams)) {
1825
+ // let genElement;
1826
+ // if(element instanceof ClassDeclaration){
1827
+ // genElement = this.createOrGetFamixClass(element) as Famix.ParametricClass;
1828
+ // } else {
1829
+ // genElement = this.createOrGetFamixInterface(element) as Famix.ParametricInterface;
1830
+ // }
1831
+ // let concElement;
1832
+ // concElement = this.createOrGetFamixConcreteElement(genElement,element,args);
1833
+ // const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1834
+ // let createConcretisation : boolean = true;
1835
+ // concretisations.forEach((conc : Famix.Concretisation) => {
1836
+ // if (genElement.fullyQualifiedName == conc.genericEntity.fullyQualifiedName && conc.concreteEntity.fullyQualifiedName == concElement.fullyQualifiedName){
1837
+ // createConcretisation = false;
1838
+ // }
1839
+ // });
1840
+
1841
+ // if (createConcretisation) {
1842
+ // const fmxConcretisation : Famix.Concretisation = this.createFamixConcretisation(concElement,genElement);
1843
+ // }
1844
+ // }
1845
+ // break;
1846
+ // }
1847
+ // parentNode = parentNode.getParent();
1848
+ // }
1849
+ // });
1850
+ // }
1851
+ // }
1852
+
1853
+ public convertToRelativePath(absolutePath: string, absolutePathProject: string) {
1854
+ logger.debug(`convertToRelativePath: absolutePath: '${absolutePath}', absolutePathProject: '${absolutePathProject}'`);
1855
+ if (absolutePath.startsWith(absolutePathProject)) {
1856
+ return absolutePath.replace(absolutePathProject, "").slice(1);
1857
+ } else if (absolutePath.startsWith("/")) {
1858
+ return absolutePath.slice(1);
1859
+ } else {
1860
+ return absolutePath;
1861
+ }
1862
+ }
1863
+ }
1864
+
1865
+ export function isPrimitiveType(typeName: string) {
1866
+ return typeName === "number" ||
1867
+ typeName === "string" ||
1868
+ typeName === "boolean" ||
1869
+ typeName === "bigint" ||
1870
+ typeName === "symbol" ||
1871
+ typeName === "undefined" ||
1872
+ typeName === "null" ||
1873
+ typeName === "any" ||
1874
+ typeName === "unknown" ||
1875
+ typeName === "never" ||
1876
+ typeName === "void";
1877
+ }
1878
+
1879
+ function initFQN(sourceElement: TSMorphObjectType, famixElement: Famix.SourcedEntity) {
1880
+ // handle special cases where an element is a Type -- need to change its name
1881
+ if (famixElement instanceof Famix.Type && !(sourceElement instanceof CommentRange) && isTypeContext(sourceElement)) {
1882
+ let fqn = FQNFunctions.getFQN(sourceElement);
1883
+ // using regex, replace [blah] with [blahType]
1884
+ fqn = fqn.replace(/\[([^\]]+)\]/g, "[$1Type]");
1885
+ logger.debug("Setting fully qualified name for " + famixElement.getJSON() + " to " + fqn);
1886
+ famixElement.fullyQualifiedName = fqn;
1887
+ return;
1888
+ }
1889
+ // catch all (except comments)
1890
+ if (!(sourceElement instanceof CommentRange)) {
1891
+ const fqn = FQNFunctions.getFQN(sourceElement);
1892
+ logger.debug("Setting fully qualified name for " + famixElement.getJSON() + " to " + fqn);
1893
+ (famixElement as Famix.NamedEntity).fullyQualifiedName = fqn;
1894
+ }
1895
+ }
1896
+
1897
+
1898
+ function isTypeContext(sourceElement: ImportDeclaration | ImportEqualsDeclaration | SourceFile | ModuleDeclaration | ConstructorDeclaration | MethodSignature | FunctionExpression | ParameterDeclaration | VariableDeclaration | PropertyDeclaration | PropertySignature | TypeParameterDeclaration | Identifier | Decorator | GetAccessorDeclaration | SetAccessorDeclaration | ImportSpecifier | EnumDeclaration | EnumMember | TypeAliasDeclaration | ExpressionWithTypeArguments | TSMorphParametricType) {
1899
+ return sourceElement instanceof ConstructorDeclaration
1900
+ || sourceElement instanceof MethodDeclaration
1901
+ || sourceElement instanceof FunctionDeclaration
1902
+ || sourceElement instanceof FunctionExpression
1903
+ || sourceElement instanceof ArrowFunction
1904
+ ||
1905
+ sourceElement instanceof ParameterDeclaration ||
1906
+ sourceElement instanceof VariableDeclaration ||
1907
+ sourceElement instanceof PropertyDeclaration ||
1908
+ sourceElement instanceof PropertySignature ||
1909
+ sourceElement instanceof TypeParameterDeclaration ||
1910
+ sourceElement instanceof Identifier ||
1911
+ sourceElement instanceof Decorator ||
1912
+ sourceElement instanceof GetAccessorDeclaration ||
1913
+ sourceElement instanceof SetAccessorDeclaration ||
1914
+ sourceElement instanceof ImportSpecifier ||
1915
+ sourceElement instanceof EnumDeclaration ||
1916
+ sourceElement instanceof EnumMember ||
1917
+ sourceElement instanceof TypeAliasDeclaration
1918
+ || sourceElement instanceof ImportDeclaration
1919
+ || sourceElement instanceof ExpressionWithTypeArguments;
1920
+ }
1921
+
1922
+
1923
+ function getInterfaceDeclarationFromExpression(expression: ExpressionWithTypeArguments): InterfaceDeclaration | undefined {
1924
+ // Step 1: Get the type of the expression
1925
+ const type = expression.getType();
1926
+
1927
+ // Step 2: Get the symbol associated with the type
1928
+ let symbol = type.getSymbol();
1929
+
1930
+ if (!symbol) {
1931
+ // If symbol is not found, try to get the symbol from the identifier
1932
+ const identifier = expression.getFirstDescendantByKind(SyntaxKind.Identifier);
1933
+ if (!identifier) {
1934
+ throw new Error(`Identifier not found for ${expression.getText()}.`);
1935
+ }
1936
+ symbol = identifier.getSymbol();
1937
+ if (!symbol) {
1938
+ throw new Error(`Symbol not found for ${identifier.getText()}.`);
1939
+ }
1940
+ }
1941
+
1942
+ // Step 3: Resolve the symbol to find the actual declaration
1943
+ const interfaceDeclaration = resolveSymbolToInterfaceDeclaration(symbol);
1944
+
1945
+ if (!interfaceDeclaration) {
1946
+ logger.error(`Interface declaration not found for ${expression.getText()}.`);
1947
+ }
1948
+
1949
+ return interfaceDeclaration;
1950
+ }
1951
+
1952
+ import { Symbol as TSMorphSymbol } from "ts-morph";
1953
+
1954
+ function resolveSymbolToInterfaceDeclaration(symbol: TSMorphSymbol): InterfaceDeclaration | undefined {
1955
+ // Get the declarations associated with the symbol
1956
+ const declarations = symbol.getDeclarations();
1957
+
1958
+ // Filter for InterfaceDeclaration
1959
+ const interfaceDeclaration = declarations.find(declaration => declaration instanceof InterfaceDeclaration) as InterfaceDeclaration | undefined;
1960
+
1961
+ if (interfaceDeclaration) {
1962
+ return interfaceDeclaration;
1963
+ }
1964
+
1965
+ // Handle imports: If the symbol is imported, resolve the import to find the actual declaration
1966
+ for (const declaration of declarations) {
1967
+ if (declaration.getKind() === SyntaxKind.ImportSpecifier) {
1968
+ const importSpecifier = declaration as ImportSpecifier;
1969
+ const importDeclaration = importSpecifier.getImportDeclaration();
1970
+ const moduleSpecifier = importDeclaration.getModuleSpecifierSourceFile();
1971
+
1972
+ if (moduleSpecifier) {
1973
+ const exportedSymbols = moduleSpecifier.getExportSymbols();
1974
+ const exportedSymbol = exportedSymbols.find(symbol => symbol.getName() === importSpecifier.getName());
1975
+ if (exportedSymbol) {
1976
+ return resolveSymbolToInterfaceDeclaration(exportedSymbol);
1977
+ }
1978
+ }
1979
+ }
1980
+ }
1981
+
1982
+ return undefined;
1983
+ }
1984
+
1985
+ // function oldGetInterfaceDeclarationFromExpression(expression: ExpressionWithTypeArguments): InterfaceDeclaration | undefined {
1986
+ // // Two cases:
1987
+ // // class A implements ImportedInterface, DeclaredInterface {}
1988
+ // const type = expression.getType();
1989
+
1990
+ // // ImportedInterface: type will a symbol
1991
+ // let symbol = type.getAliasSymbol(); // will be defined for imported interfaces
1992
+
1993
+ // if (!symbol) {
1994
+ // // DeclaredInterface: type will be an InterfaceDeclaration on Identifier node that is the child of the ExpressionWithTypeArguments
1995
+ // const identifier = expression.getFirstDescendantByKind(SyntaxKind.Identifier);
1996
+ // if (!identifier) {
1997
+ // throw new Error(`Identifier not found for ${expression.getText()}.`);
1998
+ // }
1999
+ // symbol = identifier.getSymbol();
2000
+ // if (!symbol) {
2001
+ // throw new Error(`Symbol not found for ${identifier.getText()}.`);
2002
+ // }
2003
+ // }
2004
+
2005
+ // // Step 3: Get the declarations associated with the symbol
2006
+ // const declarations = symbol.getDeclarations();
2007
+
2008
+ // // Step 4: Filter for InterfaceDeclaration
2009
+ // const interfaceDeclaration = declarations.find(declaration => declaration instanceof InterfaceDeclaration) as InterfaceDeclaration | undefined;
2010
+
2011
+ // if (!interfaceDeclaration) {
2012
+ // throw new Error(`Interface declaration not found for ${expression.getText()}.`);
2013
+ // }
2014
+
2015
+ // return interfaceDeclaration;
2016
+ // }