Browse Source

Added Owned code fix

CPKreuz 1 year ago
parent
commit
788211900e

+ 1 - 1
src/PixiEditor.Extensions.CommonApi.Diagnostics/UseGenericEnumerableForListArrayDiagnostic.cs

@@ -19,7 +19,7 @@ public class UseGenericEnumerableForListArrayDiagnostic : DiagnosticAnalyzer
     public static DiagnosticDescriptor UseGenericEnumerableForListArrayDescriptor { get; } =
         new(DiagnosticId, "Use IEnumerable<T> in Setting instead of List/Array",
             "Use IEnumerable<{0}> instead of {1} to allow passing any IEnumerable<{0}> for the value. Use the {2} extension from PixiEditor.Extensions.CommonApi.UserPreferences.Settings to access the Setting as a {1}.",
-            "PixiEditor.CommonAPI", DiagnosticSeverity.Warning, true);
+            "PixiEditor.CommonAPI", DiagnosticSeverity.Info, true);
 
     public override void Initialize(AnalysisContext context)
     {

+ 1 - 7
src/PixiEditor.Extensions.CommonApi.Diagnostics/UseOwnedDiagnostic.cs

@@ -16,7 +16,7 @@ public class UseOwnedDiagnostic : DiagnosticAnalyzer
     public const string DiagnosticId = "UseOwned";
 
     public static readonly DiagnosticDescriptor Descriptor = new(DiagnosticId, "Use .Owned() method",
-        "Use {0}.Owned{1}{2} to declare a Setting using the property name", DiagnosticConstants.Category,
+        "Use {0}.Owned{1}() to declare a Setting using the property name", DiagnosticConstants.Category,
         DiagnosticSeverity.Info, true);
     
     public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
@@ -31,11 +31,6 @@ public class UseOwnedDiagnostic : DiagnosticAnalyzer
 
     private static void AnalyzeDeclaration(SyntaxNodeAnalysisContext context)
     {
-        // var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "DiagnosticsThing");
-        // Directory.CreateDirectory(path);
-        // var file = Path.Combine(path, Guid.NewGuid().ToString());
-        // TODO: Please remove /\
-        
         var semantics = context.SemanticModel;
         var declaration = (PropertyDeclarationSyntax)context.Node;
 
@@ -83,7 +78,6 @@ public class UseOwnedDiagnostic : DiagnosticAnalyzer
             typeInfo.Type?.Name, // LocalSetting or Synced Setting
             genericType);
         
-        // TODO: Codefix please too
         context.ReportDiagnostic(diagnostic);
     }
 }

+ 66 - 0
src/PixiEditor.Extensions.CommonApi.Diagnostics/UseOwnedFix.cs

@@ -0,0 +1,66 @@
+using System.Collections.Immutable;
+using System.Composition;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace PixiEditor.Extensions.CommonApi.Diagnostics;
+
+[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseOwnedFix))]
+[Shared]
+public class UseOwnedFix : CodeFixProvider
+{
+    public override async Task RegisterCodeFixesAsync(CodeFixContext context)
+    {
+        var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+
+        var diagnostic = context.Diagnostics.First();
+        var diagnosticSpan = diagnostic.Location.SourceSpan;
+        
+        var syntax = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<PropertyDeclarationSyntax>().First();
+
+        var title = "Use .Owned() method";
+        // TODO: equivalenceKey only works for types with the same name. Is there some way to make this generic?
+        var action = CodeAction.Create(title, c => CreateChangedDocument(context.Document, syntax, c), title);
+        
+        context.RegisterCodeFix(action, diagnostic);
+    }
+
+    private static async Task<Document> CreateChangedDocument(Document document, PropertyDeclarationSyntax declaration, CancellationToken token)
+    {
+        var settingType = (GenericNameSyntax)declaration.Type;
+        var originalInvocation = (BaseObjectCreationExpressionSyntax)declaration.Initializer!.Value;
+        
+        var classIdentifier = SyntaxFactory.IdentifierName(settingType.Identifier); // Removes the <> part
+        var ownedIdentifier = SyntaxFactory.GenericName(SyntaxFactory.Identifier("Owned"), settingType.TypeArgumentList);
+        
+        var accessExpression = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, classIdentifier, ownedIdentifier);
+        var invocationExpression = SyntaxFactory.InvocationExpression(accessExpression, SkipArgument(originalInvocation.ArgumentList!));
+
+        var root = await document.GetSyntaxRootAsync(token);
+
+        var newRoot = root!.ReplaceNode(declaration.Initializer.Value, invocationExpression);
+
+        // TODO: The initializer part does not have it's generic type replaced
+        return document.WithSyntaxRoot(newRoot);
+    }
+
+    private static ArgumentListSyntax SkipArgument(ArgumentListSyntax original)
+    {
+        var list = new SeparatedSyntaxList<ArgumentSyntax>();
+        
+        foreach (var argument in original.Arguments.Skip(1))
+        {
+            list.Add(argument);
+        }
+
+        return SyntaxFactory.ArgumentList(list);
+    }
+
+    public override ImmutableArray<string> FixableDiagnosticIds { get; } = [UseOwnedDiagnostic.DiagnosticId];
+}