namespace ts . server { export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024 ; export const maxFileSize = 4 * 1024 * 1024 ; export const ProjectsUpdatedInBackgroundEvent = "projectsUpdatedInBackground" ; export const ProjectLoadingStartEvent = "projectLoadingStart" ; export const ProjectLoadingFinishEvent = "projectLoadingFinish" ; export const LargeFileReferencedEvent = "largeFileReferenced" ; export const ConfigFileDiagEvent = "configFileDiag" ; export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState" ; export const ProjectInfoTelemetryEvent = "projectInfo" ; export const OpenFileInfoTelemetryEvent = "openFileInfo" ; const ensureProjectForOpenFileSchedule = "*ensureProjectForOpenFiles*" ; export interface ProjectsUpdatedInBackgroundEvent { eventName : typeof ProjectsUpdatedInBackgroundEvent ; data : { openFiles : string [ ] ; } ; } export interface ProjectLoadingStartEvent { eventName : typeof ProjectLoadingStartEvent ; data : { project : Project ; reason : string ; } ; } export interface ProjectLoadingFinishEvent { eventName : typeof ProjectLoadingFinishEvent ; data : { project : Project ; } ; } export interface LargeFileReferencedEvent { eventName : typeof LargeFileReferencedEvent ; data : { file : string ; fileSize : number ; maxFileSize : number ; } ; } export interface ConfigFileDiagEvent { eventName : typeof ConfigFileDiagEvent ; data : { triggerFile : string ; configFileName : string ; diagnostics : readonly Diagnostic [ ] ; } ; } export interface ProjectLanguageServiceStateEvent { eventName : typeof ProjectLanguageServiceStateEvent ; data : { project : Project ; languageServiceEnabled : boolean ; } ; } /** This will be converted to the payload of a protocol.TelemetryEvent in session.defaultEventHandler. */ export interface ProjectInfoTelemetryEvent { readonly eventName : typeof ProjectInfoTelemetryEvent ; readonly data : ProjectInfoTelemetryEventData ; } export interface ProjectInfoTelemetryEventData { /** Cryptographically secure hash of project file location. */ readonly projectId : string ; /** Count of file extensions seen in the project. */ readonly fileStats : FileStats ; /**
         * Any compiler options that might contain paths will be taken out.
         * Enum compiler options will be converted to strings.
         */ readonly compilerOptions : CompilerOptions ; readonly extends : boolean | undefined ; readonly files : boolean | undefined ; readonly include : boolean | undefined ; readonly exclude : boolean | undefined ; readonly compileOnSave : boolean ; readonly typeAcquisition : ProjectInfoTypeAcquisitionData ; readonly configFileName : "tsconfig.json" | "jsconfig.json" | "other" ; readonly projectType : "external" | "configured" ; readonly languageServiceEnabled : boolean ; /** TypeScript version used by the server. */ readonly version : string ; } /**
     * Info that we may send about a file that was just opened.
     * Info about a file will only be sent once per session, even if the file changes in ways that might affect the info.
     * Currently this is only sent for '.js' files.
     */ export interface OpenFileInfoTelemetryEvent { readonly eventName : typeof OpenFileInfoTelemetryEvent ; readonly data : OpenFileInfoTelemetryEventData ; } export interface OpenFileInfoTelemetryEventData { readonly info : OpenFileInfo ; } export interface ProjectInfoTypeAcquisitionData { readonly enable : boolean | undefined ; readonly include : boolean ; readonly exclude : boolean ; } export interface FileStats { readonly js : number ; readonly jsSize ? : number ; readonly jsx : number ; readonly jsxSize ? : number ; readonly ts : number ; readonly tsSize ? : number ; readonly tsx : number ; readonly tsxSize ? : number ; readonly dts : number ; readonly dtsSize ? : number ; readonly deferred : number ; readonly deferredSize ? : number ; } export interface OpenFileInfo { readonly checkJs : boolean ; } export type ProjectServiceEvent = LargeFileReferencedEvent | ProjectsUpdatedInBackgroundEvent | ProjectLoadingStartEvent | ProjectLoadingFinishEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent ; export type ProjectServiceEventHandler = ( event : ProjectServiceEvent ) => void ; export type PerformanceEventHandler = ( event : PerformanceEvent ) => void ; export interface SafeList { [ name ] : { match : RegExp ; exclude ? : ( string | number ) [ ] [ ] ; types ? : string [ ] ; } ; } function prepareConvertersForEnumLikeCompilerOptions ( commandLineOptions ) { const map = new Map < string , ESMap < string , number > > ( ) ; for ( const option of commandLineOptions ) { if ( typeof option . type === "object" ) { const optionMap = option . type as ESMap < string , number > ; optionMap . forEach ( value => { Debug . assert ( typeof value === "number" ) ; } ) ; map . set ( option . name , optionMap ) ; } } return map ; } const compilerOptionConverters = prepareConvertersForEnumLikeCompilerOptions ( optionDeclarations ) ; const watchOptionsConverters = prepareConvertersForEnumLikeCompilerOptions ( optionsForWatch ) ; const indentStyle = new Map ( getEntries ( { none : IndentStyle . None , block : IndentStyle . Block , smart : IndentStyle . Smart } ) ) ; export interface TypesMapFile { typesMap : SafeList ; simpleMap : { [ libName ] : string ; } ; } /**
     * How to understand this block:
     *  * The 'match' property is a regexp that matches a filename.
     *  * If 'match' is successful, then:
     *     * All files from 'exclude' are removed from the project. See below.
     *     * All 'types' are included in ATA
     *  * What the heck is 'exclude' ?
     *     * An array of an array of strings and numbers
     *     * Each array is:
     *       * An array of strings and numbers
     *       * The strings are literals
     *       * The numbers refer to capture group indices from the 'match' regexp
     *          * Remember that '1' is the first group
     *       * These are concatenated together to form a new regexp
     *       * Filenames matching these regexps are excluded from the project
     * This default value is tested in tsserverProjectSystem.ts; add tests there
     *   if you are changing this so that you can be sure your regexp works!
     */ const defaultTypeSafeList = { "jquery" : { match : /jquery(-[\d\.]+)?(\.intellisense)?(\.min)?\.js$/i , types : [ "jquery" ] } , "WinJS" : { match : /^(.*\/winjs-[.\d]+)\/js\/base\.js$/i , exclude : [ [ "^" , 1 , "/.*" ] ] , types : [ "winjs" ] } , "Kendo" : { match : /^(.*\/kendo(-ui)?)\/kendo\.all(\.min)?\.js$/i , exclude : [ [ "^" , 1 , "/.*" ] ] , types : [ "kendo-ui" ] } , "Office Nuget" : { match : /^(.*\/office\/1)\/excel-\d+\.debug\.js$/i , exclude : [ [ "^" , 1 , "/.*" ] ] , types : [ "office" ] } , "References" : { match : /^(.*\/_references\.js)$/i , exclude : [ [ "^" , 1 , "$" ] ] } } ; export function convertFormatOptions ( protocolOptions ) { if ( isString ( protocolOptions . indentStyle ) ) { protocolOptions . indentStyle = indentStyle . get ( protocolOptions . indentStyle . toLowerCase ( ) ) ; Debug . assert ( protocolOptions . indentStyle !== undefined ) ; } return protocolOptions as any ; } export function convertCompilerOptions ( protocolOptions ) { compilerOptionConverters . forEach ( ( mappedValues , id ) => { const propertyValue = protocolOptions [ id ] ; if ( isString ( propertyValue ) ) { protocolOptions [ id ] = mappedValues . get ( propertyValue . toLowerCase ( ) ) ; } } ) ; return protocolOptions as any ; } export function convertWatchOptions ( protocolOptions , currentDirectory ? ) { let watchOptions ; let errors ; optionsForWatch . forEach ( option => { const propertyValue = protocolOptions [ option . name ] ; if ( propertyValue === undefined ) return ; const mappedValues = watchOptionsConverters . get ( option . name ) ; ( watchOptions || ( watchOptions = { } ) ) [ option . name ] = mappedValues ? isString ( propertyValue ) ? mappedValues . get ( propertyValue . toLowerCase ( ) ) : propertyValue : convertJsonOption ( option , propertyValue , currentDirectory || "" , errors || ( errors = [ ] ) ) ; } ) ; return watchOptions && { watchOptions , errors } ; } export function convertTypeAcquisition ( protocolOptions ) { let result ; typeAcquisitionDeclarations . forEach ( ( option ) => { const propertyValue = protocolOptions [ option . name ] ; if ( propertyValue === undefined ) return ; ( result || ( result = { } ) ) [ option . name ] = propertyValue ; } ) ; return result ; } export function tryConvertScriptKindName ( scriptKindName ) { return isString ( scriptKindName ) ? convertScriptKindName ( scriptKindName ) : scriptKindName ; } export function convertScriptKindName ( scriptKindName ) { switch ( scriptKindName ) { case "JS" : return ScriptKind . JS ; case "JSX" : return ScriptKind . JSX ; case "TS" : return ScriptKind . TS ; case "TSX" : return ScriptKind . TSX ; default : return ScriptKind . Unknown ; } } export function convertUserPreferences ( preferences ) { const { lazyConfiguredProjectsFromExternalProject , ... userPreferences } = preferences ; return userPreferences ; } export interface HostConfiguration { formatCodeOptions : FormatCodeSettings ; preferences : protocol . UserPreferences ; hostInfo : string ; extraFileExtensions ? : FileExtensionInfo [ ] ; watchOptions ? : WatchOptions ; } export interface OpenConfiguredProjectResult { configFileName ? : NormalizedPath ; configFileErrors ? : readonly Diagnostic [ ] ; } interface AssignProjectResult extends OpenConfiguredProjectResult { retainProjects : readonly ConfiguredProject [ ] | ConfiguredProject | undefined ; } interface FilePropertyReader < T > { getFileName ( f ) : string ; getScriptKind ( f , extraFileExtensions ? ) : ScriptKind ; hasMixedContent ( f , extraFileExtensions ) : boolean ; } const fileNamePropertyReader = { getFileName : x => x , getScriptKind : ( fileName , extraFileExtensions ) => { let result ; if ( extraFileExtensions ) { const fileExtension = getAnyExtensionFromPath ( fileName ) ; if ( fileExtension ) { some ( extraFileExtensions , info => { if ( info . extension === fileExtension ) { result = info . scriptKind ; return true ; } return false ; } ) ; } } return result ! ; } , hasMixedContent : ( fileName , extraFileExtensions ) => some ( extraFileExtensions , ext => ext . isMixedContent && fileExtensionIs ( fileName , ext . extension ) ) , } ; const externalFilePropertyReader = { getFileName : x => x . fileName , getScriptKind : x => tryConvertScriptKindName ( x . scriptKind ! ) , hasMixedContent : x => ! ! x . hasMixedContent , } ; function findProjectByName < T extends Project > ( projectName , projects ) { for ( const proj of projects ) { if ( proj . getProjectName ( ) === projectName ) { return proj ; } } } const noopConfigFileWatcher = { close : noop } ; interface ConfigFileExistenceInfo { /**
         * Cached value of existence of config file
         * It is true if there is configured project open for this file.
         * It can be either true or false if this is the config file that is being watched by inferred project
         *   to decide when to update the structure so that it knows about updating the project for its files
         *   (config file may include the inferred project files after the change and hence may be wont need to be in inferred project)
         */ exists : boolean ; /**
         * openFilesImpactedByConfigFiles is a map of open files that would be impacted by this config file
         *   because these are the paths being looked up for their default configured project location
         * The value in the map is true if the open file is root of the inferred project
         * It is false when the open file that would still be impacted by existence of
         *   this config file but it is not the root of inferred project
         */ openFilesImpactedByConfigFile ? : ESMap < Path , boolean > ; /**
         * The file watcher watching the config file because there is open script info that is root of
         * inferred project and will be impacted by change in the status of the config file
         * or
         * Configured project for this config file is open
         * or
         * Configured project references this config file
         */ watcher ? : FileWatcher ; /**
         * Cached parsed command line and other related information like watched directories etc
         */ config ? : ParsedConfig ; } export interface ProjectServiceOptions { host : ServerHost ; logger : Logger ; cancellationToken : HostCancellationToken ; useSingleInferredProject : boolean ; useInferredProjectPerProjectRoot : boolean ; typingsInstaller : ITypingsInstaller ; eventHandler ? : ProjectServiceEventHandler ; suppressDiagnosticEvents ? : boolean ; throttleWaitMilliseconds ? : number ; globalPlugins ? : readonly string [ ] ; pluginProbeLocations ? : readonly string [ ] ; allowLocalPluginLoads ? : boolean ; typesMapLocation ? : string ; deprecated syntaxOnly ? : boolean ; serverMode ? : LanguageServiceMode ; session : Session < unknown > | undefined ; } interface OriginalFileInfo { fileName : NormalizedPath ; path : Path ; } interface AncestorConfigFileInfo { /** config file name */ fileName : string ; /** path of open file so we can look at correct root */ path : Path ; configFileInfo : true ; } type OpenScriptInfoOrClosedFileInfo = ScriptInfo | OriginalFileInfo ; type OpenScriptInfoOrClosedOrConfigFileInfo = OpenScriptInfoOrClosedFileInfo | AncestorConfigFileInfo ; function isOpenScriptInfo ( infoOrFileNameOrConfig ) { return ! ! ( infoOrFileNameOrConfig as ScriptInfo ) . containingProjects ; } function isAncestorConfigFileInfo ( infoOrFileNameOrConfig ) { return ! ! ( infoOrFileNameOrConfig as AncestorConfigFileInfo ) . configFileInfo ; } /** Kind of operation to perform to get project reference project */ export enum ProjectReferenceProjectLoadKind { /** Find existing project for project reference */ Find , /** Find existing project or create one for the project reference */ FindCreate , /** Find existing project or create and load it for the project reference */ FindCreateLoad } export function forEachResolvedProjectReferenceProject < T > ( project , fileName , cb , projectReferenceProjectLoadKind ) ; export function forEachResolvedProjectReferenceProject < T > ( project , fileName , cb , projectReferenceProjectLoadKind , reason ) ; export function forEachResolvedProjectReferenceProject < T > ( project , fileName , cb , projectReferenceProjectLoadKind , reason ? ) { const resolvedRefs = project . getCurrentProgram ( ) ?. getResolvedProjectReferences ( ) ; if ( ! resolvedRefs ) return undefined ; let seenResolvedRefs ; const possibleDefaultRef = fileName ? project . getResolvedProjectReferenceToRedirect ( fileName ) : undefined ; if ( possibleDefaultRef ) { const configFileName = toNormalizedPath ( possibleDefaultRef . sourceFile . fileName ) ; const child = project . projectService . findConfiguredProjectByProjectName ( configFileName ) ; if ( child ) { const result = cb ( child ) ; if ( result ) return result ; } else if ( projectReferenceProjectLoadKind !== ProjectReferenceProjectLoadKind . Find ) { seenResolvedRefs = new Map ( ) ; const result = forEachResolvedProjectReferenceProjectWorker ( resolvedRefs , project . getCompilerOptions ( ) , ( ref , loadKind ) => possibleDefaultRef === ref ? callback ( ref , loadKind ) : undefined , projectReferenceProjectLoadKind , project . projectService , seenResolvedRefs ) ; if ( result ) return result ; seenResolvedRefs . clear ( ) ; } } return forEachResolvedProjectReferenceProjectWorker ( resolvedRefs , project . getCompilerOptions ( ) , ( ref , loadKind ) => possibleDefaultRef !== ref ? callback ( ref , loadKind ) : undefined , projectReferenceProjectLoadKind , project . projectService , seenResolvedRefs ) ; function callback ( ref , loadKind ) { const configFileName = toNormalizedPath ( ref . sourceFile . fileName ) ; const child = project . projectService . findConfiguredProjectByProjectName ( configFileName ) || ( loadKind === ProjectReferenceProjectLoadKind . Find ? undefined : loadKind === ProjectReferenceProjectLoadKind . FindCreate ? project . projectService . createConfiguredProject ( configFileName ) : loadKind === ProjectReferenceProjectLoadKind . FindCreateLoad ? project . projectService . createAndLoadConfiguredProject ( configFileName , reason ! ) : Debug . assertNever ( loadKind ) ) ; return child && cb ( child ) ; } } function forEachResolvedProjectReferenceProjectWorker < T > ( resolvedProjectReferences , parentOptions , cb , projectReferenceProjectLoadKind , projectService , seenResolvedRefs ) { const loadKind = parentOptions . disableReferencedProjectLoad ? ProjectReferenceProjectLoadKind . Find : projectReferenceProjectLoadKind ; return forEach ( resolvedProjectReferences , ref => { if ( ! ref ) return undefined ; const configFileName = toNormalizedPath ( ref . sourceFile . fileName ) ; const canonicalPath = projectService . toCanonicalFileName ( configFileName ) ; const seenValue = seenResolvedRefs ?. get ( canonicalPath ) ; if ( seenValue !== undefined && seenValue >= loadKind ) { return undefined ; } const result = cb ( ref , loadKind ) ; if ( result ) { return result ; } ( seenResolvedRefs || ( seenResolvedRefs = new Map ( ) ) ) . set ( canonicalPath , loadKind ) ; return ref . references && forEachResolvedProjectReferenceProjectWorker ( ref . references , ref . commandLine . options , cb , loadKind , projectService , seenResolvedRefs ) ; } ) ; } function forEachPotentialProjectReference < T > ( project , cb ) { return project . potentialProjectReferences && forEachKey ( project . potentialProjectReferences , cb ) ; } function forEachAnyProjectReferenceKind < T > ( project , cb , cbProjectRef , cbPotentialProjectRef ) { return project . getCurrentProgram ( ) ? project . forEachResolvedProjectReference ( cb ) : project . isInitialLoadPending ( ) ? forEachPotentialProjectReference ( project , cbPotentialProjectRef ) : forEach ( project . getProjectReferences ( ) , cbProjectRef ) ; } function callbackRefProject < T > ( project , cb , refPath ) { const refProject = refPath && project . projectService . configuredProjects . get ( refPath ) ; return refProject && cb ( refProject ) ; } function forEachReferencedProject < T > ( project , cb ) { return forEachAnyProjectReferenceKind ( project , resolvedRef => callbackRefProject ( project , cb , resolvedRef . sourceFile . path ) , projectRef => callbackRefProject ( project , cb , project . toPath ( resolveProjectReferencePath ( projectRef ) ) ) , potentialProjectRef => callbackRefProject ( project , cb , potentialProjectRef ) ) ; } interface NodeModulesWatcher extends FileWatcher { /** How many watchers of this directory were for closed ScriptInfo */ refreshScriptInfoRefCount : number ; /** List of project names whose module specifier cache should be cleared when package.jsons change */ affectedModuleSpecifierCacheProjects ? : Set < string > ; } function getDetailWatchInfo ( watchType , project ) { return `${ isString ( project ) ? `Config: ${ project } ` : project ? `Project: ${ project . getProjectName ( ) } ` : "" }WatchType: ${ watchType }` ; } function isScriptInfoWatchedFromNodeModules ( info ) { return ! info . isScriptOpen ( ) && info . mTime !== undefined ; } /** true if script info is part of project and is not in project because it is referenced from project reference source */ export function projectContainsInfoDirectly ( project , info ) { return project . containsScriptInfo ( info ) && ! project . isSourceOfProjectReferenceRedirect ( info . path ) ; } export function updateProjectIfDirty ( project ) { project . invalidateResolutionsOfFailedLookupLocations ( ) ; return project . dirty && project . updateGraph ( ) ; } function setProjectOptionsUsed ( project ) { if ( isConfiguredProject ( project ) ) { project . projectOptions = true ; } } export interface OpenFileArguments { fileName : string ; content ? : string ; scriptKind ? : protocol . ScriptKindName | ScriptKind ; hasMixedContent ? : boolean ; projectRootPath ? : string ; } export interface ChangeFileArguments { fileName : string ; changes : Iterator < TextChange > ; } export interface WatchOptionsAndErrors { watchOptions : WatchOptions ; errors : Diagnostic [ ] | undefined ; } export interface ParsedConfig { cachedDirectoryStructureHost : CachedDirectoryStructureHost ; /**
         * The map contains
         *   - true if project is watching config file as well as wild cards
         *   - false if just config file is watched
         */ projects : ESMap < NormalizedPath , boolean > ; parsedCommandLine ? : ParsedCommandLine ; watchedDirectories ? : Map < WildcardDirectoryWatcher > ; /**
         * true if watchedDirectories need to be updated as per parsedCommandLine's updated watched directories
         */ watchedDirectoriesStale ? : boolean ; reloadLevel ? : ConfigFileProgramReloadLevel . Partial | ConfigFileProgramReloadLevel . Full ; } function createProjectNameFactoryWithCounter ( nameFactory ) { let nextId = 1 ; return ( ) => nameFactory ( nextId ++ ) ; } export class ProjectService { readonly typingsCache : TypingsCache ; readonly documentRegistry : DocumentRegistry ; /**
         * Container of all known scripts
         */ readonly filenameToScriptInfo = new Map < string , ScriptInfo > ( ) ; private readonly nodeModulesWatchers = new Map < string , NodeModulesWatcher > ( ) ; /**
         * Contains all the deleted script info's version information so that
         * it does not reset when creating script info again
         * (and could have potentially collided with version where contents mismatch)
         */ private readonly filenameToScriptInfoVersion = new Map < string , ScriptInfoVersion > ( ) ; private readonly allJsFilesForOpenFileTelemetry = new Map < string , true > ( ) ; /**
         * Map to the real path of the infos
         */ readonly realpathToScriptInfos : MultiMap < Path , ScriptInfo > | undefined ; /**
         * maps external project file name to list of config files that were the part of this project
         */ private readonly externalProjectToConfiguredProjectMap = new Map < string , NormalizedPath [ ] > ( ) ; /**
         * external projects (configuration and list of root files is not controlled by tsserver)
         */ readonly externalProjects : ExternalProject [ ] = [ ] ; /**
         * projects built from openFileRoots
         */ readonly inferredProjects : InferredProject [ ] = [ ] ; /**
         * projects specified by a tsconfig.json file
         */ readonly configuredProjects : Map < ConfiguredProject > = new Map < string , ConfiguredProject > ( ) ; readonly newInferredProjectName = createProjectNameFactoryWithCounter ( makeInferredProjectName ) ; readonly newAutoImportProviderProjectName = createProjectNameFactoryWithCounter ( makeAutoImportProviderProjectName ) ; /**
         * Open files: with value being project root path, and key being Path of the file that is open
         */ readonly openFiles : Map < NormalizedPath | undefined > = new Map < Path , NormalizedPath | undefined > ( ) ; readonly configFileForOpenFiles : ESMap < Path , NormalizedPath | false > = new Map ( ) ; /**
         * Map of open files that are opened without complete path but have projectRoot as current directory
         */ private readonly openFilesWithNonRootedDiskPath = new Map < string , ScriptInfo > ( ) ; private compilerOptionsForInferredProjects : CompilerOptions | undefined ; private compilerOptionsForInferredProjectsPerProjectRoot = new Map < string , CompilerOptions > ( ) ; private watchOptionsForInferredProjects : WatchOptionsAndErrors | undefined ; private watchOptionsForInferredProjectsPerProjectRoot = new Map < string , WatchOptionsAndErrors | false > ( ) ; private typeAcquisitionForInferredProjects : TypeAcquisition | undefined ; private typeAcquisitionForInferredProjectsPerProjectRoot = new Map < string , TypeAcquisition | undefined > ( ) ; /**
         * Project size for configured or external projects
         */ private readonly projectToSizeMap = new Map < string , number > ( ) ; /**
         * This is a map of config file paths existence that doesnt need query to disk
         * - The entry can be present because there is inferred project that needs to watch addition of config file to directory
         *   In this case the exists could be true/false based on config file is present or not
         * - Or it is present if we have configured project open with config file at that location
         *   In this case the exists property is always true
         */ readonly configFileExistenceInfoCache = new Map < NormalizedPath , ConfigFileExistenceInfo > ( ) ; readonly throttledOperations : ThrottledOperations ; private readonly hostConfiguration : HostConfiguration ; private safelist : SafeList = defaultTypeSafeList ; private readonly legacySafelist = new Map < string , string > ( ) ; private pendingProjectUpdates = new Map < string , Project > ( ) ; pendingEnsureProjectForOpenFiles = false ; readonly currentDirectory : NormalizedPath ; readonly toCanonicalFileName : ( f : string ) => string ; public readonly host : ServerHost ; public readonly logger : Logger ; public readonly cancellationToken : HostCancellationToken ; public readonly useSingleInferredProject : boolean ; public readonly useInferredProjectPerProjectRoot : boolean ; public readonly typingsInstaller : ITypingsInstaller ; private readonly globalCacheLocationDirectoryPath : Path | undefined ; public readonly throttleWaitMilliseconds ? : number ; private readonly eventHandler ? : ProjectServiceEventHandler ; private readonly suppressDiagnosticEvents ? : boolean ; public readonly globalPlugins : readonly string [ ] ; public readonly pluginProbeLocations : readonly string [ ] ; public readonly allowLocalPluginLoads : boolean ; private currentPluginConfigOverrides : ESMap < string , any > | undefined ; public readonly typesMapLocation : string | undefined ; deprecated public readonly syntaxOnly : boolean ; public readonly serverMode : LanguageServiceMode ; /** Tracks projects that we have already sent telemetry for. */ private readonly seenProjects = new Map < string , true > ( ) ; readonly watchFactory : WatchFactory < WatchType , Project | NormalizedPath > ; private readonly sharedExtendedConfigFileWatchers = new Map < Path , SharedExtendedConfigFileWatcher < NormalizedPath > > ( ) ; private readonly extendedConfigCache = new Map < string , ExtendedConfigCacheEntry > ( ) ; readonly packageJsonCache : PackageJsonCache ; private packageJsonFilesMap : ESMap < Path , FileWatcher > | undefined ; private incompleteCompletionsCache : IncompleteCompletionsCache | undefined ; readonly session : Session < unknown > | undefined ; private performanceEventHandler ? : PerformanceEventHandler ; constructor ( opts ) { this . host = opts . host ; this . logger = opts . logger ; this . cancellationToken = opts . cancellationToken ; this . useSingleInferredProject = opts . useSingleInferredProject ; this . useInferredProjectPerProjectRoot = opts . useInferredProjectPerProjectRoot ; this . typingsInstaller = opts . typingsInstaller || nullTypingsInstaller ; this . throttleWaitMilliseconds = opts . throttleWaitMilliseconds ; this . eventHandler = opts . eventHandler ; this . suppressDiagnosticEvents = opts . suppressDiagnosticEvents ; this . globalPlugins = opts . globalPlugins || emptyArray ; this . pluginProbeLocations = opts . pluginProbeLocations || emptyArray ; this . allowLocalPluginLoads = ! ! opts . allowLocalPluginLoads ; this . typesMapLocation = ( opts . typesMapLocation === undefined ) ? combinePaths ( getDirectoryPath ( this . getExecutingFilePath ( ) ) , "typesMap.json" ) : opts . typesMapLocation ; this . session = opts . session ; if ( opts . serverMode !== undefined ) { this . serverMode = opts . serverMode ; this . syntaxOnly = this . serverMode === LanguageServiceMode . Syntactic ; } else if ( opts . syntaxOnly ) { this . serverMode = LanguageServiceMode . Syntactic ; this . syntaxOnly = true ; } else { this . serverMode = LanguageServiceMode . Semantic ; this . syntaxOnly = false ; } if ( this . host . realpath ) { this . realpathToScriptInfos = createMultiMap ( ) ; } this . currentDirectory = toNormalizedPath ( this . host . getCurrentDirectory ( ) ) ; this . toCanonicalFileName = createGetCanonicalFileName ( this . host . useCaseSensitiveFileNames ) ; this . globalCacheLocationDirectoryPath = this . typingsInstaller . globalTypingsCacheLocation ? ensureTrailingDirectorySeparator ( this . toPath ( this . typingsInstaller . globalTypingsCacheLocation ) ) : undefined ; this . throttledOperations = new ThrottledOperations ( this . host , this . logger ) ; if ( this . typesMapLocation ) { this . loadTypesMap ( ) ; } else { this . logger . info ( "No types map provided; using the default" ) ; } this . typingsInstaller . attach ( this ) ; this . typingsCache = new TypingsCache ( this . typingsInstaller ) ; this . hostConfiguration = { formatCodeOptions : getDefaultFormatCodeSettings ( this . host . newLine ) , preferences : emptyOptions , hostInfo : "Unknown host" , extraFileExtensions : [ ] , } ; this . documentRegistry = createDocumentRegistryInternal ( this . host . useCaseSensitiveFileNames , this . currentDirectory , this ) ; const watchLogLevel = this . logger . hasLevel ( LogLevel . verbose ) ? WatchLogLevel . Verbose : this . logger . loggingEnabled ( ) ? WatchLogLevel . TriggerOnly : WatchLogLevel . None ; const log = watchLogLevel !== WatchLogLevel . None ? ( s => this . logger . info ( s ) ) : noop ; this . packageJsonCache = createPackageJsonCache ( this ) ; this . watchFactory = this . serverMode !== LanguageServiceMode . Semantic ? { watchFile : returnNoopFileWatcher , watchDirectory : returnNoopFileWatcher , } : getWatchFactory ( this . host , watchLogLevel , log , getDetailWatchInfo ) ; } toPath ( fileName ) { return toPath ( fileName , this . currentDirectory , this . toCanonicalFileName ) ; } getExecutingFilePath ( ) { return this . getNormalizedAbsolutePath ( this . host . getExecutingFilePath ( ) ) ; } getNormalizedAbsolutePath ( fileName ) { return getNormalizedAbsolutePath ( fileName , this . host . getCurrentDirectory ( ) ) ; } setDocument ( key , path , sourceFile ) { const info = Debug . checkDefined ( this . getScriptInfoForPath ( path ) ) ; info . cacheSourceFile = { key , sourceFile } ; } getDocument ( key , path ) { const info = this . getScriptInfoForPath ( path ) ; return info && info . cacheSourceFile && info . cacheSourceFile . key === key ? info . cacheSourceFile . sourceFile : undefined ; } ensureInferredProjectsUpToDate_TestOnly ( ) { this . ensureProjectStructuresUptoDate ( ) ; } getCompilerOptionsForInferredProjects ( ) { return this . compilerOptionsForInferredProjects ; } onUpdateLanguageServiceStateForProject ( project , languageServiceEnabled ) { if ( ! this . eventHandler ) { return ; } const event = { eventName : ProjectLanguageServiceStateEvent , data : { project , languageServiceEnabled } } ; this . eventHandler ( event ) ; } private loadTypesMap ( ) { try { const fileContent = this . host . readFile ( this . typesMapLocation ! ) ; if ( fileContent === undefined ) { this . logger . info ( `Provided types map file "${ this . typesMapLocation }" doesn't exist` ) ; return ; } const raw = JSON . parse ( fileContent ) ; for ( const k of Object . keys ( raw . typesMap ) ) { raw . typesMap [ k ] . match = new RegExp ( raw . typesMap [ k ] . match as { } as string , "i" ) ; } this . safelist = raw . typesMap ; for ( const key in raw . simpleMap ) { if ( raw . simpleMap . hasOwnProperty ( key ) ) { this . legacySafelist . set ( key , raw . simpleMap [ key ] . toLowerCase ( ) ) ; } } } catch ( e ) { this . logger . info ( `Error loading types map: ${ e }` ) ; this . safelist = defaultTypeSafeList ; this . legacySafelist . clear ( ) ; } } updateTypingsForProject ( response ) ; internal updateTypingsForProject ( response ) ; updateTypingsForProject ( response ) { const project = this . findProject ( response . projectName ) ; if ( ! project ) { return ; } switch ( response . kind ) { case ActionSet : project . updateTypingFiles ( this . typingsCache . updateTypingsForProject ( response . projectName , response . compilerOptions , response . typeAcquisition , response . unresolvedImports , response . typings ) ) ; return ; case ActionInvalidate : this . typingsCache . enqueueInstallTypingsForProject ( project , project . lastCachedUnresolvedImportsList , true ) ; return ; } } delayEnsureProjectForOpenFiles ( ) { if ( ! this . openFiles . size ) return ; this . pendingEnsureProjectForOpenFiles = true ; this . throttledOperations . schedule ( ensureProjectForOpenFileSchedule , 2500 , ( ) => { if ( this . pendingProjectUpdates . size !== 0 ) { this . delayEnsureProjectForOpenFiles ( ) ; } else { if ( this . pendingEnsureProjectForOpenFiles ) { this . ensureProjectForOpenFiles ( ) ; this . sendProjectsUpdatedInBackgroundEvent ( ) ; } } } ) ; } private delayUpdateProjectGraph ( project ) { project . markAsDirty ( ) ; const projectName = project . getProjectName ( ) ; this . pendingProjectUpdates . set ( projectName , project ) ; this . throttledOperations . schedule ( projectName , 250 , ( ) => { if ( this . pendingProjectUpdates . delete ( projectName ) ) { updateProjectIfDirty ( project ) ; } } ) ; } hasPendingProjectUpdate ( project ) { return this . pendingProjectUpdates . has ( project . getProjectName ( ) ) ; } sendProjectsUpdatedInBackgroundEvent ( ) { if ( ! this . eventHandler ) { return ; } const event = { eventName : ProjectsUpdatedInBackgroundEvent , data : { openFiles : arrayFrom ( this . openFiles . keys ( ) , path => this . getScriptInfoForPath ( path as Path ) ! . fileName ) } } ; this . eventHandler ( event ) ; } sendLargeFileReferencedEvent ( file , fileSize ) { if ( ! this . eventHandler ) { return ; } const event = { eventName : LargeFileReferencedEvent , data : { file , fileSize , maxFileSize } } ; this . eventHandler ( event ) ; } sendProjectLoadingStartEvent ( project , reason ) { if ( ! this . eventHandler ) { return ; } project . sendLoadingProjectFinish = true ; const event = { eventName : ProjectLoadingStartEvent , data : { project , reason } } ; this . eventHandler ( event ) ; } sendProjectLoadingFinishEvent ( project ) { if ( ! this . eventHandler || ! project . sendLoadingProjectFinish ) { return ; } project . sendLoadingProjectFinish = false ; const event = { eventName : ProjectLoadingFinishEvent , data : { project } } ; this . eventHandler ( event ) ; } sendPerformanceEvent ( kind , durationMs ) { if ( this . performanceEventHandler ) { this . performanceEventHandler ( { kind , durationMs } ) ; } } delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles ( project ) { this . delayUpdateProjectGraph ( project ) ; this . delayEnsureProjectForOpenFiles ( ) ; } private delayUpdateProjectGraphs ( projects , clearSourceMapperCache ) { if ( projects . length ) { for ( const project of projects ) { if ( clearSourceMapperCache ) project . clearSourceMapperCache ( ) ; this . delayUpdateProjectGraph ( project ) ; } this . delayEnsureProjectForOpenFiles ( ) ; } } setCompilerOptionsForInferredProjects ( projectCompilerOptions , projectRootPath ? ) { Debug . assert ( projectRootPath === undefined || this . useInferredProjectPerProjectRoot , "Setting compiler options per project root path is only supported when useInferredProjectPerProjectRoot is enabled" ) ; const compilerOptions = convertCompilerOptions ( projectCompilerOptions ) ; const watchOptions = convertWatchOptions ( projectCompilerOptions , projectRootPath ) ; const typeAcquisition = convertTypeAcquisition ( projectCompilerOptions ) ; compilerOptions . allowNonTsExtensions = true ; const canonicalProjectRootPath = projectRootPath && this . toCanonicalFileName ( projectRootPath ) ; if ( canonicalProjectRootPath ) { this . compilerOptionsForInferredProjectsPerProjectRoot . set ( canonicalProjectRootPath , compilerOptions ) ; this . watchOptionsForInferredProjectsPerProjectRoot . set ( canonicalProjectRootPath , watchOptions || false ) ; this . typeAcquisitionForInferredProjectsPerProjectRoot . set ( canonicalProjectRootPath , typeAcquisition ) ; } else { this . compilerOptionsForInferredProjects = compilerOptions ; this . watchOptionsForInferredProjects = watchOptions ; this . typeAcquisitionForInferredProjects = typeAcquisition ; } for ( const project of this . inferredProjects ) { if ( canonicalProjectRootPath ? project . projectRootPath === canonicalProjectRootPath : ! project . projectRootPath || ! this . compilerOptionsForInferredProjectsPerProjectRoot . has ( project . projectRootPath ) ) { project . setCompilerOptions ( compilerOptions ) ; project . setTypeAcquisition ( typeAcquisition ) ; project . setWatchOptions ( watchOptions ?. watchOptions ) ; project . setProjectErrors ( watchOptions ?. errors ) ; project . compileOnSaveEnabled = compilerOptions . compileOnSave ! ; project . markAsDirty ( ) ; this . delayUpdateProjectGraph ( project ) ; } } this . delayEnsureProjectForOpenFiles ( ) ; } findProject ( projectName ) { if ( projectName === undefined ) { return undefined ; } if ( isInferredProjectName ( projectName ) ) { return findProjectByName ( projectName , this . inferredProjects ) ; } return this . findExternalProjectByProjectName ( projectName ) || this . findConfiguredProjectByProjectName ( toNormalizedPath ( projectName ) ) ; } private forEachProject ( cb ) { this . externalProjects . forEach ( cb ) ; this . configuredProjects . forEach ( cb ) ; this . inferredProjects . forEach ( cb ) ; } forEachEnabledProject ( cb ) { this . forEachProject ( project => { if ( ! project . isOrphan ( ) && project . languageServiceEnabled ) { cb ( project ) ; } } ) ; } getDefaultProjectForFile ( fileName , ensureProject ) { return ensureProject ? this . ensureDefaultProjectForFile ( fileName ) : this . tryGetDefaultProjectForFile ( fileName ) ; } tryGetDefaultProjectForFile ( fileName ) { const scriptInfo = this . getScriptInfoForNormalizedPath ( fileName ) ; return scriptInfo && ! scriptInfo . isOrphan ( ) ? scriptInfo . getDefaultProject ( ) : undefined ; } ensureDefaultProjectForFile ( fileName ) { return this . tryGetDefaultProjectForFile ( fileName ) || this . doEnsureDefaultProjectForFile ( fileName ) ; } private doEnsureDefaultProjectForFile ( fileName ) { this . ensureProjectStructuresUptoDate ( ) ; const scriptInfo = this . getScriptInfoForNormalizedPath ( fileName ) ; return scriptInfo ? scriptInfo . getDefaultProject ( ) : ( this . logErrorForScriptInfoNotFound ( fileName ) , Errors . ThrowNoProject ( ) ) ; } getScriptInfoEnsuringProjectsUptoDate ( uncheckedFileName ) { this . ensureProjectStructuresUptoDate ( ) ; return this . getScriptInfo ( uncheckedFileName ) ; } /**
         * Ensures the project structures are upto date
         * This means,
         * - we go through all the projects and update them if they are dirty
         * - if updates reflect some change in structure or there was pending request to ensure projects for open files
         *   ensure that each open script info has project
         */ private ensureProjectStructuresUptoDate ( ) { let hasChanges = this . pendingEnsureProjectForOpenFiles ; this . pendingProjectUpdates . clear ( ) ; const updateGraph = ( project ) => { hasChanges = updateProjectIfDirty ( project ) || hasChanges ; } ; this . externalProjects . forEach ( updateGraph ) ; this . configuredProjects . forEach ( updateGraph ) ; this . inferredProjects . forEach ( updateGraph ) ; if ( hasChanges ) { this . ensureProjectForOpenFiles ( ) ; } } getFormatCodeOptions ( file ) { const info = this . getScriptInfoForNormalizedPath ( file ) ; return info && info . getFormatCodeSettings ( ) || this . hostConfiguration . formatCodeOptions ; } getPreferences ( file ) { const info = this . getScriptInfoForNormalizedPath ( file ) ; return { ... this . hostConfiguration . preferences , ... info && info . getPreferences ( ) } ; } getHostFormatCodeOptions ( ) { return this . hostConfiguration . formatCodeOptions ; } getHostPreferences ( ) { return this . hostConfiguration . preferences ; } private onSourceFileChanged ( info , eventKind ) { if ( eventKind === FileWatcherEventKind . Deleted ) { this . handleDeletedFile ( info ) ; } else if ( ! info . isScriptOpen ( ) ) { info . delayReloadNonMixedContentFile ( ) ; this . delayUpdateProjectGraphs ( info . containingProjects , false ) ; this . handleSourceMapProjects ( info ) ; } } private handleSourceMapProjects ( info ) { if ( info . sourceMapFilePath ) { if ( isString ( info . sourceMapFilePath ) ) { const sourceMapFileInfo = this . getScriptInfoForPath ( info . sourceMapFilePath ) ; this . delayUpdateSourceInfoProjects ( sourceMapFileInfo && sourceMapFileInfo . sourceInfos ) ; } else { this . delayUpdateSourceInfoProjects ( info . sourceMapFilePath . sourceInfos ) ; } } this . delayUpdateSourceInfoProjects ( info . sourceInfos ) ; if ( info . declarationInfoPath ) { this . delayUpdateProjectsOfScriptInfoPath ( info . declarationInfoPath ) ; } } private delayUpdateSourceInfoProjects ( sourceInfos ) { if ( sourceInfos ) { sourceInfos . forEach ( ( _value , path ) => this . delayUpdateProjectsOfScriptInfoPath ( path ) ) ; } } private delayUpdateProjectsOfScriptInfoPath ( path ) { const info = this . getScriptInfoForPath ( path ) ; if ( info ) { this . delayUpdateProjectGraphs ( info . containingProjects , true ) ; } } private handleDeletedFile ( info ) { this . stopWatchingScriptInfo ( info ) ; if ( ! info . isScriptOpen ( ) ) { this . deleteScriptInfo ( info ) ; const containingProjects = info . containingProjects . slice ( ) ; info . detachAllProjects ( ) ; this . delayUpdateProjectGraphs ( containingProjects , false ) ; this . handleSourceMapProjects ( info ) ; info . closeSourceMapFileWatcher ( ) ; if ( info . declarationInfoPath ) { const declarationInfo = this . getScriptInfoForPath ( info . declarationInfoPath ) ; if ( declarationInfo ) { declarationInfo . sourceMapFilePath = undefined ; } } } } /**
         * This is to watch whenever files are added or removed to the wildcard directories
         */ private watchWildcardDirectory ( directory , flags , configFileName , config ) { return this . watchFactory . watchDirectory ( directory , fileOrDirectory => { const fileOrDirectoryPath = this . toPath ( fileOrDirectory ) ; const fsResult = config . cachedDirectoryStructureHost . addOrDeleteFileOrDirectory ( fileOrDirectory , fileOrDirectoryPath ) ; if ( getBaseFileName ( fileOrDirectoryPath ) === "package.json" && ! isInsideNodeModules ( fileOrDirectoryPath ) && ( fsResult && fsResult . fileExists || ! fsResult && this . host . fileExists ( fileOrDirectoryPath ) ) ) { this . logger . info ( `Config: ${ configFileName } Detected new package.json: ${ fileOrDirectory }` ) ; this . onAddPackageJson ( fileOrDirectoryPath ) ; } const configuredProjectForConfig = this . findConfiguredProjectByProjectName ( configFileName ) ; if ( isIgnoredFileFromWildCardWatching ( { watchedDirPath : directory , fileOrDirectory , fileOrDirectoryPath , configFileName , extraFileExtensions : this . hostConfiguration . extraFileExtensions , currentDirectory : this . currentDirectory , options : config . parsedCommandLine ! . options , program : configuredProjectForConfig ?. getCurrentProgram ( ) || config . parsedCommandLine ! . fileNames , useCaseSensitiveFileNames : this . host . useCaseSensitiveFileNames , writeLog : s => this . logger . info ( s ) , toPath : s => this . toPath ( s ) } ) ) return ; if ( config . reloadLevel !== ConfigFileProgramReloadLevel . Full ) config . reloadLevel = ConfigFileProgramReloadLevel . Partial ; config . projects . forEach ( ( watchWildcardDirectories , projectCanonicalPath ) => { if ( ! watchWildcardDirectories ) return ; const project = this . getConfiguredProjectByCanonicalConfigFilePath ( projectCanonicalPath ) ; if ( ! project ) return ; const reloadLevel = configuredProjectForConfig === project ? ConfigFileProgramReloadLevel . Partial : ConfigFileProgramReloadLevel . None ; if ( project . pendingReload !== undefined && project . pendingReload > reloadLevel ) return ; if ( this . openFiles . has ( fileOrDirectoryPath ) ) { const info = Debug . checkDefined ( this . getScriptInfoForPath ( fileOrDirectoryPath ) ) ; if ( info . isAttached ( project ) ) { const loadLevelToSet = Math . max ( reloadLevel , project . openFileWatchTriggered . get ( fileOrDirectoryPath ) || ConfigFileProgramReloadLevel . None ) as ConfigFileProgramReloadLevel ; project . openFileWatchTriggered . set ( fileOrDirectoryPath , loadLevelToSet ) ; } else { project . pendingReload = reloadLevel ; this . delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles ( project ) ; } } else { project . pendingReload = reloadLevel ; this . delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles ( project ) ; } } ) ; } , flags , this . getWatchOptionsFromProjectWatchOptions ( config . parsedCommandLine ! . watchOptions ) , WatchType . WildcardDirectory , configFileName ) ; } private delayUpdateProjectsFromParsedConfigOnConfigFileChange ( canonicalConfigFilePath , reloadReason ) { const configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ; if ( ! configFileExistenceInfo ?. config ) return false ; let scheduledAnyProjectUpdate = false ; configFileExistenceInfo . config . reloadLevel = ConfigFileProgramReloadLevel . Full ; configFileExistenceInfo . config . projects . forEach ( ( _watchWildcardDirectories , projectCanonicalPath ) => { const project = this . getConfiguredProjectByCanonicalConfigFilePath ( projectCanonicalPath ) ; if ( ! project ) return ; scheduledAnyProjectUpdate = true ; if ( projectCanonicalPath === canonicalConfigFilePath ) { if ( project . isInitialLoadPending ( ) ) return ; project . pendingReload = ConfigFileProgramReloadLevel . Full ; project . pendingReloadReason = reloadReason ; this . delayUpdateProjectGraph ( project ) ; } else { project . resolutionCache . removeResolutionsFromProjectReferenceRedirects ( this . toPath ( canonicalConfigFilePath ) ) ; this . delayUpdateProjectGraph ( project ) ; } } ) ; return scheduledAnyProjectUpdate ; } private onConfigFileChanged ( canonicalConfigFilePath , eventKind ) { const configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ! ; if ( eventKind === FileWatcherEventKind . Deleted ) { configFileExistenceInfo . exists = false ; const project = configFileExistenceInfo . config ?. projects . has ( canonicalConfigFilePath ) ? this . getConfiguredProjectByCanonicalConfigFilePath ( canonicalConfigFilePath ) : undefined ; if ( project ) this . removeProject ( project ) ; } else { configFileExistenceInfo . exists = true ; } this . delayUpdateProjectsFromParsedConfigOnConfigFileChange ( canonicalConfigFilePath , "Change in config file detected" ) ; this . reloadConfiguredProjectForFiles ( configFileExistenceInfo . openFilesImpactedByConfigFile , false , true , eventKind !== FileWatcherEventKind . Deleted ? identity : returnTrue , "Change in config file detected" ) ; this . delayEnsureProjectForOpenFiles ( ) ; } private removeProject ( project ) { this . logger . info ( "`remove Project::" ) ; project . print ( true ) ; project . close ( ) ; if ( Debug . shouldAssert ( AssertionLevel . Normal ) ) { this . filenameToScriptInfo . forEach ( info => Debug . assert ( ! info . isAttached ( project ) , "Found script Info still attached to project" , ( ) => `${ project . projectName }: ScriptInfos still attached: ${ JSON . stringify ( arrayFrom ( mapDefinedIterator ( this . filenameToScriptInfo . values ( ) , info => info . isAttached ( project ) ? { fileName : info . fileName , projects : info . containingProjects . map ( p => p . projectName ) , hasMixedContent : info . hasMixedContent } : undefined ) ) , undefined , " " ) }` ) ) ; } this . pendingProjectUpdates . delete ( project . getProjectName ( ) ) ; switch ( project . projectKind ) { case ProjectKind . External : unorderedRemoveItem ( this . externalProjects , project as ExternalProject ) ; this . projectToSizeMap . delete ( project . getProjectName ( ) ) ; break ; case ProjectKind . Configured : this . configuredProjects . delete ( ( project as ConfiguredProject ) . canonicalConfigFilePath ) ; this . projectToSizeMap . delete ( ( project as ConfiguredProject ) . canonicalConfigFilePath ) ; break ; case ProjectKind . Inferred : unorderedRemoveItem ( this . inferredProjects , project as InferredProject ) ; break ; } } assignOrphanScriptInfoToInferredProject ( info , projectRootPath ) { Debug . assert ( info . isOrphan ( ) ) ; const project = this . getOrCreateInferredProjectForProjectRootPathIfEnabled ( info , projectRootPath ) || this . getOrCreateSingleInferredProjectIfEnabled ( ) || this . getOrCreateSingleInferredWithoutProjectRoot ( info . isDynamic ? projectRootPath || this . currentDirectory : getDirectoryPath ( isRootedDiskPath ( info . fileName ) ? info . fileName : getNormalizedAbsolutePath ( info . fileName , projectRootPath ? this . getNormalizedAbsolutePath ( projectRootPath ) : this . currentDirectory ) ) ) ; project . addRoot ( info ) ; if ( info . containingProjects [ 0 ] !== project ) { info . detachFromProject ( project ) ; info . containingProjects . unshift ( project ) ; } project . updateGraph ( ) ; if ( ! this . useSingleInferredProject && ! project . projectRootPath ) { for ( const inferredProject of this . inferredProjects ) { if ( inferredProject === project || inferredProject . isOrphan ( ) ) { continue ; } const roots = inferredProject . getRootScriptInfos ( ) ; Debug . assert ( roots . length === 1 || ! ! inferredProject . projectRootPath ) ; if ( roots . length === 1 && forEach ( roots [ 0 ] . containingProjects , p => p !== roots [ 0 ] . containingProjects [ 0 ] && ! p . isOrphan ( ) ) ) { inferredProject . removeFile ( roots [ 0 ] , true , true ) ; } } } return project ; } private assignOrphanScriptInfosToInferredProject ( ) { this . openFiles . forEach ( ( projectRootPath , path ) => { const info = this . getScriptInfoForPath ( path as Path ) ! ; if ( info . isOrphan ( ) ) { this . assignOrphanScriptInfoToInferredProject ( info , projectRootPath ) ; } } ) ; } param info private closeOpenFile ( info , skipAssignOrphanScriptInfosToInferredProject ? ) { const fileExists = info . isDynamic ? false : this . host . fileExists ( info . fileName ) ; info . close ( fileExists ) ; this . stopWatchingConfigFilesForClosedScriptInfo ( info ) ; const canonicalFileName = this . toCanonicalFileName ( info . fileName ) ; if ( this . openFilesWithNonRootedDiskPath . get ( canonicalFileName ) === info ) { this . openFilesWithNonRootedDiskPath . delete ( canonicalFileName ) ; } let ensureProjectsForOpenFiles = false ; for ( const p of info . containingProjects ) { if ( isConfiguredProject ( p ) ) { if ( info . hasMixedContent ) { info . registerFileUpdate ( ) ; } const reloadLevel = p . openFileWatchTriggered . get ( info . path ) ; if ( reloadLevel !== undefined ) { p . openFileWatchTriggered . delete ( info . path ) ; if ( p . pendingReload !== undefined && p . pendingReload < reloadLevel ) { p . pendingReload = reloadLevel ; p . markFileAsDirty ( info . path ) ; } } } else if ( isInferredProject ( p ) && p . isRoot ( info ) ) { if ( p . isProjectWithSingleRoot ( ) ) { ensureProjectsForOpenFiles = true ; } p . removeFile ( info , fileExists , true ) ; } if ( ! p . languageServiceEnabled ) { p . markAsDirty ( ) ; } } this . openFiles . delete ( info . path ) ; this . configFileForOpenFiles . delete ( info . path ) ; if ( ! skipAssignOrphanScriptInfosToInferredProject && ensureProjectsForOpenFiles ) { this . assignOrphanScriptInfosToInferredProject ( ) ; } if ( fileExists ) { this . watchClosedScriptInfo ( info ) ; } else { this . handleDeletedFile ( info ) ; } return ensureProjectsForOpenFiles ; } private deleteScriptInfo ( info ) { this . filenameToScriptInfo . delete ( info . path ) ; this . filenameToScriptInfoVersion . set ( info . path , info . getVersion ( ) ) ; const realpath = info . getRealpathIfDifferent ( ) ; if ( realpath ) { this . realpathToScriptInfos ! . remove ( realpath , info ) ; } } private configFileExists ( configFileName , canonicalConfigFilePath , info ) { let configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ; if ( configFileExistenceInfo ) { if ( isOpenScriptInfo ( info ) && ! configFileExistenceInfo . openFilesImpactedByConfigFile ?. has ( info . path ) ) { ( configFileExistenceInfo . openFilesImpactedByConfigFile ||= new Map ( ) ) . set ( info . path , false ) ; } return configFileExistenceInfo . exists ; } const exists = this . host . fileExists ( configFileName ) ; let openFilesImpactedByConfigFile ; if ( isOpenScriptInfo ( info ) ) { ( openFilesImpactedByConfigFile ||= new Map ( ) ) . set ( info . path , false ) ; } configFileExistenceInfo = { exists , openFilesImpactedByConfigFile } ; this . configFileExistenceInfoCache . set ( canonicalConfigFilePath , configFileExistenceInfo ) ; return exists ; } private createConfigFileWatcherForParsedConfig ( configFileName , canonicalConfigFilePath , forProject ) { const configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ! ; if ( ! configFileExistenceInfo . watcher || configFileExistenceInfo . watcher === noopConfigFileWatcher ) { configFileExistenceInfo . watcher = this . watchFactory . watchFile ( configFileName , ( _fileName , eventKind ) => this . onConfigFileChanged ( canonicalConfigFilePath , eventKind ) , PollingInterval . High , this . getWatchOptionsFromProjectWatchOptions ( configFileExistenceInfo ?. config ?. parsedCommandLine ?. watchOptions ) , WatchType . ConfigFile , forProject ) ; } const projects = configFileExistenceInfo . config ! . projects ; projects . set ( forProject . canonicalConfigFilePath , projects . get ( forProject . canonicalConfigFilePath ) || false ) ; } /**
         * Returns true if the configFileExistenceInfo is needed/impacted by open files that are root of inferred project
         */ private configFileExistenceImpactsRootOfInferredProject ( configFileExistenceInfo ) { return configFileExistenceInfo . openFilesImpactedByConfigFile && forEachEntry ( configFileExistenceInfo . openFilesImpactedByConfigFile , identity ) ; } releaseParsedConfig ( canonicalConfigFilePath , forProject ) { const configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ! ; if ( ! configFileExistenceInfo . config ?. projects . delete ( forProject . canonicalConfigFilePath ) ) return ; if ( configFileExistenceInfo . config ?. projects . size ) return ; configFileExistenceInfo . config = undefined ; clearSharedExtendedConfigFileWatcher ( canonicalConfigFilePath , this . sharedExtendedConfigFileWatchers ) ; Debug . checkDefined ( configFileExistenceInfo . watcher ) ; if ( configFileExistenceInfo . openFilesImpactedByConfigFile ?. size ) { if ( this . configFileExistenceImpactsRootOfInferredProject ( configFileExistenceInfo ) ) { if ( ! canWatchDirectory ( getDirectoryPath ( canonicalConfigFilePath ) as Path ) ) { configFileExistenceInfo . watcher ! . close ( ) ; configFileExistenceInfo . watcher = noopConfigFileWatcher ; } } else { configFileExistenceInfo . watcher ! . close ( ) ; configFileExistenceInfo . watcher = undefined ; } } else { configFileExistenceInfo . watcher ! . close ( ) ; this . configFileExistenceInfoCache . delete ( canonicalConfigFilePath ) ; } } /**
         * Close the config file watcher in the cached ConfigFileExistenceInfo
         *   if there arent any open files that are root of inferred project and there is no parsed config held by any project
         */ private closeConfigFileWatcherOnReleaseOfOpenFile ( configFileExistenceInfo ) { if ( configFileExistenceInfo . watcher && ! configFileExistenceInfo . config && ! this . configFileExistenceImpactsRootOfInferredProject ( configFileExistenceInfo ) ) { configFileExistenceInfo . watcher . close ( ) ; configFileExistenceInfo . watcher = undefined ; } } /**
         * This is called on file close, so that we stop watching the config file for this script info
         */ private stopWatchingConfigFilesForClosedScriptInfo ( info ) { Debug . assert ( ! info . isScriptOpen ( ) ) ; this . forEachConfigFileLocation ( info , canonicalConfigFilePath => { const configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ; if ( configFileExistenceInfo ) { const infoIsRootOfInferredProject = configFileExistenceInfo . openFilesImpactedByConfigFile ?. get ( info . path ) ; configFileExistenceInfo . openFilesImpactedByConfigFile ?. delete ( info . path ) ; if ( infoIsRootOfInferredProject ) { this . closeConfigFileWatcherOnReleaseOfOpenFile ( configFileExistenceInfo ) ; } if ( ! configFileExistenceInfo . openFilesImpactedByConfigFile ?. size && ! configFileExistenceInfo . config ) { Debug . assert ( ! configFileExistenceInfo . watcher ) ; this . configFileExistenceInfoCache . delete ( canonicalConfigFilePath ) ; } } } ) ; } /**
         * This is called by inferred project whenever script info is added as a root
         */ startWatchingConfigFilesForInferredProjectRoot ( info ) { Debug . assert ( info . isScriptOpen ( ) ) ; this . forEachConfigFileLocation ( info , ( canonicalConfigFilePath , configFileName ) => { let configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ; if ( ! configFileExistenceInfo ) { configFileExistenceInfo = { exists : this . host . fileExists ( configFileName ) } ; this . configFileExistenceInfoCache . set ( canonicalConfigFilePath , configFileExistenceInfo ) ; } ( configFileExistenceInfo . openFilesImpactedByConfigFile ||= new Map ( ) ) . set ( info . path , true ) ; configFileExistenceInfo . watcher ||= canWatchDirectory ( getDirectoryPath ( canonicalConfigFilePath ) as Path ) ? this . watchFactory . watchFile ( configFileName , ( _filename , eventKind ) => this . onConfigFileChanged ( canonicalConfigFilePath , eventKind ) , PollingInterval . High , this . hostConfiguration . watchOptions , WatchType . ConfigFileForInferredRoot ) : noopConfigFileWatcher ; } ) ; } /**
         * This is called by inferred project whenever root script info is removed from it
         */ stopWatchingConfigFilesForInferredProjectRoot ( info ) { this . forEachConfigFileLocation ( info , canonicalConfigFilePath => { const configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ; if ( configFileExistenceInfo ?. openFilesImpactedByConfigFile ?. has ( info . path ) ) { Debug . assert ( info . isScriptOpen ( ) ) ; configFileExistenceInfo . openFilesImpactedByConfigFile . set ( info . path , false ) ; this . closeConfigFileWatcherOnReleaseOfOpenFile ( configFileExistenceInfo ) ; } } ) ; } /**
         * This function tries to search for a tsconfig.json for the given file.
         * This is different from the method the compiler uses because
         * the compiler can assume it will always start searching in the
         * current directory (the directory in which tsc was invoked).
         * The server must start searching from the directory containing
         * the newly opened file.
         */ private forEachConfigFileLocation ( info , action ) { if ( this . serverMode !== LanguageServiceMode . Semantic ) { return undefined ; } Debug . assert ( ! isOpenScriptInfo ( info ) || this . openFiles . has ( info . path ) ) ; const projectRootPath = this . openFiles . get ( info . path ) ; const scriptInfo = Debug . checkDefined ( this . getScriptInfo ( info . path ) ) ; if ( scriptInfo . isDynamic ) return undefined ; let searchPath = asNormalizedPath ( getDirectoryPath ( info . fileName ) ) ; const isSearchPathInProjectRoot = ( ) => containsPath ( projectRootPath ! , searchPath , this . currentDirectory , ! this . host . useCaseSensitiveFileNames ) ; const anySearchPathOk = ! projectRootPath || ! isSearchPathInProjectRoot ( ) ; let searchInDirectory = ! isAncestorConfigFileInfo ( info ) ; do { if ( searchInDirectory ) { const canonicalSearchPath = normalizedPathToPath ( searchPath , this . currentDirectory , this . toCanonicalFileName ) ; const tsconfigFileName = asNormalizedPath ( combinePaths ( searchPath , "tsconfig.json" ) ) ; let result = action ( combinePaths ( canonicalSearchPath , "tsconfig.json" ) as NormalizedPath , tsconfigFileName ) ; if ( result ) return tsconfigFileName ; const jsconfigFileName = asNormalizedPath ( combinePaths ( searchPath , "jsconfig.json" ) ) ; result = action ( combinePaths ( canonicalSearchPath , "jsconfig.json" ) as NormalizedPath , jsconfigFileName ) ; if ( result ) return jsconfigFileName ; if ( isNodeModulesDirectory ( canonicalSearchPath ) ) { break ; } } const parentPath = asNormalizedPath ( getDirectoryPath ( searchPath ) ) ; if ( parentPath === searchPath ) break ; searchPath = parentPath ; searchInDirectory = true ; } while ( anySearchPathOk || isSearchPathInProjectRoot ( ) ) ; return undefined ; } findDefaultConfiguredProject ( info ) { if ( ! info . isScriptOpen ( ) ) return undefined ; const configFileName = this . getConfigFileNameForFile ( info ) ; const project = configFileName && this . findConfiguredProjectByProjectName ( configFileName ) ; return project && projectContainsInfoDirectly ( project , info ) ? project : project ?. getDefaultChildProjectFromProjectWithReferences ( info ) ; } /**
         * This function tries to search for a tsconfig.json for the given file.
         * This is different from the method the compiler uses because
         * the compiler can assume it will always start searching in the
         * current directory (the directory in which tsc was invoked).
         * The server must start searching from the directory containing
         * the newly opened file.
         * If script info is passed in, it is asserted to be open script info
         * otherwise just file name
         */ private getConfigFileNameForFile ( info ) { if ( isOpenScriptInfo ( info ) ) { Debug . assert ( info . isScriptOpen ( ) ) ; const result = this . configFileForOpenFiles . get ( info . path ) ; if ( result !== undefined ) return result || undefined ; } this . logger . info ( `Search path: ${ getDirectoryPath ( info . fileName ) }` ) ; const configFileName = this . forEachConfigFileLocation ( info , ( canonicalConfigFilePath , configFileName ) => this . configFileExists ( configFileName , canonicalConfigFilePath , info ) ) ; if ( configFileName ) { this . logger . info ( `For info: ${ info . fileName } :: Config file name: ${ configFileName }` ) ; } else { this . logger . info ( `For info: ${ info . fileName } :: No config files found.` ) ; } if ( isOpenScriptInfo ( info ) ) { this . configFileForOpenFiles . set ( info . path , configFileName || false ) ; } return configFileName ; } private printProjects ( ) { if ( ! this . logger . hasLevel ( LogLevel . normal ) ) { return ; } this . logger . startGroup ( ) ; this . externalProjects . forEach ( printProjectWithoutFileNames ) ; this . configuredProjects . forEach ( printProjectWithoutFileNames ) ; this . inferredProjects . forEach ( printProjectWithoutFileNames ) ; this . logger . info ( "Open files: " ) ; this . openFiles . forEach ( ( projectRootPath , path ) => { const info = this . getScriptInfoForPath ( path as Path ) ! ; this . logger . info ( `\tFileName: ${ info . fileName } ProjectRootPath: ${ projectRootPath }` ) ; this . logger . info ( `\t\tProjects: ${ info . containingProjects . map ( p => p . getProjectName ( ) ) }` ) ; } ) ; this . logger . endGroup ( ) ; } findConfiguredProjectByProjectName ( configFileName ) { const canonicalConfigFilePath = asNormalizedPath ( this . toCanonicalFileName ( configFileName ) ) ; return this . getConfiguredProjectByCanonicalConfigFilePath ( canonicalConfigFilePath ) ; } private getConfiguredProjectByCanonicalConfigFilePath ( canonicalConfigFilePath ) { return this . configuredProjects . get ( canonicalConfigFilePath ) ; } private findExternalProjectByProjectName ( projectFileName ) { return findProjectByName ( projectFileName , this . externalProjects ) ; } /** Get a filename if the language service exceeds the maximum allowed program size; otherwise returns undefined. */ private getFilenameForExceededTotalSizeLimitForNonTsFiles < T > ( name , options , fileNames , propertyReader ) { if ( options && options . disableSizeLimit || ! this . host . getFileSize ) { return ; } let availableSpace = maxProgramSizeForNonTsFiles ; this . projectToSizeMap . set ( name , 0 ) ; this . projectToSizeMap . forEach ( val => ( availableSpace -= ( val || 0 ) ) ) ; let totalNonTsFileSize = 0 ; for ( const f of fileNames ) { const fileName = propertyReader . getFileName ( f ) ; if ( hasTSFileExtension ( fileName ) ) { continue ; } totalNonTsFileSize += this . host . getFileSize ( fileName ) ; if ( totalNonTsFileSize > maxProgramSizeForNonTsFiles || totalNonTsFileSize > availableSpace ) { const top5LargestFiles = fileNames . map ( f => propertyReader . getFileName ( f ) ) . filter ( name => ! hasTSFileExtension ( name ) ) . map ( name => ( { name , size : this . host . getFileSize ! ( name ) } ) ) . sort ( ( a , b ) => b . size - a . size ) . slice ( 0 , 5 ) ; this . logger . info ( `Non TS file size exceeded limit (${ totalNonTsFileSize }). Largest files: ${ top5LargestFiles . map ( file => `${ file . name }:${ file . size }` ) . join ( ", " ) }` ) ; return fileName ; } } this . projectToSizeMap . set ( name , totalNonTsFileSize ) ; } private createExternalProject ( projectFileName , files , options , typeAcquisition , excludedFiles ) { const compilerOptions = convertCompilerOptions ( options ) ; const watchOptionsAndErrors = convertWatchOptions ( options , getDirectoryPath ( normalizeSlashes ( projectFileName ) ) ) ; const project = new ExternalProject ( projectFileName , this , this . documentRegistry , compilerOptions , this . getFilenameForExceededTotalSizeLimitForNonTsFiles ( projectFileName , compilerOptions , files , externalFilePropertyReader ) , options . compileOnSave === undefined ? true : options . compileOnSave , undefined , this . currentPluginConfigOverrides , watchOptionsAndErrors ?. watchOptions ) ; project . setProjectErrors ( watchOptionsAndErrors ?. errors ) ; project . excludedFiles = excludedFiles ; this . addFilesToNonInferredProject ( project , files , externalFilePropertyReader , typeAcquisition ) ; this . externalProjects . push ( project ) ; return project ; } sendProjectTelemetry ( project ) { if ( this . seenProjects . has ( project . projectName ) ) { setProjectOptionsUsed ( project ) ; return ; } this . seenProjects . set ( project . projectName , true ) ; if ( ! this . eventHandler || ! this . host . createSHA256Hash ) { setProjectOptionsUsed ( project ) ; return ; } const projectOptions = isConfiguredProject ( project ) ? project . projectOptions as ProjectOptions : undefined ; setProjectOptionsUsed ( project ) ; const data = { projectId : this . host . createSHA256Hash ( project . projectName ) , fileStats : countEachFileTypes ( project . getScriptInfos ( ) , true ) , compilerOptions : convertCompilerOptionsForTelemetry ( project . getCompilationSettings ( ) ) , typeAcquisition : convertTypeAcquisition ( project . getTypeAcquisition ( ) ) , extends : projectOptions && projectOptions . configHasExtendsProperty , files : projectOptions && projectOptions . configHasFilesProperty , include : projectOptions && projectOptions . configHasIncludeProperty , exclude : projectOptions && projectOptions . configHasExcludeProperty , compileOnSave : project . compileOnSaveEnabled , configFileName : configFileName ( ) , projectType : project instanceof ExternalProject ? "external" : "configured" , languageServiceEnabled : project . languageServiceEnabled , version : ts . version , } ; this . eventHandler ( { eventName : ProjectInfoTelemetryEvent , data } ) ; function configFileName ( ) { if ( ! isConfiguredProject ( project ) ) { return "other" ; } return getBaseConfigFileName ( project . getConfigFilePath ( ) ) || "other" ; } function convertTypeAcquisition ( { enable , include , exclude } ) { return { enable , include : include !== undefined && include . length !== 0 , exclude : exclude !== undefined && exclude . length !== 0 , } ; } } private addFilesToNonInferredProject < T > ( project , files , propertyReader , typeAcquisition ) { this . updateNonInferredProjectFiles ( project , files , propertyReader ) ; project . setTypeAcquisition ( typeAcquisition ) ; } createConfiguredProject ( configFileName ) { this . logger . info ( `Creating configuration project ${ configFileName }` ) ; const canonicalConfigFilePath = asNormalizedPath ( this . toCanonicalFileName ( configFileName ) ) ; let configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ; if ( ! configFileExistenceInfo ) { this . configFileExistenceInfoCache . set ( canonicalConfigFilePath , configFileExistenceInfo = { exists : true } ) ; } else { configFileExistenceInfo . exists = true ; } if ( ! configFileExistenceInfo . config ) { configFileExistenceInfo . config = { cachedDirectoryStructureHost : createCachedDirectoryStructureHost ( this . host , this . host . getCurrentDirectory ( ) , this . host . useCaseSensitiveFileNames ) ! , projects : new Map ( ) , reloadLevel : ConfigFileProgramReloadLevel . Full } ; } const project = new ConfiguredProject ( configFileName , canonicalConfigFilePath , this , this . documentRegistry , configFileExistenceInfo . config . cachedDirectoryStructureHost ) ; this . configuredProjects . set ( canonicalConfigFilePath , project ) ; this . createConfigFileWatcherForParsedConfig ( configFileName , canonicalConfigFilePath , project ) ; return project ; } private createConfiguredProjectWithDelayLoad ( configFileName , reason ) { const project = this . createConfiguredProject ( configFileName ) ; project . pendingReload = ConfigFileProgramReloadLevel . Full ; project . pendingReloadReason = reason ; return project ; } createAndLoadConfiguredProject ( configFileName , reason ) { const project = this . createConfiguredProject ( configFileName ) ; this . loadConfiguredProject ( project , reason ) ; return project ; } private createLoadAndUpdateConfiguredProject ( configFileName , reason ) { const project = this . createAndLoadConfiguredProject ( configFileName , reason ) ; project . updateGraph ( ) ; return project ; } /**
         * Read the config file of the project, and update the project root file names.
         */ private loadConfiguredProject ( project , reason ) { this . sendProjectLoadingStartEvent ( project , reason ) ; const configFilename = asNormalizedPath ( normalizePath ( project . getConfigFilePath ( ) ) ) ; const configFileExistenceInfo = this . ensureParsedConfigUptoDate ( configFilename , project . canonicalConfigFilePath , this . configFileExistenceInfoCache . get ( project . canonicalConfigFilePath ) ! , project ) ; const parsedCommandLine = configFileExistenceInfo . config ! . parsedCommandLine ! ; Debug . assert ( ! ! parsedCommandLine . fileNames ) ; const compilerOptions = parsedCommandLine . options ; if ( ! project . projectOptions ) { project . projectOptions = { configHasExtendsProperty : parsedCommandLine . raw . extends !== undefined , configHasFilesProperty : parsedCommandLine . raw . files !== undefined , configHasIncludeProperty : parsedCommandLine . raw . include !== undefined , configHasExcludeProperty : parsedCommandLine . raw . exclude !== undefined } ; } project . canConfigFileJsonReportNoInputFiles = canJsonReportNoInputFiles ( parsedCommandLine . raw ) ; project . setProjectErrors ( parsedCommandLine . options . configFile ! . parseDiagnostics ) ; project . updateReferences ( parsedCommandLine . projectReferences ) ; const lastFileExceededProgramSize = this . getFilenameForExceededTotalSizeLimitForNonTsFiles ( project . canonicalConfigFilePath , compilerOptions , parsedCommandLine . fileNames , fileNamePropertyReader ) ; if ( lastFileExceededProgramSize ) { project . disableLanguageService ( lastFileExceededProgramSize ) ; this . configFileExistenceInfoCache . forEach ( ( _configFileExistenceInfo , canonicalConfigFilePath ) => this . stopWatchingWildCards ( canonicalConfigFilePath , project ) ) ; } else { project . setCompilerOptions ( compilerOptions ) ; project . setWatchOptions ( parsedCommandLine . watchOptions ) ; project . enableLanguageService ( ) ; this . watchWildcards ( configFilename , configFileExistenceInfo , project ) ; } project . enablePluginsWithOptions ( compilerOptions , this . currentPluginConfigOverrides ) ; const filesToAdd = parsedCommandLine . fileNames . concat ( project . getExternalFiles ( ) ) ; this . updateRootAndOptionsOfNonInferredProject ( project , filesToAdd , fileNamePropertyReader , compilerOptions , parsedCommandLine . typeAcquisition ! , parsedCommandLine . compileOnSave , parsedCommandLine . watchOptions ) ; } ensureParsedConfigUptoDate ( configFilename , canonicalConfigFilePath , configFileExistenceInfo , forProject ) { if ( configFileExistenceInfo . config ) { if ( ! configFileExistenceInfo . config . reloadLevel ) return configFileExistenceInfo ; if ( configFileExistenceInfo . config . reloadLevel === ConfigFileProgramReloadLevel . Partial ) { this . reloadFileNamesOfParsedConfig ( configFilename , configFileExistenceInfo . config ) ; return configFileExistenceInfo ; } } const cachedDirectoryStructureHost = configFileExistenceInfo . config ?. cachedDirectoryStructureHost || createCachedDirectoryStructureHost ( this . host , this . host . getCurrentDirectory ( ) , this . host . useCaseSensitiveFileNames ) ! ; const configFileContent = tryReadFile ( configFilename , fileName => this . host . readFile ( fileName ) ) ; const configFile = parseJsonText ( configFilename , isString ( configFileContent ) ? configFileContent : "" ) as TsConfigSourceFile ; const configFileErrors = configFile . parseDiagnostics as Diagnostic [ ] ; if ( ! isString ( configFileContent ) ) configFileErrors . push ( configFileContent ) ; const parsedCommandLine = parseJsonSourceFileConfigFileContent ( configFile , cachedDirectoryStructureHost , getDirectoryPath ( configFilename ) , { } , configFilename , [ ] , this . hostConfiguration . extraFileExtensions , this . extendedConfigCache ) ; if ( parsedCommandLine . errors . length ) { configFileErrors . push ( ... parsedCommandLine . errors ) ; } this . logger . info ( `Config: ${ configFilename } : ${ JSON . stringify ( { rootNames : parsedCommandLine . fileNames , options : parsedCommandLine . options , watchOptions : parsedCommandLine . watchOptions , projectReferences : parsedCommandLine . projectReferences } , undefined , " " ) }` ) ; const oldCommandLine = configFileExistenceInfo . config ?. parsedCommandLine ; if ( ! configFileExistenceInfo . config ) { configFileExistenceInfo . config = { parsedCommandLine , cachedDirectoryStructureHost , projects : new Map ( ) } ; } else { configFileExistenceInfo . config . parsedCommandLine = parsedCommandLine ; configFileExistenceInfo . config . watchedDirectoriesStale = true ; configFileExistenceInfo . config . reloadLevel = undefined ; } if ( ! oldCommandLine && ! isJsonEqual ( this . getWatchOptionsFromProjectWatchOptions ( undefined ) , this . getWatchOptionsFromProjectWatchOptions ( parsedCommandLine . watchOptions ) ) ) { configFileExistenceInfo . watcher ?. close ( ) ; configFileExistenceInfo . watcher = undefined ; } this . createConfigFileWatcherForParsedConfig ( configFilename , canonicalConfigFilePath , forProject ) ; updateSharedExtendedConfigFileWatcher ( canonicalConfigFilePath , parsedCommandLine . options , this . sharedExtendedConfigFileWatchers , ( extendedConfigFileName , extendedConfigFilePath ) => this . watchFactory . watchFile ( extendedConfigFileName , ( ) => { cleanExtendedConfigCache ( this . extendedConfigCache , extendedConfigFilePath , fileName => this . toPath ( fileName ) ) ; let ensureProjectsForOpenFiles = false ; this . sharedExtendedConfigFileWatchers . get ( extendedConfigFilePath ) ?. projects . forEach ( canonicalPath => { ensureProjectsForOpenFiles = this . delayUpdateProjectsFromParsedConfigOnConfigFileChange ( canonicalPath , `Change in extended config file ${ extendedConfigFileName } detected` ) || ensureProjectsForOpenFiles ; } ) ; if ( ensureProjectsForOpenFiles ) this . delayEnsureProjectForOpenFiles ( ) ; } , PollingInterval . High , this . hostConfiguration . watchOptions , WatchType . ExtendedConfigFile , configFilename ) , fileName => this . toPath ( fileName ) ) ; return configFileExistenceInfo ; } watchWildcards ( configFileName , { exists , config } , forProject ) { config ! . projects . set ( forProject . canonicalConfigFilePath , true ) ; if ( exists ) { if ( config ! . watchedDirectories && ! config ! . watchedDirectoriesStale ) return ; config ! . watchedDirectoriesStale = false ; updateWatchingWildcardDirectories ( config ! . watchedDirectories ||= new Map ( ) , new Map ( getEntries ( config ! . parsedCommandLine ! . wildcardDirectories ! ) ) , ( directory , flags ) => this . watchWildcardDirectory ( directory as Path , flags , configFileName , config ! ) ) ; } else { config ! . watchedDirectoriesStale = false ; if ( ! config ! . watchedDirectories ) return ; clearMap ( config ! . watchedDirectories , closeFileWatcherOf ) ; config ! . watchedDirectories = undefined ; } } stopWatchingWildCards ( canonicalConfigFilePath , forProject ) { const configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ! ; if ( ! configFileExistenceInfo . config || ! configFileExistenceInfo . config . projects . get ( forProject . canonicalConfigFilePath ) ) { return ; } configFileExistenceInfo . config . projects . set ( forProject . canonicalConfigFilePath , false ) ; if ( forEachEntry ( configFileExistenceInfo . config . projects , identity ) ) return ; if ( configFileExistenceInfo . config . watchedDirectories ) { clearMap ( configFileExistenceInfo . config . watchedDirectories , closeFileWatcherOf ) ; configFileExistenceInfo . config . watchedDirectories = undefined ; } configFileExistenceInfo . config . watchedDirectoriesStale = undefined ; } private updateNonInferredProjectFiles < T > ( project , files , propertyReader ) { const projectRootFilesMap = project . getRootFilesMap ( ) ; const newRootScriptInfoMap = new Map < string , true > ( ) ; for ( const f of files ) { const newRootFile = propertyReader . getFileName ( f ) ; const fileName = toNormalizedPath ( newRootFile ) ; const isDynamic = isDynamicFileName ( fileName ) ; let path ; if ( ! isDynamic && ! project . fileExists ( newRootFile ) ) { path = normalizedPathToPath ( fileName , this . currentDirectory , this . toCanonicalFileName ) ; const existingValue = projectRootFilesMap . get ( path ) ; if ( existingValue ) { if ( existingValue . info ) { project . removeFile ( existingValue . info , false , true ) ; existingValue . info = undefined ; } existingValue . fileName = fileName ; } else { projectRootFilesMap . set ( path , { fileName } ) ; } } else { const scriptKind = propertyReader . getScriptKind ( f , this . hostConfiguration . extraFileExtensions ) ; const hasMixedContent = propertyReader . hasMixedContent ( f , this . hostConfiguration . extraFileExtensions ) ; const scriptInfo = Debug . checkDefined ( this . getOrCreateScriptInfoNotOpenedByClientForNormalizedPath ( fileName , project . currentDirectory , scriptKind , hasMixedContent , project . directoryStructureHost ) ) ; path = scriptInfo . path ; const existingValue = projectRootFilesMap . get ( path ) ; if ( ! existingValue || existingValue . info !== scriptInfo ) { project . addRoot ( scriptInfo , fileName ) ; if ( scriptInfo . isScriptOpen ( ) ) { this . removeRootOfInferredProjectIfNowPartOfOtherProject ( scriptInfo ) ; } } else { existingValue . fileName = fileName ; } } newRootScriptInfoMap . set ( path , true ) ; } if ( projectRootFilesMap . size > newRootScriptInfoMap . size ) { projectRootFilesMap . forEach ( ( value , path ) => { if ( ! newRootScriptInfoMap . has ( path ) ) { if ( value . info ) { project . removeFile ( value . info , project . fileExists ( path ) , true ) ; } else { projectRootFilesMap . delete ( path ) ; } } } ) ; } project . markAsDirty ( ) ; } private updateRootAndOptionsOfNonInferredProject < T > ( project , newUncheckedFiles , propertyReader , newOptions , newTypeAcquisition , compileOnSave , watchOptions ) { project . setCompilerOptions ( newOptions ) ; project . setWatchOptions ( watchOptions ) ; if ( compileOnSave !== undefined ) { project . compileOnSaveEnabled = compileOnSave ; } this . addFilesToNonInferredProject ( project , newUncheckedFiles , propertyReader , newTypeAcquisition ) ; } /**
         * Reload the file names from config file specs and update the project graph
         */ reloadFileNamesOfConfiguredProject ( project ) { const fileNames = this . reloadFileNamesOfParsedConfig ( project . getConfigFilePath ( ) , this . configFileExistenceInfoCache . get ( project . canonicalConfigFilePath ) ! . config ! ) ; project . updateErrorOnNoInputFiles ( fileNames ) ; this . updateNonInferredProjectFiles ( project , fileNames . concat ( project . getExternalFiles ( ) ) , fileNamePropertyReader ) ; return project . updateGraph ( ) ; } private reloadFileNamesOfParsedConfig ( configFileName , config ) { if ( config . reloadLevel === undefined ) return config . parsedCommandLine ! . fileNames ; Debug . assert ( config . reloadLevel === ConfigFileProgramReloadLevel . Partial ) ; const configFileSpecs = config . parsedCommandLine ! . options . configFile ! . configFileSpecs ! ; const fileNames = getFileNamesFromConfigSpecs ( configFileSpecs , getDirectoryPath ( configFileName ) , config . parsedCommandLine ! . options , config . cachedDirectoryStructureHost , this . hostConfiguration . extraFileExtensions ) ; config . parsedCommandLine = { ... config . parsedCommandLine ! , fileNames } ; return fileNames ; } setFileNamesOfAutoImportProviderProject ( project , fileNames ) { this . updateNonInferredProjectFiles ( project , fileNames , fileNamePropertyReader ) ; } /**
         * Read the config file of the project again by clearing the cache and update the project graph
         */ reloadConfiguredProject ( project , reason , isInitialLoad , clearSemanticCache ) { const host = project . getCachedDirectoryStructureHost ( ) ; if ( clearSemanticCache ) this . clearSemanticCache ( project ) ; host . clearCache ( ) ; const configFileName = project . getConfigFilePath ( ) ; this . logger . info ( `${ isInitialLoad ? "Loading" : "Reloading" } configured project ${ configFileName }` ) ; this . loadConfiguredProject ( project , reason ) ; project . updateGraph ( ) ; this . sendConfigFileDiagEvent ( project , configFileName ) ; } private clearSemanticCache ( project ) { project . resolutionCache . clear ( ) ; project . getLanguageService ( false ) . cleanupSemanticCache ( ) ; project . markAsDirty ( ) ; } private sendConfigFileDiagEvent ( project , triggerFile ) { if ( ! this . eventHandler || this . suppressDiagnosticEvents ) { return ; } const diagnostics = project . getLanguageService ( ) . getCompilerOptionsDiagnostics ( ) ; diagnostics . push ( ... project . getAllProjectErrors ( ) ) ; this . eventHandler ( { eventName : ConfigFileDiagEvent , data : { configFileName : project . getConfigFilePath ( ) , diagnostics , triggerFile } } as ConfigFileDiagEvent ) ; } private getOrCreateInferredProjectForProjectRootPathIfEnabled ( info , projectRootPath ) { if ( ! this . useInferredProjectPerProjectRoot || ( info . isDynamic && projectRootPath === undefined ) ) { return undefined ; } if ( projectRootPath ) { const canonicalProjectRootPath = this . toCanonicalFileName ( projectRootPath ) ; for ( const project of this . inferredProjects ) { if ( project . projectRootPath === canonicalProjectRootPath ) { return project ; } } return this . createInferredProject ( projectRootPath , false , projectRootPath ) ; } let bestMatch ; for ( const project of this . inferredProjects ) { if ( ! project . projectRootPath ) continue ; if ( ! containsPath ( project . projectRootPath , info . path , this . host . getCurrentDirectory ( ) , ! this . host . useCaseSensitiveFileNames ) ) continue ; if ( bestMatch && bestMatch . projectRootPath ! . length > project . projectRootPath . length ) continue ; bestMatch = project ; } return bestMatch ; } private getOrCreateSingleInferredProjectIfEnabled ( ) { if ( ! this . useSingleInferredProject ) { return undefined ; } if ( this . inferredProjects . length > 0 && this . inferredProjects [ 0 ] . projectRootPath === undefined ) { return this . inferredProjects [ 0 ] ; } return this . createInferredProject ( undefined , true ) ; } private getOrCreateSingleInferredWithoutProjectRoot ( currentDirectory ) { Debug . assert ( ! this . useSingleInferredProject ) ; const expectedCurrentDirectory = this . toCanonicalFileName ( this . getNormalizedAbsolutePath ( currentDirectory || "" ) ) ; for ( const inferredProject of this . inferredProjects ) { if ( ! inferredProject . projectRootPath && inferredProject . isOrphan ( ) && inferredProject . canonicalCurrentDirectory === expectedCurrentDirectory ) { return inferredProject ; } } return this . createInferredProject ( currentDirectory ) ; } private createInferredProject ( currentDirectory , isSingleInferredProject ? , projectRootPath ? ) { const compilerOptions = projectRootPath && this . compilerOptionsForInferredProjectsPerProjectRoot . get ( projectRootPath ) || this . compilerOptionsForInferredProjects ! ; let watchOptionsAndErrors ; let typeAcquisition ; if ( projectRootPath ) { watchOptionsAndErrors = this . watchOptionsForInferredProjectsPerProjectRoot . get ( projectRootPath ) ; typeAcquisition = this . typeAcquisitionForInferredProjectsPerProjectRoot . get ( projectRootPath ) ; } if ( watchOptionsAndErrors === undefined ) { watchOptionsAndErrors = this . watchOptionsForInferredProjects ; } if ( typeAcquisition === undefined ) { typeAcquisition = this . typeAcquisitionForInferredProjects ; } watchOptionsAndErrors = watchOptionsAndErrors || undefined ; const project = new InferredProject ( this , this . documentRegistry , compilerOptions , watchOptionsAndErrors ?. watchOptions , projectRootPath , currentDirectory , this . currentPluginConfigOverrides , typeAcquisition ) ; project . setProjectErrors ( watchOptionsAndErrors ?. errors ) ; if ( isSingleInferredProject ) { this . inferredProjects . unshift ( project ) ; } else { this . inferredProjects . push ( project ) ; } return project ; } getOrCreateScriptInfoNotOpenedByClient ( uncheckedFileName , currentDirectory , hostToQueryFileExistsOn ) { return this . getOrCreateScriptInfoNotOpenedByClientForNormalizedPath ( toNormalizedPath ( uncheckedFileName ) , currentDirectory , undefined , undefined , hostToQueryFileExistsOn ) ; } getScriptInfo ( uncheckedFileName ) { return this . getScriptInfoForNormalizedPath ( toNormalizedPath ( uncheckedFileName ) ) ; } getScriptInfoOrConfig ( uncheckedFileName ) { const path = toNormalizedPath ( uncheckedFileName ) ; const info = this . getScriptInfoForNormalizedPath ( path ) ; if ( info ) return info ; const configProject = this . configuredProjects . get ( this . toPath ( uncheckedFileName ) ) ; return configProject && configProject . getCompilerOptions ( ) . configFile ; } logErrorForScriptInfoNotFound ( fileName ) { const names = arrayFrom ( this . filenameToScriptInfo . entries ( ) ) . map ( ( [ path , scriptInfo ] ) => ( { path , fileName : scriptInfo . fileName } ) ) ; this . logger . msg ( `Could not find file ${ JSON . stringify ( fileName ) }.\nAll files are: ${ JSON . stringify ( names ) }` , Msg . Err ) ; } /**
         * Returns the projects that contain script info through SymLink
         * Note that this does not return projects in info.containingProjects
         */ getSymlinkedProjects ( info ) { let projects ; if ( this . realpathToScriptInfos ) { const realpath = info . getRealpathIfDifferent ( ) ; if ( realpath ) { forEach ( this . realpathToScriptInfos . get ( realpath ) , combineProjects ) ; } forEach ( this . realpathToScriptInfos . get ( info . path ) , combineProjects ) ; } return projects ; function combineProjects ( toAddInfo ) { if ( toAddInfo !== info ) { for ( const project of toAddInfo . containingProjects ) { if ( project . languageServiceEnabled && ! project . isOrphan ( ) && ! project . getCompilerOptions ( ) . preserveSymlinks && ! info . isAttached ( project ) ) { if ( ! projects ) { projects = createMultiMap ( ) ; projects . add ( toAddInfo . path , project ) ; } else if ( ! forEachEntry ( projects , ( projs , path ) => path === toAddInfo . path ? false : contains ( projs , project ) ) ) { projects . add ( toAddInfo . path , project ) ; } } } } } } private watchClosedScriptInfo ( info ) { Debug . assert ( ! info . fileWatcher ) ; if ( ! info . isDynamicOrHasMixedContent ( ) && ( ! this . globalCacheLocationDirectoryPath || ! startsWith ( info . path , this . globalCacheLocationDirectoryPath ) ) ) { const indexOfNodeModules = info . path . indexOf ( "/node_modules/" ) ; if ( ! this . host . getModifiedTime || indexOfNodeModules === - 1 ) { info . fileWatcher = this . watchFactory . watchFile ( info . fileName , ( _fileName , eventKind ) => this . onSourceFileChanged ( info , eventKind ) , PollingInterval . Medium , this . hostConfiguration . watchOptions , WatchType . ClosedScriptInfo ) ; } else { info . mTime = this . getModifiedTime ( info ) ; info . fileWatcher = this . watchClosedScriptInfoInNodeModules ( info . path . substr ( 0 , indexOfNodeModules ) as Path ) ; } } } private createNodeModulesWatcher ( dir ) { const watcher = this . watchFactory . watchDirectory ( dir , fileOrDirectory => { const fileOrDirectoryPath = removeIgnoredPath ( this . toPath ( fileOrDirectory ) ) ; if ( ! fileOrDirectoryPath ) return ; const basename = getBaseFileName ( fileOrDirectoryPath ) ; if ( result . affectedModuleSpecifierCacheProjects ?. size && ( basename === "package.json" || basename === "node_modules" ) ) { result . affectedModuleSpecifierCacheProjects . forEach ( projectName => { this . findProject ( projectName ) ?. getModuleSpecifierCache ( ) ?. clear ( ) ; } ) ; } if ( result . refreshScriptInfoRefCount ) { if ( dir === fileOrDirectoryPath ) { this . refreshScriptInfosInDirectory ( dir ) ; } else { const info = this . getScriptInfoForPath ( fileOrDirectoryPath ) ; if ( info ) { if ( isScriptInfoWatchedFromNodeModules ( info ) ) { this . refreshScriptInfo ( info ) ; } } else if ( ! hasExtension ( fileOrDirectoryPath ) ) { this . refreshScriptInfosInDirectory ( fileOrDirectoryPath ) ; } } } } , WatchDirectoryFlags . Recursive , this . hostConfiguration . watchOptions , WatchType . NodeModules ) ; const result = { refreshScriptInfoRefCount : 0 , affectedModuleSpecifierCacheProjects : undefined , close : ( ) => { if ( ! result . refreshScriptInfoRefCount && ! result . affectedModuleSpecifierCacheProjects ?. size ) { watcher . close ( ) ; this . nodeModulesWatchers . delete ( dir ) ; } } , } ; this . nodeModulesWatchers . set ( dir , result ) ; return result ; } watchPackageJsonsInNodeModules ( dir , project ) { const watcher = this . nodeModulesWatchers . get ( dir ) || this . createNodeModulesWatcher ( dir ) ; ( watcher . affectedModuleSpecifierCacheProjects ||= new Set ( ) ) . add ( project . getProjectName ( ) ) ; return { close : ( ) => { watcher . affectedModuleSpecifierCacheProjects ?. delete ( project . getProjectName ( ) ) ; watcher . close ( ) ; } , } ; } private watchClosedScriptInfoInNodeModules ( dir ) { const watchDir = dir + "/node_modules" as Path ; const watcher = this . nodeModulesWatchers . get ( watchDir ) || this . createNodeModulesWatcher ( watchDir ) ; watcher . refreshScriptInfoRefCount ++ ; return { close : ( ) => { watcher . refreshScriptInfoRefCount -- ; watcher . close ( ) ; } , } ; } private getModifiedTime ( info ) { return ( this . host . getModifiedTime ! ( info . path ) || missingFileModifiedTime ) . getTime ( ) ; } private refreshScriptInfo ( info ) { const mTime = this . getModifiedTime ( info ) ; if ( mTime !== info . mTime ) { const eventKind = getFileWatcherEventKind ( info . mTime ! , mTime ) ; info . mTime = mTime ; this . onSourceFileChanged ( info , eventKind ) ; } } private refreshScriptInfosInDirectory ( dir ) { dir = dir + directorySeparator as Path ; this . filenameToScriptInfo . forEach ( info => { if ( isScriptInfoWatchedFromNodeModules ( info ) && startsWith ( info . path , dir ) ) { this . refreshScriptInfo ( info ) ; } } ) ; } private stopWatchingScriptInfo ( info ) { if ( info . fileWatcher ) { info . fileWatcher . close ( ) ; info . fileWatcher = undefined ; } } private getOrCreateScriptInfoNotOpenedByClientForNormalizedPath ( fileName , currentDirectory , scriptKind , hasMixedContent , hostToQueryFileExistsOn ) { if ( isRootedDiskPath ( fileName ) || isDynamicFileName ( fileName ) ) { return this . getOrCreateScriptInfoWorker ( fileName , currentDirectory , false , undefined , scriptKind , hasMixedContent , hostToQueryFileExistsOn ) ; } const info = this . openFilesWithNonRootedDiskPath . get ( this . toCanonicalFileName ( fileName ) ) ; if ( info ) { return info ; } return undefined ; } private getOrCreateScriptInfoOpenedByClientForNormalizedPath ( fileName , currentDirectory , fileContent , scriptKind , hasMixedContent ) { return this . getOrCreateScriptInfoWorker ( fileName , currentDirectory , true , fileContent , scriptKind , hasMixedContent ) ; } getOrCreateScriptInfoForNormalizedPath ( fileName , openedByClient , fileContent ? , scriptKind ? , hasMixedContent ? , hostToQueryFileExistsOn ? ) { return this . getOrCreateScriptInfoWorker ( fileName , this . currentDirectory , openedByClient , fileContent , scriptKind , hasMixedContent , hostToQueryFileExistsOn ) ; } private getOrCreateScriptInfoWorker ( fileName , currentDirectory , openedByClient , fileContent ? , scriptKind ? , hasMixedContent ? , hostToQueryFileExistsOn ? ) { Debug . assert ( fileContent === undefined || openedByClient , "ScriptInfo needs to be opened by client to be able to set its user defined content" ) ; const path = normalizedPathToPath ( fileName , currentDirectory , this . toCanonicalFileName ) ; let info = this . getScriptInfoForPath ( path ) ; if ( ! info ) { const isDynamic = isDynamicFileName ( fileName ) ; Debug . assert ( isRootedDiskPath ( fileName ) || isDynamic || openedByClient , "" , ( ) => `${ JSON . stringify ( { fileName , currentDirectory , hostCurrentDirectory : this . currentDirectory , openKeys : arrayFrom ( this . openFilesWithNonRootedDiskPath . keys ( ) ) } ) }\nScript info with non-dynamic relative file name can only be open script info or in context of host currentDirectory` ) ; Debug . assert ( ! isRootedDiskPath ( fileName ) || this . currentDirectory === currentDirectory || ! this . openFilesWithNonRootedDiskPath . has ( this . toCanonicalFileName ( fileName ) ) , "" , ( ) => `${ JSON . stringify ( { fileName , currentDirectory , hostCurrentDirectory : this . currentDirectory , openKeys : arrayFrom ( this . openFilesWithNonRootedDiskPath . keys ( ) ) } ) }\nOpen script files with non rooted disk path opened with current directory context cannot have same canonical names` ) ; Debug . assert ( ! isDynamic || this . currentDirectory === currentDirectory || this . useInferredProjectPerProjectRoot , "" , ( ) => `${ JSON . stringify ( { fileName , currentDirectory , hostCurrentDirectory : this . currentDirectory , openKeys : arrayFrom ( this . openFilesWithNonRootedDiskPath . keys ( ) ) } ) }\nDynamic files must always be opened with service's current directory or service should support inferred project per projectRootPath.` ) ; if ( ! openedByClient && ! isDynamic && ! ( hostToQueryFileExistsOn || this . host ) . fileExists ( fileName ) ) { return ; } info = new ScriptInfo ( this . host , fileName , scriptKind ! , ! ! hasMixedContent , path , this . filenameToScriptInfoVersion . get ( path ) ) ; this . filenameToScriptInfo . set ( info . path , info ) ; this . filenameToScriptInfoVersion . delete ( info . path ) ; if ( ! openedByClient ) { this . watchClosedScriptInfo ( info ) ; } else if ( ! isRootedDiskPath ( fileName ) && ( ! isDynamic || this . currentDirectory !== currentDirectory ) ) { this . openFilesWithNonRootedDiskPath . set ( this . toCanonicalFileName ( fileName ) , info ) ; } } if ( openedByClient ) { this . stopWatchingScriptInfo ( info ) ; info . open ( fileContent ! ) ; if ( hasMixedContent ) { info . registerFileUpdate ( ) ; } } return info ; } /**
         * This gets the script info for the normalized path. If the path is not rooted disk path then the open script info with project root context is preferred
         */ getScriptInfoForNormalizedPath ( fileName ) { return ! isRootedDiskPath ( fileName ) && this . openFilesWithNonRootedDiskPath . get ( this . toCanonicalFileName ( fileName ) ) || this . getScriptInfoForPath ( normalizedPathToPath ( fileName , this . currentDirectory , this . toCanonicalFileName ) ) ; } getScriptInfoForPath ( fileName ) { return this . filenameToScriptInfo . get ( fileName ) ; } getDocumentPositionMapper ( project , generatedFileName , sourceFileName ? ) { const declarationInfo = this . getOrCreateScriptInfoNotOpenedByClient ( generatedFileName , project . currentDirectory , this . host ) ; if ( ! declarationInfo ) { if ( sourceFileName ) { project . addGeneratedFileWatch ( generatedFileName , sourceFileName ) ; } return undefined ; } declarationInfo . getSnapshot ( ) ; if ( isString ( declarationInfo . sourceMapFilePath ) ) { const sourceMapFileInfo = this . getScriptInfoForPath ( declarationInfo . sourceMapFilePath ) ; if ( sourceMapFileInfo ) { sourceMapFileInfo . getSnapshot ( ) ; if ( sourceMapFileInfo . documentPositionMapper !== undefined ) { sourceMapFileInfo . sourceInfos = this . addSourceInfoToSourceMap ( sourceFileName , project , sourceMapFileInfo . sourceInfos ) ; return sourceMapFileInfo . documentPositionMapper ? sourceMapFileInfo . documentPositionMapper : undefined ; } } declarationInfo . sourceMapFilePath = undefined ; } else if ( declarationInfo . sourceMapFilePath ) { declarationInfo . sourceMapFilePath . sourceInfos = this . addSourceInfoToSourceMap ( sourceFileName , project , declarationInfo . sourceMapFilePath . sourceInfos ) ; return undefined ; } else if ( declarationInfo . sourceMapFilePath !== undefined ) { return undefined ; } let sourceMapFileInfo ; let mapFileNameFromDeclarationInfo ; let readMapFile = ( mapFileName , mapFileNameFromDts ) => { const mapInfo = this . getOrCreateScriptInfoNotOpenedByClient ( mapFileName , project . currentDirectory , this . host ) ; if ( ! mapInfo ) { mapFileNameFromDeclarationInfo = mapFileNameFromDts ; return undefined ; } sourceMapFileInfo = mapInfo ; const snap = mapInfo . getSnapshot ( ) ; if ( mapInfo . documentPositionMapper !== undefined ) return mapInfo . documentPositionMapper ; return getSnapshotText ( snap ) ; } ; const projectName = project . projectName ; const documentPositionMapper = getDocumentPositionMapper ( { getCanonicalFileName : this . toCanonicalFileName , log : s => this . logger . info ( s ) , getSourceFileLike : f => this . getSourceFileLike ( f , projectName , declarationInfo ) } , declarationInfo . fileName , declarationInfo . getLineInfo ( ) , readMapFile ) ; readMapFile = undefined ; if ( sourceMapFileInfo ) { declarationInfo . sourceMapFilePath = sourceMapFileInfo . path ; sourceMapFileInfo . declarationInfoPath = declarationInfo . path ; sourceMapFileInfo . documentPositionMapper = documentPositionMapper || false ; sourceMapFileInfo . sourceInfos = this . addSourceInfoToSourceMap ( sourceFileName , project , sourceMapFileInfo . sourceInfos ) ; } else if ( mapFileNameFromDeclarationInfo ) { declarationInfo . sourceMapFilePath = { watcher : this . addMissingSourceMapFile ( project . currentDirectory === this . currentDirectory ? mapFileNameFromDeclarationInfo : getNormalizedAbsolutePath ( mapFileNameFromDeclarationInfo , project . currentDirectory ) , declarationInfo . path ) , sourceInfos : this . addSourceInfoToSourceMap ( sourceFileName , project ) } ; } else { declarationInfo . sourceMapFilePath = false ; } return documentPositionMapper ; } private addSourceInfoToSourceMap ( sourceFileName , project , sourceInfos ? ) { if ( sourceFileName ) { const sourceInfo = this . getOrCreateScriptInfoNotOpenedByClient ( sourceFileName , project . currentDirectory , project . directoryStructureHost ) ! ; ( sourceInfos || ( sourceInfos = new Set ( ) ) ) . add ( sourceInfo . path ) ; } return sourceInfos ; } private addMissingSourceMapFile ( mapFileName , declarationInfoPath ) { const fileWatcher = this . watchFactory . watchFile ( mapFileName , ( ) => { const declarationInfo = this . getScriptInfoForPath ( declarationInfoPath ) ; if ( declarationInfo && declarationInfo . sourceMapFilePath && ! isString ( declarationInfo . sourceMapFilePath ) ) { this . delayUpdateProjectGraphs ( declarationInfo . containingProjects , true ) ; this . delayUpdateSourceInfoProjects ( declarationInfo . sourceMapFilePath . sourceInfos ) ; declarationInfo . closeSourceMapFileWatcher ( ) ; } } , PollingInterval . High , this . hostConfiguration . watchOptions , WatchType . MissingSourceMapFile ) ; return fileWatcher ; } getSourceFileLike ( fileName , projectNameOrProject , declarationInfo ? ) { const project = ( projectNameOrProject as Project ) . projectName ? projectNameOrProject as Project : this . findProject ( projectNameOrProject as string ) ; if ( project ) { const path = project . toPath ( fileName ) ; const sourceFile = project . getSourceFile ( path ) ; if ( sourceFile && sourceFile . resolvedPath === path ) return sourceFile ; } const info = this . getOrCreateScriptInfoNotOpenedByClient ( fileName , ( project || this ) . currentDirectory , project ? project . directoryStructureHost : this . host ) ; if ( ! info ) return undefined ; if ( declarationInfo && isString ( declarationInfo . sourceMapFilePath ) && info !== declarationInfo ) { const sourceMapInfo = this . getScriptInfoForPath ( declarationInfo . sourceMapFilePath ) ; if ( sourceMapInfo ) { ( sourceMapInfo . sourceInfos || ( sourceMapInfo . sourceInfos = new Set ( ) ) ) . add ( info . path ) ; } } if ( info . cacheSourceFile ) return info . cacheSourceFile . sourceFile ; if ( ! info . sourceFileLike ) { info . sourceFileLike = { get text ( ) { Debug . fail ( "shouldnt need text" ) ; return "" ; } , getLineAndCharacterOfPosition : pos => { const lineOffset = info . positionToLineOffset ( pos ) ; return { line : lineOffset . line - 1 , character : lineOffset . offset - 1 } ; } , getPositionOfLineAndCharacter : ( line , character , allowEdits ) => info . lineOffsetToPosition ( line + 1 , character + 1 , allowEdits ) } ; } return info . sourceFileLike ; } setPerformanceEventHandler ( performanceEventHandler ) { this . performanceEventHandler = performanceEventHandler ; } setHostConfiguration ( args ) { if ( args . file ) { const info = this . getScriptInfoForNormalizedPath ( toNormalizedPath ( args . file ) ) ; if ( info ) { info . setOptions ( convertFormatOptions ( args . formatOptions ! ) , args . preferences ) ; this . logger . info ( `Host configuration update for file ${ args . file }` ) ; } } else { if ( args . hostInfo !== undefined ) { this . hostConfiguration . hostInfo = args . hostInfo ; this . logger . info ( `Host information ${ args . hostInfo }` ) ; } if ( args . formatOptions ) { this . hostConfiguration . formatCodeOptions = { ... this . hostConfiguration . formatCodeOptions , ... convertFormatOptions ( args . formatOptions ) } ; this . logger . info ( "Format host information updated" ) ; } if ( args . preferences ) { const { lazyConfiguredProjectsFromExternalProject , includePackageJsonAutoImports , } = this . hostConfiguration . preferences ; this . hostConfiguration . preferences = { ... this . hostConfiguration . preferences , ... args . preferences } ; if ( lazyConfiguredProjectsFromExternalProject && ! this . hostConfiguration . preferences . lazyConfiguredProjectsFromExternalProject ) { this . configuredProjects . forEach ( project => { if ( project . hasExternalProjectRef ( ) && project . pendingReload === ConfigFileProgramReloadLevel . Full && ! this . pendingProjectUpdates . has ( project . getProjectName ( ) ) ) { project . updateGraph ( ) ; } } ) ; } if ( includePackageJsonAutoImports !== args . preferences . includePackageJsonAutoImports ) { this . invalidateProjectPackageJson ( undefined ) ; } } if ( args . extraFileExtensions ) { this . hostConfiguration . extraFileExtensions = args . extraFileExtensions ; this . reloadProjects ( ) ; this . logger . info ( "Host file extension mappings updated" ) ; } if ( args . watchOptions ) { this . hostConfiguration . watchOptions = convertWatchOptions ( args . watchOptions ) ?. watchOptions ; this . logger . info ( `Host watch options changed to ${ JSON . stringify ( this . hostConfiguration . watchOptions ) }, it will be take effect for next watches.` ) ; } } } getWatchOptions ( project ) { return this . getWatchOptionsFromProjectWatchOptions ( project . getWatchOptions ( ) ) ; } private getWatchOptionsFromProjectWatchOptions ( projectOptions ) { return projectOptions && this . hostConfiguration . watchOptions ? { ... this . hostConfiguration . watchOptions , ... projectOptions } : projectOptions || this . hostConfiguration . watchOptions ; } closeLog ( ) { this . logger . close ( ) ; } /**
         * This function rebuilds the project for every file opened by the client
         * This does not reload contents of open files from disk. But we could do that if needed
         */ reloadProjects ( ) { this . logger . info ( "reload projects." ) ; this . filenameToScriptInfo . forEach ( info => { if ( this . openFiles . has ( info . path ) ) return ; if ( ! info . fileWatcher ) return ; this . onSourceFileChanged ( info , this . host . fileExists ( info . fileName ) ? FileWatcherEventKind . Changed : FileWatcherEventKind . Deleted ) ; } ) ; this . pendingProjectUpdates . forEach ( ( _project , projectName ) => { this . throttledOperations . cancel ( projectName ) ; this . pendingProjectUpdates . delete ( projectName ) ; } ) ; this . throttledOperations . cancel ( ensureProjectForOpenFileSchedule ) ; this . pendingEnsureProjectForOpenFiles = false ; this . configFileExistenceInfoCache . forEach ( info => { if ( info . config ) info . config . reloadLevel = ConfigFileProgramReloadLevel . Full ; } ) ; this . reloadConfiguredProjectForFiles ( this . openFiles as ESMap < Path , NormalizedPath | undefined > , true , false , returnTrue , "User requested reload projects" ) ; this . externalProjects . forEach ( project => { this . clearSemanticCache ( project ) ; project . updateGraph ( ) ; } ) ; this . inferredProjects . forEach ( project => this . clearSemanticCache ( project ) ) ; this . ensureProjectForOpenFiles ( ) ; } /**
         * This function goes through all the openFiles and tries to file the config file for them.
         * If the config file is found and it refers to existing project, it reloads it either immediately
         * or schedules it for reload depending on delayReload option
         * If there is no existing project it just opens the configured project for the config file
         * reloadForInfo provides a way to filter out files to reload configured project for
         */ private reloadConfiguredProjectForFiles < T > ( openFiles , clearSemanticCache , delayReload , shouldReloadProjectFor , reason ) { const updatedProjects = new Map < string , true > ( ) ; const reloadChildProject = ( child ) => { if ( ! updatedProjects . has ( child . canonicalConfigFilePath ) ) { updatedProjects . set ( child . canonicalConfigFilePath , true ) ; this . reloadConfiguredProject ( child , reason , false , clearSemanticCache ) ; } } ; openFiles ?. forEach ( ( openFileValue , path ) => { this . configFileForOpenFiles . delete ( path ) ; if ( ! shouldReloadProjectFor ( openFileValue ) ) { return ; } const info = this . getScriptInfoForPath ( path ) ! ; Debug . assert ( info . isScriptOpen ( ) ) ; const configFileName = this . getConfigFileNameForFile ( info ) ; if ( configFileName ) { const project = this . findConfiguredProjectByProjectName ( configFileName ) || this . createConfiguredProject ( configFileName ) ; if ( ! updatedProjects . has ( project . canonicalConfigFilePath ) ) { updatedProjects . set ( project . canonicalConfigFilePath , true ) ; if ( delayReload ) { project . pendingReload = ConfigFileProgramReloadLevel . Full ; project . pendingReloadReason = reason ; if ( clearSemanticCache ) this . clearSemanticCache ( project ) ; this . delayUpdateProjectGraph ( project ) ; } else { this . reloadConfiguredProject ( project , reason , false , clearSemanticCache ) ; if ( ! projectContainsInfoDirectly ( project , info ) ) { const referencedProject = forEachResolvedProjectReferenceProject ( project , info . path , child => { reloadChildProject ( child ) ; return projectContainsInfoDirectly ( child , info ) ; } , ProjectReferenceProjectLoadKind . FindCreate ) ; if ( referencedProject ) { forEachResolvedProjectReferenceProject ( project , undefined , reloadChildProject , ProjectReferenceProjectLoadKind . Find ) ; } } } } } } ) ; } /**
         * Remove the root of inferred project if script info is part of another project
         */ private removeRootOfInferredProjectIfNowPartOfOtherProject ( info ) { Debug . assert ( info . containingProjects . length > 0 ) ; const firstProject = info . containingProjects [ 0 ] ; if ( ! firstProject . isOrphan ( ) && isInferredProject ( firstProject ) && firstProject . isRoot ( info ) && forEach ( info . containingProjects , p => p !== firstProject && ! p . isOrphan ( ) ) ) { firstProject . removeFile ( info , true , true ) ; } } /**
         * This function is to update the project structure for every inferred project.
         * It is called on the premise that all the configured projects are
         * up to date.
         * This will go through open files and assign them to inferred project if open file is not part of any other project
         * After that all the inferred project graphs are updated
         */ private ensureProjectForOpenFiles ( ) { this . logger . info ( "Before ensureProjectForOpenFiles:" ) ; this . printProjects ( ) ; this . openFiles . forEach ( ( projectRootPath , path ) => { const info = this . getScriptInfoForPath ( path as Path ) ! ; if ( info . isOrphan ( ) ) { this . assignOrphanScriptInfoToInferredProject ( info , projectRootPath ) ; } else { this . removeRootOfInferredProjectIfNowPartOfOtherProject ( info ) ; } } ) ; this . pendingEnsureProjectForOpenFiles = false ; this . inferredProjects . forEach ( updateProjectIfDirty ) ; this . logger . info ( "After ensureProjectForOpenFiles:" ) ; this . printProjects ( ) ; } param filename param fileContent openClientFile ( fileName , fileContent ? , scriptKind ? , projectRootPath ? ) { return this . openClientFileWithNormalizedPath ( toNormalizedPath ( fileName ) , fileContent , scriptKind , false , projectRootPath ? toNormalizedPath ( projectRootPath ) : undefined ) ; } getOriginalLocationEnsuringConfiguredProject ( project , location ) { const isSourceOfProjectReferenceRedirect = project . isSourceOfProjectReferenceRedirect ( location . fileName ) ; const originalLocation = isSourceOfProjectReferenceRedirect ? location : project . getSourceMapper ( ) . tryGetSourcePosition ( location ) ; if ( ! originalLocation ) return undefined ; const { fileName } = originalLocation ; const scriptInfo = this . getScriptInfo ( fileName ) ; if ( ! scriptInfo && ! this . host . fileExists ( fileName ) ) return undefined ; const originalFileInfo = { fileName : toNormalizedPath ( fileName ) , path : this . toPath ( fileName ) } ; const configFileName = this . getConfigFileNameForFile ( originalFileInfo ) ; if ( ! configFileName ) return undefined ; let configuredProject = this . findConfiguredProjectByProjectName ( configFileName ) ; if ( ! configuredProject ) { if ( project . getCompilerOptions ( ) . disableReferencedProjectLoad ) { if ( isSourceOfProjectReferenceRedirect ) { return location ; } return scriptInfo ?. containingProjects . length ? originalLocation : location ; } configuredProject = this . createAndLoadConfiguredProject ( configFileName , `Creating project for original file: ${ originalFileInfo . fileName }${ location !== originalLocation ? " for location: " + location . fileName : "" }` ) ; } updateProjectIfDirty ( configuredProject ) ; const projectContainsOriginalInfo = ( project ) => { const info = this . getScriptInfo ( fileName ) ; return info && projectContainsInfoDirectly ( project , info ) ; } ; if ( configuredProject . isSolution ( ) || ! projectContainsOriginalInfo ( configuredProject ) ) { configuredProject = forEachResolvedProjectReferenceProject ( configuredProject , fileName , child => { updateProjectIfDirty ( child ) ; return projectContainsOriginalInfo ( child ) ? child : undefined ; } , ProjectReferenceProjectLoadKind . FindCreateLoad , `Creating project referenced in solution ${ configuredProject . projectName } to find possible configured project for original file: ${ originalFileInfo . fileName }${ location !== originalLocation ? " for location: " + location . fileName : "" }` ) ; if ( ! configuredProject ) return undefined ; if ( configuredProject === project ) return originalLocation ; } addOriginalConfiguredProject ( configuredProject ) ; const originalScriptInfo = this . getScriptInfo ( fileName ) ; if ( ! originalScriptInfo || ! originalScriptInfo . containingProjects . length ) return undefined ; originalScriptInfo . containingProjects . forEach ( project => { if ( isConfiguredProject ( project ) ) { addOriginalConfiguredProject ( project ) ; } } ) ; return originalLocation ; function addOriginalConfiguredProject ( originalProject ) { if ( ! project . originalConfiguredProjects ) { project . originalConfiguredProjects = new Set ( ) ; } project . originalConfiguredProjects . add ( originalProject . canonicalConfigFilePath ) ; } } internal fileExists ( fileName ) { return ! ! this . getScriptInfoForNormalizedPath ( fileName ) || this . host . fileExists ( fileName ) ; } private findExternalProjectContainingOpenScriptInfo ( info ) { return find ( this . externalProjects , proj => { updateProjectIfDirty ( proj ) ; return proj . containsScriptInfo ( info ) ; } ) ; } private getOrCreateOpenScriptInfo ( fileName , fileContent , scriptKind , hasMixedContent , projectRootPath ) { const info = this . getOrCreateScriptInfoOpenedByClientForNormalizedPath ( fileName , projectRootPath ? this . getNormalizedAbsolutePath ( projectRootPath ) : this . currentDirectory , fileContent , scriptKind , hasMixedContent ) ! ; this . openFiles . set ( info . path , projectRootPath ) ; return info ; } private assignProjectToOpenedScriptInfo ( info ) { let configFileName ; let configFileErrors ; let project = this . findExternalProjectContainingOpenScriptInfo ( info ) ; let retainProjects ; let projectForConfigFileDiag ; let defaultConfigProjectIsCreated = false ; if ( ! project && this . serverMode === LanguageServiceMode . Semantic ) { configFileName = this . getConfigFileNameForFile ( info ) ; if ( configFileName ) { project = this . findConfiguredProjectByProjectName ( configFileName ) ; if ( ! project ) { project = this . createLoadAndUpdateConfiguredProject ( configFileName , `Creating possible configured project for ${ info . fileName } to open` ) ; defaultConfigProjectIsCreated = true ; } else { updateProjectIfDirty ( project ) ; } projectForConfigFileDiag = project . containsScriptInfo ( info ) ? project : undefined ; retainProjects = project ; if ( ! projectContainsInfoDirectly ( project , info ) ) { forEachResolvedProjectReferenceProject ( project , info . path , child => { updateProjectIfDirty ( child ) ; if ( ! isArray ( retainProjects ) ) { retainProjects = [ project as ConfiguredProject , child ] ; } else { retainProjects . push ( child ) ; } if ( projectContainsInfoDirectly ( child , info ) ) { projectForConfigFileDiag = child ; return child ; } if ( ! projectForConfigFileDiag && child . containsScriptInfo ( info ) ) { projectForConfigFileDiag = child ; } } , ProjectReferenceProjectLoadKind . FindCreateLoad , `Creating project referenced in solution ${ project . projectName } to find possible configured project for ${ info . fileName } to open` ) ; } if ( projectForConfigFileDiag ) { configFileName = projectForConfigFileDiag . getConfigFilePath ( ) ; if ( projectForConfigFileDiag !== project || defaultConfigProjectIsCreated ) { configFileErrors = projectForConfigFileDiag . getAllProjectErrors ( ) ; this . sendConfigFileDiagEvent ( projectForConfigFileDiag , info . fileName ) ; } } else { configFileName = undefined ; } this . createAncestorProjects ( info , project ) ; } } info . containingProjects . forEach ( updateProjectIfDirty ) ; if ( info . isOrphan ( ) ) { if ( isArray ( retainProjects ) ) { retainProjects . forEach ( project => this . sendConfigFileDiagEvent ( project , info . fileName ) ) ; } else if ( retainProjects ) { this . sendConfigFileDiagEvent ( retainProjects , info . fileName ) ; } Debug . assert ( this . openFiles . has ( info . path ) ) ; this . assignOrphanScriptInfoToInferredProject ( info , this . openFiles . get ( info . path ) ) ; } Debug . assert ( ! info . isOrphan ( ) ) ; return { configFileName , configFileErrors , retainProjects } ; } private createAncestorProjects ( info , project ) { if ( ! info . isAttached ( project ) ) return ; while ( true ) { if ( ! project . isInitialLoadPending ( ) && ( ! project . getCompilerOptions ( ) . composite || project . getCompilerOptions ( ) . disableSolutionSearching ) ) return ; const configFileName = this . getConfigFileNameForFile ( { fileName : project . getConfigFilePath ( ) , path : info . path , configFileInfo : true } ) ; if ( ! configFileName ) return ; const ancestor = this . findConfiguredProjectByProjectName ( configFileName ) || this . createConfiguredProjectWithDelayLoad ( configFileName , `Creating project possibly referencing default composite project ${ project . getProjectName ( ) } of open file ${ info . fileName }` ) ; if ( ancestor . isInitialLoadPending ( ) ) { ancestor . setPotentialProjectReference ( project . canonicalConfigFilePath ) ; } project = ancestor ; } } loadAncestorProjectTree ( forProjects ? ) { forProjects = forProjects || mapDefinedEntries ( this . configuredProjects , ( key , project ) => ! project . isInitialLoadPending ( ) ? [ key , true ] : undefined ) ; const seenProjects = new Set < NormalizedPath > ( ) ; for ( const project of arrayFrom ( this . configuredProjects . values ( ) ) ) { if ( forEachPotentialProjectReference ( project , potentialRefPath => forProjects ! . has ( potentialRefPath ) ) ) { updateProjectIfDirty ( project ) ; } this . ensureProjectChildren ( project , forProjects , seenProjects ) ; } } private ensureProjectChildren ( project , forProjects , seenProjects ) { if ( ! tryAddToSet ( seenProjects , project . canonicalConfigFilePath ) ) return ; if ( project . getCompilerOptions ( ) . disableReferencedProjectLoad ) return ; const children = project . getCurrentProgram ( ) ?. getResolvedProjectReferences ( ) ; if ( ! children ) return ; for ( const child of children ) { if ( ! child ) continue ; const referencedProject = forEachResolvedProjectReference ( child . references , ref => forProjects . has ( ref . sourceFile . path ) ? ref : undefined ) ; if ( ! referencedProject ) continue ; const configFileName = toNormalizedPath ( child . sourceFile . fileName ) ; const childProject = project . projectService . findConfiguredProjectByProjectName ( configFileName ) || project . projectService . createAndLoadConfiguredProject ( configFileName , `Creating project referenced by : ${ project . projectName } as it references project ${ referencedProject . sourceFile . fileName }` ) ; updateProjectIfDirty ( childProject ) ; this . ensureProjectChildren ( childProject , forProjects , seenProjects ) ; } } private cleanupAfterOpeningFile ( toRetainConfigProjects ) { this . removeOrphanConfiguredProjects ( toRetainConfigProjects ) ; for ( const inferredProject of this . inferredProjects . slice ( ) ) { if ( inferredProject . isOrphan ( ) ) { this . removeProject ( inferredProject ) ; } } this . removeOrphanScriptInfos ( ) ; } openClientFileWithNormalizedPath ( fileName , fileContent ? , scriptKind ? , hasMixedContent ? , projectRootPath ? ) { const info = this . getOrCreateOpenScriptInfo ( fileName , fileContent , scriptKind , hasMixedContent , projectRootPath ) ; const { retainProjects , ... result } = this . assignProjectToOpenedScriptInfo ( info ) ; this . cleanupAfterOpeningFile ( retainProjects ) ; this . telemetryOnOpenFile ( info ) ; this . printProjects ( ) ; return result ; } private removeOrphanConfiguredProjects ( toRetainConfiguredProjects ) { const toRemoveConfiguredProjects = new Map ( this . configuredProjects ) ; const markOriginalProjectsAsUsed = ( project ) => { if ( ! project . isOrphan ( ) && project . originalConfiguredProjects ) { project . originalConfiguredProjects . forEach ( ( _value , configuredProjectPath ) => { const project = this . getConfiguredProjectByCanonicalConfigFilePath ( configuredProjectPath ) ; return project && retainConfiguredProject ( project ) ; } ) ; } } ; if ( toRetainConfiguredProjects ) { if ( isArray ( toRetainConfiguredProjects ) ) { toRetainConfiguredProjects . forEach ( retainConfiguredProject ) ; } else { retainConfiguredProject ( toRetainConfiguredProjects ) ; } } this . inferredProjects . forEach ( markOriginalProjectsAsUsed ) ; this . externalProjects . forEach ( markOriginalProjectsAsUsed ) ; this . configuredProjects . forEach ( project => { if ( project . hasOpenRef ( ) ) { retainConfiguredProject ( project ) ; } else if ( toRemoveConfiguredProjects . has ( project . canonicalConfigFilePath ) ) { forEachReferencedProject ( project , ref => isRetained ( ref ) && retainConfiguredProject ( project ) ) ; } } ) ; toRemoveConfiguredProjects . forEach ( project => this . removeProject ( project ) ) ; function isRetained ( project ) { return project . hasOpenRef ( ) || ! toRemoveConfiguredProjects . has ( project . canonicalConfigFilePath ) ; } function retainConfiguredProject ( project ) { if ( toRemoveConfiguredProjects . delete ( project . canonicalConfigFilePath ) ) { markOriginalProjectsAsUsed ( project ) ; forEachReferencedProject ( project , retainConfiguredProject ) ; } } } private removeOrphanScriptInfos ( ) { const toRemoveScriptInfos = new Map ( this . filenameToScriptInfo ) ; this . filenameToScriptInfo . forEach ( info => { if ( ! info . isScriptOpen ( ) && info . isOrphan ( ) && ! info . isContainedByAutoImportProvider ( ) ) { if ( ! info . sourceMapFilePath ) return ; let sourceInfos ; if ( isString ( info . sourceMapFilePath ) ) { const sourceMapInfo = this . getScriptInfoForPath ( info . sourceMapFilePath ) ; sourceInfos = sourceMapInfo && sourceMapInfo . sourceInfos ; } else { sourceInfos = info . sourceMapFilePath . sourceInfos ; } if ( ! sourceInfos ) return ; if ( ! forEachKey ( sourceInfos , path => { const info = this . getScriptInfoForPath ( path ) ; return ! ! info && ( info . isScriptOpen ( ) || ! info . isOrphan ( ) ) ; } ) ) { return ; } } toRemoveScriptInfos . delete ( info . path ) ; if ( info . sourceMapFilePath ) { let sourceInfos ; if ( isString ( info . sourceMapFilePath ) ) { toRemoveScriptInfos . delete ( info . sourceMapFilePath ) ; const sourceMapInfo = this . getScriptInfoForPath ( info . sourceMapFilePath ) ; sourceInfos = sourceMapInfo && sourceMapInfo . sourceInfos ; } else { sourceInfos = info . sourceMapFilePath . sourceInfos ; } if ( sourceInfos ) { sourceInfos . forEach ( ( _value , path ) => toRemoveScriptInfos . delete ( path ) ) ; } } } ) ; toRemoveScriptInfos . forEach ( info => { this . stopWatchingScriptInfo ( info ) ; this . deleteScriptInfo ( info ) ; info . closeSourceMapFileWatcher ( ) ; } ) ; } private telemetryOnOpenFile ( scriptInfo ) { if ( this . serverMode !== LanguageServiceMode . Semantic || ! this . eventHandler || ! scriptInfo . isJavaScript ( ) || ! addToSeen ( this . allJsFilesForOpenFileTelemetry , scriptInfo . path ) ) { return ; } const project = scriptInfo . getDefaultProject ( ) ; if ( ! project . languageServiceEnabled ) { return ; } const sourceFile = project . getSourceFile ( scriptInfo . path ) ; const checkJs = ! ! sourceFile && ! ! sourceFile . checkJsDirective ; this . eventHandler ( { eventName : OpenFileInfoTelemetryEvent , data : { info : { checkJs } } } ) ; } param filename closeClientFile ( uncheckedFileName ) ; closeClientFile ( uncheckedFileName , skipAssignOrphanScriptInfosToInferredProject ) ; closeClientFile ( uncheckedFileName , skipAssignOrphanScriptInfosToInferredProject ? ) { const info = this . getScriptInfoForNormalizedPath ( toNormalizedPath ( uncheckedFileName ) ) ; const result = info ? this . closeOpenFile ( info , skipAssignOrphanScriptInfosToInferredProject ) : false ; if ( ! skipAssignOrphanScriptInfosToInferredProject ) { this . printProjects ( ) ; } return result ; } private collectChanges ( lastKnownProjectVersions , currentProjects , includeProjectReferenceRedirectInfo , result ) { for ( const proj of currentProjects ) { const knownProject = find ( lastKnownProjectVersions , p => p . projectName === proj . getProjectName ( ) ) ; result . push ( proj . getChangesSinceVersion ( knownProject && knownProject . version , includeProjectReferenceRedirectInfo ) ) ; } } synchronizeProjectList ( knownProjects , includeProjectReferenceRedirectInfo ? ) { const files = [ ] ; this . collectChanges ( knownProjects , this . externalProjects , includeProjectReferenceRedirectInfo , files ) ; this . collectChanges ( knownProjects , arrayFrom ( this . configuredProjects . values ( ) ) , includeProjectReferenceRedirectInfo , files ) ; this . collectChanges ( knownProjects , this . inferredProjects , includeProjectReferenceRedirectInfo , files ) ; return files ; } applyChangesInOpenFiles ( openFiles , changedFiles ? , closedFiles ? ) { let openScriptInfos ; let assignOrphanScriptInfosToInferredProject = false ; if ( openFiles ) { while ( true ) { const iterResult = openFiles . next ( ) ; if ( iterResult . done ) break ; const file = iterResult . value ; const info = this . getOrCreateOpenScriptInfo ( toNormalizedPath ( file . fileName ) , file . content , tryConvertScriptKindName ( file . scriptKind ! ) , file . hasMixedContent , file . projectRootPath ? toNormalizedPath ( file . projectRootPath ) : undefined ) ; ( openScriptInfos || ( openScriptInfos = [ ] ) ) . push ( info ) ; } } if ( changedFiles ) { while ( true ) { const iterResult = changedFiles . next ( ) ; if ( iterResult . done ) break ; const file = iterResult . value ; const scriptInfo = this . getScriptInfo ( file . fileName ) ! ; Debug . assert ( ! ! scriptInfo ) ; this . applyChangesToFile ( scriptInfo , file . changes ) ; } } if ( closedFiles ) { for ( const file of closedFiles ) { assignOrphanScriptInfosToInferredProject = this . closeClientFile ( file , true ) || assignOrphanScriptInfosToInferredProject ; } } let retainProjects ; if ( openScriptInfos ) { retainProjects = flatMap ( openScriptInfos , info => this . assignProjectToOpenedScriptInfo ( info ) . retainProjects ) ; } if ( assignOrphanScriptInfosToInferredProject ) { this . assignOrphanScriptInfosToInferredProject ( ) ; } if ( openScriptInfos ) { this . cleanupAfterOpeningFile ( retainProjects ) ; openScriptInfos . forEach ( info => this . telemetryOnOpenFile ( info ) ) ; this . printProjects ( ) ; } else if ( length ( closedFiles ) ) { this . printProjects ( ) ; } } applyChangesToFile ( scriptInfo , changes ) { while ( true ) { const iterResult = changes . next ( ) ; if ( iterResult . done ) break ; const change = iterResult . value ; scriptInfo . editContent ( change . span . start , change . span . start + change . span . length , change . newText ) ; } } private closeConfiguredProjectReferencedFromExternalProject ( configFile ) { const configuredProject = this . findConfiguredProjectByProjectName ( configFile ) ; if ( configuredProject ) { configuredProject . deleteExternalProjectReference ( ) ; if ( ! configuredProject . hasOpenRef ( ) ) { this . removeProject ( configuredProject ) ; return ; } } } closeExternalProject ( uncheckedFileName ) { const fileName = toNormalizedPath ( uncheckedFileName ) ; const configFiles = this . externalProjectToConfiguredProjectMap . get ( fileName ) ; if ( configFiles ) { for ( const configFile of configFiles ) { this . closeConfiguredProjectReferencedFromExternalProject ( configFile ) ; } this . externalProjectToConfiguredProjectMap . delete ( fileName ) ; } else { const externalProject = this . findExternalProjectByProjectName ( uncheckedFileName ) ; if ( externalProject ) { this . removeProject ( externalProject ) ; } } } openExternalProjects ( projects ) { const projectsToClose = arrayToMap ( this . externalProjects , p => p . getProjectName ( ) , _ => true ) ; forEachKey ( this . externalProjectToConfiguredProjectMap , externalProjectName => { projectsToClose . set ( externalProjectName , true ) ; } ) ; for ( const externalProject of projects ) { this . openExternalProject ( externalProject ) ; projectsToClose . delete ( externalProject . projectFileName ) ; } forEachKey ( projectsToClose , externalProjectName => { this . closeExternalProject ( externalProjectName ) ; } ) ; } /** Makes a filename safe to insert in a RegExp */ private static readonly filenameEscapeRegexp = /[-\/\\^$*+?.()|[\]{}]/g ; private static escapeFilenameForRegex ( filename ) { return filename . replace ( this . filenameEscapeRegexp , "\\$&" ) ; } resetSafeList ( ) { this . safelist = defaultTypeSafeList ; } applySafeList ( proj ) { const { rootFiles } = proj ; const typeAcquisition = proj . typeAcquisition ! ; Debug . assert ( ! ! typeAcquisition , "proj.typeAcquisition should be set by now" ) ; if ( typeAcquisition . enable === false || typeAcquisition . disableFilenameBasedTypeAcquisition ) { return [ ] ; } const typeAcqInclude = typeAcquisition . include || ( typeAcquisition . include = [ ] ) ; const excludeRules = [ ] ; const normalizedNames = rootFiles . map ( f => normalizeSlashes ( f . fileName ) ) as NormalizedPath [ ] ; const excludedFiles = [ ] ; for ( const name of Object . keys ( this . safelist ) ) { const rule = this . safelist [ name ] ; for ( const root of normalizedNames ) { if ( rule . match . test ( root ) ) { this . logger . info ( `Excluding files based on rule ${ name } matching file '${ root }'` ) ; if ( rule . types ) { for ( const type of rule . types ) { if ( typeAcqInclude . indexOf ( type ) < 0 ) { typeAcqInclude . push ( type ) ; } } } if ( rule . exclude ) { for ( const exclude of rule . exclude ) { const processedRule = root . replace ( rule . match , ( ... groups ) => { return exclude . map ( groupNumberOrString => { if ( typeof groupNumberOrString === "number" ) { if ( ! isString ( groups [ groupNumberOrString ] ) ) { this . logger . info ( `Incorrect RegExp specification in safelist rule ${ name } - not enough groups` ) ; return "\\*" ; } return ProjectService . escapeFilenameForRegex ( groups [ groupNumberOrString ] ) ; } return groupNumberOrString ; } ) . join ( "" ) ; } ) ; if ( excludeRules . indexOf ( processedRule ) === - 1 ) { excludeRules . push ( processedRule ) ; } } } else { const escaped = ProjectService . escapeFilenameForRegex ( root ) ; if ( excludeRules . indexOf ( escaped ) < 0 ) { excludeRules . push ( escaped ) ; } } } } } const excludeRegexes = excludeRules . map ( e => new RegExp ( e , "i" ) ) ; const filesToKeep = [ ] ; for ( let i = 0 ; i < proj . rootFiles . length ; i ++ ) { if ( excludeRegexes . some ( re => re . test ( normalizedNames [ i ] ) ) ) { excludedFiles . push ( normalizedNames [ i ] ) ; } else { let exclude = false ; if ( typeAcquisition . enable || typeAcquisition . enableAutoDiscovery ) { const baseName = getBaseFileName ( toFileNameLowerCase ( normalizedNames [ i ] ) ) ; if ( fileExtensionIs ( baseName , "js" ) ) { const inferredTypingName = removeFileExtension ( baseName ) ; const cleanedTypingName = removeMinAndVersionNumbers ( inferredTypingName ) ; const typeName = this . legacySafelist . get ( cleanedTypingName ) ; if ( typeName !== undefined ) { this . logger . info ( `Excluded '${ normalizedNames [ i ] }' because it matched ${ cleanedTypingName } from the legacy safelist` ) ; excludedFiles . push ( normalizedNames [ i ] ) ; exclude = true ; if ( typeAcqInclude . indexOf ( typeName ) < 0 ) { typeAcqInclude . push ( typeName ) ; } } } } if ( ! exclude ) { if ( /^.+[\.-]min\.js$/ . test ( normalizedNames [ i ] ) ) { excludedFiles . push ( normalizedNames [ i ] ) ; } else { filesToKeep . push ( proj . rootFiles [ i ] ) ; } } } } proj . rootFiles = filesToKeep ; return excludedFiles ; } openExternalProject ( proj ) { if ( proj . typingOptions && ! proj . typeAcquisition ) { const typeAcquisition = convertEnableAutoDiscoveryToEnable ( proj . typingOptions ) ; proj . typeAcquisition = typeAcquisition ; } proj . typeAcquisition = proj . typeAcquisition || { } ; proj . typeAcquisition . include = proj . typeAcquisition . include || [ ] ; proj . typeAcquisition . exclude = proj . typeAcquisition . exclude || [ ] ; if ( proj . typeAcquisition . enable === undefined ) { proj . typeAcquisition . enable = hasNoTypeScriptSource ( proj . rootFiles . map ( f => f . fileName ) ) ; } const excludedFiles = this . applySafeList ( proj ) ; let tsConfigFiles ; const rootFiles = [ ] ; for ( const file of proj . rootFiles ) { const normalized = toNormalizedPath ( file . fileName ) ; if ( getBaseConfigFileName ( normalized ) ) { if ( this . serverMode === LanguageServiceMode . Semantic && this . host . fileExists ( normalized ) ) { ( tsConfigFiles || ( tsConfigFiles = [ ] ) ) . push ( normalized ) ; } } else { rootFiles . push ( file ) ; } } if ( tsConfigFiles ) { tsConfigFiles . sort ( ) ; } const externalProject = this . findExternalProjectByProjectName ( proj . projectFileName ) ; let exisingConfigFiles ; if ( externalProject ) { externalProject . excludedFiles = excludedFiles ; if ( ! tsConfigFiles ) { const compilerOptions = convertCompilerOptions ( proj . options ) ; const watchOptionsAndErrors = convertWatchOptions ( proj . options , externalProject . getCurrentDirectory ( ) ) ; const lastFileExceededProgramSize = this . getFilenameForExceededTotalSizeLimitForNonTsFiles ( proj . projectFileName , compilerOptions , proj . rootFiles , externalFilePropertyReader ) ; if ( lastFileExceededProgramSize ) { externalProject . disableLanguageService ( lastFileExceededProgramSize ) ; } else { externalProject . enableLanguageService ( ) ; } externalProject . setProjectErrors ( watchOptionsAndErrors ?. errors ) ; this . updateRootAndOptionsOfNonInferredProject ( externalProject , proj . rootFiles , externalFilePropertyReader , compilerOptions , proj . typeAcquisition , proj . options . compileOnSave , watchOptionsAndErrors ?. watchOptions ) ; externalProject . updateGraph ( ) ; return ; } this . closeExternalProject ( proj . projectFileName ) ; } else if ( this . externalProjectToConfiguredProjectMap . get ( proj . projectFileName ) ) { if ( ! tsConfigFiles ) { this . closeExternalProject ( proj . projectFileName ) ; } else { const oldConfigFiles = this . externalProjectToConfiguredProjectMap . get ( proj . projectFileName ) ! ; let iNew = 0 ; let iOld = 0 ; while ( iNew < tsConfigFiles . length && iOld < oldConfigFiles . length ) { const newConfig = tsConfigFiles [ iNew ] ; const oldConfig = oldConfigFiles [ iOld ] ; if ( oldConfig < newConfig ) { this . closeConfiguredProjectReferencedFromExternalProject ( oldConfig ) ; iOld ++ ; } else if ( oldConfig > newConfig ) { iNew ++ ; } else { ( exisingConfigFiles || ( exisingConfigFiles = [ ] ) ) . push ( oldConfig ) ; iOld ++ ; iNew ++ ; } } for ( let i = iOld ; i < oldConfigFiles . length ; i ++ ) { this . closeConfiguredProjectReferencedFromExternalProject ( oldConfigFiles [ i ] ) ; } } } if ( tsConfigFiles ) { this . externalProjectToConfiguredProjectMap . set ( proj . projectFileName , tsConfigFiles ) ; for ( const tsconfigFile of tsConfigFiles ) { let project = this . findConfiguredProjectByProjectName ( tsconfigFile ) ; if ( ! project ) { project = this . getHostPreferences ( ) . lazyConfiguredProjectsFromExternalProject ? this . createConfiguredProjectWithDelayLoad ( tsconfigFile , `Creating configured project in external project: ${ proj . projectFileName }` ) : this . createLoadAndUpdateConfiguredProject ( tsconfigFile , `Creating configured project in external project: ${ proj . projectFileName }` ) ; } if ( project && ! contains ( exisingConfigFiles , tsconfigFile ) ) { project . addExternalProjectReference ( ) ; } } } else { this . externalProjectToConfiguredProjectMap . delete ( proj . projectFileName ) ; const project = this . createExternalProject ( proj . projectFileName , rootFiles , proj . options , proj . typeAcquisition , excludedFiles ) ; project . updateGraph ( ) ; } } hasDeferredExtension ( ) { for ( const extension of this . hostConfiguration . extraFileExtensions ! ) { if ( extension . scriptKind === ScriptKind . Deferred ) { return true ; } } return false ; } configurePlugin ( args ) { this . forEachEnabledProject ( project => project . onPluginConfigurationChanged ( args . pluginName , args . configuration ) ) ; this . currentPluginConfigOverrides = this . currentPluginConfigOverrides || new Map ( ) ; this . currentPluginConfigOverrides . set ( args . pluginName , args . configuration ) ; } getPackageJsonsVisibleToFile ( fileName , rootDir ? ) { const packageJsonCache = this . packageJsonCache ; const rootPath = rootDir && this . toPath ( rootDir ) ; const filePath = this . toPath ( fileName ) ; const result = [ ] ; const processDirectory = ( directory ) : boolean | undefined => { switch ( packageJsonCache . directoryHasPackageJson ( directory ) ) { case Ternary . Maybe : packageJsonCache . searchDirectoryAndAncestors ( directory ) ; return processDirectory ( directory ) ; case Ternary . True : const packageJsonFileName = combinePaths ( directory , "package.json" ) ; this . watchPackageJsonFile ( packageJsonFileName as Path ) ; const info = packageJsonCache . getInDirectory ( directory ) ; if ( info ) result . push ( info ) ; } if ( rootPath && rootPath === directory ) { return true ; } } ; forEachAncestorDirectory ( getDirectoryPath ( filePath ) , processDirectory ) ; return result ; } getNearestAncestorDirectoryWithPackageJson ( fileName ) { return forEachAncestorDirectory ( fileName , directory => { switch ( this . packageJsonCache . directoryHasPackageJson ( this . toPath ( directory ) ) ) { case Ternary . True : return directory ; case Ternary . False : return undefined ; case Ternary . Maybe : return this . host . fileExists ( combinePaths ( directory , "package.json" ) ) ? directory : undefined ; } } ) ; } private watchPackageJsonFile ( path ) { const watchers = this . packageJsonFilesMap || ( this . packageJsonFilesMap = new Map ( ) ) ; if ( ! watchers . has ( path ) ) { this . invalidateProjectPackageJson ( path ) ; watchers . set ( path , this . watchFactory . watchFile ( path , ( fileName , eventKind ) => { const path = this . toPath ( fileName ) ; switch ( eventKind ) { case FileWatcherEventKind . Created : return Debug . fail ( ) ; case FileWatcherEventKind . Changed : this . packageJsonCache . addOrUpdate ( path ) ; this . invalidateProjectPackageJson ( path ) ; break ; case FileWatcherEventKind . Deleted : this . packageJsonCache . delete ( path ) ; this . invalidateProjectPackageJson ( path ) ; watchers . get ( path ) ! . close ( ) ; watchers . delete ( path ) ; } } , PollingInterval . Low , this . hostConfiguration . watchOptions , WatchType . PackageJson ) ) ; } } private onAddPackageJson ( path ) { this . packageJsonCache . addOrUpdate ( path ) ; this . watchPackageJsonFile ( path ) ; } includePackageJsonAutoImports ( ) { switch ( this . hostConfiguration . preferences . includePackageJsonAutoImports ) { case "on" : return PackageJsonAutoImportPreference . On ; case "off" : return PackageJsonAutoImportPreference . Off ; default : return PackageJsonAutoImportPreference . Auto ; } } private invalidateProjectPackageJson ( packageJsonPath ) { this . configuredProjects . forEach ( invalidate ) ; this . inferredProjects . forEach ( invalidate ) ; this . externalProjects . forEach ( invalidate ) ; function invalidate ( project ) { if ( packageJsonPath ) { project . onPackageJsonChange ( packageJsonPath ) ; } else { project . onAutoImportProviderSettingsChanged ( ) ; } } } getIncompleteCompletionsCache ( ) { return this . incompleteCompletionsCache ||= createIncompleteCompletionsCache ( ) ; } } function createIncompleteCompletionsCache ( ) { let info ; return { get ( ) { return info ; } , set ( newInfo ) { info = newInfo ; } , clear ( ) { info = undefined ; } } ; } export type ScriptInfoOrConfig = ScriptInfo | TsConfigSourceFile ; export function isConfigFile ( config ) { return ( config as TsConfigSourceFile ) . kind !== undefined ; } function printProjectWithoutFileNames ( project ) { project . print ( false ) ; } }