Skip to content

Commit e3da8fb

Browse files
authored
Merge pull request #21131 from amcasey/GH15533
Unmangle scoped package names in import completions
2 parents a77c601 + 9a4fe8e commit e3da8fb

File tree

4 files changed

+69
-17
lines changed

4 files changed

+69
-17
lines changed

src/compiler/moduleNameResolver.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,13 +1097,18 @@ namespace ts {
10971097
export function getPackageNameFromAtTypesDirectory(mangledName: string): string {
10981098
const withoutAtTypePrefix = removePrefix(mangledName, "@types/");
10991099
if (withoutAtTypePrefix !== mangledName) {
1100-
return stringContains(withoutAtTypePrefix, mangledScopedPackageSeparator) ?
1101-
"@" + withoutAtTypePrefix.replace(mangledScopedPackageSeparator, ts.directorySeparator) :
1102-
withoutAtTypePrefix;
1100+
return getUnmangledNameForScopedPackage(withoutAtTypePrefix);
11031101
}
11041102
return mangledName;
11051103
}
11061104

1105+
/* @internal */
1106+
export function getUnmangledNameForScopedPackage(typesPackageName: string): string {
1107+
return stringContains(typesPackageName, mangledScopedPackageSeparator) ?
1108+
"@" + typesPackageName.replace(mangledScopedPackageSeparator, ts.directorySeparator) :
1109+
typesPackageName;
1110+
}
1111+
11071112
function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, traceEnabled: boolean, host: ModuleResolutionHost): SearchResult<Resolved> {
11081113
const result = cache && cache.get(containingDirectory);
11091114
if (result) {

src/services/pathCompletions.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,11 @@ namespace ts.Completions.PathCompletions {
314314

315315
function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: CompletionEntry[] = []): CompletionEntry[] {
316316
// Check for typings specified in compiler options
317+
const seen = createMap<true>();
317318
if (options.types) {
318-
for (const moduleName of options.types) {
319-
result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span));
319+
for (const typesName of options.types) {
320+
const moduleName = getUnmangledNameForScopedPackage(typesName);
321+
pushResult(moduleName);
320322
}
321323
}
322324
else if (host.getDirectories) {
@@ -328,32 +330,40 @@ namespace ts.Completions.PathCompletions {
328330

329331
if (typeRoots) {
330332
for (const root of typeRoots) {
331-
getCompletionEntriesFromDirectories(host, root, span, result);
333+
getCompletionEntriesFromDirectories(root);
332334
}
333335
}
334-
}
335336

336-
if (host.getDirectories) {
337337
// Also get all @types typings installed in visible node_modules directories
338338
for (const packageJson of findPackageJsons(scriptPath, host)) {
339339
const typesDir = combinePaths(getDirectoryPath(packageJson), "node_modules/@types");
340-
getCompletionEntriesFromDirectories(host, typesDir, span, result);
340+
getCompletionEntriesFromDirectories(typesDir);
341341
}
342342
}
343343

344344
return result;
345-
}
346345

347-
function getCompletionEntriesFromDirectories(host: LanguageServiceHost, directory: string, span: TextSpan, result: Push<CompletionEntry>) {
348-
if (host.getDirectories && tryDirectoryExists(host, directory)) {
349-
const directories = tryGetDirectories(host, directory);
350-
if (directories) {
351-
for (let typeDirectory of directories) {
352-
typeDirectory = normalizePath(typeDirectory);
353-
result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName, span));
346+
function getCompletionEntriesFromDirectories(directory: string) {
347+
Debug.assert(!!host.getDirectories);
348+
if (tryDirectoryExists(host, directory)) {
349+
const directories = tryGetDirectories(host, directory);
350+
if (directories) {
351+
for (let typeDirectory of directories) {
352+
typeDirectory = normalizePath(typeDirectory);
353+
const directoryName = getBaseFileName(typeDirectory);
354+
const moduleName = getUnmangledNameForScopedPackage(directoryName);
355+
pushResult(moduleName);
356+
}
354357
}
355358
}
356359
}
360+
361+
function pushResult(moduleName: string) {
362+
if (!seen.has(moduleName)) {
363+
result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span));
364+
seen.set(moduleName, true);
365+
}
366+
}
357367
}
358368

359369
function findPackageJsons(directory: string, host: LanguageServiceHost): string[] {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: app.ts
4+
////import * as A from "[|/*1*/|]";
5+
6+
// @Filename: /node_modules/@types/a__b/index.d.ts
7+
////declare module "@e/f" { function fun(): string; }
8+
9+
// @Filename: /node_modules/@types/c__d/index.d.ts
10+
////export declare let x: number;
11+
12+
// NOTE: The node_modules folder is in "/", rather than ".", because it requires
13+
// less scaffolding to mock. In particular, "/" is where we look for type roots.
14+
15+
const [replacementSpan] = test.ranges();
16+
verify.completionsAt("1", [
17+
{ name: "@a/b", replacementSpan },
18+
{ name: "@c/d", replacementSpan },
19+
{ name: "@e/f", replacementSpan },
20+
]);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @typeRoots: T1,T2
4+
5+
// @Filename: app.ts
6+
////import * as A from "[|/*1*/|]";
7+
8+
// @Filename: T1/a__b/index.d.ts
9+
////export declare let x: number;
10+
11+
// @Filename: T2/a__b/index.d.ts
12+
////export declare let x: number;
13+
14+
// Confirm that entries are de-dup'd.
15+
verify.completionsAt("1", [
16+
{ name: "@a/b", replacementSpan: test.ranges()[0] },
17+
]);

0 commit comments

Comments
 (0)