Skip to content

Commit a7b5685

Browse files
authored
Support identifier location in inlay hints (#2435)
1 parent 5be8200 commit a7b5685

52 files changed

Lines changed: 1233 additions & 442 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

internal/bundled/embed.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ func libPath() string {
2222
return scheme + "libs"
2323
}
2424

25+
func IsBundled(path string) bool {
26+
_, ok := splitPath(path)
27+
return ok
28+
}
29+
2530
// wrappedFS is implemented directly rather than going through [io/fs.FS].
2631
// Our vfs.FS works with file contents in terms of strings, and that's
2732
// what go:embed does under the hood, but going through fs.FS will cause

internal/bundled/noembed.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,7 @@ var libPath = sync.OnceValue(func() string {
4242

4343
return dir
4444
})
45+
46+
func IsBundled(path string) bool {
47+
return false
48+
}

internal/checker/nodebuilder.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,19 @@ func (b *NodeBuilder) TypeToTypeNode(typ *Type, enclosingDeclaration *ast.Node,
177177
// var _ NodeBuilderInterface = NewNodeBuilderAPI(nil, nil)
178178

179179
func NewNodeBuilder(ch *Checker, e *printer.EmitContext) *NodeBuilder {
180-
impl := newNodeBuilderImpl(ch, e)
180+
return NewNodeBuilderEx(ch, e, nil /*idToSymbol*/)
181+
}
182+
183+
func NewNodeBuilderEx(ch *Checker, e *printer.EmitContext, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *NodeBuilder {
184+
impl := newNodeBuilderImpl(ch, e, idToSymbol)
181185
return &NodeBuilder{impl: impl, ctxStack: make([]*NodeBuilderContext, 0, 1), basicHost: ch.program}
182186
}
183187

184188
func (c *Checker) getNodeBuilder() *NodeBuilder {
185-
return NewNodeBuilder(c, printer.NewEmitContext())
189+
return c.getNodeBuilderEx(nil /*idToSymbol*/)
190+
}
191+
192+
func (c *Checker) getNodeBuilderEx(idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *NodeBuilder {
193+
b := NewNodeBuilderEx(c, printer.NewEmitContext(), idToSymbol)
194+
return b
186195
}

internal/checker/nodebuilderimpl.go

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ type NodeBuilderImpl struct {
9898

9999
// reusable visitor
100100
cloneBindingNameVisitor *ast.NodeVisitor
101+
102+
// symbols for synthesized identifiers, needed for e.g. inlay hints
103+
idToSymbol map[*ast.IdentifierNode]*ast.Symbol
101104
}
102105

103106
const (
@@ -107,8 +110,11 @@ const (
107110

108111
// Node builder utility functions
109112

110-
func newNodeBuilderImpl(ch *Checker, e *printer.EmitContext) *NodeBuilderImpl {
111-
b := &NodeBuilderImpl{f: e.Factory.AsNodeFactory(), ch: ch, e: e}
113+
func newNodeBuilderImpl(ch *Checker, e *printer.EmitContext, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *NodeBuilderImpl {
114+
if idToSymbol == nil {
115+
idToSymbol = make(map[*ast.IdentifierNode]*ast.Symbol)
116+
}
117+
b := &NodeBuilderImpl{f: e.Factory.AsNodeFactory(), ch: ch, e: e, idToSymbol: idToSymbol}
112118
b.cloneBindingNameVisitor = ast.NewNodeVisitor(b.cloneBindingName, b.f, ast.NodeVisitorHooks{})
113119
return b
114120
}
@@ -482,7 +488,7 @@ func (b *NodeBuilderImpl) createEntityNameFromSymbolChain(chain []*ast.Symbol, i
482488
b.ctx.flags ^= nodebuilder.FlagsInInitialEntityName
483489
}
484490

485-
identifier := b.f.NewIdentifier(symbolName)
491+
identifier := b.newIdentifier(symbolName, symbol)
486492
b.e.AddEmitFlags(identifier, printer.EFNoAsciiEscaping)
487493
// !!! TODO: smuggle type arguments out
488494
// if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray<TypeNode | TypeParameterDeclaration>(typeParameterNodes));
@@ -499,7 +505,7 @@ func (b *NodeBuilderImpl) createEntityNameFromSymbolChain(chain []*ast.Symbol, i
499505

500506
// TODO: Audit usages of symbolToEntityNameNode - they should probably all be symbolToName
501507
func (b *NodeBuilderImpl) symbolToEntityNameNode(symbol *ast.Symbol) *ast.EntityName {
502-
identifier := b.f.NewIdentifier(symbol.Name)
508+
identifier := b.newIdentifier(symbol.Name, symbol)
503509
if symbol.Parent != nil {
504510
return b.f.NewQualifiedName(b.symbolToEntityNameNode(symbol.Parent), identifier)
505511
}
@@ -701,7 +707,7 @@ func (b *NodeBuilderImpl) createAccessFromSymbolChain(chain []*ast.Symbol, index
701707
)
702708
}
703709

704-
identifier := b.f.NewIdentifier(symbolName)
710+
identifier := b.newIdentifier(symbolName, symbol)
705711
b.e.AddEmitFlags(identifier, printer.EFNoAsciiEscaping)
706712
// !!! TODO: smuggle type arguments out
707713
// if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray<TypeNode | TypeParameterDeclaration>(typeParameterNodes));
@@ -740,7 +746,7 @@ func (b *NodeBuilderImpl) createExpressionFromSymbolChain(chain []*ast.Symbol, i
740746
}
741747

742748
if index == 0 || canUsePropertyAccess(symbolName) {
743-
identifier := b.f.NewIdentifier(symbolName)
749+
identifier := b.newIdentifier(symbolName, symbol)
744750
b.e.AddEmitFlags(identifier, printer.EFNoAsciiEscaping)
745751
// !!! TODO: smuggle type arguments out
746752
// if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray<TypeNode | TypeParameterDeclaration>(typeParameterNodes));
@@ -764,7 +770,7 @@ func (b *NodeBuilderImpl) createExpressionFromSymbolChain(chain []*ast.Symbol, i
764770
expression = b.f.NewNumericLiteral(symbolName, ast.TokenFlagsNone)
765771
}
766772
if expression == nil {
767-
expression = b.f.NewIdentifier(symbolName)
773+
expression = b.newIdentifier(symbolName, symbol)
768774
b.e.AddEmitFlags(expression, printer.EFNoAsciiEscaping)
769775
// !!! TODO: smuggle type arguments out
770776
// if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray<TypeNode | TypeParameterDeclaration>(typeParameterNodes));
@@ -1252,7 +1258,11 @@ func (b *NodeBuilderImpl) setTextRange(range_ *ast.Node, location *ast.Node) *as
12521258
return range_
12531259
}
12541260
if !ast.NodeIsSynthesized(range_) || (range_.Flags&ast.NodeFlagsSynthesized == 0) || b.ctx.enclosingFile == nil || b.ctx.enclosingFile != ast.GetSourceFileOfNode(b.e.MostOriginal(range_)) {
1261+
original := range_
12551262
range_ = range_.Clone(b.f) // if `range` is synthesized or originates in another file, copy it so it definitely has synthetic positions
1263+
if symbol, ok := b.idToSymbol[original]; ok {
1264+
b.idToSymbol[range_] = symbol
1265+
}
12561266
}
12571267
if range_ == location || location == nil {
12581268
return range_
@@ -1327,7 +1337,7 @@ func (b *NodeBuilderImpl) typeParameterToName(typeParameter *Type) *ast.Identifi
13271337
if text != rawText {
13281338
// !!! TODO: smuggle type arguments out
13291339
// const typeArguments = getIdentifierTypeArguments(result);
1330-
result = b.f.NewIdentifier(text)
1340+
result = b.newIdentifier(text, typeParameter.symbol)
13311341
// setIdentifierTypeArguments(result, typeArguments);
13321342
}
13331343

@@ -1584,18 +1594,20 @@ func (b *NodeBuilderImpl) symbolToParameterDeclaration(parameterSymbol *ast.Symb
15841594

15851595
func (b *NodeBuilderImpl) parameterToParameterDeclarationName(parameterSymbol *ast.Symbol, parameterDeclaration *ast.Node) *ast.Node {
15861596
if parameterDeclaration == nil || parameterDeclaration.Name() == nil {
1587-
return b.f.NewIdentifier(parameterSymbol.Name)
1597+
return b.newIdentifier(parameterSymbol.Name, parameterSymbol)
15881598
}
15891599

15901600
name := parameterDeclaration.Name()
15911601
switch name.Kind {
15921602
case ast.KindIdentifier:
15931603
cloned := b.f.DeepCloneNode(name)
15941604
b.e.SetEmitFlags(cloned, printer.EFNoAsciiEscaping)
1605+
b.idToSymbol[cloned] = parameterSymbol
15951606
return cloned
15961607
case ast.KindQualifiedName:
15971608
cloned := b.f.DeepCloneNode(name.AsQualifiedName().Right)
15981609
b.e.SetEmitFlags(cloned, printer.EFNoAsciiEscaping)
1610+
b.idToSymbol[cloned] = parameterSymbol
15991611
return cloned
16001612
default:
16011613
return b.cloneBindingName(name)
@@ -1667,7 +1679,7 @@ func (b *NodeBuilderImpl) typePredicateToTypePredicateNodeHelper(typePredicate *
16671679
}
16681680
var parameterName *ast.Node
16691681
if typePredicate.kind == TypePredicateKindIdentifier || typePredicate.kind == TypePredicateKindAssertsIdentifier {
1670-
parameterName = b.f.NewIdentifier(typePredicate.parameterName)
1682+
parameterName = b.newIdentifier(typePredicate.parameterName, nil /*symbol*/)
16711683
b.e.SetEmitFlags(parameterName, printer.EFNoAsciiEscaping)
16721684
} else {
16731685
parameterName = b.f.NewThisTypeNode()
@@ -1950,7 +1962,7 @@ func (b *NodeBuilderImpl) indexInfoToIndexSignatureDeclarationHelper(indexInfo *
19501962
name := getNameFromIndexInfo(indexInfo)
19511963
indexerTypeNode := b.typeToTypeNode(indexInfo.keyType)
19521964

1953-
indexingParameter := b.f.NewParameterDeclaration(nil, nil, b.f.NewIdentifier(name), nil, indexerTypeNode, nil)
1965+
indexingParameter := b.f.NewParameterDeclaration(nil, nil, b.newIdentifier(name, nil /*symbol*/), nil, indexerTypeNode, nil)
19541966
if typeNode == nil {
19551967
if indexInfo.valueType == nil {
19561968
typeNode = b.f.NewKeywordTypeNode(ast.KindAnyKeyword)
@@ -2082,10 +2094,10 @@ func (b *NodeBuilderImpl) trackComputedName(accessExpression *ast.Node, enclosin
20822094
}
20832095
}
20842096

2085-
func (b *NodeBuilderImpl) createPropertyNameNodeForIdentifierOrLiteral(name string, singleQuote bool, stringNamed bool, isMethod bool) *ast.Node {
2097+
func (b *NodeBuilderImpl) createPropertyNameNodeForIdentifierOrLiteral(name string, singleQuote bool, stringNamed bool, isMethod bool, symbol *ast.Symbol) *ast.Node {
20862098
isMethodNamedNew := isMethod && name == "new"
20872099
if !isMethodNamedNew && scanner.IsIdentifierText(name, core.LanguageVariantStandard) {
2088-
return b.f.NewIdentifier(name)
2100+
return b.newIdentifier(name, symbol)
20892101
}
20902102
if !stringNamed && !isMethodNamedNew && isNumericLiteralName(name) && jsnum.FromString(name) >= 0 {
20912103
return b.f.NewNumericLiteral(name, ast.TokenFlagsNone)
@@ -2133,7 +2145,7 @@ func (b *NodeBuilderImpl) getPropertyNameNodeForSymbol(symbol *ast.Symbol) *ast.
21332145
name = "__#private" + name
21342146
}
21352147

2136-
return b.createPropertyNameNodeForIdentifierOrLiteral(name, singleQuote, stringNamed, isMethod)
2148+
return b.createPropertyNameNodeForIdentifierOrLiteral(name, singleQuote, stringNamed, isMethod, symbol)
21372149
}
21382150

21392151
// See getNameForSymbolFromNameType for a stringy equivalent
@@ -2160,7 +2172,7 @@ func (b *NodeBuilderImpl) getPropertyNameNodeForSymbolFromNameType(symbol *ast.S
21602172
if isNumericLiteralName(name) && name[0] == '-' {
21612173
return b.f.NewComputedPropertyName(b.f.NewPrefixUnaryExpression(ast.KindMinusToken, b.f.NewNumericLiteral(name[1:], ast.TokenFlagsNone)))
21622174
}
2163-
return b.createPropertyNameNodeForIdentifierOrLiteral(name, singleQuote, stringNamed, isMethod)
2175+
return b.createPropertyNameNodeForIdentifierOrLiteral(name, singleQuote, stringNamed, isMethod, symbol)
21642176
}
21652177
if nameType.flags&TypeFlagsUniqueESSymbol != 0 {
21662178
return b.f.NewComputedPropertyName(b.symbolToExpression(nameType.AsUniqueESSymbolType().symbol, ast.SymbolFlagsValue))
@@ -2596,7 +2608,10 @@ func (b *NodeBuilderImpl) typeReferenceToTypeNode(t *Type) *ast.TypeNode {
25962608
if t.Target() == b.ch.globalArrayType || t.Target() == b.ch.globalReadonlyArrayType {
25972609
if b.ctx.flags&nodebuilder.FlagsWriteArrayAsGenericType != 0 {
25982610
typeArgumentNode := b.typeToTypeNode(typeArguments[0])
2599-
return b.f.NewTypeReferenceNode(b.f.NewIdentifier(core.IfElse(t.Target() == b.ch.globalArrayType, "Array", "ReadonlyArray")), b.f.NewNodeList([]*ast.TypeNode{typeArgumentNode}))
2611+
return b.f.NewTypeReferenceNode(
2612+
b.newIdentifier(core.IfElse(t.Target() == b.ch.globalArrayType, "Array", "ReadonlyArray"), t.Target().symbol),
2613+
b.f.NewNodeList([]*ast.TypeNode{typeArgumentNode}),
2614+
)
26002615
}
26012616
elementType := b.typeToTypeNode(typeArguments[0])
26022617
arrayType := b.f.NewArrayTypeNode(elementType)
@@ -2622,7 +2637,12 @@ func (b *NodeBuilderImpl) typeReferenceToTypeNode(t *Type) *ast.TypeNode {
26222637
labeledElementDeclaration := t.Target().AsTupleType().elementInfos[i].labeledDeclaration
26232638

26242639
if labeledElementDeclaration != nil {
2625-
tupleConstituentNodes.Nodes[i] = b.f.NewNamedTupleMember(core.IfElse(flags&ElementFlagsVariable != 0, b.f.NewToken(ast.KindDotDotDotToken), nil), b.f.NewIdentifier(b.ch.getTupleElementLabel(t.Target().AsTupleType().elementInfos[i], nil, i)), core.IfElse(flags&ElementFlagsOptional != 0, b.f.NewToken(ast.KindQuestionToken), nil), core.IfElse(flags&ElementFlagsRest != 0, b.f.NewArrayTypeNode(tupleConstituentNodes.Nodes[i]), tupleConstituentNodes.Nodes[i]))
2640+
tupleConstituentNodes.Nodes[i] = b.f.NewNamedTupleMember(
2641+
core.IfElse(flags&ElementFlagsVariable != 0, b.f.NewToken(ast.KindDotDotDotToken), nil),
2642+
b.newIdentifier(b.ch.getTupleElementLabel(t.Target().AsTupleType().elementInfos[i], nil, i), nil /*symbol*/),
2643+
core.IfElse(flags&ElementFlagsOptional != 0, b.f.NewToken(ast.KindQuestionToken), nil),
2644+
core.IfElse(flags&ElementFlagsRest != 0, b.f.NewArrayTypeNode(tupleConstituentNodes.Nodes[i]), tupleConstituentNodes.Nodes[i]),
2645+
)
26262646
} else {
26272647
switch {
26282648
case flags&ElementFlagsVariable != 0:
@@ -2997,7 +3017,7 @@ func (b *NodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode {
29973017
if b.ctx.flags&nodebuilder.FlagsGenerateNamesForShadowedTypeParams != 0 && t.flags&TypeFlagsTypeParameter != 0 {
29983018
name := b.typeParameterToName(t)
29993019
b.ctx.approximateLength += len(name.Text)
3000-
return b.f.NewTypeReferenceNode(b.f.NewIdentifier(name.Text), nil /*typeArguments*/)
3020+
return b.f.NewTypeReferenceNode(b.newIdentifier(name.Text, t.symbol), nil /*typeArguments*/)
30013021
}
30023022
// Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter.
30033023
if t.symbol != nil {
@@ -3009,7 +3029,7 @@ func (b *NodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode {
30093029
} else {
30103030
name = "?"
30113031
}
3012-
return b.f.NewTypeReferenceNode(b.f.NewIdentifier(name), nil /*typeArguments*/)
3032+
return b.f.NewTypeReferenceNode(b.newIdentifier(name, nil /*symbol*/), nil /*typeArguments*/)
30133033
}
30143034
if t.flags&TypeFlagsUnion != 0 && t.AsUnionType().origin != nil {
30153035
t = t.AsUnionType().origin
@@ -3113,3 +3133,11 @@ func (b *NodeBuilderImpl) newStringLiteralEx(text string, isSingleQuote bool) *a
31133133
func (t *TypeAlias) ToTypeReferenceNode(b *NodeBuilderImpl) *ast.Node {
31143134
return b.f.NewTypeReferenceNode(b.symbolToEntityNameNode(t.Symbol()), b.mapToTypeNodes(t.TypeArguments(), false /*isBareList*/))
31153135
}
3136+
3137+
func (b *NodeBuilderImpl) newIdentifier(text string, symbol *ast.Symbol) *ast.Node {
3138+
id := b.f.NewIdentifier(text)
3139+
if symbol != nil {
3140+
b.idToSymbol[id] = symbol
3141+
}
3142+
return id
3143+
}

internal/checker/printer.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -373,12 +373,12 @@ func (c *Checker) formatUnionTypes(types []*Type) []*Type {
373373
return result
374374
}
375375

376-
func (c *Checker) TypeToTypeNode(t *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags) *ast.TypeNode {
377-
nodeBuilder := c.getNodeBuilder()
376+
func (c *Checker) TypeToTypeNode(t *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *ast.TypeNode {
377+
nodeBuilder := c.getNodeBuilderEx(idToSymbol)
378378
return nodeBuilder.TypeToTypeNode(t, enclosingDeclaration, flags, nodebuilder.InternalFlagsNone, nil)
379379
}
380380

381-
func (c *Checker) TypePredicateToTypePredicateNode(t *TypePredicate, enclosingDeclaration *ast.Node, flags nodebuilder.Flags) *ast.TypePredicateNodeNode {
382-
nodeBuilder := c.getNodeBuilder()
381+
func (c *Checker) TypePredicateToTypePredicateNode(t *TypePredicate, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *ast.TypePredicateNodeNode {
382+
nodeBuilder := c.getNodeBuilderEx(idToSymbol)
383383
return nodeBuilder.TypePredicateToTypePredicateNode(t, enclosingDeclaration, flags, nodebuilder.InternalFlagsNone, nil)
384384
}

internal/fourslash/fourslash.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3450,12 +3450,13 @@ func (f *FourslashTest) VerifyBaselineInlayHints(
34503450
annotations = core.Map(*result.InlayHints, func(hint *lsproto.InlayHint) string {
34513451
if hint.Label.InlayHintLabelParts != nil {
34523452
for _, part := range *hint.Label.InlayHintLabelParts {
3453+
// Avoid diffs caused by lib file updates.
34533454
if part.Location != nil && isLibFile(part.Location.Uri.FileName()) {
34543455
part.Location.Range.Start = lsproto.Position{Line: 0, Character: 0}
3456+
part.Location.Range.End = lsproto.Position{Line: 0, Character: 0}
34553457
}
34563458
}
34573459
}
3458-
34593460
underline := strings.Repeat(" ", int(hint.Position.Character)) + "^"
34603461
hintJson, err := core.StringifyJson(hint, "", " ")
34613462
if err != nil {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package fourslash_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/microsoft/typescript-go/internal/fourslash"
7+
"github.com/microsoft/typescript-go/internal/ls/lsutil"
8+
"github.com/microsoft/typescript-go/internal/testutil"
9+
)
10+
11+
func TestInlayHintsIdentifierLocation(t *testing.T) {
12+
fourslash.SkipIfFailing(t)
13+
t.Parallel()
14+
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
15+
const content = `interface Foo {}
16+
const p = (a: Foo[]) => a;`
17+
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
18+
defer done()
19+
f.VerifyBaselineInlayHints(t, nil /*span*/, &lsutil.UserPreferences{InlayHints: lsutil.InlayHintsPreferences{IncludeInlayVariableTypeHints: true}})
20+
}

internal/ls/completions.go

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2483,20 +2483,6 @@ func strPtrTo(v string) *string {
24832483
return &v
24842484
}
24852485

2486-
func ptrIsTrue(ptr *bool) bool {
2487-
if ptr == nil {
2488-
return false
2489-
}
2490-
return *ptr
2491-
}
2492-
2493-
func ptrIsFalse(ptr *bool) bool {
2494-
if ptr == nil {
2495-
return false
2496-
}
2497-
return !*ptr
2498-
}
2499-
25002486
func boolToPtr(v bool) *bool {
25012487
if v {
25022488
return ptrTo(true)
@@ -5902,7 +5888,12 @@ func getJSDocParamAnnotation(
59025888
nodebuilder.FlagsUseSingleQuotesForStringLiteralType,
59035889
nodebuilder.FlagsNone,
59045890
)
5905-
typeNode := typeChecker.TypeToTypeNode(inferredType, ast.FindAncestor(initializer, ast.IsFunctionLike), builderFlags)
5891+
typeNode := typeChecker.TypeToTypeNode(
5892+
inferredType,
5893+
ast.FindAncestor(initializer, ast.IsFunctionLike),
5894+
builderFlags,
5895+
nil, /*idToSymbol*/
5896+
)
59065897
if typeNode != nil {
59075898
emitContext := printer.NewEmitContext()
59085899
// !!! snippet p

0 commit comments

Comments
 (0)