diff --git a/README.md b/README.md index 333e9b9..5cd6605 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # ReactiveUI.SourceGenerators Use source generators to generate ReactiveUI objects. +The minimum C# version is 12.0 and the minimum Visual Studio version is 17.8.0. These Source Generators were designed to work in full with ReactiveUI V19.5.31 and newer supporting all features, currently: - [Reactive] @@ -15,6 +16,8 @@ These Source Generators were designed to work in full with ReactiveUI V19.5.31 a Versions older than V19.5.31 to this: - [ReactiveCommand] all options supported except Cancellation Token asnyc methods. +For dot net framework 4.8 and older versions please add Polyfill or PolySharp package to your project to gain the IsExternalInit class and set the LangVersion to 12.0 or latest in your project file. + [analyzer codes](https://github.com/reactiveui/ReactiveUI.SourceGenerators/blob/main/src/ReactiveUI.SourceGenerators/AnalyzerReleases.Shipped.md) # Historical ways diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 3d2f431..8f4d1d7 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -2,7 +2,7 @@ true true - 4.6.0 + 4.8.0 @@ -13,8 +13,11 @@ + + + diff --git a/src/Directory.build.props b/src/Directory.build.props index d6a248a..31dd74b 100644 --- a/src/Directory.build.props +++ b/src/Directory.build.props @@ -17,7 +17,7 @@ https://github.com/reactiveui/ReactiveUI.SourceGenerators/releases https://github.com/reactiveui/reactiveui.sourcegenerators git - $(NoWarn);IDE0060;IDE1006;IDE0130;VSSpell001;RS2007 + $(NoWarn);IDE0060;IDE1006;IDE0130;VSSpell001;RS2007;NU5128 true diff --git a/src/ReactiveUI.SourceGenerator.Tests/ReactiveUI.SourceGenerators.Tests.csproj b/src/ReactiveUI.SourceGenerator.Tests/ReactiveUI.SourceGenerators.Tests.csproj index ce8b968..21fe34d 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/ReactiveUI.SourceGenerators.Tests.csproj +++ b/src/ReactiveUI.SourceGenerator.Tests/ReactiveUI.SourceGenerators.Tests.csproj @@ -19,9 +19,11 @@ - + + + diff --git a/src/ReactiveUI.SourceGenerators.CodeFixers/ReactiveUI.CodeFixes.csproj b/src/ReactiveUI.SourceGenerators.CodeFixers/ReactiveUI.SourceGenerators.Analyzers.CodeFixes.csproj similarity index 67% rename from src/ReactiveUI.SourceGenerators.CodeFixers/ReactiveUI.CodeFixes.csproj rename to src/ReactiveUI.SourceGenerators.CodeFixers/ReactiveUI.SourceGenerators.Analyzers.CodeFixes.csproj index b6fc69c..ba43a47 100644 --- a/src/ReactiveUI.SourceGenerators.CodeFixers/ReactiveUI.CodeFixes.csproj +++ b/src/ReactiveUI.SourceGenerators.CodeFixers/ReactiveUI.SourceGenerators.Analyzers.CodeFixes.csproj @@ -3,18 +3,24 @@ netstandard2.0 enable + latest true true - latest true true false - A MVVM framework that integrates with the Reactive Extensions for .NET to create elegant, testable User Interfaces that run on any mobile or desktop platform. This is the Source Generators package for ReactiveUI + true + true + true + $(NoWarn);RS1038;RS2007;NU5128 + A MVVM framework that integrates with the Reactive Extensions for .NET to create elegant, testable User Interfaces that run on any mobile or desktop platform. This is the Source Generators package for ReactiveUI - + + + diff --git a/src/ReactiveUI.SourceGenerators.sln b/src/ReactiveUI.SourceGenerators.sln index eb7af8c..4ed07cd 100644 --- a/src/ReactiveUI.SourceGenerators.sln +++ b/src/ReactiveUI.SourceGenerators.sln @@ -24,7 +24,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.SourceGenerators EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestLibs", "TestLibs", "{B86ED9C1-AFFB-4854-AD80-F4B4050CAD0A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.CodeFixes", "ReactiveUI.SourceGenerators.CodeFixers\ReactiveUI.CodeFixes.csproj", "{C89EE66E-E1DC-4A31-9322-20D95CB0D74D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.SourceGenerators.Analyzers.CodeFixes", "ReactiveUI.SourceGenerators.CodeFixers\ReactiveUI.SourceGenerators.Analyzers.CodeFixes.csproj", "{C89EE66E-E1DC-4A31-9322-20D95CB0D74D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/ReactiveUI.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/src/ReactiveUI.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index fd22411..efdaeaa 100644 --- a/src/ReactiveUI.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/src/ReactiveUI.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -37,7 +37,7 @@ internal static class DiagnosticDescriptors id: "RXUISG0002", title: "Invalid ReactiveCommand method signature", messageFormat: "The method {0}.{1} cannot be used to generate a command property, as its signature isn't compatible with any of the existing reactive command types", - category: "ReactiveUI.SourceGenerators.ReactiveCommandGenerator", + category: typeof(ReactiveCommandGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: "Cannot apply [ReactiveCommand] to methods with a signature that doesn't match any of the existing reactive command types.", @@ -53,7 +53,7 @@ internal static class DiagnosticDescriptors id: "RXUISG0003", title: "Invalid ReactiveCommand.CanExecute member name", messageFormat: "The CanExecute name must refer to a valid member, but \"{0}\" has no matches in type {1}", - category: "ReactiveUI.SourceGenerators.ReactiveCommandGenerator", + category: typeof(ReactiveCommandGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: "The CanExecute name in [ReactiveCommand] must refer to a valid member in its parent type.", @@ -69,7 +69,7 @@ internal static class DiagnosticDescriptors id: "RXUISG0004", title: "Multiple ReactiveCommand.CanExecute member name matches", messageFormat: "The CanExecute name must refer to a single member, but \"{0}\" has multiple matches in type {1}", - category: "ReactiveUI.SourceGenerators.ReactiveCommandGenerator", + category: typeof(ReactiveCommandGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: "Cannot set the CanExecute name in [ReactiveCommand] to one that has multiple matches in its parent type (it must refer to a single compatible member).", @@ -85,7 +85,7 @@ internal static class DiagnosticDescriptors id: "RXUISG0005", title: "No valid ReactiveCommand.CanExecute member match", messageFormat: "The CanExecute name must refer to a compatible member, but no valid members were found for \"{0}\" in type {1}", - category: "ReactiveUI.SourceGenerators.ReactiveCommandGenerator", + category: typeof(ReactiveCommandGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: "The CanExecute name in [ReactiveCommand] must refer to a compatible member (either a property or a method) to be used in a generated command.", @@ -101,7 +101,7 @@ internal static class DiagnosticDescriptors id: "RXUISG0006", title: "Invalid field or property targeted attribute type", messageFormat: "The method {0} annotated with [ReactiveCommand] is using attribute \"{1}\" which was not recognized as a valid type (are you missing a using directive?)", - category: "ReactiveUI.SourceGenerators.ReactiveCommandGenerator", + category: typeof(ReactiveCommandGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: "All attributes targeting the generated field or property for a method annotated with [ReactiveCommand] must correctly be resolved to valid types.", @@ -117,7 +117,7 @@ internal static class DiagnosticDescriptors id: "RXUISG0007", title: "Invalid field or property targeted attribute expression", messageFormat: "The method {0} annotated with [ReactiveCommand] is using attribute \"{1}\" with an invalid expression (are you passing any incorrect parameters to the attribute constructor?)", - category: "ReactiveUI.SourceGenerators.ReactiveCommandGenerator", + category: typeof(ReactiveCommandGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: "All attributes targeting the generated field or property for a method annotated with [ReactiveCommand] must be using valid expressions.", @@ -149,7 +149,7 @@ internal static class DiagnosticDescriptors id: "RXUISG0009", title: "Name collision for generated property", messageFormat: "The field {0}.{1} cannot be used to generate an reactive property, as its name would collide with the field name (instance fields should use the \"lowerCamel\", \"_lowerCamel\" or \"m_lowerCamel\" pattern)", - category: "ReactiveUI.SourceGenerators.ReactiveGenerator", + category: typeof(ReactiveGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: "The name of fields annotated with [Reactive] should use \"lowerCamel\", \"_lowerCamel\" or \"m_lowerCamel\" pattern to avoid collisions with the generated properties.", @@ -165,7 +165,7 @@ internal static class DiagnosticDescriptors id: "RXUISG0010", title: "Invalid property targeted attribute type", messageFormat: "The field {0} annotated with [Reactive] is using attribute \"{1}\" which was not recognized as a valid type (are you missing a using directive?)", - category: "ReactiveUI.SourceGenerators.ReactiveGenerator", + category: typeof(ReactiveGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: "All attributes targeting the generated property for a field annotated with [Reactive] must correctly be resolved to valid types.", @@ -181,7 +181,7 @@ internal static class DiagnosticDescriptors id: "RXUISG0011", title: "Invalid property targeted attribute expression", messageFormat: "The field {0} annotated with [Reactive] is using attribute \"{1}\" with an invalid expression (are you passing any incorrect parameters to the attribute constructor?)", - category: "ReactiveUI.SourceGenerators.ReactiveGenerator", + category: typeof(ReactiveGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: "All attributes targeting the generated property for a field annotated with [Reactive] must be using valid expressions.", @@ -245,7 +245,7 @@ internal static class DiagnosticDescriptors id: "RXUISG0015", title: "Invalid generated property declaration", messageFormat: "The field {0}.{1} cannot be used to generate an reactive property, as its name or type would cause conflicts with other generated members", - category: "ReactiveUI.SourceGenerators.ReactiveGenerator", + category: typeof(ReactiveGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, description: "The fields annotated with [Reactive] cannot result in a property name or have a type that would cause conflicts with other generated members.", diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.Execute.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.Execute.cs index 57c1307..b4aaa58 100644 --- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.Execute.cs +++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.Execute.cs @@ -14,6 +14,7 @@ using ReactiveUI.SourceGenerators.Helpers; using ReactiveUI.SourceGenerators.Models; using ReactiveUI.SourceGenerators.Reactive.Models; +using static ReactiveUI.SourceGenerators.Diagnostics.DiagnosticDescriptors; namespace ReactiveUI.SourceGenerators; @@ -23,8 +24,9 @@ namespace ReactiveUI.SourceGenerators; /// public sealed partial class ObservableAsPropertyGenerator { - private static PropertyInfo? GetVariableInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token) + private static Result? GetVariableInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token) { + using var builder = ImmutableArrayBuilder.Rent(); var symbol = context.TargetSymbol; token.ThrowIfCancellationRequested(); @@ -42,7 +44,12 @@ public sealed partial class ObservableAsPropertyGenerator // Validate the target type if (!IsTargetTypeValid(fieldSymbol)) { - return default; + builder.Add( + InvalidObservableAsPropertyError, + fieldSymbol, + fieldSymbol.ContainingType, + fieldSymbol.Name); + return new(default, builder.ToImmutable()); } // Get the can PropertyName member, if any @@ -55,15 +62,20 @@ public sealed partial class ObservableAsPropertyGenerator var fieldName = fieldSymbol.Name; var propertyName = GetGeneratedPropertyName(fieldSymbol); - var fieldDeclaration = (FieldDeclarationSyntax)context.TargetNode.Parent!.Parent!; - var initializer = fieldDeclaration.Declaration.Variables.FirstOrDefault()?.Initializer?.ToFullString(); - // Check for name collisions if (fieldName == propertyName) { - return default; + builder.Add( + ReactivePropertyNameCollisionError, + fieldSymbol, + fieldSymbol.ContainingType, + fieldSymbol.Name); + return new(default, builder.ToImmutable()); } + var fieldDeclaration = (FieldDeclarationSyntax)context.TargetNode.Parent!.Parent!; + var initializer = fieldDeclaration.Declaration.Variables.FirstOrDefault()?.Initializer?.ToFullString(); + token.ThrowIfCancellationRequested(); using var forwardedAttributes = ImmutableArrayBuilder.Rent(); @@ -131,6 +143,11 @@ public sealed partial class ObservableAsPropertyGenerator // lack of IntelliSense when constructing attributes over the field, but this is the best we can do from this end anyway. if (!context.SemanticModel.GetSymbolInfo(attribute, token).TryGetAttributeTypeSymbol(out var attributeTypeSymbol)) { + builder.Add( + InvalidPropertyTargetedAttributeOnObservableAsPropertyField, + attribute, + fieldSymbol, + attribute.Name); continue; } @@ -139,6 +156,11 @@ public sealed partial class ObservableAsPropertyGenerator // Try to extract the forwarded attribute if (!AttributeInfo.TryCreate(attributeTypeSymbol, context.SemanticModel, attributeArguments, token, out var attributeInfo)) { + builder.Add( + InvalidPropertyTargetedAttributeExpressionOnObservableAsPropertyField, + attribute, + fieldSymbol, + attribute.Name); continue; } @@ -162,6 +184,7 @@ public sealed partial class ObservableAsPropertyGenerator var targetInfo = TargetInfo.From(fieldSymbol.ContainingType); return new( + new( targetInfo.FileHintName, targetInfo.TargetName, targetInfo.TargetNamespace, @@ -175,7 +198,8 @@ public sealed partial class ObservableAsPropertyGenerator isReferenceTypeOrUnconstraindTypeParameter, includeMemberNotNullOnSetAccessor, forwardedPropertyAttributes, - isReadonly == false ? string.Empty : "readonly"); + isReadonly == false ? string.Empty : "readonly"), + builder.ToImmutable()); } private static string GenerateSource(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, PropertyInfo[] properties) diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.cs index b740c55..61d6aa4 100644 --- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.cs +++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.cs @@ -32,7 +32,16 @@ private static void RunObservableAsPropertyFromField(in IncrementalGeneratorInit // Generate the requested properties and methods context.RegisterSourceOutput(propertyInfo, static (context, input) => { - var groupedPropertyInfo = input.GroupBy( + foreach (var diagnostic in input.SelectMany(static x => x.Errors)) + { + // Output the diagnostics + context.ReportDiagnostic(diagnostic.ToDiagnostic()); + } + + // Gather all the properties that are valid and group them by the target information. + var groupedPropertyInfo = input + .Where(static x => x.Value != null) + .Select(static x => x.Value!).GroupBy( static info => (info.FileHintName, info.TargetName, info.TargetNamespace, info.TargetVisibility, info.TargetType), static info => info) .ToImmutableArray(); diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs index f25338c..6e0817c 100644 --- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs +++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs @@ -12,6 +12,7 @@ using ReactiveUI.SourceGenerators.Helpers; using ReactiveUI.SourceGenerators.Models; using ReactiveUI.SourceGenerators.ObservableAsProperty.Models; +using static ReactiveUI.SourceGenerators.Diagnostics.DiagnosticDescriptors; namespace ReactiveUI.SourceGenerators; @@ -21,8 +22,9 @@ namespace ReactiveUI.SourceGenerators; /// public sealed partial class ObservableAsPropertyGenerator { - private static ObservableMethodInfo? GetObservableInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token) + private static Result? GetObservableInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token) { + using var diagnostics = ImmutableArrayBuilder.Rent(); var symbol = context.TargetSymbol; token.ThrowIfCancellationRequested(); @@ -40,7 +42,11 @@ public sealed partial class ObservableAsPropertyGenerator var methodSymbol = (IMethodSymbol)symbol!; if (methodSymbol.Parameters.Length != 0) { - return default; + diagnostics.Add( + ObservableAsPropertyMethodHasParametersError, + methodSymbol, + methodSymbol.Name); + return new(default, diagnostics.ToImmutable()); } var isObservable = methodSymbol.ReturnType.IsObservableReturnType(); @@ -68,6 +74,7 @@ public sealed partial class ObservableAsPropertyGenerator var targetInfo = TargetInfo.From(methodSymbol.ContainingType); return new( + new( targetInfo.FileHintName, targetInfo.TargetName, targetInfo.TargetNamespace, @@ -80,7 +87,8 @@ public sealed partial class ObservableAsPropertyGenerator propertyName ?? (methodSymbol.Name + "Property"), observableType, false, - propertyAttributes); + propertyAttributes), + diagnostics.ToImmutable()); } if (context.TargetNode is PropertyDeclarationSyntax propertySyntax) @@ -111,6 +119,7 @@ public sealed partial class ObservableAsPropertyGenerator var targetInfo = TargetInfo.From(propertySymbol.ContainingType); return new( + new( targetInfo.FileHintName, targetInfo.TargetName, targetInfo.TargetNamespace, @@ -123,7 +132,8 @@ public sealed partial class ObservableAsPropertyGenerator propertyName ?? (propertySymbol.Name + "Property"), observableType, true, - propertyAttributes); + propertyAttributes), + diagnostics.ToImmutable()); } return default; diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.cs index ae930b2..72f54a7 100644 --- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.cs +++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.cs @@ -35,7 +35,16 @@ private static void RunObservableAsPropertyFromObservable(in IncrementalGenerato // Generate the requested properties and methods context.RegisterSourceOutput(propertyInfo, static (context, input) => { - var groupedPropertyInfo = input.GroupBy( + foreach (var diagnostic in input.SelectMany(static x => x.Errors)) + { + // Output the diagnostics + context.ReportDiagnostic(diagnostic.ToDiagnostic()); + } + + // Gather all the properties that are valid and group them by the target information. + var groupedPropertyInfo = input + .Where(static x => x.Value != null) + .Select(static x => x.Value!).GroupBy( static info => (info.FileHintName, info.TargetName, info.TargetNamespace, info.TargetVisibility, info.TargetType), static info => info) .ToImmutableArray(); diff --git a/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs index 837af44..cc95503 100644 --- a/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs +++ b/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs @@ -14,6 +14,7 @@ using ReactiveUI.SourceGenerators.Helpers; using ReactiveUI.SourceGenerators.Models; using ReactiveUI.SourceGenerators.Reactive.Models; +using static ReactiveUI.SourceGenerators.Diagnostics.DiagnosticDescriptors; namespace ReactiveUI.SourceGenerators; @@ -31,9 +32,12 @@ public sealed partial class ReactiveGenerator /// /// The context. /// The token. - /// The value. - private static PropertyInfo? GetVariableInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token) + /// + /// The value. + /// + private static Result? GetVariableInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token) { + using var builder = ImmutableArrayBuilder.Rent(); var symbol = context.TargetSymbol; if (!symbol.TryGetAttributeWithFullyQualifiedMetadataName(AttributeDefinitions.ReactiveAttributeType, out var attributeData)) @@ -48,7 +52,12 @@ public sealed partial class ReactiveGenerator if (!IsTargetTypeValid(fieldSymbol)) { - return default; + builder.Add( + InvalidReactiveError, + fieldSymbol, + fieldSymbol.ContainingType, + fieldSymbol.Name); + return new(default, builder.ToImmutable()); } token.ThrowIfCancellationRequested(); @@ -73,6 +82,16 @@ public sealed partial class ReactiveGenerator var fieldName = fieldSymbol.Name; var propertyName = fieldSymbol.GetGeneratedPropertyName(); + if (fieldName == propertyName) + { + builder.Add( + ReactivePropertyNameCollisionError, + fieldSymbol, + fieldSymbol.ContainingType, + fieldSymbol.Name); + return new(default, builder.ToImmutable()); + } + token.ThrowIfCancellationRequested(); // Get the nullability info for the property @@ -147,6 +166,11 @@ public sealed partial class ReactiveGenerator // lack of IntelliSense when constructing attributes over the field, but this is the best we can do from this end anyway. if (!context.SemanticModel.GetSymbolInfo(attribute, token).TryGetAttributeTypeSymbol(out var attributeTypeSymbol)) { + builder.Add( + InvalidPropertyTargetedAttributeOnReactiveField, + attribute, + fieldSymbol, + attribute.Name); continue; } @@ -155,6 +179,11 @@ public sealed partial class ReactiveGenerator // Try to extract the forwarded attribute if (!AttributeInfo.TryCreate(attributeTypeSymbol, context.SemanticModel, attributeArguments, token, out var attributeInfo)) { + builder.Add( + InvalidPropertyTargetedAttributeExpressionOnReactiveField, + attribute, + fieldSymbol, + attribute.Name); continue; } @@ -171,6 +200,7 @@ public sealed partial class ReactiveGenerator token.ThrowIfCancellationRequested(); return new( + new( targetInfo.FileHintName, targetInfo.TargetName, targetInfo.TargetNamespace, @@ -184,7 +214,8 @@ public sealed partial class ReactiveGenerator isReferenceTypeOrUnconstraindTypeParameter, includeMemberNotNullOnSetAccessor, forwardedAttributesString, - accessModifier); + accessModifier), + builder.ToImmutable()); } /// diff --git a/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.cs b/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.cs index ce39222..835eaac 100644 --- a/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.cs +++ b/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.cs @@ -46,7 +46,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Generate the requested properties context.RegisterSourceOutput(propertyInfo, static (context, input) => { - var groupedPropertyInfo = input.GroupBy( + foreach (var diagnostic in input.SelectMany(static x => x.Errors)) + { + // Output the diagnostics + context.ReportDiagnostic(diagnostic.ToDiagnostic()); + } + + // Gather all the properties that are valid and group them by the target information. + var groupedPropertyInfo = input + .Where(static x => x.Value != null) + .Select(static x => x.Value!).GroupBy( static info => (info.FileHintName, info.TargetName, info.TargetNamespace, info.TargetVisibility, info.TargetType), static info => info) .ToImmutableArray(); diff --git a/src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs index 280fafb..258efad 100644 --- a/src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs +++ b/src/ReactiveUI.SourceGenerators/ReactiveCommand/ReactiveCommandGenerator.Execute.cs @@ -9,12 +9,12 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using ReactiveUI.SourceGenerators.Extensions; using ReactiveUI.SourceGenerators.Helpers; using ReactiveUI.SourceGenerators.Input.Models; using ReactiveUI.SourceGenerators.Models; +using static ReactiveUI.SourceGenerators.Diagnostics.DiagnosticDescriptors; namespace ReactiveUI.SourceGenerators; diff --git a/src/ReactiveUI.SourceGenerators/ReactiveUI.SourceGenerators.csproj b/src/ReactiveUI.SourceGenerators/ReactiveUI.SourceGenerators.csproj index bd89f71..6950971 100644 --- a/src/ReactiveUI.SourceGenerators/ReactiveUI.SourceGenerators.csproj +++ b/src/ReactiveUI.SourceGenerators/ReactiveUI.SourceGenerators.csproj @@ -3,16 +3,17 @@ netstandard2.0 enable + latest true - Generated true - latest true true false + true A MVVM framework that integrates with the Reactive Extensions for .NET to create elegant, testable User Interfaces that run on any mobile or desktop platform. This is the Source Generators package for ReactiveUI + Generated