Skip to content

Commit e64e70f

Browse files
committed
Auto type reference directive shouldnt lookup in node_modules
Fixes #37708 Bug 1 part of the issue
1 parent 97b7cbc commit e64e70f

17 files changed

+207
-123
lines changed

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -4473,6 +4473,10 @@
44734473
"category": "Message",
44744474
"code": 6235
44754475
},
4476+
"Resolving type reference directive for program with typeRoots, skipping lookup in 'node_modules' folder.": {
4477+
"category": "Message",
4478+
"code": 6236
4479+
},
44764480

44774481
"Projects to reference": {
44784482
"category": "Message",

src/compiler/moduleNameResolver.ts

+19-22
Original file line numberDiff line numberDiff line change
@@ -250,27 +250,19 @@ namespace ts {
250250
}
251251

252252
if (currentDirectory !== undefined) {
253-
return getDefaultTypeRoots(currentDirectory, host);
253+
return getDefaultTypeRoots(currentDirectory);
254254
}
255255
}
256256

257257
/**
258258
* Returns the path to every node_modules/@types directory from some ancestor directory.
259259
* Returns undefined if there are none.
260260
*/
261-
function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined {
262-
if (!host.directoryExists) {
263-
return [combinePaths(currentDirectory, nodeModulesAtTypes)];
264-
// And if it doesn't exist, tough.
265-
}
266-
261+
function getDefaultTypeRoots(currentDirectory: string): string[] | undefined {
267262
let typeRoots: string[] | undefined;
268263
forEachAncestorDirectory(normalizePath(currentDirectory), directory => {
269264
const atTypes = combinePaths(directory, nodeModulesAtTypes);
270-
if (host.directoryExists!(atTypes)) {
271-
(typeRoots || (typeRoots = [])).push(atTypes);
272-
}
273-
return undefined;
265+
(typeRoots || (typeRoots = [])).push(atTypes);
274266
});
275267
return typeRoots;
276268
}
@@ -363,20 +355,25 @@ namespace ts {
363355

364356
function secondaryLookup(): PathAndPackageId | undefined {
365357
const initialLocationForSecondaryLookup = containingFile && getDirectoryPath(containingFile);
366-
367358
if (initialLocationForSecondaryLookup !== undefined) {
368-
// check secondary locations
369-
if (traceEnabled) {
370-
trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup);
371-
}
359+
// Do not resolve auto types from secondary location
372360
let result: Resolved | undefined;
373-
if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) {
374-
const searchResult = loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined);
375-
result = searchResult && searchResult.value;
361+
if (!options.typeRoots || !endsWith(containingFile!, inferredTypesContainingFile)) {
362+
// check secondary locations
363+
if (traceEnabled) {
364+
trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup);
365+
}
366+
if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) {
367+
const searchResult = loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined);
368+
result = searchResult && searchResult.value;
369+
}
370+
else {
371+
const { path: candidate } = normalizePathAndParts(combinePaths(initialLocationForSecondaryLookup, typeReferenceDirectiveName));
372+
result = nodeLoadModuleByRelativeName(Extensions.DtsOnly, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true);
373+
}
376374
}
377-
else {
378-
const { path: candidate } = normalizePathAndParts(combinePaths(initialLocationForSecondaryLookup, typeReferenceDirectiveName));
379-
result = nodeLoadModuleByRelativeName(Extensions.DtsOnly, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true);
375+
else if (traceEnabled) {
376+
trace(host, Diagnostics.Resolving_type_reference_directive_for_program_with_typeRoots_skipping_lookup_in_node_modules_folder);
380377
}
381378
const resolvedFile = resolvedTypeScriptOnly(result);
382379
if (!resolvedFile && traceEnabled) {

src/compiler/resolutionCache.ts

+27-26
Original file line numberDiff line numberDiff line change
@@ -869,26 +869,28 @@ namespace ts {
869869

870870
function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher {
871871
// Create new watch and recursive info
872-
return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => {
873-
const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
874-
if (cachedDirectoryStructureHost) {
875-
// Since the file existence changed, update the sourceFiles cache
876-
cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
877-
}
878-
879-
// For now just recompile
880-
// We could potentially store more data here about whether it was/would be really be used or not
881-
// and with that determine to trigger compilation but for now this is enough
882-
hasChangedAutomaticTypeDirectiveNames = true;
883-
resolutionHost.onChangedAutomaticTypeDirectiveNames();
872+
return canWatchTypeRootPath(typeRootPath) ?
873+
resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => {
874+
const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
875+
if (cachedDirectoryStructureHost) {
876+
// Since the file existence changed, update the sourceFiles cache
877+
cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
878+
}
884879

885-
// Since directory watchers invoked are flaky, the failed lookup location events might not be triggered
886-
// So handle to failed lookup locations here as well to ensure we are invalidating resolutions
887-
const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath);
888-
if (dirPath) {
889-
scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath);
890-
}
891-
}, WatchDirectoryFlags.Recursive);
880+
// For now just recompile
881+
// We could potentially store more data here about whether it was/would be really be used or not
882+
// and with that determine to trigger compilation but for now this is enough
883+
hasChangedAutomaticTypeDirectiveNames = true;
884+
resolutionHost.onChangedAutomaticTypeDirectiveNames();
885+
886+
// Since directory watchers invoked are flaky, the failed lookup location events might not be triggered
887+
// So handle to failed lookup locations here as well to ensure we are invalidating resolutions
888+
const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath);
889+
if (dirPath) {
890+
scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath);
891+
}
892+
}, WatchDirectoryFlags.Recursive) :
893+
noopFileWatcher;
892894
}
893895

