diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 1478f9285..9543006cc 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -31,7 +31,6 @@ body:
required: true
attributes:
label: Steps to reproduce
- render: text
description: Provide steps to reproduce the issue, or let us know why it can't be reproduced (e.g. more complex setup, environment, dependencies, etc...). If you want to insert a code snippet, make sure to properly format it (add 3 backticks ` at the start and end of your code, followed by the language in use, eg. "csharp") and to remove leading whitespace in each line (if you're pasting code from another IDE where it was indented).
placeholder: |
Example repro steps:
diff --git a/Directory.Build.props b/Directory.Build.props
index 2c852ca11..6510902b7 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -25,7 +25,7 @@
-
+
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 81b40fd16..50ebd69aa 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -34,7 +34,7 @@ jobs:
# Test solution #
- # Run .NET 8 unit tests
+ # Run .NET 8 unit tests
- script: dotnet test --no-build -c $(Build.Configuration) -f net8.0 -l "trx;LogFileName=VSTestResults_net8.0.trx"
displayName: Run .NET 8 unit tests
@@ -42,10 +42,6 @@ jobs:
- script: dotnet test --no-build -c $(Build.Configuration) -f net7.0 -l "trx;LogFileName=VSTestResults_net7.0.trx"
displayName: Run .NET 7 unit tests
- # Run .NET 6 unit tests
- - script: dotnet test --no-build -c $(Build.Configuration) -f net6.0 -l "trx;LogFileName=VSTestResults_net6.0.trx"
- displayName: Run .NET 6 unit tests
-
# Run .NET Framework 4.7.2 unit tests
- script: dotnet test --no-build -c $(Build.Configuration) -f net472 -l "trx;LogFileName=VSTestResults_net472.trx"
displayName: Run .NET Framework 4.7.2 unit tests
diff --git a/dotnet Community Toolkit.sln b/dotnet Community Toolkit.sln
index 688e32367..dc557355a 100644
--- a/dotnet Community Toolkit.sln
+++ b/dotnet Community Toolkit.sln
@@ -36,7 +36,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{88C6FFBE-3
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Diagnostics", "src\CommunityToolkit.Diagnostics\CommunityToolkit.Diagnostics.csproj", "{76F89522-CA28-458D-801D-947AB033A758}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn401", "src\CommunityToolkit.Mvvm.SourceGenerators.Roslyn401\CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.csproj", "{E24D1146-5AD8-498F-A518-4890D8BF4937}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001", "src\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.csproj", "{E24D1146-5AD8-498F-A518-4890D8BF4937}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Diagnostics.UnitTests", "tests\CommunityToolkit.Diagnostics.UnitTests\CommunityToolkit.Diagnostics.UnitTests.csproj", "{35E48D4D-6433-4B70-98A9-BA544921EE04}"
EndProject
@@ -61,27 +61,35 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Inter
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CommunityToolkit.Mvvm.SourceGenerators", "src\CommunityToolkit.Mvvm.SourceGenerators\CommunityToolkit.Mvvm.SourceGenerators.shproj", "{5E7F1212-A54B-40CA-98C5-1FF5CD1A1638}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn431", "src\CommunityToolkit.Mvvm.SourceGenerators.Roslyn431\CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.csproj", "{DF455C40-B18E-4890-8758-7CCCB5CA7052}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn4031", "src\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4031\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4031.csproj", "{DF455C40-B18E-4890-8758-7CCCB5CA7052}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CommunityToolkit.Mvvm.UnitTests", "tests\CommunityToolkit.Mvvm.UnitTests\CommunityToolkit.Mvvm.UnitTests.shproj", "{B8DCD82E-B53B-4249-AD4E-F9B99ACB9334}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Roslyn401.UnitTests", "tests\CommunityToolkit.Mvvm.Roslyn401.UnitTests\CommunityToolkit.Mvvm.Roslyn401.UnitTests.csproj", "{AD9C3223-8E37-4FD4-A0D4-A45119551D3A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Roslyn4001.UnitTests", "tests\CommunityToolkit.Mvvm.Roslyn4001.UnitTests\CommunityToolkit.Mvvm.Roslyn4001.UnitTests.csproj", "{AD9C3223-8E37-4FD4-A0D4-A45119551D3A}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Roslyn431.UnitTests", "tests\CommunityToolkit.Mvvm.Roslyn431.UnitTests\CommunityToolkit.Mvvm.Roslyn431.UnitTests.csproj", "{5B44F7F1-DCA2-4776-924E-A266F7BBF753}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Roslyn4031.UnitTests", "tests\CommunityToolkit.Mvvm.Roslyn4031.UnitTests\CommunityToolkit.Mvvm.Roslyn4031.UnitTests.csproj", "{5B44F7F1-DCA2-4776-924E-A266F7BBF753}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CommunityToolkit.Mvvm.SourceGenerators.UnitTests", "tests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests.shproj", "{FB59CE88-7732-4A63-B5BD-AC5681B7DA1A}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests", "tests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj", "{F3799252-7A66-4533-89D8-B3C312052D95}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests", "tests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests.csproj", "{F3799252-7A66-4533-89D8-B3C312052D95}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests", "tests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests.csproj", "{FE3EA695-EA0F-4E5F-9257-E059AAA23B10}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn4031.UnitTests", "tests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4031.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4031.UnitTests.csproj", "{FE3EA695-EA0F-4E5F-9257-E059AAA23B10}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CommunityToolkit.Mvvm.ExternalAssembly", "tests\CommunityToolkit.Mvvm.ExternalAssembly\CommunityToolkit.Mvvm.ExternalAssembly.shproj", "{E827A9CD-405F-43E4-84C7-68CC7E845CDC}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401", "tests\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401.csproj", "{ECFE93AA-4B98-4292-B3FA-9430D513B4F9}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4001", "tests\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4001\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4001.csproj", "{ECFE93AA-4B98-4292-B3FA-9430D513B4F9}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431", "tests\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431.csproj", "{4FCD501C-1BB5-465C-AD19-356DAB6600C6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4031", "tests\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4031\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4031.csproj", "{4FCD501C-1BB5-465C-AD19-356DAB6600C6}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.CodeFixers", "src\CommunityToolkit.Mvvm.CodeFixers\CommunityToolkit.Mvvm.CodeFixers.csproj", "{E79DCA2A-4C59-499F-85BD-F45215ED6B72}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.CodeFixers.Roslyn4001", "src\CommunityToolkit.Mvvm.CodeFixers.Roslyn4001\CommunityToolkit.Mvvm.CodeFixers.Roslyn4001.csproj", "{E79DCA2A-4C59-499F-85BD-F45215ED6B72}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120", "src\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.csproj", "{FCC13AD5-CEB8-4CC1-8250-89B616D126F2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests", "tests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests.csproj", "{C342302D-A263-42D6-B8EE-01DEF8192690}"
+EndProject
+Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CommunityToolkit.Mvvm.CodeFixers", "src\CommunityToolkit.Mvvm.CodeFixers\CommunityToolkit.Mvvm.CodeFixers.shproj", "{A2EBDA90-B720-430D-83F5-C6BCC355232C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Mvvm.CodeFixers.Roslyn4120", "src\CommunityToolkit.Mvvm.CodeFixers.Roslyn4120\CommunityToolkit.Mvvm.CodeFixers.Roslyn4120.csproj", "{98572004-D29A-486E-8053-6D409557CE44}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -457,6 +465,66 @@ Global
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|x64.Build.0 = Release|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|x86.ActiveCfg = Release|Any CPU
{E79DCA2A-4C59-499F-85BD-F45215ED6B72}.Release|x86.Build.0 = Release|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Debug|ARM.Build.0 = Debug|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Debug|x64.Build.0 = Debug|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Debug|x86.Build.0 = Debug|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Release|ARM.ActiveCfg = Release|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Release|ARM.Build.0 = Release|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Release|ARM64.Build.0 = Release|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Release|x64.ActiveCfg = Release|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Release|x64.Build.0 = Release|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Release|x86.ActiveCfg = Release|Any CPU
+ {FCC13AD5-CEB8-4CC1-8250-89B616D126F2}.Release|x86.Build.0 = Release|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Debug|ARM.Build.0 = Debug|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Debug|x64.Build.0 = Debug|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Debug|x86.Build.0 = Debug|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Release|ARM.ActiveCfg = Release|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Release|ARM.Build.0 = Release|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Release|ARM64.Build.0 = Release|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Release|x64.ActiveCfg = Release|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Release|x64.Build.0 = Release|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Release|x86.ActiveCfg = Release|Any CPU
+ {C342302D-A263-42D6-B8EE-01DEF8192690}.Release|x86.Build.0 = Release|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Debug|ARM.Build.0 = Debug|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Debug|x64.Build.0 = Debug|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Debug|x86.Build.0 = Debug|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Release|Any CPU.Build.0 = Release|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Release|ARM.ActiveCfg = Release|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Release|ARM.Build.0 = Release|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Release|ARM64.Build.0 = Release|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Release|x64.ActiveCfg = Release|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Release|x64.Build.0 = Release|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Release|x86.ActiveCfg = Release|Any CPU
+ {98572004-D29A-486E-8053-6D409557CE44}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -479,6 +547,7 @@ Global
{E827A9CD-405F-43E4-84C7-68CC7E845CDC} = {B30036C4-D514-4E5B-A323-587A061772CE}
{ECFE93AA-4B98-4292-B3FA-9430D513B4F9} = {B30036C4-D514-4E5B-A323-587A061772CE}
{4FCD501C-1BB5-465C-AD19-356DAB6600C6} = {B30036C4-D514-4E5B-A323-587A061772CE}
+ {C342302D-A263-42D6-B8EE-01DEF8192690} = {B30036C4-D514-4E5B-A323-587A061772CE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5403B0C4-F244-4F73-A35C-FE664D0F4345}
@@ -487,14 +556,19 @@ Global
tests\CommunityToolkit.Mvvm.ExternalAssembly\CommunityToolkit.Mvvm.ExternalAssembly.projitems*{4fcd501c-1bb5-465c-ad19-356dab6600c6}*SharedItemsImports = 5
tests\CommunityToolkit.Mvvm.UnitTests\CommunityToolkit.Mvvm.UnitTests.projitems*{5b44f7f1-dca2-4776-924e-a266f7bbf753}*SharedItemsImports = 5
src\CommunityToolkit.Mvvm.SourceGenerators\CommunityToolkit.Mvvm.SourceGenerators.projitems*{5e7f1212-a54b-40ca-98c5-1ff5cd1a1638}*SharedItemsImports = 13
+ src\CommunityToolkit.Mvvm.CodeFixers\CommunityToolkit.Mvvm.CodeFixers.projitems*{98572004-d29a-486e-8053-6d409557ce44}*SharedItemsImports = 5
+ src\CommunityToolkit.Mvvm.CodeFixers\CommunityToolkit.Mvvm.CodeFixers.projitems*{a2ebda90-b720-430d-83f5-c6bcc355232c}*SharedItemsImports = 13
tests\CommunityToolkit.Mvvm.UnitTests\CommunityToolkit.Mvvm.UnitTests.projitems*{ad9c3223-8e37-4fd4-a0d4-a45119551d3a}*SharedItemsImports = 5
tests\CommunityToolkit.Mvvm.UnitTests\CommunityToolkit.Mvvm.UnitTests.projitems*{b8dcd82e-b53b-4249-ad4e-f9b99acb9334}*SharedItemsImports = 13
+ tests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems*{c342302d-a263-42d6-b8ee-01def8192690}*SharedItemsImports = 5
src\CommunityToolkit.Mvvm.SourceGenerators\CommunityToolkit.Mvvm.SourceGenerators.projitems*{df455c40-b18e-4890-8758-7cccb5ca7052}*SharedItemsImports = 5
src\CommunityToolkit.Mvvm.SourceGenerators\CommunityToolkit.Mvvm.SourceGenerators.projitems*{e24d1146-5ad8-498f-a518-4890d8bf4937}*SharedItemsImports = 5
+ src\CommunityToolkit.Mvvm.CodeFixers\CommunityToolkit.Mvvm.CodeFixers.projitems*{e79dca2a-4c59-499f-85bd-f45215ed6b72}*SharedItemsImports = 5
tests\CommunityToolkit.Mvvm.ExternalAssembly\CommunityToolkit.Mvvm.ExternalAssembly.projitems*{e827a9cd-405f-43e4-84c7-68cc7e845cdc}*SharedItemsImports = 13
tests\CommunityToolkit.Mvvm.ExternalAssembly\CommunityToolkit.Mvvm.ExternalAssembly.projitems*{ecfe93aa-4b98-4292-b3fa-9430d513b4f9}*SharedItemsImports = 5
tests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems*{f3799252-7a66-4533-89d8-b3c312052d95}*SharedItemsImports = 5
tests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems*{fb59ce88-7732-4a63-b5bd-ac5681b7da1a}*SharedItemsImports = 13
+ src\CommunityToolkit.Mvvm.SourceGenerators\CommunityToolkit.Mvvm.SourceGenerators.projitems*{fcc13ad5-ceb8-4cc1-8250-89b616d126f2}*SharedItemsImports = 5
tests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems*{fe3ea695-ea0f-4e5f-9257-e059aaa23b10}*SharedItemsImports = 5
EndGlobalSection
EndGlobal
diff --git a/global.json b/global.json
new file mode 100644
index 000000000..1880a952c
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "8.0.403",
+ "rollForward": "latestFeature",
+ "allowPrerelease": false
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Common/CommunityToolkit.Common.csproj b/src/CommunityToolkit.Common/CommunityToolkit.Common.csproj
index ddf81b30c..a7d4a430b 100644
--- a/src/CommunityToolkit.Common/CommunityToolkit.Common.csproj
+++ b/src/CommunityToolkit.Common/CommunityToolkit.Common.csproj
@@ -1,7 +1,7 @@
- netstandard2.0;netstandard2.1;net6.0;net8.0
+ netstandard2.0;netstandard2.1;net8.0
diff --git a/src/CommunityToolkit.Common/Deferred/EventDeferral.cs b/src/CommunityToolkit.Common/Deferred/EventDeferral.cs
index d5a36b7cb..65660ff03 100644
--- a/src/CommunityToolkit.Common/Deferred/EventDeferral.cs
+++ b/src/CommunityToolkit.Common/Deferred/EventDeferral.cs
@@ -4,6 +4,9 @@
using System;
using System.ComponentModel;
+#if NET8_0_OR_GREATER
+using System.Runtime.CompilerServices;
+#endif
using System.Threading;
using System.Threading.Tasks;
@@ -16,8 +19,11 @@ namespace CommunityToolkit.Common.Deferred;
///
public class EventDeferral : IDisposable
{
- // TODO: If/when .NET 6 is base, we can upgrade to non-generic version
+#if NET8_0_OR_GREATER
+ private readonly TaskCompletionSource taskCompletionSource = new();
+#else
private readonly TaskCompletionSource
\ No newline at end of file
diff --git a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/INotifyPropertyChangedAttribute.cs b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/INotifyPropertyChangedAttribute.cs
index 360643eee..08b63b4c6 100644
--- a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/INotifyPropertyChangedAttribute.cs
+++ b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/INotifyPropertyChangedAttribute.cs
@@ -28,7 +28,7 @@ public sealed class INotifyPropertyChangedAttribute : Attribute
{
///
/// Gets or sets a value indicating whether or not to also generate all the additional helper methods that are found
- /// in as well (eg. .
+ /// in as well (eg. ).
/// If set to , only the event and
/// the two overloads will be generated.
/// The default value is .
diff --git a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyCanExecuteChangedForAttribute.cs b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyCanExecuteChangedForAttribute.cs
index 748281015..33f0b3fb2 100644
--- a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyCanExecuteChangedForAttribute.cs
+++ b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyCanExecuteChangedForAttribute.cs
@@ -12,8 +12,8 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
/// An attribute that can be used to support properties in generated properties. When this attribute is
/// used, the generated property setter will also call for the properties specified
/// in the attribute data, causing the validation logic for the command to be executed again. This can be useful to keep the code compact
-/// when there are one or more dependent commands that should also be notified when a property is updated. If this attribute is used in
-/// a field without , it is ignored (just like ).
+/// when there are one or more dependent commands that should also be notified when a property is updated. If this attribute is used on
+/// a property without , it is ignored (just like ).
///
/// In order to use this attribute, the target property has to implement the interface.
///
@@ -24,7 +24,7 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
/// {
/// [ObservableProperty]
/// [NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]
-/// private string name;
+/// public partial string Name { get; set; }
///
/// public IRelayCommand GreetUserCommand { get; }
/// }
@@ -34,12 +34,12 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
///
/// partial class MyViewModel
/// {
-/// public string Name
+/// public partial string Name
/// {
-/// get => name;
+/// get => field;
/// set
/// {
-/// if (SetProperty(ref name, value))
+/// if (SetProperty(ref field, value))
/// {
/// GreetUserCommand.NotifyCanExecuteChanged();
/// }
@@ -48,7 +48,10 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
/// }
///
///
-[AttributeUsage(AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
+///
+/// Just like , this attribute can also be used on fields as well.
+///
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public sealed class NotifyCanExecuteChangedForAttribute : Attribute
{
///
diff --git a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyDataErrorInfoAttribute.cs b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyDataErrorInfoAttribute.cs
index 2aa7b1159..6a741d601 100644
--- a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyDataErrorInfoAttribute.cs
+++ b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyDataErrorInfoAttribute.cs
@@ -8,7 +8,7 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
///
/// An attribute that can be used to support in generated properties, when applied to
-/// fields contained in a type that is inheriting from and using any validation attributes.
+/// partial properties contained in a type that is inheriting from and using any validation attributes.
/// When this attribute is used, the generated property setter will also call .
/// This allows generated properties to opt-in into validation behavior without having to fallback into a full explicit observable property.
///
@@ -20,7 +20,7 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
/// [NotifyDataErrorInfo]
/// [Required]
/// [MinLength(2)]
-/// private string username;
+/// public partial string Username { get; set; }
/// }
///
///
@@ -28,17 +28,23 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
///
/// partial class MyViewModel
/// {
-/// [Required]
-/// [MinLength(2)]
-/// public string Username
+/// public partial string Username
/// {
-/// get => username;
-/// set => SetProperty(ref username, value, validate: true);
+/// get => field;
+/// set => SetProperty(ref field, value, validate: true);
/// }
/// }
///
///
-[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+///
+///
+/// This attribute can also be used on a class, which will enable the validation on all generated properties contained in it.
+///
+///
+/// Just like , this attribute can also be used on fields as well.
+///
+///
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class NotifyDataErrorInfoAttribute : Attribute
{
}
diff --git a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyPropertyChangedForAttribute.cs b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyPropertyChangedForAttribute.cs
index 09178f9f5..d963ed72c 100644
--- a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyPropertyChangedForAttribute.cs
+++ b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyPropertyChangedForAttribute.cs
@@ -13,7 +13,7 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
/// used, the generated property setter will also call (or the equivalent
/// method in the target class) for the properties specified in the attribute data. This can be useful to keep the code compact when
/// there are one or more dependent properties that should also be reported as updated when the value of the annotated observable
-/// property is changed. If this attribute is used in a field without , it is ignored.
+/// property is changed. If this attribute is used on a property without , it is ignored.
///
/// In order to use this attribute, the containing type has to implement the interface
/// and expose a method with the same signature as . If the containing
@@ -27,11 +27,11 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
/// {
/// [ObservableProperty]
/// [NotifyPropertyChangedFor(nameof(FullName))]
-/// private string name;
+/// public partial string Name { get; set; }
///
/// [ObservableProperty]
/// [NotifyPropertyChangedFor(nameof(FullName))]
-/// private string surname;
+/// public partial string Surname { get; set; }
///
/// public string FullName => $"{Name} {Surname}";
/// }
@@ -41,17 +41,17 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
///
/// partial class MyViewModel
/// {
-/// public string Name
+/// public partial string Name
/// {
-/// get => name;
+/// get => field;
/// set
/// {
-/// if (!EqualityComparer<string>.Default.Equals(name, value))
+/// if (!EqualityComparer<string>.Default.Equals(field, value))
/// {
/// OnPropertyChanging(nameof(Name));
/// OnPropertyChanged(nameof(FullName));
///
-/// name = value;
+/// field = value;
///
/// OnPropertyChanged(nameof(Name));
/// OnPropertyChanged(nameof(FullName));
@@ -59,17 +59,17 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
/// }
/// }
///
-/// public string Surname
+/// public partial string Surname
/// {
-/// get => surname;
+/// get => field;
/// set
/// {
-/// if (!EqualityComparer<string>.Default.Equals(name, value))
+/// if (!EqualityComparer<string>.Default.Equals(field, value))
/// {
/// OnPropertyChanging(nameof(Surname));
/// OnPropertyChanged(nameof(FullName));
///
-/// surname = value;
+/// field = value;
///
/// OnPropertyChanged(nameof(Surname));
/// OnPropertyChanged(nameof(FullName));
@@ -79,7 +79,10 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
/// }
///
///
-[AttributeUsage(AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
+///
+/// Just like , this attribute can also be used on fields as well.
+///
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public sealed class NotifyPropertyChangedForAttribute : Attribute
{
///
diff --git a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyPropertyChangedRecipientsAttribute.cs b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyPropertyChangedRecipientsAttribute.cs
index f387acd8e..d758bbe17 100644
--- a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyPropertyChangedRecipientsAttribute.cs
+++ b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/NotifyPropertyChangedRecipientsAttribute.cs
@@ -7,7 +7,7 @@
namespace CommunityToolkit.Mvvm.ComponentModel;
///
-/// An attribute that can be used to support in generated properties, when applied to fields
+/// An attribute that can be used to support in generated properties, when applied to fields and properties
/// contained in a type that is either inheriting from , or annotated with .
/// When this attribute is used, the generated property setter will also call .
/// This allows generated properties to opt-in into broadcasting behavior without having to fallback into a full explicit observable property.
@@ -18,7 +18,7 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
/// {
/// [ObservableProperty]
/// [NotifyPropertyChangedRecipients]
-/// private string username;
+/// public partial string Username;
/// }
///
///
@@ -27,10 +27,10 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
///
/// partial class MyViewModel
/// {
-/// public string Username
+/// public partial string Username
/// {
-/// get => username;
-/// set => SetProperty(ref username, value, broadcast: true);
+/// get => field;
+/// set => SetProperty(ref field, value, broadcast: true);
/// }
/// }
///
@@ -39,7 +39,10 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
/// This attribute can also be added to a class, and if so it will affect all generated properties in that type and inherited types.
///
///
-[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+///
+/// Just like , this attribute can also be used on fields as well.
+///
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class NotifyPropertyChangedRecipientsAttribute : Attribute
{
}
diff --git a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs
index 0e765267a..83311270c 100644
--- a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs
+++ b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs
@@ -8,7 +8,7 @@
namespace CommunityToolkit.Mvvm.ComponentModel;
///
-/// An attribute that indicates that a given field should be wrapped by a generated observable property.
+/// An attribute that indicates that a given partial property should be implemented by the source generator.
/// In order to use this attribute, the containing type has to inherit from , or it
/// must be using or .
/// If the containing type also implements the (that is, if it either inherits from
@@ -20,10 +20,10 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
/// partial class MyViewModel : ObservableObject
/// {
/// [ObservableProperty]
-/// private string name;
+/// public partial string Name { get; set; }
///
/// [ObservableProperty]
-/// private bool isEnabled;
+/// public partial bool IsEnabled { get; set; }
/// }
///
///
@@ -31,27 +31,43 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
///
/// partial class MyViewModel
/// {
-/// public string Name
+/// public partial string Name
/// {
-/// get => name;
-/// set => SetProperty(ref name, value);
+/// get => field;
+/// set => SetProperty(ref field, value);
/// }
///
-/// public bool IsEnabled
+/// public partial bool IsEnabled
/// {
-/// get => isEnabled;
-/// set => SetProperty(ref isEnabled, value);
+/// get => field;
+/// set => SetProperty(ref field, value);
/// }
/// }
///
///
///
+///
+/// In order to use this attribute on partial properties, the .NET 9 SDK is required, and C# preview must
+/// be used. If that is not available, this attribute can be used to annotate fields instead, like so:
+///
+/// partial class MyViewModel : ObservableObject
+/// {
+/// [ObservableProperty]
+/// private string name;
+///
+/// [ObservableProperty]
+/// private bool isEnabled;
+/// }
+///
+///
+///
/// The generated properties will automatically use the UpperCamelCase format for their names,
/// which will be derived from the field names. The generator can also recognize fields using either
/// the _lowerCamel or m_lowerCamel naming scheme. Otherwise, the first character in the
/// source field name will be converted to uppercase (eg. isEnabled to IsEnabled).
+///
///
-[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class ObservablePropertyAttribute : Attribute
{
}
diff --git a/src/CommunityToolkit.Mvvm/Messaging/IMessenger.cs b/src/CommunityToolkit.Mvvm/Messaging/IMessenger.cs
index a007d40eb..72720a899 100644
--- a/src/CommunityToolkit.Mvvm/Messaging/IMessenger.cs
+++ b/src/CommunityToolkit.Mvvm/Messaging/IMessenger.cs
@@ -15,7 +15,7 @@ namespace CommunityToolkit.Mvvm.Messaging;
///
/// public sealed class LoginCompletedMessage { }
///
-/// Then, register your a recipient for this message:
+/// Then, register a recipient for this message:
///
/// Messenger.Default.Register<MyRecipientType, LoginCompletedMessage>(this, (r, m) =>
/// {
diff --git a/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs b/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs
index 6febfb2e1..532639c8f 100644
--- a/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs
+++ b/src/CommunityToolkit.Mvvm/Messaging/IMessengerExtensions.cs
@@ -435,7 +435,7 @@ public static TMessage Send(this IMessenger messenger, TMessage messag
/// The type of token to identify what channel to use to send the message.
/// The instance to use to send the message.
/// The token indicating what channel to use.
- /// The message that has been sen.
+ /// The message that has been sent.
///
/// This method will automatically create a new instance
/// just like , and then send it to the right recipients.
diff --git a/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/Dictionary2.cs b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/Dictionary2.cs
index c3ac541a7..4e8bbf075 100644
--- a/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/Dictionary2.cs
+++ b/src/CommunityToolkit.Mvvm/Messaging/Internals/System/Collections.Generic/Dictionary2.cs
@@ -298,7 +298,7 @@ public bool MoveNext()
while ((uint)this.index < (uint)this.count)
{
// We need to preemptively increment the current index so that we still correctly keep track
- // of the current position in the dictionary even if the users doesn't access any of the
+ // of the current position in the dictionary even if the users don't access any of the
// available properties in the enumerator. As this is a possibility, we can't rely on one of
// them to increment the index before MoveNext is invoked again. We ditch the standard enumerator
// API surface here to expose the Key/Value properties directly and minimize the memory copies.
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index 23f65c476..7239f4a2e 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -7,14 +7,6 @@
NETSTANDARD2_1_OR_GREATER
-
-
- true
- true
- true
- true
-
-
true
diff --git a/tests/CommunityToolkit.Common.UnitTests/CommunityToolkit.Common.UnitTests.csproj b/tests/CommunityToolkit.Common.UnitTests/CommunityToolkit.Common.UnitTests.csproj
index db70cacd7..e47e06627 100644
--- a/tests/CommunityToolkit.Common.UnitTests/CommunityToolkit.Common.UnitTests.csproj
+++ b/tests/CommunityToolkit.Common.UnitTests/CommunityToolkit.Common.UnitTests.csproj
@@ -1,13 +1,13 @@
- net472;net6.0;net7.0;net8.0
+ net472;net7.0;net8.0
-
-
-
+
+
+
diff --git a/tests/CommunityToolkit.Diagnostics.UnitTests/CommunityToolkit.Diagnostics.UnitTests.csproj b/tests/CommunityToolkit.Diagnostics.UnitTests/CommunityToolkit.Diagnostics.UnitTests.csproj
index 2c02dcade..633cc0fa4 100644
--- a/tests/CommunityToolkit.Diagnostics.UnitTests/CommunityToolkit.Diagnostics.UnitTests.csproj
+++ b/tests/CommunityToolkit.Diagnostics.UnitTests/CommunityToolkit.Diagnostics.UnitTests.csproj
@@ -1,13 +1,13 @@
- net472;net6.0;net7.0;net8.0
+ net472;net7.0;net8.0
-
-
-
+
+
+
diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/CommunityToolkit.HighPerformance.UnitTests.csproj b/tests/CommunityToolkit.HighPerformance.UnitTests/CommunityToolkit.HighPerformance.UnitTests.csproj
index ce059f06f..772b868c5 100644
--- a/tests/CommunityToolkit.HighPerformance.UnitTests/CommunityToolkit.HighPerformance.UnitTests.csproj
+++ b/tests/CommunityToolkit.HighPerformance.UnitTests/CommunityToolkit.HighPerformance.UnitTests.csproj
@@ -1,7 +1,7 @@
- net472;net6.0;net7.0;net8.0
+ net472;net7.0;net8.0true$(NoWarn);CA2252
@@ -11,9 +11,9 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/tests/CommunityToolkit.HighPerformance.UnitTests/Streams/Test_ReadOnlySequenceStream.cs b/tests/CommunityToolkit.HighPerformance.UnitTests/Streams/Test_ReadOnlySequenceStream.cs
new file mode 100644
index 000000000..0cb053f2a
--- /dev/null
+++ b/tests/CommunityToolkit.HighPerformance.UnitTests/Streams/Test_ReadOnlySequenceStream.cs
@@ -0,0 +1,407 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Buffers;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace CommunityToolkit.HighPerformance.UnitTests.Streams;
+
+[TestClass]
+public partial class Test_ReadOnlySequenceStream
+{
+ [TestMethod]
+ public void Test_ReadOnlySequenceStream_Lifecycle()
+ {
+ ReadOnlySequence sequence = CreateReadOnlySequence(new byte[100]);
+
+ Stream stream = sequence.AsStream();
+
+ Assert.IsTrue(stream.CanRead);
+ Assert.IsTrue(stream.CanSeek);
+ Assert.IsFalse(stream.CanWrite);
+ Assert.AreEqual(stream.Length, sequence.Length);
+ Assert.AreEqual(stream.Position, 0);
+
+ stream.Dispose();
+
+ Assert.IsFalse(stream.CanRead);
+ Assert.IsFalse(stream.CanSeek);
+ Assert.IsFalse(stream.CanWrite);
+
+ _ = Assert.ThrowsException(() => stream.Length);
+ _ = Assert.ThrowsException(() => stream.Position);
+ }
+
+ [TestMethod]
+ public void Test_ReadOnlySequenceStream_Seek()
+ {
+ Stream stream = CreateReadOnlySequence(new byte[50], new byte[50]).AsStream();
+
+ Assert.AreEqual(stream.Position, 0);
+
+ stream.Position = 42;
+
+ Assert.AreEqual(stream.Position, 42);
+
+ _ = Assert.ThrowsException(() => stream.Position = -1);
+ _ = Assert.ThrowsException(() => stream.Position = 120);
+
+ _ = stream.Seek(0, SeekOrigin.Begin);
+
+ _ = Assert.ThrowsException(() => stream.Seek(-1, SeekOrigin.Begin));
+ _ = Assert.ThrowsException(() => stream.Seek(120, SeekOrigin.Begin));
+
+ Assert.AreEqual(stream.Position, 0);
+
+ _ = stream.Seek(-1, SeekOrigin.End);
+
+ _ = Assert.ThrowsException(() => stream.Seek(20, SeekOrigin.End));
+ _ = Assert.ThrowsException(() => stream.Seek(-120, SeekOrigin.End));
+
+ Assert.AreEqual(stream.Position, stream.Length - 1);
+
+ _ = stream.Seek(42, SeekOrigin.Begin);
+ _ = stream.Seek(20, SeekOrigin.Current);
+ _ = stream.Seek(-30, SeekOrigin.Current);
+
+ _ = Assert.ThrowsException(() => stream.Seek(-64, SeekOrigin.Current));
+ _ = Assert.ThrowsException(() => stream.Seek(80, SeekOrigin.Current));
+
+ Assert.AreEqual(stream.Position, 32);
+ }
+
+ [TestMethod]
+ public void Test_ReadOnlySequenceStream_Read_Array()
+ {
+ Memory data = CreateRandomData(64);
+
+ Stream stream = CreateReadOnlySequence(data).AsStream();
+
+ stream.Position = 0;
+
+ byte[] result = new byte[data.Length];
+
+ int bytesRead = stream.Read(result, 0, result.Length);
+
+ Assert.AreEqual(bytesRead, result.Length);
+ Assert.AreEqual(stream.Position, data.Length);
+ Assert.IsTrue(data.Span.SequenceEqual(result));
+
+ stream.Dispose();
+
+ _ = Assert.ThrowsException(() => stream.Read(result, 0, result.Length));
+ }
+
+ [TestMethod]
+ public void Test_ReadOnlySequenceStream_NotFromStart_Read_Array()
+ {
+ const int offset = 8;
+
+ Memory data = CreateRandomData(64);
+
+ Stream stream = CreateReadOnlySequence(data).AsStream();
+
+ stream.Position = offset;
+
+ byte[] result = new byte[data.Length - offset];
+
+ int bytesRead = stream.Read(result, 0, result.Length);
+
+ Assert.AreEqual(bytesRead, result.Length);
+ Assert.AreEqual(stream.Position, result.Length + offset);
+ Assert.IsTrue(data.Span.Slice(offset).SequenceEqual(result));
+
+ stream.Dispose();
+
+ _ = Assert.ThrowsException(() => stream.Read(result, 0, result.Length));
+ }
+
+ [TestMethod]
+ public void Test_ReadOnlySequenceStream_ReadByte()
+ {
+ Memory data = new byte[] { 1, 128, 255, 32 };
+
+ Stream stream = CreateReadOnlySequence(data.Slice(0,2), data.Slice(2, 2)).AsStream();
+
+ Span result = stackalloc byte[4];
+
+ foreach (ref byte value in result)
+ {
+ value = checked((byte)stream.ReadByte());
+ }
+
+ Assert.AreEqual(stream.Position, data.Length);
+ Assert.IsTrue(data.Span.SequenceEqual(result));
+
+ int exitCode = stream.ReadByte();
+
+ Assert.AreEqual(exitCode, -1);
+ }
+
+ [TestMethod]
+ public void Test_ReadOnlySequenceStream_Read_Span()
+ {
+ Memory data = CreateRandomData(64);
+
+ Stream stream = CreateReadOnlySequence(data).AsStream();
+
+ stream.Position = 0;
+
+ Span result = new byte[data.Length];
+
+ int bytesRead = stream.Read(result);
+
+ Assert.AreEqual(bytesRead, result.Length);
+ Assert.AreEqual(stream.Position, data.Length);
+ Assert.IsTrue(data.Span.SequenceEqual(result));
+ }
+
+ [TestMethod]
+ public async Task Test_ReadOnlySequenceStream_ReadAsync_Memory()
+ {
+ Memory data = CreateRandomData(64);
+
+ Stream stream = CreateReadOnlySequence(data).AsStream();
+
+ Memory result = new byte[data.Length];
+
+ int bytesRead = await stream.ReadAsync(result);
+
+ Assert.AreEqual(bytesRead, result.Length);
+ Assert.AreEqual(stream.Position, data.Length);
+ Assert.IsTrue(data.Span.SequenceEqual(result.Span));
+ }
+
+ [TestMethod]
+ public void Test_ReadOnlySequenceStream_SigleSegment_CopyTo()
+ {
+ Memory data = CreateRandomData(64);
+
+ Stream source = CreateReadOnlySequence(data).AsStream();
+
+ Stream destination = new byte[100].AsMemory().AsStream();
+
+ source.CopyTo(destination);
+
+ Assert.AreEqual(source.Position, destination.Position);
+
+ destination.Position = 0;
+
+ Memory result = new byte[data.Length];
+
+ int bytesRead = destination.Read(result.Span);
+
+ Assert.AreEqual(bytesRead, result.Length);
+ Assert.AreEqual(destination.Position, data.Length);
+ Assert.IsTrue(data.Span.SequenceEqual(result.Span));
+ }
+
+ [TestMethod]
+ public void Test_ReadOnlySequenceStream_CopyTo()
+ {
+ Memory data = CreateRandomData(64);
+
+ Stream source = CreateReadOnlySequence(data.Slice(0, 32), data.Slice(32)).AsStream();
+
+ Stream destination = new byte[100].AsMemory().AsStream();
+
+ source.CopyTo(destination);
+
+ Assert.AreEqual(source.Position, destination.Position);
+
+ destination.Position = 0;
+
+ Memory result = new byte[data.Length];
+
+ int bytesRead = destination.Read(result.Span);
+
+ Assert.AreEqual(bytesRead, result.Length);
+ Assert.AreEqual(destination.Position, data.Length);
+ Assert.IsTrue(data.Span.SequenceEqual(result.Span));
+ }
+
+ [TestMethod]
+ public async Task Test_ReadOnlySequenceStream_SigleSegment_CopyToAsync()
+ {
+ Memory data = CreateRandomData(64);
+
+ Stream source = CreateReadOnlySequence(data).AsStream();
+
+ Stream destination = new byte[100].AsMemory().AsStream();
+
+ await source.CopyToAsync(destination);
+
+ Assert.AreEqual(source.Position, destination.Position);
+
+ destination.Position = 0;
+
+ Memory result = new byte[data.Length];
+
+ int bytesRead = await destination.ReadAsync(result);
+
+ Assert.AreEqual(bytesRead, result.Length);
+ Assert.AreEqual(destination.Position, data.Length);
+ Assert.IsTrue(data.Span.SequenceEqual(result.Span));
+ }
+
+ [TestMethod]
+ public async Task Test_ReadOnlySequenceStream_SigleSegment_NotFromStart_CopyToAsync()
+ {
+ const int offset = 8;
+
+ Memory data = CreateRandomData(64);
+
+ Stream source = CreateReadOnlySequence(data).AsStream();
+
+ source.Position = offset;
+
+ Stream destination = new byte[100].AsMemory().AsStream();
+
+ await source.CopyToAsync(destination);
+
+ Assert.AreEqual(source.Position, destination.Position + offset);
+
+ destination.Position = 0;
+
+ Memory result = new byte[data.Length - offset];
+
+ int bytesRead = await destination.ReadAsync(result);
+
+ Assert.AreEqual(bytesRead, result.Length);
+ Assert.AreEqual(destination.Position, data.Length - offset);
+ Assert.IsTrue(data.Span.Slice(offset).SequenceEqual(result.Span));
+ }
+
+ [TestMethod]
+ public async Task Test_ReadOnlySequenceStream_MultipleSegments_CopyToAsync()
+ {
+ Memory data = CreateRandomData(64);
+
+ Stream source = CreateReadOnlySequence(data.Slice(0, 16), data.Slice(16, 16), data.Slice(32, 16), data.Slice(48, 16)).AsStream();
+
+ Stream destination = new byte[100].AsMemory().AsStream();
+
+ await source.CopyToAsync(destination);
+
+ Assert.AreEqual(source.Position, destination.Position);
+
+ destination.Position = 0;
+
+ Memory result = new byte[data.Length];
+
+ int bytesRead = await destination.ReadAsync(result);
+
+ Assert.AreEqual(bytesRead, result.Length);
+ Assert.AreEqual(destination.Position, data.Length);
+ Assert.IsTrue(data.Span.SequenceEqual(result.Span));
+ }
+
+ [TestMethod]
+ public async Task Test_ReadOnlySequenceStream_MultipleSegments_NotFromStart_CopyToAsync()
+ {
+ const int offset = 8;
+
+ Memory data = CreateRandomData(64);
+
+ Stream source = CreateReadOnlySequence(data.Slice(0, 16), data.Slice(16, 16), data.Slice(32, 16), data.Slice(48, 16)).AsStream();
+
+ source.Position = offset;
+
+ Stream destination = new byte[100].AsMemory().AsStream();
+
+ await source.CopyToAsync(destination);
+
+ Assert.AreEqual(source.Position, destination.Position + offset);
+
+ destination.Position = 0;
+
+ Memory result = new byte[data.Length - offset];
+
+ int bytesRead = await destination.ReadAsync(result);
+
+ Assert.AreEqual(bytesRead, result.Length);
+ Assert.AreEqual(destination.Position, data.Length - offset);
+ Assert.IsTrue(data.Span.Slice(offset).SequenceEqual(result.Span));
+ }
+
+ ///
+ /// Creates a random array filled with random data.
+ ///
+ /// The number of array items to create.
+ /// The returned random array.
+ private static byte[] CreateRandomData(int count)
+ {
+ Random? random = new(DateTime.Now.Ticks.GetHashCode());
+
+ byte[] data = new byte[count];
+
+ foreach (ref byte n in MemoryMarshal.AsBytes(data.AsSpan()))
+ {
+ n = (byte)random.Next(0, byte.MaxValue);
+ }
+
+ return data;
+ }
+
+ ///
+ /// Creates a value from the input segments.
+ ///
+ /// The input segments.
+ /// The resulting value.
+ private static ReadOnlySequence CreateReadOnlySequence(params ReadOnlyMemory[] segments)
+ {
+ if (segments is not { Length: > 0 })
+ {
+ return ReadOnlySequence.Empty;
+ }
+
+ if (segments.Length == 1)
+ {
+ return new(segments[0]);
+ }
+
+ ReadOnlySequenceSegmentOfByte first = new(segments[0]);
+ ReadOnlySequenceSegmentOfByte last = first;
+ long length = first.Memory.Length;
+
+ for (int i = 1; i < segments.Length; i++)
+ {
+ ReadOnlyMemory segment = segments[i];
+
+ length += segment.Length;
+
+ last = last.Append(segment);
+ }
+
+ return new(first, 0, last, (int)(length - last.RunningIndex));
+ }
+
+ ///
+ /// A custom that supports appending new segments.
+ ///
+ private sealed class ReadOnlySequenceSegmentOfByte : ReadOnlySequenceSegment
+ {
+ public ReadOnlySequenceSegmentOfByte(ReadOnlyMemory memory)
+ {
+ Memory = memory;
+ }
+
+ public ReadOnlySequenceSegmentOfByte Append(ReadOnlyMemory memory)
+ {
+ ReadOnlySequenceSegmentOfByte nextSegment = new(memory)
+ {
+ RunningIndex = RunningIndex + Memory.Length
+ };
+
+ Next = nextSegment;
+
+ return nextSegment;
+ }
+ }
+}
diff --git a/tests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests.csproj
index 76f768ef0..5853d5a5c 100644
--- a/tests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests.csproj
+++ b/tests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests.csproj
@@ -1,18 +1,18 @@
- net472;net6.0;net7.0;net8.0
+ net472;net7.0;net8.0
-
-
-
+
+
+
-
+
\ No newline at end of file
diff --git a/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401.csproj b/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4001/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4001.csproj
similarity index 81%
rename from tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401.csproj
rename to tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4001/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4001.csproj
index ee36d9f08..ae18e53f9 100644
--- a/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn401.csproj
+++ b/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4001/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4001.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431.csproj b/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4031/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4031.csproj
similarity index 77%
rename from tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431.csproj
rename to tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4031/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4031.csproj
index 164e42e50..4a58a1151 100644
--- a/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431.csproj
+++ b/tests/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4031/CommunityToolkit.Mvvm.ExternalAssembly.Roslyn4031.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/tests/CommunityToolkit.Mvvm.Internals.UnitTests/CommunityToolkit.Mvvm.Internals.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.Internals.UnitTests/CommunityToolkit.Mvvm.Internals.UnitTests.csproj
index 5c35d66d3..acb777815 100644
--- a/tests/CommunityToolkit.Mvvm.Internals.UnitTests/CommunityToolkit.Mvvm.Internals.UnitTests.csproj
+++ b/tests/CommunityToolkit.Mvvm.Internals.UnitTests/CommunityToolkit.Mvvm.Internals.UnitTests.csproj
@@ -1,13 +1,13 @@
- net472;net6.0;net7.0;net8.0
+ net472;net7.0;net8.0
-
-
-
+
+
+
diff --git a/tests/CommunityToolkit.Mvvm.Roslyn401.UnitTests/CommunityToolkit.Mvvm.Roslyn401.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.Roslyn4001.UnitTests/CommunityToolkit.Mvvm.Roslyn4001.UnitTests.csproj
similarity index 64%
rename from tests/CommunityToolkit.Mvvm.Roslyn401.UnitTests/CommunityToolkit.Mvvm.Roslyn401.UnitTests.csproj
rename to tests/CommunityToolkit.Mvvm.Roslyn4001.UnitTests/CommunityToolkit.Mvvm.Roslyn4001.UnitTests.csproj
index 115f23003..eb46db98f 100644
--- a/tests/CommunityToolkit.Mvvm.Roslyn401.UnitTests/CommunityToolkit.Mvvm.Roslyn401.UnitTests.csproj
+++ b/tests/CommunityToolkit.Mvvm.Roslyn4001.UnitTests/CommunityToolkit.Mvvm.Roslyn4001.UnitTests.csproj
@@ -1,23 +1,23 @@
- net472;net6.0;net7.0;net8.0
+ net472;net7.0;net8.0true
-
-
-
+
+
+
-
+
-
+
-
+
diff --git a/tests/CommunityToolkit.Mvvm.Roslyn431.UnitTests/CommunityToolkit.Mvvm.Roslyn431.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.Roslyn4031.UnitTests/CommunityToolkit.Mvvm.Roslyn4031.UnitTests.csproj
similarity index 64%
rename from tests/CommunityToolkit.Mvvm.Roslyn431.UnitTests/CommunityToolkit.Mvvm.Roslyn431.UnitTests.csproj
rename to tests/CommunityToolkit.Mvvm.Roslyn4031.UnitTests/CommunityToolkit.Mvvm.Roslyn4031.UnitTests.csproj
index c1973e0eb..b70464cb4 100644
--- a/tests/CommunityToolkit.Mvvm.Roslyn431.UnitTests/CommunityToolkit.Mvvm.Roslyn431.UnitTests.csproj
+++ b/tests/CommunityToolkit.Mvvm.Roslyn4031.UnitTests/CommunityToolkit.Mvvm.Roslyn4031.UnitTests.csproj
@@ -1,23 +1,23 @@
- net472;net6.0;net7.0;net8.0
+ net472;net7.0;net8.0true
-
-
-
+
+
+
-
+
-
+
-
+
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests.csproj
similarity index 75%
rename from tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj
rename to tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests.csproj
index c1717ef47..3656cfdf1 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests.csproj
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests.csproj
@@ -1,22 +1,22 @@
- net472;net6.0;net7.0;net8.0
+ net472;net7.0;net8.0
-
-
-
+
+
+
-
-
+
+
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/Test_AsyncVoidReturningRelayCommandMethodCodeFixer.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_AsyncVoidReturningRelayCommandMethodCodeFixer.cs
similarity index 98%
rename from tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/Test_AsyncVoidReturningRelayCommandMethodCodeFixer.cs
rename to tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_AsyncVoidReturningRelayCommandMethodCodeFixer.cs
index 5062572d1..c77c38b7b 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/Test_AsyncVoidReturningRelayCommandMethodCodeFixer.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_AsyncVoidReturningRelayCommandMethodCodeFixer.cs
@@ -15,7 +15,7 @@
CommunityToolkit.Mvvm.CodeFixers.AsyncVoidReturningRelayCommandMethodCodeFixer,
Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
-namespace CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests;
+namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;
[TestClass]
public class Test_AsyncVoidReturningRelayCommandMethodCodeFixer
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/Test_ClassUsingAttributeInsteadOfInheritanceCodeFixer.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_ClassUsingAttributeInsteadOfInheritanceCodeFixer.cs
similarity index 99%
rename from tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/Test_ClassUsingAttributeInsteadOfInheritanceCodeFixer.cs
rename to tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_ClassUsingAttributeInsteadOfInheritanceCodeFixer.cs
index 018c5fc63..2e63a49f9 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/Test_ClassUsingAttributeInsteadOfInheritanceCodeFixer.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_ClassUsingAttributeInsteadOfInheritanceCodeFixer.cs
@@ -15,7 +15,7 @@
CommunityToolkit.Mvvm.CodeFixers.ClassUsingAttributeInsteadOfInheritanceCodeFixer,
Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
-namespace CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests;
+namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;
[TestClass]
public class ClassUsingAttributeInsteadOfInheritanceCodeFixer
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/Test_FieldReferenceForObservablePropertyFieldCodeFixer.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_FieldReferenceForObservablePropertyFieldCodeFixer.cs
similarity index 99%
rename from tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/Test_FieldReferenceForObservablePropertyFieldCodeFixer.cs
rename to tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_FieldReferenceForObservablePropertyFieldCodeFixer.cs
index 596a11b66..665761405 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests/Test_FieldReferenceForObservablePropertyFieldCodeFixer.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_FieldReferenceForObservablePropertyFieldCodeFixer.cs
@@ -15,7 +15,7 @@
CommunityToolkit.Mvvm.CodeFixers.FieldReferenceForObservablePropertyFieldCodeFixer,
Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
-namespace CommunityToolkit.Mvvm.SourceGenerators.Roslyn401.UnitTests;
+namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;
[TestClass]
public class Test_FieldReferenceForObservablePropertyFieldCodeFixer
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_UnsupportedRoslynVersionForPartialPropertyAnalyzer.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_UnsupportedRoslynVersionForPartialPropertyAnalyzer.cs
new file mode 100644
index 000000000..9b7ef7e40
--- /dev/null
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/Test_UnsupportedRoslynVersionForPartialPropertyAnalyzer.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;
+
+[TestClass]
+public class Test_UnsupportedRoslynVersionForPartialPropertyAnalyzer
+{
+ [TestMethod]
+ public async Task UnsupportedRoslynVersionForPartialPropertyAnalyzer_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ public string {|MVVMTK0044:Bar|} { get; set; }
+ }
+ }
+ """;
+
+ await Test_SourceGeneratorsDiagnostics.VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8);
+ }
+}
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4031.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4031.UnitTests.csproj
similarity index 59%
rename from tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests.csproj
rename to tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4031.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4031.UnitTests.csproj
index 7323b4a69..495ef1dca 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn431.UnitTests.csproj
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4031.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4031.UnitTests.csproj
@@ -1,21 +1,26 @@
- net472;net6.0;net7.0;net8.0
+ net472;net7.0;net8.0$(DefineConstants);ROSLYN_4_3_1_OR_GREATER
+
+
+
+
+
-
-
-
+
+
+
-
+
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests.csproj
new file mode 100644
index 000000000..eba477975
--- /dev/null
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net472;net7.0;net8.0
+ $(DefineConstants);ROSLYN_4_3_1_OR_GREATER;ROSLYN_4_12_0_OR_GREATER
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsCodegen.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsCodegen.cs
new file mode 100644
index 000000000..9d7092888
--- /dev/null
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsCodegen.cs
@@ -0,0 +1,928 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.ComponentModel.DataAnnotations;
+using System.IO;
+using System.Linq;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;
+
+partial class Test_SourceGeneratorsCodegen
+{
+ [TestMethod]
+ public void ObservablePropertyWithValueType_OnPartialProperty_WithNoModifiers_WorksCorrectly()
+ {
+ string source = """
+ using System;
+ 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.Preview, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ // See https://github.com/CommunityToolkit/dotnet/issues/969
+ [TestMethod]
+ public void ObservablePropertyWithValueType_OnPartialProperty_RequiredProperty_WorksCorrectly()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ public required partial int Number { get; private 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]
+ public required partial int Number
+ {
+ get => field;
+ private 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.Preview, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithValueType_OnPartialProperty_WithExplicitModifiers_WorksCorrectly1()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ public partial int Number { get; private 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]
+ public partial int Number
+ {
+ get => field;
+ private 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.Preview, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithValueType_OnPartialProperty_WithExplicitModifiers_WorksCorrectly2()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ internal partial int Number { private 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]
+ internal partial int Number
+ {
+ private 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.Preview, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithValueType_OnPartialProperty_WithExplicitModifiers_WorksCorrectly3()
+ {
+ string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ protected internal partial string Name { get; private protected 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]
+ protected internal partial string Name
+ {
+ get => field;
+ private protected set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ field = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// 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 OnNameChanging(string 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 OnNameChanging(string oldValue, string 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 OnNameChanged(string 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 OnNameChanged(string oldValue, string newValue);
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, LanguageVersion.Preview, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+[TestMethod]
+ public void ObservablePropertyWithReferenceType_NotNullable_OnPartialProperty_WorksCorrectly()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ public partial string Name { 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]
+ public partial string Name
+ {
+ get => field;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ field = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// 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 OnNameChanging(string 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 OnNameChanging(string oldValue, string 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 OnNameChanged(string 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 OnNameChanged(string oldValue, string newValue);
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, LanguageVersion.Preview, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithReferenceType_Nullable_OnPartialProperty_WorksCorrectly()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ public partial string? Name { 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]
+ public partial string? Name
+ {
+ get => field;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ field = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// 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 OnNameChanging(string? 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 OnNameChanging(string? oldValue, string? 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 OnNameChanged(string? 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 OnNameChanged(string? oldValue, string? newValue);
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, LanguageVersion.Preview, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservableProperty_OnPartialProperty_AlsoNotifyPropertyChange_WorksCorrectly()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(FullName))]
+ public partial string Name { get; set; }
+
+ public string FullName => "";
+ }
+ """;
+
+ 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]
+ public partial string Name
+ {
+ get => field;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.FullName);
+ field = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FullName);
+ }
+ }
+ }
+
+ /// 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 OnNameChanging(string 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 OnNameChanging(string oldValue, string 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 OnNameChanged(string 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 OnNameChanged(string oldValue, string newValue);
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, LanguageVersion.Preview, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservableProperty_OnPartialProperty_AlsoNotifyCanExecuteChange_WorksCorrectly()
+ {
+ string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+ using CommunityToolkit.Mvvm.Input;
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableRecipient
+ {
+ [ObservableProperty]
+ [NotifyCanExecuteChangedFor(nameof(TestCommand))]
+ public partial string Name { get; set; }
+
+ public IRelayCommand TestCommand => null;
+ }
+ """;
+
+ 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]
+ public partial string Name
+ {
+ get => field;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ field = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ TestCommand.NotifyCanExecuteChanged();
+ }
+ }
+ }
+
+ /// 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 OnNameChanging(string 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 OnNameChanging(string oldValue, string 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 OnNameChanged(string 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 OnNameChanged(string oldValue, string newValue);
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, LanguageVersion.Preview, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservableProperty_OnPartialProperty_AlsoNotifyRecipients_WorksCorrectly()
+ {
+ string source = """
+ using System;
+ using System.Windows.Input;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableRecipient
+ {
+ [ObservableProperty]
+ [NotifyPropertyChangedRecipients]
+ public partial string Name { 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]
+ public partial string Name
+ {
+ get => field;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value))
+ {
+ string __oldValue = field;
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ field = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ Broadcast(__oldValue, value, "Name");
+ }
+ }
+ }
+
+ /// 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 OnNameChanging(string 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 OnNameChanging(string oldValue, string 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 OnNameChanged(string 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 OnNameChanged(string oldValue, string newValue);
+ }
+ }
+ """;
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, LanguageVersion.Preview, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservableProperty_OnPartialProperty_AlsoNotifyDataErrorInfo_WorksCorrectly()
+ {
+ string source = """
+ using System;
+ using System.ComponentModel.DataAnnotations;
+ using System.Windows.Input;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableValidator
+ {
+ [ObservableProperty]
+ [NotifyDataErrorInfo]
+ [Required]
+ public partial string Name { get; set; }
+ }
+ """;
+
+#if NET6_0_OR_GREATER
+ 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]
+ public partial string Name
+ {
+ get => field;
+ [global::System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ field = value;
+ ValidateProperty(value, "Name");
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// 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 OnNameChanging(string 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 OnNameChanging(string oldValue, string 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 OnNameChanged(string 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 OnNameChanged(string oldValue, string newValue);
+ }
+ }
+ """;
+#else
+ 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]
+ public partial string Name
+ {
+ get => field;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ field = value;
+ ValidateProperty(value, "Name");
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// 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 OnNameChanging(string 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 OnNameChanging(string oldValue, string 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 OnNameChanged(string 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 OnNameChanged(string oldValue, string newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, LanguageVersion.Preview, ("MyApp.MyViewModel.g.cs", result));
+ }
+}
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs
new file mode 100644
index 000000000..0cadc6439
--- /dev/null
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs
@@ -0,0 +1,745 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.SourceGenerators.UnitTests.Helpers;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;
+
+partial class Test_SourceGeneratorsDiagnostics
+{
+ [TestMethod]
+ public async Task RequireCSharpLanguageVersionPreviewAnalyzer_LanguageVersionIsNotPreview_DoesnNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ public string Name { get; set; }
+ }
+ }
+ """;
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp12);
+ }
+
+ [TestMethod]
+ public async Task RequireCSharpLanguageVersionPreviewAnalyzer_LanguageVersionIsNotPreview_CSharp12_Partial_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0041:ObservableProperty|}]
+ public partial string Name { get; set; }
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+
+ // /0/Test0.cs(8,31): error CS8703: The modifier 'partial' is not valid for this item in C# 12.0. Please use language version '13.0' or greater.
+ DiagnosticResult.CompilerError("CS8703").WithSpan(8, 31, 8, 35).WithArguments("partial", "12.0", "13.0"),
+ // /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"));
+ }
+
+ [TestMethod]
+ public async Task RequireCSharpLanguageVersionPreviewAnalyzer_LanguageVersionIsNotPreview_CSharp13_Partial_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0041:ObservableProperty|}]
+ public partial string Name { get; set; }
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp13,
+
+ // /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"));
+ }
+
+ [TestMethod]
+ public async Task RequireCSharpLanguageVersionPreviewAnalyzer_LanguageVersionIsPreview_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ public string Name { get; set; }
+ }
+ }
+ """;
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, languageVersion: LanguageVersion.Preview);
+ }
+
+ [TestMethod]
+ public async Task RequireCSharpLanguageVersionPreviewAnalyzer_LanguageVersionIsPreview_Partial_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ public partial string Name { get; set; }
+ }
+ }
+ """;
+
+ 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"));
+ }
+
+ [TestMethod]
+ public async Task UseObservablePropertyOnPartialPropertyAnalyzer_LanguageVersionIsNotPreview_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+ }
+ }
+ """;
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp12);
+ }
+
+ [TestMethod]
+ public async Task UseObservablePropertyOnPartialPropertyAnalyzer_LanguageVersionIsPreview_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string {|MVVMTK0042:name|};
+ }
+ }
+ """;
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.Preview);
+ }
+
+ [TestMethod]
+ public async Task UseObservablePropertyOnPartialPropertyAnalyzer_LanguageVersionIsPreview_OnPartialProperty_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ public string Name { get; set; }
+ }
+ }
+ """;
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.Preview);
+ }
+
+ [TestMethod]
+ public async Task InvalidPropertyLevelObservablePropertyAttributeAnalyzer_OnValidPropertyDeclaration_DoesNotWarn1()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ public partial string {|CS9248:Name|} { get; set; }
+ }
+ }
+ """;
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.Preview, [], ["CS0103", "CS9248"]);
+ }
+
+ [TestMethod]
+ public async Task InvalidPropertyLevelObservablePropertyAttributeAnalyzer_OnValidPropertyDeclaration_DoesNotWarn2()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ internal partial string {|CS9248:Name|} { get; private set; }
+ }
+ }
+ """;
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.Preview, [], ["CS0103", "CS9248"]);
+ }
+
+ [TestMethod]
+ public async Task InvalidPropertyLevelObservablePropertyAttributeAnalyzer_OnValidPropertyDeclaration_DoesNotWarn3()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ protected internal partial string {|CS9248:Name|} { get; private protected set; }
+ }
+ }
+ """;
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.Preview, [], ["CS0103", "CS9248"]);
+ }
+
+ [TestMethod]
+ public async Task InvalidPropertyLevelObservablePropertyAttributeAnalyzer_OnNonPartialProperty_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0043:ObservableProperty|}]
+ public string Name { get; set; }
+ }
+ }
+ """;
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.Preview, [], ["CS9248"]);
+ }
+
+ [TestMethod]
+ public async Task InvalidPropertyLevelObservablePropertyAttributeAnalyzer_OnReadOnlyProperty_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0043:ObservableProperty|}]
+ public partial string {|CS9248:Name|} { get; }
+ }
+ }
+ """;
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.Preview, [], ["CS9248"]);
+ }
+
+ [TestMethod]
+ public async Task InvalidPropertyLevelObservablePropertyAttributeAnalyzer_OnWriteOnlyProperty_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0043:ObservableProperty|}]
+ public partial string {|CS9248:Name|} { set; }
+ }
+ }
+ """;
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.Preview, [], ["CS9248"]);
+ }
+
+ [TestMethod]
+ public async Task InvalidPropertyLevelObservablePropertyAttributeAnalyzer_OnInitOnlyProperty_Warns()
+ {
+#if NET6_0_OR_GREATER
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0043:ObservableProperty|}]
+ public partial string {|CS9248:Name|} { get; init; }
+ }
+ }
+ """;
+#else
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0043:ObservableProperty|}]
+ public partial string {|CS9248:Name|} { get; {|CS0518:init|}; }
+ }
+ }
+ """;
+#endif
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.Preview, [], ["CS0518", "CS9248"]);
+ }
+
+ [TestMethod]
+ public async Task UseObservablePropertyOnPartialPropertyAnalyzer_LanguageVersionIsPreview_OnStaticField_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private static string name;
+ }
+ }
+ """;
+
+ await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.Preview);
+ }
+
+ [TestMethod]
+ public async Task UseObservablePropertyOnPartialPropertyAnalyzer_CsWinRTAotOptimizerEnabled_Auto_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private static string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "auto")]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_NotTargetingWindows_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: []);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_OptIn_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_False_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "false")]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_Auto_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string {|MVVMTK0045:name|};
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "auto")]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_Auto_NotCSharpPreview_Warns_WithCompilationWarning()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [{|MVVMTK0051:ObservableProperty|}]
+ private string {|MVVMTK0045:name|};
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "auto")]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_True_NoXaml_Level1_DoesNotWarn()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "true"), ("CsWinRTAotWarningLevel", 1)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_True_NoXaml_Level2_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string {|MVVMTK0045:name|};
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "true"), ("CsWinRTAotWarningLevel", 2)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_True_NoXaml_1_Component_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string {|MVVMTK0045:name|};
+ }
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "true"), ("CsWinRTAotWarningLevel", 1), ("CsWinRTComponent", true)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_True_UwpXaml_Level1_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string {|MVVMTK0045:name|};
+ }
+ }
+
+ namespace Windows.UI.Xaml.Controls
+ {
+ public class Button;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "true"), ("CsWinRTAotWarningLevel", 1)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_True_WinUIXaml_Level1_Warns()
+ {
+ const string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp
+ {
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string {|MVVMTK0045:name|};
+ }
+ }
+
+ namespace Microsoft.UI.Xaml.Controls
+ {
+ public class Button;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.Preview,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "true"), ("CsWinRTAotWarningLevel", 1)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_NotTargetingWindows_DoesNotWarn()
+ {
+ const string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+ using CommunityToolkit.Mvvm.Input;
+ using WinRT;
+
+ namespace MyApp
+ {
+ [GeneratedBindableCustomProperty]
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+
+ [RelayCommand]
+ private void DoStuff()
+ {
+ }
+ }
+ }
+
+ namespace WinRT
+ {
+ public class GeneratedBindableCustomPropertyAttribute : Attribute;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: []);
+ }
+
+ [TestMethod]
+ public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_TargetingWindows_DoesNotWarn()
+ {
+ const string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+ using CommunityToolkit.Mvvm.Input;
+ using WinRT;
+
+ namespace MyApp
+ {
+ [GeneratedBindableCustomProperty]
+ public partial class SampleViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+
+ [RelayCommand]
+ private void DoStuff()
+ {
+ }
+ }
+ }
+
+ namespace WinRT
+ {
+ public class GeneratedBindableCustomPropertyAttribute : Attribute;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_TargetingWindows_BaseType_Field_Warns()
+ {
+ const string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+ using WinRT;
+
+ namespace MyApp
+ {
+ [GeneratedBindableCustomProperty]
+ public partial class {|MVVMTK0047:SampleViewModel|} : BaseViewModel
+ {
+ }
+
+ public partial class BaseViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string name;
+ }
+ }
+
+ namespace WinRT
+ {
+ public class GeneratedBindableCustomPropertyAttribute : Attribute;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
+ }
+
+ [TestMethod]
+ public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_TargetingWindows_BaseType_Method_Warns()
+ {
+ const string source = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+ using CommunityToolkit.Mvvm.Input;
+ using WinRT;
+
+ namespace MyApp
+ {
+ [GeneratedBindableCustomProperty]
+ public partial class {|MVVMTK0048:SampleViewModel|} : BaseViewModel
+ {
+ }
+
+ public partial class BaseViewModel : ObservableObject
+ {
+ [RelayCommand]
+ private void DoStuff()
+ {
+ }
+ }
+ }
+
+ namespace WinRT
+ {
+ public class GeneratedBindableCustomPropertyAttribute : Attribute;
+ }
+ """;
+
+ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(
+ source,
+ LanguageVersion.CSharp12,
+ editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
+ }
+}
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs
new file mode 100644
index 000000000..f72ca8261
--- /dev/null
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs
@@ -0,0 +1,998 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using CSharpCodeFixTest = CommunityToolkit.Mvvm.SourceGenerators.UnitTests.Helpers.CSharpCodeFixWithLanguageVersionTest<
+ CommunityToolkit.Mvvm.SourceGenerators.UseObservablePropertyOnPartialPropertyAnalyzer,
+ CommunityToolkit.Mvvm.CodeFixers.UsePartialPropertyForObservablePropertyCodeFixer,
+ Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
+using CSharpCodeFixVerifier = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<
+ CommunityToolkit.Mvvm.SourceGenerators.UseObservablePropertyOnPartialPropertyAnalyzer,
+ CommunityToolkit.Mvvm.CodeFixers.UsePartialPropertyForObservablePropertyCodeFixer,
+ Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
+
+namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;
+
+[TestClass]
+public class Test_UsePartialPropertyForObservablePropertyCodeFixer
+{
+ [TestMethod]
+ public async Task SimpleFieldWithNoReferences()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ private int i;
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ public partial int I { get; set; }
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(6,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 17, 6, 18).WithArguments("C", "i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(6,24): error CS9248: Partial property 'C.I' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(6, 24, 6, 25).WithArguments("C.I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleFieldWithNoReferences_WithAdditionalAttributes1()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ [NotifyPropertyChangedFor("hello")]
+ private int i;
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ [NotifyPropertyChangedFor("hello")]
+ public partial int I { get; set; }
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(7,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 17, 7, 18).WithArguments("C", "i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(7,24): error CS9248: Partial property 'C.I' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(7, 24, 7, 25).WithArguments("C.I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleFieldWithNoReferences_WithAdditionalAttributes2()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ [NotifyPropertyChangedFor("hello1")]
+ [NotifyCanExecuteChangedFor("hello2")]
+ private int i;
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ [NotifyPropertyChangedFor("hello1")]
+ [NotifyCanExecuteChangedFor("hello2")]
+ public partial int I { get; set; }
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(8,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(8, 17, 8, 18).WithArguments("C", "i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(8,24): error CS9248: Partial property 'C.I' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(8, 24, 8, 25).WithArguments("C.I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleFieldWithNoReferences_WithAdditionalAttributes3()
+ {
+ string original = """
+ using System.ComponentModel.DataAnnotations;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ [field: MinLength(1)]
+ [property: MinLength(2)]
+ private int i;
+ }
+ """;
+
+ string @fixed = """
+ using System.ComponentModel.DataAnnotations;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ [field: MinLength(1)]
+ [MinLength(2)]
+ public partial int I { get; set; }
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(9,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(9, 17, 9, 18).WithArguments("C", "i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(9,24): error CS9248: Partial property 'C.I' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(9, 24, 9, 25).WithArguments("C.I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleFieldWithNoReferences_WithAdditionalAttributes4()
+ {
+ string original = """
+ using System;
+ using System.ComponentModel.DataAnnotations;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ [Test("This is on the field")]
+ [field: Test("This is also on a the field, but using 'field:'")]
+ [property: Test("This is on the property")]
+ [get: Test("This is on the getter")]
+ [set: Test("This is also on the setter")]
+ [set: Test("This is a second one on the setter")]
+ [ignored: Test("This should be ignored, but still carried over")]
+ private int i;
+ }
+
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
+ public class TestAttribute(string text) : Attribute;
+ """;
+
+ string @fixed = """
+ using System;
+ using System.ComponentModel.DataAnnotations;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ [field: Test("This is on the field")]
+ [field: Test("This is also on a the field, but using 'field:'")]
+ [Test("This is on the property")]
+ [ignored: Test("This should be ignored, but still carried over")]
+ public partial int I { [Test("This is on the getter")]
+ get; [Test("This is also on the setter")]
+ [Test("This is a second one on the setter")]
+ set;
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
+ public class TestAttribute(string text) : Attribute;
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(15,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(15, 17, 15, 18).WithArguments("C", "i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(12,24): error CS9248: Partial property 'C.I' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(12, 24, 12, 25).WithArguments("C.I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleFieldWithNoReferences_WithSimpleComment()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ // This is a comment
+ [ObservableProperty]
+ private int i;
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ // This is a comment
+ [ObservableProperty]
+ public partial int I { get; set; }
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(7,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 17, 7, 18).WithArguments("C", "i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(7,24): error CS9248: Partial property 'C.I' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(7, 24, 7, 25).WithArguments("C.I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleFieldWithNoReferences_WithTwoLineComment()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ // This is a comment.
+ // This is more comment.
+ [ObservableProperty]
+ private int i;
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ // This is a comment.
+ // This is more comment.
+ [ObservableProperty]
+ public partial int I { get; set; }
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(8,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(8, 17, 8, 18).WithArguments("C", "i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(8,24): error CS9248: Partial property 'C.I' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(8, 24, 8, 25).WithArguments("C.I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleFieldWithNoReferences_WithXmlComment()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ ///
+ /// Blah blah blah.
+ ///
+ /// Blah blah blah.
+ [ObservableProperty]
+ private int i;
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ ///
+ /// Blah blah blah.
+ ///
+ /// Blah blah blah.
+ [ObservableProperty]
+ public partial int I { get; set; }
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(10,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(10, 17, 10, 18).WithArguments("C", "i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(10,24): error CS9248: Partial property 'C.I' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(10, 24, 10, 25).WithArguments("C.I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleFieldWithSomeReferences()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ private int i;
+
+ public void M()
+ {
+ i = 42;
+ }
+
+ public int N() => i;
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ public partial int I { get; set; }
+
+ public void M()
+ {
+ I = 42;
+ }
+
+ public int N() => I;
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(6,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 17, 6, 18).WithArguments("C", "i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(6,24): error CS9248: Partial property 'C.I' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(6, 24, 6, 25).WithArguments("C.I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleField_WithInitializer1()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ private int i = 42;
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ public partial int I { get; set; } = 42;
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(6,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 17, 6, 18).WithArguments("C", "i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(6,24): error CS9248: Partial property 'C.I' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(6, 24, 6, 25).WithArguments("C.I"),
+
+ // /0/Test0.cs(6,24): error CS8050: Only auto-implemented properties can have initializers.
+ DiagnosticResult.CompilerError("CS8050").WithSpan(6, 24, 6, 25),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleField_WithInitializer2()
+ {
+ string original = """
+ using System.Collections.Generic;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ private ICollection items = ["A", "B", "C"];
+ }
+ """;
+
+ string @fixed = """
+ using System.Collections.Generic;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ public partial ICollection Items { get; set; } = ["A", "B", "C"];
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(7,33): info MVVMTK0042: The field C.items using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 33, 7, 38).WithArguments("C", "items"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(7,40): error CS8050: Only auto-implemented properties can have initializers.
+ DiagnosticResult.CompilerError("CS8050").WithSpan(7, 40, 7, 45),
+
+ // /0/Test0.cs(7,40): error CS9248: Partial property 'C.Items' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(7, 40, 7, 45).WithArguments("C.Items"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleField_WithInitializer3()
+ {
+ string original = """
+ using System.Collections.Generic;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ private ICollection items = new List { "A", "B", "C" };
+ }
+ """;
+
+ string @fixed = """
+ using System.Collections.Generic;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ public partial ICollection Items { get; set; } = new List { "A", "B", "C" };
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(7,33): info MVVMTK0042: The field C.items using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 33, 7, 38).WithArguments("C", "items"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(7,40): error CS8050: Only auto-implemented properties can have initializers.
+ DiagnosticResult.CompilerError("CS8050").WithSpan(7, 40, 7, 45),
+
+ // /0/Test0.cs(7,40): error CS9248: Partial property 'C.Items' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(7, 40, 7, 45).WithArguments("C.Items"),
+ });
+
+ await test.RunAsync();
+ }
+
+ // See https://github.com/CommunityToolkit/dotnet/issues/971
+ [TestMethod]
+ public async Task SimpleField_WithNoReferences_WithRequiredModifier()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ internal required string foo;
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ public required partial string Foo { get; set; }
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(6,30): info MVVMTK0042: The field C.foo using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 30, 6, 33).WithArguments("C", "foo"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(6,36): error CS9248: Partial property 'C.Foo' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(6, 36, 6, 39).WithArguments("C.Foo"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleFieldWithSomeReferences_WithSomeThisExpressions()
+ {
+ string original = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ private int i;
+
+ public void M()
+ {
+ i = 42;
+ this.i = 42;
+ }
+
+ public int N() => i;
+
+ public int P() => this.i + Q(i) + Q(this.i);
+
+ private int Q(int i) => this.i + i;
+ }
+ """;
+
+ string @fixed = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ partial class C : ObservableObject
+ {
+ [ObservableProperty]
+ public partial int I { get; set; }
+
+ public void M()
+ {
+ I = 42;
+ I = 42;
+ }
+
+ public int N() => I;
+
+ public int P() => I + Q(I) + Q(I);
+
+ private int Q(int i) => I + i;
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(6,17): info MVVMTK0042: The field C.i using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 17, 6, 18).WithArguments("C", "i"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(6,24): error CS9248: Partial property 'C.I' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(6, 24, 6, 25).WithArguments("C.I"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleFields_WithMultipleAttributes_SingleProperty()
+ {
+ string original = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ public partial class Class1 : ObservableObject
+ {
+ [ObservableProperty, NotifyPropertyChangedFor("Age")] private string name = String.Empty;
+ }
+ """;
+
+ string @fixed = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ public partial class Class1 : ObservableObject
+ {
+ [ObservableProperty, NotifyPropertyChangedFor("Age")]
+ public partial string Name { get; set; } = String.Empty;
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(6,74): info MVVMTK0042: The field Class1.name using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 74, 6, 78).WithArguments("Class1", "name"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(7,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ DiagnosticResult.CompilerError("CS8050").WithSpan(7, 27, 7, 31),
+
+ // /0/Test0.cs(7,27): error CS9248: Partial property 'Class1.Name' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(7, 27, 7, 31).WithArguments("Class1.Name"),
+ });
+
+ await test.RunAsync();
+ }
+
+ // See https://github.com/CommunityToolkit/dotnet/issues/1007
+ [TestMethod]
+ public async Task SimpleFields_WithMultipleAttributes_WithNoBlankLines()
+ {
+ string original = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ public partial class Class1 : ObservableObject
+ {
+ [ObservableProperty, NotifyPropertyChangedFor("Age")] private string name = String.Empty;
+ [ObservableProperty] private int age;
+ }
+ """;
+
+ string @fixed = """
+ using System;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ public partial class Class1 : ObservableObject
+ {
+ [ObservableProperty, NotifyPropertyChangedFor("Age")]
+ public partial string Name { get; set; } = String.Empty;
+
+ [ObservableProperty]
+ public partial int Age { get; set; }
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(6,74): info MVVMTK0042: The field Class1.name using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(6, 74, 6, 78).WithArguments("Class1", "name"),
+
+ // /0/Test0.cs(7,38): info MVVMTK0042: The field Class1.age using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 38, 7, 41).WithArguments("Class1", "age"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(7,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ DiagnosticResult.CompilerError("CS8050").WithSpan(7, 27, 7, 31),
+
+ // /0/Test0.cs(7,27): error CS9248: Partial property 'Class1.Name' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(7, 27, 7, 31).WithArguments("Class1.Name"),
+
+ // /0/Test0.cs(10,24): error CS9248: Partial property 'Class1.Age' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(10, 24, 10, 27).WithArguments("Class1.Age"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleFields_WithMultipleAttributes_WithMixedBuckets_1()
+ {
+ string original = """
+ using System;
+ using System.ComponentModel.DataAnnotations;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ public partial class Class1 : ObservableObject
+ {
+ // Leading trivia
+ [ObservableProperty, NotifyPropertyChangedFor("A"), Display]
+ [NotifyPropertyChangedFor("B")]
+ private string _name;
+ }
+ """;
+
+ string @fixed = """
+ using System;
+ using System.ComponentModel.DataAnnotations;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ public partial class Class1 : ObservableObject
+ {
+ // Leading trivia
+ [ObservableProperty, NotifyPropertyChangedFor("A"), Display]
+ [NotifyPropertyChangedFor("B")]
+ public partial string Name { get; set; }
+ }
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(10,20): info MVVMTK0042: The field Class1._name using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(10, 20, 10, 25).WithArguments("Class1", "_name"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(10,27): error CS9248: Partial property 'Class1.Name' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(10, 27, 10, 31).WithArguments("Class1.Name"),
+ });
+
+ await test.RunAsync();
+ }
+
+ [TestMethod]
+ public async Task SimpleFields_WithMultipleAttributes_WithMixedBuckets_2()
+ {
+ string original = """
+ using System;
+ using System.ComponentModel.DataAnnotations;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ public partial class Class1 : ObservableObject
+ {
+ // Leading trivia
+ [NotifyPropertyChangedFor("B")]
+ [ObservableProperty, NotifyPropertyChangedFor("A"), Display, Test]
+ [NotifyPropertyChangedFor("C")]
+ [property: UIHint("name"), Test]
+ private string name;
+ }
+
+ public class TestAttribute : Attribute;
+ """;
+
+ string @fixed = """
+ using System;
+ using System.ComponentModel.DataAnnotations;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ public partial class Class1 : ObservableObject
+ {
+ // Leading trivia
+ [NotifyPropertyChangedFor("B")]
+ [ObservableProperty, NotifyPropertyChangedFor("A"), Display]
+ [field: Test]
+ [NotifyPropertyChangedFor("C")]
+ [UIHint("name"), Test]
+ public partial string Name { get; set; }
+ }
+
+ public class TestAttribute : Attribute;
+ """;
+
+ CSharpCodeFixTest test = new(LanguageVersion.Preview)
+ {
+ TestCode = original,
+ FixedCode = @fixed,
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
+ };
+
+ test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
+ test.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(12,20): info MVVMTK0042: The field Class1.name using [ObservableProperty] can be converted to a partial property instead, which is recommended (doing so improves the developer experience and allows other generators and analyzers to correctly see the generated property as well)
+ CSharpCodeFixVerifier.Diagnostic().WithSpan(12, 20, 12, 24).WithArguments("Class1", "name"),
+ });
+
+ test.FixedState.ExpectedDiagnostics.AddRange(new[]
+ {
+ // /0/Test0.cs(13,27): error CS9248: Partial property 'Class1.Name' must have an implementation part.
+ DiagnosticResult.CompilerError("CS9248").WithSpan(13, 27, 13, 31).WithArguments("Class1.Name"),
+ });
+
+ await test.RunAsync();
+ }
+}
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems
index 881fdb625..8f141adb6 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests.projitems
@@ -9,6 +9,7 @@
CommunityToolkit.Mvvm.SourceGenerators.UnitTests
+
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}.cs
index d76d8f356..14a15ee60 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}.cs
@@ -2,6 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
+using System.Linq;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -10,6 +13,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
#if NET472
using System.ComponentModel.DataAnnotations;
#endif
@@ -29,7 +33,7 @@ internal sealed class CSharpAnalyzerWithLanguageVersionTest : CSharpA
private readonly LanguageVersion languageVersion;
///
- /// Creates a new instance with the specified paramaters.
+ /// Creates a new instance with the specified parameters.
///
/// The C# language version to use to parse code.
private CSharpAnalyzerWithLanguageVersionTest(LanguageVersion languageVersion)
@@ -63,4 +67,38 @@ public static Task VerifyAnalyzerAsync(string source, LanguageVersion languageVe
return test.RunAsync(CancellationToken.None);
}
+
+ ///
+ /// The language version to use to run the test.
+ public static Task VerifyAnalyzerAsync(string source, LanguageVersion languageVersion, (string PropertyName, object PropertyValue)[] editorconfig)
+ {
+ CSharpAnalyzerWithLanguageVersionTest test = new(languageVersion) { TestCode = source };
+
+#if NET8_0_OR_GREATER
+ test.TestState.ReferenceAssemblies = ReferenceAssemblies.Net.Net80;
+#elif NET6_0_OR_GREATER
+ test.TestState.ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
+#else
+ test.TestState.ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net472.Default;
+ test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(RequiredAttribute).Assembly.Location));
+#endif
+ test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(ObservableObject).Assembly.Location));
+
+ // Add any editorconfig properties, if present
+ if (editorconfig.Length > 0)
+ {
+ test.SolutionTransforms.Add((solution, projectId) =>
+ solution.AddAnalyzerConfigDocument(
+ DocumentId.CreateNewId(projectId),
+ "MvvmToolkitAnalyzers.editorconfig",
+ SourceText.From($"""
+ is_global = true
+ {string.Join(Environment.NewLine, editorconfig.Select(static p => $"build_property.{p.PropertyName} = {p.PropertyValue}"))}
+ """,
+ Encoding.UTF8),
+ filePath: "/MvvmToolkitAnalyzers.editorconfig"));
+ }
+
+ return test.RunAsync(CancellationToken.None);
+ }
}
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpCodeFixWithLanguageVersionTest{TAnalyzer,TCodeFix,TVerifier}.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpCodeFixWithLanguageVersionTest{TAnalyzer,TCodeFix,TVerifier}.cs
new file mode 100644
index 000000000..55127c5c8
--- /dev/null
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Helpers/CSharpCodeFixWithLanguageVersionTest{TAnalyzer,TCodeFix,TVerifier}.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+
+namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests.Helpers;
+
+///
+/// A custom that uses a specific C# language version to parse code.
+///
+/// The type of the analyzer to produce diagnostics.
+/// The type of code fix to test.
+/// The type of verifier to use to validate the code fixer.
+internal sealed class CSharpCodeFixWithLanguageVersionTest : CSharpCodeFixTest
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ where TCodeFix : CodeFixProvider, new()
+ where TVerifier : IVerifier, new()
+{
+ ///
+ /// The C# language version to use to parse code.
+ ///
+ private readonly LanguageVersion languageVersion;
+
+ ///
+ /// Creates a new instance with the specified parameters.
+ ///
+ /// The C# language version to use to parse code.
+ public CSharpCodeFixWithLanguageVersionTest(LanguageVersion languageVersion)
+ {
+ this.languageVersion = languageVersion;
+ }
+
+ ///
+ protected override ParseOptions CreateParseOptions()
+ {
+ return new CSharpParseOptions(this.languageVersion, DocumentationMode.Diagnose);
+ }
+}
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
index c3e48fba6..d3d44ec5b 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
@@ -16,7 +16,7 @@
namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;
[TestClass]
-public class Test_SourceGeneratorsCodegen
+public partial class Test_SourceGeneratorsCodegen
{
[TestMethod]
public void ObservablePropertyWithNonNullableReferenceType_EmitsMemberNotNullAttribute()
@@ -2301,7 +2301,7 @@ partial class MyViewModel : ObservableObject
[ObservableProperty]
double @object;
- partial void OnObjectChanged(object oldValue, object NewValue)
+ partial void OnObjectChanged(double oldValue, double NewValue)
{
}
}
@@ -3018,6 +3018,382 @@ public string Name
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
}
+ [TestMethod]
+ public void ObservableProperty_NotNullableProperty_OfReferenceType_WithChangedMethods()
+ {
+ string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ string name;
+
+ partial void OnNameChanged(string? oldValue, string NewValue)
+ {
+ }
+ }
+ """;
+
+#if NET6_0_OR_GREATER
+ 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]
+ public string Name
+ {
+ get => name;
+ [global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ {
+ string? __oldValue = name;
+ OnNameChanging(value);
+ OnNameChanging(__oldValue, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ name = value;
+ OnNameChanged(value);
+ OnNameChanged(__oldValue, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// 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 OnNameChanging(string 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 OnNameChanging(string? oldValue, string 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 OnNameChanged(string 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 OnNameChanged(string? oldValue, string newValue);
+ }
+ }
+ """;
+#else
+ 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]
+ public string Name
+ {
+ get => name;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ {
+ string? __oldValue = name;
+ OnNameChanging(value);
+ OnNameChanging(__oldValue, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ name = value;
+ OnNameChanged(value);
+ OnNameChanged(__oldValue, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// 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 OnNameChanging(string 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 OnNameChanging(string? oldValue, string 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 OnNameChanged(string 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 OnNameChanged(string? oldValue, string newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservableProperty_NotNullableProperty_OfUnconstrainedGenericType_WithChangedMethods()
+ {
+ string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ T value;
+
+ partial void OnValueChanged(T? oldValue, T NewValue)
+ {
+ }
+ }
+ """;
+
+#if NET6_0_OR_GREATER
+ 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]
+ public T Value
+ {
+ get => value;
+ [global::System.Diagnostics.CodeAnalysis.MemberNotNull("value")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(this.value, value))
+ {
+ T? __oldValue = this.value;
+ OnValueChanging(value);
+ OnValueChanging(__oldValue, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Value);
+ this.value = value;
+ OnValueChanged(value);
+ OnValueChanged(__oldValue, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Value);
+ }
+ }
+ }
+
+ /// 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 OnValueChanging(T 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 OnValueChanging(T? oldValue, T 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 OnValueChanged(T 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 OnValueChanged(T? oldValue, T newValue);
+ }
+ }
+ """;
+#else
+ 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]
+ public T Value
+ {
+ get => value;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(this.value, value))
+ {
+ T? __oldValue = this.value;
+ OnValueChanging(value);
+ OnValueChanging(__oldValue, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Value);
+ this.value = value;
+ OnValueChanged(value);
+ OnValueChanged(__oldValue, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Value);
+ }
+ }
+ }
+
+ /// 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 OnValueChanging(T 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 OnValueChanging(T? oldValue, T 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 OnValueChanged(T 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 OnValueChanged(T? oldValue, T newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithForwardedAttributes_OnPropertyAccessors()
+ {
+ string source = """
+ using System.ComponentModel;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ [property: Test("Property1")]
+ [property: Test("Property2")]
+ [property: Test("Property3")]
+ [get: Test("Get1")]
+ [get: Test("Get2")]
+ [set: Test("Set1")]
+ [set: Test("Set2")]
+ private object? a;
+ }
+
+ public class TestAttribute : Attribute
+ {
+ public TestAttribute(string value)
+ {
+ }
+ }
+ """;
+
+ 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]
+ [global::MyApp.TestAttribute("Property1")]
+ [global::MyApp.TestAttribute("Property2")]
+ [global::MyApp.TestAttribute("Property3")]
+ public object? A
+ {
+ [global::MyApp.TestAttribute("Get1")]
+ [global::MyApp.TestAttribute("Get2")]
+ get => a;
+ [global::MyApp.TestAttribute("Set1")]
+ [global::MyApp.TestAttribute("Set2")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer