Skip to content

Commit 93f0934

Browse files
authored
feat: Add Diagnostics to Source Generators (#112)
* Add Diagnostics To Reactive Generator * Add Diagnostics to OAPH From Field * Add Diagnostics to OAPH Observable * Update to 4.8.0 * Update README.md
1 parent cf560f5 commit 93f0934

15 files changed

+146
-39
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# ReactiveUI.SourceGenerators
22
Use source generators to generate ReactiveUI objects.
3+
The minimum C# version is 12.0 and the minimum Visual Studio version is 17.8.0.
34

45
These Source Generators were designed to work in full with ReactiveUI V19.5.31 and newer supporting all features, currently:
56
- [Reactive]
@@ -15,6 +16,8 @@ These Source Generators were designed to work in full with ReactiveUI V19.5.31 a
1516
Versions older than V19.5.31 to this:
1617
- [ReactiveCommand] all options supported except Cancellation Token asnyc methods.
1718

19+
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.
20+
1821
[analyzer codes](https://github.com/reactiveui/ReactiveUI.SourceGenerators/blob/main/src/ReactiveUI.SourceGenerators/AnalyzerReleases.Shipped.md)
1922

2023
# Historical ways

src/Directory.Packages.props

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
5-
<CodeAnalysisVersion>4.6.0</CodeAnalysisVersion>
5+
<CodeAnalysisVersion>4.8.0</CodeAnalysisVersion>
66
</PropertyGroup>
77
<ItemGroup>
88
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
@@ -13,8 +13,11 @@
1313
<PackageVersion Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
1414
<PackageVersion Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
1515
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
16+
<PackageVersion Include="Microsoft.CodeAnalysis" Version="$(CodeAnalysisVersion)" />
1617
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="$(CodeAnalysisVersion)" />
18+
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="$(CodeAnalysisVersion)" />
1719
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(CodeAnalysisVersion)" />
20+
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(CodeAnalysisVersion)" />
1821
<PackageVersion Include="PolySharp" Version="1.14.1" />
1922

2023
<PackageVersion Include="Avalonia" Version="11.1.4" />

src/Directory.build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<PackageReleaseNotes>https://github.com/reactiveui/ReactiveUI.SourceGenerators/releases</PackageReleaseNotes>
1818
<RepositoryUrl>https://github.com/reactiveui/reactiveui.sourcegenerators</RepositoryUrl>
1919
<RepositoryType>git</RepositoryType>
20-
<NoWarn>$(NoWarn);IDE0060;IDE1006;IDE0130;VSSpell001;RS2007</NoWarn>
20+
<NoWarn>$(NoWarn);IDE0060;IDE1006;IDE0130;VSSpell001;RS2007;NU5128</NoWarn>
2121
<!-- Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
2222
<PublishRepositoryUrl>true</PublishRepositoryUrl>
2323
<!-- Embed source files that are not tracked by the source control manager in the PDB -->

src/ReactiveUI.SourceGenerator.Tests/ReactiveUI.SourceGenerators.Tests.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
<PackageReference Include="Verify.Xunit" />
2020
<PackageReference Include="Verify.SourceGenerators" />
2121
<PackageReference Include="Basic.Reference.Assemblies.Net80" />
22-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" PrivateAssets="all" VersionOverride="4.11.0" />
2322
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" VersionOverride="4.11.0" />
23+
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="all" VersionOverride="4.11.0" />
24+
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" PrivateAssets="all" VersionOverride="4.11.0" />
2425
<PackageReference Include="Microsoft.CodeAnalysis" PrivateAssets="all" VersionOverride="4.11.0" />
26+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" PrivateAssets="all" VersionOverride="4.11.0" />
2527
<PackageReference Include="FluentAssertions" />
2628
</ItemGroup>
2729

src/ReactiveUI.SourceGenerators.CodeFixers/ReactiveUI.CodeFixes.csproj renamed to src/ReactiveUI.SourceGenerators.CodeFixers/ReactiveUI.SourceGenerators.Analyzers.CodeFixes.csproj

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,24 @@
33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
55
<Nullable>enable</Nullable>
6+
<LangVersion>latest</LangVersion>
67
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
78
<IsRoslynComponent>true</IsRoslynComponent>
8-
<LangVersion>latest</LangVersion>
99
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
1010
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1111
<IncludeBuildOutput>false</IncludeBuildOutput>
12-
<PackageDescription>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</PackageDescription>
12+
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
13+
<DevelopmentDependency>true</DevelopmentDependency>
14+
<NoPackageAnalysis>true</NoPackageAnalysis>
1315

16+
<NoWarn>$(NoWarn);RS1038;RS2007;NU5128</NoWarn>
17+
<PackageDescription>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</PackageDescription>
1418
</PropertyGroup>
1519

1620
<ItemGroup>
17-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" PrivateAssets="all" />
21+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
22+
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="all" />
23+
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" PrivateAssets="all" />
1824
</ItemGroup>
1925

2026
<!-- This ensures the library will be packaged as a source generator when we use `dotnet pack` -->

src/ReactiveUI.SourceGenerators.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.SourceGenerators
2424
EndProject
2525
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestLibs", "TestLibs", "{B86ED9C1-AFFB-4854-AD80-F4B4050CAD0A}"
2626
EndProject
27-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.CodeFixes", "ReactiveUI.SourceGenerators.CodeFixers\ReactiveUI.CodeFixes.csproj", "{C89EE66E-E1DC-4A31-9322-20D95CB0D74D}"
27+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.SourceGenerators.Analyzers.CodeFixes", "ReactiveUI.SourceGenerators.CodeFixers\ReactiveUI.SourceGenerators.Analyzers.CodeFixes.csproj", "{C89EE66E-E1DC-4A31-9322-20D95CB0D74D}"
2828
EndProject
2929
Global
3030
GlobalSection(SolutionConfigurationPlatforms) = preSolution

src/ReactiveUI.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ internal static class DiagnosticDescriptors
3737
id: "RXUISG0002",
3838
title: "Invalid ReactiveCommand method signature",
3939
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",
40-
category: "ReactiveUI.SourceGenerators.ReactiveCommandGenerator",
40+
category: typeof(ReactiveCommandGenerator).FullName,
4141
defaultSeverity: DiagnosticSeverity.Error,
4242
isEnabledByDefault: true,
4343
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
5353
id: "RXUISG0003",
5454
title: "Invalid ReactiveCommand.CanExecute member name",
5555
messageFormat: "The CanExecute name must refer to a valid member, but \"{0}\" has no matches in type {1}",
56-
category: "ReactiveUI.SourceGenerators.ReactiveCommandGenerator",
56+
category: typeof(ReactiveCommandGenerator).FullName,
5757
defaultSeverity: DiagnosticSeverity.Error,
5858
isEnabledByDefault: true,
5959
description: "The CanExecute name in [ReactiveCommand] must refer to a valid member in its parent type.",
@@ -69,7 +69,7 @@ internal static class DiagnosticDescriptors
6969
id: "RXUISG0004",
7070
title: "Multiple ReactiveCommand.CanExecute member name matches",
7171
messageFormat: "The CanExecute name must refer to a single member, but \"{0}\" has multiple matches in type {1}",
72-
category: "ReactiveUI.SourceGenerators.ReactiveCommandGenerator",
72+
category: typeof(ReactiveCommandGenerator).FullName,
7373
defaultSeverity: DiagnosticSeverity.Error,
7474
isEnabledByDefault: true,
7575
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
8585
id: "RXUISG0005",
8686
title: "No valid ReactiveCommand.CanExecute member match",
8787
messageFormat: "The CanExecute name must refer to a compatible member, but no valid members were found for \"{0}\" in type {1}",
88-
category: "ReactiveUI.SourceGenerators.ReactiveCommandGenerator",
88+
category: typeof(ReactiveCommandGenerator).FullName,
8989
defaultSeverity: DiagnosticSeverity.Error,
9090
isEnabledByDefault: true,
9191
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
101101
id: "RXUISG0006",
102102
title: "Invalid field or property targeted attribute type",
103103
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?)",
104-
category: "ReactiveUI.SourceGenerators.ReactiveCommandGenerator",
104+
category: typeof(ReactiveCommandGenerator).FullName,
105105
defaultSeverity: DiagnosticSeverity.Error,
106106
isEnabledByDefault: true,
107107
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
117117
id: "RXUISG0007",
118118
title: "Invalid field or property targeted attribute expression",
119119
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?)",
120-
category: "ReactiveUI.SourceGenerators.ReactiveCommandGenerator",
120+
category: typeof(ReactiveCommandGenerator).FullName,
121121
defaultSeverity: DiagnosticSeverity.Error,
122122
isEnabledByDefault: true,
123123
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
149149
id: "RXUISG0009",
150150
title: "Name collision for generated property",
151151
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)",
152-
category: "ReactiveUI.SourceGenerators.ReactiveGenerator",
152+
category: typeof(ReactiveGenerator).FullName,
153153
defaultSeverity: DiagnosticSeverity.Error,
154154
isEnabledByDefault: true,
155155
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
165165
id: "RXUISG0010",
166166
title: "Invalid property targeted attribute type",
167167
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?)",
168-
category: "ReactiveUI.SourceGenerators.ReactiveGenerator",
168+
category: typeof(ReactiveGenerator).FullName,
169169
defaultSeverity: DiagnosticSeverity.Error,
170170
isEnabledByDefault: true,
171171
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
181181
id: "RXUISG0011",
182182
title: "Invalid property targeted attribute expression",
183183
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?)",
184-
category: "ReactiveUI.SourceGenerators.ReactiveGenerator",
184+
category: typeof(ReactiveGenerator).FullName,
185185
defaultSeverity: DiagnosticSeverity.Error,
186186
isEnabledByDefault: true,
187187
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
245245
id: "RXUISG0015",
246246
title: "Invalid generated property declaration",
247247
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",
248-
category: "ReactiveUI.SourceGenerators.ReactiveGenerator",
248+
category: typeof(ReactiveGenerator).FullName,
249249
defaultSeverity: DiagnosticSeverity.Error,
250250
isEnabledByDefault: true,
251251
description: "The fields annotated with [Reactive] cannot result in a property name or have a type that would cause conflicts with other generated members.",

