ts2famix 1.3.0 → 1.4.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 (43) hide show
  1. package/dist/analyze.js +37 -27
  2. package/dist/analyze_functions/process_functions.js +723 -0
  3. package/dist/famix_functions/EntityDictionary.js +798 -0
  4. package/dist/famix_functions/helpers_creation.js +97 -0
  5. package/dist/fqn.js +106 -95
  6. package/dist/lib/famix/src/famix_base_element.js +8 -4
  7. package/dist/lib/famix/src/famix_repository.js +32 -15
  8. package/dist/lib/famix/src/model/famix/class.js +1 -1
  9. package/dist/lib/ts-complex/cyclomatic-service.js +2 -3
  10. package/doc-uml/metamodel-full.svg +1 -0
  11. package/doc-uml/metamodel.svg +1 -0
  12. package/package.json +4 -4
  13. package/plantuml.jar +0 -0
  14. package/src/analyze.ts +46 -29
  15. package/src/analyze_functions/process_functions.ts +838 -0
  16. package/src/famix_functions/EntityDictionary.ts +915 -0
  17. package/src/famix_functions/helpers_creation.ts +77 -0
  18. package/src/fqn.ts +101 -92
  19. package/src/generate_uml.sh +3 -0
  20. package/src/lib/famix/src/famix_base_element.ts +9 -5
  21. package/src/lib/famix/src/famix_repository.ts +45 -23
  22. package/src/lib/famix/src/model/famix/class.ts +1 -1
  23. package/src/lib/ts-complex/cyclomatic-service.ts +2 -4
  24. package/src/ts2famix-cli.ts +1 -0
  25. package/dist/analyze_functions/processAccesses.js +0 -56
  26. package/dist/analyze_functions/processFiles.js +0 -554
  27. package/dist/analyze_functions/processImportClauses.js +0 -88
  28. package/dist/analyze_functions/processInheritances.js +0 -74
  29. package/dist/analyze_functions/processInvocations.js +0 -50
  30. package/dist/famix_functions/famix_functions.js +0 -523
  31. package/dist/famix_functions/famix_functions_associations.js +0 -238
  32. package/dist/famix_functions/famix_functions_index.js +0 -135
  33. package/dist/famix_functions/famix_functions_types.js +0 -115
  34. package/docs/.gitkeep +0 -0
  35. package/src/analyze_functions/processAccesses.ts +0 -58
  36. package/src/analyze_functions/processFiles.ts +0 -667
  37. package/src/analyze_functions/processImportClauses.ts +0 -95
  38. package/src/analyze_functions/processInheritances.ts +0 -85
  39. package/src/analyze_functions/processInvocations.ts +0 -52
  40. package/src/famix_functions/famix_functions.ts +0 -562
  41. package/src/famix_functions/famix_functions_associations.ts +0 -242
  42. package/src/famix_functions/famix_functions_index.ts +0 -120
  43. package/src/famix_functions/famix_functions_types.ts +0 -106
