From 89ca9250a87194daf7abba1b65af1e01ddc09f42 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 22 Oct 2025 14:44:51 -0700 Subject: [PATCH 1/5] Create clickable links in quick info from @link JSDoc tags --- internal/ls/completions.go | 6 +- internal/ls/hover.go | 112 ++++++++++++++++++------------ internal/ls/string_completions.go | 6 +- 3 files changed, 72 insertions(+), 52 deletions(-) diff --git a/internal/ls/completions.go b/internal/ls/completions.go index 65db54b937..c57ac969d5 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -5103,7 +5103,7 @@ func (l *LanguageService) getCompletionItemDetails( case symbolCompletion.symbol != nil: symbolDetails := symbolCompletion.symbol actions := l.getCompletionItemActions(ctx, checker, file, position, itemData, symbolDetails, preferences) - return createCompletionDetailsForSymbol( + return l.createCompletionDetailsForSymbol( item, symbolDetails.symbol, checker, @@ -5299,7 +5299,7 @@ type codeAction struct { changes []*lsproto.TextEdit } -func createCompletionDetailsForSymbol( +func (l *LanguageService) createCompletionDetailsForSymbol( item *lsproto.CompletionItem, symbol *ast.Symbol, checker *checker.Checker, @@ -5312,7 +5312,7 @@ func createCompletionDetailsForSymbol( details = append(details, action.description) edits = append(edits, action.changes...) } - quickInfo, documentation := getQuickInfoAndDocumentationForSymbol(checker, symbol, location) + quickInfo, documentation := l.getQuickInfoAndDocumentationForSymbol(checker, symbol, location) details = append(details, quickInfo) if len(edits) != 0 { item.AdditionalTextEdits = &edits diff --git a/internal/ls/hover.go b/internal/ls/hover.go index a608fdf024..f975799c2d 100644 --- a/internal/ls/hover.go +++ b/internal/ls/hover.go @@ -11,6 +11,7 @@ import ( "github.com/microsoft/typescript-go/internal/checker" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/scanner" ) const ( @@ -27,7 +28,7 @@ func (l *LanguageService) ProvideHover(ctx context.Context, documentURI lsproto. } c, done := program.GetTypeCheckerForFile(ctx, file) defer done() - quickInfo, documentation := getQuickInfoAndDocumentation(c, node) + quickInfo, documentation := l.getQuickInfoAndDocumentation(c, node) if quickInfo == "" { return lsproto.HoverOrNull{}, nil } @@ -43,11 +44,11 @@ func (l *LanguageService) ProvideHover(ctx context.Context, documentURI lsproto. }, nil } -func getQuickInfoAndDocumentation(c *checker.Checker, node *ast.Node) (string, string) { - return getQuickInfoAndDocumentationForSymbol(c, c.GetSymbolAtLocation(node), getNodeForQuickInfo(node)) +func (l *LanguageService) getQuickInfoAndDocumentation(c *checker.Checker, node *ast.Node) (string, string) { + return l.getQuickInfoAndDocumentationForSymbol(c, c.GetSymbolAtLocation(node), getNodeForQuickInfo(node)) } -func getQuickInfoAndDocumentationForSymbol(c *checker.Checker, symbol *ast.Symbol, node *ast.Node) (string, string) { +func (l *LanguageService) getQuickInfoAndDocumentationForSymbol(c *checker.Checker, symbol *ast.Symbol, node *ast.Node) (string, string) { quickInfo, declaration := getQuickInfoAndDeclarationAtLocation(c, symbol, node) if quickInfo == "" { return "", "" @@ -55,7 +56,7 @@ func getQuickInfoAndDocumentationForSymbol(c *checker.Checker, symbol *ast.Symbo var b strings.Builder if declaration != nil { if jsdoc := getJSDocOrTag(declaration); jsdoc != nil && !containsTypedefTag(jsdoc) { - writeComments(&b, jsdoc.Comments()) + l.writeComments(&b, c, jsdoc.Comments()) if jsdoc.Kind == ast.KindJSDoc { if tags := jsdoc.AsJSDoc().Tags; tags != nil { for _, tag := range tags.Nodes { @@ -90,7 +91,7 @@ func getQuickInfoAndDocumentationForSymbol(c *checker.Checker, symbol *ast.Symbo b.WriteString("— ") } } - writeComments(&b, comments) + l.writeComments(&b, c, comments) } } } @@ -411,61 +412,80 @@ func writeCode(b *strings.Builder, lang string, code string) { b.WriteByte('\n') } -func writeComments(b *strings.Builder, comments []*ast.Node) { +func (l *LanguageService) writeComments(b *strings.Builder, c *checker.Checker, comments []*ast.Node) { for _, comment := range comments { switch comment.Kind { case ast.KindJSDocText: b.WriteString(comment.Text()) - case ast.KindJSDocLink: - name := comment.Name() - text := comment.AsJSDocLink().Text() - if name != nil { - if text == "" { - writeEntityName(b, name) - } else { - writeEntityNameParts(b, name) - } - } - b.WriteString(text) + case ast.KindJSDocLink, ast.KindJSDocLinkPlain: + l.writeJSDocLink(b, c, comment, false /*quote*/) case ast.KindJSDocLinkCode: - // !!! TODO: This is a temporary placeholder implementation that needs to be updated later - name := comment.Name() - text := comment.AsJSDocLinkCode().Text() - if name != nil { - if text == "" { - writeEntityName(b, name) - } else { - writeEntityNameParts(b, name) - } - } - b.WriteString(text) - case ast.KindJSDocLinkPlain: - // !!! TODO: This is a temporary placeholder implementation that needs to be updated later - name := comment.Name() - text := comment.AsJSDocLinkPlain().Text() - if name != nil { - if text == "" { - writeEntityName(b, name) - } else { - writeEntityNameParts(b, name) - } - } - b.WriteString(text) + l.writeJSDocLink(b, c, comment, true /*quote*/) + } + } +} + +func (l *LanguageService) writeJSDocLink(b *strings.Builder, c *checker.Checker, link *ast.Node, quote bool) { + name := link.Name() + text := link.Text() + if name == nil { + writeQuotedString(b, text, quote) + return + } + if ast.IsIdentifier(name) && (name.Text() == "http" || name.Text() == "https") && strings.HasPrefix(text, "://") { + commentPos := strings.Index(text, " ") + if commentPos >= 0 { + writeMarkdownLink(b, text[commentPos+1:], name.Text()+text[:commentPos], quote) + } else { + linkText := name.Text() + text + writeMarkdownLink(b, linkText, linkText, quote) + } + return + } + declarations := getDeclarationsFromLocation(c, name) + if len(declarations) != 0 { + declaration := declarations[0] + file := ast.GetSourceFileOfNode(declaration) + pos := l.converters.PositionToLineAndCharacter(file, core.TextPos(scanner.GetTokenPosOfNode(declaration, file, false /*includeJSDoc*/))) + linkText := strings.Trim(text, " ") + if linkText == "" { + linkText = getEntityNameString(name) } + writeMarkdownLink(b, linkText, fmt.Sprintf("%v#%v,%v", string(FileNameToDocumentURI(file.FileName())), pos.Line+1, pos.Character+1), quote) + return } + writeQuotedString(b, getEntityNameString(name)+" "+text, quote) +} + +func writeMarkdownLink(b *strings.Builder, text string, uri string, quote bool) { + b.WriteString("[") + writeQuotedString(b, text, quote) + b.WriteString("](") + b.WriteString(uri) + b.WriteString(")") } func writeOptionalEntityName(b *strings.Builder, name *ast.Node) { if name != nil { b.WriteString(" ") - writeEntityName(b, name) + writeQuotedString(b, getEntityNameString(name), true /*quote*/) + } +} + +func writeQuotedString(b *strings.Builder, str string, quote bool) { + if quote && !strings.Contains(str, "`") { + b.WriteString("`") + b.WriteString(str) + b.WriteString("`") + } else { + b.WriteString(str) } } -func writeEntityName(b *strings.Builder, name *ast.Node) { - b.WriteString("`") - writeEntityNameParts(b, name) - b.WriteString("`") +func getEntityNameString(name *ast.Node) string { + var b strings.Builder + writeEntityNameParts(&b, name) + return b.String() } func writeEntityNameParts(b *strings.Builder, node *ast.Node) { diff --git a/internal/ls/string_completions.go b/internal/ls/string_completions.go index a1a3e6d7f8..dbd0ac3dcc 100644 --- a/internal/ls/string_completions.go +++ b/internal/ls/string_completions.go @@ -693,10 +693,10 @@ func (l *LanguageService) getStringLiteralCompletionDetails( if completions == nil { return item } - return stringLiteralCompletionDetails(item, name, contextToken, completions, file, checker) + return l.stringLiteralCompletionDetails(item, name, contextToken, completions, file, checker) } -func stringLiteralCompletionDetails( +func (l *LanguageService) stringLiteralCompletionDetails( item *lsproto.CompletionItem, name string, location *ast.Node, @@ -716,7 +716,7 @@ func stringLiteralCompletionDetails( properties := completion.fromProperties for _, symbol := range properties.symbols { if symbol.Name == name { - return createCompletionDetailsForSymbol(item, symbol, checker, location, nil /*actions*/) + return l.createCompletionDetailsForSymbol(item, symbol, checker, location, nil /*actions*/) } } case completion.fromTypes != nil: From e96bd76d73a6d18d8dd6cf106824059c6e74a62a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 23 Oct 2025 07:46:14 -0700 Subject: [PATCH 2/5] Create links with ranges + fixes and adjustments --- internal/ls/hover.go | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/internal/ls/hover.go b/internal/ls/hover.go index f975799c2d..9a28f3e307 100644 --- a/internal/ls/hover.go +++ b/internal/ls/hover.go @@ -11,7 +11,6 @@ import ( "github.com/microsoft/typescript-go/internal/checker" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/lsp/lsproto" - "github.com/microsoft/typescript-go/internal/scanner" ) const ( @@ -427,36 +426,46 @@ func (l *LanguageService) writeComments(b *strings.Builder, c *checker.Checker, func (l *LanguageService) writeJSDocLink(b *strings.Builder, c *checker.Checker, link *ast.Node, quote bool) { name := link.Name() - text := link.Text() + text := strings.Trim(link.Text(), " ") if name == nil { writeQuotedString(b, text, quote) return } if ast.IsIdentifier(name) && (name.Text() == "http" || name.Text() == "https") && strings.HasPrefix(text, "://") { - commentPos := strings.Index(text, " ") - if commentPos >= 0 { - writeMarkdownLink(b, text[commentPos+1:], name.Text()+text[:commentPos], quote) - } else { - linkText := name.Text() + text - writeMarkdownLink(b, linkText, linkText, quote) + linkText := name.Text() + text + linkUri := linkText + if commentPos := strings.IndexFunc(linkText, func(ch rune) bool { return ch == ' ' || ch == '|' }); commentPos >= 0 { + linkUri = linkText[:commentPos] + linkText = trimCommentPrefix(linkText[commentPos:]) + if linkText == "" { + linkText = linkUri + } } + writeMarkdownLink(b, linkText, linkUri, quote) return } declarations := getDeclarationsFromLocation(c, name) if len(declarations) != 0 { declaration := declarations[0] file := ast.GetSourceFileOfNode(declaration) - pos := l.converters.PositionToLineAndCharacter(file, core.TextPos(scanner.GetTokenPosOfNode(declaration, file, false /*includeJSDoc*/))) - linkText := strings.Trim(text, " ") + node := core.OrElse(ast.GetNameOfDeclaration(declaration), declaration) + loc := l.getMappedLocation(file.FileName(), createRangeFromNode(node, file)) + prefixLen := core.IfElse(strings.HasPrefix(text, "()"), 2, 0) + linkText := trimCommentPrefix(text[prefixLen:]) if linkText == "" { - linkText = getEntityNameString(name) + linkText = getEntityNameString(name) + text[:prefixLen] } - writeMarkdownLink(b, linkText, fmt.Sprintf("%v#%v,%v", string(FileNameToDocumentURI(file.FileName())), pos.Line+1, pos.Character+1), quote) + linkUri := fmt.Sprintf("%v#%v,%v-%v,%v", loc.Uri, loc.Range.Start.Line+1, loc.Range.Start.Character+1, loc.Range.End.Line+1, loc.Range.End.Character+1) + writeMarkdownLink(b, linkText, linkUri, quote) return } writeQuotedString(b, getEntityNameString(name)+" "+text, quote) } +func trimCommentPrefix(text string) string { + return strings.TrimLeft(strings.TrimPrefix(strings.TrimLeft(text, " "), "|"), " ") +} + func writeMarkdownLink(b *strings.Builder, text string, uri string, quote bool) { b.WriteString("[") writeQuotedString(b, text, quote) From f3aa7ef1e2f1245be5f617d370212830dffa3a9a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 23 Oct 2025 07:46:35 -0700 Subject: [PATCH 3/5] Accept new baselines --- .../fourslash/quickInfo/jsdocLink1.baseline | 14 +++++++------- .../fourslash/quickInfo/jsdocLink4.baseline | 14 +++++++------- .../fourslash/quickInfo/jsdocLink5.baseline | 6 +++--- .../quickInfoForJSDocWithHttpLinks.baseline | 8 ++++---- ...ickInfoForJSDocWithUnresolvedHttpLinks.baseline | 8 ++++---- .../fourslash/quickInfo/quickInfoLink10.baseline | 4 ++-- .../fourslash/quickInfo/quickInfoLink11.baseline | 8 ++++---- .../fourslash/quickInfo/quickInfoLink5.baseline | 4 ++-- .../fourslash/quickInfo/quickInfoLink6.baseline | 4 ++-- .../fourslash/quickInfo/quickInfoLink7.baseline | 4 ++-- .../fourslash/quickInfo/quickInfoLink8.baseline | 4 ++-- 11 files changed, 39 insertions(+), 39 deletions(-) diff --git a/testdata/baselines/reference/fourslash/quickInfo/jsdocLink1.baseline b/testdata/baselines/reference/fourslash/quickInfo/jsdocLink1.baseline index 8c1c73c68e..4475c15804 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/jsdocLink1.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/jsdocLink1.baseline @@ -16,14 +16,14 @@ // | ```tsx // | function CC(): void // | ``` -// | `C` +// | [C](file:///jsdocLink1.ts#1,7-1,8) // | -// | *@wat* — Makes a `C`. A default one. -// | C() -// | C|postfix text -// | unformattedpostfix text +// | *@wat* — Makes a [C](file:///jsdocLink1.ts#1,7-1,8). A default one. +// | [C()](file:///jsdocLink1.ts#1,7-1,8) +// | [postfix text](file:///jsdocLink1.ts#1,7-1,8) +// | unformatted postfix text // | -// | *@see* — `C` its great +// | *@see* — [C](file:///jsdocLink1.ts#1,7-1,8) its great // | // | ---------------------------------------------------------------------- // } @@ -41,7 +41,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nfunction CC(): void\n```\n`C`\n\n*@wat* — Makes a `C`. A default one.\nC()\nC|postfix text\nunformattedpostfix text\n\n*@see* — `C` its great\n" + "value": "```tsx\nfunction CC(): void\n```\n[C](file:///jsdocLink1.ts#1,7-1,8)\n\n*@wat* — Makes a [C](file:///jsdocLink1.ts#1,7-1,8). A default one.\n[C()](file:///jsdocLink1.ts#1,7-1,8)\n[postfix text](file:///jsdocLink1.ts#1,7-1,8)\nunformatted postfix text\n\n*@see* — [C](file:///jsdocLink1.ts#1,7-1,8) its great\n" } } } diff --git a/testdata/baselines/reference/fourslash/quickInfo/jsdocLink4.baseline b/testdata/baselines/reference/fourslash/quickInfo/jsdocLink4.baseline index 34f8283b55..b99603e33b 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/jsdocLink4.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/jsdocLink4.baseline @@ -8,7 +8,7 @@ // | ```tsx // | (method) I.bar(): void // | ``` -// | `I` +// | [I](file:///jsdocLink4.ts#1,15-1,16) // | ---------------------------------------------------------------------- // } // /** {@link I} */ @@ -18,7 +18,7 @@ // | ```tsx // | var n: number // | ``` -// | `I` +// | [I](file:///jsdocLink4.ts#1,15-1,16) // | ---------------------------------------------------------------------- // /** // * A real, very serious {@link I to an interface}. Right there. @@ -32,9 +32,9 @@ // | ```tsx // | function f(x: any): void // | ``` -// | A real, very serious Ito an interface. Right there. +// | A real, very serious [to an interface](file:///jsdocLink4.ts#1,15-1,16). Right there. // | -// | *@param* `x` — one Poshere too +// | *@param* `x` — one [here too](file:///jsdocLink4.ts#14,6-14,9) // | ---------------------------------------------------------------------- // type Pos = [number, number] [ @@ -51,7 +51,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\n(method) I.bar(): void\n```\n`I`" + "value": "```tsx\n(method) I.bar(): void\n```\n[I](file:///jsdocLink4.ts#1,15-1,16)" } } }, @@ -68,7 +68,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nvar n: number\n```\n`I`" + "value": "```tsx\nvar n: number\n```\n[I](file:///jsdocLink4.ts#1,15-1,16)" } } }, @@ -85,7 +85,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nfunction f(x: any): void\n```\nA real, very serious Ito an interface. Right there.\n\n*@param* `x` — one Poshere too" + "value": "```tsx\nfunction f(x: any): void\n```\nA real, very serious [to an interface](file:///jsdocLink4.ts#1,15-1,16). Right there.\n\n*@param* `x` — one [here too](file:///jsdocLink4.ts#14,6-14,9)" } } } diff --git a/testdata/baselines/reference/fourslash/quickInfo/jsdocLink5.baseline b/testdata/baselines/reference/fourslash/quickInfo/jsdocLink5.baseline index 462571cb64..182df343bc 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/jsdocLink5.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/jsdocLink5.baseline @@ -13,8 +13,8 @@ // | ```tsx // | function f(x: any): void // | ``` -// | g() g() g() g() 0 g()1 g() 2 -// | u() u() u() u() 0 u()1 u() 2 +// | [g()](file:///jsdocLink5.ts#1,10-1,11) [g()](file:///jsdocLink5.ts#1,10-1,11) [g()](file:///jsdocLink5.ts#1,10-1,11) [0](file:///jsdocLink5.ts#1,10-1,11) [1](file:///jsdocLink5.ts#1,10-1,11) [2](file:///jsdocLink5.ts#1,10-1,11) +// | u () u () u () u () 0 u ()1 u () 2 // | ---------------------------------------------------------------------- [ { @@ -30,7 +30,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nfunction f(x: any): void\n```\ng() g() g() g() 0 g()1 g() 2\nu() u() u() u() 0 u()1 u() 2" + "value": "```tsx\nfunction f(x: any): void\n```\n[g()](file:///jsdocLink5.ts#1,10-1,11) [g()](file:///jsdocLink5.ts#1,10-1,11) [g()](file:///jsdocLink5.ts#1,10-1,11) [0](file:///jsdocLink5.ts#1,10-1,11) [1](file:///jsdocLink5.ts#1,10-1,11) [2](file:///jsdocLink5.ts#1,10-1,11)\nu () u () u () u () 0 u ()1 u () 2" } } } diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoForJSDocWithHttpLinks.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoForJSDocWithHttpLinks.baseline index f119b73655..21e502884b 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoForJSDocWithHttpLinks.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoForJSDocWithHttpLinks.baseline @@ -52,7 +52,7 @@ // | ``` // | // | -// | *@see* — https://hva +// | *@see* — [https://hva](https://hva) // | ---------------------------------------------------------------------- // // /** {@link https://hvaD} */ @@ -62,7 +62,7 @@ // | ```tsx // | var see3: boolean // | ``` -// | https://hvaD +// | [https://hvaD](https://hvaD) // | ---------------------------------------------------------------------- [ { @@ -146,7 +146,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nvar see2: boolean\n```\n\n\n*@see* — https://hva " + "value": "```tsx\nvar see2: boolean\n```\n\n\n*@see* — [https://hva](https://hva) " } } }, @@ -163,7 +163,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nvar see3: boolean\n```\nhttps://hvaD" + "value": "```tsx\nvar see3: boolean\n```\n[https://hvaD](https://hvaD)" } } } diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoForJSDocWithUnresolvedHttpLinks.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoForJSDocWithUnresolvedHttpLinks.baseline index fcd61bd90e..efdfa13552 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoForJSDocWithUnresolvedHttpLinks.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoForJSDocWithUnresolvedHttpLinks.baseline @@ -9,7 +9,7 @@ // | ``` // | // | -// | *@see* — https://hva +// | *@see* — [https://hva](https://hva) // | ---------------------------------------------------------------------- // // /** {@link https://hvaD} */ @@ -19,7 +19,7 @@ // | ```tsx // | var see3: boolean // | ``` -// | https://hvaD +// | [https://hvaD](https://hvaD) // | ---------------------------------------------------------------------- [ { @@ -35,7 +35,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nvar see2: boolean\n```\n\n\n*@see* — https://hva " + "value": "```tsx\nvar see2: boolean\n```\n\n\n*@see* — [https://hva](https://hva) " } } }, @@ -52,7 +52,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nvar see3: boolean\n```\nhttps://hvaD" + "value": "```tsx\nvar see3: boolean\n```\n[https://hvaD](https://hvaD)" } } } diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink10.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink10.baseline index 9436eebded..e091db0a12 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink10.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink10.baseline @@ -9,7 +9,7 @@ // | ```tsx // | const a: () => number // | ``` -// | start https://vscode.dev/ | end +// | start [end](https://vscode.dev/) // | ---------------------------------------------------------------------- [ { @@ -25,7 +25,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nconst a: () => number\n```\nstart https://vscode.dev/ | end" + "value": "```tsx\nconst a: () => number\n```\nstart [end](https://vscode.dev/)" } } } diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink11.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink11.baseline index 50655f184b..922626732d 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink11.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink11.baseline @@ -14,10 +14,10 @@ // | ```tsx // | function f(): void // | ``` -// | https://vscode.dev +// | [https://vscode.dev](https://vscode.dev) // | [link text]{https://vscode.dev} -// | https://vscode.dev|link text -// | https://vscode.dev link text +// | [link text](https://vscode.dev) +// | [link text](https://vscode.dev) // | ---------------------------------------------------------------------- [ { @@ -33,7 +33,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nfunction f(): void\n```\nhttps://vscode.dev\n[link text]{https://vscode.dev}\nhttps://vscode.dev|link text\nhttps://vscode.dev link text" + "value": "```tsx\nfunction f(): void\n```\n[https://vscode.dev](https://vscode.dev)\n[link text]{https://vscode.dev}\n[link text](https://vscode.dev)\n[link text](https://vscode.dev)" } } } diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink5.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink5.baseline index 6064786e91..abac5e34e6 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink5.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink5.baseline @@ -10,7 +10,7 @@ // | ```tsx // | const B: 456 // | ``` -// | See A| constant A instead +// | See [constant A](file:///quickInfoLink5.ts#1,7-1,8) instead // | ---------------------------------------------------------------------- [ { @@ -26,7 +26,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nconst B: 456\n```\nSee A| constant A instead" + "value": "```tsx\nconst B: 456\n```\nSee [constant A](file:///quickInfoLink5.ts#1,7-1,8) instead" } } } diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink6.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink6.baseline index 44aee68e6e..bce3d3bbc1 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink6.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink6.baseline @@ -10,7 +10,7 @@ // | ```tsx // | const B: 456 // | ``` -// | See A|constant A instead +// | See [constant A](file:///quickInfoLink6.ts#1,7-1,8) instead // | ---------------------------------------------------------------------- [ { @@ -26,7 +26,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nconst B: 456\n```\nSee A|constant A instead" + "value": "```tsx\nconst B: 456\n```\nSee [constant A](file:///quickInfoLink6.ts#1,7-1,8) instead" } } } diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink7.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink7.baseline index 72b2cb44ce..79918ed05f 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink7.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink7.baseline @@ -9,7 +9,7 @@ // | ```tsx // | const B: 456 // | ``` -// | See | instead +// | See | instead // | ---------------------------------------------------------------------- [ { @@ -25,7 +25,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nconst B: 456\n```\nSee | instead" + "value": "```tsx\nconst B: 456\n```\nSee | instead" } } } diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink8.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink8.baseline index d336abcb17..07025af4a0 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink8.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoLink8.baseline @@ -10,7 +10,7 @@ // | ```tsx // | const B: 456 // | ``` -// | See A| constant A instead +// | See [constant A](file:///quickInfoLink8.ts#1,7-1,8) instead // | ---------------------------------------------------------------------- [ { @@ -26,7 +26,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```tsx\nconst B: 456\n```\nSee A| constant A instead" + "value": "```tsx\nconst B: 456\n```\nSee [constant A](file:///quickInfoLink8.ts#1,7-1,8) instead" } } } From e16738b215d3be8603b3dbfb334136a866e63d37 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 23 Oct 2025 07:52:27 -0700 Subject: [PATCH 4/5] Update fourslash test --- internal/fourslash/tests/basicQuickInfo_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/fourslash/tests/basicQuickInfo_test.go b/internal/fourslash/tests/basicQuickInfo_test.go index 44e80c9a6f..f207771e24 100644 --- a/internal/fourslash/tests/basicQuickInfo_test.go +++ b/internal/fourslash/tests/basicQuickInfo_test.go @@ -28,5 +28,5 @@ class Foo/*3*/ { ` f := fourslash.NewFourslash(t, nil /*capabilities*/, content) f.VerifyQuickInfoAt(t, "1", "var someVar: number", "Some var") - f.VerifyQuickInfoAt(t, "2", "var otherVar: number", "Other var\nSee `someVar`") + f.VerifyQuickInfoAt(t, "2", "var otherVar: number", "Other var\nSee [someVar](file:///basicQuickInfo.ts#4,5-4,12)") } From 132d35bf3997028859140fed9a71af851929e214 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 23 Oct 2025 09:58:47 -0700 Subject: [PATCH 5/5] Address CR feedback --- internal/ls/hover.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ls/hover.go b/internal/ls/hover.go index 9a28f3e307..d432908aa7 100644 --- a/internal/ls/hover.go +++ b/internal/ls/hover.go @@ -455,7 +455,7 @@ func (l *LanguageService) writeJSDocLink(b *strings.Builder, c *checker.Checker, if linkText == "" { linkText = getEntityNameString(name) + text[:prefixLen] } - linkUri := fmt.Sprintf("%v#%v,%v-%v,%v", loc.Uri, loc.Range.Start.Line+1, loc.Range.Start.Character+1, loc.Range.End.Line+1, loc.Range.End.Character+1) + linkUri := fmt.Sprintf("%s#%d,%d-%d,%d", loc.Uri, loc.Range.Start.Line+1, loc.Range.Start.Character+1, loc.Range.End.Line+1, loc.Range.End.Character+1) writeMarkdownLink(b, linkText, linkUri, quote) return }