From 712ee1b1b9711fd3e85fdd80adc143043c1b3479 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Tue, 28 Jan 2025 17:45:14 +0800 Subject: [PATCH 1/2] support quick info and go to definition on mapped keys --- src/compiler/checker.ts | 1 + src/compiler/parser.ts | 6 +- src/compiler/types.ts | 5 +- src/compiler/utilities.ts | 4 +- src/services/goToDefinition.ts | 5 +- src/services/services.ts | 6 +- tests/baselines/reference/api/typescript.d.ts | 5 +- ..._filteringGenericMappedType.baseline.jsonc | 2 +- ...inition_filteringMappedType.baseline.jsonc | 2 +- .../goToDefinition_mappedType2.baseline.jsonc | 28 ++ .../reference/quickInfoMappedType3.baseline | 403 ++++++++++++++++++ .../fourslash/goToDefinition_mappedType2.ts | 21 + tests/cases/fourslash/quickInfoMappedType2.ts | 18 + tests/cases/fourslash/quickInfoMappedType3.ts | 46 ++ tests/cases/fourslash/quickInfoMappedType4.ts | 20 + 15 files changed, 562 insertions(+), 10 deletions(-) create mode 100644 tests/baselines/reference/goToDefinition_mappedType2.baseline.jsonc create mode 100644 tests/baselines/reference/quickInfoMappedType3.baseline create mode 100644 tests/cases/fourslash/goToDefinition_mappedType2.ts create mode 100644 tests/cases/fourslash/quickInfoMappedType2.ts create mode 100644 tests/cases/fourslash/quickInfoMappedType3.ts create mode 100644 tests/cases/fourslash/quickInfoMappedType4.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e925f7fed9505..52741265bd67d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14221,6 +14221,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const stripOptional = strictNullChecks && !isOptional && modifiersProp && modifiersProp.flags & SymbolFlags.Optional; const lateFlag: CheckFlags = modifiersProp ? getIsLateCheckFlag(modifiersProp) : 0; const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName, lateFlag | CheckFlags.Mapped | (isReadonly ? CheckFlags.Readonly : 0) | (stripOptional ? CheckFlags.StripOptional : 0)) as MappedSymbol; + prop.parent = mappedType.symbol; prop.links.mappedType = type; prop.links.nameType = propNameType; prop.links.keyType = keyType; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 8c69cccba1282..6549d28742955 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4408,6 +4408,7 @@ namespace Parser { function parseMappedType() { const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.OpenBraceToken); let readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined; if (token() === SyntaxKind.ReadonlyKeyword || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { @@ -4431,7 +4432,10 @@ namespace Parser { parseSemicolon(); const members = parseList(ParsingContext.TypeMembers, parseTypeMember); parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), pos); + return withJSDoc( + finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), pos), + hasJSDoc + ) ; } function parseTupleElementType() { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ff0285e980c16..3a44883689924 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1258,7 +1258,8 @@ export type HasJSDoc = | VariableDeclaration | VariableStatement | WhileStatement - | WithStatement; + | WithStatement + | MappedTypeNode; export type HasType = | SignatureDeclaration @@ -2339,7 +2340,7 @@ export interface IndexedAccessTypeNode extends TypeNode { readonly indexType: TypeNode; } -export interface MappedTypeNode extends TypeNode, Declaration, LocalsContainer { +export interface MappedTypeNode extends TypeNode, Declaration, LocalsContainer, JSDocContainer { readonly kind: SyntaxKind.MappedType; readonly readonlyToken?: ReadonlyKeyword | PlusToken | MinusToken; readonly typeParameter: TypeParameterDeclaration; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5755369134d8a..e2e59023e9f92 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2699,7 +2699,8 @@ export function getJSDocCommentRanges(node: Node, text: string): CommentRange[] node.kind === SyntaxKind.ArrowFunction || node.kind === SyntaxKind.ParenthesizedExpression || node.kind === SyntaxKind.VariableDeclaration || - node.kind === SyntaxKind.ExportSpecifier) ? + node.kind === SyntaxKind.ExportSpecifier || + node.kind === SyntaxKind.MappedType) ? concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) : getLeadingCommentRanges(text, node.pos); // True if the comment starts with '/**' but not if it is '/**/' @@ -4538,6 +4539,7 @@ export function canHaveJSDoc(node: Node): node is HasJSDoc { case SyntaxKind.VariableStatement: case SyntaxKind.WhileStatement: case SyntaxKind.WithStatement: + case SyntaxKind.MappedType: return true; default: return false; diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 48fdb95aa0cff..16e7d3436d756 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -5,6 +5,7 @@ import { AssignmentOperatorToken, CallLikeExpression, canHaveSymbol, + CheckFlags, concatenate, createTextSpan, createTextSpanFromBounds, @@ -74,6 +75,7 @@ import { isRightSideOfPropertyAccess, isStaticModifier, isSwitchStatement, + isTransientSymbol, isTypeAliasDeclaration, isTypeReferenceNode, isVariableDeclaration, @@ -595,7 +597,8 @@ function isExpandoDeclaration(node: Declaration): boolean { } function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, failedAliasResolution?: boolean, declarationFilter?: (d: Declaration) => boolean): DefinitionInfo[] | undefined { - const filteredDeclarations = declarationFilter !== undefined ? filter(symbol.declarations, declarationFilter) : symbol.declarations; + const declarations = isTransientSymbol(symbol) && symbol.links.checkFlags & CheckFlags.Mapped && !symbol.declarations?.length ? symbol.links.syntheticOrigin?.declarations : symbol.declarations; + const filteredDeclarations = declarationFilter !== undefined ? filter(declarations, declarationFilter) : declarations; // If we have a declaration filter, we are looking for specific declaration(s), so we should not return prematurely. const signatureDefinition = !declarationFilter && (getConstructSignatureDefinition() || getCallSignatureDefinition()); if (signatureDefinition) { diff --git a/src/services/services.ts b/src/services/services.ts index fb5e9a0857929..bb648c1cf2a5e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -346,6 +346,7 @@ import { updateSourceFile, UserPreferences, VariableDeclaration, + CheckFlags, } from "./_namespaces/ts.js"; import * as NavigateTo from "./_namespaces/ts.NavigateTo.js"; import * as NavigationBar from "./_namespaces/ts.NavigationBar.js"; @@ -721,7 +722,10 @@ class SymbolObject implements Symbol { if (!this.documentationComment) { this.documentationComment = emptyArray; // Set temporarily to avoid an infinite loop finding inherited docs - if (!this.declarations && isTransientSymbol(this) && this.links.target && isTransientSymbol(this.links.target) && this.links.target.links.tupleLabelDeclaration) { + if (!this.declarations && isTransientSymbol(this) && this.links.checkFlags & CheckFlags.Mapped && this.parent?.declarations && some(this.parent?.declarations, decl => hasJSDocInheritDocTag(decl))) { + this.documentationComment = getDocumentationComment(this.parent?.declarations.concat(this.links.syntheticOrigin?.declarations || emptyArray), checker); + } + else if (!this.declarations && isTransientSymbol(this) && this.links.target && isTransientSymbol(this.links.target) && this.links.target.links.tupleLabelDeclaration) { const labelDecl = this.links.target.links.tupleLabelDeclaration; this.documentationComment = getDocumentationComment([labelDecl], checker); } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 66810d2f55d6a..e9119e0f3e7c4 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4387,7 +4387,8 @@ declare namespace ts { | VariableDeclaration | VariableStatement | WhileStatement - | WithStatement; + | WithStatement + | MappedTypeNode; type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType; type HasTypeArguments = CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement | JsxSelfClosingElement; type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute; @@ -4806,7 +4807,7 @@ declare namespace ts { readonly objectType: TypeNode; readonly indexType: TypeNode; } - interface MappedTypeNode extends TypeNode, Declaration, LocalsContainer { + interface MappedTypeNode extends TypeNode, Declaration, LocalsContainer, JSDocContainer { readonly kind: SyntaxKind.MappedType; readonly readonlyToken?: ReadonlyKeyword | PlusToken | MinusToken; readonly typeParameter: TypeParameterDeclaration; diff --git a/tests/baselines/reference/goToDefinition_filteringGenericMappedType.baseline.jsonc b/tests/baselines/reference/goToDefinition_filteringGenericMappedType.baseline.jsonc index 6e54ed057ed67..4f2e77f43c11f 100644 --- a/tests/baselines/reference/goToDefinition_filteringGenericMappedType.baseline.jsonc +++ b/tests/baselines/reference/goToDefinition_filteringGenericMappedType.baseline.jsonc @@ -20,7 +20,7 @@ { "kind": "property", "name": "id", - "containerName": "", + "containerName": "__type", "isLocal": false, "isAmbient": false, "unverified": false, diff --git a/tests/baselines/reference/goToDefinition_filteringMappedType.baseline.jsonc b/tests/baselines/reference/goToDefinition_filteringMappedType.baseline.jsonc index 38d3864efd135..f7a8e245dcc60 100644 --- a/tests/baselines/reference/goToDefinition_filteringMappedType.baseline.jsonc +++ b/tests/baselines/reference/goToDefinition_filteringMappedType.baseline.jsonc @@ -9,7 +9,7 @@ { "kind": "property", "name": "a", - "containerName": "", + "containerName": "filtered", "isLocal": false, "isAmbient": false, "unverified": false, diff --git a/tests/baselines/reference/goToDefinition_mappedType2.baseline.jsonc b/tests/baselines/reference/goToDefinition_mappedType2.baseline.jsonc new file mode 100644 index 0000000000000..6b7a3365d58e7 --- /dev/null +++ b/tests/baselines/reference/goToDefinition_mappedType2.baseline.jsonc @@ -0,0 +1,28 @@ +// === goToDefinition === +// === /tests/cases/fourslash/goToDefinition_mappedType2.ts === +// interface Foo { +// <|[|property|]: string|> +// } +// +// type JustMapIt = {[P in keyof T]: 0} +// --- (line: 6) skipped --- + +// --- (line: 11) skipped --- +// +// { +// let gotoDef!: MapItWithRemap +// gotoDef./*GOTO DEF*/mapped_property +// } + + // === Details === + [ + { + "kind": "property", + "name": "mapped_property", + "containerName": "__type", + "isLocal": false, + "isAmbient": false, + "unverified": false, + "failedAliasResolution": false + } + ] \ No newline at end of file diff --git a/tests/baselines/reference/quickInfoMappedType3.baseline b/tests/baselines/reference/quickInfoMappedType3.baseline new file mode 100644 index 0000000000000..54de481a1b393 --- /dev/null +++ b/tests/baselines/reference/quickInfoMappedType3.baseline @@ -0,0 +1,403 @@ +// === QuickInfo === +=== /tests/cases/fourslash/quickInfoMappedType3.ts === +// type Getters = /** @inheritDoc desc on Getters */ { +// [Property in keyof Type as `get${Capitalize< +// string & Property +// >}`]: () => Type[Property]; +// }; +// +// interface Person { +// // ✅ When hovering here, the documentation is displayed, as it should. +// /** +// * Person's name. +// * @example "John Doe" +// */ +// name: string; +// +// // ✅ When hovering here, the documentation is displayed, as it should. +// /** +// * Person's Age. +// * @example 30 +// */ +// age: number; +// +// // ✅ When hovering here, the documentation is displayed, as it should. +// /** +// * Person's Location. +// * @example "Brazil" +// */ +// location: string; +// } +// +// type LazyPerson = Getters; +// +// const me: LazyPerson = { +// // ❌ When hovering here, the documentation is NOT displayed. +// getName: () => "Jake Carter", +// ^^^^^^^ +// | ---------------------------------------------------------------------- +// | (property) getName: () => string +// | desc on Getters +// | Person's name. +// | ---------------------------------------------------------------------- +// // ❌ When hovering here, the documentation is NOT displayed. +// getAge: () => 35, +// ^^^^^^ +// | ---------------------------------------------------------------------- +// | (property) getAge: () => number +// | desc on Getters +// | Person's Age. +// | ---------------------------------------------------------------------- +// // ❌ When hovering here, the documentation is NOT displayed. +// getLocation: () => "United States", +// ^^^^^^^^^^^ +// | ---------------------------------------------------------------------- +// | (property) getLocation: () => string +// | desc on Getters +// | Person's Location. +// | ---------------------------------------------------------------------- +// }; +// +// // ❌ When hovering here, the documentation is NOT displayed. +// me.getName(); +// ^^^^^^^ +// | ---------------------------------------------------------------------- +// | (property) getName: () => string +// | desc on Getters +// | Person's name. +// | ---------------------------------------------------------------------- + +[ + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoMappedType3.ts", + "position": 747, + "name": "1" + }, + "item": { + "kind": "property", + "kindModifiers": "", + "textSpan": { + "start": 747, + "length": 7 + }, + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "property", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "getName", + "kind": "propertyName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "=>", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "string", + "kind": "keyword" + } + ], + "documentation": [ + { + "text": "desc on Getters", + "kind": "text" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": "Person's name.", + "kind": "text" + } + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoMappedType3.ts", + "position": 842, + "name": "2" + }, + "item": { + "kind": "property", + "kindModifiers": "", + "textSpan": { + "start": 842, + "length": 6 + }, + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "property", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "getAge", + "kind": "propertyName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "=>", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "documentation": [ + { + "text": "desc on Getters", + "kind": "text" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": "Person's Age.", + "kind": "text" + } + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoMappedType3.ts", + "position": 925, + "name": "3" + }, + "item": { + "kind": "property", + "kindModifiers": "", + "textSpan": { + "start": 925, + "length": 11 + }, + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "property", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "getLocation", + "kind": "propertyName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "=>", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "string", + "kind": "keyword" + } + ], + "documentation": [ + { + "text": "desc on Getters", + "kind": "text" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": "Person's Location.", + "kind": "text" + } + ] + } + }, + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoMappedType3.ts", + "position": 1029, + "name": "4" + }, + "item": { + "kind": "property", + "kindModifiers": "", + "textSpan": { + "start": 1029, + "length": 7 + }, + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "property", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "getName", + "kind": "propertyName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "=>", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "string", + "kind": "keyword" + } + ], + "documentation": [ + { + "text": "desc on Getters", + "kind": "text" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": "Person's name.", + "kind": "text" + } + ] + } + } +] \ No newline at end of file diff --git a/tests/cases/fourslash/goToDefinition_mappedType2.ts b/tests/cases/fourslash/goToDefinition_mappedType2.ts new file mode 100644 index 0000000000000..a83daff4d32ef --- /dev/null +++ b/tests/cases/fourslash/goToDefinition_mappedType2.ts @@ -0,0 +1,21 @@ +/// + +////interface Foo { +//// /*def*/property: string +////} +//// +////type JustMapIt = {[P in keyof T]: 0} +////type MapItWithRemap = {[P in keyof T as P extends string ? `mapped_${P}` : never]: 0} +//// +////{ +//// let gotoDef!: JustMapIt +//// gotoDef.property +////} +//// +////{ +//// let gotoDef!: MapItWithRemap +//// gotoDef.[|/*ref*/mapped_property|] +////} + + +verify.baselineGoToDefinition("ref"); diff --git a/tests/cases/fourslash/quickInfoMappedType2.ts b/tests/cases/fourslash/quickInfoMappedType2.ts new file mode 100644 index 0000000000000..55ff73e92fa93 --- /dev/null +++ b/tests/cases/fourslash/quickInfoMappedType2.ts @@ -0,0 +1,18 @@ +/// + +////type ToGet = T extends string ? `get${Capitalize}` : never; +////type Getters = /** @inheritDoc desc on Getters */ { +//// [P in keyof T as ToGet

]: () => T[P] +////}; +//// +////type Y = { +//// /** hello */ +//// d: string; +////} +//// +////type T50 = Getters; // { getFoo: () => string, getBar: () => number } +//// +////declare let y: T50; +////y.get/*3*/D; + +verify.quickInfoAt("3", "(property) getD: () => string", "desc on Getters\nhello"); diff --git a/tests/cases/fourslash/quickInfoMappedType3.ts b/tests/cases/fourslash/quickInfoMappedType3.ts new file mode 100644 index 0000000000000..afea5ec4015e6 --- /dev/null +++ b/tests/cases/fourslash/quickInfoMappedType3.ts @@ -0,0 +1,46 @@ +/// + +////type Getters = /** @inheritDoc desc on Getters */ { +//// [Property in keyof Type as `get${Capitalize< +//// string & Property +//// >}`]: () => Type[Property]; +////}; +//// +////interface Person { +//// // ✅ When hovering here, the documentation is displayed, as it should. +//// /** +//// * Person's name. +//// * @example "John Doe" +//// */ +//// name: string; +//// +//// // ✅ When hovering here, the documentation is displayed, as it should. +//// /** +//// * Person's Age. +//// * @example 30 +//// */ +//// age: number; +//// +//// // ✅ When hovering here, the documentation is displayed, as it should. +//// /** +//// * Person's Location. +//// * @example "Brazil" +//// */ +//// location: string; +////} +//// +////type LazyPerson = Getters; +//// +////const me: LazyPerson = { +//// // ❌ When hovering here, the documentation is NOT displayed. +//// /*1*/getName: () => "Jake Carter", +//// // ❌ When hovering here, the documentation is NOT displayed. +//// /*2*/getAge: () => 35, +//// // ❌ When hovering here, the documentation is NOT displayed. +//// /*3*/getLocation: () => "United States", +////}; +//// +////// ❌ When hovering here, the documentation is NOT displayed. +////me./*4*/getName(); + +verify.baselineQuickInfo(); diff --git a/tests/cases/fourslash/quickInfoMappedType4.ts b/tests/cases/fourslash/quickInfoMappedType4.ts new file mode 100644 index 0000000000000..fac1a67e633e6 --- /dev/null +++ b/tests/cases/fourslash/quickInfoMappedType4.ts @@ -0,0 +1,20 @@ +/// + +////type ToGet = T extends string ? `get${Capitalize}` : never; +////type Getters = { +//// /** @inheritDoc desc on Getters */ +//// [P in keyof T as ToGet

]: () => T[P] +//// +//// }; +//// +////type Y = { +//// /** hello */ +//// d: string; +////} +//// +////type T50 = Getters; // { getFoo: () => string, getBar: () => number } +//// +////declare let y: T50; +////y.get/*3*/D; + +verify.quickInfoAt("3", "(property) getD: () => string", undefined); From a2bcfaa6ce055c95eca455a1a4eef40b2903e611 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Sat, 1 Feb 2025 11:06:56 +0800 Subject: [PATCH 2/2] format and add tests --- src/compiler/parser.ts | 4 +-- src/services/services.ts | 2 +- .../goToDefinition_mappedType3.baseline.jsonc | 27 +++++++++++++++++ .../fourslash/goToDefinition_mappedType3.ts | 30 +++++++++++++++++++ 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/goToDefinition_mappedType3.baseline.jsonc create mode 100644 tests/cases/fourslash/goToDefinition_mappedType3.ts diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 6549d28742955..bbc0dcc40dbcc 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4434,8 +4434,8 @@ namespace Parser { parseExpected(SyntaxKind.CloseBraceToken); return withJSDoc( finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), pos), - hasJSDoc - ) ; + hasJSDoc, + ); } function parseTupleElementType() { diff --git a/src/services/services.ts b/src/services/services.ts index bb648c1cf2a5e..2035ebcf66c82 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -15,6 +15,7 @@ import { canIncludeBindAndCheckDiagnostics, changeCompilerHostLikeToUseCache, CharacterCodes, + CheckFlags, CheckJsDirective, Classifications, ClassifiedSpan, @@ -346,7 +347,6 @@ import { updateSourceFile, UserPreferences, VariableDeclaration, - CheckFlags, } from "./_namespaces/ts.js"; import * as NavigateTo from "./_namespaces/ts.NavigateTo.js"; import * as NavigationBar from "./_namespaces/ts.NavigationBar.js"; diff --git a/tests/baselines/reference/goToDefinition_mappedType3.baseline.jsonc b/tests/baselines/reference/goToDefinition_mappedType3.baseline.jsonc new file mode 100644 index 0000000000000..5357eee1c47e7 --- /dev/null +++ b/tests/baselines/reference/goToDefinition_mappedType3.baseline.jsonc @@ -0,0 +1,27 @@ +// === goToDefinition === +// === /tests/cases/fourslash/goToDefinition_mappedType3.ts === +// interface Source { +// <|[|alpha|]: number;|> +// beta: string; +// } +// +// --- (line: 6) skipped --- + +// --- (line: 22) skipped --- +// }; +// +// // ❌ In VSCode, "Go to Definition" on `alphaSuffix` does not navigate to `alpha` in `Source` +// obj./*GOTO DEF*/alphaSuffix(); + + // === Details === + [ + { + "kind": "property", + "name": "alphaSuffix", + "containerName": "__type", + "isLocal": false, + "isAmbient": false, + "unverified": false, + "failedAliasResolution": false + } + ] \ No newline at end of file diff --git a/tests/cases/fourslash/goToDefinition_mappedType3.ts b/tests/cases/fourslash/goToDefinition_mappedType3.ts new file mode 100644 index 0000000000000..5b8c70797c74f --- /dev/null +++ b/tests/cases/fourslash/goToDefinition_mappedType3.ts @@ -0,0 +1,30 @@ +/// + +////interface Source { +//// /*def*/alpha: number; +//// beta: string; +////} +//// +////// Transforming interface field names with a suffix +////type Transformed = { +//// [K in keyof T as `${K & string}Suffix`]: () => T[K]; +////}; +//// +////type Result = Transformed; +/////* +//// Expected: +//// { +//// alphaSuffix: () => number; +//// betaSuffix: () => string; +//// } +//// */ +//// +////const obj: Result = { +//// alphaSuffix: () => 42, +//// betaSuffix: () => "hello", +////}; +//// +////// ❌ In VSCode, "Go to Definition" on `alphaSuffix` does not navigate to `alpha` in `Source` +////obj.[|/*ref*/alphaSuffix|](); + +verify.baselineGoToDefinition("ref");