Skip to content

Commit 3b7410c

Browse files
Fix wildcard package export auto-import containment
1 parent 7539c04 commit 3b7410c

2 files changed

Lines changed: 46 additions & 3 deletions

File tree

src/compiler/moduleNameResolver.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,7 +2306,9 @@ function loadEntrypointsFromExportMap(
23062306
if (target.indexOf("*") !== target.lastIndexOf("*")) {
23072307
return false;
23082308
}
2309-
2309+
if (hasInvalidPackageJsonExportTargetSegments(target)) {
2310+
return false;
2311+
}
23102312
state.host.readDirectory(
23112313
scope.packageDirectory,
23122314
extensionsToExtensionsArray(extensions),
@@ -2323,8 +2325,7 @@ function loadEntrypointsFromExportMap(
23232325
});
23242326
}
23252327
else {
2326-
const partsAfterFirst = getPathComponents(target).slice(2);
2327-
if (partsAfterFirst.includes("..") || partsAfterFirst.includes(".") || partsAfterFirst.includes("node_modules")) {
2328+
if (hasInvalidPackageJsonExportTargetSegments(target)) {
23282329
return false;
23292330
}
23302331
const resolvedTarget = combinePaths(scope.packageDirectory, target);
@@ -2356,6 +2357,13 @@ function loadEntrypointsFromExportMap(
23562357
}
23572358
}
23582359

2360+
function hasInvalidPackageJsonExportTargetSegments(target: string): boolean {
2361+
// Package export targets are unrooted paths that begin with "./", so the
2362+
// first two components are the empty root marker and the leading ".".
2363+
const partsAfterFirst = getPathComponents(target).slice(2);
2364+
return partsAfterFirst.includes("..") || partsAfterFirst.includes(".") || partsAfterFirst.includes("node_modules");
2365+
}
2366+
23592367
/** @internal */
23602368
export function getTemporaryModuleResolutionState(packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): ModuleResolutionState {
23612369
return {

src/testRunner/unittests/tsserver/autoImportProvider.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,41 @@ describe("unittests:: tsserver:: autoImportProvider::", () => {
333333
assert.ok(seenSymbolNames.has("Volume"));
334334
baselineTsserverLogs("autoImportProvider", "Shared source files between AutoImportProvider and main program", session);
335335
});
336+
337+
it("Does not index wildcard export targets outside the package", () => {
338+
const files = [
339+
{
340+
path: "/user/username/projects/project/node_modules/pkg/package.json",
341+
content: jsonToReadableText({
342+
name: "pkg",
343+
version: "1.0.0",
344+
exports: {
345+
"./*": "./../../private/*.js",
346+
},
347+
}),
348+
},
349+
{ path: "/user/username/projects/project/private/leaked.d.ts", content: `export declare const leakedAutoImport: number;` },
350+
{ path: "/user/username/projects/project/package.json", content: jsonToReadableText({ dependencies: { pkg: "*" } }) },
351+
{
352+
path: "/user/username/projects/project/tsconfig.json",
353+
content: jsonToReadableText({
354+
compilerOptions: {
355+
module: "nodenext",
356+
lib: ["es5"],
357+
},
358+
files: ["index.ts"],
359+
}),
360+
},
361+
{ path: "/user/username/projects/project/index.ts", content: "" },
362+
];
363+
364+
const { session } = setup(files);
365+
openFilesForSession([files[4]], session);
366+
const project = session.getProjectService().configuredProjects.get("/user/username/projects/project/tsconfig.json")!;
367+
const completions = project.getLanguageService().getCompletionsAtPosition(files[4].path, 0, { includeCompletionsForModuleExports: true });
368+
assert.isDefined(completions);
369+
assert.isFalse(completions.entries.some(c => c.name === "leakedAutoImport"));
370+
});
336371
});
337372

338373
describe("unittests:: tsserver:: autoImportProvider:: monorepo", () => {

0 commit comments

Comments
 (0)