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

feat: Add Diagnostics to Source Generators #112

Merged
merged 5 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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
Expand Down
5 changes: 4 additions & 1 deletion src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<CodeAnalysisVersion>4.6.0</CodeAnalysisVersion>
<CodeAnalysisVersion>4.8.0</CodeAnalysisVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
Expand All @@ -13,8 +13,11 @@
<PackageVersion Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageVersion Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
<PackageVersion Include="Microsoft.CodeAnalysis" Version="$(CodeAnalysisVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="$(CodeAnalysisVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="$(CodeAnalysisVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(CodeAnalysisVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(CodeAnalysisVersion)" />
<PackageVersion Include="PolySharp" Version="1.14.1" />

<PackageVersion Include="Avalonia" Version="11.1.4" />
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.build.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<PackageReleaseNotes>https://github.com/reactiveui/ReactiveUI.SourceGenerators/releases</PackageReleaseNotes>
<RepositoryUrl>https://github.com/reactiveui/reactiveui.sourcegenerators</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<NoWarn>$(NoWarn);IDE0060;IDE1006;IDE0130;VSSpell001;RS2007</NoWarn>
<NoWarn>$(NoWarn);IDE0060;IDE1006;IDE0130;VSSpell001;RS2007;NU5128</NoWarn>
<!-- Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<!-- Embed source files that are not tracked by the source control manager in the PDB -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
<PackageReference Include="Verify.Xunit" />
<PackageReference Include="Verify.SourceGenerators" />
<PackageReference Include="Basic.Reference.Assemblies.Net80" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" PrivateAssets="all" VersionOverride="4.11.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" VersionOverride="4.11.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="all" VersionOverride="4.11.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" PrivateAssets="all" VersionOverride="4.11.0" />
<PackageReference Include="Microsoft.CodeAnalysis" PrivateAssets="all" VersionOverride="4.11.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" PrivateAssets="all" VersionOverride="4.11.0" />
<PackageReference Include="FluentAssertions" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<IsRoslynComponent>true</IsRoslynComponent>
<LangVersion>latest</LangVersion>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IncludeBuildOutput>false</IncludeBuildOutput>
<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>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<DevelopmentDependency>true</DevelopmentDependency>
<NoPackageAnalysis>true</NoPackageAnalysis>

<NoWarn>$(NoWarn);RS1038;RS2007;NU5128</NoWarn>
<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>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" PrivateAssets="all" />
</ItemGroup>

<!-- This ensures the library will be packaged as a source generator when we use `dotnet pack` -->
Expand Down
2 changes: 1 addition & 1 deletion src/ReactiveUI.SourceGenerators.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand All @@ -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.",
Expand All @@ -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).",
Expand All @@ -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.",
Expand All @@ -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.",
Expand All @@ -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.",
Expand Down Expand Up @@ -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.",
Expand All @@ -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.",
Expand All @@ -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.",
Expand Down Expand Up @@ -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.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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

Expand All @@ -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
Expand All @@ -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<AttributeInfo>.Rent();
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

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

return new(
new(
targetInfo.FileHintName,
targetInfo.TargetName,
targetInfo.TargetNamespace,
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading
Loading