92 lines
3.8 KiB
C#
92 lines
3.8 KiB
C#
namespace NatsNet.PortTracker.Audit;
|
|
|
|
/// <summary>
|
|
/// Classifies features by inspecting the SourceIndexer for their .NET implementation status.
|
|
/// Priority: n_a lookup -> method-not-found -> stub detection -> verified.
|
|
/// </summary>
|
|
public sealed class FeatureClassifier
|
|
{
|
|
public record ClassificationResult(string Status, string Reason);
|
|
|
|
public record FeatureRecord(
|
|
long Id,
|
|
string DotnetClass,
|
|
string DotnetMethod,
|
|
string GoFile,
|
|
string GoMethod);
|
|
|
|
private readonly SourceIndexer _indexer;
|
|
|
|
// N/A lookup: (goMethod pattern) -> reason
|
|
// Checked case-insensitively against go_method
|
|
private static readonly Dictionary<string, string> NaByGoMethod = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["Noticef"] = ".NET uses Microsoft.Extensions.Logging",
|
|
["Debugf"] = ".NET uses Microsoft.Extensions.Logging",
|
|
["Tracef"] = ".NET uses Microsoft.Extensions.Logging",
|
|
["Warnf"] = ".NET uses Microsoft.Extensions.Logging",
|
|
["Errorf"] = ".NET uses Microsoft.Extensions.Logging",
|
|
["Fatalf"] = ".NET uses Microsoft.Extensions.Logging",
|
|
};
|
|
|
|
// N/A lookup: go_file + go_method patterns
|
|
private static readonly List<(Func<FeatureRecord, bool> Match, string Reason)> NaPatterns =
|
|
[
|
|
// Signal handling — .NET uses IHostApplicationLifetime
|
|
(f => f.GoMethod.Equals("handleSignals", StringComparison.OrdinalIgnoreCase), ".NET uses IHostApplicationLifetime"),
|
|
(f => f.GoMethod.Equals("processSignal", StringComparison.OrdinalIgnoreCase), ".NET uses IHostApplicationLifetime"),
|
|
];
|
|
|
|
public FeatureClassifier(SourceIndexer indexer)
|
|
{
|
|
_indexer = indexer;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Classify a single feature. Returns status and reason.
|
|
/// </summary>
|
|
public ClassificationResult Classify(FeatureRecord feature)
|
|
{
|
|
// 1. N/A lookup — check go_method against known patterns
|
|
if (NaByGoMethod.TryGetValue(feature.GoMethod, out var naReason))
|
|
return new ClassificationResult("n_a", naReason);
|
|
|
|
foreach (var (match, reason) in NaPatterns)
|
|
{
|
|
if (match(feature))
|
|
return new ClassificationResult("n_a", reason);
|
|
}
|
|
|
|
// 2. Handle comma-separated dotnet_class (e.g. "ClosedRingBuffer,ClosedClient")
|
|
var classNames = feature.DotnetClass.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
var methodName = feature.DotnetMethod;
|
|
|
|
// Try each class name
|
|
foreach (var className in classNames)
|
|
{
|
|
var methods = _indexer.Lookup(className, methodName);
|
|
if (methods.Count > 0)
|
|
{
|
|
// Found the method — classify based on body analysis
|
|
// Use the "best" match: prefer non-stub over stub
|
|
var best = methods.OrderByDescending(m => m.StatementCount).First();
|
|
|
|
if (best.IsStub)
|
|
return new ClassificationResult("stub", $"Body is throw NotImplementedException at {Path.GetFileName(best.FilePath)}:{best.LineNumber}");
|
|
|
|
if (best.IsPartial)
|
|
return new ClassificationResult("stub", $"Partial implementation with NotImplementedException at {Path.GetFileName(best.FilePath)}:{best.LineNumber}");
|
|
|
|
return new ClassificationResult("verified", $"Method found with {best.StatementCount} statement(s) at {Path.GetFileName(best.FilePath)}:{best.LineNumber}");
|
|
}
|
|
}
|
|
|
|
// 3. Method not found — check if any class exists
|
|
var anyClassFound = classNames.Any(c => _indexer.HasClass(c));
|
|
if (anyClassFound)
|
|
return new ClassificationResult("deferred", "Class exists but method not found");
|
|
|
|
return new ClassificationResult("deferred", "Class not found in .NET source");
|
|
}
|
|
}
|