Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/compiler/resolutionCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ namespace ts {
perDirectoryCacheWithRedirects: CacheWithRedirects<Map<T>>,
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference) => T,
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
shouldRetryResolution: (t: T) => boolean,
reusedNames: ReadonlyArray<string> | undefined,
logChanges: boolean): (R | undefined)[] {

Expand All @@ -260,7 +261,7 @@ namespace ts {
perDirectoryResolution = createMap();
perDirectoryCache.set(dirPath, perDirectoryResolution);
}
const resolvedModules: R[] = [];
const resolvedModules: (R | undefined)[] = [];
const compilerOptions = resolutionHost.getCompilationSettings();
const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path);

Expand All @@ -278,7 +279,7 @@ namespace ts {
if (!seenNamesInFile.has(name) &&
allFilesHaveInvalidatedResolution || unmatchedRedirects || !resolution || resolution.isInvalidated ||
// If the name is unresolved import that was invalidated, recalculate
(hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && !getResolutionWithResolvedFileName(resolution))) {
(hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) {
const existingResolution = resolution;
const resolutionInDirectory = perDirectoryResolution.get(name);
if (resolutionInDirectory) {
Expand All @@ -302,7 +303,7 @@ namespace ts {
}
Debug.assert(resolution !== undefined && !resolution.isInvalidated);
seenNamesInFile.set(name, true);
resolvedModules.push(getResolutionWithResolvedFileName(resolution)!); // TODO: GH#18217
resolvedModules.push(getResolutionWithResolvedFileName(resolution));
}

// Stop watching and remove the unused name
Expand Down Expand Up @@ -339,6 +340,7 @@ namespace ts {
typeDirectiveNames, containingFile, redirectedReference,
resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives,
resolveTypeReferenceDirective, getResolvedTypeReferenceDirective,
/*shouldRetryResolution*/ resolution => resolution.resolvedTypeReferenceDirective === undefined,
/*reusedNames*/ undefined, /*logChanges*/ false
);
}
Expand All @@ -348,6 +350,7 @@ namespace ts {
moduleNames, containingFile, redirectedReference,
resolvedModuleNames, perDirectoryResolvedModuleNames,
resolveModuleName, getResolvedModule,
/*shouldRetryResolution*/ resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension),
reusedNames, logChangesWhenResolvingModule
);
}
Expand Down
4 changes: 3 additions & 1 deletion src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1186,7 +1186,9 @@ namespace ts.server {
let unresolvedImports: string[] | undefined;
file.resolvedModules.forEach((resolvedModule, name) => {
// pick unresolved non-relative names
if (!resolvedModule && !isExternalModuleNameRelative(name) && !ambientModules.some(m => m === name)) {
if ((!resolvedModule || !resolutionExtensionIsTSOrJson(resolvedModule.extension)) &&
!isExternalModuleNameRelative(name) &&
!ambientModules.some(m => m === name)) {
unresolvedImports = append(unresolvedImports, parsePackageName(name).packageName);
}
});
Expand Down
44 changes: 44 additions & 0 deletions src/testRunner/unittests/typingsInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,50 @@ namespace ts.projectSystem {
checkProjectActualFiles(service.inferredProjects[0], [file.path, node.path, commander.path]);
});

it("should redo resolution that resolved to '.js' file after typings are installed", () => {
const file: TestFSWithWatch.File = {
path: "/a/b/app.js",
content: `
import * as commander from "commander";`
};
const cachePath = "/a/cache";
const commanderJS: TestFSWithWatch.File = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should add this as a separate test case (to ensure it keeps working when there is no resolution as one test case while when resolution is to js file as another test case)

path: "/node_modules/commander/index.js",
content: "module.exports = 0",
};

const typeNames: ReadonlyArray<string> = ["commander"];
const typePath = (name: string): string => `${cachePath}/node_modules/@types/${name}/index.d.ts`;
const host = createServerHost([file, commanderJS]);
const installer = new (class extends Installer {
constructor() {
super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry(...typeNames) });
}
installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
const installedTypings = typeNames.map(name => `@types/${name}`);
const typingFiles = typeNames.map((name): TestFSWithWatch.File => ({ path: typePath(name), content: "" }));
executeCommand(this, host, installedTypings, typingFiles, cb);
}
})();
const service = createProjectService(host, { typingsInstaller: installer });
service.openClientFile(file.path);

checkWatchedFiles(host, [...flatMap(["/a/b", "/a", ""], x => [x + "/tsconfig.json", x + "/jsconfig.json"]), "/a/lib/lib.d.ts"]);
checkWatchedDirectories(host, [], /*recursive*/ false);
// Does not include cachePath because that is handled by typingsInstaller
checkWatchedDirectories(host, ["/node_modules", "/a/b/node_modules", "/a/b/node_modules/@types", "/a/b/bower_components"], /*recursive*/ true);

service.checkNumberOfProjects({ inferredProjects: 1 });
checkProjectActualFiles(service.inferredProjects[0], [file.path, commanderJS.path]);

installer.installAll(/*expectedCount*/1);
for (const name of typeNames) {
assert.isTrue(host.fileExists(typePath(name)), `typings for '${name}' should be created`);
}
host.checkTimeoutQueueLengthAndRun(2);
checkProjectActualFiles(service.inferredProjects[0], [file.path, ...typeNames.map(typePath)]);
});

it("should pick typing names from non-relative unresolved imports", () => {
const f1 = {
path: "/a/b/app.js",
Expand Down