src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.Execute.cs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using ReactiveUI.SourceGenerators.Helpers;
1515
using ReactiveUI.SourceGenerators.Models;
1616
using ReactiveUI.SourceGenerators.Reactive.Models;
17+
using static ReactiveUI.SourceGenerators.Diagnostics.DiagnosticDescriptors;
1718

1819
namespace ReactiveUI.SourceGenerators;
1920

@@ -23,8 +24,9 @@ namespace ReactiveUI.SourceGenerators;
2324
/// <seealso cref="IIncrementalGenerator" />
2425
public sealed partial class ObservableAsPropertyGenerator
2526
{
26-
private static PropertyInfo? GetVariableInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token)
27+
private static Result<PropertyInfo?>? GetVariableInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token)
2728
{
29+
using var builder = ImmutableArrayBuilder<DiagnosticInfo>.Rent();
2830
var symbol = context.TargetSymbol;
2931
token.ThrowIfCancellationRequested();
3032

@@ -42,7 +44,12 @@ public sealed partial class ObservableAsPropertyGenerator
4244
// Validate the target type
4345
if (!IsTargetTypeValid(fieldSymbol))
4446
{
45-
return default;
47+
builder.Add(
48+
InvalidObservableAsPropertyError,
49+
fieldSymbol,
50+
fieldSymbol.ContainingType,
51+
fieldSymbol.Name);
52+
return new(default, builder.ToImmutable());
4653
}
4754

4855
// Get the can PropertyName member, if any
@@ -55,15 +62,20 @@ public sealed partial class ObservableAsPropertyGenerator
5562
var fieldName = fieldSymbol.Name;
5663
var propertyName = GetGeneratedPropertyName(fieldSymbol);
5764

58-
var fieldDeclaration = (FieldDeclarationSyntax)context.TargetNode.Parent!.Parent!;
59-
var initializer = fieldDeclaration.Declaration.Variables.FirstOrDefault()?.Initializer?.ToFullString();
60-
6165
// Check for name collisions
6266
if (fieldName == propertyName)
6367
{
64-
return default;
68+
builder.Add(
69+
ReactivePropertyNameCollisionError,
70+
fieldSymbol,
71+
fieldSymbol.ContainingType,
72+
fieldSymbol.Name);
73+
return new(default, builder.ToImmutable());
6574
}
6675

76+
var fieldDeclaration = (FieldDeclarationSyntax)context.TargetNode.Parent!.Parent!;
77+
var initializer = fieldDeclaration.Declaration.Variables.FirstOrDefault()?.Initializer?.ToFullString();
78+
6779
token.ThrowIfCancellationRequested();
6880

6981
using var forwardedAttributes = ImmutableArrayBuilder<AttributeInfo>.Rent();
@@ -131,6 +143,11 @@ public sealed partial class ObservableAsPropertyGenerator
131143
// lack of IntelliSense when constructing attributes over the field, but this is the best we can do from this end anyway.
132144
if (!context.SemanticModel.GetSymbolInfo(attribute, token).TryGetAttributeTypeSymbol(out var attributeTypeSymbol))
133145
{
146+
builder.Add(
147+
InvalidPropertyTargetedAttributeOnObservableAsPropertyField,
148+
attribute,
149+
fieldSymbol,
150+
attribute.Name);
134151
continue;
135152
}
136153

@@ -139,6 +156,11 @@ public sealed partial class ObservableAsPropertyGenerator
139156
// Try to extract the forwarded attribute
140157
if (!AttributeInfo.TryCreate(attributeTypeSymbol, context.SemanticModel, attributeArguments, token, out var attributeInfo))
141158
{
159+
builder.Add(
160+
InvalidPropertyTargetedAttributeExpressionOnObservableAsPropertyField,
161+
attribute,
162+
fieldSymbol,
163+
attribute.Name);
142164
continue;
143165
}
144166

@@ -162,6 +184,7 @@ public sealed partial class ObservableAsPropertyGenerator
162184
var targetInfo = TargetInfo.From(fieldSymbol.ContainingType);
163185

164186
return new(
187+
new(
165188
targetInfo.FileHintName,
166189
targetInfo.TargetName,
167190
targetInfo.TargetNamespace,
@@ -175,7 +198,8 @@ public sealed partial class ObservableAsPropertyGenerator
175198
isReferenceTypeOrUnconstraindTypeParameter,
176199
includeMemberNotNullOnSetAccessor,
177200
forwardedPropertyAttributes,
178-
isReadonly == false ? string.Empty : "readonly");
201+
isReadonly == false ? string.Empty : "readonly"),
202+
builder.ToImmutable());
179203
}
180204

181205
private static string GenerateSource(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, PropertyInfo[] properties)

src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,16 @@ private static void RunObservableAsPropertyFromField(in IncrementalGeneratorInit
3232
// Generate the requested properties and methods
3333
context.RegisterSourceOutput(propertyInfo, static (context, input) =>
3434
{
35-
var groupedPropertyInfo = input.GroupBy(
35+
foreach (var diagnostic in input.SelectMany(static x => x.Errors))
36+
{
37+
// Output the diagnostics
38+
context.ReportDiagnostic(diagnostic.ToDiagnostic());
39+
}
40+
41+
// Gather all the properties that are valid and group them by the target information.
42+
var groupedPropertyInfo = input
43+
.Where(static x => x.Value != null)
44+
.Select(static x => x.Value!).GroupBy(
3645
static info => (info.FileHintName, info.TargetName, info.TargetNamespace, info.TargetVisibility, info.TargetType),
3746
static info => info)
3847
.ToImmutableArray();

0 commit comments

Comments
 (0)