diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b6f5616b7e3c0..d8cb421a8f784 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3599,6 +3599,10 @@ namespace ts { return previous[previous.length - 1]; } + export function skipAlias(symbol: Symbol, checker: TypeChecker) { + return symbol.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol; + } + /** See comment on `declareModuleMember` in `binder.ts`. */ export function getCombinedLocalAndExportSymbolFlags(symbol: Symbol): SymbolFlags { return symbol.exportSymbol ? symbol.exportSymbol.flags | symbol.flags : symbol.flags; diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 0fa0b72162b71..d30997aa07647 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -222,10 +222,7 @@ namespace ts.codefix { } function getUniqueSymbolId(symbol: Symbol) { - if (symbol.flags & SymbolFlags.Alias) { - return getSymbolId(checker.getAliasedSymbol(symbol)); - } - return getSymbolId(symbol); + return getSymbolId(skipAlias(symbol, checker)); } function checkSymbolHasMeaning(symbol: Symbol, meaning: SemanticMeaning) { diff --git a/src/services/completions.ts b/src/services/completions.ts index 2e594c6cbccb0..12796a9b791ee 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -596,46 +596,48 @@ namespace ts.Completions { isNewIdentifierLocation = false; // Since this is qualified name check its a type node location - const isTypeLocation = isPartOfTypeNode(node.parent) || insideJsDocTagTypeExpression; + const isTypeLocation = insideJsDocTagTypeExpression || isPartOfTypeNode(node.parent); const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node); - if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression) { + if (isEntityName(node)) { let symbol = typeChecker.getSymbolAtLocation(node); + if (symbol) { + symbol = skipAlias(symbol, typeChecker); + + if (symbol.flags & (SymbolFlags.Module | SymbolFlags.Enum)) { + // Extract module or enum members + const exportedSymbols = typeChecker.getExportsOfModule(symbol); + const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess((node.parent), symbol.getUnescapedName()); + const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol); + const isValidAccess = isRhsOfImportDeclaration ? + // Any kind is allowed when dotting off namespace in internal import equals declaration + (symbol: Symbol) => isValidTypeAccess(symbol) || isValidValueAccess(symbol) : + isTypeLocation ? isValidTypeAccess : isValidValueAccess; + for (const symbol of exportedSymbols) { + if (isValidAccess(symbol)) { + symbols.push(symbol); + } + } - // This is an alias, follow what it aliases - if (symbol && symbol.flags & SymbolFlags.Alias) { - symbol = typeChecker.getAliasedSymbol(symbol); - } - - if (symbol && symbol.flags & SymbolFlags.HasExports) { - // Extract module or enum members - const exportedSymbols = typeChecker.getExportsOfModule(symbol); - const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess((node.parent), symbol.getUnescapedName()); - const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol); - const isValidAccess = isRhsOfImportDeclaration ? - // Any kind is allowed when dotting off namespace in internal import equals declaration - (symbol: Symbol) => isValidTypeAccess(symbol) || isValidValueAccess(symbol) : - isTypeLocation ? isValidTypeAccess : isValidValueAccess; - forEach(exportedSymbols, symbol => { - if (isValidAccess(symbol)) { - symbols.push(symbol); + // If the module is merged with a value, we must get the type of the class and add its propertes (for inherited static methods). + if (!isTypeLocation && symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { + addTypeProperties(typeChecker.getTypeOfSymbolAtLocation(symbol, node)); } - }); + + return; + } } } if (!isTypeLocation) { - const type = typeChecker.getTypeAtLocation(node); - if (type) addTypeProperties(type); + addTypeProperties(typeChecker.getTypeAtLocation(node)); } } function addTypeProperties(type: Type) { - if (type) { - // Filter private properties - for (const symbol of type.getApparentProperties()) { - if (typeChecker.isValidPropertyAccess((node.parent), symbol.getUnescapedName())) { - symbols.push(symbol); - } + // Filter private properties + for (const symbol of type.getApparentProperties()) { + if (typeChecker.isValidPropertyAccess((node.parent), symbol.getUnescapedName())) { + symbols.push(symbol); } } @@ -811,15 +813,13 @@ namespace ts.Completions { symbol = symbol.exportSymbol || symbol; // This is an alias, follow what it aliases - if (symbol && symbol.flags & SymbolFlags.Alias) { - symbol = typeChecker.getAliasedSymbol(symbol); - } + symbol = skipAlias(symbol, typeChecker); if (symbol.flags & SymbolFlags.Type) { return true; } - if (symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule)) { + 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 diff --git a/tests/cases/fourslash/completionsNamespaceMergedWithClass.ts b/tests/cases/fourslash/completionsNamespaceMergedWithClass.ts new file mode 100644 index 0000000000000..414a233bdb154 --- /dev/null +++ b/tests/cases/fourslash/completionsNamespaceMergedWithClass.ts @@ -0,0 +1,21 @@ +/// + +////class C { +//// static m() { } +////} +//// +////class D extends C {} +////namespace D { +//// export type T = number; +////} +//// +////let x: D./*type*/; +////D./*value*/ + +goTo.marker("type"); +verify.completionListContains("T"); +verify.not.completionListContains("m"); + +goTo.marker("value"); +verify.not.completionListContains("T"); +verify.completionListContains("m"); diff --git a/tests/cases/fourslash/completionsNamespaceMergedWithObject.ts b/tests/cases/fourslash/completionsNamespaceMergedWithObject.ts new file mode 100644 index 0000000000000..cd799981ee768 --- /dev/null +++ b/tests/cases/fourslash/completionsNamespaceMergedWithObject.ts @@ -0,0 +1,16 @@ +/// + +////namespace N { +//// export type T = number; +////} +////const N = { m() {} }; +////let x: N./*type*/; +////N./*value*/; + +goTo.marker("type"); +verify.completionListContains("T"); +verify.not.completionListContains("m"); + +goTo.marker("value"); +verify.not.completionListContains("T"); +verify.completionListContains("m");