mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Updates to type reference directive resolution and module resolution when failed (#51715)
This commit is contained in:
@@ -5246,6 +5246,10 @@
|
||||
"category": "Message",
|
||||
"code": 6264
|
||||
},
|
||||
"Resolving type reference directive for program that specifies custom typeRoots, skipping lookup in 'node_modules' folder.": {
|
||||
"category": "Message",
|
||||
"code": 6265
|
||||
},
|
||||
|
||||
"Directory '{0}' has no containing package.json scope. Imports will not resolve.": {
|
||||
"category": "Message",
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
hasProperty,
|
||||
hasTrailingDirectorySeparator,
|
||||
hostGetCanonicalFileName,
|
||||
inferredTypesContainingFile,
|
||||
isArray,
|
||||
isDeclarationFileName,
|
||||
isExternalModuleNameRelative,
|
||||
@@ -450,7 +451,7 @@ export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffecti
|
||||
}
|
||||
|
||||
if (currentDirectory !== undefined) {
|
||||
return getDefaultTypeRoots(currentDirectory, host);
|
||||
return getDefaultTypeRoots(currentDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,19 +459,11 @@ export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffecti
|
||||
* Returns the path to every node_modules/@types directory from some ancestor directory.
|
||||
* Returns undefined if there are none.
|
||||
*/
|
||||
function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined {
|
||||
if (!host.directoryExists) {
|
||||
return [combinePaths(currentDirectory, nodeModulesAtTypes)];
|
||||
// And if it doesn't exist, tough.
|
||||
}
|
||||
|
||||
function getDefaultTypeRoots(currentDirectory: string): string[] | undefined {
|
||||
let typeRoots: string[] | undefined;
|
||||
forEachAncestorDirectory(normalizePath(currentDirectory), directory => {
|
||||
const atTypes = combinePaths(directory, nodeModulesAtTypes);
|
||||
if (host.directoryExists!(atTypes)) {
|
||||
(typeRoots || (typeRoots = [])).push(atTypes);
|
||||
}
|
||||
return undefined;
|
||||
(typeRoots ??= []).push(atTypes);
|
||||
});
|
||||
return typeRoots;
|
||||
}
|
||||
@@ -627,10 +620,18 @@ export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string
|
||||
}
|
||||
return firstDefined(typeRoots, typeRoot => {
|
||||
const candidate = combinePaths(typeRoot, typeReferenceDirectiveName);
|
||||
const candidateDirectory = getDirectoryPath(candidate);
|
||||
const directoryExists = directoryProbablyExists(candidateDirectory, host);
|
||||
const directoryExists = directoryProbablyExists(typeRoot, host);
|
||||
if (!directoryExists && traceEnabled) {
|
||||
trace(host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidateDirectory);
|
||||
trace(host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, typeRoot);
|
||||
}
|
||||
if (options.typeRoots) {
|
||||
// Custom typeRoots resolve as file or directory just like we do modules
|
||||
const resolvedFromFile = loadModuleFromFile(Extensions.Declaration, candidate, !directoryExists, moduleResolutionState);
|
||||
if (resolvedFromFile) {
|
||||
const packageDirectory = parseNodeModuleFromPath(resolvedFromFile.path);
|
||||
const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, moduleResolutionState) : undefined;
|
||||
return resolvedTypeScriptOnly(withPackageId(packageInfo, resolvedFromFile));
|
||||
}
|
||||
}
|
||||
return resolvedTypeScriptOnly(
|
||||
loadNodeModuleFromDirectory(Extensions.Declaration, candidate,
|
||||
@@ -646,20 +647,24 @@ export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string
|
||||
|
||||
function secondaryLookup(): PathAndPackageId | undefined {
|
||||
const initialLocationForSecondaryLookup = containingFile && getDirectoryPath(containingFile);
|
||||
|
||||
if (initialLocationForSecondaryLookup !== undefined) {
|
||||
// check secondary locations
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup);
|
||||
}
|
||||
let result: Resolved | undefined;
|
||||
if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) {
|
||||
const searchResult = loadModuleFromNearestNodeModulesDirectory(Extensions.Declaration, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined);
|
||||
result = searchResult && searchResult.value;
|
||||
if (!options.typeRoots || !endsWith(containingFile!, inferredTypesContainingFile)) {
|
||||
// check secondary locations
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup);
|
||||
}
|
||||
if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) {
|
||||
const searchResult = loadModuleFromNearestNodeModulesDirectory(Extensions.Declaration, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined);
|
||||
result = searchResult && searchResult.value;
|
||||
}
|
||||
else {
|
||||
const { path: candidate } = normalizePathForCJSResolution(initialLocationForSecondaryLookup, typeReferenceDirectiveName);
|
||||
result = nodeLoadModuleByRelativeName(Extensions.Declaration, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const { path: candidate } = normalizePathForCJSResolution(initialLocationForSecondaryLookup, typeReferenceDirectiveName);
|
||||
result = nodeLoadModuleByRelativeName(Extensions.Declaration, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true);
|
||||
else if (traceEnabled) {
|
||||
trace(host, Diagnostics.Resolving_type_reference_directive_for_program_that_specifies_custom_typeRoots_skipping_lookup_in_node_modules_folder);
|
||||
}
|
||||
return resolvedTypeScriptOnly(result);
|
||||
}
|
||||
@@ -1777,6 +1782,9 @@ function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleNa
|
||||
}
|
||||
resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference);
|
||||
}
|
||||
if (extensions & Extensions.Declaration) {
|
||||
resolved ??= resolveFromTypeRoot(moduleName, state);
|
||||
}
|
||||
// For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files.
|
||||
return resolved && { value: resolved.value && { resolved: resolved.value, isExternalLibraryImport: true } };
|
||||
}
|
||||
@@ -3058,12 +3066,12 @@ export function classicNameResolver(moduleName: string, containingFile: string,
|
||||
const searchName = normalizePath(combinePaths(directory, moduleName));
|
||||
return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state));
|
||||
});
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
if (resolved) return resolved;
|
||||
if (extensions & (Extensions.TypeScript | Extensions.Declaration)) {
|
||||
// If we didn't find the file normally, look it up in @types.
|
||||
return loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName, containingDirectory, state);
|
||||
let resolved = loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName, containingDirectory, state);
|
||||
if (extensions & Extensions.Declaration) resolved ??= resolveFromTypeRoot(moduleName, state);
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -3073,6 +3081,25 @@ export function classicNameResolver(moduleName: string, containingFile: string,
|
||||
}
|
||||
}
|
||||
|
||||
function resolveFromTypeRoot(moduleName: string, state: ModuleResolutionState) {
|
||||
if (!state.compilerOptions.typeRoots) return;
|
||||
for (const typeRoot of state.compilerOptions.typeRoots) {
|
||||
const candidate = combinePaths(typeRoot, moduleName);
|
||||
const directoryExists = directoryProbablyExists(typeRoot, state.host);
|
||||
if (!directoryExists && state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, typeRoot);
|
||||
}
|
||||
const resolvedFromFile = loadModuleFromFile(Extensions.Declaration, candidate, !directoryExists, state);
|
||||
if (resolvedFromFile) {
|
||||
const packageDirectory = parseNodeModuleFromPath(resolvedFromFile.path);
|
||||
const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined;
|
||||
return toSearchResult(withPackageId(packageInfo, resolvedFromFile));
|
||||
}
|
||||
const resolved = loadNodeModuleFromDirectory(Extensions.Declaration, candidate, !directoryExists, state);
|
||||
if (resolved) return toSearchResult(resolved);
|
||||
}
|
||||
}
|
||||
|
||||
// Program errors validate that `noEmit` or `emitDeclarationOnly` is also set,
|
||||
// so this function doesn't check them to avoid propagating errors.
|
||||
/** @internal */
|
||||
|
||||
@@ -1167,26 +1167,28 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
|
||||
|
||||
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);
|
||||
}
|
||||
return canWatchTypeRootPath(typeRootPath) ?
|
||||
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();
|
||||
// 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);
|
||||
// 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) :
|
||||
noopFileWatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1204,7 +1206,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
|
||||
|
||||
// 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 });
|
||||
const typeRoots = getEffectiveTypeRoots(options, { getCurrentDirectory });
|
||||
if (typeRoots) {
|
||||
mutateMap(
|
||||
typeRootsWatches,
|
||||
@@ -1220,12 +1222,11 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
function canWatchTypeRootPath(nodeTypesDirectory: string) {
|
||||
// If type roots is specified, watch that path
|
||||
if (resolutionHost.getCompilationSettings().typeRoots) return true;
|
||||
|
||||
// Otherwise can watch directory only if we can watch the parent directory of node_modules/@types
|
||||
const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory));
|
||||
const dirPath = resolutionHost.toPath(dir);
|
||||
return dirPath === rootPath || canWatchDirectoryOrFile(dirPath);
|
||||
|
||||
@@ -9473,7 +9473,6 @@ export interface EmitTextWriter extends SymbolWriter {
|
||||
}
|
||||
|
||||
export interface GetEffectiveTypeRootsHost {
|
||||
directoryExists?(directoryName: string): boolean;
|
||||
getCurrentDirectory?(): string;
|
||||
}
|
||||
|
||||
|
||||
@@ -2827,7 +2827,7 @@ export class ConfiguredProject extends Project {
|
||||
}
|
||||
|
||||
getEffectiveTypeRoots() {
|
||||
return getEffectiveTypeRoots(this.getCompilationSettings(), this.directoryStructureHost) || [];
|
||||
return getEffectiveTypeRoots(this.getCompilationSettings(), this) || [];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
||||
@@ -970,28 +970,36 @@ declare const eval: any`
|
||||
]
|
||||
});
|
||||
|
||||
verifyTscWatch({
|
||||
scenario,
|
||||
subScenario: "types should load from config file path if config exists",
|
||||
commandLineArgs: ["-w", "-p", configFilePath],
|
||||
sys: () => {
|
||||
const f1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: "let x = 1"
|
||||
};
|
||||
const config = {
|
||||
path: configFilePath,
|
||||
content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } })
|
||||
};
|
||||
const node = {
|
||||
path: "/a/b/node_modules/@types/node/index.d.ts",
|
||||
content: "declare var process: any"
|
||||
};
|
||||
const cwd = {
|
||||
path: "/a/c"
|
||||
};
|
||||
return createWatchedSystem([f1, config, node, cwd, libFile], { currentDirectory: cwd.path });
|
||||
},
|
||||
describe("types from config file", () => {
|
||||
function verifyTypesLoad(includeTypeRoots: boolean) {
|
||||
verifyTscWatch({
|
||||
scenario,
|
||||
subScenario: includeTypeRoots ?
|
||||
"types should not load from config file path if config exists but does not specifies typeRoots" :
|
||||
"types should load from config file path if config exists",
|
||||
commandLineArgs: ["-w", "-p", configFilePath],
|
||||
sys: () => {
|
||||
const f1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: "let x = 1"
|
||||
};
|
||||
const config = {
|
||||
path: configFilePath,
|
||||
content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: includeTypeRoots ? [] : undefined } })
|
||||
};
|
||||
const node = {
|
||||
path: "/a/b/node_modules/@types/node/index.d.ts",
|
||||
content: "declare var process: any"
|
||||
};
|
||||
const cwd = {
|
||||
path: "/a/c"
|
||||
};
|
||||
return createWatchedSystem([f1, config, node, cwd, libFile], { currentDirectory: cwd.path });
|
||||
},
|
||||
});
|
||||
}
|
||||
verifyTypesLoad(/*includeTypeRoots*/ false);
|
||||
verifyTypesLoad(/*includeTypeRoots*/ true);
|
||||
});
|
||||
|
||||
verifyTscWatch({
|
||||
|
||||
@@ -345,27 +345,32 @@ describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem rename
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1 });
|
||||
});
|
||||
|
||||
it("types should load from config file path if config exists", () => {
|
||||
const f1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: "let x = 1"
|
||||
};
|
||||
const config = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } })
|
||||
};
|
||||
const node = {
|
||||
path: "/a/b/node_modules/@types/node/index.d.ts",
|
||||
content: "declare var process: any"
|
||||
};
|
||||
const cwd = {
|
||||
path: "/a/c"
|
||||
};
|
||||
const host = createServerHost([f1, config, node, cwd], { currentDirectory: cwd.path });
|
||||
const projectService = createProjectService(host);
|
||||
projectService.openClientFile(f1.path);
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1 });
|
||||
checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, node.path, config.path]);
|
||||
describe("types from config file", () => {
|
||||
function verifyTypesLoad(subScenario: string, includeTypeRoots: boolean) {
|
||||
it(subScenario, () => {
|
||||
const f1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: "let x = 1"
|
||||
};
|
||||
const config = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: includeTypeRoots ? [] : undefined } })
|
||||
};
|
||||
const node = {
|
||||
path: "/a/b/node_modules/@types/node/index.d.ts",
|
||||
content: "declare var process: any"
|
||||
};
|
||||
const cwd = {
|
||||
path: "/a/c"
|
||||
};
|
||||
const host = createServerHost([f1, config, node, cwd], { currentDirectory: cwd.path });
|
||||
const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) });
|
||||
projectService.openClientFile(f1.path);
|
||||
baselineTsserverLogs("resolutionCache", subScenario, projectService);
|
||||
});
|
||||
}
|
||||
verifyTypesLoad("types should load from config file path if config exists", /*includeTypeRoots*/ false);
|
||||
verifyTypesLoad("types should not load from config file path if config exists but does not specifies typeRoots", /*includeTypeRoots*/ true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user