File: src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CodeGeneration\FieldGenerator.cs
Web Access
Project: src\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.Editing;
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(info.Generator, 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(SyntaxGenerator generator, IFieldSymbol field)
    {
        if (field.HasConstantValue)
        {
            var canUseFieldReference = field.Type != null && !field.Type.Equals(field.ContainingType);
            return EqualsValueClause(ExpressionGenerator.GenerateExpression(generator, 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];
    }
}