File: CodeModel\MethodXml\MethodXmlBuilder.cs
Web Access
Project: src\src\VisualStudio\CSharp\Impl\Microsoft.VisualStudio.LanguageServices.CSharp_ihyjvpf0_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.MethodXml;
 
namespace Microsoft.VisualStudio.LanguageServices.CSharp.CodeModel.MethodXml;
 
internal sealed class MethodXmlBuilder : AbstractMethodXmlBuilder
{
    private MethodXmlBuilder(IMethodSymbol symbol, SemanticModel semanticModel)
        : base(symbol, semanticModel)
    {
    }
 
    private void GenerateBlock(BlockSyntax block)
    {
        using (BlockTag())
        {
            foreach (var statement in block.Statements)
            {
                GenerateComments(statement.GetLeadingTrivia());
                GenerateStatement(statement);
            }
 
            // Handle any additional comments now, but only comments within the extent of this block.
            GenerateComments(block.CloseBraceToken.LeadingTrivia);
        }
    }
 
    private void GenerateComments(SyntaxTriviaList triviaList)
    {
        foreach (var trivia in triviaList)
        {
            // Multi-line comment forms are ignored.
            if (trivia.Kind() == SyntaxKind.SingleLineCommentTrivia)
            {
                // In order to be valid, the comment must appear on its own line.
                var line = Text.Lines.GetLineFromPosition(trivia.SpanStart);
                var firstNonWhitespacePosition = line.GetFirstNonWhitespacePosition() ?? -1;
                if (firstNonWhitespacePosition == trivia.SpanStart)
                {
                    using var tag = CommentTag();
 
                    // Skip initial slashes
                    var trimmedComment = trivia.ToString()[2..];
                    EncodedText(trimmedComment);
                }
            }
        }
    }
 
    private void GenerateStatement(StatementSyntax statement)
    {
        var success = false;
        var mark = GetMark();
 
        switch (statement.Kind())
        {
            case SyntaxKind.LocalDeclarationStatement:
                success = TryGenerateLocal((LocalDeclarationStatementSyntax)statement);
                break;
            case SyntaxKind.Block:
                success = true;
                GenerateBlock((BlockSyntax)statement);
                break;
            case SyntaxKind.ExpressionStatement:
                success = TryGenerateExpressionStatement((ExpressionStatementSyntax)statement);
                break;
        }
 
        if (!success)
        {
            Rewind(mark);
            GenerateUnknown(statement);
        }
 
        // Just for readability
        LineBreak();
    }
 
    private bool TryGenerateLocal(LocalDeclarationStatementSyntax localDeclarationStatement)
    {
        /*
          - <ElementType name="Local" content="eltOnly">
                <attribute type="id" /> 
                <attribute type="static" /> 
                <attribute type="instance" /> 
                <attribute type="implicit" /> 
                <attribute type="constant" /> 
            - <group order="one">
                    <element type="Type" /> 
                    <element type="ArrayType" /> 
                </group>
            - <group minOccurs="1" maxOccurs="*" order="seq">
                    <element type="LocalName" /> 
                    <element type="Expression" minOccurs="0" maxOccurs="1" /> 
                </group>
            </ElementType>
        */
 
        using (LocalTag(GetLineNumber(localDeclarationStatement)))
        {
            // Spew the type first
            if (!TryGenerateType(localDeclarationStatement.Declaration.Type))
            {
                return false;
            }
 
            // Now spew the list of variables
            foreach (var variable in localDeclarationStatement.Declaration.Variables)
            {
                GenerateName(variable.Identifier.ToString());
 
                if (variable.Initializer != null)
                {
                    if (!TryGenerateExpression(variable.Initializer.Value))
                    {
                        return false;
                    }
                }
            }
        }
 
        return true;
    }
 
    private bool TryGenerateExpressionStatement(ExpressionStatementSyntax expressionStatement)
    {
        using (ExpressionStatementTag(GetLineNumber(expressionStatement)))
        {
            return TryGenerateExpression(expressionStatement.Expression);
        }
    }
 
    private bool TryGenerateType(TypeSyntax type)
    {
        var typeSymbol = SemanticModel.GetTypeInfo(type).Type;
        if (typeSymbol == null)
        {
            return false;
        }
 
        GenerateType(typeSymbol);
        return true;
    }
 
