From 8f431c7c2874443d1b340754e567110bf0053cb3 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 20 Apr 2021 16:34:04 -0700 Subject: [PATCH 1/5] Fix completions of exports elsewhere in same file --- src/compiler/checker.ts | 17 ++++++++++++++--- src/services/completions.ts | 3 ++- .../completionsExternalModuleRenamedExports.ts | 17 +++++++++++++++++ .../completionsObjectLiteralModuleExports.ts | 17 +++++++++++++++++ 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 tests/cases/fourslash/completionsExternalModuleRenamedExports.ts create mode 100644 tests/cases/fourslash/completionsObjectLiteralModuleExports.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 588b5010b141c..84aeadb8a30f6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25106,6 +25106,7 @@ namespace ts { } function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression, kind: AssignmentDeclarationKind): Type | undefined { + if (kind === AssignmentDeclarationKind.ModuleExports) return undefined; if (!binaryExpression.symbol) return getTypeOfExpression(binaryExpression.left); if (binaryExpression.symbol.valueDeclaration) { const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration); @@ -25116,7 +25117,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 +38825,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); + copyExportedSymbols(getSymbolOfNode(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); break; case SyntaxKind.EnumDeclaration: copySymbols(getSymbolOfNode(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember); @@ -38897,6 +38897,17 @@ namespace ts { }); } } + + function copyExportedSymbols(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", +}); From 9c7d8a2875f96765be671eb1041551e68c05cbd8 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 20 Apr 2021 16:59:37 -0700 Subject: [PATCH 2/5] Undo messing up JSDoc-annotated module.exports assignments --- src/compiler/checker.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 84aeadb8a30f6..3c0333d52ddbb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25106,9 +25106,8 @@ namespace ts { } function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression, kind: AssignmentDeclarationKind): Type | undefined { - if (kind === AssignmentDeclarationKind.ModuleExports) return undefined; - if (!binaryExpression.symbol) return getTypeOfExpression(binaryExpression.left); - if (binaryExpression.symbol.valueDeclaration) { + if (!binaryExpression.symbol && kind !== AssignmentDeclarationKind.ModuleExports) return getTypeOfExpression(binaryExpression.left); + if (binaryExpression.symbol?.valueDeclaration) { const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration); if (annotated) { const type = getTypeFromTypeNode(annotated); @@ -25117,6 +25116,7 @@ namespace ts { } } } + if (kind === AssignmentDeclarationKind.ModuleExports) return undefined; const thisAccess = cast(binaryExpression.left, isAccessExpression); if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) { return undefined; From 5a0ad2e1a6bdbc4d916651cd38c674087b01b3c7 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 22 Apr 2021 10:59:31 -0700 Subject: [PATCH 3/5] Add other failing contextual type test --- ...nsSpecialAssignmentNotContextuallyTyped.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/cases/fourslash/completionsSpecialAssignmentNotContextuallyTyped.ts diff --git a/tests/cases/fourslash/completionsSpecialAssignmentNotContextuallyTyped.ts b/tests/cases/fourslash/completionsSpecialAssignmentNotContextuallyTyped.ts new file mode 100644 index 0000000000000..7cc36c109d530 --- /dev/null +++ b/tests/cases/fourslash/completionsSpecialAssignmentNotContextuallyTyped.ts @@ -0,0 +1,38 @@ +/// + +// @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" + }); +}); From 0eca724b66fb0cba785f2b608a3e392672e4f4ed Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 23 Apr 2021 13:18:45 -0700 Subject: [PATCH 4/5] Rearrange contextual type logic for special assignments --- src/compiler/checker.ts | 24 +++++++++++-------- ...nsSpecialAssignmentNotContextuallyTyped.ts | 3 ++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3c0333d52ddbb..b01e9c37585b8 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,9 +25110,9 @@ namespace ts { return isThisInitializedDeclaration(symbol?.valueDeclaration); } - function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression, kind: AssignmentDeclarationKind): Type | undefined { - if (!binaryExpression.symbol && kind !== AssignmentDeclarationKind.ModuleExports) return getTypeOfExpression(binaryExpression.left); - if (binaryExpression.symbol?.valueDeclaration) { + function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression): Type | undefined { + if (!binaryExpression.symbol) return getTypeOfExpression(binaryExpression.left); + if (binaryExpression.symbol.valueDeclaration) { const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration); if (annotated) { const type = getTypeFromTypeNode(annotated); @@ -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; diff --git a/tests/cases/fourslash/completionsSpecialAssignmentNotContextuallyTyped.ts b/tests/cases/fourslash/completionsSpecialAssignmentNotContextuallyTyped.ts index 7cc36c109d530..388d60d716c25 100644 --- a/tests/cases/fourslash/completionsSpecialAssignmentNotContextuallyTyped.ts +++ b/tests/cases/fourslash/completionsSpecialAssignmentNotContextuallyTyped.ts @@ -33,6 +33,7 @@ [1, 2, 3, 4, 5, 6].forEach(marker => { verify.completions({ marker: `${marker}`, - excludes: "a" + excludes: "a", + isNewIdentifierLocation: true, }); }); From 61521dbdc10d0bc8ff6404641e39a7e292ce0817 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 23 Apr 2021 13:22:40 -0700 Subject: [PATCH 5/5] Rename helper function --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b01e9c37585b8..97465c7f4a00c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -38832,7 +38832,7 @@ namespace ts { if (!isExternalModule(location)) break; // falls through case SyntaxKind.ModuleDeclaration: - copyExportedSymbols(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); @@ -38902,7 +38902,7 @@ namespace ts { } } - function copyExportedSymbols(source: SymbolTable, meaning: SymbolFlags): void { + function copyLocallyVisibleExportSymbols(source: SymbolTable, meaning: SymbolFlags): void { if (meaning) { source.forEach(symbol => { // Similar condition as in `resolveNameHelper`