Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,29 @@

namespace Microsoft.Gen.ComplianceReports;

internal sealed class Emitter : EmitterBase
internal sealed class ComplianceReportEmitter : JsonEmitterBase
{
private readonly Stack<int> _itemCounts = new();
private int _itemCount;

public Emitter()
public ComplianceReportEmitter()
: base(false)
{
}

/// <summary>
/// Generates JSON object containing the <see cref="ClassifiedTypes"/> for compliance report.
/// </summary>
/// <param name="classifiedTypes">The classified types.</param>
/// <param name="assemblyName">The assembly name.</param>
/// <param name="includeName">Whether to include the assembly name in the report. Defaulted to true.</param>
/// <param name="indentationLevel">The number of indentations in case its nested in other reports like <see cref="MetadataReportsGenerator"/>.Defaulted to zero.</param>
/// <returns>string report as json or String.Empty.</returns>
[SuppressMessage("Performance", "LA0002:Use 'Microsoft.Extensions.Text.NumericExtensions.ToInvariantString' for improved performance", Justification = "Can't use that in a generator")]
public string Emit(IReadOnlyCollection<ClassifiedType> classifiedTypes, string assemblyName, bool includeName = true) // show or hide assemblyName in the report,defaulted to true.
public string Emit(IReadOnlyCollection<ClassifiedType> classifiedTypes, string assemblyName,
bool includeName = true, int indentationLevel = 0) // show or hide assemblyName in the report,defaulted to true.
{
Indent(indentationLevel);
OutObject(() =>
{
// this is only for not displaying a name as part of ComplianceReport properties,it should be at the root of the report, defaulted to true for beackward compatibility
// this is only for not displaying a name as part of ComplianceReport properties,it should be at the root of the report, defaulted to true for backward compatibility
if (includeName)
{
OutNameValue("Name", assemblyName);
Expand Down Expand Up @@ -125,65 +132,9 @@ public string Emit(IReadOnlyCollection<ClassifiedType> classifiedTypes, string a
}
});
});
Unindent(indentationLevel);

return Capture();
}

private void NewItem()
{
if (_itemCount > 0)
{
Out(",");
}

OutLn();
_itemCount++;
}

private void OutObject(Action action)
{
NewItem();
_itemCounts.Push(_itemCount);
_itemCount = 0;

OutIndent();
Out("{");
Indent();
action();
OutLn();
Unindent();
OutIndent();
Out("}");

_itemCount = _itemCounts.Pop();
}

private void OutArray(string name, Action action)
{
NewItem();
_itemCounts.Push(_itemCount);
_itemCount = 0;

OutIndent();
Out($"\"{name}\": [");
Indent();
action();
OutLn();
Unindent();
OutIndent();
Out("]");

_itemCount = _itemCounts.Pop();
}