    private bool TryGenerateExpression(ExpressionSyntax expression)
    {
        using (ExpressionTag())
        {
            return TryGenerateExpressionSansTag(expression);
        }
    }
 
    private bool TryGenerateExpressionSansTag(ExpressionSyntax expression)
    {
        switch (expression.Kind())
        {
            case SyntaxKind.CharacterLiteralExpression:
                return TryGenerateCharLiteral(expression);
 
            case SyntaxKind.UnaryMinusExpression:
            case SyntaxKind.NumericLiteralExpression:
            case SyntaxKind.StringLiteralExpression:
            case SyntaxKind.TrueLiteralExpression:
            case SyntaxKind.FalseLiteralExpression:
                return TryGenerateLiteral(expression);
 
            case SyntaxKind.NullLiteralExpression:
                GenerateNullLiteral();
                return true;
 
            case SyntaxKind.ParenthesizedExpression:
                return TryGenerateParentheses((ParenthesizedExpressionSyntax)expression);
 
            case SyntaxKind.AddExpression:
            case SyntaxKind.BitwiseOrExpression:
            case SyntaxKind.BitwiseAndExpression:
                return TryGenerateBinaryOperation((BinaryExpressionSyntax)expression);
 
            case SyntaxKind.SimpleAssignmentExpression:
            case SyntaxKind.AddAssignmentExpression:
                return TryGenerateAssignment((AssignmentExpressionSyntax)expression);
 
            case SyntaxKind.CastExpression:
                return TryGenerateCast((CastExpressionSyntax)expression);
 
            case SyntaxKind.ObjectCreationExpression:
                return TryGenerateNewClass((ObjectCreationExpressionSyntax)expression);
 
            case SyntaxKind.ArrayCreationExpression:
                return TryGenerateNewArray((ArrayCreationExpressionSyntax)expression);
 
            case SyntaxKind.ArrayInitializerExpression:
                return TryGenerateArrayLiteral((InitializerExpressionSyntax)expression);
 
            case SyntaxKind.SimpleMemberAccessExpression:
                return TryGenerateNameRef((MemberAccessExpressionSyntax)expression);
 
            case SyntaxKind.IdentifierName:
                return TryGenerateNameRef((IdentifierNameSyntax)expression);
 
            case SyntaxKind.InvocationExpression:
                return GenerateMethodCall((InvocationExpressionSyntax)expression);
 
            case SyntaxKind.ElementAccessExpression:
                return TryGenerateArrayElementAccess((ElementAccessExpressionSyntax)expression);
 
            case SyntaxKind.TypeOfExpression:
                return TryGenerateTypeOfExpression((TypeOfExpressionSyntax)expression);
 
            case SyntaxKind.ThisExpression:
                GenerateThisReference();
                return true;
 
            case SyntaxKind.BaseExpression:
                GenerateBaseReference();
                return true;
        }
 
        return false;
    }
 
    private bool TryGenerateLiteral(ExpressionSyntax expression)
    {
        /*
            <ElementType name="Literal" content="eltOnly">
            - <group order="one">
                <element type="Null" /> 
                <element type="Number" /> 
                <element type="Boolean" /> 
                <element type="Char" /> 
                <element type="String" /> 
                <element type="Array" /> 
                <element type="Type" /> 
                </group>
            </ElementType>
        */
 
        using (LiteralTag())
        {
            var constantValue = SemanticModel.GetConstantValue(expression);
            if (!constantValue.HasValue)
            {
                return false;
            }
 
            var type = SemanticModel.GetTypeInfo(expression).Type;
            if (type == null)
            {
                return false;
            }
 
            switch (expression.Kind())
            {
                case SyntaxKind.UnaryMinusExpression:
                case SyntaxKind.NumericLiteralExpression:
                    GenerateNumber(constantValue.Value, type);
                    return true;
 
                case SyntaxKind.StringLiteralExpression:
                    GenerateString((string)constantValue.Value);
                    return true;
 
                case SyntaxKind.TrueLiteralExpression:
                case SyntaxKind.FalseLiteralExpression:
                    GenerateBoolean((bool)constantValue.Value);
                    return true;
            }
 
            return false;
        }
    }
 