@@ -0,0 +1,798 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.EntityDictionary = void 0;
30
+ const ts_morph_1 = require("ts-morph");
31
+ const Famix = __importStar(require("../lib/famix/src/model/famix"));
32
+ const analyze_1 = require("../analyze");
33
+ const grapheme_splitter_1 = __importDefault(require("grapheme-splitter"));
34
+ const Helpers = __importStar(require("./helpers_creation"));
35
+ const FQNFunctions = __importStar(require("../fqn"));
36
+ const famix_repository_1 = require("../lib/famix/src/famix_repository");
37
+ const path_1 = __importDefault(require("path"));
38
+ class EntityDictionary {
39
+ constructor() {
40
+ this.famixRep = new famix_repository_1.FamixRepository();
41
+ this.fmxAliasMap = new Map(); // Maps the alias names to their Famix model
42
+ this.fmxClassMap = new Map(); // Maps the fully qualified class names to their Famix model
43
+ this.fmxInterfaceMap = new Map(); // Maps the interface names to their Famix model
44
+ this.fmxNamespaceMap = new Map(); // Maps the namespace names to their Famix model
45
+ this.fmxFileMap = new Map(); // Maps the source file names to their Famix model
46
+ this.fmxTypeMap = new Map(); // Maps the type names to their Famix model
47
+ this.UNKNOWN_VALUE = '(unknown due to parsing error)'; // The value to use when a name is not usable
48
+ this.fmxElementObjectMap = new Map();
49
+ this.famixRep.setFmxElementObjectMap(this.fmxElementObjectMap);
50
+ }
51
+ /**
52
+ * Makes a Famix index file anchor
53
+ * @param sourceElement A source element
54
+ * @param famixElement The Famix model of the source element
55
+ */
56
+ makeFamixIndexFileAnchor(sourceElement, famixElement) {
57
+ analyze_1.logger.debug("making index file anchor for '" + (sourceElement === null || sourceElement === void 0 ? void 0 : sourceElement.getText()) + "' with famixElement " + famixElement.getJSON());
58
+ const fmxIndexFileAnchor = new Famix.IndexedFileAnchor();
59
+ fmxIndexFileAnchor.setElement(famixElement);
60
+ this.fmxElementObjectMap.set(famixElement, sourceElement);
61
+ if (sourceElement !== null) {
62
+ const absolutePathProject = this.famixRep.getAbsolutePath();
63
+ const absolutePath = path_1.default.normalize(sourceElement.getSourceFile().getFilePath());
64
+ const positionNodeModules = absolutePath.indexOf('node_modules');
65
+ let pathInProject = "";
66
+ if (positionNodeModules !== -1) {
67
+ const pathFromNodeModules = absolutePath.substring(positionNodeModules);
68
+ pathInProject = pathFromNodeModules;
69
+ }
70
+ else {
71
+ pathInProject = this.convertToRelativePath(absolutePath, absolutePathProject);
72
+ }
73
+ // revert any backslashes to forward slashes (path.normalize on windows introduces them)
74
+ pathInProject = pathInProject.replace(/\\/g, "/");
75
+ fmxIndexFileAnchor.setFileName(pathInProject);
76
+ let sourceStart, sourceEnd, sourceLineStart, sourceLineEnd;
77
+ if (!(sourceElement instanceof ts_morph_1.CommentRange)) {
78
+ sourceStart = sourceElement.getStart();
79
+ sourceEnd = sourceElement.getEnd();
80
+ sourceLineStart = sourceElement.getStartLineNumber();
81
+ sourceLineEnd = sourceElement.getEndLineNumber();
82
+ }
83
+ else {
84
+ sourceStart = sourceElement.getPos();
85
+ sourceEnd = sourceElement.getEnd();
86
+ }
87
+ if (analyze_1.config.expectGraphemes) {
88
+ /**
89
+ * The following logic handles the case of multi-code point characters (e.g. emoji) in the source text.
90
+ * This is needed because Pharo/Smalltalk treats multi-code point characters as a single character,
91
+ * but JavaScript treats them as multiple characters. This means that the start and end positions
92
+ * of a source element in Pharo/Smalltalk will be different than the start and end positions of the
93
+ * same source element in JavaScript. This logic finds the start and end positions of the source
94
+ * element in JavaScript and then uses those positions to set the start and end positions of the
95
+ * Famix index file anchor.
96
+ * It depends on code in the 'grapheme-splitter' package in npm.
97
+ */
98
+ const splitter = new grapheme_splitter_1.default();
99
+ const sourceFileText = sourceElement.getSourceFile().getFullText();
100
+ const hasGraphemeClusters = splitter.countGraphemes(sourceFileText) > 1;
101
+ if (hasGraphemeClusters) {
102
+ const sourceElementText = sourceFileText.substring(sourceStart, sourceEnd);
103
+ const sourceElementTextGraphemes = splitter.splitGraphemes(sourceElementText);
104
+ const sourceFileTextGraphemes = splitter.splitGraphemes(sourceFileText);
105
+ const numberOfGraphemeClustersBeforeStart = splitter.countGraphemes(sourceFileText.substring(0, sourceStart));
106
+ // find the start of the sourceElementTextGraphemes array in the sourceFileTextGraphemes array
107
+ sourceStart = Helpers.indexOfSplitArray({ searchArray: sourceFileTextGraphemes,
108
+ targetArray: sourceElementTextGraphemes,
109
+ start: sourceStart - numberOfGraphemeClustersBeforeStart });
110
+ sourceEnd = sourceStart + sourceElementTextGraphemes.length;
111
+ }
112
+ }
113
+ // note: the +1 is because the source anchor is 1-based, but ts-morph is 0-based
114
+ fmxIndexFileAnchor.setStartPos(sourceStart + 1);
115
+ fmxIndexFileAnchor.setEndPos(sourceEnd + 1);
116
+ if (!(sourceElement instanceof ts_morph_1.CommentRange)) {
117
+ fmxIndexFileAnchor.setStartLine(sourceLineStart);
118
+ fmxIndexFileAnchor.setEndLine(sourceLineEnd);
119
+ }
120
+ if (!(famixElement instanceof Famix.Association) && !(famixElement instanceof Famix.Comment) && !(sourceElement instanceof ts_morph_1.CommentRange) && !(sourceElement instanceof ts_morph_1.Identifier) && !(sourceElement instanceof ts_morph_1.ImportSpecifier) && !(sourceElement instanceof ts_morph_1.ExpressionWithTypeArguments)) {
121
+ famixElement.setFullyQualifiedName(FQNFunctions.getFQN(sourceElement));
122
+ }
123
+ }
124
+ else {
125
+ // sourceElement is null
126
+ analyze_1.logger.warn("sourceElement is null for famixElement " + famixElement.getJSON());
127
+ fmxIndexFileAnchor.setFileName("unknown");
128
+ fmxIndexFileAnchor.setStartPos(0);
129
+ fmxIndexFileAnchor.setEndPos(0);
130
+ }
131
+ this.famixRep.addElement(fmxIndexFileAnchor);
132
+ }
133
+ /**
134
+ * Creates or gets a Famix script entity or module
135
+ * @param f A source file
136
+ * @param isModule A boolean indicating if the source file is a module
137
+ * @returns The Famix model of the source file
138
+ */
139
+ createOrGetFamixFile(f, isModule) {
140
+ let fmxFile;
141
+ const fileName = f.getBaseName();
142
+ const fullyQualifiedFilename = f.getFilePath();
143
+ if (!this.fmxFileMap.has(fullyQualifiedFilename)) {
144
+ if (isModule) {
145
+ fmxFile = new Famix.Module();
146
+ }
147
+ else {
148
+ fmxFile = new Famix.ScriptEntity();
149
+ }
150
+ fmxFile.setName(fileName);
151
+ fmxFile.setNumberOfLinesOfText(f.getEndLineNumber() - f.getStartLineNumber());
152
+ fmxFile.setNumberOfCharacters(f.getFullText().length);
153
+ this.makeFamixIndexFileAnchor(f, fmxFile);
154
+ this.fmxFileMap.set(fullyQualifiedFilename, fmxFile);
155
+ this.famixRep.addElement(fmxFile);
156
+ }
157
+ else {
158
+ fmxFile = this.fmxFileMap.get(fullyQualifiedFilename);
159
+ }
160
+ this.fmxElementObjectMap.set(fmxFile, f);
161
+ return fmxFile;
162
+ }
163
+ /**
164
+ * Creates or gets a Famix namespace
165
+ * @param m A namespace
166
+ * @returns The Famix model of the namespace
167
+ */
168
+ createOrGetFamixNamespace(m) {
169
+ let fmxNamespace;
170
+ const namespaceName = m.getName();
171
+ if (!this.fmxNamespaceMap.has(namespaceName)) {
172
+ fmxNamespace = new Famix.Namespace();
173
+ fmxNamespace.setName(namespaceName);
174
+ this.makeFamixIndexFileAnchor(m, fmxNamespace);
175
+ this.fmxNamespaceMap.set(namespaceName, fmxNamespace);
176
+ this.famixRep.addElement(fmxNamespace);
177
+ }
178
+ else {
179
+ fmxNamespace = this.fmxNamespaceMap.get(namespaceName);
180
+ }
181
+ this.fmxElementObjectMap.set(fmxNamespace, m);
182
+ return fmxNamespace;
183
+ }
184
+ /**
185
+ * Creates a Famix alias
186
+ * @param a An alias
187
+ * @returns The Famix model of the alias
188
+ */
189
+ createFamixAlias(a) {
190
+ let fmxAlias;
191
+ const aliasName = a.getName();
192
+ const aliasFullyQualifiedName = a.getType().getText(); // FQNFunctions.getFQN(a);
193
+ if (!this.fmxAliasMap.has(aliasFullyQualifiedName)) {
194
+ fmxAlias = new Famix.Alias();
195
+ fmxAlias.setName(a.getName());
196
+ const aliasNameWithGenerics = aliasName + (a.getTypeParameters().length ? ("<" + a.getTypeParameters().map(tp => tp.getName()).join(", ") + ">") : "");
197
+ analyze_1.logger.debug(`> NOTE: alias ${aliasName} has fully qualified name ${aliasFullyQualifiedName} and name with generics ${aliasNameWithGenerics}.`);
198
+ const fmxType = this.createOrGetFamixType(aliasNameWithGenerics, a);
199
+ fmxAlias.setAliasedEntity(fmxType);
200
+ this.makeFamixIndexFileAnchor(a, fmxAlias);
201
+ this.fmxAliasMap.set(aliasFullyQualifiedName, fmxAlias);
202
+ this.famixRep.addElement(fmxAlias);
203
+ }
204
+ else {
205
+ fmxAlias = this.fmxAliasMap.get(aliasFullyQualifiedName);
206
+ }
207
+ this.fmxElementObjectMap.set(fmxAlias, a);
208
+ return fmxAlias;
209
+ }
210
+ /**
211
+ * Creates or gets a Famix class or parameterizable class
212
+ * @param cls A class
213
+ * @returns The Famix model of the class
214
+ */
215
+ createOrGetFamixClass(cls) {
216
+ let fmxClass;
217
+ const isAbstract = cls.isAbstract();
218
+ const classFullyQualifiedName = FQNFunctions.getFQN(cls);
219
+ const clsName = cls.getName();
220
+ if (!this.fmxClassMap.has(classFullyQualifiedName)) {
221
+ const isGeneric = cls.getTypeParameters().length;
222
+ if (isGeneric) {
223
+ fmxClass = new Famix.ParameterizableClass();
224
+ }
225
+ else {
226
+ fmxClass = new Famix.Class();
227
+ }
228
+ fmxClass.setName(clsName);
229
+ fmxClass.setFullyQualifiedName(classFullyQualifiedName);
230
+ fmxClass.setIsAbstract(isAbstract);
231
+ this.makeFamixIndexFileAnchor(cls, fmxClass);
232
+ this.fmxClassMap.set(classFullyQualifiedName, fmxClass);
233
+ this.famixRep.addElement(fmxClass);
234
+ }
235
+ else {
236
+ fmxClass = this.fmxClassMap.get(classFullyQualifiedName);
237
+ }
238
+ this.fmxElementObjectMap.set(fmxClass, cls);
239
+ return fmxClass;
240
+ }
241
+ /**
242
+ * Creates or gets a Famix interface or parameterizable interface
243
+ * @param inter An interface
244
+ * @returns The Famix model of the interface
245
+ */
246
+ createOrGetFamixInterface(inter) {
247
+ let fmxInterface;
248
+ const interName = inter.getName();
249
+ const interFullyQualifiedName = FQNFunctions.getFQN(inter);
250
+ if (!this.fmxInterfaceMap.has(interName)) {
251
+ const isGeneric = inter.getTypeParameters().length;
252
+ if (isGeneric) {
253
+ fmxInterface = new Famix.ParameterizableInterface();
254
+ }
255
+ else {
256
+ fmxInterface = new Famix.Interface();
257
+ }
258
+ fmxInterface.setName(interName);
259
+ this.makeFamixIndexFileAnchor(inter, fmxInterface);
260
+ this.fmxInterfaceMap.set(interFullyQualifiedName, fmxInterface);
261
+ this.famixRep.addElement(fmxInterface);
262
+ }
263
+ else {
264
+ fmxInterface = this.fmxInterfaceMap.get(interName);
265
+ }
266
+ this.fmxElementObjectMap.set(fmxInterface, inter);
267
+ return fmxInterface;
268
+ }
269
+ /**
270
+ * Creates a Famix property
271
+ * @param property A property
272
+ * @returns The Famix model of the property
273
+ */
274
+ createFamixProperty(property) {
275
+ const fmxProperty = new Famix.Property();
276
+ const isSignature = property instanceof ts_morph_1.PropertySignature;
277
+ fmxProperty.setName(property.getName());
278
+ let propTypeName = this.UNKNOWN_VALUE;
279
+ try {
280
+ propTypeName = property.getType().getText().trim();
281
+ }
282
+ catch (error) {
283
+ analyze_1.logger.error(`> WARNING: got exception ${error}. Failed to get usable name for property: ${property.getName()}. Continuing...`);
284
+ }
285
+ const fmxType = this.createOrGetFamixType(propTypeName, property);
286
+ fmxProperty.setDeclaredType(fmxType);
287
+ property.getModifiers().forEach(m => fmxProperty.addModifier(m.getText()));
288
+ if (!isSignature && property.getExclamationTokenNode()) {
289
+ fmxProperty.addModifier("!");
290
+ }
291
+ if (property.getQuestionTokenNode()) {
292
+ fmxProperty.addModifier("?");
293
+ }
294
+ if (property.getName().substring(0, 1) === "#") {
295
+ fmxProperty.addModifier("#");
296
+ }
297
+ if (fmxProperty.getModifiers().has("static")) {
298
+ fmxProperty.setIsClassSide(true);
299
+ }
300
+ else {
301
+ fmxProperty.setIsClassSide(false);
302
+ }
303
+ this.makeFamixIndexFileAnchor(property, fmxProperty);
304
+ this.famixRep.addElement(fmxProperty);
305
+ this.fmxElementObjectMap.set(fmxProperty, property);
306
+ return fmxProperty;
307
+ }
308
+ /**
309
+ * Creates a Famix method or accessor
310
+ * @param method A method or an accessor
311
+ * @param currentCC The cyclomatic complexity metrics of the current source file
312
+ * @returns The Famix model of the method or the accessor
313
+ */
314
+ createFamixMethod(method, currentCC) {
315
+ let fmxMethod;
316
+ if (method instanceof ts_morph_1.GetAccessorDeclaration || method instanceof ts_morph_1.SetAccessorDeclaration) {
317
+ fmxMethod = new Famix.Accessor();
318
+ const isGetter = method instanceof ts_morph_1.GetAccessorDeclaration;
319
+ const isSetter = method instanceof ts_morph_1.SetAccessorDeclaration;
320
+ if (isGetter) {
321
+ fmxMethod.setKind("getter");
322
+ }
323
+ if (isSetter) {
324
+ fmxMethod.setKind("setter");
325
+ }
326
+ this.famixRep.addElement(fmxMethod);
327
+ }
328
+ else {
329
+ fmxMethod = new Famix.Method();
330
+ this.famixRep.addElement(fmxMethod);
331
+ }
332
+ const isConstructor = method instanceof ts_morph_1.ConstructorDeclaration;
333
+ const isSignature = method instanceof ts_morph_1.MethodSignature;
334
+ const isGeneric = method.getTypeParameters().length > 0;
335
+ fmxMethod.setIsGeneric(isGeneric);
336
+ let isAbstract = false;
337
+ let isStatic = false;
338
+ if (method instanceof ts_morph_1.MethodDeclaration || method instanceof ts_morph_1.GetAccessorDeclaration || method instanceof ts_morph_1.SetAccessorDeclaration) {
339
+ isAbstract = method.isAbstract();
340
+ isStatic = method.isStatic();
341
+ }
342
+ if (isConstructor) {
343
+ fmxMethod.setKind("constructor");
344
+ }
345
+ fmxMethod.setIsAbstract(isAbstract);
346
+ fmxMethod.setIsClassSide(isStatic);
347
+ fmxMethod.setIsPrivate((method instanceof ts_morph_1.MethodDeclaration || method instanceof ts_morph_1.GetAccessorDeclaration || method instanceof ts_morph_1.SetAccessorDeclaration) ? (method.getModifiers().find(x => x.getText() === 'private')) !== undefined : false);
348
+ fmxMethod.setIsProtected((method instanceof ts_morph_1.MethodDeclaration || method instanceof ts_morph_1.GetAccessorDeclaration || method instanceof ts_morph_1.SetAccessorDeclaration) ? (method.getModifiers().find(x => x.getText() === 'protected')) !== undefined : false);
349
+ fmxMethod.setSignature(Helpers.computeSignature(method.getText()));
350
+ let methodName;
351
+ if (isConstructor) {
352
+ methodName = "constructor";
353
+ }
354
+ else {
355
+ methodName = method.getName();
356
+ }
357
+ fmxMethod.setName(methodName);
358
+ if (!isConstructor) {
359
+ if (method.getName().substring(0, 1) === "#") {
360
+ fmxMethod.setIsPrivate(true);
361
+ }
362
+ }
363
+ if (!fmxMethod.getIsPrivate() && !fmxMethod.getIsProtected()) {
364
+ fmxMethod.setIsPublic(true);
365
+ }
366
+ else {
367
+ fmxMethod.setIsPublic(false);
368
+ }
369
+ if (!isSignature) {
370
+ fmxMethod.setCyclomaticComplexity(currentCC[fmxMethod.getName()]);
371
+ }
372
+ else {
373
+ fmxMethod.setCyclomaticComplexity(0);
374
+ }
375
+ let methodTypeName = this.UNKNOWN_VALUE;
376
+ try {
377
+ methodTypeName = method.getReturnType().getText().trim();
378
+ }
379
+ catch (error) {
380
+ analyze_1.logger.error(`> WARNING: got exception ${error}. Failed to get usable name for return type of method: ${fmxMethod.getName()}. Continuing...`);
381
+ }
382
+ const fmxType = this.createOrGetFamixType(methodTypeName, method);
383
+ fmxMethod.setDeclaredType(fmxType);
384
+ fmxMethod.setNumberOfLinesOfCode(method.getEndLineNumber() - method.getStartLineNumber());
385
+ const parameters = method.getParameters();
386
+ fmxMethod.setNumberOfParameters(parameters.length);
387
+ if (!isSignature) {
388
+ fmxMethod.setNumberOfStatements(method.getStatements().length);
389
+ }
390
+ else {
391
+ fmxMethod.setNumberOfStatements(0);
392
+ }
393
+ this.makeFamixIndexFileAnchor(method, fmxMethod);
394
+ this.fmxElementObjectMap.set(fmxMethod, method);
395
+ return fmxMethod;
396
+ }
397
+ /**
398
+ * Creates a Famix function
399
+ * @param func A function
400
+ * @param currentCC The cyclomatic complexity metrics of the current source file
401
+ * @returns The Famix model of the function
402
+ */
403
+ createFamixFunction(func, currentCC) {
404
+ const fmxFunction = new Famix.Function();
405
+ if (func.getName()) {
406
+ fmxFunction.setName(func.getName());
407
+ }
408
+ else {
409
+ fmxFunction.setName("anonymous");
410
+ }
411
+ fmxFunction.setSignature(Helpers.computeSignature(func.getText()));
412
+ fmxFunction.setCyclomaticComplexity(currentCC[fmxFunction.getName()]);
413
+ const isGeneric = func.getTypeParameters().length > 0;
414
+ fmxFunction.setIsGeneric(isGeneric);
415
+ let functionTypeName = this.UNKNOWN_VALUE;
416
+ try {
417
+ functionTypeName = func.getReturnType().getText().trim();
418
+ }
419
+ catch (error) {
420
+ analyze_1.logger.error(`> WARNING: got exception ${error}. Failed to get usable name for return type of function: ${func.getName()}. Continuing...`);
421
+ }
422
+ const fmxType = this.createOrGetFamixType(functionTypeName, func);
423
+ fmxFunction.setDeclaredType(fmxType);
424
+ fmxFunction.setNumberOfLinesOfCode(func.getEndLineNumber() - func.getStartLineNumber());
425
+ const parameters = func.getParameters();
426
+ fmxFunction.setNumberOfParameters(parameters.length);
427
+ fmxFunction.setNumberOfStatements(func.getStatements().length);
428
+ this.makeFamixIndexFileAnchor(func, fmxFunction);
429
+ this.famixRep.addElement(fmxFunction);
430
+ this.fmxElementObjectMap.set(fmxFunction, func);
431
+ return fmxFunction;
432
+ }
433
+ /**
434
+ * Creates a Famix parameter
435
+ * @param param A parameter
436
+ * @returns The Famix model of the parameter
437
+ */
438
+ createFamixParameter(param) {
439
+ const fmxParam = new Famix.Parameter();
440
+ let paramTypeName = this.UNKNOWN_VALUE;
441
+ try {
442
+ paramTypeName = param.getType().getText().trim();
443
+ }
444
+ catch (error) {
445
+ analyze_1.logger.error(`> WARNING: got exception ${error}. Failed to get usable name for parameter: ${param.getName()}. Continuing...`);
446
+ }
447
+ const fmxType = this.createOrGetFamixType(paramTypeName, param);
448
+ fmxParam.setDeclaredType(fmxType);
449
+ fmxParam.setName(param.getName());
450
+ this.makeFamixIndexFileAnchor(param, fmxParam);
451
+ this.famixRep.addElement(fmxParam);
452
+ this.fmxElementObjectMap.set(fmxParam, param);
453
+ return fmxParam;
454
+ }
455
+ /**
456
+ * Creates a Famix type parameter
457
+ * @param tp A type parameter
458
+ * @returns The Famix model of the type parameter
459
+ */
460
+ createFamixParameterType(tp) {
461
+ const fmxParameterType = new Famix.ParameterType();
462
+ fmxParameterType.setName(tp.getName());
463
+ this.makeFamixIndexFileAnchor(tp, fmxParameterType);
464
+ this.famixRep.addElement(fmxParameterType);
465
+ this.fmxElementObjectMap.set(fmxParameterType, tp);
466
+ return fmxParameterType;
467
+ }
468
+ /**
469
+ * Creates a Famix variable
470
+ * @param variable A variable
471
+ * @returns The Famix model of the variable
472
+ */
473
+ createFamixVariable(variable) {
474
+ const fmxVariable = new Famix.Variable();
475
+ let variableTypeName = this.UNKNOWN_VALUE;
476
+ try {
477
+ variableTypeName = variable.getType().getText().trim();
478
+ }
479
+ catch (error) {
480
+ analyze_1.logger.error(`> WARNING: got exception ${error}. Failed to get usable name for variable: ${variable.getName()}. Continuing...`);
481
+ }
482
+ const fmxType = this.createOrGetFamixType(variableTypeName, variable);
483
+ fmxVariable.setDeclaredType(fmxType);
484
+ fmxVariable.setName(variable.getName());
485
+ this.makeFamixIndexFileAnchor(variable, fmxVariable);
486
+ this.famixRep.addElement(fmxVariable);
487
+ this.fmxElementObjectMap.set(fmxVariable, variable);
488
+ return fmxVariable;
489
+ }
490
+ /**
491
+ * Creates a Famix enum
492
+ * @param enumEntity An enum
493
+ * @returns The Famix model of the enum
494
+ */
495
+ createFamixEnum(enumEntity) {
496
+ const fmxEnum = new Famix.Enum();
497
+ fmxEnum.setName(enumEntity.getName());
498
+ this.makeFamixIndexFileAnchor(enumEntity, fmxEnum);
499
+ this.famixRep.addElement(fmxEnum);
500
+ this.fmxElementObjectMap.set(fmxEnum, enumEntity);
501
+ return fmxEnum;
502
+ }
503
+ /**
504
+ * Creates a Famix enum value
505
+ * @param enumMember An enum member
506
+ * @returns The Famix model of the enum member
507
+ */
508
+ createFamixEnumValue(enumMember) {
509
+ const fmxEnumValue = new Famix.EnumValue();
510
+ let enumValueTypeName = this.UNKNOWN_VALUE;
511
+ try {
512
+ enumValueTypeName = enumMember.getType().getText().trim();
513
+ }
514
+ catch (error) {
515
+ analyze_1.logger.error(`> WARNING: got exception ${error}. Failed to get usable name for enum value: ${enumMember.getName()}. Continuing...`);
516
+ }
517
+ const fmxType = this.createOrGetFamixType(enumValueTypeName, enumMember);
518
+ fmxEnumValue.setDeclaredType(fmxType);
519
+ fmxEnumValue.setName(enumMember.getName());
520
+ this.makeFamixIndexFileAnchor(enumMember, fmxEnumValue);
521
+ this.famixRep.addElement(fmxEnumValue);
522
+ this.fmxElementObjectMap.set(fmxEnumValue, enumMember);
523
+ return fmxEnumValue;
524
+ }
525
+ /**
526
+ * Creates or gets a Famix decorator
527
+ * @param decorator A decorator
528
+ * @param decoratedEntity A class, a method, a parameter or a property
529
+ * @returns The Famix model of the decorator
530
+ */
531
+ createOrGetFamixDecorator(decorator, decoratedEntity) {
532
+ const fmxDecorator = new Famix.Decorator();
533
+ const decoratorName = "@" + decorator.getName();
534
+ const decoratorExpression = decorator.getText().substring(1);
535
+ fmxDecorator.setName(decoratorName);
536
+ fmxDecorator.setDecoratorExpression(decoratorExpression);
537
+ const decoratedEntityFullyQualifiedName = FQNFunctions.getFQN(decoratedEntity);
538
+ const fmxDecoratedEntity = this.famixRep.getFamixEntityByFullyQualifiedName(decoratedEntityFullyQualifiedName);
539
+ fmxDecorator.setDecoratedEntity(fmxDecoratedEntity);
540
+ this.makeFamixIndexFileAnchor(decorator, fmxDecorator);
541
+ this.famixRep.addElement(fmxDecorator);
542
+ this.fmxElementObjectMap.set(fmxDecorator, decorator);
543
+ return fmxDecorator;
544
+ }
545
+ /**
546
+ * Creates a Famix comment
547
+ * @param comment A comment
548
+ * @param fmxScope The Famix model of the comment's container
549
+ * @param isJSDoc A boolean indicating if the comment is a JSDoc
550
+ * @returns The Famix model of the comment
551
+ */
552
+ createFamixComment(comment, fmxScope, isJSDoc) {
553
+ analyze_1.logger.debug(`> NOTE: creating comment ${comment.getText()} in scope ${fmxScope.getName()}.`);
554
+ const fmxComment = new Famix.Comment();
555
+ fmxComment.setContainer(fmxScope); // adds comment to the container's comments collection
556
+ fmxComment.setIsJSDoc(isJSDoc);
557
+ this.makeFamixIndexFileAnchor(comment, fmxComment);
558
+ this.famixRep.addElement(fmxComment);
559
+ this.fmxElementObjectMap.set(fmxComment, comment);
560
+ return fmxComment;
561
+ }
562
+ /**
563
+ * Creates or gets a Famix type
564
+ * @param typeName A type name
565
+ * @param element A ts-morph element
566
+ * @returns The Famix model of the type
567
+ */
568
+ createOrGetFamixType(typeName, element) {
569
+ let fmxType;
570
+ let isPrimitiveType = false;
571
+ let isParameterizedType = false;
572
+ analyze_1.logger.debug("Creating (or getting) type: '" + typeName + "' of element: " + (element === null || element === void 0 ? void 0 : element.getText()) + " of kind: " + (element === null || element === void 0 ? void 0 : element.getKindName()));
573
+ let ancestor;
574
+ if (element !== undefined) {
575
+ const typeAncestor = Helpers.findTypeAncestor(element);
576
+ const ancestorFullyQualifiedName = FQNFunctions.getFQN(typeAncestor);
577
+ ancestor = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName);
578
+ if (!ancestor) {
579
+ throw new Error(`Ancestor ${ancestorFullyQualifiedName} not found.`);
580
+ }
581
+ }
582
+ if (typeName === "number" || typeName === "string" || typeName === "boolean" || typeName === "bigint" || typeName === "symbol" || typeName === "undefined" || typeName === "null" || typeName === "any" || typeName === "unknown" || typeName === "never" || typeName === "void") {
583
+ isPrimitiveType = true;
584
+ }
585
+ if (!isPrimitiveType && typeName.includes("<") && typeName.includes(">") && !(typeName.includes("=>"))) {
586
+ isParameterizedType = true;
587
+ }
588
+ if (!this.fmxTypeMap.has(typeName)) {
589
+ if (isPrimitiveType) {
590
+ fmxType = new Famix.PrimitiveType();
591
+ fmxType.setIsStub(true);
592
+ }
593
+ else if (isParameterizedType) {
594
+ fmxType = new Famix.ParameterizedType();
595
+ const parameterTypeNames = typeName.substring(typeName.indexOf("<") + 1, typeName.indexOf(">")).split(",").map(s => s.trim());
596
+ const baseTypeName = typeName.substring(0, typeName.indexOf("<")).trim();
597
+ parameterTypeNames.forEach(parameterTypeName => {
598
+ const fmxParameterType = this.createOrGetFamixType(parameterTypeName, element);
599
+ fmxType.addArgument(fmxParameterType);
600
+ });
601
+ const fmxBaseType = this.createOrGetFamixType(baseTypeName, element);
602
+ fmxType.setBaseType(fmxBaseType);
603
+ }
604
+ else {
605
+ fmxType = new Famix.Type();
606
+ }
607
+ fmxType.setName(typeName);
608
+ fmxType.setContainer(ancestor);
609
+ this.makeFamixIndexFileAnchor(element, fmxType);
610
+ this.famixRep.addElement(fmxType);
611
+ this.fmxTypeMap.set(typeName, fmxType);
612
+ }
613
+ else {
614
+ fmxType = this.fmxTypeMap.get(typeName);
615
+ }
616
+ this.fmxElementObjectMap.set(fmxType, element);
617
+ return fmxType;
618
+ }
619
+ /**
620
+ * Creates a Famix access
621
+ * @param node A node
622
+ * @param id An id of a parameter, a variable, a property or an enum member
623
+ */
624
+ createFamixAccess(node, id) {
625
+ const fmxVar = this.famixRep.getFamixEntityById(id);
626
+ const nodeReferenceAncestor = Helpers.findAncestor(node);
627
+ const ancestorFullyQualifiedName = FQNFunctions.getFQN(nodeReferenceAncestor);
628
+ const accessor = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName);
629
+ const fmxAccess = new Famix.Access();
630
+ fmxAccess.setAccessor(accessor);
631
+ fmxAccess.setVariable(fmxVar);
632
+ this.makeFamixIndexFileAnchor(node, fmxAccess);
633
+ this.famixRep.addElement(fmxAccess);
634
+ this.fmxElementObjectMap.set(fmxAccess, node);
635
+ }
636
+ /**
637
+ * Creates a Famix invocation
638
+ * @param node A node
639
+ * @param m A method or a function
640
+ * @param id The id of the method or the function
641
+ */
642
+ createFamixInvocation(node, m, id) {
643
+ const fmxMethodOrFunction = this.famixRep.getFamixEntityById(id);
644
+ const nodeReferenceAncestor = Helpers.findAncestor(node);
645
+ const ancestorFullyQualifiedName = FQNFunctions.getFQN(nodeReferenceAncestor);
646
+ const sender = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName);
647
+ const receiverFullyQualifiedName = FQNFunctions.getFQN(m.getParent());
648
+ const receiver = this.famixRep.getFamixEntityByFullyQualifiedName(receiverFullyQualifiedName);
649
+ const fmxInvocation = new Famix.Invocation();
650
+ fmxInvocation.setSender(sender);
651
+ fmxInvocation.setReceiver(receiver);
652
+ fmxInvocation.addCandidate(fmxMethodOrFunction);
653
+ fmxInvocation.setSignature(fmxMethodOrFunction.getSignature());
654
+ this.makeFamixIndexFileAnchor(node, fmxInvocation);
655
+ this.famixRep.addElement(fmxInvocation);
656
+ this.fmxElementObjectMap.set(fmxInvocation, node);
657
+ }
658
+ /**
659
+ * Creates a Famix inheritance
660
+ * @param cls A class or an interface (subclass)
661
+ * @param inhClass The inherited class or interface (superclass)
662
+ */
663
+ createFamixInheritance(cls, inhClass) {
664
+ const fmxInheritance = new Famix.Inheritance();
665
+ // const clsName = cls.getName();
666
+ const classFullyQualifiedName = FQNFunctions.getFQN(cls);
667
+ analyze_1.logger.debug(`createFamixInheritance: classFullyQualifiedName: class fqn = ${classFullyQualifiedName}`);
668
+ let subClass;
669
+ if (cls instanceof ts_morph_1.ClassDeclaration) {
670
+ subClass = this.fmxClassMap.get(classFullyQualifiedName);
671
+ }
672
+ else {
673
+ subClass = this.fmxInterfaceMap.get(classFullyQualifiedName);
674
+ }
675
+ let inhClassName;
676
+ let inhClassFullyQualifiedName;
677
+ let superClass;
678
+ if (inhClass instanceof ts_morph_1.ClassDeclaration || inhClass instanceof ts_morph_1.InterfaceDeclaration) {
679
+ inhClassName = inhClass.getName();
680
+ inhClassFullyQualifiedName = FQNFunctions.getFQN(inhClass);
681
+ if (inhClass instanceof ts_morph_1.ClassDeclaration) {
682
+ superClass = this.fmxClassMap.get(inhClassFullyQualifiedName);
683
+ }
684
+ else {
685
+ superClass = this.fmxInterfaceMap.get(inhClassFullyQualifiedName);
686
+ }
687
+ }
688
+ else {
689
+ // inhClass is an ExpressionWithTypeArguments
690
+ inhClassName = inhClass.getExpression().getText();
691
+ // what is inhClassFullyQualifiedName? TODO
692
+ inhClassFullyQualifiedName = 'Undefined_Scope_from_importer.' + inhClassName;
693
+ }
694
+ if (superClass === undefined) {
695
+ if (inhClass instanceof ts_morph_1.ClassDeclaration) {
696
+ superClass = new Famix.Class();
697
+ this.fmxClassMap.set(inhClassFullyQualifiedName, superClass);
698
+ }
699
+ else {
700
+ superClass = new Famix.Interface();
701
+ this.fmxInterfaceMap.set(inhClassFullyQualifiedName, superClass);
702
+ }
703
+ this.fmxElementObjectMap.set(superClass, inhClass);
704
+ superClass.setName(inhClassName);
705
+ superClass.setFullyQualifiedName(inhClassFullyQualifiedName);
706
+ superClass.setIsStub(true);
707
+ this.makeFamixIndexFileAnchor(inhClass, superClass);
708
+ this.famixRep.addElement(superClass);
709
+ }
710
+ fmxInheritance.setSubclass(subClass);
711
+ fmxInheritance.setSuperclass(superClass);
712
+ this.makeFamixIndexFileAnchor(null, fmxInheritance);
713
+ this.famixRep.addElement(fmxInheritance);
714
+ this.fmxElementObjectMap.set(fmxInheritance, null);
715
+ }
716
+ /**
717
+ * Creates a Famix import clause
718
+ * @param importClauseInfo The information needed to create a Famix import clause
719
+ * @param importDeclaration The import declaration
720
+ * @param importer A source file which is a module
721
+ * @param moduleSpecifierFilePath The path of the module where the export declaration is
722
+ * @param importElement The imported entity
723
+ * @param isInExports A boolean indicating if the imported entity is in the exports
724
+ * @param isDefaultExport A boolean indicating if the imported entity is a default export
725
+ */
726
+ createFamixImportClause(importClauseInfo) {
727
+ var _a, _b;
728
+ const { importDeclaration, importer, moduleSpecifierFilePath, importElement, isInExports, isDefaultExport } = importClauseInfo;
729
+ analyze_1.logger.debug(`createFamixImportClause: Creating import clause:`);
730
+ const fmxImportClause = new Famix.ImportClause();
731
+ let importedEntity;
732
+ let importedEntityName;
733
+ const absolutePathProject = this.famixRep.getAbsolutePath();
734
+ const absolutePath = path_1.default.normalize(moduleSpecifierFilePath);
735
+ // convert the path and remove any windows backslashes introduced by path.normalize
736
+ const pathInProject = this.convertToRelativePath(absolutePath, absolutePathProject).replace(/\\/g, "/");
737
+ let pathName = "{" + pathInProject + "}.";
738
+ // Named imports, e.g. import { ClassW } from "./complexExportModule";
739
+ if (importDeclaration instanceof ts_morph_1.ImportDeclaration
740
+ && importElement instanceof ts_morph_1.ImportSpecifier) {
741
+ importedEntityName = importElement.getName();
742
+ pathName = pathName + importedEntityName;
743
+ if (isInExports) {
744
+ importedEntity = this.famixRep.getFamixEntityByFullyQualifiedName(pathName);
745
+ }
746
+ if (importedEntity === undefined) {
747
+ importedEntity = new Famix.NamedEntity();
748
+ importedEntity.setName(importedEntityName);
749
+ if (!isInExports) {
750
+ importedEntity.setIsStub(true);
751
+ }
752
+ this.makeFamixIndexFileAnchor(importElement, importedEntity);
753
+ importedEntity.setFullyQualifiedName(pathName);
754
+ }
755
+ }
756
+ // handle import equals declarations, e.g. import myModule = require("./complexExportModule");
757
+ // TypeScript can't determine the type of the imported module, so we create a Module entity
758
+ else if (importDeclaration instanceof ts_morph_1.ImportEqualsDeclaration) {
759
+ importedEntityName = importDeclaration === null || importDeclaration === void 0 ? void 0 : importDeclaration.getName();
760
+ pathName = pathName + importedEntityName;
761
+ importedEntity = new Famix.StructuralEntity();
762
+ importedEntity.setName(importedEntityName);
763
+ this.makeFamixIndexFileAnchor(importElement, importedEntity);
764
+ importedEntity.setFullyQualifiedName(pathName);
765
+ const anyType = this.createOrGetFamixType('any', undefined);
766
+ importedEntity.setDeclaredType(anyType);
767
+ }
768
+ else { // default imports, e.g. import ClassW from "./complexExportModule";
769
+ importedEntityName = importElement.getText();
770
+ pathName = pathName + (isDefaultExport ? "defaultExport" : "namespaceExport");
771
+ importedEntity = new Famix.NamedEntity();
772
+ importedEntity.setName(importedEntityName);
773
+ this.makeFamixIndexFileAnchor(importElement, importedEntity);
774
+ importedEntity.setFullyQualifiedName(pathName);
775
+ }
776
+ this.famixRep.addElement(importedEntity);
777
+ const importerFullyQualifiedName = FQNFunctions.getFQN(importer);
778
+ const fmxImporter = this.famixRep.getFamixEntityByFullyQualifiedName(importerFullyQualifiedName);
779
+ fmxImportClause.setImportingEntity(fmxImporter);
780
+ fmxImportClause.setImportedEntity(importedEntity);
781
+ if (importDeclaration instanceof ts_morph_1.ImportEqualsDeclaration) {
782
+ fmxImportClause.setModuleSpecifier(importDeclaration === null || importDeclaration === void 0 ? void 0 : importDeclaration.getModuleReference().getText());
783
+ }
784
+ else {
785
+ fmxImportClause.setModuleSpecifier(importDeclaration === null || importDeclaration === void 0 ? void 0 : importDeclaration.getModuleSpecifierValue());
786
+ }
787
+ analyze_1.logger.debug(`createFamixImportClause: ${(_a = fmxImportClause.getImportedEntity()) === null || _a === void 0 ? void 0 : _a.getName()} (of type ${Helpers.getSubTypeName(fmxImportClause.getImportedEntity())}) is imported by ${(_b = fmxImportClause.getImportingEntity()) === null || _b === void 0 ? void 0 : _b.getName()}`);
788
+ // make an index file anchor for the import clause
789
+ this.makeFamixIndexFileAnchor(importDeclaration, fmxImportClause);
790
+ fmxImporter.addOutgoingImport(fmxImportClause);
791
+ this.famixRep.addElement(fmxImportClause);
792
+ this.fmxElementObjectMap.set(fmxImportClause, importDeclaration);
793
+ }
794
+ convertToRelativePath(absolutePath, absolutePathProject) {
795
+ return absolutePath.replace(absolutePathProject, "").slice(1);
796
+ }
797
+ }
798
+ exports.EntityDictionary = EntityDictionary;