Skip to content

Commit 5853d40

Browse files
ahejlsbergCopilot
authored andcommitted
Multiple improvements to Quick Info (#2627)
1 parent 48f957f commit 5853d40

15 files changed

Lines changed: 208 additions & 186 deletions

internal/fourslash/_scripts/failingTests.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ TestAllowLateBoundSymbolsOverwriteEarlyBoundSymbols
33
TestAmbientShorthandGotoDefinition
44
TestArgumentsAreAvailableAfterEditsAtEndOfFunction
55
TestAugmentedTypesClass1
6-
TestAugmentedTypesClass3Fourslash
76
TestAutoFormattingOnPasting
87
TestAutoImportAllowImportingTsExtensionsPackageJsonImports1
98
TestAutoImportCompletionAmbientMergedModule1

internal/fourslash/_scripts/manualTests.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
augmentedTypesModule2
12
autoCloseFragment
23
autoCloseTag
34
autoImportPackageRootPathTypeModule

internal/fourslash/tests/gen/augmentedTypesModule2_test.go renamed to internal/fourslash/tests/manual/augmentedTypesModule2_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
)
1010

1111
func TestAugmentedTypesModule2(t *testing.T) {
12-
fourslash.SkipIfFailing(t)
1312
t.Parallel()
1413
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
1514
const content = `function /*11*/m2f(x: number) { };
@@ -18,7 +17,7 @@ var x: m2f./*1*/
1817
var /*2*/r = m2f/*3*/;`
1918
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
2019
defer done()
21-
f.VerifyQuickInfoAt(t, "11", "function m2f(x: number): void\nnamespace m2f", "")
20+
f.VerifyQuickInfoAt(t, "11", "function m2f(x: number): void", "")
2221
f.VerifyCompletions(t, "1", &fourslash.CompletionsExpectedList{
2322
IsIncomplete: false,
2423
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{

internal/ls/hover.go

Lines changed: 138 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,6 @@ func (l *LanguageService) ProvideHover(ctx context.Context, documentURI lsproto.
6161
}
6262

6363
func (l *LanguageService) getQuickInfoAndDocumentationForSymbol(c *checker.Checker, symbol *ast.Symbol, node *ast.Node, contentFormat lsproto.MarkupKind) (string, string) {
64-
if symbol == nil {
65-
return "", ""
66-
}
6764
quickInfo, declaration := getQuickInfoAndDeclarationAtLocation(c, symbol, node)
6865
if quickInfo == "" {
6966
return "", ""
@@ -175,29 +172,87 @@ func formatQuickInfo(quickInfo string) string {
175172
}
176173

177174
func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol, node *ast.Node) (string, *ast.Node) {
178-
var b strings.Builder
179-
var visitedAliases collections.Set[*ast.Symbol]
180175
container := getContainerNode(node)
181-
if node.Kind == ast.KindThisKeyword && ast.IsInExpressionContext(node) {
176+
if node.Kind == ast.KindThisKeyword && ast.IsInExpressionContext(node) || ast.IsThisInTypeQuery(node) {
182177
return c.TypeToStringEx(c.GetTypeAtLocation(node), container, typeFormatFlags), nil
183178
}
184-
writeSymbolMeaning := func(symbol *ast.Symbol, meaning ast.SymbolFlags, isAlias bool) *ast.Node {
185-
flags := symbol.Flags & meaning
186-
if flags == 0 {
187-
return nil
188-
}
189-
declaration := symbol.ValueDeclaration
190-
if flags&ast.SymbolFlagsProperty != 0 && declaration != nil && ast.IsMethodDeclaration(declaration) {
191-
flags = ast.SymbolFlagsMethod
192-
}
179+
if symbol == nil {
180+
return "", nil
181+
}
182+
var b strings.Builder
183+
var visitedAliases collections.Set[*ast.Symbol]
184+
var aliasLevel int
185+
writeNewLine := func() {
193186
if b.Len() != 0 {
194187
b.WriteString("\n")
195188
}
196-
if isAlias {
189+
if aliasLevel != 0 {
197190
b.WriteString("(alias) ")
198191
}
199-
switch {
200-
case flags&(ast.SymbolFlagsVariable|ast.SymbolFlagsProperty|ast.SymbolFlagsAccessor) != 0:
192+
}
193+
writeSignatures := func(signatures []*checker.Signature, prefix string, symbol *ast.Symbol) {
194+
for i, sig := range signatures {
195+
writeNewLine()
196+
if i == 3 && len(signatures) >= 5 {
197+
b.WriteString(fmt.Sprintf("// +%v more overloads", len(signatures)-3))
198+
break
199+
}
200+
b.WriteString(prefix)
201+
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
202+
b.WriteString(c.SignatureToStringEx(sig, container, typeFormatFlags|checker.TypeFormatFlagsWriteCallStyleSignature|checker.TypeFormatFlagsWriteTypeArgumentsOfSignature))
203+
}
204+
}
205+
writeTypeParams := func(params []*checker.Type) {
206+
if len(params) > 0 {
207+
b.WriteString("<")
208+
for i, tp := range params {
209+
if i != 0 {
210+
b.WriteString(", ")
211+
}
212+
b.WriteString(c.SymbolToStringEx(tp.Symbol(), nil, ast.SymbolFlagsNone, symbolFormatFlags))
213+
cons := c.GetConstraintOfTypeParameter(tp)
214+
if cons != nil {
215+
b.WriteString(" extends ")
216+
b.WriteString(c.TypeToStringEx(cons, nil, typeFormatFlags))
217+
}
218+
}
219+
b.WriteString(">")
220+
}
221+
}
222+
var writeSymbol func(*ast.Symbol) *ast.Node
223+
writeSymbol = func(symbol *ast.Symbol) *ast.Node {
224+
var declaration *ast.Node
225+
// Recursively write all meanings of alias
226+
if symbol.Flags&ast.SymbolFlagsAlias != 0 && visitedAliases.AddIfAbsent(symbol) {
227+
if aliasedSymbol := c.GetAliasedSymbol(symbol); aliasedSymbol != c.GetUnknownSymbol() {
228+
aliasLevel++
229+
declaration = writeSymbol(aliasedSymbol)
230+
aliasLevel--
231+
}
232+
}
233+
var flags ast.SymbolFlags
234+
switch getMeaningFromLocation(node) {
235+
case ast.SemanticMeaningValue:
236+
flags = symbol.Flags & (ast.SymbolFlagsValue | ast.SymbolFlagsSignature)
237+
case ast.SemanticMeaningType:
238+
flags = symbol.Flags & ast.SymbolFlagsType
239+
case ast.SemanticMeaningNamespace:
240+
flags = symbol.Flags & ast.SymbolFlagsNamespace
241+
}
242+
if flags == 0 {
243+
flags = symbol.Flags & (ast.SymbolFlagsValue | ast.SymbolFlagsSignature | ast.SymbolFlagsType | ast.SymbolFlagsNamespace)
244+
if flags == 0 {
245+
return nil
246+
}
247+
}
248+
if flags&ast.SymbolFlagsProperty != 0 && symbol.ValueDeclaration != nil && ast.IsMethodDeclaration(symbol.ValueDeclaration) {
249+
flags = ast.SymbolFlagsMethod
250+
}
251+
if flags&ast.SymbolFlagsValue != 0 {
252+
declaration = symbol.ValueDeclaration
253+
}
254+
if flags&(ast.SymbolFlagsVariable|ast.SymbolFlagsProperty|ast.SymbolFlagsAccessor) != 0 {
255+
writeNewLine()
201256
switch {
202257
case flags&ast.SymbolFlagsProperty != 0:
203258
b.WriteString("(property) ")
@@ -233,36 +288,44 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol
233288
} else {
234289
b.WriteString(c.TypeToStringEx(c.GetTypeOfSymbolAtLocation(symbol, node), container, typeFormatFlags))
235290
}
236-
case flags&ast.SymbolFlagsEnumMember != 0:
291+
}
292+
if flags&ast.SymbolFlagsEnumMember != 0 {
293+
writeNewLine()
237294
b.WriteString("(enum member) ")
238295
t := c.GetTypeOfSymbol(symbol)
239296
b.WriteString(c.TypeToStringEx(t, container, typeFormatFlags))
240297
if t.Flags()&checker.TypeFlagsLiteral != 0 {
241298
b.WriteString(" = ")
242299
b.WriteString(t.AsLiteralType().String())
243300
}
244-
case flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsMethod) != 0:
301+
}
302+
if flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsMethod) != 0 {
245303
prefix := core.IfElse(flags&ast.SymbolFlagsMethod != 0, "(method) ", "function ")
246304
if ast.IsIdentifier(node) && ast.IsFunctionLikeDeclaration(node.Parent) && node.Parent.Name() == node {
247305
declaration = node.Parent
248306
signatures := []*checker.Signature{c.GetSignatureFromDeclaration(declaration)}
249-
writeSignatures(&b, c, signatures, container, isAlias, prefix, symbol)
307+
writeSignatures(signatures, prefix, symbol)
250308
} else {
251309
signatures := getSignaturesAtLocation(c, symbol, checker.SignatureKindCall, node)
252310
if len(signatures) == 1 {
253311
if d := signatures[0].Declaration(); d != nil && d.Flags&ast.NodeFlagsJSDoc == 0 {
254312
declaration = d
255313
}
256314
}
257-
writeSignatures(&b, c, signatures, container, isAlias, prefix, symbol)
315+
writeSignatures(signatures, prefix, symbol)
316+
}
317+
}
318+
if flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0 {
319+
if flags&ast.SymbolFlagsInterface != 0 && (declaration == nil || ast.IsIdentifier(node) && ast.IsInterfaceDeclaration(node.Parent)) {
320+
declaration = core.Find(symbol.Declarations, ast.IsInterfaceDeclaration)
258321
}
259-
case flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0:
260322
if node.Kind == ast.KindThisKeyword || ast.IsThisInTypeQuery(node) {
323+
writeNewLine()
261324
b.WriteString("this")
262325
} else if node.Kind == ast.KindConstructorKeyword && (ast.IsConstructorDeclaration(node.Parent) || ast.IsConstructSignatureDeclaration(node.Parent)) {
263326
declaration = node.Parent
264327
signatures := []*checker.Signature{c.GetSignatureFromDeclaration(declaration)}
265-
writeSignatures(&b, c, signatures, container, isAlias, "constructor ", symbol)
328+
writeSignatures(signatures, "constructor ", symbol)
266329
} else {
267330
var signatures []*checker.Signature
268331
if flags&ast.SymbolFlagsClass != 0 && getCallOrNewExpression(node) != nil {
@@ -272,24 +335,34 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol
272335
if d := signatures[0].Declaration(); d != nil && d.Flags&ast.NodeFlagsJSDoc == 0 {
273336
declaration = d
274337
}
275-
writeSignatures(&b, c, signatures, container, isAlias, "constructor ", symbol)
338+
writeSignatures(signatures, "constructor ", symbol)
276339
} else {
340+
writeNewLine()
277341
b.WriteString(core.IfElse(flags&ast.SymbolFlagsClass != 0, "class ", "interface "))
278342
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
279343
params := c.GetDeclaredTypeOfSymbol(symbol).AsInterfaceType().LocalTypeParameters()
280-
writeTypeParams(&b, c, params)
344+
writeTypeParams(params)
281345
}
282346
}
283-
if flags&ast.SymbolFlagsInterface != 0 {
284-
declaration = core.Find(symbol.Declarations, ast.IsInterfaceDeclaration)
285-
}
286-
case flags&ast.SymbolFlagsEnum != 0:
347+
}
348+
if flags&ast.SymbolFlagsEnum != 0 {
349+
writeNewLine()
287350
b.WriteString("enum ")
288351
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
289-
case flags&ast.SymbolFlagsModule != 0:
352+
if declaration == nil || ast.IsIdentifier(node) && ast.IsEnumDeclaration(node.Parent) {
353+
declaration = core.Find(symbol.Declarations, ast.IsEnumDeclaration)
354+
}
355+
}
356+
if flags&ast.SymbolFlagsModule != 0 {
357+
writeNewLine()
290358
b.WriteString(core.IfElse(symbol.ValueDeclaration != nil && ast.IsSourceFile(symbol.ValueDeclaration), "module ", "namespace "))
291359
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
292-
case flags&ast.SymbolFlagsTypeParameter != 0:
360+
if declaration == nil || ast.IsIdentifier(node) && ast.IsModuleDeclaration(node.Parent) {
361+
declaration = core.Find(symbol.Declarations, ast.IsModuleDeclaration)
362+
}
363+
}
364+
if flags&ast.SymbolFlagsTypeParameter != 0 {
365+
writeNewLine()
293366
b.WriteString("(type parameter) ")
294367
tp := c.GetDeclaredTypeOfSymbol(symbol)
295368
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
@@ -298,40 +371,30 @@ func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol
298371
b.WriteString(" extends ")
299372
b.WriteString(c.TypeToStringEx(cons, container, typeFormatFlags))
300373
}
301-
declaration = core.Find(symbol.Declarations, ast.IsTypeParameterDeclaration)
302-
case flags&ast.SymbolFlagsTypeAlias != 0:
374+
if declaration == nil || ast.IsIdentifier(node) && ast.IsTypeParameterDeclaration(node.Parent) {
375+
declaration = core.Find(symbol.Declarations, ast.IsTypeParameterDeclaration)
376+
}
377+
}
378+
if flags&ast.SymbolFlagsTypeAlias != 0 {
379+
writeNewLine()
303380
b.WriteString("type ")
304381
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
305-
writeTypeParams(&b, c, c.GetTypeAliasTypeParameters(symbol))
382+
writeTypeParams(c.GetTypeAliasTypeParameters(symbol))
306383
if len(symbol.Declarations) != 0 {
307384
b.WriteString(" = ")
308385
b.WriteString(c.TypeToStringEx(c.GetDeclaredTypeOfSymbol(symbol), container, typeFormatFlags|checker.TypeFormatFlagsInTypeAlias))
309386
}
310-
declaration = core.Find(symbol.Declarations, ast.IsTypeOrJSTypeAliasDeclaration)
311-
default:
312-
b.WriteString(c.TypeToStringEx(c.GetTypeOfSymbol(symbol), container, typeFormatFlags))
313-
}
314-
return declaration
315-
}
316-
var writeSymbol func(*ast.Symbol, bool) *ast.Node
317-
writeSymbol = func(symbol *ast.Symbol, isAlias bool) *ast.Node {
318-
var declaration *ast.Node
319-
// Recursively write all meanings of alias
320-
if symbol.Flags&ast.SymbolFlagsAlias != 0 && visitedAliases.AddIfAbsent(symbol) {
321-
if aliasedSymbol := c.GetAliasedSymbol(symbol); aliasedSymbol != c.GetUnknownSymbol() {
322-
declaration = writeSymbol(aliasedSymbol, true /*isAlias*/)
387+
if declaration == nil || ast.IsIdentifier(node) && ast.IsTypeOrJSTypeAliasDeclaration(node.Parent) {
388+
declaration = core.Find(symbol.Declarations, ast.IsTypeOrJSTypeAliasDeclaration)
323389
}
324390
}
325-
// Write the value meaning, if any
326-
declaration = core.OrElse(declaration, writeSymbolMeaning(symbol, ast.SymbolFlagsValue|ast.SymbolFlagsSignature, isAlias))
327-
// Write the type meaning, if any
328-
declaration = core.OrElse(declaration, writeSymbolMeaning(symbol, ast.SymbolFlagsType&^ast.SymbolFlagsValue, isAlias))
329-
// Write the namespace meaning, if any
330-
declaration = core.OrElse(declaration, writeSymbolMeaning(symbol, ast.SymbolFlagsNamespace&^ast.SymbolFlagsValue, isAlias))
331-
// Return the first declaration
391+
if flags&ast.SymbolFlagsSignature != 0 {
392+
writeNewLine()
393+
b.WriteString(c.TypeToStringEx(c.GetTypeOfSymbol(symbol), container, typeFormatFlags))
394+
}
332395
return declaration
333396
}
334-
firstDeclaration := writeSymbol(symbol, false /*isAlias*/)
397+
firstDeclaration := writeSymbol(symbol)
335398
return b.String(), firstDeclaration
336399
}
337400

@@ -392,43 +455,6 @@ func getCallOrNewExpression(node *ast.Node) *ast.Node {
392455
return nil
393456
}
394457

395-
func writeTypeParams(b *strings.Builder, c *checker.Checker, params []*checker.Type) {
396-
if len(params) > 0 {
397-
b.WriteString("<")
398-
for i, tp := range params {
399-
if i != 0 {
400-
b.WriteString(", ")
401-
}
402-
symbol := tp.Symbol()
403-
b.WriteString(c.SymbolToStringEx(symbol, nil, ast.SymbolFlagsNone, symbolFormatFlags))
404-
cons := c.GetConstraintOfTypeParameter(tp)
405-
if cons != nil {
406-
b.WriteString(" extends ")
407-
b.WriteString(c.TypeToStringEx(cons, nil, typeFormatFlags))
408-
}
409-
}
410-
b.WriteString(">")
411-
}
412-
}
413-
414-
func writeSignatures(b *strings.Builder, c *checker.Checker, signatures []*checker.Signature, container *ast.Node, isAlias bool, prefix string, symbol *ast.Symbol) {
415-
for i, sig := range signatures {
416-
if i != 0 {
417-
b.WriteString("\n")
418-
if isAlias {
419-
b.WriteString("(alias) ")
420-
}
421-
}
422-
if i == 3 && len(signatures) >= 5 {
423-
b.WriteString(fmt.Sprintf("// +%v more overloads", len(signatures)-3))
424-
break
425-
}
426-
b.WriteString(prefix)
427-
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
428-
b.WriteString(c.SignatureToStringEx(sig, container, typeFormatFlags|checker.TypeFormatFlagsWriteCallStyleSignature|checker.TypeFormatFlagsWriteTypeArgumentsOfSignature))
429-
}
430-
}
431-
432458
func containsTypedefTag(jsdoc *ast.Node) bool {
433459
if jsdoc.Kind == ast.KindJSDoc {
434460
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
@@ -466,19 +492,29 @@ func getJSDocOrTag(c *checker.Checker, node *ast.Node) *ast.Node {
466492
(ast.IsVariableDeclaration(node.Parent) || ast.IsPropertyDeclaration(node.Parent) || ast.IsPropertyAssignment(node.Parent)) && node.Parent.Initializer() == node:
467493
return getJSDocOrTag(c, node.Parent)
468494
}
469-
if symbol := node.Symbol(); symbol != nil && node.Parent != nil && ast.IsClassOrInterfaceLike(node.Parent) {
470-
isStatic := ast.HasStaticModifier(node)
471-
for _, baseType := range c.GetBaseTypes(c.GetDeclaredTypeOfSymbol(node.Parent.Symbol())) {
472-
t := baseType
473-
if isStatic {
474-
t = c.GetTypeOfSymbol(baseType.Symbol())
475-
}
476-
if prop := c.GetPropertyOfType(t, symbol.Name); prop != nil && prop.ValueDeclaration != nil {
477-
if jsDoc := getJSDocOrTag(c, prop.ValueDeclaration); jsDoc != nil {
495+
if symbol := node.Symbol(); symbol != nil && node.Parent != nil {
496+
if ast.IsFunctionDeclaration(node) || ast.IsMethodDeclaration(node) || ast.IsMethodSignatureDeclaration(node) || ast.IsConstructorDeclaration(node) || ast.IsConstructSignatureDeclaration(node) {
497+
firstSignature := core.Find(symbol.Declarations, ast.IsFunctionLike)
498+
if firstSignature != nil && node != firstSignature {
499+
if jsDoc := getJSDocOrTag(c, firstSignature); jsDoc != nil {
478500
return jsDoc
479501
}
480502
}
481503
}
504+
if ast.IsClassOrInterfaceLike(node.Parent) {
505+
isStatic := ast.HasStaticModifier(node)
506+
for _, baseType := range c.GetBaseTypes(c.GetDeclaredTypeOfSymbol(node.Parent.Symbol())) {
507+
t := baseType
508+
if isStatic {
509+
t = c.GetTypeOfSymbol(baseType.Symbol())
510+
}
511+
if prop := c.GetPropertyOfType(t, symbol.Name); prop != nil && prop.ValueDeclaration != nil {
512+
if jsDoc := getJSDocOrTag(c, prop.ValueDeclaration); jsDoc != nil {
513+
return jsDoc
514+
}
515+
}
516+
}
517+
}
482518
}
483519
return nil
484520
}

internal/ls/utilities.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -998,10 +998,10 @@ func getMeaningFromLocation(node *ast.Node) ast.SemanticMeaning {
998998
if node.Kind != ast.KindQualifiedName {
999999
name = core.IfElse(node.Parent.Kind == ast.KindQualifiedName && node.Parent.AsQualifiedName().Right == node, node.Parent, nil)
10001000
}
1001-
if name == nil || name.Parent.Kind == ast.KindImportEqualsDeclaration {
1002-
return ast.SemanticMeaningNamespace
1001+
if name != nil && name.Parent.Kind == ast.KindImportEqualsDeclaration {
1002+
return ast.SemanticMeaningAll
10031003
}
1004-
return ast.SemanticMeaningAll
1004+
return ast.SemanticMeaningNamespace
10051005
case ast.IsDeclarationName(node):
10061006
return getMeaningFromDeclaration(parent)
10071007
case ast.IsEntityName(node) && ast.IsJSDocNameReferenceContext(node):

0 commit comments

Comments
 (0)