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; /// /// Factory for creating unified analyzer plugin adapters from existing analyzers. /// public sealed class AnalyzerPluginAdapterFactory { private readonly ILanguageAnalyzerPluginCatalog _catalog; private readonly IServiceProvider _serviceProvider; private readonly Dictionary _adapters = new(StringComparer.OrdinalIgnoreCase); private readonly object _lock = new(); // Known file patterns for each analyzer type private static readonly Dictionary 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 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" }, }; /// /// Creates a new factory instance. /// /// The language analyzer plugin catalog. /// Service provider for DI. public AnalyzerPluginAdapterFactory( ILanguageAnalyzerPluginCatalog catalog, IServiceProvider serviceProvider) { _catalog = catalog ?? throw new ArgumentNullException(nameof(catalog)); _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } /// /// Gets all available unified analyzer plugins. /// /// List of unified analyzer plugins. public IReadOnlyList GetAllPlugins() { var analyzers = _catalog.CreateAnalyzers(_serviceProvider); var result = new List(); foreach (var analyzer in analyzers) { var adapter = GetOrCreateAdapter(analyzer); if (adapter != null) { result.Add(adapter); } } return result; } /// /// Gets a unified analyzer plugin by analyzer ID. /// /// Analyzer identifier. /// Unified analyzer plugin, or null if not found. 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); } /// /// Gets the analysis capability for an analyzer. /// /// Analyzer identifier. /// Analysis capability, or null if not found. 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(); 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; } } } /// /// Extension methods for registering unified analyzer plugin services. /// public static class AnalyzerPluginAdapterExtensions { /// /// Adds unified analyzer plugin adapter services to the service collection. /// /// Service collection. /// Service collection for chaining. public static IServiceCollection AddUnifiedAnalyzerPlugins(this IServiceCollection services) { services.AddSingleton(); return services; } }