Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tweak MetaData Report format for better readability #5929

Open
wants to merge 51 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
1c62ee0
fix report format
IbrahimMNada Feb 18, 2025
0d87e45
Merge branch 'dotnet:main' into audit-report-format
IbrahimMNada Feb 19, 2025
e4ccb48
adding normalize escapes in order to handle differences between windo…
IbrahimMNada Feb 19, 2025
b54420a
Merge branch 'audit-report-format' of https://github.com/IbrahimMNada…
IbrahimMNada Feb 19, 2025
16be34f
remove unneeded Encoding.UTF8
IbrahimMNada Feb 19, 2025
caf5068
adding comment to make azure piplines happy
IbrahimMNada Feb 19, 2025
496f9f1
use temp file isnted of static ReportFileName for metadata
IbrahimMNada Feb 19, 2025
d9049fc
use temp file isnted of static ReportFileName for metrics report
IbrahimMNada Feb 19, 2025
e9f2cdd
remove white space
IbrahimMNada Feb 19, 2025
82edd87
add space
IbrahimMNada Feb 19, 2025
32bdb46
remove multiple blanks
IbrahimMNada Feb 19, 2025
a6a520c
fix checks
IbrahimMNada Feb 19, 2025
1374258
Merge branch 'main' into audit-report-format
IbrahimMNada Feb 20, 2025
edfec34
-adding parameter needs to parameterless constructors
IbrahimMNada Feb 20, 2025
9825774
Merge branch 'audit-report-format' of https://github.com/IbrahimMNada…
IbrahimMNada Feb 20, 2025
6d136e4
rename temp to temporaryReportFile & change var to actual type
IbrahimMNada Feb 20, 2025
b3d9357
remove unneeded check
IbrahimMNada Feb 20, 2025
48d0b6f
adding comment
IbrahimMNada Feb 20, 2025
ddb58f4
Merge branch 'main' into audit-report-format
IbrahimMNada Feb 20, 2025
dfb4e14
Merge branch 'main' into audit-report-format
IbrahimMNada Feb 21, 2025
ab59a4a
introduce MetadataEmitter
IbrahimMNada Feb 24, 2025
b49fc54
a
IbrahimMNada Feb 24, 2025
fb1118f
Merge branch 'audit-report-format' of https://github.com/IbrahimMNada…
IbrahimMNada Feb 24, 2025
76c4007
Adding Indentation for complince
IbrahimMNada Feb 24, 2025
98fceae
fix indentation on metrics
IbrahimMNada Feb 24, 2025
ed829de
fix form,at
IbrahimMNada Feb 24, 2025
04e0c10
Fix format for unit testing
IbrahimMNada Feb 24, 2025
4e0962a
fixing metrics formats
IbrahimMNada Feb 24, 2025
2da5f0a
fix checker
IbrahimMNada Feb 24, 2025
f095df6
Merge branch 'main' into audit-report-format
IbrahimMNada Feb 24, 2025
b8d598c
fix checks
IbrahimMNada Feb 24, 2025
0bb634a
Merge branch 'audit-report-format' of https://github.com/IbrahimMNada…
IbrahimMNada Feb 24, 2025
705e34a
Merge branch 'main' into audit-report-format
IbrahimMNada Feb 24, 2025
44b512f
adding overloads for indent
IbrahimMNada Feb 25, 2025
1df3627
enhance indentation
IbrahimMNada Feb 25, 2025
9bf78fc
Merge branch 'main' into audit-report-format
IbrahimMNada Feb 26, 2025
a454fed
fix namings
IbrahimMNada Feb 26, 2025
6edc0e3
remove white space before comma"ComplianceReport": {},
IbrahimMNada Feb 26, 2025
9d41481
rename const
IbrahimMNada Feb 26, 2025
b893bdc
fix namings
IbrahimMNada Feb 27, 2025
89c99dc
fix comma on new line
IbrahimMNada Feb 27, 2025
821513b
Merge branch 'main' into audit-report-format
IbrahimMNada Feb 27, 2025
5458782
fix idnentation
IbrahimMNada Mar 1, 2025
cc4c394
introduce JsonEmitterBase
IbrahimMNada Mar 2, 2025
bf7af23
remove blanks
IbrahimMNada Mar 2, 2025
0d4c102
overload OutNameValue
IbrahimMNada Mar 2, 2025
83b22d8
Merge branch 'main' into audit-report-format
IbrahimMNada Mar 2, 2025
53eff19
rerun
IbrahimMNada Mar 2, 2025
ac2996d
made metadata consume OutObject
IbrahimMNada Mar 2, 2025
befb637
rerun
IbrahimMNada Mar 2, 2025
7317e69
1
IbrahimMNada Mar 2, 2025
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,19 +10,29 @@

namespace Microsoft.Gen.ComplianceReports;

internal sealed class Emitter : EmitterBase
internal sealed class ComplianceReportEmitter : EmitterBase
{
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.
{
IndentMany(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
Expand Down Expand Up @@ -125,6 +135,7 @@ public string Emit(IReadOnlyCollection<ClassifiedType> classifiedTypes, string a
}
});
});
UnindentMany(indentationLevel);

return Capture();
}
Expand Down
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
96 changes: 96 additions & 0 deletions src/Generators/Microsoft.Gen.MetadataExtractor/MetadataEmitter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.Gen.ComplianceReports;
using Microsoft.Gen.MetricsReports;
using Microsoft.Gen.Shared;

namespace Microsoft.Gen.MetadataExtractor;
internal sealed class MetadataEmitter : EmitterBase
{
private readonly MetricDefinitionEmitter _metricDefinitionEmitter;
private readonly ComplianceReportEmitter _complianceReportEmitter;
private readonly string _rootNamespace;
private readonly int _indentationLevel = 4;
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 != null)
{
metadataReport.metricReport = HandleMetricReportGeneration(context, receiver);
metadataReport.complianceReport = HandleComplianceReportGeneration(context, receiver);
}

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

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)
{
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 report = _metricDefinitionEmitter.GenerateReport(reportedMetrics, context.CancellationToken, _indentationLevel);
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 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;
}

string report = _complianceReportEmitter.Emit(classifiedTypes, context.Compilation.AssemblyName!, false, _indentationLevel);
return report;
}
}
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