Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FLLUseDependencyInjection property group & Move all constants to Shared project #18

Merged
merged 4 commits into from
Mar 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.13.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Flow.Launcher.Localization.Shared\Flow.Launcher.Localization.Shared.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Immutable;
using System.Linq;
using Flow.Launcher.Localization.Shared;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand All @@ -7,7 +8,7 @@

namespace Flow.Launcher.Localization.Analyzers.Localize
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]

Check warning on line 11 in Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces. The Microsoft.CodeAnalysis.Workspaces assembly is not provided during command line compilation scenarios, so references to it could cause the compiler extension to behave unpredictably. (https://github.com/dotnet/roslyn-analyzers/blob/main/docs/rules/RS1038.md)

Check warning on line 11 in Flow.Launcher.Localization.Analyzers/Localize/ContextAvailabilityAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

This compiler extension should not be implemented in an assembly containing a reference to Microsoft.CodeAnalysis.Workspaces. The Microsoft.CodeAnalysis.Workspaces assembly is not provided during command line compilation scenarios, so references to it could cause the compiler extension to behave unpredictably. (https://github.com/dotnet/roslyn-analyzers/blob/main/docs/rules/RS1038.md)
public class ContextAvailabilityAnalyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
Expand All @@ -17,10 +18,6 @@
AnalyzerDiagnostics.ContextIsNotDeclared
);

private const string PluginContextTypeName = "PluginInitContext";

private const string PluginInterfaceName = "IPluginI18n";

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
Expand All @@ -30,6 +27,12 @@

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;

var classDeclaration = (ClassDeclarationSyntax)context.Node;
var semanticModel = context.SemanticModel;
var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration);
Expand All @@ -38,7 +41,7 @@

var contextProperty = classDeclaration.Members.OfType<PropertyDeclarationSyntax>()
.Select(p => semanticModel.GetDeclaredSymbol(p))
.FirstOrDefault(p => p?.Type.Name is PluginContextTypeName);
.FirstOrDefault(p => p?.Type.Name is Constants.PluginContextTypeName);

if (contextProperty != null)
{
Expand Down Expand Up @@ -67,7 +70,7 @@
.OfType<FieldDeclarationSyntax>()
.SelectMany(f => f.Declaration.Variables)
.Select(f => semanticModel.GetDeclaredSymbol(f))
.FirstOrDefault(f => f is IFieldSymbol fs && fs.Type.Name is PluginContextTypeName);
.FirstOrDefault(f => f is IFieldSymbol fs && fs.Type.Name is Constants.PluginContextTypeName);
var parentSyntax = fieldDeclaration
?.DeclaringSyntaxReferences[0]
.GetSyntax()
Expand All @@ -89,6 +92,6 @@
}

private static bool IsPluginEntryClass(INamedTypeSymbol namedTypeSymbol) =>
namedTypeSymbol?.Interfaces.Any(i => i.Name == PluginInterfaceName) ?? false;
namedTypeSymbol?.Interfaces.Any(i => i.Name == Constants.PluginInterfaceName) ?? false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,5 @@ InvocationExpressionSyntax invocationExpr
var newRoot = root.ReplaceNode(invocationExpr, newInnerInvocationExpr);
return context.Document.WithSyntaxRoot(newRoot);
}

}
}
18 changes: 18 additions & 0 deletions Flow.Launcher.Localization.Shared/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Text.RegularExpressions;

