diff --git a/src/services/services.ts b/src/services/services.ts index a14090e26c9..025afcd1548 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1171,6 +1171,11 @@ namespace ts { resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; directoryExists?(directoryName: string): boolean; + + /** + * getDirectories is also required for full import and type reference completions. Without it defined, certain + * completions will not be provided + */ getDirectories?(directoryName: string): string[]; } @@ -4652,7 +4657,7 @@ namespace ts { getCompletionEntriesFromTypings(host, options, scriptPath, result); - forEach(enumeratePotentialNonRelativeModules(fragment, scriptPath), moduleName => { + forEach(enumeratePotentialNonRelativeModules(fragment, scriptPath, options), moduleName => { result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); }); @@ -4704,7 +4709,7 @@ namespace ts { return undefined; } - function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string): string[] { + function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string, options: CompilerOptions): string[] { const trailingSeperator = hasTrailingDirectorySeparator(fragment); fragment = normalizePath(fragment); @@ -4733,19 +4738,21 @@ namespace ts { return moduleName; }); - forEach(enumerateNodeModulesVisibleToScript(host, scriptPath, moduleNameFragment), visibleModule => { - if (!isNestedModule) { - nonRelativeModules.push(visibleModule.canBeImported ? visibleModule.moduleName : ensureTrailingDirectorySeparator(visibleModule.moduleName)); - } - else { - const nestedFiles = host.readDirectory(visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); + if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) { + forEach(enumerateNodeModulesVisibleToScript(host, scriptPath, moduleNameFragment), visibleModule => { + if (!isNestedModule) { + nonRelativeModules.push(visibleModule.canBeImported ? visibleModule.moduleName : ensureTrailingDirectorySeparator(visibleModule.moduleName)); + } + else { + const nestedFiles = host.readDirectory(visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); - forEach(nestedFiles, (f) => { - const nestedModule = removeFileExtension(getBaseFileName(f)); - nonRelativeModules.push(nestedModule); - }); - } - }); + forEach(nestedFiles, (f) => { + const nestedModule = removeFileExtension(getBaseFileName(f)); + nonRelativeModules.push(nestedModule); + }); + } + }); + } return deduplicate(nonRelativeModules); } @@ -4791,16 +4798,18 @@ namespace ts { result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName)); }); } - else if (options.typeRoots) { + else if (host.getDirectories && options.typeRoots) { const absoluteRoots = map(options.typeRoots, rootDirectory => getAbsoluteProjectPath(rootDirectory, host, options.project)); - forEach(absoluteRoots, absoluteRoot => getCompletionEntriesFromDirectory(host, options, absoluteRoot, result)); + forEach(absoluteRoots, absoluteRoot => getCompletionEntriesFromDirectories(host, options, absoluteRoot, result)); } - // Also get all @types typings installed in visible node_modules directories - forEach(findPackageJsons(scriptPath), package => { - const typesDir = combinePaths(getDirectoryPath(package), "node_modules/@types"); - getCompletionEntriesFromDirectory(host, options, typesDir, result); - }); + if (host.getDirectories) { + // Also get all @types typings installed in visible node_modules directories + forEach(findPackageJsons(scriptPath), package => { + const typesDir = combinePaths(getDirectoryPath(package), "node_modules/@types"); + getCompletionEntriesFromDirectories(host, options, typesDir, result); + }); + } return result; } @@ -4817,16 +4826,10 @@ namespace ts { return normalizePath(host.resolvePath(path)); } - function getCompletionEntriesFromDirectory(host: LanguageServiceHost, options: CompilerOptions, directory: string, result: CompletionEntry[]) { - if (directoryProbablyExists(directory, host)) { - const typeDirectories = host.readDirectory(directory, getSupportedExtensions(options), /*exclude*/undefined, /*include*/["./*/*"]); - const seen: {[index: string]: boolean} = {}; - forEach(typeDirectories, typeFile => { - const typeDirectory = getDirectoryPath(typeFile); - if (!hasProperty(seen, typeDirectory)) { - seen[typeDirectory] = true; - result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName)); - } + function getCompletionEntriesFromDirectories(host: LanguageServiceHost, options: CompilerOptions, directory: string, result: CompletionEntry[]) { + if (host.getDirectories && directoryProbablyExists(directory, host)) { + forEach(host.getDirectories(directory), typeDirectory => { + result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName)); }); } } diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts new file mode 100644 index 00000000000..cb1f67f6349 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts @@ -0,0 +1,33 @@ +/// + +// @moduleResolution: classic + +// @Filename: dir1/dir2/dir3/dir4/test0.ts +//// import * as foo1 from "f/*import_as0*/ +//// import * as foo3 from "fake-module/*import_as1*/ + +//// import foo4 = require("f/*import_equals0*/ +//// import foo6 = require("fake-module/*import_equals1*/ + +//// var foo7 = require("f/*require0*/ +//// var foo9 = require("fake-module/*require1*/ + +// @Filename: package.json +//// { "dependencies": { "fake-module": "latest" } } +// @Filename: node_modules/fake-module/ts.ts +//// /*module1*/ + +// @Filename: dir1/dir2/dir3/package.json +//// { "dependencies": { "fake-module3": "latest" } } +// @Filename: dir1/dir2/dir3/node_modules/fake-module3/ts.ts +//// /*module3*/ + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListIsEmpty(); + + goTo.marker(kind + "1"); + verify.completionListIsEmpty(); +} \ No newline at end of file