ts2famix 2.0.3 → 2.0.4

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.
@@ -1,32 +1,50 @@
1
- import { ClassDeclaration, ConstructorDeclaration, FunctionDeclaration, Identifier, InterfaceDeclaration, MethodDeclaration, MethodSignature, ModuleDeclaration, PropertyDeclaration, PropertySignature, SourceFile, TypeParameterDeclaration, VariableDeclaration, ParameterDeclaration, Decorator, GetAccessorDeclaration, SetAccessorDeclaration, ImportSpecifier, CommentRange, EnumDeclaration, EnumMember, TypeAliasDeclaration, FunctionExpression, ExpressionWithTypeArguments, ImportDeclaration, ImportEqualsDeclaration, SyntaxKind, Expression, TypeNode, Node, ts, Scope, Type, ArrowFunction } from "ts-morph";
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";
2
9
  import { isAmbient, isNamespace } from "../analyze_functions/process_functions";
3
10
  import * as Famix from "../lib/famix/model/famix";
4
11
  import { FamixRepository } from "../lib/famix/famix_repository";
5
12
  import { logger, config } from "../analyze";
6
- import GraphemeSplitter from "grapheme-splitter";
13
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
14
+ import GraphemeSplitter = require('grapheme-splitter');
7
15
  import * as Helpers from "./helpers_creation";
8
16
  import * as FQNFunctions from "../fqn";
9
17
  import path from "path";
10
- import _ from 'lodash';
11
18
 
12
- export type TSMorphObjectType = ImportDeclaration | ImportEqualsDeclaration | SourceFile | ModuleDeclaration | ClassDeclaration | InterfaceDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature | FunctionDeclaration | FunctionExpression | ParameterDeclaration | VariableDeclaration | PropertyDeclaration | PropertySignature | TypeParameterDeclaration | Identifier | Decorator | GetAccessorDeclaration | SetAccessorDeclaration | ImportSpecifier | CommentRange | EnumDeclaration | EnumMember | TypeAliasDeclaration | ExpressionWithTypeArguments;
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 ;
13
22
 
14
- export type TypeDeclaration = TypeAliasDeclaration | PropertyDeclaration | PropertySignature | MethodDeclaration | ConstructorDeclaration | MethodSignature | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression | ParameterDeclaration | VariableDeclaration | EnumMember | ImportEqualsDeclaration;
23
+ export type TSMorphParametricType = ClassDeclaration | InterfaceDeclaration | FunctionDeclaration | MethodDeclaration | ArrowFunction;
15
24
 
16
25
  type ParametricVariantType = Famix.ParametricClass | Famix.ParametricInterface | Famix.ParametricFunction | Famix.ParametricMethod;
17
26
 
18
27
  type ConcreteElementTSMorphType = ClassDeclaration | InterfaceDeclaration | FunctionDeclaration | MethodDeclaration;
19
28
 
