From fb38a301304f3b4f6e206a264afd2c38304d17b5 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Tue, 11 Mar 2025 15:45:13 +0800 Subject: [PATCH 1/3] Move PluginClassInfo related to shared helper --- .../Localize/ContextAvailabilityAnalyzer.cs | 45 +++++++----- Flow.Launcher.Localization.Shared/Classes.cs | 29 ++++++++ .../Flow.Launcher.Localization.Shared.csproj | 2 +- Flow.Launcher.Localization.Shared/Helper.cs | 70 ++++++++++++++++++- .../Localize/LocalizeSourceGenerator.cs | 60 +--------------- 5 files changed, 127 insertions(+), 79 deletions(-) create mode 100644 Flow.Launcher.Localization.Shared/Classes.cs diff --git a/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs b/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs index 72a2775..9b4d6b0 100644 --- a/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs +++ b/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs @@ -11,6 +11,8 @@ namespace Flow.Launcher.Localization.Analyzers.Localize [DiagnosticAnalyzer(LanguageNames.CSharp)] public class ContextAvailabilityAnalyzer : DiagnosticAnalyzer { + #region DiagnosticAnalyzer + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( AnalyzerDiagnostics.ContextIsAField, AnalyzerDiagnostics.ContextIsNotStatic, @@ -25,47 +27,55 @@ public override void Initialize(AnalysisContext context) context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration); } + #endregion + + #region Analyze Methods + private static void AnalyzeNode(SyntaxNodeAnalysisContext context) { var configOptions = context.Options.AnalyzerConfigOptionsProvider; var useDI = configOptions.GetFLLUseDependencyInjection(); - - // If we use dependency injection, we don't need to check for this context property - if (useDI) return; + if (useDI) + { + // If we use dependency injection, we don't need to check for this context property + return; + } var classDeclaration = (ClassDeclarationSyntax)context.Node; var semanticModel = context.SemanticModel; - var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration); - - if (!IsPluginEntryClass(classSymbol)) return; - - var contextProperty = classDeclaration.Members.OfType() - .Select(p => semanticModel.GetDeclaredSymbol(p)) - .FirstOrDefault(p => p?.Type.Name is Constants.PluginContextTypeName); + var pluginClassInfo = Helper.GetPluginClassInfo(classDeclaration, semanticModel, context.CancellationToken); + if (pluginClassInfo == null) + { + // Cannot find class that implements IPluginI18n + return; + } - if (contextProperty != null) + // Context property is found, check if it's a valid property + if (pluginClassInfo.PropertyName != null) { - if (!contextProperty.IsStatic) + if (!pluginClassInfo.IsStatic) { context.ReportDiagnostic(Diagnostic.Create( AnalyzerDiagnostics.ContextIsNotStatic, - contextProperty.DeclaringSyntaxReferences[0].GetSyntax().GetLocation() + pluginClassInfo.CodeFixLocatioin )); return; } - if (contextProperty.DeclaredAccessibility is Accessibility.Private || contextProperty.DeclaredAccessibility is Accessibility.Protected) + if (pluginClassInfo.IsPrivate || pluginClassInfo.IsProtected) { context.ReportDiagnostic(Diagnostic.Create( AnalyzerDiagnostics.ContextAccessIsTooRestrictive, - contextProperty.DeclaringSyntaxReferences[0].GetSyntax().GetLocation() + pluginClassInfo.CodeFixLocatioin )); return; } + // If the context property is valid, we don't need to check for anything else return; } + // Context property is not found, check if it's declared as a field var fieldDeclaration = classDeclaration.Members .OfType() .SelectMany(f => f.Declaration.Variables) @@ -75,7 +85,6 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) ?.DeclaringSyntaxReferences[0] .GetSyntax() .FirstAncestorOrSelf(); - if (parentSyntax != null) { context.ReportDiagnostic(Diagnostic.Create( @@ -85,13 +94,13 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) return; } + // Context property is not found, report an error context.ReportDiagnostic(Diagnostic.Create( AnalyzerDiagnostics.ContextIsNotDeclared, classDeclaration.Identifier.GetLocation() )); } - private static bool IsPluginEntryClass(INamedTypeSymbol namedTypeSymbol) => - namedTypeSymbol?.Interfaces.Any(i => i.Name == Constants.PluginInterfaceName) ?? false; + #endregion } } diff --git a/Flow.Launcher.Localization.Shared/Classes.cs b/Flow.Launcher.Localization.Shared/Classes.cs new file mode 100644 index 0000000..bbd18f5 --- /dev/null +++ b/Flow.Launcher.Localization.Shared/Classes.cs @@ -0,0 +1,29 @@ +using Microsoft.CodeAnalysis; + +namespace Flow.Launcher.Localization.Shared +{ + public class PluginClassInfo + { + public Location Location { get; } + public string ClassName { get; } + public string PropertyName { get; } + public bool IsStatic { get; } + public bool IsPrivate { get; } + public bool IsProtected { get; } + public Location CodeFixLocatioin { get; } + + public string ContextAccessor => $"{ClassName}.{PropertyName}"; + public bool IsValid => PropertyName != null && IsStatic && (!IsPrivate) && (!IsProtected); + + public PluginClassInfo(Location location, string className, string propertyName, bool isStatic, bool isPrivate, bool isProtected, Location codeFixLocatioin) + { + Location = location; + ClassName = className; + PropertyName = propertyName; + IsStatic = isStatic; + IsPrivate = isPrivate; + IsProtected = isProtected; + CodeFixLocatioin = codeFixLocatioin; + } + } +} diff --git a/Flow.Launcher.Localization.Shared/Flow.Launcher.Localization.Shared.csproj b/Flow.Launcher.Localization.Shared/Flow.Launcher.Localization.Shared.csproj index ae15976..74cca20 100644 --- a/Flow.Launcher.Localization.Shared/Flow.Launcher.Localization.Shared.csproj +++ b/Flow.Launcher.Localization.Shared/Flow.Launcher.Localization.Shared.csproj @@ -8,7 +8,7 @@ - + diff --git a/Flow.Launcher.Localization.Shared/Helper.cs b/Flow.Launcher.Localization.Shared/Helper.cs index 94b186c..d9502ac 100644 --- a/Flow.Launcher.Localization.Shared/Helper.cs +++ b/Flow.Launcher.Localization.Shared/Helper.cs @@ -1,9 +1,16 @@ -using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Linq; +using System.Threading; namespace Flow.Launcher.Localization.Shared { public static class Helper { + #region Build Properties + public static bool GetFLLUseDependencyInjection(this AnalyzerConfigOptionsProvider configOptions) { if (!configOptions.GlobalOptions.TryGetValue("build_property.FLLUseDependencyInjection", out var result) || @@ -13,5 +20,66 @@ public static bool GetFLLUseDependencyInjection(this AnalyzerConfigOptionsProvid } return useDI; } + + #endregion + + #region Plugin Class Info + + /// + /// If cannot find the class that implements IPluginI18n, return null. + /// If cannot find the context property, return PluginClassInfo with null context property name. + /// + public static PluginClassInfo GetPluginClassInfo(ClassDeclarationSyntax classDecl, SemanticModel semanticModel, CancellationToken ct) + { + var classSymbol = semanticModel.GetDeclaredSymbol(classDecl, ct); + if (!IsPluginEntryClass(classSymbol)) + { + // Cannot find class that implements IPluginI18n + return null; + } + + var property = GetContextProperty(classDecl); + var location = GetLocation(semanticModel.SyntaxTree, classDecl); + if (property is null) + { + // Cannot find context + return new PluginClassInfo(location, classDecl.Identifier.Text, null, false, false, false, null); + } + + var modifiers = property.Modifiers; + var codeFixLocation = GetCodeFixLocation(property, semanticModel); + return new PluginClassInfo( + location, + classDecl.Identifier.Text, + property.Identifier.Text, + modifiers.Any(SyntaxKind.StaticKeyword), + modifiers.Any(SyntaxKind.PrivateKeyword), + modifiers.Any(SyntaxKind.ProtectedKeyword), + codeFixLocation); + } + + private static bool IsPluginEntryClass(INamedTypeSymbol namedTypeSymbol) + { + return namedTypeSymbol?.Interfaces.Any(i => i.Name == Constants.PluginInterfaceName) ?? false; + } + + private static PropertyDeclarationSyntax GetContextProperty(ClassDeclarationSyntax classDecl) + { + return classDecl.Members + .OfType() + .FirstOrDefault(p => p.Type.ToString() == Constants.PluginContextTypeName); + } + + private static Location GetLocation(SyntaxTree syntaxTree, CSharpSyntaxNode classDeclaration) + { + return Location.Create(syntaxTree, classDeclaration.GetLocation().SourceSpan); + } + + private static Location GetCodeFixLocation(PropertyDeclarationSyntax property, SemanticModel semanticModel) + { + return semanticModel.GetDeclaredSymbol(property).DeclaringSyntaxReferences[0].GetSyntax().GetLocation(); + } + + #endregion } } diff --git a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs index 9f797c8..0ef37fa 100644 --- a/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs +++ b/Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs @@ -7,7 +7,6 @@ using System.Xml.Linq; using Flow.Launcher.Localization.Shared; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; @@ -57,7 +56,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var pluginClasses = context.SyntaxProvider .CreateSyntaxProvider( predicate: (n, _) => n is ClassDeclarationSyntax, - transform: GetPluginClassInfo) + transform: (c, t) => Helper.GetPluginClassInfo((ClassDeclarationSyntax)c.Node, c.SemanticModel, t)) .Where(info => info != null) .Collect(); @@ -428,35 +427,6 @@ private static string GetLocalizationKeyFromInvocation(GeneratorSyntaxContext co #region Get Plugin Class Info - private static PluginClassInfo GetPluginClassInfo(GeneratorSyntaxContext context, CancellationToken ct) - { - var classDecl = (ClassDeclarationSyntax)context.Node; - var location = GetLocation(context.SemanticModel.SyntaxTree, classDecl); - if (!classDecl.BaseList?.Types.Any(t => t.Type.ToString() == Constants.PluginInterfaceName) ?? true) - { - // Cannot find class that implements IPluginI18n - return null; - } - - var property = classDecl.Members - .OfType() - .FirstOrDefault(p => p.Type.ToString() == Constants.PluginContextTypeName); - if (property is null) - { - // Cannot find context - return new PluginClassInfo(location, classDecl.Identifier.Text, null, false, false, false); - } - - var modifiers = property.Modifiers; - return new PluginClassInfo( - location, - classDecl.Identifier.Text, - property.Identifier.Text, - modifiers.Any(SyntaxKind.StaticKeyword), - modifiers.Any(SyntaxKind.PrivateKeyword), - modifiers.Any(SyntaxKind.ProtectedKeyword)); - } - private static PluginClassInfo GetValidPluginInfo( ImmutableArray pluginClasses, SourceProductionContext context, @@ -535,11 +505,6 @@ private static PluginClassInfo GetValidPluginInfo( return null; } - private static Location GetLocation(SyntaxTree syntaxTree, CSharpSyntaxNode classDeclaration) - { - return Location.Create(syntaxTree, classDeclaration.GetLocation().SourceSpan); - } - #endregion #region Generate Source @@ -772,29 +737,6 @@ public LocalizableString(string key, string value, string summary, IEnumerable $"{ClassName}.{PropertyName}"; - public bool IsValid => PropertyName != null && IsStatic && (!IsPrivate) && (!IsProtected); - - public PluginClassInfo(Location location, string className, string propertyName, bool isStatic, bool isPrivate, bool isProtected) - { - Location = location; - ClassName = className; - PropertyName = propertyName; - IsStatic = isStatic; - IsPrivate = isPrivate; - IsProtected = isProtected; - } - } - #endregion } } From 70820fc8ba5673ecb831d0d4985ec34066d16327 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Tue, 11 Mar 2025 15:46:24 +0800 Subject: [PATCH 2/3] Add regions --- ...textAvailabilityAnalyzerCodeFixProvider.cs | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzerCodeFixProvider.cs b/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzerCodeFixProvider.cs index 54df68a..8ef920c 100644 --- a/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzerCodeFixProvider.cs +++ b/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzerCodeFixProvider.cs @@ -17,6 +17,8 @@ namespace Flow.Launcher.Localization.Analyzers.Localize [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ContextAvailabilityAnalyzerCodeFixProvider)), Shared] public class ContextAvailabilityAnalyzerCodeFixProvider : CodeFixProvider { + #region CodeFixProvider + public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create( AnalyzerDiagnostics.ContextIsAField.Id, AnalyzerDiagnostics.ContextIsNotStatic.Id, @@ -82,21 +84,9 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) } } - private static MemberDeclarationSyntax GetStaticContextPropertyDeclaration(string propertyName = "Context") => - SyntaxFactory.ParseMemberDeclaration( - $"internal static {Constants.PluginContextTypeName} {propertyName} {{ get; private set; }} = null!;" - ); + #endregion - private static Document GetFormattedDocument(CodeFixContext context, SyntaxNode root) - { - var formattedRoot = Formatter.Format( - root, - Formatter.Annotation, - context.Document.Project.Solution.Workspace - ); - - return context.Document.WithSyntaxRoot(formattedRoot); - } + #region Fix Methods private static Document FixContextNotDeclared(CodeFixContext context, SyntaxNode root, TextSpan diagnosticSpan) { @@ -160,6 +150,24 @@ private static Document FixContextIsAFieldError(CodeFixContext context, SyntaxNo return GetFormattedDocument(context, newRoot); } + #region Utils + + private static MemberDeclarationSyntax GetStaticContextPropertyDeclaration(string propertyName = "Context") => + SyntaxFactory.ParseMemberDeclaration( + $"internal static {Constants.PluginContextTypeName} {propertyName} {{ get; private set; }} = null!;" + ); + + private static Document GetFormattedDocument(CodeFixContext context, SyntaxNode root) + { + var formattedRoot = Formatter.Format( + root, + Formatter.Annotation, + context.Document.Project.Solution.Workspace + ); + + return context.Document.WithSyntaxRoot(formattedRoot); + } + private static PropertyDeclarationSyntax FixRestrictivePropertyModifiers(PropertyDeclarationSyntax propertyDeclaration) { var newModifiers = SyntaxFactory.TokenList(); @@ -185,5 +193,9 @@ private static T GetDeclarationSyntax(SyntaxNode root, TextSpan diagnosticSpa ?.AncestorsAndSelf() .OfType() .First(); + + #endregion + + #endregion } } From dfda18c0b4a912d6fed029776de87e02051464c9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 15 Mar 2025 14:05:10 +0800 Subject: [PATCH 3/3] Fix typos --- .../Localize/ContextAvailabilityAnalyzer.cs | 4 ++-- Flow.Launcher.Localization.Shared/Classes.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs b/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs index 9b4d6b0..fa9da76 100644 --- a/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs +++ b/Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs @@ -57,7 +57,7 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( AnalyzerDiagnostics.ContextIsNotStatic, - pluginClassInfo.CodeFixLocatioin + pluginClassInfo.CodeFixLocation )); return; } @@ -66,7 +66,7 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( AnalyzerDiagnostics.ContextAccessIsTooRestrictive, - pluginClassInfo.CodeFixLocatioin + pluginClassInfo.CodeFixLocation )); return; } diff --git a/Flow.Launcher.Localization.Shared/Classes.cs b/Flow.Launcher.Localization.Shared/Classes.cs index bbd18f5..9c78d98 100644 --- a/Flow.Launcher.Localization.Shared/Classes.cs +++ b/Flow.Launcher.Localization.Shared/Classes.cs @@ -10,12 +10,12 @@ public class PluginClassInfo public bool IsStatic { get; } public bool IsPrivate { get; } public bool IsProtected { get; } - public Location CodeFixLocatioin { get; } + public Location CodeFixLocation { get; } public string ContextAccessor => $"{ClassName}.{PropertyName}"; public bool IsValid => PropertyName != null && IsStatic && (!IsPrivate) && (!IsProtected); - public PluginClassInfo(Location location, string className, string propertyName, bool isStatic, bool isPrivate, bool isProtected, Location codeFixLocatioin) + public PluginClassInfo(Location location, string className, string propertyName, bool isStatic, bool isPrivate, bool isProtected, Location codeFixLocation) { Location = location; ClassName = className; @@ -23,7 +23,7 @@ public PluginClassInfo(Location location, string className, string propertyName, IsStatic = isStatic; IsPrivate = isPrivate; IsProtected = isProtected; - CodeFixLocatioin = codeFixLocatioin; + CodeFixLocation = codeFixLocation; } } }