tests fixes and sprints work

This commit is contained in:
master
2026-01-22 19:08:46 +02:00
parent c32fff8f86
commit 726d70dc7f
881 changed files with 134434 additions and 6228 deletions

View File

@@ -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)];
}
}