// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.IO;
using System.Linq;
using System.Reflection;
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
using Microsoft.CodeAnalysis;
namespace CommunityToolkit.Mvvm.SourceGenerators;
///
/// A source generator for necessary nullability attributes.
///
[Generator(LanguageNames.CSharp)]
public sealed class NullabilityAttributesGenerator : IIncrementalGenerator
{
///
/// The System.Diagnostics.CodeAnalysis.NotNullAttribute metadata name.
///
private const string NotNullAttributeMetadataName = "System.Diagnostics.CodeAnalysis.NotNullAttribute";
///
/// The System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute metadata name.
///
private const string NotNullIfNotNullAttributeMetadataName = "System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute";
///
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Check that the target attributes are not available in the consuming project. To ensure that this
// works fine both in .NET (Core) and .NET Standard implementations, we also need to check that the
// target types are declared as public (we assume that in this case those types are from the BCL).
// This avoids issues on .NET Standard with Roslyn also seeing internal types from referenced assemblies.
// Check whether [NotNull] is not available
IncrementalValueProvider isNotNullAttributeNotAvailable =
context.CompilationProvider
.Select(static (item, _) => !item.HasAccessibleTypeWithMetadataName(NotNullAttributeMetadataName));
// Generate the [NotNull] type
context.RegisterConditionalSourceOutput(isNotNullAttributeNotAvailable, static context =>
{
string source = LoadAttributeSourceWithMetadataName(NotNullAttributeMetadataName);
context.AddSource($"{NotNullAttributeMetadataName}.g.cs", source);
});
// Check whether [NotNullIfNotNull] is not available
IncrementalValueProvider isNotNullIfNotNullAttributeNotAvailable =
context.CompilationProvider
.Select(static (item, _) => !item.HasAccessibleTypeWithMetadataName(NotNullIfNotNullAttributeMetadataName));
// Generate the [NotNullIfNotNull] type
context.RegisterConditionalSourceOutput(isNotNullIfNotNullAttributeNotAvailable, static context =>
{
string source = LoadAttributeSourceWithMetadataName(NotNullIfNotNullAttributeMetadataName);
context.AddSource($"{NotNullIfNotNullAttributeMetadataName}.g.cs", source);
});
}
///
/// Gets the generated source for a specified attribute.
///
private static string LoadAttributeSourceWithMetadataName(string typeFullName)
{
string typeName = typeFullName.Split('.').Last();
string filename = $"CommunityToolkit.Mvvm.SourceGenerators.EmbeddedResources.{typeName}.cs";
Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(filename);
StreamReader reader = new(stream);
return reader.ReadToEnd();
}
}