private void OutNameValue(string name, string value)
{
value = value
.Replace("\\", "\\\\")
.Replace("\"", "\\\"");

NewItem();
OutIndent();
Out($"\"{name}\": \"{value}\"");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,18 @@ public sealed class ComplianceReportsGenerator : ISourceGenerator
private readonly string _fileName;
private string? _directory;

/// <summary>
/// Initializes a new instance of the <see cref="ComplianceReportsGenerator"/> class.
/// </summary>
public ComplianceReportsGenerator()
: this(null)
: this(filePath: null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ComplianceReportsGenerator"/> class.
/// </summary>
/// <param name="filePath">The report path and name.</param>
public ComplianceReportsGenerator(string? filePath)
{
if (filePath is not null)
Expand Down Expand Up @@ -76,7 +83,7 @@ public void Execute(GeneratorExecutionContext context)
return;
}

var emitter = new Emitter();
var emitter = new ComplianceReportEmitter();
string report = emitter.Emit(classifiedTypes, context.Compilation.AssemblyName!);

context.CancellationToken.ThrowIfCancellationRequested();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<Compile Include="..\Shared\TypeDeclarationSyntaxReceiver.cs" LinkBase="Shared" />
<Compile Include="..\Shared\GeneratorUtilities.cs" LinkBase="Shared" />
<Compile Include="..\Shared\EmitterBase.cs" LinkBase="Shared" />
<Compile Include="..\Shared\JsonEmitterBase.cs" LinkBase="Shared" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<ItemGroup>
<Compile Update="Parsing\Resources.Designer.cs" DesignTime="True" AutoGen="True" DependentUpon="Resources.resx" />
<Compile Include="..\Shared\*.cs" LinkBase="Shared" />
<Compile Remove="..\Shared\JsonEmitterBase.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
98 changes: 98 additions & 0 deletions src/Generators/Microsoft.Gen.MetadataExtractor/MetadataEmitter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.Gen.ComplianceReports;
using Microsoft.Gen.Metrics.Model;
using Microsoft.Gen.MetricsReports;
using Microsoft.Gen.Shared;

namespace Microsoft.Gen.MetadataExtractor;
internal sealed class MetadataEmitter : JsonEmitterBase
{
private const int IndentationLevel = 2;
private readonly MetricDefinitionEmitter _metricDefinitionEmitter;
private readonly ComplianceReportEmitter _complianceReportEmitter;
private readonly string _rootNamespace;

public MetadataEmitter(string rootNamespace)
: base(emitPreamble: false)
{
_metricDefinitionEmitter = new MetricDefinitionEmitter();
_complianceReportEmitter = new ComplianceReportEmitter();
_rootNamespace = rootNamespace;
}

[SuppressMessage("Performance", "LA0002:Use 'Microsoft.Extensions.Text.NumericExtensions.ToInvariantString' for improved performance", Justification = "Can't use that in a generator")]
public string Emit(GeneratorExecutionContext context)
{
(string metricReport, string complianceReport) metadataReport = (string.Empty, string.Empty);

var receiver = context.SyntaxReceiver as TypeDeclarationSyntaxReceiver;
if (receiver is not null)
{
metadataReport.metricReport = HandleMetricReportGeneration(context, receiver);
metadataReport.complianceReport = HandleComplianceReportGeneration(context, receiver);
}

OutObject(() =>
{
OutNameValue("Name", context.Compilation.AssemblyName!, isSingle: true);
OutIndent();
Out("\"ComplianceReport\": ");
Out($"{(string.IsNullOrEmpty(metadataReport.complianceReport) ? "{}" : metadataReport.complianceReport)},");
OutLn();
OutIndent();
Out("\"MetricReport\": ");
Out((string.IsNullOrEmpty(metadataReport.metricReport) ? "[]" : metadataReport.metricReport));
});

return Capture();
}

/// <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 string HandleMetricReportGeneration(GeneratorExecutionContext context, TypeDeclarationSyntaxReceiver receiver)
{
Metrics.Parser meteringParser = new Metrics.Parser(context.Compilation, context.ReportDiagnostic, context.CancellationToken);
IReadOnlyList<MetricType> meteringClasses = meteringParser.GetMetricClasses(receiver.TypeDeclarations);

if (meteringClasses.Count == 0)
{
return string.Empty;
}

_ = context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(_rootNamespace, out var rootNamespace);
ReportedMetricClass[] reportedMetrics = MetricsReportsHelpers.MapToCommonModel(meteringClasses, rootNamespace);
return _metricDefinitionEmitter.GenerateReport(reportedMetrics, context.CancellationToken, IndentationLevel);
}

/// <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 string HandleComplianceReportGeneration(GeneratorExecutionContext context, TypeDeclarationSyntaxReceiver receiver)
{
if (!SymbolLoader.TryLoad(context.Compilation, out var symbolHolder))
{
return string.Empty;
}

Parser parser = new Parser(context.Compilation, symbolHolder!, context.CancellationToken);
IReadOnlyList<ClassifiedType> classifiedTypes = parser.GetClassifiedTypes(receiver.TypeDeclarations);
if (classifiedTypes.Count == 0)
{
// nothing to do
return string.Empty;
}

return _complianceReportEmitter.Emit(classifiedTypes, context.Compilation.AssemblyName!, false, IndentationLevel);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
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;

Expand All @@ -23,22 +21,31 @@ public sealed class MetadataReportsGenerator : ISourceGenerator
private const string RootNamespace = "build_property.rootnamespace";
private const string FallbackFileName = "MetadataReport.json";
private readonly string _fileName;
private string? _directory;

/// <summary>
/// Initializes a new instance of the <see cref="MetadataReportsGenerator"/> class.
/// </summary>
public MetadataReportsGenerator()
: this(FallbackFileName)
: this(filePath: null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MetadataReportsGenerator"/> class.
/// </summary>
/// <param name="reportFileName">The report file name.</param>
public MetadataReportsGenerator(string reportFileName)
/// <param name="filePath">The report path and name.</param>
public MetadataReportsGenerator(string? filePath)
{
_fileName = reportFileName;
if (filePath is not null)
{
_directory = Path.GetDirectoryName(filePath);
_fileName = Path.GetFileName(filePath);
}
else
{
_fileName = FallbackFileName;
}
}

/// <summary>
Expand All @@ -61,21 +68,17 @@ public void Execute(GeneratorExecutionContext context)
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)
_directory ??= GeneratorUtilities.TryRetrieveOptionsValue(options, ReportOutputPathMSBuildProperty, out var reportOutputPath)
? reportOutputPath!
: GeneratorUtilities.GetDefaultReportOutputPath(options);
if (string.IsNullOrWhiteSpace(path))

if (string.IsNullOrWhiteSpace(_directory))
{
// Report diagnostic:
var diagnostic = new DiagnosticDescriptor(
Expand All @@ -91,74 +94,13 @@ public void Execute(GeneratorExecutionContext context)
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) + " }");
MetadataEmitter emitter = new MetadataEmitter(RootNamespace);

#pragma warning disable RS1035 // Do not use APIs banned for analyzers
_ = Directory.CreateDirectory(path);
_ = Directory.CreateDirectory(_directory);

File.WriteAllText(Path.Combine(path, _fileName), reportStringBuilder.ToString(), Encoding.UTF8);
File.WriteAllText(Path.Combine(_directory, _fileName), emitter.Emit(context), 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;
}
}
Loading
Loading