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: "" }, + ] +});