Skip to content

Commit

Permalink
Override in project diag working somewhat
Browse files Browse the repository at this point in the history
  • Loading branch information
dicko2 committed Nov 21, 2024
1 parent b9939ca commit 6ba7497
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<PackageReference Include="Microsoft.Build.Framework" Version="17.8.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.8.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.8.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

Expand Down
15 changes: 15 additions & 0 deletions src/Agoda.CodeCompass.MSBuild/Class1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
public void ConfigureServices(IServiceCollection services)

Check failure on line 1 in src/Agoda.CodeCompass.MSBuild/Class1.cs

View workflow job for this annotation

GitHub Actions / Build Package

The modifier 'public' is not valid for this item

Check failure on line 1 in src/Agoda.CodeCompass.MSBuild/Class1.cs

View workflow job for this annotation

GitHub Actions / Build Package

The modifier 'public' is not valid for this item
{
services.AddDataServices().AddBusinessServices().AddInfrastructureServices();
// ... more method calls
}

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddDataServices(this IServiceCollection services)
{
services.AddSingleton<IDatabase, Database>();
services.AddTransient<IUserRepository, UserRepository>(); // ... more registrations return services;
}
// ... more extension methods
}
11 changes: 11 additions & 0 deletions src/Agoda.CodeCompass.MSBuild/TechDebtInfo.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.MSBuild;

namespace Agoda.CodeCompass.Models;

public class TechDebtInfo
Expand All @@ -7,4 +12,10 @@ public class TechDebtInfo
public required string Priority { get; init; }
public string? Rationale { get; init; }
public string? Recommendation { get; init; }

// public async Task<TechDebtInfo> From(DiagnosticAnalyzer analyzer)
public static async Task From(DiagnosticAnalyzer analyzer)
{

}
}
210 changes: 196 additions & 14 deletions src/Agoda.CodeCompass.MSBuild/TechDebtMetadata.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Diagnostics;
using Agoda.CodeCompass.Models;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;

namespace Agoda.CodeCompass.Data;

