Skip to content

Commit 997a83d

Browse files
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.
1 parent a1ef42b commit 997a83d

3 files changed

Lines changed: 322 additions & 3 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package fourslash_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/microsoft/TypeScript/tsc/internal/fourslash"
7+
. "github.com/microsoft/TypeScript/tsc/internal/fourslash/tests/util"
8+
"github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto"
9+
"github.com/microsoft/TypeScript/tsc/internal/testutil"
10+
)
11+
12+
func TestHoverImportEqualsAliasJSDoc(t *testing.T) {
13+
t.Parallel()
14+
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
15+
const content = `declare namespace a {
16+
/** on the declaration */
17+
export function fn(): void;
18+
/** on the declaration */
19+
export const value: number;
20+
/** on the declaration */
21+
export interface Shape {}
22+
export function bare(): void;
23+
}
24+
declare namespace b {
25+
/** on the alias */
26+
export import fn = a.fn;
27+
/** on the alias */
28+
export import value = a.value;
29+
/** on the alias */
30+
export import Shape = a.Shape;
31+
/** on the alias */
32+
export import bare = a.bare;
33+
}
34+
b./*fn*/fn;
35+
b./*fnCall*/fn();
36+
b./*value*/value;
37+
let shape: b./*Shape*/Shape;
38+
b./*bare*/bare;
39+
b./*bareCall*/bare();`
40+
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
41+
defer done()
42+
f.VerifyBaselineHover(t)
43+
f.VerifyCompletions(t, "fn", &fourslash.CompletionsExpectedList{
44+
IsIncomplete: false,
45+
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
46+
CommitCharacters: &DefaultCommitCharacters,
47+
EditRange: Ignored,
48+
},
49+
Items: &fourslash.CompletionsExpectedItems{
50+
Includes: []fourslash.CompletionsExpectedItem{
51+
&lsproto.CompletionItem{
52+
Label: "fn",
53+
Detail: new("(alias) function a.fn(): void"),
54+
Documentation: &lsproto.StringOrMarkupContent{
55+
MarkupContent: &lsproto.MarkupContent{
56+
Kind: lsproto.MarkupKindMarkdown,
57+
Value: "on the alias",
58+
},
59+
},
60+
},
61+
},
62+
},
63+
})
64+
}

‎tsc/internal/ls/hover.go‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,10 @@ func (l *LanguageService) getQuickInfoAndDocumentationForSymbol(c *checker.Check
198198
}
199199

200200
// getDocumentationForSymbol tries each documentation source in turn (call-signature documentation,
201-
// declaration JSDoc, root-symbol JSDoc, alias target JSDoc) and returns the first non-empty result,
202-
// formatted for contentFormat. commentOnly restricts the result to the JSDoc summary, excluding the
203-
// @tag section.
201+
// an alias's own JSDoc, root-symbol JSDoc, declaration JSDoc, alias target JSDoc) and returns the
202+
// first non-empty result, formatted for contentFormat. At a call site the alias's own JSDoc is tried
203+
// after the declaration's instead. commentOnly restricts the result to the JSDoc summary, excluding
204+
// the @tag section.
204205
type documentationLocationMapper func(*ast.SourceFile, core.TextRange) (lsproto.Location, spanmap.Fidelity)
205206

