File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\Services\SyntaxFacts\CSharpAccessibilityFacts.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 Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using System.Diagnostics.CodeAnalysis;
 
#if CODE_STYLE
using Microsoft.CodeAnalysis.Internal.Editing;
#else
using Microsoft.CodeAnalysis.Editing;
#endif
 
namespace Microsoft.CodeAnalysis.CSharp.LanguageService;
 
internal class CSharpAccessibilityFacts : IAccessibilityFacts
{
    public static readonly IAccessibilityFacts Instance = new CSharpAccessibilityFacts();
 
    private CSharpAccessibilityFacts()
    {
    }
 
    public bool CanHaveAccessibility(SyntaxNode declaration, bool ignoreDeclarationModifiers = false)
    {
        switch (declaration.Kind())
        {
            case SyntaxKind.ClassDeclaration:
            case SyntaxKind.RecordDeclaration:
            case SyntaxKind.StructDeclaration:
            case SyntaxKind.RecordStructDeclaration:
            case SyntaxKind.InterfaceDeclaration:
            case SyntaxKind.EnumDeclaration:
            case SyntaxKind.DelegateDeclaration:
                return ignoreDeclarationModifiers || !((MemberDeclarationSyntax)declaration).Modifiers.Any(SyntaxKind.FileKeyword);
 
            case SyntaxKind.FieldDeclaration:
            case SyntaxKind.EventFieldDeclaration:
            case SyntaxKind.GetAccessorDeclaration:
            case SyntaxKind.SetAccessorDeclaration:
            case SyntaxKind.InitAccessorDeclaration:
            case SyntaxKind.AddAccessorDeclaration:
            case SyntaxKind.RemoveAccessorDeclaration:
                return true;
 
            case SyntaxKind.VariableDeclaration:
            case SyntaxKind.VariableDeclarator:
                var declarationKind = GetDeclarationKind(declaration);
                return declarationKind is DeclarationKind.Field or DeclarationKind.Event;
 
            case SyntaxKind.ConstructorDeclaration:
                // Static constructor can't have accessibility
                return ignoreDeclarationModifiers || !((ConstructorDeclarationSyntax)declaration).Modifiers.Any(SyntaxKind.StaticKeyword);
 
            case SyntaxKind.PropertyDeclaration:
                return ((PropertyDeclarationSyntax)declaration).ExplicitInterfaceSpecifier == null;
 
            case SyntaxKind.IndexerDeclaration:
                return ((IndexerDeclarationSyntax)declaration).ExplicitInterfaceSpecifier == null;
 
            case SyntaxKind.OperatorDeclaration:
                return ((OperatorDeclarationSyntax)declaration).ExplicitInterfaceSpecifier == null;
 
            case SyntaxKind.ConversionOperatorDeclaration:
                return ((ConversionOperatorDeclarationSyntax)declaration).ExplicitInterfaceSpecifier == null;
 
            case SyntaxKind.MethodDeclaration:
                var method = (MethodDeclarationSyntax)declaration;
                if (method.ExplicitInterfaceSpecifier != null)
                {
                    // explicit interface methods can't have accessibility.
                    return false;
                }
 
                if (method.Modifiers.Any(SyntaxKind.PartialKeyword))
                {
                    // partial methods can't have accessibility modifiers.
                    return false;
                }
 
                return true;
 
            case SyntaxKind.EventDeclaration:
                return ((EventDeclarationSyntax)declaration).ExplicitInterfaceSpecifier == null;
 
            default:
                return false;
        }
    }
 
    public Accessibility GetAccessibility(SyntaxNode declaration)
    {
        if (!CanHaveAccessibility(declaration))
            return Accessibility.NotApplicable;
 
        var modifierTokens = GetModifierTokens(declaration);
        GetAccessibilityAndModifiers(modifierTokens, out var accessibility, out _, out _);
        return accessibility;
    }
 