namespace Flow.Launcher.Localization.Shared
{
public static class Constants
{
public const string DefaultNamespace = "Flow.Launcher";
public const string ClassName = "Localize";
public const string PluginInterfaceName = "IPluginI18n";
public const string PluginContextTypeName = "PluginInitContext";
public const string SystemPrefixUri = "clr-namespace:System;assembly=mscorlib";
public const string XamlPrefixUri = "http://schemas.microsoft.com/winfx/2006/xaml";
public const string XamlTag = "String";
public const string KeyAttribute = "Key";

public static readonly Regex LanguagesXamlRegex = new Regex(@"\\Languages\\[^\\]+\.xaml$", RegexOptions.IgnoreCase);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>0.0.1</Version>
<TargetFramework>netstandard2.0</TargetFramework>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<RootNamespace>Flow.Launcher.Localization.Shared</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.13.0" />
</ItemGroup>

</Project>
17 changes: 17 additions & 0 deletions Flow.Launcher.Localization.Shared/Helper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.CodeAnalysis.Diagnostics;

namespace Flow.Launcher.Localization.Shared
{
public static class Helper
{
public static bool GetFLLUseDependencyInjection(this AnalyzerConfigOptionsProvider configOptions)
{
if (!configOptions.GlobalOptions.TryGetValue("build_property.FLLUseDependencyInjection", out var result) ||
!bool.TryParse(result, out var useDI))
{
return false; // Default to false
}
return useDI;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Flow.Launcher.Localization.Shared\Flow.Launcher.Localization.Shared.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
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;

namespace Flow.Launcher.Localization.SourceGenerators.Localize
Expand All @@ -21,19 +22,6 @@ public partial class LocalizeSourceGenerator : IIncrementalGenerator
{
#region Fields

private const string CoreNamespace1 = "Flow.Launcher";
private const string CoreNamespace2 = "Flow.Launcher.Core";
private const string DefaultNamespace = "Flow.Launcher";
private const string ClassName = "Localize";
private const string PluginInterfaceName = "IPluginI18n";
private const string PluginContextTypeName = "PluginInitContext";
private const string systemPrefixUri = "clr-namespace:System;assembly=mscorlib";
private const string xamlPrefixUri = "http://schemas.microsoft.com/winfx/2006/xaml";
private const string XamlTag = "String";
private const string KeyTag = "Key";

private static readonly Regex _languagesXamlRegex = new Regex(@"\\Languages\\[^\\]+\.xaml$", RegexOptions.IgnoreCase);

private static readonly Version PackageVersion = typeof(LocalizeSourceGenerator).Assembly.GetName().Version;

private static readonly ImmutableArray<LocalizableString> _emptyLocalizableStrings = ImmutableArray<LocalizableString>.Empty;
Expand All @@ -50,7 +38,7 @@ public partial class LocalizeSourceGenerator : IIncrementalGenerator
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var xamlFiles = context.AdditionalTextsProvider
.Where(file => _languagesXamlRegex.IsMatch(file.Path));
.Where(file => Constants.LanguagesXamlRegex.IsMatch(file.Path));

var localizedStrings = xamlFiles
.Select((file, ct) => ParseXamlFile(file, ct))
Expand All @@ -75,7 +63,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

var compilation = context.CompilationProvider;

var combined = localizedStrings.Combine(invocationKeys).Combine(pluginClasses).Combine(compilation).Combine(xamlFiles.Collect());
var configOptions = context.AnalyzerConfigOptionsProvider;

var combined = localizedStrings.Combine(invocationKeys).Combine(pluginClasses).Combine(configOptions).Combine(compilation).Combine(xamlFiles.Collect());

context.RegisterSourceOutput(combined, Execute);
}
Expand All @@ -86,10 +76,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
/// <param name="spc">The source production context.</param>
/// <param name="data">The provided data.</param>
private void Execute(SourceProductionContext spc,
((((ImmutableArray<LocalizableString> LocalizableStrings,
ImmutableHashSet<string> InvocationKeys),
ImmutableArray<PluginClassInfo> PluginClassInfos),
Compilation Compilation),
(((((ImmutableArray<LocalizableString> LocalizableStrings,
ImmutableHashSet<string> InvocationKeys),
ImmutableArray<PluginClassInfo> PluginClassInfos),
AnalyzerConfigOptionsProvider ConfigOptionsProvider),
Compilation Compilation),
ImmutableArray<AdditionalText> AdditionalTexts) data)
{
var xamlFiles = data.AdditionalTexts;
Expand All @@ -103,23 +94,28 @@ private void Execute(SourceProductionContext spc,
}

var compilation = data.Item1.Compilation;
var pluginClasses = data.Item1.Item1.PluginClassInfos;
var usedKeys = data.Item1.Item1.Item1.InvocationKeys;
var localizedStrings = data.Item1.Item1.Item1.LocalizableStrings;
var configOptions = data.Item1.Item1.ConfigOptionsProvider;
var pluginClasses = data.Item1.Item1.Item1.PluginClassInfos;
var usedKeys = data.Item1.Item1.Item1.Item1.InvocationKeys;
var localizedStrings = data.Item1.Item1.Item1.Item1.LocalizableStrings;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think those Item1 are getting out of hand. Isn't there a way to access them by their actual names?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really do not know how to name those things, like ((((ImmutableArray<LocalizableString> LocalizableStrings, ImmutableHashSet<string> InvocationKeys), ImmutableArray<PluginClassInfo> PluginClassInfos), AnalyzerConfigOptionsProvider ConfigOptionsProvider), Compilation Compilation) such type.

image

Use Others or Left? I have no idea. If you have any good name for me, please let me know.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just curious if it's possible to avoid using tuples altogether and just use a record:

private void Execute(SourceProductionContext context, GeneratorInputs data)
{

I don't have in-depth knowledge about source generators, so I don't know if it's possible or not. Let's leave it as is for now, and just remember that this is something that should probably change in the future.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that .NetCore2.0 cannot support record....


var assemblyName = compilation.AssemblyName ?? DefaultNamespace;
var assemblyName = compilation.AssemblyName ?? Constants.DefaultNamespace;
var optimizationLevel = compilation.Options.OptimizationLevel;
var useDI = configOptions.GetFLLUseDependencyInjection();

var pluginInfo = GetValidPluginInfo(pluginClasses, spc);
var isCoreAssembly = assemblyName == CoreNamespace1 || assemblyName == CoreNamespace2;

PluginClassInfo pluginInfo = null;
if (!useDI)
{
pluginInfo = GetValidPluginInfo(pluginClasses, spc);
}

GenerateSource(
spc,
xamlFiles[0],
localizedStrings,
optimizationLevel,
assemblyName,
isCoreAssembly,
useDI,
pluginInfo,
usedKeys);
}
Expand Down Expand Up @@ -155,11 +151,11 @@ private static ImmutableArray<LocalizableString> ParseXamlFile(AdditionalText fi
string uri = attr.Value;
string prefix = attr.Name.LocalName;

if (uri == systemPrefixUri)
if (uri == Constants.SystemPrefixUri)
{
systemPrefix = prefix;
}
else if (uri == xamlPrefixUri)
else if (uri == Constants.XamlPrefixUri)
{
xamlPrefix = prefix;
}
Expand All @@ -179,14 +175,14 @@ private static ImmutableArray<LocalizableString> ParseXamlFile(AdditionalText fi
}

var localizableStrings = new List<LocalizableString>();
foreach (var element in doc.Descendants(systemNs + XamlTag)) // "String" elements in system namespace
foreach (var element in doc.Descendants(systemNs + Constants.XamlTag)) // "String" elements in system namespace
{
if (ct.IsCancellationRequested)
{
return _emptyLocalizableStrings;
}

var key = element.Attribute(xNs + KeyTag)?.Value; // "Key" attribute in xaml namespace
var key = element.Attribute(xNs + Constants.KeyAttribute)?.Value; // "Key" attribute in xaml namespace
var value = element.Value;
var comment = element.PreviousNode as XComment;

Expand Down Expand Up @@ -424,7 +420,7 @@ private static string GetLocalizationKeyFromInvocation(GeneratorSyntaxContext co
parts.Reverse();

// Check if the first part is ClassName and there's at least one more part
if (parts.Count < 2 || parts[0] != ClassName)
if (parts.Count < 2 || parts[0] != Constants.ClassName)
{
return null;
}
Expand All @@ -440,15 +436,15 @@ private static PluginClassInfo GetPluginClassInfo(GeneratorSyntaxContext context
{
var classDecl = (ClassDeclarationSyntax)context.Node;
var location = GetLocation(context.SemanticModel.SyntaxTree, classDecl);
if (!classDecl.BaseList?.Types.Any(t => t.Type.ToString() == PluginInterfaceName) ?? true)
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<PropertyDeclarationSyntax>()
.FirstOrDefault(p => p.Type.ToString() == PluginContextTypeName);
.FirstOrDefault(p => p.Type.ToString() == Constants.PluginContextTypeName);
if (property is null)
{
// Cannot find context
Expand Down Expand Up @@ -532,7 +528,7 @@ private static void GenerateSource(
ImmutableArray<LocalizableString> localizedStrings,
OptimizationLevel optimizationLevel,
string assemblyName,
bool isCoreAssembly,
bool useDI,
PluginClassInfo pluginInfo,
IEnumerable<string> usedKeys)
{
Expand Down Expand Up @@ -560,13 +556,6 @@ private static void GenerateSource(
GeneratedHeaderFromPath(sourceBuilder, xamlFile.Path);
sourceBuilder.AppendLine();

// Generate usings
if (isCoreAssembly)
{
sourceBuilder.AppendLine("using Flow.Launcher.Core.Resource;");
sourceBuilder.AppendLine();
}

// Generate nullable enable
sourceBuilder.AppendLine("#nullable enable");
sourceBuilder.AppendLine();
Expand Down Expand Up @@ -603,11 +592,26 @@ private static void GenerateSource(

// Generate class
sourceBuilder.AppendLine($"[System.CodeDom.Compiler.GeneratedCode(\"{nameof(LocalizeSourceGenerator)}\", \"{PackageVersion}\")]");
sourceBuilder.AppendLine($"public static class {ClassName}");
sourceBuilder.AppendLine($"public static class {Constants.ClassName}");
sourceBuilder.AppendLine("{");

// Generate localization methods
var tabString = Spacing(1);

// Generate API instance
string getTranslation = null;
if (useDI)
{
sourceBuilder.AppendLine($"{tabString}private static Flow.Launcher.Plugin.IPublicAPI? api = null;");
sourceBuilder.AppendLine($"{tabString}private static Flow.Launcher.Plugin.IPublicAPI Api => api ??= CommunityToolkit.Mvvm.DependencyInjection.Ioc.Default.GetRequiredService<Flow.Launcher.Plugin.IPublicAPI>();");
sourceBuilder.AppendLine();
getTranslation = "Api.GetTranslation";
}
else if (pluginInfo?.IsValid == true)
{
getTranslation = $"{pluginInfo.ContextAccessor}.API.GetTranslation";
}

// Generate localization methods
foreach (var ls in localizedStrings)
{
// TODO: Add support for usedKeys
Expand All @@ -617,13 +621,13 @@ private static void GenerateSource(
}*/

GenerateDocComments(sourceBuilder, ls, tabString);
GenerateLocalizationMethod(sourceBuilder, ls, isCoreAssembly, pluginInfo, tabString);
GenerateLocalizationMethod(sourceBuilder, ls, getTranslation, tabString);
}

sourceBuilder.AppendLine("}");

// Add source to context
spc.AddSource($"{ClassName}.{assemblyName}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
spc.AddSource($"{Constants.ClassName}.{assemblyName}.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
}

private static void GeneratedHeaderFromPath(StringBuilder sb, string xamlFilePath)
Expand Down Expand Up @@ -673,8 +677,7 @@ private static void GenerateDocComments(StringBuilder sb, LocalizableString ls,
private static void GenerateLocalizationMethod(
StringBuilder sb,
LocalizableString ls,
bool isCoreAssembly,
PluginClassInfo pluginInfo,
string getTranslation,
string tabString)
{
sb.Append($"{tabString}public static string {ls.Key}(");
Expand All @@ -687,18 +690,8 @@ private static void GenerateLocalizationMethod(
? $", {string.Join(", ", parameters.Select(p => p.Name))}"
: string.Empty;

if (isCoreAssembly)
{
var getTranslation = "InternationalizationManager.Instance.GetTranslation";
sb.AppendLine(parameters.Count > 0
? !ls.Format ?
$"string.Format({getTranslation}(\"{ls.Key}\"){formatArgs});"
: $"string.Format(System.Globalization.CultureInfo.CurrentCulture, {getTranslation}(\"{ls.Key}\"){formatArgs});"
: $"{getTranslation}(\"{ls.Key}\");");
}
else if (pluginInfo?.IsValid == true)
if (!(string.IsNullOrEmpty(getTranslation)))
{
var getTranslation = $"{pluginInfo.ContextAccessor}.API.GetTranslation";
sb.AppendLine(parameters.Count > 0
? !ls.Format ?
$"string.Format({getTranslation}(\"{ls.Key}\"){formatArgs});"
Expand Down
Loading
Loading