diff --git a/dotnet Community Toolkit.sln b/dotnet Community Toolkit.sln index 8a94f3c65..6d56ee910 100644 --- a/dotnet Community Toolkit.sln +++ b/dotnet Community Toolkit.sln @@ -81,12 +81,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Exter EndProject 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.Roslyn4110", "src\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.csproj", "{FCC13AD5-CEB8-4CC1-8250-89B616D126F2}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests", "tests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests\CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.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.Roslyn4110", "src\CommunityToolkit.Mvvm.CodeFixers.Roslyn4110\CommunityToolkit.Mvvm.CodeFixers.Roslyn4110.csproj", "{98572004-D29A-486E-8053-6D409557CE44}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -501,6 +505,26 @@ Global {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 @@ -532,11 +556,14 @@ 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 diff --git a/src/CommunityToolkit.Mvvm.CodeFixers.Roslyn4001/CommunityToolkit.Mvvm.CodeFixers.Roslyn4001.csproj b/src/CommunityToolkit.Mvvm.CodeFixers.Roslyn4001/CommunityToolkit.Mvvm.CodeFixers.Roslyn4001.csproj new file mode 100644 index 000000000..9f443712d --- /dev/null +++ b/src/CommunityToolkit.Mvvm.CodeFixers.Roslyn4001/CommunityToolkit.Mvvm.CodeFixers.Roslyn4001.csproj @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/CommunityToolkit.Mvvm.CodeFixers.Roslyn4110/CommunityToolkit.Mvvm.CodeFixers.Roslyn4110.csproj b/src/CommunityToolkit.Mvvm.CodeFixers.Roslyn4110/CommunityToolkit.Mvvm.CodeFixers.Roslyn4110.csproj new file mode 100644 index 000000000..9f443712d --- /dev/null +++ b/src/CommunityToolkit.Mvvm.CodeFixers.Roslyn4110/CommunityToolkit.Mvvm.CodeFixers.Roslyn4110.csproj @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.csproj b/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.csproj deleted file mode 100644 index 01020c625..000000000 --- a/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - netstandard2.0 - false - true - - - - - - - - - - - diff --git a/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.projitems b/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.projitems new file mode 100644 index 000000000..40339da45 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.projitems @@ -0,0 +1,20 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + a2ebda90-b720-430d-83f5-c6bcc355232c + + + CommunityToolkit.Mvvm.CodeFixers + + + + + + + + + + + \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.props b/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.props new file mode 100644 index 000000000..04381ec56 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.props @@ -0,0 +1,37 @@ + + + + netstandard2.0 + false + true + + + + + + + $(MSBuildProjectName.Substring(0, $([MSBuild]::Subtract($(MSBuildProjectName.Length), 11)))) + + + $(MSBuildProjectName.Substring($([MSBuild]::Subtract($(MSBuildProjectName.Length), 10)))) + + + $(MSBuildProjectName.Substring($([MSBuild]::Subtract($(MSBuildProjectName.Length), 4)), 1)) + $(MSBuildProjectName.Substring($([MSBuild]::Subtract($(MSBuildProjectName.Length), 3)), 2)) + $(MSBuildProjectName.Substring($([MSBuild]::Subtract($(MSBuildProjectName.Length), 1)), 1)) + $(MvvmToolkitSourceGeneratorRoslynMajorVersion).$(MvvmToolkitSourceGeneratorRoslynMinorVersion).$(MvvmToolkitSourceGeneratorRoslynPatchVersion) + + + $(DefineConstants);ROSLYN_4_3_1_OR_GREATER + $(DefineConstants);ROSLYN_4_11_0_OR_GREATER + + + + + + + + + + + \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.shproj b/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.shproj new file mode 100644 index 000000000..f36e53832 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.CodeFixers/CommunityToolkit.Mvvm.CodeFixers.shproj @@ -0,0 +1,13 @@ + + + + a2ebda90-b720-430d-83f5-c6bcc355232c + 14.0 + + + + + + + + diff --git a/src/CommunityToolkit.Mvvm.CodeFixers/UsePartialPropertyForObservablePropertyCodeFixer.cs b/src/CommunityToolkit.Mvvm.CodeFixers/UsePartialPropertyForObservablePropertyCodeFixer.cs index f688fa8f8..f6e2684e1 100644 --- a/src/CommunityToolkit.Mvvm.CodeFixers/UsePartialPropertyForObservablePropertyCodeFixer.cs +++ b/src/CommunityToolkit.Mvvm.CodeFixers/UsePartialPropertyForObservablePropertyCodeFixer.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if ROSLYN_4_11_0_OR_GREATER + using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; @@ -229,7 +231,7 @@ private static async Task ConvertToPartialProperty( // Create the following property declaration: // // - // public partial + // // { // // get; @@ -239,7 +241,7 @@ private static async Task ConvertToPartialProperty( // } PropertyDeclarationSyntax propertyDeclaration = PropertyDeclaration(fieldDeclaration.Declaration.Type, Identifier(propertyName)) - .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.PartialKeyword)) + .WithModifiers(GetPropertyModifiers(fieldDeclaration)) .AddAttributeLists(propertyAttributes.ToArray()) .WithAdditionalAnnotations(Formatter.Annotation) .AddAccessorListAccessors( @@ -260,7 +262,7 @@ private static async Task ConvertToPartialProperty( // Create an editor to perform all mutations. This allows to keep track of multiple // replacements for nodes on the same original tree, which otherwise wouldn't work. - SyntaxEditor editor = new(root, document.Project.Solution.Workspace); + SyntaxEditor editor = new(root, document.Project.Solution.Workspace.Services); editor.ReplaceNode(fieldDeclaration, propertyDeclaration); @@ -291,4 +293,25 @@ private static async Task ConvertToPartialProperty( return document.WithSyntaxRoot(editor.GetChangedRoot()); } + + /// + /// Gets all modifiers that need to be added to a generated property. + /// + /// The for the field being updated. + /// The list of necessary modifiers for . + private static SyntaxTokenList GetPropertyModifiers(FieldDeclarationSyntax fieldDeclaration) + { + SyntaxTokenList propertyModifiers = TokenList(Token(SyntaxKind.PublicKeyword)); + + // Add the 'required' modifier if the field also had it + if (fieldDeclaration.Modifiers.Any(SyntaxKind.RequiredKeyword)) + { + propertyModifiers = propertyModifiers.Add(Token(SyntaxKind.RequiredKeyword)); + } + + // Always add 'partial' last + return propertyModifiers.Add(Token(SyntaxKind.PartialKeyword)); + } } + +#endif diff --git a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj index f0246d204..44b02cfa8 100644 --- a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj +++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj @@ -69,7 +69,8 @@ - + + @@ -120,9 +121,9 @@ - - - + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests.csproj index e66b04fda..21b341ca9 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests.csproj +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4001.UnitTests.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests.csproj b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests.csproj index 15077e2aa..83566c52d 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests.csproj +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests.csproj @@ -16,7 +16,7 @@ - + diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs index 6ecdabb23..932c8096f 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4110.UnitTests/Test_UsePartialPropertyForObservablePropertyCodeFixer.cs @@ -650,4 +650,51 @@ partial class C : ObservableObject 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(5,6): info MVVMTK0042: The field C.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(5, 6, 5, 24).WithArguments("C", "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(); + } }