Initial commit
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
using BlazorMonaco;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using RobotNet10.ScriptEditor.Helpers.Monaco;
|
||||
using RobotNet10.ScriptEditor.Helpers.Monaco.Languages;
|
||||
|
||||
namespace RobotNet10.ScriptEditor.Helpers;
|
||||
|
||||
public static class SignatureHelpExtensions
|
||||
{
|
||||
public static async Task<SignatureHelpResult?> GetSignatureHelpAsync(this Document document, int line, int column)
|
||||
{
|
||||
var invocation = await GetInvocation(document, line, column);
|
||||
if (invocation is null) return null;
|
||||
|
||||
var response = new SignatureHelp();
|
||||
foreach (var comma in invocation.Separators)
|
||||
{
|
||||
if (comma.Span.Start > invocation.Position)
|
||||
{
|
||||
break;
|
||||
}
|
||||
response.ActiveParameter += 1;
|
||||
}
|
||||
|
||||
var signaturesSet = new HashSet<SignatureInformation>();
|
||||
var bestScore = int.MinValue;
|
||||
SignatureInformation? bestScoredItem = null;
|
||||
|
||||
var types = invocation.ArgumentTypes;
|
||||
ISymbol? throughSymbol = null;
|
||||
ISymbol? throughType = null;
|
||||
var methodGroup = invocation.SemanticModel.GetMemberGroup(invocation.Receiver).OfType<IMethodSymbol>();
|
||||
if (invocation.Receiver is MemberAccessExpressionSyntax syntax)
|
||||
{
|
||||
var throughExpression = syntax.Expression;
|
||||
throughSymbol = invocation.SemanticModel.GetSpeculativeSymbolInfo(invocation.Position, throughExpression, SpeculativeBindingOption.BindAsExpression).Symbol;
|
||||
throughType = invocation.SemanticModel.GetSpeculativeTypeInfo(invocation.Position, throughExpression, SpeculativeBindingOption.BindAsTypeOrNamespace).Type;
|
||||
var includeInstance = throughSymbol != null && throughSymbol is not ITypeSymbol ||
|
||||
throughExpression is LiteralExpressionSyntax ||
|
||||
throughExpression is TypeOfExpressionSyntax;
|
||||
var includeStatic = throughSymbol is INamedTypeSymbol || throughType != null;
|
||||
methodGroup = methodGroup.Where(m => m.IsStatic && includeStatic || !m.IsStatic && includeInstance);
|
||||
}
|
||||
else if (invocation.Receiver is SimpleNameSyntax && invocation.IsInStaticContext)
|
||||
{
|
||||
methodGroup = methodGroup.Where(m => m.IsStatic || m.MethodKind == MethodKind.LocalFunction);
|
||||
}
|
||||
|
||||
foreach (var methodOverload in methodGroup)
|
||||
{
|
||||
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];
|
||||
if (bestScoredItem == null)
|
||||
{
|
||||
response.ActiveSignature = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
response.ActiveSignature = signaturesList.IndexOf((SignatureInformation)bestScoredItem);
|
||||
}
|
||||
|
||||
return new SignatureHelpResult()
|
||||
{
|
||||
Value = response,
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task<InvocationContext?> GetInvocation(Document document, int line, int column)
|
||||
{
|
||||
var sourceText = await document.GetTextAsync();
|
||||
var position = sourceText.Lines.GetPosition(new LinePosition(line, column));
|
||||
var tree = await document.GetSyntaxTreeAsync();
|
||||
|
||||
if (tree is null) return null;
|
||||
|
||||
var root = await tree.GetRootAsync();
|
||||
if (root is null) return null;
|
||||
|
||||
try
|
||||
{
|
||||
var node = root.FindToken(position).Parent;
|
||||
|
||||
// Walk up until we find a node that we're interested in.
|
||||
while (node != null)
|
||||
{
|
||||
if (node is InvocationExpressionSyntax invocation && invocation.ArgumentList.Span.Contains(position))
|
||||
{
|
||||
var semanticModel = await document.GetSemanticModelAsync();
|
||||
return semanticModel is null ? null : new InvocationContext(semanticModel, position, invocation.Expression, invocation.ArgumentList, invocation.IsInStaticContext());
|
||||
}
|
||||
|
||||
if (node is BaseObjectCreationExpressionSyntax objectCreation && (objectCreation.ArgumentList?.Span.Contains(position) ?? false))
|
||||
{
|
||||
var semanticModel = await document.GetSemanticModelAsync();
|
||||
return semanticModel is null ? null : new InvocationContext(semanticModel, position, objectCreation, objectCreation.ArgumentList, objectCreation.IsInStaticContext());
|
||||
}
|
||||
|
||||
if (node is AttributeSyntax attributeSyntax && (attributeSyntax.ArgumentList?.Span.Contains(position) ?? false))
|
||||
{
|
||||
var semanticModel = await document.GetSemanticModelAsync();
|
||||
return semanticModel is null ? null : new InvocationContext(semanticModel, position, attributeSyntax, attributeSyntax.ArgumentList, attributeSyntax.IsInStaticContext());
|
||||
}
|
||||
|
||||
node = node.Parent;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int InvocationScore(IMethodSymbol symbol, IEnumerable<TypeInfo> types)
|
||||
{
|
||||
var parameters = symbol.Parameters;
|
||||
if (parameters.Length < types.Count())
|
||||
{
|
||||
return int.MinValue;
|
||||
}
|
||||
|
||||
var score = 0;
|
||||
var invocationEnum = types.GetEnumerator();
|
||||
var definitionEnum = parameters.GetEnumerator();
|
||||
while (invocationEnum.MoveNext() && definitionEnum.MoveNext())
|
||||
{
|
||||
if (invocationEnum.Current.ConvertedType == null)
|
||||
{
|
||||
// 1 point for having a parameter
|
||||
score += 1;
|
||||
}
|
||||
else if (SymbolEqualityComparer.Default.Equals(invocationEnum.Current.ConvertedType, definitionEnum.Current.Type))
|
||||
{
|
||||
// 2 points for having a parameter and being
|
||||
// the same type
|
||||
score += 2;
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
private static SignatureInformation BuildSignature(IMethodSymbol symbol)
|
||||
{
|
||||
var StructuredDocumentation = DocumentationConverter.GetStructuredDocumentation(symbol);
|
||||
|
||||
return new SignatureInformation
|
||||
{
|
||||
Documentation = new MarkdownString()
|
||||
{
|
||||
Value = StructuredDocumentation?.SummaryText ?? "",
|
||||
},
|
||||
Label = symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
|
||||
Parameters = [..symbol.Parameters.Select(parameter => new ParameterInformation()
|
||||
{
|
||||
Label = parameter.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
|
||||
Documentation = new MarkdownString()
|
||||
{
|
||||
Value = StructuredDocumentation?.GetParameterText(parameter.Name) ?? string.Empty,
|
||||
},
|
||||
})],
|
||||
ActiveParameter = null,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsInStaticContext(this SyntaxNode node)
|
||||
{
|
||||
// this/base calls are always static.
|
||||
if (node.FirstAncestorOrSelf<ConstructorInitializerSyntax>() != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var memberDeclaration = node.FirstAncestorOrSelf<MemberDeclarationSyntax>();
|
||||
if (memberDeclaration == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (memberDeclaration.Kind())
|
||||
{
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.ConstructorDeclaration:
|
||||
case SyntaxKind.EventDeclaration:
|
||||
case SyntaxKind.IndexerDeclaration:
|
||||
return GetModifiers(memberDeclaration).Any(SyntaxKind.StaticKeyword);
|
||||
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
return GetModifiers(memberDeclaration).Any(SyntaxKind.StaticKeyword) ||
|
||||
node.IsFoundUnder((PropertyDeclarationSyntax p) => p.Initializer);
|
||||
|
||||
case SyntaxKind.FieldDeclaration:
|
||||
case SyntaxKind.EventFieldDeclaration:
|
||||
// Inside a field one can only access static members of a type (unless it's top-level).
|
||||
return !memberDeclaration.Parent.IsKind(SyntaxKind.CompilationUnit);
|
||||
|
||||
case SyntaxKind.DestructorDeclaration:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Global statements are not a static context.
|
||||
if (node.FirstAncestorOrSelf<GlobalStatementSyntax>() != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// any other location is considered static
|
||||
return true;
|
||||
}
|
||||
|
||||
private static SyntaxTokenList GetModifiers(SyntaxNode member)
|
||||
{
|
||||
if (member != null)
|
||||
{
|
||||
switch (member.Kind())
|
||||
{
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
return ((EnumDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.StructDeclaration:
|
||||
return ((TypeDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.DelegateDeclaration:
|
||||
return ((DelegateDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.FieldDeclaration:
|
||||
return ((FieldDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.EventFieldDeclaration:
|
||||
return ((EventFieldDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.ConstructorDeclaration:
|
||||
return ((ConstructorDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.DestructorDeclaration:
|
||||
return ((DestructorDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
return ((PropertyDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.EventDeclaration:
|
||||
return ((EventDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.IndexerDeclaration:
|
||||
return ((IndexerDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.OperatorDeclaration:
|
||||
return ((OperatorDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.ConversionOperatorDeclaration:
|
||||
return ((ConversionOperatorDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
return ((MethodDeclarationSyntax)member).Modifiers;
|
||||
case SyntaxKind.GetAccessorDeclaration:
|
||||
case SyntaxKind.SetAccessorDeclaration:
|
||||
case SyntaxKind.AddAccessorDeclaration:
|
||||
case SyntaxKind.RemoveAccessorDeclaration:
|
||||
return ((AccessorDeclarationSyntax)member).Modifiers;
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private static bool IsFoundUnder<TParent>(this SyntaxNode node, Func<TParent, SyntaxNode?> childGetter)
|
||||
where TParent : SyntaxNode
|
||||
{
|
||||
var ancestor = node.GetAncestor<TParent>();
|
||||
if (ancestor == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var child = childGetter(ancestor);
|
||||
|
||||
// See if node passes through child on the way up to ancestor.
|
||||
return node.GetAncestorsOrThis<SyntaxNode>().Contains(child);
|
||||
}
|
||||
|
||||
private static TNode? GetAncestor<TNode>(this SyntaxNode node)
|
||||
where TNode : SyntaxNode
|
||||
{
|
||||
var current = node.Parent;
|
||||
while (current != null)
|
||||
{
|
||||
if (current is TNode tNode)
|
||||
{
|
||||
return tNode;
|
||||
}
|
||||
|
||||
current = current.GetParent();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IEnumerable<TNode> GetAncestorsOrThis<TNode>(this SyntaxNode node)
|
||||
where TNode : SyntaxNode
|
||||
{
|
||||
var current = node;
|
||||
while (current != null)
|
||||
{
|
||||
if (current is TNode tNode)
|
||||
{
|
||||
yield return tNode;
|
||||
}
|
||||
|
||||
current = current.GetParent();
|
||||
}
|
||||
}
|
||||
|
||||
private static SyntaxNode? GetParent(this SyntaxNode node)
|
||||
{
|
||||
return node is IStructuredTriviaSyntax trivia ? trivia.ParentTrivia.Token.Parent : node.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user