瀏覽代碼

Added preference constants attribute

CPKreuz 1 年之前
父節點
當前提交
3c62ff439a

+ 6 - 6
src/PixiEditor.Extensions/Common/UserPreferences/IPreferences.cs

@@ -43,7 +43,7 @@ public interface IPreferences
     /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
     /// <param name="name">The name of the setting.</param>
     /// <param name="value">The new value.</param>
-    public void UpdatePreference<T>(string name, T value);
+    public void UpdatePreference<T>([RemotePreferenceConstant] string name, T value);
 
     /// <summary>
     /// Updates a editor setting and calls all added callbacks.
@@ -51,7 +51,7 @@ public interface IPreferences
     /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
     /// <param name="name">The name of the setting</param>
     /// <param name="value">The new value</param>
-    public void UpdateLocalPreference<T>(string name, T value);
+    public void UpdateLocalPreference<T>([LocalPreferenceConstant] string name, T value);
 
 #nullable enable
 
@@ -61,7 +61,7 @@ public interface IPreferences
     /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
     /// <param name="name">The name of the setting</param>
     /// <returns>The setting or the default of <typeparamref name="T"/> if it has not been set yet</returns>
-    public T? GetPreference<T>(string name);
+    public T? GetPreference<T>([RemotePreferenceConstant] string name);
 
     /// <summary>
     /// Reads the user preference that is called <paramref name="name"/>, if the setting does not exist the default of <paramref name="fallbackValue"/> will be used
@@ -69,7 +69,7 @@ public interface IPreferences
     /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
     /// <param name="name">The name of the setting</param>
     /// <returns>The setting or the <paramref name="fallbackValue"/> if it has not been set yet</returns>
-    public T? GetPreference<T>(string name, T? fallbackValue);
+    public T? GetPreference<T>([RemotePreferenceConstant] string name, T? fallbackValue);
 
     /// <summary>
     /// Reads the editor setting that is called <paramref name="name"/>, if the setting does not exist the deafult of <typeparamref name="T"/> will be used
@@ -77,7 +77,7 @@ public interface IPreferences
     /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
     /// <param name="name">The name of the setting</param>
     /// <returns>The editor setting or the default of <typeparamref name="T"/> if it has not been set yet</returns>
-    public T? GetLocalPreference<T>(string name);
+    public T? GetLocalPreference<T>([LocalPreferenceConstant] string name);
 
     /// <summary>
     /// Reads the editor setting that is called <paramref name="name"/>, if the setting does not exist the <paramref name="fallbackValue"/> will be used
@@ -85,7 +85,7 @@ public interface IPreferences
     /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
     /// <param name="name">The name of the setting</param>
     /// <returns>The editor setting or the <paramref name="fallbackValue"/> if it has not been set yet</returns>
-    public T? GetLocalPreference<T>(string name, T? fallbackValue);
+    public T? GetLocalPreference<T>([LocalPreferenceConstant] string name, T? fallbackValue);
 
     protected static void SetAsCurrent(IPreferences provider)
     {

+ 11 - 0
src/PixiEditor.Extensions/Common/UserPreferences/PreferenceContantAttribute.cs

@@ -0,0 +1,11 @@
+namespace PixiEditor.Extensions.Common.UserPreferences;
+
+[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)]
+public abstract class PreferenceConstantAttribute : Attribute
+{ }
+
+public class LocalPreferenceConstantAttribute : PreferenceConstantAttribute
+{ }
+
+public class RemotePreferenceConstantAttribute : PreferenceConstantAttribute
+{ }

+ 11 - 0
src/PixiEditor.Extensions/Common/UserPreferences/PreferencesConstants.cs

@@ -2,29 +2,40 @@
 
 public static class PreferencesConstants
 {
+    [LocalPreferenceConstant]
     public const string FavouritePalettes = nameof(FavouritePalettes);
     
+    [LocalPreferenceConstant]
     public const string RecentlyOpened = nameof(RecentlyOpened);
 
+    [RemotePreferenceConstant]
     public const string MaxOpenedRecently = nameof(MaxOpenedRecently);
     public const int MaxOpenedRecentlyDefault = 8;
     
+    [RemotePreferenceConstant]
     public const string DisableNewsPanel = nameof(DisableNewsPanel);
     
+    [RemotePreferenceConstant]
     public const string LastCheckedNewsIds = nameof(LastCheckedNewsIds);
     
+    [RemotePreferenceConstant]
     public const string NewsPanelCollapsed = nameof(NewsPanelCollapsed);
     
+    [RemotePreferenceConstant]
     public const string AutosavePeriodMinutes = nameof(AutosavePeriodMinutes);
     public const double AutosavePeriodDefault = 3;
 
+    [LocalPreferenceConstant]
     public const string UnsavedNextSessionFiles = nameof(UnsavedNextSessionFiles);
 
+    [RemotePreferenceConstant]
     public const string AutosaveToDocumentPath = nameof(AutosaveToDocumentPath);
     public const bool AutosaveToDocumentPathDefault = false;
     
+    [RemotePreferenceConstant]
     public const string SaveSessionStateEnabled = nameof(SaveSessionStateEnabled);
     public const bool SaveSessionStateDefault = true;
 
+    [LocalPreferenceConstant]
     public const string LastCrashFile = nameof(LastCrashFile);
 }

+ 4 - 4
src/PixiEditor/ViewModels/SubViewModels/UserPreferences/SettingsGroup.cs

