176 lines
6.3 KiB
C#
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;
|
|
}
|
|
}
|