diff --git a/src/services/completions.ts b/src/services/completions.ts index 66a1e2cc72e..d7aa66bdf4c 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1349,18 +1349,17 @@ namespace ts.Completions { } for (const symbol of typeChecker.getExportsOfModule(moduleSymbol)) { - // If `symbol.parent !== ...`, this is an `export * from "foo"` re-export. Those don't create new symbols. - if (typeChecker.getMergedSymbol(symbol.parent!) !== resolvedModuleSymbol) { - continue; - } // If this is `export { _break as break };` (a keyword) -- skip this and prefer the keyword completion. if (some(symbol.declarations, d => isExportSpecifier(d) && !!d.propertyName && isIdentifierANonContextualKeyword(d.name))) { continue; } + // If `symbol.parent !== moduleSymbol`, this is an `export * from "foo"` re-export. Those don't create new symbols. + const isExportStarFromReExport = typeChecker.getMergedSymbol(symbol.parent!) !== resolvedModuleSymbol; // If `!!d.parent.parent.moduleSpecifier`, this is `export { foo } from "foo"` re-export, which creates a new symbol (thus isn't caught by the first check). - if (some(symbol.declarations, d => isExportSpecifier(d) && !d.propertyName && !!d.parent.parent.moduleSpecifier)) { - // Walk the export chain back one module (step 1 or 2 in diagrammed example) - const nearestExportSymbolId = getSymbolId(Debug.assertDefined(getNearestExportSymbol(symbol))); + if (isExportStarFromReExport || some(symbol.declarations, d => isExportSpecifier(d) && !d.propertyName && !!d.parent.parent.moduleSpecifier)) { + // Walk the export chain back one module (step 1 or 2 in diagrammed example). + // Or, in the case of `export * from "foo"`, `symbol` already points to the original export, so just use that. + const nearestExportSymbolId = getSymbolId(isExportStarFromReExport ? symbol : Debug.assertDefined(getNearestExportSymbol(symbol))); const symbolHasBeenSeen = !!symbolToOriginInfoMap[nearestExportSymbolId] || aliasesToAlreadyIncludedSymbols.has(nearestExportSymbolId.toString()); if (!symbolHasBeenSeen) { aliasesToReturnIfOriginalsAreMissing.set(nearestExportSymbolId.toString(), { alias: symbol, moduleSymbol }); diff --git a/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport3.ts b/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport3.ts new file mode 100644 index 00000000000..8533461e0b8 --- /dev/null +++ b/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport3.ts @@ -0,0 +1,48 @@ +/// + +//@noEmit: true + +//@Filename: /package.json +////{ +//// "dependencies": { +//// "b": "*", +//// } +////} + +//@Filename: /node_modules/a/index.d.ts +////export const foo = 0; + +//@Filename: /node_modules/a/package.json +////{ +//// "name": "a", +//// "types": "./index.d.ts" +////} + +//@Filename: /node_modules/b/index.d.ts +////export * from "a"; + +//@Filename: /node_modules/b/package.json +////{ +//// "name": "b", +//// "types": "./index.d.ts" +////} + +//@Filename: /src/index.ts +////fo/**/ + +verify.completions({ + marker: test.marker(""), + includes: [ + completion.undefinedVarEntry, + { + name: "foo", + source: "/node_modules/b/index", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions + }, + ...completion.statementKeywordsWithTypes + ], + preferences: { + includeCompletionsForModuleExports: true + } +}); diff --git a/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport4.ts b/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport4.ts new file mode 100644 index 00000000000..83ac6526b25 --- /dev/null +++ b/tests/cases/fourslash/completionsImport_filteredByPackageJson_reexport4.ts @@ -0,0 +1,57 @@ +/// + +//@noEmit: true + +//@Filename: /package.json +////{ +//// "dependencies": { +//// "c": "*", +//// } +////} + +//@Filename: /node_modules/a/index.d.ts +////export const foo = 0; + +//@Filename: /node_modules/a/package.json +////{ +//// "name": "a", +//// "types": "./index.d.ts" +////} + +//@Filename: /node_modules/b/index.d.ts +////export * from "a"; + +//@Filename: /node_modules/b/package.json +////{ +//// "name": "b", +//// "types": "./index.d.ts" +////} + +//@Filename: /node_modules/c/index.d.ts +////export * from "a"; + +//@Filename: /node_modules/c/package.json +////{ +//// "name": "c", +//// "types": "./index.d.ts" +////} + +//@Filename: /src/index.ts +////fo/**/ + +verify.completions({ + marker: test.marker(""), + includes: [ + completion.undefinedVarEntry, + { + name: "foo", + source: "/node_modules/c/index", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions + }, + ...completion.statementKeywordsWithTypes + ], + preferences: { + includeCompletionsForModuleExports: true + } +});