ts2famix 2.0.1 → 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 +32 -12
- 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,7 +1,7 @@
|
|
|
1
1
|
import * as Famix from "../lib/famix/model/famix";
|
|
2
2
|
import { logger } from "../analyze";
|
|
3
3
|
import { ConstructorDeclaration, Identifier, FunctionDeclaration, MethodDeclaration, MethodSignature, PropertyDeclaration, PropertySignature, VariableDeclaration, ParameterDeclaration, GetAccessorDeclaration, SetAccessorDeclaration, EnumMember, TypeAliasDeclaration, Node, SyntaxKind, FunctionExpression } from "ts-morph";
|
|
4
|
-
import {
|
|
4
|
+
import { TSMorphTypeDeclaration } from "./EntityDictionary";
|
|
5
5
|
|
|
6
6
|
interface SearchParameters {
|
|
7
7
|
searchArray: string[];
|
|
@@ -87,23 +87,31 @@ export function findAncestor(node: Identifier): Node {
|
|
|
87
87
|
* @param element A ts-morph element
|
|
88
88
|
* @returns The ancestor of the ts-morph element
|
|
89
89
|
*/
|
|
90
|
-
export function findTypeAncestor(element:
|
|
90
|
+
export function findTypeAncestor(element: Node): Node | undefined {
|
|
91
91
|
let ancestor: Node | undefined;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
a.getKind()
|
|
97
|
-
a.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
92
|
+
const ancestors = element.getAncestors();
|
|
93
|
+
console.log(`Ancestors count: ${ancestors.length}`);
|
|
94
|
+
|
|
95
|
+
ancestor = ancestors.find(a => {
|
|
96
|
+
const kind = a.getKind();
|
|
97
|
+
const kindName = a.getKindName();
|
|
98
|
+
return kind === SyntaxKind.MethodDeclaration ||
|
|
99
|
+
kind === SyntaxKind.Constructor ||
|
|
100
|
+
kind === SyntaxKind.MethodSignature ||
|
|
101
|
+
kind === SyntaxKind.FunctionDeclaration ||
|
|
102
|
+
kind === SyntaxKind.FunctionExpression ||
|
|
103
|
+
kind === SyntaxKind.ModuleDeclaration ||
|
|
104
|
+
kind === SyntaxKind.SourceFile ||
|
|
105
|
+
kindName === "GetAccessor" ||
|
|
106
|
+
kindName === "SetAccessor" ||
|
|
107
|
+
kind === SyntaxKind.ClassDeclaration ||
|
|
108
|
+
kind === SyntaxKind.InterfaceDeclaration;
|
|
109
|
+
});
|
|
110
|
+
|
|
104
111
|
if (!ancestor) {
|
|
105
112
|
throw new Error(`Type ancestor not found for ${element.getKindName()}`);
|
|
106
|
-
}
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
return ancestor;
|
|
108
116
|
}
|
|
109
117
|
|
package/src/fqn.ts
CHANGED
|
@@ -1,71 +1,391 @@
|
|
|
1
|
-
import { ArrowFunction, CallExpression, ClassDeclaration,
|
|
1
|
+
import { ArrowFunction, CallExpression, ClassDeclaration, ConstructorDeclaration, Decorator, EnumDeclaration, ExpressionWithTypeArguments, FunctionDeclaration, FunctionExpression, GetAccessorDeclaration, Identifier, ImportDeclaration, ImportEqualsDeclaration, InterfaceDeclaration, MethodDeclaration, MethodSignature, ModuleDeclaration, Node, PropertyDeclaration, SetAccessorDeclaration, SourceFile, SyntaxKind, TypeParameterDeclaration, VariableDeclaration } from "ts-morph";
|
|
2
2
|
import { entityDictionary, logger } from "./analyze";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import {
|
|
4
|
+
import { TSMorphTypeDeclaration } from "./famix_functions/EntityDictionary";
|
|
5
5
|
|
|
6
|
-
type FQNNode = SourceFile | VariableDeclaration | ArrowFunction | Identifier | MethodDeclaration | MethodSignature | FunctionDeclaration | FunctionExpression | PropertyDeclaration |
|
|
6
|
+
type FQNNode = SourceFile | VariableDeclaration | ArrowFunction | Identifier | MethodDeclaration | MethodSignature | FunctionDeclaration | FunctionExpression | PropertyDeclaration | TSMorphTypeDeclaration | EnumDeclaration | ImportDeclaration | ImportEqualsDeclaration | CallExpression | GetAccessorDeclaration | SetAccessorDeclaration | ConstructorDeclaration | TypeParameterDeclaration | ClassDeclaration | InterfaceDeclaration | Decorator | ModuleDeclaration;
|
|
7
7
|
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
8
9
|
function isFQNNode(node: Node): node is FQNNode {
|
|
9
10
|
return Node.isVariableDeclaration(node) || Node.isArrowFunction(node) || Node.isIdentifier(node) || Node.isMethodDeclaration(node) || Node.isClassDeclaration(node) || Node.isClassExpression(node) || Node.isDecorator(node) || Node.isModuleDeclaration(node) || Node.isCallExpression(node);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Builds a map of method positions to their property keys in object literals.
|
|
15
|
+
* Scans all variable declarations in a source file, targeting object literals with any keys
|
|
16
|
+
* (e.g., `3: { method() {} }` or `add: { compute() {} }`), and maps each method's start position to its key.
|
|
17
|
+
* Logs each step for debugging.
|
|
18
|
+
*
|
|
19
|
+
* @param sourceFile The TypeScript source file to analyze
|
|
20
|
+
* @returns A Map where keys are method start positions and values are their property keys (e.g., "3", "add")
|
|
21
|
+
*/
|
|
22
|
+
function buildStageMethodMap(sourceFile: SourceFile): Map<number, string> {
|
|
23
|
+
const stageMap = new Map<number, string>();
|
|
24
|
+
|
|
25
|
+
sourceFile.getVariableDeclarations().forEach(varDecl => {
|
|
26
|
+
// const varName = varDecl.getName();
|
|
27
|
+
const initializer = varDecl.getInitializer();
|
|
28
|
+
|
|
29
|
+
if (!initializer || !Node.isObjectLiteralExpression(initializer)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
initializer.getProperties().forEach(prop => {
|
|
34
|
+
let key: string | undefined;
|
|
35
|
+
|
|
36
|
+
if (Node.isPropertyAssignment(prop)) {
|
|
37
|
+
const nameNode = prop.getNameNode();
|
|
38
|
+
|
|
39
|
+
if (Node.isIdentifier(nameNode)) {
|
|
40
|
+
key = nameNode.getText();
|
|
41
|
+
} else if (Node.isStringLiteral(nameNode)) {
|
|
42
|
+
key = nameNode.getText().replace(/^"(.+)"$/, '$1').replace(/^'(.+)'$/, '$1');
|
|
43
|
+
} else if (Node.isNumericLiteral(nameNode)) {
|
|
44
|
+
key = nameNode.getText();
|
|
45
|
+
} else if (Node.isComputedPropertyName(nameNode)) {
|
|
46
|
+
const expression = nameNode.getExpression();
|
|
47
|
+
if (Node.isIdentifier(expression)) {
|
|
48
|
+
// Resolve variable value if possible
|
|
49
|
+
const symbol = expression.getSymbol();
|
|
50
|
+
if (symbol) {
|
|
51
|
+
const decl = symbol.getDeclarations()[0];
|
|
52
|
+
if (Node.isVariableDeclaration(decl) && decl.getInitializer()) {
|
|
53
|
+
const init = decl.getInitializer()!;
|
|
54
|
+
if (Node.isStringLiteral(init) || Node.isNumericLiteral(init)) {
|
|
55
|
+
key = init.getText().replace(/^"(.+)"$/, '$1').replace(/^'(.+)'$/, '$1');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!key) {
|
|
60
|
+
key = expression.getText();
|
|
61
|
+
}
|
|
62
|
+
} else if (Node.isBinaryExpression(expression) && expression.getOperatorToken().getText() === '+') {
|
|
63
|
+
// Handle simple string concatenation (e.g., "A" + "B")
|
|
64
|
+
const left = expression.getLeft();
|
|
65
|
+
const right = expression.getRight();
|
|
66
|
+
if (Node.isStringLiteral(left) && Node.isStringLiteral(right)) {
|
|
67
|
+
key = left.getLiteralText() + right.getLiteralText();
|
|
68
|
+
}
|
|
69
|
+
} else if (Node.isTemplateExpression(expression)) {
|
|
70
|
+
// Handle template literals (e.g., `key-${1}`)
|
|
71
|
+
const head = expression.getHead().getLiteralText();
|
|
72
|
+
const spans = expression.getTemplateSpans();
|
|
73
|
+
if (spans.length === 1 && Node.isNumericLiteral(spans[0].getExpression())) {
|
|
74
|
+
const num = spans[0].getExpression().getText();
|
|
75
|
+
key = `${head}${num}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!key) {
|
|
79
|
+
key = expression.getText(); // Fallback
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const propInitializer = prop.getInitializer();
|
|
86
|
+
if (propInitializer && Node.isObjectLiteralExpression(propInitializer)) {
|
|
87
|
+
propInitializer.getDescendantsOfKind(SyntaxKind.MethodDeclaration).forEach(method => {
|
|
88
|
+
// const methodName = method.getName();
|
|
89
|
+
const pos = method.getStart();
|
|
90
|
+
if (key) {
|
|
91
|
+
stageMap.set(pos, key);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
10
98
|
|
|
99
|
+
return stageMap;
|
|
11
100
|
}
|
|
12
101
|
|
|
13
|
-
|
|
14
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Builds a map of method positions to their index in class/interface/namespace declarations
|
|
104
|
+
* @param sourceFile The TypeScript source file to analyze
|
|
105
|
+
* @returns A Map where keys are method start positions and values are their positional index (1-based)
|
|
106
|
+
*/
|
|
107
|
+
function buildMethodPositionMap(sourceFile: SourceFile): Map<number, number> {
|
|
108
|
+
const positionMap = new Map<number, number>();
|
|
109
|
+
// console.log(`[buildMethodPositionMap] Starting analysis for file: ${sourceFile.getFilePath()}`);
|
|
110
|
+
|
|
111
|
+
// Helper function to process modules recursively
|
|
112
|
+
function processModule(moduleNode: ModuleDeclaration, modulePath: string) {
|
|
113
|
+
// console.log(`[buildMethodPositionMap] Processing module: ${modulePath}`);
|
|
114
|
+
|
|
115
|
+
// Handle functions directly in the module
|
|
116
|
+
const functions = moduleNode.getFunctions();
|
|
117
|
+
const functionCounts = new Map<string, number>();
|
|
118
|
+
|
|
119
|
+
functions.forEach(func => {
|
|
120
|
+
const funcName = func.getName() || `Unnamed_Function(${func.getStart()})`;
|
|
121
|
+
const count = (functionCounts.get(funcName) || 0) + 1;
|
|
122
|
+
functionCounts.set(funcName, count);
|
|
123
|
+
positionMap.set(func.getStart(), count);
|
|
124
|
+
// console.log(`[buildMethodPositionMap] Module function: ${funcName}, position: ${func.getStart()}, index: ${count}`);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Handle classes within the module
|
|
128
|
+
const classes = moduleNode.getClasses();
|
|
129
|
+
classes.forEach(classNode => {
|
|
130
|
+
// console.log(`[buildMethodPositionMap] Processing class in module: ${classNode.getName() || 'Unnamed'}`);
|
|
131
|
+
const methods = classNode.getMethods();
|
|
132
|
+
const methodCounts = new Map<string, number>();
|
|
133
|
+
|
|
134
|
+
methods.forEach(method => {
|
|
135
|
+
const methodName = method.getName();
|
|
136
|
+
const count = (methodCounts.get(methodName) || 0) + 1;
|
|
137
|
+
methodCounts.set(methodName, count);
|
|
138
|
+
positionMap.set(method.getStart(), count);
|
|
139
|
+
// console.log(`[buildMethodPositionMap] Module class method: ${methodName}, position: ${method.getStart()}, index: ${count}`);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Handle interfaces within the module
|
|
144
|
+
const interfaces = moduleNode.getInterfaces();
|
|
145
|
+
interfaces.forEach(interfaceNode => {
|
|
146
|
+
// console.log(`[buildMethodPositionMap] Processing interface in module: ${interfaceNode.getName() || 'Unnamed'}`);
|
|
147
|
+
const methods = interfaceNode.getMethods();
|
|
148
|
+
const methodCounts = new Map<string, number>();
|
|
149
|
+
|
|
150
|
+
methods.forEach(method => {
|
|
151
|
+
const methodName = method.getName();
|
|
152
|
+
const count = (methodCounts.get(methodName) || 0) + 1;
|
|
153
|
+
methodCounts.set(methodName, count);
|
|
154
|
+
positionMap.set(method.getStart(), count);
|
|
155
|
+
// console.log(`[buildMethodPositionMap] Module interface method: ${methodName}, position: ${method.getStart()}, index: ${count}`);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Recursively process nested modules
|
|
160
|
+
const nestedModules = moduleNode.getModules();
|
|
161
|
+
nestedModules.forEach(nestedModule => {
|
|
162
|
+
if (Node.isModuleDeclaration(nestedModule)) {
|
|
163
|
+
const nestedModuleName = nestedModule.getName();
|
|
164
|
+
const newModulePath = `${modulePath}.${nestedModuleName}`;
|
|
165
|
+
processModule(nestedModule, newModulePath);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function trackArrowFunctions(container: Node) {
|
|
172
|
+
const arrows = container.getDescendantsOfKind(SyntaxKind.ArrowFunction);
|
|
173
|
+
arrows.forEach(arrow => {
|
|
174
|
+
const parent = arrow.getParent();
|
|
175
|
+
if (Node.isBlock(parent) || Node.isSourceFile(parent)) {
|
|
176
|
+
// Use negative numbers for arrow functions to distinguish from methods
|
|
177
|
+
positionMap.set(arrow.getStart(), -1 * (positionMap.size + 1));
|
|
178
|
+
// console.log(`[buildMethodPositionMap] Arrow function at ${arrow.getStart()}`);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Handle top-level classes
|
|
184
|
+
sourceFile.getClasses().forEach(classNode => {
|
|
185
|
+
// console.log(`[buildMethodPositionMap] Processing class: ${classNode.getName() || 'Unnamed'}`);
|
|
186
|
+
const methods = classNode.getMethods();
|
|
187
|
+
const methodCounts = new Map<string, number>();
|
|
188
|
+
|
|
189
|
+
methods.forEach(method => {
|
|
190
|
+
const methodName = method.getName();
|
|
191
|
+
const count = (methodCounts.get(methodName) || 0) + 1;
|
|
192
|
+
methodCounts.set(methodName, count);
|
|
193
|
+
positionMap.set(method.getStart(), count);
|
|
194
|
+
// console.log(`[buildMethodPositionMap] Class method: ${methodName}, position: ${method.getStart()}, index: ${count}`);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
methods.forEach(method => trackArrowFunctions(method));
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Handle top-level interfaces
|
|
201
|
+
sourceFile.getInterfaces().forEach(interfaceNode => {
|
|
202
|
+
// console.log(`[buildMethodPositionMap] Processing interface: ${interfaceNode.getName() || 'Unnamed'}`);
|
|
203
|
+
const methods = interfaceNode.getMethods();
|
|
204
|
+
const methodCounts = new Map<string, number>();
|
|
205
|
+
|
|
206
|
+
methods.forEach(method => {
|
|
207
|
+
const methodName = method.getName();
|
|
208
|
+
const count = (methodCounts.get(methodName) || 0) + 1;
|
|
209
|
+
methodCounts.set(methodName, count);
|
|
210
|
+
positionMap.set(method.getStart(), count);
|
|
211
|
+
// console.log(`[buildMethodPositionMap] Interface method: ${methodName}, position: ${method.getStart()}, index: ${count}`);
|
|
212
|
+
});
|
|
213
|
+
methods.forEach(method => trackArrowFunctions(method));
|
|
214
|
+
|
|
215
|
+
});
|
|
15
216
|
|
|
217
|
+
// Handle top-level namespaces/modules
|
|
218
|
+
sourceFile.getModules().forEach(moduleNode => {
|
|
219
|
+
if (Node.isModuleDeclaration(moduleNode)) {
|
|
220
|
+
const moduleName = moduleNode.getName();
|
|
221
|
+
processModule(moduleNode, moduleName);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
// console.log(`[buildMethodPositionMap] Final positionMap:`, Array.from(positionMap.entries()));
|
|
227
|
+
return positionMap;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generates a fully qualified name (FQN) for a given AST node.
|
|
232
|
+
* Constructs an FQN by traversing the node's ancestry, adding names and keys
|
|
233
|
+
* (numeric or string from object literals ...) as needed, prefixed with the file's relative path.
|
|
234
|
+
*
|
|
235
|
+
* @param node The AST node to generate an FQN for
|
|
236
|
+
* @returns A string representing the node's FQN (e.g., "{path}.operations.add.compute[MethodDeclaration]")
|
|
237
|
+
*/
|
|
238
|
+
export function getFQN(node: FQNNode | Node): string {
|
|
16
239
|
const sourceFile = node.getSourceFile();
|
|
17
|
-
|
|
240
|
+
const absolutePathProject = entityDictionary.famixRep.getAbsolutePath();
|
|
241
|
+
const parts: string[] = [];
|
|
18
242
|
let currentNode: Node | undefined = node;
|
|
19
243
|
|
|
244
|
+
const stageMap = buildStageMethodMap(sourceFile);
|
|
245
|
+
const methodPositionMap = buildMethodPositionMap(sourceFile);
|
|
246
|
+
|
|
20
247
|
while (currentNode && !Node.isSourceFile(currentNode)) {
|
|
21
248
|
const { line, column } = sourceFile.getLineAndColumnAtPos(currentNode.getStart());
|
|
22
249
|
const lc = `${line}:${column}`;
|
|
23
|
-
|
|
24
|
-
|
|
250
|
+
|
|
251
|
+
if (Node.isClassDeclaration(currentNode) ||
|
|
252
|
+
Node.isClassExpression(currentNode) ||
|
|
25
253
|
Node.isInterfaceDeclaration(currentNode) ||
|
|
26
|
-
Node.isFunctionDeclaration(currentNode) ||
|
|
27
|
-
Node.isMethodDeclaration(currentNode) ||
|
|
28
|
-
Node.isModuleDeclaration(currentNode) ||
|
|
29
|
-
Node.isVariableDeclaration(currentNode) ||
|
|
254
|
+
Node.isFunctionDeclaration(currentNode) ||
|
|
255
|
+
Node.isMethodDeclaration(currentNode) ||
|
|
256
|
+
Node.isModuleDeclaration(currentNode) ||
|
|
257
|
+
Node.isVariableDeclaration(currentNode) ||
|
|
30
258
|
Node.isGetAccessorDeclaration(currentNode) ||
|
|
31
259
|
Node.isSetAccessorDeclaration(currentNode) ||
|
|
260
|
+
Node.isPropertyDeclaration(currentNode) ||
|
|
261
|
+
Node.isParameterDeclaration(currentNode) ||
|
|
262
|
+
Node.isDecorator(currentNode) ||
|
|
263
|
+
Node.isTypeAliasDeclaration(currentNode) ||
|
|
264
|
+
Node.isEnumDeclaration(currentNode) ||
|
|
265
|
+
Node.isEnumMember(currentNode) ||
|
|
266
|
+
Node.isParametered(currentNode) ||
|
|
267
|
+
Node.isPropertySignature(currentNode) ||
|
|
268
|
+
Node.isArrayLiteralExpression(currentNode) ||
|
|
269
|
+
Node.isImportSpecifier(currentNode) ||
|
|
32
270
|
Node.isIdentifier(currentNode)) {
|
|
33
|
-
let name
|
|
34
|
-
|
|
271
|
+
let name: string;
|
|
272
|
+
if (Node.isImportSpecifier(currentNode)) {
|
|
273
|
+
const alias = currentNode.getAliasNode()?.getText();
|
|
274
|
+
if (alias) {
|
|
275
|
+
let importDecl: Node | undefined = currentNode;
|
|
276
|
+
while (importDecl && !Node.isImportDeclaration(importDecl)) {
|
|
277
|
+
importDecl = importDecl.getParent();
|
|
278
|
+
}
|
|
279
|
+
const moduleSpecifier = importDecl && Node.isImportDeclaration(importDecl)
|
|
280
|
+
? importDecl.getModuleSpecifier().getLiteralText()
|
|
281
|
+
: "unknown";
|
|
282
|
+
name = currentNode.getName();
|
|
283
|
+
name = `${name} as ${alias}[ImportSpecifier<${moduleSpecifier}>]`;
|
|
284
|
+
} else {
|
|
285
|
+
name = currentNode.getName();
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
// if constructor, use "constructor" as name
|
|
289
|
+
if (Node.isConstructorDeclaration(currentNode)) {
|
|
290
|
+
name = "constructor";
|
|
291
|
+
} else {
|
|
292
|
+
name = Node.isIdentifier(currentNode) ? currentNode.getText()
|
|
293
|
+
: 'getName' in currentNode && typeof currentNode['getName'] === 'function'
|
|
294
|
+
? ((currentNode as { getName(): string }).getName() +
|
|
295
|
+
((Node.isClassDeclaration(currentNode) ||
|
|
296
|
+
Node.isInterfaceDeclaration(currentNode) ||
|
|
297
|
+
Node.isMethodDeclaration(currentNode) ||
|
|
298
|
+
Node.isFunctionDeclaration(currentNode)) &&
|
|
299
|
+
'getTypeParameters' in currentNode &&
|
|
300
|
+
currentNode.getTypeParameters().length > 0
|
|
301
|
+
? getParameters(currentNode)
|
|
302
|
+
: ''))
|
|
303
|
+
: `Unnamed_${currentNode.getKindName()}(${lc})`;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (Node.isMethodSignature(currentNode)) {
|
|
308
|
+
const method = currentNode as MethodSignature;
|
|
309
|
+
const params = method.getParameters().map(p => {
|
|
310
|
+
const typeText = p.getType().getText().replace(/\s+/g, "");
|
|
311
|
+
return typeText || "any"; // Fallback for untyped parameters
|
|
312
|
+
});
|
|
313
|
+
const returnType = method.getReturnType().getText().replace(/\s+/g, "") || "void";
|
|
314
|
+
name = `${name}(${params.join(",")}):${returnType}`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
parts.unshift(name);
|
|
318
|
+
|
|
319
|
+
// Apply positional index for MethodDeclaration, MethodSignature, and FunctionDeclaration
|
|
320
|
+
if (Node.isMethodDeclaration(currentNode) ||
|
|
321
|
+
Node.isMethodSignature(currentNode) ||
|
|
322
|
+
Node.isFunctionDeclaration(currentNode)) {
|
|
323
|
+
const key = stageMap.get(currentNode.getStart());
|
|
324
|
+
if (key) {
|
|
325
|
+
parts.unshift(key);
|
|
326
|
+
// console.log(`[getFQN] Applied stageMap key: ${key} for ${currentNode.getKindName()} at position ${currentNode.getStart()}`);
|
|
327
|
+
} else {
|
|
328
|
+
const positionIndex = methodPositionMap.get(currentNode.getStart());
|
|
329
|
+
if (positionIndex && positionIndex > 1) {
|
|
330
|
+
parts.unshift(positionIndex.toString());
|
|
331
|
+
// console.log(`[getFQN] Applied positionIndex: ${positionIndex} for ${currentNode.getKindName()} at position ${currentNode.getStart()}`);
|
|
332
|
+
} else {
|
|
333
|
+
console.log(`[getFQN] No positionIndex applied for ${currentNode.getKindName()} at position ${currentNode.getStart()}, positionIndex: ${positionIndex || 'none'}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else if (Node.isArrowFunction(currentNode) ||
|
|
339
|
+
Node.isBlock(currentNode) ||
|
|
340
|
+
Node.isForInStatement(currentNode) ||
|
|
341
|
+
Node.isForOfStatement(currentNode) ||
|
|
342
|
+
Node.isForStatement(currentNode) ||
|
|
343
|
+
Node.isCatchClause(currentNode)) {
|
|
344
|
+
const name = `${currentNode.getKindName()}(${lc})`;
|
|
345
|
+
parts.unshift(name);
|
|
346
|
+
}
|
|
347
|
+
else if (Node.isTypeParameterDeclaration(currentNode)) {
|
|
348
|
+
const arrowParent = currentNode.getFirstAncestorByKind(SyntaxKind.ArrowFunction);
|
|
349
|
+
if (arrowParent) {
|
|
350
|
+
const arrowIndex = Math.abs(methodPositionMap.get(arrowParent.getStart()) || 0);
|
|
351
|
+
if (arrowIndex > 0) {
|
|
352
|
+
parts.unshift(arrowIndex.toString());
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
parts.unshift(currentNode.getName());
|
|
356
|
+
// Removed continue to allow ancestor processing
|
|
357
|
+
}
|
|
358
|
+
else if (Node.isConstructorDeclaration(currentNode)) {
|
|
359
|
+
const name = "constructor";
|
|
35
360
|
parts.unshift(name);
|
|
36
|
-
} else if (Node.isArrowFunction(currentNode) ||
|
|
37
|
-
Node.isBlock(currentNode) ||
|
|
38
|
-
Node.isForInStatement(currentNode) ||
|
|
39
|
-
Node.isForOfStatement(currentNode) ||
|
|
40
|
-
Node.isForStatement(currentNode) ||
|
|
41
|
-
Node.isCatchClause(currentNode)) {
|
|
42
|
-
parts.unshift(`${currentNode.getKindName()}(${lc})`);
|
|
43
|
-
} else if (Node.isConstructorDeclaration(currentNode)) {
|
|
44
|
-
parts.unshift(`constructor`);
|
|
45
361
|
} else {
|
|
46
|
-
|
|
47
|
-
//console.log(`Ignoring node kind: ${currentNode.getKindName()}`);
|
|
362
|
+
console.log(`[getFQN] Ignoring node kind: ${currentNode.getKindName()}`);
|
|
48
363
|
}
|
|
364
|
+
|
|
49
365
|
currentNode = currentNode.getParent();
|
|
50
366
|
}
|
|
51
367
|
|
|
368
|
+
let relativePath = entityDictionary.convertToRelativePath(
|
|
369
|
+
path.normalize(sourceFile.getFilePath()),
|
|
370
|
+
absolutePathProject
|
|
371
|
+
).replace(/\\/g, "/");
|
|
52
372
|
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
373
|
+
// if (relativePath.includes("..")) {
|
|
374
|
+
// }
|
|
375
|
+
if (relativePath.startsWith("/")) {
|
|
376
|
+
relativePath = relativePath.slice(1);
|
|
377
|
+
}
|
|
58
378
|
parts.unshift(`{${relativePath}}`);
|
|
59
|
-
const fqn = parts.join(".") + `[${node.getKindName()}]`; // disambiguate
|
|
60
379
|
|
|
61
|
-
|
|
380
|
+
const fqn = parts.join(".") + `[${node.getKindName()}]`;
|
|
381
|
+
// console.log(`[getFQN] Final FQN: ${fqn}`);
|
|
62
382
|
return fqn;
|
|
63
383
|
}
|
|
64
384
|
|
|
65
385
|
|
|
66
386
|
export function getUniqueFQN(node: Node): string | undefined {
|
|
67
387
|
const absolutePathProject = entityDictionary.famixRep.getAbsolutePath();
|
|
68
|
-
|
|
388
|
+
const parts: string[] = [];
|
|
69
389
|
|
|
70
390
|
if (node instanceof SourceFile) {
|
|
71
391
|
return entityDictionary.convertToRelativePath(path.normalize(node.getFilePath()), absolutePathProject).replace(/\\/g, "/");
|
|
@@ -75,6 +395,9 @@ export function getUniqueFQN(node: Node): string | undefined {
|
|
|
75
395
|
while (currentNode) {
|
|
76
396
|
if (Node.isSourceFile(currentNode)) {
|
|
77
397
|
const relativePath = entityDictionary.convertToRelativePath(path.normalize(currentNode.getFilePath()), absolutePathProject).replace(/\\/g, "/");
|
|
398
|
+
if (relativePath.includes("..")) {
|
|
399
|
+
logger.error(`Relative path contains ../: ${relativePath}`);
|
|
400
|
+
}
|
|
78
401
|
parts.unshift(relativePath); // Add file path at the start
|
|
79
402
|
break;
|
|
80
403
|
} else if (currentNode.getSymbol()) {
|
|
@@ -95,15 +418,20 @@ export function getUniqueFQN(node: Node): string | undefined {
|
|
|
95
418
|
* @returns The name of the node, or an empty string if it doesn't have one
|
|
96
419
|
*/
|
|
97
420
|
export function getNameOfNode(a: Node): string {
|
|
421
|
+
let cKind: ClassDeclaration | undefined;
|
|
422
|
+
let iKind: InterfaceDeclaration | undefined;
|
|
423
|
+
let mKind: MethodDeclaration | undefined;
|
|
424
|
+
let fKind: FunctionDeclaration | undefined;
|
|
425
|
+
let alias: TSMorphTypeDeclaration | undefined;
|
|
98
426
|
switch (a.getKind()) {
|
|
99
427
|
case SyntaxKind.SourceFile:
|
|
100
428
|
return a.asKind(SyntaxKind.SourceFile)!.getBaseName();
|
|
101
429
|
|
|
102
430
|
case SyntaxKind.ModuleDeclaration:
|
|
103
|
-
return a.asKind(SyntaxKind.ModuleDeclaration)!.getName();
|
|
431
|
+
return a.asKind(SyntaxKind.ModuleDeclaration)!.getName();
|
|
104
432
|
|
|
105
433
|
case SyntaxKind.ClassDeclaration:
|
|
106
|
-
|
|
434
|
+
cKind = a.asKind(SyntaxKind.ClassDeclaration);
|
|
107
435
|
if (cKind && cKind.getTypeParameters().length > 0) {
|
|
108
436
|
return cKind.getName() + getParameters(a);
|
|
109
437
|
} else {
|
|
@@ -111,21 +439,21 @@ export function getNameOfNode(a: Node): string {
|
|
|
111
439
|
}
|
|
112
440
|
|
|
113
441
|
case SyntaxKind.InterfaceDeclaration:
|
|
114
|
-
|
|
442
|
+
iKind = a.asKind(SyntaxKind.InterfaceDeclaration);
|
|
115
443
|
if (iKind && iKind.getTypeParameters().length > 0) {
|
|
116
444
|
return iKind.getName() + getParameters(a);
|
|
117
445
|
} else {
|
|
118
446
|
return iKind?.getName() || "";
|
|
119
447
|
}
|
|
120
|
-
|
|
448
|
+
|
|
121
449
|
case SyntaxKind.PropertyDeclaration:
|
|
122
|
-
return a.asKind(SyntaxKind.PropertyDeclaration)!.getName();
|
|
450
|
+
return a.asKind(SyntaxKind.PropertyDeclaration)!.getName();
|
|
123
451
|
|
|
124
452
|
case SyntaxKind.PropertySignature:
|
|
125
|
-
return a.asKind(SyntaxKind.PropertySignature)!.getName();
|
|
126
|
-
|
|
453
|
+
return a.asKind(SyntaxKind.PropertySignature)!.getName();
|
|
454
|
+
|
|
127
455
|
case SyntaxKind.MethodDeclaration:
|
|
128
|
-
|
|
456
|
+
mKind = a.asKind(SyntaxKind.MethodDeclaration);
|
|
129
457
|
if (mKind && mKind.getTypeParameters().length > 0) {
|
|
130
458
|
return mKind.getName() + getParameters(a);
|
|
131
459
|
} else {
|
|
@@ -133,16 +461,16 @@ export function getNameOfNode(a: Node): string {
|
|
|
133
461
|
}
|
|
134
462
|
|
|
135
463
|
case SyntaxKind.MethodSignature:
|
|
136
|
-
return a.asKind(SyntaxKind.MethodSignature)!.getName();
|
|
464
|
+
return a.asKind(SyntaxKind.MethodSignature)!.getName();
|
|
137
465
|
|
|
138
466
|
case SyntaxKind.GetAccessor:
|
|
139
467
|
return a.asKind(SyntaxKind.GetAccessor)!.getName();
|
|
140
468
|
|
|
141
469
|
case SyntaxKind.SetAccessor:
|
|
142
470
|
return a.asKind(SyntaxKind.SetAccessor)!.getName();
|
|
143
|
-
|
|
471
|
+
|
|
144
472
|
case SyntaxKind.FunctionDeclaration:
|
|
145
|
-
|
|
473
|
+
fKind = a.asKind(SyntaxKind.FunctionDeclaration);
|
|
146
474
|
if (fKind && fKind.getTypeParameters().length > 0) {
|
|
147
475
|
return fKind.getName() + getParameters(a);
|
|
148
476
|
} else {
|
|
@@ -151,15 +479,15 @@ export function getNameOfNode(a: Node): string {
|
|
|
151
479
|
|
|
152
480
|
case SyntaxKind.FunctionExpression:
|
|
153
481
|
return a.asKind(SyntaxKind.FunctionExpression)?.getName() || "anonymous";
|
|
154
|
-
|
|
482
|
+
|
|
155
483
|
case SyntaxKind.Parameter:
|
|
156
484
|
return a.asKind(SyntaxKind.Parameter)!.getName();
|
|
157
|
-
|
|
485
|
+
|
|
158
486
|
case SyntaxKind.VariableDeclaration:
|
|
159
487
|
return a.asKind(SyntaxKind.VariableDeclaration)!.getName();
|
|
160
488
|
|
|
161
489
|
case SyntaxKind.Decorator:
|
|
162
|
-
return "@" + a.asKind(SyntaxKind.Decorator)!.getName();
|
|
490
|
+
return "@" + a.asKind(SyntaxKind.Decorator)!.getName();
|
|
163
491
|
|
|
164
492
|
case SyntaxKind.TypeParameter:
|
|
165
493
|
return a.asKind(SyntaxKind.TypeParameter)!.getName();
|
|
@@ -171,15 +499,21 @@ export function getNameOfNode(a: Node): string {
|
|
|
171
499
|
return a.asKind(SyntaxKind.EnumMember)!.getName();
|
|
172
500
|
|
|
173
501
|
case SyntaxKind.TypeAliasDeclaration:
|
|
502
|
+
// special case for parameterized types
|
|
503
|
+
alias = a.asKind(SyntaxKind.TypeAliasDeclaration);
|
|
504
|
+
if (alias && alias.getTypeParameters().length > 0) {
|
|
505
|
+
return alias.getName() + "<" + alias.getTypeParameters().map(tp => tp.getName()).join(", ") + ">";
|
|
506
|
+
}
|
|
174
507
|
return a.asKind(SyntaxKind.TypeAliasDeclaration)!.getName();
|
|
175
508
|
|
|
176
509
|
case SyntaxKind.Constructor:
|
|
177
|
-
return "constructor";
|
|
178
|
-
|
|
510
|
+
return "constructor";
|
|
511
|
+
|
|
179
512
|
default:
|
|
513
|
+
// throw new Error(`getNameOfNode called on a node that doesn't have a name: ${a.getKindName()}`);
|
|
180
514
|
// ancestor hasn't got a useful name
|
|
181
515
|
return "";
|
|
182
|
-
|
|
516
|
+
}
|
|
183
517
|
}
|
|
184
518
|
|
|
185
519
|
/**
|
|
@@ -207,3 +541,69 @@ export function getParameters(a: Node): string {
|
|
|
207
541
|
}
|
|
208
542
|
return paramString;
|
|
209
543
|
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Gets the FQN of an unresolved interface that is being implemented or extended
|
|
547
|
+
* @param unresolvedInheritedClassOrInterface The expression with type arguments representing the interface
|
|
548
|
+
* @returns The FQN of the unresolved interface
|
|
549
|
+
*/
|
|
550
|
+
export function getFQNUnresolvedInheritedClassOrInterface(unresolvedInheritedClassOrInterface: ExpressionWithTypeArguments): string {
|
|
551
|
+
// Check for either ClassDeclaration or InterfaceDeclaration ancestor
|
|
552
|
+
const classAncestor = unresolvedInheritedClassOrInterface.getFirstAncestorByKind(SyntaxKind.ClassDeclaration);
|
|
553
|
+
const interfaceAncestor = unresolvedInheritedClassOrInterface.getFirstAncestorByKind(SyntaxKind.InterfaceDeclaration);
|
|
554
|
+
|
|
555
|
+
// Validate the context
|
|
556
|
+
if (!classAncestor && !interfaceAncestor) {
|
|
557
|
+
throw new Error("getFQNUnresolvedClassOrInterface called on a node that is not in an implements or extends context");
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Check if it's a valid implements/extends context
|
|
561
|
+
let isValidContext = false;
|
|
562
|
+
|
|
563
|
+
let classExtendsClass = false;
|
|
564
|
+
|
|
565
|
+
if (classAncestor) {
|
|
566
|
+
// check if the class is extending or implementing an interface
|
|
567
|
+
const extendsClause = classAncestor.getExtends();
|
|
568
|
+
const implementsClause = classAncestor.getImplements();
|
|
569
|
+
isValidContext = (extendsClause !== undefined) || (implementsClause && implementsClause.length > 0);
|
|
570
|
+
classExtendsClass = extendsClause !== undefined;
|
|
571
|
+
} else if (interfaceAncestor) {
|
|
572
|
+
// Check extends clause for interfaces
|
|
573
|
+
const extendsClause = interfaceAncestor.getExtends();
|
|
574
|
+
isValidContext = extendsClause && extendsClause.length > 0;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (!isValidContext) {
|
|
578
|
+
throw new Error("getFQNUnresolvedInterface called on a node that is not in a valid implements or extends context");
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// get the name of the interface
|
|
582
|
+
const name = unresolvedInheritedClassOrInterface.getExpression().getText();
|
|
583
|
+
|
|
584
|
+
// Find where it's imported - search the entire source file
|
|
585
|
+
const sourceFile = unresolvedInheritedClassOrInterface.getSourceFile();
|
|
586
|
+
const importDecls = sourceFile.getImportDeclarations();
|
|
587
|
+
|
|
588
|
+
for (const importDecl of importDecls) {
|
|
589
|
+
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
590
|
+
const importClause = importDecl.getImportClause();
|
|
591
|
+
|
|
592
|
+
if (importClause) {
|
|
593
|
+
const namedImports = importClause.getNamedImports();
|
|
594
|
+
// declarationName is ClassDeclaration if "class extends class"
|
|
595
|
+
const declarationName = classExtendsClass ? "ClassDeclaration" : "InterfaceDeclaration";
|
|
596
|
+
|
|
597
|
+
for (const namedImport of namedImports) {
|
|
598
|
+
if (namedImport.getName() === name) {
|
|
599
|
+
logger.debug(`Found import for ${name} in ${moduleSpecifier}`);
|
|
600
|
+
return `{module:${moduleSpecifier}}.${name}[${declarationName}]`;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// If not found, return a default FQN format
|
|
607
|
+
return `{unknown-module}.${name}[InterfaceDeclaration]`;
|
|
608
|
+
}
|
|
609
|
+
|