Browse Source

Added a analyzer for when Setting.Owned can be used

CPKreuz 1 year ago
parent
commit
92c6b0a077

+ 11 - 0
src/PixiEditor.Extensions.CommonApi.Diagnostics/DiagnosticConstants.cs

@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace PixiEditor.Extensions.CommonApi.Diagnostics;
+
+internal static class DiagnosticConstants
+{
+    public const string Category = "PixiEditor.CommonAPI";
+    
+    public const string SettingNamespace = "PixiEditor.Extensions.CommonApi.UserPreferences.Settings";
+    public static List<string> settingNames = ["SyncedSetting", "LocalSetting"];
+}

+ 8 - 0
src/PixiEditor.Extensions.CommonApi.Diagnostics/DiagnosticHelpers.cs

@@ -0,0 +1,8 @@
+using Microsoft.CodeAnalysis;
+
+namespace PixiEditor.Extensions.CommonApi.Diagnostics;
+
+internal static class DiagnosticHelpers
+{
+    public static bool IsSettingType(TypeInfo info) => info.Type?.ContainingNamespace.ToString() == DiagnosticConstants.SettingNamespace && DiagnosticConstants.settingNames.Contains(info.Type.Name);
+}

+ 1 - 1
src/PixiEditor.Extensions.CommonApi.Diagnostics/PixiEditor.Extensions.CommonApi.Diagnostics.csproj

@@ -4,7 +4,7 @@
         <TargetFramework>netstandard2.0</TargetFramework>
         <LangVersion>latest</LangVersion>
         <Nullable>enable</Nullable>
-        <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
+<!--        <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>-->
     </PropertyGroup>
 
     <ItemGroup>

+ 2 - 5
src/PixiEditor.Extensions.CommonApi.Diagnostics/UseGenericEnumerableForListArrayDiagnostic.cs

@@ -14,9 +14,6 @@ public class UseGenericEnumerableForListArrayDiagnostic : DiagnosticAnalyzer
     private const string ListNamespace = "System.Collections.Generic";
     private const string ListName = "List";
     
-    private const string SettingNamespace = "PixiEditor.Extensions.CommonApi.UserPreferences.Settings";
-    private static string[] settingNames = ["SyncedSetting", "LocalSetting"];
-
     public const string DiagnosticId = "UseGenericEnumerableForListArray";
     
     public static DiagnosticDescriptor UseGenericEnumerableForListArrayDescriptor { get; } =
@@ -36,9 +33,9 @@ public class UseGenericEnumerableForListArrayDiagnostic : DiagnosticAnalyzer
         var semanticModel = context.SemanticModel;
         
         var name = (GenericNameSyntax)context.Node;
-        var symbol = semanticModel.GetTypeInfo(name, context.CancellationToken);
+        var typeInfo = semanticModel.GetTypeInfo(name, context.CancellationToken);
 
-        if (symbol.Type?.ContainingNamespace.ToString() != SettingNamespace || !settingNames.Contains(symbol.Type.Name))
+        if (!DiagnosticHelpers.IsSettingType(typeInfo))
         {
             return;
         }

+ 89 - 0
src/PixiEditor.Extensions.CommonApi.Diagnostics/UseOwnedDiagnostic.cs

@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace PixiEditor.Extensions.CommonApi.Diagnostics;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+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,
+        DiagnosticSeverity.Info, true);
+    
+    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
+        [Descriptor];
+    
+    public override void Initialize(AnalysisContext context)
+    {
+        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+        context.EnableConcurrentExecution();
+        context.RegisterSyntaxNodeAction(AnalyzeDeclaration, SyntaxKind.PropertyDeclaration);
+    }
+
+    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;
+
+        var typeInfo = semantics.GetTypeInfo(declaration.Type, context.CancellationToken);
+
+        if (!DiagnosticHelpers.IsSettingType(typeInfo))
+        {
+            return;
+        }
+        
+        // TODO: Also handle => new()
+        if (declaration.Initializer is not { Value: BaseObjectCreationExpressionSyntax { ArgumentList.Arguments: { Count: > 0 } arguments } initializerExpression })
+        {
+            return;
+        }
+
+        var nameArgument = arguments.First();
+
+        var operation = semantics.GetOperation(nameArgument.Expression);
+
+        bool isLiteralMatch = operation is ILiteralOperation { ConstantValue.Value: string s1 } &&
+                              s1 == declaration.Identifier.ValueText;
+
+        bool isNameOfMatch = operation is INameOfOperation { ConstantValue.Value: string s2 } &&
+                             s2 == declaration.Identifier.ValueText;
+        
+        if (!isLiteralMatch && !isNameOfMatch)
+        {
+            return;
+        }
+        
+        var genericType = string.Empty;
+
+        var fallbackValueArgument = arguments.Skip(1).FirstOrDefault();
+
+        var settingType = ((GenericNameSyntax)declaration.Type).TypeArgumentList.Arguments.First();
+        if (fallbackValueArgument == null || !SymbolEqualityComparer.Default.Equals(
+                semantics.GetTypeInfo(fallbackValueArgument.Expression).Type,
+                semantics.GetTypeInfo(settingType).Type))
+        {
+            genericType = $"<{settingType}>";
+        }
+
+        var diagnostic = Diagnostic.Create(Descriptor, initializerExpression.GetLocation(),
+            typeInfo.Type?.Name, // LocalSetting or Synced Setting
+            genericType);
+        
+        // TODO: Codefix please too
+        context.ReportDiagnostic(diagnostic);
+    }
+}