| File: MetadataReportsGenerator.cs | Web Access |
| Project: src\src\Generators\Microsoft.Gen.MetadataExtractor\Microsoft.Gen.MetadataExtractor.csproj (Microsoft.Gen.MetadataExtractor) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; using System.IO; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.Gen.ComplianceReports; using Microsoft.Gen.MetricsReports; using Microsoft.Gen.Shared; using Microsoft.Shared.DiagnosticIds; namespace Microsoft.Gen.MetadataExtractor; /// <summary> /// Generates reports for compliance & metrics annotations. /// </summary> [Generator] public sealed class MetadataReportsGenerator : ISourceGenerator { private const string GenerateMetadataMSBuildProperty = "build_property.GenerateMetadataReport"; private const string ReportOutputPathMSBuildProperty = "build_property.MetadataReportOutputPath"; private const string RootNamespace = "build_property.rootnamespace"; private const string FallbackFileName = "MetadataReport.json"; private readonly string _fileName; /// <summary> /// Initializes a new instance of the <see cref="MetadataReportsGenerator"/> class. /// </summary> public MetadataReportsGenerator() : this(FallbackFileName) { } /// <summary> /// Initializes a new instance of the <see cref="MetadataReportsGenerator"/> class. /// </summary> /// <param name="reportFileName">The report file name.</param> public MetadataReportsGenerator(string reportFileName) { _fileName = reportFileName; } /// <summary> /// Initializes the generator. /// </summary> /// <param name="context">The generator initialization context.</param> public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(TypeDeclarationSyntaxReceiver.Create); } /// <summary> /// Generates reports for compliance & metrics annotations. /// </summary> /// <param name="context">The generator execution context.</param> public void Execute(GeneratorExecutionContext context) { context.CancellationToken.ThrowIfCancellationRequested(); if (context.SyntaxReceiver is not TypeDeclarationSyntaxReceiver || ((TypeDeclarationSyntaxReceiver)context.SyntaxReceiver).TypeDeclarations.Count == 0 || !GeneratorUtilities.ShouldGenerateReport(context, GenerateMetadataMSBuildProperty)) { return; } if ((context.SyntaxReceiver is not TypeDeclarationSyntaxReceiver || ((TypeDeclarationSyntaxReceiver)context.SyntaxReceiver).TypeDeclarations.Count == 0)) { // nothing to do yet return; } var options = context.AnalyzerConfigOptions.GlobalOptions; var path = GeneratorUtilities.TryRetrieveOptionsValue(options, ReportOutputPathMSBuildProperty, out var reportOutputPath) ? reportOutputPath! : GeneratorUtilities.GetDefaultReportOutputPath(options); if (string.IsNullOrWhiteSpace(path)) { // Report diagnostic: var diagnostic = new DiagnosticDescriptor( DiagnosticIds.AuditReports.AUDREPGEN000, "MetadataReport generator couldn't resolve output path for the report. It won't be generated.", "Both <MetadataReportOutputPath> and <OutputPath> MSBuild properties are not set. The report won't be generated.", nameof(DiagnosticIds.AuditReports), DiagnosticSeverity.Info, isEnabledByDefault: true, helpLinkUri: string.Format(CultureInfo.InvariantCulture, DiagnosticIds.UrlFormat, DiagnosticIds.AuditReports.AUDREPGEN000)); context.ReportDiagnostic(Diagnostic.Create(diagnostic, location: null)); return; } (string metricReport, string complianceReport) metadataReport = (string.Empty, string.Empty); metadataReport.metricReport = HandleMetricReportGeneration(context, (TypeDeclarationSyntaxReceiver)context.SyntaxReceiver); metadataReport.complianceReport = HandleComplianceReportGeneration(context, (TypeDeclarationSyntaxReceiver)context.SyntaxReceiver); StringBuilder reportStringBuilder = new StringBuilder() .Append("{ \"Name\": \"") .Append(context.Compilation.AssemblyName!) .Append("\", \"ComplianceReport\": ") .Append((string.IsNullOrEmpty(metadataReport.complianceReport) ? "{}" : metadataReport.complianceReport)) .Append(" ,") .Append(" \"MetricReport\": ") .Append((string.IsNullOrEmpty(metadataReport.metricReport) ? "[]" : metadataReport.metricReport) + " }"); #pragma warning disable RS1035 // Do not use APIs banned for analyzers _ = Directory.CreateDirectory(path); File.WriteAllText(Path.Combine(path, _fileName), reportStringBuilder.ToString(), Encoding.UTF8); #pragma warning restore RS1035 // Do not use APIs banned for analyzers } /// <summary> /// used to generate the report for metrics annotations. /// </summary> /// <param name="context">The generator execution context.</param> /// <param name="receiver">The typeDeclaration syntax receiver.</param> /// <returns>string report as json or String.Empty.</returns> private static string HandleMetricReportGeneration(GeneratorExecutionContext context, TypeDeclarationSyntaxReceiver receiver) { var meteringParser = new Metrics.Parser(context.Compilation, context.ReportDiagnostic, context.CancellationToken); var meteringClasses = meteringParser.GetMetricClasses(receiver.TypeDeclarations); if (meteringClasses.Count == 0) { return string.Empty; } _ = context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(RootNamespace, out var rootNamespace); var reportedMetrics = MetricsReportsHelpers.MapToCommonModel(meteringClasses, rootNamespace); var emitter = new MetricDefinitionEmitter(); var report = emitter.GenerateReport(reportedMetrics, context.CancellationToken); return report; } /// <summary> /// used to generate the report for compliance annotations. /// </summary> /// <param name="context">The generator execution context.</param> /// <param name="receiver">The type declaration syntax receiver.</param> /// <returns>string report as json or String.Empty.</returns> private static string HandleComplianceReportGeneration(GeneratorExecutionContext context, TypeDeclarationSyntaxReceiver receiver) { if (!SymbolLoader.TryLoad(context.Compilation, out var symbolHolder)) { return string.Empty; } var parser = new Parser(context.Compilation, symbolHolder!, context.CancellationToken); var classifiedTypes = parser.GetClassifiedTypes(receiver.TypeDeclarations); if (classifiedTypes.Count == 0) { // nothing to do return string.Empty; } var emitter = new Emitter(); string report = emitter.Emit(classifiedTypes, context.Compilation.AssemblyName!, false); return report; } }