diff --git a/src/services/completions.ts b/src/services/completions.ts index f1e33087fdc4b..1573db9a2499a 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1128,23 +1128,13 @@ namespace ts.Completions { return false; } - function symbolCanBeReferencedAtTypeLocation(symbol: Symbol): boolean { - symbol = symbol.exportSymbol || symbol; - - // This is an alias, follow what it aliases - symbol = skipAlias(symbol, typeChecker); - - if (symbol.flags & SymbolFlags.Type) { - return true; - } - - if (symbol.flags & SymbolFlags.Module) { - const exportedSymbols = typeChecker.getExportsOfModule(symbol); - // If the exported symbols contains type, - // symbol can be referenced at locations where type is allowed - return exportedSymbols.some(symbolCanBeReferencedAtTypeLocation); - } - return false; + /** True if symbol is a type or a module containing at least one type. */ + function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, seenModules = createMap()): boolean { + const sym = skipAlias(symbol.exportSymbol || symbol, typeChecker); + return !!(sym.flags & SymbolFlags.Type) || + !!(sym.flags & SymbolFlags.Module) && + addToSeen(seenModules, getSymbolId(sym)) && + typeChecker.getExportsOfModule(sym).some(e => symbolCanBeReferencedAtTypeLocation(e, seenModules)); } function getSymbolsFromOtherSourceFileExports(symbols: Symbol[], tokenText: string, target: ScriptTarget): void { diff --git a/tests/cases/fourslash/completionsRecursiveNamespace.ts b/tests/cases/fourslash/completionsRecursiveNamespace.ts new file mode 100644 index 0000000000000..687399b587e34 --- /dev/null +++ b/tests/cases/fourslash/completionsRecursiveNamespace.ts @@ -0,0 +1,9 @@ +/// + +////declare namespace N { +//// export import M = N; +////} +////type T = N./**/ + +// Previously this would crash in `symbolCanBeReferencedAtTypeLocation` due to the namespace exporting itself. +verify.completions({ marker: "", exact: undefined });