File: src\roslyn\src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CodeGeneration\FieldGenerator.cs
Web Access
Project: src\roslyn\src\Workspaces\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj (Microsoft.CodeAnalysis.CSharp.Workspaces)
// 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.

using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration;

using static CodeGenerationHelpers;
using static CSharpCodeGenerationHelpers;
using static CSharpSyntaxTokens;
using static SyntaxFactory;

internal static class FieldGenerator
{
    private static MemberDeclarationSyntax? LastField(
        SyntaxList<MemberDeclarationSyntax> members,
        FieldDeclarationSyntax fieldDeclaration)
    {
        var lastConst = members.OfType<FieldDeclarationSyntax>()
                               .Where(f => f.Modifiers.Any(SyntaxKind.ConstKeyword))
                               .LastOrDefault();

        // Place a const after the last existing const.  If we don't have a last const
        // we'll just place the const before the first member in the type.
        if (fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
        {
            return lastConst;
        }

        var lastReadOnly = members.OfType<FieldDeclarationSyntax>()
                                  .Where(f => f.Modifiers.Any(SyntaxKind.ReadOnlyKeyword))
                                  .LastOrDefault();

        var lastNormal = members.OfType<FieldDeclarationSyntax>()
                                .Where(f => !f.Modifiers.Any(SyntaxKind.ReadOnlyKeyword) && !f.Modifiers.Any(SyntaxKind.ConstKeyword))
                                .LastOrDefault();

        // Place a readonly field after the last readonly field if we have one.  Otherwise
        // after the last field/const.
        return fieldDeclaration.Modifiers.Any(SyntaxKind.ReadOnlyKeyword)
            ? lastReadOnly ?? lastConst ?? lastNormal
            : lastNormal ?? lastReadOnly ?? lastConst;
    }

    internal static CompilationUnitSyntax AddFieldTo(
        CompilationUnitSyntax destination,
        IFieldSymbol field,
        CSharpCodeGenerationContextInfo info,
        IList<bool>? availableIndices,
        CancellationToken cancellationToken)
    {
        var declaration = GenerateFieldDeclaration(field, info, cancellationToken);

        // Place the field after the last field or const, or at the start of the type
        // declaration.
        var members = Insert(destination.Members, declaration, info, availableIndices,
            after: m => LastField(m, declaration), before: FirstMember);
        return destination.WithMembers([.. members]);
    }

    internal static TypeDeclarationSyntax AddFieldTo(
        TypeDeclarationSyntax destination,
        IFieldSymbol field,
        CSharpCodeGenerationContextInfo info,
        IList<bool>? availableIndices,
        CancellationToken cancellationToken)
    {
        var declaration = GenerateFieldDeclaration(field, info, cancellationToken);

        // Place the field after the last field or const, or at the start of the type
        // declaration.
        var members = Insert(destination.Members, declaration, info, availableIndices,
            after: m => LastField(m, declaration), before: FirstMember);

        return AddMembersTo(destination, members, cancellationToken);
    }

    public static FieldDeclarationSyntax GenerateFieldDeclaration(
        IFieldSymbol field, CSharpCodeGenerationContextInfo info, CancellationToken cancellationToken)
    {
        var reusableSyntax = GetReuseableSyntaxNodeForSymbol<FieldDeclarationSyntax>(field, info);
        if (reusableSyntax != null)
        {
            return reusableSyntax;
        }

        var initializer = CodeGenerationFieldInfo.GetInitializer(field) is ExpressionSyntax initializerNode
            ? EqualsValueClause(initializerNode)
            : GenerateEqualsValue(field);

        var fieldDeclaration = FieldDeclaration(
            AttributeGenerator.GenerateAttributeLists(field.GetAttributes(), info),
            GenerateModifiers(field, info),
            VariableDeclaration(
                field.Type.GenerateTypeSyntax(),
                [AddAnnotationsTo(field, VariableDeclarator(field.Name.ToIdentifierToken(), null, initializer))]));

        return AddFormatterAndCodeGeneratorAnnotationsTo(
            ConditionallyAddDocumentationCommentTo(fieldDeclaration, field, info, cancellationToken));
    }

    private static EqualsValueClauseSyntax? GenerateEqualsValue(IFieldSymbol field)
    {
        if (field.HasConstantValue)
        {
            var canUseFieldReference = field.Type != null && !field.Type.Equals(field.ContainingType);
            return EqualsValueClause(ExpressionGenerator.GenerateExpression(field.Type, field.ConstantValue, canUseFieldReference));
        }

        return null;
    }

    private static SyntaxTokenList GenerateModifiers(IFieldSymbol field, CSharpCodeGenerationContextInfo info)
    {
        using var _ = ArrayBuilder<SyntaxToken>.GetInstance(out var tokens);

        AddAccessibilityModifiers(field.DeclaredAccessibility, tokens, info, Accessibility.Private);
        if (field.IsConst)
        {
            tokens.Add(ConstKeyword);
        }
        else
        {
            if (field.IsStatic)
                tokens.Add(StaticKeyword);

            if (field.IsReadOnly)
                tokens.Add(ReadOnlyKeyword);

            if (field.IsRequired)
                tokens.Add(RequiredKeyword);
        }

        if (CodeGenerationFieldInfo.GetIsUnsafe(field))
            tokens.Add(UnsafeKeyword);

        return [.. tokens];
    }
}