894896
/**
@@ -906,7 +908,7 @@ namespace ts {
906908

907909
// we need to assume the directories exist to ensure that we can get all the type root directories that get included
908910
// But filter directories that are at root level to say directory doesnt exist, so that we arent watching them
909-
const typeRoots = getEffectiveTypeRoots(options, { directoryExists: directoryExistsForTypeRootWatch, getCurrentDirectory });
911+
const typeRoots = getEffectiveTypeRoots(options, { getCurrentDirectory });
910912
if (typeRoots) {
911913
mutateMap(
912914
typeRootsWatches,
@@ -922,12 +924,11 @@ namespace ts {
922924
}
923925
}
924926

925-
/**
926-
* Use this function to return if directory exists to get type roots to watch
927-
* If we return directory exists then only the paths will be added to type roots
928-
* Hence return true for all directories except root directories which are filtered from watching
929-
*/
930-
function directoryExistsForTypeRootWatch(nodeTypesDirectory: string) {
927+
function canWatchTypeRootPath(nodeTypesDirectory: string) {
928+
// If type roots is specified, watch that path
929+
if (resolutionHost.getCompilationSettings().typeRoots) return true;
930+
931+
// Otherwise can watch directory only if we can watch the parent directory of node_modules/@types
931932
const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory));
932933
const dirPath = resolutionHost.toPath(dir);
933934
return dirPath === rootPath || canWatchDirectory(dirPath);

src/compiler/types.ts

-1
Original file line numberDiff line numberDiff line change
@@ -7748,7 +7748,6 @@ namespace ts {
77487748
}
77497749

77507750
export interface GetEffectiveTypeRootsHost {
7751-
directoryExists?(directoryName: string): boolean;
77527751
getCurrentDirectory?(): string;
77537752
}
77547753

src/server/project.ts

-4
Original file line numberDiff line numberDiff line change
@@ -2278,10 +2278,6 @@ namespace ts.server {
22782278
return !!this.externalProjectRefCount;
22792279
}
22802280

