From 64bd8e32311dbc26cc02d9f702d2545df7321168 Mon Sep 17 00:00:00 2001 From: BELHADJ Mohamed El Amin Date: Mon, 12 May 2025 11:43:35 +0200 Subject: [PATCH 1/6] Fix duplicate FQN errors for nested and top-level modules --- src/fqn.ts | 56 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/fqn.ts b/src/fqn.ts index 73b71290..520006c1 100644 --- a/src/fqn.ts +++ b/src/fqn.ts @@ -112,6 +112,25 @@ function buildMethodPositionMap(sourceFile: SourceFile): Map { function processModule(moduleNode: ModuleDeclaration, modulePath: string) { // console.log(`[buildMethodPositionMap] Processing module: ${modulePath}`); + // Track nested modules + const nestedModuleCounts = new Map(); // Track only nested modules + 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) { // Only set index for second and subsequent nested modules + positionMap.set(nestedModule.getStart(), count); + // console.log(`[buildMethodPositionMap] Nested module: ${nestedModuleName}, position: ${nestedModule.getStart()}, index: ${count}`); + } else { + // console.log(`[buildMethodPositionMap] Nested module: ${nestedModuleName}, position: ${nestedModule.getStart()}, no index assigned (first occurrence)`); + } + const newModulePath = `${modulePath}.${nestedModuleName}`; + processModule(nestedModule, newModulePath); + } + }); + // Handle functions directly in the module const functions = moduleNode.getFunctions(); const functionCounts = new Map(); @@ -155,17 +174,6 @@ 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) { @@ -211,18 +219,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(); // Track top-level modules 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) { // Only set index for second and subsequent top-level modules + positionMap.set(moduleNode.getStart(), count); + // console.log(`[buildMethodPositionMap] Top-level module: ${moduleName}, position: ${moduleNode.getStart()}, index: ${count}`); + } else { + // console.log(`[buildMethodPositionMap] Top-level module: ${moduleName}, position: ${moduleNode.getStart()}, no index assigned (first occurrence)`); + } processModule(moduleNode, moduleName); } }); - // console.log(`[buildMethodPositionMap] Final positionMap:`, Array.from(positionMap.entries())); return positionMap; } @@ -316,19 +331,20 @@ 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, and ModuleDeclaration + if (Node.isMethodDeclaration(currentNode) || + Node.isMethodSignature(currentNode) || + Node.isFunctionDeclaration(currentNode) || + Node.isModuleDeclaration(currentNode)) { const key = stageMap.get(currentNode.getStart()); if (key) { parts.unshift(key); - // console.log(`[getFQN] Applied stageMap key: ${key} for ${currentNode.getKindName()} at position ${currentNode.getStart()}`); + console.log(`[getFQN] Applied stageMap key: ${key} for ${currentNode.getKindName()} at position ${currentNode.getStart()}`); } else { const positionIndex = methodPositionMap.get(currentNode.getStart()); if (positionIndex && positionIndex > 1) { parts.unshift(positionIndex.toString()); - // console.log(`[getFQN] Applied positionIndex: ${positionIndex} for ${currentNode.getKindName()} at position ${currentNode.getStart()}`); + 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'}`); } @@ -378,7 +394,7 @@ export function getFQN(node: FQNNode | Node): string { parts.unshift(`{${relativePath}}`); const fqn = parts.join(".") + `[${node.getKindName()}]`; - // console.log(`[getFQN] Final FQN: ${fqn}`); + console.log(`[getFQN] Final FQN: ${fqn}`); return fqn; } From d8aaf05f7ea624a16cc2d77c0f75f0fd5c9fcb2b Mon Sep 17 00:00:00 2001 From: BELHADJ Mohamed El Amin Date: Thu, 15 May 2025 14:13:02 +0200 Subject: [PATCH 2/6] Fix duplicate FQN for ImportEqualsDeclaration\n\nPrevent duplicate FQN errors for ImportEqualsDeclaration nodes by including\ndeclaration names in getFQN --- src/fqn.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fqn.ts b/src/fqn.ts index 520006c1..c8ae0183 100644 --- a/src/fqn.ts +++ b/src/fqn.ts @@ -282,6 +282,7 @@ export function getFQN(node: FQNNode | Node): string { Node.isPropertySignature(currentNode) || Node.isArrayLiteralExpression(currentNode) || Node.isImportSpecifier(currentNode) || + Node.isImportEqualsDeclaration(currentNode) || // Added for ImportEqualsDeclaration Node.isIdentifier(currentNode)) { let name: string; if (Node.isImportSpecifier(currentNode)) { @@ -339,14 +340,14 @@ export function getFQN(node: FQNNode | Node): string { const key = stageMap.get(currentNode.getStart()); if (key) { parts.unshift(key); - console.log(`[getFQN] Applied stageMap key: ${key} for ${currentNode.getKindName()} at position ${currentNode.getStart()}`); + // console.log(`[getFQN] Applied stageMap key: ${key} for ${currentNode.getKindName()} at position ${currentNode.getStart()}`); } else { const positionIndex = methodPositionMap.get(currentNode.getStart()); if (positionIndex && positionIndex > 1) { parts.unshift(positionIndex.toString()); - console.log(`[getFQN] Applied positionIndex: ${positionIndex} for ${currentNode.getKindName()} at position ${currentNode.getStart()}`); + // 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'}`); + // console.log(`[getFQN] No positionIndex applied for ${currentNode.getKindName()} at position ${currentNode.getStart()}, positionIndex: ${positionIndex || 'none'}`); } } } @@ -369,13 +370,12 @@ 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()}`); + // console.log(`[getFQN] Ignoring node kind: ${currentNode.getKindName()}`); } currentNode = currentNode.getParent(); @@ -394,7 +394,7 @@ export function getFQN(node: FQNNode | Node): string { parts.unshift(`{${relativePath}}`); const fqn = parts.join(".") + `[${node.getKindName()}]`; - console.log(`[getFQN] Final FQN: ${fqn}`); + // console.log(`[getFQN] Final FQN: ${fqn}`); return fqn; } From 544d93044482e9c0224e4e2acfbbd037985b4713 Mon Sep 17 00:00:00 2001 From: BELHADJ Mohamed El Amin Date: Thu, 15 May 2025 14:17:51 +0200 Subject: [PATCH 3/6] Default constructor parameters without visibility modifiers to public instead of throw error --- src/analyze_functions/process_functions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 From 3344106551adc066f20f631dfc6cdeedbd7767f2 Mon Sep 17 00:00:00 2001 From: BELHADJ Mohamed El Amin Date: Thu, 15 May 2025 14:21:25 +0200 Subject: [PATCH 4/6] Skip class accessor errors in createFamixAccess by skipping Famix.Class with debug logs --- src/famix_functions/EntityDictionary.ts | 103 +++++++++++++----------- src/famix_functions/helpers_creation.ts | 2 +- 2 files changed, 57 insertions(+), 48 deletions(-) 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(); From 14594a4bb439360954c677848514a4035c26a5ee Mon Sep 17 00:00:00 2001 From: BELHADJ Mohamed El Amin Date: Mon, 19 May 2025 13:36:30 +0200 Subject: [PATCH 5/6] Resolve Duplicate FQNs by Indexing Arrow Functions and Function Expressions --- src/fqn.ts | 68 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/src/fqn.ts b/src/fqn.ts index c8ae0183..0fd07dae 100644 --- a/src/fqn.ts +++ b/src/fqn.ts @@ -104,7 +104,7 @@ function buildStageMethodMap(sourceFile: SourceFile): Map { * @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) */ -function buildMethodPositionMap(sourceFile: SourceFile): Map { +export function buildMethodPositionMap(sourceFile: SourceFile): Map { const positionMap = new Map(); // console.log(`[buildMethodPositionMap] Starting analysis for file: ${sourceFile.getFilePath()}`); @@ -178,12 +178,36 @@ function buildMethodPositionMap(sourceFile: SourceFile): Map { function trackArrowFunctions(container: Node) { const arrows = container.getDescendantsOfKind(SyntaxKind.ArrowFunction); + const functionExpressions = container.getDescendantsOfKind(SyntaxKind.FunctionExpression); + let funcIndex = 0; // Start at 0, increment before assigning + + // Process Arrow Functions 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()}`); + // Allow arrow functions in blocks, source files, or call expressions (e.g., D3.js each) + if (Node.isBlock(parent) || Node.isSourceFile(parent) || Node.isCallExpression(parent)) { + funcIndex++; // Increment to get 1, 2, 3, ... + positionMap.set(arrow.getStart(), funcIndex); // Use positive indices for arrow functions + const { line, column } = sourceFile.getLineAndColumnAtPos(arrow.getStart()); + // console.log(`[buildMethodPositionMap] Arrow function at ${arrow.getStart()} (line: ${line}, col: ${column}), parent: ${parent.getKindName()}, index: ${funcIndex}`); + } else { + const { line, column } = sourceFile.getLineAndColumnAtPos(arrow.getStart()); + // console.log(`[buildMethodPositionMap] Skipping arrow function at ${arrow.getStart()} (line: ${line}, col: ${column}), parent: ${parent.getKindName()}`); + } + }); + + // Process Function Expressions + functionExpressions.forEach(funcExpr => { + const parent = funcExpr.getParent(); + // Allow function expressions in blocks, source files, or call expressions + if (Node.isBlock(parent) || Node.isSourceFile(parent) || Node.isCallExpression(parent)) { + funcIndex++; // Increment to get next index + positionMap.set(funcExpr.getStart(), funcIndex); // Use positive indices for function expressions + const { line, column } = sourceFile.getLineAndColumnAtPos(funcExpr.getStart()); + // console.log(`[buildMethodPositionMap] Function expression at ${funcExpr.getStart()} (line: ${line}, col: ${column}), parent: ${parent.getKindName()}, index: ${funcIndex}`); + } else { + const { line, column } = sourceFile.getLineAndColumnAtPos(funcExpr.getStart()); + // console.log(`[buildMethodPositionMap] Skipping function expression at ${funcExpr.getStart()} (line: ${line}, col: ${column}), parent: ${parent.getKindName()}`); } }); } @@ -204,6 +228,17 @@ function buildMethodPositionMap(sourceFile: SourceFile): Map { 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 => { @@ -238,6 +273,9 @@ function buildMethodPositionMap(sourceFile: SourceFile): Map { } }); + // Handle top-level arrow functions and function expressions + trackArrowFunctions(sourceFile); + // console.log(`[buildMethodPositionMap] Final positionMap:`, Array.from(positionMap.entries())); return positionMap; } @@ -282,7 +320,6 @@ export function getFQN(node: FQNNode | Node): string { Node.isPropertySignature(currentNode) || Node.isArrayLiteralExpression(currentNode) || Node.isImportSpecifier(currentNode) || - Node.isImportEqualsDeclaration(currentNode) || // Added for ImportEqualsDeclaration Node.isIdentifier(currentNode)) { let name: string; if (Node.isImportSpecifier(currentNode)) { @@ -332,11 +369,11 @@ export function getFQN(node: FQNNode | Node): string { parts.unshift(name); - // Apply positional index for MethodDeclaration, MethodSignature, FunctionDeclaration, and ModuleDeclaration + // Apply positional index for MethodDeclaration, MethodSignature, FunctionDeclaration, and FunctionExpression if (Node.isMethodDeclaration(currentNode) || Node.isMethodSignature(currentNode) || - Node.isFunctionDeclaration(currentNode) || - Node.isModuleDeclaration(currentNode)) { + Node.isFunctionDeclaration(currentNode) || + Node.isFunctionExpression(currentNode)) { const key = stageMap.get(currentNode.getStart()); if (key) { parts.unshift(key); @@ -360,7 +397,15 @@ export function getFQN(node: FQNNode | Node): string { Node.isCatchClause(currentNode)) { const name = `${currentNode.getKindName()}(${lc})`; parts.unshift(name); - } + // Apply funcIndex for ArrowFunction + 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) { @@ -370,6 +415,7 @@ 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"; @@ -386,8 +432,6 @@ export function getFQN(node: FQNNode | Node): string { absolutePathProject ).replace(/\\/g, "/"); - // if (relativePath.includes("..")) { - // } if (relativePath.startsWith("/")) { relativePath = relativePath.slice(1); } From 0bf3b569bef7ee1533285380d2304a2eb6bcbd35 Mon Sep 17 00:00:00 2001 From: BELHADJ Mohamed El Amin Date: Thu, 5 Jun 2025 09:29:30 +0200 Subject: [PATCH 6/6] enhance buildMethodPositionMap to handle well properties --- src/fqn.ts | 74 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/src/fqn.ts b/src/fqn.ts index 0fd07dae..7619b0c7 100644 --- a/src/fqn.ts +++ b/src/fqn.ts @@ -100,9 +100,9 @@ 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) */ export function buildMethodPositionMap(sourceFile: SourceFile): Map { const positionMap = new Map(); @@ -113,18 +113,16 @@ export function buildMethodPositionMap(sourceFile: SourceFile): Map(); // Track only 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) { // Only set index for second and subsequent nested modules + if (count > 1) { positionMap.set(nestedModule.getStart(), count); // console.log(`[buildMethodPositionMap] Nested module: ${nestedModuleName}, position: ${nestedModule.getStart()}, index: ${count}`); - } else { - // console.log(`[buildMethodPositionMap] Nested module: ${nestedModuleName}, position: ${nestedModule.getStart()}, no index assigned (first occurrence)`); } const newModulePath = `${modulePath}.${nestedModuleName}`; processModule(nestedModule, newModulePath); @@ -157,6 +155,18 @@ export function buildMethodPositionMap(sourceFile: SourceFile): 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 @@ -179,35 +189,25 @@ export function buildMethodPositionMap(sourceFile: SourceFile): Map { const parent = arrow.getParent(); - // Allow arrow functions in blocks, source files, or call expressions (e.g., D3.js each) if (Node.isBlock(parent) || Node.isSourceFile(parent) || Node.isCallExpression(parent)) { - funcIndex++; // Increment to get 1, 2, 3, ... - positionMap.set(arrow.getStart(), funcIndex); // Use positive indices for arrow functions + 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}`); - } else { - const { line, column } = sourceFile.getLineAndColumnAtPos(arrow.getStart()); - // console.log(`[buildMethodPositionMap] Skipping arrow function at ${arrow.getStart()} (line: ${line}, col: ${column}), parent: ${parent.getKindName()}`); } }); - // Process Function Expressions functionExpressions.forEach(funcExpr => { const parent = funcExpr.getParent(); - // Allow function expressions in blocks, source files, or call expressions if (Node.isBlock(parent) || Node.isSourceFile(parent) || Node.isCallExpression(parent)) { - funcIndex++; // Increment to get next index - positionMap.set(funcExpr.getStart(), funcIndex); // Use positive indices for function expressions + 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}`); - } else { - const { line, column } = sourceFile.getLineAndColumnAtPos(funcExpr.getStart()); - // console.log(`[buildMethodPositionMap] Skipping function expression at ${funcExpr.getStart()} (line: ${line}, col: ${column}), parent: ${parent.getKindName()}`); } }); } @@ -226,6 +226,18 @@ export function buildMethodPositionMap(sourceFile: SourceFile): 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)); }); @@ -257,17 +269,15 @@ export function buildMethodPositionMap(sourceFile: SourceFile): Map(); // Track top-level 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) { // Only set index for second and subsequent top-level modules + if (count > 1) { positionMap.set(moduleNode.getStart(), count); // console.log(`[buildMethodPositionMap] Top-level module: ${moduleName}, position: ${moduleNode.getStart()}, index: ${count}`); - } else { - // console.log(`[buildMethodPositionMap] Top-level module: ${moduleName}, position: ${moduleNode.getStart()}, no index assigned (first occurrence)`); } processModule(moduleNode, moduleName); } @@ -338,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 { @@ -361,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}`; @@ -369,11 +378,12 @@ export function getFQN(node: FQNNode | Node): string { parts.unshift(name); - // Apply positional index for MethodDeclaration, MethodSignature, FunctionDeclaration, and FunctionExpression + // Apply positional index for MethodDeclaration, MethodSignature, FunctionDeclaration, FunctionExpression, and PropertyDeclaration if (Node.isMethodDeclaration(currentNode) || Node.isMethodSignature(currentNode) || Node.isFunctionDeclaration(currentNode) || - Node.isFunctionExpression(currentNode)) { + Node.isFunctionExpression(currentNode) || + Node.isPropertyDeclaration(currentNode)) { const key = stageMap.get(currentNode.getStart()); if (key) { parts.unshift(key); @@ -383,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'}`); } } } @@ -397,7 +405,6 @@ export function getFQN(node: FQNNode | Node): string { Node.isCatchClause(currentNode)) { const name = `${currentNode.getKindName()}(${lc})`; parts.unshift(name); - // Apply funcIndex for ArrowFunction if (Node.isArrowFunction(currentNode)) { const funcIndex = methodPositionMap.get(currentNode.getStart()); if (funcIndex && funcIndex > 0) { @@ -415,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();