diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts
index a77cf7648e7e7..bb41bf4d594bc 100644
--- a/src/services/stringCompletions.ts
+++ b/src/services/stringCompletions.ts
@@ -47,6 +47,7 @@ import {
getBaseFileName,
getConditions,
getContextualTypeFromParent,
+ getDeclarationEmitExtensionForPath,
getDirectoryPath,
getEffectiveTypeRoots,
getEmitModuleResolutionKind,
@@ -1023,6 +1024,8 @@ function getModulesForPathsPattern(
const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + fragmentDirectory) : normalizedPrefixDirectory;
const normalizedSuffix = normalizePath(parsed.suffix);
+ const declarationExtension = normalizedSuffix && getDeclarationEmitExtensionForPath("_" + normalizedSuffix);
+ const matchingSuffixes = declarationExtension ? [changeExtension(normalizedSuffix, declarationExtension), normalizedSuffix] : [normalizedSuffix];
// Need to normalize after combining: If we combinePaths("a", "../b"), we want "b" and not "a/../b".
const baseDirectory = normalizePath(combinePaths(packageDirectory, expandedPrefixDirectory));
const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase;
@@ -1035,9 +1038,11 @@ function getModulesForPathsPattern(
// interpreted as "any character" can only return *too many* results as compared to the literal
// interpretation, so we can filter those superfluous results out via `trimPrefixAndSuffix` as we've always
// done.
- const includeGlob = normalizedSuffix ? "**/*" + normalizedSuffix : "./*";
+ const includeGlobs = normalizedSuffix
+ ? matchingSuffixes.map(suffix => "**/*" + suffix)
+ : ["./*"];
- const matches = mapDefined(tryReadDirectory(host, baseDirectory, extensionOptions.extensionsToSearch, /*exclude*/ undefined, [includeGlob]), match => {
+ const matches = mapDefined(tryReadDirectory(host, baseDirectory, extensionOptions.extensionsToSearch, /*exclude*/ undefined, includeGlobs), match => {
const trimmedWithPattern = trimPrefixAndSuffix(match);
if (trimmedWithPattern) {
if (containsSlash(trimmedWithPattern)) {
@@ -1057,8 +1062,10 @@ function getModulesForPathsPattern(
return [...matches, ...directories];
function trimPrefixAndSuffix(path: string): string | undefined {
- const inner = withoutStartAndEnd(normalizePath(path), completePrefix, normalizedSuffix);
- return inner === undefined ? undefined : removeLeadingDirectorySeparator(inner);
+ return firstDefined(matchingSuffixes, suffix => {
+ const inner = withoutStartAndEnd(normalizePath(path), completePrefix, suffix);
+ return inner === undefined ? undefined : removeLeadingDirectorySeparator(inner);
+ });
}
}
diff --git a/tests/cases/fourslash/pathCompletionsPackageJsonExportsWildcard7.ts b/tests/cases/fourslash/pathCompletionsPackageJsonExportsWildcard7.ts
new file mode 100644
index 0000000000000..7bd9389520e71
--- /dev/null
+++ b/tests/cases/fourslash/pathCompletionsPackageJsonExportsWildcard7.ts
@@ -0,0 +1,25 @@
+///
+
+// @module: nodenext
+
+// @Filename: /node_modules/foo/package.json
+//// {
+//// "name": "foo",
+//// "exports": {
+//// "./*": "./dist/*.js"
+//// }
+//// }
+
+// @Filename: /node_modules/foo/dist/blah.d.ts
+//// export const blah = 0;
+
+// @Filename: /index.mts
+//// import { } from "foo//**/";
+
+verify.completions({
+ marker: "",
+ isNewIdentifierLocation: true,
+ exact: [
+ { name: "blah", kind: "script", kindModifiers: "" },
+ ]
+});
diff --git a/tests/cases/fourslash/pathCompletionsPackageJsonExportsWildcard8.ts b/tests/cases/fourslash/pathCompletionsPackageJsonExportsWildcard8.ts
new file mode 100644
index 0000000000000..243a01d360f05
--- /dev/null
+++ b/tests/cases/fourslash/pathCompletionsPackageJsonExportsWildcard8.ts
@@ -0,0 +1,28 @@
+///
+
+// @module: nodenext
+
+// @Filename: /node_modules/foo/package.json
+//// {
+//// "name": "foo",
+//// "exports": {
+//// "./*": "./dist/*.js"
+//// }
+//// }
+
+// @Filename: /node_modules/foo/dist/blah.js
+//// export const blah = 0;
+
+// @Filename: /node_modules/foo/dist/blah.d.ts
+//// export declare const blah: 0;
+
+// @Filename: /index.mts
+//// import { } from "foo//**/";
+
+verify.completions({
+ marker: "",
+ isNewIdentifierLocation: true,
+ exact: [
+ { name: "blah", kind: "script", kindModifiers: "" },
+ ]
+});
diff --git a/tests/cases/fourslash/pathCompletionsPackageJsonExportsWildcard9.ts b/tests/cases/fourslash/pathCompletionsPackageJsonExportsWildcard9.ts
new file mode 100644
index 0000000000000..abc1092b1560a
--- /dev/null
+++ b/tests/cases/fourslash/pathCompletionsPackageJsonExportsWildcard9.ts
@@ -0,0 +1,27 @@
+///
+
+// @module: nodenext
+// @allowJs: true
+// @maxNodeModuleJsDepth: 1
+
+// @Filename: /node_modules/foo/package.json
+//// {
+//// "name": "foo",
+//// "exports": {
+//// "./*": "./dist/*.js"
+//// }
+//// }
+
+// @Filename: /node_modules/foo/dist/blah.js
+//// export const blah = 0;
+
+// @Filename: /index.mts
+//// import { } from "foo//**/";
+
+verify.completions({
+ marker: "",
+ isNewIdentifierLocation: true,
+ exact: [
+ { name: "blah", kind: "script", kindModifiers: "" },
+ ]
+});