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.
package/src/analyze.ts CHANGED
@@ -6,7 +6,7 @@ import * as processFunctions from "./analyze_functions/process_functions";
6
6
  import { EntityDictionary } from "./famix_functions/EntityDictionary";
7
7
  import path from "path";
8
8
 
9
- export const logger = new Logger({ name: "ts2famix", minLevel: 3 });
9
+ export const logger = new Logger({ name: "ts2famix", minLevel: 2 });
10
10
  export const config = { "expectGraphemes": false };
11
11
  export const entityDictionary = new EntityDictionary();
12
12
 
@@ -2,13 +2,14 @@ import { ClassDeclaration, MethodDeclaration, VariableStatement, FunctionDeclara
2
2
  import * as Famix from "../lib/famix/model/famix";
3
3
  import { calculate } from "../lib/ts-complex/cyclomatic-service";
4
4
  import * as fs from 'fs';
5
- import { logger , entityDictionary } from "../analyze";
5
+ import { logger, entityDictionary } from "../analyze";
6
6
  import { getFQN } from "../fqn";
7
+ import { InvocableType } from "src/famix_functions/EntityDictionary";
7
8
 
8
9
  export type AccessibleTSMorphElement = ParameterDeclaration | VariableDeclaration | PropertyDeclaration | EnumMember;
9
10
  export type FamixID = number;
10
11
 
11
- export const methodsAndFunctionsWithId = new Map<number, MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression | ArrowFunction>(); // Maps the Famix method, constructor, getter, setter and function ids to their ts-morph method, constructor, getter, setter or function object
12
+ export const methodsAndFunctionsWithId = new Map<number, InvocableType>(); // Maps the Famix method, constructor, getter, setter and function ids to their ts-morph method, constructor, getter, setter or function object
12
13
 
13
14
  export const accessMap = new Map<FamixID, AccessibleTSMorphElement>(); // Maps the Famix parameter, variable, property and enum value ids to their ts-morph parameter, variable, property or enum member object
14
15
  export const classes = new Array<ClassDeclaration>(); // Array of all the classes of the source files
@@ -16,6 +17,7 @@ export const interfaces = new Array<InterfaceDeclaration>(); // Array of all the
16
17
  export const modules = new Array<SourceFile>(); // Array of all the source files which are modules
17
18
  export const listOfExportMaps = new Array<ReadonlyMap<string, ExportedDeclarations[]>>(); // Array of all the export maps
18
19
  export let currentCC: { [key: string]: number }; // Stores the cyclomatic complexity metrics for the current source file
20
+ const processedNodesWithTypeParams = new Set<number>(); // Set of nodes that have been processed and have type parameters
19
21
 
20
22
  /**
21
23
  * Checks if the file has any imports or exports to be considered a module
@@ -47,6 +49,7 @@ export function getModulePath(importDecl: ImportDeclaration): string {
47
49
  return path;
48
50
  }
49
51
 
52
+
50
53
  /**
51
54
  * Gets the interfaces implemented or extended by a class or an interface
52
55
  * @param interfaces An array of interfaces
@@ -77,19 +80,15 @@ export function getImplementedOrExtendedInterfaces(interfaces: Array<InterfaceDe
77
80
  return implementedOrExtendedInterfaces;
78
81
  }
79
82
 
80
- /**
81
- * Builds a Famix model for an array of source files
82
- * @param sourceFiles An array of source files
83
- */
84
83
  export function processFiles(sourceFiles: Array<SourceFile>): void {
85
84
  sourceFiles.forEach(file => {
86
85
  logger.info(`File: >>>>>>>>>> ${file.getFilePath()}`);
87
86
 
88
- // Computes the cyclomatic complexity metrics for the current source file if it exists (i.e. if it is not from a jest test)
89
- if (fs.existsSync(file.getFilePath()))
87
+ if (fs.existsSync(file.getFilePath())) {
90
88
  currentCC = calculate(file.getFilePath());
91
- else
89
+ } else {
92
90
  currentCC = {};
91
+ }
93
92
 
94
93
  processFile(file);
95
94
  });
@@ -114,22 +113,18 @@ function processFile(f: SourceFile): void {
114
113
  logger.debug(`processFile: file: ${f.getBaseName()}, fqn = ${fmxFile.fullyQualifiedName}`);
115
114
 
116
115
  processComments(f, fmxFile);
117
-
118
116
  processAliases(f, fmxFile);
119
-
120
117
  processClasses(f, fmxFile);
121
-
122
- processInterfaces(f, fmxFile);
123
-
124
- processVariables(f, fmxFile);
125
-
118
+ processInterfaces(f, fmxFile);
119
+ processModules(f, fmxFile);
120
+ processVariables(f, fmxFile); // This will handle our object literal methods
126
121
  processEnums(f, fmxFile);
127
-
128
122
  processFunctions(f, fmxFile);
123
+
129
124
 
130
- processModules(f, fmxFile);
131
125
  }
132
126
 
127
+
133
128
  export function isAmbient(node: ModuleDeclaration): boolean {
134
129
  // An ambient module has the DeclareKeyword modifier.
135
130
  return (node.getModifiers()?.some(modifier => modifier.getKind() === SyntaxKind.DeclareKeyword)) ?? false;
@@ -162,7 +157,7 @@ function processModule(m: ModuleDeclaration): Famix.Module {
162
157
  processVariables(m, fmxModule);
163
158
 
164
159
  processEnums(m, fmxModule);
165
-
160
+
166
161
  processFunctions(m, fmxModule);
167
162
 
168
163
  processModules(m, fmxModule);
@@ -192,7 +187,7 @@ function processAliases(m: ContainerTypes, fmxScope: ScopedTypes): void {
192
187
  * @param m A container (a source file or a namespace)
193
188
  * @param fmxScope The Famix model of the container
194
189
  */
195
- function processClasses(m: SourceFile | ModuleDeclaration, fmxScope: Famix.ScriptEntity | Famix.Module ): void {
190
+ function processClasses(m: SourceFile | ModuleDeclaration, fmxScope: Famix.ScriptEntity | Famix.Module): void {
196
191
  logger.debug(`processClasses: ---------- Finding Classes:`);
197
192
  const classesInArrowFunctions = getClassesDeclaredInArrowFunctions(m);
198
193
  const classes = m.getClasses().concat(classesInArrowFunctions);
@@ -205,7 +200,7 @@ function processClasses(m: SourceFile | ModuleDeclaration, fmxScope: Famix.Scrip
205
200
  function getArrowFunctionClasses(f: ArrowFunction): ClassDeclaration[] {
206
201
  const classes: ClassDeclaration[] = [];
207
202
 
208
- function findClasses(node: any) {
203
+ function findClasses(node: Node) {
209
204
  if (node.getKind() === SyntaxKind.ClassDeclaration) {
210
205
  classes.push(node as ClassDeclaration);
211
206
  }
@@ -232,7 +227,7 @@ function getClassesDeclaredInArrowFunctions(s: SourceFile | ModuleDeclaration):
232
227
  * @param m A container (a source file or a namespace)
233
228
  * @param fmxScope The Famix model of the container
234
229
  */
235
- function processInterfaces(m: SourceFile | ModuleDeclaration, fmxScope: Famix.ScriptEntity | Famix.Module ): void {
230
+ function processInterfaces(m: SourceFile | ModuleDeclaration, fmxScope: Famix.ScriptEntity | Famix.Module): void {
236
231
  logger.debug(`processInterfaces: ---------- Finding Interfaces:`);
237
232
  m.getInterfaces().forEach(i => {
238
233
  const fmxInterface = processInterface(i);
@@ -252,6 +247,26 @@ function processVariables(m: ContainerTypes, fmxScope: Famix.ScriptEntity | Fami
252
247
  fmxVariables.forEach(fmxVariable => {
253
248
  fmxScope.addVariable(fmxVariable);
254
249
  });
250
+
251
+ // Check each VariableDeclaration for object literal methods
252
+ v.getDeclarations().forEach(varDecl => {
253
+ const varName = varDecl.getName();
254
+ console.log(`Checking variable: ${varName} at pos=${varDecl.getStart()}`);
255
+ const initializer = varDecl.getInitializer();
256
+ if (initializer && Node.isObjectLiteralExpression(initializer)) {
257
+ initializer.getProperties().forEach(prop => {
258
+ if (Node.isPropertyAssignment(prop)) {
259
+ const nested = prop.getInitializer();
260
+ if (nested && Node.isObjectLiteralExpression(nested)) {
261
+ nested.getDescendantsOfKind(SyntaxKind.MethodDeclaration).forEach(method => {
262
+ console.log(`Found object literal method: ${method.getName()} at pos=${method.getStart()}`);
263
+ entityDictionary.createOrGetFamixMethod(method, currentCC);
264
+ });
265
+ }
266
+ }
267
+ });
268
+ }
269
+ });
255
270
  });
256
271
  }
257
272
 
@@ -286,7 +301,7 @@ function processFunctions(m: ContainerTypes, fmxScope: ScopedTypes): void {
286
301
  arrowFunctions.forEach(af => {
287
302
  const fmxFunction = processFunction(af);
288
303
  fmxScope.addFunction(fmxFunction);
289
- })
304
+ });
290
305
  }
291
306
 
292
307
  /**
@@ -294,7 +309,7 @@ function processFunctions(m: ContainerTypes, fmxScope: ScopedTypes): void {
294
309
  * @param m A container (a source file or a namespace)
295
310
  * @param fmxScope The Famix model of the container
296
311
  */
297
- function processModules(m: SourceFile | ModuleDeclaration, fmxScope: Famix.ScriptEntity | Famix.Module ): void {
312
+ function processModules(m: SourceFile | ModuleDeclaration, fmxScope: Famix.ScriptEntity | Famix.Module): void {
298
313
  logger.debug(`Finding Modules:`);
299
314
  m.getModules().forEach(md => {
300
315
  const fmxModule = processModule(md);
@@ -344,7 +359,7 @@ function processClass(c: ClassDeclaration): Famix.Class | Famix.ParametricClass
344
359
  const fmxAcc = processMethod(acc);
345
360
  fmxClass.addMethod(fmxAcc);
346
361
  });
347
-
362
+
348
363
  c.getSetAccessors().forEach(acc => {
349
364
  const fmxAcc = processMethod(acc);
350
365
  fmxClass.addMethod(fmxAcc);
@@ -469,8 +484,8 @@ function processFunction(f: FunctionDeclaration | FunctionExpression | ArrowFunc
469
484
  logger.debug(`Function: ${(f instanceof ArrowFunction ? "anonymous" : f.getName() ? f.getName() : "anonymous")}, (${f.getType().getText()}), fqn = ${getFQN(f)}`);
470
485
 
471
486
  let fmxFunction;
472
- if( f instanceof ArrowFunction) {
473
- fmxFunction = entityDictionary.createFamixArrowFunction(f, currentCC);
487
+ if (f instanceof ArrowFunction) {
488
+ fmxFunction = entityDictionary.createOrGetFamixArrowFunction(f, currentCC);
474
489
  } else {
475
490
  fmxFunction = entityDictionary.createOrGetFamixFunction(f, currentCC);
476
491
  }
@@ -554,7 +569,7 @@ function processParameterAsProperty(param: ParameterDeclaration, classDecl: Clas
554
569
  if (classDecl instanceof ClassDeclaration) {
555
570
  const fmxClass = entityDictionary.createOrGetFamixClass(classDecl);
556
571
  fmxClass.addProperty(fmxProperty);
557
- } else {
572
+ } else {
558
573
  throw new Error("Unexpected type ClassExpression.");
559
574
  }
560
575
 
@@ -606,7 +621,7 @@ function convertParameterToPropertyRepresentation(param: ParameterDeclaration) {
606
621
  * @returns A Famix.Parameter representing the parameter
607
622
  */
608
623
  function processParameter(paramDecl: ParameterDeclaration): Famix.Parameter {
609
- const fmxParam = entityDictionary.createFamixParameter(paramDecl);
624
+ const fmxParam = entityDictionary.createOrGetFamixParameter(paramDecl); // create or GET
610
625
 
611
626
  logger.debug(`parameter: ${paramDecl.getName()}, (${paramDecl.getType().getText()}), fqn = ${fmxParam.fullyQualifiedName}`);
612
627
 
@@ -624,17 +639,34 @@ function processParameter(paramDecl: ParameterDeclaration): Famix.Parameter {
624
639
  return fmxParam;
625
640
  }
626
641
 
627
- /**
628
- * Builds a Famix model for the type parameters of a class, an interface, a method or a function
629
- * @param e A class, an interface, a method or a function
630
- * @param fmxScope The Famix model of the class, the interface, the method or the function
631
- */
632
- function processTypeParameters(e: ClassDeclaration | InterfaceDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression |ArrowFunction, fmxScope: Famix.ParametricClass | Famix.ParametricInterface | Famix.Method | Famix.Accessor | Famix.Function | Famix.ArrowFunction): void {
642
+ function processTypeParameters(
643
+ e: ClassDeclaration | InterfaceDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression | ArrowFunction,
644
+ fmxScope: Famix.ParametricClass | Famix.ParametricInterface | Famix.Method | Famix.Accessor | Famix.Function | Famix.ArrowFunction
645
+ ): void {
633
646
  logger.debug(`Finding Type Parameters:`);
634
- e.getTypeParameters().forEach(tp => {
647
+ const nodeStart = e.getStart();
648
+
649
+ // Check if this node has already been processed
650
+ if (processedNodesWithTypeParams.has(nodeStart)) {
651
+ return;
652
+ }
653
+
654
+ // Get type parameters
655
+ const typeParams = e.getTypeParameters();
656
+
657
+ // Process each type parameter
658
+ typeParams.forEach((tp) => {
635
659
  const fmxParam = processTypeParameter(tp);
636
660
  fmxScope.addGenericParameter(fmxParam);
637
661
  });
662
+
663
+ // Log if no type parameters were found
664
+ if (typeParams.length === 0) {
665
+ logger.debug(`[processTypeParameters] No type parameters found for this node`);
666
+ }
667
+
668
+ // Mark this node as processed
669
+ processedNodesWithTypeParams.add(nodeStart);
638
670
  }
639
671
 
640
672
  /**
@@ -644,11 +676,8 @@ function processTypeParameters(e: ClassDeclaration | InterfaceDeclaration | Meth
644
676
  */
645
677
  function processTypeParameter(tp: TypeParameterDeclaration): Famix.ParameterType {
646
678
  const fmxTypeParameter = entityDictionary.createFamixParameterType(tp);
647
-
648
679
  logger.debug(`type parameter: ${tp.getName()}, (${tp.getType().getText()}), fqn = ${fmxTypeParameter.fullyQualifiedName}`);
649
-
650
680
  processComments(tp, fmxTypeParameter);
651
-
652
681
  return fmxTypeParameter;
653
682
  }
654
683
 
@@ -666,7 +695,7 @@ function processVariableStatement(v: VariableStatement): Array<Famix.Variable> {
666
695
  const fmxVar = processVariable(variable);
667
696
  processComments(v, fmxVar);
668
697
  fmxVariables.push(fmxVar);
669
- });
698
+ });
670
699
 
671
700
  return fmxVariables;
672
701
  }
@@ -677,7 +706,7 @@ function processVariableStatement(v: VariableStatement): Array<Famix.Variable> {
677
706
  * @returns A Famix.Variable representing the variable
678
707
  */
679
708
  function processVariable(v: VariableDeclaration): Famix.Variable {
680
- const fmxVar = entityDictionary.createFamixVariable(v);
709
+ const fmxVar = entityDictionary.createOrGetFamixVariable(v);
681
710
 
682
711
  logger.debug(`variable: ${v.getName()}, (${v.getType().getText()}), ${v.getInitializer() ? "initializer: " + v.getInitializer()!.getText() : "initializer: "}, fqn = ${fmxVar.fullyQualifiedName}`);
683
712
 
@@ -695,7 +724,7 @@ function processVariable(v: VariableDeclaration): Famix.Variable {
695
724
  * @returns A Famix.Enum representing the enum
696
725
  */
697
726
  function processEnum(e: EnumDeclaration): Famix.Enum {
698
- const fmxEnum = entityDictionary.createFamixEnum(e);
727
+ const fmxEnum = entityDictionary.createOrGetFamixEnum(e);
699
728
 
700
729
  logger.debug(`enum: ${e.getName()}, (${e.getType().getText()}), fqn = ${fmxEnum.fullyQualifiedName}`);
701
730
 
@@ -798,8 +827,8 @@ export function processAccesses(accessMap: Map<FamixID, AccessibleTSMorphElement
798
827
  accessMap.forEach((v, id) => {
799
828
  logger.debug(`Accesses to ${v.getName()}`);
800
829
  // try {
801
- const temp_nodes = v.findReferencesAsNodes() as Array<Identifier>;
802
- temp_nodes.forEach(node => processNodeForAccesses(node, id));
830
+ const temp_nodes = v.findReferencesAsNodes() as Array<Identifier>;
831
+ temp_nodes.forEach(node => processNodeForAccesses(node, id));
803
832
  // } catch (error) {
804
833
  // logger.error(`> WARNING: got exception "${error}".\nContinuing...`);
805
834
  // }
@@ -813,16 +842,16 @@ export function processAccesses(accessMap: Map<FamixID, AccessibleTSMorphElement
813
842
  */
814
843
  function processNodeForAccesses(n: Identifier, id: number): void {
815
844
  // try {
816
- // sometimes node's first ancestor is a PropertyDeclaration, which is not an access
817
- // see https://github.com/fuhrmanator/FamixTypeScriptImporter/issues/9
818
- // check for a node whose first ancestor is a property declaration and bail?
819
- // This may be a bug in ts-morph?
820
- if (n.getFirstAncestorOrThrow().getKindName() === "PropertyDeclaration") {
821
- logger.debug(`processNodeForAccesses: node kind: ${n.getKindName()}, ${n.getText()}, (${n.getType().getText()})'s first ancestor is a PropertyDeclaration. Skipping...`);
822
- return;
823
- }
824
- entityDictionary.createFamixAccess(n, id);
825
- logger.debug(`processNodeForAccesses: node kind: ${n.getKindName()}, ${n.getText()}, (${n.getType().getText()})`);
845
+ // sometimes node's first ancestor is a PropertyDeclaration, which is not an access
846
+ // see https://github.com/fuhrmanator/FamixTypeScriptImporter/issues/9
847
+ // check for a node whose first ancestor is a property declaration and bail?
848
+ // This may be a bug in ts-morph?
849
+ if (n.getFirstAncestorOrThrow().getKindName() === "PropertyDeclaration") {
850
+ logger.debug(`processNodeForAccesses: node kind: ${n.getKindName()}, ${n.getText()}, (${n.getType().getText()})'s first ancestor is a PropertyDeclaration. Skipping...`);
851
+ return;
852
+ }
853
+ entityDictionary.createFamixAccess(n, id);
854
+ logger.debug(`processNodeForAccesses: node kind: ${n.getKindName()}, ${n.getText()}, (${n.getType().getText()})`);
826
855
  // } catch (error) {
827
856
  // logger.error(`> Got exception "${error}".\nScopeDeclaration invalid for "${n.getSymbol().fullyQualifiedName}".\nContinuing...`);
828
857
  // }
@@ -844,12 +873,14 @@ export function processImportClausesForImportEqualsDeclarations(sourceFiles: Arr
844
873
  // const importedEntity = node.getName();
845
874
  // create a famix import clause
846
875
  const namedImport = node.getNameNode();
847
- entityDictionary.oldCreateFamixImportClause({importDeclaration: node,
848
- importerSourceFile: sourceFile,
849
- moduleSpecifierFilePath: node.getModuleReference().getText(),
850
- importElement: namedImport,
851
- isInExports: exports.find(e => e.has(namedImport.getText())) !== undefined,
852
- isDefaultExport: false});
876
+ entityDictionary.oldCreateOrGetFamixImportClause({
877
+ importDeclaration: node,
878
+ importerSourceFile: sourceFile,
879
+ moduleSpecifierFilePath: node.getModuleReference().getText(),
880
+ importElement: namedImport,
881
+ isInExports: exports.find(e => e.has(namedImport.getText())) !== undefined,
882
+ isDefaultExport: false
883
+ });
853
884
  // entityDictionary.createFamixImportClause(importedEntity, importingEntity);
854
885
  }
855
886
  });
@@ -865,7 +896,7 @@ export function processImportClausesForImportEqualsDeclarations(sourceFiles: Arr
865
896
  export function processImportClausesForModules(modules: Array<SourceFile>, exports: Array<ReadonlyMap<string, ExportedDeclarations[]>>): void {
866
897
  logger.info(`Creating import clauses from ${modules.length} modules:`);
867
898
  modules.forEach(module => {
868
- const modulePath = module.getFilePath() + module.getBaseName();
899
+ const modulePath = module.getFilePath();
869
900
  module.getImportDeclarations().forEach(impDecl => {
870
901
  logger.info(`Importing ${impDecl.getModuleSpecifierValue()} in ${modulePath}`);
871
902
  const path = getModulePath(impDecl);
@@ -873,39 +904,45 @@ export function processImportClausesForModules(modules: Array<SourceFile>, expor
873
904
  impDecl.getNamedImports().forEach(namedImport => {
874
905
  logger.info(`Importing (named) ${namedImport.getName()} from ${impDecl.getModuleSpecifierValue()} in ${modulePath}`);
875
906
  const importedEntityName = namedImport.getName();
876
- let importFoundInExports = isInExports(exports, importedEntityName);
877
- entityDictionary.oldCreateFamixImportClause({importDeclaration: impDecl,
878
- importerSourceFile: module,
879
- moduleSpecifierFilePath: path,
880
- importElement: namedImport,
881
- isInExports: importFoundInExports,
882
- isDefaultExport: false});
907
+ const importFoundInExports = isInExports(exports, importedEntityName);
908
+ logger.debug(`Processing ImportSpecifier: ${namedImport.getText()}, pos=${namedImport.getStart()}`);
909
+ entityDictionary.oldCreateOrGetFamixImportClause({
910
+ importDeclaration: impDecl,
911
+ importerSourceFile: module,
912
+ moduleSpecifierFilePath: path,
913
+ importElement: namedImport,
914
+ isInExports: importFoundInExports,
915
+ isDefaultExport: false
916
+ });
883
917
  });
884
918
 
885
919
  const defaultImport = impDecl.getDefaultImport();
886
920
  if (defaultImport !== undefined) {
887
921
  logger.info(`Importing (default) ${defaultImport.getText()} from ${impDecl.getModuleSpecifierValue()} in ${modulePath}`);
888
- // call with module, impDecl.getModuleSpecifierValue(), path, defaultImport, false, true
889
- entityDictionary.oldCreateFamixImportClause({importDeclaration: impDecl,
922
+ logger.debug(`Processing Default Import: ${defaultImport.getText()}, pos=${defaultImport.getStart()}`);
923
+ entityDictionary.oldCreateOrGetFamixImportClause({
924
+ importDeclaration: impDecl,
890
925
  importerSourceFile: module,
891
926
  moduleSpecifierFilePath: path,
892
927
  importElement: defaultImport,
893
928
  isInExports: false,
894
- isDefaultExport: true});
929
+ isDefaultExport: true
930
+ });
895
931
  }
896
932
 
897
933
  const namespaceImport = impDecl.getNamespaceImport();
898
934
  if (namespaceImport !== undefined) {
899
935
  logger.info(`Importing (namespace) ${namespaceImport.getText()} from ${impDecl.getModuleSpecifierValue()} in ${modulePath}`);
900
- entityDictionary.oldCreateFamixImportClause({importDeclaration: impDecl,
901
- importerSourceFile: module,
902
- moduleSpecifierFilePath: path,
903
- importElement: namespaceImport,
904
- isInExports: false,
905
- isDefaultExport: false});
906
- // entityDictionary.createFamixImportClause(module, impDecl.getModuleSpecifierValue(), path, namespaceImport, false, false);
936
+ entityDictionary.oldCreateOrGetFamixImportClause({
937
+ importDeclaration: impDecl,
938
+ importerSourceFile: module,
939
+ moduleSpecifierFilePath: path,
940
+ importElement: namespaceImport,
941
+ isInExports: false,
942
+ isDefaultExport: false
943
+ });
907
944
  }
908
- });
945
+ });
909
946
  });
910
947
  }
911
948
 
@@ -925,33 +962,44 @@ function isInExports(exports: ReadonlyMap<string, ExportedDeclarations[]>[], imp
925
962
  * @param interfaces An array of interfaces
926
963
  */
927
964
  export function processInheritances(classes: ClassDeclaration[], interfaces: InterfaceDeclaration[]): void {
928
- logger.info(`processInheritances: Creating inheritances:`);
965
+ logger.info(`Creating inheritances:`);
929
966
  classes.forEach(cls => {
930
- logger.debug(`processInheritances: Checking class inheritance for ${cls.getName()}`);
931
- const extClass = cls.getBaseClass();
932
- if (extClass !== undefined) {
933
- entityDictionary.createFamixInheritance(cls, extClass);
934
-
935
- logger.debug(`processInheritances: class: ${cls.getName()}, (${cls.getType().getText()}), extClass: ${extClass.getName()}, (${extClass.getType().getText()})`);
936
- }
937
-
938
- logger.debug(`processInheritances: Checking interface inheritance for ${cls.getName()}`);
939
- const implementedInterfaces = getImplementedOrExtendedInterfaces(interfaces, cls);
940
- implementedInterfaces.forEach(impInter => {
941
- entityDictionary.createFamixInheritance(cls, impInter);
967
+ logger.debug(`Checking class inheritance for ${cls.getName()}`);
968
+ const baseClass = cls.getBaseClass();
969
+ if (baseClass !== undefined) {
970
+ entityDictionary.createOrGetFamixInheritance(cls, baseClass);
971
+ logger.debug(`class: ${cls.getName()}, (${cls.getType().getText()}), extClass: ${baseClass.getName()}, (${baseClass.getType().getText()})`);
972
+ } // this is false when the class extends an undefined class
973
+ else {
974
+ // check for "extends" of unresolved class
975
+ const undefinedExtendedClass = cls.getExtends();
976
+ if (undefinedExtendedClass) {
977
+ entityDictionary.createOrGetFamixInheritance(cls, undefinedExtendedClass);
978
+ logger.debug(`class: ${cls.getName()}, (${cls.getType().getText()}), undefinedExtendedClass: ${undefinedExtendedClass.getText()}`);
979
+ }
980
+ }
942
981
 
943
- logger.debug(`processInheritances: class: ${cls.getName()}, (${cls.getType().getText()}), impInter: ${(impInter instanceof InterfaceDeclaration) ? impInter.getName() : impInter.getExpression().getText()}, (${(impInter instanceof InterfaceDeclaration) ? impInter.getType().getText() : impInter.getExpression().getText()})`);
944
- });
982
+ logger.debug(`Checking interface inheritance for ${cls.getName()}`);
983
+ const implementedInterfaces = getImplementedOrExtendedInterfaces(interfaces, cls);
984
+ implementedInterfaces.forEach(implementedIF => {
985
+ entityDictionary.createOrGetFamixInheritance(cls, implementedIF);
986
+ logger.debug(`class: ${cls.getName()}, (${cls.getType().getText()}), impInter: ${(implementedIF instanceof InterfaceDeclaration) ? implementedIF.getName() : implementedIF.getExpression().getText()}, (${(implementedIF instanceof InterfaceDeclaration) ? implementedIF.getType().getText() : implementedIF.getExpression().getText()})`);
987
+ });
945
988
  });
946
989
 
947
- interfaces.forEach(inter => {
948
- logger.debug(`processInheritances: Checking interface inheritance for ${inter.getName()}`);
949
- const extendedInterfaces = getImplementedOrExtendedInterfaces(interfaces, inter);
950
- extendedInterfaces.forEach(extInter => {
951
- entityDictionary.createFamixInheritance(inter, extInter);
990
+ interfaces.forEach(interFace => {
991
+ try {
992
+ logger.debug(`Checking interface inheritance for ${interFace.getName()}`);
993
+ const extendedInterfaces = getImplementedOrExtendedInterfaces(interfaces, interFace);
994
+ extendedInterfaces.forEach(extendedInterface => {
995
+ entityDictionary.createOrGetFamixInheritance(interFace, extendedInterface);
952
996
 
953
- logger.debug(`processInheritances: inter: ${inter.getName()}, (${inter.getType().getText()}), extInter: ${(extInter instanceof InterfaceDeclaration) ? extInter.getName() : extInter.getExpression().getText()}, (${(extInter instanceof InterfaceDeclaration) ? extInter.getType().getText() : extInter.getExpression().getText()})`);
954
- });
997
+ logger.debug(`interFace: ${interFace.getName()}, (${interFace.getType().getText()}), extendedInterface: ${(extendedInterface instanceof InterfaceDeclaration) ? extendedInterface.getName() : extendedInterface.getExpression().getText()}, (${(extendedInterface instanceof InterfaceDeclaration) ? extendedInterface.getType().getText() : extendedInterface.getExpression().getText()})`);
998
+ });
999
+ }
1000
+ catch (error) {
1001
+ logger.error(`> WARNING: got exception ${error}. Continuing...`);
1002
+ }
955
1003
  });
956
1004
  }
957
1005
 
@@ -959,34 +1007,36 @@ export function processInheritances(classes: ClassDeclaration[], interfaces: Int
959
1007
  * Builds a Famix model for the invocations of the methods and functions of the source files
960
1008
  * @param methodsAndFunctionsWithId A map of methods and functions with their id
961
1009
  */
962
- export function processInvocations(methodsAndFunctionsWithId: Map<number, MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression | ArrowFunction>): void {
1010
+ export function processInvocations(methodsAndFunctionsWithId: Map<number, InvocableType>): void {
963
1011
  logger.info(`Creating invocations:`);
964
- methodsAndFunctionsWithId.forEach((m, id) => {
965
- if (!(m instanceof ArrowFunction)) {
966
- logger.debug(`Invocations to ${(m instanceof MethodDeclaration || m instanceof GetAccessorDeclaration || m instanceof SetAccessorDeclaration || m instanceof FunctionDeclaration) ? m.getName() : ((m instanceof ConstructorDeclaration) ? 'constructor' : (m.getName() ? m.getName() : 'anonymous'))}`);
1012
+ methodsAndFunctionsWithId.forEach((invocable, id) => {
1013
+ if (!(invocable instanceof ArrowFunction)) { // ArrowFunctions are not directly invoked
1014
+ logger.debug(`Invocations to ${(invocable instanceof MethodDeclaration || invocable instanceof GetAccessorDeclaration || invocable instanceof SetAccessorDeclaration || invocable instanceof FunctionDeclaration) ? invocable.getName() : ((invocable instanceof ConstructorDeclaration) ? 'constructor' : (invocable.getName() ? invocable.getName() : 'anonymous'))} (${invocable.getType().getText()})`);
967
1015
  try {
968
- const temp_nodes = m.findReferencesAsNodes() as Array<Identifier>;
969
- temp_nodes.forEach(node => processNodeForInvocations(node, m, id));
1016
+ const nodesReferencingInvocable = invocable.findReferencesAsNodes() as Array<Identifier>;
1017
+ nodesReferencingInvocable.forEach(
1018
+ nodeReferencingInvocable => processNodeForInvocations(nodeReferencingInvocable, invocable, id));
970
1019
  } catch (error) {
971
1020
  logger.error(`> WARNING: got exception ${error}. Continuing...`);
972
1021
  }
1022
+ } else {
1023
+ logger.debug(`Skipping invocation to ArrowFunction: ${(invocable.getBodyText())}`);
973
1024
  }
974
1025
  });
975
1026
  }
976
1027
 
977
1028
  /**
978
1029
  * Builds a Famix model for an invocation of a method or a function
979
- * @param n A node
980
- * @param m A method or a function
1030
+ * @param nodeReferencingInvocable A node
1031
+ * @param invocable A method or a function
981
1032
  * @param id The id of the method or the function
982
1033
  */
983
- function processNodeForInvocations(n: Identifier, m: MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionDeclaration | FunctionExpression, id: number): void {
1034
+ function processNodeForInvocations(nodeReferencingInvocable: Identifier, invocable: InvocableType, id: number): void {
984
1035
  try {
985
- entityDictionary.createFamixInvocation(n, m, id);
986
-
987
- logger.debug(`node: node, (${n.getType().getText()})`);
1036
+ entityDictionary.createFamixInvocation(nodeReferencingInvocable, invocable, id);
1037
+ logger.debug(`node: ${nodeReferencingInvocable.getKindName()}, (${nodeReferencingInvocable.getType().getText()})`);
988
1038
  } catch (error) {
989
- logger.error(`> WARNING: got exception ${error}. ScopeDeclaration invalid for ${n.getSymbol()!.getFullyQualifiedName()}. Continuing...`);
1039
+ logger.error(`> WARNING: got exception ${error}. ScopeDeclaration invalid for ${nodeReferencingInvocable.getSymbol()!.getFullyQualifiedName()}. Continuing...`);
990
1040
  }
991
1041
  }
992
1042
 
@@ -1002,18 +1052,18 @@ export function processConcretisations(classes: ClassDeclaration[], interfaces:
1002
1052
  entityDictionary.createFamixConcretisationClassOrInterfaceSpecialisation(cls);
1003
1053
  entityDictionary.createFamixConcretisationGenericInstantiation(cls);
1004
1054
  entityDictionary.createFamixConcretisationInterfaceClass(cls);
1005
- entityDictionary.createFamixConcretisationTypeInstanciation(cls);
1055
+ entityDictionary.createFamixConcretisationTypeInstanciation(cls);
1006
1056
 
1007
1057
  });
1008
1058
  interfaces.forEach(inter => {
1009
1059
  logger.debug(`processConcretisations: Checking interface concretisation for ${inter.getName()}`);
1010
- entityDictionary.createFamixConcretisationTypeInstanciation(inter);
1011
- entityDictionary.createFamixConcretisationClassOrInterfaceSpecialisation(inter)
1060
+ entityDictionary.createFamixConcretisationTypeInstanciation(inter);
1061
+ entityDictionary.createFamixConcretisationClassOrInterfaceSpecialisation(inter);
1012
1062
  });
1013
1063
  functions.forEach(func => {
1014
1064
  if(func instanceof FunctionDeclaration || func instanceof MethodDeclaration ){
1015
1065
  logger.debug(`processConcretisations: Checking Method concretisation`);
1016
1066
  entityDictionary.createFamixConcretisationFunctionInstantiation(func);
1017
1067
  }
1018
- })
1068
+ });
1019
1069
  }