diff --git a/src/analyze_functions/process_functions.ts b/src/analyze_functions/process_functions.ts index 8c977746..22ed3ca1 100644 --- a/src/analyze_functions/process_functions.ts +++ b/src/analyze_functions/process_functions.ts @@ -251,7 +251,7 @@ function processVariables(m: ContainerTypes, fmxScope: Famix.ScriptEntity | Fami // Check each VariableDeclaration for object literal methods v.getDeclarations().forEach(varDecl => { const varName = varDecl.getName(); - console.log(`Checking variable: ${varName} at pos=${varDecl.getStart()}`); + // console.log(`Checking variable: ${varName} at pos=${varDecl.getStart()}`); const initializer = varDecl.getInitializer(); if (initializer && Node.isObjectLiteralExpression(initializer)) { initializer.getProperties().forEach(prop => { @@ -259,7 +259,7 @@ function processVariables(m: ContainerTypes, fmxScope: Famix.ScriptEntity | Fami const nested = prop.getInitializer(); if (nested && Node.isObjectLiteralExpression(nested)) { nested.getDescendantsOfKind(SyntaxKind.MethodDeclaration).forEach(method => { - console.log(`Found object literal method: ${method.getName()} at pos=${method.getStart()}`); + // console.log(`Found object literal method: ${method.getName()} at pos=${method.getStart()}`); entityDictionary.createOrGetFamixMethod(method, currentCC); }); } @@ -590,7 +590,7 @@ function convertParameterToPropertyRepresentation(param: ParameterDeclaration) { const paramType = param.getType().getText(param); // Determine visibility - let scope: Scope; + let scope: Scope = Scope.Public; if (param.hasModifier(SyntaxKind.PrivateKeyword)) { scope = Scope.Private; } else if (param.hasModifier(SyntaxKind.ProtectedKeyword)) { @@ -598,7 +598,7 @@ function convertParameterToPropertyRepresentation(param: ParameterDeclaration) { } else if (param.hasModifier(SyntaxKind.PublicKeyword)) { scope = Scope.Public; } else { - throw new Error(`Parameter property ${paramName} in constructor does not have a visibility modifier.`); + console.log(`[convertParameterToPropertyRepresentation] Parameter ${paramName} has no visibility modifier, defaulting to public.`); } // Determine if readonly diff --git a/src/famix_functions/EntityDictionary.ts b/src/famix_functions/EntityDictionary.ts index e631762f..b2a1bc40 100644 --- a/src/famix_functions/EntityDictionary.ts +++ b/src/famix_functions/EntityDictionary.ts @@ -1031,10 +1031,10 @@ export class EntityDictionary { ancestor = this.createOrGetFamixType(typeAncestor.getText(), typeAncestor.getType(), typeAncestor as TSMorphTypeDeclaration); // console.log('Ancestor not found in repo, creating it'); } else { - console.log(`Found ancestor in famixRep: ${ancestor.fullyQualifiedName}`); + // console.log(`Found ancestor in famixRep: ${ancestor.fullyQualifiedName}`); } } else { - console.log(`No type ancestor found for ${typeName} - proceeding without container`); + // console.log(`No type ancestor found for ${typeName} - proceeding without container`); } } @@ -1180,55 +1180,64 @@ export class EntityDictionary { return fmxType; } - /** - * Creates a Famix access - * @param node A node - * @param id An id of a parameter, a variable, a property or an enum member - */ - public createFamixAccess(node: Identifier, id: number): void { - const fmxVar = this.famixRep.getFamixEntityById(id) as Famix.StructuralEntity; - if (!fmxVar) { - throw new Error(`Famix entity with id ${id} not found, for node ${node.getText()} in ${node.getSourceFile().getBaseName()} at line ${node.getStartLineNumber()}.`); - } - - logger.debug(`Creating FamixAccess. Node: [${node.getKindName()}] '${node.getText()}' at line ${node.getStartLineNumber()} in ${node.getSourceFile().getBaseName()}, id: ${id} refers to fmxVar '${fmxVar.fullyQualifiedName}'.`); - - const nodeReferenceAncestor = Helpers.findAncestor(node); - if (!nodeReferenceAncestor) { - logger.error(`No ancestor found for node '${node.getText()}'`); - return; - } - - const ancestorFullyQualifiedName = FQNFunctions.getFQN(nodeReferenceAncestor); - const accessor = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName) as Famix.ContainerEntity; - if (!accessor) { - logger.error(`Ancestor ${ancestorFullyQualifiedName} of kind ${nodeReferenceAncestor.getKindName()} not found.`); - return; // Bail out for now - } else { - logger.debug(`Found accessor to be ${accessor.fullyQualifiedName}.`); - } - - // Ensure accessor is a method, function, script, or module - if (!(accessor instanceof Famix.Method) && !(accessor instanceof Famix.ArrowFunction) && !(accessor instanceof Famix.Function) && !(accessor instanceof Famix.ScriptEntity) && !(accessor instanceof Famix.Module)) { - logger.error(`Accessor ${accessor.fullyQualifiedName} is not a method, function, etc.`); - return; - } - - // Avoid duplicates - const foundAccess = this.famixRep.getFamixAccessByAccessorAndVariable(accessor, fmxVar); - if (foundAccess) { - logger.debug(`FamixAccess already exists for accessor ${accessor.fullyQualifiedName} and variable ${fmxVar.fullyQualifiedName}.`); +/** + * Creates a Famix access + * @param node A node + * @param id An id of a parameter, a variable, a property or an enum member + */ +public createFamixAccess(node: Identifier, id: number): void { + const fmxVar = this.famixRep.getFamixEntityById(id) as Famix.StructuralEntity; + if (!fmxVar) { + throw new Error(`Famix entity with id ${id} not found, for node ${node.getText()} in ${node.getSourceFile().getBaseName()} at line ${node.getStartLineNumber()}.`); + } + + logger.debug(`Creating FamixAccess. Node: [${node.getKindName()}] '${node.getText()}' at line ${node.getStartLineNumber()} in ${node.getSourceFile().getBaseName()}, id: ${id} refers to fmxVar '${fmxVar.fullyQualifiedName}'.`); + + const nodeReferenceAncestor = Helpers.findAncestor(node); + if (!nodeReferenceAncestor) { + logger.error(`No ancestor found for node '${node.getText()}'`); + return; + } + + const ancestorFullyQualifiedName = FQNFunctions.getFQN(nodeReferenceAncestor); + const accessor = this.famixRep.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName) as Famix.ContainerEntity; + if (!accessor) { + logger.error(`Ancestor ${ancestorFullyQualifiedName} of kind ${nodeReferenceAncestor.getKindName()} not found.`); + return; + } else { + logger.debug(`Found accessor to be ${accessor.fullyQualifiedName}.`); + } + + // Check if the accessor is a valid type (method, function, script, module, or class for specific cases) + if (!(accessor instanceof Famix.Method) && + !(accessor instanceof Famix.ArrowFunction) && + !(accessor instanceof Famix.Function) && + !(accessor instanceof Famix.ScriptEntity) && + !(accessor instanceof Famix.Module)) { + // Handle class-level accesses (e.g., decorators, DI) + if (accessor instanceof Famix.Class) { + logger.debug(`Skipping FamixAccess for class accessor ${accessor.fullyQualifiedName} (node: '${node.getText()}', parent: ${node.getParent()?.getKindName() || 'unknown'}). Likely decorator or DI reference.`); return; } - - const fmxAccess = new Famix.Access(); - fmxAccess.accessor = accessor; - fmxAccess.variable = fmxVar; - this.famixRep.addElement(fmxAccess); - this.fmxElementObjectMap.set(fmxAccess, node); - logger.debug(`Created access: ${accessor.fullyQualifiedName} -> ${fmxVar.fullyQualifiedName}`); + logger.error(`Accessor ${accessor.fullyQualifiedName} is not a method, function, etc.`); + return; + } + + // Avoid duplicates + const foundAccess = this.famixRep.getFamixAccessByAccessorAndVariable(accessor, fmxVar); + if (foundAccess) { + logger.debug(`FamixAccess already exists for accessor ${accessor.fullyQualifiedName} and variable ${fmxVar.fullyQualifiedName}.`); + return; } + const fmxAccess = new Famix.Access(); + fmxAccess.accessor = accessor; + fmxAccess.variable = fmxVar; + this.famixRep.addElement(fmxAccess); + this.fmxElementObjectMap.set(fmxAccess, node); + logger.debug(`Created access: ${accessor.fullyQualifiedName} -> ${fmxVar.fullyQualifiedName}`); +} + /** * Creates a Famix invocation * @param nodeReferringToInvocable A node diff --git a/src/famix_functions/helpers_creation.ts b/src/famix_functions/helpers_creation.ts index ae9276ec..871198b3 100644 --- a/src/famix_functions/helpers_creation.ts +++ b/src/famix_functions/helpers_creation.ts @@ -90,7 +90,7 @@ export function findAncestor(node: Identifier): Node { export function findTypeAncestor(element: Node): Node | undefined { let ancestor: Node | undefined; const ancestors = element.getAncestors(); - console.log(`Ancestors count: ${ancestors.length}`); + // console.log(`Ancestors count: ${ancestors.length}`); ancestor = ancestors.find(a => { const kind = a.getKind(); diff --git a/src/fqn.ts b/src/fqn.ts index 73b71290..7619b0c7 100644 --- a/src/fqn.ts +++ b/src/fqn.ts @@ -100,11 +100,11 @@ function buildStageMethodMap(sourceFile: SourceFile): Map { } /** - * Builds a map of method positions to their index in class/interface/namespace declarations + * Builds a map of method and property positions to their index in class/interface/namespace declarations * @param sourceFile The TypeScript source file to analyze - * @returns A Map where keys are method start positions and values are their positional index (1-based) + * @returns A Map where keys are node start positions and values are their positional index (1-based) */ -function buildMethodPositionMap(sourceFile: SourceFile): Map { +export function buildMethodPositionMap(sourceFile: SourceFile): Map { const positionMap = new Map(); // console.log(`[buildMethodPositionMap] Starting analysis for file: ${sourceFile.getFilePath()}`); @@ -112,6 +112,23 @@ function buildMethodPositionMap(sourceFile: SourceFile): Map { function processModule(moduleNode: ModuleDeclaration, modulePath: string) { // console.log(`[buildMethodPositionMap] Processing module: ${modulePath}`); + // Track nested modules + const nestedModuleCounts = new Map(); + const nestedModules = moduleNode.getModules(); + nestedModules.forEach(nestedModule => { + if (Node.isModuleDeclaration(nestedModule)) { + const nestedModuleName = nestedModule.getName(); + const count = (nestedModuleCounts.get(nestedModuleName) || 0) + 1; + nestedModuleCounts.set(nestedModuleName, count); + if (count > 1) { + positionMap.set(nestedModule.getStart(), count); + // console.log(`[buildMethodPositionMap] Nested module: ${nestedModuleName}, position: ${nestedModule.getStart()}, index: ${count}`); + } + const newModulePath = `${modulePath}.${nestedModuleName}`; + processModule(nestedModule, newModulePath); + } + }); + // Handle functions directly in the module const functions = moduleNode.getFunctions(); const functionCounts = new Map(); @@ -138,6 +155,18 @@ function buildMethodPositionMap(sourceFile: SourceFile): Map { positionMap.set(method.getStart(), count); // console.log(`[buildMethodPositionMap] Module class method: ${methodName}, position: ${method.getStart()}, index: ${count}`); }); + + // Handle properties within the class + const properties = classNode.getProperties(); + const propertyCounts = new Map(); + + properties.forEach(property => { + const propertyName = property.getName(); + const count = (propertyCounts.get(propertyName) || 0) + 1; + propertyCounts.set(propertyName, count); + positionMap.set(property.getStart(), count); + // console.log(`[buildMethodPositionMap] Module class property: ${propertyName}, position: ${property.getStart()}, index: ${count}`); + }); }); // Handle interfaces within the module @@ -155,27 +184,30 @@ function buildMethodPositionMap(sourceFile: SourceFile): Map { // console.log(`[buildMethodPositionMap] Module interface method: ${methodName}, position: ${method.getStart()}, index: ${count}`); }); }); - - // Recursively process nested modules - const nestedModules = moduleNode.getModules(); - nestedModules.forEach(nestedModule => { - if (Node.isModuleDeclaration(nestedModule)) { - const nestedModuleName = nestedModule.getName(); - const newModulePath = `${modulePath}.${nestedModuleName}`; - processModule(nestedModule, newModulePath); - } - }); - } function trackArrowFunctions(container: Node) { const arrows = container.getDescendantsOfKind(SyntaxKind.ArrowFunction); + const functionExpressions = container.getDescendantsOfKind(SyntaxKind.FunctionExpression); + let funcIndex = 0; + arrows.forEach(arrow => { const parent = arrow.getParent(); - if (Node.isBlock(parent) || Node.isSourceFile(parent)) { - // Use negative numbers for arrow functions to distinguish from methods - positionMap.set(arrow.getStart(), -1 * (positionMap.size + 1)); - // console.log(`[buildMethodPositionMap] Arrow function at ${arrow.getStart()}`); + if (Node.isBlock(parent) || Node.isSourceFile(parent) || Node.isCallExpression(parent)) { + funcIndex++; + positionMap.set(arrow.getStart(), funcIndex); + const { line, column } = sourceFile.getLineAndColumnAtPos(arrow.getStart()); + // console.log(`[buildMethodPositionMap] Arrow function at ${arrow.getStart()} (line: ${line}, col: ${column}), parent: ${parent.getKindName()}, index: ${funcIndex}`); + } + }); + + functionExpressions.forEach(funcExpr => { + const parent = funcExpr.getParent(); + if (Node.isBlock(parent) || Node.isSourceFile(parent) || Node.isCallExpression(parent)) { + funcIndex++; + positionMap.set(funcExpr.getStart(), funcIndex); + const { line, column } = sourceFile.getLineAndColumnAtPos(funcExpr.getStart()); + // console.log(`[buildMethodPositionMap] Function expression at ${funcExpr.getStart()} (line: ${line}, col: ${column}), parent: ${parent.getKindName()}, index: ${funcIndex}`); } }); } @@ -194,8 +226,31 @@ function buildMethodPositionMap(sourceFile: SourceFile): Map { // console.log(`[buildMethodPositionMap] Class method: ${methodName}, position: ${method.getStart()}, index: ${count}`); }); + // Handle properties in top-level classes + const properties = classNode.getProperties(); + const propertyCounts = new Map(); + + properties.forEach(property => { + const propertyName = property.getName(); + const count = (propertyCounts.get(propertyName) || 0) + 1; + propertyCounts.set(propertyName, count); + positionMap.set(property.getStart(), count); + // console.log(`[buildMethodPositionMap] Class property: ${propertyName}, position: ${property.getStart()}, index: ${count}`); + }); + methods.forEach(method => trackArrowFunctions(method)); }); + + // Handle top-level functions + const topLevelFunctionCounts = new Map(); + sourceFile.getFunctions().forEach(func => { + const funcName = func.getName() || `Unnamed_Function(${func.getStart()})`; + const count = (topLevelFunctionCounts.get(funcName) || 0) + 1; + topLevelFunctionCounts.set(funcName, count); + positionMap.set(func.getStart(), count); + // console.log(`[buildMethodPositionMap] Top-level function: ${funcName}, position: ${func.getStart()}, index: ${count}`); + trackArrowFunctions(func); + }); // Handle top-level interfaces sourceFile.getInterfaces().forEach(interfaceNode => { @@ -211,17 +266,25 @@ function buildMethodPositionMap(sourceFile: SourceFile): Map { // console.log(`[buildMethodPositionMap] Interface method: ${methodName}, position: ${method.getStart()}, index: ${count}`); }); methods.forEach(method => trackArrowFunctions(method)); - }); // Handle top-level namespaces/modules + const topLevelModuleCounts = new Map(); sourceFile.getModules().forEach(moduleNode => { if (Node.isModuleDeclaration(moduleNode)) { const moduleName = moduleNode.getName(); + const count = (topLevelModuleCounts.get(moduleName) || 0) + 1; + topLevelModuleCounts.set(moduleName, count); + if (count > 1) { + positionMap.set(moduleNode.getStart(), count); + // console.log(`[buildMethodPositionMap] Top-level module: ${moduleName}, position: ${moduleNode.getStart()}, index: ${count}`); + } processModule(moduleNode, moduleName); } }); + // Handle top-level arrow functions and function expressions + trackArrowFunctions(sourceFile); // console.log(`[buildMethodPositionMap] Final positionMap:`, Array.from(positionMap.entries())); return positionMap; @@ -285,7 +348,6 @@ export function getFQN(node: FQNNode | Node): string { name = currentNode.getName(); } } else { - // if constructor, use "constructor" as name if (Node.isConstructorDeclaration(currentNode)) { name = "constructor"; } else { @@ -308,7 +370,7 @@ export function getFQN(node: FQNNode | Node): string { const method = currentNode as MethodSignature; const params = method.getParameters().map(p => { const typeText = p.getType().getText().replace(/\s+/g, ""); - return typeText || "any"; // Fallback for untyped parameters + return typeText || "any"; }); const returnType = method.getReturnType().getText().replace(/\s+/g, "") || "void"; name = `${name}(${params.join(",")}):${returnType}`; @@ -316,10 +378,12 @@ export function getFQN(node: FQNNode | Node): string { parts.unshift(name); - // Apply positional index for MethodDeclaration, MethodSignature, and FunctionDeclaration - if (Node.isMethodDeclaration(currentNode) || - Node.isMethodSignature(currentNode) || - Node.isFunctionDeclaration(currentNode)) { + // Apply positional index for MethodDeclaration, MethodSignature, FunctionDeclaration, FunctionExpression, and PropertyDeclaration + if (Node.isMethodDeclaration(currentNode) || + Node.isMethodSignature(currentNode) || + Node.isFunctionDeclaration(currentNode) || + Node.isFunctionExpression(currentNode) || + Node.isPropertyDeclaration(currentNode)) { const key = stageMap.get(currentNode.getStart()); if (key) { parts.unshift(key); @@ -329,8 +393,6 @@ export function getFQN(node: FQNNode | Node): string { if (positionIndex && positionIndex > 1) { parts.unshift(positionIndex.toString()); // console.log(`[getFQN] Applied positionIndex: ${positionIndex} for ${currentNode.getKindName()} at position ${currentNode.getStart()}`); - } else { - console.log(`[getFQN] No positionIndex applied for ${currentNode.getKindName()} at position ${currentNode.getStart()}, positionIndex: ${positionIndex || 'none'}`); } } } @@ -343,7 +405,14 @@ export function getFQN(node: FQNNode | Node): string { Node.isCatchClause(currentNode)) { const name = `${currentNode.getKindName()}(${lc})`; parts.unshift(name); - } + if (Node.isArrowFunction(currentNode)) { + const funcIndex = methodPositionMap.get(currentNode.getStart()); + if (funcIndex && funcIndex > 0) { + parts.unshift(funcIndex.toString()); + // console.log(`[getFQN] Applied funcIndex: ${funcIndex} for ArrowFunction at position ${currentNode.getStart()}`); + } + } + } else if (Node.isTypeParameterDeclaration(currentNode)) { const arrowParent = currentNode.getFirstAncestorByKind(SyntaxKind.ArrowFunction); if (arrowParent) { @@ -353,13 +422,10 @@ export function getFQN(node: FQNNode | Node): string { } } parts.unshift(currentNode.getName()); - // Removed continue to allow ancestor processing } else if (Node.isConstructorDeclaration(currentNode)) { const name = "constructor"; parts.unshift(name); - } else { - console.log(`[getFQN] Ignoring node kind: ${currentNode.getKindName()}`); } currentNode = currentNode.getParent(); @@ -370,8 +436,6 @@ export function getFQN(node: FQNNode | Node): string { absolutePathProject ).replace(/\\/g, "/"); - // if (relativePath.includes("..")) { - // } if (relativePath.startsWith("/")) { relativePath = relativePath.slice(1); }