forked from dotnet/interactive
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSignatureHelpService.cs
More file actions
183 lines (151 loc) · 6.71 KB
/
Copy pathSignatureHelpService.cs
File metadata and controls
183 lines (151 loc) · 6.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.DotNet.Interactive.CSharpProject.Models;
using Microsoft.DotNet.Interactive.CSharpProject.Models.SignatureHelp;
// Adapted from https://github.com/OmniSharp/omnisharp-roslyn/blob/master/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs
namespace Microsoft.DotNet.Interactive.CSharpProject.LanguageServices;
internal class SignatureHelpService
{
public static async Task<SignatureHelpResult> GetSignatureHelpAsync(Func<Task<SemanticModel>> getSemanticModel, SyntaxNode node, int position)
{
var invocation = await GetInvocation(getSemanticModel, node, position);
return InternalGetSignatureHelp(invocation);
}
private static SignatureHelpResult InternalGetSignatureHelp(InvocationContext invocation)
{
var response = new SignatureHelpResult();
if (invocation is null)
{
return response;
}
// define active parameter by position
foreach (var comma in invocation.Separators)
{
if (comma.Span.Start > invocation.Position)
{
break;
}
response.ActiveParameter += 1;
}
// process all signatures, define active signature by types
var signaturesSet = new HashSet<SignatureInformation>();
var bestScore = int.MinValue;
SignatureInformation bestScoredItem = null;
var types = invocation.ArgumentTypes;
foreach (var methodOverload in GetMethodOverloads(invocation.SemanticModel, invocation.Receiver))
{
var signature = BuildSignature(methodOverload);
signaturesSet.Add(signature);
var score = InvocationScore(methodOverload, types);
if (score > bestScore)
{
bestScore = score;
bestScoredItem = signature;
}
}
var signaturesList = signaturesSet.ToList();
response.Signatures = signaturesList;
response.ActiveSignature = signaturesList.IndexOf(bestScoredItem);
return response;
}
internal static async Task<InvocationContext> GetInvocation(Document document, int position)
{
var tree = await document.GetSyntaxTreeAsync();
var root = await tree.GetRootAsync();
var node = root.FindToken(position).Parent;
return await GetInvocation(() => document.GetSemanticModelAsync(), node, position);
}
internal static async Task<InvocationContext> GetInvocation(Func<Task<SemanticModel>> getSemanticModel, SyntaxNode node, int position)
{
// Walk up until we find a node that we're interested in.
while (node != null)
{
switch (node)
{
case InvocationExpressionSyntax invocation when invocation.ArgumentList.Span.Contains(position):
{
var semanticModel = await getSemanticModel();
return new InvocationContext(semanticModel, position, invocation.Expression, invocation.ArgumentList);
}
case ObjectCreationExpressionSyntax objectCreation when objectCreation.ArgumentList?.Span.Contains(position) ?? false:
{
var semanticModel = await getSemanticModel();
return new InvocationContext(semanticModel, position, objectCreation, objectCreation.ArgumentList);
}
case AttributeSyntax attributeSyntax when attributeSyntax.ArgumentList.Span.Contains(position):
{
var semanticModel = await getSemanticModel();
return new InvocationContext(semanticModel, position, attributeSyntax, attributeSyntax.ArgumentList);
}
}
node = node.Parent;
}
return null;
}
private static IEnumerable<IMethodSymbol> GetMethodOverloads(SemanticModel semanticModel, SyntaxNode node)
{
ISymbol symbol = null;
var symbolInfo = semanticModel.GetSymbolInfo(node);
if (symbolInfo.Symbol != null)
{
symbol = symbolInfo.Symbol;
}
else if (!symbolInfo.CandidateSymbols.IsEmpty)
{
symbol = symbolInfo.CandidateSymbols.First();
}
return symbol?.ContainingType == null
? Array.Empty<IMethodSymbol>()
: symbol.ContainingType.GetMembers(symbol.Name).OfType<IMethodSymbol>();
}
private static int InvocationScore(IMethodSymbol symbol, IEnumerable<TypeInfo> types)
{
var parameters = GetParameters(symbol).ToList();
var typeInfos = types.ToList();
if (parameters.Count() < typeInfos.Count)
{
return int.MinValue;
}
var score = 0;
foreach (var (invocation, definition) in typeInfos.Zip(parameters, (i, d) => (i, d)))
{
if (invocation.ConvertedType == null)
{
// 1 point for having a parameter
score += 1;
}
else if (definition.Type != null && (SymbolEqualityComparer.Default.Equals(invocation.ConvertedType, definition.Type)))
{
// 2 points for having a parameter and being
// the same type
score += 2;
}
}
return score;
}
private static SignatureInformation BuildSignature(IMethodSymbol symbol)
{
var documentation = DocumentationConverter.GetDocumentation(symbol, "\n");
if (documentation is null)
{
documentation = new("text/markdown", "");
}
var label = symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
var parameters = GetParameters(symbol)
.Select(parameter => new ParameterInformation
(label: parameter.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
documentation: DocumentationConverter.GetDocumentation(parameter, "\n")))
.ToArray();
return new(label, documentation, parameters);
}
private static IEnumerable<IParameterSymbol> GetParameters(IMethodSymbol methodSymbol)
{
return !methodSymbol.IsExtensionMethod ? methodSymbol.Parameters : methodSymbol.Parameters.RemoveAt(0);
}
}