    public static void GetAccessibilityAndModifiers(SyntaxTokenList modifierList, out Accessibility accessibility, out DeclarationModifiers modifiers, out bool isDefault)
    {
        accessibility = Accessibility.NotApplicable;
        modifiers = DeclarationModifiers.None;
        isDefault = false;
 
        foreach (var token in modifierList)
        {
            accessibility = (token.Kind(), accessibility) switch
            {
                (SyntaxKind.PublicKeyword, _) => Accessibility.Public,
 
                (SyntaxKind.PrivateKeyword, Accessibility.Protected) => Accessibility.ProtectedAndInternal,
                (SyntaxKind.PrivateKeyword, _) => Accessibility.Private,
 
                (SyntaxKind.InternalKeyword, Accessibility.Protected) => Accessibility.ProtectedOrInternal,
                (SyntaxKind.InternalKeyword, _) => Accessibility.Internal,
 
                (SyntaxKind.ProtectedKeyword, Accessibility.Private) => Accessibility.ProtectedAndInternal,
                (SyntaxKind.ProtectedKeyword, Accessibility.Internal) => Accessibility.ProtectedOrInternal,
                (SyntaxKind.ProtectedKeyword, _) => Accessibility.Protected,
 
                _ => accessibility,
            };
 
            modifiers |= token.Kind() switch
            {
                SyntaxKind.AbstractKeyword => DeclarationModifiers.Abstract,
                SyntaxKind.NewKeyword => DeclarationModifiers.New,
                SyntaxKind.OverrideKeyword => DeclarationModifiers.Override,
                SyntaxKind.VirtualKeyword => DeclarationModifiers.Virtual,
                SyntaxKind.StaticKeyword => DeclarationModifiers.Static,
                SyntaxKind.AsyncKeyword => DeclarationModifiers.Async,
                SyntaxKind.ConstKeyword => DeclarationModifiers.Const,
                SyntaxKind.ReadOnlyKeyword => DeclarationModifiers.ReadOnly,
                SyntaxKind.SealedKeyword => DeclarationModifiers.Sealed,
                SyntaxKind.UnsafeKeyword => DeclarationModifiers.Unsafe,
                SyntaxKind.PartialKeyword => DeclarationModifiers.Partial,
                SyntaxKind.RefKeyword => DeclarationModifiers.Ref,
                SyntaxKind.VolatileKeyword => DeclarationModifiers.Volatile,
                SyntaxKind.ExternKeyword => DeclarationModifiers.Extern,
                SyntaxKind.FileKeyword => DeclarationModifiers.File,
                SyntaxKind.RequiredKeyword => DeclarationModifiers.Required,
                _ => DeclarationModifiers.None,
            };
 
            isDefault |= token.Kind() == SyntaxKind.DefaultKeyword;
        }
    }
 