    private bool TryGenerateCharLiteral(ExpressionSyntax expression)
    {
        // For non-letters and digits, generate a cast of the numeric value to a char.
        // Otherwise, we might end up generating invalid XML.
        if (expression.Kind() != SyntaxKind.CharacterLiteralExpression)
        {
            return false;
        }
 
        var constantValue = SemanticModel.GetConstantValue(expression);
        if (!constantValue.HasValue)
        {
            return false;
        }
 
        var ch = (char)constantValue.Value;
 
        if (!char.IsLetterOrDigit(ch))
        {
            using (CastTag())
            {
                GenerateType(SpecialType.System_Char);
 
                using (ExpressionTag())
                using (LiteralTag())
                {
                    GenerateNumber((ushort)ch, SpecialType.System_UInt16);
                }
            }
        }
        else
        {
            using (LiteralTag())
            {
                GenerateChar(ch);
            }
        }
 
        return true;
    }
 
    private bool TryGenerateParentheses(ParenthesizedExpressionSyntax parenthesizedExpression)
    {
        using (ParenthesesTag())
        {
            return TryGenerateExpression(parenthesizedExpression.Expression);
        }
    }
 
    private bool TryGenerateBinaryOperation(BinaryExpressionSyntax binaryExpression)
    {
        BinaryOperatorKind kind;
        switch (binaryExpression.Kind())
        {
            case SyntaxKind.AddExpression:
                kind = BinaryOperatorKind.Plus;
                break;
            case SyntaxKind.BitwiseOrExpression:
                kind = BinaryOperatorKind.BitwiseOr;
                break;
            case SyntaxKind.BitwiseAndExpression:
                kind = BinaryOperatorKind.BitwiseAnd;
                break;
            default:
                return false;
        }
 
        using (BinaryOperationTag(kind))
        {
            return TryGenerateExpression(binaryExpression.Left)
                && TryGenerateExpression(binaryExpression.Right);
        }
    }
 
    private bool TryGenerateAssignment(AssignmentExpressionSyntax binaryExpression)
    {
        var kind = BinaryOperatorKind.None;
        switch (binaryExpression.Kind())
        {
            case SyntaxKind.AddAssignmentExpression:
                kind = BinaryOperatorKind.AddDelegate;
                break;
        }
 
        using (AssignmentTag(kind))
        {
            return TryGenerateExpression(binaryExpression.Left)
                && TryGenerateExpression(binaryExpression.Right);
        }
    }
 
    private bool TryGenerateCast(CastExpressionSyntax castExpression)
    {
        var type = SemanticModel.GetTypeInfo(castExpression.Type).Type;
        if (type == null)
        {
            return false;
        }
 
        using (CastTag())
        {
            GenerateType(type);
 
            return TryGenerateExpression(castExpression.Expression);
        }
    }
 
    private bool TryGenerateNewClass(ObjectCreationExpressionSyntax objectCreationExpression)
    {
        if (SemanticModel.GetSymbolInfo(objectCreationExpression.Type).Symbol is not ITypeSymbol type)
        {
            return false;
        }
 
        using (NewClassTag())
        {
            GenerateType(type);
 
            foreach (var argument in objectCreationExpression.ArgumentList.Arguments)
            {
                if (!TryGenerateArgument(argument))
                {
                    return false;
                }
            }
        }
 
        return true;
    }
 
    private bool TryGenerateNewArray(ArrayCreationExpressionSyntax arrayCreationExpression)
    {
        var type = SemanticModel.GetTypeInfo(arrayCreationExpression).Type;
        if (type == null)
        {
            return false;
        }
 
        using (NewArrayTag())
        {
            GenerateType(type);
 
            if (arrayCreationExpression.Initializer != null)
            {
                using (BoundTag())
                using (ExpressionTag())
                using (LiteralTag())
                using (NumberTag())
                {
                    EncodedText(arrayCreationExpression.Initializer.Expressions.Count.ToString());
                }
 
                if (!TryGenerateExpression(arrayCreationExpression.Initializer))
                {
                    return false;
                }
            }
            else
            {
                foreach (var rankSpecifier in arrayCreationExpression.Type.RankSpecifiers)
                {
                    foreach (var size in rankSpecifier.Sizes)
                    {
                        using (BoundTag())
                        {
                            if (!TryGenerateExpression(size))
                            {
                                return false;
                            }
                        }
                    }
                }
            }
        }
 
        return true;
    }
 
