Skip to content

Commit 24712e5

Browse files
authored
Merge pull request #23 from Flow-Launcher/plugin_class_info
Move PluginClassInfo & Add code regions
2 parents 36c7395 + dfda18c commit 24712e5

File tree

6 files changed

+153
-93
lines changed

6 files changed

+153
-93
lines changed

Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs

+27-18
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ namespace Flow.Launcher.Localization.Analyzers.Localize
1111
[DiagnosticAnalyzer(LanguageNames.CSharp)]
1212
public class ContextAvailabilityAnalyzer : DiagnosticAnalyzer
1313
{
14+
#region DiagnosticAnalyzer
15+
1416
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
1517
AnalyzerDiagnostics.ContextIsAField,
1618
AnalyzerDiagnostics.ContextIsNotStatic,
@@ -25,47 +27,55 @@ public override void Initialize(AnalysisContext context)
2527
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration);
2628
}
2729

30+
#endregion
31+
32+
#region Analyze Methods
33+
2834
private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
2935
{
3036
var configOptions = context.Options.AnalyzerConfigOptionsProvider;
3137
var useDI = configOptions.GetFLLUseDependencyInjection();
32-
33-
// If we use dependency injection, we don't need to check for this context property
34-
if (useDI) return;
38+
if (useDI)
39+
{
40+
// If we use dependency injection, we don't need to check for this context property
41+
return;
42+
}
3543

3644
var classDeclaration = (ClassDeclarationSyntax)context.Node;
3745
var semanticModel = context.SemanticModel;
38-
var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration);
39-
40-
if (!IsPluginEntryClass(classSymbol)) return;
41-
42-
var contextProperty = classDeclaration.Members.OfType<PropertyDeclarationSyntax>()
43-
.Select(p => semanticModel.GetDeclaredSymbol(p))
44-
.FirstOrDefault(p => p?.Type.Name is Constants.PluginContextTypeName);
46+
var pluginClassInfo = Helper.GetPluginClassInfo(classDeclaration, semanticModel, context.CancellationToken);
47+
if (pluginClassInfo == null)
48+
{
49+
// Cannot find class that implements IPluginI18n
50+
return;
51+
}
4552

46-
if (contextProperty != null)
53+
// Context property is found, check if it's a valid property
54+
if (pluginClassInfo.PropertyName != null)
4755
{
48-
if (!contextProperty.IsStatic)
56+
if (!pluginClassInfo.IsStatic)
4957
{
5058
context.ReportDiagnostic(Diagnostic.Create(
5159
AnalyzerDiagnostics.ContextIsNotStatic,
52-
contextProperty.DeclaringSyntaxReferences[0].GetSyntax().GetLocation()
60+
pluginClassInfo.CodeFixLocation
5361
));
5462
return;
5563
}
5664

57-
if (contextProperty.DeclaredAccessibility is Accessibility.Private || contextProperty.DeclaredAccessibility is Accessibility.Protected)
65+
if (pluginClassInfo.IsPrivate || pluginClassInfo.IsProtected)
5866
{
5967
context.ReportDiagnostic(Diagnostic.Create(
6068
AnalyzerDiagnostics.ContextAccessIsTooRestrictive,
61-
contextProperty.DeclaringSyntaxReferences[0].GetSyntax().GetLocation()
69+
pluginClassInfo.CodeFixLocation
6270
));
6371
return;
6472
}
6573

74+
// If the context property is valid, we don't need to check for anything else
6675
return;
6776
}
6877

78+
// Context property is not found, check if it's declared as a field
6979
var fieldDeclaration = classDeclaration.Members
7080
.OfType<FieldDeclarationSyntax>()
7181
.SelectMany(f => f.Declaration.Variables)
@@ -75,7 +85,6 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
7585
?.DeclaringSyntaxReferences[0]
7686
.GetSyntax()
7787
.FirstAncestorOrSelf<FieldDeclarationSyntax>();
78-
7988
if (parentSyntax != null)
8089
{
8190
context.ReportDiagnostic(Diagnostic.Create(
@@ -85,13 +94,13 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
8594
return;
8695
}
8796

97+
// Context property is not found, report an error
8898
context.ReportDiagnostic(Diagnostic.Create(
8999
AnalyzerDiagnostics.ContextIsNotDeclared,
90100
classDeclaration.Identifier.GetLocation()
91101
));
92102
}
93103

94-
private static bool IsPluginEntryClass(INamedTypeSymbol namedTypeSymbol) =>
95-
namedTypeSymbol?.Interfaces.Any(i => i.Name == Constants.PluginInterfaceName) ?? false;
104+
#endregion
96105
}
97106
}

Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzerCodeFixProvider.cs

