From b25ed7151d1dab274cf9131353db926252b3fd56 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 11 Nov 2025 11:13:16 -0800 Subject: [PATCH 1/9] Add C# 14.0 support for ObservableProperty on partial properties Updated the ObservableProperty generator and diagnostics to allow usage with C# 14.0 or 'preview' language versions, not just 'preview'. Added a new helper method for language version comparison and updated diagnostic descriptors and analyzer logic to reflect the new minimum version requirement. --- .../ObservablePropertyGenerator.Execute.cs | 11 ++++++++--- .../RequiresCSharpLanguageVersionPreviewAnalyzer.cs | 8 ++++---- .../Diagnostics/DiagnosticDescriptors.cs | 10 +++++----- .../Extensions/CompilationExtensions.cs | 11 +++++++++++ 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs index 17a97d5bd..b00e4741b 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs @@ -80,11 +80,16 @@ public static bool IsCandidateValidForCompilation(MemberDeclarationSyntax node, return false; } - // If the target is a property, we only support using C# preview. - // This is because the generator is relying on the 'field' keyword. - if (node is PropertyDeclarationSyntax && !semanticModel.Compilation.IsLanguageVersionPreview()) + // If the target is a property, we must either be using C# preview, or C# 14 or above. When we're using + // an older version of Roslyn, we know properties will never be supported anyway, so we have nothing to + // check. When we add Roslyn 18.0 support, we can also update this check to check for at least C# 14. + if (node is PropertyDeclarationSyntax) { +#if ROSLYN_4_12_0_OR_GREATER + return semanticModel.Compilation.HasLanguageVersionGreaterThan(LanguageVersion.CSharp13) || semanticModel.Compilation.IsLanguageVersionPreview(); +#else return false; +#endif } // All other cases are supported, the syntax filter is already validating that diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/RequiresCSharpLanguageVersionPreviewAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/RequiresCSharpLanguageVersionPreviewAnalyzer.cs index 7cb7cd233..ef12e0ab0 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/RequiresCSharpLanguageVersionPreviewAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/RequiresCSharpLanguageVersionPreviewAnalyzer.cs @@ -15,13 +15,13 @@ namespace CommunityToolkit.Mvvm.SourceGenerators; /// -/// A diagnostic analyzer that generates errors when a property using [ObservableProperty] on a partial property is in a project with the C# language version not set to preview. +/// A diagnostic analyzer that generates errors when a property using [ObservableProperty] on a partial property is in a project with the C# language version not set to 14.0 or 'preview'. /// [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RequiresCSharpLanguageVersionPreviewAnalyzer : DiagnosticAnalyzer { /// - public override ImmutableArray SupportedDiagnostics { get; } = [CSharpLanguageVersionIsNotPreviewForObservableProperty]; + public override ImmutableArray SupportedDiagnostics { get; } = [CSharpLanguageVersionIsNot14OrPreviewForObservableProperty]; /// public override void Initialize(AnalysisContext context) @@ -31,7 +31,7 @@ public override void Initialize(AnalysisContext context) context.RegisterCompilationStartAction(static context => { - // If the language version is set to preview, we'll never emit diagnostics + // If the language version is set to preview or if we are set to at least C# 14.0, we'll never emit diagnostics if (context.Compilation.IsLanguageVersionPreview()) { return; @@ -69,7 +69,7 @@ public override void Initialize(AnalysisContext context) if (context.Symbol.TryGetAttributeWithType(observablePropertySymbol, out AttributeData? observablePropertyAttribute)) { context.ReportDiagnostic(Diagnostic.Create( - CSharpLanguageVersionIsNotPreviewForObservableProperty, + CSharpLanguageVersionIsNot14OrPreviewForObservableProperty, observablePropertyAttribute.GetLocation(), context.Symbol)); } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index b62fcb7ae..7c297892d 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -691,17 +691,17 @@ internal static class DiagnosticDescriptors /// /// Gets a for the C# language version not being sufficient for [ObservableProperty] on partial properties. /// - /// Format: "Using [ObservableProperty] on partial properties requires the C# language version to be set to 'preview', as support for the 'field' keyword is needed by the source generators to emit valid code (add preview to your .csproj/.props file)". + /// Format: "Using [ObservableProperty] on partial properties requires the C# language version to be set to at least 14.0 or 'preview', as support for the 'field' keyword is needed by the source generators to emit valid code (add 14.0 or preview to your .csproj/.props file)". /// /// - public static readonly DiagnosticDescriptor CSharpLanguageVersionIsNotPreviewForObservableProperty = new DiagnosticDescriptor( + public static readonly DiagnosticDescriptor CSharpLanguageVersionIsNot14OrPreviewForObservableProperty = new DiagnosticDescriptor( id: "MVVMTK0041", - title: "C# language version is not 'preview'", - messageFormat: """Using [ObservableProperty] on partial properties requires the C# language version to be set to 'preview', as support for the 'field' keyword is needed by the source generators to emit valid code (add preview to your .csproj/.props file)""", + title: "C# language version is not at least 14.0 or 'preview'", + messageFormat: """Using [ObservableProperty] on partial properties requires the C# language version to be set to at least 14.0 or 'preview', as support for the 'field' keyword is needed by the source generators to emit valid code (add 14.0 or preview to your .csproj/.props file)""", category: typeof(ObservablePropertyGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, - description: "The C# language version must be set to 'preview' when using [ObservableProperty] on partial properties for the source generators to emit valid code (the preview option must be set in the .csproj/.props file).", + description: "The C# language version must be set to at least 14.0 or 'preview' when using [ObservableProperty] on partial properties for the source generators to emit valid code (the 14.0 or preview option must be set in the .csproj/.props file).", helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0041"); /// diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/CompilationExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/CompilationExtensions.cs index 6a584bbfb..98d24e846 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/CompilationExtensions.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/CompilationExtensions.cs @@ -27,6 +27,17 @@ public static bool HasLanguageVersionAtLeastEqualTo(this Compilation compilation return ((CSharpCompilation)compilation).LanguageVersion >= languageVersion; } + /// + /// Checks whether a given compilation (assumed to be for C#) is using a language version greater than a specified one. + /// + /// The to consider for analysis. + /// The minimum language version to check. + /// Whether is using a language version greater than the specified one. + public static bool HasLanguageVersionGreaterThan(this Compilation compilation, LanguageVersion languageVersion) + { + return ((CSharpCompilation)compilation).LanguageVersion > languageVersion; + } + /// /// Checks whether a given compilation (assumed to be for C#) is using the preview language version. /// From 75a4422e1b2a8a4beccec71b604a80f3cd392996 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 11 Nov 2025 11:15:49 -0800 Subject: [PATCH 2/9] Add Roslyn5000 source generator project to solution Created a new CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000 project targeting netstandard2.0 and added it to the solution file. --- dotnet.slnx | 4 +--- ...ommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.csproj | 7 +++++++ 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.csproj diff --git a/dotnet.slnx b/dotnet.slnx index 5d7e0583a..d5c2cbad9 100644 --- a/dotnet.slnx +++ b/dotnet.slnx @@ -1,7 +1,4 @@ - - - @@ -58,6 +55,7 @@ + diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.csproj b/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.csproj new file mode 100644 index 000000000..dbdcea46b --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + From cff27614f75d5a3a1105c2a3c3bc39bf48e2fc7c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 11 Nov 2025 11:23:03 -0800 Subject: [PATCH 3/9] Update Roslyn 5.0 support in source generators Refactored the Roslyn 5000 project to import shared props and projitems files. Added support for the ROSLYN_5_0_0_OR_GREATER constant, updated NoWarn conditions for RS2003, and introduced a temporary workaround for Roslyn 5.0.0 versioning in the props file. --- ...ommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.csproj | 7 +++---- .../CommunityToolkit.Mvvm.SourceGenerators.props | 6 +++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.csproj b/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.csproj index dbdcea46b..de3752c63 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.csproj +++ b/src/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.csproj @@ -1,7 +1,6 @@  - - netstandard2.0 - + + - + \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.props b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.props index 1517ae8cf..70ad36cf8 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.props +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.props @@ -41,9 +41,13 @@ $(DefineConstants);ROSLYN_4_3_1_OR_GREATER $(DefineConstants);ROSLYN_4_12_0_OR_GREATER + $(DefineConstants);ROSLYN_5_0_0_OR_GREATER - $(NoWarn);RS2003 + $(NoWarn);RS2003 + + + 5.0.0-2.final From a602bf8834042ad1752b439bcaed7bf2405775c0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 11 Nov 2025 11:23:42 -0800 Subject: [PATCH 4/9] Update summary for DiagnosticsExtensions class Revised the XML summary comment to clarify that the extension methods are for working with diagnostics from incremental generator pipelines, rather than specifically for GeneratorExecutionContext. --- .../Extensions/DiagnosticsExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/DiagnosticsExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/DiagnosticsExtensions.cs index d6edd89f3..1f63dafb1 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/DiagnosticsExtensions.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/DiagnosticsExtensions.cs @@ -12,7 +12,7 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; /// -/// Extension methods for , specifically for reporting diagnostics. +/// Extension methods for working with diagnostics from incremental generator pipelines. /// internal static class DiagnosticsExtensions { From 9d6c470e8b8844beafc02d9a19bdeb2db25aec1f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 11 Nov 2025 11:24:51 -0800 Subject: [PATCH 5/9] Add Roslyn 5.0 source generator and code fixer support Added project references and packaging entries for Roslyn 5.0 source generators and code fixers to support analyzers for the latest Roslyn version. --- src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj index e3130dba5..ab1b769b0 100644 --- a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj +++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj @@ -69,6 +69,7 @@ + @@ -123,9 +124,11 @@ + + \ No newline at end of file From 2c2948293fcf2b64f94b9dd8dffee812bd419e68 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 11 Nov 2025 11:28:25 -0800 Subject: [PATCH 6/9] Update analyzer for C# 14.0 language version support Renamed RequiresCSharpLanguageVersionPreviewAnalyzer to RequiresCSharpLanguageVersion14OrPreviewAnalyzer and updated logic to check for C# 14.0 or preview language versions. Adjusted generator and unit tests to reference the new analyzer and updated version checks for compatibility with Roslyn 5.0.0 or greater. --- .../CommunityToolkit.Mvvm.SourceGenerators.projitems | 2 +- .../ObservablePropertyGenerator.Execute.cs | 6 ++++-- ...equiresCSharpLanguageVersion14OrPreviewAnalyzer.cs} | 6 +++++- .../Test_SourceGeneratorsDiagnostics.cs | 10 +++++----- 4 files changed, 15 insertions(+), 9 deletions(-) rename src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/{RequiresCSharpLanguageVersionPreviewAnalyzer.cs => RequiresCSharpLanguageVersion14OrPreviewAnalyzer.cs} (94%) diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems index 9e719e412..c3294780f 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems @@ -57,7 +57,7 @@ - + diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs index b00e4741b..2b2756ec4 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs @@ -85,8 +85,10 @@ public static bool IsCandidateValidForCompilation(MemberDeclarationSyntax node, // check. When we add Roslyn 18.0 support, we can also update this check to check for at least C# 14. if (node is PropertyDeclarationSyntax) { -#if ROSLYN_4_12_0_OR_GREATER - return semanticModel.Compilation.HasLanguageVersionGreaterThan(LanguageVersion.CSharp13) || semanticModel.Compilation.IsLanguageVersionPreview(); +#if ROSLYN_5_0_0_OR_GREATER + return semanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp14); +#elif ROSLYN_4_12_0_OR_GREATER + return semanticModel.Compilation.IsLanguageVersionPreview(); #else return false; #endif diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/RequiresCSharpLanguageVersionPreviewAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/RequiresCSharpLanguageVersion14OrPreviewAnalyzer.cs similarity index 94% rename from src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/RequiresCSharpLanguageVersionPreviewAnalyzer.cs rename to src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/RequiresCSharpLanguageVersion14OrPreviewAnalyzer.cs index ef12e0ab0..edfb39f23 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/RequiresCSharpLanguageVersionPreviewAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/RequiresCSharpLanguageVersion14OrPreviewAnalyzer.cs @@ -18,7 +18,7 @@ namespace CommunityToolkit.Mvvm.SourceGenerators; /// A diagnostic analyzer that generates errors when a property using [ObservableProperty] on a partial property is in a project with the C# language version not set to 14.0 or 'preview'. /// [DiagnosticAnalyzer(LanguageNames.CSharp)] -public sealed class RequiresCSharpLanguageVersionPreviewAnalyzer : DiagnosticAnalyzer +public sealed class RequiresCSharpLanguageVersion14OrPreviewAnalyzer : DiagnosticAnalyzer { /// public override ImmutableArray SupportedDiagnostics { get; } = [CSharpLanguageVersionIsNot14OrPreviewForObservableProperty]; @@ -32,7 +32,11 @@ public override void Initialize(AnalysisContext context) context.RegisterCompilationStartAction(static context => { // If the language version is set to preview or if we are set to at least C# 14.0, we'll never emit diagnostics +#if ROSLYN_5_0_0_OR_GREATER + if (context.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp14)) +#else if (context.Compilation.IsLanguageVersionPreview()) +#endif { return; } diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs index e6845bbe5..0fecd4cc9 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs @@ -28,7 +28,7 @@ public partial class SampleViewModel : ObservableObject } """; - await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp12); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp12); } [TestMethod] @@ -47,7 +47,7 @@ public partial class SampleViewModel : ObservableObject } """; - await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( source, LanguageVersion.CSharp12, @@ -73,7 +73,7 @@ public partial class SampleViewModel : ObservableObject } """; - await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( source, LanguageVersion.CSharp13, @@ -97,7 +97,7 @@ public partial class SampleViewModel : ObservableObject } """; - await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, languageVersion: LanguageVersion.Preview); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, languageVersion: LanguageVersion.Preview); } [TestMethod] @@ -116,7 +116,7 @@ public partial class SampleViewModel : ObservableObject } """; - await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( source, LanguageVersion.Preview, From 6423696979f67a2240cc4bb914faa3b8e8815df4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 12 Nov 2025 10:26:33 -0800 Subject: [PATCH 7/9] Add Roslyn 5.0.0 unit test project Introduces CommunityToolkit.Mvvm.Roslyn5000.UnitTests.csproj targeting net472, net8.0, and net9.0. Updates the solution file to include the new test project for Roslyn 5.0.0 compatibility testing. --- dotnet.slnx | 1 + ...tyToolkit.Mvvm.Roslyn5000.UnitTests.csproj | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/CommunityToolkit.Mvvm.Roslyn5000.UnitTests/CommunityToolkit.Mvvm.Roslyn5000.UnitTests.csproj diff --git a/dotnet.slnx b/dotnet.slnx index d5c2cbad9..0db87f15e 100644 --- a/dotnet.slnx +++ b/dotnet.slnx @@ -37,6 +37,7 @@ + diff --git a/tests/CommunityToolkit.Mvvm.Roslyn5000.UnitTests/CommunityToolkit.Mvvm.Roslyn5000.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.Roslyn5000.UnitTests/CommunityToolkit.Mvvm.Roslyn5000.UnitTests.csproj new file mode 100644 index 000000000..2d17602e9 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.Roslyn5000.UnitTests/CommunityToolkit.Mvvm.Roslyn5000.UnitTests.csproj @@ -0,0 +1,35 @@ + + + + net472;net8.0;net9.0 + preview + true + $(DefineConstants);ROSLYN_4_12_0_OR_GREATER;ROSLYN_5_0_0_OR_GREATER + + + $(NoWarn);MVVMTK0042 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From be027fded8dff6fb8975b6997259bdd162fe1bc2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 12 Nov 2025 10:30:31 -0800 Subject: [PATCH 8/9] Add Roslyn5000 unit test project and fix project reference Added CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.UnitTests project to the solution and corrected the project reference in CommunityToolkit.Mvvm.csproj to point to the correct Roslyn5000 source generator. This enables testing for Roslyn 5.0.0 support. --- dotnet.slnx | 1 + .../CommunityToolkit.Mvvm.csproj | 2 +- ...urceGenerators.Roslyn5000.UnitTests.csproj | 35 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.UnitTests.csproj diff --git a/dotnet.slnx b/dotnet.slnx index 0db87f15e..b1c170d4b 100644 --- a/dotnet.slnx +++ b/dotnet.slnx @@ -41,6 +41,7 @@ + diff --git a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj index ab1b769b0..b1a46c87f 100644 --- a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj +++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj @@ -69,7 +69,7 @@ - + diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.UnitTests.csproj new file mode 100644 index 000000000..7c3dd3749 --- /dev/null +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn5000.UnitTests.csproj @@ -0,0 +1,35 @@ + + + + net472;net8.0;net9.0 + $(DefineConstants);ROSLYN_4_3_1_OR_GREATER;ROSLYN_4_12_0_OR_GREATER;ROSLYN_5_0_0_OR_GREATER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From e04247a1f00b9d10b9aa05842a48a11a42c6f8a7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 12 Nov 2025 10:36:31 -0800 Subject: [PATCH 9/9] Add C# 14 partial property tests and fix formatting Introduces new unit tests for ObservableProperty with value types on partial properties using C# 14 and Roslyn 5.0.0 or greater. Also updates formatting in diagnostics tests for consistency and readability. --- .../Test_SourceGeneratorsCodegen.cs | 76 +++++++++++++++++ .../Test_SourceGeneratorsDiagnostics.cs | 84 ++++++++++++------- 2 files changed, 131 insertions(+), 29 deletions(-) diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsCodegen.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsCodegen.cs index 978017735..314f49e8c 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsCodegen.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsCodegen.cs @@ -91,6 +91,82 @@ partial int Number VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, LanguageVersion.Preview, ("MyApp.MyViewModel.g.cs", result)); } +#if ROSLYN_5_0_0_OR_GREATER + [TestMethod] + public void ObservablePropertyWithValueType_OnPartialProperty_WithNoModifiers_CSharp14_WorksCorrectly() + { + string source = """ + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp; + + partial class MyViewModel : ObservableObject + { + [ObservableProperty] + partial int Number { get; set; } + } + """; + + string result = """ + // + #pragma warning disable + #nullable enable + namespace MyApp + { + /// + partial class MyViewModel + { + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + partial int Number + { + get => field; + set + { + if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value)) + { + OnNumberChanging(value); + OnNumberChanging(default, value); + OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Number); + field = value; + OnNumberChanged(value); + OnNumberChanged(default, value); + OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Number); + } + } + } + + /// Executes the logic for when is changing. + /// The new property value being set. + /// This method is invoked right before the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] + partial void OnNumberChanging(int value); + /// Executes the logic for when is changing. + /// The previous property value that is being replaced. + /// The new property value being set. + /// This method is invoked right before the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] + partial void OnNumberChanging(int oldValue, int newValue); + /// Executes the logic for when just changed. + /// The new property value that was set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] + partial void OnNumberChanged(int value); + /// Executes the logic for when just changed. + /// The previous property value that was replaced. + /// The new property value that was set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] + partial void OnNumberChanged(int oldValue, int newValue); + } + } + """; + + VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, LanguageVersion.CSharp14, ("MyApp.MyViewModel.g.cs", result)); + } +#endif + // See https://github.com/CommunityToolkit/dotnet/issues/969 [TestMethod] public void ObservablePropertyWithValueType_OnPartialProperty_RequiredProperty_WorksCorrectly() diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs index 0fecd4cc9..28af166f7 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs @@ -22,7 +22,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] public string Name { get; set; } } } @@ -41,7 +41,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0041:ObservableProperty|}] + [{|MVVMTK0041:ObservableProperty|}] public partial string Name { get; set; } } } @@ -67,7 +67,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0041:ObservableProperty|}] + [{|MVVMTK0041:ObservableProperty|}] public partial string Name { get; set; } } } @@ -81,6 +81,32 @@ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + source, + LanguageVersion.Preview, + + // /0/Test0.cs(8,31): error CS9248: Partial property 'SampleViewModel.Name' must have an implementation part. + DiagnosticResult.CompilerError("CS9248").WithSpan(8, 31, 8, 35).WithArguments("MyApp.SampleViewModel.Name")); + } +#endif + [TestMethod] public async Task RequireCSharpLanguageVersionPreviewAnalyzer_LanguageVersionIsPreview_DoesNotWarn() { @@ -91,7 +117,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] public string Name { get; set; } } } @@ -110,7 +136,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] public partial string Name { get; set; } } } @@ -134,7 +160,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private string name; } } @@ -153,7 +179,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private string {|MVVMTK0042:name|}; } } @@ -172,7 +198,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] public string Name { get; set; } } } @@ -191,7 +217,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] public partial string {|CS9248:Name|} { get; set; } } } @@ -210,7 +236,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] internal partial string {|CS9248:Name|} { get; private set; } } } @@ -229,7 +255,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] protected internal partial string {|CS9248:Name|} { get; private protected set; } } } @@ -248,7 +274,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0043:ObservableProperty|}] + [{|MVVMTK0043:ObservableProperty|}] public string Name { get; set; } } } @@ -267,7 +293,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0043:ObservableProperty|}] + [{|MVVMTK0043:ObservableProperty|}] public static partial string {|CS9248:Name|} { get; set; } } } @@ -286,7 +312,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0043:ObservableProperty|}] + [{|MVVMTK0043:ObservableProperty|}] public partial string {|CS9248:Name|} { get; } } } @@ -305,7 +331,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0043:ObservableProperty|}] + [{|MVVMTK0043:ObservableProperty|}] public partial string {|CS9248:Name|} { set; } } } @@ -325,7 +351,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [{|MVVMTK0043:ObservableProperty|}] + [{|MVVMTK0043:ObservableProperty|}] public partial string {|CS9248:Name|} { get; init; } } } @@ -358,7 +384,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private static string name; } } @@ -377,7 +403,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private static string name; } } @@ -399,7 +425,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private string name; } } @@ -421,7 +447,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private string name; } } @@ -443,7 +469,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private string name; } } @@ -465,7 +491,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private string {|MVVMTK0045:name|}; } } @@ -565,7 +591,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private string name; } } @@ -587,7 +613,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private string {|MVVMTK0045:name|}; } } @@ -609,7 +635,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private string {|MVVMTK0045:name|}; } } @@ -631,7 +657,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private string {|MVVMTK0045:name|}; } } @@ -658,7 +684,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] private string {|MVVMTK0045:name|}; } } @@ -1016,7 +1042,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] public partial string {|CS9248:Name|} { get; set; } } } @@ -1060,7 +1086,7 @@ namespace MyApp { public partial class SampleViewModel : ObservableObject { - [ObservableProperty] + [ObservableProperty] public partial string Name { get; set; } [GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "1.0.0")]