diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 75be36474222f..15910957e4364 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1741,7 +1741,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!node) { return undefined; } - if (contextFlags! & ContextFlags.Completions) { + if (contextFlags! & ContextFlags.IgnoreNodeInferences) { return runWithInferenceBlockedFromSourceNode(node, () => getContextualType(node, contextFlags)); } return getContextualType(node, contextFlags); @@ -32827,7 +32827,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags: ContextFlags | undefined) { - if (isJsxOpeningElement(node) && contextFlags !== ContextFlags.Completions) { + if (isJsxOpeningElement(node) && contextFlags !== ContextFlags.IgnoreNodeInferences) { const index = findContextualNode(node.parent, /*includeCaches*/ !contextFlags); if (index >= 0) { // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1cfe3e04ba68d..c449135c59bd5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5524,11 +5524,11 @@ export const enum IntersectionFlags { // dprint-ignore /** @internal */ export const enum ContextFlags { - None = 0, - Signature = 1 << 0, // Obtaining contextual signature - NoConstraints = 1 << 1, // Don't obtain type variable constraints - Completions = 1 << 2, // Ignore inference to current node and parent nodes out to the containing call for completions - SkipBindingPatterns = 1 << 3, // Ignore contextual types applied by binding patterns + None = 0, + Signature = 1 << 0, // Obtaining contextual signature + NoConstraints = 1 << 1, // Don't obtain type variable constraints + IgnoreNodeInferences = 1 << 2, // Ignore inference to current node and parent nodes out to the containing call for, for example, completions + SkipBindingPatterns = 1 << 3, // Ignore contextual types applied by binding patterns } // NOTE: If modifying this enum, must modify `TypeFormatFlags` too! diff --git a/src/services/completions.ts b/src/services/completions.ts index dc01ea8ede4b9..9e18ab213c54d 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -3281,7 +3281,7 @@ function getContextualType(previousToken: Node, position: number, sourceFile: So isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) ? // completion at `x ===/**/` should be for the right side checker.getTypeAtLocation(parent.left) : - checker.getContextualType(previousToken as Expression, ContextFlags.Completions) || checker.getContextualType(previousToken as Expression); + checker.getContextualType(previousToken as Expression, ContextFlags.IgnoreNodeInferences) || checker.getContextualType(previousToken as Expression); } } @@ -3964,7 +3964,7 @@ function getCompletionData( // Cursor is inside a JSX self-closing element or opening element const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes); if (!attrsType) return GlobalsSearch.Continue; - const completionsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.Completions); + const completionsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.IgnoreNodeInferences); symbols = concatenate(symbols, filterJsxAttributes(getPropertiesForObjectExpression(attrsType, completionsType, jsxContainer.attributes, typeChecker), jsxContainer.attributes.properties)); setSortTextToOptionalMember(); completionKind = CompletionKind.MemberLike; @@ -4562,7 +4562,7 @@ function getCompletionData( } return GlobalsSearch.Continue; } - const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completions); + const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.IgnoreNodeInferences); const hasStringIndexType = (completionsType || instantiatedType).getStringIndexType(); const hasNumberIndextype = (completionsType || instantiatedType).getNumberIndexType(); isNewIdentifierLocation = !!hasStringIndexType || !!hasNumberIndextype; diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index b8b131ca3b56f..a1425f8ab0d35 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -6,6 +6,7 @@ import { CallLikeExpression, canHaveSymbol, concatenate, + ContextFlags, createTextSpan, createTextSpanFromBounds, createTextSpanFromNode, @@ -72,6 +73,8 @@ import { isNameOfFunctionDeclaration, isNewExpressionTarget, isObjectBindingPattern, + isObjectLiteralElementLike, + isObjectLiteralExpression, isPropertyName, isRightSideOfPropertyAccess, isStaticModifier, @@ -312,7 +315,20 @@ function getDefinitionFromObjectLiteralElement(typeChecker: TypeChecker, node: N if (element) { const contextualType = element && typeChecker.getContextualType(element.parent); if (contextualType) { - return flatMap(getPropertySymbolsFromContextualType(element, typeChecker, contextualType, /*unionSymbolOk*/ false), propertySymbol => getDefinitionFromSymbol(typeChecker, propertySymbol, node)); + let properties = getPropertySymbolsFromContextualType(element, typeChecker, contextualType, /*unionSymbolOk*/ false); + if (properties.length === 1) { + const declaration = properties[0].valueDeclaration; + const withoutNodeInferencesType = declaration && isObjectLiteralExpression(declaration.parent) && isObjectLiteralElementLike(declaration) && declaration.name === node ? + typeChecker.getContextualType(element.parent, ContextFlags.IgnoreNodeInferences) : + undefined; + if (withoutNodeInferencesType) { + const withoutNodeInferencesProperties = getPropertySymbolsFromContextualType(element, typeChecker, withoutNodeInferencesType, /*unionSymbolOk*/ false); + if (withoutNodeInferencesProperties.length) { + properties = withoutNodeInferencesProperties; + } + } + } + return flatMap(properties, propertySymbol => getDefinitionFromSymbol(typeChecker, propertySymbol, node)); } } return emptyArray; diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 6c5523ae1953c..fa24a8ae88618 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -545,7 +545,7 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL } } - function fromContextualType(contextFlags: ContextFlags = ContextFlags.Completions): StringLiteralCompletionsFromTypes | undefined { + function fromContextualType(contextFlags: ContextFlags = ContextFlags.IgnoreNodeInferences): StringLiteralCompletionsFromTypes | undefined { // Get completion for string literal from string literal type // i.e. var x: "hi" | "hello" = "/*completion position*/" const types = getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker, contextFlags)); @@ -603,7 +603,7 @@ function stringLiteralCompletionsForObjectLiteral(checker: TypeChecker, objectLi const contextualType = checker.getContextualType(objectLiteralExpression); if (!contextualType) return undefined; - const completionsType = checker.getContextualType(objectLiteralExpression, ContextFlags.Completions); + const completionsType = checker.getContextualType(objectLiteralExpression, ContextFlags.IgnoreNodeInferences); const symbols = getPropertiesForObjectExpression( contextualType, completionsType, diff --git a/tests/baselines/reference/goToDefinitionObjectLiteralProperties2.baseline.jsonc b/tests/baselines/reference/goToDefinitionObjectLiteralProperties2.baseline.jsonc new file mode 100644 index 0000000000000..b2e4ae4010978 --- /dev/null +++ b/tests/baselines/reference/goToDefinitionObjectLiteralProperties2.baseline.jsonc @@ -0,0 +1,154 @@ +// === goToDefinition === +// === /tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts === +// type C = { +// <|[|foo|]: string;|> +// bar: number; +// }; +// +// declare function fn(arg: T): T; +// +// fn({ +// foo/*GOTO DEF*/: "", +// bar: true, +// }); +// +// --- (line: 13) skipped --- + + // === Details === + [ + { + "kind": "property", + "name": "foo", + "containerName": "__type", + "isLocal": false, + "isAmbient": false, + "unverified": false + } + ] + + + +// === goToDefinition === +// === /tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts === +// type C = { +// foo: string; +// <|[|bar|]: number;|> +// }; +// +// declare function fn(arg: T): T; +// +// fn({ +// foo: "", +// bar/*GOTO DEF*/: true, +// }); +// +// const result = fn({ +// --- (line: 14) skipped --- + + // === Details === + [ + { + "kind": "property", + "name": "bar", + "containerName": "__type", + "isLocal": false, + "isAmbient": false, + "unverified": false + } + ] + + + +// === goToDefinition === +// === /tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts === +// type C = { +// <|[|foo|]: string;|> +// bar: number; +// }; +// +// --- (line: 6) skipped --- + +// --- (line: 10) skipped --- +// }); +// +// const result = fn({ +// foo/*GOTO DEF*/: "", +// bar: 1, +// }); +// +// // this one shouldn't go to the constraint type +// result.foo; + + // === Details === + [ + { + "kind": "property", + "name": "foo", + "containerName": "__type", + "isLocal": false, + "isAmbient": false, + "unverified": false + } + ] + + + +// === goToDefinition === +// === /tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts === +// type C = { +// foo: string; +// <|[|bar|]: number;|> +// }; +// +// declare function fn(arg: T): T; +// --- (line: 7) skipped --- + +// --- (line: 11) skipped --- +// +// const result = fn({ +// foo: "", +// bar/*GOTO DEF*/: 1, +// }); +// +// // this one shouldn't go to the constraint type +// result.foo; + + // === Details === + [ + { + "kind": "property", + "name": "bar", + "containerName": "__type", + "isLocal": false, + "isAmbient": false, + "unverified": false + } + ] + + + +// === goToDefinition === +// === /tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts === +// --- (line: 10) skipped --- +// }); +// +// const result = fn({ +// <|[|foo|]: ""|>, +// bar: 1, +// }); +// +// // this one shouldn't go to the constraint type +// result.foo/*GOTO DEF*/; + + // === Details === + [ + { + "kind": "property", + "name": "foo", + "containerName": "__object", + "isLocal": false, + "isAmbient": false, + "unverified": false, + "failedAliasResolution": false + } + ] \ No newline at end of file diff --git a/tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts b/tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts new file mode 100644 index 0000000000000..2bcdc71a0df54 --- /dev/null +++ b/tests/cases/fourslash/goToDefinitionObjectLiteralProperties2.ts @@ -0,0 +1,23 @@ +/// + +//// type C = { +//// foo: string; +//// bar: number; +//// }; +//// +//// declare function fn(arg: T): T; +//// +//// fn({ +//// foo/*1*/: "", +//// bar/*2*/: true, +//// }); +//// +//// const result = fn({ +//// foo/*3*/: "", +//// bar/*4*/: 1, +//// }); +//// +//// // this one shouldn't go to the constraint type +//// result.foo/*5*/; + +verify.baselineGoToDefinition("1", "2", "3", "4", "5");