ts2famix 2.1.0-beta.2 → 3.0.0

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