|
5 | 5 | using System.IO;
|
6 | 6 | using System.Linq;
|
7 | 7 | using System.Reflection;
|
8 |
| -using System.Text; |
9 | 8 | using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
|
10 | 9 | using Microsoft.CodeAnalysis;
|
11 |
| -using Microsoft.CodeAnalysis.Text; |
12 | 10 |
|
13 | 11 | namespace CommunityToolkit.Mvvm.SourceGenerators;
|
14 | 12 |
|
15 | 13 | /// <summary>
|
16 | 14 | /// A source generator for necessary nullability attributes.
|
17 | 15 | /// </summary>
|
18 |
| -[Generator] |
19 |
| -public sealed class NullabilityAttributesGenerator : ISourceGenerator |
| 16 | +[Generator(LanguageNames.CSharp)] |
| 17 | +public sealed class NullabilityAttributesGenerator : IIncrementalGenerator |
20 | 18 | {
|
21 |
| - /// <inheritdoc/> |
22 |
| - public void Initialize(GeneratorInitializationContext context) |
23 |
| - { |
24 |
| - } |
| 19 | + /// <summary> |
| 20 | + /// The <c>System.Diagnostics.CodeAnalysis.NotNullAttribute</c> metadata name. |
| 21 | + /// </summary> |
| 22 | + private const string NotNullAttributeMetadataName = "System.Diagnostics.CodeAnalysis.NotNullAttribute"; |
| 23 | + |
| 24 | + /// <summary> |
| 25 | + /// The <c>System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute</c> metadata name. |
| 26 | + /// </summary> |
| 27 | + private const string NotNullIfNotNullAttributeMetadataName = "System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute"; |
25 | 28 |
|
26 | 29 | /// <inheritdoc/>
|
27 |
| - public void Execute(GeneratorExecutionContext context) |
| 30 | + public void Initialize(IncrementalGeneratorInitializationContext context) |
28 | 31 | {
|
29 |
| - AddSourceCodeIfTypeIsNotPresent(context, "System.Diagnostics.CodeAnalysis.NotNullAttribute"); |
30 |
| - AddSourceCodeIfTypeIsNotPresent(context, "System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute"); |
| 32 | + // Check that the target attributes are not available in the consuming project. To ensure that this |
| 33 | + // works fine both in .NET (Core) and .NET Standard implementations, we also need to check that the |
| 34 | + // target types are declared as public (we assume that in this case those types are from the BCL). |
| 35 | + // This avoids issues on .NET Standard with Roslyn also seeing internal types from referenced assemblies. |
| 36 | + |
| 37 | + // Check whether [NotNull] is not available |
| 38 | + IncrementalValueProvider<bool> isNotNullAttributeNotAvailable = |
| 39 | + context.CompilationProvider |
| 40 | + .Select(static (item, _) => !item.HasAccessibleTypeWithMetadataName(NotNullAttributeMetadataName)); |
| 41 | + |
| 42 | + // Generate the [NotNull] type |
| 43 | + context.RegisterConditionalSourceOutput(isNotNullAttributeNotAvailable, static context => |
| 44 | + { |
| 45 | + string source = LoadAttributeSourceWithMetadataName(NotNullAttributeMetadataName); |
| 46 | + |
| 47 | + context.AddSource(NotNullAttributeMetadataName, source); |
| 48 | + }); |
| 49 | + |
| 50 | + // Check whether [NotNullIfNotNull] is not available |
| 51 | + IncrementalValueProvider<bool> isNotNullIfNotNullAttributeNotAvailable = |
| 52 | + context.CompilationProvider |
| 53 | + .Select(static (item, _) => !item.HasAccessibleTypeWithMetadataName(NotNullIfNotNullAttributeMetadataName)); |
| 54 | + |
| 55 | + // Generate the [NotNullIfNotNull] type |
| 56 | + context.RegisterConditionalSourceOutput(isNotNullIfNotNullAttributeNotAvailable, static context => |
| 57 | + { |
| 58 | + string source = LoadAttributeSourceWithMetadataName(NotNullIfNotNullAttributeMetadataName); |
| 59 | + |
| 60 | + context.AddSource(NotNullIfNotNullAttributeMetadataName, source); |
| 61 | + }); |
31 | 62 | }
|
32 | 63 |
|
33 | 64 | /// <summary>
|
34 |
| - /// Adds the source for a given attribute type if it's not present already in the compilation. |
| 65 | + /// Gets the generated source for a specified attribute. |
35 | 66 | /// </summary>
|
36 |
| - private void AddSourceCodeIfTypeIsNotPresent(GeneratorExecutionContext context, string typeFullName) |
| 67 | + private static string LoadAttributeSourceWithMetadataName(string typeFullName) |
37 | 68 | {
|
38 |
| - // Check that the target attributes are not available in the consuming project. To ensure that |
39 |
| - // this works fine both in .NET (Core) and .NET Standard implementations, we also need to check |
40 |
| - // that the target types are declared as public (we assume that in this case those types are from the BCL). |
41 |
| - // This avoids issues on .NET Standard with Roslyn also seeing internal types from referenced assemblies. |
42 |
| - if (context.Compilation.HasAccessibleTypeWithMetadataName(typeFullName)) |
43 |
| - { |
44 |
| - return; |
45 |
| - } |
46 |
| - |
47 | 69 | string typeName = typeFullName.Split('.').Last();
|
48 | 70 | string filename = $"CommunityToolkit.Mvvm.SourceGenerators.EmbeddedResources.{typeName}.cs";
|
49 | 71 |
|
50 | 72 | Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(filename);
|
51 | 73 | StreamReader reader = new(stream);
|
52 | 74 |
|
53 |
| - string source = reader.ReadToEnd(); |
54 |
| - |
55 |
| - context.AddSource($"{typeFullName}.cs", SourceText.From(source, Encoding.UTF8)); |
| 75 | + return reader.ReadToEnd(); |
56 | 76 | }
|
57 | 77 | }
|
0 commit comments