2281-
getEffectiveTypeRoots() {
2282-
return getEffectiveTypeRoots(this.getCompilationSettings(), this.directoryStructureHost) || [];
2283-
}
2284-
22852281
/*@internal*/
22862282
updateErrorOnNoInputFiles(fileNameResult: ExpandResult) {
22872283
updateErrorForNoInputFiles(fileNameResult, this.getConfigFilePath(), this.configFileSpecs!, this.projectErrors!, this.canConfigFileJsonReportNoInputFiles);

src/services/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ namespace ts {
250250
readFile?(path: string, encoding?: string): string | undefined;
251251
realpath?(path: string): string;
252252
fileExists?(path: string): boolean;
253+
directoryExists?(directoryName: string): boolean;
253254

254255
/*
255256
* LS host can optionally implement these methods to support automatic updating when new type libraries are installed

src/testRunner/unittests/tscWatch/programUpdates.ts

+31-23
Original file line numberDiff line numberDiff line change
@@ -812,29 +812,37 @@ declare const eval: any`
812812
]
813813
});
814814

815-
verifyTscWatch({
816-
scenario,
817-
subScenario: "types should load from config file path if config exists",
818-
commandLineArgs: ["-w", "-p", configFilePath],
819-
sys: () => {
820-
const f1 = {
821-
path: "/a/b/app.ts",
822-
content: "let x = 1"
823-
};
824-
const config = {
825-
path: configFilePath,
826-
content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } })
827-
};
828-
const node = {
829-
path: "/a/b/node_modules/@types/node/index.d.ts",
830-
content: "declare var process: any"
831-
};
832-
const cwd = {
833-
path: "/a/c"
834-
};
835-
return createWatchedSystem([f1, config, node, cwd, libFile], { currentDirectory: cwd.path });
836-
},
837-
changes: emptyArray
815+
describe("types from config file", () => {
816+
function verifyTypesLoad(includeTypeRoots: boolean) {
817+
verifyTscWatch({
818+
scenario,
819+
subScenario: includeTypeRoots ?
820+
"types should not load from config file path if config exists but does not specifies typeRoots" :
821+
"types should load from config file path if config exists",
822+
commandLineArgs: ["-w", "-p", configFilePath],
823+
sys: () => {
824+
const f1 = {
825+
path: "/a/b/app.ts",
826+
content: "let x = 1"
827+
};
828+
const config = {
829+
path: configFilePath,
830+
content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: includeTypeRoots ? [] : undefined } })
831+
};
832+
const node = {
833+
path: "/a/b/node_modules/@types/node/index.d.ts",
834+
content: "declare var process: any"
835+
};
836+
const cwd = {
837+
path: "/a/c"
838+
};
839+
return createWatchedSystem([f1, config, node, cwd, libFile], { currentDirectory: cwd.path });
840+
},
841+
changes: emptyArray
842+
});
843+
}
844+
verifyTypesLoad(/*includeTypeRoots*/ false);
845+
verifyTypesLoad(/*includeTypeRoots*/ true);
838846
});
839847

840848
verifyTscWatch({

src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ namespace ts.projectSystem {
391391
const { configFileName } = projectService.openClientFile(file1.path);
392392
assert.equal(configFileName, tsconfigFile.path as server.NormalizedPath, `should find config`);
393393
checkNumberOfConfiguredProjects(projectService, 1);
394-
const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, `${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules`].concat(getNodeModuleDirectories(getDirectoryPath(canonicalFrontendDir)));
394+
const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, `${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules`];
395395

396396
const project = projectService.configuredProjects.get(canonicalConfigPath)!;
397397
verifyProjectAndWatchedDirectories();

src/testRunner/unittests/tsserver/resolutionCache.ts

+31-21
Original file line numberDiff line numberDiff line change
@@ -418,27 +418,37 @@ namespace ts.projectSystem {
418418
projectService.checkNumberOfProjects({ configuredProjects: 1 });
419419
});
420420