    public static DeclarationKind GetDeclarationKind(SyntaxNode declaration)
    {
        switch (declaration.Kind())
        {
            case SyntaxKind.ClassDeclaration:
            case SyntaxKind.RecordDeclaration:
                return DeclarationKind.Class;
            case SyntaxKind.StructDeclaration:
            case SyntaxKind.RecordStructDeclaration:
                return DeclarationKind.Struct;
            case SyntaxKind.InterfaceDeclaration:
                return DeclarationKind.Interface;
            case SyntaxKind.EnumDeclaration:
                return DeclarationKind.Enum;
            case SyntaxKind.DelegateDeclaration:
                return DeclarationKind.Delegate;
 
            case SyntaxKind.MethodDeclaration:
                return DeclarationKind.Method;
            case SyntaxKind.OperatorDeclaration:
                return DeclarationKind.Operator;
            case SyntaxKind.ConversionOperatorDeclaration:
                return DeclarationKind.ConversionOperator;
            case SyntaxKind.ConstructorDeclaration:
                return DeclarationKind.Constructor;
            case SyntaxKind.DestructorDeclaration:
                return DeclarationKind.Destructor;
 
            case SyntaxKind.PropertyDeclaration:
                return DeclarationKind.Property;
            case SyntaxKind.IndexerDeclaration:
                return DeclarationKind.Indexer;
            case SyntaxKind.EventDeclaration:
                return DeclarationKind.CustomEvent;
            case SyntaxKind.EnumMemberDeclaration:
                return DeclarationKind.EnumMember;
            case SyntaxKind.CompilationUnit:
                return DeclarationKind.CompilationUnit;
            case SyntaxKind.NamespaceDeclaration:
            case SyntaxKind.FileScopedNamespaceDeclaration:
                return DeclarationKind.Namespace;
            case SyntaxKind.UsingDirective:
                return DeclarationKind.NamespaceImport;
            case SyntaxKind.Parameter:
                return DeclarationKind.Parameter;
 
            case SyntaxKind.ParenthesizedLambdaExpression:
            case SyntaxKind.SimpleLambdaExpression:
                return DeclarationKind.LambdaExpression;
 
            case SyntaxKind.FieldDeclaration:
                var fd = (FieldDeclarationSyntax)declaration;
                if (fd.Declaration != null && fd.Declaration.Variables.Count == 1)
                {
                    // this node is considered the declaration if it contains only one variable.
                    return DeclarationKind.Field;
                }
                else
                {
                    return DeclarationKind.None;
                }
 
            case SyntaxKind.EventFieldDeclaration:
                var ef = (EventFieldDeclarationSyntax)declaration;
                if (ef.Declaration != null && ef.Declaration.Variables.Count == 1)
                {
                    // this node is considered the declaration if it contains only one variable.
                    return DeclarationKind.Event;
                }
                else
                {
                    return DeclarationKind.None;
                }
 
            case SyntaxKind.LocalDeclarationStatement:
                var ld = (LocalDeclarationStatementSyntax)declaration;
                if (ld.Declaration != null && ld.Declaration.Variables.Count == 1)
                {
                    // this node is considered the declaration if it contains only one variable.
                    return DeclarationKind.Variable;
                }
                else
                {
                    return DeclarationKind.None;
                }
 
            case SyntaxKind.VariableDeclaration:
                {
                    var vd = (VariableDeclarationSyntax)declaration;
                    if (vd.Variables.Count == 1 && vd.Parent == null)
                    {
                        // this node is the declaration if it contains only one variable and has no parent.
                        return DeclarationKind.Variable;
                    }
                    else
                    {
                        return DeclarationKind.None;
                    }
                }
 
            case SyntaxKind.VariableDeclarator:
                {
                    var vd = declaration.Parent as VariableDeclarationSyntax;
 
                    // this node is considered the declaration if it is one among many, or it has no parent
                    if (vd == null || vd.Variables.Count > 1)
                    {
                        if (ParentIsFieldDeclaration(vd))
                        {
                            return DeclarationKind.Field;
                        }
                        else if (ParentIsEventFieldDeclaration(vd))
                        {
                            return DeclarationKind.Event;
                        }
                        else
                        {
                            return DeclarationKind.Variable;
                        }
                    }
 
                    break;
                }
 
            case SyntaxKind.AttributeList:
                var list = (AttributeListSyntax)declaration;
                if (list.Attributes.Count == 1)
                {
                    return DeclarationKind.Attribute;
                }
 
                break;
 
            case SyntaxKind.Attribute:
                if (declaration.Parent is not AttributeListSyntax parentList || parentList.Attributes.Count > 1)
                {
                    return DeclarationKind.Attribute;
                }
 
                break;
 
            case SyntaxKind.GetAccessorDeclaration:
                return DeclarationKind.GetAccessor;
            case SyntaxKind.SetAccessorDeclaration:
            case SyntaxKind.InitAccessorDeclaration:
                return DeclarationKind.SetAccessor;
            case SyntaxKind.AddAccessorDeclaration:
                return DeclarationKind.AddAccessor;
            case SyntaxKind.RemoveAccessorDeclaration:
                return DeclarationKind.RemoveAccessor;
        }
 
        return DeclarationKind.None;
    }
 
    public static SyntaxTokenList GetModifierTokens(SyntaxNode declaration)
        => declaration switch
        {
            MemberDeclarationSyntax memberDecl => memberDecl.Modifiers,
            ParameterSyntax parameter => parameter.Modifiers,
            LocalDeclarationStatementSyntax localDecl => localDecl.Modifiers,
            LocalFunctionStatementSyntax localFunc => localFunc.Modifiers,
            AccessorDeclarationSyntax accessor => accessor.Modifiers,
            VariableDeclarationSyntax varDecl => GetModifierTokens(varDecl.GetRequiredParent()),
            VariableDeclaratorSyntax varDecl => GetModifierTokens(varDecl.GetRequiredParent()),
            AnonymousFunctionExpressionSyntax anonymous => anonymous.Modifiers,
            _ => default,
        };
 
    public static bool ParentIsFieldDeclaration([NotNullWhen(true)] SyntaxNode? node)
        => node?.Parent.IsKind(SyntaxKind.FieldDeclaration) ?? false;
 
    public static bool ParentIsEventFieldDeclaration([NotNullWhen(true)] SyntaxNode? node)
        => node?.Parent.IsKind(SyntaxKind.EventFieldDeclaration) ?? false;
 
    public static bool ParentIsLocalDeclarationStatement([NotNullWhen(true)] SyntaxNode? node)
        => node?.Parent.IsKind(SyntaxKind.LocalDeclarationStatement) ?? false;
}