Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MVVM Toolkit vNext: source generators #3873

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
66a62ae
Added initial draft of SG project and ObservableObjectAttribute
Sergio0694 Mar 21, 2021
8e7a4a2
Added unit test file and reference
Sergio0694 Mar 21, 2021
d53262c
Added solution filter for .NET projects
Sergio0694 Mar 21, 2021
fa4e2dc
Fixed header generation
Sergio0694 Mar 21, 2021
36ce871
Added INotifyPropertyChangedAttribute and NotifyPropertyChangedObject
Sergio0694 Mar 21, 2021
a045fd6
Code refactoring, improved generator modularization
Sergio0694 Mar 21, 2021
a1358f4
Added generator for [INotifyPropertyChanged]
Sergio0694 Mar 21, 2021
d27556f
Minor fixes to code generators and unit tests
Sergio0694 Mar 21, 2021
684755e
Added support for custom members filtering
Sergio0694 Mar 21, 2021
d1d32f0
Added INotifyPropertyChangedAttribute.IncludeAdditionalHelperMethods
Sergio0694 Mar 21, 2021
baa62cd
Added [ObservableRecipient] attribute, code refactoring
Sergio0694 Mar 21, 2021
cd29cef
Added [ObservableRecipient] tests, minor bug fixes
Sergio0694 Mar 22, 2021
8b415a2
Fixed an issue with base type list formatting
Sergio0694 Mar 22, 2021
e8cccce
Minor code refactoring
Sergio0694 Mar 22, 2021
1b621d2
Enabled constructor generation for [ObservableRecipient]
Sergio0694 Mar 22, 2021
c6cce1c
Skipped ObservableRecipient.SetProperty overloads when conflicting
Sergio0694 Mar 22, 2021
e873484
Fixed missing trivia from generated ObservableValidator constructors
Sergio0694 Mar 22, 2021
b86b242
Fixed errors in unit tests
Sergio0694 Mar 22, 2021
49d5e1a
Added missing file headers
Sergio0694 Mar 22, 2021
a20d09e
Added initial setup for custom diagnostics
Sergio0694 Mar 22, 2021
6d0eedb
Enabled diagnostics for failed generators
Sergio0694 Mar 22, 2021
956a600
Added support for custom target type validation
Sergio0694 Mar 22, 2021
58db7bd
Added target type diagnostics to attributes
Sergio0694 Mar 22, 2021
3465156
Initial draft for IMessengerRegisterAllGenerator
Sergio0694 Mar 22, 2021
c86fab9
Fixed duplicate attributes in generated type
Sergio0694 Mar 22, 2021
24625e2
Optimized the LINQ expression for message registration
Sergio0694 Mar 22, 2021
777e020
Enabled loading of generated message registration methods
Sergio0694 Mar 22, 2021
7ac91fe
Fixed unit test for generated RegisterAll method
Sergio0694 Mar 22, 2021
bb1c295
Fixed header generation in generated files
Sergio0694 Mar 22, 2021
c1ae917
Added optimized method lookup on NS2.1 and up
Sergio0694 Mar 22, 2021
7bffd22
Added generation of non-generic RegisterAll methods
Sergio0694 Mar 22, 2021
3a0966c
Enabled AOT-friendly path for message registration
Sergio0694 Mar 22, 2021
d28c75e
Fixed a bug when running on .NET Standard 2.0
Sergio0694 Mar 22, 2021
c514929
Improved filenames for generated files
Sergio0694 Mar 23, 2021
c7210ae
Enabled source generator packing into MVVM Toolkit
Sergio0694 Mar 23, 2021
d2624c8
Fixed analyzer target condition
Sergio0694 Mar 23, 2021
4c035f8
Added ObservableValidatorValidateAllPropertiesGenerator
Sergio0694 Mar 24, 2021
7c88c3d
Enabled generated code for ValidateAllProperties
Sergio0694 Mar 24, 2021
e5af579
Switched symbol comparisons to GetTypeByMetadataName()
Sergio0694 Mar 26, 2021
bfd21a2
Removed unnecessary ConstructUnboundGenericType() calls
Sergio0694 Mar 26, 2021
4de5fd4
Switched usage of ISymbol.ConstructedFrom to OriginalDefinition
Sergio0694 Mar 26, 2021
a2ac073
Added custom SyntaxReceiver to TransitiveMembersGenerator
Sergio0694 Mar 26, 2021
9a543e8
Minor code refactoring
Sergio0694 Mar 26, 2021
2acbff6
Switched to SyntaxReceiver in remaining generators
Sergio0694 Mar 26, 2021
7d87d87
Minor code style tweaks
Sergio0694 Mar 26, 2021
06595ae
Added debugging attributes to validator/messaging generated classes
Sergio0694 Mar 26, 2021
3e29388
Added debugging attributes to remaining generated classes
Sergio0694 Mar 27, 2021
d51b0ab
Minor code refactoring
Sergio0694 Mar 28, 2021
8b708f6
Added initial version of ObservablePropertyAttribute
Sergio0694 Mar 28, 2021
4cd8a7c
Added XML docs copying from fields to generated properties
Sergio0694 Mar 29, 2021
0a08615
Added handling for modifiers in sealed classes
Sergio0694 Mar 29, 2021
0ed3e4d
Added [GeneratedCode] attribute to some generators, refactoring
Sergio0694 Mar 29, 2021
79b36fb
Switched to global:: paths for other generated files
Sergio0694 Mar 29, 2021
263d406
Fixed leading trivia in some generated files
Sergio0694 Mar 29, 2021
62f883d
Added handling for optional INotifyPropertyChanging in properties
Sergio0694 Mar 29, 2021
e4f5648
Added AlsoNotifyForAttribute type
Sergio0694 Mar 30, 2021
02c87eb
Added initial support for generated validation property attributes
Sergio0694 Mar 30, 2021
9c33591
Added unit tests for validation attributes generation
Sergio0694 Mar 30, 2021
96f6e97
Enabled validation support for generated properties
Sergio0694 Mar 30, 2021
c0d4f00
Removed unnecessary if statement in generated code
Sergio0694 Mar 30, 2021
6188d3d
Improved constructor definition for [AlsoNotifyForAttribute] type
Sergio0694 Mar 30, 2021
25cf030
Added diagnostic for observable property + validation without base class
Sergio0694 Mar 30, 2021
4145318
Added diagnostic for failure in ObservablePropertyGenerator
Sergio0694 Mar 30, 2021
6b6d66a
Minor optimization to [ObservableProperty] syntax receiver
Sergio0694 Mar 31, 2021
dadbd48
Added draft tests for source generator diagnostics
Sergio0694 Mar 31, 2021
30d91ea
Added ICommandAttribute type
Sergio0694 Mar 31, 2021
2eacae7
Initial draft implementation of ICommandGenerator
Sergio0694 Mar 31, 2021
44762f4
Enabled ICommandGenerator for RelayCommand type
Sergio0694 Mar 31, 2021
6def774
Enabled [ICommand] generation for all command types
Sergio0694 Mar 31, 2021
56f386d
Stripped Async suffix to generated command names
Sergio0694 Mar 31, 2021
5a08c7a
Added ICommandGenerator diagnostics
Sergio0694 Apr 1, 2021
2e8149f
Added support for <summary> XML docs for generated commands
Sergio0694 Apr 2, 2021
27f429f
Improved [ObservableProperty] codegen for ObservableObject
Sergio0694 Apr 2, 2021
b733e68
Enabled nullability annotations for [ObservableProperty]
Sergio0694 Apr 4, 2021
8567605
Initial support for cached property names
Sergio0694 Apr 11, 2021
386276c
Enabled property args caching for non dependent names
Sergio0694 Apr 11, 2021
1ba2b28
Enabled property args caching for ObservableValidator properties
Sergio0694 Apr 11, 2021
4deae23
Minor code refactoring
Sergio0694 Apr 12, 2021
9184ba5
Improved code generation for ValidateAllProperties generator
Sergio0694 Apr 23, 2021
5ba929f
Improved code generation for IRecipient generator
Sergio0694 Apr 23, 2021
7f5bd6c
Switched generated command field to concrete type
Sergio0694 Apr 25, 2021
c5b9050
Performance improvements to validation/messenger generated code
Sergio0694 Apr 25, 2021
134058d
Renamed [AlsoNotifyFor] attribute to [AlsoNotifyChangeFor]
Sergio0694 Jul 13, 2021
fe54a9c
Switch branch name to main in analyzer files
Sergio0694 Jul 16, 2021
eb0b475
Fixed an incorrect example in XML docs
Sergio0694 Jul 16, 2021
37caf88
Improved XML docs for [ObservableProperty]
Sergio0694 Jul 21, 2021
806b6da
Fixed aka.ms link for generator errors info
Sergio0694 Jul 21, 2021
9283f2c
Switched to fully qualified names to resolve all symbols
Sergio0694 Jul 23, 2021
fa939b4
Simplified generated filenames
Sergio0694 Jul 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
; Shipped analyzer releases
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
MVVMTK0001 | Microsoft.Toolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0002 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableObjectGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0003 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableRecipientGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0004 | Microsoft.Toolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0005 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableObjectGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0006 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableObjectGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0007 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableRecipientGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0008 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableRecipientGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0009 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0010 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0011 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error
MVVMTK0012 | Microsoft.Toolkit.Mvvm.SourceGenerators.ICommandGenerator | Error | See https://aka.ms/mvvmtoolkit/error
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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.

namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class NotNullWhenAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="NotNullWhenAttribute"/> class.
/// </summary>
/// <param name="returnValue">The return value condition. If the method returns this value, the associated parameter will not be null.</param>
public NotNullWhenAttribute(bool returnValue)
{
ReturnValue = returnValue;
}

/// <summary>
/// Gets a value indicating whether the annotated parameter will be null depending on the return value.
/// </summary>
public bool ReturnValue { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Toolkit.Mvvm.SourceGenerators.Extensions;
using static Microsoft.Toolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;

namespace Microsoft.Toolkit.Mvvm.SourceGenerators
{
/// <summary>
/// A source generator for the <c>INotifyPropertyChangedAttribute</c> type.
/// </summary>
[Generator]
public sealed class INotifyPropertyChangedGenerator : TransitiveMembersGenerator
{
/// <summary>
/// Initializes a new instance of the <see cref="INotifyPropertyChangedGenerator"/> class.
/// </summary>
public INotifyPropertyChangedGenerator()
: base("Microsoft.Toolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute")
{
}

/// <inheritdoc/>
protected override DiagnosticDescriptor TargetTypeErrorDescriptor => INotifyPropertyChangedGeneratorError;

/// <inheritdoc/>
protected override bool ValidateTargetType(
GeneratorExecutionContext context,
AttributeData attributeData,
ClassDeclarationSyntax classDeclaration,
INamedTypeSymbol classDeclarationSymbol,
[NotNullWhen(false)] out DiagnosticDescriptor? descriptor)
{
INamedTypeSymbol iNotifyPropertyChangedSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged")!;

// Check if the type already implements INotifyPropertyChanged
if (classDeclarationSymbol.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, iNotifyPropertyChangedSymbol)))
{
descriptor = DuplicateINotifyPropertyChangedInterfaceForINotifyPropertyChangedAttributeError;

return false;
}

descriptor = null;

return true;
}

/// <inheritdoc/>
protected override IEnumerable<MemberDeclarationSyntax> FilterDeclaredMembers(
GeneratorExecutionContext context,
AttributeData attributeData,
ClassDeclarationSyntax classDeclaration,
INamedTypeSymbol classDeclarationSymbol,
ClassDeclarationSyntax sourceDeclaration)
{
// If requested, only include the event and the basic methods to raise it, but not the additional helpers
if (attributeData.HasNamedArgument("IncludeAdditionalHelperMethods", false))
{
return sourceDeclaration.Members.Where(static member =>
{
return member
is EventFieldDeclarationSyntax
or MethodDeclarationSyntax { Identifier: { ValueText: "OnPropertyChanged" } };
});
}

return sourceDeclaration.Members;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.Toolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;

namespace Microsoft.Toolkit.Mvvm.SourceGenerators
{
/// <summary>
/// A source generator for the <c>ObservableObjectAttribute</c> type.
/// </summary>
[Generator]
public sealed class ObservableObjectGenerator : TransitiveMembersGenerator
{
/// <summary>
/// Initializes a new instance of the <see cref="ObservableObjectGenerator"/> class.
/// </summary>
public ObservableObjectGenerator()
: base("Microsoft.Toolkit.Mvvm.ComponentModel.ObservableObjectAttribute")
{
}

/// <inheritdoc/>
protected override DiagnosticDescriptor TargetTypeErrorDescriptor => ObservableObjectGeneratorError;

/// <inheritdoc/>
protected override bool ValidateTargetType(
GeneratorExecutionContext context,
AttributeData attributeData,
ClassDeclarationSyntax classDeclaration,
INamedTypeSymbol classDeclarationSymbol,
[NotNullWhen(false)] out DiagnosticDescriptor? descriptor)
{
INamedTypeSymbol
iNotifyPropertyChangedSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged")!,
iNotifyPropertyChangingSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanging")!;

// Check if the type already implements INotifyPropertyChanged...
if (classDeclarationSymbol.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, iNotifyPropertyChangedSymbol)))
{
descriptor = DuplicateINotifyPropertyChangedInterfaceForObservableObjectAttributeError;

return false;
}

// ...or INotifyPropertyChanging
if (classDeclarationSymbol.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, iNotifyPropertyChangingSymbol)))
{
descriptor = DuplicateINotifyPropertyChangingInterfaceForObservableObjectAttributeError;

return false;
}

descriptor = null;

return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Microsoft.Toolkit.Mvvm.SourceGenerators
{
/// <inheritdoc cref="ObservablePropertyGenerator"/>
public sealed partial class ObservablePropertyGenerator
{
/// <summary>
/// An <see cref="ISyntaxContextReceiver"/> that selects candidate nodes to process.
/// </summary>
private sealed class SyntaxReceiver : ISyntaxContextReceiver
{
/// <summary>
/// The list of info gathered during exploration.
/// </summary>
private readonly List<Item> gatheredInfo = new();

/// <summary>
/// Gets the collection of gathered info to process.
/// </summary>
public IReadOnlyCollection<Item> GatheredInfo => this.gatheredInfo;

/// <inheritdoc/>
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
{
if (context.Node is FieldDeclarationSyntax { AttributeLists: { Count: > 0 } } fieldDeclaration &&
context.SemanticModel.Compilation.GetTypeByMetadataName("Microsoft.Toolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") is INamedTypeSymbol attributeSymbol)
{
SyntaxTriviaList leadingTrivia = fieldDeclaration.GetLeadingTrivia();

foreach (VariableDeclaratorSyntax variableDeclarator in fieldDeclaration.Declaration.Variables)
{
if (context.SemanticModel.GetDeclaredSymbol(variableDeclarator) is IFieldSymbol fieldSymbol &&
fieldSymbol.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeSymbol)))
{
this.gatheredInfo.Add(new Item(leadingTrivia, fieldSymbol));
}
}
}
}

/// <summary>
/// A model for a group of item representing a discovered type to process.
/// </summary>
/// <param name="LeadingTrivia">The leading trivia for the field declaration.</param>
/// <param name="FieldSymbol">The <see cref="IFieldSymbol"/> instance for the target field.</param>
public sealed record Item(SyntaxTriviaList LeadingTrivia, IFieldSymbol FieldSymbol);
}
}
}
Loading