tests fixes and sprints work
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// EnhancedGoLicenseDetector.cs
|
||||
// Sprint: SPRINT_20260119_024_Scanner_license_detection_enhancements
|
||||
// Task: TASK-024-007 - Upgrade Go license detector
|
||||
// Description: Enhanced Go license detection returning LicenseDetectionResult
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Core.Licensing;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Go.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Enhanced Go license detector that returns full LicenseDetectionResult.
|
||||
/// </summary>
|
||||
internal sealed class EnhancedGoLicenseDetector
|
||||
{
|
||||
private readonly ILicenseCategorizationService _categorizationService;
|
||||
private readonly ILicenseTextExtractor _textExtractor;
|
||||
private readonly ICopyrightExtractor _copyrightExtractor;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new enhanced Go license detector with the specified services.
|
||||
/// </summary>
|
||||
public EnhancedGoLicenseDetector(
|
||||
ILicenseCategorizationService categorizationService,
|
||||
ILicenseTextExtractor textExtractor,
|
||||
ICopyrightExtractor copyrightExtractor)
|
||||
{
|
||||
_categorizationService = categorizationService;
|
||||
_textExtractor = textExtractor;
|
||||
_copyrightExtractor = copyrightExtractor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new enhanced Go license detector with default services.
|
||||
/// </summary>
|
||||
public EnhancedGoLicenseDetector()
|
||||
{
|
||||
_categorizationService = new LicenseCategorizationService();
|
||||
_textExtractor = new LicenseTextExtractor();
|
||||
_copyrightExtractor = new CopyrightExtractor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects license for a Go module at the given path.
|
||||
/// </summary>
|
||||
/// <param name="modulePath">Path to the Go module directory.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>The full license detection result.</returns>
|
||||
public async Task<LicenseDetectionResult?> DetectAsync(string modulePath, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(modulePath) || !Directory.Exists(modulePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract license files from the directory
|
||||
var licenseTextResults = await _textExtractor.ExtractFromDirectoryAsync(modulePath, ct);
|
||||
var primaryLicenseResult = licenseTextResults.FirstOrDefault();
|
||||
|
||||
// Use existing detector for SPDX identification
|
||||
var basicResult = GoLicenseDetector.DetectLicense(modulePath);
|
||||
|
||||
if (!basicResult.IsDetected && primaryLicenseResult is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get SPDX ID from existing detector or from text extraction
|
||||
var spdxId = basicResult.SpdxIdentifier
|
||||
?? primaryLicenseResult?.DetectedLicenseId
|
||||
?? "LicenseRef-Unknown";
|
||||
|
||||
// Map confidence
|
||||
var confidence = MapConfidence(basicResult.Confidence, primaryLicenseResult?.Confidence);
|
||||
|
||||
// Get copyright notices
|
||||
var copyrightNotices = primaryLicenseResult?.CopyrightNotices ?? [];
|
||||
var primaryCopyright = copyrightNotices.Length > 0
|
||||
? copyrightNotices[0].FullText
|
||||
: null;
|
||||
|
||||
// Check for dual licensing (common in Go: MIT OR Apache-2.0)
|
||||
var isExpression = spdxId.Contains(" OR ", StringComparison.OrdinalIgnoreCase) ||
|
||||
spdxId.Contains(" AND ", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var result = new LicenseDetectionResult
|
||||
{
|
||||
SpdxId = spdxId,
|
||||
OriginalText = basicResult.RawLicenseName,
|
||||
Confidence = confidence,
|
||||
Method = DetermineDetectionMethod(basicResult, primaryLicenseResult),
|
||||
SourceFile = basicResult.LicenseFile ?? primaryLicenseResult?.SourceFile ?? "LICENSE",
|
||||
Category = LicenseCategory.Unknown,
|
||||
Obligations = [],
|
||||
LicenseText = primaryLicenseResult?.FullText,
|
||||
LicenseTextHash = primaryLicenseResult?.TextHash,
|
||||
CopyrightNotice = primaryCopyright,
|
||||
IsExpression = isExpression,
|
||||
ExpressionComponents = isExpression ? ParseExpressionComponents(spdxId) : []
|
||||
};
|
||||
|
||||
return _categorizationService.Enrich(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects license for a vendored Go module.
|
||||
/// </summary>
|
||||
public async Task<LicenseDetectionResult?> DetectVendoredAsync(
|
||||
string vendorPath,
|
||||
string modulePath,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(vendorPath) || string.IsNullOrWhiteSpace(modulePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var vendoredModulePath = Path.Combine(vendorPath, modulePath.Replace('/', Path.DirectorySeparatorChar));
|
||||
|
||||
if (Directory.Exists(vendoredModulePath))
|
||||
{
|
||||
return await DetectAsync(vendoredModulePath, ct);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects license from license file content synchronously.
|
||||
/// </summary>
|
||||
public LicenseDetectionResult? DetectFromContent(string content, string? sourceFile = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var basicResult = GoLicenseDetector.AnalyzeLicenseContent(content, sourceFile);
|
||||
var textResult = _textExtractor.Extract(content, sourceFile);
|
||||
|
||||
var spdxId = basicResult.SpdxIdentifier
|
||||
?? textResult.DetectedLicenseId
|
||||
?? "LicenseRef-Unknown";
|
||||
|
||||
var confidence = MapConfidence(basicResult.Confidence, textResult.Confidence);
|
||||
|
||||
var copyrightNotices = textResult.CopyrightNotices;
|
||||
var primaryCopyright = copyrightNotices.Length > 0
|
||||
? copyrightNotices[0].FullText
|
||||
: null;
|
||||
|
||||
var isExpression = spdxId.Contains(" OR ", StringComparison.OrdinalIgnoreCase) ||
|
||||
spdxId.Contains(" AND ", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var result = new LicenseDetectionResult
|
||||
{
|
||||
SpdxId = spdxId,
|
||||
OriginalText = basicResult.RawLicenseName,
|
||||
Confidence = confidence,
|
||||
Method = LicenseDetectionMethod.LicenseFile,
|
||||
SourceFile = sourceFile ?? "LICENSE",
|
||||
Category = LicenseCategory.Unknown,
|
||||
Obligations = [],
|
||||
LicenseText = content,
|
||||
LicenseTextHash = textResult.TextHash,
|
||||
CopyrightNotice = primaryCopyright,
|
||||
IsExpression = isExpression,
|
||||
ExpressionComponents = isExpression ? ParseExpressionComponents(spdxId) : []
|
||||
};
|
||||
|
||||
return _categorizationService.Enrich(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects license synchronously without full text extraction.
|
||||
/// </summary>
|
||||
public LicenseDetectionResult? Detect(string modulePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(modulePath) || !Directory.Exists(modulePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var basicResult = GoLicenseDetector.DetectLicense(modulePath);
|
||||
|
||||
if (!basicResult.IsDetected)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var confidence = MapConfidence(basicResult.Confidence, null);
|
||||
var isExpression = basicResult.SpdxIdentifier?.Contains(" OR ", StringComparison.OrdinalIgnoreCase) == true ||
|
||||
basicResult.SpdxIdentifier?.Contains(" AND ", StringComparison.OrdinalIgnoreCase) == true;
|
||||
|
||||
var result = new LicenseDetectionResult
|
||||
{
|
||||
SpdxId = basicResult.SpdxIdentifier!,
|
||||
OriginalText = basicResult.RawLicenseName,
|
||||
Confidence = confidence,
|
||||
Method = LicenseDetectionMethod.LicenseFile,
|
||||
SourceFile = basicResult.LicenseFile ?? "LICENSE",
|
||||
Category = LicenseCategory.Unknown,
|
||||
Obligations = [],
|
||||
IsExpression = isExpression,
|
||||
ExpressionComponents = isExpression ? ParseExpressionComponents(basicResult.SpdxIdentifier!) : []
|
||||
};
|
||||
|
||||
return _categorizationService.Enrich(result);
|
||||
}
|
||||
|
||||
private static LicenseDetectionConfidence MapConfidence(
|
||||
GoLicenseDetector.LicenseConfidence goConfidence,
|
||||
LicenseDetectionConfidence? textConfidence)
|
||||
{
|
||||
// Use the higher confidence from either source
|
||||
var goMapped = goConfidence switch
|
||||
{
|
||||
GoLicenseDetector.LicenseConfidence.High => LicenseDetectionConfidence.High,
|
||||
GoLicenseDetector.LicenseConfidence.Medium => LicenseDetectionConfidence.Medium,
|
||||
GoLicenseDetector.LicenseConfidence.Low => LicenseDetectionConfidence.Low,
|
||||
_ => LicenseDetectionConfidence.None
|
||||
};
|
||||
|
||||
if (textConfidence.HasValue && textConfidence.Value > goMapped)
|
||||
{
|
||||
return textConfidence.Value;
|
||||
}
|
||||
|
||||
return goMapped;
|
||||
}
|
||||
|
||||
private static LicenseDetectionMethod DetermineDetectionMethod(
|
||||
GoLicenseDetector.LicenseInfo basicResult,
|
||||
LicenseTextExtractionResult? textResult)
|
||||
{
|
||||
// If we have high confidence from SPDX identifier in file
|
||||
if (basicResult.Confidence == GoLicenseDetector.LicenseConfidence.High)
|
||||
{
|
||||
return LicenseDetectionMethod.SpdxHeader;
|
||||
}
|
||||
|
||||
// Pattern matching from license file
|
||||
if (basicResult.IsDetected || textResult?.DetectedLicenseId is not null)
|
||||
{
|
||||
return LicenseDetectionMethod.PatternMatching;
|
||||
}
|
||||
|
||||
return LicenseDetectionMethod.KeywordFallback;
|
||||
}
|
||||
|
||||
private static ImmutableArray<string> ParseExpressionComponents(string expression)
|
||||
{
|
||||
var components = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var tokens = expression
|
||||
.Replace("(", " ")
|
||||
.Replace(")", " ")
|
||||
.Split([' '], StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
var upper = token.ToUpperInvariant();
|
||||
if (upper is not "OR" and not "AND" and not "WITH")
|
||||
{
|
||||
components.Add(token);
|
||||
}
|
||||
}
|
||||
|
||||
return [.. components.OrderBy(c => c, StringComparer.Ordinal)];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user