From f456194cb66c9f70499a8031f7855113ad6d90e0 Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Tue, 21 Feb 2023 23:11:39 +0000 Subject: [PATCH 01/17] Add initial AddPropertyAccess --- CodeMaid.VS2022/CodeMaid.VS2022.csproj | 9 + CodeMaid/CodeMaid.csproj | 11 +- CodeMaidShared/CodeMaidPackage.cs | 4 + CodeMaidShared/CodeMaidShared.projitems | 2 + CodeMaidShared/Helpers/CommandHelper.cs | 6 + .../AddExplicitAccessModifierLogic.cs | 274 ++++++++++++++++++ .../Logic/Cleaning/CodeCleanupManager.cs | 4 +- CodeMaidShared/Logic/Cleaning/Global.cs | 82 ++++++ 8 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Global.cs diff --git a/CodeMaid.VS2022/CodeMaid.VS2022.csproj b/CodeMaid.VS2022/CodeMaid.VS2022.csproj index 10e8a1cc0..8e3cedd87 100644 --- a/CodeMaid.VS2022/CodeMaid.VS2022.csproj +++ b/CodeMaid.VS2022/CodeMaid.VS2022.csproj @@ -721,6 +721,15 @@ 1.0.2 + + 4.4.0 + + + 4.4.0 + + + 4.4.0 + 17.0.0-previews-2-31512-422 runtime diff --git a/CodeMaid/CodeMaid.csproj b/CodeMaid/CodeMaid.csproj index e0ea74656..98d8e6c65 100644 --- a/CodeMaid/CodeMaid.csproj +++ b/CodeMaid/CodeMaid.csproj @@ -36,7 +36,7 @@ full false bin\Debug\ - DEBUG;TRACE + DEBUG prompt 4 false @@ -339,6 +339,15 @@ 1.0.2 + + 4.4.0 + + + 4.4.0 + + + 4.4.0 + 16.10.31321.278 runtime diff --git a/CodeMaidShared/CodeMaidPackage.cs b/CodeMaidShared/CodeMaidPackage.cs index f8a1ef571..80e81b2cd 100644 --- a/CodeMaidShared/CodeMaidPackage.cs +++ b/CodeMaidShared/CodeMaidPackage.cs @@ -206,6 +206,10 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke await RegisterCommandsAsync(); await RegisterEventListenersAsync(); + var componentModel = (IComponentModel)this.GetService((typeof(SComponentModel))); + + //var workspace = componentModel.GetService + Instance = this; } diff --git a/CodeMaidShared/CodeMaidShared.projitems b/CodeMaidShared/CodeMaidShared.projitems index 8fa3bac20..7853d9d1c 100644 --- a/CodeMaidShared/CodeMaidShared.projitems +++ b/CodeMaidShared/CodeMaidShared.projitems @@ -83,10 +83,12 @@ + + diff --git a/CodeMaidShared/Helpers/CommandHelper.cs b/CodeMaidShared/Helpers/CommandHelper.cs index 962064e7e..20ccce7f4 100644 --- a/CodeMaidShared/Helpers/CommandHelper.cs +++ b/CodeMaidShared/Helpers/CommandHelper.cs @@ -54,6 +54,12 @@ public Command FindCommand(params string[] commandNames) { if (commandNames == null || commandNames.Length == 0) return null; + var commands = _package.IDE.Commands.OfType().Select(x => x.Name).ToArray(); + + var strings = string.Join(",\n", commands); + + var c = _package.IDE.Commands.OfType().Where(x => x.Name.Contains("Cleanup")).ToArray(); + return _package.IDE.Commands.OfType().FirstOrDefault(x => commandNames.Contains(x.Name)); } diff --git a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs new file mode 100644 index 000000000..d5c067117 --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs @@ -0,0 +1,274 @@ +using EnvDTE; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Elfie.Model; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.VisualStudio.Shell; +using SteveCadwallader.CodeMaid.Helpers; +using SteveCadwallader.CodeMaid.Model.CodeItems; +using SteveCadwallader.CodeMaid.Properties; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection.Metadata; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Xml.Linq; + +namespace SteveCadwallader.CodeMaid.Logic.Cleaning +{ + /// + /// A class for encapsulating insertion of explicit access modifier logic. + /// + internal static class AddExplicitAccessModifierLogic + { + //#region Constants + + //private const string PartialKeyword = "partial"; + + //#endregion Constants + + #region Constructors + + /// + /// The singleton instance of the class. + /// + //private static AddExplicitAccessModifierLogic _instance; + + /// + /// Gets an instance of the class. + /// + /// An instance of the class. + //internal static AddExplicitAccessModifierLogic GetInstance() + //{ + // return _instance ?? (_instance = new AddExplicitAccessModifierLogic()); + //} + + ///// + ///// Initializes a new instance of the class. + ///// + //private AddExplicitAccessModifierLogic() + //{ + //} + + #endregion Constructors + + public static void InsertExplicitMemberModifiers(CodeMaidPackage package) + { + Start(package); + } + //int MyProperty { get; set; } + //public int MyProperty2 { get; set; } + //public required int MyProperty3 { get; set; } + + private static void Start(CodeMaidPackage package) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + Global.Package = package; + + var document = Global.GetActiveDocument(); + + if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) + { + root = Process(root, document).Result; + + document = document.WithSyntaxRoot(root); + + Global.Workspace.TryApplyChanges(document.Project.Solution); + } + } + + public static async Task Process(SyntaxNode root, Microsoft.CodeAnalysis.Document document) + { + if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods) return root; + + //var propertyNodes = root.(node => node.IsKind(SyntaxKind.PropertyDeclaration)); + var propertyNodes = root.DescendantNodes().OfType(); + + var semanticModel = await document.GetRequiredSemanticModelAsync(default).ConfigureAwait(false); + + if (propertyNodes.Any()) + { + //root = root.ReplaceNodes(propertyNodes, + // (originalNode, newNode) => + // { + // var symbol = semanticModel.GetDeclaredSymbol(originalNode, default); + + // if (symbol is null) + // { + // throw new ArgumentNullException(nameof(symbol)); + // } + + // var editor = new SyntaxEditor(originalNode, Global.Workspace.Services); + + // AddAccessibilityModifiersHelpers.UpdateDeclaration(editor, symbol, originalNode); + // return originalNode; + // }); + + //foreach (var node in propertyNodes) + //{ + // var symbol = semanticModel.GetDeclaredSymbol(node, default); + + // if (symbol is null) + // { + // throw new ArgumentNullException(nameof(symbol)); + // } + // var editor = new SyntaxEditor(node, Global.Workspace.Services); + // AddAccessibilityModifiersHelpers.UpdateDeclaration(editor, symbol, node); + //} + + var editor = new SyntaxEditor(root, Global.Workspace.Services); + root = root.ReplaceNodes(propertyNodes, + (originalNode, newNode) => + { + var symbol = semanticModel.GetDeclaredSymbol(originalNode); + + if (symbol is null) + { + throw new ArgumentNullException(nameof(symbol)); + } + //AddAccessibilityModifiersHelpers.UpdateDeclaration(editor, symbol, node); + + var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); + + return UpdateAccessibility(originalNode, preferredAccessibility); + + SyntaxNode UpdateAccessibility(SyntaxNode declaration, Accessibility preferredAccessibility) + { + var generator = editor.Generator; + + // If there was accessibility on the member, then remove it. If there was no accessibility, then add + // the preferred accessibility for this member. + var newNode = generator.GetAccessibility(declaration) == Accessibility.NotApplicable + ? generator.WithAccessibility(declaration, preferredAccessibility) + : generator.WithAccessibility(declaration, Accessibility.NotApplicable); + + return newNode; + } + }); + } + + return Formatter.Format(root, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); + } + + //public void FixAllAsync( + // Document document, ImmutableArray diagnostics, + // SyntaxEditor editor) + //{ + // var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + // foreach (var diagnostic in diagnostics) + // { + // var declaration = diagnostic.AdditionalLocations[0].FindNode(cancellationToken); + // var declarator = MapToDeclarator(declaration); + // var symbol = semanticModel.GetDeclaredSymbol(declarator, cancellationToken); + // Contract.ThrowIfNull(symbol); + // AddAccessibilityModifiersHelpers.UpdateDeclaration(editor, symbol, declaration); + // } + //} + } + + internal static class DocumentExtensions + { + public static async ValueTask GetRequiredSemanticModelAsync(this Microsoft.CodeAnalysis.Document document, CancellationToken cancellationToken) + { + if (document.TryGetSemanticModel(out var semanticModel)) + return semanticModel; + + semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + return semanticModel ?? throw new InvalidOperationException($"Syntax tree is required to accomplish the task but is not supported by document {document.Name}"); + } + } + + internal static partial class AddAccessibilityModifiersHelpers + { + public static void UpdateDeclaration( + SyntaxEditor editor, ISymbol symbol, SyntaxNode declaration) + { + if (symbol is null) + { + throw new ArgumentNullException(nameof(symbol)); + } + + var preferredAccessibility = GetPreferredAccessibility(symbol); + + // Check to see if we need to add or remove + // If there's a modifier, then we need to remove it, otherwise no modifier, add it. + editor.ReplaceNode( + declaration, + (currentDeclaration, _) => UpdateAccessibility(currentDeclaration, preferredAccessibility)); + + return; + + SyntaxNode UpdateAccessibility(SyntaxNode declaration, Accessibility preferredAccessibility) + { + var generator = editor.Generator; + + // If there was accessibility on the member, then remove it. If there was no accessibility, then add + // the preferred accessibility for this member. + return generator.GetAccessibility(declaration) == Accessibility.NotApplicable + ? generator.WithAccessibility(declaration, preferredAccessibility) + : generator.WithAccessibility(declaration, Accessibility.NotApplicable); + } + } + + internal static Accessibility GetPreferredAccessibility(ISymbol symbol) + { + // If we have an overridden member, then if we're adding an accessibility modifier, use the + // accessibility of the member we're overriding as both should be consistent here. + // TODO Check override + //if (symbol.GetOverriddenMember() is { DeclaredAccessibility: var accessibility }) + // return accessibility; + + // Default abstract members to be protected, and virtual members to be public. They can't be private as + // that's not legal. And these are reasonable default values for them. + if (symbol is IMethodSymbol or IPropertySymbol or IEventSymbol) + { + if (symbol.IsAbstract) + return Accessibility.Protected; + + if (symbol.IsVirtual) + return Accessibility.Public; + } + + // Otherwise, default to whatever accessibility no-accessibility means for this member; + return symbol.DeclaredAccessibility; + } + + // internal static Symbol GetOverriddenMember(Symbol substitutedOverridingMember, Symbol overriddenByDefinitionMember) + // { + // Debug.Assert(!substitutedOverridingMember.IsDefinition); + + // if ((object)overriddenByDefinitionMember != null) + // { + // NamedTypeSymbol overriddenByDefinitionContaining = overriddenByDefinitionMember.ContainingType; + // NamedTypeSymbol overriddenByDefinitionContainingTypeDefinition = overriddenByDefinitionContaining.OriginalDefinition; + // for (NamedTypeSymbol baseType = substitutedOverridingMember.ContainingType.BaseTypeNoUseSiteDiagnostics; + // (object)baseType != null; + // baseType = baseType.BaseTypeNoUseSiteDiagnostics) + // { + // if (TypeSymbol.Equals(baseType.OriginalDefinition, overriddenByDefinitionContainingTypeDefinition, TypeCompareKind.ConsiderEverything2)) + // { + // if (TypeSymbol.Equals(baseType, overriddenByDefinitionContaining, TypeCompareKind.ConsiderEverything2)) + // { + // return overriddenByDefinitionMember; + // } + + // return overriddenByDefinitionMember.OriginalDefinition.SymbolAsMember(baseType); + // } + // } + + // throw ExceptionUtilities.Unreachable(); + // } + + // return null; + // } + } +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs index 850536ca5..e41ad8cb7 100644 --- a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs +++ b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs @@ -324,7 +324,9 @@ private void RunCodeCleanupCSharp(Document document) _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnFields(fields); _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnInterfaces(interfaces); _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnMethods(methods); - _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnProperties(properties); + //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnProperties(properties); + AddExplicitAccessModifierLogic.InsertExplicitMemberModifiers(_package); + _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnStructs(structs); // Perform insertion of whitespace cleanup. diff --git a/CodeMaidShared/Logic/Cleaning/Global.cs b/CodeMaidShared/Logic/Cleaning/Global.cs new file mode 100644 index 000000000..6fd43791b --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Global.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows; +using System.Windows.Media; +using System.Xml.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.Win32; +using Task = System.Threading.Tasks.Task; +using Document = Microsoft.CodeAnalysis.Document; +using Solution = Microsoft.CodeAnalysis.Solution; +using DteDocument = EnvDTE.Document; +using Microsoft.VisualStudio.LanguageServices; + +namespace SteveCadwallader.CodeMaid.Logic.Cleaning +{ + public static class Global + { + static public AsyncPackage Package; + + static public T GetService() + => (T)Package?.GetServiceAsync(typeof(T))?.Result; + + static public DteDocument GetActiveDteDocument() + { + ThreadHelper.ThrowIfNotOnUIThread(); + dynamic dte = GetService(); + return (DteDocument)dte.ActiveDocument; + } + + static IVsStatusbar Statusbar; + + internal static void SetStatusMessage(string message) + { + if (Statusbar == null) + { + Statusbar = GetService(); + // StatusBar = Package.GetGlobalService(typeof(IVsStatusbar)) as IVsStatusbar; + } + + Statusbar.SetText(message); + } + + public static Document GetActiveDocument() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + Solution solution = Workspace.CurrentSolution; + string activeDocPath = GetActiveDteDocument()?.FullName; + + if (activeDocPath != null) + return solution.Projects + .SelectMany(x => x.Documents) + .FirstOrDefault(x => x.SupportsSyntaxTree && + x.SupportsSemanticModel && + x.FilePath == activeDocPath); + return null; + } + + private static VisualStudioWorkspace workspace = null; + + static public VisualStudioWorkspace Workspace + { + get + { + if (workspace == null) + { + IComponentModel componentModel = GetService() as IComponentModel; + workspace = componentModel.GetService(); + } + return workspace; + } + } + } +} \ No newline at end of file From 1af27d68d5c64e40630c71c2e4bfd092f82196bb Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Wed, 22 Feb 2023 20:27:21 +0000 Subject: [PATCH 02/17] Add helper methods --- CodeMaid.UnitTests/CachedSettingSetTests.cs | 5 +- CodeMaid.UnitTests/CodeMaid.UnitTests.csproj | 1 + CodeMaid.UnitTests/RoslynTests.cs | 54 ++ CodeMaidShared/CodeMaidShared.projitems | 2 + .../AddExplicitAccessModifierLogic.cs | 359 +++++++- .../Cleaning/CSharpAccessibilityFacts.cs | 336 +++++++ .../Logic/Cleaning/CodeCleanupManager.cs | 1 + CodeMaidShared/Logic/Cleaning/Global.cs | 26 +- .../Logic/Cleaning/SyntaxNodeExtensions.cs | 837 ++++++++++++++++++ 9 files changed, 1558 insertions(+), 63 deletions(-) create mode 100644 CodeMaid.UnitTests/RoslynTests.cs create mode 100644 CodeMaidShared/Logic/Cleaning/CSharpAccessibilityFacts.cs create mode 100644 CodeMaidShared/Logic/Cleaning/SyntaxNodeExtensions.cs diff --git a/CodeMaid.UnitTests/CachedSettingSetTests.cs b/CodeMaid.UnitTests/CachedSettingSetTests.cs index b99d5fd55..b3e487204 100644 --- a/CodeMaid.UnitTests/CachedSettingSetTests.cs +++ b/CodeMaid.UnitTests/CachedSettingSetTests.cs @@ -1,4 +1,7 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.VisualStudio.TestTools.UnitTesting; using SteveCadwallader.CodeMaid.Helpers; using SteveCadwallader.CodeMaid.Properties; using System; diff --git a/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj b/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj index 23fc90cc9..b08886a19 100644 --- a/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj +++ b/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj @@ -66,6 +66,7 @@ + diff --git a/CodeMaid.UnitTests/RoslynTests.cs b/CodeMaid.UnitTests/RoslynTests.cs new file mode 100644 index 000000000..5fac64e28 --- /dev/null +++ b/CodeMaid.UnitTests/RoslynTests.cs @@ -0,0 +1,54 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace SteveCadwallader.CodeMaid.UnitTests; + +[TestClass] +public class RoslynTests +{ + public class EmtpyStatementRemoval : CSharpSyntaxRewriter + { + public override SyntaxNode Visit(SyntaxNode node) + { + return base.Visit(node); + } + + public override SyntaxNode VisitEmptyStatement(EmptyStatementSyntax node) + { + //Construct an EmptyStatementSyntax with a missing semicolon + return node.WithSemicolonToken( + SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken) + .WithLeadingTrivia(node.SemicolonToken.LeadingTrivia) + .WithTrailingTrivia(node.SemicolonToken.TrailingTrivia)); + } + } + + [TestMethod] + public void RunRewriter() + { + var tree = CSharpSyntaxTree.ParseText(@" + public class Sample + { + public void Foo() + { + Console.WriteLine(); + + #region SomeRegion + + //Some other code + + #endregion SomeRegion + + ; + } + }"); + + var rewriter = new EmtpyStatementRemoval(); + var result = rewriter.Visit(tree.GetRoot()); + Console.WriteLine(result.ToFullString()); + Assert.AreEqual(1, 1); + } +} \ No newline at end of file diff --git a/CodeMaidShared/CodeMaidShared.projitems b/CodeMaidShared/CodeMaidShared.projitems index 7853d9d1c..d181072cd 100644 --- a/CodeMaidShared/CodeMaidShared.projitems +++ b/CodeMaidShared/CodeMaidShared.projitems @@ -83,6 +83,7 @@ + @@ -93,6 +94,7 @@ + diff --git a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs index d5c067117..689aabae2 100644 --- a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs +++ b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs @@ -1,39 +1,27 @@ -using EnvDTE; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Elfie.Model; using Microsoft.CodeAnalysis.Formatting; using Microsoft.VisualStudio.Shell; -using SteveCadwallader.CodeMaid.Helpers; -using SteveCadwallader.CodeMaid.Model.CodeItems; +using SteveCadwallader.CodeMaid; +using SteveCadwallader.CodeMaid.Logic.Cleaning; using SteveCadwallader.CodeMaid.Properties; using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; -using System.Diagnostics.Contracts; using System.Linq; -using System.Reflection.Metadata; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using System.Windows.Controls; -using System.Xml.Linq; -namespace SteveCadwallader.CodeMaid.Logic.Cleaning +namespace CodeMaidShared.Logic.Cleaning { /// /// A class for encapsulating insertion of explicit access modifier logic. /// internal static class AddExplicitAccessModifierLogic { - //#region Constants - - //private const string PartialKeyword = "partial"; - - //#endregion Constants - #region Constructors /// @@ -89,42 +77,13 @@ public static async Task Process(SyntaxNode root, Microsoft.CodeAnal { if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods) return root; - //var propertyNodes = root.(node => node.IsKind(SyntaxKind.PropertyDeclaration)); - var propertyNodes = root.DescendantNodes().OfType(); + var propertyNodes = root.DescendantNodes().Where(node => node.IsKind(SyntaxKind.PropertyDeclaration)); var semanticModel = await document.GetRequiredSemanticModelAsync(default).ConfigureAwait(false); + var editor = new SyntaxEditor(root, Global.Workspace.Services); if (propertyNodes.Any()) { - //root = root.ReplaceNodes(propertyNodes, - // (originalNode, newNode) => - // { - // var symbol = semanticModel.GetDeclaredSymbol(originalNode, default); - - // if (symbol is null) - // { - // throw new ArgumentNullException(nameof(symbol)); - // } - - // var editor = new SyntaxEditor(originalNode, Global.Workspace.Services); - - // AddAccessibilityModifiersHelpers.UpdateDeclaration(editor, symbol, originalNode); - // return originalNode; - // }); - - //foreach (var node in propertyNodes) - //{ - // var symbol = semanticModel.GetDeclaredSymbol(node, default); - - // if (symbol is null) - // { - // throw new ArgumentNullException(nameof(symbol)); - // } - // var editor = new SyntaxEditor(node, Global.Workspace.Services); - // AddAccessibilityModifiersHelpers.UpdateDeclaration(editor, symbol, node); - //} - - var editor = new SyntaxEditor(root, Global.Workspace.Services); root = root.ReplaceNodes(propertyNodes, (originalNode, newNode) => { @@ -134,6 +93,15 @@ public static async Task Process(SyntaxNode root, Microsoft.CodeAnal { throw new ArgumentNullException(nameof(symbol)); } + + if (!AccessibilityHelper.ShouldUpdateAccessibilityModifier(originalNode as MemberDeclarationSyntax, AccessibilityModifiersRequired.Always, out var accessibility, out var canChange) || !canChange) + { + newNode = originalNode; + return newNode; + } + + //return UpdateAccessibility(originalNode, accessibility); + //AddAccessibilityModifiersHelpers.UpdateDeclaration(editor, symbol, node); var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); @@ -271,4 +239,301 @@ internal static Accessibility GetPreferredAccessibility(ISymbol symbol) // return null; // } } + + internal enum AccessibilityModifiersRequired + { + // The rule is not run + Never = 0, + + // Accessibility modifiers are added if missing, even if default + Always = 1, + + // Future proofing for when C# adds default interface methods. At that point + // accessibility modifiers will be allowed in interfaces, and some people may + // want to require them, while some may want to keep the traditional C# style + // that public interface members do not need accessibility modifiers. + ForNonInterfaceMembers = 2, + + // Remove any accessibility modifier that matches the default + OmitIfDefault = 3 + } + + internal static class AccessibilityHelper + { + public static bool ShouldUpdateAccessibilityModifier( + MemberDeclarationSyntax member, + AccessibilityModifiersRequired option, + out Accessibility accessibility, + out bool modifierAdded) + { + modifierAdded = false; + accessibility = Accessibility.NotApplicable; + + // Have to have a name to report the issue on. + var name = member.GetNameToken(); + if (name.IsKind(SyntaxKind.None)) + return false; + + // Certain members never have accessibility. Don't bother reporting on them. + if (!CSharpAccessibilityFacts.CanHaveAccessibility(member)) + return false; + + // This analyzer bases all of its decisions on the accessibility + accessibility = CSharpAccessibilityFacts.GetAccessibility(member); + + // Omit will flag any accessibility values that exist and are default + // The other options will remove or ignore accessibility + var isOmit = option == AccessibilityModifiersRequired.OmitIfDefault; + modifierAdded = !isOmit; + + if (isOmit) + { + if (accessibility == Accessibility.NotApplicable) + return false; + + var parentKind = member.GetRequiredParent().Kind(); + switch (parentKind) + { + // Check for default modifiers in namespace and outside of namespace + case SyntaxKind.CompilationUnit: + case SyntaxKind.FileScopedNamespaceDeclaration: + case SyntaxKind.NamespaceDeclaration: + { + // Default is internal + if (accessibility != Accessibility.Internal) + return false; + } + + break; + + case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: + { + // Inside a type, default is private + if (accessibility != Accessibility.Private) + return false; + } + + break; + + default: + return false; // Unknown parent kind, don't do anything + } + } + else + { + // Mode is always, so we have to flag missing modifiers + if (accessibility != Accessibility.NotApplicable) + return false; + } + + return true; + } + } + + internal static partial class MemberDeclarationSyntaxExtensions + { + private static readonly ConditionalWeakTable>> s_declarationCache = new(); + + public static SyntaxToken GetNameToken(this MemberDeclarationSyntax member) + { + if (member != null) + { + switch (member.Kind()) + { + case SyntaxKind.EnumDeclaration: + return ((EnumDeclarationSyntax)member).Identifier; + + case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: + return ((TypeDeclarationSyntax)member).Identifier; + + case SyntaxKind.DelegateDeclaration: + return ((DelegateDeclarationSyntax)member).Identifier; + + case SyntaxKind.FieldDeclaration: + return ((FieldDeclarationSyntax)member).Declaration.Variables.First().Identifier; + + case SyntaxKind.EventFieldDeclaration: + return ((EventFieldDeclarationSyntax)member).Declaration.Variables.First().Identifier; + + case SyntaxKind.PropertyDeclaration: + return ((PropertyDeclarationSyntax)member).Identifier; + + case SyntaxKind.EventDeclaration: + return ((EventDeclarationSyntax)member).Identifier; + + case SyntaxKind.MethodDeclaration: + return ((MethodDeclarationSyntax)member).Identifier; + + case SyntaxKind.ConstructorDeclaration: + return ((ConstructorDeclarationSyntax)member).Identifier; + + case SyntaxKind.DestructorDeclaration: + return ((DestructorDeclarationSyntax)member).Identifier; + + case SyntaxKind.IndexerDeclaration: + return ((IndexerDeclarationSyntax)member).ThisKeyword; + + case SyntaxKind.OperatorDeclaration: + return ((OperatorDeclarationSyntax)member).OperatorToken; + } + } + + // Conversion operators don't have names. + return default; + } + + public static int GetArity(this MemberDeclarationSyntax member) + { + if (member != null) + { + switch (member.Kind()) + { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: + return ((TypeDeclarationSyntax)member).Arity; + + case SyntaxKind.DelegateDeclaration: + return ((DelegateDeclarationSyntax)member).Arity; + + case SyntaxKind.MethodDeclaration: + return ((MethodDeclarationSyntax)member).Arity; + } + } + + return 0; + } + + public static TypeParameterListSyntax GetTypeParameterList(this MemberDeclarationSyntax member) + { + if (member != null) + { + switch (member.Kind()) + { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: + return ((TypeDeclarationSyntax)member).TypeParameterList; + + case SyntaxKind.DelegateDeclaration: + return ((DelegateDeclarationSyntax)member).TypeParameterList; + + case SyntaxKind.MethodDeclaration: + return ((MethodDeclarationSyntax)member).TypeParameterList; + } + } + + return null; + } + + public static MemberDeclarationSyntax WithParameterList( + this MemberDeclarationSyntax member, + BaseParameterListSyntax parameterList) + { + if (member != null) + { + switch (member.Kind()) + { + case SyntaxKind.DelegateDeclaration: + return ((DelegateDeclarationSyntax)member).WithParameterList((ParameterListSyntax)parameterList); + + case SyntaxKind.MethodDeclaration: + return ((MethodDeclarationSyntax)member).WithParameterList((ParameterListSyntax)parameterList); + + case SyntaxKind.ConstructorDeclaration: + return ((ConstructorDeclarationSyntax)member).WithParameterList((ParameterListSyntax)parameterList); + + case SyntaxKind.IndexerDeclaration: + return ((IndexerDeclarationSyntax)member).WithParameterList((BracketedParameterListSyntax)parameterList); + + case SyntaxKind.OperatorDeclaration: + return ((OperatorDeclarationSyntax)member).WithParameterList((ParameterListSyntax)parameterList); + + case SyntaxKind.ConversionOperatorDeclaration: + return ((ConversionOperatorDeclarationSyntax)member).WithParameterList((ParameterListSyntax)parameterList); + } + } + + return null; + } + + public static TypeSyntax GetMemberType(this MemberDeclarationSyntax member) + { + if (member != null) + { + switch (member.Kind()) + { + case SyntaxKind.DelegateDeclaration: + return ((DelegateDeclarationSyntax)member).ReturnType; + + case SyntaxKind.MethodDeclaration: + return ((MethodDeclarationSyntax)member).ReturnType; + + case SyntaxKind.OperatorDeclaration: + return ((OperatorDeclarationSyntax)member).ReturnType; + + case SyntaxKind.PropertyDeclaration: + return ((PropertyDeclarationSyntax)member).Type; + + case SyntaxKind.IndexerDeclaration: + return ((IndexerDeclarationSyntax)member).Type; + + case SyntaxKind.EventDeclaration: + return ((EventDeclarationSyntax)member).Type; + + case SyntaxKind.EventFieldDeclaration: + return ((EventFieldDeclarationSyntax)member).Declaration.Type; + + case SyntaxKind.FieldDeclaration: + return ((FieldDeclarationSyntax)member).Declaration.Type; + } + } + + return null; + } + + public static bool HasMethodShape(this MemberDeclarationSyntax memberDeclaration) + => memberDeclaration is BaseMethodDeclarationSyntax; + + public static BlockSyntax GetBody(this MemberDeclarationSyntax memberDeclaration) + => memberDeclaration switch + { + BaseMethodDeclarationSyntax method => method.Body, + _ => null, + }; + + public static ArrowExpressionClauseSyntax GetExpressionBody(this MemberDeclarationSyntax memberDeclaration) + => memberDeclaration switch + { + BaseMethodDeclarationSyntax method => method.ExpressionBody, + IndexerDeclarationSyntax indexer => indexer.ExpressionBody, + PropertyDeclarationSyntax property => property.ExpressionBody, + _ => null, + }; + + public static MemberDeclarationSyntax WithBody(this MemberDeclarationSyntax memberDeclaration, BlockSyntax body) + => (memberDeclaration as BaseMethodDeclarationSyntax)?.WithBody(body); + } + + internal class Class1 + { + private protected Class1() + { + } + } + partial class ExampleClass + { + partial void ExampleMethod(); + } } \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/CSharpAccessibilityFacts.cs b/CodeMaidShared/Logic/Cleaning/CSharpAccessibilityFacts.cs new file mode 100644 index 000000000..b6e1bdb8d --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/CSharpAccessibilityFacts.cs @@ -0,0 +1,336 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodeMaidShared.Logic.Cleaning +{ + internal static class CSharpAccessibilityFacts + { + public static 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 static 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.Parent ?? throw new InvalidOperationException("Token's parent was null")), + VariableDeclaratorSyntax varDecl => GetModifierTokens(varDecl.Parent ?? throw new InvalidOperationException("Token's parent was null")), + AnonymousFunctionExpressionSyntax anonymous => anonymous.Modifiers, + _ => default, + }; + + public static bool ParentIsFieldDeclaration(SyntaxNode? node) + => node?.Parent.IsKind(SyntaxKind.FieldDeclaration) ?? false; + + public static bool ParentIsEventFieldDeclaration(SyntaxNode? node) + => node?.Parent.IsKind(SyntaxKind.EventFieldDeclaration) ?? false; + + public static bool ParentIsLocalDeclarationStatement(SyntaxNode? node) + => node?.Parent.IsKind(SyntaxKind.LocalDeclarationStatement) ?? false; + } +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs index e41ad8cb7..1e33cd673 100644 --- a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs +++ b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs @@ -1,3 +1,4 @@ +using CodeMaidShared.Logic.Cleaning; using EnvDTE; using SteveCadwallader.CodeMaid.Helpers; using SteveCadwallader.CodeMaid.Logic.Formatting; diff --git a/CodeMaidShared/Logic/Cleaning/Global.cs b/CodeMaidShared/Logic/Cleaning/Global.cs index 6fd43791b..365cf834e 100644 --- a/CodeMaidShared/Logic/Cleaning/Global.cs +++ b/CodeMaidShared/Logic/Cleaning/Global.cs @@ -1,23 +1,12 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Windows; -using System.Windows.Media; -using System.Xml.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio; +using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.LanguageServices; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.Win32; -using Task = System.Threading.Tasks.Task; +using System.Linq; using Document = Microsoft.CodeAnalysis.Document; -using Solution = Microsoft.CodeAnalysis.Solution; using DteDocument = EnvDTE.Document; -using Microsoft.VisualStudio.LanguageServices; +using Solution = Microsoft.CodeAnalysis.Solution; namespace SteveCadwallader.CodeMaid.Logic.Cleaning { @@ -79,4 +68,11 @@ static public VisualStudioWorkspace Workspace } } } + + internal class Class1 + { + private protected Class1() + { + } + } } \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/SyntaxNodeExtensions.cs b/CodeMaidShared/Logic/Cleaning/SyntaxNodeExtensions.cs new file mode 100644 index 000000000..2a0b97782 --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/SyntaxNodeExtensions.cs @@ -0,0 +1,837 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace CodeMaidShared.Logic.Cleaning +{ + internal static class SyntaxNodeExtensions + { + public static SyntaxNode GetRequiredParent(this SyntaxNode node) + => node.Parent ?? throw new InvalidOperationException("Node's parent was null"); + + //public static IEnumerable GetAncestors(this SyntaxNode node) + //{ + // var current = node.Parent; + + // while (current != null) + // { + // yield return current; + + // current = current.GetParent(ascendOutOfTrivia: true); + // } + //} + + //public static IEnumerable GetAncestors(this SyntaxNode node) + // where TNode : SyntaxNode + //{ + // var current = node.Parent; + // while (current != null) + // { + // if (current is TNode tNode) + // { + // yield return tNode; + // } + + // current = current.GetParent(ascendOutOfTrivia: true); + // } + //} + + //public static TNode? GetAncestor(this SyntaxNode node) + // where TNode : SyntaxNode + //{ + // var current = node.Parent; + // while (current != null) + // { + // if (current is TNode tNode) + // { + // return tNode; + // } + + // current = current.GetParent(ascendOutOfTrivia: true); + // } + + // return null; + //} + + //public static TNode? GetAncestorOrThis(this SyntaxNode? node) + // where TNode : SyntaxNode + //{ + // return node?.GetAncestorsOrThis().FirstOrDefault(); + //} + + //public static IEnumerable GetAncestorsOrThis(this SyntaxNode? node) + // where TNode : SyntaxNode + //{ + // var current = node; + // while (current != null) + // { + // if (current is TNode tNode) + // { + // yield return tNode; + // } + + // current = current.GetParent(ascendOutOfTrivia: true); + // } + //} + + //public static bool HasAncestor(this SyntaxNode node) + // where TNode : SyntaxNode + //{ + // return node.GetAncestors().Any(); + //} + + //public static bool CheckParent(this SyntaxNode? node, Func valueChecker) where T : SyntaxNode + //{ + // if (node?.Parent is not T parentNode) + // { + // return false; + // } + + // return valueChecker(parentNode); + //} + + ///// + ///// Returns true if is a given token is a child token of a certain type of parent node. + ///// + ///// The type of the parent node. + ///// The node that we are testing. + ///// A function that, when given the parent node, returns the child token we are interested in. + //public static bool IsChildNode(this SyntaxNode node, Func childGetter) + // where TParent : SyntaxNode + //{ + // var ancestor = node.GetAncestor(); + // if (ancestor == null) + // { + // return false; + // } + + // var ancestorNode = childGetter(ancestor); + + // return node == ancestorNode; + //} + + ///// + ///// Returns true if this node is found underneath the specified child in the given parent. + ///// + //public static bool IsFoundUnder(this SyntaxNode node, Func childGetter) + // where TParent : SyntaxNode + //{ + // var ancestor = node.GetAncestor(); + // if (ancestor == null) + // { + // return false; + // } + + // var child = childGetter(ancestor); + + // // See if node passes through child on the way up to ancestor. + // return node.GetAncestorsOrThis().Contains(child); + //} + + //public static SyntaxNode GetCommonRoot(this SyntaxNode node1, SyntaxNode node2) + //{ + // Contract.ThrowIfTrue(node1.RawKind == 0 || node2.RawKind == 0); + + // // find common starting node from two nodes. + // // as long as two nodes belong to same tree, there must be at least one common root (Ex, compilation unit) + // var ancestors = node1.GetAncestorsOrThis(); + // var set = new HashSet(node2.GetAncestorsOrThis()); + + // return ancestors.First(set.Contains); + //} + + //public static int Width(this SyntaxNode node) + // => node.Span.Length; + + //public static int FullWidth(this SyntaxNode node) + // => node.FullSpan.Length; + + //public static SyntaxNode? FindInnermostCommonNode(this IEnumerable nodes, Func predicate) + // => nodes.FindInnermostCommonNode()?.FirstAncestorOrSelf(predicate); + + //public static SyntaxNode? FindInnermostCommonNode(this IEnumerable nodes) + //{ + // // Two collections we use to make this operation as efficient as possible. One is a + // // stack of the current shared ancestor chain shared by all nodes so far. It starts + // // with the full ancestor chain of the first node, and can only get smaller over time. + // // It should be log(n) with the size of the tree as it's only storing a parent chain. + // // + // // The second is a set with the exact same contents as the array. It's used for O(1) + // // lookups if a node is in the ancestor chain or not. + + // using var _1 = ArrayBuilder.GetInstance(out var commonAncestorsStack); + // using var _2 = PooledHashSet.GetInstance(out var commonAncestorsSet); + + // var first = true; + // foreach (var node in nodes) + // { + // // If we're just starting, initialize the ancestors set/array with the ancestors of + // // this node. + // if (first) + // { + // first = false; + // foreach (var ancestor in node.ValueAncestorsAndSelf()) + // { + // commonAncestorsSet.Add(ancestor); + // commonAncestorsStack.Add(ancestor); + // } + + // // Reverse the ancestors stack so that we go downwards with CompilationUnit at + // // the start, and then go down to this starting node. This enables cheap + // // popping later on. + // commonAncestorsStack.ReverseContents(); + // continue; + // } + + // // On a subsequent node, walk its ancestors to find the first match + // var commonAncestor = FindCommonAncestor(node, commonAncestorsSet); + // if (commonAncestor == null) + // { + // // So this shouldn't happen as long as the nodes are from the same tree. And + // // the caller really shouldn't be calling from different trees. However, the + // // previous impl supported that, so continue to have this behavior. + // // + // // If this doesn't fire, that means that all callers seem sane. If it does + // // fire, we can relax this (but we should consider fixing the caller). + // Debug.Fail("Could not find common ancestor."); + // return null; + // } + + // // Now remove everything in the ancestors array up to that common ancestor. This is + // // generally quite efficient. Either we settle on a common node quickly. and don't + // // need to do work here, or we keep tossing data from our common-ancestor scratch + // // pad, making further work faster. + // while (commonAncestorsStack.Count > 0 && + // commonAncestorsStack.Peek() != commonAncestor) + // { + // commonAncestorsSet.Remove(commonAncestorsStack.Peek()); + // commonAncestorsStack.Pop(); + // } + + // if (commonAncestorsStack.Count == 0) + // { + // // So this shouldn't happen as long as the nodes are from the same tree. And + // // the caller really shouldn't be calling from different trees. However, the + // // previous impl supported that, so continue to have this behavior. + // // + // // If this doesn't fire, that means that all callers seem sane. If it does + // // fire, we can relax this (but we should consider fixing the caller). + // Debug.Fail("Could not find common ancestor."); + // return null; + // } + // } + + // // The common ancestor is the one at the end of the ancestor stack. This could be empty + // // in the case where the caller passed in an empty enumerable of nodes. + // return commonAncestorsStack.Count == 0 ? null : commonAncestorsStack.Peek(); + + // // local functions + // static SyntaxNode? FindCommonAncestor(SyntaxNode node, HashSet commonAncestorsSet) + // { + // foreach (var ancestor in node.ValueAncestorsAndSelf()) + // { + // if (commonAncestorsSet.Contains(ancestor)) + // return ancestor; + // } + + // return null; + // } + //} + + //public static TSyntaxNode? FindInnermostCommonNode(this IEnumerable nodes) where TSyntaxNode : SyntaxNode + // => (TSyntaxNode?)nodes.FindInnermostCommonNode(t => t is TSyntaxNode); + + ///// + ///// create a new root node from the given root after adding annotations to the tokens + ///// + ///// tokens should belong to the given root + ///// + //public static SyntaxNode AddAnnotations(this SyntaxNode root, IEnumerable> pairs) + //{ + // Contract.ThrowIfNull(root); + // Contract.ThrowIfNull(pairs); + + // var tokenMap = pairs.GroupBy(p => p.Item1, p => p.Item2).ToDictionary(g => g.Key, g => g.ToArray()); + // return root.ReplaceTokens(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations(tokenMap[o])); + //} + + ///// + ///// create a new root node from the given root after adding annotations to the nodes + ///// + ///// nodes should belong to the given root + ///// + //public static SyntaxNode AddAnnotations(this SyntaxNode root, IEnumerable> pairs) + //{ + // Contract.ThrowIfNull(root); + // Contract.ThrowIfNull(pairs); + + // var tokenMap = pairs.GroupBy(p => p.Item1, p => p.Item2).ToDictionary(g => g.Key, g => g.ToArray()); + // return root.ReplaceNodes(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations(tokenMap[o])); + //} + + //public static TextSpan GetContainedSpan(this IEnumerable nodes) + //{ + // Contract.ThrowIfNull(nodes); + // Contract.ThrowIfFalse(nodes.Any()); + + // var fullSpan = nodes.First().Span; + // foreach (var node in nodes) + // { + // fullSpan = TextSpan.FromBounds( + // Math.Min(fullSpan.Start, node.SpanStart), + // Math.Max(fullSpan.End, node.Span.End)); + // } + + // return fullSpan; + //} + + //public static bool OverlapsHiddenPosition(this SyntaxNode node, CancellationToken cancellationToken) + // => node.OverlapsHiddenPosition(node.Span, cancellationToken); + + //public static bool OverlapsHiddenPosition(this SyntaxNode node, TextSpan span, CancellationToken cancellationToken) + // => node.SyntaxTree.OverlapsHiddenPosition(span, cancellationToken); + + //public static bool OverlapsHiddenPosition(this SyntaxNode declaration, SyntaxNode startNode, SyntaxNode endNode, CancellationToken cancellationToken) + //{ + // var start = startNode.Span.End; + // var end = endNode.SpanStart; + + // var textSpan = TextSpan.FromBounds(start, end); + // return declaration.OverlapsHiddenPosition(textSpan, cancellationToken); + //} + + //public static IEnumerable GetAnnotatedNodes(this SyntaxNode node, SyntaxAnnotation syntaxAnnotation) where T : SyntaxNode + // => node.GetAnnotatedNodesAndTokens(syntaxAnnotation).Select(n => n.AsNode()).OfType(); + + ///// + ///// Creates a new tree of nodes from the existing tree with the specified old nodes replaced with a newly computed nodes. + ///// + ///// The root of the tree that contains all the specified nodes. + ///// The nodes from the tree to be replaced. + ///// A function that computes a replacement node for + ///// the argument nodes. The first argument is one of the original specified nodes. The second argument is + ///// the same node possibly rewritten with replaced descendants. + ///// + //public static Task ReplaceNodesAsync( + // this TRootNode root, + // IEnumerable nodes, + // Func> computeReplacementAsync, + // CancellationToken cancellationToken) where TRootNode : SyntaxNode + //{ + // return root.ReplaceSyntaxAsync( + // nodes: nodes, computeReplacementNodeAsync: computeReplacementAsync, + // tokens: null, computeReplacementTokenAsync: null, + // trivia: null, computeReplacementTriviaAsync: null, + // cancellationToken: cancellationToken); + //} + + ///// + ///// Creates a new tree of tokens from the existing tree with the specified old tokens replaced with a newly computed tokens. + ///// + ///// The root of the tree that contains all the specified tokens. + ///// The tokens from the tree to be replaced. + ///// A function that computes a replacement token for + ///// the argument tokens. The first argument is one of the originally specified tokens. The second argument is + ///// the same token possibly rewritten with replaced trivia. + ///// + //public static Task ReplaceTokensAsync( + // this TRootNode root, + // IEnumerable tokens, + // Func> computeReplacementAsync, + // CancellationToken cancellationToken) where TRootNode : SyntaxNode + //{ + // return root.ReplaceSyntaxAsync( + // nodes: null, computeReplacementNodeAsync: null, + // tokens: tokens, computeReplacementTokenAsync: computeReplacementAsync, + // trivia: null, computeReplacementTriviaAsync: null, + // cancellationToken: cancellationToken); + //} + + //public static Task ReplaceTriviaAsync( + // this TRoot root, + // IEnumerable trivia, + // Func> computeReplacementAsync, + // CancellationToken cancellationToken) where TRoot : SyntaxNode + //{ + // return root.ReplaceSyntaxAsync( + // nodes: null, computeReplacementNodeAsync: null, + // tokens: null, computeReplacementTokenAsync: null, + // trivia: trivia, computeReplacementTriviaAsync: computeReplacementAsync, + // cancellationToken: cancellationToken); + //} + + //public static async Task ReplaceSyntaxAsync( + // this TRoot root, + // IEnumerable? nodes, + // Func>? computeReplacementNodeAsync, + // IEnumerable? tokens, + // Func>? computeReplacementTokenAsync, + // IEnumerable? trivia, + // Func>? computeReplacementTriviaAsync, + // CancellationToken cancellationToken) + // where TRoot : SyntaxNode + //{ + // // index all nodes, tokens and trivia by the full spans they cover + // var nodesToReplace = nodes != null ? nodes.ToDictionary(n => n.FullSpan) : new Dictionary(); + // var tokensToReplace = tokens != null ? tokens.ToDictionary(t => t.FullSpan) : new Dictionary(); + // var triviaToReplace = trivia != null ? trivia.ToDictionary(t => t.FullSpan) : new Dictionary(); + + // var nodeReplacements = new Dictionary(); + // var tokenReplacements = new Dictionary(); + // var triviaReplacements = new Dictionary(); + + // var retryAnnotations = new AnnotationTable("RetryReplace"); + + // var spans = new List(nodesToReplace.Count + tokensToReplace.Count + triviaToReplace.Count); + // spans.AddRange(nodesToReplace.Keys); + // spans.AddRange(tokensToReplace.Keys); + // spans.AddRange(triviaToReplace.Keys); + + // while (spans.Count > 0) + // { + // // sort the spans of the items to be replaced so we can tell if any overlap + // spans.Sort((x, y) => + // { + // // order by end offset, and then by length + // var d = x.End - y.End; + + // if (d == 0) + // { + // d = x.Length - y.Length; + // } + + // return d; + // }); + + // // compute replacements for all nodes that will go in the same batch + // // only spans that do not overlap go in the same batch. + // TextSpan previous = default; + // foreach (var span in spans) + // { + // // only add to replacement map if we don't intersect with the previous node. This taken with the sort order + // // should ensure that parent nodes are not processed in the same batch as child nodes. + // if (previous == default || !previous.IntersectsWith(span)) + // { + // if (nodesToReplace.TryGetValue(span, out var currentNode)) + // { + // var original = (SyntaxNode?)retryAnnotations.GetAnnotations(currentNode).SingleOrDefault() ?? currentNode; + // var newNode = await computeReplacementNodeAsync!(original, currentNode, cancellationToken).ConfigureAwait(false); + // nodeReplacements[currentNode] = newNode; + // } + // else if (tokensToReplace.TryGetValue(span, out var currentToken)) + // { + // var original = (SyntaxToken?)retryAnnotations.GetAnnotations(currentToken).SingleOrDefault() ?? currentToken; + // var newToken = await computeReplacementTokenAsync!(original, currentToken, cancellationToken).ConfigureAwait(false); + // tokenReplacements[currentToken] = newToken; + // } + // else if (triviaToReplace.TryGetValue(span, out var currentTrivia)) + // { + // var original = (SyntaxTrivia?)retryAnnotations.GetAnnotations(currentTrivia).SingleOrDefault() ?? currentTrivia; + // var newTrivia = await computeReplacementTriviaAsync!(original, currentTrivia, cancellationToken).ConfigureAwait(false); + // triviaReplacements[currentTrivia] = newTrivia; + // } + // } + + // previous = span; + // } + + // var retryNodes = false; + // var retryTokens = false; + // var retryTrivia = false; + + // // replace nodes in batch + // // submit all nodes so we can annotate the ones we don't replace + // root = root.ReplaceSyntax( + // nodes: nodesToReplace.Values, + // computeReplacementNode: (original, rewritten) => + // { + // if (rewritten != original || !nodeReplacements.TryGetValue(original, out var replaced)) + // { + // // the subtree did change, or we didn't have a replacement for it in this batch + // // so we need to add an annotation so we can find this node again for the next batch. + // replaced = retryAnnotations.WithAdditionalAnnotations(rewritten, original); + // retryNodes = true; + // } + + // return replaced; + // }, + // tokens: tokensToReplace.Values, + // computeReplacementToken: (original, rewritten) => + // { + // if (rewritten != original || !tokenReplacements.TryGetValue(original, out var replaced)) + // { + // // the subtree did change, or we didn't have a replacement for it in this batch + // // so we need to add an annotation so we can find this node again for the next batch. + // replaced = retryAnnotations.WithAdditionalAnnotations(rewritten, original); + // retryTokens = true; + // } + + // return replaced; + // }, + // trivia: triviaToReplace.Values, + // computeReplacementTrivia: (original, rewritten) => + // { + // if (!triviaReplacements.TryGetValue(original, out var replaced)) + // { + // // the subtree did change, or we didn't have a replacement for it in this batch + // // so we need to add an annotation so we can find this node again for the next batch. + // replaced = retryAnnotations.WithAdditionalAnnotations(rewritten, original); + // retryTrivia = true; + // } + + // return replaced; + // }); + + // nodesToReplace.Clear(); + // tokensToReplace.Clear(); + // triviaToReplace.Clear(); + // spans.Clear(); + + // // prepare next batch out of all remaining annotated nodes + // if (retryNodes) + // { + // nodesToReplace = retryAnnotations.GetAnnotatedNodes(root).ToDictionary(n => n.FullSpan); + // spans.AddRange(nodesToReplace.Keys); + // } + + // if (retryTokens) + // { + // tokensToReplace = retryAnnotations.GetAnnotatedTokens(root).ToDictionary(t => t.FullSpan); + // spans.AddRange(tokensToReplace.Keys); + // } + + // if (retryTrivia) + // { + // triviaToReplace = retryAnnotations.GetAnnotatedTrivia(root).ToDictionary(t => t.FullSpan); + // spans.AddRange(triviaToReplace.Keys); + // } + // } + + // return root; + //} + + ///// + ///// Look inside a trivia list for a skipped token that contains the given position. + ///// + //private static readonly Func s_findSkippedTokenForward = FindSkippedTokenForward; + + ///// + ///// Look inside a trivia list for a skipped token that contains the given position. + ///// + //private static SyntaxToken FindSkippedTokenForward(SyntaxTriviaList triviaList, int position) + //{ + // foreach (var trivia in triviaList) + // { + // if (trivia.HasStructure) + // { + // if (trivia.GetStructure() is ISkippedTokensTriviaSyntax skippedTokensTrivia) + // { + // foreach (var token in skippedTokensTrivia.Tokens) + // { + // if (token.Span.Length > 0 && position <= token.Span.End) + // { + // return token; + // } + // } + // } + // } + // } + + // return default; + //} + + ///// + ///// Look inside a trivia list for a skipped token that contains the given position. + ///// + //private static readonly Func s_findSkippedTokenBackward = FindSkippedTokenBackward; + + ///// + ///// Look inside a trivia list for a skipped token that contains the given position. + ///// + //private static SyntaxToken FindSkippedTokenBackward(SyntaxTriviaList triviaList, int position) + //{ + // foreach (var trivia in triviaList.Reverse()) + // { + // if (trivia.HasStructure) + // { + // if (trivia.GetStructure() is ISkippedTokensTriviaSyntax skippedTokensTrivia) + // { + // foreach (var token in skippedTokensTrivia.Tokens) + // { + // if (token.Span.Length > 0 && token.SpanStart <= position) + // { + // return token; + // } + // } + // } + // } + // } + + // return default; + //} + + //private static SyntaxToken GetInitialToken( + // SyntaxNode root, + // int position, + // bool includeSkipped = false, + // bool includeDirectives = false, + // bool includeDocumentationComments = false) + //{ + // return (position < root.FullSpan.End || !(root is ICompilationUnitSyntax)) + // ? root.FindToken(position, includeSkipped || includeDirectives || includeDocumentationComments) + // : root.GetLastToken(includeZeroWidth: true, includeSkipped: true, includeDirectives: true, includeDocumentationComments: true) + // .GetPreviousToken(includeZeroWidth: false, includeSkipped: includeSkipped, includeDirectives: includeDirectives, includeDocumentationComments: includeDocumentationComments); + //} + + ///// + ///// If the position is inside of token, return that token; otherwise, return the token to the right. + ///// + //public static SyntaxToken FindTokenOnRightOfPosition( + // this SyntaxNode root, + // int position, + // bool includeSkipped = false, + // bool includeDirectives = false, + // bool includeDocumentationComments = false) + //{ + // var findSkippedToken = includeSkipped ? s_findSkippedTokenForward : ((l, p) => default); + + // var token = GetInitialToken(root, position, includeSkipped, includeDirectives, includeDocumentationComments); + + // if (position < token.SpanStart) + // { + // var skippedToken = findSkippedToken(token.LeadingTrivia, position); + // token = skippedToken.RawKind != 0 ? skippedToken : token; + // } + // else if (token.Span.End <= position) + // { + // do + // { + // var skippedToken = findSkippedToken(token.TrailingTrivia, position); + // token = skippedToken.RawKind != 0 + // ? skippedToken + // : token.GetNextToken(includeZeroWidth: false, includeSkipped: includeSkipped, includeDirectives: includeDirectives, includeDocumentationComments: includeDocumentationComments); + // } + // while (token.RawKind != 0 && token.Span.End <= position && token.Span.End <= root.FullSpan.End); + // } + + // if (token.Span.Length == 0) + // { + // token = token.GetNextToken(); + // } + + // return token; + //} + + ///// + ///// If the position is inside of token, return that token; otherwise, return the token to the left. + ///// + //public static SyntaxToken FindTokenOnLeftOfPosition( + // this SyntaxNode root, + // int position, + // bool includeSkipped = false, + // bool includeDirectives = false, + // bool includeDocumentationComments = false) + //{ + // var findSkippedToken = includeSkipped ? s_findSkippedTokenBackward : ((l, p) => default); + + // var token = GetInitialToken(root, position, includeSkipped, includeDirectives, includeDocumentationComments); + + // if (position <= token.SpanStart) + // { + // do + // { + // var skippedToken = findSkippedToken(token.LeadingTrivia, position); + // token = skippedToken.RawKind != 0 + // ? skippedToken + // : token.GetPreviousToken(includeZeroWidth: false, includeSkipped: includeSkipped, includeDirectives: includeDirectives, includeDocumentationComments: includeDocumentationComments); + // } + // while (position <= token.SpanStart && root.FullSpan.Start < token.SpanStart); + // } + // else if (token.Span.End < position) + // { + // var skippedToken = findSkippedToken(token.TrailingTrivia, position); + // token = skippedToken.RawKind != 0 ? skippedToken : token; + // } + + // if (token.Span.Length == 0) + // { + // token = token.GetPreviousToken(); + // } + + // return token; + //} + + //public static T WithPrependedLeadingTrivia( + // this T node, + // params SyntaxTrivia[] trivia) where T : SyntaxNode + //{ + // if (trivia.Length == 0) + // { + // return node; + // } + + // return node.WithPrependedLeadingTrivia((IEnumerable)trivia); + //} + + //public static T WithPrependedLeadingTrivia( + // this T node, + // SyntaxTriviaList trivia) where T : SyntaxNode + //{ + // if (trivia.Count == 0) + // { + // return node; + // } + + // return node.WithLeadingTrivia(trivia.Concat(node.GetLeadingTrivia())); + //} + + //public static T WithPrependedLeadingTrivia( + // this T node, + // IEnumerable trivia) where T : SyntaxNode + //{ + // var list = new SyntaxTriviaList(); + // list = list.AddRange(trivia); + + // return node.WithPrependedLeadingTrivia(list); + //} + + //public static T WithAppendedTrailingTrivia( + // this T node, + // params SyntaxTrivia[] trivia) where T : SyntaxNode + //{ + // if (trivia.Length == 0) + // { + // return node; + // } + + // return node.WithAppendedTrailingTrivia((IEnumerable)trivia); + //} + + //public static T WithAppendedTrailingTrivia( + // this T node, + // SyntaxTriviaList trivia) where T : SyntaxNode + //{ + // if (trivia.Count == 0) + // { + // return node; + // } + + // return node.WithTrailingTrivia(node.GetTrailingTrivia().Concat(trivia)); + //} + + //public static T WithAppendedTrailingTrivia( + // this T node, + // IEnumerable trivia) where T : SyntaxNode + //{ + // var list = new SyntaxTriviaList(); + // list = list.AddRange(trivia); + + // return node.WithAppendedTrailingTrivia(list); + //} + + //public static T With( + // this T node, + // IEnumerable leadingTrivia, + // IEnumerable trailingTrivia) where T : SyntaxNode + //{ + // return node.WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTrivia); + //} + + ///// + ///// Creates a new token with the leading trivia removed. + ///// + //public static SyntaxToken WithoutLeadingTrivia(this SyntaxToken token) + //{ + // return token.WithLeadingTrivia(default(SyntaxTriviaList)); + //} + + ///// + ///// Creates a new token with the trailing trivia removed. + ///// + //public static SyntaxToken WithoutTrailingTrivia(this SyntaxToken token) + //{ + // return token.WithTrailingTrivia(default(SyntaxTriviaList)); + //} + + //// Copy of the same function in SyntaxNode.cs + //public static SyntaxNode? GetParent(this SyntaxNode node, bool ascendOutOfTrivia) + //{ + // var parent = node.Parent; + // if (parent == null && ascendOutOfTrivia) + // { + // if (node is IStructuredTriviaSyntax structuredTrivia) + // { + // parent = structuredTrivia.ParentTrivia.Token.Parent; + // } + // } + + // return parent; + //} + + //public static TNode? FirstAncestorOrSelfUntil(this SyntaxNode? node, Func predicate) + // where TNode : SyntaxNode + //{ + // for (var current = node; current != null; current = current.GetParent(ascendOutOfTrivia: true)) + // { + // if (current is TNode tnode) + // { + // return tnode; + // } + + // if (predicate(current)) + // { + // break; + // } + // } + + // return null; + //} + + ///// + ///// Gets a list of ancestor nodes (including this node) + ///// + //public static ValueAncestorsAndSelfEnumerable ValueAncestorsAndSelf(this SyntaxNode syntaxNode, bool ascendOutOfTrivia = true) + // => new(syntaxNode, ascendOutOfTrivia); + + //public readonly struct ValueAncestorsAndSelfEnumerable + //{ + // private readonly SyntaxNode _syntaxNode; + // private readonly bool _ascendOutOfTrivia; + + // public ValueAncestorsAndSelfEnumerable(SyntaxNode syntaxNode, bool ascendOutOfTrivia) + // { + // _syntaxNode = syntaxNode; + // _ascendOutOfTrivia = ascendOutOfTrivia; + // } + + // public Enumerator GetEnumerator() + // => new(_syntaxNode, _ascendOutOfTrivia); + + // public struct Enumerator + // { + // private readonly SyntaxNode _start; + // private readonly bool _ascendOutOfTrivia; + + // public Enumerator(SyntaxNode syntaxNode, bool ascendOutOfTrivia) + // { + // _start = syntaxNode; + // _ascendOutOfTrivia = ascendOutOfTrivia; + // Current = null!; + // } + + // public SyntaxNode Current { get; private set; } + + // public bool MoveNext() + // { + // Current = Current == null ? _start : GetParent(Current, _ascendOutOfTrivia)!; + // return Current != null; + // } + // } + //} + } +} \ No newline at end of file From 9ebad23a8df45b3cf34023aef7df68939a1be74c Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Wed, 22 Feb 2023 20:35:12 +0000 Subject: [PATCH 03/17] Code cleanup --- .../AddExplicitAccessModifierLogic.cs | 69 +++---------------- 1 file changed, 11 insertions(+), 58 deletions(-) diff --git a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs index 689aabae2..c941d7d90 100644 --- a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs +++ b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs @@ -125,27 +125,11 @@ SyntaxNode UpdateAccessibility(SyntaxNode declaration, Accessibility preferredAc return Formatter.Format(root, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); } - - //public void FixAllAsync( - // Document document, ImmutableArray diagnostics, - // SyntaxEditor editor) - //{ - // var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - - // foreach (var diagnostic in diagnostics) - // { - // var declaration = diagnostic.AdditionalLocations[0].FindNode(cancellationToken); - // var declarator = MapToDeclarator(declaration); - // var symbol = semanticModel.GetDeclaredSymbol(declarator, cancellationToken); - // Contract.ThrowIfNull(symbol); - // AddAccessibilityModifiersHelpers.UpdateDeclaration(editor, symbol, declaration); - // } - //} } internal static class DocumentExtensions { - public static async ValueTask GetRequiredSemanticModelAsync(this Microsoft.CodeAnalysis.Document document, CancellationToken cancellationToken) + public static async ValueTask GetRequiredSemanticModelAsync(this Document document, CancellationToken cancellationToken) { if (document.TryGetSemanticModel(out var semanticModel)) return semanticModel; @@ -192,8 +176,8 @@ internal static Accessibility GetPreferredAccessibility(ISymbol symbol) // If we have an overridden member, then if we're adding an accessibility modifier, use the // accessibility of the member we're overriding as both should be consistent here. // TODO Check override - //if (symbol.GetOverriddenMember() is { DeclaredAccessibility: var accessibility }) - // return accessibility; + if (symbol.GetOverriddenMember() is { DeclaredAccessibility: var accessibility }) + return accessibility; // Default abstract members to be protected, and virtual members to be public. They can't be private as // that's not legal. And these are reasonable default values for them. @@ -210,34 +194,14 @@ internal static Accessibility GetPreferredAccessibility(ISymbol symbol) return symbol.DeclaredAccessibility; } - // internal static Symbol GetOverriddenMember(Symbol substitutedOverridingMember, Symbol overriddenByDefinitionMember) - // { - // Debug.Assert(!substitutedOverridingMember.IsDefinition); - - // if ((object)overriddenByDefinitionMember != null) - // { - // NamedTypeSymbol overriddenByDefinitionContaining = overriddenByDefinitionMember.ContainingType; - // NamedTypeSymbol overriddenByDefinitionContainingTypeDefinition = overriddenByDefinitionContaining.OriginalDefinition; - // for (NamedTypeSymbol baseType = substitutedOverridingMember.ContainingType.BaseTypeNoUseSiteDiagnostics; - // (object)baseType != null; - // baseType = baseType.BaseTypeNoUseSiteDiagnostics) - // { - // if (TypeSymbol.Equals(baseType.OriginalDefinition, overriddenByDefinitionContainingTypeDefinition, TypeCompareKind.ConsiderEverything2)) - // { - // if (TypeSymbol.Equals(baseType, overriddenByDefinitionContaining, TypeCompareKind.ConsiderEverything2)) - // { - // return overriddenByDefinitionMember; - // } - - // return overriddenByDefinitionMember.OriginalDefinition.SymbolAsMember(baseType); - // } - // } - - // throw ExceptionUtilities.Unreachable(); - // } - - // return null; - // } + public static ISymbol? GetOverriddenMember(this ISymbol? symbol) + => symbol switch + { + IMethodSymbol method => method.OverriddenMethod, + IPropertySymbol property => property.OverriddenProperty, + IEventSymbol @event => @event.OverriddenEvent, + _ => null, + }; } internal enum AccessibilityModifiersRequired @@ -525,15 +489,4 @@ public static ArrowExpressionClauseSyntax GetExpressionBody(this MemberDeclarati public static MemberDeclarationSyntax WithBody(this MemberDeclarationSyntax memberDeclaration, BlockSyntax body) => (memberDeclaration as BaseMethodDeclarationSyntax)?.WithBody(body); } - - internal class Class1 - { - private protected Class1() - { - } - } - partial class ExampleClass - { - partial void ExampleMethod(); - } } \ No newline at end of file From d96b0d04ae7500904508f4eaa845af8488d210f2 Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Wed, 22 Feb 2023 23:34:00 +0000 Subject: [PATCH 04/17] Added unit tests --- CodeMaid.UnitTests/RoslynTests.cs | 61 +++++---- .../AddExplicitAccessModifierLogic.cs | 128 ++++++++---------- .../Logic/Cleaning/CodeCleanupManager.cs | 2 +- 3 files changed, 95 insertions(+), 96 deletions(-) diff --git a/CodeMaid.UnitTests/RoslynTests.cs b/CodeMaid.UnitTests/RoslynTests.cs index 5fac64e28..133297372 100644 --- a/CodeMaid.UnitTests/RoslynTests.cs +++ b/CodeMaid.UnitTests/RoslynTests.cs @@ -3,52 +3,65 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.VisualStudio.LanguageServices; +using Microsoft.CodeAnalysis.Text; +using CodeMaidShared.Logic.Cleaning; +using Microsoft.CodeAnalysis.Formatting; namespace SteveCadwallader.CodeMaid.UnitTests; [TestClass] public class RoslynTests { - public class EmtpyStatementRemoval : CSharpSyntaxRewriter + public class Rewriter : CSharpSyntaxRewriter { - public override SyntaxNode Visit(SyntaxNode node) + private readonly Func _writer; + + public Rewriter(Func writer) { - return base.Visit(node); + _writer=writer; } - public override SyntaxNode VisitEmptyStatement(EmptyStatementSyntax node) + public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node) { - //Construct an EmptyStatementSyntax with a missing semicolon - return node.WithSemicolonToken( - SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken) - .WithLeadingTrivia(node.SemicolonToken.LeadingTrivia) - .WithTrailingTrivia(node.SemicolonToken.TrailingTrivia)); + return _writer(node); } } [TestMethod] - public void RunRewriter() + public void ShouldAddPropertyAccessor() { - var tree = CSharpSyntaxTree.ParseText(@" - public class Sample - { - public void Foo() - { - Console.WriteLine(); + var source = +""" +public class Sample +{ + int Prop { get; set; } +} +"""; + var workspace = new AdhocWorkspace(); - #region SomeRegion + var projName = "TestProject"; + var projectId = ProjectId.CreateNewId(); + var versionStamp = VersionStamp.Create(); + var projectInfo = ProjectInfo.Create(projectId, versionStamp, projName, projName, LanguageNames.CSharp); + var newProject = workspace.AddProject(projectInfo); + var sourceText = SourceText.From(source); + var newDocument = workspace.AddDocument(newProject.Id, "NewFile.cs", sourceText); - //Some other code + var syntaxTree = newDocument.GetSyntaxRootAsync().Result; + var syntaxGenerator = SyntaxGenerator.GetGenerator(newDocument); + var semanticModel = newDocument.GetSemanticModelAsync().Result; - #endregion SomeRegion + var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + var result = new Rewriter(x => sut.Process(x)).Visit(syntaxTree); - ; - } - }"); + newDocument = newDocument.WithSyntaxRoot(result); + newDocument = Formatter.FormatAsync(newDocument, SyntaxAnnotation.ElasticAnnotation).Result; - var rewriter = new EmtpyStatementRemoval(); - var result = rewriter.Visit(tree.GetRoot()); + result = newDocument.GetSyntaxRootAsync().Result; Console.WriteLine(result.ToFullString()); + var c = result.ToFullString(); Assert.AreEqual(1, 1); } } \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs index c941d7d90..8a43d6d1a 100644 --- a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs +++ b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs @@ -17,11 +17,40 @@ namespace CodeMaidShared.Logic.Cleaning { + internal static class Runner + { + //public static void InsertExplicitMemberModifiers(CodeMaidPackage package) + //{ + // Start(package); + //} + + //private static void Start(CodeMaidPackage package) + //{ + // ThreadHelper.ThrowIfNotOnUIThread(); + + // Global.Package = package; + + // var document = Global.GetActiveDocument(); + + // if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) + // { + // root = Process(root, document).Result; + + // document = document.WithSyntaxRoot(root); + + // Global.Workspace.TryApplyChanges(document.Project.Solution); + // } + //} + } + /// /// A class for encapsulating insertion of explicit access modifier logic. /// - internal static class AddExplicitAccessModifierLogic + internal class AddExplicitAccessModifierLogic { + private readonly SemanticModel _semanticModel; + private readonly SyntaxGenerator _syntaxGenerator; + #region Constructors /// @@ -38,92 +67,49 @@ internal static class AddExplicitAccessModifierLogic // return _instance ?? (_instance = new AddExplicitAccessModifierLogic()); //} - ///// - ///// Initializes a new instance of the class. - ///// - //private AddExplicitAccessModifierLogic() - //{ - //} - - #endregion Constructors - - public static void InsertExplicitMemberModifiers(CodeMaidPackage package) + /// + /// Initializes a new instance of the class. + /// + public AddExplicitAccessModifierLogic(SemanticModel semanticModel, SyntaxGenerator syntaxGenerator) { - Start(package); + _semanticModel = semanticModel; + _syntaxGenerator = syntaxGenerator; } - //int MyProperty { get; set; } - //public int MyProperty2 { get; set; } - //public required int MyProperty3 { get; set; } - private static void Start(CodeMaidPackage package) - { - ThreadHelper.ThrowIfNotOnUIThread(); + #endregion Constructors - Global.Package = package; + public SyntaxNode Process(PropertyDeclarationSyntax node) + { + if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods) return node; - var document = Global.GetActiveDocument(); + var symbol = _semanticModel.GetDeclaredSymbol(node); - if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) + if (symbol is null) { - root = Process(root, document).Result; - - document = document.WithSyntaxRoot(root); - - Global.Workspace.TryApplyChanges(document.Project.Solution); + throw new ArgumentNullException(nameof(symbol)); } - } - - public static async Task Process(SyntaxNode root, Microsoft.CodeAnalysis.Document document) - { - if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods) return root; - - var propertyNodes = root.DescendantNodes().Where(node => node.IsKind(SyntaxKind.PropertyDeclaration)); - - var semanticModel = await document.GetRequiredSemanticModelAsync(default).ConfigureAwait(false); - var editor = new SyntaxEditor(root, Global.Workspace.Services); - if (propertyNodes.Any()) + if (!AccessibilityHelper.ShouldUpdateAccessibilityModifier(node, AccessibilityModifiersRequired.Always, out var accessibility, out var canChange) || !canChange) { - root = root.ReplaceNodes(propertyNodes, - (originalNode, newNode) => - { - var symbol = semanticModel.GetDeclaredSymbol(originalNode); - - if (symbol is null) - { - throw new ArgumentNullException(nameof(symbol)); - } - - if (!AccessibilityHelper.ShouldUpdateAccessibilityModifier(originalNode as MemberDeclarationSyntax, AccessibilityModifiersRequired.Always, out var accessibility, out var canChange) || !canChange) - { - newNode = originalNode; - return newNode; - } - - //return UpdateAccessibility(originalNode, accessibility); - - //AddAccessibilityModifiersHelpers.UpdateDeclaration(editor, symbol, node); - - var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); + return node; + } - return UpdateAccessibility(originalNode, preferredAccessibility); + var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); - SyntaxNode UpdateAccessibility(SyntaxNode declaration, Accessibility preferredAccessibility) - { - var generator = editor.Generator; + return UpdateAccessibility(node, preferredAccessibility); - // If there was accessibility on the member, then remove it. If there was no accessibility, then add - // the preferred accessibility for this member. - var newNode = generator.GetAccessibility(declaration) == Accessibility.NotApplicable - ? generator.WithAccessibility(declaration, preferredAccessibility) - : generator.WithAccessibility(declaration, Accessibility.NotApplicable); + SyntaxNode UpdateAccessibility(SyntaxNode declaration, Accessibility preferredAccessibility) + { + // If there was accessibility on the member, then remove it. If there was no accessibility, then add + // the preferred accessibility for this member. + var newNode = _syntaxGenerator.GetAccessibility(declaration) == Accessibility.NotApplicable + ? _syntaxGenerator.WithAccessibility(declaration, preferredAccessibility) + : _syntaxGenerator.WithAccessibility(declaration, Accessibility.NotApplicable); - return newNode; - } - }); + return newNode; } - return Formatter.Format(root, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); + //return Formatter.Format(root, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); } } diff --git a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs index 1e33cd673..302e575d6 100644 --- a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs +++ b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs @@ -326,7 +326,7 @@ private void RunCodeCleanupCSharp(Document document) _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnInterfaces(interfaces); _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnMethods(methods); //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnProperties(properties); - AddExplicitAccessModifierLogic.InsertExplicitMemberModifiers(_package); + //AddExplicitAccessModifierLogic.InsertExplicitMemberModifiers(_package); _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnStructs(structs); From 11617381e8aeae3110877265d0b680536b20294a Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Thu, 23 Feb 2023 14:51:33 +0000 Subject: [PATCH 05/17] Add tests and additional cleanups --- CodeMaid.UnitTests/RoslynTests.cs | 253 +++++++++++++++--- CodeMaid.config | 2 +- .../AddExplicitAccessModifierLogic.cs | 120 +++++++-- .../Logic/Cleaning/CodeCleanupManager.cs | 6 +- 4 files changed, 316 insertions(+), 65 deletions(-) diff --git a/CodeMaid.UnitTests/RoslynTests.cs b/CodeMaid.UnitTests/RoslynTests.cs index 133297372..60e8aab0c 100644 --- a/CodeMaid.UnitTests/RoslynTests.cs +++ b/CodeMaid.UnitTests/RoslynTests.cs @@ -1,67 +1,250 @@ -using Microsoft.CodeAnalysis.CSharp; +using CodeMaidShared.Logic.Cleaning; using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -using Microsoft.VisualStudio.LanguageServices; -using Microsoft.CodeAnalysis.Text; -using CodeMaidShared.Logic.Cleaning; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Threading.Tasks; namespace SteveCadwallader.CodeMaid.UnitTests; +internal class Rewriter : CSharpSyntaxRewriter +{ + internal Func PropertyWriter { get; set; } + internal Func MethodWriter { get; set; } + internal Func ClassWriter { get; set; } + + public Rewriter() + { + PropertyWriter = x => x; + MethodWriter = x => x; + ClassWriter = x => x; + } + + public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node) + { + return PropertyWriter(node); + } + + public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) + { + return MethodWriter(node); + } + + public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) + { + return ClassWriter(node); + } +} + [TestClass] public class RoslynTests { - public class Rewriter : CSharpSyntaxRewriter + private readonly TestWorkspace testWorkspace; + + public RoslynTests() { - private readonly Func _writer; + testWorkspace = new TestWorkspace(); + } - public Rewriter(Func writer) - { - _writer=writer; - } + [TestMethod] + public async Task ShouldAddPropertyAccessorAsync() + { + var source = +""" +public class Sample +{ + int Prop { get; set; } +} +"""; - public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node) - { - return _writer(node); - } + var expected = +""" +public class Sample +{ + private int Prop { get; set; } +} +"""; + + var document = testWorkspace.SetDocument(source); + + var syntaxTree = await document.GetSyntaxRootAsync(); + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + var semanticModel = await document.GetSemanticModelAsync(); + + var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + var rewriter = new Rewriter() { PropertyWriter = sut.ProcessProperty }; + var result = rewriter.Visit(syntaxTree); + + result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); + + Console.WriteLine(result.ToFullString()); + var actual = result.ToFullString(); + Assert.AreEqual(expected, actual); } [TestMethod] - public void ShouldAddPropertyAccessor() + public async Task ShouldAddPropertyAccessor2Async() { var source = """ public class Sample { - int Prop { get; set; } + required int Prop { get; set; } +} +"""; + + var expected = +""" +public class Sample +{ + private required int Prop { get; set; } +} +"""; + + var document = testWorkspace.SetDocument(source); + + var syntaxTree = await document.GetSyntaxRootAsync(); + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + var semanticModel = await document.GetSemanticModelAsync(); + + var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + var rewriter = new Rewriter() { PropertyWriter = sut.ProcessProperty }; + var result = rewriter.Visit(syntaxTree); + + result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); + + Console.WriteLine(result.ToFullString()); + var actual = result.ToFullString(); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public async Task ShouldAddPropertyAccessorMethodsAsync() + { + var source = +""" +public partial class ExampleClass +{ + void Do() + { + } +} +"""; + + var expected = +""" +public partial class ExampleClass +{ + private void Do() + { + } +} +"""; + + var document = testWorkspace.SetDocument(source); + + var syntaxTree = await document.GetSyntaxRootAsync(); + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + var semanticModel = await document.GetSemanticModelAsync(); + + var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + var rewriter = new Rewriter() { PropertyWriter = sut.ProcessProperty, MethodWriter = sut.ProcessMethod }; + var result = rewriter.Visit(syntaxTree); + + result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); + + Console.WriteLine(result.ToFullString()); + var actual = result.ToFullString(); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public async Task ShouldAddPropertyAccessorClassAsync() + { + var source = +""" +public partial class Temp +{ +} + +partial class Temp +{ } """; - var workspace = new AdhocWorkspace(); - var projName = "TestProject"; - var projectId = ProjectId.CreateNewId(); - var versionStamp = VersionStamp.Create(); - var projectInfo = ProjectInfo.Create(projectId, versionStamp, projName, projName, LanguageNames.CSharp); - var newProject = workspace.AddProject(projectInfo); - var sourceText = SourceText.From(source); - var newDocument = workspace.AddDocument(newProject.Id, "NewFile.cs", sourceText); + var expected = +""" +public partial class Temp +{ +} - var syntaxTree = newDocument.GetSyntaxRootAsync().Result; - var syntaxGenerator = SyntaxGenerator.GetGenerator(newDocument); - var semanticModel = newDocument.GetSemanticModelAsync().Result; +public partial class Temp +{ +} +"""; + + var document = testWorkspace.SetDocument(source); + + var syntaxTree = await document.GetSyntaxRootAsync(); + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + var semanticModel = await document.GetSemanticModelAsync(); var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - var result = new Rewriter(x => sut.Process(x)).Visit(syntaxTree); + var rewriter = new Rewriter() { PropertyWriter = sut.ProcessProperty, MethodWriter = sut.ProcessMethod, ClassWriter = sut.ProcessClass }; + var result = rewriter.Visit(syntaxTree); - newDocument = newDocument.WithSyntaxRoot(result); - newDocument = Formatter.FormatAsync(newDocument, SyntaxAnnotation.ElasticAnnotation).Result; + result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); - result = newDocument.GetSyntaxRootAsync().Result; Console.WriteLine(result.ToFullString()); - var c = result.ToFullString(); - Assert.AreEqual(1, 1); + var actual = result.ToFullString(); + Assert.AreEqual(expected, actual); + } + + + public class TestWorkspace + { + public TestWorkspace() + { + var source = +""" +public class Sample +{ +} +"""; + + Workspace = new AdhocWorkspace(); + + var projName = "TestProject"; + var projectId = ProjectId.CreateNewId(); + var versionStamp = VersionStamp.Create(); + var projectInfo = ProjectInfo.Create(projectId, versionStamp, projName, projName, LanguageNames.CSharp); + var newProject = Workspace.AddProject(projectInfo); + + var sourceText = SourceText.From(source); + Document = Workspace.AddDocument(newProject.Id, "NewFile.cs", sourceText); + } + + public AdhocWorkspace Workspace { get; private set; } + + public Document Document { get; private set; } + + public Document SetDocument(string text) + { + var newDocument = Document.WithText(SourceText.From(text)); + Workspace.TryApplyChanges(newDocument.Project.Solution); + return newDocument; + } + } + + public partial class Temp + { + } + + partial class Temp + { } } \ No newline at end of file diff --git a/CodeMaid.config b/CodeMaid.config index 12830564e..90e2efd29 100644 --- a/CodeMaid.config +++ b/CodeMaid.config @@ -11,7 +11,7 @@ \.Designer\.cs$||\.Designer\.vb$||\.resx$||CodeMaid.IntegrationTests\\.*\\Data\\||CodeMaid\\CodeMaid.cs||CodeMaid\\source.extension.cs - True + False False diff --git a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs index 8a43d6d1a..82d76e811 100644 --- a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs +++ b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs @@ -17,30 +17,34 @@ namespace CodeMaidShared.Logic.Cleaning { - internal static class Runner - { - //public static void InsertExplicitMemberModifiers(CodeMaidPackage package) - //{ - // Start(package); - //} - - //private static void Start(CodeMaidPackage package) - //{ - // ThreadHelper.ThrowIfNotOnUIThread(); - // Global.Package = package; + internal class Rewriter : CSharpSyntaxRewriter + { + internal Func PropertyWriter { get; set; } + internal Func MethodWriter { get; set; } + internal Func ClassWriter { get; set; } - // var document = Global.GetActiveDocument(); + public Rewriter() + { + PropertyWriter = x => x; + MethodWriter = x => x; + ClassWriter = x => x; + } - // if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) - // { - // root = Process(root, document).Result; + public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node) + { + return PropertyWriter(node); + } - // document = document.WithSyntaxRoot(root); + public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) + { + return MethodWriter(node); + } - // Global.Workspace.TryApplyChanges(document.Project.Solution); - // } - //} + public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) + { + return ClassWriter(node); + } } /// @@ -48,8 +52,11 @@ internal static class Runner /// internal class AddExplicitAccessModifierLogic { + #region Fields private readonly SemanticModel _semanticModel; private readonly SyntaxGenerator _syntaxGenerator; + #endregion Fields + #region Constructors @@ -62,10 +69,29 @@ internal class AddExplicitAccessModifierLogic /// Gets an instance of the class. /// /// An instance of the class. - //internal static AddExplicitAccessModifierLogic GetInstance() - //{ - // return _instance ?? (_instance = new AddExplicitAccessModifierLogic()); - //} + internal static AddExplicitAccessModifierLogic GetInstance(AsyncPackage package) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + Global.Package = package; + + var document = Global.GetActiveDocument(); + + if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) + { + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + var semanticModel = document.GetSemanticModelAsync().Result; + + return new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + + root = Formatter.Format(root, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); + + document = document.WithSyntaxRoot(root); + Global.Workspace.TryApplyChanges(document.Project.Solution); + } + + throw new InvalidOperationException(); + } /// /// Initializes a new instance of the class. @@ -78,10 +104,54 @@ public AddExplicitAccessModifierLogic(SemanticModel semanticModel, SyntaxGenerat #endregion Constructors - public SyntaxNode Process(PropertyDeclarationSyntax node) + public static void Process(AsyncPackage package) + { + var mod = GetInstance(package); + + var document = Global.GetActiveDocument(); + + if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) + { + var rewriter = new Rewriter() { PropertyWriter = mod.ProcessProperty, MethodWriter = mod.ProcessMethod, ClassWriter = mod.ProcessClass }; + var result = rewriter.Visit(root); + + root = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); + + document = document.WithSyntaxRoot(root); + Global.Workspace.TryApplyChanges(document.Project.Solution); + } + throw new InvalidOperationException(); + + } + + public SyntaxNode ProcessProperty(PropertyDeclarationSyntax node) + { + if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods) return node; + return GenericApplyAccessibility(node); + + //return Formatter.Format(root, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); + } + + public SyntaxNode ProcessClass(ClassDeclarationSyntax node) + { + if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses) return node; + return GenericApplyAccessibility(node); + } + + public SyntaxNode ProcessMethod(MethodDeclarationSyntax node) { if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods) return node; + return GenericApplyAccessibility(node); + } + public SyntaxNode ProcessMethod(ClassDeclarationSyntax node) + { + if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses) return node; + return GenericApplyAccessibility(node); + } + + private SyntaxNode GenericApplyAccessibility(MemberDeclarationSyntax node) + { var symbol = _semanticModel.GetDeclaredSymbol(node); if (symbol is null) @@ -108,8 +178,6 @@ SyntaxNode UpdateAccessibility(SyntaxNode declaration, Accessibility preferredAc return newNode; } - - //return Formatter.Format(root, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); } } diff --git a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs index 302e575d6..b41c51e2f 100644 --- a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs +++ b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs @@ -318,15 +318,15 @@ private void RunCodeCleanupCSharp(Document document) _insertBlankLinePaddingLogic.InsertPaddingBeforeSingleLineComments(textDocument); // Perform insertion of explicit access modifier cleanup. - _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnClasses(classes); _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnDelegates(delegates); _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnEnumerations(enumerations); _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnEvents(events); _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnFields(fields); _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnInterfaces(interfaces); - _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnMethods(methods); + //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnClasses(classes); + //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnMethods(methods); //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnProperties(properties); - //AddExplicitAccessModifierLogic.InsertExplicitMemberModifiers(_package); + //AddExplicitAccessModifierLogic.Process(_package); _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnStructs(structs); From 0faac9d7fd1eed8986c0c3a5426011351c890ced Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Fri, 24 Feb 2023 14:57:14 +0000 Subject: [PATCH 06/17] Add depth first bottom up rewriting --- CodeMaid.UnitTests/RoslynTests.cs | 131 +++++++++++++++--- .../AddExplicitAccessModifierLogic.cs | 52 +++++-- 2 files changed, 150 insertions(+), 33 deletions(-) diff --git a/CodeMaid.UnitTests/RoslynTests.cs b/CodeMaid.UnitTests/RoslynTests.cs index 60e8aab0c..776d0be6d 100644 --- a/CodeMaid.UnitTests/RoslynTests.cs +++ b/CodeMaid.UnitTests/RoslynTests.cs @@ -13,30 +13,33 @@ namespace SteveCadwallader.CodeMaid.UnitTests; internal class Rewriter : CSharpSyntaxRewriter { - internal Func PropertyWriter { get; set; } - internal Func MethodWriter { get; set; } - internal Func ClassWriter { get; set; } + internal Func PropertyWriter { get; set; } + internal Func MethodWriter { get; set; } + internal Func ClassWriter { get; set; } public Rewriter() { - PropertyWriter = x => x; - MethodWriter = x => x; - ClassWriter = x => x; + PropertyWriter = (_, x) => x; + MethodWriter = (_, x) => x; + ClassWriter = (_, x) => x; } public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node) { - return PropertyWriter(node); + var newNode = base.VisitPropertyDeclaration(node); + return PropertyWriter(node, newNode as PropertyDeclarationSyntax); } public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) { - return MethodWriter(node); + var newNode = base.VisitMethodDeclaration(node); + return MethodWriter(node, newNode as MethodDeclarationSyntax); } public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) { - return ClassWriter(node); + var newNode = base.VisitClassDeclaration(node); + return ClassWriter(node, newNode as ClassDeclarationSyntax); } } @@ -194,7 +197,52 @@ public partial class Temp var semanticModel = await document.GetSemanticModelAsync(); var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - var rewriter = new Rewriter() { PropertyWriter = sut.ProcessProperty, MethodWriter = sut.ProcessMethod, ClassWriter = sut.ProcessClass }; + var rewriter = new Rewriter() + { + PropertyWriter = sut.ProcessProperty, + MethodWriter = sut.ProcessMethod, + ClassWriter = sut.ProcessClass + }; + var result = rewriter.Visit(syntaxTree); + + result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); + + Console.WriteLine(result.ToFullString()); + var actual = result.ToFullString(); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public async Task TestDepthAsync() + { + var source = +""" +class Temp +{ + int MyProperty { get; set; } +} +"""; + + var expected = +""" +internal class Temp +{ + private int MyProperty { get; set; } +} +"""; + var document = testWorkspace.SetDocument(source); + + var syntaxTree = await document.GetSyntaxRootAsync(); + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + var semanticModel = await document.GetSemanticModelAsync(); + + var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + var rewriter = new Rewriter() + { + PropertyWriter = sut.ProcessProperty, + MethodWriter = sut.ProcessMethod, + ClassWriter = sut.ProcessClass + }; var result = rewriter.Visit(syntaxTree); result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); @@ -204,6 +252,61 @@ public partial class Temp Assert.AreEqual(expected, actual); } + [TestMethod] + public async Task TestNestAsync() + { + var source = +""" +class Temp +{ + int MyProperty { get; set; } +} + +public class Outer +{ + class Temp + { + int MyProperty { get; set; } + } +} +"""; + + var expected = +""" +internal class Temp +{ + private int MyProperty { get; set; } +} + +public class Outer +{ + private class Temp + { + private int MyProperty { get; set; } + } +} +"""; + var document = testWorkspace.SetDocument(source); + + var syntaxTree = await document.GetSyntaxRootAsync(); + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + var semanticModel = await document.GetSemanticModelAsync(); + + var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + var rewriter = new Rewriter() + { + PropertyWriter = sut.ProcessProperty, + MethodWriter = sut.ProcessMethod, + ClassWriter = sut.ProcessClass + }; + var result = rewriter.Visit(syntaxTree); + + result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); + + Console.WriteLine(result.ToFullString()); + var actual = result.ToFullString(); + Assert.AreEqual(expected, actual); + } public class TestWorkspace { @@ -239,12 +342,4 @@ public Document SetDocument(string text) return newDocument; } } - - public partial class Temp - { - } - - partial class Temp - { - } } \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs index 82d76e811..68396e03e 100644 --- a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs +++ b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs @@ -18,13 +18,13 @@ namespace CodeMaidShared.Logic.Cleaning { - internal class Rewriter : CSharpSyntaxRewriter + internal class RoslynRewriter : CSharpSyntaxRewriter { internal Func PropertyWriter { get; set; } internal Func MethodWriter { get; set; } internal Func ClassWriter { get; set; } - public Rewriter() + public RoslynRewriter() { PropertyWriter = x => x; MethodWriter = x => x; @@ -112,7 +112,7 @@ public static void Process(AsyncPackage package) if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) { - var rewriter = new Rewriter() { PropertyWriter = mod.ProcessProperty, MethodWriter = mod.ProcessMethod, ClassWriter = mod.ProcessClass }; + var rewriter = new RoslynRewriter() { }; var result = rewriter.Visit(root); root = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); @@ -124,30 +124,52 @@ public static void Process(AsyncPackage package) } - public SyntaxNode ProcessProperty(PropertyDeclarationSyntax node) + public SyntaxNode ProcessProperty(PropertyDeclarationSyntax original, PropertyDeclarationSyntax node) { - if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods) return node; - return GenericApplyAccessibility(node); - - //return Formatter.Format(root, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); + if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties) return node; + return GenericApplyAccessibility(original, node); } - public SyntaxNode ProcessClass(ClassDeclarationSyntax node) + public SyntaxNode ProcessClass(ClassDeclarationSyntax original, ClassDeclarationSyntax node) { if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses) return node; - return GenericApplyAccessibility(node); + return GenericApplyAccessibility(original, node); } - public SyntaxNode ProcessMethod(MethodDeclarationSyntax node) + public SyntaxNode ProcessMethod(MethodDeclarationSyntax original, MethodDeclarationSyntax node) { if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods) return node; - return GenericApplyAccessibility(node); + return GenericApplyAccessibility(original, node); } - public SyntaxNode ProcessMethod(ClassDeclarationSyntax node) + private SyntaxNode GenericApplyAccessibility(MemberDeclarationSyntax original, MemberDeclarationSyntax newNode) { - if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses) return node; - return GenericApplyAccessibility(node); + var symbol = _semanticModel.GetDeclaredSymbol(original); + + if (symbol is null) + { + throw new ArgumentNullException(nameof(symbol)); + } + + if (!AccessibilityHelper.ShouldUpdateAccessibilityModifier(original, AccessibilityModifiersRequired.Always, out var accessibility, out var canChange) || !canChange) + { + return newNode; + } + + var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); + + return UpdateAccessibility(newNode, preferredAccessibility); + + SyntaxNode UpdateAccessibility(SyntaxNode declaration, Accessibility preferredAccessibility) + { + // If there was accessibility on the member, then remove it. If there was no accessibility, then add + // the preferred accessibility for this member. + var newNode = _syntaxGenerator.GetAccessibility(declaration) == Accessibility.NotApplicable + ? _syntaxGenerator.WithAccessibility(declaration, preferredAccessibility) + : _syntaxGenerator.WithAccessibility(declaration, Accessibility.NotApplicable); + + return newNode; + } } private SyntaxNode GenericApplyAccessibility(MemberDeclarationSyntax node) From 23cb44524543ad46ac9c6ad5a8dbafcba9052fe0 Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Fri, 24 Feb 2023 16:58:56 +0000 Subject: [PATCH 07/17] Refactor tests and AddAccess --- CodeMaid.UnitTests/RoslynTests.cs | 159 ++++-------------- .../AddExplicitAccessModifierLogic.cs | 16 +- 2 files changed, 49 insertions(+), 126 deletions(-) diff --git a/CodeMaid.UnitTests/RoslynTests.cs b/CodeMaid.UnitTests/RoslynTests.cs index 776d0be6d..d8d472a00 100644 --- a/CodeMaid.UnitTests/RoslynTests.cs +++ b/CodeMaid.UnitTests/RoslynTests.cs @@ -1,7 +1,6 @@ using CodeMaidShared.Logic.Cleaning; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Text; @@ -13,33 +12,25 @@ namespace SteveCadwallader.CodeMaid.UnitTests; internal class Rewriter : CSharpSyntaxRewriter { - internal Func PropertyWriter { get; set; } - internal Func MethodWriter { get; set; } - internal Func ClassWriter { get; set; } + internal Func MemberWriter { get; set; } public Rewriter() { - PropertyWriter = (_, x) => x; - MethodWriter = (_, x) => x; - ClassWriter = (_, x) => x; + MemberWriter = (_, x) => x; } - public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node) + public override SyntaxNode Visit(SyntaxNode node) { - var newNode = base.VisitPropertyDeclaration(node); - return PropertyWriter(node, newNode as PropertyDeclarationSyntax); - } + var newNode = base.Visit(node); + newNode = MemberWriter(node, newNode); - public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) - { - var newNode = base.VisitMethodDeclaration(node); - return MethodWriter(node, newNode as MethodDeclarationSyntax); + return newNode; } - public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) + public SyntaxNode Process(SyntaxNode root, Workspace workspace) { - var newNode = base.VisitClassDeclaration(node); - return ClassWriter(node, newNode as ClassDeclarationSyntax); + var rewrite = Visit(root); + return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); } } @@ -72,21 +63,7 @@ public class Sample } """; - var document = testWorkspace.SetDocument(source); - - var syntaxTree = await document.GetSyntaxRootAsync(); - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); - var semanticModel = await document.GetSemanticModelAsync(); - - var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - var rewriter = new Rewriter() { PropertyWriter = sut.ProcessProperty }; - var result = rewriter.Visit(syntaxTree); - - result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); - - Console.WriteLine(result.ToFullString()); - var actual = result.ToFullString(); - Assert.AreEqual(expected, actual); + await testWorkspace.VerifyCleanupAsync(source, expected); } [TestMethod] @@ -108,21 +85,7 @@ public class Sample } """; - var document = testWorkspace.SetDocument(source); - - var syntaxTree = await document.GetSyntaxRootAsync(); - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); - var semanticModel = await document.GetSemanticModelAsync(); - - var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - var rewriter = new Rewriter() { PropertyWriter = sut.ProcessProperty }; - var result = rewriter.Visit(syntaxTree); - - result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); - - Console.WriteLine(result.ToFullString()); - var actual = result.ToFullString(); - Assert.AreEqual(expected, actual); + await testWorkspace.VerifyCleanupAsync(source, expected); } [TestMethod] @@ -148,21 +111,7 @@ private void Do() } """; - var document = testWorkspace.SetDocument(source); - - var syntaxTree = await document.GetSyntaxRootAsync(); - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); - var semanticModel = await document.GetSemanticModelAsync(); - - var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - var rewriter = new Rewriter() { PropertyWriter = sut.ProcessProperty, MethodWriter = sut.ProcessMethod }; - var result = rewriter.Visit(syntaxTree); - - result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); - - Console.WriteLine(result.ToFullString()); - var actual = result.ToFullString(); - Assert.AreEqual(expected, actual); + await testWorkspace.VerifyCleanupAsync(source, expected); } [TestMethod] @@ -190,26 +139,7 @@ public partial class Temp } """; - var document = testWorkspace.SetDocument(source); - - var syntaxTree = await document.GetSyntaxRootAsync(); - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); - var semanticModel = await document.GetSemanticModelAsync(); - - var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - var rewriter = new Rewriter() - { - PropertyWriter = sut.ProcessProperty, - MethodWriter = sut.ProcessMethod, - ClassWriter = sut.ProcessClass - }; - var result = rewriter.Visit(syntaxTree); - - result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); - - Console.WriteLine(result.ToFullString()); - var actual = result.ToFullString(); - Assert.AreEqual(expected, actual); + await testWorkspace.VerifyCleanupAsync(source, expected); } [TestMethod] @@ -230,26 +160,8 @@ internal class Temp private int MyProperty { get; set; } } """; - var document = testWorkspace.SetDocument(source); - - var syntaxTree = await document.GetSyntaxRootAsync(); - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); - var semanticModel = await document.GetSemanticModelAsync(); - var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - var rewriter = new Rewriter() - { - PropertyWriter = sut.ProcessProperty, - MethodWriter = sut.ProcessMethod, - ClassWriter = sut.ProcessClass - }; - var result = rewriter.Visit(syntaxTree); - - result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); - - Console.WriteLine(result.ToFullString()); - var actual = result.ToFullString(); - Assert.AreEqual(expected, actual); + await testWorkspace.VerifyCleanupAsync(source, expected); } [TestMethod] @@ -286,30 +198,29 @@ private class Temp } } """; - var document = testWorkspace.SetDocument(source); - - var syntaxTree = await document.GetSyntaxRootAsync(); - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); - var semanticModel = await document.GetSemanticModelAsync(); + await testWorkspace.VerifyCleanupAsync(source, expected); + } - var sut = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - var rewriter = new Rewriter() + public class TestWorkspace + { + public async Task VerifyCleanupAsync(string input, string expected) { - PropertyWriter = sut.ProcessProperty, - MethodWriter = sut.ProcessMethod, - ClassWriter = sut.ProcessClass - }; - var result = rewriter.Visit(syntaxTree); + var document = SetDocument(input); - result = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, testWorkspace.Workspace); + var syntaxTree = await Document.GetSyntaxRootAsync(); + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + var semanticModel = await Document.GetSemanticModelAsync(); - Console.WriteLine(result.ToFullString()); - var actual = result.ToFullString(); - Assert.AreEqual(expected, actual); - } + var modifierLogic = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + var rewriter = new Rewriter() + { + MemberWriter = modifierLogic.ProcessMember + }; + var result = rewriter.Process(syntaxTree, Workspace); + + Assert.AreEqual(expected, result.ToFullString()); + } - public class TestWorkspace - { public TestWorkspace() { var source = @@ -337,9 +248,9 @@ public class Sample public Document SetDocument(string text) { - var newDocument = Document.WithText(SourceText.From(text)); - Workspace.TryApplyChanges(newDocument.Project.Solution); - return newDocument; + Document = Document.WithText(SourceText.From(text)); + Assert.IsTrue(Workspace.TryApplyChanges(Document.Project.Solution)); + return Document; } } } \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs index 68396e03e..7261acb73 100644 --- a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs +++ b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs @@ -124,6 +124,18 @@ public static void Process(AsyncPackage package) } + public SyntaxNode ProcessMember(SyntaxNode original, SyntaxNode node) + { + return node switch + { + ClassDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses => GenericApplyAccessibility(original, node), + PropertyDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties=> GenericApplyAccessibility(original, node), + MethodDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods=> GenericApplyAccessibility(original, node), + StructDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnStructs=> GenericApplyAccessibility(original, node), + _ => node, + }; + } + public SyntaxNode ProcessProperty(PropertyDeclarationSyntax original, PropertyDeclarationSyntax node) { if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties) return node; @@ -142,7 +154,7 @@ public SyntaxNode ProcessMethod(MethodDeclarationSyntax original, MethodDeclarat return GenericApplyAccessibility(original, node); } - private SyntaxNode GenericApplyAccessibility(MemberDeclarationSyntax original, MemberDeclarationSyntax newNode) + private SyntaxNode GenericApplyAccessibility(SyntaxNode original, SyntaxNode newNode) { var symbol = _semanticModel.GetDeclaredSymbol(original); @@ -151,7 +163,7 @@ private SyntaxNode GenericApplyAccessibility(MemberDeclarationSyntax original, M throw new ArgumentNullException(nameof(symbol)); } - if (!AccessibilityHelper.ShouldUpdateAccessibilityModifier(original, AccessibilityModifiersRequired.Always, out var accessibility, out var canChange) || !canChange) + if (!AccessibilityHelper.ShouldUpdateAccessibilityModifier(original as MemberDeclarationSyntax, AccessibilityModifiersRequired.Always, out var accessibility, out var canChange) || !canChange) { return newNode; } From 227a5ec7dc41577646441c8cc2234fcabd5b246a Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Fri, 24 Feb 2023 18:39:42 +0000 Subject: [PATCH 08/17] Add tests and cleanup --- CodeMaid.UnitTests/Cleanup/RoslynTests.cs | 309 ++++++++++++++++++ CodeMaid.UnitTests/Cleanup/TestWorkspace.cs | 61 ++++ CodeMaid.UnitTests/CodeMaid.UnitTests.csproj | 3 +- CodeMaid.UnitTests/RoslynTests.cs | 256 --------------- .../AddExplicitAccessModifierLogic.cs | 256 +-------------- 5 files changed, 388 insertions(+), 497 deletions(-) create mode 100644 CodeMaid.UnitTests/Cleanup/RoslynTests.cs create mode 100644 CodeMaid.UnitTests/Cleanup/TestWorkspace.cs delete mode 100644 CodeMaid.UnitTests/RoslynTests.cs diff --git a/CodeMaid.UnitTests/Cleanup/RoslynTests.cs b/CodeMaid.UnitTests/Cleanup/RoslynTests.cs new file mode 100644 index 000000000..1645b8a05 --- /dev/null +++ b/CodeMaid.UnitTests/Cleanup/RoslynTests.cs @@ -0,0 +1,309 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Threading.Tasks; + +namespace SteveCadwallader.CodeMaid.UnitTests.Cleanup; + +[TestClass] +public class RoslynTests +{ + private readonly TestWorkspace testWorkspace; + + public RoslynTests() + { + testWorkspace = new TestWorkspace(); + } + + [TestMethod] + public async Task ShouldAddClassAccessorAsync() + { + var source = +""" +class MyClass +{ +} +"""; + + var expected = +""" +internal class MyClass +{ +} +"""; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddSamePartialClassAccessorsAsync() + { + var source = +""" +public partial class Temp +{ +} + +partial class Temp +{ +} +"""; + + var expected = +""" +public partial class Temp +{ +} + +public partial class Temp +{ +} +"""; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddNestedClassAccessorAsync() + { + var source = +""" +class Temp +{ + int MyProperty { get; set; } +} + +public class Outer +{ + class Temp + { + int MyProperty { get; set; } + } +} +"""; + + var expected = +""" +internal class Temp +{ + private int MyProperty { get; set; } +} + +public class Outer +{ + private class Temp + { + private int MyProperty { get; set; } + } +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddStructAccessorAsync() + { + var source = +""" +struct MyStruct +{ +} +"""; + + var expected = +""" +internal struct MyStruct +{ +} +"""; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddRefStructAccessorAsync() + { + var source = +""" +ref struct MyStruct +{ +} + +readonly ref struct MyReadonlyStruct +{ +} +"""; + + var expected = +""" +internal ref struct MyStruct +{ +} + +internal readonly ref struct MyReadonlyStruct +{ +} +"""; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddPropertyAccessorAsync() + { + var source = +""" +class Sample +{ + int Prop { get; set; } +} +"""; + + var expected = +""" +internal class Sample +{ + private int Prop { get; set; } +} +"""; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldNotRemoveRequiredPropertyAsync() + { + var source = +""" +class Sample +{ + required int Prop { get; set; } +} +"""; + + var expected = +""" +internal class Sample +{ + private required int Prop { get; set; } +} +"""; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddMethodsAccessorAsync() + { + var source = +""" +class ExampleClass +{ + void Do() + { + } +} +"""; + + var expected = +""" +internal class ExampleClass +{ + private void Do() + { + } +} +"""; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldNotAddPartialMethodAccessorAsync() + { + var source = +""" +public partial class ExampleClass +{ + partial void Do() + { + } +} +"""; + + var expected = +""" +public partial class ExampleClass +{ + partial void Do() + { + } +} +"""; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + + [TestMethod] + public async Task ShouldAddDefaultAbstractVirtualAccessorsAsync() + { + var source = +""" +abstract class MyAbstract +{ + virtual void VirtualMethod() + { + } + + abstract void AbstractMethod(); +} +"""; + + var expected = +""" +internal abstract class MyAbstract +{ + public virtual void VirtualMethod() + { + } + + protected abstract void AbstractMethod(); +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task TestInheritsAbstractAsync() + { + var source = +""" +abstract class MyAbstract +{ + private protected abstract void AbstractMethod(); +} + +class Derive : MyAbstract +{ + override void AbstractMethod() + { + } +} +"""; + + var expected = +""" +internal abstract class MyAbstract +{ + private protected abstract void AbstractMethod(); +} + +internal class Derive : MyAbstract +{ + private protected override void AbstractMethod() + { + } +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } +} \ No newline at end of file diff --git a/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs b/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs new file mode 100644 index 000000000..0b84091bc --- /dev/null +++ b/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs @@ -0,0 +1,61 @@ +using CodeMaidShared.Logic.Cleaning; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Threading.Tasks; + +namespace SteveCadwallader.CodeMaid.UnitTests.Cleanup; + +public class TestWorkspace +{ + public async Task VerifyCleanupAsync(string input, string expected) + { + var document = SetDocument(input); + + var syntaxTree = await Document.GetSyntaxRootAsync(); + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + var semanticModel = await Document.GetSemanticModelAsync(); + + var modifierLogic = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + var rewriter = new RoslynRewriter() + { + MemberWriter = modifierLogic.ProcessMember + }; + var result = rewriter.Process(syntaxTree, Workspace); + + Assert.AreEqual(expected, result.ToFullString()); + } + + public TestWorkspace() + { + var source = +""" +public class ThisShouldAppear +{ +} +"""; + + Workspace = new AdhocWorkspace(); + + var projName = "TestProject"; + var projectId = ProjectId.CreateNewId(); + var versionStamp = VersionStamp.Create(); + var projectInfo = ProjectInfo.Create(projectId, versionStamp, projName, projName, LanguageNames.CSharp); + var newProject = Workspace.AddProject(projectInfo); + + var sourceText = SourceText.From(source); + Document = Workspace.AddDocument(newProject.Id, "NewFile.cs", sourceText); + } + + public AdhocWorkspace Workspace { get; private set; } + + public Document Document { get; private set; } + + public Document SetDocument(string text) + { + Document = Document.WithText(SourceText.From(text)); + Assert.IsTrue(Workspace.TryApplyChanges(Document.Project.Solution)); + return Document; + } +} \ No newline at end of file diff --git a/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj b/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj index b08886a19..f2d563d81 100644 --- a/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj +++ b/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj @@ -66,7 +66,8 @@ - + + diff --git a/CodeMaid.UnitTests/RoslynTests.cs b/CodeMaid.UnitTests/RoslynTests.cs deleted file mode 100644 index d8d472a00..000000000 --- a/CodeMaid.UnitTests/RoslynTests.cs +++ /dev/null @@ -1,256 +0,0 @@ -using CodeMaidShared.Logic.Cleaning; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Threading.Tasks; - -namespace SteveCadwallader.CodeMaid.UnitTests; - -internal class Rewriter : CSharpSyntaxRewriter -{ - internal Func MemberWriter { get; set; } - - public Rewriter() - { - MemberWriter = (_, x) => x; - } - - public override SyntaxNode Visit(SyntaxNode node) - { - var newNode = base.Visit(node); - newNode = MemberWriter(node, newNode); - - return newNode; - } - - public SyntaxNode Process(SyntaxNode root, Workspace workspace) - { - var rewrite = Visit(root); - return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); - } -} - -[TestClass] -public class RoslynTests -{ - private readonly TestWorkspace testWorkspace; - - public RoslynTests() - { - testWorkspace = new TestWorkspace(); - } - - [TestMethod] - public async Task ShouldAddPropertyAccessorAsync() - { - var source = -""" -public class Sample -{ - int Prop { get; set; } -} -"""; - - var expected = -""" -public class Sample -{ - private int Prop { get; set; } -} -"""; - - await testWorkspace.VerifyCleanupAsync(source, expected); - } - - [TestMethod] - public async Task ShouldAddPropertyAccessor2Async() - { - var source = -""" -public class Sample -{ - required int Prop { get; set; } -} -"""; - - var expected = -""" -public class Sample -{ - private required int Prop { get; set; } -} -"""; - - await testWorkspace.VerifyCleanupAsync(source, expected); - } - - [TestMethod] - public async Task ShouldAddPropertyAccessorMethodsAsync() - { - var source = -""" -public partial class ExampleClass -{ - void Do() - { - } -} -"""; - - var expected = -""" -public partial class ExampleClass -{ - private void Do() - { - } -} -"""; - - await testWorkspace.VerifyCleanupAsync(source, expected); - } - - [TestMethod] - public async Task ShouldAddPropertyAccessorClassAsync() - { - var source = -""" -public partial class Temp -{ -} - -partial class Temp -{ -} -"""; - - var expected = -""" -public partial class Temp -{ -} - -public partial class Temp -{ -} -"""; - - await testWorkspace.VerifyCleanupAsync(source, expected); - } - - [TestMethod] - public async Task TestDepthAsync() - { - var source = -""" -class Temp -{ - int MyProperty { get; set; } -} -"""; - - var expected = -""" -internal class Temp -{ - private int MyProperty { get; set; } -} -"""; - - await testWorkspace.VerifyCleanupAsync(source, expected); - } - - [TestMethod] - public async Task TestNestAsync() - { - var source = -""" -class Temp -{ - int MyProperty { get; set; } -} - -public class Outer -{ - class Temp - { - int MyProperty { get; set; } - } -} -"""; - - var expected = -""" -internal class Temp -{ - private int MyProperty { get; set; } -} - -public class Outer -{ - private class Temp - { - private int MyProperty { get; set; } - } -} -"""; - await testWorkspace.VerifyCleanupAsync(source, expected); - } - - public class TestWorkspace - { - public async Task VerifyCleanupAsync(string input, string expected) - { - var document = SetDocument(input); - - var syntaxTree = await Document.GetSyntaxRootAsync(); - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); - var semanticModel = await Document.GetSemanticModelAsync(); - - var modifierLogic = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - var rewriter = new Rewriter() - { - MemberWriter = modifierLogic.ProcessMember - }; - var result = rewriter.Process(syntaxTree, Workspace); - - Assert.AreEqual(expected, result.ToFullString()); - } - - public TestWorkspace() - { - var source = -""" -public class Sample -{ -} -"""; - - Workspace = new AdhocWorkspace(); - - var projName = "TestProject"; - var projectId = ProjectId.CreateNewId(); - var versionStamp = VersionStamp.Create(); - var projectInfo = ProjectInfo.Create(projectId, versionStamp, projName, projName, LanguageNames.CSharp); - var newProject = Workspace.AddProject(projectInfo); - - var sourceText = SourceText.From(source); - Document = Workspace.AddDocument(newProject.Id, "NewFile.cs", sourceText); - } - - public AdhocWorkspace Workspace { get; private set; } - - public Document Document { get; private set; } - - public Document SetDocument(string text) - { - Document = Document.WithText(SourceText.From(text)); - Assert.IsTrue(Workspace.TryApplyChanges(Document.Project.Solution)); - return Document; - } - } -} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs index 7261acb73..573f89ef0 100644 --- a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs +++ b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs @@ -4,46 +4,38 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.VisualStudio.Shell; -using SteveCadwallader.CodeMaid; using SteveCadwallader.CodeMaid.Logic.Cleaning; using SteveCadwallader.CodeMaid.Properties; using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace CodeMaidShared.Logic.Cleaning { - internal class RoslynRewriter : CSharpSyntaxRewriter { - internal Func PropertyWriter { get; set; } - internal Func MethodWriter { get; set; } - internal Func ClassWriter { get; set; } + internal Func MemberWriter { get; set; } public RoslynRewriter() { - PropertyWriter = x => x; - MethodWriter = x => x; - ClassWriter = x => x; + MemberWriter = (_, x) => x; } - public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node) + public override SyntaxNode Visit(SyntaxNode node) { - return PropertyWriter(node); - } + var newNode = base.Visit(node); + newNode = MemberWriter(node, newNode); - public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) - { - return MethodWriter(node); + return newNode; } - public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) + public SyntaxNode Process(SyntaxNode root, Workspace workspace) { - return ClassWriter(node); + var rewrite = Visit(root); + return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); } } @@ -53,10 +45,11 @@ public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) internal class AddExplicitAccessModifierLogic { #region Fields + private readonly SemanticModel _semanticModel; private readonly SyntaxGenerator _syntaxGenerator; - #endregion Fields + #endregion Fields #region Constructors @@ -84,8 +77,6 @@ internal static AddExplicitAccessModifierLogic GetInstance(AsyncPackage package) return new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - root = Formatter.Format(root, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); - document = document.WithSyntaxRoot(root); Global.Workspace.TryApplyChanges(document.Project.Solution); } @@ -112,7 +103,7 @@ public static void Process(AsyncPackage package) if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) { - var rewriter = new RoslynRewriter() { }; + var rewriter = new RoslynRewriter() { }; var result = rewriter.Visit(root); root = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); @@ -121,7 +112,6 @@ public static void Process(AsyncPackage package) Global.Workspace.TryApplyChanges(document.Project.Solution); } throw new InvalidOperationException(); - } public SyntaxNode ProcessMember(SyntaxNode original, SyntaxNode node) @@ -129,31 +119,13 @@ public SyntaxNode ProcessMember(SyntaxNode original, SyntaxNode node) return node switch { ClassDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses => GenericApplyAccessibility(original, node), - PropertyDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties=> GenericApplyAccessibility(original, node), - MethodDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods=> GenericApplyAccessibility(original, node), - StructDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnStructs=> GenericApplyAccessibility(original, node), + PropertyDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties => GenericApplyAccessibility(original, node), + MethodDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods => GenericApplyAccessibility(original, node), + StructDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnStructs => GenericApplyAccessibility(original, node), _ => node, }; } - public SyntaxNode ProcessProperty(PropertyDeclarationSyntax original, PropertyDeclarationSyntax node) - { - if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties) return node; - return GenericApplyAccessibility(original, node); - } - - public SyntaxNode ProcessClass(ClassDeclarationSyntax original, ClassDeclarationSyntax node) - { - if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses) return node; - return GenericApplyAccessibility(original, node); - } - - public SyntaxNode ProcessMethod(MethodDeclarationSyntax original, MethodDeclarationSyntax node) - { - if (!Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods) return node; - return GenericApplyAccessibility(original, node); - } - private SyntaxNode GenericApplyAccessibility(SyntaxNode original, SyntaxNode newNode) { var symbol = _semanticModel.GetDeclaredSymbol(original); @@ -176,36 +148,7 @@ SyntaxNode UpdateAccessibility(SyntaxNode declaration, Accessibility preferredAc { // If there was accessibility on the member, then remove it. If there was no accessibility, then add // the preferred accessibility for this member. - var newNode = _syntaxGenerator.GetAccessibility(declaration) == Accessibility.NotApplicable - ? _syntaxGenerator.WithAccessibility(declaration, preferredAccessibility) - : _syntaxGenerator.WithAccessibility(declaration, Accessibility.NotApplicable); - - return newNode; - } - } - - private SyntaxNode GenericApplyAccessibility(MemberDeclarationSyntax node) - { - var symbol = _semanticModel.GetDeclaredSymbol(node); - - if (symbol is null) - { - throw new ArgumentNullException(nameof(symbol)); - } - - if (!AccessibilityHelper.ShouldUpdateAccessibilityModifier(node, AccessibilityModifiersRequired.Always, out var accessibility, out var canChange) || !canChange) - { - return node; - } - - var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); - - return UpdateAccessibility(node, preferredAccessibility); - - SyntaxNode UpdateAccessibility(SyntaxNode declaration, Accessibility preferredAccessibility) - { - // If there was accessibility on the member, then remove it. If there was no accessibility, then add - // the preferred accessibility for this member. + // TODO remove? var newNode = _syntaxGenerator.GetAccessibility(declaration) == Accessibility.NotApplicable ? _syntaxGenerator.WithAccessibility(declaration, preferredAccessibility) : _syntaxGenerator.WithAccessibility(declaration, Accessibility.NotApplicable); @@ -229,41 +172,10 @@ public static async ValueTask GetRequiredSemanticModelAsync(this internal static partial class AddAccessibilityModifiersHelpers { - public static void UpdateDeclaration( - SyntaxEditor editor, ISymbol symbol, SyntaxNode declaration) - { - if (symbol is null) - { - throw new ArgumentNullException(nameof(symbol)); - } - - var preferredAccessibility = GetPreferredAccessibility(symbol); - - // Check to see if we need to add or remove - // If there's a modifier, then we need to remove it, otherwise no modifier, add it. - editor.ReplaceNode( - declaration, - (currentDeclaration, _) => UpdateAccessibility(currentDeclaration, preferredAccessibility)); - - return; - - SyntaxNode UpdateAccessibility(SyntaxNode declaration, Accessibility preferredAccessibility) - { - var generator = editor.Generator; - - // If there was accessibility on the member, then remove it. If there was no accessibility, then add - // the preferred accessibility for this member. - return generator.GetAccessibility(declaration) == Accessibility.NotApplicable - ? generator.WithAccessibility(declaration, preferredAccessibility) - : generator.WithAccessibility(declaration, Accessibility.NotApplicable); - } - } - internal static Accessibility GetPreferredAccessibility(ISymbol symbol) { // If we have an overridden member, then if we're adding an accessibility modifier, use the // accessibility of the member we're overriding as both should be consistent here. - // TODO Check override if (symbol.GetOverriddenMember() is { DeclaredAccessibility: var accessibility }) return accessibility; @@ -440,141 +352,5 @@ public static SyntaxToken GetNameToken(this MemberDeclarationSyntax member) // Conversion operators don't have names. return default; } - - public static int GetArity(this MemberDeclarationSyntax member) - { - if (member != null) - { - switch (member.Kind()) - { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.RecordDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.StructDeclaration: - case SyntaxKind.RecordStructDeclaration: - return ((TypeDeclarationSyntax)member).Arity; - - case SyntaxKind.DelegateDeclaration: - return ((DelegateDeclarationSyntax)member).Arity; - - case SyntaxKind.MethodDeclaration: - return ((MethodDeclarationSyntax)member).Arity; - } - } - - return 0; - } - - public static TypeParameterListSyntax GetTypeParameterList(this MemberDeclarationSyntax member) - { - if (member != null) - { - switch (member.Kind()) - { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.RecordDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.StructDeclaration: - case SyntaxKind.RecordStructDeclaration: - return ((TypeDeclarationSyntax)member).TypeParameterList; - - case SyntaxKind.DelegateDeclaration: - return ((DelegateDeclarationSyntax)member).TypeParameterList; - - case SyntaxKind.MethodDeclaration: - return ((MethodDeclarationSyntax)member).TypeParameterList; - } - } - - return null; - } - - public static MemberDeclarationSyntax WithParameterList( - this MemberDeclarationSyntax member, - BaseParameterListSyntax parameterList) - { - if (member != null) - { - switch (member.Kind()) - { - case SyntaxKind.DelegateDeclaration: - return ((DelegateDeclarationSyntax)member).WithParameterList((ParameterListSyntax)parameterList); - - case SyntaxKind.MethodDeclaration: - return ((MethodDeclarationSyntax)member).WithParameterList((ParameterListSyntax)parameterList); - - case SyntaxKind.ConstructorDeclaration: - return ((ConstructorDeclarationSyntax)member).WithParameterList((ParameterListSyntax)parameterList); - - case SyntaxKind.IndexerDeclaration: - return ((IndexerDeclarationSyntax)member).WithParameterList((BracketedParameterListSyntax)parameterList); - - case SyntaxKind.OperatorDeclaration: - return ((OperatorDeclarationSyntax)member).WithParameterList((ParameterListSyntax)parameterList); - - case SyntaxKind.ConversionOperatorDeclaration: - return ((ConversionOperatorDeclarationSyntax)member).WithParameterList((ParameterListSyntax)parameterList); - } - } - - return null; - } - - public static TypeSyntax GetMemberType(this MemberDeclarationSyntax member) - { - if (member != null) - { - switch (member.Kind()) - { - case SyntaxKind.DelegateDeclaration: - return ((DelegateDeclarationSyntax)member).ReturnType; - - case SyntaxKind.MethodDeclaration: - return ((MethodDeclarationSyntax)member).ReturnType; - - case SyntaxKind.OperatorDeclaration: - return ((OperatorDeclarationSyntax)member).ReturnType; - - case SyntaxKind.PropertyDeclaration: - return ((PropertyDeclarationSyntax)member).Type; - - case SyntaxKind.IndexerDeclaration: - return ((IndexerDeclarationSyntax)member).Type; - - case SyntaxKind.EventDeclaration: - return ((EventDeclarationSyntax)member).Type; - - case SyntaxKind.EventFieldDeclaration: - return ((EventFieldDeclarationSyntax)member).Declaration.Type; - - case SyntaxKind.FieldDeclaration: - return ((FieldDeclarationSyntax)member).Declaration.Type; - } - } - - return null; - } - - public static bool HasMethodShape(this MemberDeclarationSyntax memberDeclaration) - => memberDeclaration is BaseMethodDeclarationSyntax; - - public static BlockSyntax GetBody(this MemberDeclarationSyntax memberDeclaration) - => memberDeclaration switch - { - BaseMethodDeclarationSyntax method => method.Body, - _ => null, - }; - - public static ArrowExpressionClauseSyntax GetExpressionBody(this MemberDeclarationSyntax memberDeclaration) - => memberDeclaration switch - { - BaseMethodDeclarationSyntax method => method.ExpressionBody, - IndexerDeclarationSyntax indexer => indexer.ExpressionBody, - PropertyDeclarationSyntax property => property.ExpressionBody, - _ => null, - }; - - public static MemberDeclarationSyntax WithBody(this MemberDeclarationSyntax memberDeclaration, BlockSyntax body) - => (memberDeclaration as BaseMethodDeclarationSyntax)?.WithBody(body); } } \ No newline at end of file From 78dfb8e6c17e4d6c90476def9060fee1e51d0159 Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Fri, 24 Feb 2023 19:17:28 +0000 Subject: [PATCH 09/17] Move objects helpers to files --- CodeMaid.UnitTests/Cleanup/RoslynTests.cs | 12 +- CodeMaid.UnitTests/Cleanup/TestWorkspace.cs | 2 +- CodeMaidShared/CodeMaidShared.projitems | 12 +- .../AddExplicitAccessModifierLogic.cs | 356 -------- .../Cleaning/CSharpAccessibilityFacts.cs | 336 ------- CodeMaidShared/Logic/Cleaning/Global.cs | 78 -- .../Roslyn/AccessibilityModifiersRequired.cs | 19 + .../Roslyn/AddExplicitAccessModifierLogic.cs | 118 +++ .../Roslyn/CSharpAccessibilityFacts.cs | 403 +++++++++ .../Logic/Cleaning/Roslyn/Global.cs | 70 ++ .../Logic/Cleaning/Roslyn/RoslynCleanup.cs | 30 + .../Logic/Cleaning/Roslyn/RoslynExtensions.cs | 109 +++ .../Logic/Cleaning/SyntaxNodeExtensions.cs | 837 ------------------ 13 files changed, 768 insertions(+), 1614 deletions(-) delete mode 100644 CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs delete mode 100644 CodeMaidShared/Logic/Cleaning/CSharpAccessibilityFacts.cs delete mode 100644 CodeMaidShared/Logic/Cleaning/Global.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/AccessibilityModifiersRequired.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/AddExplicitAccessModifierLogic.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/CSharpAccessibilityFacts.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/Global.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs delete mode 100644 CodeMaidShared/Logic/Cleaning/SyntaxNodeExtensions.cs diff --git a/CodeMaid.UnitTests/Cleanup/RoslynTests.cs b/CodeMaid.UnitTests/Cleanup/RoslynTests.cs index 1645b8a05..a53b0f6e2 100644 --- a/CodeMaid.UnitTests/Cleanup/RoslynTests.cs +++ b/CodeMaid.UnitTests/Cleanup/RoslynTests.cs @@ -242,7 +242,6 @@ partial void Do() await testWorkspace.VerifyCleanupAsync(source, expected); } - [TestMethod] public async Task ShouldAddDefaultAbstractVirtualAccessorsAsync() { @@ -306,4 +305,13 @@ private protected override void AbstractMethod() """; await testWorkspace.VerifyCleanupAsync(source, expected); } -} \ No newline at end of file +} + +//public interface MyInterface +//{ +// void Do(); + +// void Doer() +// { +// } +//} \ No newline at end of file diff --git a/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs b/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs index 0b84091bc..b0ebd0584 100644 --- a/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs +++ b/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs @@ -18,7 +18,7 @@ public async Task VerifyCleanupAsync(string input, string expected) var semanticModel = await Document.GetSemanticModelAsync(); var modifierLogic = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - var rewriter = new RoslynRewriter() + var rewriter = new RoslynCleanup() { MemberWriter = modifierLogic.ProcessMember }; diff --git a/CodeMaidShared/CodeMaidShared.projitems b/CodeMaidShared/CodeMaidShared.projitems index d181072cd..dd3142a48 100644 --- a/CodeMaidShared/CodeMaidShared.projitems +++ b/CodeMaidShared/CodeMaidShared.projitems @@ -83,18 +83,19 @@ - - + + + - + - + @@ -388,4 +389,7 @@ Resources.resx + + + \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs deleted file mode 100644 index 573f89ef0..000000000 --- a/CodeMaidShared/Logic/Cleaning/AddExplicitAccessModifierLogic.cs +++ /dev/null @@ -1,356 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.VisualStudio.Shell; -using SteveCadwallader.CodeMaid.Logic.Cleaning; -using SteveCadwallader.CodeMaid.Properties; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; - -namespace CodeMaidShared.Logic.Cleaning -{ - internal class RoslynRewriter : CSharpSyntaxRewriter - { - internal Func MemberWriter { get; set; } - - public RoslynRewriter() - { - MemberWriter = (_, x) => x; - } - - public override SyntaxNode Visit(SyntaxNode node) - { - var newNode = base.Visit(node); - newNode = MemberWriter(node, newNode); - - return newNode; - } - - public SyntaxNode Process(SyntaxNode root, Workspace workspace) - { - var rewrite = Visit(root); - return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); - } - } - - /// - /// A class for encapsulating insertion of explicit access modifier logic. - /// - internal class AddExplicitAccessModifierLogic - { - #region Fields - - private readonly SemanticModel _semanticModel; - private readonly SyntaxGenerator _syntaxGenerator; - - #endregion Fields - - #region Constructors - - /// - /// The singleton instance of the class. - /// - //private static AddExplicitAccessModifierLogic _instance; - - /// - /// Gets an instance of the class. - /// - /// An instance of the class. - internal static AddExplicitAccessModifierLogic GetInstance(AsyncPackage package) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - Global.Package = package; - - var document = Global.GetActiveDocument(); - - if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) - { - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); - var semanticModel = document.GetSemanticModelAsync().Result; - - return new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - - document = document.WithSyntaxRoot(root); - Global.Workspace.TryApplyChanges(document.Project.Solution); - } - - throw new InvalidOperationException(); - } - - /// - /// Initializes a new instance of the class. - /// - public AddExplicitAccessModifierLogic(SemanticModel semanticModel, SyntaxGenerator syntaxGenerator) - { - _semanticModel = semanticModel; - _syntaxGenerator = syntaxGenerator; - } - - #endregion Constructors - - public static void Process(AsyncPackage package) - { - var mod = GetInstance(package); - - var document = Global.GetActiveDocument(); - - if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) - { - var rewriter = new RoslynRewriter() { }; - var result = rewriter.Visit(root); - - root = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); - - document = document.WithSyntaxRoot(root); - Global.Workspace.TryApplyChanges(document.Project.Solution); - } - throw new InvalidOperationException(); - } - - public SyntaxNode ProcessMember(SyntaxNode original, SyntaxNode node) - { - return node switch - { - ClassDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses => GenericApplyAccessibility(original, node), - PropertyDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties => GenericApplyAccessibility(original, node), - MethodDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods => GenericApplyAccessibility(original, node), - StructDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnStructs => GenericApplyAccessibility(original, node), - _ => node, - }; - } - - private SyntaxNode GenericApplyAccessibility(SyntaxNode original, SyntaxNode newNode) - { - var symbol = _semanticModel.GetDeclaredSymbol(original); - - if (symbol is null) - { - throw new ArgumentNullException(nameof(symbol)); - } - - if (!AccessibilityHelper.ShouldUpdateAccessibilityModifier(original as MemberDeclarationSyntax, AccessibilityModifiersRequired.Always, out var accessibility, out var canChange) || !canChange) - { - return newNode; - } - - var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); - - return UpdateAccessibility(newNode, preferredAccessibility); - - SyntaxNode UpdateAccessibility(SyntaxNode declaration, Accessibility preferredAccessibility) - { - // If there was accessibility on the member, then remove it. If there was no accessibility, then add - // the preferred accessibility for this member. - // TODO remove? - var newNode = _syntaxGenerator.GetAccessibility(declaration) == Accessibility.NotApplicable - ? _syntaxGenerator.WithAccessibility(declaration, preferredAccessibility) - : _syntaxGenerator.WithAccessibility(declaration, Accessibility.NotApplicable); - - return newNode; - } - } - } - - internal static class DocumentExtensions - { - public static async ValueTask GetRequiredSemanticModelAsync(this Document document, CancellationToken cancellationToken) - { - if (document.TryGetSemanticModel(out var semanticModel)) - return semanticModel; - - semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - return semanticModel ?? throw new InvalidOperationException($"Syntax tree is required to accomplish the task but is not supported by document {document.Name}"); - } - } - - internal static partial class AddAccessibilityModifiersHelpers - { - internal static Accessibility GetPreferredAccessibility(ISymbol symbol) - { - // If we have an overridden member, then if we're adding an accessibility modifier, use the - // accessibility of the member we're overriding as both should be consistent here. - if (symbol.GetOverriddenMember() is { DeclaredAccessibility: var accessibility }) - return accessibility; - - // Default abstract members to be protected, and virtual members to be public. They can't be private as - // that's not legal. And these are reasonable default values for them. - if (symbol is IMethodSymbol or IPropertySymbol or IEventSymbol) - { - if (symbol.IsAbstract) - return Accessibility.Protected; - - if (symbol.IsVirtual) - return Accessibility.Public; - } - - // Otherwise, default to whatever accessibility no-accessibility means for this member; - return symbol.DeclaredAccessibility; - } - - public static ISymbol? GetOverriddenMember(this ISymbol? symbol) - => symbol switch - { - IMethodSymbol method => method.OverriddenMethod, - IPropertySymbol property => property.OverriddenProperty, - IEventSymbol @event => @event.OverriddenEvent, - _ => null, - }; - } - - internal enum AccessibilityModifiersRequired - { - // The rule is not run - Never = 0, - - // Accessibility modifiers are added if missing, even if default - Always = 1, - - // Future proofing for when C# adds default interface methods. At that point - // accessibility modifiers will be allowed in interfaces, and some people may - // want to require them, while some may want to keep the traditional C# style - // that public interface members do not need accessibility modifiers. - ForNonInterfaceMembers = 2, - - // Remove any accessibility modifier that matches the default - OmitIfDefault = 3 - } - - internal static class AccessibilityHelper - { - public static bool ShouldUpdateAccessibilityModifier( - MemberDeclarationSyntax member, - AccessibilityModifiersRequired option, - out Accessibility accessibility, - out bool modifierAdded) - { - modifierAdded = false; - accessibility = Accessibility.NotApplicable; - - // Have to have a name to report the issue on. - var name = member.GetNameToken(); - if (name.IsKind(SyntaxKind.None)) - return false; - - // Certain members never have accessibility. Don't bother reporting on them. - if (!CSharpAccessibilityFacts.CanHaveAccessibility(member)) - return false; - - // This analyzer bases all of its decisions on the accessibility - accessibility = CSharpAccessibilityFacts.GetAccessibility(member); - - // Omit will flag any accessibility values that exist and are default - // The other options will remove or ignore accessibility - var isOmit = option == AccessibilityModifiersRequired.OmitIfDefault; - modifierAdded = !isOmit; - - if (isOmit) - { - if (accessibility == Accessibility.NotApplicable) - return false; - - var parentKind = member.GetRequiredParent().Kind(); - switch (parentKind) - { - // Check for default modifiers in namespace and outside of namespace - case SyntaxKind.CompilationUnit: - case SyntaxKind.FileScopedNamespaceDeclaration: - case SyntaxKind.NamespaceDeclaration: - { - // Default is internal - if (accessibility != Accessibility.Internal) - return false; - } - - break; - - case SyntaxKind.ClassDeclaration: - case SyntaxKind.RecordDeclaration: - case SyntaxKind.StructDeclaration: - case SyntaxKind.RecordStructDeclaration: - { - // Inside a type, default is private - if (accessibility != Accessibility.Private) - return false; - } - - break; - - default: - return false; // Unknown parent kind, don't do anything - } - } - else - { - // Mode is always, so we have to flag missing modifiers - if (accessibility != Accessibility.NotApplicable) - return false; - } - - return true; - } - } - - internal static partial class MemberDeclarationSyntaxExtensions - { - private static readonly ConditionalWeakTable>> s_declarationCache = new(); - - public static SyntaxToken GetNameToken(this MemberDeclarationSyntax member) - { - if (member != null) - { - switch (member.Kind()) - { - case SyntaxKind.EnumDeclaration: - return ((EnumDeclarationSyntax)member).Identifier; - - case SyntaxKind.ClassDeclaration: - case SyntaxKind.RecordDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.StructDeclaration: - case SyntaxKind.RecordStructDeclaration: - return ((TypeDeclarationSyntax)member).Identifier; - - case SyntaxKind.DelegateDeclaration: - return ((DelegateDeclarationSyntax)member).Identifier; - - case SyntaxKind.FieldDeclaration: - return ((FieldDeclarationSyntax)member).Declaration.Variables.First().Identifier; - - case SyntaxKind.EventFieldDeclaration: - return ((EventFieldDeclarationSyntax)member).Declaration.Variables.First().Identifier; - - case SyntaxKind.PropertyDeclaration: - return ((PropertyDeclarationSyntax)member).Identifier; - - case SyntaxKind.EventDeclaration: - return ((EventDeclarationSyntax)member).Identifier; - - case SyntaxKind.MethodDeclaration: - return ((MethodDeclarationSyntax)member).Identifier; - - case SyntaxKind.ConstructorDeclaration: - return ((ConstructorDeclarationSyntax)member).Identifier; - - case SyntaxKind.DestructorDeclaration: - return ((DestructorDeclarationSyntax)member).Identifier; - - case SyntaxKind.IndexerDeclaration: - return ((IndexerDeclarationSyntax)member).ThisKeyword; - - case SyntaxKind.OperatorDeclaration: - return ((OperatorDeclarationSyntax)member).OperatorToken; - } - } - - // Conversion operators don't have names. - return default; - } - } -} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/CSharpAccessibilityFacts.cs b/CodeMaidShared/Logic/Cleaning/CSharpAccessibilityFacts.cs deleted file mode 100644 index b6e1bdb8d..000000000 --- a/CodeMaidShared/Logic/Cleaning/CSharpAccessibilityFacts.cs +++ /dev/null @@ -1,336 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Editing; -using System; -using System.Collections.Generic; -using System.Text; - -namespace CodeMaidShared.Logic.Cleaning -{ - internal static class CSharpAccessibilityFacts - { - public static 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 static 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.Parent ?? throw new InvalidOperationException("Token's parent was null")), - VariableDeclaratorSyntax varDecl => GetModifierTokens(varDecl.Parent ?? throw new InvalidOperationException("Token's parent was null")), - AnonymousFunctionExpressionSyntax anonymous => anonymous.Modifiers, - _ => default, - }; - - public static bool ParentIsFieldDeclaration(SyntaxNode? node) - => node?.Parent.IsKind(SyntaxKind.FieldDeclaration) ?? false; - - public static bool ParentIsEventFieldDeclaration(SyntaxNode? node) - => node?.Parent.IsKind(SyntaxKind.EventFieldDeclaration) ?? false; - - public static bool ParentIsLocalDeclarationStatement(SyntaxNode? node) - => node?.Parent.IsKind(SyntaxKind.LocalDeclarationStatement) ?? false; - } -} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Global.cs b/CodeMaidShared/Logic/Cleaning/Global.cs deleted file mode 100644 index 365cf834e..000000000 --- a/CodeMaidShared/Logic/Cleaning/Global.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.LanguageServices; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using System.Linq; -using Document = Microsoft.CodeAnalysis.Document; -using DteDocument = EnvDTE.Document; -using Solution = Microsoft.CodeAnalysis.Solution; - -namespace SteveCadwallader.CodeMaid.Logic.Cleaning -{ - public static class Global - { - static public AsyncPackage Package; - - static public T GetService() - => (T)Package?.GetServiceAsync(typeof(T))?.Result; - - static public DteDocument GetActiveDteDocument() - { - ThreadHelper.ThrowIfNotOnUIThread(); - dynamic dte = GetService(); - return (DteDocument)dte.ActiveDocument; - } - - static IVsStatusbar Statusbar; - - internal static void SetStatusMessage(string message) - { - if (Statusbar == null) - { - Statusbar = GetService(); - // StatusBar = Package.GetGlobalService(typeof(IVsStatusbar)) as IVsStatusbar; - } - - Statusbar.SetText(message); - } - - public static Document GetActiveDocument() - { - ThreadHelper.ThrowIfNotOnUIThread(); - - Solution solution = Workspace.CurrentSolution; - string activeDocPath = GetActiveDteDocument()?.FullName; - - if (activeDocPath != null) - return solution.Projects - .SelectMany(x => x.Documents) - .FirstOrDefault(x => x.SupportsSyntaxTree && - x.SupportsSemanticModel && - x.FilePath == activeDocPath); - return null; - } - - private static VisualStudioWorkspace workspace = null; - - static public VisualStudioWorkspace Workspace - { - get - { - if (workspace == null) - { - IComponentModel componentModel = GetService() as IComponentModel; - workspace = componentModel.GetService(); - } - return workspace; - } - } - } - - internal class Class1 - { - private protected Class1() - { - } - } -} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/AccessibilityModifiersRequired.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/AccessibilityModifiersRequired.cs new file mode 100644 index 000000000..64dac4fff --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/AccessibilityModifiersRequired.cs @@ -0,0 +1,19 @@ +namespace CodeMaidShared.Logic.Cleaning; + +internal enum AccessibilityModifiersRequired +{ + // The rule is not run + Never = 0, + + // Accessibility modifiers are added if missing, even if default + Always = 1, + + // Future proofing for when C# adds default interface methods. At that point + // accessibility modifiers will be allowed in interfaces, and some people may + // want to require them, while some may want to keep the traditional C# style + // that public interface members do not need accessibility modifiers. + ForNonInterfaceMembers = 2, + + // Remove any accessibility modifier that matches the default + OmitIfDefault = 3 +} diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/AddExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/AddExplicitAccessModifierLogic.cs new file mode 100644 index 000000000..4ef89f04c --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/AddExplicitAccessModifierLogic.cs @@ -0,0 +1,118 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.VisualStudio.Shell; +using SteveCadwallader.CodeMaid.Logic.Cleaning; +using SteveCadwallader.CodeMaid.Properties; +using System; + +namespace CodeMaidShared.Logic.Cleaning; + +/// +/// A class for encapsulating insertion of explicit access modifier logic. +/// +internal class AddExplicitAccessModifierLogic +{ + #region Fields + + private readonly SemanticModel _semanticModel; + private readonly SyntaxGenerator _syntaxGenerator; + + #endregion Fields + + #region Constructors + + /// + /// The singleton instance of the class. + /// + //private static AddExplicitAccessModifierLogic _instance; + + /// + /// Gets an instance of the class. + /// + /// An instance of the class. + internal static AddExplicitAccessModifierLogic GetInstance(AsyncPackage package) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + Global.Package = package; + + var document = Global.GetActiveDocument(); + + if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) + { + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + var semanticModel = document.GetSemanticModelAsync().Result; + + return new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + + document = document.WithSyntaxRoot(root); + Global.Workspace.TryApplyChanges(document.Project.Solution); + } + + throw new InvalidOperationException(); + } + + /// + /// Initializes a new instance of the class. + /// + public AddExplicitAccessModifierLogic(SemanticModel semanticModel, SyntaxGenerator syntaxGenerator) + { + _semanticModel = semanticModel; + _syntaxGenerator = syntaxGenerator; + } + + #endregion Constructors + + public static void Process(AsyncPackage package) + { + var mod = GetInstance(package); + + var document = Global.GetActiveDocument(); + + if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) + { + var rewriter = new RoslynCleanup() { }; + var result = rewriter.Visit(root); + + root = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); + + document = document.WithSyntaxRoot(root); + Global.Workspace.TryApplyChanges(document.Project.Solution); + } + throw new InvalidOperationException(); + } + + public SyntaxNode ProcessMember(SyntaxNode original, SyntaxNode node) + { + return node switch + { + ClassDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses => AddAccessibility(original, node), + PropertyDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties => AddAccessibility(original, node), + MethodDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods => AddAccessibility(original, node), + StructDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnStructs => AddAccessibility(original, node), + _ => node, + }; + } + + private SyntaxNode AddAccessibility(SyntaxNode original, SyntaxNode newNode) + { + var symbol = _semanticModel.GetDeclaredSymbol(original); + + if (symbol is null) + { + throw new ArgumentNullException(nameof(symbol)); + } + + if (!CSharpAccessibilityFacts.ShouldUpdateAccessibilityModifier(original as MemberDeclarationSyntax, AccessibilityModifiersRequired.Always, out var _, out var canChange) || !canChange) + { + return newNode; + } + + var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); + + return _syntaxGenerator.WithAccessibility(newNode, preferredAccessibility); + } +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/CSharpAccessibilityFacts.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/CSharpAccessibilityFacts.cs new file mode 100644 index 000000000..38b9dd0f9 --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/CSharpAccessibilityFacts.cs @@ -0,0 +1,403 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using SteveCadwallader.CodeMaid.Logic.Cleaning; +using System; + +namespace CodeMaidShared.Logic.Cleaning; + +internal static class CSharpAccessibilityFacts +{ + public static bool ShouldUpdateAccessibilityModifier( + MemberDeclarationSyntax member, + AccessibilityModifiersRequired option, + out Accessibility accessibility, + out bool modifierAdded) + { + modifierAdded = false; + accessibility = Accessibility.NotApplicable; + + // Have to have a name to report the issue on. + var name = member.GetNameToken(); + if (name.IsKind(SyntaxKind.None)) + return false; + + // Certain members never have accessibility. Don't bother reporting on them. + if (!CanHaveAccessibility(member)) + return false; + + // This analyzer bases all of its decisions on the accessibility + accessibility = GetAccessibility(member); + + // Omit will flag any accessibility values that exist and are default + // The other options will remove or ignore accessibility + var isOmit = option == AccessibilityModifiersRequired.OmitIfDefault; + modifierAdded = !isOmit; + + if (isOmit) + { + if (accessibility == Accessibility.NotApplicable) + return false; + + var parentKind = member.GetRequiredParent().Kind(); + switch (parentKind) + { + // Check for default modifiers in namespace and outside of namespace + case SyntaxKind.CompilationUnit: + case SyntaxKind.FileScopedNamespaceDeclaration: + case SyntaxKind.NamespaceDeclaration: + { + // Default is internal + if (accessibility != Accessibility.Internal) + return false; + } + + break; + + case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: + { + // Inside a type, default is private + if (accessibility != Accessibility.Private) + return false; + } + + break; + + default: + return false; // Unknown parent kind, don't do anything + } + } + else + { + // Mode is always, so we have to flag missing modifiers + if (accessibility != Accessibility.NotApplicable) + return false; + } + + return true; + } + + public static 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 static 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.Parent ?? throw new InvalidOperationException("Token's parent was null")), + VariableDeclaratorSyntax varDecl => GetModifierTokens(varDecl.Parent ?? throw new InvalidOperationException("Token's parent was null")), + AnonymousFunctionExpressionSyntax anonymous => anonymous.Modifiers, + _ => default, + }; + + public static bool ParentIsFieldDeclaration(SyntaxNode? node) + => node?.Parent.IsKind(SyntaxKind.FieldDeclaration) ?? false; + + public static bool ParentIsEventFieldDeclaration(SyntaxNode? node) + => node?.Parent.IsKind(SyntaxKind.EventFieldDeclaration) ?? false; +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/Global.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/Global.cs new file mode 100644 index 000000000..844c3df53 --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/Global.cs @@ -0,0 +1,70 @@ +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.LanguageServices; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using System.Linq; +using Document = Microsoft.CodeAnalysis.Document; +using DteDocument = EnvDTE.Document; +using Solution = Microsoft.CodeAnalysis.Solution; + +namespace SteveCadwallader.CodeMaid.Logic.Cleaning; + +internal static class Global +{ + static public AsyncPackage Package; + + static public T GetService() + => (T)Package?.GetServiceAsync(typeof(T))?.Result; + + static public DteDocument GetActiveDteDocument() + { + ThreadHelper.ThrowIfNotOnUIThread(); + dynamic dte = GetService(); + return (DteDocument)dte.ActiveDocument; + } + + static IVsStatusbar Statusbar; + + internal static void SetStatusMessage(string message) + { + if (Statusbar == null) + { + Statusbar = GetService(); + // StatusBar = Package.GetGlobalService(typeof(IVsStatusbar)) as IVsStatusbar; + } + + Statusbar.SetText(message); + } + + public static Document GetActiveDocument() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + Solution solution = Workspace.CurrentSolution; + string activeDocPath = GetActiveDteDocument()?.FullName; + + if (activeDocPath != null) + return solution.Projects + .SelectMany(x => x.Documents) + .FirstOrDefault(x => x.SupportsSyntaxTree && + x.SupportsSemanticModel && + x.FilePath == activeDocPath); + return null; + } + + private static VisualStudioWorkspace workspace = null; + + static public VisualStudioWorkspace Workspace + { + get + { + if (workspace == null) + { + IComponentModel componentModel = GetService() as IComponentModel; + workspace = componentModel.GetService(); + } + return workspace; + } + } +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs new file mode 100644 index 000000000..6a486268f --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs @@ -0,0 +1,30 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; +using System; +using Microsoft.CodeAnalysis.Formatting; + +namespace CodeMaidShared.Logic.Cleaning; + +internal class RoslynCleanup : CSharpSyntaxRewriter +{ + internal Func MemberWriter { get; set; } + + public RoslynCleanup() + { + MemberWriter = (_, x) => x; + } + + public override SyntaxNode Visit(SyntaxNode node) + { + var newNode = base.Visit(node); + newNode = MemberWriter(node, newNode); + + return newNode; + } + + public SyntaxNode Process(SyntaxNode root, Workspace workspace) + { + var rewrite = Visit(root); + return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); + } +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs new file mode 100644 index 000000000..eac3dc762 --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs @@ -0,0 +1,109 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace SteveCadwallader.CodeMaid.Logic.Cleaning; + +internal static class RoslynExtensions +{ + public static async ValueTask GetRequiredSemanticModelAsync(this Document document, CancellationToken cancellationToken) + { + if (document.TryGetSemanticModel(out var semanticModel)) + return semanticModel; + + semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + return semanticModel ?? throw new InvalidOperationException($"Syntax tree is required to accomplish the task but is not supported by document {document.Name}"); + } + + public static SyntaxNode GetRequiredParent(this SyntaxNode node) + => node.Parent ?? throw new InvalidOperationException("Node's parent was null"); + + public static SyntaxToken GetNameToken(this MemberDeclarationSyntax member) + { + if (member != null) + { + switch (member.Kind()) + { + case SyntaxKind.EnumDeclaration: + return ((EnumDeclarationSyntax)member).Identifier; + + case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: + return ((TypeDeclarationSyntax)member).Identifier; + + case SyntaxKind.DelegateDeclaration: + return ((DelegateDeclarationSyntax)member).Identifier; + + case SyntaxKind.FieldDeclaration: + return ((FieldDeclarationSyntax)member).Declaration.Variables.First().Identifier; + + case SyntaxKind.EventFieldDeclaration: + return ((EventFieldDeclarationSyntax)member).Declaration.Variables.First().Identifier; + + case SyntaxKind.PropertyDeclaration: + return ((PropertyDeclarationSyntax)member).Identifier; + + case SyntaxKind.EventDeclaration: + return ((EventDeclarationSyntax)member).Identifier; + + case SyntaxKind.MethodDeclaration: + return ((MethodDeclarationSyntax)member).Identifier; + + case SyntaxKind.ConstructorDeclaration: + return ((ConstructorDeclarationSyntax)member).Identifier; + + case SyntaxKind.DestructorDeclaration: + return ((DestructorDeclarationSyntax)member).Identifier; + + case SyntaxKind.IndexerDeclaration: + return ((IndexerDeclarationSyntax)member).ThisKeyword; + + case SyntaxKind.OperatorDeclaration: + return ((OperatorDeclarationSyntax)member).OperatorToken; + } + } + + // Conversion operators don't have names. + return default; + } +} + +internal static partial class AddAccessibilityModifiersHelpers +{ + internal static Accessibility GetPreferredAccessibility(ISymbol symbol) + { + // If we have an overridden member, then if we're adding an accessibility modifier, use the + // accessibility of the member we're overriding as both should be consistent here. + if (symbol.GetOverriddenMember() is { DeclaredAccessibility: var accessibility }) + return accessibility; + + // Default abstract members to be protected, and virtual members to be public. They can't be private as + // that's not legal. And these are reasonable default values for them. + if (symbol is IMethodSymbol or IPropertySymbol or IEventSymbol) + { + if (symbol.IsAbstract) + return Accessibility.Protected; + + if (symbol.IsVirtual) + return Accessibility.Public; + } + + // Otherwise, default to whatever accessibility no-accessibility means for this member; + return symbol.DeclaredAccessibility; + } + + public static ISymbol? GetOverriddenMember(this ISymbol? symbol) + => symbol switch + { + IMethodSymbol method => method.OverriddenMethod, + IPropertySymbol property => property.OverriddenProperty, + IEventSymbol @event => @event.OverriddenEvent, + _ => null, + }; +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/SyntaxNodeExtensions.cs b/CodeMaidShared/Logic/Cleaning/SyntaxNodeExtensions.cs deleted file mode 100644 index 2a0b97782..000000000 --- a/CodeMaidShared/Logic/Cleaning/SyntaxNodeExtensions.cs +++ /dev/null @@ -1,837 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace CodeMaidShared.Logic.Cleaning -{ - internal static class SyntaxNodeExtensions - { - public static SyntaxNode GetRequiredParent(this SyntaxNode node) - => node.Parent ?? throw new InvalidOperationException("Node's parent was null"); - - //public static IEnumerable GetAncestors(this SyntaxNode node) - //{ - // var current = node.Parent; - - // while (current != null) - // { - // yield return current; - - // current = current.GetParent(ascendOutOfTrivia: true); - // } - //} - - //public static IEnumerable GetAncestors(this SyntaxNode node) - // where TNode : SyntaxNode - //{ - // var current = node.Parent; - // while (current != null) - // { - // if (current is TNode tNode) - // { - // yield return tNode; - // } - - // current = current.GetParent(ascendOutOfTrivia: true); - // } - //} - - //public static TNode? GetAncestor(this SyntaxNode node) - // where TNode : SyntaxNode - //{ - // var current = node.Parent; - // while (current != null) - // { - // if (current is TNode tNode) - // { - // return tNode; - // } - - // current = current.GetParent(ascendOutOfTrivia: true); - // } - - // return null; - //} - - //public static TNode? GetAncestorOrThis(this SyntaxNode? node) - // where TNode : SyntaxNode - //{ - // return node?.GetAncestorsOrThis().FirstOrDefault(); - //} - - //public static IEnumerable GetAncestorsOrThis(this SyntaxNode? node) - // where TNode : SyntaxNode - //{ - // var current = node; - // while (current != null) - // { - // if (current is TNode tNode) - // { - // yield return tNode; - // } - - // current = current.GetParent(ascendOutOfTrivia: true); - // } - //} - - //public static bool HasAncestor(this SyntaxNode node) - // where TNode : SyntaxNode - //{ - // return node.GetAncestors().Any(); - //} - - //public static bool CheckParent(this SyntaxNode? node, Func valueChecker) where T : SyntaxNode - //{ - // if (node?.Parent is not T parentNode) - // { - // return false; - // } - - // return valueChecker(parentNode); - //} - - ///// - ///// Returns true if is a given token is a child token of a certain type of parent node. - ///// - ///// The type of the parent node. - ///// The node that we are testing. - ///// A function that, when given the parent node, returns the child token we are interested in. - //public static bool IsChildNode(this SyntaxNode node, Func childGetter) - // where TParent : SyntaxNode - //{ - // var ancestor = node.GetAncestor(); - // if (ancestor == null) - // { - // return false; - // } - - // var ancestorNode = childGetter(ancestor); - - // return node == ancestorNode; - //} - - ///// - ///// Returns true if this node is found underneath the specified child in the given parent. - ///// - //public static bool IsFoundUnder(this SyntaxNode node, Func childGetter) - // where TParent : SyntaxNode - //{ - // var ancestor = node.GetAncestor(); - // if (ancestor == null) - // { - // return false; - // } - - // var child = childGetter(ancestor); - - // // See if node passes through child on the way up to ancestor. - // return node.GetAncestorsOrThis().Contains(child); - //} - - //public static SyntaxNode GetCommonRoot(this SyntaxNode node1, SyntaxNode node2) - //{ - // Contract.ThrowIfTrue(node1.RawKind == 0 || node2.RawKind == 0); - - // // find common starting node from two nodes. - // // as long as two nodes belong to same tree, there must be at least one common root (Ex, compilation unit) - // var ancestors = node1.GetAncestorsOrThis(); - // var set = new HashSet(node2.GetAncestorsOrThis()); - - // return ancestors.First(set.Contains); - //} - - //public static int Width(this SyntaxNode node) - // => node.Span.Length; - - //public static int FullWidth(this SyntaxNode node) - // => node.FullSpan.Length; - - //public static SyntaxNode? FindInnermostCommonNode(this IEnumerable nodes, Func predicate) - // => nodes.FindInnermostCommonNode()?.FirstAncestorOrSelf(predicate); - - //public static SyntaxNode? FindInnermostCommonNode(this IEnumerable nodes) - //{ - // // Two collections we use to make this operation as efficient as possible. One is a - // // stack of the current shared ancestor chain shared by all nodes so far. It starts - // // with the full ancestor chain of the first node, and can only get smaller over time. - // // It should be log(n) with the size of the tree as it's only storing a parent chain. - // // - // // The second is a set with the exact same contents as the array. It's used for O(1) - // // lookups if a node is in the ancestor chain or not. - - // using var _1 = ArrayBuilder.GetInstance(out var commonAncestorsStack); - // using var _2 = PooledHashSet.GetInstance(out var commonAncestorsSet); - - // var first = true; - // foreach (var node in nodes) - // { - // // If we're just starting, initialize the ancestors set/array with the ancestors of - // // this node. - // if (first) - // { - // first = false; - // foreach (var ancestor in node.ValueAncestorsAndSelf()) - // { - // commonAncestorsSet.Add(ancestor); - // commonAncestorsStack.Add(ancestor); - // } - - // // Reverse the ancestors stack so that we go downwards with CompilationUnit at - // // the start, and then go down to this starting node. This enables cheap - // // popping later on. - // commonAncestorsStack.ReverseContents(); - // continue; - // } - - // // On a subsequent node, walk its ancestors to find the first match - // var commonAncestor = FindCommonAncestor(node, commonAncestorsSet); - // if (commonAncestor == null) - // { - // // So this shouldn't happen as long as the nodes are from the same tree. And - // // the caller really shouldn't be calling from different trees. However, the - // // previous impl supported that, so continue to have this behavior. - // // - // // If this doesn't fire, that means that all callers seem sane. If it does - // // fire, we can relax this (but we should consider fixing the caller). - // Debug.Fail("Could not find common ancestor."); - // return null; - // } - - // // Now remove everything in the ancestors array up to that common ancestor. This is - // // generally quite efficient. Either we settle on a common node quickly. and don't - // // need to do work here, or we keep tossing data from our common-ancestor scratch - // // pad, making further work faster. - // while (commonAncestorsStack.Count > 0 && - // commonAncestorsStack.Peek() != commonAncestor) - // { - // commonAncestorsSet.Remove(commonAncestorsStack.Peek()); - // commonAncestorsStack.Pop(); - // } - - // if (commonAncestorsStack.Count == 0) - // { - // // So this shouldn't happen as long as the nodes are from the same tree. And - // // the caller really shouldn't be calling from different trees. However, the - // // previous impl supported that, so continue to have this behavior. - // // - // // If this doesn't fire, that means that all callers seem sane. If it does - // // fire, we can relax this (but we should consider fixing the caller). - // Debug.Fail("Could not find common ancestor."); - // return null; - // } - // } - - // // The common ancestor is the one at the end of the ancestor stack. This could be empty - // // in the case where the caller passed in an empty enumerable of nodes. - // return commonAncestorsStack.Count == 0 ? null : commonAncestorsStack.Peek(); - - // // local functions - // static SyntaxNode? FindCommonAncestor(SyntaxNode node, HashSet commonAncestorsSet) - // { - // foreach (var ancestor in node.ValueAncestorsAndSelf()) - // { - // if (commonAncestorsSet.Contains(ancestor)) - // return ancestor; - // } - - // return null; - // } - //} - - //public static TSyntaxNode? FindInnermostCommonNode(this IEnumerable nodes) where TSyntaxNode : SyntaxNode - // => (TSyntaxNode?)nodes.FindInnermostCommonNode(t => t is TSyntaxNode); - - ///// - ///// create a new root node from the given root after adding annotations to the tokens - ///// - ///// tokens should belong to the given root - ///// - //public static SyntaxNode AddAnnotations(this SyntaxNode root, IEnumerable> pairs) - //{ - // Contract.ThrowIfNull(root); - // Contract.ThrowIfNull(pairs); - - // var tokenMap = pairs.GroupBy(p => p.Item1, p => p.Item2).ToDictionary(g => g.Key, g => g.ToArray()); - // return root.ReplaceTokens(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations(tokenMap[o])); - //} - - ///// - ///// create a new root node from the given root after adding annotations to the nodes - ///// - ///// nodes should belong to the given root - ///// - //public static SyntaxNode AddAnnotations(this SyntaxNode root, IEnumerable> pairs) - //{ - // Contract.ThrowIfNull(root); - // Contract.ThrowIfNull(pairs); - - // var tokenMap = pairs.GroupBy(p => p.Item1, p => p.Item2).ToDictionary(g => g.Key, g => g.ToArray()); - // return root.ReplaceNodes(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations(tokenMap[o])); - //} - - //public static TextSpan GetContainedSpan(this IEnumerable nodes) - //{ - // Contract.ThrowIfNull(nodes); - // Contract.ThrowIfFalse(nodes.Any()); - - // var fullSpan = nodes.First().Span; - // foreach (var node in nodes) - // { - // fullSpan = TextSpan.FromBounds( - // Math.Min(fullSpan.Start, node.SpanStart), - // Math.Max(fullSpan.End, node.Span.End)); - // } - - // return fullSpan; - //} - - //public static bool OverlapsHiddenPosition(this SyntaxNode node, CancellationToken cancellationToken) - // => node.OverlapsHiddenPosition(node.Span, cancellationToken); - - //public static bool OverlapsHiddenPosition(this SyntaxNode node, TextSpan span, CancellationToken cancellationToken) - // => node.SyntaxTree.OverlapsHiddenPosition(span, cancellationToken); - - //public static bool OverlapsHiddenPosition(this SyntaxNode declaration, SyntaxNode startNode, SyntaxNode endNode, CancellationToken cancellationToken) - //{ - // var start = startNode.Span.End; - // var end = endNode.SpanStart; - - // var textSpan = TextSpan.FromBounds(start, end); - // return declaration.OverlapsHiddenPosition(textSpan, cancellationToken); - //} - - //public static IEnumerable GetAnnotatedNodes(this SyntaxNode node, SyntaxAnnotation syntaxAnnotation) where T : SyntaxNode - // => node.GetAnnotatedNodesAndTokens(syntaxAnnotation).Select(n => n.AsNode()).OfType(); - - ///// - ///// Creates a new tree of nodes from the existing tree with the specified old nodes replaced with a newly computed nodes. - ///// - ///// The root of the tree that contains all the specified nodes. - ///// The nodes from the tree to be replaced. - ///// A function that computes a replacement node for - ///// the argument nodes. The first argument is one of the original specified nodes. The second argument is - ///// the same node possibly rewritten with replaced descendants. - ///// - //public static Task ReplaceNodesAsync( - // this TRootNode root, - // IEnumerable nodes, - // Func> computeReplacementAsync, - // CancellationToken cancellationToken) where TRootNode : SyntaxNode - //{ - // return root.ReplaceSyntaxAsync( - // nodes: nodes, computeReplacementNodeAsync: computeReplacementAsync, - // tokens: null, computeReplacementTokenAsync: null, - // trivia: null, computeReplacementTriviaAsync: null, - // cancellationToken: cancellationToken); - //} - - ///// - ///// Creates a new tree of tokens from the existing tree with the specified old tokens replaced with a newly computed tokens. - ///// - ///// The root of the tree that contains all the specified tokens. - ///// The tokens from the tree to be replaced. - ///// A function that computes a replacement token for - ///// the argument tokens. The first argument is one of the originally specified tokens. The second argument is - ///// the same token possibly rewritten with replaced trivia. - ///// - //public static Task ReplaceTokensAsync( - // this TRootNode root, - // IEnumerable tokens, - // Func> computeReplacementAsync, - // CancellationToken cancellationToken) where TRootNode : SyntaxNode - //{ - // return root.ReplaceSyntaxAsync( - // nodes: null, computeReplacementNodeAsync: null, - // tokens: tokens, computeReplacementTokenAsync: computeReplacementAsync, - // trivia: null, computeReplacementTriviaAsync: null, - // cancellationToken: cancellationToken); - //} - - //public static Task ReplaceTriviaAsync( - // this TRoot root, - // IEnumerable trivia, - // Func> computeReplacementAsync, - // CancellationToken cancellationToken) where TRoot : SyntaxNode - //{ - // return root.ReplaceSyntaxAsync( - // nodes: null, computeReplacementNodeAsync: null, - // tokens: null, computeReplacementTokenAsync: null, - // trivia: trivia, computeReplacementTriviaAsync: computeReplacementAsync, - // cancellationToken: cancellationToken); - //} - - //public static async Task ReplaceSyntaxAsync( - // this TRoot root, - // IEnumerable? nodes, - // Func>? computeReplacementNodeAsync, - // IEnumerable? tokens, - // Func>? computeReplacementTokenAsync, - // IEnumerable? trivia, - // Func>? computeReplacementTriviaAsync, - // CancellationToken cancellationToken) - // where TRoot : SyntaxNode - //{ - // // index all nodes, tokens and trivia by the full spans they cover - // var nodesToReplace = nodes != null ? nodes.ToDictionary(n => n.FullSpan) : new Dictionary(); - // var tokensToReplace = tokens != null ? tokens.ToDictionary(t => t.FullSpan) : new Dictionary(); - // var triviaToReplace = trivia != null ? trivia.ToDictionary(t => t.FullSpan) : new Dictionary(); - - // var nodeReplacements = new Dictionary(); - // var tokenReplacements = new Dictionary(); - // var triviaReplacements = new Dictionary(); - - // var retryAnnotations = new AnnotationTable("RetryReplace"); - - // var spans = new List(nodesToReplace.Count + tokensToReplace.Count + triviaToReplace.Count); - // spans.AddRange(nodesToReplace.Keys); - // spans.AddRange(tokensToReplace.Keys); - // spans.AddRange(triviaToReplace.Keys); - - // while (spans.Count > 0) - // { - // // sort the spans of the items to be replaced so we can tell if any overlap - // spans.Sort((x, y) => - // { - // // order by end offset, and then by length - // var d = x.End - y.End; - - // if (d == 0) - // { - // d = x.Length - y.Length; - // } - - // return d; - // }); - - // // compute replacements for all nodes that will go in the same batch - // // only spans that do not overlap go in the same batch. - // TextSpan previous = default; - // foreach (var span in spans) - // { - // // only add to replacement map if we don't intersect with the previous node. This taken with the sort order - // // should ensure that parent nodes are not processed in the same batch as child nodes. - // if (previous == default || !previous.IntersectsWith(span)) - // { - // if (nodesToReplace.TryGetValue(span, out var currentNode)) - // { - // var original = (SyntaxNode?)retryAnnotations.GetAnnotations(currentNode).SingleOrDefault() ?? currentNode; - // var newNode = await computeReplacementNodeAsync!(original, currentNode, cancellationToken).ConfigureAwait(false); - // nodeReplacements[currentNode] = newNode; - // } - // else if (tokensToReplace.TryGetValue(span, out var currentToken)) - // { - // var original = (SyntaxToken?)retryAnnotations.GetAnnotations(currentToken).SingleOrDefault() ?? currentToken; - // var newToken = await computeReplacementTokenAsync!(original, currentToken, cancellationToken).ConfigureAwait(false); - // tokenReplacements[currentToken] = newToken; - // } - // else if (triviaToReplace.TryGetValue(span, out var currentTrivia)) - // { - // var original = (SyntaxTrivia?)retryAnnotations.GetAnnotations(currentTrivia).SingleOrDefault() ?? currentTrivia; - // var newTrivia = await computeReplacementTriviaAsync!(original, currentTrivia, cancellationToken).ConfigureAwait(false); - // triviaReplacements[currentTrivia] = newTrivia; - // } - // } - - // previous = span; - // } - - // var retryNodes = false; - // var retryTokens = false; - // var retryTrivia = false; - - // // replace nodes in batch - // // submit all nodes so we can annotate the ones we don't replace - // root = root.ReplaceSyntax( - // nodes: nodesToReplace.Values, - // computeReplacementNode: (original, rewritten) => - // { - // if (rewritten != original || !nodeReplacements.TryGetValue(original, out var replaced)) - // { - // // the subtree did change, or we didn't have a replacement for it in this batch - // // so we need to add an annotation so we can find this node again for the next batch. - // replaced = retryAnnotations.WithAdditionalAnnotations(rewritten, original); - // retryNodes = true; - // } - - // return replaced; - // }, - // tokens: tokensToReplace.Values, - // computeReplacementToken: (original, rewritten) => - // { - // if (rewritten != original || !tokenReplacements.TryGetValue(original, out var replaced)) - // { - // // the subtree did change, or we didn't have a replacement for it in this batch - // // so we need to add an annotation so we can find this node again for the next batch. - // replaced = retryAnnotations.WithAdditionalAnnotations(rewritten, original); - // retryTokens = true; - // } - - // return replaced; - // }, - // trivia: triviaToReplace.Values, - // computeReplacementTrivia: (original, rewritten) => - // { - // if (!triviaReplacements.TryGetValue(original, out var replaced)) - // { - // // the subtree did change, or we didn't have a replacement for it in this batch - // // so we need to add an annotation so we can find this node again for the next batch. - // replaced = retryAnnotations.WithAdditionalAnnotations(rewritten, original); - // retryTrivia = true; - // } - - // return replaced; - // }); - - // nodesToReplace.Clear(); - // tokensToReplace.Clear(); - // triviaToReplace.Clear(); - // spans.Clear(); - - // // prepare next batch out of all remaining annotated nodes - // if (retryNodes) - // { - // nodesToReplace = retryAnnotations.GetAnnotatedNodes(root).ToDictionary(n => n.FullSpan); - // spans.AddRange(nodesToReplace.Keys); - // } - - // if (retryTokens) - // { - // tokensToReplace = retryAnnotations.GetAnnotatedTokens(root).ToDictionary(t => t.FullSpan); - // spans.AddRange(tokensToReplace.Keys); - // } - - // if (retryTrivia) - // { - // triviaToReplace = retryAnnotations.GetAnnotatedTrivia(root).ToDictionary(t => t.FullSpan); - // spans.AddRange(triviaToReplace.Keys); - // } - // } - - // return root; - //} - - ///// - ///// Look inside a trivia list for a skipped token that contains the given position. - ///// - //private static readonly Func s_findSkippedTokenForward = FindSkippedTokenForward; - - ///// - ///// Look inside a trivia list for a skipped token that contains the given position. - ///// - //private static SyntaxToken FindSkippedTokenForward(SyntaxTriviaList triviaList, int position) - //{ - // foreach (var trivia in triviaList) - // { - // if (trivia.HasStructure) - // { - // if (trivia.GetStructure() is ISkippedTokensTriviaSyntax skippedTokensTrivia) - // { - // foreach (var token in skippedTokensTrivia.Tokens) - // { - // if (token.Span.Length > 0 && position <= token.Span.End) - // { - // return token; - // } - // } - // } - // } - // } - - // return default; - //} - - ///// - ///// Look inside a trivia list for a skipped token that contains the given position. - ///// - //private static readonly Func s_findSkippedTokenBackward = FindSkippedTokenBackward; - - ///// - ///// Look inside a trivia list for a skipped token that contains the given position. - ///// - //private static SyntaxToken FindSkippedTokenBackward(SyntaxTriviaList triviaList, int position) - //{ - // foreach (var trivia in triviaList.Reverse()) - // { - // if (trivia.HasStructure) - // { - // if (trivia.GetStructure() is ISkippedTokensTriviaSyntax skippedTokensTrivia) - // { - // foreach (var token in skippedTokensTrivia.Tokens) - // { - // if (token.Span.Length > 0 && token.SpanStart <= position) - // { - // return token; - // } - // } - // } - // } - // } - - // return default; - //} - - //private static SyntaxToken GetInitialToken( - // SyntaxNode root, - // int position, - // bool includeSkipped = false, - // bool includeDirectives = false, - // bool includeDocumentationComments = false) - //{ - // return (position < root.FullSpan.End || !(root is ICompilationUnitSyntax)) - // ? root.FindToken(position, includeSkipped || includeDirectives || includeDocumentationComments) - // : root.GetLastToken(includeZeroWidth: true, includeSkipped: true, includeDirectives: true, includeDocumentationComments: true) - // .GetPreviousToken(includeZeroWidth: false, includeSkipped: includeSkipped, includeDirectives: includeDirectives, includeDocumentationComments: includeDocumentationComments); - //} - - ///// - ///// If the position is inside of token, return that token; otherwise, return the token to the right. - ///// - //public static SyntaxToken FindTokenOnRightOfPosition( - // this SyntaxNode root, - // int position, - // bool includeSkipped = false, - // bool includeDirectives = false, - // bool includeDocumentationComments = false) - //{ - // var findSkippedToken = includeSkipped ? s_findSkippedTokenForward : ((l, p) => default); - - // var token = GetInitialToken(root, position, includeSkipped, includeDirectives, includeDocumentationComments); - - // if (position < token.SpanStart) - // { - // var skippedToken = findSkippedToken(token.LeadingTrivia, position); - // token = skippedToken.RawKind != 0 ? skippedToken : token; - // } - // else if (token.Span.End <= position) - // { - // do - // { - // var skippedToken = findSkippedToken(token.TrailingTrivia, position); - // token = skippedToken.RawKind != 0 - // ? skippedToken - // : token.GetNextToken(includeZeroWidth: false, includeSkipped: includeSkipped, includeDirectives: includeDirectives, includeDocumentationComments: includeDocumentationComments); - // } - // while (token.RawKind != 0 && token.Span.End <= position && token.Span.End <= root.FullSpan.End); - // } - - // if (token.Span.Length == 0) - // { - // token = token.GetNextToken(); - // } - - // return token; - //} - - ///// - ///// If the position is inside of token, return that token; otherwise, return the token to the left. - ///// - //public static SyntaxToken FindTokenOnLeftOfPosition( - // this SyntaxNode root, - // int position, - // bool includeSkipped = false, - // bool includeDirectives = false, - // bool includeDocumentationComments = false) - //{ - // var findSkippedToken = includeSkipped ? s_findSkippedTokenBackward : ((l, p) => default); - - // var token = GetInitialToken(root, position, includeSkipped, includeDirectives, includeDocumentationComments); - - // if (position <= token.SpanStart) - // { - // do - // { - // var skippedToken = findSkippedToken(token.LeadingTrivia, position); - // token = skippedToken.RawKind != 0 - // ? skippedToken - // : token.GetPreviousToken(includeZeroWidth: false, includeSkipped: includeSkipped, includeDirectives: includeDirectives, includeDocumentationComments: includeDocumentationComments); - // } - // while (position <= token.SpanStart && root.FullSpan.Start < token.SpanStart); - // } - // else if (token.Span.End < position) - // { - // var skippedToken = findSkippedToken(token.TrailingTrivia, position); - // token = skippedToken.RawKind != 0 ? skippedToken : token; - // } - - // if (token.Span.Length == 0) - // { - // token = token.GetPreviousToken(); - // } - - // return token; - //} - - //public static T WithPrependedLeadingTrivia( - // this T node, - // params SyntaxTrivia[] trivia) where T : SyntaxNode - //{ - // if (trivia.Length == 0) - // { - // return node; - // } - - // return node.WithPrependedLeadingTrivia((IEnumerable)trivia); - //} - - //public static T WithPrependedLeadingTrivia( - // this T node, - // SyntaxTriviaList trivia) where T : SyntaxNode - //{ - // if (trivia.Count == 0) - // { - // return node; - // } - - // return node.WithLeadingTrivia(trivia.Concat(node.GetLeadingTrivia())); - //} - - //public static T WithPrependedLeadingTrivia( - // this T node, - // IEnumerable trivia) where T : SyntaxNode - //{ - // var list = new SyntaxTriviaList(); - // list = list.AddRange(trivia); - - // return node.WithPrependedLeadingTrivia(list); - //} - - //public static T WithAppendedTrailingTrivia( - // this T node, - // params SyntaxTrivia[] trivia) where T : SyntaxNode - //{ - // if (trivia.Length == 0) - // { - // return node; - // } - - // return node.WithAppendedTrailingTrivia((IEnumerable)trivia); - //} - - //public static T WithAppendedTrailingTrivia( - // this T node, - // SyntaxTriviaList trivia) where T : SyntaxNode - //{ - // if (trivia.Count == 0) - // { - // return node; - // } - - // return node.WithTrailingTrivia(node.GetTrailingTrivia().Concat(trivia)); - //} - - //public static T WithAppendedTrailingTrivia( - // this T node, - // IEnumerable trivia) where T : SyntaxNode - //{ - // var list = new SyntaxTriviaList(); - // list = list.AddRange(trivia); - - // return node.WithAppendedTrailingTrivia(list); - //} - - //public static T With( - // this T node, - // IEnumerable leadingTrivia, - // IEnumerable trailingTrivia) where T : SyntaxNode - //{ - // return node.WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTrivia); - //} - - ///// - ///// Creates a new token with the leading trivia removed. - ///// - //public static SyntaxToken WithoutLeadingTrivia(this SyntaxToken token) - //{ - // return token.WithLeadingTrivia(default(SyntaxTriviaList)); - //} - - ///// - ///// Creates a new token with the trailing trivia removed. - ///// - //public static SyntaxToken WithoutTrailingTrivia(this SyntaxToken token) - //{ - // return token.WithTrailingTrivia(default(SyntaxTriviaList)); - //} - - //// Copy of the same function in SyntaxNode.cs - //public static SyntaxNode? GetParent(this SyntaxNode node, bool ascendOutOfTrivia) - //{ - // var parent = node.Parent; - // if (parent == null && ascendOutOfTrivia) - // { - // if (node is IStructuredTriviaSyntax structuredTrivia) - // { - // parent = structuredTrivia.ParentTrivia.Token.Parent; - // } - // } - - // return parent; - //} - - //public static TNode? FirstAncestorOrSelfUntil(this SyntaxNode? node, Func predicate) - // where TNode : SyntaxNode - //{ - // for (var current = node; current != null; current = current.GetParent(ascendOutOfTrivia: true)) - // { - // if (current is TNode tnode) - // { - // return tnode; - // } - - // if (predicate(current)) - // { - // break; - // } - // } - - // return null; - //} - - ///// - ///// Gets a list of ancestor nodes (including this node) - ///// - //public static ValueAncestorsAndSelfEnumerable ValueAncestorsAndSelf(this SyntaxNode syntaxNode, bool ascendOutOfTrivia = true) - // => new(syntaxNode, ascendOutOfTrivia); - - //public readonly struct ValueAncestorsAndSelfEnumerable - //{ - // private readonly SyntaxNode _syntaxNode; - // private readonly bool _ascendOutOfTrivia; - - // public ValueAncestorsAndSelfEnumerable(SyntaxNode syntaxNode, bool ascendOutOfTrivia) - // { - // _syntaxNode = syntaxNode; - // _ascendOutOfTrivia = ascendOutOfTrivia; - // } - - // public Enumerator GetEnumerator() - // => new(_syntaxNode, _ascendOutOfTrivia); - - // public struct Enumerator - // { - // private readonly SyntaxNode _start; - // private readonly bool _ascendOutOfTrivia; - - // public Enumerator(SyntaxNode syntaxNode, bool ascendOutOfTrivia) - // { - // _start = syntaxNode; - // _ascendOutOfTrivia = ascendOutOfTrivia; - // Current = null!; - // } - - // public SyntaxNode Current { get; private set; } - - // public bool MoveNext() - // { - // Current = Current == null ? _start : GetParent(Current, _ascendOutOfTrivia)!; - // return Current != null; - // } - // } - //} - } -} \ No newline at end of file From 84a8da3e78ca63ef57c00b9d15151067458907be Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Fri, 24 Feb 2023 21:53:11 +0000 Subject: [PATCH 10/17] Add logic to RunCodeCleanupCSharp --- CodeMaidShared/CodeMaidShared.projitems | 1 + .../Logic/Cleaning/CodeCleanupManager.cs | 5 +- .../Roslyn/AddExplicitAccessModifierLogic.cs | 55 ++++--------------- .../Logic/Cleaning/Roslyn/RoslynCleanup.cs | 33 ++++++++++- 4 files changed, 46 insertions(+), 48 deletions(-) diff --git a/CodeMaidShared/CodeMaidShared.projitems b/CodeMaidShared/CodeMaidShared.projitems index dd3142a48..ce867e741 100644 --- a/CodeMaidShared/CodeMaidShared.projitems +++ b/CodeMaidShared/CodeMaidShared.projitems @@ -95,6 +95,7 @@ + diff --git a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs index b41c51e2f..ce8dedca2 100644 --- a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs +++ b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs @@ -324,11 +324,12 @@ private void RunCodeCleanupCSharp(Document document) _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnFields(fields); _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnInterfaces(interfaces); //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnClasses(classes); + //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnStructs(structs); //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnMethods(methods); //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnProperties(properties); - //AddExplicitAccessModifierLogic.Process(_package); - _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnStructs(structs); + RoslynCleanup.RunExplicit(_package); + // Perform insertion of whitespace cleanup. _insertWhitespaceLogic.InsertEOFTrailingNewLine(textDocument); diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/AddExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/AddExplicitAccessModifierLogic.cs index 4ef89f04c..253f42b8f 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/AddExplicitAccessModifierLogic.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/AddExplicitAccessModifierLogic.cs @@ -2,8 +2,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.VisualStudio.Shell; using SteveCadwallader.CodeMaid.Logic.Cleaning; using SteveCadwallader.CodeMaid.Properties; using System; @@ -29,31 +27,14 @@ internal class AddExplicitAccessModifierLogic /// //private static AddExplicitAccessModifierLogic _instance; - /// - /// Gets an instance of the class. - /// - /// An instance of the class. - internal static AddExplicitAccessModifierLogic GetInstance(AsyncPackage package) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - Global.Package = package; - - var document = Global.GetActiveDocument(); - - if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) - { - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); - var semanticModel = document.GetSemanticModelAsync().Result; - - return new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - - document = document.WithSyntaxRoot(root); - Global.Workspace.TryApplyChanges(document.Project.Solution); - } - - throw new InvalidOperationException(); - } + ///// + ///// Gets an instance of the class. + ///// + ///// An instance of the class. + //internal static AddExplicitAccessModifierLogic GetInstance(AsyncPackage package) + //{ + // return new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + //} /// /// Initializes a new instance of the class. @@ -66,23 +47,11 @@ public AddExplicitAccessModifierLogic(SemanticModel semanticModel, SyntaxGenerat #endregion Constructors - public static void Process(AsyncPackage package) + public static RoslynCleanup Initialize(RoslynCleanup cleanup, SemanticModel model, SyntaxGenerator generator) { - var mod = GetInstance(package); - - var document = Global.GetActiveDocument(); - - if (document != null && document.TryGetSyntaxRoot(out SyntaxNode root)) - { - var rewriter = new RoslynCleanup() { }; - var result = rewriter.Visit(root); - - root = Formatter.Format(result, SyntaxAnnotation.ElasticAnnotation, Global.Workspace); - - document = document.WithSyntaxRoot(root); - Global.Workspace.TryApplyChanges(document.Project.Solution); - } - throw new InvalidOperationException(); + var explicitLogic = new AddExplicitAccessModifierLogic(model, generator); + cleanup.MemberWriter = explicitLogic.ProcessMember; + return cleanup; } public SyntaxNode ProcessMember(SyntaxNode original, SyntaxNode node) diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs index 6a486268f..386242019 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs @@ -1,13 +1,16 @@ -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis; -using System; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.VisualStudio.Shell; +using SteveCadwallader.CodeMaid.Logic.Cleaning; +using System; namespace CodeMaidShared.Logic.Cleaning; internal class RoslynCleanup : CSharpSyntaxRewriter { - internal Func MemberWriter { get; set; } + public Func MemberWriter { get; set; } public RoslynCleanup() { @@ -27,4 +30,28 @@ public SyntaxNode Process(SyntaxNode root, Workspace workspace) var rewrite = Visit(root); return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); } + + public static void RunExplicit(AsyncPackage package) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + Global.Package = package; + + var document = Global.GetActiveDocument(); + + if (document == null || !document.TryGetSyntaxRoot(out SyntaxNode root)) + { + throw new InvalidOperationException(); + } + + var semanticModel = document.GetSemanticModelAsync().Result; + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + + var cleaner = new RoslynCleanup(); + AddExplicitAccessModifierLogic.Initialize(cleaner, semanticModel, syntaxGenerator); + cleaner.Process(root, Global.Workspace); + + document = document.WithSyntaxRoot(root); + Global.Workspace.TryApplyChanges(document.Project.Solution); + } } \ No newline at end of file From 683f3a41d689448d00f4534b05f63c1a71bb2902 Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Sat, 25 Feb 2023 14:54:11 +0000 Subject: [PATCH 11/17] Replace all InsertExplicitAccessModifiers functions with roslyn versions. --- CodeMaid.UnitTests/Cleanup/RoslynTests.cs | 268 +++++++++++++++++- CodeMaid.UnitTests/Cleanup/TestWorkspace.cs | 2 +- CodeMaidShared/CodeMaidShared.projitems | 2 +- .../Logic/Cleaning/CodeCleanupManager.cs | 10 +- .../Roslyn/AddExplicitAccessModifierLogic.cs | 87 ------ .../Roslyn/CSharpAccessibilityFacts.cs | 21 ++ .../Logic/Cleaning/Roslyn/RoslynCleanup.cs | 23 +- .../Logic/Cleaning/Roslyn/RoslynExtensions.cs | 2 +- ...RoslynInsertExplicitAccessModifierLogic.cs | 110 +++++++ 9 files changed, 421 insertions(+), 104 deletions(-) delete mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/AddExplicitAccessModifierLogic.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs diff --git a/CodeMaid.UnitTests/Cleanup/RoslynTests.cs b/CodeMaid.UnitTests/Cleanup/RoslynTests.cs index a53b0f6e2..c46b245bc 100644 --- a/CodeMaid.UnitTests/Cleanup/RoslynTests.cs +++ b/CodeMaid.UnitTests/Cleanup/RoslynTests.cs @@ -305,13 +305,267 @@ private protected override void AbstractMethod() """; await testWorkspace.VerifyCleanupAsync(source, expected); } + + [TestMethod] + public async Task TestShouldNotRemoveFileAsync() + { + var source = +""" +file class MyFile +{ + int Prop { get; set; } +} + +file struct MyFileStruct +{ + int Prop { get; set; } +} +"""; + + var expected = +""" +file class MyFile +{ + private int Prop { get; set; } } -//public interface MyInterface -//{ -// void Do(); +file struct MyFileStruct +{ + private int Prop { get; set; } +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddDelegateAccessorAsync() + { + var source = +""" +class MyDelegate +{ + delegate int PerformCalculation(int x, int y); +} +"""; + + var expected = +""" +internal class MyDelegate +{ + private delegate int PerformCalculation(int x, int y); +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddEventAccessorAsync() + { + var source = +""" +class MyEvent +{ + event MyEventHandler MyEvent; +} +"""; + + var expected = +""" +internal class MyEvent +{ + private event MyEventHandler MyEvent; +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddEnumAccessorAsync() + { + var source = +""" +enum MyEnum +{ + Some = 0, + None = 1, +} +"""; + + var expected = +""" +internal enum MyEnum +{ + Some = 0, + None = 1, +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddNestedEnumAccessorAsync() + { + var source = +""" +class MyClass +{ + enum MyEnum + { + Some = 0, + None = 1, + } +} +"""; + + var expected = +""" +internal class MyClass +{ + private enum MyEnum + { + Some = 0, + None = 1, + } +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddInterfaceAccessorAsync() + { + var source = +""" +interface IMyInterface +{ + int MyProp { get; set; } + + void Do(); +} +"""; + + var expected = +""" +internal interface IMyInterface +{ + int MyProp { get; set; } + + void Do(); +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddNestedInterfaceAccessorAsync() + { + var source = +""" +class MyClass +{ + interface IMyInterface + { + int MyProp { get; set; } + + void Do(); + } +} +"""; + + var expected = +""" +internal class MyClass +{ + private interface IMyInterface + { + int MyProp { get; set; } + + void Do(); + } +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldAddFieldAccessorAsync() + { + var source = +""" +class MyClass +{ + int _number; +} +"""; + + var expected = +""" +internal class MyClass +{ + private int _number; +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldNotChangeInvalidSyntaxAsync() + { + var source = +""" +class ITemp +{ + void Do() + { + int MyProperty { get; set; } + } +} +"""; + + var expected = +""" +internal class ITemp +{ + private void Do() + { + int MyProperty { get; set; } + } +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldNotChangeInterfaceDescendantsAsync() + { + var source = +""" +interface IInterface +{ + class C + { + class D + { + + } + } +} +"""; + + var expected = +""" +internal interface IInterface +{ + class C + { + class D + { + + } + } +} +"""; + await testWorkspace.VerifyCleanupAsync(source, expected); + } +} -// void Doer() -// { -// } -//} \ No newline at end of file diff --git a/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs b/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs index b0ebd0584..851db73df 100644 --- a/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs +++ b/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs @@ -17,7 +17,7 @@ public async Task VerifyCleanupAsync(string input, string expected) var syntaxGenerator = SyntaxGenerator.GetGenerator(document); var semanticModel = await Document.GetSemanticModelAsync(); - var modifierLogic = new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + var modifierLogic = new RoslynInsertExplicitAccessModifierLogic(semanticModel, syntaxGenerator); var rewriter = new RoslynCleanup() { MemberWriter = modifierLogic.ProcessMember diff --git a/CodeMaidShared/CodeMaidShared.projitems b/CodeMaidShared/CodeMaidShared.projitems index ce867e741..28595ef6c 100644 --- a/CodeMaidShared/CodeMaidShared.projitems +++ b/CodeMaidShared/CodeMaidShared.projitems @@ -90,7 +90,7 @@ - + diff --git a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs index ce8dedca2..9416f4e53 100644 --- a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs +++ b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs @@ -318,11 +318,11 @@ private void RunCodeCleanupCSharp(Document document) _insertBlankLinePaddingLogic.InsertPaddingBeforeSingleLineComments(textDocument); // Perform insertion of explicit access modifier cleanup. - _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnDelegates(delegates); - _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnEnumerations(enumerations); - _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnEvents(events); - _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnFields(fields); - _insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnInterfaces(interfaces); + //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnDelegates(delegates); + //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnEnumerations(enumerations); + //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnEvents(events); + //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnFields(fields); + //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnInterfaces(interfaces); //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnClasses(classes); //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnStructs(structs); //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnMethods(methods); diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/AddExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/AddExplicitAccessModifierLogic.cs deleted file mode 100644 index 253f42b8f..000000000 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/AddExplicitAccessModifierLogic.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Editing; -using SteveCadwallader.CodeMaid.Logic.Cleaning; -using SteveCadwallader.CodeMaid.Properties; -using System; - -namespace CodeMaidShared.Logic.Cleaning; - -/// -/// A class for encapsulating insertion of explicit access modifier logic. -/// -internal class AddExplicitAccessModifierLogic -{ - #region Fields - - private readonly SemanticModel _semanticModel; - private readonly SyntaxGenerator _syntaxGenerator; - - #endregion Fields - - #region Constructors - - /// - /// The singleton instance of the class. - /// - //private static AddExplicitAccessModifierLogic _instance; - - ///// - ///// Gets an instance of the class. - ///// - ///// An instance of the class. - //internal static AddExplicitAccessModifierLogic GetInstance(AsyncPackage package) - //{ - // return new AddExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - //} - - /// - /// Initializes a new instance of the class. - /// - public AddExplicitAccessModifierLogic(SemanticModel semanticModel, SyntaxGenerator syntaxGenerator) - { - _semanticModel = semanticModel; - _syntaxGenerator = syntaxGenerator; - } - - #endregion Constructors - - public static RoslynCleanup Initialize(RoslynCleanup cleanup, SemanticModel model, SyntaxGenerator generator) - { - var explicitLogic = new AddExplicitAccessModifierLogic(model, generator); - cleanup.MemberWriter = explicitLogic.ProcessMember; - return cleanup; - } - - public SyntaxNode ProcessMember(SyntaxNode original, SyntaxNode node) - { - return node switch - { - ClassDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses => AddAccessibility(original, node), - PropertyDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties => AddAccessibility(original, node), - MethodDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods => AddAccessibility(original, node), - StructDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnStructs => AddAccessibility(original, node), - _ => node, - }; - } - - private SyntaxNode AddAccessibility(SyntaxNode original, SyntaxNode newNode) - { - var symbol = _semanticModel.GetDeclaredSymbol(original); - - if (symbol is null) - { - throw new ArgumentNullException(nameof(symbol)); - } - - if (!CSharpAccessibilityFacts.ShouldUpdateAccessibilityModifier(original as MemberDeclarationSyntax, AccessibilityModifiersRequired.Always, out var _, out var canChange) || !canChange) - { - return newNode; - } - - var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); - - return _syntaxGenerator.WithAccessibility(newNode, preferredAccessibility); - } -} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/CSharpAccessibilityFacts.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/CSharpAccessibilityFacts.cs index 38b9dd0f9..16d46fc3a 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/CSharpAccessibilityFacts.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/CSharpAccessibilityFacts.cs @@ -27,6 +27,9 @@ public static bool ShouldUpdateAccessibilityModifier( if (!CanHaveAccessibility(member)) return false; + //if (!IsParentValid(member)) + // return false; + // This analyzer bases all of its decisions on the accessibility accessibility = GetAccessibility(member); @@ -81,6 +84,24 @@ public static bool ShouldUpdateAccessibilityModifier( return true; } + // TODO Unneeded if descendants of an interface are not passed into ShouldUpdateAccessibilityModifier + // Cant check for grandparent interfaces + public static bool IsParentValid(SyntaxNode node) + { + if(node.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.InterfaceDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration or SyntaxKind.EnumDeclaration or SyntaxKind.DelegateDeclaration) + { + return node.Parent.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration or SyntaxKind.NamespaceDeclaration or SyntaxKind.FileScopedNamespaceDeclaration; + } + + // Non object declarations must be inside an object declaration. + // Can probably simplify to: Parent is not interface or Parent is class/struct/record/namespace + if (node.Kind() is SyntaxKind.EventFieldDeclaration or SyntaxKind.FieldDeclaration or SyntaxKind.PropertyDeclaration or SyntaxKind.MethodDeclaration) + { + return node.Parent.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration; + } + return true; + } + public static bool CanHaveAccessibility(SyntaxNode declaration, bool ignoreDeclarationModifiers = false) { switch (declaration.Kind()) diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs index 386242019..be397ce94 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs @@ -12,15 +12,34 @@ internal class RoslynCleanup : CSharpSyntaxRewriter { public Func MemberWriter { get; set; } + // Use this messy functions to ensure that the current node is not a descendant of an interface. + // This is to mimic the recursive CSharpAddAccessibilityModifiersDiagnosticAnalyzer.ProcessMemberDeclaration + // search where any non structs/classes are ignored. + // FindAncestorOrSelf might help but would be slower. + // Dont terminate on finding an interface in case I want to roslynize more cleanup functions. + + private bool InsideInterface { get; set; } + public RoslynCleanup() { MemberWriter = (_, x) => x; + InsideInterface = false; } public override SyntaxNode Visit(SyntaxNode node) { + var inInterface = InsideInterface; + if (node.IsKind(SyntaxKind.InterfaceDeclaration)) + InsideInterface = true; + var newNode = base.Visit(node); - newNode = MemberWriter(node, newNode); + + if (inInterface == false) + { + newNode = MemberWriter(node, newNode); + } + + InsideInterface = inInterface; return newNode; } @@ -48,7 +67,7 @@ public static void RunExplicit(AsyncPackage package) var syntaxGenerator = SyntaxGenerator.GetGenerator(document); var cleaner = new RoslynCleanup(); - AddExplicitAccessModifierLogic.Initialize(cleaner, semanticModel, syntaxGenerator); + RoslynInsertExplicitAccessModifierLogic.Initialize(cleaner, semanticModel, syntaxGenerator); cleaner.Process(root, Global.Workspace); document = document.WithSyntaxRoot(root); diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs index eac3dc762..5eaac871a 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs @@ -74,7 +74,7 @@ public static SyntaxToken GetNameToken(this MemberDeclarationSyntax member) } } -internal static partial class AddAccessibilityModifiersHelpers +internal static class AddAccessibilityModifiersHelpers { internal static Accessibility GetPreferredAccessibility(ISymbol symbol) { diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs new file mode 100644 index 000000000..9c9b4df0e --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs @@ -0,0 +1,110 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.VisualStudio.PlatformUI; +using SteveCadwallader.CodeMaid.Logic.Cleaning; +using SteveCadwallader.CodeMaid.Properties; +using System; + +namespace CodeMaidShared.Logic.Cleaning; + +/// +/// A class for encapsulating insertion of explicit access modifier logic. +/// +internal class RoslynInsertExplicitAccessModifierLogic +{ + #region Fields + + private readonly SemanticModel _semanticModel; + private readonly SyntaxGenerator _syntaxGenerator; + + #endregion Fields + + #region Constructors + + /// + /// The singleton instance of the class. + /// + //private static RoslynInsertExplicitAccessModifierLogic _instance; + + ///// + ///// Gets an instance of the class. + ///// + ///// An instance of the class. + //internal static RoslynInsertExplicitAccessModifierLogic GetInstance(AsyncPackage package) + //{ + // return new RoslynInsertExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + //} + + /// + /// Initializes a new instance of the class. + /// + public RoslynInsertExplicitAccessModifierLogic(SemanticModel semanticModel, SyntaxGenerator syntaxGenerator) + { + _semanticModel = semanticModel; + _syntaxGenerator = syntaxGenerator; + } + + #endregion Constructors + + public static RoslynCleanup Initialize(RoslynCleanup cleanup, SemanticModel model, SyntaxGenerator generator) + { + var explicitLogic = new RoslynInsertExplicitAccessModifierLogic(model, generator); + cleanup.MemberWriter = explicitLogic.ProcessMember; + return cleanup; + } + + public SyntaxNode ProcessMember(SyntaxNode original, SyntaxNode node) + { + return node switch + { + DelegateDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnDelegates => AddAccessibility(original, node), + EventFieldDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnEvents => AddAccessibility(original, node), + EnumDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnEnumerations => AddAccessibility(original, node), + FieldDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnFields => AddAccessibility(original, node), + InterfaceDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnInterfaces => AddAccessibility(original, node), + + PropertyDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties => AddAccessibility(original, node), + MethodDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods => AddAccessibility(original, node), + + ClassDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses => AddAccessibility(original, node), + StructDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnStructs => AddAccessibility(original, node), + + //RecordDeclarationSyntax when node.IsKind(SyntaxKind.RecordDeclaration) && Settings.Default.Cleaning_InsertExplicitAccessModifiersOnRecords => AddAccessibility(original, node), + //RecordDeclarationSyntax when node.IsKind(SyntaxKind.RecordStructDeclaration) && Settings.Default.Cleaning_InsertExplicitAccessModifiersOnRecordStructs => AddAccessibility(original, node), + + _ => node, + }; + } + + private SyntaxNode AddAccessibility(SyntaxNode original, SyntaxNode newNode) + { + if (!CSharpAccessibilityFacts.ShouldUpdateAccessibilityModifier(original as MemberDeclarationSyntax, AccessibilityModifiersRequired.Always, out var _, out var canChange)) + { + return newNode; + } + + var mapped = MapToDeclarator(original); + + var symbol = _semanticModel.GetDeclaredSymbol(mapped); + if (symbol is null) + { + throw new ArgumentNullException(nameof(symbol)); + } + + var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); + + return _syntaxGenerator.WithAccessibility(newNode, preferredAccessibility); + } + + private static SyntaxNode MapToDeclarator(SyntaxNode node) + { + return node switch + { + FieldDeclarationSyntax field => field.Declaration.Variables[0], + EventFieldDeclarationSyntax eventField => eventField.Declaration.Variables[0], + _ => node, + }; + } +} \ No newline at end of file From f98cf1be03a0e457baea4bce4ade8edcb62fca76 Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Sat, 25 Feb 2023 18:31:45 +0000 Subject: [PATCH 12/17] Rename RunExplicit to BuildAndrun --- CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs | 2 +- CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs index 9416f4e53..bb81908e4 100644 --- a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs +++ b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs @@ -328,7 +328,7 @@ private void RunCodeCleanupCSharp(Document document) //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnMethods(methods); //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnProperties(properties); - RoslynCleanup.RunExplicit(_package); + RoslynCleanup.BuildAndrun(_package); // Perform insertion of whitespace cleanup. diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs index be397ce94..21bfdc24a 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs @@ -50,7 +50,7 @@ public SyntaxNode Process(SyntaxNode root, Workspace workspace) return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); } - public static void RunExplicit(AsyncPackage package) + public static void BuildAndrun(AsyncPackage package) { ThreadHelper.ThrowIfNotOnUIThread(); From 4fd8a0729321866539f3d7c2aae6fc865e102c7d Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Sat, 25 Feb 2023 21:53:09 +0000 Subject: [PATCH 13/17] Added non elastic Generator methods. --- CodeMaidShared/CodeMaidShared.projitems | 1 + .../Cleaning/Roslyn/InternalGenerator.cs | 455 ++++++++++++++++++ .../Logic/Cleaning/Roslyn/RoslynCleanup.cs | 3 +- ...RoslynInsertExplicitAccessModifierLogic.cs | 4 +- 4 files changed, 460 insertions(+), 3 deletions(-) create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/InternalGenerator.cs diff --git a/CodeMaidShared/CodeMaidShared.projitems b/CodeMaidShared/CodeMaidShared.projitems index 28595ef6c..ab6939e09 100644 --- a/CodeMaidShared/CodeMaidShared.projitems +++ b/CodeMaidShared/CodeMaidShared.projitems @@ -90,6 +90,7 @@ + diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/InternalGenerator.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/InternalGenerator.cs new file mode 100644 index 000000000..06c5e806b --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/InternalGenerator.cs @@ -0,0 +1,455 @@ +using CodeMaidShared.Logic.Cleaning; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SteveCadwallader.CodeMaid.Logic.Cleaning; + +internal static class InternalGenerator +{ + private static SyntaxTokenList GetModifierTokens(SyntaxNode declaration) + => CSharpAccessibilityFacts.GetModifierTokens(declaration); + + private static void GetAccessibilityAndModifiers(SyntaxTokenList modifierList, out Accessibility accessibility, out DeclarationModifiers modifiers, out bool isDefault) + => CSharpAccessibilityFacts.GetAccessibilityAndModifiers(modifierList, out accessibility, out modifiers, out isDefault); + + public static SyntaxNode WithAccessibility(SyntaxNode declaration, Accessibility accessibility) + { + if (!CSharpAccessibilityFacts.CanHaveAccessibility(declaration) && + accessibility != Accessibility.NotApplicable) + { + return declaration; + } + + return Isolate(declaration, d => + { + 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-local 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); + }); + } + + private static SyntaxNode SetModifierTokens(SyntaxNode declaration, SyntaxTokenList modifiers) + => declaration switch + { + MemberDeclarationSyntax memberDecl => memberDecl.WithModifiers(modifiers), + ParameterSyntax parameter => parameter.WithModifiers(modifiers), + LocalDeclarationStatementSyntax localDecl => localDecl.WithModifiers(modifiers), + LocalFunctionStatementSyntax localFunc => localFunc.WithModifiers(modifiers), + AccessorDeclarationSyntax accessor => accessor.WithModifiers(modifiers), + AnonymousFunctionExpressionSyntax anonymous => anonymous.WithModifiers(modifiers), + _ => declaration, + }; + + internal static SyntaxTokenList Merge(SyntaxTokenList original, SyntaxTokenList newList) + { + // return tokens from newList, but use original tokens of kind matches + return new SyntaxTokenList(newList.Select( + token => Any(original, token.RawKind) + ? original.First(tk => tk.RawKind == token.RawKind) + : token)); + } + + private static bool Any(SyntaxTokenList original, int rawKind) + { + foreach (var token in original) + { + if (token.RawKind == rawKind) + { + return true; + } + } + + return false; + } + + private static SyntaxTokenList AsModifierList(Accessibility accessibility, DeclarationModifiers modifiers, SyntaxKind kind) + => AsModifierList(accessibility, GetAllowedModifiers(kind) & modifiers); + + private static readonly DeclarationModifiers s_fieldModifiers = + DeclarationModifiers.Const | + DeclarationModifiers.New | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Required | + DeclarationModifiers.Static | + DeclarationModifiers.Unsafe | + DeclarationModifiers.Volatile; + + private static readonly DeclarationModifiers s_methodModifiers = + DeclarationModifiers.Abstract | + DeclarationModifiers.Async | + DeclarationModifiers.Extern | + DeclarationModifiers.New | + DeclarationModifiers.Override | + DeclarationModifiers.Partial | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Sealed | + DeclarationModifiers.Static | + DeclarationModifiers.Virtual | + DeclarationModifiers.Unsafe; + + private static readonly DeclarationModifiers s_constructorModifiers = + DeclarationModifiers.Extern | + DeclarationModifiers.Static | + DeclarationModifiers.Unsafe; + + private static readonly DeclarationModifiers s_destructorModifiers = DeclarationModifiers.Unsafe; + private static readonly DeclarationModifiers s_propertyModifiers = + DeclarationModifiers.Abstract | + DeclarationModifiers.Extern | + DeclarationModifiers.New | + DeclarationModifiers.Override | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Required | + DeclarationModifiers.Sealed | + DeclarationModifiers.Static | + DeclarationModifiers.Virtual | + DeclarationModifiers.Unsafe; + + private static readonly DeclarationModifiers s_eventModifiers = + DeclarationModifiers.Abstract | + DeclarationModifiers.Extern | + DeclarationModifiers.New | + DeclarationModifiers.Override | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Sealed | + DeclarationModifiers.Static | + DeclarationModifiers.Virtual | + DeclarationModifiers.Unsafe; + + private static readonly DeclarationModifiers s_eventFieldModifiers = + DeclarationModifiers.New | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Static | + DeclarationModifiers.Unsafe; + + private static readonly DeclarationModifiers s_indexerModifiers = + DeclarationModifiers.Abstract | + DeclarationModifiers.Extern | + DeclarationModifiers.New | + DeclarationModifiers.Override | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Sealed | + DeclarationModifiers.Static | + DeclarationModifiers.Virtual | + DeclarationModifiers.Unsafe; + + private static readonly DeclarationModifiers s_classModifiers = + DeclarationModifiers.Abstract | + DeclarationModifiers.New | + DeclarationModifiers.Partial | + DeclarationModifiers.Sealed | + DeclarationModifiers.Static | + DeclarationModifiers.Unsafe | + DeclarationModifiers.File; + + private static readonly DeclarationModifiers s_recordModifiers = + DeclarationModifiers.Abstract | + DeclarationModifiers.New | + DeclarationModifiers.Partial | + DeclarationModifiers.Sealed | + DeclarationModifiers.Unsafe | + DeclarationModifiers.File; + + private static readonly DeclarationModifiers s_structModifiers = + DeclarationModifiers.New | + DeclarationModifiers.Partial | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Ref | + DeclarationModifiers.Unsafe | + DeclarationModifiers.File; + + private static readonly DeclarationModifiers s_interfaceModifiers = DeclarationModifiers.New | DeclarationModifiers.Partial | DeclarationModifiers.Unsafe | DeclarationModifiers.File; + private static readonly DeclarationModifiers s_accessorModifiers = DeclarationModifiers.Abstract | DeclarationModifiers.New | DeclarationModifiers.Override | DeclarationModifiers.Virtual; + + private static readonly DeclarationModifiers s_localFunctionModifiers = + DeclarationModifiers.Async | + DeclarationModifiers.Static | + DeclarationModifiers.Unsafe | + DeclarationModifiers.Extern; + + private static readonly DeclarationModifiers s_lambdaModifiers = + DeclarationModifiers.Async | + DeclarationModifiers.Static; + + private static DeclarationModifiers GetAllowedModifiers(SyntaxKind kind) + { + switch (kind) + { + case SyntaxKind.RecordDeclaration: + return s_recordModifiers; + + case SyntaxKind.ClassDeclaration: + return s_classModifiers; + + case SyntaxKind.EnumDeclaration: + return DeclarationModifiers.New | DeclarationModifiers.File; + + case SyntaxKind.DelegateDeclaration: + return DeclarationModifiers.New | DeclarationModifiers.Unsafe | DeclarationModifiers.File; + + case SyntaxKind.InterfaceDeclaration: + return s_interfaceModifiers; + + case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: + return s_structModifiers; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.OperatorDeclaration: + case SyntaxKind.ConversionOperatorDeclaration: + return s_methodModifiers; + + case SyntaxKind.ConstructorDeclaration: + return s_constructorModifiers; + + case SyntaxKind.DestructorDeclaration: + return s_destructorModifiers; + + case SyntaxKind.FieldDeclaration: + return s_fieldModifiers; + + case SyntaxKind.PropertyDeclaration: + return s_propertyModifiers; + + case SyntaxKind.IndexerDeclaration: + return s_indexerModifiers; + + case SyntaxKind.EventFieldDeclaration: + return s_eventFieldModifiers; + + case SyntaxKind.EventDeclaration: + return s_eventModifiers; + + case SyntaxKind.GetAccessorDeclaration: + case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.AddAccessorDeclaration: + case SyntaxKind.RemoveAccessorDeclaration: + return s_accessorModifiers; + + case SyntaxKind.LocalFunctionStatement: + return s_localFunctionModifiers; + + case SyntaxKind.ParenthesizedLambdaExpression: + case SyntaxKind.SimpleLambdaExpression: + case SyntaxKind.AnonymousMethodExpression: + return s_lambdaModifiers; + + case SyntaxKind.EnumMemberDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.LocalDeclarationStatement: + default: + return DeclarationModifiers.None; + } + } + + private static SyntaxTokenList AsModifierList(Accessibility accessibility, DeclarationModifiers modifiers) + { + var list = new List(); + + switch (accessibility) + { + case Accessibility.Internal: + list.Add(SyntaxFactory.Token(SyntaxKind.InternalKeyword)); + break; + + case Accessibility.Public: + list.Add(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + break; + + case Accessibility.Private: + list.Add(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); + break; + + case Accessibility.Protected: + list.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); + break; + + case Accessibility.ProtectedOrInternal: + list.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); + list.Add(SyntaxFactory.Token(SyntaxKind.InternalKeyword)); + break; + + case Accessibility.ProtectedAndInternal: + list.Add(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); + list.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); + break; + + case Accessibility.NotApplicable: + break; + } + + if (modifiers.IsFile) + list.Add(SyntaxFactory.Token(SyntaxKind.FileKeyword)); + + if (modifiers.IsAbstract) + list.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); + + if (modifiers.IsNew) + list.Add(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + + if (modifiers.IsSealed) + list.Add(SyntaxFactory.Token(SyntaxKind.SealedKeyword)); + + if (modifiers.IsOverride) + list.Add(SyntaxFactory.Token(SyntaxKind.OverrideKeyword)); + + if (modifiers.IsVirtual) + list.Add(SyntaxFactory.Token(SyntaxKind.VirtualKeyword)); + + if (modifiers.IsStatic) + list.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + + if (modifiers.IsAsync) + list.Add(SyntaxFactory.Token(SyntaxKind.AsyncKeyword)); + + if (modifiers.IsConst) + list.Add(SyntaxFactory.Token(SyntaxKind.ConstKeyword)); + + if (modifiers.IsReadOnly) + list.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + + if (modifiers.IsUnsafe) + list.Add(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); + + if (modifiers.IsVolatile) + list.Add(SyntaxFactory.Token(SyntaxKind.VolatileKeyword)); + + if (modifiers.IsExtern) + list.Add(SyntaxFactory.Token(SyntaxKind.ExternKeyword)); + + if (modifiers.IsRequired) + list.Add(SyntaxFactory.Token(SyntaxKind.RequiredKeyword)); + + // partial and ref must be last + if (modifiers.IsRef) + list.Add(SyntaxFactory.Token(SyntaxKind.RefKeyword)); + + if (modifiers.IsPartial) + list.Add(SyntaxFactory.Token(SyntaxKind.PartialKeyword)); + + list = list.Select(x => x.WithTrailingTrivia(SyntaxFactory.Space)).ToList(); + + return SyntaxFactory.TokenList(list); + } + + private static SyntaxNode Isolate(SyntaxNode declaration, Func editor) + => PreserveTrivia(AsIsolatedDeclaration(declaration), editor); + + private static SyntaxNode AsIsolatedDeclaration(SyntaxNode declaration) + { + switch (declaration.Kind()) + { + case SyntaxKind.VariableDeclaration: + var vd = (VariableDeclarationSyntax)declaration; + if (vd.Parent != null && vd.Variables.Count == 1) + { + return AsIsolatedDeclaration(vd.Parent); + } + + break; + + case SyntaxKind.VariableDeclarator: + var v = (VariableDeclaratorSyntax)declaration; + if (v.Parent != null && v.Parent.Parent != null) + { + return ClearTrivia(WithVariable(v.Parent.Parent, v)); + } + + break; + + case SyntaxKind.Attribute: + var attr = (AttributeSyntax)declaration; + if (attr.Parent != null) + { + var attrList = (AttributeListSyntax)attr.Parent; + return attrList.WithAttributes(SyntaxFactory.SingletonSeparatedList(attr)).WithTarget(null); + } + + break; + } + + return declaration; + } + + private static SyntaxNode WithVariable(SyntaxNode declaration, VariableDeclaratorSyntax variable) + { + var vd = GetVariableDeclaration(declaration); + if (vd != null) + { + return WithVariableDeclaration(declaration, vd.WithVariables(SyntaxFactory.SingletonSeparatedList(variable))); + } + + return declaration; + } + + private static VariableDeclarationSyntax? GetVariableDeclaration(SyntaxNode declaration) + => declaration.Kind() switch + { + SyntaxKind.FieldDeclaration => ((FieldDeclarationSyntax)declaration).Declaration, + SyntaxKind.EventFieldDeclaration => ((EventFieldDeclarationSyntax)declaration).Declaration, + SyntaxKind.LocalDeclarationStatement => ((LocalDeclarationStatementSyntax)declaration).Declaration, + _ => null, + }; + + private static SyntaxNode WithVariableDeclaration(SyntaxNode declaration, VariableDeclarationSyntax variables) + => declaration.Kind() switch + { + SyntaxKind.FieldDeclaration => ((FieldDeclarationSyntax)declaration).WithDeclaration(variables), + SyntaxKind.EventFieldDeclaration => ((EventFieldDeclarationSyntax)declaration).WithDeclaration(variables), + SyntaxKind.LocalDeclarationStatement => ((LocalDeclarationStatementSyntax)declaration).WithDeclaration(variables), + _ => declaration, + }; + + public static TNode ClearTrivia(TNode node) where TNode : SyntaxNode + { + if (node != null) + { + return node.WithLeadingTrivia(SyntaxFactory.ElasticMarker) + .WithTrailingTrivia(SyntaxFactory.ElasticMarker); + } + else + { + return null; + } + } + + private static SyntaxNode? PreserveTrivia(TNode? node, Func nodeChanger) where TNode : SyntaxNode + { + if (node == null) + { + return node; + } + + var nodeWithoutTrivia = node.WithoutLeadingTrivia().WithoutTrailingTrivia(); + + var changedNode = nodeChanger(nodeWithoutTrivia); + if (changedNode == nodeWithoutTrivia) + { + return node; + } + + return changedNode + .WithLeadingTrivia(node.GetLeadingTrivia().Concat(changedNode.GetLeadingTrivia())) + .WithTrailingTrivia(changedNode.GetTrailingTrivia().Concat(node.GetTrailingTrivia())); + } +} diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs index 21bfdc24a..069ce8e9e 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs @@ -47,7 +47,8 @@ public override SyntaxNode Visit(SyntaxNode node) public SyntaxNode Process(SyntaxNode root, Workspace workspace) { var rewrite = Visit(root); - return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); + return rewrite; + //return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); } public static void BuildAndrun(AsyncPackage package) diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs index 9c9b4df0e..8fbb216e0 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs @@ -94,8 +94,8 @@ private SyntaxNode AddAccessibility(SyntaxNode original, SyntaxNode newNode) } var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); - - return _syntaxGenerator.WithAccessibility(newNode, preferredAccessibility); + return InternalGenerator.WithAccessibility(newNode, preferredAccessibility); + //return _syntaxGenerator.WithAccessibility(newNode, preferredAccessibility); } private static SyntaxNode MapToDeclarator(SyntaxNode node) From 32ba6215519037c014276bae2befdbf784028623 Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Wed, 1 Mar 2023 13:34:32 +0000 Subject: [PATCH 14/17] Convert to block scoped namespaces --- CodeMaid.UnitTests/Cleanup/RoslynTests.cs | 398 +++++----- CodeMaid.UnitTests/Cleanup/TestWorkspace.cs | 73 +- .../Roslyn/AccessibilityModifiersRequired.cs | 31 +- .../Roslyn/CSharpAccessibilityFacts.cs | 657 ++++++++-------- .../Logic/Cleaning/Roslyn/Global.cs | 85 +- .../Cleaning/Roslyn/InternalGenerator.cs | 735 +++++++++--------- .../Logic/Cleaning/Roslyn/RoslynCleanup.cs | 100 +-- .../Logic/Cleaning/Roslyn/RoslynExtensions.cs | 147 ++-- ...RoslynInsertExplicitAccessModifierLogic.cs | 158 ++-- 9 files changed, 1195 insertions(+), 1189 deletions(-) diff --git a/CodeMaid.UnitTests/Cleanup/RoslynTests.cs b/CodeMaid.UnitTests/Cleanup/RoslynTests.cs index c46b245bc..8e94966d6 100644 --- a/CodeMaid.UnitTests/Cleanup/RoslynTests.cs +++ b/CodeMaid.UnitTests/Cleanup/RoslynTests.cs @@ -1,43 +1,43 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Threading.Tasks; -namespace SteveCadwallader.CodeMaid.UnitTests.Cleanup; - -[TestClass] -public class RoslynTests +namespace SteveCadwallader.CodeMaid.UnitTests.Cleanup { - private readonly TestWorkspace testWorkspace; - - public RoslynTests() + [TestClass] + public class RoslynTests { - testWorkspace = new TestWorkspace(); - } + private readonly TestWorkspace testWorkspace; - [TestMethod] - public async Task ShouldAddClassAccessorAsync() - { - var source = -""" + public RoslynTests() + { + testWorkspace = new TestWorkspace(); + } + + [TestMethod] + public async Task ShouldAddClassAccessorAsync() + { + var source = + """ class MyClass { } """; - var expected = -""" + var expected = + """ internal class MyClass { } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddSamePartialClassAccessorsAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddSamePartialClassAccessorsAsync() + { + var source = + """ public partial class Temp { } @@ -47,8 +47,8 @@ partial class Temp } """; - var expected = -""" + var expected = + """ public partial class Temp { } @@ -58,14 +58,14 @@ public partial class Temp } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddNestedClassAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddNestedClassAccessorAsync() + { + var source = + """ class Temp { int MyProperty { get; set; } @@ -80,8 +80,8 @@ class Temp } """; - var expected = -""" + var expected = + """ internal class Temp { private int MyProperty { get; set; } @@ -95,34 +95,34 @@ private class Temp } } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddStructAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddStructAccessorAsync() + { + var source = + """ struct MyStruct { } """; - var expected = -""" + var expected = + """ internal struct MyStruct { } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddRefStructAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddRefStructAccessorAsync() + { + var source = + """ ref struct MyStruct { } @@ -132,8 +132,8 @@ readonly ref struct MyReadonlyStruct } """; - var expected = -""" + var expected = + """ internal ref struct MyStruct { } @@ -143,58 +143,58 @@ internal readonly ref struct MyReadonlyStruct } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddPropertyAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddPropertyAccessorAsync() + { + var source = + """ class Sample { int Prop { get; set; } } """; - var expected = -""" + var expected = + """ internal class Sample { private int Prop { get; set; } } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldNotRemoveRequiredPropertyAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldNotRemoveRequiredPropertyAsync() + { + var source = + """ class Sample { required int Prop { get; set; } } """; - var expected = -""" + var expected = + """ internal class Sample { private required int Prop { get; set; } } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddMethodsAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddMethodsAccessorAsync() + { + var source = + """ class ExampleClass { void Do() @@ -203,8 +203,8 @@ void Do() } """; - var expected = -""" + var expected = + """ internal class ExampleClass { private void Do() @@ -213,14 +213,14 @@ private void Do() } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldNotAddPartialMethodAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldNotAddPartialMethodAccessorAsync() + { + var source = + """ public partial class ExampleClass { partial void Do() @@ -229,8 +229,8 @@ partial void Do() } """; - var expected = -""" + var expected = + """ public partial class ExampleClass { partial void Do() @@ -239,14 +239,14 @@ partial void Do() } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddDefaultAbstractVirtualAccessorsAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddDefaultAbstractVirtualAccessorsAsync() + { + var source = + """ abstract class MyAbstract { virtual void VirtualMethod() @@ -257,8 +257,8 @@ virtual void VirtualMethod() } """; - var expected = -""" + var expected = + """ internal abstract class MyAbstract { public virtual void VirtualMethod() @@ -268,14 +268,14 @@ public virtual void VirtualMethod() protected abstract void AbstractMethod(); } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task TestInheritsAbstractAsync() - { - var source = -""" + [TestMethod] + public async Task TestInheritsAbstractAsync() + { + var source = + """ abstract class MyAbstract { private protected abstract void AbstractMethod(); @@ -289,8 +289,8 @@ override void AbstractMethod() } """; - var expected = -""" + var expected = + """ internal abstract class MyAbstract { private protected abstract void AbstractMethod(); @@ -303,14 +303,14 @@ private protected override void AbstractMethod() } } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task TestShouldNotRemoveFileAsync() - { - var source = -""" + [TestMethod] + public async Task TestShouldNotRemoveFileAsync() + { + var source = + """ file class MyFile { int Prop { get; set; } @@ -322,8 +322,8 @@ file struct MyFileStruct } """; - var expected = -""" + var expected = + """ file class MyFile { private int Prop { get; set; } @@ -334,56 +334,56 @@ file struct MyFileStruct private int Prop { get; set; } } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddDelegateAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddDelegateAccessorAsync() + { + var source = + """ class MyDelegate { delegate int PerformCalculation(int x, int y); } """; - var expected = -""" + var expected = + """ internal class MyDelegate { private delegate int PerformCalculation(int x, int y); } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddEventAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddEventAccessorAsync() + { + var source = + """ class MyEvent { event MyEventHandler MyEvent; } """; - var expected = -""" + var expected = + """ internal class MyEvent { private event MyEventHandler MyEvent; } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddEnumAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddEnumAccessorAsync() + { + var source = + """ enum MyEnum { Some = 0, @@ -391,22 +391,22 @@ enum MyEnum } """; - var expected = -""" + var expected = + """ internal enum MyEnum { Some = 0, None = 1, } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddNestedEnumAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddNestedEnumAccessorAsync() + { + var source = + """ class MyClass { enum MyEnum @@ -417,8 +417,8 @@ enum MyEnum } """; - var expected = -""" + var expected = + """ internal class MyClass { private enum MyEnum @@ -428,14 +428,14 @@ private enum MyEnum } } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddInterfaceAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddInterfaceAccessorAsync() + { + var source = + """ interface IMyInterface { int MyProp { get; set; } @@ -444,8 +444,8 @@ interface IMyInterface } """; - var expected = -""" + var expected = + """ internal interface IMyInterface { int MyProp { get; set; } @@ -453,14 +453,14 @@ internal interface IMyInterface void Do(); } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddNestedInterfaceAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddNestedInterfaceAccessorAsync() + { + var source = + """ class MyClass { interface IMyInterface @@ -472,8 +472,8 @@ interface IMyInterface } """; - var expected = -""" + var expected = + """ internal class MyClass { private interface IMyInterface @@ -484,35 +484,35 @@ private interface IMyInterface } } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldAddFieldAccessorAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldAddFieldAccessorAsync() + { + var source = + """ class MyClass { int _number; } """; - var expected = -""" + var expected = + """ internal class MyClass { private int _number; } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldNotChangeInvalidSyntaxAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldNotChangeInvalidSyntaxAsync() + { + var source = + """ class ITemp { void Do() @@ -522,8 +522,8 @@ void Do() } """; - var expected = -""" + var expected = + """ internal class ITemp { private void Do() @@ -532,14 +532,14 @@ private void Do() } } """; - await testWorkspace.VerifyCleanupAsync(source, expected); - } + await testWorkspace.VerifyCleanupAsync(source, expected); + } - [TestMethod] - public async Task ShouldNotChangeInterfaceDescendantsAsync() - { - var source = -""" + [TestMethod] + public async Task ShouldNotChangeInterfaceDescendantsAsync() + { + var source = + """ interface IInterface { class C @@ -552,8 +552,8 @@ class D } """; - var expected = -""" + var expected = + """ internal interface IInterface { class C @@ -565,7 +565,7 @@ class D } } """; - await testWorkspace.VerifyCleanupAsync(source, expected); + await testWorkspace.VerifyCleanupAsync(source, expected); + } } -} - +} \ No newline at end of file diff --git a/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs b/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs index 851db73df..745530774 100644 --- a/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs +++ b/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs @@ -5,57 +5,58 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Threading.Tasks; -namespace SteveCadwallader.CodeMaid.UnitTests.Cleanup; - -public class TestWorkspace +namespace SteveCadwallader.CodeMaid.UnitTests.Cleanup { - public async Task VerifyCleanupAsync(string input, string expected) + public class TestWorkspace { - var document = SetDocument(input); + public async Task VerifyCleanupAsync(string input, string expected) + { + var document = SetDocument(input); - var syntaxTree = await Document.GetSyntaxRootAsync(); - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); - var semanticModel = await Document.GetSemanticModelAsync(); + var syntaxTree = await Document.GetSyntaxRootAsync(); + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + var semanticModel = await Document.GetSemanticModelAsync(); - var modifierLogic = new RoslynInsertExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - var rewriter = new RoslynCleanup() - { - MemberWriter = modifierLogic.ProcessMember - }; - var result = rewriter.Process(syntaxTree, Workspace); + var modifierLogic = new RoslynInsertExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + var rewriter = new RoslynCleanup() + { + MemberWriter = modifierLogic.ProcessMember + }; + var result = rewriter.Process(syntaxTree, Workspace); - Assert.AreEqual(expected, result.ToFullString()); - } + Assert.AreEqual(expected, result.ToFullString()); + } - public TestWorkspace() - { - var source = -""" + public TestWorkspace() + { + var source = + """ public class ThisShouldAppear { } """; - Workspace = new AdhocWorkspace(); + Workspace = new AdhocWorkspace(); - var projName = "TestProject"; - var projectId = ProjectId.CreateNewId(); - var versionStamp = VersionStamp.Create(); - var projectInfo = ProjectInfo.Create(projectId, versionStamp, projName, projName, LanguageNames.CSharp); - var newProject = Workspace.AddProject(projectInfo); + var projName = "TestProject"; + var projectId = ProjectId.CreateNewId(); + var versionStamp = VersionStamp.Create(); + var projectInfo = ProjectInfo.Create(projectId, versionStamp, projName, projName, LanguageNames.CSharp); + var newProject = Workspace.AddProject(projectInfo); - var sourceText = SourceText.From(source); - Document = Workspace.AddDocument(newProject.Id, "NewFile.cs", sourceText); - } + var sourceText = SourceText.From(source); + Document = Workspace.AddDocument(newProject.Id, "NewFile.cs", sourceText); + } - public AdhocWorkspace Workspace { get; private set; } + public AdhocWorkspace Workspace { get; private set; } - public Document Document { get; private set; } + public Document Document { get; private set; } - public Document SetDocument(string text) - { - Document = Document.WithText(SourceText.From(text)); - Assert.IsTrue(Workspace.TryApplyChanges(Document.Project.Solution)); - return Document; + public Document SetDocument(string text) + { + Document = Document.WithText(SourceText.From(text)); + Assert.IsTrue(Workspace.TryApplyChanges(Document.Project.Solution)); + return Document; + } } } \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/AccessibilityModifiersRequired.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/AccessibilityModifiersRequired.cs index 64dac4fff..ae3eaa308 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/AccessibilityModifiersRequired.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/AccessibilityModifiersRequired.cs @@ -1,19 +1,20 @@ -namespace CodeMaidShared.Logic.Cleaning; - -internal enum AccessibilityModifiersRequired +namespace CodeMaidShared.Logic.Cleaning { - // The rule is not run - Never = 0, + internal enum AccessibilityModifiersRequired + { + // The rule is not run + Never = 0, - // Accessibility modifiers are added if missing, even if default - Always = 1, + // Accessibility modifiers are added if missing, even if default + Always = 1, - // Future proofing for when C# adds default interface methods. At that point - // accessibility modifiers will be allowed in interfaces, and some people may - // want to require them, while some may want to keep the traditional C# style - // that public interface members do not need accessibility modifiers. - ForNonInterfaceMembers = 2, + // Future proofing for when C# adds default interface methods. At that point + // accessibility modifiers will be allowed in interfaces, and some people may + // want to require them, while some may want to keep the traditional C# style + // that public interface members do not need accessibility modifiers. + ForNonInterfaceMembers = 2, - // Remove any accessibility modifier that matches the default - OmitIfDefault = 3 -} + // Remove any accessibility modifier that matches the default + OmitIfDefault = 3 + } +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/CSharpAccessibilityFacts.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/CSharpAccessibilityFacts.cs index 16d46fc3a..52f6b3572 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/CSharpAccessibilityFacts.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/CSharpAccessibilityFacts.cs @@ -5,420 +5,421 @@ using SteveCadwallader.CodeMaid.Logic.Cleaning; using System; -namespace CodeMaidShared.Logic.Cleaning; - -internal static class CSharpAccessibilityFacts +namespace CodeMaidShared.Logic.Cleaning { - public static bool ShouldUpdateAccessibilityModifier( - MemberDeclarationSyntax member, - AccessibilityModifiersRequired option, - out Accessibility accessibility, - out bool modifierAdded) + internal static class CSharpAccessibilityFacts { - modifierAdded = false; - accessibility = Accessibility.NotApplicable; - - // Have to have a name to report the issue on. - var name = member.GetNameToken(); - if (name.IsKind(SyntaxKind.None)) - return false; + public static bool ShouldUpdateAccessibilityModifier( + MemberDeclarationSyntax member, + AccessibilityModifiersRequired option, + out Accessibility accessibility, + out bool modifierAdded) + { + modifierAdded = false; + accessibility = Accessibility.NotApplicable; - // Certain members never have accessibility. Don't bother reporting on them. - if (!CanHaveAccessibility(member)) - return false; + // Have to have a name to report the issue on. + var name = member.GetNameToken(); + if (name.IsKind(SyntaxKind.None)) + return false; - //if (!IsParentValid(member)) - // return false; + // Certain members never have accessibility. Don't bother reporting on them. + if (!CanHaveAccessibility(member)) + return false; - // This analyzer bases all of its decisions on the accessibility - accessibility = GetAccessibility(member); + //if (!IsParentValid(member)) + // return false; - // Omit will flag any accessibility values that exist and are default - // The other options will remove or ignore accessibility - var isOmit = option == AccessibilityModifiersRequired.OmitIfDefault; - modifierAdded = !isOmit; + // This analyzer bases all of its decisions on the accessibility + accessibility = GetAccessibility(member); - if (isOmit) - { - if (accessibility == Accessibility.NotApplicable) - return false; + // Omit will flag any accessibility values that exist and are default + // The other options will remove or ignore accessibility + var isOmit = option == AccessibilityModifiersRequired.OmitIfDefault; + modifierAdded = !isOmit; - var parentKind = member.GetRequiredParent().Kind(); - switch (parentKind) + if (isOmit) { - // Check for default modifiers in namespace and outside of namespace - case SyntaxKind.CompilationUnit: - case SyntaxKind.FileScopedNamespaceDeclaration: - case SyntaxKind.NamespaceDeclaration: - { - // Default is internal - if (accessibility != Accessibility.Internal) - return false; - } + if (accessibility == Accessibility.NotApplicable) + return false; - break; + var parentKind = member.GetRequiredParent().Kind(); + switch (parentKind) + { + // Check for default modifiers in namespace and outside of namespace + case SyntaxKind.CompilationUnit: + case SyntaxKind.FileScopedNamespaceDeclaration: + case SyntaxKind.NamespaceDeclaration: + { + // Default is internal + if (accessibility != Accessibility.Internal) + return false; + } - case SyntaxKind.ClassDeclaration: - case SyntaxKind.RecordDeclaration: - case SyntaxKind.StructDeclaration: - case SyntaxKind.RecordStructDeclaration: - { - // Inside a type, default is private - if (accessibility != Accessibility.Private) - return false; - } + break; - break; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: + { + // Inside a type, default is private + if (accessibility != Accessibility.Private) + return false; + } - default: - return false; // Unknown parent kind, don't do anything - } - } - else - { - // Mode is always, so we have to flag missing modifiers - if (accessibility != Accessibility.NotApplicable) - return false; - } + break; - return true; - } + default: + return false; // Unknown parent kind, don't do anything + } + } + else + { + // Mode is always, so we have to flag missing modifiers + if (accessibility != Accessibility.NotApplicable) + return false; + } - // TODO Unneeded if descendants of an interface are not passed into ShouldUpdateAccessibilityModifier - // Cant check for grandparent interfaces - public static bool IsParentValid(SyntaxNode node) - { - if(node.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.InterfaceDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration or SyntaxKind.EnumDeclaration or SyntaxKind.DelegateDeclaration) - { - return node.Parent.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration or SyntaxKind.NamespaceDeclaration or SyntaxKind.FileScopedNamespaceDeclaration; + return true; } - // Non object declarations must be inside an object declaration. - // Can probably simplify to: Parent is not interface or Parent is class/struct/record/namespace - if (node.Kind() is SyntaxKind.EventFieldDeclaration or SyntaxKind.FieldDeclaration or SyntaxKind.PropertyDeclaration or SyntaxKind.MethodDeclaration) + // TODO Unneeded if descendants of an interface are not passed into ShouldUpdateAccessibilityModifier + // Cant check for grandparent interfaces + public static bool IsParentValid(SyntaxNode node) { - return node.Parent.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration; + if (node.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.InterfaceDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration or SyntaxKind.EnumDeclaration or SyntaxKind.DelegateDeclaration) + { + return node.Parent.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration or SyntaxKind.NamespaceDeclaration or SyntaxKind.FileScopedNamespaceDeclaration; + } + + // Non object declarations must be inside an object declaration. + // Can probably simplify to: Parent is not interface or Parent is class/struct/record/namespace + if (node.Kind() is SyntaxKind.EventFieldDeclaration or SyntaxKind.FieldDeclaration or SyntaxKind.PropertyDeclaration or SyntaxKind.MethodDeclaration) + { + return node.Parent.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration; + } + return true; } - return true; - } - public static bool CanHaveAccessibility(SyntaxNode declaration, bool ignoreDeclarationModifiers = false) - { - switch (declaration.Kind()) + public static bool CanHaveAccessibility(SyntaxNode declaration, bool ignoreDeclarationModifiers = false) { - 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; - } + 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; - } + if (method.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + // partial methods can't have accessibility modifiers. + return false; + } - return true; + return true; - case SyntaxKind.EventDeclaration: - return ((EventDeclarationSyntax)declaration).ExplicitInterfaceSpecifier == null; + case SyntaxKind.EventDeclaration: + return ((EventDeclarationSyntax)declaration).ExplicitInterfaceSpecifier == null; - default: - return false; + default: + return false; + } } - } - public static 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 Accessibility GetAccessibility(SyntaxNode declaration) + { + if (!CanHaveAccessibility(declaration)) + return Accessibility.NotApplicable; - public static void GetAccessibilityAndModifiers(SyntaxTokenList modifierList, out Accessibility accessibility, out DeclarationModifiers modifiers, out bool isDefault) - { - accessibility = Accessibility.NotApplicable; - modifiers = DeclarationModifiers.None; - isDefault = false; + var modifierTokens = GetModifierTokens(declaration); + GetAccessibilityAndModifiers(modifierTokens, out var accessibility, out _, out _); + return accessibility; + } - foreach (var token in modifierList) + public static void GetAccessibilityAndModifiers(SyntaxTokenList modifierList, out Accessibility accessibility, out DeclarationModifiers modifiers, out bool isDefault) { - accessibility = (token.Kind(), accessibility) switch - { - (SyntaxKind.PublicKeyword, _) => Accessibility.Public, + accessibility = Accessibility.NotApplicable; + modifiers = DeclarationModifiers.None; + isDefault = false; - (SyntaxKind.PrivateKeyword, Accessibility.Protected) => Accessibility.ProtectedAndInternal, - (SyntaxKind.PrivateKeyword, _) => Accessibility.Private, + foreach (var token in modifierList) + { + accessibility = (token.Kind(), accessibility) switch + { + (SyntaxKind.PublicKeyword, _) => Accessibility.Public, - (SyntaxKind.InternalKeyword, Accessibility.Protected) => Accessibility.ProtectedOrInternal, - (SyntaxKind.InternalKeyword, _) => Accessibility.Internal, + (SyntaxKind.PrivateKeyword, Accessibility.Protected) => Accessibility.ProtectedAndInternal, + (SyntaxKind.PrivateKeyword, _) => Accessibility.Private, - (SyntaxKind.ProtectedKeyword, Accessibility.Private) => Accessibility.ProtectedAndInternal, - (SyntaxKind.ProtectedKeyword, Accessibility.Internal) => Accessibility.ProtectedOrInternal, - (SyntaxKind.ProtectedKeyword, _) => Accessibility.Protected, + (SyntaxKind.InternalKeyword, Accessibility.Protected) => Accessibility.ProtectedOrInternal, + (SyntaxKind.InternalKeyword, _) => Accessibility.Internal, - _ => accessibility, - }; + (SyntaxKind.ProtectedKeyword, Accessibility.Private) => Accessibility.ProtectedAndInternal, + (SyntaxKind.ProtectedKeyword, Accessibility.Internal) => Accessibility.ProtectedOrInternal, + (SyntaxKind.ProtectedKeyword, _) => Accessibility.Protected, - 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, - }; + _ => accessibility, + }; - isDefault |= token.Kind() == SyntaxKind.DefaultKeyword; + 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()) + public static DeclarationKind GetDeclarationKind(SyntaxNode declaration) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.RecordDeclaration: - return DeclarationKind.Class; + switch (declaration.Kind()) + { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: + return DeclarationKind.Class; - case SyntaxKind.StructDeclaration: - case SyntaxKind.RecordStructDeclaration: - return DeclarationKind.Struct; + case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: + return DeclarationKind.Struct; - case SyntaxKind.InterfaceDeclaration: - return DeclarationKind.Interface; + case SyntaxKind.InterfaceDeclaration: + return DeclarationKind.Interface; - case SyntaxKind.EnumDeclaration: - return DeclarationKind.Enum; + case SyntaxKind.EnumDeclaration: + return DeclarationKind.Enum; - case SyntaxKind.DelegateDeclaration: - return DeclarationKind.Delegate; + case SyntaxKind.DelegateDeclaration: + return DeclarationKind.Delegate; - case SyntaxKind.MethodDeclaration: - return DeclarationKind.Method; + case SyntaxKind.MethodDeclaration: + return DeclarationKind.Method; - case SyntaxKind.OperatorDeclaration: - return DeclarationKind.Operator; + case SyntaxKind.OperatorDeclaration: + return DeclarationKind.Operator; - case SyntaxKind.ConversionOperatorDeclaration: - return DeclarationKind.ConversionOperator; + case SyntaxKind.ConversionOperatorDeclaration: + return DeclarationKind.ConversionOperator; - case SyntaxKind.ConstructorDeclaration: - return DeclarationKind.Constructor; + case SyntaxKind.ConstructorDeclaration: + return DeclarationKind.Constructor; - case SyntaxKind.DestructorDeclaration: - return DeclarationKind.Destructor; + case SyntaxKind.DestructorDeclaration: + return DeclarationKind.Destructor; - case SyntaxKind.PropertyDeclaration: - return DeclarationKind.Property; + case SyntaxKind.PropertyDeclaration: + return DeclarationKind.Property; - case SyntaxKind.IndexerDeclaration: - return DeclarationKind.Indexer; + case SyntaxKind.IndexerDeclaration: + return DeclarationKind.Indexer; - case SyntaxKind.EventDeclaration: - return DeclarationKind.CustomEvent; + case SyntaxKind.EventDeclaration: + return DeclarationKind.CustomEvent; - case SyntaxKind.EnumMemberDeclaration: - return DeclarationKind.EnumMember; + case SyntaxKind.EnumMemberDeclaration: + return DeclarationKind.EnumMember; - case SyntaxKind.CompilationUnit: - return DeclarationKind.CompilationUnit; + case SyntaxKind.CompilationUnit: + return DeclarationKind.CompilationUnit; - case SyntaxKind.NamespaceDeclaration: - case SyntaxKind.FileScopedNamespaceDeclaration: - return DeclarationKind.Namespace; + case SyntaxKind.NamespaceDeclaration: + case SyntaxKind.FileScopedNamespaceDeclaration: + return DeclarationKind.Namespace; - case SyntaxKind.UsingDirective: - return DeclarationKind.NamespaceImport; + case SyntaxKind.UsingDirective: + return DeclarationKind.NamespaceImport; - case SyntaxKind.Parameter: - return DeclarationKind.Parameter; + case SyntaxKind.Parameter: + return DeclarationKind.Parameter; - case SyntaxKind.ParenthesizedLambdaExpression: - case SyntaxKind.SimpleLambdaExpression: - return DeclarationKind.LambdaExpression; + 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.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.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.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.VariableDeclaration: - { - var vd = (VariableDeclarationSyntax)declaration; - if (vd.Variables.Count == 1 && vd.Parent == null) + case SyntaxKind.LocalDeclarationStatement: + var ld = (LocalDeclarationStatementSyntax)declaration; + if (ld.Declaration != null && ld.Declaration.Variables.Count == 1) { - // this node is the declaration if it contains only one variable and has no parent. + // this node is considered the declaration if it contains only one variable. 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) + case SyntaxKind.VariableDeclaration: { - if (ParentIsFieldDeclaration(vd)) + var vd = (VariableDeclarationSyntax)declaration; + if (vd.Variables.Count == 1 && vd.Parent == null) { - return DeclarationKind.Field; + // this node is the declaration if it contains only one variable and has no parent. + return DeclarationKind.Variable; } - else if (ParentIsEventFieldDeclaration(vd)) + else { - return DeclarationKind.Event; + return DeclarationKind.None; } - else + } + + 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) { - return DeclarationKind.Variable; + if (ParentIsFieldDeclaration(vd)) + { + return DeclarationKind.Field; + } + else if (ParentIsEventFieldDeclaration(vd)) + { + return DeclarationKind.Event; + } + else + { + return DeclarationKind.Variable; + } } + + break; } - break; - } + case SyntaxKind.AttributeList: + var list = (AttributeListSyntax)declaration; + if (list.Attributes.Count == 1) + { + return DeclarationKind.Attribute; + } - case SyntaxKind.AttributeList: - var list = (AttributeListSyntax)declaration; - if (list.Attributes.Count == 1) - { - return DeclarationKind.Attribute; - } + break; - break; + case SyntaxKind.Attribute: + if (declaration.Parent is not AttributeListSyntax parentList || parentList.Attributes.Count > 1) + { + return DeclarationKind.Attribute; + } - case SyntaxKind.Attribute: - if (declaration.Parent is not AttributeListSyntax parentList || parentList.Attributes.Count > 1) - { - return DeclarationKind.Attribute; - } + break; - break; + case SyntaxKind.GetAccessorDeclaration: + return DeclarationKind.GetAccessor; - case SyntaxKind.GetAccessorDeclaration: - return DeclarationKind.GetAccessor; + case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.InitAccessorDeclaration: + return DeclarationKind.SetAccessor; - case SyntaxKind.SetAccessorDeclaration: - case SyntaxKind.InitAccessorDeclaration: - return DeclarationKind.SetAccessor; + case SyntaxKind.AddAccessorDeclaration: + return DeclarationKind.AddAccessor; - case SyntaxKind.AddAccessorDeclaration: - return DeclarationKind.AddAccessor; + case SyntaxKind.RemoveAccessorDeclaration: + return DeclarationKind.RemoveAccessor; + } - case SyntaxKind.RemoveAccessorDeclaration: - return DeclarationKind.RemoveAccessor; + return DeclarationKind.None; } - 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.Parent ?? throw new InvalidOperationException("Token's parent was null")), + VariableDeclaratorSyntax varDecl => GetModifierTokens(varDecl.Parent ?? throw new InvalidOperationException("Token's parent was null")), + AnonymousFunctionExpressionSyntax anonymous => anonymous.Modifiers, + _ => default, + }; - 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.Parent ?? throw new InvalidOperationException("Token's parent was null")), - VariableDeclaratorSyntax varDecl => GetModifierTokens(varDecl.Parent ?? throw new InvalidOperationException("Token's parent was null")), - AnonymousFunctionExpressionSyntax anonymous => anonymous.Modifiers, - _ => default, - }; - - public static bool ParentIsFieldDeclaration(SyntaxNode? node) - => node?.Parent.IsKind(SyntaxKind.FieldDeclaration) ?? false; - - public static bool ParentIsEventFieldDeclaration(SyntaxNode? node) - => node?.Parent.IsKind(SyntaxKind.EventFieldDeclaration) ?? false; + public static bool ParentIsFieldDeclaration(SyntaxNode? node) + => node?.Parent.IsKind(SyntaxKind.FieldDeclaration) ?? false; + + public static bool ParentIsEventFieldDeclaration(SyntaxNode? node) + => node?.Parent.IsKind(SyntaxKind.EventFieldDeclaration) ?? false; + } } \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/Global.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/Global.cs index 844c3df53..3c0cd0520 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/Global.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/Global.cs @@ -8,63 +8,64 @@ using DteDocument = EnvDTE.Document; using Solution = Microsoft.CodeAnalysis.Solution; -namespace SteveCadwallader.CodeMaid.Logic.Cleaning; - -internal static class Global +namespace SteveCadwallader.CodeMaid.Logic.Cleaning { - static public AsyncPackage Package; - - static public T GetService() - => (T)Package?.GetServiceAsync(typeof(T))?.Result; - - static public DteDocument GetActiveDteDocument() + internal static class Global { - ThreadHelper.ThrowIfNotOnUIThread(); - dynamic dte = GetService(); - return (DteDocument)dte.ActiveDocument; - } + static public AsyncPackage Package; - static IVsStatusbar Statusbar; + static public T GetService() + => (T)Package?.GetServiceAsync(typeof(T))?.Result; - internal static void SetStatusMessage(string message) - { - if (Statusbar == null) + static public DteDocument GetActiveDteDocument() { - Statusbar = GetService(); - // StatusBar = Package.GetGlobalService(typeof(IVsStatusbar)) as IVsStatusbar; + ThreadHelper.ThrowIfNotOnUIThread(); + dynamic dte = GetService(); + return (DteDocument)dte.ActiveDocument; } - Statusbar.SetText(message); - } + static IVsStatusbar Statusbar; - public static Document GetActiveDocument() - { - ThreadHelper.ThrowIfNotOnUIThread(); + internal static void SetStatusMessage(string message) + { + if (Statusbar == null) + { + Statusbar = GetService(); + // StatusBar = Package.GetGlobalService(typeof(IVsStatusbar)) as IVsStatusbar; + } - Solution solution = Workspace.CurrentSolution; - string activeDocPath = GetActiveDteDocument()?.FullName; + Statusbar.SetText(message); + } - if (activeDocPath != null) - return solution.Projects - .SelectMany(x => x.Documents) - .FirstOrDefault(x => x.SupportsSyntaxTree && - x.SupportsSemanticModel && - x.FilePath == activeDocPath); - return null; - } + public static Document GetActiveDocument() + { + ThreadHelper.ThrowIfNotOnUIThread(); - private static VisualStudioWorkspace workspace = null; + Solution solution = Workspace.CurrentSolution; + string activeDocPath = GetActiveDteDocument()?.FullName; - static public VisualStudioWorkspace Workspace - { - get + if (activeDocPath != null) + return solution.Projects + .SelectMany(x => x.Documents) + .FirstOrDefault(x => x.SupportsSyntaxTree && + x.SupportsSemanticModel && + x.FilePath == activeDocPath); + return null; + } + + private static VisualStudioWorkspace workspace = null; + + static public VisualStudioWorkspace Workspace { - if (workspace == null) + get { - IComponentModel componentModel = GetService() as IComponentModel; - workspace = componentModel.GetService(); + if (workspace == null) + { + IComponentModel componentModel = GetService() as IComponentModel; + workspace = componentModel.GetService(); + } + return workspace; } - return workspace; } } } \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/InternalGenerator.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/InternalGenerator.cs index 06c5e806b..3bde09c0d 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/InternalGenerator.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/InternalGenerator.cs @@ -7,449 +7,450 @@ using System.Collections.Generic; using System.Linq; -namespace SteveCadwallader.CodeMaid.Logic.Cleaning; - -internal static class InternalGenerator +namespace SteveCadwallader.CodeMaid.Logic.Cleaning { - private static SyntaxTokenList GetModifierTokens(SyntaxNode declaration) - => CSharpAccessibilityFacts.GetModifierTokens(declaration); - - private static void GetAccessibilityAndModifiers(SyntaxTokenList modifierList, out Accessibility accessibility, out DeclarationModifiers modifiers, out bool isDefault) - => CSharpAccessibilityFacts.GetAccessibilityAndModifiers(modifierList, out accessibility, out modifiers, out isDefault); - - public static SyntaxNode WithAccessibility(SyntaxNode declaration, Accessibility accessibility) + internal static class InternalGenerator { - if (!CSharpAccessibilityFacts.CanHaveAccessibility(declaration) && - accessibility != Accessibility.NotApplicable) - { - return declaration; - } + private static SyntaxTokenList GetModifierTokens(SyntaxNode declaration) + => CSharpAccessibilityFacts.GetModifierTokens(declaration); - return Isolate(declaration, d => + private static void GetAccessibilityAndModifiers(SyntaxTokenList modifierList, out Accessibility accessibility, out DeclarationModifiers modifiers, out bool isDefault) + => CSharpAccessibilityFacts.GetAccessibilityAndModifiers(modifierList, out accessibility, out modifiers, out isDefault); + + public static SyntaxNode WithAccessibility(SyntaxNode declaration, Accessibility accessibility) { - var tokens = GetModifierTokens(d); - GetAccessibilityAndModifiers(tokens, out _, out var modifiers, out _); - if (modifiers.IsFile && accessibility != Accessibility.NotApplicable) + if (!CSharpAccessibilityFacts.CanHaveAccessibility(declaration) && + 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-local type '{0}' cannot use accessibility modifiers. - modifiers = modifiers.WithIsFile(false); + return declaration; } - if (modifiers.IsStatic && declaration.IsKind(SyntaxKind.ConstructorDeclaration) && accessibility != Accessibility.NotApplicable) + return Isolate(declaration, d => { - // If user wants to add accessibility for a static constructor, we remove static modifier - modifiers = modifiers.WithIsStatic(false); - } + 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-local type '{0}' cannot use accessibility modifiers. + modifiers = modifiers.WithIsFile(false); + } - var newTokens = Merge(tokens, AsModifierList(accessibility, modifiers)); - return SetModifierTokens(d, newTokens); - }); - } + 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); + } - private static SyntaxNode SetModifierTokens(SyntaxNode declaration, SyntaxTokenList modifiers) - => declaration switch - { - MemberDeclarationSyntax memberDecl => memberDecl.WithModifiers(modifiers), - ParameterSyntax parameter => parameter.WithModifiers(modifiers), - LocalDeclarationStatementSyntax localDecl => localDecl.WithModifiers(modifiers), - LocalFunctionStatementSyntax localFunc => localFunc.WithModifiers(modifiers), - AccessorDeclarationSyntax accessor => accessor.WithModifiers(modifiers), - AnonymousFunctionExpressionSyntax anonymous => anonymous.WithModifiers(modifiers), - _ => declaration, - }; - - internal static SyntaxTokenList Merge(SyntaxTokenList original, SyntaxTokenList newList) - { - // return tokens from newList, but use original tokens of kind matches - return new SyntaxTokenList(newList.Select( - token => Any(original, token.RawKind) - ? original.First(tk => tk.RawKind == token.RawKind) - : token)); - } + var newTokens = Merge(tokens, AsModifierList(accessibility, modifiers)); + return SetModifierTokens(d, newTokens); + }); + } - private static bool Any(SyntaxTokenList original, int rawKind) - { - foreach (var token in original) + private static SyntaxNode SetModifierTokens(SyntaxNode declaration, SyntaxTokenList modifiers) + => declaration switch + { + MemberDeclarationSyntax memberDecl => memberDecl.WithModifiers(modifiers), + ParameterSyntax parameter => parameter.WithModifiers(modifiers), + LocalDeclarationStatementSyntax localDecl => localDecl.WithModifiers(modifiers), + LocalFunctionStatementSyntax localFunc => localFunc.WithModifiers(modifiers), + AccessorDeclarationSyntax accessor => accessor.WithModifiers(modifiers), + AnonymousFunctionExpressionSyntax anonymous => anonymous.WithModifiers(modifiers), + _ => declaration, + }; + + internal static SyntaxTokenList Merge(SyntaxTokenList original, SyntaxTokenList newList) { - if (token.RawKind == rawKind) + // return tokens from newList, but use original tokens of kind matches + return new SyntaxTokenList(newList.Select( + token => Any(original, token.RawKind) + ? original.First(tk => tk.RawKind == token.RawKind) + : token)); + } + + private static bool Any(SyntaxTokenList original, int rawKind) + { + foreach (var token in original) { - return true; + if (token.RawKind == rawKind) + { + return true; + } } + + return false; } - return false; - } + private static SyntaxTokenList AsModifierList(Accessibility accessibility, DeclarationModifiers modifiers, SyntaxKind kind) + => AsModifierList(accessibility, GetAllowedModifiers(kind) & modifiers); - private static SyntaxTokenList AsModifierList(Accessibility accessibility, DeclarationModifiers modifiers, SyntaxKind kind) - => AsModifierList(accessibility, GetAllowedModifiers(kind) & modifiers); - - private static readonly DeclarationModifiers s_fieldModifiers = - DeclarationModifiers.Const | - DeclarationModifiers.New | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Required | - DeclarationModifiers.Static | - DeclarationModifiers.Unsafe | - DeclarationModifiers.Volatile; - - private static readonly DeclarationModifiers s_methodModifiers = - DeclarationModifiers.Abstract | - DeclarationModifiers.Async | - DeclarationModifiers.Extern | + private static readonly DeclarationModifiers s_fieldModifiers = + DeclarationModifiers.Const | DeclarationModifiers.New | - DeclarationModifiers.Override | - DeclarationModifiers.Partial | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Sealed | - DeclarationModifiers.Static | - DeclarationModifiers.Virtual | - DeclarationModifiers.Unsafe; - - private static readonly DeclarationModifiers s_constructorModifiers = - DeclarationModifiers.Extern | - DeclarationModifiers.Static | - DeclarationModifiers.Unsafe; - - private static readonly DeclarationModifiers s_destructorModifiers = DeclarationModifiers.Unsafe; - private static readonly DeclarationModifiers s_propertyModifiers = - DeclarationModifiers.Abstract | - DeclarationModifiers.Extern | - DeclarationModifiers.New | - DeclarationModifiers.Override | DeclarationModifiers.ReadOnly | DeclarationModifiers.Required | - DeclarationModifiers.Sealed | - DeclarationModifiers.Static | - DeclarationModifiers.Virtual | - DeclarationModifiers.Unsafe; - - private static readonly DeclarationModifiers s_eventModifiers = - DeclarationModifiers.Abstract | - DeclarationModifiers.Extern | - DeclarationModifiers.New | - DeclarationModifiers.Override | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Sealed | - DeclarationModifiers.Static | - DeclarationModifiers.Virtual | - DeclarationModifiers.Unsafe; - - private static readonly DeclarationModifiers s_eventFieldModifiers = - DeclarationModifiers.New | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Static | - DeclarationModifiers.Unsafe; - - private static readonly DeclarationModifiers s_indexerModifiers = - DeclarationModifiers.Abstract | - DeclarationModifiers.Extern | - DeclarationModifiers.New | - DeclarationModifiers.Override | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Sealed | - DeclarationModifiers.Static | - DeclarationModifiers.Virtual | - DeclarationModifiers.Unsafe; - - private static readonly DeclarationModifiers s_classModifiers = - DeclarationModifiers.Abstract | - DeclarationModifiers.New | - DeclarationModifiers.Partial | - DeclarationModifiers.Sealed | - DeclarationModifiers.Static | - DeclarationModifiers.Unsafe | - DeclarationModifiers.File; - - private static readonly DeclarationModifiers s_recordModifiers = - DeclarationModifiers.Abstract | - DeclarationModifiers.New | - DeclarationModifiers.Partial | - DeclarationModifiers.Sealed | - DeclarationModifiers.Unsafe | - DeclarationModifiers.File; - - private static readonly DeclarationModifiers s_structModifiers = - DeclarationModifiers.New | - DeclarationModifiers.Partial | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Ref | - DeclarationModifiers.Unsafe | - DeclarationModifiers.File; - - private static readonly DeclarationModifiers s_interfaceModifiers = DeclarationModifiers.New | DeclarationModifiers.Partial | DeclarationModifiers.Unsafe | DeclarationModifiers.File; - private static readonly DeclarationModifiers s_accessorModifiers = DeclarationModifiers.Abstract | DeclarationModifiers.New | DeclarationModifiers.Override | DeclarationModifiers.Virtual; - - private static readonly DeclarationModifiers s_localFunctionModifiers = - DeclarationModifiers.Async | DeclarationModifiers.Static | DeclarationModifiers.Unsafe | - DeclarationModifiers.Extern; - - private static readonly DeclarationModifiers s_lambdaModifiers = - DeclarationModifiers.Async | - DeclarationModifiers.Static; - - private static DeclarationModifiers GetAllowedModifiers(SyntaxKind kind) - { - switch (kind) + DeclarationModifiers.Volatile; + + private static readonly DeclarationModifiers s_methodModifiers = + DeclarationModifiers.Abstract | + DeclarationModifiers.Async | + DeclarationModifiers.Extern | + DeclarationModifiers.New | + DeclarationModifiers.Override | + DeclarationModifiers.Partial | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Sealed | + DeclarationModifiers.Static | + DeclarationModifiers.Virtual | + DeclarationModifiers.Unsafe; + + private static readonly DeclarationModifiers s_constructorModifiers = + DeclarationModifiers.Extern | + DeclarationModifiers.Static | + DeclarationModifiers.Unsafe; + + private static readonly DeclarationModifiers s_destructorModifiers = DeclarationModifiers.Unsafe; + private static readonly DeclarationModifiers s_propertyModifiers = + DeclarationModifiers.Abstract | + DeclarationModifiers.Extern | + DeclarationModifiers.New | + DeclarationModifiers.Override | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Required | + DeclarationModifiers.Sealed | + DeclarationModifiers.Static | + DeclarationModifiers.Virtual | + DeclarationModifiers.Unsafe; + + private static readonly DeclarationModifiers s_eventModifiers = + DeclarationModifiers.Abstract | + DeclarationModifiers.Extern | + DeclarationModifiers.New | + DeclarationModifiers.Override | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Sealed | + DeclarationModifiers.Static | + DeclarationModifiers.Virtual | + DeclarationModifiers.Unsafe; + + private static readonly DeclarationModifiers s_eventFieldModifiers = + DeclarationModifiers.New | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Static | + DeclarationModifiers.Unsafe; + + private static readonly DeclarationModifiers s_indexerModifiers = + DeclarationModifiers.Abstract | + DeclarationModifiers.Extern | + DeclarationModifiers.New | + DeclarationModifiers.Override | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Sealed | + DeclarationModifiers.Static | + DeclarationModifiers.Virtual | + DeclarationModifiers.Unsafe; + + private static readonly DeclarationModifiers s_classModifiers = + DeclarationModifiers.Abstract | + DeclarationModifiers.New | + DeclarationModifiers.Partial | + DeclarationModifiers.Sealed | + DeclarationModifiers.Static | + DeclarationModifiers.Unsafe | + DeclarationModifiers.File; + + private static readonly DeclarationModifiers s_recordModifiers = + DeclarationModifiers.Abstract | + DeclarationModifiers.New | + DeclarationModifiers.Partial | + DeclarationModifiers.Sealed | + DeclarationModifiers.Unsafe | + DeclarationModifiers.File; + + private static readonly DeclarationModifiers s_structModifiers = + DeclarationModifiers.New | + DeclarationModifiers.Partial | + DeclarationModifiers.ReadOnly | + DeclarationModifiers.Ref | + DeclarationModifiers.Unsafe | + DeclarationModifiers.File; + + private static readonly DeclarationModifiers s_interfaceModifiers = DeclarationModifiers.New | DeclarationModifiers.Partial | DeclarationModifiers.Unsafe | DeclarationModifiers.File; + private static readonly DeclarationModifiers s_accessorModifiers = DeclarationModifiers.Abstract | DeclarationModifiers.New | DeclarationModifiers.Override | DeclarationModifiers.Virtual; + + private static readonly DeclarationModifiers s_localFunctionModifiers = + DeclarationModifiers.Async | + DeclarationModifiers.Static | + DeclarationModifiers.Unsafe | + DeclarationModifiers.Extern; + + private static readonly DeclarationModifiers s_lambdaModifiers = + DeclarationModifiers.Async | + DeclarationModifiers.Static; + + private static DeclarationModifiers GetAllowedModifiers(SyntaxKind kind) { - case SyntaxKind.RecordDeclaration: - return s_recordModifiers; + switch (kind) + { + case SyntaxKind.RecordDeclaration: + return s_recordModifiers; - case SyntaxKind.ClassDeclaration: - return s_classModifiers; + case SyntaxKind.ClassDeclaration: + return s_classModifiers; - case SyntaxKind.EnumDeclaration: - return DeclarationModifiers.New | DeclarationModifiers.File; + case SyntaxKind.EnumDeclaration: + return DeclarationModifiers.New | DeclarationModifiers.File; - case SyntaxKind.DelegateDeclaration: - return DeclarationModifiers.New | DeclarationModifiers.Unsafe | DeclarationModifiers.File; + case SyntaxKind.DelegateDeclaration: + return DeclarationModifiers.New | DeclarationModifiers.Unsafe | DeclarationModifiers.File; - case SyntaxKind.InterfaceDeclaration: - return s_interfaceModifiers; + case SyntaxKind.InterfaceDeclaration: + return s_interfaceModifiers; - case SyntaxKind.StructDeclaration: - case SyntaxKind.RecordStructDeclaration: - return s_structModifiers; + case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: + return s_structModifiers; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.OperatorDeclaration: - case SyntaxKind.ConversionOperatorDeclaration: - return s_methodModifiers; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.OperatorDeclaration: + case SyntaxKind.ConversionOperatorDeclaration: + return s_methodModifiers; - case SyntaxKind.ConstructorDeclaration: - return s_constructorModifiers; + case SyntaxKind.ConstructorDeclaration: + return s_constructorModifiers; - case SyntaxKind.DestructorDeclaration: - return s_destructorModifiers; + case SyntaxKind.DestructorDeclaration: + return s_destructorModifiers; - case SyntaxKind.FieldDeclaration: - return s_fieldModifiers; + case SyntaxKind.FieldDeclaration: + return s_fieldModifiers; - case SyntaxKind.PropertyDeclaration: - return s_propertyModifiers; + case SyntaxKind.PropertyDeclaration: + return s_propertyModifiers; - case SyntaxKind.IndexerDeclaration: - return s_indexerModifiers; + case SyntaxKind.IndexerDeclaration: + return s_indexerModifiers; - case SyntaxKind.EventFieldDeclaration: - return s_eventFieldModifiers; + case SyntaxKind.EventFieldDeclaration: + return s_eventFieldModifiers; - case SyntaxKind.EventDeclaration: - return s_eventModifiers; + case SyntaxKind.EventDeclaration: + return s_eventModifiers; - case SyntaxKind.GetAccessorDeclaration: - case SyntaxKind.SetAccessorDeclaration: - case SyntaxKind.AddAccessorDeclaration: - case SyntaxKind.RemoveAccessorDeclaration: - return s_accessorModifiers; + case SyntaxKind.GetAccessorDeclaration: + case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.AddAccessorDeclaration: + case SyntaxKind.RemoveAccessorDeclaration: + return s_accessorModifiers; - case SyntaxKind.LocalFunctionStatement: - return s_localFunctionModifiers; + case SyntaxKind.LocalFunctionStatement: + return s_localFunctionModifiers; - case SyntaxKind.ParenthesizedLambdaExpression: - case SyntaxKind.SimpleLambdaExpression: - case SyntaxKind.AnonymousMethodExpression: - return s_lambdaModifiers; + case SyntaxKind.ParenthesizedLambdaExpression: + case SyntaxKind.SimpleLambdaExpression: + case SyntaxKind.AnonymousMethodExpression: + return s_lambdaModifiers; - case SyntaxKind.EnumMemberDeclaration: - case SyntaxKind.Parameter: - case SyntaxKind.LocalDeclarationStatement: - default: - return DeclarationModifiers.None; + case SyntaxKind.EnumMemberDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.LocalDeclarationStatement: + default: + return DeclarationModifiers.None; + } } - } - - private static SyntaxTokenList AsModifierList(Accessibility accessibility, DeclarationModifiers modifiers) - { - var list = new List(); - switch (accessibility) + private static SyntaxTokenList AsModifierList(Accessibility accessibility, DeclarationModifiers modifiers) { - case Accessibility.Internal: - list.Add(SyntaxFactory.Token(SyntaxKind.InternalKeyword)); - break; - - case Accessibility.Public: - list.Add(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); - break; - - case Accessibility.Private: - list.Add(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); - break; - - case Accessibility.Protected: - list.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); - break; - - case Accessibility.ProtectedOrInternal: - list.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); - list.Add(SyntaxFactory.Token(SyntaxKind.InternalKeyword)); - break; - - case Accessibility.ProtectedAndInternal: - list.Add(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); - list.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); - break; - - case Accessibility.NotApplicable: - break; - } + var list = new List(); - if (modifiers.IsFile) - list.Add(SyntaxFactory.Token(SyntaxKind.FileKeyword)); - - if (modifiers.IsAbstract) - list.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); - - if (modifiers.IsNew) - list.Add(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - - if (modifiers.IsSealed) - list.Add(SyntaxFactory.Token(SyntaxKind.SealedKeyword)); - - if (modifiers.IsOverride) - list.Add(SyntaxFactory.Token(SyntaxKind.OverrideKeyword)); - - if (modifiers.IsVirtual) - list.Add(SyntaxFactory.Token(SyntaxKind.VirtualKeyword)); - - if (modifiers.IsStatic) - list.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + switch (accessibility) + { + case Accessibility.Internal: + list.Add(SyntaxFactory.Token(SyntaxKind.InternalKeyword)); + break; + + case Accessibility.Public: + list.Add(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + break; + + case Accessibility.Private: + list.Add(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); + break; + + case Accessibility.Protected: + list.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); + break; + + case Accessibility.ProtectedOrInternal: + list.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); + list.Add(SyntaxFactory.Token(SyntaxKind.InternalKeyword)); + break; + + case Accessibility.ProtectedAndInternal: + list.Add(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); + list.Add(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); + break; + + case Accessibility.NotApplicable: + break; + } - if (modifiers.IsAsync) - list.Add(SyntaxFactory.Token(SyntaxKind.AsyncKeyword)); + if (modifiers.IsFile) + list.Add(SyntaxFactory.Token(SyntaxKind.FileKeyword)); - if (modifiers.IsConst) - list.Add(SyntaxFactory.Token(SyntaxKind.ConstKeyword)); + if (modifiers.IsAbstract) + list.Add(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); - if (modifiers.IsReadOnly) - list.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + if (modifiers.IsNew) + list.Add(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - if (modifiers.IsUnsafe) - list.Add(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); + if (modifiers.IsSealed) + list.Add(SyntaxFactory.Token(SyntaxKind.SealedKeyword)); - if (modifiers.IsVolatile) - list.Add(SyntaxFactory.Token(SyntaxKind.VolatileKeyword)); + if (modifiers.IsOverride) + list.Add(SyntaxFactory.Token(SyntaxKind.OverrideKeyword)); - if (modifiers.IsExtern) - list.Add(SyntaxFactory.Token(SyntaxKind.ExternKeyword)); + if (modifiers.IsVirtual) + list.Add(SyntaxFactory.Token(SyntaxKind.VirtualKeyword)); - if (modifiers.IsRequired) - list.Add(SyntaxFactory.Token(SyntaxKind.RequiredKeyword)); + if (modifiers.IsStatic) + list.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); - // partial and ref must be last - if (modifiers.IsRef) - list.Add(SyntaxFactory.Token(SyntaxKind.RefKeyword)); + if (modifiers.IsAsync) + list.Add(SyntaxFactory.Token(SyntaxKind.AsyncKeyword)); - if (modifiers.IsPartial) - list.Add(SyntaxFactory.Token(SyntaxKind.PartialKeyword)); + if (modifiers.IsConst) + list.Add(SyntaxFactory.Token(SyntaxKind.ConstKeyword)); - list = list.Select(x => x.WithTrailingTrivia(SyntaxFactory.Space)).ToList(); + if (modifiers.IsReadOnly) + list.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); - return SyntaxFactory.TokenList(list); - } + if (modifiers.IsUnsafe) + list.Add(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); - private static SyntaxNode Isolate(SyntaxNode declaration, Func editor) - => PreserveTrivia(AsIsolatedDeclaration(declaration), editor); + if (modifiers.IsVolatile) + list.Add(SyntaxFactory.Token(SyntaxKind.VolatileKeyword)); - private static SyntaxNode AsIsolatedDeclaration(SyntaxNode declaration) - { - switch (declaration.Kind()) - { - case SyntaxKind.VariableDeclaration: - var vd = (VariableDeclarationSyntax)declaration; - if (vd.Parent != null && vd.Variables.Count == 1) - { - return AsIsolatedDeclaration(vd.Parent); - } + if (modifiers.IsExtern) + list.Add(SyntaxFactory.Token(SyntaxKind.ExternKeyword)); - break; + if (modifiers.IsRequired) + list.Add(SyntaxFactory.Token(SyntaxKind.RequiredKeyword)); - case SyntaxKind.VariableDeclarator: - var v = (VariableDeclaratorSyntax)declaration; - if (v.Parent != null && v.Parent.Parent != null) - { - return ClearTrivia(WithVariable(v.Parent.Parent, v)); - } + // partial and ref must be last + if (modifiers.IsRef) + list.Add(SyntaxFactory.Token(SyntaxKind.RefKeyword)); - break; + if (modifiers.IsPartial) + list.Add(SyntaxFactory.Token(SyntaxKind.PartialKeyword)); - case SyntaxKind.Attribute: - var attr = (AttributeSyntax)declaration; - if (attr.Parent != null) - { - var attrList = (AttributeListSyntax)attr.Parent; - return attrList.WithAttributes(SyntaxFactory.SingletonSeparatedList(attr)).WithTarget(null); - } + list = list.Select(x => x.WithTrailingTrivia(SyntaxFactory.Space)).ToList(); - break; + return SyntaxFactory.TokenList(list); } - return declaration; - } + private static SyntaxNode Isolate(SyntaxNode declaration, Func editor) + => PreserveTrivia(AsIsolatedDeclaration(declaration), editor); - private static SyntaxNode WithVariable(SyntaxNode declaration, VariableDeclaratorSyntax variable) - { - var vd = GetVariableDeclaration(declaration); - if (vd != null) + private static SyntaxNode AsIsolatedDeclaration(SyntaxNode declaration) { - return WithVariableDeclaration(declaration, vd.WithVariables(SyntaxFactory.SingletonSeparatedList(variable))); - } + switch (declaration.Kind()) + { + case SyntaxKind.VariableDeclaration: + var vd = (VariableDeclarationSyntax)declaration; + if (vd.Parent != null && vd.Variables.Count == 1) + { + return AsIsolatedDeclaration(vd.Parent); + } + + break; + + case SyntaxKind.VariableDeclarator: + var v = (VariableDeclaratorSyntax)declaration; + if (v.Parent != null && v.Parent.Parent != null) + { + return ClearTrivia(WithVariable(v.Parent.Parent, v)); + } + + break; + + case SyntaxKind.Attribute: + var attr = (AttributeSyntax)declaration; + if (attr.Parent != null) + { + var attrList = (AttributeListSyntax)attr.Parent; + return attrList.WithAttributes(SyntaxFactory.SingletonSeparatedList(attr)).WithTarget(null); + } + + break; + } - return declaration; - } + return declaration; + } - private static VariableDeclarationSyntax? GetVariableDeclaration(SyntaxNode declaration) - => declaration.Kind() switch + private static SyntaxNode WithVariable(SyntaxNode declaration, VariableDeclaratorSyntax variable) { - SyntaxKind.FieldDeclaration => ((FieldDeclarationSyntax)declaration).Declaration, - SyntaxKind.EventFieldDeclaration => ((EventFieldDeclarationSyntax)declaration).Declaration, - SyntaxKind.LocalDeclarationStatement => ((LocalDeclarationStatementSyntax)declaration).Declaration, - _ => null, - }; - - private static SyntaxNode WithVariableDeclaration(SyntaxNode declaration, VariableDeclarationSyntax variables) - => declaration.Kind() switch - { - SyntaxKind.FieldDeclaration => ((FieldDeclarationSyntax)declaration).WithDeclaration(variables), - SyntaxKind.EventFieldDeclaration => ((EventFieldDeclarationSyntax)declaration).WithDeclaration(variables), - SyntaxKind.LocalDeclarationStatement => ((LocalDeclarationStatementSyntax)declaration).WithDeclaration(variables), - _ => declaration, - }; + var vd = GetVariableDeclaration(declaration); + if (vd != null) + { + return WithVariableDeclaration(declaration, vd.WithVariables(SyntaxFactory.SingletonSeparatedList(variable))); + } - public static TNode ClearTrivia(TNode node) where TNode : SyntaxNode - { - if (node != null) - { - return node.WithLeadingTrivia(SyntaxFactory.ElasticMarker) - .WithTrailingTrivia(SyntaxFactory.ElasticMarker); + return declaration; } - else + + private static VariableDeclarationSyntax? GetVariableDeclaration(SyntaxNode declaration) + => declaration.Kind() switch + { + SyntaxKind.FieldDeclaration => ((FieldDeclarationSyntax)declaration).Declaration, + SyntaxKind.EventFieldDeclaration => ((EventFieldDeclarationSyntax)declaration).Declaration, + SyntaxKind.LocalDeclarationStatement => ((LocalDeclarationStatementSyntax)declaration).Declaration, + _ => null, + }; + + private static SyntaxNode WithVariableDeclaration(SyntaxNode declaration, VariableDeclarationSyntax variables) + => declaration.Kind() switch + { + SyntaxKind.FieldDeclaration => ((FieldDeclarationSyntax)declaration).WithDeclaration(variables), + SyntaxKind.EventFieldDeclaration => ((EventFieldDeclarationSyntax)declaration).WithDeclaration(variables), + SyntaxKind.LocalDeclarationStatement => ((LocalDeclarationStatementSyntax)declaration).WithDeclaration(variables), + _ => declaration, + }; + + public static TNode ClearTrivia(TNode node) where TNode : SyntaxNode { - return null; + if (node != null) + { + return node.WithLeadingTrivia(SyntaxFactory.ElasticMarker) + .WithTrailingTrivia(SyntaxFactory.ElasticMarker); + } + else + { + return null; + } } - } - private static SyntaxNode? PreserveTrivia(TNode? node, Func nodeChanger) where TNode : SyntaxNode - { - if (node == null) + private static SyntaxNode? PreserveTrivia(TNode? node, Func nodeChanger) where TNode : SyntaxNode { - return node; - } + if (node == null) + { + return node; + } - var nodeWithoutTrivia = node.WithoutLeadingTrivia().WithoutTrailingTrivia(); + var nodeWithoutTrivia = node.WithoutLeadingTrivia().WithoutTrailingTrivia(); - var changedNode = nodeChanger(nodeWithoutTrivia); - if (changedNode == nodeWithoutTrivia) - { - return node; - } + var changedNode = nodeChanger(nodeWithoutTrivia); + if (changedNode == nodeWithoutTrivia) + { + return node; + } - return changedNode - .WithLeadingTrivia(node.GetLeadingTrivia().Concat(changedNode.GetLeadingTrivia())) - .WithTrailingTrivia(changedNode.GetTrailingTrivia().Concat(node.GetTrailingTrivia())); + return changedNode + .WithLeadingTrivia(node.GetLeadingTrivia().Concat(changedNode.GetLeadingTrivia())) + .WithTrailingTrivia(changedNode.GetTrailingTrivia().Concat(node.GetTrailingTrivia())); + } } -} +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs index 069ce8e9e..fef375555 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs @@ -1,77 +1,77 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Formatting; using Microsoft.VisualStudio.Shell; using SteveCadwallader.CodeMaid.Logic.Cleaning; using System; -namespace CodeMaidShared.Logic.Cleaning; - -internal class RoslynCleanup : CSharpSyntaxRewriter +namespace CodeMaidShared.Logic.Cleaning { - public Func MemberWriter { get; set; } - - // Use this messy functions to ensure that the current node is not a descendant of an interface. - // This is to mimic the recursive CSharpAddAccessibilityModifiersDiagnosticAnalyzer.ProcessMemberDeclaration - // search where any non structs/classes are ignored. - // FindAncestorOrSelf might help but would be slower. - // Dont terminate on finding an interface in case I want to roslynize more cleanup functions. - - private bool InsideInterface { get; set; } - - public RoslynCleanup() + internal class RoslynCleanup : CSharpSyntaxRewriter { - MemberWriter = (_, x) => x; - InsideInterface = false; - } + public Func MemberWriter { get; set; } - public override SyntaxNode Visit(SyntaxNode node) - { - var inInterface = InsideInterface; - if (node.IsKind(SyntaxKind.InterfaceDeclaration)) - InsideInterface = true; + // Use this messy functions to ensure that the current node is not a descendant of an interface. + // This is to mimic the recursive CSharpAddAccessibilityModifiersDiagnosticAnalyzer.ProcessMemberDeclaration + // search where any non structs/classes are ignored. + // FindAncestorOrSelf might help but would be slower. + // Dont terminate on finding an interface in case I want to roslynize more cleanup functions. - var newNode = base.Visit(node); + private bool InsideInterface { get; set; } - if (inInterface == false) + public RoslynCleanup() { - newNode = MemberWriter(node, newNode); + MemberWriter = (_, x) => x; + InsideInterface = false; } - InsideInterface = inInterface; + public override SyntaxNode Visit(SyntaxNode node) + { + var inInterface = InsideInterface; + if (node.IsKind(SyntaxKind.InterfaceDeclaration)) + InsideInterface = true; - return newNode; - } + var newNode = base.Visit(node); - public SyntaxNode Process(SyntaxNode root, Workspace workspace) - { - var rewrite = Visit(root); - return rewrite; - //return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); - } + if (inInterface == false) + { + newNode = MemberWriter(node, newNode); + } - public static void BuildAndrun(AsyncPackage package) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - Global.Package = package; + InsideInterface = inInterface; - var document = Global.GetActiveDocument(); + return newNode; + } - if (document == null || !document.TryGetSyntaxRoot(out SyntaxNode root)) + public SyntaxNode Process(SyntaxNode root, Workspace workspace) { - throw new InvalidOperationException(); + var rewrite = Visit(root); + return rewrite; + //return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); } - var semanticModel = document.GetSemanticModelAsync().Result; - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + public static void BuildAndrun(AsyncPackage package) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + Global.Package = package; - var cleaner = new RoslynCleanup(); - RoslynInsertExplicitAccessModifierLogic.Initialize(cleaner, semanticModel, syntaxGenerator); - cleaner.Process(root, Global.Workspace); + var document = Global.GetActiveDocument(); - document = document.WithSyntaxRoot(root); - Global.Workspace.TryApplyChanges(document.Project.Solution); + if (document == null || !document.TryGetSyntaxRoot(out SyntaxNode root)) + { + throw new InvalidOperationException(); + } + + var semanticModel = document.GetSemanticModelAsync().Result; + var syntaxGenerator = SyntaxGenerator.GetGenerator(document); + + var cleaner = new RoslynCleanup(); + RoslynInsertExplicitAccessModifierLogic.Initialize(cleaner, semanticModel, syntaxGenerator); + cleaner.Process(root, Global.Workspace); + + document = document.WithSyntaxRoot(root); + Global.Workspace.TryApplyChanges(document.Project.Solution); + } } } \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs index 5eaac871a..4d2687389 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs @@ -5,105 +5,106 @@ using System.Threading; using System.Threading.Tasks; -namespace SteveCadwallader.CodeMaid.Logic.Cleaning; - -internal static class RoslynExtensions +namespace SteveCadwallader.CodeMaid.Logic.Cleaning { - public static async ValueTask GetRequiredSemanticModelAsync(this Document document, CancellationToken cancellationToken) + internal static class RoslynExtensions { - if (document.TryGetSemanticModel(out var semanticModel)) - return semanticModel; + public static async ValueTask GetRequiredSemanticModelAsync(this Document document, CancellationToken cancellationToken) + { + if (document.TryGetSemanticModel(out var semanticModel)) + return semanticModel; - semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - return semanticModel ?? throw new InvalidOperationException($"Syntax tree is required to accomplish the task but is not supported by document {document.Name}"); - } + semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + return semanticModel ?? throw new InvalidOperationException($"Syntax tree is required to accomplish the task but is not supported by document {document.Name}"); + } - public static SyntaxNode GetRequiredParent(this SyntaxNode node) - => node.Parent ?? throw new InvalidOperationException("Node's parent was null"); + public static SyntaxNode GetRequiredParent(this SyntaxNode node) + => node.Parent ?? throw new InvalidOperationException("Node's parent was null"); - public static SyntaxToken GetNameToken(this MemberDeclarationSyntax member) - { - if (member != null) + public static SyntaxToken GetNameToken(this MemberDeclarationSyntax member) { - switch (member.Kind()) + if (member != null) { - case SyntaxKind.EnumDeclaration: - return ((EnumDeclarationSyntax)member).Identifier; + switch (member.Kind()) + { + case SyntaxKind.EnumDeclaration: + return ((EnumDeclarationSyntax)member).Identifier; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.RecordDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.StructDeclaration: - case SyntaxKind.RecordStructDeclaration: - return ((TypeDeclarationSyntax)member).Identifier; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: + return ((TypeDeclarationSyntax)member).Identifier; - case SyntaxKind.DelegateDeclaration: - return ((DelegateDeclarationSyntax)member).Identifier; + case SyntaxKind.DelegateDeclaration: + return ((DelegateDeclarationSyntax)member).Identifier; - case SyntaxKind.FieldDeclaration: - return ((FieldDeclarationSyntax)member).Declaration.Variables.First().Identifier; + case SyntaxKind.FieldDeclaration: + return ((FieldDeclarationSyntax)member).Declaration.Variables.First().Identifier; - case SyntaxKind.EventFieldDeclaration: - return ((EventFieldDeclarationSyntax)member).Declaration.Variables.First().Identifier; + case SyntaxKind.EventFieldDeclaration: + return ((EventFieldDeclarationSyntax)member).Declaration.Variables.First().Identifier; - case SyntaxKind.PropertyDeclaration: - return ((PropertyDeclarationSyntax)member).Identifier; + case SyntaxKind.PropertyDeclaration: + return ((PropertyDeclarationSyntax)member).Identifier; - case SyntaxKind.EventDeclaration: - return ((EventDeclarationSyntax)member).Identifier; + case SyntaxKind.EventDeclaration: + return ((EventDeclarationSyntax)member).Identifier; - case SyntaxKind.MethodDeclaration: - return ((MethodDeclarationSyntax)member).Identifier; + case SyntaxKind.MethodDeclaration: + return ((MethodDeclarationSyntax)member).Identifier; - case SyntaxKind.ConstructorDeclaration: - return ((ConstructorDeclarationSyntax)member).Identifier; + case SyntaxKind.ConstructorDeclaration: + return ((ConstructorDeclarationSyntax)member).Identifier; - case SyntaxKind.DestructorDeclaration: - return ((DestructorDeclarationSyntax)member).Identifier; + case SyntaxKind.DestructorDeclaration: + return ((DestructorDeclarationSyntax)member).Identifier; - case SyntaxKind.IndexerDeclaration: - return ((IndexerDeclarationSyntax)member).ThisKeyword; + case SyntaxKind.IndexerDeclaration: + return ((IndexerDeclarationSyntax)member).ThisKeyword; - case SyntaxKind.OperatorDeclaration: - return ((OperatorDeclarationSyntax)member).OperatorToken; + case SyntaxKind.OperatorDeclaration: + return ((OperatorDeclarationSyntax)member).OperatorToken; + } } - } - // Conversion operators don't have names. - return default; + // Conversion operators don't have names. + return default; + } } -} -internal static class AddAccessibilityModifiersHelpers -{ - internal static Accessibility GetPreferredAccessibility(ISymbol symbol) + internal static class AddAccessibilityModifiersHelpers { - // If we have an overridden member, then if we're adding an accessibility modifier, use the - // accessibility of the member we're overriding as both should be consistent here. - if (symbol.GetOverriddenMember() is { DeclaredAccessibility: var accessibility }) - return accessibility; - - // Default abstract members to be protected, and virtual members to be public. They can't be private as - // that's not legal. And these are reasonable default values for them. - if (symbol is IMethodSymbol or IPropertySymbol or IEventSymbol) + internal static Accessibility GetPreferredAccessibility(ISymbol symbol) { - if (symbol.IsAbstract) - return Accessibility.Protected; + // If we have an overridden member, then if we're adding an accessibility modifier, use the + // accessibility of the member we're overriding as both should be consistent here. + if (symbol.GetOverriddenMember() is { DeclaredAccessibility: var accessibility }) + return accessibility; + + // Default abstract members to be protected, and virtual members to be public. They can't be private as + // that's not legal. And these are reasonable default values for them. + if (symbol is IMethodSymbol or IPropertySymbol or IEventSymbol) + { + if (symbol.IsAbstract) + return Accessibility.Protected; + + if (symbol.IsVirtual) + return Accessibility.Public; + } - if (symbol.IsVirtual) - return Accessibility.Public; + // Otherwise, default to whatever accessibility no-accessibility means for this member; + return symbol.DeclaredAccessibility; } - // Otherwise, default to whatever accessibility no-accessibility means for this member; - return symbol.DeclaredAccessibility; + public static ISymbol? GetOverriddenMember(this ISymbol? symbol) + => symbol switch + { + IMethodSymbol method => method.OverriddenMethod, + IPropertySymbol property => property.OverriddenProperty, + IEventSymbol @event => @event.OverriddenEvent, + _ => null, + }; } - - public static ISymbol? GetOverriddenMember(this ISymbol? symbol) - => symbol switch - { - IMethodSymbol method => method.OverriddenMethod, - IPropertySymbol property => property.OverriddenProperty, - IEventSymbol @event => @event.OverriddenEvent, - _ => null, - }; } \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs index 8fbb216e0..e7c18bc50 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs @@ -2,109 +2,109 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -using Microsoft.VisualStudio.PlatformUI; using SteveCadwallader.CodeMaid.Logic.Cleaning; using SteveCadwallader.CodeMaid.Properties; using System; -namespace CodeMaidShared.Logic.Cleaning; - -/// -/// A class for encapsulating insertion of explicit access modifier logic. -/// -internal class RoslynInsertExplicitAccessModifierLogic +namespace CodeMaidShared.Logic.Cleaning { - #region Fields + /// + /// A class for encapsulating insertion of explicit access modifier logic. + /// + internal class RoslynInsertExplicitAccessModifierLogic + { + #region Fields - private readonly SemanticModel _semanticModel; - private readonly SyntaxGenerator _syntaxGenerator; + private readonly SemanticModel _semanticModel; + private readonly SyntaxGenerator _syntaxGenerator; - #endregion Fields + #endregion Fields - #region Constructors + #region Constructors - /// - /// The singleton instance of the class. - /// - //private static RoslynInsertExplicitAccessModifierLogic _instance; + /// + /// The singleton instance of the class. + /// + //private static RoslynInsertExplicitAccessModifierLogic _instance; - ///// - ///// Gets an instance of the class. - ///// - ///// An instance of the class. - //internal static RoslynInsertExplicitAccessModifierLogic GetInstance(AsyncPackage package) - //{ - // return new RoslynInsertExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - //} + ///// + ///// Gets an instance of the class. + ///// + ///// An instance of the class. + //internal static RoslynInsertExplicitAccessModifierLogic GetInstance(AsyncPackage package) + //{ + // return new RoslynInsertExplicitAccessModifierLogic(semanticModel, syntaxGenerator); + //} - /// - /// Initializes a new instance of the class. - /// - public RoslynInsertExplicitAccessModifierLogic(SemanticModel semanticModel, SyntaxGenerator syntaxGenerator) - { - _semanticModel = semanticModel; - _syntaxGenerator = syntaxGenerator; - } - - #endregion Constructors + /// + /// Initializes a new instance of the class. + /// + public RoslynInsertExplicitAccessModifierLogic(SemanticModel semanticModel, SyntaxGenerator syntaxGenerator) + { + _semanticModel = semanticModel; + _syntaxGenerator = syntaxGenerator; + } - public static RoslynCleanup Initialize(RoslynCleanup cleanup, SemanticModel model, SyntaxGenerator generator) - { - var explicitLogic = new RoslynInsertExplicitAccessModifierLogic(model, generator); - cleanup.MemberWriter = explicitLogic.ProcessMember; - return cleanup; - } + #endregion Constructors - public SyntaxNode ProcessMember(SyntaxNode original, SyntaxNode node) - { - return node switch + public static RoslynCleanup Initialize(RoslynCleanup cleanup, SemanticModel model, SyntaxGenerator generator) { - DelegateDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnDelegates => AddAccessibility(original, node), - EventFieldDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnEvents => AddAccessibility(original, node), - EnumDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnEnumerations => AddAccessibility(original, node), - FieldDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnFields => AddAccessibility(original, node), - InterfaceDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnInterfaces => AddAccessibility(original, node), + var explicitLogic = new RoslynInsertExplicitAccessModifierLogic(model, generator); + cleanup.MemberWriter = explicitLogic.ProcessMember; + return cleanup; + } - PropertyDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties => AddAccessibility(original, node), - MethodDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods => AddAccessibility(original, node), + public SyntaxNode ProcessMember(SyntaxNode original, SyntaxNode node) + { + return node switch + { + DelegateDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnDelegates => AddAccessibility(original, node), + EventFieldDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnEvents => AddAccessibility(original, node), + EnumDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnEnumerations => AddAccessibility(original, node), + FieldDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnFields => AddAccessibility(original, node), + InterfaceDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnInterfaces => AddAccessibility(original, node), - ClassDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses => AddAccessibility(original, node), - StructDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnStructs => AddAccessibility(original, node), + PropertyDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties => AddAccessibility(original, node), + MethodDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods => AddAccessibility(original, node), - //RecordDeclarationSyntax when node.IsKind(SyntaxKind.RecordDeclaration) && Settings.Default.Cleaning_InsertExplicitAccessModifiersOnRecords => AddAccessibility(original, node), - //RecordDeclarationSyntax when node.IsKind(SyntaxKind.RecordStructDeclaration) && Settings.Default.Cleaning_InsertExplicitAccessModifiersOnRecordStructs => AddAccessibility(original, node), + ClassDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses => AddAccessibility(original, node), + StructDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnStructs => AddAccessibility(original, node), - _ => node, - }; - } + //RecordDeclarationSyntax when node.IsKind(SyntaxKind.RecordDeclaration) && Settings.Default.Cleaning_InsertExplicitAccessModifiersOnRecords => AddAccessibility(original, node), + //RecordDeclarationSyntax when node.IsKind(SyntaxKind.RecordStructDeclaration) && Settings.Default.Cleaning_InsertExplicitAccessModifiersOnRecordStructs => AddAccessibility(original, node), - private SyntaxNode AddAccessibility(SyntaxNode original, SyntaxNode newNode) - { - if (!CSharpAccessibilityFacts.ShouldUpdateAccessibilityModifier(original as MemberDeclarationSyntax, AccessibilityModifiersRequired.Always, out var _, out var canChange)) - { - return newNode; + _ => node, + }; } - var mapped = MapToDeclarator(original); - - var symbol = _semanticModel.GetDeclaredSymbol(mapped); - if (symbol is null) + private SyntaxNode AddAccessibility(SyntaxNode original, SyntaxNode newNode) { - throw new ArgumentNullException(nameof(symbol)); + if (!CSharpAccessibilityFacts.ShouldUpdateAccessibilityModifier(original as MemberDeclarationSyntax, AccessibilityModifiersRequired.Always, out var _, out var canChange)) + { + return newNode; + } + + var mapped = MapToDeclarator(original); + + var symbol = _semanticModel.GetDeclaredSymbol(mapped); + if (symbol is null) + { + throw new ArgumentNullException(nameof(symbol)); + } + + var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); + return InternalGenerator.WithAccessibility(newNode, preferredAccessibility); + //return _syntaxGenerator.WithAccessibility(newNode, preferredAccessibility); } - var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); - return InternalGenerator.WithAccessibility(newNode, preferredAccessibility); - //return _syntaxGenerator.WithAccessibility(newNode, preferredAccessibility); - } - - private static SyntaxNode MapToDeclarator(SyntaxNode node) - { - return node switch + private static SyntaxNode MapToDeclarator(SyntaxNode node) { - FieldDeclarationSyntax field => field.Declaration.Variables[0], - EventFieldDeclarationSyntax eventField => eventField.Declaration.Variables[0], - _ => node, - }; + return node switch + { + FieldDeclarationSyntax field => field.Declaration.Variables[0], + EventFieldDeclarationSyntax eventField => eventField.Declaration.Variables[0], + _ => node, + }; + } } } \ No newline at end of file From e97ea1b10938b5e5bf3e519857c62becd03d3f4a Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Wed, 8 Mar 2023 17:56:34 +0000 Subject: [PATCH 15/17] Merge add padding logic and middleware logic refactor --- CodeMaid.UnitTests/Cleanup/AddPaddingTests.cs | 414 ++++++++++++++++++ ...RoslynTests.cs => InsertModifiersTests.cs} | 201 +++++---- CodeMaid.UnitTests/Cleanup/TestWorkspace.cs | 17 +- CodeMaid.UnitTests/CodeMaid.UnitTests.csproj | 5 +- CodeMaid.VS2022/CodeMaid.VS2022.csproj | 2 +- CodeMaid.config | 48 +- CodeMaid/CodeMaid.csproj | 2 +- CodeMaidShared/CodeMaidShared.projitems | 18 +- .../Logic/Cleaning/CodeCleanupManager.cs | 97 ++-- .../Logic/Cleaning/Roslyn/Global.cs | 35 +- .../Roslyn/Handlers/IRoslynNodeMiddleware.cs | 13 + .../Roslyn/Handlers/IRoslynTokenMiddleware.cs | 13 + .../InsertExplicitAccessorMiddleware.cs | 60 +++ .../Handlers/InsertNodePaddingMiddleware.cs | 43 ++ .../Handlers/InsertTokenPaddingMiddleware.cs | 93 ++++ ...RoslynInsertExplicitAccessModifierLogic.cs | 51 +-- .../Handlers/RoslynInsertPaddingLogic.cs | 161 +++++++ .../Cleaning/Roslyn/InternalGenerator.cs | 202 +-------- .../Logic/Cleaning/Roslyn/RoslynCleaner.cs | 65 +++ .../Logic/Cleaning/Roslyn/RoslynCleanup.cs | 77 ---- .../Logic/Cleaning/Roslyn/RoslynExtensions.cs | 55 +++ 21 files changed, 1191 insertions(+), 481 deletions(-) create mode 100644 CodeMaid.UnitTests/Cleanup/AddPaddingTests.cs rename CodeMaid.UnitTests/Cleanup/{RoslynTests.cs => InsertModifiersTests.cs} (92%) create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/IRoslynNodeMiddleware.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/IRoslynTokenMiddleware.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/InsertExplicitAccessorMiddleware.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/InsertNodePaddingMiddleware.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/InsertTokenPaddingMiddleware.cs rename CodeMaidShared/Logic/Cleaning/Roslyn/{ => Handlers}/RoslynInsertExplicitAccessModifierLogic.cs (68%) create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/RoslynInsertPaddingLogic.cs create mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleaner.cs delete mode 100644 CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs diff --git a/CodeMaid.UnitTests/Cleanup/AddPaddingTests.cs b/CodeMaid.UnitTests/Cleanup/AddPaddingTests.cs new file mode 100644 index 000000000..d702c459b --- /dev/null +++ b/CodeMaid.UnitTests/Cleanup/AddPaddingTests.cs @@ -0,0 +1,414 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SteveCadwallader.CodeMaid.Properties; +using System.Threading.Tasks; + +namespace SteveCadwallader.CodeMaid.UnitTests.Cleanup +{ + // TODO: Add setup/teradown to set codemaid settings. + [TestClass] + public class AddPaddingTests + { + private readonly TestWorkspace testWorkspace; + + public AddPaddingTests() + { + testWorkspace = new TestWorkspace(); + } + + [TestMethod] + public async Task ShouldPadClassesAsync() + { + var source = +@" +internal class MyClass +{ +} +internal class MyClass2 +{ +} +"; + + var expected = +@" +internal class MyClass +{ +} + +internal class MyClass2 +{ +} +"; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldPadPropertiesAsync() + { + var source = +@" +internal class Temp +{ + public int MyProperty { get; set; } + private void Do() + { + } +} +"; + + var expected = +@" +internal class Temp +{ + public int MyProperty { get; set; } + + private void Do() + { + } +} +"; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldPadMixedTypeDeclaAsync() + { + var source = +@" +internal struct Struct +{ +} +internal enum MyEnum +{ + Some = 0, + None = 1, +} +"; + + var expected = +@" +internal struct Struct +{ +} + +internal enum MyEnum +{ + Some = 0, + None = 1, +} +"; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldPadSwitchCaseAsync() + { + var source = +@" +public class Class +{ + private void Do() + { + int number = 1; + + switch (number) + { + case 0: + Console.WriteLine(""The number is zero""); + break; + + case 1: + Console.WriteLine(""The number is one""); + break; + + case 2: + Console.WriteLine(""The number is two""); + break; + + default: + Console.WriteLine(""The number is not zero, one, or two""); + break; + } + } +} +"; + + var expected = +@" +public class Class +{ + private void Do() + { + int number = 1; + + switch (number) + { + case 0: + Console.WriteLine(""The number is zero""); + break; + + case 1: + Console.WriteLine(""The number is one""); + break; + + case 2: + Console.WriteLine(""The number is two""); + break; + + default: + Console.WriteLine(""The number is not zero, one, or two""); + break; + } + } +} +"; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldPadTypeAsync() + { + var source = +@" +class Class where T : struct +{ +} +"; + + var expected = +@" +internal class Class where T : struct +{ +} +"; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldBetweenMultLineAccessorAsync() + { + Settings.Default.Cleaning_InsertBlankLinePaddingBetweenPropertiesMultiLineAccessors = true; + Assert.IsTrue(Settings.Default.Cleaning_InsertBlankLinePaddingBetweenPropertiesMultiLineAccessors); + + var source = +@" +class Class +{ + public string FullName + { + get + { + return _fullName; + } + set => _fullName = value; + } + + public string FirstName + { + get => _firstName; + set => _firstName = value; + } +} +"; + + var expected = +@" +internal class Class +{ + public string FullName + { + get + { + return _fullName; + } + + set => _fullName = value; + } + + public string FirstName + { + get => _firstName; + set => _firstName = value; + } +} +"; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldPadCommentsAsync() + { + var source = +@" +internal + // + class Temp +{ + private void Do() + { + } + // Single + private void Foo() + { + // Should not pad + var a = 10; + } +} +"; + + var expected = +@" +internal + + // + class Temp +{ + private void Do() + { + } + + // Single + private void Foo() + { + // Should not pad + var a = 10; + } +} +"; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldPadRegionAsync() + { + var source = +@" +#region FirstReg +public class Attr : Attribute +{ + public void Do() { } + #endregion FirstReg + #region SecondReg + public Attr(int i) + { + } + #endregion SecondReg +} +"; + + var expected = +@" +#region FirstReg + +public class Attr : Attribute +{ + public void Do() { } + + #endregion FirstReg + + #region SecondReg + + public Attr(int i) + { + } + + #endregion SecondReg +} +"; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldPadRegionWithBracesAsync() + { + var source = +@" +public class Attrib : Attribute +{ + #region FirstReg + public void Do() + { + #endregion FirstReg + } + + public void Foo() + { + #region SecondReg + } + #endregion SecondReg +} +"; + + var expected = +@" +public class Attrib : Attribute +{ + #region FirstReg + + public void Do() + { + #endregion FirstReg + } + + public void Foo() + { + #region SecondReg + } + + #endregion SecondReg +} +"; + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] + public async Task ShouldPadMixedTypeDeclaAndCommentsAsync() + { + var source = +@" +internal struct MyStruct +{ +} +/// +/// +/// +internal struct Struct +{ +} +// Should Pad +internal enum MyEnum +{ + Some = 0, + None = 1, +} +"; + + var expected = +@" +internal struct MyStruct +{ +} + +/// +/// +/// +internal struct Struct +{ +} + +// Should Pad +internal enum MyEnum +{ + Some = 0, + None = 1, +} +"; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + } +} \ No newline at end of file diff --git a/CodeMaid.UnitTests/Cleanup/RoslynTests.cs b/CodeMaid.UnitTests/Cleanup/InsertModifiersTests.cs similarity index 92% rename from CodeMaid.UnitTests/Cleanup/RoslynTests.cs rename to CodeMaid.UnitTests/Cleanup/InsertModifiersTests.cs index 8e94966d6..63ff1c9d3 100644 --- a/CodeMaid.UnitTests/Cleanup/RoslynTests.cs +++ b/CodeMaid.UnitTests/Cleanup/InsertModifiersTests.cs @@ -1,14 +1,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using SteveCadwallader.CodeMaid.Properties; using System.Threading.Tasks; namespace SteveCadwallader.CodeMaid.UnitTests.Cleanup { [TestClass] - public class RoslynTests + public class InsertModifiersTests { private readonly TestWorkspace testWorkspace; - public RoslynTests() + public InsertModifiersTests() { testWorkspace = new TestWorkspace(); } @@ -17,27 +18,55 @@ public RoslynTests() public async Task ShouldAddClassAccessorAsync() { var source = - """ +@" class MyClass { } -"""; +"; var expected = - """ +@" internal class MyClass { } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } + [TestMethod] + public async Task ShouldPadClassAccessorAsync() + { + var source = +@" +internal class MyClass +{ +} +internal class MyClass2 +{ +} +"; + + var expected = +@" +internal class MyClass +{ +} + +internal class MyClass2 +{ +} +"; + + await testWorkspace.VerifyCleanupAsync(source, expected); + } + + [TestMethod] public async Task ShouldAddSamePartialClassAccessorsAsync() { var source = - """ +@" public partial class Temp { } @@ -45,10 +74,10 @@ public partial class Temp partial class Temp { } -"""; +"; var expected = - """ +@" public partial class Temp { } @@ -56,7 +85,7 @@ public partial class Temp public partial class Temp { } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -65,7 +94,7 @@ public partial class Temp public async Task ShouldAddNestedClassAccessorAsync() { var source = - """ +@" class Temp { int MyProperty { get; set; } @@ -78,10 +107,10 @@ class Temp int MyProperty { get; set; } } } -"""; +"; var expected = - """ +@" internal class Temp { private int MyProperty { get; set; } @@ -94,7 +123,7 @@ private class Temp private int MyProperty { get; set; } } } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -102,18 +131,18 @@ private class Temp public async Task ShouldAddStructAccessorAsync() { var source = - """ +@" struct MyStruct { } -"""; +"; var expected = - """ +@" internal struct MyStruct { } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -122,7 +151,7 @@ internal struct MyStruct public async Task ShouldAddRefStructAccessorAsync() { var source = - """ +@" ref struct MyStruct { } @@ -130,10 +159,10 @@ ref struct MyStruct readonly ref struct MyReadonlyStruct { } -"""; +"; var expected = - """ +@" internal ref struct MyStruct { } @@ -141,7 +170,7 @@ internal ref struct MyStruct internal readonly ref struct MyReadonlyStruct { } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -150,20 +179,20 @@ internal readonly ref struct MyReadonlyStruct public async Task ShouldAddPropertyAccessorAsync() { var source = - """ +@" class Sample { int Prop { get; set; } } -"""; +"; var expected = - """ +@" internal class Sample { private int Prop { get; set; } } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -172,20 +201,20 @@ internal class Sample public async Task ShouldNotRemoveRequiredPropertyAsync() { var source = - """ +@" class Sample { required int Prop { get; set; } } -"""; +"; var expected = - """ +@" internal class Sample { private required int Prop { get; set; } } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -194,24 +223,24 @@ internal class Sample public async Task ShouldAddMethodsAccessorAsync() { var source = - """ +@" class ExampleClass { void Do() { } } -"""; +"; var expected = - """ +@" internal class ExampleClass { private void Do() { } } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -220,24 +249,24 @@ private void Do() public async Task ShouldNotAddPartialMethodAccessorAsync() { var source = - """ +@" public partial class ExampleClass { partial void Do() { } } -"""; +"; var expected = - """ +@" public partial class ExampleClass { partial void Do() { } } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -246,7 +275,7 @@ partial void Do() public async Task ShouldAddDefaultAbstractVirtualAccessorsAsync() { var source = - """ +@" abstract class MyAbstract { virtual void VirtualMethod() @@ -255,10 +284,10 @@ virtual void VirtualMethod() abstract void AbstractMethod(); } -"""; +"; var expected = - """ +@" internal abstract class MyAbstract { public virtual void VirtualMethod() @@ -267,7 +296,7 @@ public virtual void VirtualMethod() protected abstract void AbstractMethod(); } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -275,7 +304,7 @@ public virtual void VirtualMethod() public async Task TestInheritsAbstractAsync() { var source = - """ +@" abstract class MyAbstract { private protected abstract void AbstractMethod(); @@ -287,10 +316,10 @@ override void AbstractMethod() { } } -"""; +"; var expected = - """ +@" internal abstract class MyAbstract { private protected abstract void AbstractMethod(); @@ -302,7 +331,7 @@ private protected override void AbstractMethod() { } } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -310,7 +339,7 @@ private protected override void AbstractMethod() public async Task TestShouldNotRemoveFileAsync() { var source = - """ +@" file class MyFile { int Prop { get; set; } @@ -320,10 +349,10 @@ file struct MyFileStruct { int Prop { get; set; } } -"""; +"; var expected = - """ +@" file class MyFile { private int Prop { get; set; } @@ -333,7 +362,7 @@ file struct MyFileStruct { private int Prop { get; set; } } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -341,20 +370,20 @@ file struct MyFileStruct public async Task ShouldAddDelegateAccessorAsync() { var source = - """ +@" class MyDelegate { delegate int PerformCalculation(int x, int y); } -"""; +"; var expected = - """ +@" internal class MyDelegate { private delegate int PerformCalculation(int x, int y); } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -362,20 +391,20 @@ internal class MyDelegate public async Task ShouldAddEventAccessorAsync() { var source = - """ +@" class MyEvent { event MyEventHandler MyEvent; } -"""; +"; var expected = - """ +@" internal class MyEvent { private event MyEventHandler MyEvent; } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -383,22 +412,22 @@ internal class MyEvent public async Task ShouldAddEnumAccessorAsync() { var source = - """ +@" enum MyEnum { Some = 0, None = 1, } -"""; +"; var expected = - """ +@" internal enum MyEnum { Some = 0, None = 1, } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -406,7 +435,7 @@ internal enum MyEnum public async Task ShouldAddNestedEnumAccessorAsync() { var source = - """ +@" class MyClass { enum MyEnum @@ -415,10 +444,10 @@ enum MyEnum None = 1, } } -"""; +"; var expected = - """ +@" internal class MyClass { private enum MyEnum @@ -427,7 +456,7 @@ private enum MyEnum None = 1, } } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -435,24 +464,24 @@ private enum MyEnum public async Task ShouldAddInterfaceAccessorAsync() { var source = - """ +@" interface IMyInterface { int MyProp { get; set; } void Do(); } -"""; +"; var expected = - """ +@" internal interface IMyInterface { int MyProp { get; set; } void Do(); } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -460,7 +489,7 @@ internal interface IMyInterface public async Task ShouldAddNestedInterfaceAccessorAsync() { var source = - """ +@" class MyClass { interface IMyInterface @@ -470,10 +499,10 @@ interface IMyInterface void Do(); } } -"""; +"; var expected = - """ +@" internal class MyClass { private interface IMyInterface @@ -483,7 +512,7 @@ private interface IMyInterface void Do(); } } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -491,20 +520,20 @@ private interface IMyInterface public async Task ShouldAddFieldAccessorAsync() { var source = - """ +@" class MyClass { int _number; } -"""; +"; var expected = - """ +@" internal class MyClass { private int _number; } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -512,7 +541,7 @@ internal class MyClass public async Task ShouldNotChangeInvalidSyntaxAsync() { var source = - """ +@" class ITemp { void Do() @@ -520,10 +549,10 @@ void Do() int MyProperty { get; set; } } } -"""; +"; var expected = - """ +@" internal class ITemp { private void Do() @@ -531,7 +560,7 @@ private void Do() int MyProperty { get; set; } } } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } @@ -539,7 +568,7 @@ private void Do() public async Task ShouldNotChangeInterfaceDescendantsAsync() { var source = - """ +@" interface IInterface { class C @@ -550,10 +579,10 @@ class D } } } -"""; +"; var expected = - """ +@" internal interface IInterface { class C @@ -564,7 +593,7 @@ class D } } } -"""; +"; await testWorkspace.VerifyCleanupAsync(source, expected); } } diff --git a/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs b/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs index 745530774..626feeb06 100644 --- a/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs +++ b/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs @@ -1,6 +1,5 @@ using CodeMaidShared.Logic.Cleaning; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Threading.Tasks; @@ -14,14 +13,14 @@ public async Task VerifyCleanupAsync(string input, string expected) var document = SetDocument(input); var syntaxTree = await Document.GetSyntaxRootAsync(); - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); var semanticModel = await Document.GetSemanticModelAsync(); - var modifierLogic = new RoslynInsertExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - var rewriter = new RoslynCleanup() - { - MemberWriter = modifierLogic.ProcessMember - }; + var rewriter = new RoslynCleaner(); + InsertExplicitAccessorMiddleware.Initialize(rewriter, semanticModel); + InsertNodePaddingMiddleware.Initialize(rewriter); + + InsertTokenPaddingMiddleware.Initialize(rewriter); + var result = rewriter.Process(syntaxTree, Workspace); Assert.AreEqual(expected, result.ToFullString()); @@ -30,11 +29,11 @@ public async Task VerifyCleanupAsync(string input, string expected) public TestWorkspace() { var source = - """ +@" public class ThisShouldAppear { } -"""; +"; Workspace = new AdhocWorkspace(); diff --git a/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj b/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj index f2d563d81..82f05674a 100644 --- a/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj +++ b/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj @@ -18,7 +18,7 @@ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest - latest + 10.0 @@ -54,6 +54,7 @@ + @@ -66,7 +67,7 @@ - + diff --git a/CodeMaid.VS2022/CodeMaid.VS2022.csproj b/CodeMaid.VS2022/CodeMaid.VS2022.csproj index 8e3cedd87..97c213d23 100644 --- a/CodeMaid.VS2022/CodeMaid.VS2022.csproj +++ b/CodeMaid.VS2022/CodeMaid.VS2022.csproj @@ -27,7 +27,7 @@ Program $(DevEnvDir)devenv.exe /rootsuffix Exp - latest + 10.0 true diff --git a/CodeMaid.config b/CodeMaid.config index 90e2efd29..ad5551e15 100644 --- a/CodeMaid.config +++ b/CodeMaid.config @@ -1,27 +1,27 @@ - - -
- - - - - - \.Designer\.cs$||\.Designer\.vb$||\.resx$||CodeMaid.IntegrationTests\\.*\\Data\\||CodeMaid\\CodeMaid.cs||CodeMaid\\source.extension.cs - - - False - - - False - - - True - - - True - - - + + +
+ + + + + + True + + + True + + + True + + + True + + + \ No newline at end of file diff --git a/CodeMaid/CodeMaid.csproj b/CodeMaid/CodeMaid.csproj index 98d8e6c65..3dd6b4043 100644 --- a/CodeMaid/CodeMaid.csproj +++ b/CodeMaid/CodeMaid.csproj @@ -27,7 +27,7 @@ Program $(DevEnvDir)devenv.exe /rootsuffix Exp - latest + 10.0 diff --git a/CodeMaidShared/CodeMaidShared.projitems b/CodeMaidShared/CodeMaidShared.projitems index ab6939e09..cbfc04b6a 100644 --- a/CodeMaidShared/CodeMaidShared.projitems +++ b/CodeMaidShared/CodeMaidShared.projitems @@ -83,21 +83,27 @@ - + + - - + + + + + + + - - + + @@ -392,6 +398,6 @@ - + \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs index bb81908e4..f9d2bc466 100644 --- a/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs +++ b/CodeMaidShared/Logic/Cleaning/CodeCleanupManager.cs @@ -1,5 +1,7 @@ using CodeMaidShared.Logic.Cleaning; using EnvDTE; +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.Shell; using SteveCadwallader.CodeMaid.Helpers; using SteveCadwallader.CodeMaid.Logic.Formatting; using SteveCadwallader.CodeMaid.Logic.Reorganizing; @@ -9,6 +11,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Document = EnvDTE.Document; +using TextDocument = EnvDTE.TextDocument; + namespace SteveCadwallader.CodeMaid.Logic.Cleaning { @@ -274,48 +279,48 @@ private void RunCodeCleanupCSharp(Document document) _removeWhitespaceLogic.RemoveMultipleConsecutiveBlankLines(textDocument); // Perform insertion of blank line padding cleanup. - _insertBlankLinePaddingLogic.InsertPaddingBeforeRegionTags(regions); - _insertBlankLinePaddingLogic.InsertPaddingAfterRegionTags(regions); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeRegionTags(regions); + //_insertBlankLinePaddingLogic.InsertPaddingAfterRegionTags(regions); - _insertBlankLinePaddingLogic.InsertPaddingBeforeEndRegionTags(regions); - _insertBlankLinePaddingLogic.InsertPaddingAfterEndRegionTags(regions); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeEndRegionTags(regions); + //_insertBlankLinePaddingLogic.InsertPaddingAfterEndRegionTags(regions); - _insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(usingStatementsThatStartBlocks); - _insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(usingStatementsThatEndBlocks); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(usingStatementsThatStartBlocks); + //_insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(usingStatementsThatEndBlocks); - _insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(namespaces); - _insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(namespaces); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(namespaces); + //_insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(namespaces); - _insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(classes); - _insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(classes); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(classes); + //_insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(classes); - _insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(delegates); - _insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(delegates); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(delegates); + //_insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(delegates); - _insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(enumerations); - _insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(enumerations); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(enumerations); + //_insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(enumerations); - _insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(events); - _insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(events); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(events); + //_insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(events); - _insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(fields); - _insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(fields); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(fields); + //_insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(fields); - _insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(interfaces); - _insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(interfaces); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(interfaces); + //_insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(interfaces); - _insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(methods); - _insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(methods); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(methods); + //_insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(methods); - _insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(properties); - _insertBlankLinePaddingLogic.InsertPaddingBetweenMultiLinePropertyAccessors(properties); - _insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(properties); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(properties); + //_insertBlankLinePaddingLogic.InsertPaddingBetweenMultiLinePropertyAccessors(properties); + //_insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(properties); - _insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(structs); - _insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(structs); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeCodeElements(structs); + //_insertBlankLinePaddingLogic.InsertPaddingAfterCodeElements(structs); - _insertBlankLinePaddingLogic.InsertPaddingBeforeCaseStatements(textDocument); - _insertBlankLinePaddingLogic.InsertPaddingBeforeSingleLineComments(textDocument); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeCaseStatements(textDocument); + //_insertBlankLinePaddingLogic.InsertPaddingBeforeSingleLineComments(textDocument); // Perform insertion of explicit access modifier cleanup. //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnDelegates(delegates); @@ -328,7 +333,7 @@ private void RunCodeCleanupCSharp(Document document) //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnMethods(methods); //_insertExplicitAccessModifierLogic.InsertExplicitAccessModifiersOnProperties(properties); - RoslynCleanup.BuildAndrun(_package); + RoslynBuildAndRun(_package); // Perform insertion of whitespace cleanup. @@ -344,6 +349,38 @@ private void RunCodeCleanupCSharp(Document document) _commentFormatLogic.FormatComments(textDocument); } + public static void RoslynBuildAndRun(AsyncPackage package) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + Global.Package = package; + + var document = Global.GetActiveDocument(package); + + if (document == null || !document.TryGetSyntaxRoot(out SyntaxNode root)) + { + throw new InvalidOperationException(); + } + + var semanticModel = document.GetSemanticModelAsync().Result; + + var cleaner = new RoslynCleaner(); + + InsertExplicitAccessorMiddleware.Initialize(cleaner, semanticModel); + InsertNodePaddingMiddleware.Initialize(cleaner); + + InsertTokenPaddingMiddleware.Initialize(cleaner); + + var newRoot = cleaner.Process(root, Global.GetWorkspace(package)); + + document = document.WithSyntaxRoot(newRoot); + var success = Global.GetWorkspace(package).TryApplyChanges(document.Project.Solution); + if (!success) + { + OutputWindowHelper.DiagnosticWriteLine("Error applying roslyn cleanup changes."); + } + } + /// /// Attempts to run code cleanup on the specified VB.Net document. /// diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/Global.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/Global.cs index 3c0cd0520..99acbb97c 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/Global.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/Global.cs @@ -12,37 +12,37 @@ namespace SteveCadwallader.CodeMaid.Logic.Cleaning { internal static class Global { - static public AsyncPackage Package; + public static AsyncPackage Package; - static public T GetService() - => (T)Package?.GetServiceAsync(typeof(T))?.Result; + public static T GetService(AsyncPackage package) + => (T)package?.GetServiceAsync(typeof(T))?.Result; - static public DteDocument GetActiveDteDocument() + public static DteDocument GetActiveDteDocument(AsyncPackage package) { ThreadHelper.ThrowIfNotOnUIThread(); - dynamic dte = GetService(); + dynamic dte = GetService(package); return (DteDocument)dte.ActiveDocument; } static IVsStatusbar Statusbar; - internal static void SetStatusMessage(string message) + internal static void SetStatusMessage(AsyncPackage package, string message) { if (Statusbar == null) { - Statusbar = GetService(); + Statusbar = GetService(package); // StatusBar = Package.GetGlobalService(typeof(IVsStatusbar)) as IVsStatusbar; } - + ThreadHelper.ThrowIfNotOnUIThread(); Statusbar.SetText(message); } - public static Document GetActiveDocument() + public static Document GetActiveDocument(AsyncPackage package) { ThreadHelper.ThrowIfNotOnUIThread(); - Solution solution = Workspace.CurrentSolution; - string activeDocPath = GetActiveDteDocument()?.FullName; + Solution solution = GetWorkspace(package).CurrentSolution; + string activeDocPath = GetActiveDteDocument(package)?.FullName; if (activeDocPath != null) return solution.Projects @@ -55,17 +55,14 @@ public static Document GetActiveDocument() private static VisualStudioWorkspace workspace = null; - static public VisualStudioWorkspace Workspace + public static VisualStudioWorkspace GetWorkspace(AsyncPackage package) { - get + if (workspace == null) { - if (workspace == null) - { - IComponentModel componentModel = GetService() as IComponentModel; - workspace = componentModel.GetService(); - } - return workspace; + IComponentModel componentModel = GetService(package) as IComponentModel; + workspace = componentModel.GetService(); } + return workspace; } } } \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/IRoslynNodeMiddleware.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/IRoslynNodeMiddleware.cs new file mode 100644 index 000000000..5051408b4 --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/IRoslynNodeMiddleware.cs @@ -0,0 +1,13 @@ +using Microsoft.CodeAnalysis; +using System; + +namespace CodeMaidShared.Logic.Cleaning +{ + internal interface IRoslynNodeMiddleware + { + public SyntaxNode Invoke(SyntaxNode original, SyntaxNode newNode); + + // TODO this is messy, don't know how else to do it. + public void SetNodeDelegate(Func next); + } +} diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/IRoslynTokenMiddleware.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/IRoslynTokenMiddleware.cs new file mode 100644 index 000000000..207f4830d --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/IRoslynTokenMiddleware.cs @@ -0,0 +1,13 @@ +using Microsoft.CodeAnalysis; +using System; + +namespace CodeMaidShared.Logic.Cleaning +{ + internal interface IRoslynTokenMiddleware + { + public SyntaxToken Invoke(SyntaxToken original, SyntaxToken newToken); + + // TODO this is messy, don't know how else to do it. + public void SetTokenDelegate(Func next); + } +} diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/InsertExplicitAccessorMiddleware.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/InsertExplicitAccessorMiddleware.cs new file mode 100644 index 000000000..894db824d --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/InsertExplicitAccessorMiddleware.cs @@ -0,0 +1,60 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Editing; +using System; + +namespace CodeMaidShared.Logic.Cleaning +{ + internal class InsertExplicitAccessorMiddleware : IRoslynNodeMiddleware + { + private RoslynInsertExplicitAccessModifierLogic _insertAccess; + public InsertExplicitAccessorMiddleware(SemanticModel semanticModel) + { + _insertAccess = new RoslynInsertExplicitAccessModifierLogic(semanticModel); + } + private Func Next { get; set; } + + // Use this messy functions to ensure that the current node is not a descendant of an interface. + // This is to mimic the recursive CSharpAddAccessibilityModifiersDiagnosticAnalyzer.ProcessMemberDeclaration + // search where any non structs/classes are ignored. + // FindAncestorOrSelf might help but would be slower. + // Dont terminate on finding an interface in case I want to roslynize more cleanup functions. + private bool InsideInterface { get; set; } + + public static RoslynCleaner Initialize(RoslynCleaner cleanup, SemanticModel model) + { + cleanup.AddNodeMiddleware(new InsertExplicitAccessorMiddleware(model)); + + return cleanup; + } + + public SyntaxNode Invoke(SyntaxNode original, SyntaxNode newNode) + { + if (original == null) + { + return original; + } + + var inInterface = InsideInterface; + + if (original.IsKind(SyntaxKind.InterfaceDeclaration)) + InsideInterface = true; + + // Might have to account for namespaces here. + newNode = Next(original, newNode); + + if (inInterface == false) + { + newNode = _insertAccess.TryAddExplicitModifier(original, newNode); + } + + InsideInterface = inInterface; + return newNode; + } + + public void SetNodeDelegate(Func next) + { + Next = next; + } + } +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/InsertNodePaddingMiddleware.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/InsertNodePaddingMiddleware.cs new file mode 100644 index 000000000..1284d0336 --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/InsertNodePaddingMiddleware.cs @@ -0,0 +1,43 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; + +namespace CodeMaidShared.Logic.Cleaning +{ + internal class InsertNodePaddingMiddleware : IRoslynNodeMiddleware + { + public InsertNodePaddingMiddleware() { } + + private bool ShouldAddPadding { get; set; } + private bool IsFirstNode { get; set; } + private Func Next { get; set; } + + public static RoslynCleaner Initialize(RoslynCleaner cleanup) + { + cleanup.AddNodeMiddleware(new InsertNodePaddingMiddleware()); + return cleanup; + } + + public SyntaxNode Invoke(SyntaxNode original, SyntaxNode newNode) + { + var shouldAddPadding = ShouldAddPadding; + var isFirst = IsFirstNode; + + ShouldAddPadding = false; + IsFirstNode = true; + + newNode = Next(original, newNode); + + (newNode, ShouldAddPadding) = RoslynInsertPaddingLogic.TryAddPadding(original, newNode, shouldAddPadding, isFirst); + + // Have to ignore inheritance/type/attribute nodes until the first member node. + IsFirstNode = isFirst ? newNode is TypeParameterListSyntax or AttributeArgumentListSyntax or BaseListSyntax or TypeParameterConstraintClauseSyntax : false; + return newNode; + } + + public void SetNodeDelegate(Func next) + { + Next = next; + } + } +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/InsertTokenPaddingMiddleware.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/InsertTokenPaddingMiddleware.cs new file mode 100644 index 000000000..d1f48bcfb --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/InsertTokenPaddingMiddleware.cs @@ -0,0 +1,93 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using SteveCadwallader.CodeMaid.Logic.Cleaning; +using SteveCadwallader.CodeMaid.Properties; +using System; +using System.Linq; + +namespace CodeMaidShared.Logic.Cleaning +{ + internal class InsertTokenPaddingMiddleware : IRoslynTokenMiddleware + { + public bool BeforeIsOpen { get; set; } = false; + private Func Next { get; set; } + + public SyntaxToken Invoke(SyntaxToken token, SyntaxToken newToken) + { + var beforeIsOpenToken = BeforeIsOpen; + + if (!token.IsKind(SyntaxKind.None)) + { + BeforeIsOpen = token.IsKind(SyntaxKind.OpenBraceToken); + } + newToken = Next(token, newToken); + + // Read trivia: + // Assume that leading trivia must start on a new line. + // Valid line is a single line with white space and endofline, preceeded by a non blank line. + // Also check that + return TryPadTrivia(newToken, beforeIsOpenToken); + } + public void SetTokenDelegate(Func next) + { + Next = next; + } + + public static void Initialize(RoslynCleaner roslynCleanup) + { + var tokenPadding = new InsertTokenPaddingMiddleware(); + roslynCleanup.AddTokenMiddleware(tokenPadding); + } + + // TODO Revisit logic, cleanup, refactor. Consider calculating the trivia lines on the go while inserting padding. + // Might not work if all trivia is on the same line. + private static SyntaxToken TryPadTrivia(SyntaxToken newToken, bool priorIsOpen) + { + var trivia = newToken.LeadingTrivia.ToList(); + + var (triviaLines, last) = RoslynExtensions.ReadTriviaLines(trivia); + triviaLines.Add((newToken.Kind(), last)); + + // Set previous line type to either open brace or a place holder. + var prevLine = priorIsOpen ? SyntaxKind.OpenBraceToken : SyntaxKind.BadDirectiveTrivia; + + var insertCount = 0; + + for (int i = 0; i < triviaLines.Count; i++) + { + var (currentLine, pos) = triviaLines[i]; + if (TryAddPadding(prevLine, currentLine)) + { + trivia.Insert(pos + insertCount, SyntaxFactory.EndOfLine(Environment.NewLine)); + insertCount++; + } + prevLine = currentLine; + } + + if (insertCount > 0) + { + return newToken.WithLeadingTrivia(trivia); + } + + return newToken; + } + + private static bool TryAddPadding(SyntaxKind prior, SyntaxKind current) + { + return current switch + { + SyntaxKind.RegionDirectiveTrivia when prior is not (SyntaxKind.WhitespaceTrivia or SyntaxKind.OpenBraceToken) &&Settings.Default.Cleaning_InsertBlankLinePaddingBeforeRegionTags => true, + + not (SyntaxKind.WhitespaceTrivia or SyntaxKind.CloseBraceToken) when prior == SyntaxKind.RegionDirectiveTrivia &&Settings.Default.Cleaning_InsertBlankLinePaddingAfterRegionTags => true, + + SyntaxKind.EndRegionDirectiveTrivia when prior is not (SyntaxKind.WhitespaceTrivia or SyntaxKind.OpenBraceToken) &&Settings.Default.Cleaning_InsertBlankLinePaddingBeforeEndRegionTags => true, + + not (SyntaxKind.WhitespaceTrivia or SyntaxKind.CloseBraceToken) when prior == SyntaxKind.EndRegionDirectiveTrivia &&Settings.Default.Cleaning_InsertBlankLinePaddingAfterEndRegionTags => true, + + SyntaxKind.SingleLineCommentTrivia when prior is not (SyntaxKind.SingleLineCommentTrivia or SyntaxKind.SingleLineDocumentationCommentTrivia or SyntaxKind.WhitespaceTrivia or SyntaxKind.OpenBraceToken) => true, + + _ => false, + }; + } + } +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/RoslynInsertExplicitAccessModifierLogic.cs similarity index 68% rename from CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs rename to CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/RoslynInsertExplicitAccessModifierLogic.cs index e7c18bc50..8264c6f64 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynInsertExplicitAccessModifierLogic.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/RoslynInsertExplicitAccessModifierLogic.cs @@ -1,7 +1,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Editing; using SteveCadwallader.CodeMaid.Logic.Cleaning; using SteveCadwallader.CodeMaid.Properties; using System; @@ -16,64 +15,41 @@ internal class RoslynInsertExplicitAccessModifierLogic #region Fields private readonly SemanticModel _semanticModel; - private readonly SyntaxGenerator _syntaxGenerator; #endregion Fields #region Constructors - /// - /// The singleton instance of the class. - /// - //private static RoslynInsertExplicitAccessModifierLogic _instance; - - ///// - ///// Gets an instance of the class. - ///// - ///// An instance of the class. - //internal static RoslynInsertExplicitAccessModifierLogic GetInstance(AsyncPackage package) - //{ - // return new RoslynInsertExplicitAccessModifierLogic(semanticModel, syntaxGenerator); - //} - /// /// Initializes a new instance of the class. /// - public RoslynInsertExplicitAccessModifierLogic(SemanticModel semanticModel, SyntaxGenerator syntaxGenerator) + public RoslynInsertExplicitAccessModifierLogic(SemanticModel semanticModel) { _semanticModel = semanticModel; - _syntaxGenerator = syntaxGenerator; } #endregion Constructors - public static RoslynCleanup Initialize(RoslynCleanup cleanup, SemanticModel model, SyntaxGenerator generator) + public SyntaxNode TryAddExplicitModifier(SyntaxNode original, SyntaxNode newNode) { - var explicitLogic = new RoslynInsertExplicitAccessModifierLogic(model, generator); - cleanup.MemberWriter = explicitLogic.ProcessMember; - return cleanup; - } - - public SyntaxNode ProcessMember(SyntaxNode original, SyntaxNode node) - { - return node switch + return newNode switch { - DelegateDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnDelegates => AddAccessibility(original, node), - EventFieldDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnEvents => AddAccessibility(original, node), - EnumDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnEnumerations => AddAccessibility(original, node), - FieldDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnFields => AddAccessibility(original, node), - InterfaceDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnInterfaces => AddAccessibility(original, node), + DelegateDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnDelegates => AddAccessibility(original, newNode), + EventFieldDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnEvents => AddAccessibility(original, newNode), + EnumDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnEnumerations => AddAccessibility(original, newNode), + FieldDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnFields => AddAccessibility(original, newNode), + InterfaceDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnInterfaces => AddAccessibility(original, newNode), - PropertyDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties => AddAccessibility(original, node), - MethodDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods => AddAccessibility(original, node), + PropertyDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnProperties => AddAccessibility(original, newNode), + MethodDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnMethods => AddAccessibility(original, newNode), - ClassDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses => AddAccessibility(original, node), - StructDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnStructs => AddAccessibility(original, node), + ClassDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnClasses => AddAccessibility(original, newNode), + StructDeclarationSyntax when Settings.Default.Cleaning_InsertExplicitAccessModifiersOnStructs => AddAccessibility(original, newNode), //RecordDeclarationSyntax when node.IsKind(SyntaxKind.RecordDeclaration) && Settings.Default.Cleaning_InsertExplicitAccessModifiersOnRecords => AddAccessibility(original, node), //RecordDeclarationSyntax when node.IsKind(SyntaxKind.RecordStructDeclaration) && Settings.Default.Cleaning_InsertExplicitAccessModifiersOnRecordStructs => AddAccessibility(original, node), - _ => node, + _ => newNode, }; } @@ -94,7 +70,6 @@ private SyntaxNode AddAccessibility(SyntaxNode original, SyntaxNode newNode) var preferredAccessibility = AddAccessibilityModifiersHelpers.GetPreferredAccessibility(symbol); return InternalGenerator.WithAccessibility(newNode, preferredAccessibility); - //return _syntaxGenerator.WithAccessibility(newNode, preferredAccessibility); } private static SyntaxNode MapToDeclarator(SyntaxNode node) diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/RoslynInsertPaddingLogic.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/RoslynInsertPaddingLogic.cs new file mode 100644 index 000000000..bcef61ac2 --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/Handlers/RoslynInsertPaddingLogic.cs @@ -0,0 +1,161 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using SteveCadwallader.CodeMaid.Logic.Cleaning; +using SteveCadwallader.CodeMaid.Properties; + +namespace CodeMaidShared.Logic.Cleaning +{ + /// + /// A class for encapsulating insertion of blank line padding logic. + /// + internal static class RoslynInsertPaddingLogic + { + public static (SyntaxNode node, bool requiresPaddingAfter) TryAddPadding(SyntaxNode original, SyntaxNode newNode, bool previousRequiresPaddingStart, bool isFirst) + { + // Assume that whitespace blank lines are considered valid padding. + // Add padding to start + // If addPaddingStart + // when no padding at first leading trivia + // when not first node. + // Else + // when settings require padding + // when does not contain a new line in any of the trivia + // when not first node + + // Return addTrailingPadding when setting.AddPaddingEnd + + // If Settings require padding at the end set true + + // Cannot add padding to end, if padding is needed let the next node add padding. + bool requiresPaddingAfter = RequiresPaddingAfter(newNode); + bool shouldAddPaddingBefore = RequiresPaddingBefore(newNode); + + if (isFirst || StartHasPadding(newNode)) + { + return (newNode, requiresPaddingAfter); + } + + var containsAnyPadding = HasAnyPadding(newNode); + + if (previousRequiresPaddingStart || (shouldAddPaddingBefore && !containsAnyPadding)) + { + newNode = InternalGenerator.AddBlankLineToStart(newNode); + } + + return (newNode, requiresPaddingAfter); + } + + private static bool RequiresPaddingBefore(SyntaxNode newNode) + { + bool shouldAddPaddingBefore = (newNode.Kind(), Settings.Default) switch + { + // Pad multiline accessors, assume that other nodes cannot be inside an accessor list. + (SyntaxKind.GetAccessorDeclaration or SyntaxKind.SetAccessorDeclaration or SyntaxKind.InitAccessorDeclaration, { Cleaning_InsertBlankLinePaddingBetweenPropertiesMultiLineAccessors: true }) when newNode.SpansMultipleLines() => true, + + (SyntaxKind.UsingStatement, { Cleaning_InsertBlankLinePaddingBeforeUsingStatementBlocks: true }) => true, + (SyntaxKind.NamespaceDeclaration or SyntaxKind.FileScopedNamespaceDeclaration, { Cleaning_InsertBlankLinePaddingBeforeNamespaces: true }) => true, + (SyntaxKind.DefaultSwitchLabel or SyntaxKind.CaseSwitchLabel, { Cleaning_InsertBlankLinePaddingBeforeCaseStatements: true }) => true, + (SyntaxKind.ClassDeclaration, { Cleaning_InsertBlankLinePaddingBeforeClasses: true }) => true, + (SyntaxKind.DelegateDeclaration, { Cleaning_InsertBlankLinePaddingBeforeDelegates: true }) => true, + (SyntaxKind.EnumDeclaration, { Cleaning_InsertBlankLinePaddingBeforeEnumerations: true }) => true, + (SyntaxKind.EventDeclaration, { Cleaning_InsertBlankLinePaddingBeforeEvents: true }) => true, + + (SyntaxKind.FieldDeclaration, { Cleaning_InsertBlankLinePaddingBeforeFieldsMultiLine: true }) when newNode.SpansMultipleLines() => true, + (SyntaxKind.FieldDeclaration, { Cleaning_InsertBlankLinePaddingBeforeFieldsSingleLine: true }) when newNode.SpansMultipleLines() == false => true, + + (SyntaxKind.InterfaceDeclaration, { Cleaning_InsertBlankLinePaddingBeforeInterfaces: true }) => true, + (SyntaxKind.MethodDeclaration, { Cleaning_InsertBlankLinePaddingBeforeMethods: true }) => true, + + (SyntaxKind.PropertyDeclaration, { Cleaning_InsertBlankLinePaddingBeforePropertiesMultiLine: true }) when newNode.SpansMultipleLines() => true, + (SyntaxKind.PropertyDeclaration, { Cleaning_InsertBlankLinePaddingBeforePropertiesSingleLine: true }) when newNode.SpansMultipleLines() == false => true, + + (SyntaxKind.StructDeclaration, { Cleaning_InsertBlankLinePaddingBeforeStructs: true }) => true, + + (SyntaxKind.RecordDeclaration, { Cleaning_InsertBlankLinePaddingBeforeStructs: true }) => true, + (SyntaxKind.RecordStructDeclaration, { Cleaning_InsertBlankLinePaddingBeforeStructs: true }) => true, + + _ => false, + }; + return shouldAddPaddingBefore; + } + + private static bool RequiresPaddingAfter(SyntaxNode newNode) + { + bool shouldAddPaddingAfter = (newNode.Kind(), Settings.Default) switch + { + // Pad multiline accessors, assume that other nodes cannot be inside an accessor list. + (SyntaxKind.GetAccessorDeclaration or SyntaxKind.SetAccessorDeclaration or SyntaxKind.InitAccessorDeclaration, { Cleaning_InsertBlankLinePaddingBetweenPropertiesMultiLineAccessors: true }) when newNode.SpansMultipleLines() => true, + + (SyntaxKind.UsingStatement, { Cleaning_InsertBlankLinePaddingAfterUsingStatementBlocks: true }) => true, + (SyntaxKind.NamespaceDeclaration or SyntaxKind.FileScopedNamespaceDeclaration, { Cleaning_InsertBlankLinePaddingAfterNamespaces: true }) => true, + (SyntaxKind.ClassDeclaration, { Cleaning_InsertBlankLinePaddingAfterClasses: true }) => true, + (SyntaxKind.DelegateDeclaration, { Cleaning_InsertBlankLinePaddingAfterDelegates: true }) => true, + (SyntaxKind.EnumDeclaration, { Cleaning_InsertBlankLinePaddingAfterEnumerations: true }) => true, + (SyntaxKind.EventDeclaration, { Cleaning_InsertBlankLinePaddingAfterEvents: true }) => true, + + (SyntaxKind.FieldDeclaration, { Cleaning_InsertBlankLinePaddingAfterFieldsSingleLine: true }) when newNode.SpansMultipleLines() => true, + (SyntaxKind.FieldDeclaration, { Cleaning_InsertBlankLinePaddingAfterFieldsSingleLine: true }) when newNode.SpansMultipleLines() == false => true, + + (SyntaxKind.InterfaceDeclaration, { Cleaning_InsertBlankLinePaddingAfterInterfaces: true }) => true, + (SyntaxKind.MethodDeclaration, { Cleaning_InsertBlankLinePaddingAfterMethods: true }) => true, + + (SyntaxKind.PropertyDeclaration, { Cleaning_InsertBlankLinePaddingAfterPropertiesMultiLine: true }) when newNode.SpansMultipleLines() => true, + (SyntaxKind.PropertyDeclaration, { Cleaning_InsertBlankLinePaddingAfterPropertiesSingleLine: true }) when newNode.SpansMultipleLines() == false => true, + + (SyntaxKind.StructDeclaration, { Cleaning_InsertBlankLinePaddingAfterStructs: true }) => true, + + (SyntaxKind.RecordDeclaration, { Cleaning_InsertBlankLinePaddingAfterStructs: true }) => true, + (SyntaxKind.RecordStructDeclaration, { Cleaning_InsertBlankLinePaddingAfterStructs: true }) => true, + + _ => false, + }; + return shouldAddPaddingAfter; + } + + private static bool StartHasPadding(SyntaxNode node) + { + foreach (var item in node.GetLeadingTrivia()) + { + var kind = item.Kind(); + if (kind == SyntaxKind.WhitespaceTrivia) + { + continue; + } + if (kind == SyntaxKind.EndOfLineTrivia) + { + return true; + } + + return false; + } + + return false; + } + + private static bool HasAnyPadding(SyntaxNode node) + { + var isPadding = true; + foreach (var item in node.GetLeadingTrivia()) + { + var kind = item.Kind(); + if (kind == SyntaxKind.WhitespaceTrivia) + { + continue; + } + if (kind == SyntaxKind.EndOfLineTrivia) + { + if (isPadding) + { + return true; + } + isPadding = true; + continue; + } + + isPadding = false; + } + + return false; + } + } +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/InternalGenerator.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/InternalGenerator.cs index 3bde09c0d..5acbb820f 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/InternalGenerator.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/InternalGenerator.cs @@ -11,12 +11,6 @@ namespace SteveCadwallader.CodeMaid.Logic.Cleaning { internal static class InternalGenerator { - private static SyntaxTokenList GetModifierTokens(SyntaxNode declaration) - => CSharpAccessibilityFacts.GetModifierTokens(declaration); - - private static void GetAccessibilityAndModifiers(SyntaxTokenList modifierList, out Accessibility accessibility, out DeclarationModifiers modifiers, out bool isDefault) - => CSharpAccessibilityFacts.GetAccessibilityAndModifiers(modifierList, out accessibility, out modifiers, out isDefault); - public static SyntaxNode WithAccessibility(SyntaxNode declaration, Accessibility accessibility) { if (!CSharpAccessibilityFacts.CanHaveAccessibility(declaration) && @@ -27,8 +21,9 @@ public static SyntaxNode WithAccessibility(SyntaxNode declaration, Accessibility return Isolate(declaration, d => { - var tokens = GetModifierTokens(d); - GetAccessibilityAndModifiers(tokens, out _, out var modifiers, out _); + var a = d as TypeDeclarationSyntax; + var tokens = CSharpAccessibilityFacts.GetModifierTokens(d); + CSharpAccessibilityFacts.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. @@ -48,6 +43,16 @@ public static SyntaxNode WithAccessibility(SyntaxNode declaration, Accessibility }); } + public static SyntaxNode AddBlankLineToStart(SyntaxNode declaration) + { + var originalTrivia = declaration.GetLeadingTrivia(); + var newTrivia = originalTrivia.Insert(0, SyntaxFactory.EndOfLine(Environment.NewLine)); + + var newNode = declaration.WithLeadingTrivia(newTrivia); + + return newNode; + } + private static SyntaxNode SetModifierTokens(SyntaxNode declaration, SyntaxTokenList modifiers) => declaration switch { @@ -82,186 +87,6 @@ private static bool Any(SyntaxTokenList original, int rawKind) return false; } - private static SyntaxTokenList AsModifierList(Accessibility accessibility, DeclarationModifiers modifiers, SyntaxKind kind) - => AsModifierList(accessibility, GetAllowedModifiers(kind) & modifiers); - - private static readonly DeclarationModifiers s_fieldModifiers = - DeclarationModifiers.Const | - DeclarationModifiers.New | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Required | - DeclarationModifiers.Static | - DeclarationModifiers.Unsafe | - DeclarationModifiers.Volatile; - - private static readonly DeclarationModifiers s_methodModifiers = - DeclarationModifiers.Abstract | - DeclarationModifiers.Async | - DeclarationModifiers.Extern | - DeclarationModifiers.New | - DeclarationModifiers.Override | - DeclarationModifiers.Partial | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Sealed | - DeclarationModifiers.Static | - DeclarationModifiers.Virtual | - DeclarationModifiers.Unsafe; - - private static readonly DeclarationModifiers s_constructorModifiers = - DeclarationModifiers.Extern | - DeclarationModifiers.Static | - DeclarationModifiers.Unsafe; - - private static readonly DeclarationModifiers s_destructorModifiers = DeclarationModifiers.Unsafe; - private static readonly DeclarationModifiers s_propertyModifiers = - DeclarationModifiers.Abstract | - DeclarationModifiers.Extern | - DeclarationModifiers.New | - DeclarationModifiers.Override | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Required | - DeclarationModifiers.Sealed | - DeclarationModifiers.Static | - DeclarationModifiers.Virtual | - DeclarationModifiers.Unsafe; - - private static readonly DeclarationModifiers s_eventModifiers = - DeclarationModifiers.Abstract | - DeclarationModifiers.Extern | - DeclarationModifiers.New | - DeclarationModifiers.Override | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Sealed | - DeclarationModifiers.Static | - DeclarationModifiers.Virtual | - DeclarationModifiers.Unsafe; - - private static readonly DeclarationModifiers s_eventFieldModifiers = - DeclarationModifiers.New | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Static | - DeclarationModifiers.Unsafe; - - private static readonly DeclarationModifiers s_indexerModifiers = - DeclarationModifiers.Abstract | - DeclarationModifiers.Extern | - DeclarationModifiers.New | - DeclarationModifiers.Override | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Sealed | - DeclarationModifiers.Static | - DeclarationModifiers.Virtual | - DeclarationModifiers.Unsafe; - - private static readonly DeclarationModifiers s_classModifiers = - DeclarationModifiers.Abstract | - DeclarationModifiers.New | - DeclarationModifiers.Partial | - DeclarationModifiers.Sealed | - DeclarationModifiers.Static | - DeclarationModifiers.Unsafe | - DeclarationModifiers.File; - - private static readonly DeclarationModifiers s_recordModifiers = - DeclarationModifiers.Abstract | - DeclarationModifiers.New | - DeclarationModifiers.Partial | - DeclarationModifiers.Sealed | - DeclarationModifiers.Unsafe | - DeclarationModifiers.File; - - private static readonly DeclarationModifiers s_structModifiers = - DeclarationModifiers.New | - DeclarationModifiers.Partial | - DeclarationModifiers.ReadOnly | - DeclarationModifiers.Ref | - DeclarationModifiers.Unsafe | - DeclarationModifiers.File; - - private static readonly DeclarationModifiers s_interfaceModifiers = DeclarationModifiers.New | DeclarationModifiers.Partial | DeclarationModifiers.Unsafe | DeclarationModifiers.File; - private static readonly DeclarationModifiers s_accessorModifiers = DeclarationModifiers.Abstract | DeclarationModifiers.New | DeclarationModifiers.Override | DeclarationModifiers.Virtual; - - private static readonly DeclarationModifiers s_localFunctionModifiers = - DeclarationModifiers.Async | - DeclarationModifiers.Static | - DeclarationModifiers.Unsafe | - DeclarationModifiers.Extern; - - private static readonly DeclarationModifiers s_lambdaModifiers = - DeclarationModifiers.Async | - DeclarationModifiers.Static; - - private static DeclarationModifiers GetAllowedModifiers(SyntaxKind kind) - { - switch (kind) - { - case SyntaxKind.RecordDeclaration: - return s_recordModifiers; - - case SyntaxKind.ClassDeclaration: - return s_classModifiers; - - case SyntaxKind.EnumDeclaration: - return DeclarationModifiers.New | DeclarationModifiers.File; - - case SyntaxKind.DelegateDeclaration: - return DeclarationModifiers.New | DeclarationModifiers.Unsafe | DeclarationModifiers.File; - - case SyntaxKind.InterfaceDeclaration: - return s_interfaceModifiers; - - case SyntaxKind.StructDeclaration: - case SyntaxKind.RecordStructDeclaration: - return s_structModifiers; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.OperatorDeclaration: - case SyntaxKind.ConversionOperatorDeclaration: - return s_methodModifiers; - - case SyntaxKind.ConstructorDeclaration: - return s_constructorModifiers; - - case SyntaxKind.DestructorDeclaration: - return s_destructorModifiers; - - case SyntaxKind.FieldDeclaration: - return s_fieldModifiers; - - case SyntaxKind.PropertyDeclaration: - return s_propertyModifiers; - - case SyntaxKind.IndexerDeclaration: - return s_indexerModifiers; - - case SyntaxKind.EventFieldDeclaration: - return s_eventFieldModifiers; - - case SyntaxKind.EventDeclaration: - return s_eventModifiers; - - case SyntaxKind.GetAccessorDeclaration: - case SyntaxKind.SetAccessorDeclaration: - case SyntaxKind.AddAccessorDeclaration: - case SyntaxKind.RemoveAccessorDeclaration: - return s_accessorModifiers; - - case SyntaxKind.LocalFunctionStatement: - return s_localFunctionModifiers; - - case SyntaxKind.ParenthesizedLambdaExpression: - case SyntaxKind.SimpleLambdaExpression: - case SyntaxKind.AnonymousMethodExpression: - return s_lambdaModifiers; - - case SyntaxKind.EnumMemberDeclaration: - case SyntaxKind.Parameter: - case SyntaxKind.LocalDeclarationStatement: - default: - return DeclarationModifiers.None; - } - } - private static SyntaxTokenList AsModifierList(Accessibility accessibility, DeclarationModifiers modifiers) { var list = new List(); @@ -347,6 +172,7 @@ private static SyntaxTokenList AsModifierList(Accessibility accessibility, Decla if (modifiers.IsPartial) list.Add(SyntaxFactory.Token(SyntaxKind.PartialKeyword)); + // Modified list = list.Select(x => x.WithTrailingTrivia(SyntaxFactory.Space)).ToList(); return SyntaxFactory.TokenList(list); diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleaner.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleaner.cs new file mode 100644 index 000000000..cd5e779c2 --- /dev/null +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleaner.cs @@ -0,0 +1,65 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.VisualStudio.Shell; +using SteveCadwallader.CodeMaid.Logic.Cleaning; +using System; + +namespace CodeMaidShared.Logic.Cleaning +{ + internal class RoslynCleaner : CSharpSyntaxRewriter + { + public RoslynCleaner() + { + UpdateNodePipeline = (x, _) => base.Visit(x); + UpdateTokenPipeline = (x, _) => base.VisitToken(x); + } + + private Func UpdateNodePipeline { get; set; } + private Func UpdateTokenPipeline { get; set; } + + public override SyntaxNode Visit(SyntaxNode original) + { + if (original == null) + { + return original; + } + var newNode = original; + + return UpdateNodePipeline(original, newNode); + } + + public override SyntaxToken VisitToken(SyntaxToken token) + { + if (token == null) + { + return token; + } + var newToken = token; + + return UpdateTokenPipeline(token, newToken); + } + + public SyntaxNode Process(SyntaxNode root, Workspace workspace) + { + var rewrite = Visit(root); + return rewrite; + + //return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); + } + + public void AddNodeMiddleware(IRoslynNodeMiddleware middleware) + { + middleware.SetNodeDelegate(UpdateNodePipeline); + + UpdateNodePipeline = middleware.Invoke; + } + + public void AddTokenMiddleware(IRoslynTokenMiddleware middleware) + { + middleware.SetTokenDelegate(UpdateTokenPipeline); + + UpdateTokenPipeline = middleware.Invoke; + } + } +} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs deleted file mode 100644 index fef375555..000000000 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynCleanup.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.VisualStudio.Shell; -using SteveCadwallader.CodeMaid.Logic.Cleaning; -using System; - -namespace CodeMaidShared.Logic.Cleaning -{ - internal class RoslynCleanup : CSharpSyntaxRewriter - { - public Func MemberWriter { get; set; } - - // Use this messy functions to ensure that the current node is not a descendant of an interface. - // This is to mimic the recursive CSharpAddAccessibilityModifiersDiagnosticAnalyzer.ProcessMemberDeclaration - // search where any non structs/classes are ignored. - // FindAncestorOrSelf might help but would be slower. - // Dont terminate on finding an interface in case I want to roslynize more cleanup functions. - - private bool InsideInterface { get; set; } - - public RoslynCleanup() - { - MemberWriter = (_, x) => x; - InsideInterface = false; - } - - public override SyntaxNode Visit(SyntaxNode node) - { - var inInterface = InsideInterface; - if (node.IsKind(SyntaxKind.InterfaceDeclaration)) - InsideInterface = true; - - var newNode = base.Visit(node); - - if (inInterface == false) - { - newNode = MemberWriter(node, newNode); - } - - InsideInterface = inInterface; - - return newNode; - } - - public SyntaxNode Process(SyntaxNode root, Workspace workspace) - { - var rewrite = Visit(root); - return rewrite; - //return Formatter.Format(rewrite, SyntaxAnnotation.ElasticAnnotation, workspace); - } - - public static void BuildAndrun(AsyncPackage package) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - Global.Package = package; - - var document = Global.GetActiveDocument(); - - if (document == null || !document.TryGetSyntaxRoot(out SyntaxNode root)) - { - throw new InvalidOperationException(); - } - - var semanticModel = document.GetSemanticModelAsync().Result; - var syntaxGenerator = SyntaxGenerator.GetGenerator(document); - - var cleaner = new RoslynCleanup(); - RoslynInsertExplicitAccessModifierLogic.Initialize(cleaner, semanticModel, syntaxGenerator); - cleaner.Process(root, Global.Workspace); - - document = document.WithSyntaxRoot(root); - Global.Workspace.TryApplyChanges(document.Project.Solution); - } - } -} \ No newline at end of file diff --git a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs index 4d2687389..a03360205 100644 --- a/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs +++ b/CodeMaidShared/Logic/Cleaning/Roslyn/RoslynExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -9,6 +10,60 @@ namespace SteveCadwallader.CodeMaid.Logic.Cleaning { internal static class RoslynExtensions { + // TODO Refactor + public static (List<(SyntaxKind, int)>, int) ReadTriviaLines(List trivia) + { + var output = new List<(SyntaxKind, int)>(); + var lineType = SyntaxKind.WhitespaceTrivia; + var position = 0; + + for (int i = 0; i < trivia.Count; i++) + { + var value = trivia[i]; + if (value.IsKind(SyntaxKind.EndOfLineTrivia)) + { + output.Add((lineType, position)); + position = i + 1; + lineType = SyntaxKind.WhitespaceTrivia; + } + else if (value.IsKind(SyntaxKind.WhitespaceTrivia)) + { + } + else if (value.Kind() is SyntaxKind.SingleLineCommentTrivia or SyntaxKind.SingleLineDocumentationCommentTrivia) + { + if (lineType == SyntaxKind.WhitespaceTrivia) + { + lineType = value.Kind(); + } + } + else if (value.Kind() is SyntaxKind.RegionDirectiveTrivia or SyntaxKind.EndRegionDirectiveTrivia) + { + if (lineType == SyntaxKind.WhitespaceTrivia) + { + lineType = value.Kind(); + output.Add((lineType, position)); + position = i + 1; + lineType = SyntaxKind.WhitespaceTrivia; + } + } + else + { + lineType = SyntaxKind.BadDirectiveTrivia; + } + } + + return (output, position); + } + + public static bool SpansMultipleLines(this SyntaxNode node) + { + var startLine = node.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line; + var endLine = node.SyntaxTree.GetLineSpan(node.Span).EndLinePosition.Line; + + return startLine != endLine; + } + + // TODO use GetRequiredSemanticModelAsync public static async ValueTask GetRequiredSemanticModelAsync(this Document document, CancellationToken cancellationToken) { if (document.TryGetSemanticModel(out var semanticModel)) From 5e17da9b262ea3c891edfa42a4eae2cece59fb2f Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Wed, 8 Mar 2023 18:05:59 +0000 Subject: [PATCH 16/17] Fix appveyor --- CodeMaid.UnitTests/CodeMaid.UnitTests.csproj | 2 +- CodeMaid.VS2022/CodeMaid.VS2022.csproj | 2 +- CodeMaid/CodeMaid.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj b/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj index 82f05674a..2a2ce0800 100644 --- a/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj +++ b/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj @@ -18,7 +18,7 @@ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest - 10.0 + latest diff --git a/CodeMaid.VS2022/CodeMaid.VS2022.csproj b/CodeMaid.VS2022/CodeMaid.VS2022.csproj index 97c213d23..8e3cedd87 100644 --- a/CodeMaid.VS2022/CodeMaid.VS2022.csproj +++ b/CodeMaid.VS2022/CodeMaid.VS2022.csproj @@ -27,7 +27,7 @@ Program $(DevEnvDir)devenv.exe /rootsuffix Exp - 10.0 + latest true diff --git a/CodeMaid/CodeMaid.csproj b/CodeMaid/CodeMaid.csproj index 3dd6b4043..98d8e6c65 100644 --- a/CodeMaid/CodeMaid.csproj +++ b/CodeMaid/CodeMaid.csproj @@ -27,7 +27,7 @@ Program $(DevEnvDir)devenv.exe /rootsuffix Exp - 10.0 + latest From 4c0bb3825a8b675fc00c14441ceab61fefa617ba Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Wed, 8 Mar 2023 18:05:59 +0000 Subject: [PATCH 17/17] Fix appveyor --- CodeMaid.UnitTests/Cleanup/TestWorkspace.cs | 13 +++++++++++-- CodeMaid.UnitTests/CodeMaid.UnitTests.csproj | 5 ++++- CodeMaid.VS2022/CodeMaid.VS2022.csproj | 2 +- CodeMaid/CodeMaid.csproj | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs b/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs index 626feeb06..ef1d0f70d 100644 --- a/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs +++ b/CodeMaid.UnitTests/Cleanup/TestWorkspace.cs @@ -2,7 +2,11 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; +using Shouldly; +using System; using System.Threading.Tasks; +using StringAssert = NUnit.Framework.StringAssert; namespace SteveCadwallader.CodeMaid.UnitTests.Cleanup { @@ -22,8 +26,13 @@ public async Task VerifyCleanupAsync(string input, string expected) InsertTokenPaddingMiddleware.Initialize(rewriter); var result = rewriter.Process(syntaxTree, Workspace); + var resultString = result.ToFullString(); - Assert.AreEqual(expected, result.ToFullString()); + //To support cross platform line endings use shouldly's IgnoreLineEndings option. + // TODO: Add cross platform string compare and remove shoudly. + resultString.ShouldBe(expected, StringCompareShould.IgnoreLineEndings); + //NUnit.Framework.Assert.AreEqual(expected, resultString); + //StringAssert.AreEqualIgnoringCase(expected, result.ToFullString()); } public TestWorkspace() @@ -54,7 +63,7 @@ public class ThisShouldAppear public Document SetDocument(string text) { Document = Document.WithText(SourceText.From(text)); - Assert.IsTrue(Workspace.TryApplyChanges(Document.Project.Solution)); + NUnit.Framework.Assert.IsTrue(Workspace.TryApplyChanges(Document.Project.Solution)); return Document; } } diff --git a/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj b/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj index 82f05674a..0a4d1e02f 100644 --- a/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj +++ b/CodeMaid.UnitTests/CodeMaid.UnitTests.csproj @@ -18,7 +18,7 @@ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest - 10.0 + latest @@ -104,6 +104,9 @@ 4.0.0 + + 4.1.0 + diff --git a/CodeMaid.VS2022/CodeMaid.VS2022.csproj b/CodeMaid.VS2022/CodeMaid.VS2022.csproj index 97c213d23..8e3cedd87 100644 --- a/CodeMaid.VS2022/CodeMaid.VS2022.csproj +++ b/CodeMaid.VS2022/CodeMaid.VS2022.csproj @@ -27,7 +27,7 @@ Program $(DevEnvDir)devenv.exe /rootsuffix Exp - 10.0 + latest true diff --git a/CodeMaid/CodeMaid.csproj b/CodeMaid/CodeMaid.csproj index 3dd6b4043..98d8e6c65 100644 --- a/CodeMaid/CodeMaid.csproj +++ b/CodeMaid/CodeMaid.csproj @@ -27,7 +27,7 @@ Program $(DevEnvDir)devenv.exe /rootsuffix Exp - 10.0 + latest