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/dist/analyze.js +19 -9
- package/dist/analyze_functions/process_functions.js +127 -67
- package/dist/famix_functions/EntityDictionary.js +803 -356
- package/dist/famix_functions/helpers_creation.js +35 -19
- package/dist/fqn.js +384 -18
- package/dist/lib/famix/famix_repository.js +76 -2
- package/dist/lib/famix/index.js +18 -8
- package/dist/refactorer/refactor-getter-setter.js +18 -8
- package/dist/ts2famix-cli-wrapper.js +18 -8
- package/dist/ts2famix-cli.js +18 -8
- package/dist/ts2famix-tsconfig.js +18 -8
- package/doc-uml/famix-typescript-model.puml +29 -17
- package/doc-uml/famix-typescript-model.svg +1 -1
- package/eslint.config.mjs +28 -0
- package/jest.config.json +1 -1
- package/package.json +14 -10
- package/src/analyze.ts +1 -1
- package/src/analyze_functions/process_functions.ts +168 -118
- package/src/famix_functions/EntityDictionary.ts +909 -441
- package/src/famix_functions/helpers_creation.ts +23 -15
- package/src/fqn.ts +450 -50
- package/src/lib/famix/famix_repository.ts +46 -1
- package/tsconfig.json +1 -0
|
@@ -1,32 +1,50 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
|
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<
|
|
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<
|
|
29
|
-
private
|
|
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
|
-
|
|
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
|
-
//
|
|
101
|
-
if (
|
|
102
|
-
//
|
|
103
|
-
const fullyQualifiedName = (famixElement as
|
|
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
|
|
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
|
|
255
|
+
* @param moduleDeclaration A module
|
|
230
256
|
* @returns The Famix model of the module
|
|
231
257
|
*/
|
|
232
|
-
public createOrGetFamixModule(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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
|
|
288
|
+
* @param typeAliasDeclaration An alias
|
|
261
289
|
* @returns The Famix model of the alias
|
|
262
290
|
*/
|
|
263
|
-
public createFamixAlias(
|
|
291
|
+
public createFamixAlias(typeAliasDeclaration: TypeAliasDeclaration): Famix.Alias {
|
|
264
292
|
let fmxAlias: Famix.Alias;
|
|
265
|
-
const aliasName =
|
|
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 =
|
|
271
|
-
const aliasNameWithGenerics = aliasName + (
|
|
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,
|
|
303
|
+
const fmxType = this.createOrGetFamixType(aliasNameWithGenerics, typeAliasDeclaration.getType(), typeAliasDeclaration);
|
|
275
304
|
fmxAlias.aliasedEntity = fmxType;
|
|
276
|
-
initFQN(
|
|
277
|
-
this.makeFamixIndexFileAnchor(
|
|
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,
|
|
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
|
|
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
|
-
|
|
399
|
-
|
|
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(
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
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)
|
|
535
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
578
|
-
fmxMethod.
|
|
579
|
-
|
|
580
|
-
|
|
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
|
-
|
|
591
|
-
}
|
|
592
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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
|
|
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
|
|
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(
|
|
909
|
-
|
|
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
|
-
|
|
929
|
-
|
|
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(
|
|
933
|
-
|
|
975
|
+
if (isPrimitiveType(typeName)) {
|
|
976
|
+
return this.createOrGetFamixPrimitiveType(typeName);
|
|
934
977
|
}
|
|
935
978
|
|
|
936
|
-
if (
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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
|
-
|
|
953
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
969
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
1015
|
-
* @param
|
|
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(
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1021
|
-
const
|
|
1022
|
-
|
|
1023
|
-
const
|
|
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(
|
|
1030
|
-
fmxInvocation.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,
|
|
1266
|
+
this.fmxElementObjectMap.set(fmxInvocation,nodeReferringToInvocable);
|
|
1035
1267
|
}
|
|
1036
1268
|
|
|
1037
1269
|
/**
|
|
1038
1270
|
* Creates a Famix inheritance
|
|
1039
|
-
* @param
|
|
1040
|
-
* @param
|
|
1271
|
+
* @param baseClassOrInterface A class or an interface (subclass)
|
|
1272
|
+
* @param inheritedClassOrInterface The inherited class or interface (superclass)
|
|
1041
1273
|
*/
|
|
1042
|
-
public
|
|
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
|
-
|
|
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 (
|
|
1049
|
-
subClass = this.
|
|
1050
|
-
}
|
|
1051
|
-
|
|
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 ${
|
|
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 (
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
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
|
-
|
|
1333
|
+
}
|
|
1095
1334
|
|
|
1096
|
-
|
|
1097
|
-
superClass.fullyQualifiedName = inhClassFullyQualifiedName;
|
|
1098
|
-
superClass.isStub = true;
|
|
1335
|
+
this.fmxElementObjectMap.set(superClass, inheritedClassOrInterface);
|
|
1099
1336
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
1470
|
+
initFQN(importElement, importedEntity);
|
|
1471
|
+
logger.debug(`Assigned FQN to default/namespace entity: ${importedEntity.fullyQualifiedName}`);
|
|
1189
1472
|
this.makeFamixIndexFileAnchor(importElement, importedEntity);
|
|
1190
1473
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
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(`
|
|
1204
|
-
|
|
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)
|
|
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
|
|
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
|
-
|
|
1509
|
+
if (!this.fmxFunctionAndMethodMap.has(functionFullyQualifiedName)) {
|
|
1223
1510
|
|
|
1224
|
-
|
|
1511
|
+
const arrowFunction = arrowExpression.asKindOrThrow(SyntaxKind.ArrowFunction);
|
|
1225
1512
|
|
|
1226
|
-
|
|
1227
|
-
fmxArrowFunction = new Famix.ParametricArrowFunction();
|
|
1228
|
-
}
|
|
1229
|
-
else {
|
|
1230
|
-
fmxArrowFunction = new Famix.ArrowFunction();
|
|
1231
|
-
}
|
|
1513
|
+
const isGeneric = arrowFunction.getTypeParameters().length > 0;
|
|
1232
1514
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1515
|
+
if (isGeneric) {
|
|
1516
|
+
fmxArrowFunction = new Famix.ParametricArrowFunction();
|
|
1517
|
+
}
|
|
1518
|
+
else {
|
|
1519
|
+
fmxArrowFunction = new Famix.ArrowFunction();
|
|
1520
|
+
}
|
|
1236
1521
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
functionName =
|
|
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
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
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
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1531
|
+
if (functionName) {
|
|
1532
|
+
fmxArrowFunction.name = functionName;
|
|
1533
|
+
}
|
|
1534
|
+
else {
|
|
1535
|
+
fmxArrowFunction.name = "anonymous";
|
|
1536
|
+
}
|
|
1254
1537
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
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
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
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
|
-
|
|
1308
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
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
|
-
|
|
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
|
+
// }
|