|  | 
|  | 1 | +// Licensed to the .NET Foundation under one or more agreements. | 
|  | 2 | +// The .NET Foundation licenses this file to you under the MIT license. | 
|  | 3 | + | 
|  | 4 | +using System.Globalization; | 
|  | 5 | +using System.IO; | 
|  | 6 | +using System.Text; | 
|  | 7 | +using Microsoft.CodeAnalysis; | 
|  | 8 | +using Microsoft.Gen.ComplianceReports; | 
|  | 9 | +using Microsoft.Gen.MetricsReports; | 
|  | 10 | +using Microsoft.Gen.Shared; | 
|  | 11 | +using Microsoft.Shared.DiagnosticIds; | 
|  | 12 | + | 
|  | 13 | +namespace Microsoft.Gen.MetadataExtractor; | 
|  | 14 | + | 
|  | 15 | +/// <summary> | 
|  | 16 | +/// Generates reports for compliance & metrics annotations. | 
|  | 17 | +/// </summary> | 
|  | 18 | +[Generator] | 
|  | 19 | +public sealed class MetadataReportsGenerator : ISourceGenerator | 
|  | 20 | +{ | 
|  | 21 | +    private const string GenerateMetadataMSBuildProperty = "build_property.GenerateMetadataReport"; | 
|  | 22 | +    private const string ReportOutputPathMSBuildProperty = "build_property.MetadataReportOutputPath"; | 
|  | 23 | +    private const string RootNamespace = "build_property.rootnamespace"; | 
|  | 24 | +    private const string FallbackFileName = "MetadataReport.json"; | 
|  | 25 | +    private readonly string _fileName; | 
|  | 26 | + | 
|  | 27 | +    /// <summary> | 
|  | 28 | +    /// Initializes a new instance of the <see cref="MetadataReportsGenerator"/> class. | 
|  | 29 | +    /// </summary> | 
|  | 30 | +    public MetadataReportsGenerator() | 
|  | 31 | +    : this(FallbackFileName) | 
|  | 32 | +    { | 
|  | 33 | +    } | 
|  | 34 | + | 
|  | 35 | +    /// <summary> | 
|  | 36 | +    /// Initializes a new instance of the <see cref="MetadataReportsGenerator"/> class. | 
|  | 37 | +    /// </summary> | 
|  | 38 | +    /// <param name="reportFileName">The report file name.</param> | 
|  | 39 | +    public MetadataReportsGenerator(string reportFileName) | 
|  | 40 | +    { | 
|  | 41 | +        _fileName = reportFileName; | 
|  | 42 | +    } | 
|  | 43 | + | 
|  | 44 | +    /// <summary> | 
|  | 45 | +    /// Initializes the generator. | 
|  | 46 | +    /// </summary> | 
|  | 47 | +    /// <param name="context">The generator initialization context.</param> | 
|  | 48 | +    public void Initialize(GeneratorInitializationContext context) | 
|  | 49 | +    { | 
|  | 50 | +        context.RegisterForSyntaxNotifications(TypeDeclarationSyntaxReceiver.Create); | 
|  | 51 | +    } | 
|  | 52 | + | 
|  | 53 | +    /// <summary> | 
|  | 54 | +    /// Generates reports for compliance & metrics annotations. | 
|  | 55 | +    /// </summary> | 
|  | 56 | +    /// <param name="context">The generator execution context.</param> | 
|  | 57 | +    public void Execute(GeneratorExecutionContext context) | 
|  | 58 | +    { | 
|  | 59 | +        context.CancellationToken.ThrowIfCancellationRequested(); | 
|  | 60 | + | 
|  | 61 | +        if (context.SyntaxReceiver is not TypeDeclarationSyntaxReceiver || | 
|  | 62 | +            ((TypeDeclarationSyntaxReceiver)context.SyntaxReceiver).TypeDeclarations.Count == 0 || | 
|  | 63 | +            !GeneratorUtilities.ShouldGenerateReport(context, GenerateMetadataMSBuildProperty)) | 
|  | 64 | +        { | 
|  | 65 | +            return; | 
|  | 66 | +        } | 
|  | 67 | + | 
|  | 68 | +        if ((context.SyntaxReceiver is not TypeDeclarationSyntaxReceiver || ((TypeDeclarationSyntaxReceiver)context.SyntaxReceiver).TypeDeclarations.Count == 0)) | 
|  | 69 | +        { | 
|  | 70 | +            // nothing to do yet | 
|  | 71 | +            return; | 
|  | 72 | +        } | 
|  | 73 | + | 
|  | 74 | +        var options = context.AnalyzerConfigOptions.GlobalOptions; | 
|  | 75 | +        var path = GeneratorUtilities.TryRetrieveOptionsValue(options, ReportOutputPathMSBuildProperty, out var reportOutputPath) | 
|  | 76 | +            ? reportOutputPath! | 
|  | 77 | +            : GeneratorUtilities.GetDefaultReportOutputPath(options); | 
|  | 78 | +        if (string.IsNullOrWhiteSpace(path)) | 
|  | 79 | +        { | 
|  | 80 | +            // Report diagnostic: | 
|  | 81 | +            var diagnostic = new DiagnosticDescriptor( | 
|  | 82 | +                DiagnosticIds.AuditReports.AUDREPGEN000, | 
|  | 83 | +                "MetricsReports generator couldn't resolve output path for the report. It won't be generated.", | 
|  | 84 | +                "Both <MetadataReportOutputPath> and <OutputPath> MSBuild properties are not set. The report won't be generated.", | 
|  | 85 | +                nameof(DiagnosticIds.AuditReports), | 
|  | 86 | +                DiagnosticSeverity.Info, | 
|  | 87 | +                isEnabledByDefault: true, | 
|  | 88 | +                helpLinkUri: string.Format(CultureInfo.InvariantCulture, DiagnosticIds.UrlFormat, DiagnosticIds.AuditReports.AUDREPGEN000)); | 
|  | 89 | + | 
|  | 90 | +            context.ReportDiagnostic(Diagnostic.Create(diagnostic, location: null)); | 
|  | 91 | +            return; | 
|  | 92 | +        } | 
|  | 93 | + | 
|  | 94 | +        (string metricReport, string complianceReport) metadataReport = (string.Empty, string.Empty); | 
|  | 95 | +        metadataReport.metricReport = HandleMetricReportGeneration(context, (TypeDeclarationSyntaxReceiver)context.SyntaxReceiver); | 
|  | 96 | +        metadataReport.complianceReport = HandleComplianceReportGeneration(context, (TypeDeclarationSyntaxReceiver)context.SyntaxReceiver); | 
|  | 97 | + | 
|  | 98 | +        StringBuilder reportStringBuilder = new StringBuilder() | 
|  | 99 | +            .Append("{ \"Name\": \"") | 
|  | 100 | +            .Append(context.Compilation.AssemblyName!) | 
|  | 101 | +            .Append("\", \"ComplianceReport\": ") | 
|  | 102 | +            .Append((string.IsNullOrEmpty(metadataReport.complianceReport) ? "{}" : metadataReport.complianceReport)) | 
|  | 103 | +            .Append(" ,") | 
|  | 104 | +            .Append(" \"MetricReport\": ") | 
|  | 105 | +            .Append((string.IsNullOrEmpty(metadataReport.metricReport) ? "[]" : metadataReport.metricReport) + " }"); | 
|  | 106 | + | 
|  | 107 | +#pragma warning disable RS1035 // Do not use APIs banned for analyzers | 
|  | 108 | +        File.WriteAllText(Path.Combine(path, _fileName), reportStringBuilder.ToString(), Encoding.UTF8); | 
|  | 109 | +#pragma warning restore RS1035 // Do not use APIs banned for analyzers | 
|  | 110 | + | 
|  | 111 | +    } | 
|  | 112 | + | 
|  | 113 | +    /// <summary> | 
|  | 114 | +    /// used to generate the report for metrics annotations. | 
|  | 115 | +    /// </summary> | 
|  | 116 | +    /// <param name="context">The generator execution context.</param> | 
|  | 117 | +    /// <param name="receiver">The typeDeclaration syntax receiver.</param> | 
|  | 118 | +    /// <returns>string report as json or String.Empty.</returns> | 
|  | 119 | +    private static string HandleMetricReportGeneration(GeneratorExecutionContext context, TypeDeclarationSyntaxReceiver receiver) | 
|  | 120 | +    { | 
|  | 121 | +        var meteringParser = new Metrics.Parser(context.Compilation, context.ReportDiagnostic, context.CancellationToken); | 
|  | 122 | +        var meteringClasses = meteringParser.GetMetricClasses(receiver.TypeDeclarations); | 
|  | 123 | + | 
|  | 124 | +        if (meteringClasses.Count == 0) | 
|  | 125 | +        { | 
|  | 126 | +            return string.Empty; | 
|  | 127 | +        } | 
|  | 128 | + | 
|  | 129 | +        _ = context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(RootNamespace, out var rootNamespace); | 
|  | 130 | +        var reportedMetrics = MetricsReportsHelpers.MapToCommonModel(meteringClasses, rootNamespace); | 
|  | 131 | +        var emitter = new MetricDefinitionEmitter(); | 
|  | 132 | +        var report = emitter.GenerateReport(reportedMetrics, context.CancellationToken); | 
|  | 133 | +        return report; | 
|  | 134 | +    } | 
|  | 135 | + | 
|  | 136 | +    /// <summary> | 
|  | 137 | +    /// used to generate the report for compliance annotations. | 
|  | 138 | +    /// </summary> | 
|  | 139 | +    /// <param name="context">The generator execution context.</param> | 
|  | 140 | +    /// <param name="receiver">The type declaration syntax receiver.</param> | 
|  | 141 | +    /// <returns>string report as json or String.Empty.</returns> | 
|  | 142 | +    private static string HandleComplianceReportGeneration(GeneratorExecutionContext context, TypeDeclarationSyntaxReceiver receiver) | 
|  | 143 | +    { | 
|  | 144 | +        if (!SymbolLoader.TryLoad(context.Compilation, out var symbolHolder)) | 
|  | 145 | +        { | 
|  | 146 | +            return string.Empty; | 
|  | 147 | +        } | 
|  | 148 | + | 
|  | 149 | +        var parser = new Parser(context.Compilation, symbolHolder!, context.CancellationToken); | 
|  | 150 | +        var classifiedTypes = parser.GetClassifiedTypes(receiver.TypeDeclarations); | 
|  | 151 | +        if (classifiedTypes.Count == 0) | 
|  | 152 | +        { | 
|  | 153 | +            // nothing to do | 
|  | 154 | +            return string.Empty; | 
|  | 155 | +        } | 
|  | 156 | + | 
|  | 157 | +        var emitter = new Emitter(); | 
|  | 158 | +        string report = emitter.Emit(classifiedTypes, context.Compilation.AssemblyName!, false); | 
|  | 159 | + | 
|  | 160 | +        return report; | 
|  | 161 | +    } | 
|  | 162 | +} | 
0 commit comments