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 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(); 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(); 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 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 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() != null) { return true; } var memberDeclaration = node.FirstAncestorOrSelf(); 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() != 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(this SyntaxNode node, Func childGetter) where TParent : SyntaxNode { var ancestor = node.GetAncestor(); if (ancestor == null) { return false; } var child = childGetter(ancestor); // See if node passes through child on the way up to ancestor. return node.GetAncestorsOrThis().Contains(child); } private static TNode? GetAncestor(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 GetAncestorsOrThis(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; } }