import type * as ts from 'typescript' ; import { Path } from './Path' ; import { PackletAnalyzer } from './PackletAnalyzer' ; enum RefFileKind { Import , ReferenceFile , TypeReferenceDirective } interface RefFile { referencedFileName : string ; kind : RefFileKind ; index : number ; file : string ; } enum FileIncludeKind { RootFile , SourceFromProjectReference , OutputFromProjectReference , Import , ReferenceFile , TypeReferenceDirective , LibFile , LibReferenceDirective , AutomaticTypeDirectiveFile } type FileIncludeReason = { kind : FileIncludeKind ; file : string | undefined ; } ; interface ITsProgramInternals extends ts . Program { getRefFileMap ? : ( ) => Map < string , RefFile [ ] > | undefined ; getFileIncludeReasons ? : ( ) => Map < string , FileIncludeReason [ ] > ; } /**
 * Represents a packlet that imports another packlet.
 */ export interface IPackletImport { /**
     * The name of the packlet being imported.
     */ packletName : string ; /**
     * The absolute path of the file that imports the packlet.
     */ fromFilePath : string ; } /**
 * Used to build a linked list of imports that represent a circular dependency.
 */ interface IImportListNode extends IPackletImport { /**
     * The previous link in the linked list.
     */ previousNode : IImportListNode | undefined ; } export class DependencyAnalyzer { param packletName param startingPackletName param refFileMap param fileIncludeReasonsMap param program param packletsFolderPath param visitedPacklets param previousNode private static _walkImports ( packletName , startingPackletName , refFileMap , fileIncludeReasonsMap , program , packletsFolderPath , visitedPacklets , previousNode ) { visitedPacklets . add ( packletName ) ; const packletEntryPoint = Path . join ( packletsFolderPath , packletName , 'index' ) ; const tsSourceFile = program . getSourceFile ( packletEntryPoint + '.ts' ) || program . getSourceFile ( packletEntryPoint + '.tsx' ) ; if ( ! tsSourceFile ) { return undefined ; } const referencingFilePaths = [ ] ; if ( refFileMap ) { const refFiles = refFileMap . get ( ( tsSourceFile as any ) . path as any ) ; if ( refFiles ) { for ( const refFile of refFiles ) { if ( refFile . kind === RefFileKind . Import ) { referencingFilePaths . push ( refFile . file ) ; } } } } else if ( fileIncludeReasonsMap ) { const fileIncludeReasons = fileIncludeReasonsMap . get ( ( tsSourceFile as any ) . path as any ) ; if ( fileIncludeReasons ) { for ( const fileIncludeReason of fileIncludeReasons ) { if ( fileIncludeReason . kind === FileIncludeKind . Import ) { if ( fileIncludeReason . file ) { referencingFilePaths . push ( fileIncludeReason . file ) ; } } } } } for ( const referencingFilePath of referencingFilePaths ) { if ( Path . isUnder ( referencingFilePath , packletsFolderPath ) ) { const referencingRelativePath = Path . relative ( packletsFolderPath , referencingFilePath ) ; const referencingPathParts = referencingRelativePath . split ( /[\/\\]+/ ) ; const referencingPackletName = referencingPathParts [ 0 ] ; if ( referencingPackletName === startingPackletName ) { if ( previousNode ) { const importListNode = { previousNode : previousNode , fromFilePath : referencingFilePath , packletName : packletName } ; return importListNode ; } } if ( ! visitedPacklets . has ( referencingPackletName ) ) { const importListNode = { previousNode : previousNode , fromFilePath : referencingFilePath , packletName : packletName } ; const result = DependencyAnalyzer . _walkImports ( referencingPackletName , startingPackletName , refFileMap , fileIncludeReasonsMap , program , packletsFolderPath , visitedPacklets , importListNode ) ; if ( result ) { return result ; } } } } return undefined ; } remarks public static checkEntryPointForCircularImport ( packletName , packletAnalyzer , program ) { const programInternals = program ; let refFileMap ; let fileIncludeReasonsMap ; if ( programInternals . getRefFileMap ) { refFileMap = programInternals . getRefFileMap ( ) ; } else if ( programInternals . getFileIncludeReasons ) { fileIncludeReasonsMap = programInternals . getFileIncludeReasons ( ) ; } else { throw new Error ( 'Your TypeScript compiler version is not supported; please upgrade @rushstack/eslint-plugin-packlets' + ' or report a GitHub issue' ) ; } const visitedPacklets = new Set ( ) ; const listNode = DependencyAnalyzer . _walkImports ( packletName , packletName , refFileMap , fileIncludeReasonsMap , program , packletAnalyzer . packletsFolderPath ! , visitedPacklets , undefined ) ; if ( listNode ) { const packletImports = [ ] ; for ( let current = listNode ; current ; current = current . previousNode ) { packletImports . push ( { fromFilePath : current . fromFilePath , packletName : current . packletName } ) ; } return packletImports ; } return undefined ; } }