public static class TechDebtMetadata
{
private static readonly Dictionary<string, TechDebtInfo> RuleMetadata = new()
private static readonly ConcurrentDictionary<string, TechDebtInfo> AnalyzerMetadata = new();

internal static readonly Dictionary<string, TechDebtInfo> PredefinedMetadata = new()
{
// Agoda Rules
["AG0002"] = new TechDebtInfo { Minutes = 15, Category = "AgodaSpecific", Priority = "Medium", Rationale = "Agoda-specific implementation issue", Recommendation = "Follow Agoda's implementation guidelines" },
Expand Down Expand Up @@ -294,22 +310,188 @@ public static class TechDebtMetadata
["SUP003"] = new TechDebtInfo { Minutes = 15, Category = "BestPractices", Priority = "Medium", Rationale = "Follow Supply guideline", Recommendation = "No Direct HttpContext Access, go via our Library" }
};

public static TechDebtInfo? GetTechDebtInfo(string ruleId) =>
RuleMetadata.TryGetValue(ruleId, out var info) ? info : null;

public static IEnumerable<string> GetAllRuleIds() => RuleMetadata.Keys;
private static List<DiagnosticAnalyzer> GetProjectAnalyzers(string projectPath)
{
var analyzerPaths = GetAnalyzerPaths(projectPath);
var analyzers = new List<DiagnosticAnalyzer>();

foreach (var analyzerPath in analyzerPaths)
{
try
{
var assembly = Assembly.LoadFrom(analyzerPath);

var analyzerTypes = assembly.GetTypes()
.Where(t => !t.IsAbstract && !t.IsInterface &&
typeof(DiagnosticAnalyzer).IsAssignableFrom(t));

foreach (var analyzerType in analyzerTypes)
{
if (Activator.CreateInstance(analyzerType) is DiagnosticAnalyzer analyzer)
{
analyzers.Add(analyzer);
}
}
}
catch (Exception ex)
{
// Log but continue with other analyzers
Console.WriteLine($"Error loading analyzer {analyzerPath}: {ex.Message}");
}
}

return analyzers;
}

private static List<string> GetAnalyzerPaths(string projectPath)
{
var analyzerPaths = new List<string>();
var projectDir = Path.GetDirectoryName(projectPath);

// Load project file
var projectXml = XDocument.Load(projectPath);
var ns = projectXml.Root.GetDefaultNamespace();

// Get direct analyzer references
var analyzerReferences = projectXml.Descendants(ns + "Analyzer")
.Select(x => x.Attribute("Include")?.Value)
.Where(x => !string.IsNullOrEmpty(x));

analyzerPaths.AddRange(analyzerReferences);

// Get package references
var packageReferences = projectXml.Descendants(ns + "PackageReference")
.Select(x => new
{
Id = x.Attribute("Include")?.Value,
Version = x.Attribute("Version")?.Value
})
.Where(x => !string.IsNullOrEmpty(x.Id) && !string.IsNullOrEmpty(x.Version));

foreach (var package in packageReferences)
{
var packagePath = Path.Combine(
projectDir,
"obj",
"project.nuget.cache");

if (File.Exists(packagePath))
{
var nugetCache = XDocument.Load(packagePath);
var packageFolder = nugetCache.Descendants("PackageFolder")
.FirstOrDefault(x => x.Attribute("id")?.Value == package.Id &&
x.Attribute("version")?.Value == package.Version);

if (packageFolder != null)
{
var analyzerDir = Path.Combine(packageFolder.Value, "analyzers", "dotnet");
if (Directory.Exists(analyzerDir))
{
analyzerPaths.AddRange(Directory.GetFiles(analyzerDir, "*.dll"));
}
}
}
}

return analyzerPaths;
}
private static string GetPriorityFromSeverity(DiagnosticSeverity severity)
{
return severity switch
{
DiagnosticSeverity.Error => "High",
DiagnosticSeverity.Warning => "Medium",
_ => "Low"
};
}

public static TechDebtInfo? GetTechDebtInfo(string ruleId)
{
// Check predefined first, then analyzer metadata
if (PredefinedMetadata.TryGetValue(ruleId, out var predefinedInfo))
{
return predefinedInfo;
}

if (AnalyzerMetadata.TryGetValue(ruleId, out var analyzerInfo))
{
return analyzerInfo;
}

return null;
}

public static IEnumerable<string> GetAllRuleIds()
{
return PredefinedMetadata.Keys
.Concat(AnalyzerMetadata.Keys)
.Distinct();
}

public static void UpdateMetadataFromDiagnostics(IEnumerable<Diagnostic> diagnostics)
{
foreach (var diagnostic in diagnostics)
{
var ruleId = diagnostic.Id;
var descriptor = diagnostic.Descriptor;

// Skip if we already have predefined metadata for this rule
if (PredefinedMetadata.ContainsKey(ruleId))
continue;

var properties = diagnostic.Properties ?? ImmutableDictionary<string, string>.Empty;

// Try to get tech debt minutes from properties
int minutes = 15; // Default value
if (properties.TryGetValue("techDebtMinutes", out var techDebtMinutesStr))
{
if (!int.TryParse(techDebtMinutesStr, out minutes))
{
minutes = 15; // Fallback to default if parsing fails
}
}

// Determine category based on descriptor or fallback to a default
string category = "BestPractices"; // Default category
if (properties.TryGetValue("category", out var categoryFromProps))
{
category = categoryFromProps;
}
else if (descriptor.Category != null)
{
category = descriptor.Category;
}

// Determine priority based on diagnostic severity
string priority = GetPriorityFromSeverity(descriptor.DefaultSeverity);

public static IEnumerable<string> GetRuleIdsByCategory(string category) =>
RuleMetadata.Where(kvp => kvp.Value.Category == category)
.Select(kvp => kvp.Key);
// Create rationale from diagnostic description
string rationale = descriptor.Description.ToString();
if (properties.TryGetValue("rationale", out var rationaleFromProps))
{
rationale = rationaleFromProps;
}

public static int GetEstimatedTotalMinutes(IEnumerable<string> ruleIds) =>
ruleIds.Sum(ruleId => GetTechDebtInfo(ruleId)?.Minutes ?? 0);
// Get recommendation from help link or message
string recommendation = descriptor.HelpLinkUri ?? descriptor.MessageFormat.ToString();
if (properties.TryGetValue("recommendation", out var recommendationFromProps))
{
recommendation = recommendationFromProps;
}

public static IEnumerable<string> GetAllCategories() =>
RuleMetadata.Select(kvp => kvp.Value.Category).Distinct();
// Create or update the tech debt info
var techDebtInfo = new TechDebtInfo
{
Minutes = minutes,
Category = category,
Priority = priority,
Rationale = rationale,
Recommendation = recommendation
};

public static IDictionary<string, int> GetTotalMinutesByCategory() =>
RuleMetadata.GroupBy(kvp => kvp.Value.Category)
.ToDictionary(g => g.Key, g => g.Sum(kvp => kvp.Value.Minutes));
// Add to analyzer metadata if not already in predefined
AnalyzerMetadata.TryAdd(ruleId, techDebtInfo);
}
}
}
34 changes: 30 additions & 4 deletions src/Agoda.CodeCompass.MSBuild/TechDebtReportTask.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
using Agoda.CodeCompass.Data;
using Agoda.CodeCompass.MSBuild.Sarif;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;
using System.Text.Json;
using Task = Microsoft.Build.Utilities.Task;

namespace Agoda.CodeCompass.MSBuild;

public class TechDebtSarifTask : Task
{
[Required]
public string InputPath { get; set; } = string.Empty;

[Required]
public string OutputPath { get; set; } = string.Empty;

Expand All @@ -25,6 +25,33 @@ public override bool Execute()
try
{
var inputSarif = File.ReadAllText(InputPath);
var solutionPath = Environment.GetEnvironmentVariable("SolutionDir");
if (string.IsNullOrEmpty(solutionPath))
{
throw new InvalidOperationException("SolutionDir is not set.");
}
var solutionFile = Directory.EnumerateFiles(solutionPath, "*.sln").FirstOrDefault();
if (solutionFile == null)
{
throw new InvalidOperationException("Solution file not found in SolutionDir.");
}

using var workspace = MSBuildWorkspace.Create();
var solution = workspace.OpenSolutionAsync(solutionFile).Result;

var allDiagnostics = new List<Diagnostic>();
foreach (var project in solution.Projects)
{
var compilation = project.GetCompilationAsync().Result;
if (compilation != null)
{
allDiagnostics.AddRange(compilation.GetDiagnostics());
}
}

// Update metadata with the diagnostics
TechDebtMetadata.UpdateMetadataFromDiagnostics(allDiagnostics);

var outputSarif = SarifReporter.AddTechDebtToSarif(inputSarif);
File.WriteAllText(OutputPath, outputSarif);
return true;
Expand All @@ -40,5 +67,4 @@ public override bool Execute()
return false;
}
}
}

}

0 comments on commit 6ba7497

Please sign in to comment.