-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDriverRunResult.cs
More file actions
204 lines (185 loc) · 8.01 KB
/
Copy pathDriverRunResult.cs
File metadata and controls
204 lines (185 loc) · 8.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
using System.Collections.Immutable;
using System.Globalization;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Purview.SourceGeneratorFramework.Logging;
using Purview.SourceGeneratorFramework.Testing.Models;
namespace Purview.SourceGeneratorFramework.Testing;
/// <summary>
/// The result of a source generator test run.
/// </summary>
/// <param name="AllSyntaxTrees">All syntax trees generated during the run.</param>
/// <param name="PrimarySyntaxTrees">
/// The primary syntax trees generated during the run, excluding anything detailed by <see cref="SourceGeneratorTestOptions.ExcludeGeneratedSourceHintNames"/>.
/// <para>
/// This is useful for excluding generated source such as attribute markers.
/// </para>
/// </param>
/// <param name="LogEntries">The log entries generated during the run.</param>
/// <param name="AnalyzerResult">The result of the analyzer compilation run, if any.</param>
/// <param name="CompilationResult">The result of the compilation run.</param>
/// <param name="DriverResult">The result of the generator driver run.</param>
public sealed record class DriverRunResult(
GeneratorDriverRunResult DriverResult,
CompilationRunResult CompilationResult,
AnalyzerCompilationRunResult? AnalyzerResult,
ImmutableArray<SyntaxTree> AllSyntaxTrees,
ImmutableArray<SyntaxTree> PrimarySyntaxTrees,
ImmutableArray<LogEntry> LogEntries
)
{
/// <summary>
/// Throws <see cref="DriverRunValidationException"/> containing all generation exceptions,
/// compilation errors, emit errors, and generator log errors found in the run.
/// </summary>
public void EnsureValid()
{
var generationExceptions = DriverResult
.Results.Where(result => result.Exception is not null)
.Select(result => new GeneratorFailure(
result.Generator.GetType().FullName ?? result.Generator.GetType().Name,
result.Exception!
))
.ToList();
var compilationErrors = CompilationResult
.Compilation.GetDiagnostics()
.Where(d => d.Severity == DiagnosticSeverity.Error)
.ToList();
var logErrors = LogEntries.Where(e => e.Type == SourceGenLogLevel.Fatal).ToList();
var compilationErrorKeys = new HashSet<string>(compilationErrors.Select(GetDiagnosticKey));
var emitErrors = CompilationResult
.Diagnostics.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.Where(diagnostic => !compilationErrorKeys.Contains(GetDiagnosticKey(diagnostic)))
.ToList();
if (
generationExceptions.Count == 0
&& compilationErrors.Count == 0
&& emitErrors.Count == 0
&& logErrors.Count == 0
)
return;
throw new DriverRunValidationException(
this,
generationExceptions,
compilationErrors,
emitErrors,
logErrors,
AllSyntaxTrees,
CompilationResult.Compilation.SyntaxTrees
);
}
static string GetDiagnosticKey(Diagnostic diagnostic) =>
$"{diagnostic.Id}|{diagnostic.Location.SourceTree?.FilePath}|{diagnostic.Location.SourceSpan.Start}|{diagnostic.Location.SourceSpan.Length}|{diagnostic.GetMessage(CultureInfo.InvariantCulture)}";
/// <summary>
/// Gets the source text of a generated tree with the specified hint name.
/// </summary>
/// <param name="hintName">The hint name of the generated tree - this can be the whole of end of the hint name.</param>
/// <param name="matchMode">The mode to use when matching the hint name.</param>
/// <returns>The source text of the generated tree, or <see langword="null"/> if not found.</returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="hintName"/> is <see langword="null"/> or whitespace.</exception>
/// <remarks>
/// The <paramref name="hintName"/> is matched using <see cref="StringComparison.Ordinal"/>.
/// </remarks>
public string? GetSource(string hintName, HintNameMatchMode matchMode = HintNameMatchMode.Suffix)
{
if (string.IsNullOrWhiteSpace(hintName))
throw new ArgumentException("Value cannot be null or whitespace.", nameof(hintName));
var hasExtension = hintName.EndsWith(".cs", StringComparison.Ordinal);
var predicate = matchMode switch
{
HintNameMatchMode.Suffix => new Func<string, bool>(s =>
{
if (!hasExtension)
{
var postSuffix = ".cs";
if (
s.EndsWith(hintName + postSuffix, StringComparison.Ordinal)
|| s.EndsWith(hintName + ".g" + postSuffix, StringComparison.Ordinal)
)
return true;
}
return s.EndsWith(hintName, StringComparison.Ordinal);
}),
HintNameMatchMode.Partial => new Func<string, bool>(s => s.Contains(hintName, StringComparison.Ordinal)),
HintNameMatchMode.Exact => new Func<string, bool>(s => s.Equals(hintName, StringComparison.Ordinal)),
_ => throw new ArgumentOutOfRangeException(nameof(matchMode), matchMode, "Invalid match mode."),
};
// Find the generated source with the specified hint name
return DriverResult
.Results.SelectMany(static r => r.GeneratedSources)
.Where(s => predicate(s.HintName))
.Select(static s => s.SourceText.ToString())
.SingleOrDefault();
}
/// <summary>
/// Gets the source text of the first primary generated tree.
/// </summary>
public string GetSource()
{
var tree = PrimarySyntaxTrees.FirstOrDefault();
return tree?.GetText().ToString() ?? string.Empty;
}
/// <summary>
/// Finds a generated tree whose file path ends with the specified suffix.
/// </summary>
/// <param name="filePathSuffix">The suffix to match.</param>
/// <returns>The matching syntax tree, or <see langword="null"/> if none is found.</returns>
public SyntaxTree? GetGeneratedTree(string filePathSuffix) =>
AllSyntaxTrees.FirstOrDefault(tree => tree.FilePath.EndsWith(filePathSuffix, StringComparison.Ordinal));
/// <summary>
/// Gets the symbol for a type by its metadata name.
/// </summary>
/// <param name="identity">The identity of the type.</param>
/// <returns>The symbol for the type, or <see langword="null"/> if the type is not found.</returns>
public ISymbol? GetTypeByMetadataName(TypeIdentity identity) =>
CompilationResult.Compilation.GetTypeByMetadataName(identity.MetadataFullName);
/// <summary>
/// Gets the symbol for a type by its metadata name.
/// </summary>
/// <param name="fullyQualifiedMetadataName">The fully qualified metadata name of the type.</param>
/// <returns>The symbol for the type, or <see langword="null"/> if the type is not found.</returns>
public ISymbol? GetTypeByMetadataName(string fullyQualifiedMetadataName) =>
CompilationResult.Compilation.GetTypeByMetadataName(fullyQualifiedMetadataName);
}
/// <summary>
/// The result of a compilation run, including the compilation, the resulting assembly (if successful), and any diagnostics produced during compilation.
/// </summary>
/// <param name="Compilation">The compilation that was run.</param>
/// <param name="Assembly">The resulting assembly, or <see langword="null"/> if the compilation failed.</param>
/// <param name="Diagnostics">The diagnostics produced during the compilation.</param>
public sealed record class CompilationRunResult(
Compilation Compilation,
Assembly? Assembly,
ImmutableArray<Diagnostic> Diagnostics
);
/// <summary>
/// The result of a compilation run with analyzers applied, including the compilation and any diagnostics produced during compilation.
/// </summary>
/// <param name="Compilation">The compilation that was run, with analyzers applied.</param>
/// <param name="Diagnostics">The diagnostics produced just by the analyzers defined by <see cref="SourceGeneratorTestOptions.AnalyzerTypes"/>.</param>
public sealed record class AnalyzerCompilationRunResult(
CompilationWithAnalyzers Compilation,
ImmutableArray<Diagnostic> Diagnostics
);
/// <summary>
/// Specifies how to match hint names when retrieving generated source code from a <see cref="DriverRunResult"/>.
/// </summary>
public enum HintNameMatchMode
{
/// <summary>
/// Match the hint name by suffix.
/// </summary>
/// <remarks>
/// Note this will automatically check for <c>.cs</c> if it's excluded.
/// </remarks>
Suffix,
/// <summary>
/// Match the hint name by partial match.
/// </summary>
Partial,
/// <summary>
/// Match the hint name exactly.
/// </summary>
Exact,
}