@@ -6,27 +6,27 @@ namespace PixiEditor.ViewModels.SubViewModels.UserPreferences;
 
 internal class SettingsGroup : NotifyableObject
 {
-    protected static T GetPreference<T>(string name)
+    protected static T GetPreference<T>([RemotePreferenceConstant] string name)
     {
         return IPreferences.Current.GetPreference<T>(name);
     }
 
 #nullable enable
 
-    protected static T? GetPreference<T>(string name, T? fallbackValue)
+    protected static T? GetPreference<T>([RemotePreferenceConstant] string name, T? fallbackValue)
     {
         return IPreferences.Current.GetPreference(name, fallbackValue);
     }
 
 #nullable disable
 
-    protected void RaiseAndUpdatePreference<T>(string name, T value)
+    protected void RaiseAndUpdatePreference<T>([RemotePreferenceConstant] string name, T value)
     {
         RaisePropertyChanged(name);
         IPreferences.Current.UpdatePreference(name, value);
     }
 
-    protected void RaiseAndUpdatePreference<T>(ref T backingStore, T value, [CallerMemberName] string name = "")
+    protected void RaiseAndUpdatePreference<T>(ref T backingStore, T value, [CallerMemberName, RemotePreferenceConstant] string name = "")
     {
         SetProperty(ref backingStore, value, propertyName: name);
         IPreferences.Current.UpdatePreference(name, value);

+ 1 - 1
src/PixiEditor/ViewModels/ViewModelMain.cs

@@ -266,7 +266,7 @@ internal class ViewModelMain : ViewModelBase
             }
         }
         
-        Preferences.UpdateLocalPreference(PreferencesConstants.UnsavedNextSessionFiles, list);
+        Preferences.UpdatePreference(PreferencesConstants.UnsavedNextSessionFiles, list);
     }
 
     /// <summary>

+ 112 - 0
src/PixiEditorGen/PreferencesDiagnostics.cs

@@ -0,0 +1,112 @@
+using System.Collections.Immutable;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace PixiEditorGen;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public class PreferencesDiagnostics : DiagnosticAnalyzer
+{
+    private static DiagnosticDescriptor wrongDestinationDescriptor = new("WrongDestination",
+        "Wrong preferences destination", "Preference '{0}' should be used with {1} preferences",
+        "PixiEditor", DiagnosticSeverity.Error, true);
+    
+    public override void Initialize(AnalysisContext context)
+    {
+        PostLogMessage("hello");
+        
+        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+        context.EnableConcurrentExecution();
+        context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression);
+    }
+
+    private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
+    {
+        var invocation = (InvocationExpressionSyntax)context.Node;
+
+        if (invocation.ArgumentList.Arguments.Count == 0) return;
+        
+        var methodNameExpr = invocation.Expression switch
+        {
+            MemberAccessExpressionSyntax memberAccess => memberAccess.Name, // for `obj.Method()` or `Class.Method()`
+            IdentifierNameSyntax identifierName => identifierName,          // for `Method()`
+            _ => null
+        };
+
+        var methodSymbol = context.SemanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol;
+
+        if (methodSymbol == null)
+        {
+            return;
+        }
+
+        for (var i = 0; i < methodSymbol.Parameters.Length; i++)
+        {
+            if (invocation.ArgumentList.Arguments[i].Expression is not MemberAccessExpressionSyntax member)
+            {
+                continue;
+            }
+
+            var parameterSymbol = methodSymbol.Parameters[i];
+            
+            foreach (var attributeData in parameterSymbol.GetAttributes())
+            {
+                if (attributeData.AttributeClass?.BaseType?.Name != "PreferenceConstantAttribute")
+                {
+                    continue;
+                }
+
+                if (context.SemanticModel.GetSymbolInfo(member).Symbol is not { } symbol)
+                {
+                    continue;
+                }
+
+                var memberAttributeData = symbol.GetAttributes()
+                    .FirstOrDefault(x => x.AttributeClass?.BaseType?.Name == "PreferenceConstantAttribute");
+                
+                if (memberAttributeData != null && memberAttributeData.AttributeClass?.Name != attributeData.AttributeClass?.Name)
+                {
+                    var diagnostic = Diagnostic.Create(
+                        wrongDestinationDescriptor,
+                        methodNameExpr?.GetLocation() ?? invocation.GetLocation(), symbol.ToDisplayString(),
+                        memberAttributeData.AttributeClass?.Name.Replace("PreferenceConstantAttribute", "").ToLower());
+
+                    context.ReportDiagnostic(diagnostic);
+                }
+
+                break;
+            }
+        }
+    }
+
+    static void PostLogMessage(string message)
+    {
+        Task.Run(() => PostLogMessageAsync(message));
+    }
+
+    static async Task PostLogMessageAsync(string logMessage)
+    {
+        using var client = new HttpClient();
+        
+        var content = new StringContent(logMessage, Encoding.UTF8, "text/plain");
+        HttpResponseMessage response = await client.PostAsync("http://localhost:8080", content);
+
+        if (response.IsSuccessStatusCode)
+        {
+            string responseBody = await response.Content.ReadAsStringAsync();
+            Console.WriteLine(responseBody);
+        }
+        else
+        {
+            Console.WriteLine($"Error: {response.StatusCode}");
+        }
+    }
+    
+    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
+    {
+        get => ImmutableArray.Create(wrongDestinationDescriptor);
+    }
+}