When directory watcher is invoked with any file from node_modules package, invalidate for file paths in that package (#43974)

Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>
This commit is contained in:
Sheetal Nandi
2021-05-06 09:48:26 -07:00
committed by GitHub
parent e0d7703cf3
commit 233f28ca27
4 changed files with 625 additions and 19 deletions

View File

@@ -1150,7 +1150,7 @@ namespace ts {
}
const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state);
if (resolvedFromFile) {
const packageDirectory = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile) : undefined;
const packageDirectory = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile.path) : undefined;
const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined;
return withPackageId(packageInfo, resolvedFromFile);
}
@@ -1184,8 +1184,9 @@ namespace ts {
* For `/node_modules/@types/foo/bar/index.d.ts` this is packageDirectory: "@types/foo"
* For `/node_modules/foo/bar/index.d.ts` this is packageDirectory: "foo"
*/
function parseNodeModuleFromPath(resolved: PathAndExtension): string | undefined {
const path = normalizePath(resolved.path);
/* @internal */
export function parseNodeModuleFromPath(resolved: string): string | undefined {
const path = normalizePath(resolved);
const idx = path.lastIndexOf(nodeModulesPathPart);
if (idx === -1) {
return undefined;

View File

@@ -153,9 +153,9 @@ namespace ts {
const resolvedFileToResolution = createMultiMap<ResolutionWithFailedLookupLocations>();
let hasChangedAutomaticTypeDirectiveNames = false;
const failedLookupChecks: Path[] = [];
const startsWithPathChecks: Path[] = [];
const isInDirectoryChecks: Path[] = [];
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();
@@ -248,9 +248,9 @@ namespace ts {
resolvedTypeReferenceDirectives.clear();
resolvedFileToResolution.clear();
resolutionsWithFailedLookups.length = 0;
failedLookupChecks.length = 0;
startsWithPathChecks.length = 0;
isInDirectoryChecks.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();
@@ -766,7 +766,7 @@ namespace ts {
if (isCreatingWatchedDirectory) {
// Watching directory is created
// Invalidate any resolution has failed lookup in this directory
isInDirectoryChecks.push(fileOrDirectoryPath);
(isInDirectoryChecks ||= []).push(fileOrDirectoryPath);
}
else {
// If something to do with folder/file starting with "." in node_modules folder, skip it
@@ -785,8 +785,8 @@ namespace ts {
if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || isNodeModulesDirectory(fileOrDirectoryPath) ||
isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) {
// Invalidate any resolution from this directory
failedLookupChecks.push(fileOrDirectoryPath);
startsWithPathChecks.push(fileOrDirectoryPath);
(failedLookupChecks ||= []).push(fileOrDirectoryPath);
(startsWithPathChecks ||= new Set()).add(fileOrDirectoryPath);
}
else {
if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) {
@@ -797,21 +797,27 @@ namespace ts {
return false;
}
// Resolution need to be invalidated if failed lookup location is same as the file or directory getting created
failedLookupChecks.push(fileOrDirectoryPath);
(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.length && !startsWithPathChecks.length && !isInDirectoryChecks.length) {
if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks) {
return false;
}
const invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution);
failedLookupChecks.length = 0;
startsWithPathChecks.length = 0;
isInDirectoryChecks.length = 0;
failedLookupChecks = undefined;
startsWithPathChecks = undefined;
isInDirectoryChecks = undefined;
return invalidated;
}
@@ -819,8 +825,8 @@ namespace ts {
return resolution.failedLookupLocations.some(location => {
const locationPath = resolutionHost.toPath(location);
return contains(failedLookupChecks, locationPath) ||
startsWithPathChecks.some(fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath)) ||
isInDirectoryChecks.some(fileOrDirectoryPath => isInDirectoryPath(fileOrDirectoryPath, locationPath));
firstDefinedIterator(startsWithPathChecks?.keys() || emptyIterator, fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath) ? true : undefined) ||
isInDirectoryChecks?.some(fileOrDirectoryPath => isInDirectoryPath(fileOrDirectoryPath, locationPath));
});
}