206207
func (l *LanguageService) documentationLocationMapper(feature spanmap.Feature) documentationLocationMapper {
@@ -215,6 +216,13 @@ func getDocumentationForSymbol(getMappedLocation documentationLocationMapper, c
215216
return documentation
216217
}
217218

219+
// For an alias, declaration is the target's, and the alias's own JSDoc comes before it. At a
220+
// call site declaration is the resolved signature's instead, and that comes first.
221+
aliasDocumentation := documentationFromAliasDeclaration(getMappedLocation, c, symbol, node, contentFormat, commentOnly)
222+
if aliasDocumentation != "" && getCallOrNewExpression(node) == nil {
223+
return aliasDocumentation
224+
}
225+
218226
documentation = documentationFromRootSymbols(getMappedLocation, c, symbol, node, contentFormat, commentOnly)
219227
if documentation != "" {
220228
return documentation
@@ -225,6 +233,10 @@ func getDocumentationForSymbol(getMappedLocation documentationLocationMapper, c
225233
return documentation
226234
}
227235

236+
if aliasDocumentation != "" {
237+
return aliasDocumentation
238+
}
239+
228240
return documentationFromAlias(getMappedLocation, c, symbol, node, contentFormat, commentOnly)
229241
}
230242

@@ -246,6 +258,15 @@ func documentationFromSignature(getMappedLocation documentationLocationMapper, c
246258
return ""
247259
}
248260

261+
// documentationFromAliasDeclaration returns the JSDoc written on the alias itself, as on
262+
// `/** ... */ import x = a.x`.
263+
func documentationFromAliasDeclaration(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string {
264+
if symbol == nil || symbol.Flags&ast.SymbolFlagsAlias == 0 {
265+
return ""
266+
}
267+
return getDocumentationFromDeclaration(getMappedLocation, c, symbol, core.FirstOrNil(symbol.Declarations), node, contentFormat, commentOnly)
268+
}
269+
249270
func documentationFromAlias(getMappedLocation documentationLocationMapper, c *checker.Checker, symbol *ast.Symbol, node *ast.Node, contentFormat lsproto.MarkupKind, commentOnly bool) string {
250271
if symbol == nil || symbol.Flags&ast.SymbolFlagsAlias == 0 {
251272
return ""
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// === QuickInfo ===
2+
=== /hoverImportEqualsAliasJSDoc.ts ===
3+
// declare namespace a {
4+
// /** on the declaration */
5+
// export function fn(): void;
6+
// /** on the declaration */
7+
// export const value: number;
8+
// /** on the declaration */
9+
// export interface Shape {}
10+
// export function bare(): void;
11+
// }
12+
// declare namespace b {
13+
// /** on the alias */
14+
// export import fn = a.fn;
15+
// /** on the alias */
16+
// export import value = a.value;
17+
// /** on the alias */
18+
// export import Shape = a.Shape;
19+
// /** on the alias */
20+
// export import bare = a.bare;
21+
// }
22+
// b.fn;
23+
// ^^
24+
// | ----------------------------------------------------------------------
25+
// | ```typescript
26+
// | (alias) function a.fn(): void
27+
// | ```
28+
// | on the alias
29+
// | ----------------------------------------------------------------------
30+
// b.fn();
31+
// ^^
32+
// | ----------------------------------------------------------------------
33+
// | ```typescript
34+
// | (alias) function a.fn(): void
35+
// | ```
36+
// | on the declaration
37+
// | ----------------------------------------------------------------------
38+
// b.value;
39+
// ^^^^^
40+
// | ----------------------------------------------------------------------
41+
// | ```typescript
42+
// | (alias) const a.value: number
43+
// | ```
44+
// | on the alias
45+
// | ----------------------------------------------------------------------
46+
// let shape: b.Shape;
47+
// ^^^^^
48+
// | ----------------------------------------------------------------------
49+
// | ```typescript
50+
// | (alias) interface a.Shape
51+
// | ```
52+
// | on the alias
53+
// | ----------------------------------------------------------------------
54+
// b.bare;
55+
// ^^^^
56+
// | ----------------------------------------------------------------------
57+
// | ```typescript
58+
// | (alias) function a.bare(): void
59+
// | ```
60+
// | on the alias
61+
// | ----------------------------------------------------------------------
62+
// b.bare();
63+
// ^^^^
64+
// | ----------------------------------------------------------------------
65+
// | ```typescript
66+
// | (alias) function a.bare(): void
67+
// | ```
68+
// | on the alias
69+
// | ----------------------------------------------------------------------
70+
[
71+
{
72+
"marker": {
73+
"Position": 496,
74+
"LSPosition": {
75+
"line": 19,
76+
"character": 2
77+
},
78+
"Name": "fn",
79+
"Data": {}
80+
},
81+
"item": {
82+
"contents": {
83+
"kind": "markdown",
84+
"value": "```typescript\n(alias) function a.fn(): void\n```\non the alias"
85+
},
86+
"range": {
87+
"start": {
88+
"line": 19,
89+
"character": 2
90+
},
91+
"end": {
92+
"line": 19,
93+
"character": 4
94+
}
95+
}
96+
}
97+
},
98+
{
99+
"marker": {
100+
"Position": 502,
101+
"LSPosition": {
102+
"line": 20,
103+
"character": 2
104+
},
105+
"Name": "fnCall",
106+
"Data": {}
107+
},
108+
"item": {
109+
"contents": {
110+
"kind": "markdown",
111+
"value": "```typescript\n(alias) function a.fn(): void\n```\non the declaration"
112+
},
113+
"range": {
114+
"start": {
115+
"line": 20,
116+
"character": 2
117+
},
118+
"end": {
119+
"line": 20,
120+
"character": 4
121+
}
122+
}
123+
}
124+
},
125+
{
126+
"marker": {
127+
"Position": 510,
128+
"LSPosition": {
129+
"line": 21,
130+
"character": 2
131+
},
132+
"Name": "value",
133+
"Data": {}
134+
},
135+
"item": {
136+
"contents": {
137+
"kind": "markdown",
138+
"value": "```typescript\n(alias) const a.value: number\n```\non the alias"
139+
},
140+
"range": {
141+
"start": {
142+
"line": 21,
143+
"character": 2
144+
},
145+
"end": {
146+
"line": 21,
147+
"character": 7
148+
}
149+
}
150+
}
151+
},
152+
{
153+
"marker": {
154+
"Position": 530,
155+
"LSPosition": {
156+
"line": 22,
157+
"character": 13
158+
},
159+
"Name": "Shape",
160+
"Data": {}
161+
},
162+
"item": {
163+
"contents": {
164+
"kind": "markdown",
165+
"value": "```typescript\n(alias) interface a.Shape\n```\non the alias"
166+
},
167+
"range": {
168+
"start": {
169+
"line": 22,
170+
"character": 13
171+
},
172+
"end": {
173+
"line": 22,
174+
"character": 18
175+
}
176+
},
177+
"canIncreaseVerbosity": true
178+
}
179+
},
180+
{
181+
"marker": {
182+
"Position": 539,
183+
"LSPosition": {
184+
"line": 23,
185+
"character": 2
186+
},
187+
"Name": "bare",
188+
"Data": {}
189+
},
190+
"item": {
191+
"contents": {
192+
"kind": "markdown",
193+
"value": "```typescript\n(alias) function a.bare(): void\n```\non the alias"
194+
},
195+
"range": {
196+
"start": {
197+
"line": 23,
198+
"character": 2
199+
},
200+
"end": {
201+
"line": 23,
202+
"character": 6
203+
}
204+
}
205+
}
206+
},
207+
{
208+
"marker": {
209+
"Position": 547,
210+
"LSPosition": {
211+
"line": 24,
212+
"character": 2
213+
},
214+
"Name": "bareCall",
215+
"Data": {}
216+
},
217+
"item": {
218+
"contents": {
219+
"kind": "markdown",
220+
"value": "```typescript\n(alias) function a.bare(): void\n```\non the alias"
221+
},
222+
"range": {
223+
"start": {
224+
"line": 24,
225+
"character": 2
226+
},
227+
"end": {
228+
"line": 24,
229+
"character": 6
230+
}
231+
}
232+
}
233+
}
234+
]

0 commit comments

Comments
 (0)