Files
git.stella-ops.org/src/Scanner/StellaOps.Scanner.Analyzers.Plugin.Unified/AnalyzerPluginAdapterFactory.cs
2026-02-01 21:37:40 +02:00

176 lines
6.3 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using StellaOps.Plugin.Abstractions;
using StellaOps.Plugin.Abstractions.Capabilities;
using StellaOps.Scanner.Analyzers.Lang;
using StellaOps.Scanner.Analyzers.Lang.Plugin;
namespace StellaOps.Scanner.Analyzers.Plugin.Unified;
/// <summary>
/// Factory for creating unified analyzer plugin adapters from existing analyzers.
/// </summary>
public sealed class AnalyzerPluginAdapterFactory
{
private readonly ILanguageAnalyzerPluginCatalog _catalog;
private readonly IServiceProvider _serviceProvider;
private readonly Dictionary<string, AnalyzerPluginAdapter> _adapters = new(StringComparer.OrdinalIgnoreCase);
private readonly object _lock = new();
// Known file patterns for each analyzer type
private static readonly Dictionary<string, string[]> KnownFilePatterns = new(StringComparer.OrdinalIgnoreCase)
{
["dotnet"] = new[] { "*.csproj", "*.fsproj", "*.vbproj", "*.sln", "packages.config", "*.deps.json", "Directory.Packages.props" },
["go"] = new[] { "go.mod", "go.sum", "Gopkg.lock", "Gopkg.toml" },
["java"] = new[] { "pom.xml", "build.gradle", "build.gradle.kts", "*.jar" },
["node"] = new[] { "package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml" },
["python"] = new[] { "requirements.txt", "Pipfile", "Pipfile.lock", "pyproject.toml", "poetry.lock", "setup.py" },
["ruby"] = new[] { "Gemfile", "Gemfile.lock", "*.gemspec" },
["rust"] = new[] { "Cargo.toml", "Cargo.lock" },
["php"] = new[] { "composer.json", "composer.lock" },
["deno"] = new[] { "deno.json", "deno.jsonc", "import_map.json" },
["bun"] = new[] { "bun.lockb", "bunfig.toml" },
};
private static readonly Dictionary<string, string[]> KnownEcosystems = new(StringComparer.OrdinalIgnoreCase)
{
["dotnet"] = new[] { "nuget", "dotnet" },
["go"] = new[] { "go", "golang" },
["java"] = new[] { "maven", "gradle", "java" },
["node"] = new[] { "npm", "yarn", "pnpm", "nodejs" },
["python"] = new[] { "pypi", "pip", "poetry", "python" },
["ruby"] = new[] { "rubygems", "bundler", "ruby" },
["rust"] = new[] { "cargo", "crates.io", "rust" },
["php"] = new[] { "composer", "packagist", "php" },
["deno"] = new[] { "deno", "jsr" },
["bun"] = new[] { "bun", "npm" },
};
/// <summary>
/// Creates a new factory instance.
/// </summary>
/// <param name="catalog">The language analyzer plugin catalog.</param>
/// <param name="serviceProvider">Service provider for DI.</param>
public AnalyzerPluginAdapterFactory(
ILanguageAnalyzerPluginCatalog catalog,
IServiceProvider serviceProvider)
{
_catalog = catalog ?? throw new ArgumentNullException(nameof(catalog));
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
/// <summary>
/// Gets all available unified analyzer plugins.
/// </summary>
/// <returns>List of unified analyzer plugins.</returns>
public IReadOnlyList<IPlugin> GetAllPlugins()
{
var analyzers = _catalog.CreateAnalyzers(_serviceProvider);
var result = new List<IPlugin>();
foreach (var analyzer in analyzers)
{
var adapter = GetOrCreateAdapter(analyzer);
if (adapter != null)
{
result.Add(adapter);
}
}
return result;
}
/// <summary>
/// Gets a unified analyzer plugin by analyzer ID.
/// </summary>
/// <param name="analyzerId">Analyzer identifier.</param>
/// <returns>Unified analyzer plugin, or null if not found.</returns>
public IPlugin? GetPlugin(string analyzerId)
{
var analyzers = _catalog.CreateAnalyzers(_serviceProvider);
var analyzer = analyzers.FirstOrDefault(a =>
a.Id.Equals(analyzerId, StringComparison.OrdinalIgnoreCase));
if (analyzer == null)
return null;
return GetOrCreateAdapter(analyzer);
}
/// <summary>
/// Gets the analysis capability for an analyzer.
/// </summary>
/// <param name="analyzerId">Analyzer identifier.</param>
/// <returns>Analysis capability, or null if not found.</returns>
public IAnalysisCapability? GetCapability(string analyzerId)
{
return GetPlugin(analyzerId) as IAnalysisCapability;
}
private AnalyzerPluginAdapter? GetOrCreateAdapter(ILanguageAnalyzer analyzer)
{
lock (_lock)
{
if (_adapters.TryGetValue(analyzer.Id, out var existing))
{
return existing;
}
var plugin = _catalog.Plugins.FirstOrDefault(p =>
{
try
{
var created = p.CreateAnalyzer(_serviceProvider);
return created?.Id.Equals(analyzer.Id, StringComparison.OrdinalIgnoreCase) == true;
}
catch
{
return false;
}
});
if (plugin == null)
{
return null;
}
var filePatterns = KnownFilePatterns.TryGetValue(analyzer.Id, out var patterns)
? patterns
: Array.Empty<string>();
var ecosystems = KnownEcosystems.TryGetValue(analyzer.Id, out var eco)
? eco
: new[] { analyzer.Id };
var adapter = new AnalyzerPluginAdapter(
analyzer,
plugin,
_serviceProvider,
filePatterns,
ecosystems);
_adapters[analyzer.Id] = adapter;
return adapter;
}
}
}
/// <summary>
/// Extension methods for registering unified analyzer plugin services.
/// </summary>
public static class AnalyzerPluginAdapterExtensions
{
/// <summary>
/// Adds unified analyzer plugin adapter services to the service collection.
/// </summary>
/// <param name="services">Service collection.</param>
/// <returns>Service collection for chaining.</returns>
public static IServiceCollection AddUnifiedAnalyzerPlugins(this IServiceCollection services)
{
services.AddSingleton<AnalyzerPluginAdapterFactory>();
return services;
}
}