421-
it("types should load from config file path if config exists", () => {
422-
const f1 = {
423-
path: "/a/b/app.ts",
424-
content: "let x = 1"
425-
};
426-
const config = {
427-
path: "/a/b/tsconfig.json",
428-
content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } })
429-
};
430-
const node = {
431-
path: "/a/b/node_modules/@types/node/index.d.ts",
432-
content: "declare var process: any"
433-
};
434-
const cwd = {
435-
path: "/a/c"
436-
};
437-
const host = createServerHost([f1, config, node, cwd], { currentDirectory: cwd.path });
438-
const projectService = createProjectService(host);
439-
projectService.openClientFile(f1.path);
440-
projectService.checkNumberOfProjects({ configuredProjects: 1 });
441-
checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, node.path, config.path]);
421+
describe("types from config file", () => {
422+
function verifyTypesLoad(includeTypeRoots: boolean) {
423+
const f1 = {
424+
path: "/a/b/app.ts",
425+
content: "let x = 1"
426+
};
427+
const config = {
428+
path: "/a/b/tsconfig.json",
429+
content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: includeTypeRoots ? [] : undefined } })
430+
};
431+
const node = {
432+
path: "/a/b/node_modules/@types/node/index.d.ts",
433+
content: "declare var process: any"
434+
};
435+
const cwd = {
436+
path: "/a/c"
437+
};
438+
const host = createServerHost([f1, config, node, cwd], { currentDirectory: cwd.path });
439+
const projectService = createProjectService(host);
440+
projectService.openClientFile(f1.path);
441+
projectService.checkNumberOfProjects({ configuredProjects: 1 });
442+
checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path, ...(includeTypeRoots ? [] : [node.path])]);
443+
}
444+
445+
it("types should load from config file path if config exists", () => {
446+
verifyTypesLoad(/*includeTypeRoots*/ false);
447+
});
448+
449+
it("types should not load from config file path if config exists but does not specifies typeRoots", () => {
450+
verifyTypesLoad(/*includeTypeRoots*/ true);
451+
});
442452
});
443453
});
444454

tests/baselines/reference/api/tsserverlibrary.d.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -3722,7 +3722,6 @@ declare namespace ts {
37223722
noEmitHelpers?: boolean;
37233723
}
37243724
export interface GetEffectiveTypeRootsHost {
3725-
directoryExists?(directoryName: string): boolean;
37263725
getCurrentDirectory?(): string;
37273726
}
37283727
export interface TextSpan {
@@ -5334,6 +5333,7 @@ declare namespace ts {
53345333
readFile?(path: string, encoding?: string): string | undefined;
53355334
realpath?(path: string): string;
53365335
fileExists?(path: string): boolean;
5336+
directoryExists?(directoryName: string): boolean;
53375337
getTypeRootsVersion?(): number;
53385338
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedModule | undefined)[];
53395339
getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined;
@@ -9304,7 +9304,6 @@ declare namespace ts.server {
93049304
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void;
93059305
getTypeAcquisition(): TypeAcquisition;
93069306
close(): void;
9307-
getEffectiveTypeRoots(): string[];
93089307
}
93099308
/**
93109309
* Project whose configuration is handled externally, such as in a '.csproj'.

tests/baselines/reference/api/typescript.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3722,7 +3722,6 @@ declare namespace ts {
37223722
noEmitHelpers?: boolean;
37233723
}
37243724
export interface GetEffectiveTypeRootsHost {
3725-
directoryExists?(directoryName: string): boolean;
37263725
getCurrentDirectory?(): string;
37273726
}
37283727
export interface TextSpan {
@@ -5334,6 +5333,7 @@ declare namespace ts {
53345333
readFile?(path: string, encoding?: string): string | undefined;
53355334
realpath?(path: string): string;
53365335
fileExists?(path: string): boolean;
5336+
directoryExists?(directoryName: string): boolean;
53375337
getTypeRootsVersion?(): number;
53385338
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedModule | undefined)[];
53395339
getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined;

tests/baselines/reference/tscWatch/programUpdates/types-should-load-from-config-file-path-if-config-exists.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Input::
33
let x = 1
44

55
//// [/a/b/tsconfig.json]
6-
{"compilerOptions":{"types":["node"],"typeRoots":[]}}
6+
{"compilerOptions":{"types":["node"]}}
77

88
//// [/a/b/node_modules/@types/node/index.d.ts]
99
declare var process: any
@@ -33,7 +33,7 @@ Output::
3333

3434

3535
Program root files: ["/a/b/app.ts"]
36-
Program options: {"types":["node"],"typeRoots":[],"watch":true,"project":"/a/b/tsconfig.json","configFilePath":"/a/b/tsconfig.json"}
36+
Program options: {"types":["node"],"watch":true,"project":"/a/b/tsconfig.json","configFilePath":"/a/b/tsconfig.json"}
3737
Program files::
3838
/a/lib/lib.d.ts
3939
/a/b/app.ts

0 commit comments

Comments
 (0)