From d7279294cd26549cc849f15b241863bbe286f03e Mon Sep 17 00:00:00 2001 From: Patrick Kettner Date: Tue, 6 Oct 2026 07:36:38 -0400 Subject: [PATCH 1/3] Show the JSDoc written on an import alias in hover and completions A comment on `import x = a.x` was never read. Hover and completion details showed the target's comment, or nothing when the target had none. 6.0 shows the alias's own comment, except on a call, where the resolved signature's documentation still wins. --- .../tests/hoverImportEqualsAliasJSDoc_test.go | 64 +++++ tsc/internal/ls/hover.go | 27 +- .../hoverImportEqualsAliasJSDoc.baseline | 234 ++++++++++++++++++ 3 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 tsc/internal/fourslash/tests/hoverImportEqualsAliasJSDoc_test.go create mode 100644 tsc/testdata/baselines/reference/fourslash/quickInfo/hoverImportEqualsAliasJSDoc.baseline diff --git a/tsc/internal/fourslash/tests/hoverImportEqualsAliasJSDoc_test.go b/tsc/internal/fourslash/tests/hoverImportEqualsAliasJSDoc_test.go new file mode 100644 index 0000000000000..fffa388fb3513 --- /dev/null +++ b/tsc/internal/fourslash/tests/hoverImportEqualsAliasJSDoc_test.go @@ -0,0 +1,64 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/fourslash" + . "github.com/microsoft/TypeScript/tsc/internal/fourslash/tests/util" + "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto" + "github.com/microsoft/TypeScript/tsc/internal/testutil" +) + +func TestHoverImportEqualsAliasJSDoc(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `declare namespace a { + /** on the declaration */ + export function fn(): void; + /** on the declaration */ + export const value: number; + /** on the declaration */ + export interface Shape {} + export function bare(): void; +} +declare namespace b { + /** on the alias */ + export import fn = a.fn; + /** on the alias */ + export import value = a.value; + /** on the alias */ + export import Shape = a.Shape; + /** on the alias */ + export import bare = a.bare; +} +b./*fn*/fn; +b./*fnCall*/fn(); +b./*value*/value; +let shape: b./*Shape*/Shape; +b./*bare*/bare; +b./*bareCall*/bare();` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyBaselineHover(t) + f.VerifyCompletions(t, "fn", &fourslash.CompletionsExpectedList{ + IsIncomplete: false, + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &DefaultCommitCharacters, + EditRange: Ignored, + }, + Items: &fourslash.CompletionsExpectedItems{ + Includes: []fourslash.CompletionsExpectedItem{ + &lsproto.CompletionItem{ + Label: "fn", + Detail: new("(alias) function a.fn(): void"), + Documentation: &lsproto.StringOrMarkupContent{ + MarkupContent: &lsproto.MarkupContent{ + Kind: lsproto.MarkupKindMarkdown, + Value: "on the alias", + }, + }, + }, + }, + }, + }) +} diff --git a/tsc/internal/ls/hover.go b/tsc/internal/ls/hover.go index a6a326a003df4..dd9bbcbf53fe9 100644 --- a/tsc/internal/ls/hover.go +++ b/tsc/internal/ls/hover.go @@ -206,15 +206,23 @@ func (l *LanguageService) documentationLocationMapper(feature spanmap.Feature) d } // getDocumentationForSymbol tries each documentation source in turn (call-signature documentation, -// declaration JSDoc, root-symbol JSDoc, alias target JSDoc) and returns the first non-empty result, -// formatted for contentFormat. commentOnly restricts the result to the JSDoc summary, excluding the -// @tag section. +// an alias's own JSDoc, root-symbol JSDoc, declaration JSDoc, alias target JSDoc) and returns the +// first non-empty result, formatted for contentFormat. At a call site the alias's own JSDoc is tried +// after the declaration's instead. commentOnly restricts the result to the JSDoc summary, excluding +// the @tag section. func getDocumentationForSymbol(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, declaration *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { documentation := documentationFromSignature(getMappedLocation, c, symbol, getCallOrNewExpression(node), node, contentFormat, commentOnly) if documentation != "" { return documentation } + // For an alias, declaration is the target's, and the alias's own JSDoc comes before it. At a + // call site declaration is the resolved signature's instead, and that comes first. + aliasDocumentation := documentationFromAliasDeclaration(getMappedLocation, c, symbol, node, contentFormat, commentOnly) + if aliasDocumentation != "" && getCallOrNewExpression(node) == nil { + return aliasDocumentation + } + documentation = documentationFromRootSymbols(getMappedLocation, c, symbol, node, contentFormat, commentOnly) if documentation != "" { return documentation @@ -225,6 +233,10 @@ func getDocumentationForSymbol(getMappedLocation documentationLocationMapper, c return documentation } + if aliasDocumentation != "" { + return aliasDocumentation + } + return documentationFromAlias(getMappedLocation, c, symbol, node, contentFormat, commentOnly) } @@ -246,6 +258,15 @@ func documentationFromSignature(getMappedLocation documentationLocationMapper, c return "" } +// documentationFromAliasDeclaration returns the JSDoc written on the alias itself, as on +// `/** ... */ import x = a.x`. +func documentationFromAliasDeclaration(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { + if symbol == nil || symbol.Flags&ast.SymbolFlagsAlias == 0 { + return "" + } + return getDocumentationFromDeclaration(getMappedLocation, c, symbol, core.FirstOrNil(symbol.Declarations), node, contentFormat, commentOnly) +} + func documentationFromAlias(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { if symbol == nil || symbol.Flags()&ast.SymbolFlagsAlias == 0 { return "" diff --git a/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverImportEqualsAliasJSDoc.baseline b/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverImportEqualsAliasJSDoc.baseline new file mode 100644 index 0000000000000..cb2763999a471 --- /dev/null +++ b/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverImportEqualsAliasJSDoc.baseline @@ -0,0 +1,234 @@ +// === QuickInfo === +=== /hoverImportEqualsAliasJSDoc.ts === +// declare namespace a { +// /** on the declaration */ +// export function fn(): void; +// /** on the declaration */ +// export const value: number; +// /** on the declaration */ +// export interface Shape {} +// export function bare(): void; +// } +// declare namespace b { +// /** on the alias */ +// export import fn = a.fn; +// /** on the alias */ +// export import value = a.value; +// /** on the alias */ +// export import Shape = a.Shape; +// /** on the alias */ +// export import bare = a.bare; +// } +// b.fn; +// ^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function a.fn(): void +// | ``` +// | on the alias +// | ---------------------------------------------------------------------- +// b.fn(); +// ^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function a.fn(): void +// | ``` +// | on the declaration +// | ---------------------------------------------------------------------- +// b.value; +// ^^^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) const a.value: number +// | ``` +// | on the alias +// | ---------------------------------------------------------------------- +// let shape: b.Shape; +// ^^^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) interface a.Shape +// | ``` +// | on the alias +// | ---------------------------------------------------------------------- +// b.bare; +// ^^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function a.bare(): void +// | ``` +// | on the alias +// | ---------------------------------------------------------------------- +// b.bare(); +// ^^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function a.bare(): void +// | ``` +// | on the alias +// | ---------------------------------------------------------------------- +[ + { + "marker": { + "Position": 496, + "LSPosition": { + "line": 19, + "character": 2 + }, + "Name": "fn", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function a.fn(): void\n```\non the alias" + }, + "range": { + "start": { + "line": 19, + "character": 2 + }, + "end": { + "line": 19, + "character": 4 + } + } + } + }, + { + "marker": { + "Position": 502, + "LSPosition": { + "line": 20, + "character": 2 + }, + "Name": "fnCall", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function a.fn(): void\n```\non the declaration" + }, + "range": { + "start": { + "line": 20, + "character": 2 + }, + "end": { + "line": 20, + "character": 4 + } + } + } + }, + { + "marker": { + "Position": 510, + "LSPosition": { + "line": 21, + "character": 2 + }, + "Name": "value", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) const a.value: number\n```\non the alias" + }, + "range": { + "start": { + "line": 21, + "character": 2 + }, + "end": { + "line": 21, + "character": 7 + } + } + } + }, + { + "marker": { + "Position": 530, + "LSPosition": { + "line": 22, + "character": 13 + }, + "Name": "Shape", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) interface a.Shape\n```\non the alias" + }, + "range": { + "start": { + "line": 22, + "character": 13 + }, + "end": { + "line": 22, + "character": 18 + } + }, + "canIncreaseVerbosity": true + } + }, + { + "marker": { + "Position": 539, + "LSPosition": { + "line": 23, + "character": 2 + }, + "Name": "bare", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function a.bare(): void\n```\non the alias" + }, + "range": { + "start": { + "line": 23, + "character": 2 + }, + "end": { + "line": 23, + "character": 6 + } + } + } + }, + { + "marker": { + "Position": 547, + "LSPosition": { + "line": 24, + "character": 2 + }, + "Name": "bareCall", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function a.bare(): void\n```\non the alias" + }, + "range": { + "start": { + "line": 24, + "character": 2 + }, + "end": { + "line": 24, + "character": 6 + } + } + } + } +] \ No newline at end of file From c257c1467372a4cc04b60266668c642857a6391a Mon Sep 17 00:00:00 2001 From: Patrick Kettner Date: Tue, 6 Oct 2026 19:10:34 -0400 Subject: [PATCH 2/3] Use the checker's alias declaration, skip tag-only alias comments On a merged symbol Declarations[0] is the namespace, so the alias comment was never read. An alias comment with no description was hiding the target's. --- tsc/internal/checker/services.go | 4 + .../tests/hoverImportEqualsAliasJSDoc_test.go | 11 +- tsc/internal/ls/hover.go | 27 ++-- .../hoverImportEqualsAliasJSDoc.baseline | 125 ++++++++++++++---- 4 files changed, 134 insertions(+), 33 deletions(-) diff --git a/tsc/internal/checker/services.go b/tsc/internal/checker/services.go index 90f3f2f901844..52376f4ae3da1 100644 --- a/tsc/internal/checker/services.go +++ b/tsc/internal/checker/services.go @@ -399,6 +399,10 @@ func (c *Checker) SkipAlias(symbol *ast.Symbol) *ast.Symbol { return symbol } +func (c *Checker) GetDeclarationOfAliasSymbol(symbol *ast.Symbol) *ast.Node { + return c.getDeclarationOfAliasSymbol(symbol) +} + func (c *Checker) GetRootSymbols(symbol *ast.Symbol) []*ast.Symbol { roots := c.getImmediateRootSymbols(symbol) if len(roots) == 0 { diff --git a/tsc/internal/fourslash/tests/hoverImportEqualsAliasJSDoc_test.go b/tsc/internal/fourslash/tests/hoverImportEqualsAliasJSDoc_test.go index fffa388fb3513..12539258e8f24 100644 --- a/tsc/internal/fourslash/tests/hoverImportEqualsAliasJSDoc_test.go +++ b/tsc/internal/fourslash/tests/hoverImportEqualsAliasJSDoc_test.go @@ -20,6 +20,10 @@ func TestHoverImportEqualsAliasJSDoc(t *testing.T) { /** on the declaration */ export interface Shape {} export function bare(): void; + /** on the declaration */ + export function tagged(): void; + /** on the declaration */ + export function plain(): void; } declare namespace b { /** on the alias */ @@ -30,13 +34,18 @@ declare namespace b { export import Shape = a.Shape; /** on the alias */ export import bare = a.bare; + /** @deprecated on the alias */ + export import tagged = a.tagged; + export import plain = a.plain; } b./*fn*/fn; b./*fnCall*/fn(); b./*value*/value; let shape: b./*Shape*/Shape; b./*bare*/bare; -b./*bareCall*/bare();` +b./*bareCall*/bare(); +b./*tagged*/tagged; +b./*plain*/plain;` f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) defer done() f.VerifyBaselineHover(t) diff --git a/tsc/internal/ls/hover.go b/tsc/internal/ls/hover.go index dd9bbcbf53fe9..67c3fe7efea3b 100644 --- a/tsc/internal/ls/hover.go +++ b/tsc/internal/ls/hover.go @@ -211,16 +211,19 @@ func (l *LanguageService) documentationLocationMapper(feature spanmap.Feature) d // after the declaration's instead. commentOnly restricts the result to the JSDoc summary, excluding // the @tag section. func getDocumentationForSymbol(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, declaration *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { - documentation := documentationFromSignature(getMappedLocation, c, symbol, getCallOrNewExpression(node), node, contentFormat, commentOnly) + callNode := getCallOrNewExpression(node) + documentation := documentationFromSignature(getMappedLocation, c, symbol, callNode, node, contentFormat, commentOnly) if documentation != "" { return documentation } // For an alias, declaration is the target's, and the alias's own JSDoc comes before it. At a // call site declaration is the resolved signature's instead, and that comes first. - aliasDocumentation := documentationFromAliasDeclaration(getMappedLocation, c, symbol, node, contentFormat, commentOnly) - if aliasDocumentation != "" && getCallOrNewExpression(node) == nil { - return aliasDocumentation + if callNode == nil { + documentation = documentationFromAliasDeclaration(getMappedLocation, c, symbol, node, contentFormat, commentOnly) + if documentation != "" { + return documentation + } } documentation = documentationFromRootSymbols(getMappedLocation, c, symbol, node, contentFormat, commentOnly) @@ -233,8 +236,11 @@ func getDocumentationForSymbol(getMappedLocation documentationLocationMapper, c return documentation } - if aliasDocumentation != "" { - return aliasDocumentation + if callNode != nil { + documentation = documentationFromAliasDeclaration(getMappedLocation, c, symbol, node, contentFormat, commentOnly) + if documentation != "" { + return documentation + } } return documentationFromAlias(getMappedLocation, c, symbol, node, contentFormat, commentOnly) @@ -259,12 +265,17 @@ func documentationFromSignature(getMappedLocation documentationLocationMapper, c } // documentationFromAliasDeclaration returns the JSDoc written on the alias itself, as on -// `/** ... */ import x = a.x`. +// `/** ... */ import x = a.x`. A comment made only of tags leaves the target's documentation in +// place, so it returns nothing for one. func documentationFromAliasDeclaration(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { if symbol == nil || symbol.Flags&ast.SymbolFlagsAlias == 0 { return "" } - return getDocumentationFromDeclaration(getMappedLocation, c, symbol, core.FirstOrNil(symbol.Declarations), node, contentFormat, commentOnly) + declaration := c.GetDeclarationOfAliasSymbol(symbol) + if getDocumentationFromDeclaration(getMappedLocation, c, symbol, declaration, node, contentFormat, true /*commentOnly*/) == "" { + return "" + } + return getDocumentationFromDeclaration(getMappedLocation, c, symbol, declaration, node, contentFormat, commentOnly) } func documentationFromAlias(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { diff --git a/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverImportEqualsAliasJSDoc.baseline b/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverImportEqualsAliasJSDoc.baseline index cb2763999a471..26827768b9a57 100644 --- a/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverImportEqualsAliasJSDoc.baseline +++ b/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverImportEqualsAliasJSDoc.baseline @@ -8,6 +8,10 @@ // /** on the declaration */ // export interface Shape {} // export function bare(): void; +// /** on the declaration */ +// export function tagged(): void; +// /** on the declaration */ +// export function plain(): void; // } // declare namespace b { // /** on the alias */ @@ -18,6 +22,9 @@ // export import Shape = a.Shape; // /** on the alias */ // export import bare = a.bare; +// /** @deprecated on the alias */ +// export import tagged = a.tagged; +// export import plain = a.plain; // } // b.fn; // ^^ @@ -67,12 +74,28 @@ // | ``` // | on the alias // | ---------------------------------------------------------------------- +// b.tagged; +// ^^^^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function a.tagged(): void +// | ``` +// | on the declaration +// | ---------------------------------------------------------------------- +// b.plain; +// ^^^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function a.plain(): void +// | ``` +// | on the declaration +// | ---------------------------------------------------------------------- [ { "marker": { - "Position": 496, + "Position": 735, "LSPosition": { - "line": 19, + "line": 26, "character": 2 }, "Name": "fn", @@ -85,11 +108,11 @@ }, "range": { "start": { - "line": 19, + "line": 26, "character": 2 }, "end": { - "line": 19, + "line": 26, "character": 4 } } @@ -97,9 +120,9 @@ }, { "marker": { - "Position": 502, + "Position": 741, "LSPosition": { - "line": 20, + "line": 27, "character": 2 }, "Name": "fnCall", @@ -112,11 +135,11 @@ }, "range": { "start": { - "line": 20, + "line": 27, "character": 2 }, "end": { - "line": 20, + "line": 27, "character": 4 } } @@ -124,9 +147,9 @@ }, { "marker": { - "Position": 510, + "Position": 749, "LSPosition": { - "line": 21, + "line": 28, "character": 2 }, "Name": "value", @@ -139,11 +162,11 @@ }, "range": { "start": { - "line": 21, + "line": 28, "character": 2 }, "end": { - "line": 21, + "line": 28, "character": 7 } } @@ -151,9 +174,9 @@ }, { "marker": { - "Position": 530, + "Position": 769, "LSPosition": { - "line": 22, + "line": 29, "character": 13 }, "Name": "Shape", @@ -166,11 +189,11 @@ }, "range": { "start": { - "line": 22, + "line": 29, "character": 13 }, "end": { - "line": 22, + "line": 29, "character": 18 } }, @@ -179,9 +202,9 @@ }, { "marker": { - "Position": 539, + "Position": 778, "LSPosition": { - "line": 23, + "line": 30, "character": 2 }, "Name": "bare", @@ -194,11 +217,11 @@ }, "range": { "start": { - "line": 23, + "line": 30, "character": 2 }, "end": { - "line": 23, + "line": 30, "character": 6 } } @@ -206,9 +229,9 @@ }, { "marker": { - "Position": 547, + "Position": 786, "LSPosition": { - "line": 24, + "line": 31, "character": 2 }, "Name": "bareCall", @@ -221,14 +244,68 @@ }, "range": { "start": { - "line": 24, + "line": 31, "character": 2 }, "end": { - "line": 24, + "line": 31, "character": 6 } } } + }, + { + "marker": { + "Position": 796, + "LSPosition": { + "line": 32, + "character": 2 + }, + "Name": "tagged", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function a.tagged(): void\n```\non the declaration" + }, + "range": { + "start": { + "line": 32, + "character": 2 + }, + "end": { + "line": 32, + "character": 8 + } + } + } + }, + { + "marker": { + "Position": 806, + "LSPosition": { + "line": 33, + "character": 2 + }, + "Name": "plain", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function a.plain(): void\n```\non the declaration" + }, + "range": { + "start": { + "line": 33, + "character": 2 + }, + "end": { + "line": 33, + "character": 7 + } + } + } } ] \ No newline at end of file From 2d474ecd0b99b9ed1a20ad6e864fa3260e55d99b Mon Sep 17 00:00:00 2001 From: Patrick Kettner Date: Wed, 14 Oct 2026 23:01:38 -0400 Subject: [PATCH 3/3] Show every comment on an alias chain in hover and completions A reference to an alias showed the comment of one declaration on its chain, and which one depended on whether the name was called, so a comment on an import binding or a re-export was lost at some sites. getDocumentationForSymbol now walks the alias chain and joins each alias declaration's comment, outermost first, ahead of the implementation's, with the tag sections following in the same order. The parser attaches JSDoc to import specifiers the way it already does for export specifiers, and checkAliasSymbol no longer reports a deprecated specifier against itself. --- tsc/internal/checker/checker.go | 2 +- .../tests/hoverAliasChainJSDoc_test.go | 156 +++++ .../tests/hoverImportEqualsAliasJSDoc_test.go | 73 --- tsc/internal/ls/hover.go | 157 +++-- tsc/internal/parser/parser.go | 5 +- tsc/internal/parser/parser_test.go | 35 ++ tsc/internal/parser/utilities.go | 2 +- .../hoverAliasChainJSDocShapes.baseline | 584 ++++++++++++++++++ .../hoverImportEqualsAliasJSDoc.baseline | 311 ---------- .../quickInfo/quickInfoAlias.baseline | 5 +- .../vSQuickInfo/quickInfoAliasVS.baseline | 6 +- 11 files changed, 878 insertions(+), 458 deletions(-) create mode 100644 tsc/internal/fourslash/tests/hoverAliasChainJSDoc_test.go delete mode 100644 tsc/internal/fourslash/tests/hoverImportEqualsAliasJSDoc_test.go create mode 100644 tsc/testdata/baselines/reference/fourslash/quickInfo/hoverAliasChainJSDocShapes.baseline delete mode 100644 tsc/testdata/baselines/reference/fourslash/quickInfo/hoverImportEqualsAliasJSDoc.baseline diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index d392785d4181f..9705a2c911b84 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -7054,7 +7054,7 @@ func (c *Checker) checkAliasSymbol(node *ast.Node) { } if ast.IsImportSpecifier(node) { targetSymbol := c.resolveAliasWithDeprecationCheck(symbol, node) - if c.isDeprecatedSymbol(targetSymbol) && targetSymbol.Declarations() != nil { + if targetSymbol != symbol && c.isDeprecatedSymbol(targetSymbol) && targetSymbol.Declarations() != nil { c.addDeprecatedSuggestion(node, targetSymbol.Declarations(), targetSymbol.Name()) } } diff --git a/tsc/internal/fourslash/tests/hoverAliasChainJSDoc_test.go b/tsc/internal/fourslash/tests/hoverAliasChainJSDoc_test.go new file mode 100644 index 0000000000000..bea1275b35023 --- /dev/null +++ b/tsc/internal/fourslash/tests/hoverAliasChainJSDoc_test.go @@ -0,0 +1,156 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/fourslash" + . "github.com/microsoft/TypeScript/tsc/internal/fourslash/tests/util" + "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto" + "github.com/microsoft/TypeScript/tsc/internal/testutil" +) + +func TestHoverAliasChainJSDoc(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /lib.ts +/** + * Doc on impl + */ +export function foo() {} +// @Filename: /main.ts +import * as lib from "./lib.js"; + +/** + * Doc on export import alias + */ +export import bar = lib.foo +// @Filename: /use.ts +import { + /** + * Doc on import alias + */ + /*specifier*/bar +} from "./main.js" + +/*call*/bar() +/*reference*/bar` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + const documentation = "Doc on import alias\nDoc on export import alias\nDoc on impl" + f.VerifyQuickInfoAt(t, "call", "(alias) function foo(): void", documentation) + f.VerifyQuickInfoAt(t, "reference", "(alias) function foo(): void", documentation) + f.VerifyQuickInfoAt(t, "specifier", "(alias) function foo(): void", documentation) +} + +func TestHoverAliasChainJSDocShapes(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /lib.ts +/** + * Doc on impl + */ +export function foo() {} +// @Filename: /main.ts +import * as lib from "./lib.js"; + +/** + * Doc on export import alias + */ +export import bar = lib.foo +// @Filename: /namespaceImport.ts +import * as m from "./main.js"; +m./*namespaceImportCall*/bar(); +m./*namespaceImport*/bar; +// @Filename: /uncommentedReexport.ts +export { foo } from "./lib.js"; +// @Filename: /middleHop.ts +import { + /** Doc on import alias */ + foo +} from "./uncommentedReexport.js"; +/*middleHopCall*/foo(); +/*middleHop*/foo; +// @Filename: /impl.ts +/** on impl */ +export function f() {} +/** on impl */ +export default function d() {} +// @Filename: /reexport.ts +export { /** on re-export */ f as g } from "./impl.js"; +// @Filename: /useReexport.ts +import { g } from "./reexport.js"; +/*reexportCall*/g(); +/*reexport*/g; +// @Filename: /useDefault.ts +/** before import */ +import d from "./impl.js"; +/*defaultImportCall*/d(); +/*defaultImport*/d; +// @Filename: /namespaces.ts +declare namespace a { + /** on the declaration */ + export function fn(): void; + /** on the declaration */ + export interface S {} + /** Same */ + export function same(): void; + /** on the declaration */ + export function tagged(): void; +} +declare namespace b { + /** on the alias */ + export import fn = a.fn; + export namespace S {} + /** on the alias */ + export import S = a.S; + /** Same */ + export import same = a.same; + /** @deprecated on the alias */ + export import tagged = a.tagged; +} +b./*importEqualsCall*/fn(); +b./*importEquals*/fn; +let s: b./*merged*/S; +b./*sameTextCall*/same(); +b./*sameText*/same; +b./*tagOnlyCall*/tagged(); +b./*tagOnly*/tagged; +// @Filename: /useDeprecated.ts +import { /** @deprecated */ dep } from "./deprecated.js"; +[|dep|]; +// @Filename: /deprecated.ts +export function dep() {}` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyBaselineHover(t) + f.VerifyCompletions(t, "importEquals", &fourslash.CompletionsExpectedList{ + IsIncomplete: false, + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &DefaultCommitCharacters, + EditRange: Ignored, + }, + Items: &fourslash.CompletionsExpectedItems{ + Includes: []fourslash.CompletionsExpectedItem{ + &lsproto.CompletionItem{ + Label: "fn", + Detail: new("(alias) function a.fn(): void"), + Documentation: &lsproto.StringOrMarkupContent{ + MarkupContent: &lsproto.MarkupContent{ + Kind: lsproto.MarkupKindMarkdown, + Value: "on the alias\non the declaration", + }, + }, + }, + }, + }, + }) + f.GoToFile(t, "/useDeprecated.ts") + f.VerifySuggestionDiagnostics(t, []*lsproto.Diagnostic{ + { + Code: &lsproto.IntegerOrString{Integer: new(int32(6385))}, + Message: lsproto.StringOrMarkupContent{String: new("'dep' is deprecated.")}, + Tags: &[]lsproto.DiagnosticTag{lsproto.DiagnosticTagDeprecated}, + Range: f.Ranges()[0].LSRange, + }, + }) +} diff --git a/tsc/internal/fourslash/tests/hoverImportEqualsAliasJSDoc_test.go b/tsc/internal/fourslash/tests/hoverImportEqualsAliasJSDoc_test.go deleted file mode 100644 index 12539258e8f24..0000000000000 --- a/tsc/internal/fourslash/tests/hoverImportEqualsAliasJSDoc_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package fourslash_test - -import ( - "testing" - - "github.com/microsoft/TypeScript/tsc/internal/fourslash" - . "github.com/microsoft/TypeScript/tsc/internal/fourslash/tests/util" - "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto" - "github.com/microsoft/TypeScript/tsc/internal/testutil" -) - -func TestHoverImportEqualsAliasJSDoc(t *testing.T) { - t.Parallel() - defer testutil.RecoverAndFail(t, "Panic on fourslash test") - const content = `declare namespace a { - /** on the declaration */ - export function fn(): void; - /** on the declaration */ - export const value: number; - /** on the declaration */ - export interface Shape {} - export function bare(): void; - /** on the declaration */ - export function tagged(): void; - /** on the declaration */ - export function plain(): void; -} -declare namespace b { - /** on the alias */ - export import fn = a.fn; - /** on the alias */ - export import value = a.value; - /** on the alias */ - export import Shape = a.Shape; - /** on the alias */ - export import bare = a.bare; - /** @deprecated on the alias */ - export import tagged = a.tagged; - export import plain = a.plain; -} -b./*fn*/fn; -b./*fnCall*/fn(); -b./*value*/value; -let shape: b./*Shape*/Shape; -b./*bare*/bare; -b./*bareCall*/bare(); -b./*tagged*/tagged; -b./*plain*/plain;` - f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) - defer done() - f.VerifyBaselineHover(t) - f.VerifyCompletions(t, "fn", &fourslash.CompletionsExpectedList{ - IsIncomplete: false, - ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ - CommitCharacters: &DefaultCommitCharacters, - EditRange: Ignored, - }, - Items: &fourslash.CompletionsExpectedItems{ - Includes: []fourslash.CompletionsExpectedItem{ - &lsproto.CompletionItem{ - Label: "fn", - Detail: new("(alias) function a.fn(): void"), - Documentation: &lsproto.StringOrMarkupContent{ - MarkupContent: &lsproto.MarkupContent{ - Kind: lsproto.MarkupKindMarkdown, - Value: "on the alias", - }, - }, - }, - }, - }, - }) -} diff --git a/tsc/internal/ls/hover.go b/tsc/internal/ls/hover.go index 67c3fe7efea3b..ab0c5f47e5930 100644 --- a/tsc/internal/ls/hover.go +++ b/tsc/internal/ls/hover.go @@ -205,87 +205,102 @@ func (l *LanguageService) documentationLocationMapper(feature spanmap.Feature) d } } -// getDocumentationForSymbol tries each documentation source in turn (call-signature documentation, -// an alias's own JSDoc, root-symbol JSDoc, declaration JSDoc, alias target JSDoc) and returns the -// first non-empty result, formatted for contentFormat. At a call site the alias's own JSDoc is tried -// after the declaration's instead. commentOnly restricts the result to the JSDoc summary, excluding -// the @tag section. +// getDocumentationForSymbol assembles the documentation shown for symbol at node. Every alias on +// the way from symbol to the implementation contributes the JSDoc written on its own declaration, +// outermost first, and the implementation contributes the first of: the resolved call or construct +// signature's declaration, the root symbols, declaration (the one quick info picked), the alias +// target's declaration. Descriptions are joined with a newline, then the tag sections follow in the +// same order, with exact duplicates dropped from both. node only decides which implementation +// declaration is picked, so a reference and a call to the same alias show the same lines. commentOnly +// leaves the tag sections out. func getDocumentationForSymbol(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, declaration *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { - callNode := getCallOrNewExpression(node) - documentation := documentationFromSignature(getMappedLocation, c, symbol, callNode, node, contentFormat, commentOnly) - if documentation != "" { - return documentation + var parts documentationParts + for _, aliasDeclaration := range aliasChainDeclarations(c, symbol) { + parts.add(getDocumentationPartsFromDeclaration(getMappedLocation, c, symbol, aliasDeclaration, node, contentFormat)) } - - // For an alias, declaration is the target's, and the alias's own JSDoc comes before it. At a - // call site declaration is the resolved signature's instead, and that comes first. - if callNode == nil { - documentation = documentationFromAliasDeclaration(getMappedLocation, c, symbol, node, contentFormat, commentOnly) - if documentation != "" { - return documentation - } + description, tags := documentationFromSignature(getMappedLocation, c, symbol, getCallOrNewExpression(node), node, contentFormat) + if description == "" && tags == "" { + description, tags = documentationFromRootSymbols(getMappedLocation, c, symbol, node, contentFormat) } + if description == "" && tags == "" { + description, tags = getDocumentationPartsFromDeclaration(getMappedLocation, c, symbol, declaration, node, contentFormat) + } + if description == "" && tags == "" { + description, tags = documentationFromAlias(getMappedLocation, c, symbol, node, contentFormat) + } + parts.add(description, tags) + return parts.join(commentOnly) +} - documentation = documentationFromRootSymbols(getMappedLocation, c, symbol, node, contentFormat, commentOnly) - if documentation != "" { - return documentation +// documentationParts collects the descriptions and tag sections of several declarations, keeping +// each distinct one once in the order it was added. +type documentationParts struct { + descriptions []string + tags []string +} + +func (p *documentationParts) add(description string, tags string) { + if description != "" { + p.descriptions = core.AppendIfUnique(p.descriptions, description) } + if tags != "" { + p.tags = core.AppendIfUnique(p.tags, tags) + } +} - documentation = getDocumentationFromDeclaration(getMappedLocation, c, symbol, declaration, node, contentFormat, commentOnly) - if documentation != "" { +func (p *documentationParts) join(commentOnly bool) string { + documentation := strings.Join(p.descriptions, "\n") + if commentOnly { return documentation } + return documentation + strings.Join(p.tags, "") +} - if callNode != nil { - documentation = documentationFromAliasDeclaration(getMappedLocation, c, symbol, node, contentFormat, commentOnly) - if documentation != "" { - return documentation +// aliasChainDeclarations returns the alias declaration of symbol and of each alias it resolves +// through, outermost first, up to the first symbol that is not an alias. +func aliasChainDeclarations(c *checker.Checker, symbol *ast.Symbol) []*ast.Node { + var declarations []*ast.Node + var visited collections.Set[*ast.Symbol] + for symbol != nil && symbol.Flags()&ast.SymbolFlagsAlias != 0 && visited.AddIfAbsent(symbol) { + declaration := c.GetDeclarationOfAliasSymbol(symbol) + if declaration == nil { + break + } + declarations = append(declarations, declaration) + symbol = c.GetImmediateAliasedSymbol(symbol) + if symbol == c.GetUnknownSymbol() { + break } } - - return documentationFromAlias(getMappedLocation, c, symbol, node, contentFormat, commentOnly) + return declarations } -func documentationFromSignature(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, location *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { +func documentationFromSignature(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, location *ast.Node, contentFormat lsproto.MarkupKind) (string, string) { if node == nil { - return "" + return "", "" } signature := c.GetResolvedSignature(node) if signature == nil { - return "" + return "", "" } declaration := signature.Declaration() if declaration == nil { - return "" + return "", "" } if ast.IsCallSignatureDeclaration(declaration) || ast.IsConstructSignatureDeclaration(declaration) { - return getDocumentationFromDeclaration(getMappedLocation, c, symbol, declaration, location, contentFormat, commentOnly) + return getDocumentationPartsFromDeclaration(getMappedLocation, c, symbol, declaration, location, contentFormat) } - return "" + return "", "" } -// documentationFromAliasDeclaration returns the JSDoc written on the alias itself, as on -// `/** ... */ import x = a.x`. A comment made only of tags leaves the target's documentation in -// place, so it returns nothing for one. -func documentationFromAliasDeclaration(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { - if symbol == nil || symbol.Flags&ast.SymbolFlagsAlias == 0 { - return "" - } - declaration := c.GetDeclarationOfAliasSymbol(symbol) - if getDocumentationFromDeclaration(getMappedLocation, c, symbol, declaration, node, contentFormat, true /*commentOnly*/) == "" { - return "" - } - return getDocumentationFromDeclaration(getMappedLocation, c, symbol, declaration, node, contentFormat, commentOnly) -} - -func documentationFromAlias(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { +func documentationFromAlias(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, contentFormat lsproto.MarkupKind) (string, string) { if symbol == nil || symbol.Flags()&ast.SymbolFlagsAlias == 0 { - return "" + return "", "" } aliasedSymbol := c.GetAliasedSymbol(symbol) if aliasedSymbol == nil || aliasedSymbol == c.GetUnknownSymbol() { - return "" + return "", "" } candidates := []*ast.Symbol{aliasedSymbol} @@ -299,25 +314,25 @@ func documentationFromAlias(getMappedLocation documentationLocationMapper, c *ch continue } - if documentation := getDocumentationFromDeclaration(getMappedLocation, c, candidate, aliasedDeclaration, node, contentFormat, commentOnly); documentation != "" { - return documentation + if description, tags := getDocumentationPartsFromDeclaration(getMappedLocation, c, candidate, aliasedDeclaration, node, contentFormat); description != "" || tags != "" { + return description, tags } } - return "" + return "", "" } -func documentationFromRootSymbols(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { +func documentationFromRootSymbols(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, contentFormat lsproto.MarkupKind) (string, string) { if symbol == nil { - return "" + return "", "" } rootSymbols := c.GetRootSymbols(symbol) if len(rootSymbols) <= 1 { - return "" + return "", "" } - var docs []string + var parts documentationParts for _, rootSymbol := range rootSymbols { if rootSymbol == nil { continue @@ -327,23 +342,31 @@ func documentationFromRootSymbols(getMappedLocation documentationLocationMapper, declarations = []*ast.Node{rootSymbol.ValueDeclaration()} } for _, declaration := range declarations { - if documentation := getDocumentationFromDeclaration(getMappedLocation, c, rootSymbol, declaration, node, contentFormat, commentOnly); documentation != "" { - docs = core.AppendIfUnique(docs, documentation) - } + parts.add(getDocumentationPartsFromDeclaration(getMappedLocation, c, rootSymbol, declaration, node, contentFormat)) } } - return strings.Join(docs, "\n") + return strings.Join(parts.descriptions, "\n"), strings.Join(parts.tags, "") } func getDocumentationFromDeclaration(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, declaration *ast.Node, location *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string { + description, tags := getDocumentationPartsFromDeclaration(getMappedLocation, c, symbol, declaration, location, contentFormat) + if commentOnly { + return description + } + return description + tags +} + +// getDocumentationPartsFromDeclaration renders the JSDoc on declaration as its description and its +// tag section, each empty when there is none. +func getDocumentationPartsFromDeclaration(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, declaration *ast.Node, location *ast.Node, contentFormat lsproto.MarkupKind) (string, string) { if declaration == nil { - return "" + return "", "" } isMarkdown := contentFormat == lsproto.MarkupKindMarkdown - var b strings.Builder + var description, b strings.Builder if jsdoc := getJSDocOrTag(c, declaration, &collections.Set[*ast.Symbol]{}); jsdoc != nil && !(declaration.Flags&ast.NodeFlagsReparsed == 0 && containsTypedefTag(jsdoc)) { - writeComments(getMappedLocation, &b, c, jsdoc.Comments(), isMarkdown) - if jsdoc.Kind == ast.KindJSDoc && !commentOnly { + writeComments(getMappedLocation, &description, c, jsdoc.Comments(), isMarkdown) + if jsdoc.Kind == ast.KindJSDoc { if tags := jsdoc.AsJSDoc().Tags; tags != nil { for _, tag := range tags.Nodes { if tag.Kind == ast.KindJSDocTypeTag || tag.Kind == ast.KindJSDocTypedefTag || tag.Kind == ast.KindJSDocCallbackTag { @@ -422,7 +445,7 @@ func getDocumentationFromDeclaration(getMappedLocation documentationLocationMapp } } } - return b.String() + return description.String(), b.String() } func formatQuickInfo(quickInfo string) string { diff --git a/tsc/internal/parser/parser.go b/tsc/internal/parser/parser.go index c670d64ad21c2..5aa73c1e817a4 100644 --- a/tsc/internal/parser/parser.go +++ b/tsc/internal/parser/parser.go @@ -2466,6 +2466,7 @@ func (p *Parser) parseNamedImports() *ast.Node { func (p *Parser) parseImportSpecifier() *ast.Node { pos := p.nodePos() + jsdoc := p.jsdocScannerInfo() isTypeOnly, propertyName, name := p.parseImportOrExportSpecifier(ast.KindImportSpecifier) var identifierName *ast.Node if name.Kind == ast.KindIdentifier { @@ -2475,7 +2476,9 @@ func (p *Parser) parseImportSpecifier() *ast.Node { identifierName = p.newIdentifier("") p.finishNode(identifierName, name.Pos()) } - result := p.checkJSSyntax(p.finishNode(p.factory.NewImportSpecifier(isTypeOnly, propertyName, identifierName), pos)) + result := p.finishNode(p.factory.NewImportSpecifier(isTypeOnly, propertyName, identifierName), pos) + p.withJSDoc(result, jsdoc) + p.checkJSSyntax(result) return result } diff --git a/tsc/internal/parser/parser_test.go b/tsc/internal/parser/parser_test.go index ffa96713eb444..2d0201e7a0010 100644 --- a/tsc/internal/parser/parser_test.go +++ b/tsc/internal/parser/parser_test.go @@ -460,6 +460,41 @@ const value = 0;` } } +func TestImportSpecifierJSDoc(t *testing.T) { + t.Parallel() + sourceText := `import { /** a */ a, + /** b */ b } from "m"` + tests := []struct { + fileName tspath.RootedFilePath + pathKey tspath.PathKey + scriptKind core.ScriptKind + }{ + {fileName: "/index.ts", pathKey: "/index.ts", scriptKind: core.ScriptKindTS}, + {fileName: "/index.js", pathKey: "/index.js", scriptKind: core.ScriptKindJS}, + } + for _, test := range tests { + t.Run(string(test.fileName), func(t *testing.T) { + t.Parallel() + opts := ast.SourceFileParseOptions{ + FileName: test.fileName, + PathKey: test.pathKey, + } + file := parser.ParseSourceFile(opts, sourceText, test.scriptKind) + importDeclaration := file.Statements.Nodes[0] + assert.Assert(t, ast.IsImportDeclaration(importDeclaration)) + assert.Equal(t, len(importDeclaration.JSDoc(file)), 0) + + specifiers := importDeclaration.AsImportDeclaration().ImportClause.AsImportClause().NamedBindings.AsNamedImports().Elements.Nodes + assert.Equal(t, len(specifiers), 2) + for i, expected := range []string{"a", "b"} { + jsDocs := specifiers[i].JSDoc(file) + assert.Equal(t, len(jsDocs), 1) + assert.Equal(t, scanner.GetTextOfJSDocComment(jsDocs[0].AsJSDoc().Comment), expected) + } + }) + } +} + func TestJSDocTypeSourcePropagatesToConstructedReparse(t *testing.T) { t.Parallel() sourceText := `/** diff --git a/tsc/internal/parser/utilities.go b/tsc/internal/parser/utilities.go index 48a4ecf7ed3cb..87aba361f7c7d 100644 --- a/tsc/internal/parser/utilities.go +++ b/tsc/internal/parser/utilities.go @@ -27,7 +27,7 @@ func tokenIsIdentifierOrKeywordOrGreaterThan(token ast.Kind) bool { func GetJSDocCommentRanges(f *ast.NodeFactory, commentRanges []ast.CommentRange, node *ast.Node, text string) []ast.CommentRange { switch node.Kind { - case ast.KindParameter, ast.KindTypeParameter, ast.KindFunctionExpression, ast.KindArrowFunction, ast.KindParenthesizedExpression, ast.KindVariableDeclaration, ast.KindExportSpecifier: + case ast.KindParameter, ast.KindTypeParameter, ast.KindFunctionExpression, ast.KindArrowFunction, ast.KindParenthesizedExpression, ast.KindVariableDeclaration, ast.KindImportSpecifier, ast.KindExportSpecifier: for commentRange := range scanner.GetTrailingCommentRanges(f, text, node.Pos()) { commentRanges = append(commentRanges, commentRange) } diff --git a/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverAliasChainJSDocShapes.baseline b/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverAliasChainJSDocShapes.baseline new file mode 100644 index 0000000000000..aab779921497d --- /dev/null +++ b/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverAliasChainJSDocShapes.baseline @@ -0,0 +1,584 @@ +// === QuickInfo === +=== /middleHop.ts === +// import { +// /** Doc on import alias */ +// foo +// } from "./uncommentedReexport.js"; +// foo(); +// ^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function foo(): void +// | ``` +// | Doc on import alias +// | Doc on impl +// | ---------------------------------------------------------------------- +// foo; +// ^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function foo(): void +// | ``` +// | Doc on import alias +// | Doc on impl +// | ---------------------------------------------------------------------- +=== /namespaceImport.ts === +// import * as m from "./main.js"; +// m.bar(); +// ^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function foo(): void +// | ``` +// | Doc on export import alias +// | Doc on impl +// | ---------------------------------------------------------------------- +// m.bar; +// ^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function foo(): void +// | ``` +// | Doc on export import alias +// | Doc on impl +// | ---------------------------------------------------------------------- + + +=== /namespaces.ts === +// declare namespace a { +// /** on the declaration */ +// export function fn(): void; +// /** on the declaration */ +// export interface S {} +// /** Same */ +// export function same(): void; +// /** on the declaration */ +// export function tagged(): void; +// } +// declare namespace b { +// /** on the alias */ +// export import fn = a.fn; +// export namespace S {} +// /** on the alias */ +// export import S = a.S; +// /** Same */ +// export import same = a.same; +// /** @deprecated on the alias */ +// export import tagged = a.tagged; +// } +// b.fn(); +// ^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function a.fn(): void +// | ``` +// | on the alias +// | on the declaration +// | ---------------------------------------------------------------------- +// b.fn; +// ^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function a.fn(): void +// | ``` +// | on the alias +// | on the declaration +// | ---------------------------------------------------------------------- +// let s: b.S; +// ^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) interface a.S +// | ``` +// | on the alias +// | on the declaration +// | ---------------------------------------------------------------------- +// b.same(); +// ^^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function a.same(): void +// | ``` +// | Same +// | ---------------------------------------------------------------------- +// b.same; +// ^^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function a.same(): void +// | ``` +// | Same +// | ---------------------------------------------------------------------- +// b.tagged(); +// ^^^^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function a.tagged(): void +// | ``` +// | on the declaration +// | +// | *@deprecated* — on the alias +// | ---------------------------------------------------------------------- +// b.tagged; +// ^^^^^^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function a.tagged(): void +// | ``` +// | on the declaration +// | +// | *@deprecated* — on the alias +// | ---------------------------------------------------------------------- + + +=== /useDefault.ts === +// /** before import */ +// import d from "./impl.js"; +// d(); +// ^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function d(): void +// | ``` +// | on impl +// | ---------------------------------------------------------------------- +// d; +// ^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function d(): void +// | ``` +// | on impl +// | ---------------------------------------------------------------------- + + +=== /useReexport.ts === +// import { g } from "./reexport.js"; +// g(); +// ^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function f(): void +// | ``` +// | on re-export +// | on impl +// | ---------------------------------------------------------------------- +// g; +// ^ +// | ---------------------------------------------------------------------- +// | ```typescript +// | (alias) function f(): void +// | ``` +// | on re-export +// | on impl +// | ---------------------------------------------------------------------- + + +[ + { + "marker": { + "Position": 34, + "LSPosition": { + "line": 1, + "character": 2 + }, + "Name": "namespaceImportCall", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function foo(): void\n```\nDoc on export import alias\nDoc on impl" + }, + "range": { + "start": { + "line": 1, + "character": 2 + }, + "end": { + "line": 1, + "character": 5 + } + } + } + }, + { + "marker": { + "Position": 43, + "LSPosition": { + "line": 2, + "character": 2 + }, + "Name": "namespaceImport", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function foo(): void\n```\nDoc on export import alias\nDoc on impl" + }, + "range": { + "start": { + "line": 2, + "character": 2 + }, + "end": { + "line": 2, + "character": 5 + } + } + } + }, + { + "marker": { + "Position": 83, + "LSPosition": { + "line": 4, + "character": 0 + }, + "Name": "middleHopCall", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function foo(): void\n```\nDoc on import alias\nDoc on impl" + }, + "range": { + "start": { + "line": 4, + "character": 0 + }, + "end": { + "line": 4, + "character": 3 + } + } + } + }, + { + "marker": { + "Position": 90, + "LSPosition": { + "line": 5, + "character": 0 + }, + "Name": "middleHop", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function foo(): void\n```\nDoc on import alias\nDoc on impl" + }, + "range": { + "start": { + "line": 5, + "character": 0 + }, + "end": { + "line": 5, + "character": 3 + } + } + } + }, + { + "marker": { + "Position": 35, + "LSPosition": { + "line": 1, + "character": 0 + }, + "Name": "reexportCall", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function f(): void\n```\non re-export\non impl" + }, + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 1 + } + } + } + }, + { + "marker": { + "Position": 40, + "LSPosition": { + "line": 2, + "character": 0 + }, + "Name": "reexport", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function f(): void\n```\non re-export\non impl" + }, + "range": { + "start": { + "line": 2, + "character": 0 + }, + "end": { + "line": 2, + "character": 1 + } + } + } + }, + { + "marker": { + "Position": 48, + "LSPosition": { + "line": 2, + "character": 0 + }, + "Name": "defaultImportCall", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function d(): void\n```\non impl" + }, + "range": { + "start": { + "line": 2, + "character": 0 + }, + "end": { + "line": 2, + "character": 1 + } + } + } + }, + { + "marker": { + "Position": 53, + "LSPosition": { + "line": 3, + "character": 0 + }, + "Name": "defaultImport", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function d(): void\n```\non impl" + }, + "range": { + "start": { + "line": 3, + "character": 0 + }, + "end": { + "line": 3, + "character": 1 + } + } + } + }, + { + "marker": { + "Position": 536, + "LSPosition": { + "line": 21, + "character": 2 + }, + "Name": "importEqualsCall", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function a.fn(): void\n```\non the alias\non the declaration" + }, + "range": { + "start": { + "line": 21, + "character": 2 + }, + "end": { + "line": 21, + "character": 4 + } + } + } + }, + { + "marker": { + "Position": 544, + "LSPosition": { + "line": 22, + "character": 2 + }, + "Name": "importEquals", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function a.fn(): void\n```\non the alias\non the declaration" + }, + "range": { + "start": { + "line": 22, + "character": 2 + }, + "end": { + "line": 22, + "character": 4 + } + } + } + }, + { + "marker": { + "Position": 557, + "LSPosition": { + "line": 23, + "character": 9 + }, + "Name": "merged", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) interface a.S\n```\non the alias\non the declaration" + }, + "range": { + "start": { + "line": 23, + "character": 9 + }, + "end": { + "line": 23, + "character": 10 + } + }, + "canIncreaseVerbosity": true + } + }, + { + "marker": { + "Position": 562, + "LSPosition": { + "line": 24, + "character": 2 + }, + "Name": "sameTextCall", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function a.same(): void\n```\nSame" + }, + "range": { + "start": { + "line": 24, + "character": 2 + }, + "end": { + "line": 24, + "character": 6 + } + } + } + }, + { + "marker": { + "Position": 572, + "LSPosition": { + "line": 25, + "character": 2 + }, + "Name": "sameText", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function a.same(): void\n```\nSame" + }, + "range": { + "start": { + "line": 25, + "character": 2 + }, + "end": { + "line": 25, + "character": 6 + } + } + } + }, + { + "marker": { + "Position": 580, + "LSPosition": { + "line": 26, + "character": 2 + }, + "Name": "tagOnlyCall", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function a.tagged(): void\n```\non the declaration\n\n*@deprecated* — on the alias" + }, + "range": { + "start": { + "line": 26, + "character": 2 + }, + "end": { + "line": 26, + "character": 8 + } + } + } + }, + { + "marker": { + "Position": 592, + "LSPosition": { + "line": 27, + "character": 2 + }, + "Name": "tagOnly", + "Data": {} + }, + "item": { + "contents": { + "kind": "markdown", + "value": "```typescript\n(alias) function a.tagged(): void\n```\non the declaration\n\n*@deprecated* — on the alias" + }, + "range": { + "start": { + "line": 27, + "character": 2 + }, + "end": { + "line": 27, + "character": 8 + } + } + } + } +] \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverImportEqualsAliasJSDoc.baseline b/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverImportEqualsAliasJSDoc.baseline deleted file mode 100644 index 26827768b9a57..0000000000000 --- a/tsc/testdata/baselines/reference/fourslash/quickInfo/hoverImportEqualsAliasJSDoc.baseline +++ /dev/null @@ -1,311 +0,0 @@ -// === QuickInfo === -=== /hoverImportEqualsAliasJSDoc.ts === -// declare namespace a { -// /** on the declaration */ -// export function fn(): void; -// /** on the declaration */ -// export const value: number; -// /** on the declaration */ -// export interface Shape {} -// export function bare(): void; -// /** on the declaration */ -// export function tagged(): void; -// /** on the declaration */ -// export function plain(): void; -// } -// declare namespace b { -// /** on the alias */ -// export import fn = a.fn; -// /** on the alias */ -// export import value = a.value; -// /** on the alias */ -// export import Shape = a.Shape; -// /** on the alias */ -// export import bare = a.bare; -// /** @deprecated on the alias */ -// export import tagged = a.tagged; -// export import plain = a.plain; -// } -// b.fn; -// ^^ -// | ---------------------------------------------------------------------- -// | ```typescript -// | (alias) function a.fn(): void -// | ``` -// | on the alias -// | ---------------------------------------------------------------------- -// b.fn(); -// ^^ -// | ---------------------------------------------------------------------- -// | ```typescript -// | (alias) function a.fn(): void -// | ``` -// | on the declaration -// | ---------------------------------------------------------------------- -// b.value; -// ^^^^^ -// | ---------------------------------------------------------------------- -// | ```typescript -// | (alias) const a.value: number -// | ``` -// | on the alias -// | ---------------------------------------------------------------------- -// let shape: b.Shape; -// ^^^^^ -// | ---------------------------------------------------------------------- -// | ```typescript -// | (alias) interface a.Shape -// | ``` -// | on the alias -// | ---------------------------------------------------------------------- -// b.bare; -// ^^^^ -// | ---------------------------------------------------------------------- -// | ```typescript -// | (alias) function a.bare(): void -// | ``` -// | on the alias -// | ---------------------------------------------------------------------- -// b.bare(); -// ^^^^ -// | ---------------------------------------------------------------------- -// | ```typescript -// | (alias) function a.bare(): void -// | ``` -// | on the alias -// | ---------------------------------------------------------------------- -// b.tagged; -// ^^^^^^ -// | ---------------------------------------------------------------------- -// | ```typescript -// | (alias) function a.tagged(): void -// | ``` -// | on the declaration -// | ---------------------------------------------------------------------- -// b.plain; -// ^^^^^ -// | ---------------------------------------------------------------------- -// | ```typescript -// | (alias) function a.plain(): void -// | ``` -// | on the declaration -// | ---------------------------------------------------------------------- -[ - { - "marker": { - "Position": 735, - "LSPosition": { - "line": 26, - "character": 2 - }, - "Name": "fn", - "Data": {} - }, - "item": { - "contents": { - "kind": "markdown", - "value": "```typescript\n(alias) function a.fn(): void\n```\non the alias" - }, - "range": { - "start": { - "line": 26, - "character": 2 - }, - "end": { - "line": 26, - "character": 4 - } - } - } - }, - { - "marker": { - "Position": 741, - "LSPosition": { - "line": 27, - "character": 2 - }, - "Name": "fnCall", - "Data": {} - }, - "item": { - "contents": { - "kind": "markdown", - "value": "```typescript\n(alias) function a.fn(): void\n```\non the declaration" - }, - "range": { - "start": { - "line": 27, - "character": 2 - }, - "end": { - "line": 27, - "character": 4 - } - } - } - }, - { - "marker": { - "Position": 749, - "LSPosition": { - "line": 28, - "character": 2 - }, - "Name": "value", - "Data": {} - }, - "item": { - "contents": { - "kind": "markdown", - "value": "```typescript\n(alias) const a.value: number\n```\non the alias" - }, - "range": { - "start": { - "line": 28, - "character": 2 - }, - "end": { - "line": 28, - "character": 7 - } - } - } - }, - { - "marker": { - "Position": 769, - "LSPosition": { - "line": 29, - "character": 13 - }, - "Name": "Shape", - "Data": {} - }, - "item": { - "contents": { - "kind": "markdown", - "value": "```typescript\n(alias) interface a.Shape\n```\non the alias" - }, - "range": { - "start": { - "line": 29, - "character": 13 - }, - "end": { - "line": 29, - "character": 18 - } - }, - "canIncreaseVerbosity": true - } - }, - { - "marker": { - "Position": 778, - "LSPosition": { - "line": 30, - "character": 2 - }, - "Name": "bare", - "Data": {} - }, - "item": { - "contents": { - "kind": "markdown", - "value": "```typescript\n(alias) function a.bare(): void\n```\non the alias" - }, - "range": { - "start": { - "line": 30, - "character": 2 - }, - "end": { - "line": 30, - "character": 6 - } - } - } - }, - { - "marker": { - "Position": 786, - "LSPosition": { - "line": 31, - "character": 2 - }, - "Name": "bareCall", - "Data": {} - }, - "item": { - "contents": { - "kind": "markdown", - "value": "```typescript\n(alias) function a.bare(): void\n```\non the alias" - }, - "range": { - "start": { - "line": 31, - "character": 2 - }, - "end": { - "line": 31, - "character": 6 - } - } - } - }, - { - "marker": { - "Position": 796, - "LSPosition": { - "line": 32, - "character": 2 - }, - "Name": "tagged", - "Data": {} - }, - "item": { - "contents": { - "kind": "markdown", - "value": "```typescript\n(alias) function a.tagged(): void\n```\non the declaration" - }, - "range": { - "start": { - "line": 32, - "character": 2 - }, - "end": { - "line": 32, - "character": 8 - } - } - } - }, - { - "marker": { - "Position": 806, - "LSPosition": { - "line": 33, - "character": 2 - }, - "Name": "plain", - "Data": {} - }, - "item": { - "contents": { - "kind": "markdown", - "value": "```typescript\n(alias) function a.plain(): void\n```\non the declaration" - }, - "range": { - "start": { - "line": 33, - "character": 2 - }, - "end": { - "line": 33, - "character": 7 - } - } - } - } -] \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/fourslash/quickInfo/quickInfoAlias.baseline b/tsc/testdata/baselines/reference/fourslash/quickInfo/quickInfoAlias.baseline index d7147de14dac8..2d5c9a4a20fe2 100644 --- a/tsc/testdata/baselines/reference/fourslash/quickInfo/quickInfoAlias.baseline +++ b/tsc/testdata/baselines/reference/fourslash/quickInfo/quickInfoAlias.baseline @@ -29,8 +29,11 @@ // | ```typescript // | (alias) const x: 0 // | ``` +// | Doc 3 // | Doc // | +// | *@tag* — Tag text 3 +// | // | *@tag* — Tag text // | ---------------------------------------------------------------------- @@ -76,7 +79,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```typescript\n(alias) const x: 0\n```\nDoc\n\n*@tag* — Tag text" + "value": "```typescript\n(alias) const x: 0\n```\nDoc 3\nDoc\n\n*@tag* — Tag text 3\n\n*@tag* — Tag text" }, "range": { "start": { diff --git a/tsc/testdata/baselines/reference/fourslash/vSQuickInfo/quickInfoAliasVS.baseline b/tsc/testdata/baselines/reference/fourslash/vSQuickInfo/quickInfoAliasVS.baseline index 656d7e854cb99..9fba95bdc0eed 100644 --- a/tsc/testdata/baselines/reference/fourslash/vSQuickInfo/quickInfoAliasVS.baseline +++ b/tsc/testdata/baselines/reference/fourslash/vSQuickInfo/quickInfoAliasVS.baseline @@ -45,7 +45,7 @@ // | [punctuation] ": " // | [string] "0" // | ClassifiedTextElement -// | [text] "Doc" +// | [text] "Doc 3\nDoc" // | ---------------------------------------------------------------------- @@ -160,7 +160,7 @@ "item": { "contents": { "kind": "markdown", - "value": "```typescript\n(alias) const x: 0\n```\nDoc\n\n*@tag* — Tag text" + "value": "```typescript\n(alias) const x: 0\n```\nDoc 3\nDoc\n\n*@tag* — Tag text 3\n\n*@tag* — Tag text" }, "range": { "start": { @@ -233,7 +233,7 @@ "Runs": [ { "ClassificationTypeName": "text", - "Text": "Doc", + "Text": "Doc 3\nDoc", "_vs_type": "ClassifiedTextRun" } ],