mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-17 12:19:32 -05:00
* Add moduleDetection compiler flag to allow for changing how modules are parsed The default setting is 'auto', where JSX containing files under react-jsx and react-jsxdev are always parsed as modules, and esm-format files under module: node12+ are always parsed as modules, in addition to the 'legacy' detection mode's conditions for other files. (Declaration files are exempt from these new conditions) The 'legacy' mode preserves TS's behavior prior to the introduction of this flag - a file is parsed as a module if it contains an import, export, or import.meta expression. In addition, there is a 'force' mode that forces all non-declaration files to be parsed as modules. (Declaration files are still only modules if they contain a top-level import or export.) This technically breaks the parser API, but it's kinda-sorta backwards compatible so long as you don't need the functionality associated with more recent compiler flags. * Fix post-merge lint * Rename function * Update default value documentation * PR feedback * Fix lint and typo
988 lines
56 KiB
TypeScript
988 lines
56 KiB
TypeScript
/*@internal*/
|
|
namespace ts {
|
|
/** This is the cache of module/typedirectives resolution that can be retained across program */
|
|
export interface ResolutionCache {
|
|
startRecordingFilesWithChangedResolutions(): void;
|
|
finishRecordingFilesWithChangedResolutions(): Path[] | undefined;
|
|
|
|
resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[];
|
|
getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined;
|
|
resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[];
|
|
|
|
invalidateResolutionsOfFailedLookupLocations(): boolean;
|
|
invalidateResolutionOfFile(filePath: Path): void;
|
|
removeResolutionsOfFile(filePath: Path): void;
|
|
removeResolutionsFromProjectReferenceRedirects(filePath: Path): void;
|
|
setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: ESMap<Path, readonly string[]>): void;
|
|
createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution;
|
|
hasChangedAutomaticTypeDirectiveNames(): boolean;
|
|
isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean;
|
|
|
|
|
|
startCachingPerDirectoryResolution(): void;
|
|
finishCachingPerDirectoryResolution(): void;
|
|
|
|
updateTypeRootsWatch(): void;
|
|
closeTypeRootsWatch(): void;
|
|
|
|
getModuleResolutionCache(): ModuleResolutionCache;
|
|
|
|
clear(): void;
|
|
}
|
|
|
|
interface ResolutionWithFailedLookupLocations {
|
|
readonly failedLookupLocations: string[];
|
|
isInvalidated?: boolean;
|
|
refCount?: number;
|
|
// Files that have this resolution using
|
|
files?: Path[];
|
|
}
|
|
|
|
interface ResolutionWithResolvedFileName {
|
|
resolvedFileName: string | undefined;
|
|
packagetId?: PackageId;
|
|
}
|
|
|
|
interface CachedResolvedModuleWithFailedLookupLocations extends ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations {
|
|
}
|
|
|
|
interface CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations {
|
|
}
|
|
|
|
export interface ResolutionCacheHost extends MinimalResolutionCacheHost {
|
|
toPath(fileName: string): Path;
|
|
getCanonicalFileName: GetCanonicalFileName;
|
|
getCompilationSettings(): CompilerOptions;
|
|
watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
|
|
onInvalidatedResolution(): void;
|
|
watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
|
|
onChangedAutomaticTypeDirectiveNames(): void;
|
|
scheduleInvalidateResolutionsOfFailedLookupLocations(): void;
|
|
getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined;
|
|
projectName?: string;
|
|
getGlobalCache?(): string | undefined;
|
|
globalCacheResolutionModuleName?(externalModuleName: string): string;
|
|
writeLog(s: string): void;
|
|
getCurrentProgram(): Program | undefined;
|
|
fileIsOpen(filePath: Path): boolean;
|
|
onDiscoveredSymlink?(): void;
|
|
}
|
|
|
|
interface DirectoryWatchesOfFailedLookup {
|
|
/** watcher for the directory of failed lookup */
|
|
watcher: FileWatcher;
|
|
/** ref count keeping this directory watch alive */
|
|
refCount: number;
|
|
/** is the directory watched being non recursive */
|
|
nonRecursive?: boolean;
|
|
}
|
|
|
|
interface DirectoryOfFailedLookupWatch {
|
|
dir: string;
|
|
dirPath: Path;
|
|
nonRecursive?: boolean;
|
|
}
|
|
|
|
export function removeIgnoredPath(path: Path): Path | undefined {
|
|
// Consider whole staging folder as if node_modules changed.
|
|
if (endsWith(path, "/node_modules/.staging")) {
|
|
return removeSuffix(path, "/.staging") as Path;
|
|
}
|
|
|
|
return some(ignoredPaths, searchPath => stringContains(path, searchPath)) ?
|
|
undefined :
|
|
path;
|
|
}
|
|
|
|
/**
|
|
* Filter out paths like
|
|
* "/", "/user", "/user/username", "/user/username/folderAtRoot",
|
|
* "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot"
|
|
* @param dirPath
|
|
*/
|
|
export function canWatchDirectory(dirPath: Path) {
|
|
const rootLength = getRootLength(dirPath);
|
|
if (dirPath.length === rootLength) {
|
|
// Ignore "/", "c:/"
|
|
return false;
|
|
}
|
|
|
|
let nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength);
|
|
if (nextDirectorySeparator === -1) {
|
|
// ignore "/user", "c:/users" or "c:/folderAtRoot"
|
|
return false;
|
|
}
|
|
|
|
let pathPartForUserCheck = dirPath.substring(rootLength, nextDirectorySeparator + 1);
|
|
const isNonDirectorySeparatorRoot = rootLength > 1 || dirPath.charCodeAt(0) !== CharacterCodes.slash;
|
|
if (isNonDirectorySeparatorRoot &&
|
|
dirPath.search(/[a-zA-Z]:/) !== 0 && // Non dos style paths
|
|
pathPartForUserCheck.search(/[a-zA-z]\$\//) === 0) { // Dos style nextPart
|
|
nextDirectorySeparator = dirPath.indexOf(directorySeparator, nextDirectorySeparator + 1);
|
|
if (nextDirectorySeparator === -1) {
|
|
// ignore "//vda1cs4850/c$/folderAtRoot"
|
|
return false;
|
|
}
|
|
|
|
pathPartForUserCheck = dirPath.substring(rootLength + pathPartForUserCheck.length, nextDirectorySeparator + 1);
|
|
}
|
|
|
|
if (isNonDirectorySeparatorRoot &&
|
|
pathPartForUserCheck.search(/users\//i) !== 0) {
|
|
// Paths like c:/folderAtRoot/subFolder are allowed
|
|
return true;
|
|
}
|
|
|
|
for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) {
|
|
searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1;
|
|
if (searchIndex === 0) {
|
|
// Folder isnt at expected minimum levels
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
type GetResolutionWithResolvedFileName<T extends ResolutionWithFailedLookupLocations = ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName = ResolutionWithResolvedFileName> =
|
|
(resolution: T) => R | undefined;
|
|
|
|
export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache {
|
|
let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
|
|
let filesWithInvalidatedResolutions: Set<Path> | undefined;
|
|
let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyESMap<Path, readonly string[]> | undefined;
|
|
const nonRelativeExternalModuleResolutions = createMultiMap<ResolutionWithFailedLookupLocations>();
|
|
|
|
const resolutionsWithFailedLookups: ResolutionWithFailedLookupLocations[] = [];
|
|
const resolvedFileToResolution = createMultiMap<ResolutionWithFailedLookupLocations>();
|
|
|
|
let hasChangedAutomaticTypeDirectiveNames = false;
|
|
let failedLookupChecks: Path[] | undefined;
|
|
let startsWithPathChecks: Set<Path> | undefined;
|
|
let isInDirectoryChecks: Path[] | undefined;
|
|
|
|
const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217
|
|
const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost();
|
|
|
|
// The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file.
|
|
// The key in the map is source file's path.
|
|
// The values are Map of resolutions with key being name lookedup.
|
|
const resolvedModuleNames = new Map<Path, ModeAwareCache<CachedResolvedModuleWithFailedLookupLocations>>();
|
|
const perDirectoryResolvedModuleNames: CacheWithRedirects<ModeAwareCache<CachedResolvedModuleWithFailedLookupLocations>> = createCacheWithRedirects();
|
|
const nonRelativeModuleNameCache: CacheWithRedirects<PerModuleNameCache> = createCacheWithRedirects();
|
|
const moduleResolutionCache = createModuleResolutionCache(
|
|
getCurrentDirectory(),
|
|
resolutionHost.getCanonicalFileName,
|
|
/*options*/ undefined,
|
|
perDirectoryResolvedModuleNames,
|
|
nonRelativeModuleNameCache,
|
|
);
|
|
|
|
const resolvedTypeReferenceDirectives = new Map<Path, ModeAwareCache<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
|
|
const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects<ModeAwareCache<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations>> = createCacheWithRedirects();
|
|
const typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache(
|
|
getCurrentDirectory(),
|
|
resolutionHost.getCanonicalFileName,
|
|
/*options*/ undefined,
|
|
moduleResolutionCache.getPackageJsonInfoCache(),
|
|
perDirectoryResolvedTypeReferenceDirectives
|
|
);
|
|
|
|
/**
|
|
* These are the extensions that failed lookup files will have by default,
|
|
* any other extension of failed lookup will be store that path in custom failed lookup path
|
|
* This helps in not having to comb through all resolutions when files are added/removed
|
|
* Note that .d.ts file also has .d.ts extension hence will be part of default extensions
|
|
*/
|
|
const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json];
|
|
const customFailedLookupPaths = new Map<string, number>();
|
|
|
|
const directoryWatchesOfFailedLookups = new Map<string, DirectoryWatchesOfFailedLookup>();
|
|
const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory()));
|
|
const rootPath = (rootDir && resolutionHost.toPath(rootDir)) as Path; // TODO: GH#18217
|
|
const rootSplitLength = rootPath !== undefined ? rootPath.split(directorySeparator).length : 0;
|
|
|
|
// TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames
|
|
const typeRootsWatches = new Map<string, FileWatcher>();
|
|
|
|
return {
|
|
getModuleResolutionCache: () => moduleResolutionCache,
|
|
startRecordingFilesWithChangedResolutions,
|
|
finishRecordingFilesWithChangedResolutions,
|
|
// perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
|
|
// (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
|
|
startCachingPerDirectoryResolution: clearPerDirectoryResolutions,
|
|
finishCachingPerDirectoryResolution,
|
|
resolveModuleNames,
|
|
getResolvedModuleWithFailedLookupLocationsFromCache,
|
|
resolveTypeReferenceDirectives,
|
|
removeResolutionsFromProjectReferenceRedirects,
|
|
removeResolutionsOfFile,
|
|
hasChangedAutomaticTypeDirectiveNames: () => hasChangedAutomaticTypeDirectiveNames,
|
|
invalidateResolutionOfFile,
|
|
invalidateResolutionsOfFailedLookupLocations,
|
|
setFilesWithInvalidatedNonRelativeUnresolvedImports,
|
|
createHasInvalidatedResolution,
|
|
isFileWithInvalidatedNonRelativeUnresolvedImports,
|
|
updateTypeRootsWatch,
|
|
closeTypeRootsWatch,
|
|
clear
|
|
};
|
|
|
|
function getResolvedModule(resolution: CachedResolvedModuleWithFailedLookupLocations) {
|
|
return resolution.resolvedModule;
|
|
}
|
|
|
|
function getResolvedTypeReferenceDirective(resolution: CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations) {
|
|
return resolution.resolvedTypeReferenceDirective;
|
|
}
|
|
|
|
function isInDirectoryPath(dir: Path | undefined, file: Path) {
|
|
if (dir === undefined || file.length <= dir.length) {
|
|
return false;
|
|
}
|
|
return startsWith(file, dir) && file[dir.length] === directorySeparator;
|
|
}
|
|
|
|
function clear() {
|
|
clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf);
|
|
customFailedLookupPaths.clear();
|
|
nonRelativeExternalModuleResolutions.clear();
|
|
closeTypeRootsWatch();
|
|
resolvedModuleNames.clear();
|
|
resolvedTypeReferenceDirectives.clear();
|
|
resolvedFileToResolution.clear();
|
|
resolutionsWithFailedLookups.length = 0;
|
|
failedLookupChecks = undefined;
|
|
startsWithPathChecks = undefined;
|
|
isInDirectoryChecks = undefined;
|
|
// perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
|
|
// (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
|
|
clearPerDirectoryResolutions();
|
|
hasChangedAutomaticTypeDirectiveNames = false;
|
|
}
|
|
|
|
function startRecordingFilesWithChangedResolutions() {
|
|
filesWithChangedSetOfUnresolvedImports = [];
|
|
}
|
|
|
|
function finishRecordingFilesWithChangedResolutions() {
|
|
const collected = filesWithChangedSetOfUnresolvedImports;
|
|
filesWithChangedSetOfUnresolvedImports = undefined;
|
|
return collected;
|
|
}
|
|
|
|
function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean {
|
|
if (!filesWithInvalidatedNonRelativeUnresolvedImports) {
|
|
return false;
|
|
}
|
|
|
|
// Invalidated if file has unresolved imports
|
|
const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path);
|
|
return !!value && !!value.length;
|
|
}
|
|
|
|
function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution {
|
|
// Ensure pending resolutions are applied
|
|
invalidateResolutionsOfFailedLookupLocations();
|
|
if (forceAllFilesAsInvalidated) {
|
|
// Any file asked would have invalidated resolution
|
|
filesWithInvalidatedResolutions = undefined;
|
|
return returnTrue;
|
|
}
|
|
const collected = filesWithInvalidatedResolutions;
|
|
filesWithInvalidatedResolutions = undefined;
|
|
return path => (!!collected && collected.has(path)) ||
|
|
isFileWithInvalidatedNonRelativeUnresolvedImports(path);
|
|
}
|
|
|
|
function clearPerDirectoryResolutions() {
|
|
moduleResolutionCache.clear();
|
|
typeReferenceDirectiveResolutionCache.clear();
|
|
nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions);
|
|
nonRelativeExternalModuleResolutions.clear();
|
|
}
|
|
|
|
function finishCachingPerDirectoryResolution() {
|
|
filesWithInvalidatedNonRelativeUnresolvedImports = undefined;
|
|
clearPerDirectoryResolutions();
|
|
directoryWatchesOfFailedLookups.forEach((watcher, path) => {
|
|
if (watcher.refCount === 0) {
|
|
directoryWatchesOfFailedLookups.delete(path);
|
|
watcher.watcher.close();
|
|
}
|
|
});
|
|
hasChangedAutomaticTypeDirectiveNames = false;
|
|
}
|
|
|
|
function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, _containingSourceFile?: never, mode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): CachedResolvedModuleWithFailedLookupLocations {
|
|
const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference, mode);
|
|
// return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts
|
|
if (!resolutionHost.getGlobalCache) {
|
|
return primaryResult;
|
|
}
|
|
|
|
// otherwise try to load typings from @types
|
|
const globalCache = resolutionHost.getGlobalCache();
|
|
if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTS(primaryResult.resolvedModule.extension))) {
|
|
// create different collection of failed lookup locations for second pass
|
|
// if it will fail and we've already found something during the first pass - we don't want to pollute its results
|
|
const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(
|
|
Debug.checkDefined(resolutionHost.globalCacheResolutionModuleName)(moduleName),
|
|
resolutionHost.projectName,
|
|
compilerOptions,
|
|
host,
|
|
globalCache,
|
|
moduleResolutionCache,
|
|
);
|
|
if (resolvedModule) {
|
|
// Modify existing resolution so its saved in the directory cache as well
|
|
(primaryResult.resolvedModule as any) = resolvedModule;
|
|
primaryResult.failedLookupLocations.push(...failedLookupLocations);
|
|
return primaryResult;
|
|
}
|
|
}
|
|
|
|
// Default return the result from the first pass
|
|
return primaryResult;
|
|
}
|
|
|
|
function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, _containingSourceFile?: SourceFile, resolutionMode?: SourceFile["impliedNodeFormat"] | undefined): CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations {
|
|
return ts.resolveTypeReferenceDirective(typeReferenceDirectiveName, containingFile, options, host, redirectedReference, typeReferenceDirectiveResolutionCache, resolutionMode);
|
|
}
|
|
|
|
interface ResolveNamesWithLocalCacheInput<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName> {
|
|
names: readonly string[] | readonly FileReference[];
|
|
containingFile: string;
|
|
redirectedReference: ResolvedProjectReference | undefined;
|
|
cache: ESMap<Path, ModeAwareCache<T>>;
|
|
perDirectoryCacheWithRedirects: CacheWithRedirects<ModeAwareCache<T>>;
|
|
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => T;
|
|
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>;
|
|
shouldRetryResolution: (t: T) => boolean;
|
|
reusedNames?: readonly string[];
|
|
logChanges?: boolean;
|
|
containingSourceFile?: SourceFile;
|
|
containingSourceFileMode?: SourceFile["impliedNodeFormat"];
|
|
}
|
|
function resolveNamesWithLocalCache<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>({
|
|
names, containingFile, redirectedReference,
|
|
cache, perDirectoryCacheWithRedirects,
|
|
loader, getResolutionWithResolvedFileName,
|
|
shouldRetryResolution, reusedNames, logChanges, containingSourceFile, containingSourceFileMode
|
|
}: ResolveNamesWithLocalCacheInput<T, R>): (R | undefined)[] {
|
|
const path = resolutionHost.toPath(containingFile);
|
|
const resolutionsInFile = cache.get(path) || cache.set(path, createModeAwareCache()).get(path)!;
|
|
const dirPath = getDirectoryPath(path);
|
|
const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference);
|
|
let perDirectoryResolution = perDirectoryCache.get(dirPath);
|
|
if (!perDirectoryResolution) {
|
|
perDirectoryResolution = createModeAwareCache();
|
|
perDirectoryCache.set(dirPath, perDirectoryResolution);
|
|
}
|
|
const resolvedModules: (R | undefined)[] = [];
|
|
const compilerOptions = resolutionHost.getCompilationSettings();
|
|
const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path);
|
|
|
|
// All the resolutions in this file are invalidated if this file wasn't resolved using same redirect
|
|
const program = resolutionHost.getCurrentProgram();
|
|
const oldRedirect = program && program.getResolvedProjectReferenceToRedirect(containingFile);
|
|
const unmatchedRedirects = oldRedirect ?
|
|
!redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path :
|
|
!!redirectedReference;
|
|
|
|
const seenNamesInFile = createModeAwareCache<true>();
|
|
let i = 0;
|
|
for (const entry of names) {
|
|
const name = isString(entry) ? entry : entry.fileName.toLowerCase();
|
|
// Imports supply a `containingSourceFile` but no `containingSourceFileMode` - it would be redundant
|
|
// they require calculating the mode for a given import from it's position in the resolution table, since a given
|
|
// import's syntax may override the file's default mode.
|
|
// Type references instead supply a `containingSourceFileMode` and a non-string entry which contains
|
|
// a default file mode override if applicable.
|
|
const mode = !isString(entry) ? getModeForFileReference(entry, containingSourceFileMode) :
|
|
containingSourceFile ? getModeForResolutionAtIndex(containingSourceFile, i) : undefined;
|
|
i++;
|
|
let resolution = resolutionsInFile.get(name, mode);
|
|
// Resolution is valid if it is present and not invalidated
|
|
if (!seenNamesInFile.has(name, mode) &&
|
|
unmatchedRedirects || !resolution || resolution.isInvalidated ||
|
|
// If the name is unresolved import that was invalidated, recalculate
|
|
(hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) {
|
|
const existingResolution = resolution;
|
|
const resolutionInDirectory = perDirectoryResolution.get(name, mode);
|
|
if (resolutionInDirectory) {
|
|
resolution = resolutionInDirectory;
|
|
const host = resolutionHost.getCompilerHost?.() || resolutionHost;
|
|
if (isTraceEnabled(compilerOptions, host)) {
|
|
const resolved = getResolutionWithResolvedFileName(resolution);
|
|
trace(
|
|
host,
|
|
loader === resolveModuleName as unknown ?
|
|
resolved?.resolvedFileName ?
|
|
resolved.packagetId ?
|
|
Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4:
|
|
Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3:
|
|
Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_not_resolved :
|
|
resolved?.resolvedFileName ?
|
|
resolved.packagetId ?
|
|
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4 :
|
|
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3 :
|
|
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_not_resolved,
|
|
name,
|
|
containingFile,
|
|
getDirectoryPath(containingFile),
|
|
resolved?.resolvedFileName,
|
|
resolved?.packagetId && packageIdToString(resolved.packagetId)
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
resolution = loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference, containingSourceFile, mode);
|
|
perDirectoryResolution.set(name, mode, resolution);
|
|
if (resolutionHost.onDiscoveredSymlink && resolutionIsSymlink(resolution)) {
|
|
resolutionHost.onDiscoveredSymlink();
|
|
}
|
|
}
|
|
resolutionsInFile.set(name, mode, resolution);
|
|
watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution, path, getResolutionWithResolvedFileName);
|
|
if (existingResolution) {
|
|
stopWatchFailedLookupLocationOfResolution(existingResolution, path, getResolutionWithResolvedFileName);
|
|
}
|
|
|
|
if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
|
|
filesWithChangedSetOfUnresolvedImports.push(path);
|
|
// reset log changes to avoid recording the same file multiple times
|
|
logChanges = false;
|
|
}
|
|
}
|
|
else {
|
|
const host = resolutionHost.getCompilerHost?.() || resolutionHost;
|
|
if (isTraceEnabled(compilerOptions, host) && !seenNamesInFile.has(name, mode)) {
|
|
const resolved = getResolutionWithResolvedFileName(resolution);
|
|
trace(
|
|
host,
|
|
loader === resolveModuleName as unknown ?
|
|
resolved?.resolvedFileName ?
|
|
resolved.packagetId ?
|
|
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
|
|
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 :
|
|
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved :
|
|
resolved?.resolvedFileName ?
|
|
resolved.packagetId ?
|
|
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
|
|
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2 :
|
|
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_not_resolved,
|
|
name,
|
|
containingFile,
|
|
resolved?.resolvedFileName,
|
|
resolved?.packagetId && packageIdToString(resolved.packagetId)
|
|
);
|
|
}
|
|
}
|
|
Debug.assert(resolution !== undefined && !resolution.isInvalidated);
|
|
seenNamesInFile.set(name, mode, true);
|
|
resolvedModules.push(getResolutionWithResolvedFileName(resolution));
|
|
}
|
|
|
|
// Stop watching and remove the unused name
|
|
resolutionsInFile.forEach((resolution, name, mode) => {
|
|
if (!seenNamesInFile.has(name, mode) && !contains(reusedNames, name)) {
|
|
stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName);
|
|
resolutionsInFile.delete(name, mode);
|
|
}
|
|
});
|
|
|
|
return resolvedModules;
|
|
|
|
function resolutionIsEqualTo(oldResolution: T | undefined, newResolution: T | undefined): boolean {
|
|
if (oldResolution === newResolution) {
|
|
return true;
|
|
}
|
|
if (!oldResolution || !newResolution) {
|
|
return false;
|
|
}
|
|
const oldResult = getResolutionWithResolvedFileName(oldResolution);
|
|
const newResult = getResolutionWithResolvedFileName(newResolution);
|
|
if (oldResult === newResult) {
|
|
return true;
|
|
}
|
|
if (!oldResult || !newResult) {
|
|
return false;
|
|
}
|
|
return oldResult.resolvedFileName === newResult.resolvedFileName;
|
|
}
|
|
}
|
|
|
|
function resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[] {
|
|
return resolveNamesWithLocalCache<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolvedTypeReferenceDirective>({
|
|
names: typeDirectiveNames,
|
|
containingFile,
|
|
redirectedReference,
|
|
cache: resolvedTypeReferenceDirectives,
|
|
perDirectoryCacheWithRedirects: perDirectoryResolvedTypeReferenceDirectives,
|
|
loader: resolveTypeReferenceDirective,
|
|
getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective,
|
|
shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined,
|
|
containingSourceFileMode: containingFileMode
|
|
});
|
|
}
|
|
|
|
function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[] {
|
|
return resolveNamesWithLocalCache<CachedResolvedModuleWithFailedLookupLocations, ResolvedModuleFull>({
|
|
names: moduleNames,
|
|
containingFile,
|
|
redirectedReference,
|
|
cache: resolvedModuleNames,
|
|
perDirectoryCacheWithRedirects: perDirectoryResolvedModuleNames,
|
|
loader: resolveModuleName,
|
|
getResolutionWithResolvedFileName: getResolvedModule,
|
|
shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension),
|
|
reusedNames,
|
|
logChanges: logChangesWhenResolvingModule,
|
|
containingSourceFile,
|
|
});
|
|
}
|
|
|
|
function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined {
|
|
const cache = resolvedModuleNames.get(resolutionHost.toPath(containingFile));
|
|
if (!cache) return undefined;
|
|
return cache.get(moduleName, resolutionMode);
|
|
}
|
|
|
|
function isNodeModulesAtTypesDirectory(dirPath: Path) {
|
|
return endsWith(dirPath, "/node_modules/@types");
|
|
}
|
|
|
|
function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch | undefined {
|
|
if (isInDirectoryPath(rootPath, failedLookupLocationPath)) {
|
|
// Ensure failed look up is normalized path
|
|
failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory());
|
|
const failedLookupPathSplit = failedLookupLocationPath.split(directorySeparator);
|
|
const failedLookupSplit = failedLookupLocation.split(directorySeparator);
|
|
Debug.assert(failedLookupSplit.length === failedLookupPathSplit.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`);
|
|
if (failedLookupPathSplit.length > rootSplitLength + 1) {
|
|
// Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution
|
|
return {
|
|
dir: failedLookupSplit.slice(0, rootSplitLength + 1).join(directorySeparator),
|
|
dirPath: failedLookupPathSplit.slice(0, rootSplitLength + 1).join(directorySeparator) as Path
|
|
};
|
|
}
|
|
else {
|
|
// Always watch root directory non recursively
|
|
return {
|
|
dir: rootDir!,
|
|
dirPath: rootPath,
|
|
nonRecursive: false
|
|
};
|
|
}
|
|
}
|
|
|
|
return getDirectoryToWatchFromFailedLookupLocationDirectory(
|
|
getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())),
|
|
getDirectoryPath(failedLookupLocationPath)
|
|
);
|
|
}
|
|
|
|
function getDirectoryToWatchFromFailedLookupLocationDirectory(dir: string, dirPath: Path): DirectoryOfFailedLookupWatch | undefined {
|
|
// If directory path contains node module, get the most parent node_modules directory for watching
|
|
while (pathContainsNodeModules(dirPath)) {
|
|
dir = getDirectoryPath(dir);
|
|
dirPath = getDirectoryPath(dirPath);
|
|
}
|
|
|
|
// If the directory is node_modules use it to watch, always watch it recursively
|
|
if (isNodeModulesDirectory(dirPath)) {
|
|
return canWatchDirectory(getDirectoryPath(dirPath)) ? { dir, dirPath } : undefined;
|
|
}
|
|
|
|
let nonRecursive = true;
|
|
// Use some ancestor of the root directory
|
|
let subDirectoryPath: Path | undefined, subDirectory: string | undefined;
|
|
if (rootPath !== undefined) {
|
|
while (!isInDirectoryPath(dirPath, rootPath)) {
|
|
const parentPath = getDirectoryPath(dirPath);
|
|
if (parentPath === dirPath) {
|
|
break;
|
|
}
|
|
nonRecursive = false;
|
|
subDirectoryPath = dirPath;
|
|
subDirectory = dir;
|
|
dirPath = parentPath;
|
|
dir = getDirectoryPath(dir);
|
|
}
|
|
}
|
|
|
|
return canWatchDirectory(dirPath) ? { dir: subDirectory || dir, dirPath: subDirectoryPath || dirPath, nonRecursive } : undefined;
|
|
}
|
|
|
|
function isPathWithDefaultFailedLookupExtension(path: Path) {
|
|
return fileExtensionIsOneOf(path, failedLookupDefaultExtensions);
|
|
}
|
|
|
|
function watchFailedLookupLocationsOfExternalModuleResolutions<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
|
|
name: string,
|
|
resolution: T,
|
|
filePath: Path,
|
|
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
|
|
) {
|
|
if (resolution.refCount) {
|
|
resolution.refCount++;
|
|
Debug.assertIsDefined(resolution.files);
|
|
}
|
|
else {
|
|
resolution.refCount = 1;
|
|
Debug.assert(length(resolution.files) === 0); // This resolution shouldnt be referenced by any file yet
|
|
if (isExternalModuleNameRelative(name)) {
|
|
watchFailedLookupLocationOfResolution(resolution);
|
|
}
|
|
else {
|
|
nonRelativeExternalModuleResolutions.add(name, resolution);
|
|
}
|
|
const resolved = getResolutionWithResolvedFileName(resolution);
|
|
if (resolved && resolved.resolvedFileName) {
|
|
resolvedFileToResolution.add(resolutionHost.toPath(resolved.resolvedFileName), resolution);
|
|
}
|
|
}
|
|
(resolution.files || (resolution.files = [])).push(filePath);
|
|
}
|
|
|
|
function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) {
|
|
Debug.assert(!!resolution.refCount);
|
|
|
|
const { failedLookupLocations } = resolution;
|
|
if (!failedLookupLocations.length) return;
|
|
resolutionsWithFailedLookups.push(resolution);
|
|
|
|
let setAtRoot = false;
|
|
for (const failedLookupLocation of failedLookupLocations) {
|
|
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
|
|
const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
|
|
if (toWatch) {
|
|
const { dir, dirPath, nonRecursive } = toWatch;
|
|
// If the failed lookup location path is not one of the supported extensions,
|
|
// store it in the custom path
|
|
if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) {
|
|
const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0;
|
|
customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1);
|
|
}
|
|
if (dirPath === rootPath) {
|
|
Debug.assert(!nonRecursive);
|
|
setAtRoot = true;
|
|
}
|
|
else {
|
|
setDirectoryWatcher(dir, dirPath, nonRecursive);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (setAtRoot) {
|
|
// This is always non recursive
|
|
setDirectoryWatcher(rootDir!, rootPath, /*nonRecursive*/ true); // TODO: GH#18217
|
|
}
|
|
}
|
|
|
|
function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) {
|
|
const program = resolutionHost.getCurrentProgram();
|
|
if (!program || !program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name)) {
|
|
resolutions.forEach(watchFailedLookupLocationOfResolution);
|
|
}
|
|
}
|
|
|
|
function setDirectoryWatcher(dir: string, dirPath: Path, nonRecursive?: boolean) {
|
|
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
|
|
if (dirWatcher) {
|
|
Debug.assert(!!nonRecursive === !!dirWatcher.nonRecursive);
|
|
dirWatcher.refCount++;
|
|
}
|
|
else {
|
|
directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath, nonRecursive), refCount: 1, nonRecursive });
|
|
}
|
|
}
|
|
|
|
function stopWatchFailedLookupLocationOfResolution<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
|
|
resolution: T,
|
|
filePath: Path,
|
|
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
|
|
) {
|
|
unorderedRemoveItem(Debug.checkDefined(resolution.files), filePath);
|
|
resolution.refCount!--;
|
|
if (resolution.refCount) {
|
|
return;
|
|
}
|
|
const resolved = getResolutionWithResolvedFileName(resolution);
|
|
if (resolved && resolved.resolvedFileName) {
|
|
resolvedFileToResolution.remove(resolutionHost.toPath(resolved.resolvedFileName), resolution);
|
|
}
|
|
|
|
if (!unorderedRemoveItem(resolutionsWithFailedLookups, resolution)) {
|
|
// If not watching failed lookups, it wont be there in resolutionsWithFailedLookups
|
|
return;
|
|
}
|
|
|
|
const { failedLookupLocations } = resolution;
|
|
let removeAtRoot = false;
|
|
for (const failedLookupLocation of failedLookupLocations) {
|
|
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
|
|
const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
|
|
if (toWatch) {
|
|
const { dirPath } = toWatch;
|
|
const refCount = customFailedLookupPaths.get(failedLookupLocationPath);
|
|
if (refCount) {
|
|
if (refCount === 1) {
|
|
customFailedLookupPaths.delete(failedLookupLocationPath);
|
|
}
|
|
else {
|
|
Debug.assert(refCount > 1);
|
|
customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1);
|
|
}
|
|
}
|
|
|
|
if (dirPath === rootPath) {
|
|
removeAtRoot = true;
|
|
}
|
|
else {
|
|
removeDirectoryWatcher(dirPath);
|
|
}
|
|
}
|
|
}
|
|
if (removeAtRoot) {
|
|
removeDirectoryWatcher(rootPath);
|
|
}
|
|
}
|
|
|
|
function removeDirectoryWatcher(dirPath: string) {
|
|
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath)!;
|
|
// Do not close the watcher yet since it might be needed by other failed lookup locations.
|
|
dirWatcher.refCount--;
|
|
}
|
|
|
|
function createDirectoryWatcher(directory: string, dirPath: Path, nonRecursive: boolean | undefined) {
|
|
return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => {
|
|
const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
|
|
if (cachedDirectoryStructureHost) {
|
|
// Since the file existence changed, update the sourceFiles cache
|
|
cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
|
|
}
|
|
|
|
scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath);
|
|
}, nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive);
|
|
}
|
|
|
|
function removeResolutionsOfFileFromCache<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
|
|
cache: ESMap<string, ModeAwareCache<T>>,
|
|
filePath: Path,
|
|
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
|
|
) {
|
|
// Deleted file, stop watching failed lookups for all the resolutions in the file
|
|
const resolutions = cache.get(filePath);
|
|
if (resolutions) {
|
|
resolutions.forEach(resolution => stopWatchFailedLookupLocationOfResolution(resolution, filePath, getResolutionWithResolvedFileName));
|
|
cache.delete(filePath);
|
|
}
|
|
}
|
|
|
|
function removeResolutionsFromProjectReferenceRedirects(filePath: Path) {
|
|
if (!fileExtensionIs(filePath, Extension.Json)) return;
|
|
|
|
const program = resolutionHost.getCurrentProgram();
|
|
if (!program) return;
|
|
|
|
// If this file is input file for the referenced project, get it
|
|
const resolvedProjectReference = program.getResolvedProjectReferenceByPath(filePath);
|
|
if (!resolvedProjectReference) return;
|
|
|
|
// filePath is for the projectReference and the containing file is from this project reference, invalidate the resolution
|
|
resolvedProjectReference.commandLine.fileNames.forEach(f => removeResolutionsOfFile(resolutionHost.toPath(f)));
|
|
}
|
|
|
|
function removeResolutionsOfFile(filePath: Path) {
|
|
removeResolutionsOfFileFromCache(resolvedModuleNames, filePath, getResolvedModule);
|
|
removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath, getResolvedTypeReferenceDirective);
|
|
}
|
|
|
|
function invalidateResolutions(resolutions: ResolutionWithFailedLookupLocations[] | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean) {
|
|
if (!resolutions) return false;
|
|
let invalidated = false;
|
|
for (const resolution of resolutions) {
|
|
if (resolution.isInvalidated || !canInvalidate(resolution)) continue;
|
|
resolution.isInvalidated = invalidated = true;
|
|
for (const containingFilePath of Debug.checkDefined(resolution.files)) {
|
|
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = new Set())).add(containingFilePath);
|
|
// When its a file with inferred types resolution, invalidate type reference directive resolution
|
|
hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames || endsWith(containingFilePath, inferredTypesContainingFile);
|
|
}
|
|
}
|
|
return invalidated;
|
|
}
|
|
|
|
function invalidateResolutionOfFile(filePath: Path) {
|
|
removeResolutionsOfFile(filePath);
|
|
// Resolution is invalidated if the resulting file name is same as the deleted file path
|
|
const prevHasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
|
|
if (invalidateResolutions(resolvedFileToResolution.get(filePath), returnTrue) &&
|
|
hasChangedAutomaticTypeDirectiveNames &&
|
|
!prevHasChangedAutomaticTypeDirectiveNames) {
|
|
resolutionHost.onChangedAutomaticTypeDirectiveNames();
|
|
}
|
|
}
|
|
|
|
function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: ReadonlyESMap<Path, readonly string[]>) {
|
|
Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined);
|
|
filesWithInvalidatedNonRelativeUnresolvedImports = filesMap;
|
|
}
|
|
|
|
function scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) {
|
|
if (isCreatingWatchedDirectory) {
|
|
// Watching directory is created
|
|
// Invalidate any resolution has failed lookup in this directory
|
|
(isInDirectoryChecks ||= []).push(fileOrDirectoryPath);
|
|
}
|
|
else {
|
|
// If something to do with folder/file starting with "." in node_modules folder, skip it
|
|
const updatedPath = removeIgnoredPath(fileOrDirectoryPath);
|
|
if (!updatedPath) return false;
|
|
fileOrDirectoryPath = updatedPath;
|
|
|
|
// prevent saving an open file from over-eagerly triggering invalidation
|
|
if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) {
|
|
return false;
|
|
}
|
|
|
|
// Some file or directory in the watching directory is created
|
|
// Return early if it does not have any of the watching extension or not the custom failed lookup path
|
|
const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath);
|
|
if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || isNodeModulesDirectory(fileOrDirectoryPath) ||
|
|
isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) {
|
|
// Invalidate any resolution from this directory
|
|
(failedLookupChecks ||= []).push(fileOrDirectoryPath);
|
|
(startsWithPathChecks ||= new Set()).add(fileOrDirectoryPath);
|
|
}
|
|
else {
|
|
if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) {
|
|
return false;
|
|
}
|
|
// Ignore emits from the program
|
|
if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) {
|
|
return false;
|
|
}
|
|
// Resolution need to be invalidated if failed lookup location is same as the file or directory getting created
|
|
(failedLookupChecks ||= []).push(fileOrDirectoryPath);
|
|
|
|
// If the invalidated file is from a node_modules package, invalidate everything else
|
|
// in the package since we might not get notifications for other files in the package.
|
|
// This hardens our logic against unreliable file watchers.
|
|
const packagePath = parseNodeModuleFromPath(fileOrDirectoryPath);
|
|
if (packagePath) (startsWithPathChecks ||= new Set()).add(packagePath as Path);
|
|
}
|
|
}
|
|
resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations();
|
|
}
|
|
|
|
function invalidateResolutionsOfFailedLookupLocations() {
|
|
if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks) {
|
|
return false;
|
|
}
|
|
|
|
const invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution);
|
|
failedLookupChecks = undefined;
|
|
startsWithPathChecks = undefined;
|
|
isInDirectoryChecks = undefined;
|
|
return invalidated;
|
|
}
|
|
|
|
function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) {
|
|
return resolution.failedLookupLocations.some(location => {
|
|
const locationPath = resolutionHost.toPath(location);
|
|
return contains(failedLookupChecks, locationPath) ||
|
|
firstDefinedIterator(startsWithPathChecks?.keys() || emptyIterator, fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath) ? true : undefined) ||
|
|
isInDirectoryChecks?.some(fileOrDirectoryPath => isInDirectoryPath(fileOrDirectoryPath, locationPath));
|
|
});
|
|
}
|
|
|
|
function closeTypeRootsWatch() {
|
|
clearMap(typeRootsWatches, closeFileWatcher);
|
|
}
|
|
|
|
function getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot: string, typeRootPath: Path): Path | undefined {
|
|
if (isInDirectoryPath(rootPath, typeRootPath)) {
|
|
return rootPath;
|
|
}
|
|
const toWatch = getDirectoryToWatchFromFailedLookupLocationDirectory(typeRoot, typeRootPath);
|
|
return toWatch && directoryWatchesOfFailedLookups.has(toWatch.dirPath) ? toWatch.dirPath : undefined;
|
|
}
|
|
|
|
function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher {
|
|
// Create new watch and recursive info
|
|
return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => {
|
|
const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
|
|
if (cachedDirectoryStructureHost) {
|
|
// Since the file existence changed, update the sourceFiles cache
|
|
cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
|
|
}
|
|
|
|
// For now just recompile
|
|
// We could potentially store more data here about whether it was/would be really be used or not
|
|
// and with that determine to trigger compilation but for now this is enough
|
|
hasChangedAutomaticTypeDirectiveNames = true;
|
|
resolutionHost.onChangedAutomaticTypeDirectiveNames();
|
|
|
|
// Since directory watchers invoked are flaky, the failed lookup location events might not be triggered
|
|
// So handle to failed lookup locations here as well to ensure we are invalidating resolutions
|
|
const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath);
|
|
if (dirPath) {
|
|
scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath);
|
|
}
|
|
}, WatchDirectoryFlags.Recursive);
|
|
}
|
|
|
|
/**
|
|
* Watches the types that would get added as part of getAutomaticTypeDirectiveNames
|
|
* To be called when compiler options change
|
|
*/
|
|
function updateTypeRootsWatch() {
|
|
const options = resolutionHost.getCompilationSettings();
|
|
if (options.types) {
|
|
// No need to do any watch since resolution cache is going to handle the failed lookups
|
|
// for the types added by this
|
|
closeTypeRootsWatch();
|
|
return;
|
|
}
|
|
|
|
// we need to assume the directories exist to ensure that we can get all the type root directories that get included
|
|
// But filter directories that are at root level to say directory doesnt exist, so that we arent watching them
|
|
const typeRoots = getEffectiveTypeRoots(options, { directoryExists: directoryExistsForTypeRootWatch, getCurrentDirectory });
|
|
if (typeRoots) {
|
|
mutateMap(
|
|
typeRootsWatches,
|
|
arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)),
|
|
{
|
|
createNewValue: createTypeRootsWatch,
|
|
onDeleteValue: closeFileWatcher
|
|
}
|
|
);
|
|
}
|
|
else {
|
|
closeTypeRootsWatch();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Use this function to return if directory exists to get type roots to watch
|
|
* If we return directory exists then only the paths will be added to type roots
|
|
* Hence return true for all directories except root directories which are filtered from watching
|
|
*/
|
|
function directoryExistsForTypeRootWatch(nodeTypesDirectory: string) {
|
|
const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory));
|
|
const dirPath = resolutionHost.toPath(dir);
|
|
return dirPath === rootPath || canWatchDirectory(dirPath);
|
|
}
|
|
}
|
|
|
|
function resolutionIsSymlink(resolution: ResolutionWithFailedLookupLocations) {
|
|
return !!(
|
|
(resolution as ResolvedModuleWithFailedLookupLocations).resolvedModule?.originalPath ||
|
|
(resolution as ResolvedTypeReferenceDirectiveWithFailedLookupLocations).resolvedTypeReferenceDirective?.originalPath
|
|
);
|
|
}
|
|
}
|