Skip to content

Commit 9cc7d41

Browse files
committed
Add FilterWithLanguageVersion extension to gate on language version
1 parent 13a9149 commit 9cc7d41

File tree

8 files changed

+84
-60
lines changed

8 files changed

+84
-60
lines changed

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,22 @@ public sealed partial class ObservablePropertyGenerator : IIncrementalGenerator
2727
/// <inheritdoc/>
2828
public void Initialize(IncrementalGeneratorInitializationContext context)
2929
{
30-
// Validate the language version. Note that we're emitting this diagnostic in each generator (excluding the one
31-
// only emitting the nullability annotation attributes if missing) so that the diagnostic is emitted only when
32-
// users are using one of these generators, and not by default as soon as they add a reference to the MVVM Toolkit.
33-
// This ensures that users not using any of the source generators won't be broken when upgrading to this new version.
34-
IncrementalValueProvider<bool> isGeneratorSupported =
35-
context.ParseOptionsProvider
36-
.Select(static (item, _) => item is CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp8 });
37-
38-
// Emit the diagnostic, if needed
39-
context.ReportDiagnosticsIsNotSupported(isGeneratorSupported, Diagnostic.Create(UnsupportedCSharpLanguageVersionError, null));
40-
4130
// Get all field declarations with at least one attribute
4231
IncrementalValuesProvider<IFieldSymbol> fieldSymbols =
4332
context.SyntaxProvider
4433
.CreateSyntaxProvider(
4534
static (node, _) => node is FieldDeclarationSyntax { Parent: ClassDeclarationSyntax or RecordDeclarationSyntax, AttributeLists.Count: > 0 },
4635
static (context, _) => ((FieldDeclarationSyntax)context.Node).Declaration.Variables.Select(v => (IFieldSymbol)context.SemanticModel.GetDeclaredSymbol(v)!))
47-
.Combine(isGeneratorSupported)
48-
.Where(static item => item.Right)
49-
.SelectMany(static (item, _) => item.Left);
36+
.SelectMany(static (item, _) => item);
5037