29
+ export type InvocableType = MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression | ArrowFunction;
30
+
20
31
  export class EntityDictionary {
21
32
 
22
33
  public famixRep = new FamixRepository();
23
34
  private fmxAliasMap = new Map<string, Famix.Alias>(); // Maps the alias names to their Famix model
24
35
  private fmxClassMap = new Map<string, Famix.Class | Famix.ParametricClass>(); // Maps the fully qualified class names to their Famix model
25
36
  private fmxInterfaceMap = new Map<string, Famix.Interface | Famix.ParametricInterface>(); // Maps the interface names to their Famix model
26
- private fmxModuleMap = new Map<string, Famix.Module>(); // Maps the namespace names to their Famix model
37
+ private fmxModuleMap = new Map<ModuleDeclaration, Famix.Module>(); // Maps the namespace names to their Famix model
27
38
  private fmxFileMap = new Map<string, Famix.ScriptEntity | Famix.Module>(); // Maps the source file names to their Famix model
28
- private fmxTypeMap = new Map<string, Famix.Type | Famix.PrimitiveType | Famix.ParameterType>(); // Maps the type names to their Famix model
29
- private fmxFunctionAndMethodMap = new Map<string, Famix.Function | Famix.ParametricFunction | Famix.Method | Famix.ParametricMethod> // Maps the function names to their Famix model
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
30
48
  private UNKNOWN_VALUE = '(unknown due to parsing error)'; // The value to use when a name is not usable
31
49
  public fmxElementObjectMap = new Map<Famix.Entity,TSMorphObjectType>();
32
50
  public tsMorphElementObjectMap = new Map<TSMorphObjectType,Famix.Entity>();
@@ -80,8 +98,10 @@ export class EntityDictionary {
80
98
  sourceAnchor.startPos = sourceStart + 1;
81
99
  sourceAnchor.endPos = sourceEnd + 1;
82
100
 
83
- const fileName = node.getSourceFile().getFilePath();
84
-
101
+ let fileName = node.getSourceFile().getFilePath() as string;
102
+ if (fileName.startsWith("/")) {
103
+ fileName = fileName.substring(1);
104
+ }
85
105
  sourceAnchor.element = fmx;
86
106
  sourceAnchor.fileName = fileName;
87
107
  fmx.sourceAnchor = sourceAnchor;
@@ -97,10 +117,10 @@ export class EntityDictionary {
97
117
  * @param famixElement The Famix model of the source element
98
118
  */
99
119
  public makeFamixIndexFileAnchor(sourceElement: TSMorphObjectType, famixElement: Famix.SourcedEntity): void {
100
- // check if famixElement doesn't have a valid fullyQualifiedName
101
- if (typeof (famixElement as any).getFullyQualifiedName === 'function') {
102
- // The method exists
103
- const fullyQualifiedName = (famixElement as any).fullyQualifiedName;
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;
104
124
  if (!fullyQualifiedName || fullyQualifiedName === this.UNKNOWN_VALUE) {
105
125
  throw new Error(`Famix element ${famixElement.constructor.name} has no valid fullyQualifiedName.`);
106
126
  }
@@ -130,13 +150,19 @@ export class EntityDictionary {
130
150
  // revert any backslashes to forward slashes (path.normalize on windows introduces them)
131
151
  pathInProject = pathInProject.replace(/\\/g, "/");
132
152
 
153
+ if (pathInProject.startsWith("/")) {
154
+ pathInProject = pathInProject.substring(1);
155
+ }
156
+
133
157
  fmxIndexFileAnchor.fileName = pathInProject;
134
- let sourceStart, sourceEnd, sourceLineStart, sourceLineEnd: number;
158
+ let sourceStart, sourceEnd
159
+ // ,sourceLineStart, sourceLineEnd
160
+ : number;
135
161
  if (!(sourceElement instanceof CommentRange)) {
136
162
  sourceStart = sourceElement.getStart();
137
163
  sourceEnd = sourceElement.getEnd();
138
- sourceLineStart = sourceElement.getStartLineNumber();
139
- sourceLineEnd = sourceElement.getEndLineNumber();
164
+ // sourceLineStart = sourceElement.getStartLineNumber();
165
+ // sourceLineEnd = sourceElement.getEndLineNumber();
140
166
  } else {
141
167
  sourceStart = sourceElement.getPos();
142
168
  sourceEnd = sourceElement.getEnd();
@@ -226,55 +252,58 @@ export class EntityDictionary {
226
252
 
227
253
  /**
228
254
  * Creates or gets a Famix Module
229
- * @param m A module
255
+ * @param moduleDeclaration A module
230
256
  * @returns The Famix model of the module
231
257
  */
232
- public createOrGetFamixModule(m: ModuleDeclaration): Famix.Module {
233
- let fmxModule: Famix.Module;
234
- const moduleName = m.getName();
235
- const foundModuleName = this.fmxModuleMap.get(moduleName);
236
- if (!foundModuleName) {
237
- fmxModule = new Famix.Module();
238
- fmxModule.name = moduleName;
239
- fmxModule.isAmbient = isAmbient(m);
240
- fmxModule.isNamespace = isNamespace(m);
241
- fmxModule.isModule = !fmxModule.isNamespace && !fmxModule.isAmbient;
242
-
243
- initFQN(m, fmxModule);
244
- this.makeFamixIndexFileAnchor(m, fmxModule);
245
-
246
- this.fmxModuleMap.set(moduleName, fmxModule);
247
-
248
- this.famixRep.addElement(fmxModule);
249
- }
250
- else {
251
- fmxModule = foundModuleName;
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
+ }
252
266
  }
253
267
 
254
- this.fmxElementObjectMap.set(fmxModule,m);
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);
255
283
  return fmxModule;
256
284
  }
257
285
 
258
286
  /**
259
287
  * Creates a Famix alias
260
- * @param a An alias
288
+ * @param typeAliasDeclaration An alias
261
289
  * @returns The Famix model of the alias
262
290
  */
263
- public createFamixAlias(a: TypeAliasDeclaration): Famix.Alias {
291
+ public createFamixAlias(typeAliasDeclaration: TypeAliasDeclaration): Famix.Alias {
264
292
  let fmxAlias: Famix.Alias;
265
- const aliasName = a.getName();
266
- const aliasFullyQualifiedName = a.getType().getText(); // FQNFunctions.getFQN(a);
293
+ const aliasName = typeAliasDeclaration.getName();
294
+ //const aliasFullyQualifiedName = a.getType().getText(); // FQNFunctions.getFQN(a);
295
+ const aliasFullyQualifiedName = FQNFunctions.getFQN(typeAliasDeclaration);
267
296
  const foundAlias = this.fmxAliasMap.get(aliasFullyQualifiedName);
268
297
  if (!foundAlias) {
269
298
  fmxAlias = new Famix.Alias();
270
- fmxAlias.name = a.getName();
271
- const aliasNameWithGenerics = aliasName + (a.getTypeParameters().length ? ("<" + a.getTypeParameters().map(tp => tp.getName()).join(", ") + ">") : "");
299
+ fmxAlias.name = typeAliasDeclaration.getName();
300
+ const aliasNameWithGenerics = aliasName + (typeAliasDeclaration.getTypeParameters().length ? ("<" + typeAliasDeclaration.getTypeParameters().map(tp => tp.getName()).join(", ") + ">") : "");
272
301
  logger.debug(`> NOTE: alias ${aliasName} has fully qualified name ${aliasFullyQualifiedName} and name with generics ${aliasNameWithGenerics}.`);
273
302
 
274
- const fmxType = this.createOrGetFamixType(aliasNameWithGenerics, a);
303
+ const fmxType = this.createOrGetFamixType(aliasNameWithGenerics, typeAliasDeclaration.getType(), typeAliasDeclaration);
275
304
  fmxAlias.aliasedEntity = fmxType;
276
- initFQN(a, fmxAlias);
277
- this.makeFamixIndexFileAnchor(a, fmxAlias);
305
+ initFQN(typeAliasDeclaration, fmxAlias);
306
+ this.makeFamixIndexFileAnchor(typeAliasDeclaration, fmxAlias);
278
307
 
279
308
  this.fmxAliasMap.set(aliasFullyQualifiedName, fmxAlias);
280
309
 
@@ -283,7 +312,7 @@ export class EntityDictionary {
283
312
  else {
284
313
  fmxAlias = foundAlias;
285
314
  }
286
- this.fmxElementObjectMap.set(fmxAlias,a);
315
+ this.fmxElementObjectMap.set(fmxAlias,typeAliasDeclaration);
287
316
 
288
317
  return fmxAlias;
289
318
  }
@@ -309,7 +338,8 @@ export class EntityDictionary {
309
338
  }
310
339
 
311
340
  fmxClass.name = clsName;
312
- fmxClass.fullyQualifiedName = classFullyQualifiedName;
341
+ initFQN(cls, fmxClass);
342
+ // fmxClass.fullyQualifiedName = classFullyQualifiedName;
313
343
  fmxClass.isAbstract = isAbstract;
314
344
 
315
345
  this.makeFamixIndexFileAnchor(cls, fmxClass);
@@ -379,10 +409,10 @@ export class EntityDictionary {
379
409
  let params = "";
380
410
 
381
411
  concreteArguments.map((param) => {
382
- params = params+param.getText()+','
383
- })
412
+ params = params+param.getText()+',';
413
+ });
384
414
 
385
- params = params.substring(0, params.length - 1)
415
+ params = params.substring(0, params.length - 1);
386
416
 
387
417
  fullyQualifiedFilename = Helpers.replaceLastBetweenTags(fullyQualifiedFilename,params);
388
418
 
@@ -395,9 +425,13 @@ export class EntityDictionary {
395
425
  concElement.fullyQualifiedName = fullyQualifiedFilename;
396
426
  concElement.clearGenericParameters();
397
427
  concreteArguments.map((param) => {
398
- const parameter = this.createOrGetFamixConcreteType(param);
399
- concElement.addConcreteParameter(parameter);
400
- })
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
+ });
401
435
 
402
436
  if (concreteElement instanceof Famix.ParametricClass) {
403
437
  this.fmxClassMap.set(fullyQualifiedFilename, concElement as Famix.ParametricClass);
@@ -441,7 +475,7 @@ export class EntityDictionary {
441
475
  logger.error(`> WARNING: got exception ${error}. Failed to get usable name for property: ${property.getName()}. Continuing...`);
442
476
  }
443
477
 
444
- const fmxType = this.createOrGetFamixType(propTypeName, property);
478
+ const fmxType = this.createOrGetFamixType(propTypeName, property.getType(), property);
445
479
  fmxProperty.declaredType = fmxType;
446
480
 
447
481
  // add the visibility (public, private, etc.) to the fmxProperty
@@ -495,106 +529,85 @@ export class EntityDictionary {
495
529
  * @param currentCC The cyclomatic complexity metrics of the current source file
496
530
  * @returns The Famix model of the method or the accessor
497
531
  */
498
- public createOrGetFamixMethod(method: MethodDeclaration | ConstructorDeclaration | MethodSignature | GetAccessorDeclaration | SetAccessorDeclaration, currentCC: { [key: string]: number }): Famix.Method | Famix.Accessor | Famix.ParametricMethod {
499
- let fmxMethod: Famix.Method | Famix.Accessor | Famix.ParametricMethod;
500
- const isGeneric = method.getTypeParameters().length > 0;
501
- const functionFullyQualifiedName = FQNFunctions.getFQN(method);
502
- if (!this.fmxFunctionAndMethodMap.has(functionFullyQualifiedName)) {
503
-
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;
504
548
  if (method instanceof GetAccessorDeclaration || method instanceof SetAccessorDeclaration) {
505
549
  fmxMethod = new Famix.Accessor();
506
550
  const isGetter = method instanceof GetAccessorDeclaration;
507
551
  const isSetter = method instanceof SetAccessorDeclaration;
508
- if (isGetter) {(fmxMethod as Famix.Accessor).kind = "getter";}
509
- if (isSetter) {(fmxMethod as Famix.Accessor).kind = "setter";}
510
- this.famixRep.addElement(fmxMethod);
511
- }
512
- else {
513
- if (isGeneric) {
514
- fmxMethod = new Famix.ParametricMethod();
515
- }
516
- else {
517
- fmxMethod = new Famix.Method();
518
- }
519
- this.famixRep.addElement(fmxMethod);
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();
520
556
  }
557
+
521
558
  const isConstructor = method instanceof ConstructorDeclaration;
522
559
  const isSignature = method instanceof MethodSignature;
523
-
524
560
  let isAbstract = false;
525
561
  let isStatic = false;
526
562
  if (method instanceof MethodDeclaration || method instanceof GetAccessorDeclaration || method instanceof SetAccessorDeclaration) {
527
563
  isAbstract = method.isAbstract();
528
564
  isStatic = method.isStatic();
529
565
  }
530
-
531
- if (isConstructor) {(fmxMethod as Famix.Accessor).kind = "constructor";}
566
+
567
+ if (isConstructor) { (fmxMethod as Famix.Accessor).kind = "constructor"; }
532
568
  fmxMethod.isAbstract = isAbstract;
533
569
  fmxMethod.isClassSide = isStatic;
534
- fmxMethod.isPrivate = (method instanceof MethodDeclaration || method instanceof GetAccessorDeclaration || method instanceof SetAccessorDeclaration) ? (method.getModifiers().find(x => x.getText() === 'private')) !== undefined : false;
535
- fmxMethod.isProtected = (method instanceof MethodDeclaration || method instanceof GetAccessorDeclaration || method instanceof SetAccessorDeclaration) ? (method.getModifiers().find(x => x.getText() === 'protected')) !== undefined : false;
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;
536
574
  fmxMethod.signature = Helpers.computeSignature(method.getText());
537
-
538
- let methodName: string;
539
- if (isConstructor) {
540
- methodName = "constructor";
541
- }
542
- else {
543
- methodName = (method as MethodDeclaration | MethodSignature | GetAccessorDeclaration | SetAccessorDeclaration).getName();
544
- }
575
+
576
+ const methodName = isConstructor ? "constructor" : method.getName();
545
577
  fmxMethod.name = methodName;
546
-
547
- if (!isConstructor) {
548
- if (method.getName().substring(0, 1) === "#") {
549
- fmxMethod.isPrivate = true;
550
- }
551
- }
552
-
553
- if (!fmxMethod.isPrivate && !fmxMethod.isProtected) {
554
- fmxMethod.isPublic = true;
555
- }
556
- else {
557
- fmxMethod.isPublic = false;
558
- }
559
-
560
- if (!isSignature) {
561
- fmxMethod.cyclomaticComplexity = currentCC[fmxMethod.name];
562
- }
563
- else {
564
- fmxMethod.cyclomaticComplexity = 0;
578
+
579
+ if (!isConstructor && methodName.startsWith("#")) {
580
+ fmxMethod.isPrivate = true;
565
581
  }
566
-
567
- let methodTypeName = this.UNKNOWN_VALUE;
582
+ fmxMethod.isPublic = !fmxMethod.isPrivate && !fmxMethod.isProtected;
583
+
584
+ fmxMethod.cyclomaticComplexity = isSignature ? 0 : (currentCC[methodName] || 0);
585
+ let methodTypeName = this.UNKNOWN_VALUE;
568
586
  try {
569
- methodTypeName = method.getReturnType().getText().trim();
587
+ methodTypeName = method.getReturnType().getText().trim();
588
+ logger.debug(`Method return type: ${methodTypeName}`);
570
589
  } catch (error) {
571
- logger.error(`> WARNING: got exception ${error}. Failed to get usable name for return type of method: ${fmxMethod.name}. Continuing...`);
590
+ logger.error(`Failed to get return type for ${fqn}: ${error}`);
572
591
  }
573
-
574
- const fmxType = this.createOrGetFamixType(methodTypeName, method);
592
+
593
+ const fmxType = this.createOrGetFamixType(methodTypeName, method.getType(), method);
594
+ // console.log(`Created/retrieved return type with FQN: ${fmxType.fullyQualifiedName}`);
575
595
  fmxMethod.declaredType = fmxType;
576
596
  fmxMethod.numberOfLinesOfCode = method.getEndLineNumber() - method.getStartLineNumber();
577
- const parameters = method.getParameters();
578
- fmxMethod.numberOfParameters = parameters.length;
579
-
580
- if (!isSignature) {
581
- fmxMethod.numberOfStatements = method.getStatements().length;
582
- }
583
- else {
584
- fmxMethod.numberOfStatements = 0;
585
- }
586
-
597
+ fmxMethod.numberOfParameters = method.getParameters().length;
598
+ fmxMethod.numberOfStatements = isSignature ? 0 : method.getStatements().length;
599
+
600
+ // Add to famixRep
587
601
  initFQN(method, fmxMethod);
602
+ this.famixRep.addElement(fmxMethod);
588
603
  this.makeFamixIndexFileAnchor(method, fmxMethod);
589
-
590
- this.fmxFunctionAndMethodMap.set(functionFullyQualifiedName, fmxMethod);
591
- }
592
- else {
593
- fmxMethod = this.fmxFunctionAndMethodMap.get(functionFullyQualifiedName) as (Famix.Method | Famix.Accessor | Famix.ParametricMethod);
604
+ this.fmxFunctionAndMethodMap.set(fqn, fmxMethod);
605
+ logger.debug(`Added method ${fqn} to famixRep`);
606
+ } else {
607
+ logger.debug(`Method ${fqn} already exists`);
594
608
  }
595
-
596
- this.fmxElementObjectMap.set(fmxMethod,method);
597
-
609
+
610
+ this.fmxElementObjectMap.set(fmxMethod, method);
598
611
  return fmxMethod;
599
612
  }
600
613
 
@@ -606,7 +619,7 @@ export class EntityDictionary {
606
619
  */
607
620
  public createOrGetFamixFunction(func: FunctionDeclaration | FunctionExpression, currentCC: { [key: string]: number }): Famix.Function | Famix.ParametricFunction {
608
621
  let fmxFunction: Famix.Function | Famix.ParametricFunction;
609
- const isGeneric = func.getTypeParameters().length > 0;
622
+ const isGeneric = func.getTypeParameters().length > 0;
610
623
  const functionFullyQualifiedName = FQNFunctions.getFQN(func);
611
624
  if (!this.fmxFunctionAndMethodMap.has(functionFullyQualifiedName)) {
612
625
  if (isGeneric) {
@@ -626,7 +639,8 @@ export class EntityDictionary {
626
639
 
627
640
  fmxFunction.signature = Helpers.computeSignature(func.getText());
628
641
  fmxFunction.cyclomaticComplexity = currentCC[fmxFunction.name];
629
- fmxFunction.fullyQualifiedName = functionFullyQualifiedName;
642
+ initFQN(func, fmxFunction);
643
+ // fmxFunction.fullyQualifiedName = functionFullyQualifiedName;
630
644
 
631
645
  let functionTypeName = this.UNKNOWN_VALUE;
632
646
  try {
@@ -635,7 +649,7 @@ export class EntityDictionary {
635
649
  logger.error(`> WARNING: got exception ${error}. Failed to get usable name for return type of function: ${func.getName()}. Continuing...`);
636
650
  }
637
651
 
638
- const fmxType = this.createOrGetFamixType(functionTypeName, func);
652
+ const fmxType = this.createOrGetFamixType(functionTypeName, func.getType(), func);
639
653
  fmxFunction.declaredType = fmxType;
640
654
  fmxFunction.numberOfLinesOfCode = func.getEndLineNumber() - func.getStartLineNumber();
641
655
  const parameters = func.getParameters();
@@ -661,7 +675,16 @@ export class EntityDictionary {
661
675
  * @param param A parameter
662
676
  * @returns The Famix model of the parameter
663
677
  */
664
- public createFamixParameter(param: ParameterDeclaration): Famix.Parameter {
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
+
665
688
  const fmxParam = new Famix.Parameter();
666
689
 
667
690
  let paramTypeName = this.UNKNOWN_VALUE;
@@ -671,7 +694,7 @@ export class EntityDictionary {
671
694
  logger.error(`> WARNING: got exception ${error}. Failed to get usable name for parameter: ${param.getName()}. Continuing...`);
672
695
  }
673
696
 
674
- const fmxType = this.createOrGetFamixType(paramTypeName, param);
697
+ const fmxType = this.createOrGetFamixType(paramTypeName, param.getType(), param);
675
698
  fmxParam.declaredType = fmxType;
676
699
  fmxParam.name = param.getName();
677
700
 
@@ -680,7 +703,8 @@ export class EntityDictionary {
680
703
 
681
704
  this.famixRep.addElement(fmxParam);
682
705
 
683
- this.fmxElementObjectMap.set(fmxParam,param);
706
+ this.fmxElementObjectMap.set(fmxParam, param);
707
+ this.fmxParameterMap.set(param, fmxParam);
684
708
 
685
709
  return fmxParam;
686
710
  }
@@ -705,84 +729,110 @@ export class EntityDictionary {
705
729
  return fmxParameterType;
706
730
  }
707
731
 
708
- /**
709
- * Creates a Famix type parameter
710
- * @param tp A type parameter
711
- * @returns The Famix model of the type parameter
712
- */
713
- public createOrGetFamixConcreteType(param: TypeNode): Famix.ParameterType | Famix.PrimitiveType | Famix.Class | Famix.Interface {
714
- const typeParameterDeclaration = param.getSymbol()?.getDeclarations()[0] as TypeParameterDeclaration;
715
- const parameterTypeName : string = param.getText();
716
- let fmxParameterType: Famix.Type | Famix.Class | Famix.Interface | undefined = undefined;
717
-
718
- let isClassOrInterface = false;
719
- if (this.fmxClassMap.has(parameterTypeName)){
720
- this.fmxClassMap.forEach((obj, name) => {
721
- if(obj instanceof Famix.ParametricClass){
722
- if (name === param.getText() && obj.genericParameters.size>0) {
723
- fmxParameterType = obj;
724
- isClassOrInterface = true;
725
- }
726
- } else {
727
- if (name === param.getText()) {
728
- fmxParameterType = obj;
729
- isClassOrInterface = true;
730
- }
731
- }
732
- })
733
- }
734
-
735
- if (this.fmxInterfaceMap.has(parameterTypeName)){
736
- this.fmxInterfaceMap.forEach((obj, name) => {
737
- if(obj instanceof Famix.ParametricInterface){
738
- if (name === param.getText() && obj.genericParameters.size>0) {
739
- fmxParameterType = obj;
740
- isClassOrInterface = true;
741
- }
742
- } else {
743
- if (name === param.getText()) {
744
- fmxParameterType = obj;
745
- isClassOrInterface = true;
746
- }
747
- }
748
- })
749
- }
750
732
 
751
- if(!isClassOrInterface){
752
- if (!this.fmxTypeMap.has(parameterTypeName)) {
753
- if (parameterTypeName === "number" || parameterTypeName === "string" || parameterTypeName === "boolean" || parameterTypeName === "bigint" || parameterTypeName === "symbol" || parameterTypeName === "undefined" || parameterTypeName === "null" || parameterTypeName === "any" || parameterTypeName === "unknown" || parameterTypeName === "never" || parameterTypeName === "void") {
754
- fmxParameterType = new Famix.PrimitiveType();
755
- fmxParameterType.isStub = true;
756
- } else {
757
- fmxParameterType = new Famix.ParameterType();
758
- }
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
+ // }
759
801
 
760
- fmxParameterType.name = parameterTypeName;
761
- this.famixRep.addElement(fmxParameterType);
762
- this.fmxTypeMap.set(parameterTypeName, fmxParameterType);
763
- this.fmxElementObjectMap.set(fmxParameterType,typeParameterDeclaration);
764
- }
765
- else {
766
- const result = this.fmxTypeMap.get(parameterTypeName);
767
- if (result) {
768
- fmxParameterType = result;
769
- } else {
770
- throw new Error(`Famix type ${parameterTypeName} is not found in the Type map.`);
771
- }
772
- }
773
- }
774
- if (!fmxParameterType) {
775
- throw new Error(`fmxParameterType was undefined for parameterTypeName ${parameterTypeName}`);
776
- }
777
- return fmxParameterType;
778
- }
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
+ // }
779
821
 
780
822
  /**
781
823
  * Creates a Famix variable
782
824
  * @param variable A variable
783
825
  * @returns The Famix model of the variable
784
826
  */
785
- public createFamixVariable(variable: VariableDeclaration): Famix.Variable {
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
+ }
786
836
  const fmxVariable = new Famix.Variable();
787
837
 
788
838
  let variableTypeName = this.UNKNOWN_VALUE;
@@ -792,7 +842,7 @@ export class EntityDictionary {
792
842
  logger.error(`> WARNING: got exception ${error}. Failed to get usable name for variable: ${variable.getName()}. Continuing...`);
793
843
  }
794
844
 
795
- const fmxType = this.createOrGetFamixType(variableTypeName, variable);
845
+ const fmxType = this.createOrGetFamixType(variableTypeName, variable.getType(), variable);
796
846
  fmxVariable.declaredType = fmxType;
797
847
  fmxVariable.name = variable.getName();
798
848
  initFQN(variable, fmxVariable);
@@ -801,6 +851,7 @@ export class EntityDictionary {
801
851
  this.famixRep.addElement(fmxVariable);
802
852
 
803
853
  this.fmxElementObjectMap.set(fmxVariable,variable);
854
+ this.fmxVariableMap.set(variable, fmxVariable);
804
855
 
805
856
  return fmxVariable;
806
857
  }
@@ -810,7 +861,15 @@ export class EntityDictionary {
810
861
  * @param enumEntity An enum
811
862
  * @returns The Famix model of the enum
812
863
  */
813
- public createFamixEnum(enumEntity: EnumDeclaration): Famix.Enum {
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
+ }
814
873
  const fmxEnum = new Famix.Enum();
815
874
  fmxEnum.name = enumEntity.getName();
816
875
  initFQN(enumEntity, fmxEnum);
@@ -819,6 +878,7 @@ export class EntityDictionary {
819
878
  this.famixRep.addElement(fmxEnum);
820
879
 
821
880
  this.fmxElementObjectMap.set(fmxEnum,enumEntity);
881
+ this.fmxEnumMap.set(enumEntity, fmxEnum);
822
882
 
823
883
  return fmxEnum;
824
884
  }
@@ -838,7 +898,7 @@ export class EntityDictionary {
838
898
  logger.error(`> WARNING: got exception ${error}. Failed to get usable name for enum value: ${enumMember.getName()}. Continuing...`);
839
899
  }
840
900
 
841
- const fmxType = this.createOrGetFamixType(enumValueTypeName, enumMember);
901
+ const fmxType = this.createOrGetFamixType(enumValueTypeName, enumMember.getType(), enumMember);
842
902
  fmxEnumValue.declaredType = fmxType;
843
903
  fmxEnumValue.name = enumMember.getName();
844
904
  initFQN(enumMember, fmxEnumValue);
@@ -905,77 +965,218 @@ export class EntityDictionary {
905
965
  * @param element A ts-morph element
906
966
  * @returns The Famix model of the type
907
967
  */
908
- public createOrGetFamixType(typeName: string, element: TypeDeclaration): Famix.Type | Famix.PrimitiveType | Famix.ParameterType {
909
- let fmxType: Famix.Type | Famix.PrimitiveType | Famix.ParameterType;
910
- let isPrimitiveType = false;
911
- let isParameterType = false;
912
-
913
- logger.debug("Creating (or getting) type: '" + typeName + "' of element: " + element?.getText() + " of kind: " + element?.getKindName());
914
- let ancestor: Famix.ContainerEntity | undefined = undefined;
915
- if (element !== undefined) {
916
- const typeAncestor = Helpers.findTypeAncestor(element);
917
- if (!typeAncestor) {
918
- throw new Error(`Ancestor not found for element ${element.getText()}.`);
919
- }
920
- const ancestorFullyQualifiedName = FQNFunctions.getFQN(typeAncestor);
921
- ancestor = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName) as Famix.ContainerEntity;
922
- if (!ancestor) {
923
- logger.debug(`Ancestor ${FQNFunctions.getFQN(typeAncestor)} not found. Adding the new type.`);
924
- ancestor = this.createOrGetFamixType(typeAncestor.getText(), typeAncestor as TypeDeclaration);
925
- }
926
- }
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()}`);
927
970
 
928
- if (typeName === "number" || typeName === "string" || typeName === "boolean" || typeName === "bigint" || typeName === "symbol" || typeName === "undefined" || typeName === "null" || typeName === "any" || typeName === "unknown" || typeName === "never" || typeName === "void") {
929
- isPrimitiveType = true;
930
- }
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;
931
974
 
932
- if(!isPrimitiveType && typeName.includes("<") && typeName.includes(">") && !(typeName.includes("=>"))) {
933
- isParameterType = true;
975
+ if (isPrimitiveType(typeName)) {
976
+ return this.createOrGetFamixPrimitiveType(typeName);
934
977
  }
935
978
 
936
- if (!this.fmxTypeMap.has(typeName)) {
937
- if (isPrimitiveType) {
938
- fmxType = new Famix.PrimitiveType();
939
- fmxType.isStub = true;
940
- }
941
- else if (isParameterType) {
942
- fmxType = new Famix.ParameterType();
943
- const parameterTypeNames = typeName.substring(typeName.indexOf("<") + 1, typeName.indexOf(">")).split(",").map(s => s.trim());
944
- const baseTypeName = typeName.substring(0, typeName.indexOf("<")).trim();
945
- parameterTypeNames.forEach(parameterTypeName => {
946
- const fmxParameterType = this.createOrGetFamixType(parameterTypeName, element);
947
- (fmxType as Famix.ParameterType).addArgument(fmxParameterType);
948
- });
949
- const fmxBaseType = this.createOrGetFamixType(baseTypeName, element);
950
- (fmxType as Famix.ParameterType).baseType = fmxBaseType;
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;
951
988
  }
952
- else {
953
- fmxType = new Famix.Type();
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
+ }
954
1003
  }
1004
+
1005
+ this.famixRep.addElement(fmxType);
1006
+ this.fmxTypeMap.set(element, fmxType);
1007
+ this.fmxElementObjectMap.set(fmxType, element);
1008
+ return fmxType;
1009
+ }
955
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();
956
1042
  fmxType.name = typeName;
957
- if (!ancestor) {
1043
+ // console.log(`Created new type with name: ${typeName}`);
1044
+ if (ancestor) {
1045
+ fmxType.container = ancestor;
1046
+ } else {
958
1047
  throw new Error(`Ancestor not found for type ${typeName}.`);
959
1048
  }
960
- fmxType.container = ancestor;
1049
+
961
1050
  initFQN(element, fmxType);
1051
+ // console.log(`Type FQN after init: ${fmxType.fullyQualifiedName}`);
962
1052
  this.makeFamixIndexFileAnchor(element, fmxType);
963
-
964
1053
  this.famixRep.addElement(fmxType);
965
-
966
- this.fmxTypeMap.set(typeName, 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;
967
1062
  }
968
- else {
969
- const result = this.fmxTypeMap.get(typeName);
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);
970
1079
  if (result) {
971
- fmxType = result;
1080
+ return result;
972
1081
  } else {
973
- throw new Error(`Famix type ${typeName} is not found in the Type map.`);
1082
+ throw new Error(`Famix type ${typeName} is not found (undefined) in the Type map.`);
974
1083
  }
975
1084
  }
976
1085
 
977
- this.fmxElementObjectMap.set(fmxType,element);
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
978
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
+ }
979
1180
  return fmxType;
980
1181
  }
981
1182
 
@@ -989,128 +1190,199 @@ export class EntityDictionary {
989
1190
  if (!fmxVar) {
990
1191
  throw new Error(`Famix entity with id ${id} not found, for node ${node.getText()} in ${node.getSourceFile().getBaseName()} at line ${node.getStartLineNumber()}.`);
991
1192
  }
992
-
1193
+
993
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}'.`);
994
-
1195
+
995
1196
  const nodeReferenceAncestor = Helpers.findAncestor(node);
1197
+ if (!nodeReferenceAncestor) {
1198
+ logger.error(`No ancestor found for node '${node.getText()}'`);
1199
+ return;
1200
+ }
1201
+
996
1202
  const ancestorFullyQualifiedName = FQNFunctions.getFQN(nodeReferenceAncestor);
997
- let accessor = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName) as Famix.ContainerEntity;
1203
+ const accessor = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName) as Famix.ContainerEntity;
998
1204
  if (!accessor) {
999
1205
  logger.error(`Ancestor ${ancestorFullyQualifiedName} of kind ${nodeReferenceAncestor.getKindName()} not found.`);
1000
- // accessor = this.createOrGetFamixType(ancestorFullyQualifiedName, nodeReferenceAncestor as TypeDeclaration);
1206
+ return; // Bail out for now
1207
+ } else {
1208
+ logger.debug(`Found accessor to be ${accessor.fullyQualifiedName}.`);
1001
1209
  }
1002
-
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
+
1003
1224
  const fmxAccess = new Famix.Access();
1004
1225
  fmxAccess.accessor = accessor;
1005
1226
  fmxAccess.variable = fmxVar;
1006
-
1007
1227
  this.famixRep.addElement(fmxAccess);
1008
-
1009
- this.fmxElementObjectMap.set(fmxAccess,node);
1228
+ this.fmxElementObjectMap.set(fmxAccess, node);
1229
+ logger.debug(`Created access: ${accessor.fullyQualifiedName} -> ${fmxVar.fullyQualifiedName}`);
1010
1230
  }
1011
1231
 
1012
1232
  /**
1013
1233
  * Creates a Famix invocation
1014
- * @param node A node
1015
- * @param m A method or a function
1234
+ * @param nodeReferringToInvocable A node
1235
+ * @param invocable A method or a function
1016
1236
  * @param id The id of the method or the function
1017
1237
  */
1018
- public createFamixInvocation(node: Identifier, m: MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression, id: number): void {
1019
- const fmxMethodOrFunction = this.famixRep.getFamixEntityById(id) as Famix.BehavioralEntity;
1020
- const nodeReferenceAncestor = Helpers.findAncestor(node);
1021
- const ancestorFullyQualifiedName = FQNFunctions.getFQN(nodeReferenceAncestor);
1022
- const sender = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName) as Famix.ContainerEntity;
1023
- const receiverFullyQualifiedName = FQNFunctions.getFQN(m.getParent());
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());
1024
1256
  const receiver = this.famixRep.getFamixEntityByFullyQualifiedName(receiverFullyQualifiedName) as Famix.NamedEntity;
1025
1257
 
1026
1258
  const fmxInvocation = new Famix.Invocation();
1027
1259
  fmxInvocation.sender = sender;
1028
1260
  fmxInvocation.receiver = receiver;
1029
- fmxInvocation.addCandidate(fmxMethodOrFunction);
1030
- fmxInvocation.signature = fmxMethodOrFunction.signature;
1261
+ fmxInvocation.addCandidate(fmxInvocable);
1262
+ fmxInvocation.signature = fmxInvocable.signature;
1031
1263
 
1032
1264
  this.famixRep.addElement(fmxInvocation);
1033
1265
 
1034
- this.fmxElementObjectMap.set(fmxInvocation,node);
1266
+ this.fmxElementObjectMap.set(fmxInvocation,nodeReferringToInvocable);
1035
1267
  }
1036
1268
 
1037
1269
  /**
1038
1270
  * Creates a Famix inheritance
1039
- * @param cls A class or an interface (subclass)
1040
- * @param inhClass The inherited class or interface (superclass)
1271
+ * @param baseClassOrInterface A class or an interface (subclass)
1272
+ * @param inheritedClassOrInterface The inherited class or interface (superclass)
1041
1273
  */
1042
- public createFamixInheritance(cls: ClassDeclaration | InterfaceDeclaration, inhClass: ClassDeclaration | InterfaceDeclaration | ExpressionWithTypeArguments): void {
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}].`);
1043
1276
  const fmxInheritance = new Famix.Inheritance();
1044
- // const clsName = cls.getName();
1045
- const classFullyQualifiedName = FQNFunctions.getFQN(cls);
1046
- logger.debug(`createFamixInheritance: classFullyQualifiedName: class fqn = ${classFullyQualifiedName}`);
1277
+
1047
1278
  let subClass: Famix.Class | Famix.Interface | undefined;
1048
- if (cls instanceof ClassDeclaration) {
1049
- subClass = this.fmxClassMap.get(classFullyQualifiedName);
1050
- }
1051
- else {
1052
- subClass = this.fmxInterfaceMap.get(classFullyQualifiedName);
1279
+ if (baseClassOrInterface instanceof ClassDeclaration) {
1280
+ subClass = this.createOrGetFamixClass(baseClassOrInterface);
1281
+ } else {
1282
+ subClass = this.createOrGetFamixInterface(baseClassOrInterface);
1053
1283
  }
1284
+
1054
1285
  if (!subClass) {
1055
- throw new Error(`Subclass ${classFullyQualifiedName} not found in Class or Interface maps.`);
1286
+ throw new Error(`Subclass ${baseClassOrInterface} not found in Class or Interface maps.`);
1056
1287
  }
1057
-
1058
- let inhClassName: string | undefined;
1059
- let inhClassFullyQualifiedName: string;
1288
+
1060
1289
  let superClass: Famix.Class | Famix.Interface | undefined;
1061
- if (inhClass instanceof ClassDeclaration || inhClass instanceof InterfaceDeclaration) {
1062
- inhClassName = inhClass.getName();
1063
- if (!inhClassName) {
1064
- throw new Error(`Inherited class or interface name not found for ${inhClass.getText()}.`);
1065
- }
1066
- inhClassFullyQualifiedName = FQNFunctions.getFQN(inhClass);
1067
- if (inhClass instanceof ClassDeclaration) {
1068
- superClass = this.fmxClassMap.get(inhClassFullyQualifiedName);
1069
- }
1070
- else {
1071
- superClass = this.fmxInterfaceMap.get(inhClassFullyQualifiedName);
1072
- }
1073
- if (!superClass) {
1074
- throw new Error(`Superclass ${classFullyQualifiedName} not found in Class or Interface maps.`);
1075
- }
1076
- }
1077
- else {
1078
- // inhClass is an ExpressionWithTypeArguments
1079
- inhClassName = inhClass.getExpression().getText();
1080
- // what is inhClassFullyQualifiedName? TODO
1081
- inhClassFullyQualifiedName = 'Undefined_Scope_from_importer.' + inhClassName;
1082
- }
1083
1290
 
1084
- if (superClass === undefined) {
1085
- if (inhClass instanceof ClassDeclaration) {
1086
- superClass = new Famix.Class();
1087
- this.fmxClassMap.set(inhClassFullyQualifiedName, superClass);
1088
- }
1089
- else {
1090
- superClass = new Famix.Interface();
1091
- this.fmxInterfaceMap.set(inhClassFullyQualifiedName, superClass);
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()}.`);
1092
1331
  }
1093
1332
 
1094
- this.fmxElementObjectMap.set(superClass,inhClass);
1333
+ }
1095
1334
 
1096
- superClass.name = inhClassName;
1097
- superClass.fullyQualifiedName = inhClassFullyQualifiedName;
1098
- superClass.isStub = true;
1335
+ this.fmxElementObjectMap.set(superClass, inheritedClassOrInterface);
1099
1336
 
1100
- this.makeFamixIndexFileAnchor(inhClass, superClass);
1101
-
1102
- this.famixRep.addElement(superClass);
1103
- }
1337
+ this.makeFamixIndexFileAnchor(inheritedClassOrInterface, superClass);
1338
+
1339
+ this.famixRep.addElement(superClass);
1104
1340
 
1105
1341
  fmxInheritance.subclass = subClass;
1106
1342
  fmxInheritance.superclass = superClass;
1107
1343
 
1108
1344
  this.famixRep.addElement(fmxInheritance);
1345
+ // no FQN for inheritance
1109
1346
 
1110
1347
  // We don't map inheritance to the source code element because there are two elements (super, sub)
1111
1348
  // this.fmxElementObjectMap.set(fmxInheritance, null);
1112
1349
 
1113
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
+ }
1114
1386
 
1115
1387
  public createFamixImportClause(importedEntity: Famix.NamedEntity, importingEntity: Famix.Module) {
1116
1388
  const fmxImportClause = new Famix.ImportClause();
@@ -1130,31 +1402,41 @@ export class EntityDictionary {
1130
1402
  * @param isInExports A boolean indicating if the imported entity is in the exports
1131
1403
  * @param isDefaultExport A boolean indicating if the imported entity is a default export
1132
1404
  */
1133
- public oldCreateFamixImportClause(importClauseInfo: {importDeclaration?: ImportDeclaration | ImportEqualsDeclaration, importerSourceFile: SourceFile, moduleSpecifierFilePath: string, importElement: ImportSpecifier | Identifier, isInExports: boolean, isDefaultExport: boolean}): void {
1405
+ public oldCreateOrGetFamixImportClause(importClauseInfo: {importDeclaration?: ImportDeclaration | ImportEqualsDeclaration, importerSourceFile: SourceFile, moduleSpecifierFilePath: string, importElement: ImportSpecifier | Identifier, isInExports: boolean, isDefaultExport: boolean}): void {
1134
1406
  const {importDeclaration, importerSourceFile: importer, moduleSpecifierFilePath, importElement, isInExports, isDefaultExport} = importClauseInfo;
1135
- logger.debug(`createFamixImportClause: Creating import clause:`);
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()}.`);
1136
1418
  const fmxImportClause = new Famix.ImportClause();
1137
-
1419
+
1138
1420
  let importedEntity: Famix.NamedEntity | Famix.StructuralEntity | undefined = undefined;
1139
1421
  let importedEntityName: string;
1140
-
1422
+
1141
1423
  const absolutePathProject = this.famixRep.getAbsolutePath();
1142
1424
 
1143
1425
  const absolutePath = path.normalize(moduleSpecifierFilePath);
1144
- // convert the path and remove any windows backslashes introduced by path.normalize
1426
+ logger.debug(`createFamixImportClause: absolutePath: ${absolutePath}`);
1427
+ logger.debug(`createFamixImportClause: convertToRelativePath: ${this.convertToRelativePath(absolutePath, absolutePathProject)}`);
1145
1428
  const pathInProject: string = this.convertToRelativePath(absolutePath, absolutePathProject).replace(/\\/g, "/");
1429
+ logger.debug(`createFamixImportClause: pathInProject: ${pathInProject}`);
1146
1430
  let pathName = "{" + pathInProject + "}.";
1147
-
1148
- // Named imports, e.g. import { ClassW } from "./complexExportModule";
1149
-
1150
- // Start with simple import clause (without referring to the actual variable)
1151
-
1431
+ logger.debug(`createFamixImportClause: pathName: ${pathName}`);
1432
+
1152
1433
  if (importDeclaration instanceof ImportDeclaration
1153
1434
  && importElement instanceof ImportSpecifier) {
1154
1435
  importedEntityName = importElement.getName();
1155
1436
  pathName = pathName + importedEntityName;
1156
1437
  if (isInExports) {
1157
1438
  importedEntity = this.famixRep.getFamixEntityByFullyQualifiedName(pathName) as Famix.NamedEntity;
1439
+ logger.debug(`Found exported entity: ${pathName}`);
1158
1440
  }
1159
1441
  if (importedEntity === undefined) {
1160
1442
  importedEntity = new Famix.NamedEntity();
@@ -1162,34 +1444,37 @@ export class EntityDictionary {
1162
1444
  if (!isInExports) {
1163
1445
  importedEntity.isStub = true;
1164
1446
  }
1165
- importedEntity.fullyQualifiedName = pathName;
1447
+ logger.debug(`Creating named entity ${importedEntityName} for ImportSpecifier ${importElement.getText()}`);
1448
+ initFQN(importElement, importedEntity);
1449
+ logger.debug(`Assigned FQN to entity: ${importedEntity.fullyQualifiedName}`);
1166
1450
  this.makeFamixIndexFileAnchor(importElement, importedEntity);
1167
- // must add entity to repository
1168
1451
  this.famixRep.addElement(importedEntity);
1452
+ logger.debug(`Added entity to repository: ${importedEntity.fullyQualifiedName}`);
1169
1453
  }
1170
1454
  }
1171
- // handle import equals declarations, e.g. import myModule = require("./complexExportModule");
1172
- // TypeScript can't determine the type of the imported module, so we create a Module entity
1173
1455
  else if (importDeclaration instanceof ImportEqualsDeclaration) {
1174
1456
  importedEntityName = importDeclaration?.getName();
1175
1457
  pathName = pathName + importedEntityName;
1176
1458
  importedEntity = new Famix.StructuralEntity();
1177
1459
  importedEntity.name = importedEntityName;
1178
1460
  initFQN(importDeclaration, importedEntity);
1461
+ logger.debug(`Assigned FQN to ImportEquals entity: ${importedEntity.fullyQualifiedName}`);
1179
1462
  this.makeFamixIndexFileAnchor(importElement, importedEntity);
1180
- importedEntity.fullyQualifiedName = pathName;
1181
- const anyType = this.createOrGetFamixType('any', importDeclaration);
1463
+ const anyType = this.createOrGetFamixType('any', undefined, importDeclaration);
1182
1464
  (importedEntity as Famix.StructuralEntity).declaredType = anyType;
1183
- } else { // default imports, e.g. import ClassW from "./complexExportModule";
1465
+ } else {
1184
1466
  importedEntityName = importElement.getText();
1185
1467
  pathName = pathName + (isDefaultExport ? "defaultExport" : "namespaceExport");
1186
1468
  importedEntity = new Famix.NamedEntity();
1187
1469
  importedEntity.name = importedEntityName;
1188
- importedEntity.fullyQualifiedName = pathName;
1470
+ initFQN(importElement, importedEntity);
1471
+ logger.debug(`Assigned FQN to default/namespace entity: ${importedEntity.fullyQualifiedName}`);
1189
1472
  this.makeFamixIndexFileAnchor(importElement, importedEntity);
1190
1473
  }
1191
- // I don't think it should be added to the repository if it exists already
1192
- if (!isInExports) this.famixRep.addElement(importedEntity);
1474
+ if (!isInExports) {
1475
+ this.famixRep.addElement(importedEntity);
1476
+ logger.debug(`Added non-exported entity to repository: ${importedEntity.fullyQualifiedName}`);
1477
+ }
1193
1478
  const importerFullyQualifiedName = FQNFunctions.getFQN(importer);
1194
1479
  const fmxImporter = this.famixRep.getFamixEntityByFullyQualifiedName(importerFullyQualifiedName) as Famix.Module;
1195
1480
  fmxImportClause.importingEntity = fmxImporter;
@@ -1200,14 +1485,15 @@ export class EntityDictionary {
1200
1485
  fmxImportClause.moduleSpecifier = importDeclaration?.getModuleSpecifierValue() as string;
1201
1486
  }
1202
1487
 
1203
- logger.debug(`createFamixImportClause: ${fmxImportClause.importedEntity?.name} (of type ${
1204
- Helpers.getSubTypeName(fmxImportClause.importedEntity)}) is imported by ${fmxImportClause.importingEntity?.name}`);
1205
-
1488
+ logger.debug(`ImportClause: ${fmxImportClause.importedEntity?.name} (type=${Helpers.getSubTypeName(fmxImportClause.importedEntity)}) imported by ${fmxImportClause.importingEntity?.name}`);
1489
+
1206
1490
  fmxImporter.addOutgoingImport(fmxImportClause);
1207
-
1208
1491
  this.famixRep.addElement(fmxImportClause);
1209
-
1210
- if (importDeclaration) this.fmxElementObjectMap.set(fmxImportClause, importDeclaration);
1492
+
1493
+ if (importDeclaration) {
1494
+ this.fmxElementObjectMap.set(fmxImportClause, importDeclaration);
1495
+ this.fmxImportClauseMap.set(importDeclaration, fmxImportClause);
1496
+ }
1211
1497
  }
1212
1498
 
1213
1499
  /**
@@ -1215,60 +1501,67 @@ export class EntityDictionary {
1215
1501
  * @param arrowExpression An Expression
1216
1502
  * @returns The Famix model of the variable
1217
1503
  */
1218
- public createFamixArrowFunction(arrowExpression: Expression, currentCC: { [key: string]: number } ): Famix.ArrowFunction | Famix.ParametricArrowFunction {
1504
+ public createOrGetFamixArrowFunction(arrowExpression: Expression, currentCC: { [key: string]: number } ): Famix.ArrowFunction | Famix.ParametricArrowFunction {
1219
1505
 
1220
1506
  let fmxArrowFunction: Famix.ArrowFunction | Famix.ParametricArrowFunction;
1507
+ const functionFullyQualifiedName = FQNFunctions.getFQN(arrowExpression);
1221
1508
 
1222
- const arrowFunction = arrowExpression.asKindOrThrow(SyntaxKind.ArrowFunction);
1509
+ if (!this.fmxFunctionAndMethodMap.has(functionFullyQualifiedName)) {
1223
1510
 
1224
- const isGeneric = arrowFunction.getTypeParameters().length > 0;
1511
+ const arrowFunction = arrowExpression.asKindOrThrow(SyntaxKind.ArrowFunction);
1225
1512
 
1226
- if (isGeneric) {
1227
- fmxArrowFunction = new Famix.ParametricArrowFunction();
1228
- }
1229
- else {
1230
- fmxArrowFunction = new Famix.ArrowFunction();
1231
- }
1513
+ const isGeneric = arrowFunction.getTypeParameters().length > 0;
1232
1514
 
1233
- // Get the parent of the arrow function (the variable declaration)
1234
- const parent = arrowFunction.getParentIfKind(SyntaxKind.VariableDeclaration);
1235
- let functionName = '(NO_NAME)';
1515
+ if (isGeneric) {
1516
+ fmxArrowFunction = new Famix.ParametricArrowFunction();
1517
+ }
1518
+ else {
1519
+ fmxArrowFunction = new Famix.ArrowFunction();
1520
+ }
1236
1521
 
1237
- if (parent && parent instanceof VariableDeclaration) {
1238
- // Get the name of the variable
1239
- functionName = parent.getName();
1240
- }
1522
+ // Get the parent of the arrow function (the variable declaration)
1523
+ const parent = arrowFunction.getParentIfKind(SyntaxKind.VariableDeclaration);
1524
+ let functionName = '(NO_NAME)';
1241
1525
 
1242
- if (functionName) {
1243
- fmxArrowFunction.name = functionName;
1244
- }
1245
- else {
1246
- fmxArrowFunction.name = "anonymous";
1247
- }
1526
+ if (parent && parent instanceof VariableDeclaration) {
1527
+ // Get the name of the variable
1528
+ functionName = parent.getName();
1529
+ }
1248
1530
 
1249
- // Signature of an arrow function is (parameters) => return_type
1250
- const parametersSignature = arrowFunction.getParameters().map(p => p.getText()).join(", ");
1251
- const returnTypeSignature = arrowFunction.getReturnType().getText();
1252
- fmxArrowFunction.signature = `(${parametersSignature}) => ${returnTypeSignature}`;
1253
- fmxArrowFunction.cyclomaticComplexity = currentCC[fmxArrowFunction.name];
1531
+ if (functionName) {
1532
+ fmxArrowFunction.name = functionName;
1533
+ }
1534
+ else {
1535
+ fmxArrowFunction.name = "anonymous";
1536
+ }
1254
1537
 
1255
- let functionTypeName = this.UNKNOWN_VALUE;
1256
- try {
1257
- functionTypeName = arrowFunction.getReturnType().getText().trim();
1258
- } catch (error) {
1259
- logger.error(`> WARNING: got exception ${error}. Failed to get usable name for return type of function: ${functionName}. Continuing...`);
1260
- }
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
+ }
1261
1550
 
1262
- const fmxType = this.createOrGetFamixType(functionTypeName, arrowFunction as unknown as FunctionDeclaration);
1263
- fmxArrowFunction.declaredType = fmxType;
1264
- fmxArrowFunction.numberOfLinesOfCode = arrowFunction.getEndLineNumber() - arrowFunction.getStartLineNumber();
1265
- const parameters = arrowFunction.getParameters();
1266
- fmxArrowFunction.numberOfParameters = parameters.length;
1267
- fmxArrowFunction.numberOfStatements = arrowFunction.getStatements().length;
1268
- initFQN(arrowExpression as unknown as TSMorphObjectType, fmxArrowFunction);
1269
- this.makeFamixIndexFileAnchor(arrowExpression as unknown as TSMorphObjectType, fmxArrowFunction);
1270
- this.famixRep.addElement(fmxArrowFunction);
1271
- this.fmxElementObjectMap.set(fmxArrowFunction,arrowFunction as unknown as TSMorphObjectType);
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
+ }
1272
1565
 
1273
1566
  return fmxArrowFunction;
1274
1567
  }
@@ -1285,8 +1578,8 @@ export class EntityDictionary {
1285
1578
  fmxConcretisation.concreteEntity = conEntity;
1286
1579
  fmxConcretisation.genericEntity = genEntity;
1287
1580
  // this.fmxElementObjectMap.set(fmxConcretisation,null);
1288
- this.famixRep.addElement(fmxConcretisation);
1289
- const parameterConcretisation = this.createFamixParameterConcretisation(fmxConcretisation);
1581
+ this.famixRep.addElement(fmxConcretisation);
1582
+ // const parameterConcretisation = this.createFamixParameterConcretisation(fmxConcretisation);
1290
1583
 
1291
1584
  return fmxConcretisation;
1292
1585
  }
@@ -1304,8 +1597,8 @@ export class EntityDictionary {
1304
1597
  const concreteParameters = conClass.concreteParameters;
1305
1598
  const genericParameters = genClass.genericParameters;
1306
1599
 
1307
- let conClassTypeParametersIterator = concreteParameters.values();
1308
- let genClassTypeParametersIterator = genericParameters.values();
1600
+ const conClassTypeParametersIterator = concreteParameters.values();
1601
+ const genClassTypeParametersIterator = genericParameters.values();
1309
1602
  let fmxParameterConcretisation : Famix.ParameterConcretisation | undefined = undefined;
1310
1603
 
1311
1604
  for (let i = 0; i < genericParameters.size; i++) {
@@ -1318,7 +1611,7 @@ export class EntityDictionary {
1318
1611
  createParameterConcretisation = false;
1319
1612
  fmxParameterConcretisation = param;
1320
1613
  }
1321
- })
1614
+ });
1322
1615
  if (createParameterConcretisation) {
1323
1616
  fmxParameterConcretisation = new Famix.ParameterConcretisation();
1324
1617
  fmxParameterConcretisation.genericParameter = genClassTypeParameter;
@@ -1376,11 +1669,10 @@ export class EntityDictionary {
1376
1669
  genEntity = this.createOrGetFamixInterface(EntityDeclaration) as Famix.ParametricInterface;
1377
1670
  }
1378
1671
  const genParams = EntityDeclaration.getTypeParameters().map((param) => param.getText());
1379
- const args = element.getHeritageClauses()[0].getTypeNodes()[0].getTypeArguments()
1672
+ const args = element.getHeritageClauses()[0].getTypeNodes()[0].getTypeArguments();
1380
1673
  const conParams = element.getHeritageClauses()[0].getTypeNodes()[0].getTypeArguments().map((param) => param.getText());
1381
1674
  if (!Helpers.arraysAreEqual(conParams,genParams)) {
1382
- let conEntity;
1383
- conEntity = this.createOrGetFamixConcreteElement(genEntity,EntityDeclaration,args);
1675
+ const conEntity = this.createOrGetFamixConcreteElement(genEntity,EntityDeclaration,args);
1384
1676
  const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1385
1677
  let createConcretisation : boolean = true;
1386
1678
  concretisations.forEach((conc : Famix.Concretisation) => {
@@ -1421,8 +1713,7 @@ export class EntityDictionary {
1421
1713
  const genEntity = this.createOrGetFamixClass(cls) as Famix.ParametricClass;
1422
1714
  const genParams = cls.getTypeParameters().map((param) => param.getText());
1423
1715
  if (!Helpers.arraysAreEqual(conParams,genParams)) {
1424
- let conEntity;
1425
- conEntity = this.createOrGetFamixConcreteElement(genEntity,cls,instance.getTypeArguments());
1716
+ const conEntity = this.createOrGetFamixConcreteElement(genEntity,cls,instance.getTypeArguments());
1426
1717
  const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1427
1718
  let createConcretisation : boolean = true;
1428
1719
  concretisations.forEach((conc : Famix.Concretisation) => {
@@ -1436,7 +1727,7 @@ export class EntityDictionary {
1436
1727
  }
1437
1728
  }
1438
1729
  }
1439
- })
1730
+ });
1440
1731
  }
1441
1732
  // TODO: This function seems unfinished
1442
1733
  }
@@ -1451,7 +1742,7 @@ export class EntityDictionary {
1451
1742
  const genParams = element.getTypeParameters().map(param => param.getText());
1452
1743
  const uses = element.findReferencesAsNodes();
1453
1744
  uses.forEach(usage => {
1454
- let currentNode: Node | undefined = usage;
1745
+ let currentNode: TsMorphNode | undefined = usage;
1455
1746
 
1456
1747
  while (currentNode) {
1457
1748
  if (currentNode.getKind() === SyntaxKind.CallExpression) {
@@ -1470,8 +1761,7 @@ export class EntityDictionary {
1470
1761
  } else {
1471
1762
  genElement = this.createOrGetFamixMethod(element, {}) as Famix.ParametricMethod;
1472
1763
  }
1473
- let concElement;
1474
- concElement = this.createOrGetFamixConcreteElement(genElement,element,args);
1764
+ const concElement = this.createOrGetFamixConcreteElement(genElement,element,args);
1475
1765
  const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1476
1766
  let createConcretisation : boolean = true;
1477
1767
  concretisations.forEach((conc : Famix.Concretisation) => {
@@ -1532,13 +1822,13 @@ export class EntityDictionary {
1532
1822
  * @param element A variable or a function
1533
1823
  * @param inter An interface
1534
1824
  */
1535
- public createFamixConcretisationTypeInstanciation(element: InterfaceDeclaration | ClassDeclaration){
1825
+ public createFamixConcretisationTypeInstanciation(element: InterfaceDeclaration | ClassDeclaration) {
1536
1826
 
1537
1827
  const isGeneric = element.getTypeParameters().length > 0;
1538
1828
  if (isGeneric) {
1539
1829
  const genParams = element.getTypeParameters().map(param => param.getText());
1540
1830
  const uses = element.findReferencesAsNodes();
1541
- uses.forEach(use => {
1831
+ uses.forEach(use => {
1542
1832
  let parentNode = use.getParent();
1543
1833
  while (parentNode) {
1544
1834
  if (parentNode.getKind() === SyntaxKind.TypeReference) {
@@ -1547,30 +1837,29 @@ export class EntityDictionary {
1547
1837
  throw new Error(`TypeReferenceNode not found for ${parentNode.getText()}`);
1548
1838
  }
1549
1839
  const typeReferenceNodeIsGeneric = typeReferenceNode.getTypeArguments().length > 0;
1550
- if (typeReferenceNodeIsGeneric) {}
1551
- const args = typeReferenceNode.getTypeArguments();
1552
- const conParams = typeReferenceNode.getTypeArguments().map(param => param.getText());
1553
- if (!Helpers.arraysAreEqual(conParams,genParams)) {
1554
- let genElement;
1555
- if(element instanceof ClassDeclaration){
1556
- genElement = this.createOrGetFamixClass(element) as Famix.ParametricClass;
1557
- } else {
1558
- genElement = this.createOrGetFamixInterface(element) as Famix.ParametricInterface;
1559
- }
1560
- let concElement;
1561
- concElement = this.createOrGetFamixConcreteElement(genElement,element,args);
1562
- const concretisations = this.famixRep._getAllEntitiesWithType("Concretisation") as Set<Famix.Concretisation>;
1563
- let createConcretisation : boolean = true;
1564
- concretisations.forEach((conc : Famix.Concretisation) => {
1565
- if (genElement.fullyQualifiedName == conc.genericEntity.fullyQualifiedName && conc.concreteEntity.fullyQualifiedName == concElement.fullyQualifiedName){
1566
- createConcretisation = false;
1567
- }
1568
- });
1569
-
1570
- if (createConcretisation) {
1571
- const fmxConcretisation : Famix.Concretisation = this.createFamixConcretisation(concElement,genElement);
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;
1572
1856
  }
1857
+ });
1858
+
1859
+ if (createConcretisation) {
1860
+ const fmxConcretisation: Famix.Concretisation = this.createFamixConcretisation(concElement, genElement);
1573
1861
  }
1862
+ }
1574
1863
  break;
1575
1864
  }
1576
1865
  parentNode = parentNode.getParent();
@@ -1580,14 +1869,193 @@ export class EntityDictionary {
1580
1869
  }
1581
1870
 
1582
1871
  public convertToRelativePath(absolutePath: string, absolutePathProject: string) {
1583
- return absolutePath.replace(absolutePathProject, "").slice(1);
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
+ }
1584
1880
  }
1585
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
+
1586
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)
1587
1909
  if (!(sourceElement instanceof CommentRange)) {
1588
1910
  const fqn = FQNFunctions.getFQN(sourceElement);
1589
1911
  logger.debug("Setting fully qualified name for " + famixElement.getJSON() + " to " + fqn);
1590
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
+ }
1591
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;
1592
2028
  }
1593
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
+ // }