// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace CommunityToolkit.Mvvm.SourceGenerators;
///
/// A source generator for properties validation without relying on compiled LINQ expressions.
///
[Generator]
public sealed partial class ObservableValidatorValidateAllPropertiesGenerator : ISourceGenerator
{
///
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(static () => new SyntaxReceiver());
}
///
public void Execute(GeneratorExecutionContext context)
{
// Get the syntax receiver with the candidate nodes
if (context.SyntaxContextReceiver is not SyntaxReceiver syntaxReceiver ||
syntaxReceiver.GatheredInfo.Count == 0)
{
return;
}
// Validate the language version (this needs at least C# 8.0 due to static local functions being used).
// If a lower C# version is set, just skip the execution silently. The fallback path will be used just fine.
if (context.ParseOptions is not CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp8 })
{
return;
}
// Get the symbol for the required attributes
INamedTypeSymbol validationSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.ValidationAttribute")!;
INamedTypeSymbol observablePropertySymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute")!;
// Prepare the attributes to add to the first class declaration
AttributeListSyntax[] classAttributes = new[]
{
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode"))
.AddArgumentListArguments(
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(GetType().FullName))),
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(GetType().Assembly.GetName().Version.ToString())))))),
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))),
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))),
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments(
AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))),
AttributeList(SingletonSeparatedList(
Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments(
AttributeArgument(LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal("This type is not intended to be used directly by user code"))))))
};
foreach (INamedTypeSymbol classSymbol in syntaxReceiver.GatheredInfo)
{
// Create a static factory method creating a delegate that can be used to validate all properties in a given class.
// This pattern is used so that the library doesn't have to use MakeGenericType(...) at runtime, nor use unsafe casts
// over the created delegate to be able to cache it as an Action