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