• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import * as ts from "./_namespaces/ts";
2import {
3    asNormalizedPath, createModuleSpecifierCache, emptyArray, Errors, FileStats, forEachResolvedProjectReferenceProject,
4    LogLevel, ModuleImportResult, Msg, NormalizedPath, projectContainsInfoDirectly, ProjectOptions,
5    ProjectReferenceProjectLoadKind, ProjectService, protocol, ScriptInfo, ServerHost, Session, toNormalizedPath,
6    TypingsCache, updateProjectIfDirty,
7} from "./_namespaces/ts.server";
8import {
9    addRange, append, ApplyCodeActionCommandResult, arrayFrom, arrayToMap, BuilderState, CachedDirectoryStructureHost,
10    changesAffectModuleResolution, clearMap, cloneCompilerOptions, closeFileWatcher, closeFileWatcherOf, combinePaths,
11    CompilerHost, CompilerOptions, concatenate, ConditionCheckResult, ConfigFileProgramReloadLevel,
12    createCacheableExportInfoMap, createLanguageService, createResolutionCache, createSymlinkCache, Debug, Diagnostic,
13    DiagnosticCategory, DirectoryStructureHost, DirectoryWatcherCallback, DocumentPositionMapper, DocumentRegistry,
14    enumerateInsertsAndDeletes, ESMap, every, explainFiles, ExportInfoMap, Extension, FileCheckModuleInfo,
15    fileExtensionIs, FileReference, FileWatcher, FileWatcherCallback, FileWatcherEventKind, filter, firstDefined,
16    flatMap, forEach, forEachEntry, forEachKey, generateDjb2Hash, getAllowJSCompilerOption,
17    getAutomaticTypeDirectiveNames, GetCanonicalFileName, getDeclarationEmitOutputFilePathWorker,
18    getDefaultCompilerOptions, getDefaultLibFileName, getDefaultLibFilePath, getDirectoryPath, getEffectiveTypeRoots,
19    getEmitDeclarations, getEntrypointsFromPackageJsonInfo, getNormalizedAbsolutePath, getOrUpdate, getStringComparer,
20    HasInvalidatedResolutions, HostCancellationToken, inferredTypesContainingFile, InstallPackageOptions,
21    IScriptSnapshot, isDeclarationFileName, isExternalModuleNameRelative, isInsideNodeModules, isOhpm, JSDoc,
22    JsDocNodeCheckConfig, JsDocTagInfo, JsTyping, LanguageService, LanguageServiceHost, LanguageServiceMode, map, Map,
23    mapDefined, maybeBind, ModuleKind, ModuleResolutionCache, ModuleResolutionHost, noop, noopFileWatcher,
24    normalizePath, normalizeSlashes, orderedRemoveItem, outFile, PackageJsonAutoImportPreference, PackageJsonInfo,
25    ParsedCommandLine, parsePackageName, Path, perfLogger, PerformanceEvent, PluginImport, PollingInterval, Program,
26    ProjectPackageJsonInfo, ProjectReference, removeFileExtension, ResolutionCache, resolutionExtensionIsTSOrJson,
27    ResolvedModuleFull, ResolvedModuleWithFailedLookupLocations, ResolvedProjectReference,
28    ResolvedTypeReferenceDirective, resolvePackageNameToPackageJson, returnFalse, returnTrue, ScriptKind, Set, some,
29    sort, sortAndDeduplicate, SortedReadonlyArray, SourceFile, SourceMapper, startsWith, stripQuotes, StructureIsReused,
30    SymlinkCache, TagCheckParam, ThrottledCancellationToken, timestamp, toPath, tracing, TypeAcquisition,
31    updateErrorForNoInputFiles, updateMissingFilePathsWatch, WatchDirectoryFlags, WatchOptions, WatchType,
32} from "./_namespaces/ts";
33
34export enum ProjectKind {
35    Inferred,
36    Configured,
37    External,
38    AutoImportProvider,
39    Auxiliary,
40}
41
42/** @internal */
43export type Mutable<T> = { -readonly [K in keyof T]: T[K]; };
44
45/** @internal */
46export function countEachFileTypes(infos: ScriptInfo[], includeSizes = false): FileStats {
47    const result: Mutable<FileStats> = {
48        js: 0, jsSize: 0,
49        jsx: 0, jsxSize: 0,
50        ts: 0, tsSize: 0,
51        tsx: 0, tsxSize: 0,
52        dts: 0, dtsSize: 0,
53        deferred: 0, deferredSize: 0,
54        ets: 0, etsSize: 0,
55        dets: 0, detsSize: 0,
56    };
57    for (const info of infos) {
58        const fileSize = includeSizes ? info.getTelemetryFileSize() : 0;
59        switch (info.scriptKind) {
60            case ScriptKind.JS:
61                result.js += 1;
62                result.jsSize! += fileSize;
63                break;
64            case ScriptKind.JSX:
65                result.jsx += 1;
66                result.jsxSize! += fileSize;
67                break;
68            case ScriptKind.TS:
69                if (isDeclarationFileName(info.fileName)) {
70                    result.dts += 1;
71                    result.dtsSize! += fileSize;
72                }
73                else {
74                    result.ts += 1;
75                    result.tsSize! += fileSize;
76                }
77                break;
78            case ScriptKind.TSX:
79                result.tsx += 1;
80                result.tsxSize! += fileSize;
81                break;
82            case ScriptKind.Deferred:
83                result.deferred += 1;
84                result.deferredSize! += fileSize;
85                break;
86            case ScriptKind.ETS:
87                if (fileExtensionIs(info.fileName, Extension.Dets)) {
88                    result.dets += 1;
89                    result.detsSize! += fileSize;
90                }
91                else {
92                    result.ets += 1;
93                    result.etsSize! += fileSize;
94                }
95                break;
96        }
97    }
98    return result;
99}
100
101function hasOneOrMoreJsAndNoTsFiles(project: Project) {
102    const counts = countEachFileTypes(project.getScriptInfos());
103    return counts.js > 0 && counts.ts === 0 && counts.tsx === 0;
104}
105
106export function allRootFilesAreJsOrDts(project: Project): boolean {
107    const counts = countEachFileTypes(project.getRootScriptInfos());
108    return counts.ts === 0 && counts.tsx === 0;
109}
110
111export function allFilesAreJsOrDts(project: Project): boolean {
112    const counts = countEachFileTypes(project.getScriptInfos());
113    return counts.ts === 0 && counts.tsx === 0;
114}
115
116/** @internal */
117export function hasNoTypeScriptSource(fileNames: string[]): boolean {
118    return !fileNames.some(fileName => (fileExtensionIs(fileName, Extension.Ts) && !isDeclarationFileName(fileName)) || fileExtensionIs(fileName, Extension.Tsx) || (fileExtensionIs(fileName, Extension.Ets) && !isDeclarationFileName(fileName)));
119}
120
121/** @internal */
122export interface ProjectFilesWithTSDiagnostics extends protocol.ProjectFiles {
123    projectErrors: readonly Diagnostic[];
124}
125
126export interface PluginCreateInfo {
127    project: Project;
128    languageService: LanguageService;
129    languageServiceHost: LanguageServiceHost;
130    serverHost: ServerHost;
131    session?: Session<unknown>;
132    config: any;
133}
134
135export interface PluginModule {
136    create(createInfo: PluginCreateInfo): LanguageService;
137    getExternalFiles?(proj: Project): string[];
138    onConfigurationChanged?(config: any): void;
139}
140
141export interface PluginModuleWithName {
142    name: string;
143    module: PluginModule;
144}
145
146export type PluginModuleFactory = (mod: { typescript: typeof ts }) => PluginModule;
147
148/** @internal */
149export interface BeginEnablePluginResult {
150    pluginConfigEntry: PluginImport;
151    pluginConfigOverrides: Map<any> | undefined;
152    resolvedModule: PluginModuleFactory | undefined;
153    errorLogs: string[] | undefined;
154}
155
156/**
157 * The project root can be script info - if root is present,
158 * or it could be just normalized path if root wasn't present on the host(only for non inferred project)
159 *
160 * @internal
161 */
162export interface ProjectRootFile {
163    fileName: NormalizedPath;
164    info?: ScriptInfo;
165}
166
167interface GeneratedFileWatcher {
168    generatedFilePath: Path;
169    watcher: FileWatcher;
170}
171type GeneratedFileWatcherMap = GeneratedFileWatcher | ESMap<Path, GeneratedFileWatcher>;
172function isGeneratedFileWatcher(watch: GeneratedFileWatcherMap): watch is GeneratedFileWatcher {
173    return (watch as GeneratedFileWatcher).generatedFilePath !== undefined;
174}
175
176/** @internal */
177export interface EmitResult {
178    emitSkipped: boolean;
179    diagnostics: readonly Diagnostic[];
180}
181
182export abstract class Project implements LanguageServiceHost, ModuleResolutionHost {
183    private rootFiles: ScriptInfo[] = [];
184    private rootFilesMap = new Map<string, ProjectRootFile>();
185    private program: Program | undefined;
186    private externalFiles: SortedReadonlyArray<string> | undefined;
187    private missingFilesMap: ESMap<Path, FileWatcher> | undefined;
188    private generatedFilesMap: GeneratedFileWatcherMap | undefined;
189
190    /** @internal */
191    protected readonly plugins: PluginModuleWithName[] = [];
192
193    /**
194     * This is map from files to unresolved imports in it
195     * Maop does not contain entries for files that do not have unresolved imports
196     * This helps in containing the set of files to invalidate
197     *
198     * @internal
199     */
200    cachedUnresolvedImportsPerFile = new Map<Path, readonly string[]>();
201
202    /** @internal */
203    lastCachedUnresolvedImportsList: SortedReadonlyArray<string> | undefined;
204    /** @internal */
205    private hasAddedorRemovedFiles = false;
206    /** @internal */
207    private hasAddedOrRemovedSymlinks = false;
208
209    /** @internal */
210    lastFileExceededProgramSize: string | undefined;
211
212    // wrapper over the real language service that will suppress all semantic operations
213    protected languageService: LanguageService;
214
215    public languageServiceEnabled: boolean;
216
217    readonly trace?: (s: string) => void;
218    readonly realpath?: (path: string) => string;
219
220    /** @internal */
221    hasInvalidatedResolutions: HasInvalidatedResolutions | undefined;
222
223    /** @internal */
224    resolutionCache: ResolutionCache;
225
226    private builderState: BuilderState | undefined;
227    /**
228     * Set of files names that were updated since the last call to getChangesSinceVersion.
229     */
230    private updatedFileNames: Set<string> | undefined;
231    /**
232     * Set of files that was returned from the last call to getChangesSinceVersion.
233     */
234    private lastReportedFileNames: ESMap<string, boolean> | undefined;
235    /**
236     * Last version that was reported.
237     */
238    private lastReportedVersion = 0;
239    /**
240     * Current project's program version. (incremented everytime new program is created that is not complete reuse from the old one)
241     * This property is changed in 'updateGraph' based on the set of files in program
242     */
243    private projectProgramVersion = 0;
244    /**
245     * Current version of the project state. It is changed when:
246     * - new root file was added/removed
247     * - edit happen in some file that is currently included in the project.
248     * This property is different from projectStructureVersion since in most cases edits don't affect set of files in the project
249     */
250    private projectStateVersion = 0;
251
252    protected projectErrors: Diagnostic[] | undefined;
253
254    protected isInitialLoadPending: () => boolean = returnFalse;
255
256    /** @internal */
257    dirty = false;
258
259    /** @internal */
260    typingFiles: SortedReadonlyArray<string> = emptyArray;
261
262    /** @internal */
263    originalConfiguredProjects: Set<NormalizedPath> | undefined;
264
265    /** @internal */
266    private packageJsonsForAutoImport: Set<string> | undefined;
267
268    /** @internal */
269    private noDtsResolutionProject?: AuxiliaryProject | undefined;
270
271    /** @internal */
272    getResolvedProjectReferenceToRedirect(_fileName: string): ResolvedProjectReference | undefined {
273        return undefined;
274    }
275
276    /** @internal */ useSourceOfProjectReferenceRedirect?(): boolean;
277    /** @internal */ getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;
278
279    private readonly cancellationToken: ThrottledCancellationToken;
280
281    public isNonTsProject() {
282        updateProjectIfDirty(this);
283        return allFilesAreJsOrDts(this);
284    }
285
286    public isJsOnlyProject() {
287        updateProjectIfDirty(this);
288        return hasOneOrMoreJsAndNoTsFiles(this);
289    }
290
291    public static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void, logErrors?: (message: string) => void): {} | undefined {
292        const resolvedPath = normalizeSlashes(host.resolvePath(combinePaths(initialDir, "node_modules")));
293        log(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`);
294        const result = host.require!(resolvedPath, moduleName); // TODO: GH#18217
295        if (result.error) {
296            const err = result.error.stack || result.error.message || JSON.stringify(result.error);
297            (logErrors || log)(`Failed to load module '${moduleName}' from ${resolvedPath}: ${err}`);
298            return undefined;
299        }
300        return result.module;
301    }
302
303    /** @internal */
304    public static async importServicePluginAsync(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void, logErrors?: (message: string) => void): Promise<{} | undefined> {
305        Debug.assertIsDefined(host.importPlugin);
306        const resolvedPath = combinePaths(initialDir, "node_modules");
307        log(`Dynamically importing ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`);
308        let result: ModuleImportResult;
309        try {
310            result = await host.importPlugin(resolvedPath, moduleName);
311        }
312        catch (e) {
313            result = { module: undefined, error: e };
314        }
315        if (result.error) {
316            const err = result.error.stack || result.error.message || JSON.stringify(result.error);
317            (logErrors || log)(`Failed to dynamically import module '${moduleName}' from ${resolvedPath}: ${err}`);
318            return undefined;
319        }
320        return result.module;
321    }
322
323    /** @internal */
324    readonly currentDirectory: string;
325
326    /** @internal */
327    public directoryStructureHost: DirectoryStructureHost;
328
329    /** @internal */
330    public readonly getCanonicalFileName: GetCanonicalFileName;
331
332    /** @internal */
333    private exportMapCache: ExportInfoMap | undefined;
334    /** @internal */
335    private changedFilesForExportMapCache: Set<Path> | undefined;
336    /** @internal */
337    private moduleSpecifierCache = createModuleSpecifierCache(this);
338    /** @internal */
339    private symlinks: SymlinkCache | undefined;
340    /** @internal */
341    autoImportProviderHost: AutoImportProviderProject | false | undefined;
342    /** @internal */
343    protected typeAcquisition: TypeAcquisition | undefined;
344
345    /** @internal */
346    constructor(
347        /** @internal */ readonly projectName: string,
348        readonly projectKind: ProjectKind,
349        readonly projectService: ProjectService,
350        private documentRegistry: DocumentRegistry,
351        hasExplicitListOfFiles: boolean,
352        lastFileExceededProgramSize: string | undefined,
353        private compilerOptions: CompilerOptions,
354        public compileOnSaveEnabled: boolean,
355        protected watchOptions: WatchOptions | undefined,
356        directoryStructureHost: DirectoryStructureHost,
357        currentDirectory: string | undefined,
358    ) {
359        this.directoryStructureHost = directoryStructureHost;
360        this.currentDirectory = this.projectService.getNormalizedAbsolutePath(currentDirectory || "");
361        this.getCanonicalFileName = this.projectService.toCanonicalFileName;
362
363        if (this.projectService.host.getFileCheckedModuleInfo) {
364            this.getFileCheckedModuleInfo = this.projectService.host.getFileCheckedModuleInfo;;
365        }
366        if (this.projectService.host.getJsDocNodeCheckedConfig) {
367            this.getJsDocNodeCheckedConfig = this.projectService.host.getJsDocNodeCheckedConfig;
368        }
369        if (this.projectService.host.getJsDocNodeConditionCheckedResult) {
370            this.getJsDocNodeConditionCheckedResult = this.projectService.host.getJsDocNodeConditionCheckedResult;
371        }
372
373        this.cancellationToken = new ThrottledCancellationToken(this.projectService.cancellationToken, this.projectService.throttleWaitMilliseconds);
374        if (!this.compilerOptions) {
375            this.compilerOptions = getDefaultCompilerOptions();
376            this.compilerOptions.allowNonTsExtensions = true;
377            this.compilerOptions.allowJs = true;
378        }
379        else if (hasExplicitListOfFiles || getAllowJSCompilerOption(this.compilerOptions) || this.projectService.hasDeferredExtension()) {
380            // If files are listed explicitly or allowJs is specified, allow all extensions
381            this.compilerOptions.allowNonTsExtensions = true;
382        }
383
384        switch (projectService.serverMode) {
385            case LanguageServiceMode.Semantic:
386                this.languageServiceEnabled = true;
387                break;
388            case LanguageServiceMode.PartialSemantic:
389                this.languageServiceEnabled = true;
390                this.compilerOptions.noResolve = true;
391                this.compilerOptions.types = [];
392                break;
393            case LanguageServiceMode.Syntactic:
394                this.languageServiceEnabled = false;
395                this.compilerOptions.noResolve = true;
396                this.compilerOptions.types = [];
397                break;
398            default:
399                Debug.assertNever(projectService.serverMode);
400        }
401
402        this.setInternalCompilerOptionsForEmittingJsFiles();
403        const host = this.projectService.host;
404        if (this.projectService.logger.loggingEnabled()) {
405            this.trace = s => this.writeLog(s);
406        }
407        else if (host.trace) {
408            this.trace = s => host.trace!(s);
409        }
410        this.realpath = maybeBind(host, host.realpath);
411
412        // Use the current directory as resolution root only if the project created using current directory string
413        this.resolutionCache = createResolutionCache(
414            this,
415            currentDirectory && this.currentDirectory,
416            /*logChangesWhenResolvingModule*/ true
417        );
418        this.languageService = createLanguageService(this, this.documentRegistry, this.projectService.serverMode);
419        if (lastFileExceededProgramSize) {
420            this.disableLanguageService(lastFileExceededProgramSize);
421        }
422        this.markAsDirty();
423        if (projectKind !== ProjectKind.AutoImportProvider) {
424            this.projectService.pendingEnsureProjectForOpenFiles = true;
425        }
426    }
427
428    getFileCheckedModuleInfo?(containFilePath: string): FileCheckModuleInfo {
429        Debug.log(containFilePath);
430        return {
431            fileNeedCheck: false,
432            checkPayload: undefined,
433            currentFileName: "",
434        }
435    }
436
437    getJsDocNodeCheckedConfig(jsDocFileCheckedInfo: FileCheckModuleInfo, sourceFilePath: string): JsDocNodeCheckConfig {
438        Debug.log(jsDocFileCheckedInfo.fileNeedCheck.toString());
439        Debug.log(sourceFilePath);
440        return {
441            nodeNeedCheck: false,
442            checkConfig: []
443        };
444    }
445
446    getJsDocNodeConditionCheckedResult(jsDocFileCheckedInfo: FileCheckModuleInfo, jsDocs: JsDocTagInfo[]):ConditionCheckResult {
447        Debug.log(jsDocFileCheckedInfo.checkPayload);
448        Debug.log(jsDocs.toString());
449        return {
450            valid: true,
451            message: "",
452            type: DiagnosticCategory.Warning
453        };
454    }
455
456    getTagNameNeededCheckByFile(filePath: string): TagCheckParam {
457        Debug.log(filePath);
458        return {
459            needCheck: false,
460            checkConfig: []
461        };
462    }
463
464    getExpressionCheckedResultsByFile?(filePath: string, jsDocs: JSDoc[]): ConditionCheckResult {
465        Debug.log(filePath);
466        Debug.log(jsDocs.toString());
467        return {
468            valid: true,
469        };
470    }
471
472    isKnownTypesPackageName(name: string): boolean {
473        return this.typingsCache.isKnownTypesPackageName(name);
474    }
475    installPackage(options: InstallPackageOptions): Promise<ApplyCodeActionCommandResult> {
476        return this.typingsCache.installPackage({ ...options, projectName: this.projectName, projectRootPath: this.toPath(this.currentDirectory) });
477    }
478
479    /** @internal */
480    getGlobalTypingsCacheLocation() {
481        return this.getGlobalCache();
482    }
483
484    private get typingsCache(): TypingsCache {
485        return this.projectService.typingsCache;
486    }
487
488    /** @internal */
489    getSymlinkCache(): SymlinkCache {
490        if (!this.symlinks) {
491            this.symlinks = createSymlinkCache(this.getCurrentDirectory(), this.getCanonicalFileName, isOhpm(this.compilerOptions.packageManagerType));
492        }
493        if (this.program && !this.symlinks.hasProcessedResolutions()) {
494            this.symlinks.setSymlinksFromResolutions(
495                this.program.getSourceFiles(),
496                this.program.getResolvedTypeReferenceDirectives());
497        }
498        return this.symlinks;
499    }
500
501    // Method of LanguageServiceHost
502    getCompilationSettings() {
503        return this.compilerOptions;
504    }
505
506    // Method to support public API
507    getCompilerOptions() {
508        return this.getCompilationSettings();
509    }
510
511    getNewLine() {
512        return this.projectService.host.newLine;
513    }
514
515    getProjectVersion() {
516        return this.projectStateVersion.toString();
517    }
518
519    getProjectReferences(): readonly ProjectReference[] | undefined {
520        return undefined;
521    }
522
523    getScriptFileNames() {
524        if (!this.rootFiles) {
525            return ts.emptyArray;
526        }
527
528        let result: string[] | undefined;
529        this.rootFilesMap.forEach(value => {
530            if (this.languageServiceEnabled || (value.info && value.info.isScriptOpen())) {
531                // if language service is disabled - process only files that are open
532                (result || (result = [])).push(value.fileName);
533            }
534        });
535
536        return addRange(result, this.typingFiles) || ts.emptyArray;
537    }
538
539    private getOrCreateScriptInfoAndAttachToProject(fileName: string) {
540        const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.currentDirectory, this.directoryStructureHost);
541        if (scriptInfo) {
542            const existingValue = this.rootFilesMap.get(scriptInfo.path);
543            if (existingValue && existingValue.info !== scriptInfo) {
544                // This was missing path earlier but now the file exists. Update the root
545                this.rootFiles.push(scriptInfo);
546                existingValue.info = scriptInfo;
547            }
548            scriptInfo.attachToProject(this);
549        }
550        return scriptInfo;
551    }
552
553    getScriptKind(fileName: string) {
554        const info = this.getOrCreateScriptInfoAndAttachToProject(fileName);
555        return (info && info.scriptKind)!; // TODO: GH#18217
556    }
557
558    getScriptVersion(filename: string) {
559        // Don't attach to the project if version is asked
560
561        const info = this.projectService.getOrCreateScriptInfoNotOpenedByClient(filename, this.currentDirectory, this.directoryStructureHost);
562        return (info && info.getLatestVersion())!; // TODO: GH#18217
563    }
564
565    getScriptSnapshot(filename: string): IScriptSnapshot | undefined {
566        const scriptInfo = this.getOrCreateScriptInfoAndAttachToProject(filename);
567        if (scriptInfo) {
568            return scriptInfo.getSnapshot();
569        }
570    }
571
572    getCancellationToken(): HostCancellationToken {
573        return this.cancellationToken;
574    }
575
576    getCurrentDirectory(): string {
577        return this.currentDirectory;
578    }
579
580    getDefaultLibFileName() {
581        const nodeModuleBinDir = getDirectoryPath(normalizePath(this.projectService.getExecutingFilePath()));
582        return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilerOptions));
583    }
584
585    useCaseSensitiveFileNames() {
586        return this.projectService.host.useCaseSensitiveFileNames;
587    }
588
589    readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] {
590        return this.directoryStructureHost.readDirectory!(path, extensions, exclude, include, depth);
591    }
592
593    readFile(fileName: string): string | undefined {
594        return this.projectService.host.readFile(fileName);
595    }
596
597    writeFile(fileName: string, content: string): void {
598        return this.projectService.host.writeFile(fileName, content);
599    }
600
601    fileExists(file: string): boolean {
602        // As an optimization, don't hit the disks for files we already know don't exist
603        // (because we're watching for their creation).
604        const path = this.toPath(file);
605        return !this.isWatchedMissingFile(path) && this.directoryStructureHost.fileExists(file);
606    }
607
608    resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference, _options?: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[] {
609        return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference, containingSourceFile);
610    }
611
612    getModuleResolutionCache(): ModuleResolutionCache | undefined {
613        return this.resolutionCache.getModuleResolutionCache();
614    }
615
616    getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined {
617        return this.resolutionCache.getResolvedModuleWithFailedLookupLocationsFromCache(moduleName, containingFile, resolutionMode);
618    }
619
620    resolveTypeReferenceDirectives(typeDirectiveNames: string[] | FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, _options?: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[] {
621        return this.resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference, containingFileMode);
622    }
623
624    directoryExists(path: string): boolean {
625        return this.directoryStructureHost.directoryExists!(path); // TODO: GH#18217
626    }
627
628    getDirectories(path: string): string[] {
629        return this.directoryStructureHost.getDirectories!(path); // TODO: GH#18217
630    }
631
632    /** @internal */
633    getCachedDirectoryStructureHost(): CachedDirectoryStructureHost {
634        return undefined!; // TODO: GH#18217
635    }
636
637    /** @internal */
638    toPath(fileName: string) {
639        return toPath(fileName, this.currentDirectory, this.projectService.toCanonicalFileName);
640    }
641
642    /** @internal */
643    watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) {
644        return this.projectService.watchFactory.watchDirectory(
645            directory,
646            cb,
647            flags,
648            this.projectService.getWatchOptions(this),
649            WatchType.FailedLookupLocations,
650            this
651        );
652    }
653
654    /** @internal */
655    watchAffectingFileLocation(file: string, cb: FileWatcherCallback) {
656        return this.projectService.watchFactory.watchFile(
657            file,
658            cb,
659            PollingInterval.High,
660            this.projectService.getWatchOptions(this),
661            WatchType.AffectingFileLocation,
662            this
663        );
664    }
665
666    /** @internal */
667    clearInvalidateResolutionOfFailedLookupTimer() {
668        return this.projectService.throttledOperations.cancel(`${this.getProjectName()}FailedLookupInvalidation`);
669    }
670
671    /** @internal */
672    scheduleInvalidateResolutionsOfFailedLookupLocations() {
673        this.projectService.throttledOperations.schedule(`${this.getProjectName()}FailedLookupInvalidation`, /*delay*/ 1000, () => {
674            if (this.resolutionCache.invalidateResolutionsOfFailedLookupLocations()) {
675                this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this);
676            }
677        });
678    }
679
680    /** @internal */
681    invalidateResolutionsOfFailedLookupLocations() {
682        if (this.clearInvalidateResolutionOfFailedLookupTimer() &&
683            this.resolutionCache.invalidateResolutionsOfFailedLookupLocations()) {
684            this.markAsDirty();
685            this.projectService.delayEnsureProjectForOpenFiles();
686        }
687    }
688
689    /** @internal */
690    onInvalidatedResolution() {
691        this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this);
692    }
693
694    /** @internal */
695    watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) {
696        return this.projectService.watchFactory.watchDirectory(
697            directory,
698            cb,
699            flags,
700            this.projectService.getWatchOptions(this),
701            WatchType.TypeRoots,
702            this
703        );
704    }
705
706    /** @internal */
707    hasChangedAutomaticTypeDirectiveNames() {
708        return this.resolutionCache.hasChangedAutomaticTypeDirectiveNames();
709    }
710
711    /** @internal */
712    onChangedAutomaticTypeDirectiveNames() {
713        this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this);
714    }
715
716    /** @internal */
717    getGlobalCache() {
718        return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined;
719    }
720
721    /** @internal */
722    globalCacheResolutionModuleName = JsTyping.nonRelativeModuleNameForTypingCache;
723
724    /** @internal */
725    fileIsOpen(filePath: Path) {
726        return this.projectService.openFiles.has(filePath);
727    }
728
729    /** @internal */
730    writeLog(s: string) {
731        this.projectService.logger.info(s);
732    }
733
734    log(s: string) {
735        this.writeLog(s);
736    }
737
738    error(s: string) {
739        this.projectService.logger.msg(s, Msg.Err);
740    }
741
742    private setInternalCompilerOptionsForEmittingJsFiles() {
743        if (this.projectKind === ProjectKind.Inferred || this.projectKind === ProjectKind.External) {
744            this.compilerOptions.noEmitForJsFiles = true;
745        }
746    }
747
748    /**
749     * Get the errors that dont have any file name associated
750     */
751    getGlobalProjectErrors(): readonly Diagnostic[] {
752        return filter(this.projectErrors, diagnostic => !diagnostic.file) || emptyArray;
753    }
754
755    /**
756     * Get all the project errors
757     */
758    getAllProjectErrors(): readonly Diagnostic[] {
759        return this.projectErrors || emptyArray;
760    }
761
762    setProjectErrors(projectErrors: Diagnostic[] | undefined) {
763        this.projectErrors = projectErrors;
764    }
765
766    getLanguageService(ensureSynchronized = true): LanguageService {
767        if (ensureSynchronized) {
768            updateProjectIfDirty(this);
769        }
770        return this.languageService;
771    }
772
773    /** @internal */
774    getSourceMapper(): SourceMapper {
775        return this.getLanguageService().getSourceMapper();
776    }
777
778    /** @internal */
779    clearSourceMapperCache() {
780        this.languageService.clearSourceMapperCache();
781    }
782
783    /** @internal */
784    getDocumentPositionMapper(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined {
785        return this.projectService.getDocumentPositionMapper(this, generatedFileName, sourceFileName);
786    }
787
788    /** @internal */
789    getSourceFileLike(fileName: string) {
790        return this.projectService.getSourceFileLike(fileName, this);
791    }
792
793    /** @internal */
794    shouldEmitFile(scriptInfo: ScriptInfo | undefined) {
795        return scriptInfo &&
796            !scriptInfo.isDynamicOrHasMixedContent() &&
797            !this.program!.isSourceOfProjectReferenceRedirect(scriptInfo.path);
798    }
799
800    getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
801        if (!this.languageServiceEnabled) {
802            return [];
803        }
804        updateProjectIfDirty(this);
805        this.builderState = BuilderState.create(this.program!, this.projectService.toCanonicalFileName, this.builderState, /*disableUseFileVersionAsSignature*/ true);
806        return mapDefined(
807            BuilderState.getFilesAffectedBy(
808                this.builderState,
809                this.program!,
810                scriptInfo.path,
811                this.cancellationToken,
812                maybeBind(this.projectService.host, this.projectService.host.createHash),
813                this.getCanonicalFileName,
814            ),
815            sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined
816        );
817    }
818
819    /**
820     * Returns true if emit was conducted
821     */
822    emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): EmitResult {
823        if (!this.languageServiceEnabled || !this.shouldEmitFile(scriptInfo)) {
824            return { emitSkipped: true, diagnostics: emptyArray };
825        }
826        const { emitSkipped, diagnostics, outputFiles } = this.getLanguageService().getEmitOutput(scriptInfo.fileName);
827        if (!emitSkipped) {
828            for (const outputFile of outputFiles) {
829                const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, this.currentDirectory);
830                writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark);
831            }
832
833            // Update the signature
834            if (this.builderState && getEmitDeclarations(this.compilerOptions)) {
835                const dtsFiles = outputFiles.filter(f => isDeclarationFileName(f.name));
836                if (dtsFiles.length === 1) {
837                    const sourceFile = this.program!.getSourceFile(scriptInfo.fileName)!;
838                    const signature = this.projectService.host.createHash ?
839                        this.projectService.host.createHash(dtsFiles[0].text) :
840                        generateDjb2Hash(dtsFiles[0].text);
841                    BuilderState.updateSignatureOfFile(this.builderState, signature, sourceFile.resolvedPath);
842                }
843            }
844        }
845
846        return { emitSkipped, diagnostics };
847    }
848
849    enableLanguageService() {
850        if (this.languageServiceEnabled || this.projectService.serverMode === LanguageServiceMode.Syntactic) {
851            return;
852        }
853        this.languageServiceEnabled = true;
854        this.lastFileExceededProgramSize = undefined;
855        this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ true);
856    }
857
858    disableLanguageService(lastFileExceededProgramSize?: string) {
859        if (!this.languageServiceEnabled) {
860            return;
861        }
862        Debug.assert(this.projectService.serverMode !== LanguageServiceMode.Syntactic);
863        this.languageService.cleanupSemanticCache();
864        this.languageServiceEnabled = false;
865        this.lastFileExceededProgramSize = lastFileExceededProgramSize;
866        this.builderState = undefined;
867        if (this.autoImportProviderHost) {
868            this.autoImportProviderHost.close();
869        }
870        this.autoImportProviderHost = undefined;
871        this.resolutionCache.closeTypeRootsWatch();
872        this.clearGeneratedFileWatch();
873        this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false);
874    }
875
876    getProjectName() {
877        return this.projectName;
878    }
879
880    protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition): TypeAcquisition {
881        if (!newTypeAcquisition || !newTypeAcquisition.include) {
882            // Nothing to filter out, so just return as-is
883            return newTypeAcquisition;
884        }
885        return { ...newTypeAcquisition, include: this.removeExistingTypings(newTypeAcquisition.include) };
886    }
887
888    getExternalFiles(): SortedReadonlyArray<string> {
889        return sort(flatMap(this.plugins, plugin => {
890            if (typeof plugin.module.getExternalFiles !== "function") return;
891            try {
892                return plugin.module.getExternalFiles(this);
893            }
894            catch (e) {
895                this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`);
896                if (e.stack) {
897                    this.projectService.logger.info(e.stack);
898                }
899            }
900        }));
901    }
902
903    getSourceFile(path: Path) {
904        if (!this.program) {
905            return undefined;
906        }
907        return this.program.getSourceFileByPath(path);
908    }
909
910    /** @internal */
911    getSourceFileOrConfigFile(path: Path): SourceFile | undefined {
912        const options = this.program!.getCompilerOptions();
913        return path === options.configFilePath ? options.configFile : this.getSourceFile(path);
914    }
915
916    close() {
917        if (this.program) {
918            // if we have a program - release all files that are enlisted in program but arent root
919            // The releasing of the roots happens later
920            // The project could have pending update remaining and hence the info could be in the files but not in program graph
921            for (const f of this.program.getSourceFiles()) {
922                this.detachScriptInfoIfNotRoot(f.fileName);
923            }
924            this.program.forEachResolvedProjectReference(ref =>
925                this.detachScriptInfoFromProject(ref.sourceFile.fileName));
926        }
927
928        // Release external files
929        forEach(this.externalFiles, externalFile => this.detachScriptInfoIfNotRoot(externalFile));
930        // Always remove root files from the project
931        for (const root of this.rootFiles) {
932            root.detachFromProject(this);
933        }
934        this.projectService.pendingEnsureProjectForOpenFiles = true;
935
936        this.rootFiles = undefined!;
937        this.rootFilesMap = undefined!;
938        this.externalFiles = undefined;
939        this.program = undefined;
940        this.builderState = undefined;
941        this.resolutionCache.clear();
942        this.resolutionCache = undefined!;
943        this.cachedUnresolvedImportsPerFile = undefined!;
944        this.moduleSpecifierCache = undefined!;
945        this.directoryStructureHost = undefined!;
946        this.exportMapCache = undefined;
947        this.projectErrors = undefined;
948        this.plugins.length = 0;
949
950        // Clean up file watchers waiting for missing files
951        if (this.missingFilesMap) {
952            clearMap(this.missingFilesMap, closeFileWatcher);
953            this.missingFilesMap = undefined;
954        }
955        this.clearGeneratedFileWatch();
956        this.clearInvalidateResolutionOfFailedLookupTimer();
957        if (this.autoImportProviderHost) {
958            this.autoImportProviderHost.close();
959        }
960        this.autoImportProviderHost = undefined;
961        if (this.noDtsResolutionProject) {
962            this.noDtsResolutionProject.close();
963        }
964        this.noDtsResolutionProject = undefined;
965
966        // signal language service to release source files acquired from document registry
967        this.languageService.dispose();
968        this.languageService = undefined!;
969    }
970
971    private detachScriptInfoIfNotRoot(uncheckedFilename: string) {
972        const info = this.projectService.getScriptInfo(uncheckedFilename);
973        // We might not find the script info in case its not associated with the project any more
974        // and project graph was not updated (eg delayed update graph in case of files changed/deleted on the disk)
975        if (info && !this.isRoot(info)) {
976            info.detachFromProject(this);
977        }
978    }
979
980    isClosed() {
981        return this.rootFiles === undefined;
982    }
983
984    hasRoots() {
985        return this.rootFiles && this.rootFiles.length > 0;
986    }
987
988    /** @internal */
989    isOrphan() {
990        return false;
991    }
992
993    getRootFiles() {
994        return this.rootFiles && this.rootFiles.map(info => info.fileName);
995    }
996
997    /** @internal */
998    getRootFilesMap() {
999        return this.rootFilesMap;
1000    }
1001
1002    getRootScriptInfos() {
1003        return this.rootFiles;
1004    }
1005
1006    getScriptInfos(): ScriptInfo[] {
1007        if (!this.languageServiceEnabled) {
1008            // if language service is not enabled - return just root files
1009            return this.rootFiles;
1010        }
1011        return map(this.program!.getSourceFiles(), sourceFile => {
1012            const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.resolvedPath);
1013            Debug.assert(!!scriptInfo, "getScriptInfo", () => `scriptInfo for a file '${sourceFile.fileName}' Path: '${sourceFile.path}' / '${sourceFile.resolvedPath}' is missing.`);
1014            return scriptInfo;
1015        });
1016    }
1017
1018    getExcludedFiles(): readonly NormalizedPath[] {
1019        return emptyArray;
1020    }
1021
1022    getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) {
1023        if (!this.program) {
1024            return [];
1025        }
1026
1027        if (!this.languageServiceEnabled) {
1028            // if language service is disabled assume that all files in program are root files + default library
1029            let rootFiles = this.getRootFiles();
1030            if (this.compilerOptions) {
1031                const defaultLibrary = getDefaultLibFilePath(this.compilerOptions);
1032                if (defaultLibrary) {
1033                    (rootFiles || (rootFiles = [])).push(asNormalizedPath(defaultLibrary));
1034                }
1035            }
1036            return rootFiles;
1037        }
1038        const result: NormalizedPath[] = [];
1039        for (const f of this.program.getSourceFiles()) {
1040            if (excludeFilesFromExternalLibraries && this.program.isSourceFileFromExternalLibrary(f)) {
1041                continue;
1042            }
1043            result.push(asNormalizedPath(f.fileName));
1044        }
1045        if (!excludeConfigFiles) {
1046            const configFile = this.program.getCompilerOptions().configFile;
1047            if (configFile) {
1048                result.push(asNormalizedPath(configFile.fileName));
1049                if (configFile.extendedSourceFiles) {
1050                    for (const f of configFile.extendedSourceFiles) {
1051                        result.push(asNormalizedPath(f));
1052                    }
1053                }
1054            }
1055        }
1056        return result;
1057    }
1058
1059    /** @internal */
1060    getFileNamesWithRedirectInfo(includeProjectReferenceRedirectInfo: boolean) {
1061        return this.getFileNames().map((fileName): protocol.FileWithProjectReferenceRedirectInfo => ({
1062            fileName,
1063            isSourceOfProjectReferenceRedirect: includeProjectReferenceRedirectInfo && this.isSourceOfProjectReferenceRedirect(fileName)
1064         }));
1065    }
1066
1067    hasConfigFile(configFilePath: NormalizedPath) {
1068        if (this.program && this.languageServiceEnabled) {
1069            const configFile = this.program.getCompilerOptions().configFile;
1070            if (configFile) {
1071                if (configFilePath === asNormalizedPath(configFile.fileName)) {
1072                    return true;
1073                }
1074                if (configFile.extendedSourceFiles) {
1075                    for (const f of configFile.extendedSourceFiles) {
1076                        if (configFilePath === asNormalizedPath(f)) {
1077                            return true;
1078                        }
1079                    }
1080                }
1081            }
1082        }
1083        return false;
1084    }
1085
1086    containsScriptInfo(info: ScriptInfo): boolean {
1087        if (this.isRoot(info)) return true;
1088        if (!this.program) return false;
1089        const file = this.program.getSourceFileByPath(info.path);
1090        return !!file && file.resolvedPath === info.path;
1091    }
1092
1093    containsFile(filename: NormalizedPath, requireOpen?: boolean): boolean {
1094        const info = this.projectService.getScriptInfoForNormalizedPath(filename);
1095        if (info && (info.isScriptOpen() || !requireOpen)) {
1096            return this.containsScriptInfo(info);
1097        }
1098        return false;
1099    }
1100
1101    isRoot(info: ScriptInfo) {
1102        return this.rootFilesMap && this.rootFilesMap.get(info.path)?.info === info;
1103    }
1104
1105    // add a root file to project
1106    addRoot(info: ScriptInfo, fileName?: NormalizedPath) {
1107        Debug.assert(!this.isRoot(info));
1108        this.rootFiles.push(info);
1109        this.rootFilesMap.set(info.path, { fileName: fileName || info.fileName, info });
1110        info.attachToProject(this);
1111
1112        this.markAsDirty();
1113    }
1114
1115    // add a root file that doesnt exist on host
1116    addMissingFileRoot(fileName: NormalizedPath) {
1117        const path = this.projectService.toPath(fileName);
1118        this.rootFilesMap.set(path, { fileName });
1119        this.markAsDirty();
1120    }
1121
1122    removeFile(info: ScriptInfo, fileExists: boolean, detachFromProject: boolean) {
1123        if (this.isRoot(info)) {
1124            this.removeRoot(info);
1125        }
1126        if (fileExists) {
1127            // If file is present, just remove the resolutions for the file
1128            this.resolutionCache.removeResolutionsOfFile(info.path);
1129        }
1130        else {
1131            this.resolutionCache.invalidateResolutionOfFile(info.path);
1132        }
1133        this.cachedUnresolvedImportsPerFile.delete(info.path);
1134
1135        if (detachFromProject) {
1136            info.detachFromProject(this);
1137        }
1138
1139        this.markAsDirty();
1140    }
1141
1142    registerFileUpdate(fileName: string) {
1143        (this.updatedFileNames || (this.updatedFileNames = new Set<string>())).add(fileName);
1144    }
1145
1146    /** @internal */
1147    markFileAsDirty(changedFile: Path) {
1148        this.markAsDirty();
1149        if (this.exportMapCache && !this.exportMapCache.isEmpty()) {
1150            (this.changedFilesForExportMapCache ||= new Set()).add(changedFile);
1151        }
1152    }
1153
1154    markAsDirty() {
1155        if (!this.dirty) {
1156            this.projectStateVersion++;
1157            this.dirty = true;
1158        }
1159    }
1160
1161    /** @internal */
1162    onAutoImportProviderSettingsChanged() {
1163        if (this.autoImportProviderHost === false) {
1164            this.autoImportProviderHost = undefined;
1165        }
1166        else {
1167            this.autoImportProviderHost?.markAsDirty();
1168        }
1169    }
1170
1171    /** @internal */
1172    onPackageJsonChange(packageJsonPath: Path) {
1173        if (this.packageJsonsForAutoImport?.has(packageJsonPath)) {
1174            this.moduleSpecifierCache.clear();
1175            if (this.autoImportProviderHost) {
1176                this.autoImportProviderHost.markAsDirty();
1177            }
1178        }
1179    }
1180
1181    /** @internal */
1182    onFileAddedOrRemoved(isSymlink: boolean | undefined) {
1183        this.hasAddedorRemovedFiles = true;
1184        if (isSymlink) {
1185            this.hasAddedOrRemovedSymlinks = true;
1186        }
1187    }
1188
1189    /** @internal */
1190    onDiscoveredSymlink() {
1191        this.hasAddedOrRemovedSymlinks = true;
1192    }
1193
1194    /**
1195     * Updates set of files that contribute to this project
1196     * @returns: true if set of files in the project stays the same and false - otherwise.
1197     */
1198    updateGraph(): boolean {
1199        tracing?.push(tracing.Phase.Session, "updateGraph", { name: this.projectName, kind: ProjectKind[this.projectKind] });
1200        perfLogger.logStartUpdateGraph();
1201        this.resolutionCache.startRecordingFilesWithChangedResolutions();
1202
1203        const hasNewProgram = this.updateGraphWorker();
1204        const hasAddedorRemovedFiles = this.hasAddedorRemovedFiles;
1205        this.hasAddedorRemovedFiles = false;
1206        this.hasAddedOrRemovedSymlinks = false;
1207
1208        const changedFiles: readonly Path[] = this.resolutionCache.finishRecordingFilesWithChangedResolutions() || emptyArray;
1209
1210        for (const file of changedFiles) {
1211            // delete cached information for changed files
1212            this.cachedUnresolvedImportsPerFile.delete(file);
1213        }
1214
1215        // update builder only if language service is enabled
1216        // otherwise tell it to drop its internal state
1217        if (this.languageServiceEnabled && this.projectService.serverMode === LanguageServiceMode.Semantic) {
1218            // 1. no changes in structure, no changes in unresolved imports - do nothing
1219            // 2. no changes in structure, unresolved imports were changed - collect unresolved imports for all files
1220            // (can reuse cached imports for files that were not changed)
1221            // 3. new files were added/removed, but compilation settings stays the same - collect unresolved imports for all new/modified files
1222            // (can reuse cached imports for files that were not changed)
1223            // 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch
1224            if (hasNewProgram || changedFiles.length) {
1225                this.lastCachedUnresolvedImportsList = getUnresolvedImports(this.program!, this.cachedUnresolvedImportsPerFile);
1226            }
1227
1228            this.projectService.typingsCache.enqueueInstallTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasAddedorRemovedFiles);
1229        }
1230        else {
1231            this.lastCachedUnresolvedImportsList = undefined;
1232        }
1233
1234        const isFirstProgramLoad = this.projectProgramVersion === 0 && hasNewProgram;
1235        if (hasNewProgram) {
1236            this.projectProgramVersion++;
1237        }
1238        if (hasAddedorRemovedFiles) {
1239            if (!this.autoImportProviderHost) this.autoImportProviderHost = undefined;
1240            this.autoImportProviderHost?.markAsDirty();
1241        }
1242        if (isFirstProgramLoad) {
1243            // Preload auto import provider so it's not created during completions request
1244            this.getPackageJsonAutoImportProvider();
1245        }
1246        perfLogger.logStopUpdateGraph();
1247        tracing?.pop();
1248        return !hasNewProgram;
1249    }
1250
1251    /** @internal */
1252    updateTypingFiles(typingFiles: SortedReadonlyArray<string>) {
1253        if (enumerateInsertsAndDeletes<string, string>(typingFiles, this.typingFiles, getStringComparer(!this.useCaseSensitiveFileNames()),
1254            /*inserted*/ noop,
1255            removed => this.detachScriptInfoFromProject(removed)
1256        )) {
1257            // If typing files changed, then only schedule project update
1258            this.typingFiles = typingFiles;
1259            // Invalidate files with unresolved imports
1260            this.resolutionCache.setFilesWithInvalidatedNonRelativeUnresolvedImports(this.cachedUnresolvedImportsPerFile);
1261            this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this);
1262        }
1263    }
1264
1265    /** @internal */
1266    getCurrentProgram(): Program | undefined {
1267        return this.program;
1268    }
1269
1270    protected removeExistingTypings(include: string[]): string[] {
1271        const existing = getAutomaticTypeDirectiveNames(this.getCompilerOptions(), this.directoryStructureHost);
1272        return include.filter(i => existing.indexOf(i) < 0);
1273    }
1274
1275    private updateGraphWorker() {
1276        const oldProgram = this.languageService.getCurrentProgram();
1277        Debug.assert(!this.isClosed(), "Called update graph worker of closed project");
1278        this.writeLog(`Starting updateGraphWorker: Project: ${this.getProjectName()}`);
1279        const start = timestamp();
1280        this.hasInvalidatedResolutions = this.resolutionCache.createHasInvalidatedResolutions(returnFalse);
1281        this.resolutionCache.startCachingPerDirectoryResolution();
1282        this.program = this.languageService.getProgram(); // TODO: GH#18217
1283        this.dirty = false;
1284        tracing?.push(tracing.Phase.Session, "finishCachingPerDirectoryResolution");
1285        this.resolutionCache.finishCachingPerDirectoryResolution(this.program, oldProgram);
1286        tracing?.pop();
1287
1288        Debug.assert(oldProgram === undefined || this.program !== undefined);
1289
1290        // bump up the version if
1291        // - oldProgram is not set - this is a first time updateGraph is called
1292        // - newProgram is different from the old program and structure of the old program was not reused.
1293        let hasNewProgram = false;
1294        if (this.program && (!oldProgram || (this.program !== oldProgram && this.program.structureIsReused !== StructureIsReused.Completely))) {
1295            hasNewProgram = true;
1296            if (oldProgram) {
1297                for (const f of oldProgram.getSourceFiles()) {
1298                    const newFile = this.program.getSourceFileByPath(f.resolvedPath);
1299                    if (!newFile || (f.resolvedPath === f.path && newFile.resolvedPath !== f.path)) {
1300                        // new program does not contain this file - detach it from the project
1301                        // - remove resolutions only if the new program doesnt contain source file by the path (not resolvedPath since path is used for resolution)
1302                        this.detachScriptInfoFromProject(f.fileName, !!this.program.getSourceFileByPath(f.path));
1303                    }
1304                }
1305
1306                oldProgram.forEachResolvedProjectReference(resolvedProjectReference => {
1307                    if (!this.program!.getResolvedProjectReferenceByPath(resolvedProjectReference.sourceFile.path)) {
1308                        this.detachScriptInfoFromProject(resolvedProjectReference.sourceFile.fileName);
1309                    }
1310                });
1311            }
1312
1313            // Update the missing file paths watcher
1314            updateMissingFilePathsWatch(
1315                this.program,
1316                this.missingFilesMap || (this.missingFilesMap = new Map()),
1317                // Watch the missing files
1318                missingFilePath => this.addMissingFileWatcher(missingFilePath)
1319            );
1320
1321            if (this.generatedFilesMap) {
1322                const outPath = outFile(this.compilerOptions);
1323                if (isGeneratedFileWatcher(this.generatedFilesMap)) {
1324                    // --out
1325                    if (!outPath || !this.isValidGeneratedFileWatcher(
1326                        removeFileExtension(outPath) + Extension.Dts,
1327                        this.generatedFilesMap,
1328                    )) {
1329                        this.clearGeneratedFileWatch();
1330                    }
1331                }
1332                else {
1333                    // MultiFile
1334                    if (outPath) {
1335                        this.clearGeneratedFileWatch();
1336                    }
1337                    else {
1338                        this.generatedFilesMap.forEach((watcher, source) => {
1339                            const sourceFile = this.program!.getSourceFileByPath(source);
1340                            if (!sourceFile ||
1341                                sourceFile.resolvedPath !== source ||
1342                                !this.isValidGeneratedFileWatcher(
1343                                    getDeclarationEmitOutputFilePathWorker(sourceFile.fileName, this.compilerOptions, this.currentDirectory, this.program!.getCommonSourceDirectory(), this.getCanonicalFileName),
1344                                    watcher
1345                                )) {
1346                                closeFileWatcherOf(watcher);
1347                                (this.generatedFilesMap as ESMap<string, GeneratedFileWatcher>).delete(source);
1348                            }
1349                        });
1350                    }
1351                }
1352            }
1353
1354            // Watch the type locations that would be added to program as part of automatic type resolutions
1355            if (this.languageServiceEnabled && this.projectService.serverMode === LanguageServiceMode.Semantic) {
1356                this.resolutionCache.updateTypeRootsWatch();
1357            }
1358        }
1359
1360        if (this.exportMapCache && !this.exportMapCache.isEmpty()) {
1361            this.exportMapCache.releaseSymbols();
1362            if (this.hasAddedorRemovedFiles || oldProgram && !this.program!.structureIsReused) {
1363                this.exportMapCache.clear();
1364            }
1365            else if (this.changedFilesForExportMapCache && oldProgram && this.program) {
1366                forEachKey(this.changedFilesForExportMapCache, fileName => {
1367                    const oldSourceFile = oldProgram.getSourceFileByPath(fileName);
1368                    const sourceFile = this.program!.getSourceFileByPath(fileName);
1369                    if (!oldSourceFile || !sourceFile) {
1370                        this.exportMapCache!.clear();
1371                        return true;
1372                    }
1373                    return this.exportMapCache!.onFileChanged(oldSourceFile, sourceFile, !!this.getTypeAcquisition().enable);
1374                });
1375            }
1376        }
1377        if (this.changedFilesForExportMapCache) {
1378            this.changedFilesForExportMapCache.clear();
1379        }
1380
1381        if (this.hasAddedOrRemovedSymlinks || this.program && !this.program.structureIsReused && this.getCompilerOptions().preserveSymlinks) {
1382            // With --preserveSymlinks, we may not determine that a file is a symlink, so we never set `hasAddedOrRemovedSymlinks`
1383            this.symlinks = undefined;
1384            this.moduleSpecifierCache.clear();
1385        }
1386
1387        const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray<string>;
1388        this.externalFiles = this.getExternalFiles();
1389        enumerateInsertsAndDeletes<string, string>(this.externalFiles, oldExternalFiles, getStringComparer(!this.useCaseSensitiveFileNames()),
1390            // Ensure a ScriptInfo is created for new external files. This is performed indirectly
1391            // by the host for files in the program when the program is retrieved above but
1392            // the program doesn't contain external files so this must be done explicitly.
1393            inserted => {
1394                const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(inserted, this.currentDirectory, this.directoryStructureHost);
1395                scriptInfo?.attachToProject(this);
1396            },
1397            removed => this.detachScriptInfoFromProject(removed)
1398        );
1399        const elapsed = timestamp() - start;
1400        this.sendPerformanceEvent("UpdateGraph", elapsed);
1401        this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} Version: ${this.getProjectVersion()} structureChanged: ${hasNewProgram}${this.program ? ` structureIsReused:: ${(ts as any).StructureIsReused[this.program.structureIsReused]}` : ""} Elapsed: ${elapsed}ms`);
1402        if (this.hasAddedorRemovedFiles) {
1403            this.print(/*writeProjectFileNames*/ true);
1404        }
1405        else if (this.program !== oldProgram) {
1406            this.writeLog(`Different program with same set of files`);
1407        }
1408        return hasNewProgram;
1409    }
1410
1411    /** @internal */
1412    sendPerformanceEvent(kind: PerformanceEvent["kind"], durationMs: number) {
1413        this.projectService.sendPerformanceEvent(kind, durationMs);
1414    }
1415
1416    private detachScriptInfoFromProject(uncheckedFileName: string, noRemoveResolution?: boolean) {
1417        const scriptInfoToDetach = this.projectService.getScriptInfo(uncheckedFileName);
1418        if (scriptInfoToDetach) {
1419            scriptInfoToDetach.detachFromProject(this);
1420            if (!noRemoveResolution) {
1421                this.resolutionCache.removeResolutionsOfFile(scriptInfoToDetach.path);
1422            }
1423        }
1424    }
1425
1426    private addMissingFileWatcher(missingFilePath: Path) {
1427        if (isConfiguredProject(this)) {
1428            // If this file is referenced config file, we are already watching it, no need to watch again
1429            const configFileExistenceInfo = this.projectService.configFileExistenceInfoCache.get(missingFilePath as string as NormalizedPath);
1430            if (configFileExistenceInfo?.config?.projects.has(this.canonicalConfigFilePath)) return noopFileWatcher;
1431        }
1432        const fileWatcher = this.projectService.watchFactory.watchFile(
1433            missingFilePath,
1434            (fileName, eventKind) => {
1435                if (isConfiguredProject(this)) {
1436                    this.getCachedDirectoryStructureHost().addOrDeleteFile(fileName, missingFilePath, eventKind);
1437                }
1438
1439                if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap!.has(missingFilePath)) {
1440                    this.missingFilesMap!.delete(missingFilePath);
1441                    fileWatcher.close();
1442
1443                    // When a missing file is created, we should update the graph.
1444                    this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this);
1445                }
1446            },
1447            PollingInterval.Medium,
1448            this.projectService.getWatchOptions(this),
1449            WatchType.MissingFile,
1450            this
1451        );
1452        return fileWatcher;
1453    }
1454
1455    private isWatchedMissingFile(path: Path) {
1456        return !!this.missingFilesMap && this.missingFilesMap.has(path);
1457    }
1458
1459    /** @internal */
1460    addGeneratedFileWatch(generatedFile: string, sourceFile: string) {
1461        if (outFile(this.compilerOptions)) {
1462            // Single watcher
1463            if (!this.generatedFilesMap) {
1464                this.generatedFilesMap = this.createGeneratedFileWatcher(generatedFile);
1465            }
1466        }
1467        else {
1468            // Map
1469            const path = this.toPath(sourceFile);
1470            if (this.generatedFilesMap) {
1471                if (isGeneratedFileWatcher(this.generatedFilesMap)) {
1472                    Debug.fail(`${this.projectName} Expected to not have --out watcher for generated file with options: ${JSON.stringify(this.compilerOptions)}`);
1473                }
1474                if (this.generatedFilesMap.has(path)) return;
1475            }
1476            else {
1477                this.generatedFilesMap = new Map();
1478            }
1479            this.generatedFilesMap.set(path, this.createGeneratedFileWatcher(generatedFile));
1480        }
1481    }
1482
1483    private createGeneratedFileWatcher(generatedFile: string): GeneratedFileWatcher {
1484        return {
1485            generatedFilePath: this.toPath(generatedFile),
1486            watcher: this.projectService.watchFactory.watchFile(
1487                generatedFile,
1488                () => {
1489                    this.clearSourceMapperCache();
1490                    this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this);
1491                },
1492                PollingInterval.High,
1493                this.projectService.getWatchOptions(this),
1494                WatchType.MissingGeneratedFile,
1495                this
1496            )
1497        };
1498    }
1499
1500    private isValidGeneratedFileWatcher(generateFile: string, watcher: GeneratedFileWatcher) {
1501        return this.toPath(generateFile) === watcher.generatedFilePath;
1502    }
1503
1504    private clearGeneratedFileWatch() {
1505        if (this.generatedFilesMap) {
1506            if (isGeneratedFileWatcher(this.generatedFilesMap)) {
1507                closeFileWatcherOf(this.generatedFilesMap);
1508            }
1509            else {
1510                clearMap(this.generatedFilesMap, closeFileWatcherOf);
1511            }
1512            this.generatedFilesMap = undefined;
1513        }
1514    }
1515
1516    getScriptInfoForNormalizedPath(fileName: NormalizedPath): ScriptInfo | undefined {
1517        const scriptInfo = this.projectService.getScriptInfoForPath(this.toPath(fileName));
1518        if (scriptInfo && !scriptInfo.isAttached(this)) {
1519            return Errors.ThrowProjectDoesNotContainDocument(fileName, this);
1520        }
1521        return scriptInfo;
1522    }
1523
1524    getScriptInfo(uncheckedFileName: string) {
1525        return this.projectService.getScriptInfo(uncheckedFileName);
1526    }
1527
1528    filesToString(writeProjectFileNames: boolean) {
1529        if (this.isInitialLoadPending()) return "\tFiles (0) InitialLoadPending\n";
1530        if (!this.program) return "\tFiles (0) NoProgram\n";
1531        const sourceFiles = this.program.getSourceFiles();
1532        let strBuilder = `\tFiles (${sourceFiles.length})\n`;
1533        if (writeProjectFileNames) {
1534            for (const file of sourceFiles) {
1535                strBuilder += `\t${file.fileName}\n`;
1536            }
1537            strBuilder += "\n\n";
1538            explainFiles(this.program, s => strBuilder += `\t${s}\n`);
1539        }
1540        return strBuilder;
1541    }
1542
1543    /** @internal */
1544    print(writeProjectFileNames: boolean) {
1545        this.writeLog(`Project '${this.projectName}' (${ProjectKind[this.projectKind]})`);
1546        this.writeLog(this.filesToString(writeProjectFileNames && this.projectService.logger.hasLevel(LogLevel.verbose)));
1547        this.writeLog("-----------------------------------------------");
1548        if (this.autoImportProviderHost) {
1549            this.autoImportProviderHost.print(/*writeProjectFileNames*/ false);
1550        }
1551    }
1552
1553    setCompilerOptions(compilerOptions: CompilerOptions) {
1554        if (compilerOptions) {
1555            compilerOptions.allowNonTsExtensions = true;
1556            const oldOptions = this.compilerOptions;
1557            this.compilerOptions = compilerOptions;
1558            this.setInternalCompilerOptionsForEmittingJsFiles();
1559            this.noDtsResolutionProject?.setCompilerOptions(this.getCompilerOptionsForNoDtsResolutionProject());
1560            if (changesAffectModuleResolution(oldOptions, compilerOptions)) {
1561                // reset cached unresolved imports if changes in compiler options affected module resolution
1562                this.cachedUnresolvedImportsPerFile.clear();
1563                this.lastCachedUnresolvedImportsList = undefined;
1564                this.resolutionCache.clear();
1565                this.moduleSpecifierCache.clear();
1566            }
1567            this.markAsDirty();
1568        }
1569    }
1570
1571    /** @internal */
1572    setWatchOptions(watchOptions: WatchOptions | undefined) {
1573        this.watchOptions = watchOptions;
1574    }
1575
1576    /** @internal */
1577    getWatchOptions(): WatchOptions | undefined {
1578        return this.watchOptions;
1579    }
1580
1581    setTypeAcquisition(newTypeAcquisition: TypeAcquisition | undefined): void {
1582        if (newTypeAcquisition) {
1583            this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition);
1584        }
1585    }
1586
1587    getTypeAcquisition() {
1588        return this.typeAcquisition || {};
1589    }
1590
1591    /** @internal */
1592    getChangesSinceVersion(lastKnownVersion?: number, includeProjectReferenceRedirectInfo?: boolean): ProjectFilesWithTSDiagnostics {
1593        const includeProjectReferenceRedirectInfoIfRequested =
1594            includeProjectReferenceRedirectInfo
1595                ? (files: ESMap<string, boolean>) => arrayFrom(files.entries(), ([fileName, isSourceOfProjectReferenceRedirect]): protocol.FileWithProjectReferenceRedirectInfo => ({
1596                    fileName,
1597                    isSourceOfProjectReferenceRedirect
1598                }))
1599                : (files: ESMap<string, boolean>) => arrayFrom(files.keys());
1600
1601        // Update the graph only if initial configured project load is not pending
1602        if (!this.isInitialLoadPending()) {
1603            updateProjectIfDirty(this);
1604        }
1605
1606        const info: protocol.ProjectVersionInfo = {
1607            projectName: this.getProjectName(),
1608            version: this.projectProgramVersion,
1609            isInferred: isInferredProject(this),
1610            options: this.getCompilationSettings(),
1611            languageServiceDisabled: !this.languageServiceEnabled,
1612            lastFileExceededProgramSize: this.lastFileExceededProgramSize
1613        };
1614        const updatedFileNames = this.updatedFileNames;
1615        this.updatedFileNames = undefined;
1616        // check if requested version is the same that we have reported last time
1617        if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
1618            // if current structure version is the same - return info without any changes
1619            if (this.projectProgramVersion === this.lastReportedVersion && !updatedFileNames) {
1620                return { info, projectErrors: this.getGlobalProjectErrors() };
1621            }
1622            // compute and return the difference
1623            const lastReportedFileNames = this.lastReportedFileNames;
1624            const externalFiles = this.getExternalFiles().map((f): protocol.FileWithProjectReferenceRedirectInfo => ({
1625                fileName: toNormalizedPath(f),
1626                isSourceOfProjectReferenceRedirect: false
1627            }));
1628            const currentFiles = arrayToMap(
1629                this.getFileNamesWithRedirectInfo(!!includeProjectReferenceRedirectInfo).concat(externalFiles),
1630                info => info.fileName,
1631                info => info.isSourceOfProjectReferenceRedirect
1632            );
1633
1634            const added: ESMap<string, boolean> = new Map<string, boolean>();
1635            const removed: ESMap<string, boolean> = new Map<string, boolean>();
1636
1637            const updated: string[] = updatedFileNames ? arrayFrom(updatedFileNames.keys()) : [];
1638            const updatedRedirects: protocol.FileWithProjectReferenceRedirectInfo[] = [];
1639
1640            forEachEntry(currentFiles, (isSourceOfProjectReferenceRedirect, fileName) => {
1641                if (!lastReportedFileNames.has(fileName)) {
1642                    added.set(fileName, isSourceOfProjectReferenceRedirect);
1643                }
1644                else if (includeProjectReferenceRedirectInfo && isSourceOfProjectReferenceRedirect !== lastReportedFileNames.get(fileName)) {
1645                    updatedRedirects.push({
1646                        fileName,
1647                        isSourceOfProjectReferenceRedirect
1648                    });
1649                }
1650            });
1651            forEachEntry(lastReportedFileNames, (isSourceOfProjectReferenceRedirect, fileName) => {
1652                if (!currentFiles.has(fileName)) {
1653                    removed.set(fileName, isSourceOfProjectReferenceRedirect);
1654                }
1655            });
1656            this.lastReportedFileNames = currentFiles;
1657            this.lastReportedVersion = this.projectProgramVersion;
1658            return {
1659                info,
1660                changes: {
1661                    added: includeProjectReferenceRedirectInfoIfRequested(added),
1662                    removed: includeProjectReferenceRedirectInfoIfRequested(removed),
1663                    updated: includeProjectReferenceRedirectInfo
1664                        ? updated.map((fileName): protocol.FileWithProjectReferenceRedirectInfo => ({
1665                            fileName,
1666                            isSourceOfProjectReferenceRedirect: this.isSourceOfProjectReferenceRedirect(fileName)
1667                        }))
1668                        : updated,
1669                    updatedRedirects: includeProjectReferenceRedirectInfo ? updatedRedirects : undefined
1670                },
1671                projectErrors: this.getGlobalProjectErrors()
1672            };
1673        }
1674        else {
1675            // unknown version - return everything
1676            const projectFileNames = this.getFileNamesWithRedirectInfo(!!includeProjectReferenceRedirectInfo);
1677            const externalFiles = this.getExternalFiles().map((f): protocol.FileWithProjectReferenceRedirectInfo => ({
1678                fileName: toNormalizedPath(f),
1679                isSourceOfProjectReferenceRedirect: false
1680            }));
1681            const allFiles = projectFileNames.concat(externalFiles);
1682            this.lastReportedFileNames = arrayToMap(
1683                allFiles,
1684                info => info.fileName,
1685                info => info.isSourceOfProjectReferenceRedirect
1686            );
1687            this.lastReportedVersion = this.projectProgramVersion;
1688            return {
1689                info,
1690                files: includeProjectReferenceRedirectInfo ? allFiles : allFiles.map(f => f.fileName),
1691                projectErrors: this.getGlobalProjectErrors()
1692            };
1693        }
1694    }
1695
1696    // remove a root file from project
1697    protected removeRoot(info: ScriptInfo): void {
1698        orderedRemoveItem(this.rootFiles, info);
1699        this.rootFilesMap.delete(info.path);
1700    }
1701
1702    /** @internal */
1703    isSourceOfProjectReferenceRedirect(fileName: string) {
1704        return !!this.program && this.program.isSourceOfProjectReferenceRedirect(fileName);
1705    }
1706
1707    /** @internal */
1708    protected getGlobalPluginSearchPaths() {
1709        // Search any globally-specified probe paths, then our peer node_modules
1710        return [
1711            ...this.projectService.pluginProbeLocations,
1712            // ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/
1713            combinePaths(this.projectService.getExecutingFilePath(), "../../.."),
1714        ];
1715    }
1716
1717    protected enableGlobalPlugins(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined): void {
1718        if (!this.projectService.globalPlugins.length) return;
1719        const host = this.projectService.host;
1720
1721        if (!host.require && !host.importPlugin) {
1722            this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
1723            return;
1724        }
1725
1726        // Enable global plugins with synthetic configuration entries
1727        const searchPaths = this.getGlobalPluginSearchPaths();
1728        for (const globalPluginName of this.projectService.globalPlugins) {
1729            // Skip empty names from odd commandline parses
1730            if (!globalPluginName) continue;
1731
1732            // Skip already-locally-loaded plugins
1733            if (options.plugins && options.plugins.some(p => p.name === globalPluginName)) continue;
1734
1735            // Provide global: true so plugins can detect why they can't find their config
1736            this.projectService.logger.info(`Loading global plugin ${globalPluginName}`);
1737
1738            this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths, pluginConfigOverrides);
1739        }
1740    }
1741
1742    /**
1743     * Performs the initial steps of enabling a plugin by finding and instantiating the module for a plugin synchronously using 'require'.
1744     *
1745     * @internal
1746     */
1747    beginEnablePluginSync(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | undefined): BeginEnablePluginResult {
1748        Debug.assertIsDefined(this.projectService.host.require);
1749
1750        let errorLogs: string[] | undefined;
1751        const log = (message: string) => this.projectService.logger.info(message);
1752        const logError = (message: string) => {
1753            (errorLogs ??= []).push(message);
1754        };
1755        const resolvedModule = firstDefined(searchPaths, searchPath =>
1756            Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log, logError) as PluginModuleFactory | undefined);
1757        return { pluginConfigEntry, pluginConfigOverrides, resolvedModule, errorLogs };
1758    }
1759
1760    /**
1761     * Performs the initial steps of enabling a plugin by finding and instantiating the module for a plugin asynchronously using dynamic `import`.
1762     *
1763     * @internal
1764     */
1765    async beginEnablePluginAsync(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | undefined): Promise<BeginEnablePluginResult> {
1766        Debug.assertIsDefined(this.projectService.host.importPlugin);
1767
1768        let errorLogs: string[] | undefined;
1769        const log = (message: string) => this.projectService.logger.info(message);
1770        const logError = (message: string) => {
1771            (errorLogs ??= []).push(message);
1772        };
1773
1774        let resolvedModule: PluginModuleFactory | undefined;
1775        for (const searchPath of searchPaths) {
1776            resolvedModule = await Project.importServicePluginAsync(pluginConfigEntry.name, searchPath, this.projectService.host, log, logError) as PluginModuleFactory | undefined;
1777            if (resolvedModule !== undefined) {
1778                break;
1779            }
1780        }
1781        return { pluginConfigEntry, pluginConfigOverrides, resolvedModule, errorLogs };
1782    }
1783
1784    /**
1785     * Performs the remaining steps of enabling a plugin after its module has been instantiated.
1786     *
1787     * @internal
1788     */
1789    endEnablePlugin({ pluginConfigEntry, pluginConfigOverrides, resolvedModule, errorLogs }: BeginEnablePluginResult) {
1790        if (resolvedModule) {
1791            const configurationOverride = pluginConfigOverrides && pluginConfigOverrides.get(pluginConfigEntry.name);
1792            if (configurationOverride) {
1793                // Preserve the name property since it's immutable
1794                const pluginName = pluginConfigEntry.name;
1795                pluginConfigEntry = configurationOverride;
1796                pluginConfigEntry.name = pluginName;
1797            }
1798
1799            this.enableProxy(resolvedModule, pluginConfigEntry);
1800        }
1801        else {
1802            forEach(errorLogs, message => this.projectService.logger.info(message));
1803            this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name}`);
1804        }
1805    }
1806
1807    protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | undefined): void {
1808        this.projectService.requestEnablePlugin(this, pluginConfigEntry, searchPaths, pluginConfigOverrides);
1809    }
1810
1811    private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) {
1812        try {
1813            if (typeof pluginModuleFactory !== "function") {
1814                this.projectService.logger.info(`Skipped loading plugin ${configEntry.name} because it did not expose a proper factory function`);
1815                return;
1816            }
1817
1818            const info: PluginCreateInfo = {
1819                config: configEntry,
1820                project: this,
1821                languageService: this.languageService,
1822                languageServiceHost: this,
1823                serverHost: this.projectService.host,
1824                session: this.projectService.session
1825            };
1826
1827            const pluginModule = pluginModuleFactory({ typescript: ts });
1828            const newLS = pluginModule.create(info);
1829            for (const k of Object.keys(this.languageService)) {
1830                // eslint-disable-next-line local/no-in-operator
1831                if (!(k in newLS)) {
1832                    this.projectService.logger.info(`Plugin activation warning: Missing proxied method ${k} in created LS. Patching.`);
1833                    (newLS as any)[k] = (this.languageService as any)[k];
1834                }
1835            }
1836            this.projectService.logger.info(`Plugin validation succeeded`);
1837            this.languageService = newLS;
1838            this.plugins.push({ name: configEntry.name, module: pluginModule });
1839        }
1840        catch (e) {
1841            this.projectService.logger.info(`Plugin activation failed: ${e}`);
1842        }
1843    }
1844
1845    /** @internal */
1846    onPluginConfigurationChanged(pluginName: string, configuration: any) {
1847        this.plugins.filter(plugin => plugin.name === pluginName).forEach(plugin => {
1848            if (plugin.module.onConfigurationChanged) {
1849                plugin.module.onConfigurationChanged(configuration);
1850            }
1851        });
1852    }
1853
1854    /** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */
1855    refreshDiagnostics() {
1856        this.projectService.sendProjectsUpdatedInBackgroundEvent();
1857    }
1858
1859    /** @internal */
1860    getPackageJsonsVisibleToFile(fileName: string, rootDir?: string): readonly ProjectPackageJsonInfo[] {
1861        if (this.projectService.serverMode !== LanguageServiceMode.Semantic) return emptyArray;
1862        return this.projectService.getPackageJsonsVisibleToFile(fileName, rootDir);
1863    }
1864
1865    /** @internal */
1866    getNearestAncestorDirectoryWithPackageJson(fileName: string): string | undefined {
1867        return this.projectService.getNearestAncestorDirectoryWithPackageJson(fileName);
1868    }
1869
1870    /** @internal */
1871    getPackageJsonsForAutoImport(rootDir?: string): readonly ProjectPackageJsonInfo[] {
1872        const packageJsons = this.getPackageJsonsVisibleToFile(combinePaths(this.currentDirectory, inferredTypesContainingFile), rootDir);
1873        this.packageJsonsForAutoImport = new Set(packageJsons.map(p => p.fileName));
1874        return packageJsons;
1875    }
1876
1877    /** @internal */
1878    getPackageJsonCache() {
1879        return this.projectService.packageJsonCache;
1880    }
1881
1882    /** @internal */
1883    getCachedExportInfoMap() {
1884        return this.exportMapCache ||= createCacheableExportInfoMap(this);
1885    }
1886
1887    /** @internal */
1888    clearCachedExportInfoMap() {
1889        this.exportMapCache?.clear();
1890    }
1891
1892    /** @internal */
1893    getModuleSpecifierCache() {
1894        return this.moduleSpecifierCache;
1895    }
1896
1897    /** @internal */
1898    includePackageJsonAutoImports(): PackageJsonAutoImportPreference {
1899        if (this.projectService.includePackageJsonAutoImports() === PackageJsonAutoImportPreference.Off ||
1900            !this.languageServiceEnabled ||
1901            isInsideNodeModules(this.currentDirectory) ||
1902            !this.isDefaultProjectForOpenFiles()) {
1903            return PackageJsonAutoImportPreference.Off;
1904        }
1905        return this.projectService.includePackageJsonAutoImports();
1906    }
1907
1908    /** @internal */
1909    getModuleResolutionHostForAutoImportProvider(): ModuleResolutionHost {
1910        if (this.program) {
1911            return {
1912                fileExists: this.program.fileExists,
1913                directoryExists: this.program.directoryExists,
1914                realpath: this.program.realpath || this.projectService.host.realpath?.bind(this.projectService.host),
1915                getCurrentDirectory: this.getCurrentDirectory.bind(this),
1916                readFile: this.projectService.host.readFile.bind(this.projectService.host),
1917                getDirectories: this.projectService.host.getDirectories.bind(this.projectService.host),
1918                trace: this.projectService.host.trace?.bind(this.projectService.host),
1919                useCaseSensitiveFileNames: this.program.useCaseSensitiveFileNames(),
1920            };
1921        }
1922        return this.projectService.host;
1923    }
1924
1925    /** @internal */
1926    getPackageJsonAutoImportProvider(): Program | undefined {
1927        if (this.autoImportProviderHost === false) {
1928            return undefined;
1929        }
1930        if (this.projectService.serverMode !== LanguageServiceMode.Semantic) {
1931            this.autoImportProviderHost = false;
1932            return undefined;
1933        }
1934        if (this.autoImportProviderHost) {
1935            updateProjectIfDirty(this.autoImportProviderHost);
1936            if (this.autoImportProviderHost.isEmpty()) {
1937                this.autoImportProviderHost.close();
1938                this.autoImportProviderHost = undefined;
1939                return undefined;
1940            }
1941            return this.autoImportProviderHost.getCurrentProgram();
1942        }
1943
1944        const dependencySelection = this.includePackageJsonAutoImports();
1945        if (dependencySelection) {
1946            tracing?.push(tracing.Phase.Session, "getPackageJsonAutoImportProvider");
1947            const start = timestamp();
1948            this.autoImportProviderHost = AutoImportProviderProject.create(dependencySelection, this, this.getModuleResolutionHostForAutoImportProvider(), this.documentRegistry);
1949            if (this.autoImportProviderHost) {
1950                updateProjectIfDirty(this.autoImportProviderHost);
1951                this.sendPerformanceEvent("CreatePackageJsonAutoImportProvider", timestamp() - start);
1952                tracing?.pop();
1953                return this.autoImportProviderHost.getCurrentProgram();
1954            }
1955            tracing?.pop();
1956        }
1957    }
1958
1959    /** @internal */
1960    private isDefaultProjectForOpenFiles(): boolean {
1961        return !!forEachEntry(
1962            this.projectService.openFiles,
1963            (_, fileName) => this.projectService.tryGetDefaultProjectForFile(toNormalizedPath(fileName)) === this);
1964    }
1965
1966    /** @internal */
1967    watchNodeModulesForPackageJsonChanges(directoryPath: string) {
1968        return this.projectService.watchPackageJsonsInNodeModules(this.toPath(directoryPath), this);
1969    }
1970
1971    /** @internal */
1972    getIncompleteCompletionsCache() {
1973        return this.projectService.getIncompleteCompletionsCache();
1974    }
1975
1976    /** @internal */
1977    getNoDtsResolutionProject(rootFileNames: readonly string[]): Project {
1978        Debug.assert(this.projectService.serverMode === LanguageServiceMode.Semantic);
1979        if (!this.noDtsResolutionProject) {
1980            this.noDtsResolutionProject = new AuxiliaryProject(this.projectService, this.documentRegistry, this.getCompilerOptionsForNoDtsResolutionProject());
1981        }
1982
1983        enumerateInsertsAndDeletes<NormalizedPath, NormalizedPath>(
1984            rootFileNames.map(toNormalizedPath),
1985            this.noDtsResolutionProject.getRootFiles(),
1986            getStringComparer(!this.useCaseSensitiveFileNames()),
1987            pathToAdd => {
1988                const info = this.projectService.getOrCreateScriptInfoNotOpenedByClient(
1989                    pathToAdd,
1990                    this.currentDirectory,
1991                    this.noDtsResolutionProject!.directoryStructureHost);
1992                if (info) {
1993                    this.noDtsResolutionProject!.addRoot(info, pathToAdd);
1994                }
1995            },
1996            pathToRemove => {
1997                // It may be preferable to remove roots only once project grows to a certain size?
1998                const info = this.noDtsResolutionProject!.getScriptInfo(pathToRemove);
1999                if (info) {
2000                    this.noDtsResolutionProject!.removeRoot(info);
2001                }
2002            },
2003        );
2004
2005        return this.noDtsResolutionProject;
2006    }
2007
2008    /** @internal */
2009    private getCompilerOptionsForNoDtsResolutionProject() {
2010        return {
2011            ...this.getCompilerOptions(),
2012            noDtsResolution: true,
2013            allowJs: true,
2014            maxNodeModuleJsDepth: 3,
2015            diagnostics: false,
2016            skipLibCheck: true,
2017            sourceMap: false,
2018            types: ts.emptyArray,
2019            lib: ts.emptyArray,
2020            noLib: true,
2021        };
2022    }
2023}
2024
2025function getUnresolvedImports(program: Program, cachedUnresolvedImportsPerFile: ESMap<Path, readonly string[]>): SortedReadonlyArray<string> {
2026    const sourceFiles = program.getSourceFiles();
2027    tracing?.push(tracing.Phase.Session, "getUnresolvedImports", { count: sourceFiles.length });
2028    const ambientModules = program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName()));
2029    const result = sortAndDeduplicate(flatMap(sourceFiles, sourceFile =>
2030        extractUnresolvedImportsFromSourceFile(sourceFile, ambientModules, cachedUnresolvedImportsPerFile)));
2031    tracing?.pop();
2032    return result;
2033}
2034function extractUnresolvedImportsFromSourceFile(file: SourceFile, ambientModules: readonly string[], cachedUnresolvedImportsPerFile: ESMap<Path, readonly string[]>): readonly string[] {
2035    return getOrUpdate(cachedUnresolvedImportsPerFile, file.path, () => {
2036        if (!file.resolvedModules) return emptyArray;
2037        let unresolvedImports: string[] | undefined;
2038        file.resolvedModules.forEach((resolvedModule, name) => {
2039            // pick unresolved non-relative names
2040            if ((!resolvedModule || !resolutionExtensionIsTSOrJson(resolvedModule.extension)) &&
2041                !isExternalModuleNameRelative(name) &&
2042                !ambientModules.some(m => m === name)) {
2043                unresolvedImports = append(unresolvedImports, parsePackageName(name).packageName);
2044            }
2045        });
2046        return unresolvedImports || emptyArray;
2047    });
2048}
2049
2050/**
2051 * If a file is opened and no tsconfig (or jsconfig) is found,
2052 * the file and its imports/references are put into an InferredProject.
2053 */
2054export class InferredProject extends Project {
2055    private _isJsInferredProject = false;
2056
2057    toggleJsInferredProject(isJsInferredProject: boolean) {
2058        if (isJsInferredProject !== this._isJsInferredProject) {
2059            this._isJsInferredProject = isJsInferredProject;
2060            this.setCompilerOptions();
2061        }
2062    }
2063
2064    setCompilerOptions(options?: CompilerOptions) {
2065        // Avoid manipulating the given options directly
2066        if (!options && !this.getCompilationSettings()) {
2067            return;
2068        }
2069        const newOptions = cloneCompilerOptions(options || this.getCompilationSettings());
2070        if (this._isJsInferredProject && typeof newOptions.maxNodeModuleJsDepth !== "number") {
2071            newOptions.maxNodeModuleJsDepth = 2;
2072        }
2073        else if (!this._isJsInferredProject) {
2074            newOptions.maxNodeModuleJsDepth = undefined;
2075        }
2076        newOptions.allowJs = true;
2077        super.setCompilerOptions(newOptions);
2078    }
2079
2080    /** this is canonical project root path */
2081    readonly projectRootPath: string | undefined;
2082
2083    /**
2084     * stored only if their is no projectRootPath and this isnt single inferred project
2085     *
2086     * @internal
2087     */
2088    readonly canonicalCurrentDirectory: string | undefined;
2089
2090    /** @internal */
2091    constructor(
2092        projectService: ProjectService,
2093        documentRegistry: DocumentRegistry,
2094        compilerOptions: CompilerOptions,
2095        watchOptions: WatchOptions | undefined,
2096        projectRootPath: NormalizedPath | undefined,
2097        currentDirectory: string | undefined,
2098        pluginConfigOverrides: ESMap<string, any> | undefined,
2099        typeAcquisition: TypeAcquisition | undefined) {
2100        super(projectService.newInferredProjectName(),
2101            ProjectKind.Inferred,
2102            projectService,
2103            documentRegistry,
2104            // TODO: GH#18217
2105            /*files*/ undefined!,
2106            /*lastFileExceededProgramSize*/ undefined,
2107            compilerOptions,
2108            /*compileOnSaveEnabled*/ false,
2109            watchOptions,
2110            projectService.host,
2111            currentDirectory);
2112        this.typeAcquisition = typeAcquisition;
2113        this.projectRootPath = projectRootPath && projectService.toCanonicalFileName(projectRootPath);
2114        if (!projectRootPath && !projectService.useSingleInferredProject) {
2115            this.canonicalCurrentDirectory = projectService.toCanonicalFileName(this.currentDirectory);
2116        }
2117        this.enableGlobalPlugins(this.getCompilerOptions(), pluginConfigOverrides);
2118    }
2119
2120    addRoot(info: ScriptInfo) {
2121        Debug.assert(info.isScriptOpen());
2122        this.projectService.startWatchingConfigFilesForInferredProjectRoot(info);
2123        if (!this._isJsInferredProject && info.isJavaScript()) {
2124            this.toggleJsInferredProject(/*isJsInferredProject*/ true);
2125        }
2126        super.addRoot(info);
2127    }
2128
2129    removeRoot(info: ScriptInfo) {
2130        this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info);
2131        super.removeRoot(info);
2132        if (this._isJsInferredProject && info.isJavaScript()) {
2133            if (every(this.getRootScriptInfos(), rootInfo => !rootInfo.isJavaScript())) {
2134                this.toggleJsInferredProject(/*isJsInferredProject*/ false);
2135            }
2136        }
2137    }
2138
2139    /** @internal */
2140    isOrphan() {
2141        return !this.hasRoots();
2142    }
2143
2144    isProjectWithSingleRoot() {
2145        // - when useSingleInferredProject is not set and projectRootPath is not set,
2146        //   we can guarantee that this will be the only root
2147        // - other wise it has single root if it has single root script info
2148        return (!this.projectRootPath && !this.projectService.useSingleInferredProject) ||
2149            this.getRootScriptInfos().length === 1;
2150    }
2151
2152    close() {
2153        forEach(this.getRootScriptInfos(), info => this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info));
2154        super.close();
2155    }
2156
2157    getTypeAcquisition(): TypeAcquisition {
2158        return this.typeAcquisition || {
2159            enable: allRootFilesAreJsOrDts(this),
2160            include: ts.emptyArray,
2161            exclude: ts.emptyArray
2162        };
2163    }
2164}
2165
2166class AuxiliaryProject extends Project {
2167    constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) {
2168        super(projectService.newAuxiliaryProjectName(),
2169            ProjectKind.Auxiliary,
2170            projectService,
2171            documentRegistry,
2172            /*hasExplicitListOfFiles*/ false,
2173            /*lastFileExceededProgramSize*/ undefined,
2174            compilerOptions,
2175            /*compileOnSaveEnabled*/ false,
2176            /*watchOptions*/ undefined,
2177            projectService.host,
2178            /*currentDirectory*/ undefined);
2179    }
2180
2181    isOrphan(): boolean {
2182        return true;
2183    }
2184
2185    /** @internal */
2186    scheduleInvalidateResolutionsOfFailedLookupLocations(): void {
2187        // Invalidation will happen on-demand as part of updateGraph
2188        return;
2189    }
2190}
2191
2192export class AutoImportProviderProject extends Project {
2193    /** @internal */
2194    private static readonly maxDependencies = 10;
2195
2196    /** @internal */
2197    static getRootFileNames(dependencySelection: PackageJsonAutoImportPreference, hostProject: Project, moduleResolutionHost: ModuleResolutionHost, compilerOptions: CompilerOptions): string[] {
2198        if (!dependencySelection) {
2199            return ts.emptyArray;
2200        }
2201
2202        const program = hostProject.getCurrentProgram();
2203        if (!program) {
2204            return ts.emptyArray;
2205        }
2206
2207        const start = timestamp();
2208        let dependencyNames: Set<string> | undefined;
2209        let rootNames: string[] | undefined;
2210        const rootFileName = combinePaths(hostProject.currentDirectory, inferredTypesContainingFile);
2211        const packageJsons = hostProject.getPackageJsonsForAutoImport(combinePaths(hostProject.currentDirectory, rootFileName));
2212        for (const packageJson of packageJsons) {
2213            packageJson.dependencies?.forEach((_, dependenyName) => addDependency(dependenyName));
2214            packageJson.peerDependencies?.forEach((_, dependencyName) => addDependency(dependencyName));
2215        }
2216
2217        let dependenciesAdded = 0;
2218        if (dependencyNames) {
2219            const symlinkCache = hostProject.getSymlinkCache();
2220            for (const name of arrayFrom(dependencyNames.keys())) {
2221                // Avoid creating a large project that would significantly slow down time to editor interactivity
2222                if (dependencySelection === PackageJsonAutoImportPreference.Auto && dependenciesAdded > this.maxDependencies) {
2223                    hostProject.log(`AutoImportProviderProject: attempted to add more than ${this.maxDependencies} dependencies. Aborting.`);
2224                    return ts.emptyArray;
2225                }
2226
2227                // 1. Try to load from the implementation package. For many dependencies, the
2228                //    package.json will exist, but the package will not contain any typings,
2229                //    so `entrypoints` will be undefined. In that case, or if the dependency
2230                //    is missing altogether, we will move on to trying the @types package (2).
2231                const packageJson = resolvePackageNameToPackageJson(
2232                    name,
2233                    hostProject.currentDirectory,
2234                    compilerOptions,
2235                    moduleResolutionHost,
2236                    program.getModuleResolutionCache());
2237                if (packageJson) {
2238                    const entrypoints = getRootNamesFromPackageJson(packageJson, program, symlinkCache);
2239                    if (entrypoints) {
2240                        rootNames = concatenate(rootNames, entrypoints);
2241                        dependenciesAdded += entrypoints.length ? 1 : 0;
2242                        continue;
2243                    }
2244                }
2245
2246                // 2. Try to load from the @types package in the tree and in the global
2247                //    typings cache location, if enabled.
2248                const done = forEach([hostProject.currentDirectory, hostProject.getGlobalTypingsCacheLocation()], directory => {
2249                    if (directory) {
2250                        const typesPackageJson = resolvePackageNameToPackageJson(
2251                            `@types/${name}`,
2252                            directory,
2253                            compilerOptions,
2254                            moduleResolutionHost,
2255                            program.getModuleResolutionCache());
2256                        if (typesPackageJson) {
2257                            const entrypoints = getRootNamesFromPackageJson(typesPackageJson, program, symlinkCache);
2258                            rootNames = concatenate(rootNames, entrypoints);
2259                            dependenciesAdded += entrypoints?.length ? 1 : 0;
2260                            return true;
2261                        }
2262                    }
2263                });
2264
2265                if (done) continue;
2266
2267                // 3. If the @types package did not exist and the user has settings that
2268                //    allow processing JS from node_modules, go back to the implementation
2269                //    package and load the JS.
2270                if (packageJson && compilerOptions.allowJs && compilerOptions.maxNodeModuleJsDepth) {
2271                    const entrypoints = getRootNamesFromPackageJson(packageJson, program, symlinkCache, /*allowJs*/ true);
2272                    rootNames = concatenate(rootNames, entrypoints);
2273                    dependenciesAdded += entrypoints?.length ? 1 : 0;
2274                }
2275            }
2276        }
2277
2278        if (rootNames?.length) {
2279            hostProject.log(`AutoImportProviderProject: found ${rootNames.length} root files in ${dependenciesAdded} dependencies in ${timestamp() - start} ms`);
2280        }
2281        return rootNames || ts.emptyArray;
2282
2283        function addDependency(dependency: string) {
2284            if (!startsWith(dependency, "@types/")) {
2285                (dependencyNames || (dependencyNames = new Set())).add(dependency);
2286            }
2287        }
2288
2289        function getRootNamesFromPackageJson(packageJson: PackageJsonInfo, program: Program, symlinkCache: SymlinkCache, resolveJs?: boolean) {
2290            const entrypoints = getEntrypointsFromPackageJsonInfo(
2291                packageJson,
2292                compilerOptions,
2293                moduleResolutionHost,
2294                program.getModuleResolutionCache(),
2295                resolveJs);
2296            if (entrypoints) {
2297                const real = moduleResolutionHost.realpath?.(packageJson.packageDirectory);
2298                const isSymlink = real && real !== packageJson.packageDirectory;
2299                if (isSymlink) {
2300                    symlinkCache.setSymlinkedDirectory(packageJson.packageDirectory, {
2301                        real,
2302                        realPath: hostProject.toPath(real),
2303                    });
2304                }
2305
2306                return mapDefined(entrypoints, entrypoint => {
2307                    const resolvedFileName = isSymlink ? entrypoint.replace(packageJson.packageDirectory, real) : entrypoint;
2308                    if (!program.getSourceFile(resolvedFileName) && !(isSymlink && program.getSourceFile(entrypoint))) {
2309                        return resolvedFileName;
2310                    }
2311                });
2312            }
2313        }
2314    }
2315
2316    /** @internal */
2317    static readonly compilerOptionsOverrides: CompilerOptions = {
2318        diagnostics: false,
2319        skipLibCheck: true,
2320        sourceMap: false,
2321        types: ts.emptyArray,
2322        lib: ts.emptyArray,
2323        noLib: true,
2324    };
2325
2326    /** @internal */
2327    static create(dependencySelection: PackageJsonAutoImportPreference, hostProject: Project, moduleResolutionHost: ModuleResolutionHost, documentRegistry: DocumentRegistry): AutoImportProviderProject | undefined {
2328        if (dependencySelection === PackageJsonAutoImportPreference.Off) {
2329            return undefined;
2330        }
2331
2332        const compilerOptions = {
2333            ...hostProject.getCompilerOptions(),
2334            ...this.compilerOptionsOverrides,
2335        };
2336
2337        const rootNames = this.getRootFileNames(dependencySelection, hostProject, moduleResolutionHost, compilerOptions);
2338        if (!rootNames.length) {
2339            return undefined;
2340        }
2341
2342        return new AutoImportProviderProject(hostProject, rootNames, documentRegistry, compilerOptions);
2343    }
2344
2345    private rootFileNames: string[] | undefined;
2346
2347    /** @internal */
2348    constructor(
2349        private hostProject: Project,
2350        initialRootNames: string[],
2351        documentRegistry: DocumentRegistry,
2352        compilerOptions: CompilerOptions,
2353    ) {
2354        super(hostProject.projectService.newAutoImportProviderProjectName(),
2355            ProjectKind.AutoImportProvider,
2356            hostProject.projectService,
2357            documentRegistry,
2358            /*hasExplicitListOfFiles*/ false,
2359            /*lastFileExceededProgramSize*/ undefined,
2360            compilerOptions,
2361            /*compileOnSaveEnabled*/ false,
2362            hostProject.getWatchOptions(),
2363            hostProject.projectService.host,
2364            hostProject.currentDirectory);
2365
2366        this.rootFileNames = initialRootNames;
2367        this.useSourceOfProjectReferenceRedirect = maybeBind(this.hostProject, this.hostProject.useSourceOfProjectReferenceRedirect);
2368        this.getParsedCommandLine = maybeBind(this.hostProject, this.hostProject.getParsedCommandLine);
2369    }
2370
2371    /** @internal */
2372    isEmpty() {
2373        return !some(this.rootFileNames);
2374    }
2375
2376    isOrphan() {
2377        return true;
2378    }
2379
2380    updateGraph() {
2381        let rootFileNames = this.rootFileNames;
2382        if (!rootFileNames) {
2383            rootFileNames = AutoImportProviderProject.getRootFileNames(
2384                this.hostProject.includePackageJsonAutoImports(),
2385                this.hostProject,
2386                this.hostProject.getModuleResolutionHostForAutoImportProvider(),
2387                this.getCompilationSettings());
2388        }
2389
2390        this.projectService.setFileNamesOfAutoImportProviderProject(this, rootFileNames);
2391        this.rootFileNames = rootFileNames;
2392        const oldProgram = this.getCurrentProgram();
2393        const hasSameSetOfFiles = super.updateGraph();
2394        if (oldProgram && oldProgram !== this.getCurrentProgram()) {
2395            this.hostProject.clearCachedExportInfoMap();
2396        }
2397        return hasSameSetOfFiles;
2398    }
2399
2400    /** @internal */
2401    scheduleInvalidateResolutionsOfFailedLookupLocations(): void {
2402        // Invalidation will happen on-demand as part of updateGraph
2403        return;
2404    }
2405
2406    hasRoots() {
2407        return !!this.rootFileNames?.length;
2408    }
2409
2410    markAsDirty() {
2411        this.rootFileNames = undefined;
2412        super.markAsDirty();
2413    }
2414
2415    getScriptFileNames() {
2416        return this.rootFileNames || ts.emptyArray;
2417    }
2418
2419    getLanguageService(): never {
2420        throw new Error("AutoImportProviderProject language service should never be used. To get the program, use `project.getCurrentProgram()`.");
2421    }
2422
2423    /** @internal */
2424    onAutoImportProviderSettingsChanged(): never {
2425        throw new Error("AutoImportProviderProject is an auto import provider; use `markAsDirty()` instead.");
2426    }
2427
2428    /** @internal */
2429    onPackageJsonChange(): never {
2430        throw new Error("package.json changes should be notified on an AutoImportProvider's host project");
2431    }
2432
2433    getModuleResolutionHostForAutoImportProvider(): never {
2434        throw new Error("AutoImportProviderProject cannot provide its own host; use `hostProject.getModuleResolutionHostForAutomImportProvider()` instead.");
2435    }
2436
2437    getProjectReferences() {
2438        return this.hostProject.getProjectReferences();
2439    }
2440
2441    /** @internal */
2442    includePackageJsonAutoImports() {
2443        return PackageJsonAutoImportPreference.Off;
2444    }
2445
2446    getTypeAcquisition(): TypeAcquisition {
2447        return { enable: false };
2448    }
2449
2450    /** @internal */
2451    getSymlinkCache() {
2452        return this.hostProject.getSymlinkCache();
2453    }
2454
2455    /** @internal */
2456    getModuleResolutionCache() {
2457        return this.hostProject.getCurrentProgram()?.getModuleResolutionCache();
2458    }
2459}
2460
2461/**
2462 * If a file is opened, the server will look for a tsconfig (or jsconfig)
2463 * and if successful create a ConfiguredProject for it.
2464 * Otherwise it will create an InferredProject.
2465 */
2466export class ConfiguredProject extends Project {
2467    /** @internal */
2468    pendingReload: ConfigFileProgramReloadLevel | undefined;
2469    /** @internal */
2470    pendingReloadReason: string | undefined;
2471
2472    /** @internal */
2473    openFileWatchTriggered = new Map<string, ConfigFileProgramReloadLevel>();
2474
2475    /** @internal */
2476    canConfigFileJsonReportNoInputFiles = false;
2477
2478    /** Ref count to the project when opened from external project */
2479    private externalProjectRefCount = 0;
2480
2481    private projectReferences: readonly ProjectReference[] | undefined;
2482
2483    /**
2484     * Potential project references before the project is actually loaded (read config file)
2485     *
2486     * @internal
2487     */
2488    potentialProjectReferences: Set<string> | undefined;
2489
2490    /** @internal */
2491    projectOptions?: ProjectOptions | true;
2492
2493    /** @internal */
2494    isInitialLoadPending: () => boolean = returnTrue;
2495
2496    /** @internal */
2497    sendLoadingProjectFinish = false;
2498
2499    /** @internal */
2500    private compilerHost?: CompilerHost;
2501
2502    /** @internal */
2503    constructor(configFileName: NormalizedPath,
2504        readonly canonicalConfigFilePath: NormalizedPath,
2505        projectService: ProjectService,
2506        documentRegistry: DocumentRegistry,
2507        cachedDirectoryStructureHost: CachedDirectoryStructureHost) {
2508        super(configFileName,
2509            ProjectKind.Configured,
2510            projectService,
2511            documentRegistry,
2512            /*hasExplicitListOfFiles*/ false,
2513            /*lastFileExceededProgramSize*/ undefined,
2514            /*compilerOptions*/ {},
2515            /*compileOnSaveEnabled*/ false,
2516            /*watchOptions*/ undefined,
2517            cachedDirectoryStructureHost,
2518            getDirectoryPath(configFileName)
2519        );
2520    }
2521
2522    /** @internal */
2523    setCompilerHost(host: CompilerHost) {
2524        this.compilerHost = host;
2525    }
2526
2527    /** @internal */
2528    getCompilerHost(): CompilerHost | undefined {
2529        return this.compilerHost;
2530    }
2531
2532    /** @internal */
2533    useSourceOfProjectReferenceRedirect() {
2534        return this.languageServiceEnabled;
2535    }
2536
2537    /** @internal */
2538    getParsedCommandLine(fileName: string) {
2539        const configFileName = asNormalizedPath(normalizePath(fileName));
2540        const canonicalConfigFilePath = asNormalizedPath(this.projectService.toCanonicalFileName(configFileName));
2541        // Ensure the config file existience info is cached
2542        let configFileExistenceInfo = this.projectService.configFileExistenceInfoCache.get(canonicalConfigFilePath);
2543        if (!configFileExistenceInfo) {
2544            this.projectService.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFileExistenceInfo = { exists: this.projectService.host.fileExists(configFileName) });
2545        }
2546        // Ensure we have upto date parsed command line
2547        this.projectService.ensureParsedConfigUptoDate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, this);
2548        // Watch wild cards if LS is enabled
2549        if (this.languageServiceEnabled && this.projectService.serverMode === LanguageServiceMode.Semantic) {
2550            this.projectService.watchWildcards(configFileName, configFileExistenceInfo, this);
2551        }
2552        return configFileExistenceInfo.exists ? configFileExistenceInfo.config!.parsedCommandLine : undefined;
2553    }
2554
2555    /** @internal */
2556    onReleaseParsedCommandLine(fileName: string) {
2557        this.releaseParsedConfig(asNormalizedPath(this.projectService.toCanonicalFileName(asNormalizedPath(normalizePath(fileName)))));
2558    }
2559
2560    /** @internal */
2561    private releaseParsedConfig(canonicalConfigFilePath: NormalizedPath) {
2562        this.projectService.stopWatchingWildCards(canonicalConfigFilePath, this);
2563        this.projectService.releaseParsedConfig(canonicalConfigFilePath, this);
2564    }
2565
2566    /**
2567     * If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
2568     * @returns: true if set of files in the project stays the same and false - otherwise.
2569     */
2570    updateGraph(): boolean {
2571        const isInitialLoad = this.isInitialLoadPending();
2572        this.isInitialLoadPending = returnFalse;
2573        const reloadLevel = this.pendingReload;
2574        this.pendingReload = ConfigFileProgramReloadLevel.None;
2575        let result: boolean;
2576        switch (reloadLevel) {
2577            case ConfigFileProgramReloadLevel.Partial:
2578                this.openFileWatchTriggered.clear();
2579                result = this.projectService.reloadFileNamesOfConfiguredProject(this);
2580                break;
2581            case ConfigFileProgramReloadLevel.Full:
2582                this.openFileWatchTriggered.clear();
2583                const reason = Debug.checkDefined(this.pendingReloadReason);
2584                this.pendingReloadReason = undefined;
2585                this.projectService.reloadConfiguredProject(this, reason, isInitialLoad, /*clearSemanticCache*/ false);
2586                result = true;
2587                break;
2588            default:
2589                result = super.updateGraph();
2590        }
2591        this.compilerHost = undefined;
2592        this.projectService.sendProjectLoadingFinishEvent(this);
2593        this.projectService.sendProjectTelemetry(this);
2594        return result;
2595    }
2596
2597    /** @internal */
2598    getCachedDirectoryStructureHost() {
2599        return this.directoryStructureHost as CachedDirectoryStructureHost;
2600    }
2601
2602    getConfigFilePath() {
2603        return asNormalizedPath(this.getProjectName());
2604    }
2605
2606    getProjectReferences(): readonly ProjectReference[] | undefined {
2607        return this.projectReferences;
2608    }
2609
2610    updateReferences(refs: readonly ProjectReference[] | undefined) {
2611        this.projectReferences = refs;
2612        this.potentialProjectReferences = undefined;
2613    }
2614
2615    /** @internal */
2616    setPotentialProjectReference(canonicalConfigPath: NormalizedPath) {
2617        Debug.assert(this.isInitialLoadPending());
2618        (this.potentialProjectReferences || (this.potentialProjectReferences = new Set())).add(canonicalConfigPath);
2619    }
2620
2621    /** @internal */
2622    getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined {
2623        const program = this.getCurrentProgram();
2624        return program && program.getResolvedProjectReferenceToRedirect(fileName);
2625    }
2626
2627    /** @internal */
2628    forEachResolvedProjectReference<T>(
2629        cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined
2630    ): T | undefined {
2631        return this.getCurrentProgram()?.forEachResolvedProjectReference(cb);
2632    }
2633
2634    /** @internal */
2635    enablePluginsWithOptions(options: CompilerOptions, pluginConfigOverrides: ESMap<string, any> | undefined): void {
2636        this.plugins.length = 0;
2637        if (!options.plugins?.length && !this.projectService.globalPlugins.length) return;
2638        const host = this.projectService.host;
2639        if (!host.require && !host.importPlugin) {
2640            this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
2641            return;
2642        }
2643
2644        const searchPaths = this.getGlobalPluginSearchPaths();
2645        if (this.projectService.allowLocalPluginLoads) {
2646            const local = getDirectoryPath(this.canonicalConfigFilePath);
2647            this.projectService.logger.info(`Local plugin loading enabled; adding ${local} to search paths`);
2648            searchPaths.unshift(local);
2649        }
2650
2651        // Enable tsconfig-specified plugins
2652        if (options.plugins) {
2653            for (const pluginConfigEntry of options.plugins) {
2654                this.enablePlugin(pluginConfigEntry, searchPaths, pluginConfigOverrides);
2655            }
2656        }
2657
2658        return this.enableGlobalPlugins(options, pluginConfigOverrides);
2659    }
2660
2661    /**
2662     * Get the errors that dont have any file name associated
2663     */
2664    getGlobalProjectErrors(): readonly Diagnostic[] {
2665        return filter(this.projectErrors, diagnostic => !diagnostic.file) || emptyArray;
2666    }
2667
2668    /**
2669     * Get all the project errors
2670     */
2671    getAllProjectErrors(): readonly Diagnostic[] {
2672        return this.projectErrors || emptyArray;
2673    }
2674
2675    setProjectErrors(projectErrors: Diagnostic[]) {
2676        this.projectErrors = projectErrors;
2677    }
2678
2679    close() {
2680        this.projectService.configFileExistenceInfoCache.forEach((_configFileExistenceInfo, canonicalConfigFilePath) =>
2681            this.releaseParsedConfig(canonicalConfigFilePath));
2682        this.projectErrors = undefined;
2683        this.openFileWatchTriggered.clear();
2684        this.compilerHost = undefined;
2685        super.close();
2686    }
2687
2688    /** @internal */
2689    addExternalProjectReference() {
2690        this.externalProjectRefCount++;
2691    }
2692
2693    /** @internal */
2694    deleteExternalProjectReference() {
2695        this.externalProjectRefCount--;
2696    }
2697
2698    /** @internal */
2699    isSolution() {
2700        return this.getRootFilesMap().size === 0 &&
2701            !this.canConfigFileJsonReportNoInputFiles;
2702    }
2703
2704    /**
2705     * Find the configured project from the project references in project which contains the info directly
2706     *
2707     * @internal
2708     */
2709    getDefaultChildProjectFromProjectWithReferences(info: ScriptInfo) {
2710        return forEachResolvedProjectReferenceProject(
2711            this,
2712            info.path,
2713            child => projectContainsInfoDirectly(child, info) ?
2714                child :
2715                undefined,
2716            ProjectReferenceProjectLoadKind.Find
2717        );
2718    }
2719
2720    /**
2721     * Returns true if the project is needed by any of the open script info/external project
2722     *
2723     * @internal
2724     */
2725    hasOpenRef() {
2726        if (!!this.externalProjectRefCount) {
2727            return true;
2728        }
2729
2730        // Closed project doesnt have any reference
2731        if (this.isClosed()) {
2732            return false;
2733        }
2734
2735        const configFileExistenceInfo = this.projectService.configFileExistenceInfoCache.get(this.canonicalConfigFilePath)!;
2736        if (this.projectService.hasPendingProjectUpdate(this)) {
2737            // If there is pending update for this project,
2738            // we dont know if this project would be needed by any of the open files impacted by this config file
2739            // In that case keep the project alive if there are open files impacted by this project
2740            return !!configFileExistenceInfo.openFilesImpactedByConfigFile?.size;
2741        }
2742
2743        // If there is no pending update for this project,
2744        // We know exact set of open files that get impacted by this configured project as the files in the project
2745        // The project is referenced only if open files impacted by this project are present in this project
2746        return !!configFileExistenceInfo.openFilesImpactedByConfigFile && forEachEntry(
2747            configFileExistenceInfo.openFilesImpactedByConfigFile,
2748            (_value, infoPath) => {
2749                const info = this.projectService.getScriptInfoForPath(infoPath)!;
2750                return this.containsScriptInfo(info) ||
2751                    !!forEachResolvedProjectReferenceProject(
2752                        this,
2753                        info.path,
2754                        child => child.containsScriptInfo(info),
2755                        ProjectReferenceProjectLoadKind.Find
2756                    );
2757            }
2758        ) || false;
2759    }
2760
2761    /** @internal */
2762    hasExternalProjectRef() {
2763        return !!this.externalProjectRefCount;
2764    }
2765
2766    getEffectiveTypeRoots() {
2767        return getEffectiveTypeRoots(this.getCompilationSettings(), this.directoryStructureHost) || [];
2768    }
2769
2770    /** @internal */
2771    updateErrorOnNoInputFiles(fileNames: string[]) {
2772        updateErrorForNoInputFiles(fileNames, this.getConfigFilePath(), this.getCompilerOptions().configFile!.configFileSpecs!, this.projectErrors!, this.canConfigFileJsonReportNoInputFiles);
2773    }
2774}
2775
2776/**
2777 * Project whose configuration is handled externally, such as in a '.csproj'.
2778 * These are created only if a host explicitly calls `openExternalProject`.
2779 */
2780export class ExternalProject extends Project {
2781    excludedFiles: readonly NormalizedPath[] = [];
2782    /** @internal */
2783    constructor(public externalProjectName: string,
2784        projectService: ProjectService,
2785        documentRegistry: DocumentRegistry,
2786        compilerOptions: CompilerOptions,
2787        lastFileExceededProgramSize: string | undefined,
2788        public compileOnSaveEnabled: boolean,
2789        projectFilePath?: string,
2790        pluginConfigOverrides?: ESMap<string, any>,
2791        watchOptions?: WatchOptions) {
2792        super(externalProjectName,
2793            ProjectKind.External,
2794            projectService,
2795            documentRegistry,
2796            /*hasExplicitListOfFiles*/ true,
2797            lastFileExceededProgramSize,
2798            compilerOptions,
2799            compileOnSaveEnabled,
2800            watchOptions,
2801            projectService.host,
2802            getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName)));
2803        this.enableGlobalPlugins(this.getCompilerOptions(), pluginConfigOverrides);
2804    }
2805
2806    updateGraph() {
2807        const result = super.updateGraph();
2808        this.projectService.sendProjectTelemetry(this);
2809        return result;
2810    }
2811
2812    getExcludedFiles() {
2813        return this.excludedFiles;
2814    }
2815}
2816
2817/** @internal */
2818export function isInferredProject(project: Project): project is InferredProject {
2819    return project.projectKind === ProjectKind.Inferred;
2820}
2821
2822/** @internal */
2823export function isConfiguredProject(project: Project): project is ConfiguredProject {
2824    return project.projectKind === ProjectKind.Configured;
2825}
2826
2827/** @internal */
2828export function isExternalProject(project: Project): project is ExternalProject {
2829    return project.projectKind === ProjectKind.External;
2830}
2831