diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 588b5010b141c..97465c7f4a00c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25043,12 +25043,11 @@ namespace ts { switch (kind) { case AssignmentDeclarationKind.None: return getTypeOfExpression(binaryExpression.left); + case AssignmentDeclarationKind.ThisProperty: + return getContextualTypeForThisPropertyAssignment(binaryExpression); case AssignmentDeclarationKind.Property: - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.Prototype: - case AssignmentDeclarationKind.PrototypeProperty: if (isPossiblyAliasedThisProperty(binaryExpression, kind)) { - return getContextualTypeForThisPropertyAssignment(binaryExpression, kind); + return getContextualTypeForThisPropertyAssignment(binaryExpression); } // If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration. // See `bindStaticPropertyAssignment` in `binder.ts`. @@ -25081,9 +25080,15 @@ namespace ts { } return isInJSFile(decl) ? undefined : getTypeOfExpression(binaryExpression.left); } + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.Prototype: + case AssignmentDeclarationKind.PrototypeProperty: + let valueDeclaration = binaryExpression.left.symbol?.valueDeclaration; + // falls through case AssignmentDeclarationKind.ModuleExports: - case AssignmentDeclarationKind.ThisProperty: - return getContextualTypeForThisPropertyAssignment(binaryExpression, kind); + valueDeclaration ||= binaryExpression.symbol?.valueDeclaration; + const annotated = valueDeclaration && getEffectiveTypeAnnotationNode(valueDeclaration); + return annotated ? getTypeFromTypeNode(annotated) : undefined; case AssignmentDeclarationKind.ObjectDefinePropertyValue: case AssignmentDeclarationKind.ObjectDefinePropertyExports: case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: @@ -25105,7 +25110,7 @@ namespace ts { return isThisInitializedDeclaration(symbol?.valueDeclaration); } - function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression, kind: AssignmentDeclarationKind): Type | undefined { + function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression): Type | undefined { if (!binaryExpression.symbol) return getTypeOfExpression(binaryExpression.left); if (binaryExpression.symbol.valueDeclaration) { const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration); @@ -25116,7 +25121,6 @@ namespace ts { } } } - if (kind === AssignmentDeclarationKind.ModuleExports) return undefined; const thisAccess = cast(binaryExpression.left, isAccessExpression); if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) { return undefined; @@ -38825,10 +38829,10 @@ namespace ts { switch (location.kind) { case SyntaxKind.SourceFile: - if (!isExternalOrCommonJsModule(location)) break; + if (!isExternalModule(location)) break; // falls through case SyntaxKind.ModuleDeclaration: - copySymbols(getSymbolOfNode(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); + copyLocallyVisibleExportSymbols(getSymbolOfNode(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); break; case SyntaxKind.EnumDeclaration: copySymbols(getSymbolOfNode(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember); @@ -38897,6 +38901,17 @@ namespace ts { }); } } + + function copyLocallyVisibleExportSymbols(source: SymbolTable, meaning: SymbolFlags): void { + if (meaning) { + source.forEach(symbol => { + // Similar condition as in `resolveNameHelper` + if (!getDeclarationOfKind(symbol, SyntaxKind.ExportSpecifier) && !getDeclarationOfKind(symbol, SyntaxKind.NamespaceExport)) { + copySymbol(symbol, meaning); + } + }); + } + } } function isTypeDeclarationName(name: Node): boolean { diff --git a/src/services/completions.ts b/src/services/completions.ts index 509558b35591a..098f002580856 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -2992,7 +2992,8 @@ namespace ts.Completions { if (type) { return type; } - if (isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken) { + if (isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken && node === node.parent.left) { + // Object literal is assignment pattern: ({ | } = x) return typeChecker.getTypeAtLocation(node.parent); } return undefined; diff --git a/tests/cases/fourslash/completionsExternalModuleRenamedExports.ts b/tests/cases/fourslash/completionsExternalModuleRenamedExports.ts new file mode 100644 index 0000000000000..0ef929ea91b86 --- /dev/null +++ b/tests/cases/fourslash/completionsExternalModuleRenamedExports.ts @@ -0,0 +1,17 @@ +/// + +// @Filename: other.ts +//// export {}; + +// @Filename: index.ts +//// const c = 0; +//// export { c as yeahThisIsTotallyInScopeHuh }; +//// export * as alsoNotInScope from "./other"; +//// +//// /**/ + +verify.completions({ + marker: "", + includes: "c", + excludes: ["yeahThisIsTotallyInScopeHuh", "alsoNotInScope"], +}); diff --git a/tests/cases/fourslash/completionsObjectLiteralModuleExports.ts b/tests/cases/fourslash/completionsObjectLiteralModuleExports.ts new file mode 100644 index 0000000000000..d340d274cf89a --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralModuleExports.ts @@ -0,0 +1,17 @@ +/// + +// @allowJs: true +// @checkJs: true + +// @Filename: index.js +//// const almanac = 0; +//// module.exports = { +//// a/**/ +//// }; + +verify.completions({ + marker: "", + isNewIdentifierLocation: true, + includes: "almanac", + excludes: "a", +}); diff --git a/tests/cases/fourslash/completionsSpecialAssignmentNotContextuallyTyped.ts b/tests/cases/fourslash/completionsSpecialAssignmentNotContextuallyTyped.ts new file mode 100644 index 0000000000000..388d60d716c25 --- /dev/null +++ b/tests/cases/fourslash/completionsSpecialAssignmentNotContextuallyTyped.ts @@ -0,0 +1,39 @@ +/// + +// @checkJs: true +// @allowJs: true + +// @Filename: index.js +//// module.exports = { +//// a/*1*/ +//// } +//// +//// exports.foo = { +//// a/*2*/ +//// } +//// +//// function F() { +//// this.blah = { +//// a/*3*/ +//// }; +//// } +//// +//// F.foo = { +//// a/*4*/ +//// } +//// +//// F.prototype = { +//// a/*5*/ +//// } +//// +//// F.prototype.x = { +//// a/*6*/ +//// } + +[1, 2, 3, 4, 5, 6].forEach(marker => { + verify.completions({ + marker: `${marker}`, + excludes: "a", + isNewIdentifierLocation: true, + }); +});