diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index 3ba8fffee3e30..aacfcd25d420d 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -1348,7 +1348,12 @@ public override SyntaxNode InsertMembers(SyntaxNode declaration, int index, IEnu }; private static bool CanHaveAccessibility(SyntaxNode declaration) - => CSharpAccessibilityFacts.Instance.CanHaveAccessibility(declaration); + // For certain declarations, the answer of CanHaveAccessibility depends on the modifiers. + // For example, static constructors cannot have accessibility, but constructors in general can. + // The same applies to file-local declarations (e.g, "file class C { }"). + // For such declarations, we want to return true. This is because we can be explicitly asked to put accessibility. + // In such cases, we'll drop the modifier that prevents us from having accessibility. + => CSharpAccessibilityFacts.Instance.CanHaveAccessibility(declaration, ignoreDeclarationModifiers: true); public override Accessibility GetAccessibility(SyntaxNode declaration) => CSharpAccessibilityFacts.Instance.GetAccessibility(declaration); @@ -1368,6 +1373,20 @@ public override SyntaxNode WithAccessibility(SyntaxNode declaration, Accessibili { var tokens = GetModifierTokens(d); GetAccessibilityAndModifiers(tokens, out _, out var modifiers, out _); + if (modifiers.IsFile && accessibility != Accessibility.NotApplicable) + { + // If user wants to set accessibility for a file-local declaration, we remove file. + // Otherwise, code will be in error: + // error CS9052: File type '{0}' cannot use accessibility modifiers. + modifiers = modifiers.WithIsFile(false); + } + + if (modifiers.IsStatic && declaration.IsKind(SyntaxKind.ConstructorDeclaration) && accessibility != Accessibility.NotApplicable) + { + // If user wants to add accessibility for a static constructor, we remove static modifier + modifiers = modifiers.WithIsStatic(false); + } + var newTokens = Merge(tokens, AsModifierList(accessibility, modifiers)); return SetModifierTokens(d, newTokens); }); @@ -1572,6 +1591,15 @@ private SyntaxNode WithModifiersInternal(SyntaxNode declaration, DeclarationModi { var tokens = GetModifierTokens(d); GetAccessibilityAndModifiers(tokens, out var accessibility, out var tmp, out _); + if (accessibility != Accessibility.NotApplicable) + { + if (modifiers.IsFile || + (modifiers.IsStatic && declaration.IsKind(SyntaxKind.ConstructorDeclaration))) + { + // We remove the accessibility if the modifiers don't allow it. + accessibility = Accessibility.NotApplicable; + } + } var newTokens = Merge(tokens, AsModifierList(accessibility, modifiers)); return SetModifierTokens(d, newTokens); }); @@ -1628,6 +1656,9 @@ private static SyntaxTokenList AsModifierList(Accessibility accessibility, Decla break; } + if (modifiers.IsFile) + list.Add(SyntaxFactory.Token(SyntaxKind.FileKeyword)); + if (modifiers.IsAbstract) list.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); diff --git a/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs b/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs index 1829328657951..83856f7093c3a 100644 --- a/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs +++ b/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs @@ -2284,6 +2284,74 @@ public void TestWithModifiers_AllowedModifiers() Generator.GetModifiers(Generator.WithModifiers(SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration), allModifiers))); } + [Fact] + public void TestAddPublicToStaticConstructor() + { + var ctor = Generator.ConstructorDeclaration("C", modifiers: DeclarationModifiers.Static); + VerifySyntax(ctor, @"static C() +{ +}"); + + var publicCtor = Generator.WithAccessibility(ctor, Accessibility.Public); + VerifySyntax(publicCtor, @"public C() +{ +}"); + } + + [Fact] + public void TestAddStaticToPublicConstructor() + { + var ctor = Generator.ConstructorDeclaration("C", accessibility: Accessibility.Public); + VerifySyntax(ctor, @"public C() +{ +}"); + + var staticCtor = Generator.WithModifiers(ctor, DeclarationModifiers.Static); + VerifySyntax(staticCtor, @"static C() +{ +}"); + } + + [Fact] + public void TestAddAbstractToFileClass() + { + var fileClass = (ClassDeclarationSyntax)SyntaxFactory.ParseMemberDeclaration("file class C { }"); + var fileAbstractClass = Generator.WithModifiers(fileClass, Generator.GetModifiers(fileClass).WithIsAbstract(true)); + VerifySyntax(fileAbstractClass, @"file abstract class C +{ +}"); + } + + [Fact] + public void TestAddPublicToFileClass() + { + var fileClass = (ClassDeclarationSyntax)SyntaxFactory.ParseMemberDeclaration("file class C { }"); + var filePublicClass = Generator.WithAccessibility(fileClass, Accessibility.Public); + VerifySyntax(filePublicClass, @"public class C +{ +}"); + } + + [Fact] + public void TestAddFileModifierToAbstractClass() + { + var abstractClass = (ClassDeclarationSyntax)SyntaxFactory.ParseMemberDeclaration("abstract class C { }"); + var fileAbstractClass = Generator.WithModifiers(abstractClass, Generator.GetModifiers(abstractClass).WithIsFile(true)); + VerifySyntax(fileAbstractClass, @"file abstract class C +{ +}"); + } + + [Fact] + public void TestAddFileModifierToPublicClass() + { + var publicClass = (ClassDeclarationSyntax)SyntaxFactory.ParseMemberDeclaration("public class C { }"); + var filePublicClass = Generator.WithModifiers(publicClass, Generator.GetModifiers(publicClass).WithIsFile(true)); + VerifySyntax(filePublicClass, @"file class C +{ +}"); + } + [Fact] public void TestGetType() { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpAccessibilityFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpAccessibilityFacts.cs index 0f19e230090f4..08fa2b0c4a2ba 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpAccessibilityFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpAccessibilityFacts.cs @@ -23,7 +23,7 @@ private CSharpAccessibilityFacts() { } - public bool CanHaveAccessibility(SyntaxNode declaration) + public bool CanHaveAccessibility(SyntaxNode declaration, bool ignoreDeclarationModifiers = false) { switch (declaration.Kind()) { @@ -34,7 +34,7 @@ public bool CanHaveAccessibility(SyntaxNode declaration) case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.DelegateDeclaration: - return !((MemberDeclarationSyntax)declaration).Modifiers.Any(SyntaxKind.FileKeyword); + return ignoreDeclarationModifiers || !((MemberDeclarationSyntax)declaration).Modifiers.Any(SyntaxKind.FileKeyword); case SyntaxKind.FieldDeclaration: case SyntaxKind.EventFieldDeclaration: @@ -52,7 +52,7 @@ public bool CanHaveAccessibility(SyntaxNode declaration) case SyntaxKind.ConstructorDeclaration: // Static constructor can't have accessibility - return !((ConstructorDeclarationSyntax)declaration).Modifiers.Any(SyntaxKind.StaticKeyword); + return ignoreDeclarationModifiers || !((ConstructorDeclarationSyntax)declaration).Modifiers.Any(SyntaxKind.StaticKeyword); case SyntaxKind.PropertyDeclaration: return ((PropertyDeclarationSyntax)declaration).ExplicitInterfaceSpecifier == null; @@ -141,6 +141,7 @@ public static void GetAccessibilityAndModifiers(SyntaxTokenList modifierList, ou SyntaxKind.RefKeyword => DeclarationModifiers.Ref, SyntaxKind.VolatileKeyword => DeclarationModifiers.Volatile, SyntaxKind.ExternKeyword => DeclarationModifiers.Extern, + SyntaxKind.FileKeyword => DeclarationModifiers.File, _ => DeclarationModifiers.None, }; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/IAccessibilityFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/IAccessibilityFacts.cs index c0b3e350484b9..1f54c840e2a98 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/IAccessibilityFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/IAccessibilityFacts.cs @@ -6,7 +6,12 @@ namespace Microsoft.CodeAnalysis.LanguageServices { internal interface IAccessibilityFacts { - bool CanHaveAccessibility(SyntaxNode declaration); + /// + /// Returns whether a given declaration can have accessibility or not. + /// + /// The declaration node to check + /// A flag that indicates whether to consider modifiers on the given declaration that blocks adding accessibility. + bool CanHaveAccessibility(SyntaxNode declaration, bool ignoreDeclarationModifiers = false); Accessibility GetAccessibility(SyntaxNode declaration); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicAccessibilityFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicAccessibilityFacts.vb index cee652c1b0da4..5ff79eb177b2a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicAccessibilityFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicAccessibilityFacts.vb @@ -20,7 +20,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Private Sub New() End Sub - Public Function CanHaveAccessibility(declaration As SyntaxNode) As Boolean Implements IAccessibilityFacts.CanHaveAccessibility + Public Function CanHaveAccessibility(declaration As SyntaxNode, Optional ignoreDeclarationModifiers As Boolean = False) As Boolean Implements IAccessibilityFacts.CanHaveAccessibility Select Case declaration.Kind Case SyntaxKind.ClassBlock, SyntaxKind.ClassStatement, diff --git a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb index 2be866568a94b..c4ca32fea8aa5 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb @@ -2608,7 +2608,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration End Function Private Shared Function CanHaveAccessibility(declaration As SyntaxNode) As Boolean - Return VisualBasicAccessibilityFacts.Instance.CanHaveAccessibility(declaration) + Return VisualBasicAccessibilityFacts.Instance.CanHaveAccessibility(declaration, ignoreDeclarationModifiers:=True) End Function Private Function WithAccessibilityInternal(declaration As SyntaxNode, accessibility As Accessibility) As SyntaxNode