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