From dcfc53ba29ee32a4538b4c6830091c7c7abdf5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Maur=C3=A9?= Date: Wed, 10 Jun 2026 14:19:59 -0400 Subject: [PATCH 1/3] Ajout de mes modifications locales pour l'import TypeScript --- package.json | 7 +- src/analyze.ts | 42 +++++++ src/analyze_functions/process_functions.ts | 2 +- src/famix_functions/EntityDictionary.ts | 4 +- src/famix_functions/helpers_path.ts | 12 ++ src/helpers/famixIndexFileAnchorHelper.ts | 24 ++++ src/helpers/incrementalUpdateHelper.ts | 106 ++++++++++++++++++ src/helpers/index.ts | 2 + .../transientDependencyResolverHelper.ts | 104 +++++++++++++++++ src/index.ts | 15 +++ src/lib/famix/famix_repository.ts | 43 +++++++ src/lib/famix/model/famix/class.ts | 12 ++ src/lib/famix/model/famix/import_clause.ts | 4 +- src/lib/famix/model/famix/inheritance.ts | 4 +- src/lib/famix/model/famix/interface.ts | 10 ++ src/lib/famix/model/famix/module.ts | 5 + src/lib/famix/model/famix/named_entity.ts | 5 + src/lib/famix/model/famix/sourced_entity.ts | 21 ++-- tsconfig.json | 7 +- 19 files changed, 410 insertions(+), 19 deletions(-) create mode 100644 src/famix_functions/helpers_path.ts create mode 100644 src/helpers/famixIndexFileAnchorHelper.ts create mode 100644 src/helpers/incrementalUpdateHelper.ts create mode 100644 src/helpers/index.ts create mode 100644 src/helpers/transientDependencyResolverHelper.ts create mode 100644 src/index.ts diff --git a/package.json b/package.json index cc1f065e..a1842089 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "ts2famix", "version": "3.1.0", "description": "A TypeScript to JSON importer for Moose.", - "main": "dist/ts2famix-cli.js", + "main": "dist/index.js", + "types": "dist/index.d.ts", "scripts": { "dev": "ts-node src/ts2famix-cli.ts", "debug": "node --inspect-brk node_modules/.bin/ts-node", @@ -66,5 +67,9 @@ }, "engines": { "node": ">=18.20.4" + }, + "exports": { + ".": "./dist/index.js", + "./cli": "./dist/ts2famix-cli.js" } } diff --git a/src/analyze.ts b/src/analyze.ts index eee81f4a..b8bfb22b 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -5,7 +5,17 @@ import { Logger } from "tslog"; import * as processFunctions from "./analyze_functions/process_functions"; import { EntityDictionary } from "./famix_functions/EntityDictionary"; import path from "path"; +import { SourceFile } from "ts-morph"; +import { FamixBaseElement } from "./lib/famix/famix_base_element"; +import { getFamixIndexFileAnchorFileName, getDirectDependentAssociations, getSourceFilesToUpdate, removeDependentAssociations } from "./helpers"; +import { getTransientDependentEntities } from "./helpers/transientDependencyResolverHelper"; + +export enum SourceFileChangeType { + Create = 0, + Update = 1, + Delete = 2, +} export const logger = new Logger({ name: "ts2famix", minLevel: 2 }); export const config = { "expectGraphemes": false }; export const entityDictionary = new EntityDictionary(); @@ -104,6 +114,38 @@ export class Importer { return entityDictionary.famixRep; } + public updateFamixModelIncrementally(sourceFileChangeMap: Map): void { + const allChangedSourceFiles = Array.from(sourceFileChangeMap.values()).flat(); + + const removedEntities: FamixBaseElement[] = []; + allChangedSourceFiles.forEach(file => { + const filePath = getFamixIndexFileAnchorFileName(file.getFilePath(), entityDictionary.getAbsolutePath()); + const removed = entityDictionary.famixRep.removeEntitiesBySourceFile(filePath); + removedEntities.push(...removed); + }); + + const allSourceFiles = this.project.getSourceFiles(); + const directDependentAssociations = getDirectDependentAssociations(removedEntities); + const transientDependentAssociations = getTransientDependentEntities(entityDictionary, sourceFileChangeMap); + const associationsToRemove = [...directDependentAssociations, ...transientDependentAssociations]; + + removeDependentAssociations(entityDictionary.famixRep, associationsToRemove); + + const sourceFilesToEnsure = getSourceFilesToUpdate( + associationsToRemove, sourceFileChangeMap, allSourceFiles, entityDictionary.getAbsolutePath() + ); + + processFunctions.processFiles(sourceFilesToEnsure); + const sourceFilesToDelete = sourceFileChangeMap.get(SourceFileChangeType.Delete) || []; + const existingSourceFiles = allSourceFiles.filter(file => !sourceFilesToDelete.includes(file)); + this.processReferences(sourceFilesToEnsure, existingSourceFiles); + } + private processReferences(sourceFiles: SourceFile[], allExistingSourceFiles: SourceFile[]): void { + const allSourceFilesArray = Array.from(allExistingSourceFiles); + processFunctions.processImportClausesForImportEqualsDeclarations(allSourceFilesArray, processFunctions.listOfExportMaps); + const modules = sourceFiles.filter(f => processFunctions.isSourceFileAModule(f)); + processFunctions.processImportClausesForModules(modules, processFunctions.listOfExportMaps); + } } diff --git a/src/analyze_functions/process_functions.ts b/src/analyze_functions/process_functions.ts index 8c977746..405b4860 100644 --- a/src/analyze_functions/process_functions.ts +++ b/src/analyze_functions/process_functions.ts @@ -24,7 +24,7 @@ const processedNodesWithTypeParams = new Set(); // Set of nodes that hav * @param sourceFile A source file * @returns A boolean indicating if the file is a module */ -function isSourceFileAModule(sourceFile: SourceFile): boolean { +export function isSourceFileAModule(sourceFile: SourceFile): boolean { return sourceFile.getImportDeclarations().length > 0 || sourceFile.getExportedDeclarations().size > 0; } diff --git a/src/famix_functions/EntityDictionary.ts b/src/famix_functions/EntityDictionary.ts index 41710dbc..dee7d9dd 100644 --- a/src/famix_functions/EntityDictionary.ts +++ b/src/famix_functions/EntityDictionary.ts @@ -52,7 +52,9 @@ export class EntityDictionary { constructor() { this.famixRep.setFmxElementObjectMap(this.fmxElementObjectMap); } - + public getAbsolutePath(): string { + return this.famixRep.getAbsolutePath(); + } public addSourceAnchor(fmx: Famix.SourcedEntity, node: TSMorphObjectType): Famix.IndexedFileAnchor { const sourceAnchor: Famix.IndexedFileAnchor = new Famix.IndexedFileAnchor(); let sourceStart, sourceEnd: number; diff --git a/src/famix_functions/helpers_path.ts b/src/famix_functions/helpers_path.ts new file mode 100644 index 00000000..b3811b59 --- /dev/null +++ b/src/famix_functions/helpers_path.ts @@ -0,0 +1,12 @@ +import { logger } from "../analyze"; + +export function convertToRelativePath(absolutePath: string, absolutePathProject: string) { + logger.debug(`convertToRelativePath: absolutePath: '${absolutePath}', absolutePathProject: '${absolutePathProject}'`); + if (absolutePath.startsWith(absolutePathProject)) { + return absolutePath.replace(absolutePathProject, "").slice(1); + } else if (absolutePath.startsWith("/")) { + return absolutePath.slice(1); + } else { + return absolutePath; + } +} \ No newline at end of file diff --git a/src/helpers/famixIndexFileAnchorHelper.ts b/src/helpers/famixIndexFileAnchorHelper.ts new file mode 100644 index 00000000..2325704e --- /dev/null +++ b/src/helpers/famixIndexFileAnchorHelper.ts @@ -0,0 +1,24 @@ +import { convertToRelativePath } from "../famix_functions/helpers_path"; +import path from "path"; + +export const getFamixIndexFileAnchorFileName = (absolutePath: string, absolutePathProject: string) => { + absolutePath = path.normalize(absolutePath); + const positionNodeModules = absolutePath.indexOf('node_modules'); + + let pathInProject: string = ""; + + if (positionNodeModules !== -1) { + const pathFromNodeModules = absolutePath.substring(positionNodeModules); + pathInProject = pathFromNodeModules; + } else { + pathInProject = convertToRelativePath(absolutePath, absolutePathProject); + } + + // revert any backslashes to forward slashes (path.normalize on windows introduces them) + pathInProject = pathInProject.replace(/\\/g, "/"); + + if (pathInProject.startsWith("/")) { + pathInProject = pathInProject.substring(1); + } + return pathInProject; +}; diff --git a/src/helpers/incrementalUpdateHelper.ts b/src/helpers/incrementalUpdateHelper.ts new file mode 100644 index 00000000..a5f138d9 --- /dev/null +++ b/src/helpers/incrementalUpdateHelper.ts @@ -0,0 +1,106 @@ +import { Class } from '../lib/famix/model/famix/class'; +import { FamixBaseElement } from "../lib/famix/famix_base_element"; +import { ImportClause, IndexedFileAnchor, Inheritance, Interface, NamedEntity } from '../lib/famix/model/famix'; +import { EntityWithSourceAnchor } from '../lib/famix/model/famix/sourced_entity'; +import { SourceFileChangeType } from '../analyze'; +import { SourceFile } from 'ts-morph'; +import { getFamixIndexFileAnchorFileName } from './famixIndexFileAnchorHelper'; +import { FamixRepository } from '../lib/famix/famix_repository'; + +// TODO: add tests for these methods +export const getSourceFilesToUpdate = ( + dependentAssociations: EntityWithSourceAnchor[], + sourceFileChangeMap: Map, + allSourceFiles: SourceFile[], + projectBaseUrl: string +) => { + const sourceFilesToEnsureEntities = [ + ...(sourceFileChangeMap.get(SourceFileChangeType.Create) || []), + ...(sourceFileChangeMap.get(SourceFileChangeType.Update) || []), + ]; + + const dependentFileNames = getDependentSourceFileNames(dependentAssociations); + const dependentFileNamesToAdd = Array.from(dependentFileNames) + .map(fileName => getFamixIndexFileAnchorFileName(fileName, projectBaseUrl)) + .filter( + fileName => !Array.from(sourceFileChangeMap.values()) + .flat().some(sourceFile => sourceFile.getFilePath() === fileName)); + + const dependentFiles = allSourceFiles.filter( + sourceFile => { + const filePath = getFamixIndexFileAnchorFileName(sourceFile.getFilePath(), projectBaseUrl); + return dependentFileNamesToAdd.includes(filePath); + } + ); + + return sourceFilesToEnsureEntities.concat(dependentFiles); +}; + +const getDependentSourceFileNames = (dependentAssociations: EntityWithSourceAnchor[]) => { + const dependentFileNames = new Set(); + + dependentAssociations.forEach(entity => { + // todo: ? sourceAnchor instead of indexedfileAnchor + dependentFileNames.add((entity.sourceAnchor as IndexedFileAnchor).fileName); + }); + + return dependentFileNames; +}; + +/** + * Finds all the associations that include the given entities as dependencies + */ +export const getDirectDependentAssociations = (entities: FamixBaseElement[]) => { + const dependentAssociations: EntityWithSourceAnchor[] = []; + + entities.forEach(entity => { + dependentAssociations.push(...getDependentAssociationsForEntity(entity)); + }); + + return dependentAssociations; +}; + +const getDependentAssociationsForEntity = (entity: FamixBaseElement) => { + const dependentAssociations: EntityWithSourceAnchor[] = []; + + const addElementFileToSet = (association: EntityWithSourceAnchor) => { + dependentAssociations.push(association); + }; + + if (entity instanceof Class) { + Array.from(entity.subInheritances).forEach(inheritance => { + addElementFileToSet(inheritance); + }); + } else if (entity instanceof Interface) { + Array.from(entity.subInheritances).forEach(inheritance => { + addElementFileToSet(inheritance); + }); + } + + if (entity instanceof NamedEntity) { + Array.from(entity.incomingImports).forEach(importClause => { + addElementFileToSet(importClause); + }); + } + // TODO: add other associations + + return dependentAssociations; +}; + +export const removeDependentAssociations = ( + famixRep: FamixRepository, + dependentAssociations: EntityWithSourceAnchor[]) => { + // NOTE: removing the depending associations because they will be recreated later + famixRep.removeElements(dependentAssociations); + famixRep.removeElements(dependentAssociations.map(x => x.sourceAnchor)); + + dependentAssociations.forEach(association => { + if (association instanceof Inheritance) { + association.superclass.removeSubInheritance(association); + association.subclass.removeSuperInheritance(association); + } else if (association instanceof ImportClause) { + association.importedEntity.incomingImports.delete(association); + association.importingEntity.outgoingImports.delete(association); + } + }); +}; \ No newline at end of file diff --git a/src/helpers/index.ts b/src/helpers/index.ts new file mode 100644 index 00000000..37be9db6 --- /dev/null +++ b/src/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './incrementalUpdateHelper'; +export * from './famixIndexFileAnchorHelper'; \ No newline at end of file diff --git a/src/helpers/transientDependencyResolverHelper.ts b/src/helpers/transientDependencyResolverHelper.ts new file mode 100644 index 00000000..f435108f --- /dev/null +++ b/src/helpers/transientDependencyResolverHelper.ts @@ -0,0 +1,104 @@ +import { EntityWithSourceAnchor } from "../lib/famix/model/famix/sourced_entity"; +import { EntityDictionary } from "../famix_functions/EntityDictionary"; +import { Class, ImportClause, IndexedFileAnchor, Interface } from "../lib/famix/model/famix"; +import { getFamixIndexFileAnchorFileName } from "./famixIndexFileAnchorHelper"; +import { SourceFileChangeType } from "../analyze"; +import { SourceFile } from "ts-morph"; + +// TODO: add tests for these methods + +/** + * NOTE: for now the case when we create a new file and there were imports from it + * even if it didn't exist may not be working. + * + * Ex.,: + * fileA: *does not exists yet* + * fileB: import { Something } from './fileA'; + * ------------------------ + * fileA: export class Something { } + * + * (the fileB may not be updated here) +*/ + +/** + * Based on import clauses finds the dependent files and returns the associations + * that are transitively dependent on the changed files. It does it recursively. + */ +export const getTransientDependentEntities = ( + entityDictionary: EntityDictionary, + sourceFileChangeMap: Map, +) => { + const absoluteProjectPath = entityDictionary.getAbsolutePath(); + + const changedFilesNames = Array.from(sourceFileChangeMap.values()) + .flat() + .map(sourceFile => getFamixIndexFileAnchorFileName(sourceFile.getFilePath(), absoluteProjectPath)); + + const transientDependentAssociations = getTransientDependentAssociations(entityDictionary, changedFilesNames); + + return transientDependentAssociations; +}; + +const getTransientDependentAssociations = ( + entityDictionary: EntityDictionary, + changedFilesNames: string [] +) => { + const importClauses = entityDictionary.famixRep.getImportClauses(); + + const transientDependentAssociations: Set = new Set(); + + const unprocessedFiles: Set = new Set(changedFilesNames); + const processedFiles: Set = new Set(); + + while (unprocessedFiles.size > 0) { + const file: string = unprocessedFiles.values().next().value!; + unprocessedFiles.delete(file); + processedFiles.add(file); + + importClauses.forEach(importClause => { + if (importClause.moduleSpecifier === file) { + transientDependentAssociations.add(importClause); + if (importClause.importedEntity.isStub) { + transientDependentAssociations.add(importClause.importedEntity); + } + + const importingEntityFileName = (importClause.sourceAnchor as IndexedFileAnchor).fileName; + + if (!unprocessedFiles.has(importingEntityFileName) && !processedFiles.has(importingEntityFileName)) { + unprocessedFiles.add(importingEntityFileName); + } + + getOtherTransientDependencies(entityDictionary, importClause, transientDependentAssociations); + } + }); + } + + return transientDependentAssociations; +}; + +const getOtherTransientDependencies = ( + entityDictionary: EntityDictionary, + importClause: ImportClause, + transientDependentAssociations: Set +) => { + const importedEntity = importClause.importedEntity; + const importingEntityFileName = (importClause.sourceAnchor as IndexedFileAnchor).fileName; + + const inheritances = entityDictionary.famixRep.getInheritances(); + + if (importedEntity instanceof Class || importedEntity instanceof Interface || importedEntity.isStub) { + inheritances.forEach(inheritance => { + const doesInheritanceContainImportedEntity = inheritance.superclass === importClause.importedEntity && + importingEntityFileName === (inheritance.sourceAnchor as IndexedFileAnchor).fileName; + + if (doesInheritanceContainImportedEntity) { + transientDependentAssociations.add(inheritance); + } else if (inheritance.superclass.isStub) { + transientDependentAssociations.add(inheritance); + transientDependentAssociations.add(inheritance.superclass); + } + }); + } + + // TODO: find the other associations (access, invocation) between the imported entity and the sourceFile +}; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..4a0afa82 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,15 @@ +export { Importer, logger, config, SourceFileChangeType} from "./analyze"; +export { FamixRepository } from "./lib/famix/famix_repository"; +export { FamixBaseElement } from "./lib/famix/famix_base_element"; +export * from './helpers'; + +import { Project } from 'ts-morph'; + +export const getTsMorphProject = (tsConfigFilePath: string, baseUrl: string) => { + return new Project({ + tsConfigFilePath, + compilerOptions: { + baseUrl: baseUrl, + } + }); +}; \ No newline at end of file diff --git a/src/lib/famix/famix_repository.ts b/src/lib/famix/famix_repository.ts index 0703f601..51231f66 100644 --- a/src/lib/famix/famix_repository.ts +++ b/src/lib/famix/famix_repository.ts @@ -3,6 +3,7 @@ import { Class, Interface, Variable, Method, ArrowFunction, Function as FamixFun import * as Famix from "./model/famix"; import { TSMorphObjectType } from "../../famix_functions/EntityDictionary"; import { logger } from "../../analyze"; +import { EntityWithSourceAnchor } from "./model/famix/sourced_entity"; /** * This class is used to store all Famix elements @@ -285,4 +286,46 @@ export class FamixRepository { ret = ret.substring(0, ret.length - 1); return ret + "]"; } + public removeElements(entities: FamixBaseElement[]): void { + for (const entity of entities) { + this.elements.delete(entity); + } + } + + public getImportClauses(): Famix.ImportClause[] { + return Array.from(this.elements.values()).filter(e => e instanceof Famix.ImportClause) as Famix.ImportClause[]; + } + + public getInheritances(): Famix.Inheritance[] { + return Array.from(this.elements.values()).filter(e => e instanceof Famix.Inheritance) as Famix.Inheritance[]; + } + private getElementsBySourceFile(sourceFile: string): FamixBaseElement[] { + return Array.from(this.elements.values()).filter(e => { + if (e instanceof EntityWithSourceAnchor && e.sourceAnchor && e.sourceAnchor instanceof Famix.IndexedFileAnchor) { + return e.sourceAnchor.fileName === sourceFile; + } else if (e instanceof Famix.IndexedFileAnchor) { + return e.fileName === sourceFile; + } + }); } + + public removeEntitiesBySourceFile(sourceFile: string): FamixBaseElement[] { + const entitiesToRemove = this.getElementsBySourceFile(sourceFile); + this.removeElements(entitiesToRemove); + this.removeRelatedAssociations(entitiesToRemove); + return entitiesToRemove; + } + + public removeRelatedAssociations(entities: FamixBaseElement[]): void { + for (const entity of entities) { + if (entity instanceof Famix.Inheritance) { + entity.subclass.removeSuperInheritance(entity); + entity.superclass.removeSubInheritance(entity); + } else if (entity instanceof Famix.ImportClause) { + entity.importingEntity.removeOutgoingImport(entity); + entity.importedEntity.removeIncomingImport(entity); + } + } + } +} + diff --git a/src/lib/famix/model/famix/class.ts b/src/lib/famix/model/famix/class.ts index 799b4425..6adb391a 100644 --- a/src/lib/famix/model/famix/class.ts +++ b/src/lib/famix/model/famix/class.ts @@ -33,6 +33,12 @@ export class Class extends Type { superInheritance.subclass = this; } } + public removeSuperInheritance(superInheritance: Inheritance): void { + if (this._superInheritances.has(superInheritance)) { + this._superInheritances.delete(superInheritance); + } + } + private _subInheritances: Set = new Set(); @@ -42,6 +48,12 @@ export class Class extends Type { subInheritance.superclass = this; } } + public removeSubInheritance(subInheritance: Inheritance): void { + if (this._subInheritances.has(subInheritance)) { + this._subInheritances.delete(subInheritance); + } + } + public getJSON(): string { diff --git a/src/lib/famix/model/famix/import_clause.ts b/src/lib/famix/model/famix/import_clause.ts index 054bf0ce..6b98847c 100644 --- a/src/lib/famix/model/famix/import_clause.ts +++ b/src/lib/famix/model/famix/import_clause.ts @@ -1,9 +1,9 @@ import { FamixJSONExporter } from "../../famix_JSON_exporter"; -import { Entity } from "./entity"; +import { EntityWithSourceAnchor } from "./sourced_entity"; import { Module } from "./module"; import { NamedEntity } from "./named_entity"; -export class ImportClause extends Entity { +export class ImportClause extends EntityWithSourceAnchor { private _importingEntity!: Module; private _importedEntity!: NamedEntity; diff --git a/src/lib/famix/model/famix/inheritance.ts b/src/lib/famix/model/famix/inheritance.ts index 54e90ebe..71e432e1 100644 --- a/src/lib/famix/model/famix/inheritance.ts +++ b/src/lib/famix/model/famix/inheritance.ts @@ -1,9 +1,9 @@ import { FamixJSONExporter } from "../../famix_JSON_exporter"; import { Class } from "./class"; -import { Entity } from "./entity"; +import { EntityWithSourceAnchor } from "./sourced_entity"; import { Interface } from "./interface"; -export class Inheritance extends Entity { +export class Inheritance extends EntityWithSourceAnchor { private _superclass!: Class | Interface; private _subclass!: Class | Interface; diff --git a/src/lib/famix/model/famix/interface.ts b/src/lib/famix/model/famix/interface.ts index 1ee33ae6..c602a803 100644 --- a/src/lib/famix/model/famix/interface.ts +++ b/src/lib/famix/model/famix/interface.ts @@ -32,6 +32,11 @@ export class Interface extends Type { superInheritance.subclass = this; } } + public removeSuperInheritance(superInheritance: Inheritance): void { + if (this._superInheritances.has(superInheritance)) { + this._superInheritances.delete(superInheritance); + } + } private _subInheritances: Set = new Set(); @@ -41,6 +46,11 @@ export class Interface extends Type { subInheritance.superclass = this; } } + public removeSubInheritance(subInheritance: Inheritance): void { + if (this._subInheritances.has(subInheritance)) { + this._subInheritances.delete(subInheritance); + } + } public getJSON(): string { diff --git a/src/lib/famix/model/famix/module.ts b/src/lib/famix/model/famix/module.ts index c04c5d04..2b404810 100644 --- a/src/lib/famix/model/famix/module.ts +++ b/src/lib/famix/model/famix/module.ts @@ -44,6 +44,11 @@ export class Module extends ScriptEntity { importClause.importingEntity = this; // opposite } } + removeOutgoingImport(importClause: ImportClause) { + if (this._outgoingImports.has(importClause)) { + this._outgoingImports.delete(importClause); + } + } public getJSON(): string { const json: FamixJSONExporter = new FamixJSONExporter("Module", this); diff --git a/src/lib/famix/model/famix/named_entity.ts b/src/lib/famix/model/famix/named_entity.ts index 609bf7f3..51ef13ac 100644 --- a/src/lib/famix/model/famix/named_entity.ts +++ b/src/lib/famix/model/famix/named_entity.ts @@ -25,6 +25,11 @@ export class NamedEntity extends SourcedEntity { anImport.importedEntity = this; // opposite } } + public removeIncomingImport(anImport: ImportClause): void { + if (this._incomingImports.has(anImport)) { + this._incomingImports.delete(anImport); + } + } private _name!: string; private _aliases: Set = new Set(); diff --git a/src/lib/famix/model/famix/sourced_entity.ts b/src/lib/famix/model/famix/sourced_entity.ts index e4a514e9..064d992b 100644 --- a/src/lib/famix/model/famix/sourced_entity.ts +++ b/src/lib/famix/model/famix/sourced_entity.ts @@ -5,10 +5,19 @@ import { Comment } from "./comment"; import { SourceAnchor } from "./source_anchor"; import { logger } from "../../../../analyze"; -export class SourcedEntity extends Entity { +export abstract class EntityWithSourceAnchor extends Entity { + protected _sourceAnchor!: SourceAnchor; + get sourceAnchor() { + return this._sourceAnchor; + } + set sourceAnchor(sourceAnchor: SourceAnchor) { + this._sourceAnchor = sourceAnchor; + } +} + +export class SourcedEntity extends EntityWithSourceAnchor { private _isStub!: boolean; - private _sourceAnchor!: SourceAnchor; private _comments: Set = new Set(); public addComment(comment: Comment): void { @@ -44,11 +53,7 @@ export class SourcedEntity extends Entity { this._isStub = isStub; } - get sourceAnchor() { - return this._sourceAnchor; - } - - set sourceAnchor(sourceAnchor: SourceAnchor) { + override set sourceAnchor(sourceAnchor: SourceAnchor) { if (this._sourceAnchor === undefined) { this._sourceAnchor = sourceAnchor; sourceAnchor.element = this; @@ -67,4 +72,4 @@ export class SourcedEntity extends Entity { this._declaredSourceLanguage = declaredSourceLanguage; declaredSourceLanguage.addSourcedEntity(this); } -} +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 4632f218..a5a74030 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,10 +7,9 @@ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - // "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + //"sourceMap": true, /* Generates corresponding '.map' file. */ "outDir": "./dist", /* Redirect output structure to the directory. */ "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ From c62d7064ecd5d5f3217fbd905b200e6fd0fd1b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Maur=C3=A9?= Date: Thu, 11 Jun 2026 16:02:21 -0400 Subject: [PATCH 2/3] feat: expose API for VSCode extension - add SourceFileChangeType, getTsMorphProject, helpers --- src/index.ts | 6 ++++-- tsconfig.json | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4a0afa82..8ec60894 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,17 @@ export { Importer, logger, config, SourceFileChangeType} from "./analyze"; +//export { Importer, config, SourceFileChangeType} from "./analyze"; export { FamixRepository } from "./lib/famix/famix_repository"; export { FamixBaseElement } from "./lib/famix/famix_base_element"; export * from './helpers'; import { Project } from 'ts-morph'; - +import { logger } from "./analyze"; +logger.info('Initializing ts-morph project'); export const getTsMorphProject = (tsConfigFilePath: string, baseUrl: string) => { return new Project({ tsConfigFilePath, compilerOptions: { baseUrl: baseUrl, } - }); + });// le main doit etre le point de départ }; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index a5a74030..9fe8027e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ "declaration": true, /* Generates corresponding '.d.ts' file. */ "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - //"sourceMap": true, /* Generates corresponding '.map' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ "outDir": "./dist", /* Redirect output structure to the directory. */ "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ @@ -47,7 +47,7 @@ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + //"inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ From 594c0b6675c0a9e7367ae33b653dea2adbefe649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Maur=C3=A9?= Date: Tue, 16 Jun 2026 11:02:29 -0400 Subject: [PATCH 3/3] fix: reset entityDictionary and processFunctions state between calls to famixRepFromProject --- src/analyze.ts | 4 ++-- src/analyze_functions/process_functions.ts | 9 +++++++++ src/famix_functions/EntityDictionary.ts | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/analyze.ts b/src/analyze.ts index b8bfb22b..317fd41b 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -105,9 +105,9 @@ export class Importer { */ public famixRepFromProject(project: Project): FamixRepository { //const sourceFileNames = project.getSourceFiles().map(f => f.getFilePath()) as Array; - + entityDictionary.reset(); //const famixRep = this.famixRepFromPaths(sourceFileNames); - + processFunctions.resetProcessFunctions(); initFamixRep(project); this.processEntities(project); diff --git a/src/analyze_functions/process_functions.ts b/src/analyze_functions/process_functions.ts index 405b4860..2b33dfe0 100644 --- a/src/analyze_functions/process_functions.ts +++ b/src/analyze_functions/process_functions.ts @@ -19,6 +19,15 @@ export const listOfExportMaps = new Array(); // Set of nodes that have been processed and have type parameters +export function resetProcessFunctions(): void { + methodsAndFunctionsWithId.clear(); + accessMap.clear(); + classes.length = 0; + interfaces.length = 0; + modules.length = 0; + listOfExportMaps.length = 0; +} + /** * Checks if the file has any imports or exports to be considered a module * @param sourceFile A source file diff --git a/src/famix_functions/EntityDictionary.ts b/src/famix_functions/EntityDictionary.ts index dee7d9dd..64409e1a 100644 --- a/src/famix_functions/EntityDictionary.ts +++ b/src/famix_functions/EntityDictionary.ts @@ -52,6 +52,26 @@ export class EntityDictionary { constructor() { this.famixRep.setFmxElementObjectMap(this.fmxElementObjectMap); } + public reset(): void { + this.famixRep = new FamixRepository(); + this.fmxElementObjectMap = new Map(); + this.tsMorphElementObjectMap = new Map(); + this.fmxAliasMap = new Map(); + this.fmxClassMap = new Map(); + this.fmxInterfaceMap = new Map(); + this.fmxModuleMap = new Map(); + this.fmxFileMap = new Map(); + this.fmxTypeMap = new Map(); + this.fmxPrimitiveTypeMap = new Map(); + this.fmxFunctionAndMethodMap = new Map(); + this.fmxArrowFunctionMap = new Map(); + this.fmxParameterMap = new Map(); + this.fmxVariableMap = new Map(); + this.fmxImportClauseMap = new Map(); + this.fmxEnumMap = new Map(); + this.fmxInheritanceMap = new Map(); + this.famixRep.setFmxElementObjectMap(this.fmxElementObjectMap); +} public getAbsolutePath(): string { return this.famixRep.getAbsolutePath(); }