    private bool TryGenerateArrayLiteral(InitializerExpressionSyntax initializerExpression)
    {
        using (LiteralTag())
        using (ArrayTag())
        {
            foreach (var expression in initializerExpression.Expressions)
            {
                if (!TryGenerateExpression(expression))
                {
                    return false;
                }
            }
        }
 
        return true;
    }
 
    private bool TryGenerateNameRef(MemberAccessExpressionSyntax memberAccessExpression)
    {
        var symbol = SemanticModel.GetSymbolInfo(memberAccessExpression).Symbol;
 
        // No null check for 'symbol' here. If 'symbol' unknown, we'll
        // generate an "unknown" name ref.
 
        using (NameRefTag(GetVariableKind(symbol)))
        {
            var leftHandSymbol = SemanticModel.GetSymbolInfo(memberAccessExpression.Expression).Symbol;
            if (leftHandSymbol is IAliasSymbol alias)
                leftHandSymbol = alias.Target;
 
            // If the left-hand side is a named type, we generate a literal expression
            // with the type name. Otherwise, we generate the expression normally.
            if (leftHandSymbol is INamedTypeSymbol namedType)
            {
                using (ExpressionTag())
                using (LiteralTag())
                {
                    GenerateType(namedType);
                }
            }
            else if (!TryGenerateExpression(memberAccessExpression.Expression))
            {
                return false;
            }
 
            GenerateName(memberAccessExpression.Name.Identifier.ValueText);
        }
 
        return true;
    }
 
    private bool TryGenerateNameRef(IdentifierNameSyntax identifierName)
    {
        var symbol = SemanticModel.GetSymbolInfo(identifierName).Symbol;
 
        // No null check for 'symbol' here. If 'symbol' unknown, we'll
        // generate an "unknown" name ref.
 
        var variableKind = GetVariableKind(symbol);
 
        using (NameRefTag(variableKind))
        {
            if (symbol != null && variableKind != VariableKind.Local)
            {
                using (ExpressionTag())
                {
                    GenerateThisReference();
                }
            }
 
            GenerateName(identifierName.Identifier.ToString());
        }
 
        return true;
    }
 
    private bool GenerateMethodCall(InvocationExpressionSyntax invocationExpression)
    {
        using (MethodCallTag())
        {
            if (!TryGenerateExpression(invocationExpression.Expression))
            {
                return false;
            }
 
            foreach (var argument in invocationExpression.ArgumentList.Arguments)
            {
                if (!TryGenerateArgument(argument))
                {
                    return false;
                }
            }
        }
 
        return true;
    }
 
    private bool TryGenerateTypeOfExpression(TypeOfExpressionSyntax typeOfExpression)
    {
        if (typeOfExpression.Type == null)
        {
            return false;
        }
 
        var type = SemanticModel.GetTypeInfo(typeOfExpression.Type).Type;
        if (type == null)
        {
            return false;
        }
 
        GenerateType(type);
 
        return true;
    }
 
    private bool TryGenerateArrayElementAccess(ElementAccessExpressionSyntax elementAccessExpression)
    {
        using (ArrayElementAccessTag())
        {
            if (!TryGenerateExpression(elementAccessExpression.Expression))
            {
                return false;
            }
 
            foreach (var argument in elementAccessExpression.ArgumentList.Arguments)
            {
                if (!TryGenerateExpression(argument.Expression))
                {
                    return false;
                }
            }
        }
 
        return true;
    }
 
    private bool TryGenerateArgument(ArgumentSyntax argument)
    {
        using (ArgumentTag())
        {
            return TryGenerateExpression(argument.Expression);
        }
    }
 
    public static string Generate(MethodDeclarationSyntax methodDeclaration, SemanticModel semanticModel)
    {
        var symbol = semanticModel.GetDeclaredSymbol(methodDeclaration);
        var builder = new MethodXmlBuilder(symbol, semanticModel);
 
        builder.GenerateBlock(methodDeclaration.Body);
 
        return builder.ToString();
    }
}