Skip to content

Commit 52350a9

Browse files
authored
Fix Microsoft.Extensions.AuditReports output path (#4945)
* Fix Microsoft.Extensions.AuditReports output path * fix golden reports * add tests and the README
1 parent 76b6d4a commit 52350a9

19 files changed

+492
-3933
lines changed

docs/list-of-diagnostics.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ if desired.
4141
| `EXTEXP0016` | Hosting integration testing experiments |
4242
| `EXTEXP0017` | Contextual options experiments |
4343

44-
4544
# LoggerMessage
4645

4746
| Diagnostic ID | Description |
@@ -108,3 +107,10 @@ if desired.
108107
| `METGEN017` | Gauge is not supported yet |
109108
| `METGEN018` | Xml comment was not parsed correctly |
110109
| `METGEN019` | A metric class has cycles in its type hierarchy |
110+
111+
## AuditReports
112+
113+
| Diagnostic ID | Description |
114+
| :---------------- | :---------- |
115+
| `AUDREPGEN000` | MetricsReports generator couldn't resolve output path for the report |
116+
| `AUDREPGEN001` | ComplianceReports generator couldn't resolve output path for the report |

global.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"sdk": {
3-
"version": "8.0.101"
3+
"version": "8.0.200"
44
},
55
"tools": {
6-
"dotnet": "8.0.101",
6+
"dotnet": "8.0.200",
77
"runtimes": {
88
"dotnet/x64": [
99
"6.0.22"

src/Generators/Microsoft.Gen.ComplianceReports/ComplianceReportsGenerator.cs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Globalization;
45
using System.IO;
6+
using System.Text;
57
using Microsoft.CodeAnalysis;
68
using Microsoft.Gen.Shared;
9+
using Microsoft.Shared.DiagnosticIds;
710

811
namespace Microsoft.Gen.ComplianceReports;
912

@@ -18,8 +21,8 @@ public sealed class ComplianceReportsGenerator : ISourceGenerator
1821

1922
private const string FallbackFileName = "ComplianceReport.json";
2023

24+
private readonly string _fileName;
2125
private string? _directory;
22-
private string _fileName;
2326

2427
public ComplianceReportsGenerator()
2528
: this(null)
@@ -55,7 +58,7 @@ public void Execute(GeneratorExecutionContext context)
5558

5659
if (!GeneratorUtilities.ShouldGenerateReport(context, GenerateComplianceReportsMSBuildProperty))
5760
{
58-
// By default, compliance reports are only generated only during build time and not during design time to prevent the file being written on every keystroke in VS.
61+
// By default, compliance reports are generated only during build time and not during design time to prevent the file being written on every keystroke in VS.
5962
return;
6063
}
6164

@@ -78,19 +81,30 @@ public void Execute(GeneratorExecutionContext context)
7881

7982
context.CancellationToken.ThrowIfCancellationRequested();
8083

81-
if (_directory == null)
84+
var options = context.AnalyzerConfigOptions.GlobalOptions;
85+
_directory ??= GeneratorUtilities.TryRetrieveOptionsValue(options, ComplianceReportOutputPathMSBuildProperty, out var reportOutputPath)
86+
? reportOutputPath!
87+
: GeneratorUtilities.GetDefaultReportOutputPath(options);
88+
89+
if (string.IsNullOrWhiteSpace(_directory))
8290
{
83-
_ = context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(ComplianceReportOutputPathMSBuildProperty, out _directory);
84-
if (string.IsNullOrWhiteSpace(_directory))
85-
{
86-
// no valid output path
87-
return;
88-
}
91+
// Report diagnostic:
92+
var diagnostic = new DiagnosticDescriptor(
93+
DiagnosticIds.AuditReports.AUDREPGEN001,
94+
"ComplianceReports generator couldn't resolve output path for the report. It won't be generated.",
95+
"Both <ComplianceReportOutputPath> and <OutputPath> MSBuild properties are not set. The report won't be generated.",
96+
nameof(DiagnosticIds.AuditReports),
97+
DiagnosticSeverity.Info,
98+
isEnabledByDefault: true,
99+
helpLinkUri: string.Format(CultureInfo.InvariantCulture, DiagnosticIds.UrlFormat, DiagnosticIds.AuditReports.AUDREPGEN001));
100+
101+
context.ReportDiagnostic(Diagnostic.Create(diagnostic, location: null));
102+
return;
89103
}
90104

91105
_ = Directory.CreateDirectory(_directory);
92106

93107
// Write report as JSON file.
94-
File.WriteAllText(Path.Combine(_directory, _fileName), report);
108+
File.WriteAllText(Path.Combine(_directory, _fileName), report, Encoding.UTF8);
95109
}
96110
}

src/Generators/Microsoft.Gen.MetricsReports/MetricsReportsGenerator.cs

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Generic;
5+
using System.Globalization;
56
using System.IO;
67
using System.Linq;
78
using System.Text;
89
using Microsoft.CodeAnalysis;
910
using Microsoft.CodeAnalysis.Diagnostics;
1011
using Microsoft.Gen.Metrics.Model;
1112
using Microsoft.Gen.Shared;
13+
using Microsoft.Shared.DiagnosticIds;
1214

1315
namespace Microsoft.Gen.MetricsReports;
1416

@@ -18,13 +20,19 @@ public class MetricsReportsGenerator : ISourceGenerator
1820
private const string GenerateMetricDefinitionReport = "build_property.GenerateMetricsReport";
1921
private const string RootNamespace = "build_property.rootnamespace";
2022
private const string ReportOutputPath = "build_property.MetricsReportOutputPath";
21-
private const string CompilationOutputPath = "build_property.outputpath";
22-
private const string CurrentProjectPath = "build_property.projectdir";
2323
private const string FileName = "MetricsReport.json";
2424

25-
private string? _compilationOutputPath;
26-
private string? _currentProjectPath;
27-
private string? _reportOutputPath;
25+
private readonly string _fileName;
26+
27+
public MetricsReportsGenerator()
28+
: this(FileName)
29+
{
30+
}
31+
32+
internal MetricsReportsGenerator(string reportFileName)
33+
{
34+
_fileName = reportFileName;
35+
}
2836

2937
public void Initialize(GeneratorInitializationContext context)
3038
{
@@ -35,8 +43,9 @@ public void Execute(GeneratorExecutionContext context)
3543
{
3644
context.CancellationToken.ThrowIfCancellationRequested();
3745

38-
var receiver = context.SyntaxReceiver as ClassDeclarationSyntaxReceiver;
39-
if (receiver == null || receiver.ClassDeclarations.Count == 0 || !GeneratorUtilities.ShouldGenerateReport(context, GenerateMetricDefinitionReport))
46+
if (context.SyntaxReceiver is not ClassDeclarationSyntaxReceiver receiver ||
47+
receiver.ClassDeclarations.Count == 0 ||
48+
!GeneratorUtilities.ShouldGenerateReport(context, GenerateMetricDefinitionReport))
4049
{
4150
return;
4251
}
@@ -52,13 +61,24 @@ public void Execute(GeneratorExecutionContext context)
5261

5362
var options = context.AnalyzerConfigOptions.GlobalOptions;
5463

55-
var path = (_reportOutputPath != null || options.TryGetValue(ReportOutputPath, out _reportOutputPath))
56-
? _reportOutputPath
57-
: GetDefaultReportOutputPath(options);
64+
var path = GeneratorUtilities.TryRetrieveOptionsValue(options, ReportOutputPath, out var reportOutputPath)
65+
? reportOutputPath!
66+
: GeneratorUtilities.GetDefaultReportOutputPath(options);
5867

5968
if (string.IsNullOrWhiteSpace(path))
6069
{
61-
// Report diagnostic. Tell that it is either <MetricDefinitionReportOutputPath> missing or <CompilerVisibleProperty Include="OutputPath"/> visibility to compiler.
70+
// Report diagnostic:
71+
var diagnostic = new DiagnosticDescriptor(
72+
DiagnosticIds.AuditReports.AUDREPGEN000,
73+
"MetricsReports generator couldn't resolve output path for the report. It won't be generated.",
74+
"Both <MetricsReportOutputPath> and <OutputPath> MSBuild properties are not set. The report won't be generated.",
75+
nameof(DiagnosticIds.AuditReports),
76+
DiagnosticSeverity.Info,
77+
isEnabledByDefault: true,
78+
helpLinkUri: string.Format(CultureInfo.InvariantCulture, DiagnosticIds.UrlFormat, DiagnosticIds.AuditReports.AUDREPGEN000));
79+
80+
context.ReportDiagnostic(Diagnostic.Create(diagnostic, location: null));
81+
6282
return;
6383
}
6484

@@ -68,7 +88,7 @@ public void Execute(GeneratorExecutionContext context)
6888
var reportedMetrics = MapToCommonModel(meteringClasses, rootNamespace);
6989
var report = emitter.GenerateReport(reportedMetrics, context.CancellationToken);
7090

71-
File.WriteAllText(Path.Combine(path, FileName), report, Encoding.UTF8);
91+
File.WriteAllText(Path.Combine(path, _fileName), report, Encoding.UTF8);
7292
}
7393

7494
private static ReportedMetricClass[] MapToCommonModel(IReadOnlyList<MetricType> meteringClasses, string? rootNamespace)
@@ -89,19 +109,4 @@ private static ReportedMetricClass[] MapToCommonModel(IReadOnlyList<MetricType>
89109

90110
return reportedMetrics.ToArray();
91111
}
92-
93-
private string GetDefaultReportOutputPath(AnalyzerConfigOptions options)
94-
{
95-
if (_currentProjectPath != null && _compilationOutputPath != null)
96-
{
97-
return _currentProjectPath + _compilationOutputPath;
98-
}
99-
100-
_ = options.TryGetValue(CompilationOutputPath, out _compilationOutputPath);
101-
_ = options.TryGetValue(CurrentProjectPath, out _currentProjectPath);
102-
103-
return string.IsNullOrWhiteSpace(_currentProjectPath) || string.IsNullOrWhiteSpace(_compilationOutputPath)
104-
? string.Empty
105-
: _currentProjectPath + _compilationOutputPath;
106-
}
107112
}

src/Generators/Shared/GeneratorUtilities.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Diagnostics.CodeAnalysis;
7+
using System.IO;
78
using System.Linq;
89
using System.Threading;
910
using Microsoft.CodeAnalysis;
1011
using Microsoft.CodeAnalysis.CSharp;
1112
using Microsoft.CodeAnalysis.CSharp.Syntax;
13+
using Microsoft.CodeAnalysis.Diagnostics;
1214

1315
[assembly: System.Resources.NeutralResourcesLanguage("en-us")]
1416

@@ -21,6 +23,9 @@ namespace Microsoft.Gen.Shared;
2123
#endif
2224
internal static class GeneratorUtilities
2325
{
26+
private const string CompilationOutputPath = "build_property.outputpath";
27+
private const string CurrentProjectPath = "build_property.projectdir";
28+
2429
public static string GeneratedCodeAttribute { get; } = $"global::System.CodeDom.Compiler.GeneratedCodeAttribute(" +
2530
$"\"{typeof(GeneratorUtilities).Assembly.GetName().Name}\", " +
2631
$"\"{typeof(GeneratorUtilities).Assembly.GetName().Version}\")";
@@ -136,4 +141,26 @@ public static bool ShouldGenerateReport(GeneratorExecutionContext context, strin
136141

137142
return string.Equals(generateFiles, bool.TrueString, StringComparison.OrdinalIgnoreCase);
138143
}
144+
145+
public static bool TryRetrieveOptionsValue(AnalyzerConfigOptions options, string name, out string? value)
146+
=> options.TryGetValue(name, out value) && !string.IsNullOrWhiteSpace(value);
147+
148+
public static string GetDefaultReportOutputPath(AnalyzerConfigOptions options)
149+
{
150+
if (!TryRetrieveOptionsValue(options, CompilationOutputPath, out var compilationOutputPath))
151+
{
152+
return string.Empty;
153+
}
154+
155+
// If <OutputPath> is absolute - return it right away:
156+
if (Path.IsPathRooted(compilationOutputPath))
157+
{
158+
return compilationOutputPath!;
159+
}
160+
161+
// Get <ProjectDir> and combine it with <OutputPath> if the former isn't empty:
162+
return TryRetrieveOptionsValue(options, CurrentProjectPath, out var currentProjectPath)
163+
? Path.Combine(currentProjectPath!, compilationOutputPath!)
164+
: string.Empty;
165+
}
139166
}

0 commit comments

Comments
 (0)