+26-14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ namespace Flow.Launcher.Localization.Analyzers.Localize
1717
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ContextAvailabilityAnalyzerCodeFixProvider)), Shared]
1818
public class ContextAvailabilityAnalyzerCodeFixProvider : CodeFixProvider
1919
{
20+
#region CodeFixProvider
21+
2022
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
2123
AnalyzerDiagnostics.ContextIsAField.Id,
2224
AnalyzerDiagnostics.ContextIsNotStatic.Id,
@@ -82,21 +84,9 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
8284
}
8385
}
8486

85-
private static MemberDeclarationSyntax GetStaticContextPropertyDeclaration(string propertyName = "Context") =>
86-
SyntaxFactory.ParseMemberDeclaration(
87-
$"internal static {Constants.PluginContextTypeName} {propertyName} {{ get; private set; }} = null!;"
88-
);
87+
#endregion
8988

90-
private static Document GetFormattedDocument(CodeFixContext context, SyntaxNode root)
91-
{
92-
var formattedRoot = Formatter.Format(
93-
root,
94-
Formatter.Annotation,
95-
context.Document.Project.Solution.Workspace
96-
);
97-
98-
return context.Document.WithSyntaxRoot(formattedRoot);
99-
}
89+
#region Fix Methods
10090

10191
private static Document FixContextNotDeclared(CodeFixContext context, SyntaxNode root, TextSpan diagnosticSpan)
10292
{
@@ -160,6 +150,24 @@ private static Document FixContextIsAFieldError(CodeFixContext context, SyntaxNo
160150
return GetFormattedDocument(context, newRoot);
161151
}
162152

153+
#region Utils
154+
155+
private static MemberDeclarationSyntax GetStaticContextPropertyDeclaration(string propertyName = "Context") =>
156+
SyntaxFactory.ParseMemberDeclaration(
157+
$"internal static {Constants.PluginContextTypeName} {propertyName} {{ get; private set; }} = null!;"
158+
);
159+
160+
private static Document GetFormattedDocument(CodeFixContext context, SyntaxNode root)
161+
{
162+
var formattedRoot = Formatter.Format(
163+
root,
164+
Formatter.Annotation,
165+
context.Document.Project.Solution.Workspace
166+
);
167+
168+
return context.Document.WithSyntaxRoot(formattedRoot);
169+
}
170+
163171
private static PropertyDeclarationSyntax FixRestrictivePropertyModifiers(PropertyDeclarationSyntax propertyDeclaration)
164172
{
165173
var newModifiers = SyntaxFactory.TokenList();
@@ -185,5 +193,9 @@ private static T GetDeclarationSyntax<T>(SyntaxNode root, TextSpan diagnosticSpa
185193
?.AncestorsAndSelf()
186194
.OfType<T>()
187195
.First();
196+
197+
#endregion
198+
199+
#endregion
188200
}
189201
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace Flow.Launcher.Localization.Shared
4+
{
5+
public class PluginClassInfo
6+
{
7+
public Location Location { get; }
8+
public string ClassName { get; }
9+
public string PropertyName { get; }
10+
public bool IsStatic { get; }
11+
public bool IsPrivate { get; }
12+
public bool IsProtected { get; }
13+
public Location CodeFixLocation { get; }
14+
15+
public string ContextAccessor => $"{ClassName}.{PropertyName}";
16+
public bool IsValid => PropertyName != null && IsStatic && (!IsPrivate) && (!IsProtected);
17+
18+
public PluginClassInfo(Location location, string className, string propertyName, bool isStatic, bool isPrivate, bool isProtected, Location codeFixLocation)
19+
{
20+
Location = location;
21+
ClassName = className;
22+
PropertyName = propertyName;
23+
IsStatic = isStatic;
24+
IsPrivate = isPrivate;
25+
IsProtected = isProtected;
26+
CodeFixLocation = codeFixLocation;
27+
}
28+
}
29+
}

Flow.Launcher.Localization.Shared/Flow.Launcher.Localization.Shared.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.13.0" />
10+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
1111
</ItemGroup>
1212

1313
</Project>

Flow.Launcher.Localization.Shared/Helper.cs

+69-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
using Microsoft.CodeAnalysis.Diagnostics;
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
using Microsoft.CodeAnalysis.Diagnostics;
5+
using System.Linq;
6+
using System.Threading;
27

38
namespace Flow.Launcher.Localization.Shared
49
{
510
public static class Helper
611
{
12+
#region Build Properties
13+
714
public static bool GetFLLUseDependencyInjection(this AnalyzerConfigOptionsProvider configOptions)
815
{
916
if (!configOptions.GlobalOptions.TryGetValue("build_property.FLLUseDependencyInjection", out var result) ||
@@ -13,5 +20,66 @@ public static bool GetFLLUseDependencyInjection(this AnalyzerConfigOptionsProvid
1320
}
1421
return useDI;
1522
}
23+
24+
#endregion
25+
26+
#region Plugin Class Info
27+
28+
/// <summary>
29+
/// If cannot find the class that implements IPluginI18n, return null.
30+
/// If cannot find the context property, return PluginClassInfo with null context property name.
31+
/// </summary>
32+
public static PluginClassInfo GetPluginClassInfo(ClassDeclarationSyntax classDecl, SemanticModel semanticModel, CancellationToken ct)
33+
{
34+
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl, ct);
35+
if (!IsPluginEntryClass(classSymbol))
36+
{
37+
// Cannot find class that implements IPluginI18n
38+
return null;
39+
}
40+
41+
var property = GetContextProperty(classDecl);
42+
var location = GetLocation(semanticModel.SyntaxTree, classDecl);
43+
if (property is null)
44+
{
45+
// Cannot find context
46+
return new PluginClassInfo(location, classDecl.Identifier.Text, null, false, false, false, null);
47+
}
48+
49+
var modifiers = property.Modifiers;
50+
var codeFixLocation = GetCodeFixLocation(property, semanticModel);
51+
return new PluginClassInfo(
52+
location,
53+
classDecl.Identifier.Text,
54+
property.Identifier.Text,
55+
modifiers.Any(SyntaxKind.StaticKeyword),
56+
modifiers.Any(SyntaxKind.PrivateKeyword),
57+
modifiers.Any(SyntaxKind.ProtectedKeyword),
58+
codeFixLocation);
59+
}
60+
61+
private static bool IsPluginEntryClass(INamedTypeSymbol namedTypeSymbol)
62+
{
63+
return namedTypeSymbol?.Interfaces.Any(i => i.Name == Constants.PluginInterfaceName) ?? false;
64+
}
65+
66+
private static PropertyDeclarationSyntax GetContextProperty(ClassDeclarationSyntax classDecl)
67+
{
68+
return classDecl.Members
69+
.OfType<PropertyDeclarationSyntax>()
70+
.FirstOrDefault(p => p.Type.ToString() == Constants.PluginContextTypeName);
71+
}
72+
73+
private static Location GetLocation(SyntaxTree syntaxTree, CSharpSyntaxNode classDeclaration)
74+
{
75+
return Location.Create(syntaxTree, classDeclaration.GetLocation().SourceSpan);
76+
}
77+
78+
private static Location GetCodeFixLocation(PropertyDeclarationSyntax property, SemanticModel semanticModel)
79+
{
80+
return semanticModel.GetDeclaredSymbol(property).DeclaringSyntaxReferences[0].GetSyntax().GetLocation();
81+
}
82+
83+
#endregion
1684
}
1785
}

Flow.Launcher.Localization.SourceGenerators/Localize/LocalizeSourceGenerator.cs

+1-59
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Xml.Linq;
88
using Flow.Launcher.Localization.Shared;
99
using Microsoft.CodeAnalysis;
10-
using Microsoft.CodeAnalysis.CSharp;
1110
using Microsoft.CodeAnalysis.CSharp.Syntax;
1211
using Microsoft.CodeAnalysis.Diagnostics;
1312
using Microsoft.CodeAnalysis.Text;
@@ -56,7 +55,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
5655
var pluginClasses = context.SyntaxProvider
5756
.CreateSyntaxProvider(
5857
predicate: (n, _) => n is ClassDeclarationSyntax,
59-
transform: GetPluginClassInfo)
58+
transform: (c, t) => Helper.GetPluginClassInfo((ClassDeclarationSyntax)c.Node, c.SemanticModel, t))
6059
.Where(info => info != null)
6160
.Collect();
6261

@@ -424,35 +423,6 @@ private static string GetLocalizationKeyFromInvocation(GeneratorSyntaxContext co
424423

425424
#region Get Plugin Class Info
426425

427-
private static PluginClassInfo GetPluginClassInfo(GeneratorSyntaxContext context, CancellationToken ct)
428-
{
429-
var classDecl = (ClassDeclarationSyntax)context.Node;
430-
var location = GetLocation(context.SemanticModel.SyntaxTree, classDecl);
431-
if (!classDecl.BaseList?.Types.Any(t => t.Type.ToString() == Constants.PluginInterfaceName) ?? true)
432-
{
433-
// Cannot find class that implements IPluginI18n
434-
return null;
435-
}
436-
437-
var property = classDecl.Members
438-
.OfType<PropertyDeclarationSyntax>()
439-
.FirstOrDefault(p => p.Type.ToString() == Constants.PluginContextTypeName);
440-
if (property is null)
441-
{
442-
// Cannot find context
443-
return new PluginClassInfo(location, classDecl.Identifier.Text, null, false, false, false);
444-
}
445-
446-
var modifiers = property.Modifiers;
447-
return new PluginClassInfo(
448-
location,
449-
classDecl.Identifier.Text,
450-
property.Identifier.Text,
451-
modifiers.Any(SyntaxKind.StaticKeyword),
452-
modifiers.Any(SyntaxKind.PrivateKeyword),
453-
modifiers.Any(SyntaxKind.ProtectedKeyword));
454-
}
455-
456426
private static PluginClassInfo GetValidPluginInfo(
457427
ImmutableArray<PluginClassInfo> pluginClasses,
458428
SourceProductionContext context,
@@ -531,11 +501,6 @@ private static PluginClassInfo GetValidPluginInfo(
531501
return null;
532502
}
533503

534-
private static Location GetLocation(SyntaxTree syntaxTree, CSharpSyntaxNode classDeclaration)
535-
{
536-
return Location.Create(syntaxTree, classDeclaration.GetLocation().SourceSpan);
537-
}
538-
539504
#endregion
540505

541506
#region Generate Source
@@ -756,29 +721,6 @@ public LocalizableString(string key, string value, string summary, IEnumerable<L
756721
}
757722
}
758723

759-
public class PluginClassInfo
760-
{
761-
public Location Location { get; }
762-
public string ClassName { get; }
763-
public string PropertyName { get; }
764-
public bool IsStatic { get; }
765-
public bool IsPrivate { get; }
766-
public bool IsProtected { get; }
767-
768-
public string ContextAccessor => $"{ClassName}.{PropertyName}";
769-
public bool IsValid => PropertyName != null && IsStatic && (!IsPrivate) && (!IsProtected);
770-
771-
public PluginClassInfo(Location location, string className, string propertyName, bool isStatic, bool isPrivate, bool isProtected)
772-
{
773-
Location = location;
774-
ClassName = className;
775-
PropertyName = propertyName;
776-
IsStatic = isStatic;
777-
IsPrivate = isPrivate;
778-
IsProtected = isProtected;
779-
}
780-
}
781-
782724
#endregion
783725
}
784726
}

0 commit comments

Comments
 (0)