Returned to all generator and shouted roslyn down the mouth

This commit is contained in:
2025-08-20 03:43:46 +01:00
parent 43affa3a85
commit afb31f8ce0
2 changed files with 417 additions and 115 deletions

View File

@@ -1482,6 +1482,8 @@ dotnet_diagnostic.ph2141.severity = none
dotnet_diagnostic.ph2144.severity = none dotnet_diagnostic.ph2144.severity = none
dotnet_diagnostic.ph2151.severity = none dotnet_diagnostic.ph2151.severity = none
dotnet_diagnostic.mt1001.severity = warning dotnet_diagnostic.mt1001.severity = warning
dotnet_diagnostic.RS1035.severity = none
dotnet_diagnostic.RS1042.severity = none
[*.{razor}] [*.{razor}]
resharper_normalize_tag_names = false resharper_normalize_tag_names = false

View File

@@ -1,161 +1,461 @@
#nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace Aaru.Generators; namespace Aaru.Generators;
[Generator] [Generator]
public sealed class PluginRegisterGenerator : IIncrementalGenerator public class PluginRegisterGenerator : ISourceGenerator
{ {
// name → (registration method, directOnly) #region ISourceGenerator Members
static readonly (string Name, string Method, bool DirectOnly)[] PluginMap = new[]
/// <inheritdoc />
public void Initialize(GeneratorInitializationContext context) =>
// Nothing to do
context.RegisterForSyntaxNotifications(() => new PluginFinder());
/// <inheritdoc />
public void Execute(GeneratorExecutionContext context)
{ {
("IArchive", "RegisterArchivePlugins", true), ("IChecksum", "RegisterChecksumPlugins", true), /*
("IFilesystem", "RegisterFilesystemPlugins", true), ("IFilter", "RegisterFilterPlugins", true), #if DEBUG
("IFloppyImage", "RegisterFloppyImagePlugins", true), if(!Debugger.IsAttached)
("IMediaImage", "RegisterMediaImagePlugins", true), // direct only {
("IPartition", "RegisterPartitionPlugins", true), Debugger.Launch();
("IReadOnlyFilesystem", "RegisterReadOnlyFilesystemPlugins", true), }
("IWritableFloppyImage", "RegisterWritableFloppyImagePlugins", true), #endif
("IWritableImage", "RegisterWritableImagePlugins", false), // inherited OK */
("IByteAddressableImage", "RegisterByteAddressablePlugins", false),
("IFluxImage", "RegisterFluxImagePlugins", true),
("IWritableFluxImage", "RegisterWritableFluxImagePlugins", false)
// …add more as needed… ClassDeclarationSyntax pluginRegister = ((PluginFinder)context.SyntaxReceiver)?.Register;
};
#region IIncrementalGenerator Members if(pluginRegister == null) return;
public void Initialize(IncrementalGeneratorInitializationContext ctx) string @namespace =
{ (pluginRegister.Ancestors().FirstOrDefault(x => x is FileScopedNamespaceDeclarationSyntax) as
// 1) grab every ClassDeclarationSyntax that has a base list FileScopedNamespaceDeclarationSyntax)?.Name.ToString();
IncrementalValueProvider<ImmutableArray<ClassDeclarationSyntax>> classSyntaxes = ctx.SyntaxProvider
.CreateSyntaxProvider((node, ct) => node is ClassDeclarationSyntax cds && cds.BaseList != null,
(ctx, ct) => (ClassDeclarationSyntax)ctx.Node)
.Collect();
// 2) combine with the compilation for symbol lookups @namespace ??=
IncrementalValueProvider<(Compilation Left, ImmutableArray<ClassDeclarationSyntax> Right)> (pluginRegister.Ancestors().FirstOrDefault(x => x is NamespaceDeclarationSyntax) as
compilationAndClasses = ctx.CompilationProvider.Combine(classSyntaxes); NamespaceDeclarationSyntax)?.ToString();
// 3) register our source output string className = pluginRegister.Identifier.Text;
ctx.RegisterSourceOutput(compilationAndClasses,
(spc, source) =>
{
(Compilation? compilation, ImmutableArray<ClassDeclarationSyntax> classDecls) =
source;
if(compilation is null) return; List<string> archives = ((PluginFinder)context.SyntaxReceiver)?.Archives;
List<string> checksums = ((PluginFinder)context.SyntaxReceiver)?.Checksums;
List<string> fileSystems = ((PluginFinder)context.SyntaxReceiver)?.FileSystems;
List<string> filters = ((PluginFinder)context.SyntaxReceiver)?.Filters;
List<string> floppyImagePlugins = ((PluginFinder)context.SyntaxReceiver)?.FloppyImagePlugins;
List<string> partitionPlugins = ((PluginFinder)context.SyntaxReceiver)?.PartitionPlugins;
List<string> mediaImagePlugins = ((PluginFinder)context.SyntaxReceiver)?.MediaImagePlugins;
List<string> readOnlyFileSystems = ((PluginFinder)context.SyntaxReceiver)?.ReadOnlyFileSystems;
List<string> writableFloppyImagePlugins = ((PluginFinder)context.SyntaxReceiver)?.WritableFloppyImagePlugins;
List<string> writableImagePlugins = ((PluginFinder)context.SyntaxReceiver)?.WritableImagePlugins;
List<string> byteAddressableImagePlugins = ((PluginFinder)context.SyntaxReceiver)?.ByteAddressableImagePlugins;
List<string> fluxImagePlugins = ((PluginFinder)context.SyntaxReceiver)?.FluxImagePlugins;
List<string> writableFluxImagePlugins = ((PluginFinder)context.SyntaxReceiver)?.WritableFluxImagePlugins;
// load all plugininterface symbols StringBuilder sb = new();
(string Name, string Method, bool DirectOnly, INamedTypeSymbol? Symbol)[]
ifaceDefs = PluginMap.Select(x =>
{
INamedTypeSymbol? sym =
compilation
.GetTypeByMetadataName($"Aaru.CommonTypes.Interfaces.{x.Name}");
return (x.Name, x.Method, x.DirectOnly, Symbol: sym); sb.AppendLine("""
}) // /***************************************************************************
.Where(x => x.Symbol is not null) // Aaru Data Preservation Suite
.ToArray(); // ----------------------------------------------------------------------------
//
// Filename : Register.g.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ Description ] ----------------------------------------------------------
//
// Registers all plugins in this assembly.
//
// --[ License ] --------------------------------------------------------------
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
""");
// load IPluginRegister sb.AppendLine();
INamedTypeSymbol? registerSym = sb.AppendLine("using System;");
compilation sb.AppendLine("using System.Collections.Generic;");
.GetTypeByMetadataName("Aaru.CommonTypes.Interfaces.IPluginRegister"); sb.AppendLine("using Aaru.CommonTypes.Interfaces;");
sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
sb.AppendLine();
sb.AppendLine($"namespace {@namespace};");
sb.AppendLine();
sb.AppendLine($"public sealed partial class {className} : IPluginRegister");
sb.AppendLine("{");
var plugins = new List<PluginInfo>(); if(archives?.Count > 0)
{
sb.AppendLine(" public void RegisterArchivePlugins(IServiceCollection services)");
sb.AppendLine(" {");
foreach(ClassDeclarationSyntax? decl in classDecls.Distinct()) foreach(string plugin in archives.Distinct())
{ sb.AppendLine($" services.AddTransient<IArchive, {plugin}>();");
SemanticModel model = compilation.GetSemanticModel(decl.SyntaxTree);
var cls = model.GetDeclaredSymbol(decl, spc.CancellationToken) sb.AppendLine(" }");
as INamedTypeSymbol; }
else
sb.AppendLine(" public void RegisterArchivePlugins(IServiceCollection services) {}");
if(cls is null) continue; sb.AppendLine();
// direct vs. all (transitive) interfaces if(checksums?.Count > 0)
ImmutableArray<INamedTypeSymbol> directIfaces = cls.Interfaces; {
ImmutableArray<INamedTypeSymbol> allIfaces = cls.AllInterfaces; sb.AppendLine(" public void RegisterChecksumPlugins(IServiceCollection services)");
sb.AppendLine(" {");
var info = new PluginInfo foreach(string plugin in checksums.Distinct())
{ sb.AppendLine($" services.AddTransient<IChecksum, {plugin}>();");
Namespace = cls.ContainingNamespace.ToDisplayString(),
ClassName = cls.Name,
IsRegister =
registerSym != null &&
allIfaces.Contains(registerSym, SymbolEqualityComparer.Default)
};
// for each plugin interface, choose direct or inherited match sb.AppendLine(" }");
foreach((string Name, string Method, bool DirectOnly, }
INamedTypeSymbol? Symbol) in ifaceDefs) else
{ sb.AppendLine(" public void RegisterChecksumPlugins(IServiceCollection services) {}");
bool matches = DirectOnly
? directIfaces.Contains(Symbol!,
SymbolEqualityComparer.Default)
: allIfaces.Contains(Symbol!,
SymbolEqualityComparer.Default);
if(matches) info.Interfaces.Add((Name, Method)); sb.AppendLine();
}
if(info.IsRegister || info.Interfaces.Count > 0) plugins.Add(info); if(fileSystems?.Count > 0)
} {
sb.AppendLine(" public void RegisterFilesystemPlugins(IServiceCollection services)");
sb.AppendLine(" {");
if(plugins.Count == 0) return; foreach(string plugin in fileSystems.Distinct())
sb.AppendLine($" services.AddTransient<IFilesystem, {plugin}>();");
// find the one class that implements IPluginRegister sb.AppendLine(" }");
PluginInfo? registrar = plugins.FirstOrDefault(p => p.IsRegister); }
else
sb.AppendLine(" public void RegisterFilesystemPlugins(IServiceCollection services) {}");
if(registrar is null) return; sb.AppendLine();
// build the generated file if(filters?.Count > 0)
var sb = new StringBuilder(); {
sb.AppendLine("using Microsoft.Extensions.DependencyInjection;"); sb.AppendLine(" public void RegisterFilterPlugins(IServiceCollection services)");
sb.AppendLine("using Aaru.CommonTypes.Interfaces;"); sb.AppendLine(" {");
sb.AppendLine($"namespace {registrar.Namespace};");
sb.AppendLine($"public sealed partial class {registrar.ClassName} : IPluginRegister");
sb.AppendLine("{");
// emit one registration method per plugininterface foreach(string plugin in filters.Distinct())
foreach((string Name, string Method, bool _) in PluginMap) sb.AppendLine($" services.AddTransient<IFilter, {plugin}>();");
{
sb.AppendLine($" public void {Method}(IServiceCollection services)");
sb.AppendLine(" {");
foreach(string? impl in plugins sb.AppendLine(" }");
.Where(pi => pi.Interfaces.Any(i => i.Name == Name)) }
.Select(pi => pi.ClassName) else
.Distinct()) sb.AppendLine(" public void RegisterFilterPlugins(IServiceCollection services) {}");
sb.AppendLine($" services.AddTransient<{Name}, {impl}>();");
sb.AppendLine(" }"); sb.AppendLine();
}
sb.AppendLine("}"); if(floppyImagePlugins?.Count > 0)
spc.AddSource("Register.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8)); {
}); sb.AppendLine(" public void RegisterFloppyImagePlugins(IServiceCollection services)");
sb.AppendLine(" {");
foreach(string plugin in floppyImagePlugins.Distinct())
sb.AppendLine($" services.AddTransient<IFloppyImage, {plugin}>();");
sb.AppendLine(" }");
}
else
sb.AppendLine(" public void RegisterFloppyImagePlugins(IServiceCollection services) {}");
sb.AppendLine();
if(mediaImagePlugins?.Count > 0)
{
sb.AppendLine(" public void RegisterMediaImagePlugins(IServiceCollection services)");
sb.AppendLine(" {");
foreach(string plugin in mediaImagePlugins.Distinct())
sb.AppendLine($" services.AddTransient<IMediaImage, {plugin}>();");
sb.AppendLine(" }");
}
else
sb.AppendLine(" public void RegisterMediaImagePlugins(IServiceCollection services) {}");
sb.AppendLine();
if(partitionPlugins?.Count > 0)
{
sb.AppendLine(" public void RegisterPartitionPlugins(IServiceCollection services)");
sb.AppendLine(" {");
foreach(string plugin in partitionPlugins.Distinct())
sb.AppendLine($" services.AddTransient<IPartition, {plugin}>();");
sb.AppendLine(" }");
}
else
sb.AppendLine(" public void RegisterPartitionPlugins(IServiceCollection services) {}");
sb.AppendLine();
if(readOnlyFileSystems?.Count > 0)
{
sb.AppendLine(" public void RegisterReadOnlyFilesystemPlugins(IServiceCollection services)");
sb.AppendLine(" {");
foreach(string plugin in readOnlyFileSystems.Distinct())
sb.AppendLine($" services.AddTransient<IReadOnlyFilesystem, {plugin}>();");
sb.AppendLine(" }");
}
else
sb.AppendLine(" public void RegisterReadOnlyFilesystemPlugins(IServiceCollection services) {}");
sb.AppendLine();
if(writableFloppyImagePlugins?.Count > 0)
{
sb.AppendLine(" public void RegisterWritableFloppyImagePlugins(IServiceCollection services)");
sb.AppendLine(" {");
foreach(string plugin in writableFloppyImagePlugins.Distinct())
sb.AppendLine($" services.AddTransient<IWritableFloppyImage, {plugin}>();");
sb.AppendLine(" }");
}
else
sb.AppendLine(" public void RegisterWritableFloppyImagePlugins(IServiceCollection services) {}");
sb.AppendLine();
if(writableImagePlugins?.Count > 0)
{
sb.AppendLine(" public void RegisterWritableImagePlugins(IServiceCollection services)");
sb.AppendLine(" {");
foreach(string plugin in writableImagePlugins.Distinct())
sb.AppendLine($" services.AddTransient<IBaseWritableImage, {plugin}>();");
sb.AppendLine(" }");
}
else
sb.AppendLine(" public void RegisterWritableImagePlugins(IServiceCollection services) {}");
sb.AppendLine();
if(byteAddressableImagePlugins?.Count > 0)
{
sb.AppendLine(" public void RegisterByteAddressablePlugins(IServiceCollection services)");
sb.AppendLine(" {");
foreach(string plugin in byteAddressableImagePlugins.Distinct())
sb.AppendLine($" services.AddTransient<IByteAddressableImage, {plugin}>();");
sb.AppendLine(" }");
}
else
sb.AppendLine(" public void RegisterByteAddressablePlugins(IServiceCollection services) {}");
if(fluxImagePlugins?.Count > 0)
{
sb.AppendLine(" public void RegisterFluxImagePlugins(IServiceCollection services)");
sb.AppendLine(" {");
foreach(string plugin in fluxImagePlugins.Distinct())
sb.AppendLine($" services.AddTransient<IFluxImage, {plugin}>();");
sb.AppendLine(" }");
}
else
sb.AppendLine(" public void RegisterFluxImagePlugins(IServiceCollection services) {}");
if(writableFluxImagePlugins?.Count > 0)
{
sb.AppendLine(" public void RegisterWritableFluxImagePlugins(IServiceCollection services)");
sb.AppendLine(" {");
foreach(string plugin in writableFluxImagePlugins.Distinct())
sb.AppendLine($" services.AddTransient<IWritableFluxImage, {plugin}>();");
sb.AppendLine(" }");
}
else
sb.AppendLine(" public void RegisterWritableFluxImagePlugins(IServiceCollection services) {}");
sb.AppendLine("}");
context.AddSource("Register.g.cs", sb.ToString());
} }
#endregion #endregion
#region Nested type: PluginInfo #region Nested type: PluginFinder
class PluginInfo sealed class PluginFinder : ISyntaxReceiver
{ {
public string ClassName = ""; public List<string> Archives { get; } = [];
public readonly List<(string Name, string Method)> Interfaces = new(); public List<string> Checksums { get; } = [];
public bool IsRegister; public List<string> FileSystems { get; } = [];
public string Namespace = ""; public List<string> Filters { get; } = [];
public List<string> FloppyImagePlugins { get; } = [];
public List<string> MediaImagePlugins { get; } = [];
public List<string> PartitionPlugins { get; } = [];
public List<string> ReadOnlyFileSystems { get; } = [];
public List<string> WritableFloppyImagePlugins { get; } = [];
public List<string> WritableImagePlugins { get; } = [];
public List<string> ByteAddressableImagePlugins { get; } = [];
public List<string> FluxImagePlugins { get; } = [];
public List<string> WritableFluxImagePlugins { get; } = [];
public ClassDeclarationSyntax Register { get; private set; }
#region ISyntaxReceiver Members
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if(syntaxNode is not ClassDeclarationSyntax plugin) return;
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText ==
"IPluginRegister") ==
true)
Register = plugin;
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText ==
"IArchive") ==
true)
{
if(!Archives.Contains(plugin.Identifier.Text)) Archives.Add(plugin.Identifier.Text);
}
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText ==
"IChecksum") ==
true)
{
if(!Checksums.Contains(plugin.Identifier.Text)) Checksums.Add(plugin.Identifier.Text);
}
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText ==
"IFilesystem") ==
true)
{
if(!FileSystems.Contains(plugin.Identifier.Text)) FileSystems.Add(plugin.Identifier.Text);
}
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText ==
"IFilter") ==
true)
{
if(!Filters.Contains(plugin.Identifier.Text)) Filters.Add(plugin.Identifier.Text);
}
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText ==
"IFloppyImage") ==
true)
{
if(!FloppyImagePlugins.Contains(plugin.Identifier.Text)) FloppyImagePlugins.Add(plugin.Identifier.Text);
}
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText ==
"IFluxImage") ==
true)
{
if(!FluxImagePlugins.Contains(plugin.Identifier.Text)) FluxImagePlugins.Add(plugin.Identifier.Text);
}
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText is "IMediaImage"
or "IOpticalMediaImage"
or "IFloppyImage"
or "ITapeImage"
or "IFluxImage") ==
true)
{
if(!MediaImagePlugins.Contains(plugin.Identifier.Text)) MediaImagePlugins.Add(plugin.Identifier.Text);
}
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText ==
"IPartition") ==
true)
{
if(!PartitionPlugins.Contains(plugin.Identifier.Text)) PartitionPlugins.Add(plugin.Identifier.Text);
}
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText ==
"IReadOnlyFilesystem") ==
true)
{
if(!ReadOnlyFileSystems.Contains(plugin.Identifier.Text))
ReadOnlyFileSystems.Add(plugin.Identifier.Text);
}
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText ==
"IWritableFloppyImage") ==
true)
{
if(!WritableFloppyImagePlugins.Contains(plugin.Identifier.Text))
WritableFloppyImagePlugins.Add(plugin.Identifier.Text);
}
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText ==
"IWritableFluxImage") ==
true)
{
if(!WritableFluxImagePlugins.Contains(plugin.Identifier.Text))
WritableFluxImagePlugins.Add(plugin.Identifier.Text);
}
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText is "IWritableImage"
or "IWritableOpticalImage"
or "IWritableTapeImage"
or "IByteAddressableImage"
or "IWritableFluxImage") ==
true)
{
if(!WritableImagePlugins.Contains(plugin.Identifier.Text))
WritableImagePlugins.Add(plugin.Identifier.Text);
}
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
.ValueText ==
"IByteAddressableImage") ==
true)
{
if(!ByteAddressableImagePlugins.Contains(plugin.Identifier.Text))
ByteAddressableImagePlugins.Add(plugin.Identifier.Text);
}
MediaImagePlugins.AddRange(WritableImagePlugins.Where(t => !ByteAddressableImagePlugins.Contains(t)));
FileSystems.AddRange(ReadOnlyFileSystems);
}
#endregion
} }
#endregion #endregion