5138
// Filter the fields using [ObservableProperty]
5239
IncrementalValuesProvider<IFieldSymbol> fieldSymbolsWithAttribute =
5340
fieldSymbols
5441
.Where(static item => item.GetAttributes().Any(a => a.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") == true));
5542

43+
// Filter by language version
44+
context.FilterWithLanguageVersion(ref fieldSymbolsWithAttribute, LanguageVersion.CSharp8, UnsupportedCSharpLanguageVersionError);
45+
5646
// Gather info for all annotated fields
5747
IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result<PropertyInfo> Info)> propertyInfoWithErrors =
5848
fieldSymbolsWithAttribute

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,12 @@ public sealed partial class ObservableValidatorValidateAllPropertiesGenerator :
2121
/// <inheritdoc/>
2222
public void Initialize(IncrementalGeneratorInitializationContext context)
2323
{
24-
// Validate the language version (this needs at least C# 8.0 due to static local functions being used).
25-
// If a lower C# version is set, just skip the execution silently. The fallback path will be used just fine.
26-
IncrementalValueProvider<bool> isGeneratorSupported =
27-
context.ParseOptionsProvider
28-
.Select(static (item, _) => item is CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp8 });
29-
3024
// Get all class declarations
3125
IncrementalValuesProvider<INamedTypeSymbol> typeSymbols =
3226
context.SyntaxProvider
3327
.CreateSyntaxProvider(
3428
static (node, _) => node is ClassDeclarationSyntax,
35-
static (context, _) => (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!)
36-
.Combine(isGeneratorSupported)
37-
.Where(static item => item.Right)
38-
.Select(static (item, _) => item.Left);
29+
static (context, _) => (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!);
3930

4031
// Get the types that inherit from ObservableValidator and gather their info
4132
IncrementalValuesProvider<ValidationInfo> validationInfo =

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44

55
using System.Collections.Generic;
66
using System.Collections.Immutable;
7-
using System.IO;
87
using System.Linq;
9-
using System.Reflection;
108
using System.Text;
119
using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics;
1210
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
@@ -15,6 +13,7 @@
1513
using Microsoft.CodeAnalysis.CSharp;
1614
using Microsoft.CodeAnalysis.CSharp.Syntax;
1715
using Microsoft.CodeAnalysis.Text;
16+
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
1817

1918
namespace CommunityToolkit.Mvvm.SourceGenerators;
2019

@@ -73,20 +72,12 @@ private protected TransitiveMembersGenerator(string attributeType, IEqualityComp
7372
/// <inheritdoc/>
7473
public void Initialize(IncrementalGeneratorInitializationContext context)
7574
{
76-
// Validate the language version
77-
IncrementalValueProvider<bool> isGeneratorSupported =
78-
context.ParseOptionsProvider
79-
.Select(static (item, _) => item is CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp8 });
80-
8175
// Get all class declarations
8276
IncrementalValuesProvider<INamedTypeSymbol> typeSymbols =
8377
context.SyntaxProvider
8478
.CreateSyntaxProvider(
8579
static (node, _) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0 },
86-
static (context, _) => (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!)
87-
.Combine(isGeneratorSupported)
88-
.Where(static item => item.Right)
89-
.Select(static (item, _) => item.Left);
80+
static (context, _) => (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!);
9081

9182
// Filter the types with the target attribute
9283
IncrementalValuesProvider<(INamedTypeSymbol Symbol, TInfo Info)> typeSymbolsWithInfo =
@@ -97,6 +88,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
9788
.Where(static item => item.Attribute is not null)!
9889
.Select((item, _) => (item.Symbol, GetInfo(item.Symbol, item.Attribute!)));
9990

91+
// Filter by language version
92+
context.FilterWithLanguageVersion(ref typeSymbolsWithInfo, LanguageVersion.CSharp8, UnsupportedCSharpLanguageVersionError);
93+
10094
// Gather all generation info, and any diagnostics
10195
IncrementalValuesProvider<Result<(HierarchyInfo Hierarchy, bool IsSealed, TInfo Info)>> generationInfoWithErrors =
10296
typeSymbolsWithInfo.Select((item, _) =>

CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,11 @@ internal static class DiagnosticDescriptors
131131
public static readonly DiagnosticDescriptor UnsupportedCSharpLanguageVersionError = new(
132132
id: "MVVMTK0008",
133133
title: "Unsupported C# language version",
134-
messageFormat: "The source generator features from the MVVM Toolkit require consuming projects to set the C# language version to at least C# 9.0",
134+
messageFormat: "The source generator features from the MVVM Toolkit require consuming projects to set the C# language version to at least C# 8.0",
135135
category: typeof(CSharpParseOptions).FullName,
136136
defaultSeverity: DiagnosticSeverity.Error,
137137
isEnabledByDefault: true,
138-
description: "The source generator features from the MVVM Toolkit require consuming projects to set the C# language version to at least C# 9.0. Make sure to add <LangVersion>9.0</LangVersion> (or above) to your .csproj file.",
138+
description: "The source generator features from the MVVM Toolkit require consuming projects to set the C# language version to at least C# 8.0. Make sure to add <LangVersion>8.0</LangVersion> (or above) to your .csproj file.",
139139
helpLinkUri: "https://aka.ms/mvvmtoolkit");
140140

141141
/// <summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp;
7+
8+
namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
9+
10+
/// <summary>
11+
/// Extension methods for <see cref="IncrementalGeneratorInitializationContext"/>.
12+
/// </summary>
13+
internal static partial class IncrementalGeneratorInitializationContextExtensions
14+
{
15+
/// <summary>
16+
/// Implements a gate for a language version over items in an input <see cref="IncrementalValuesProvider{TValues}"/> source.
17+
/// </summary>
18+
/// <typeparam name="T">The type of items in the input <see cref="IncrementalValuesProvider{TValues}"/> source.</typeparam>
19+
/// <param name="context">The input <see cref="IncrementalGeneratorInitializationContext"/> value being used.</param>
20+
/// <param name="source">The source <see cref="IncrementalValuesProvider{TValues}"/> instance.</param>
21+
/// <param name="languageVersion">The minimum language version to gate for.</param>
22+
/// <param name="diagnosticDescriptor">The <see cref="DiagnosticDescriptor"/> to emit if the gate detects invalid usage.</param>
23+
/// <remarks>
24+
/// Items in <paramref name="source"/> will be filtered out if the gate fails. If it passes, items will remain untouched.
25+
/// </remarks>
26+
public static void FilterWithLanguageVersion<T>(
27+
this IncrementalGeneratorInitializationContext context,
28+
ref IncrementalValuesProvider<T> source,
29+
LanguageVersion languageVersion,
30+
DiagnosticDescriptor diagnosticDescriptor)
31+
{
32+
// Check whether the target language version is supported
33+
IncrementalValueProvider<bool> isGeneratorSupported =
34+
context.ParseOptionsProvider
35+
.Select((item, _) => item is CSharpParseOptions options && options.LanguageVersion >= languageVersion);
36+
37+
// Combine each data item with the supported flag
38+
IncrementalValuesProvider<(T Data, bool IsGeneratorSupported)> dataWithSupportedInfo =
39+
source
40+
.Combine(isGeneratorSupported);
41+
42+
// Get a marker node to show whether an invalid attribute is used
43+
IncrementalValueProvider<bool> isUnsupportedAttributeUsed =
44+
dataWithSupportedInfo
45+
.Select(static (item, _) => item.IsGeneratorSupported)
46+
.Where(static item => !item)
47+
.Collect()
48+
.Select(static (item, _) => item.Length > 0);
49+
50+
// Report them to the output
51+
context.RegisterSourceOutput(isUnsupportedAttributeUsed, (context, diagnostic) =>
52+
{
53+
if (diagnostic)
54+
{
55+
context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, null));
56+
}
57+
});
58+
59+
// Only let data through if the minimum language version is supported
60+
source =
61+
dataWithSupportedInfo
62+
.Where(static item => item.IsGeneratorSupported)
63+
.Select(static (item, _) => item.Data);
64+
}
65+
}

CommunityToolkit.Mvvm.SourceGenerators/Input/ICommandGenerator.cs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,12 @@ public sealed partial class ICommandGenerator : IIncrementalGenerator
2626
/// <inheritdoc/>
2727
public void Initialize(IncrementalGeneratorInitializationContext context)
2828
{
29-
// Validate the language version
30-
IncrementalValueProvider<bool> isGeneratorSupported =
31-
context.ParseOptionsProvider
32-
.Select(static (item, _) => item is CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp8 });
33-
34-
// Emit the diagnostic, if needed
35-
context.ReportDiagnosticsIsNotSupported(isGeneratorSupported, Diagnostic.Create(UnsupportedCSharpLanguageVersionError, null));
36-
3729
// Get all method declarations with at least one attribute
3830
IncrementalValuesProvider<IMethodSymbol> methodSymbols =
3931
context.SyntaxProvider
4032
.CreateSyntaxProvider(
4133
static (node, _) => node is MethodDeclarationSyntax { Parent: ClassDeclarationSyntax, AttributeLists.Count: > 0 },
42-
static (context, _) => (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!)
43-
.Combine(isGeneratorSupported)
44-
.Where(static item => item.Right)
45-
.Select(static (item, _) => item.Left);
34+
static (context, _) => (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!);
4635

4736
// Filter the methods using [ICommand]
4837
IncrementalValuesProvider<(IMethodSymbol Symbol, AttributeData Attribute)> methodSymbolsWithAttributeData =
@@ -52,6 +41,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
5241
Attribute: item.GetAttributes().FirstOrDefault(a => a.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.Input.ICommandAttribute") == true)))
5342
.Where(static item => item.Attribute is not null)!;
5443

44+
// Filter by language version
45+
context.FilterWithLanguageVersion(ref methodSymbolsWithAttributeData, LanguageVersion.CSharp8, UnsupportedCSharpLanguageVersionError);
46+
5547
// Gather info for all annotated command methods
5648
IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result<CommandInfo?> Info)> commandInfoWithErrors =
5749
methodSymbolsWithAttributeData

CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,12 @@ public sealed partial class IMessengerRegisterAllGenerator : IIncrementalGenerat
2222
/// <inheritdoc/>
2323
public void Initialize(IncrementalGeneratorInitializationContext context)
2424
{
25-
// Validate the language version
26-
IncrementalValueProvider<bool> isGeneratorSupported =
27-
context.ParseOptionsProvider
28-
.Select(static (item, _) => item is CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp8 });
29-
3025
// Get all class declarations
3126
IncrementalValuesProvider<INamedTypeSymbol> typeSymbols =
3227
context.SyntaxProvider
3328
.CreateSyntaxProvider(
3429
static (node, _) => node is ClassDeclarationSyntax,
35-
static (context, _) => (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!)
36-
.Combine(isGeneratorSupported)
37-
.Where(static item => item.Right)
38-
.Select(static (item, _) => item.Left);
30+
static (context, _) => (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!);
3931

4032
// Get the target IRecipient<TMessage> interfaces and filter out other types
4133
IncrementalValuesProvider<(INamedTypeSymbol Type, ImmutableArray<INamedTypeSymbol> Interfaces)> typeAndInterfaceSymbols =

tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ public partial class SampleViewModel : ObservableValidator
304304
}
305305
}";
306306

307-
// This is explicitly allowed in C# < 9.0, as it doesn't use any new features
307+
// This is explicitly allowed in C# < 8.0, as it doesn't use any new features
308308
VerifyGeneratedDiagnostics<ObservableValidatorValidateAllPropertiesGenerator>(
309309
CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)));
310310
}
@@ -351,7 +351,7 @@ public void Receive(MyMessage message)
351351
}
352352
}";
353353

354-
// This is explicitly allowed in C# < 9.0, as it doesn't use any new features
354+
// This is explicitly allowed in C# < 8.0, as it doesn't use any new features
355355
VerifyGeneratedDiagnostics<IMessengerRegisterAllGenerator>(
356356
CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3)));
357357
}

0 commit comments

Comments
 (0)